aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-03-04 03:30:29 -0800
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-03-04 03:30:29 -0800
commit29f240ac790edb1f13417f5b6064233b15cc9c8c (patch)
tree63562e8f59590bb527214c6117d77d76d2918700
parentc53dc308be9970c6169ff36f5662d1bd5bad262a (diff)
downloadbun-29f240ac790edb1f13417f5b6064233b15cc9c8c.tar.gz
bun-29f240ac790edb1f13417f5b6064233b15cc9c8c.tar.zst
bun-29f240ac790edb1f13417f5b6064233b15cc9c8c.zip
[bun.js] Add `atob` and `btoa`
-rw-r--r--integration/bunjs-only-snippets/atob.test.js78
-rw-r--r--src/javascript/jsc/bindings/ZigGlobalObject.cpp81
2 files changed, 157 insertions, 2 deletions
diff --git a/integration/bunjs-only-snippets/atob.test.js b/integration/bunjs-only-snippets/atob.test.js
new file mode 100644
index 000000000..c146aa855
--- /dev/null
+++ b/integration/bunjs-only-snippets/atob.test.js
@@ -0,0 +1,78 @@
+import { expect, describe, it } from "bun:test";
+
+function expectInvalidCharacters(val) {
+ try {
+ atob(val);
+ throw new Error("Expected error");
+ } catch (error) {
+ expect(error.message).toBe("The string contains invalid characters.");
+ }
+}
+
+it("atob", () => {
+ expect(atob("YQ==")).toBe("a");
+ expect(atob("YWI=")).toBe("ab");
+ expect(atob("YWJj")).toBe("abc");
+ expect(atob("YWJjZA==")).toBe("abcd");
+ expect(atob("YWJjZGU=")).toBe("abcde");
+ expect(atob("YWJjZGVm")).toBe("abcdef");
+ expect(atob("zzzz")).toBe("Ï<ó");
+ expect(atob("")).toBe("");
+ expect(atob(null)).toBe("žée");
+ expect(atob("6ek=")).toBe("éé");
+ expect(atob("6ek")).toBe("éé");
+ expect(atob("gIE=")).toBe("€");
+ expect(atob("zz")).toBe("Ï");
+ expect(atob("zzz")).toBe("Ï<");
+ expect(atob("zzz=")).toBe("Ï<");
+ expect(atob(" YQ==")).toBe("a");
+ expect(atob("YQ==\u000a")).toBe("a");
+
+ try {
+ atob();
+ } catch (error) {
+ expect(error.name).toBe("TypeError");
+ }
+ expectInvalidCharacters(undefined);
+ expectInvalidCharacters(" abcd===");
+ expectInvalidCharacters("abcd=== ");
+ expectInvalidCharacters("abcd ===");
+ expectInvalidCharacters("тест");
+ expectInvalidCharacters("z");
+ expectInvalidCharacters("zzz==");
+ expectInvalidCharacters("zzz===");
+ expectInvalidCharacters("zzz====");
+ expectInvalidCharacters("zzz=====");
+ expectInvalidCharacters("zzzzz");
+ expectInvalidCharacters("z=zz");
+ expectInvalidCharacters("=");
+ expectInvalidCharacters("==");
+ expectInvalidCharacters("===");
+ expectInvalidCharacters("====");
+
+ expectInvalidCharacters("=====");
+});
+
+it("btoa", () => {
+ expect(btoa("a")).toBe("YQ==");
+ expect(btoa("ab")).toBe("YWI=");
+ expect(btoa("abc")).toBe("YWJj");
+ expect(btoa("abcd")).toBe("YWJjZA==");
+ expect(btoa("abcde")).toBe("YWJjZGU=");
+ expect(btoa("abcdef")).toBe("YWJjZGVm");
+ expect(typeof btoa).toBe("function");
+ try {
+ btoa();
+ throw new Error("Expected error");
+ } catch (error) {
+ expect(error.name).toBe("TypeError");
+ }
+ var window = "[object Window]";
+ expect(btoa("")).toBe("");
+ expect(btoa(null)).toBe("bnVsbA==");
+ expect(btoa(undefined)).toBe("dW5kZWZpbmVk");
+ expect(btoa(window)).toBe("W29iamVjdCBXaW5kb3dd");
+ expect(btoa("éé")).toBe("6ek=");
+ expect(btoa("\u0080\u0081")).toBe("gIE=");
+ expect(btoa(Bun)).toBe(btoa("[object Bun]"));
+});
diff --git a/src/javascript/jsc/bindings/ZigGlobalObject.cpp b/src/javascript/jsc/bindings/ZigGlobalObject.cpp
index a3ec63516..6c2847596 100644
--- a/src/javascript/jsc/bindings/ZigGlobalObject.cpp
+++ b/src/javascript/jsc/bindings/ZigGlobalObject.cpp
@@ -65,7 +65,7 @@
#include <cstdlib>
#include <exception>
#include <iostream>
-
+#include <wtf/text/Base64.h>
// #include <JavaScriptCore/CachedType.h>
#include <JavaScriptCore/JSCallbackObject.h>
#include <JavaScriptCore/JSClassRef.h>
@@ -455,13 +455,76 @@ static JSC_DEFINE_HOST_FUNCTION(functionClearTimeout,
return Bun__Timer__clearTimeout(globalObject, JSC::JSValue::encode(num));
}
+static JSC_DECLARE_HOST_FUNCTION(functionBTOA);
+
+static JSC_DEFINE_HOST_FUNCTION(functionBTOA,
+ (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ JSC::VM& vm = globalObject->vm();
+
+ if (callFrame->argumentCount() == 0) {
+ auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
+ JSC::throwTypeError(globalObject, scope, "btoa requires 1 argument (a string)"_s);
+ return JSC::JSValue::encode(JSC::JSValue {});
+ }
+
+ const String& stringToEncode = callFrame->argument(0).toWTFString(globalObject);
+
+ if (!stringToEncode || stringToEncode.isNull()) {
+ return JSC::JSValue::encode(JSC::jsString(vm, WTF::String()));
+ }
+
+ if (!stringToEncode.isAllLatin1()) {
+ auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
+ // TODO: DOMException
+ JSC::throwTypeError(globalObject, scope, "The string contains invalid characters."_s);
+ return JSC::JSValue::encode(JSC::JSValue {});
+ }
+
+ return JSC::JSValue::encode(JSC::jsString(vm, WTF::base64EncodeToString(stringToEncode.latin1())));
+}
+
+static JSC_DECLARE_HOST_FUNCTION(functionATOB);
+
+static JSC_DEFINE_HOST_FUNCTION(functionATOB,
+ (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ JSC::VM& vm = globalObject->vm();
+
+ if (callFrame->argumentCount() == 0) {
+ auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
+ JSC::throwTypeError(globalObject, scope, "atob requires 1 argument (a string)"_s);
+ return JSC::JSValue::encode(JSC::JSValue {});
+ }
+
+ const WTF::String& encodedString = callFrame->argument(0).toWTFString(globalObject);
+
+ if (encodedString.isNull()) {
+ return JSC::JSValue::encode(JSC::jsString(vm, ""));
+ }
+
+ auto decodedData = WTF::base64Decode(encodedString, {
+ WTF::Base64DecodeOptions::ValidatePadding,
+ WTF::Base64DecodeOptions::IgnoreSpacesAndNewLines,
+ WTF::Base64DecodeOptions::DiscardVerticalTab,
+ });
+ if (!decodedData) {
+ auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
+ // TODO: DOMException
+ JSC::throwTypeError(globalObject, scope, "The string contains invalid characters."_s);
+ return JSC::JSValue::encode(JSC::JSValue {});
+ }
+
+ return JSC::JSValue::encode(JSC::jsString(vm, WTF::String(decodedData->data(), decodedData->size())));
+}
+
// This is not a publicly exposed API currently.
// This is used by the bundler to make Response, Request, FetchEvent,
// and any other objects available globally.
void GlobalObject::installAPIGlobals(JSClassRef* globals, int count)
{
WTF::Vector<GlobalPropertyInfo> extraStaticGlobals;
- extraStaticGlobals.reserveCapacity((size_t)count + 3 + 4);
+ extraStaticGlobals.reserveCapacity((size_t)count + 3 + 7);
int i = 0;
for (; i < count - 1; i++) {
@@ -531,6 +594,20 @@ void GlobalObject::installAPIGlobals(JSClassRef* globals, int count)
"clearInterval", functionClearInterval),
JSC::PropertyAttribute::DontDelete | 0 });
+ JSC::Identifier atobIdentifier = JSC::Identifier::fromString(vm(), "atob"_s);
+ extraStaticGlobals.uncheckedAppend(
+ GlobalPropertyInfo { atobIdentifier,
+ JSC::JSFunction::create(vm(), JSC::jsCast<JSC::JSGlobalObject*>(this), 0,
+ "atob", functionATOB),
+ JSC::PropertyAttribute::DontDelete | 0 });
+
+ JSC::Identifier btoaIdentifier = JSC::Identifier::fromString(vm(), "btoa"_s);
+ extraStaticGlobals.uncheckedAppend(
+ GlobalPropertyInfo { btoaIdentifier,
+ JSC::JSFunction::create(vm(), JSC::jsCast<JSC::JSGlobalObject*>(this), 0,
+ "btoa", functionBTOA),
+ JSC::PropertyAttribute::DontDelete | 0 });
+
auto clientData = Bun::clientData(vm());
this->addStaticGlobals(extraStaticGlobals.data(), extraStaticGlobals.size());