diff options
author | 2022-03-04 03:30:29 -0800 | |
---|---|---|
committer | 2022-03-04 03:30:29 -0800 | |
commit | 29f240ac790edb1f13417f5b6064233b15cc9c8c (patch) | |
tree | 63562e8f59590bb527214c6117d77d76d2918700 | |
parent | c53dc308be9970c6169ff36f5662d1bd5bad262a (diff) | |
download | bun-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.js | 78 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/ZigGlobalObject.cpp | 81 |
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()); |