-
Notifications
You must be signed in to change notification settings - Fork 156
Issue #16 references not having atob and btoa (enc/dec base64) #1032
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -2321,6 +2321,7 @@ JSContext *JS_NewContext(JSRuntime *rt) | |||||||||
JS_AddIntrinsicPromise(ctx); | ||||||||||
JS_AddIntrinsicBigInt(ctx); | ||||||||||
JS_AddIntrinsicWeakRef(ctx); | ||||||||||
JS_AddIntrinsicBase64(ctx); | ||||||||||
|
||||||||||
JS_AddPerformance(ctx); | ||||||||||
|
||||||||||
|
@@ -57422,6 +57423,144 @@ static void insert_weakref_record(JSValueConst target, | |||||||||
*pwr = wr; | ||||||||||
} | ||||||||||
|
||||||||||
/* urlsafe_base64 atob/btoa */ | ||||||||||
|
||||||||||
// Base64 encoding table | ||||||||||
static const char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; | ||||||||||
|
||||||||||
// btoa: binary to ASCII (base64 encode) | ||||||||||
char* btoa(JSContext *ctx, const char* bin, size_t len) { | ||||||||||
if (bin == NULL || len == 0) return NULL; | ||||||||||
// Calculate output length (including padding) | ||||||||||
size_t out_len = 4 * ((len + 2) / 3) + 1; | ||||||||||
// Allocate memory for output string (plus null terminator) | ||||||||||
char* out = (char*)js_mallocz(ctx, out_len); | ||||||||||
|
||||||||||
if (out == NULL) return NULL; | ||||||||||
|
||||||||||
// Base64 encoding process | ||||||||||
size_t i, j; | ||||||||||
|
||||||||||
for (i = 0, j = 0; i < len; i += 3, j += 4) { | ||||||||||
uint32_t triple = (bin[i] << 16); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Otherwise if |
||||||||||
if (i + 1 < len) triple |= (bin[i + 1] << 8); | ||||||||||
if (i + 2 < len) triple |= bin[i + 2]; | ||||||||||
|
||||||||||
out[j] = base64_table[(triple >> 18) & 0x3F]; | ||||||||||
out[j + 1] = base64_table[(triple >> 12) & 0x3F]; | ||||||||||
out[j + 2] = (i + 1 < len) ? base64_table[(triple >> 6) & 0x3F] : '='; | ||||||||||
out[j + 3] = (i + 2 < len) ? base64_table[triple & 0x3F] : '='; | ||||||||||
} | ||||||||||
|
||||||||||
out[out_len] = '\0'; | ||||||||||
return out; | ||||||||||
} | ||||||||||
|
||||||||||
static JSValue js_base64_btoa(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { | ||||||||||
const char *fmt_str = NULL; | ||||||||||
const char *result_str = NULL; | ||||||||||
JSValue v; | ||||||||||
|
||||||||||
if (argc > 0) { | ||||||||||
fmt_str = JS_ToCString(ctx, argv[0]); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
result_str = btoa(ctx, fmt_str, strlen(fmt_str)); | ||||||||||
v = JS_NewString(ctx, result_str); | ||||||||||
JS_FreeCString(ctx, fmt_str); | ||||||||||
return v; | ||||||||||
} else { | ||||||||||
return JS_ThrowTypeError(ctx, "ERR_MISSING_ARGS"); | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
static int base64_index(char c) { | ||||||||||
if (c >= 'A' && c <= 'Z') | ||||||||||
return c - 'A'; | ||||||||||
if (c >= 'a' && c <= 'z') | ||||||||||
return c - 'a' + 26; | ||||||||||
if (c >= '0' && c <= '9') | ||||||||||
return c - '0' + 52; | ||||||||||
if (c == '-') | ||||||||||
return 62; | ||||||||||
if (c == '_') | ||||||||||
return 63; | ||||||||||
|
||||||||||
return -1; // Invalid character | ||||||||||
} | ||||||||||
|
||||||||||
// atob: ASCII to binary (base64 decode) | ||||||||||
char* atob(JSContext *ctx, const char* str) { | ||||||||||
if (str == NULL) return NULL; | ||||||||||
|
||||||||||
size_t str_len = strlen(str); | ||||||||||
|
||||||||||
if (str_len % 4 != 0) return NULL; // Invalid base64 string | ||||||||||
|
||||||||||
// Calculate output length | ||||||||||
size_t len = str_len / 4 * 3; | ||||||||||
|
||||||||||
if (str[str_len - 1] == '=') len--; | ||||||||||
|
||||||||||
if (str[str_len - 2] == '=') len--; | ||||||||||
|
||||||||||
// Allocate memory for output data | ||||||||||
size_t out_len = len+1; | ||||||||||
char* out = (char*)js_mallocz(ctx, out_len); | ||||||||||
|
||||||||||
if (out == NULL) return NULL; | ||||||||||
|
||||||||||
// Base64 decoding process | ||||||||||
size_t i, j; | ||||||||||
|
||||||||||
for (i = 0, j = 0; i < str_len; i += 4, j += 3) { | ||||||||||
int a = base64_index(str[i]); | ||||||||||
int b = base64_index(str[i + 1]); | ||||||||||
int c = str[i + 2] == '=' ? 0 : base64_index(str[i + 2]); | ||||||||||
int d = str[i + 3] == '=' ? 0 : base64_index(str[i + 3]); | ||||||||||
|
||||||||||
if (a == -1 || b == -1 || c == -1 || d == -1) { | ||||||||||
js_free(ctx, out); | ||||||||||
return NULL; // Invalid character | ||||||||||
} | ||||||||||
|
||||||||||
uint32_t triple = (a << 18) | (b << 12) | (c << 6) | d; | ||||||||||
out[j] = (triple >> 16) & 0xFF; | ||||||||||
|
||||||||||
if (j + 1 < len) out[j + 1] = (triple >> 8) & 0xFF; | ||||||||||
if (j + 2 < len) out[j + 2] = triple & 0xFF; | ||||||||||
} | ||||||||||
|
||||||||||
out[out_len] = '\0'; | ||||||||||
return out; | ||||||||||
} | ||||||||||
|
||||||||||
|
||||||||||
static JSValue js_base64_atob(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { | ||||||||||
const char *fmt_str = NULL; | ||||||||||
const char *result_str = NULL; | ||||||||||
JSValue v; | ||||||||||
|
||||||||||
if (argc > 0) { | ||||||||||
fmt_str = JS_ToCString(ctx, argv[0]); | ||||||||||
result_str = atob(ctx, fmt_str); | ||||||||||
v = JS_NewString(ctx, result_str); | ||||||||||
JS_FreeCString(ctx, fmt_str); | ||||||||||
return v; | ||||||||||
} else { | ||||||||||
return JS_ThrowTypeError(ctx, "ERR_MISSING_ARGS"); | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
static const JSCFunctionListEntry js_base64_funcs[] = { | ||||||||||
JS_CFUNC_DEF("btoa", 1, js_base64_btoa ), | ||||||||||
JS_CFUNC_DEF("atob", 1, js_base64_atob ), | ||||||||||
}; | ||||||||||
|
||||||||||
void JS_AddIntrinsicBase64(JSContext *ctx) | ||||||||||
{ | ||||||||||
JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_base64_funcs, countof(js_base64_funcs)); | ||||||||||
} | ||||||||||
|
||||||||||
|
||||||||||
/* CallSite */ | ||||||||||
|
||||||||||
static void js_callsite_finalizer(JSRuntime *rt, JSValueConst val) | ||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { assert, assertArrayEquals, assertThrows } from "./assert.js"; | ||
|
||
let source_string = "QuickJS! //+!@#$%^&*()"; | ||
|
||
// created with node. | ||
let target_string = "UXVpY2tKUyEgLy8rIUAjJCVeJiooKQ=="; | ||
|
||
function test_atob() { | ||
let encoded_string = btoa(source_string); | ||
assert(encoded_string == target_string); | ||
} | ||
|
||
function test_btoa() { | ||
let decoded_string = atob(target_string); | ||
assert(decoded_string == source_string); | ||
} | ||
|
||
function test_btoatob() { | ||
assert(source_string == atob(btoa(source_string))); | ||
} | ||
|
||
function test_atobtoa() { | ||
assert(target_string == btoa(atob(target_string))); | ||
} | ||
|
||
function test_fromarray() { | ||
const binaryData = new Uint8Array([72, 101, 108, 108, 111]); // 'Hello' | ||
const text = String.fromCharCode.apply(null, binaryData); | ||
const targetText = "SGVsbG8="; | ||
assert(btoa(text) == targetText); | ||
} | ||
|
||
function test_nonbase64_characters() { | ||
const f = () => { | ||
atob("!@#$%^"); | ||
}; | ||
|
||
assert_throws(TypeError, f); | ||
} | ||
|
||
test_atob(); | ||
test_btoa(); | ||
test_btoatob(); | ||
test_atobtoa(); | ||
test_fromarray(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary cast but more substantially, allocating should preferably be done by the caller, because then the caller can use e.g. a stack-allocated buffer for small inputs.