add base64 and base64url encoder/decoders

This commit is contained in:
2025-07-12 10:31:54 -05:00
parent ac9f40fd26
commit f0afdfc7d9
2 changed files with 159 additions and 0 deletions

View File

@@ -411,5 +411,9 @@ function format_number(num, format) {
}
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;

View File

@@ -139,10 +139,165 @@ JSC_CCALL(text_base32_to_blob,
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;
// well 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 URLsafe, 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[] = {
MIST_FUNC_DEF(text, blob_to_hex, 1),
MIST_FUNC_DEF(text, blob_to_base32, 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)