add base64 and base64url encoder/decoders
This commit is contained in:
@@ -411,5 +411,9 @@ function format_number(num, format) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
text.base32_to_blob = that.base32_to_blob
|
text.base32_to_blob = that.base32_to_blob
|
||||||
|
text.base64_to_blob = that.base64_to_blob
|
||||||
|
text.base64url_to_blob = that.base64url_to_blob
|
||||||
|
text.blob_to_base64 = that.blob_to_base64
|
||||||
|
text.blob_to_base64url = that.blob_to_base64url
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
@@ -139,10 +139,165 @@ JSC_CCALL(text_base32_to_blob,
|
|||||||
return val;
|
return val;
|
||||||
)
|
)
|
||||||
|
|
||||||
|
static int base64_char_to_val_standard(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;
|
||||||
|
}
|
||||||
|
static int base64_char_to_val_url(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*─── blob → Base64 (standard, with ‘+’ and ‘/’, padded) ───────────────────*/
|
||||||
|
JSC_CCALL(text_blob_to_base64,
|
||||||
|
size_t blob_len;
|
||||||
|
void *blob_data = js_get_blob_data(js, &blob_len, argv[0]);
|
||||||
|
if (!blob_data) return JS_ThrowTypeError(js, "Expected stone blob");
|
||||||
|
const uint8_t *bytes = blob_data;
|
||||||
|
static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
"abcdefghijklmnopqrstuvwxyz"
|
||||||
|
"0123456789+/";
|
||||||
|
size_t out_len = ((blob_len + 2) / 3) * 4;
|
||||||
|
char *out = malloc(out_len + 1);
|
||||||
|
if (!out) return JS_ThrowOutOfMemory(js);
|
||||||
|
|
||||||
|
size_t in_i = 0, out_i = 0;
|
||||||
|
while (in_i < blob_len) {
|
||||||
|
uint32_t buf = 0;
|
||||||
|
int to_read = (blob_len - in_i < 3 ? blob_len - in_i : 3);
|
||||||
|
for (int j = 0; j < to_read; ++j) {
|
||||||
|
buf = (buf << 8) | bytes[in_i++];
|
||||||
|
}
|
||||||
|
buf <<= 8 * (3 - to_read);
|
||||||
|
out[out_i++] = b64[(buf >> 18) & 0x3F];
|
||||||
|
out[out_i++] = b64[(buf >> 12) & 0x3F];
|
||||||
|
out[out_i++] = (to_read > 1 ? b64[(buf >> 6) & 0x3F] : '=');
|
||||||
|
out[out_i++] = (to_read > 2 ? b64[ buf & 0x3F] : '=');
|
||||||
|
}
|
||||||
|
out[out_len] = '\0';
|
||||||
|
JSValue v = JS_NewString(js, out);
|
||||||
|
free(out);
|
||||||
|
return v;
|
||||||
|
)
|
||||||
|
|
||||||
|
/*─── Base64 → blob (standard, expects ‘+’ and ‘/’, pads allowed) ────────────*/
|
||||||
|
JSC_CCALL(text_base64_to_blob,
|
||||||
|
const char *str = JS_ToCString(js, argv[0]);
|
||||||
|
if (!str) return JS_ThrowTypeError(js, "Expected string");
|
||||||
|
size_t len = strlen(str);
|
||||||
|
// strip padding for length calculation
|
||||||
|
size_t eff = len;
|
||||||
|
while (eff > 0 && str[eff-1] == '=') eff--;
|
||||||
|
size_t out_len = (eff * 6) / 8;
|
||||||
|
uint8_t *out = malloc(out_len);
|
||||||
|
if (!out) { JS_FreeCString(js, str); return JS_ThrowOutOfMemory(js); }
|
||||||
|
|
||||||
|
size_t in_i = 0, out_i = 0;
|
||||||
|
while (in_i < eff) {
|
||||||
|
uint32_t buf = 0;
|
||||||
|
int to_read = (eff - in_i < 4 ? eff - in_i : 4);
|
||||||
|
for (int j = 0; j < to_read; ++j) {
|
||||||
|
int v = base64_char_to_val_standard(str[in_i++]);
|
||||||
|
if (v < 0) { free(out); JS_FreeCString(js, str);
|
||||||
|
return JS_ThrowTypeError(js, "Invalid base64 character"); }
|
||||||
|
buf = (buf << 6) | v;
|
||||||
|
}
|
||||||
|
buf <<= 6 * (4 - to_read);
|
||||||
|
int bytes_out = (to_read * 6) / 8;
|
||||||
|
for (int j = 0; j < bytes_out && out_i < out_len; ++j) {
|
||||||
|
out[out_i++] = (buf >> (16 - 8*j)) & 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue v = js_new_blob_stoned_copy(js, out, out_len);
|
||||||
|
free(out);
|
||||||
|
JS_FreeCString(js, str);
|
||||||
|
return v;
|
||||||
|
)
|
||||||
|
|
||||||
|
/*─── blob → Base64URL (no padding, ‘-’ and ‘_’) ─────────────────────────────*/
|
||||||
|
JSC_CCALL(text_blob_to_base64url,
|
||||||
|
size_t blob_len;
|
||||||
|
void *blob_data = js_get_blob_data(js, &blob_len, argv[0]);
|
||||||
|
if (!blob_data) return JS_ThrowTypeError(js, "Expected stone blob");
|
||||||
|
const uint8_t *bytes = blob_data;
|
||||||
|
static const char b64url[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
"abcdefghijklmnopqrstuvwxyz"
|
||||||
|
"0123456789-_";
|
||||||
|
size_t raw_len = ((blob_len + 2) / 3) * 4;
|
||||||
|
// we’ll drop any trailing '='
|
||||||
|
char *out = malloc(raw_len + 1);
|
||||||
|
if (!out) return JS_ThrowOutOfMemory(js);
|
||||||
|
|
||||||
|
size_t in_i = 0, out_i = 0;
|
||||||
|
while (in_i < blob_len) {
|
||||||
|
uint32_t buf = 0;
|
||||||
|
int to_read = (blob_len - in_i < 3 ? blob_len - in_i : 3);
|
||||||
|
for (int j = 0; j < to_read; ++j) {
|
||||||
|
buf = (buf << 8) | bytes[in_i++];
|
||||||
|
}
|
||||||
|
buf <<= 8 * (3 - to_read);
|
||||||
|
out[out_i++] = b64url[(buf >> 18) & 0x3F];
|
||||||
|
out[out_i++] = b64url[(buf >> 12) & 0x3F];
|
||||||
|
if (to_read > 1) out[out_i++] = b64url[(buf >> 6) & 0x3F];
|
||||||
|
if (to_read > 2) out[out_i++] = b64url[ buf & 0x3F];
|
||||||
|
}
|
||||||
|
out[out_i] = '\0';
|
||||||
|
JSValue v = JS_NewString(js, out);
|
||||||
|
free(out);
|
||||||
|
return v;
|
||||||
|
)
|
||||||
|
|
||||||
|
/*─── Base64URL → blob (accepts ‘-’ / ‘_’, no padding needed) ─────────────────*/
|
||||||
|
JSC_CCALL(text_base64url_to_blob,
|
||||||
|
const char *str = JS_ToCString(js, argv[0]);
|
||||||
|
if (!str) return JS_ThrowTypeError(js, "Expected string");
|
||||||
|
size_t len = strlen(str);
|
||||||
|
size_t eff = len; // no '=' in URL‐safe, but strip if present
|
||||||
|
while (eff > 0 && str[eff-1] == '=') eff--;
|
||||||
|
size_t out_len = (eff * 6) / 8;
|
||||||
|
uint8_t *out = malloc(out_len);
|
||||||
|
if (!out) { JS_FreeCString(js, str); return JS_ThrowOutOfMemory(js); }
|
||||||
|
|
||||||
|
size_t in_i = 0, out_i = 0;
|
||||||
|
while (in_i < eff) {
|
||||||
|
uint32_t buf = 0;
|
||||||
|
int to_read = (eff - in_i < 4 ? eff - in_i : 4);
|
||||||
|
for (int j = 0; j < to_read; ++j) {
|
||||||
|
int v = base64_char_to_val_url(str[in_i++]);
|
||||||
|
if (v < 0) { free(out); JS_FreeCString(js, str);
|
||||||
|
return JS_ThrowTypeError(js, "Invalid base64url character"); }
|
||||||
|
buf = (buf << 6) | v;
|
||||||
|
}
|
||||||
|
buf <<= 6 * (4 - to_read);
|
||||||
|
int bytes_out = (to_read * 6) / 8;
|
||||||
|
for (int j = 0; j < bytes_out && out_i < out_len; ++j) {
|
||||||
|
out[out_i++] = (buf >> (16 - 8*j)) & 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue v = js_new_blob_stoned_copy(js, out, out_len);
|
||||||
|
free(out);
|
||||||
|
JS_FreeCString(js, str);
|
||||||
|
return v;
|
||||||
|
)
|
||||||
|
|
||||||
static const JSCFunctionListEntry js_text_funcs[] = {
|
static const JSCFunctionListEntry js_text_funcs[] = {
|
||||||
MIST_FUNC_DEF(text, blob_to_hex, 1),
|
MIST_FUNC_DEF(text, blob_to_hex, 1),
|
||||||
MIST_FUNC_DEF(text, blob_to_base32, 1),
|
MIST_FUNC_DEF(text, blob_to_base32, 1),
|
||||||
MIST_FUNC_DEF(text, base32_to_blob, 1),
|
MIST_FUNC_DEF(text, base32_to_blob, 1),
|
||||||
|
MIST_FUNC_DEF(text, blob_to_base64, 1),
|
||||||
|
MIST_FUNC_DEF(text, base64_to_blob, 1),
|
||||||
|
MIST_FUNC_DEF(text, blob_to_base64url, 1),
|
||||||
|
MIST_FUNC_DEF(text, base64url_to_blob, 1),
|
||||||
};
|
};
|
||||||
|
|
||||||
JSValue js_text_use(JSContext *js)
|
JSValue js_text_use(JSContext *js)
|
||||||
|
|||||||
Reference in New Issue
Block a user