Skip to content

Instantly share code, notes, and snippets.

@graham
Created April 20, 2025 03:42
Show Gist options
  • Save graham/0f6f0f7de6c2839155c7f36ee7625588 to your computer and use it in GitHub Desktop.
Save graham/0f6f0f7de6c2839155c7f36ee7625588 to your computer and use it in GitHub Desktop.
Base64 atob/bota for quickjs
#include <iostream>
#include <thread>
#include <string>
#include <queue>
#include <condition_variable>
#include <memory>
#include <thread>
#include <mutex>
#include <atomic>
#include <functional>
#include <map>
#include <fmt/core.h>
#include "quickjs.h"
// 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);
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]);
result_str = btoa(ctx, fmt_str, strlen(fmt_str));
v = JS_NewString(ctx, result_str);
//JS_FreeCString(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, 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)
{
JSValue global_obj = JS_GetGlobalObject(ctx);
JS_SetPropertyFunctionList(ctx, global_obj, js_base64_funcs, 2);
}
typedef struct TestCase
{
const char *source;
const char *target;
} TestCase;
int main() {
fmt::println("Hello World...");
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
JS_AddIntrinsicBase64(ctx);
TestCase cases[] = {
{
"jK2f8dL0pQ\0", "aksyZjhkTDBwUQ==\0"
},
{
"2UGaXr5PVDQLwC0oTm1JZpRy8jKvN4eHiOu6tYb9fS7A3MxdWskzlFcgqEBhnI6jBl7EdcSxNVpzYWrkwHK0m1Q5tX8bvUIfsFCPuGyZ94LToeDJ3OaA2q0hMniR75jv\0",
"MlVHYVhyNVBWRFFMd0Mwb1RtMUpacFJ5OGpLdk40ZUhpT3U2dFliOWZTN0EzTXhkV3NremxGY2dxRUJobkk2akJsN0VkY1N4TlZwellXcmt3SEswbTFRNXRYOGJ2VUlmc0ZDUHVHeVo5NExUb2VESjNPYUEycTBoTW5pUjc1anY=\0"
},
{
"v2jPZqcuWrFY6R3DBgA8T5mXHN4KE7ksSbL9CxQtiJGnwypV1fdoOaIlM0ehU4zS7Gr2bX5E9TsWpfLcYo83VKAHJaRDQ6vukn0wixmMdPNt1BFjZ7ICgylOeSbh5XV4\0",
"djJqUFpxY3VXckZZNlIzREJnQThUNW1YSE40S0U3a3NTYkw5Q3hRdGlKR253eXBWMWZkb09hSWxNMGVoVTR6UzdHcjJiWDVFOVRzV3BmTGNZbzgzVktBSEphUkRRNnZ1a24wd2l4bU1kUE50MUJGalo3SUNneWxPZVNiaDVYVjQ=\0"
},
{ "jK2f8dL0pQ\0", "aksyZjhkTDBwUQ==\0" },
{ "sA5pO8iU4y\0", "c0E1cE84aVU0eQ==\0" },
{ "dF6gH3jK2l\0", "ZEY2Z0gzaksybA==\0" },
{ "zX4cV5bN6m\0", "elg0Y1Y1Yk42bQ==\0" },
{ "wQ2eR5tY8u\0", "d1EyZVI1dFk4dQ==\0" },
{ "vB9nM6cX3z\0", "dkI5bk02Y1gzeg==\0" },
{ "yT7rE4wQ1e\0", "eVQ3ckU0d1ExZQ==\0" },
{ "hG3fD2sA9p\0", "aEczZkQyc0E5cA==\0" },
{ "kL8jH5gF2d\0", "a0w4akg1Z0YyZA==\0" },
{ "bN4vC7xZ6l\0", "Yk40dkM3eFo2bA==\0" },
{ "oI5uY2tR9e\0", "b0k1dVkydFI5ZQ==\0" },
{ "gF1dS8aP4o\0", "Z0YxZFM4YVA0bw==\0" },
{ "tY6rE3wQ8i\0", "dFk2ckUzd1E4aQ==\0" },
{ "uI9oP2lK5j\0", "dUk5b1AybEs1ag==\0" }
};
for (int i = 0; i < 16; i++)
{
const char* source = cases[i].source;
const char* target = cases[i].target;
{ // basic btoa test
std::string code_enc = std::string(fmt::format("btoa('{}')", source));
JSValue result_enc = JS_Eval(ctx, code_enc.c_str(), code_enc.length(), "<test>", 0);
const char *str_enc = JS_ToCString(ctx, result_enc);
fmt::println("{} 1 Equal: {} ", i, strcmp(target, str_enc) == 0);
JS_FreeCString(ctx, str_enc);
}
{ // basic atob test
std::string code_enc = std::string(fmt::format("atob('{}')", target));
JSValue result_enc = JS_Eval(ctx, code_enc.c_str(), code_enc.length(), "<test>", 0);
const char *str_enc = JS_ToCString(ctx, result_enc);
fmt::println("{} 2 Equal: {}", i, strcmp(source, str_enc) == 0);
JS_FreeCString(ctx, str_enc);
}
{ // b to a to b
std::string code_enc = std::string(fmt::format("btoa(atob('{}'))", target));
JSValue result_enc = JS_Eval(ctx, code_enc.c_str(), code_enc.length(), "<test>", 0);
const char *str_enc = JS_ToCString(ctx, result_enc);
fmt::println("{} 3 Equal: {}", i, strcmp(target, str_enc) == 0);
JS_FreeCString(ctx, str_enc);
}
{ // b to a to b
std::string code_enc = std::string(fmt::format("atob(btoa('{}'))", source));
JSValue result_enc = JS_Eval(ctx, code_enc.c_str(), code_enc.length(), "<test>", 0);
const char *str_enc = JS_ToCString(ctx, result_enc);
fmt::println("{} 4 Equal: {} ", i, strcmp(source, str_enc) == 0);
JS_FreeCString(ctx, str_enc);
}
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment