|
| 1 | +#include "Base64.h" |
| 2 | + |
| 3 | +#include <vector> |
| 4 | + |
| 5 | +#include "Util.h" |
| 6 | + |
| 7 | +using namespace v8; |
| 8 | + |
| 9 | +namespace tns { |
| 10 | + |
| 11 | +namespace { |
| 12 | + |
| 13 | +constexpr char kAlphabet[] = |
| 14 | + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
| 15 | + |
| 16 | +// 6-bit value per ASCII byte; 0xFF marks everything outside the alphabet. |
| 17 | +constexpr uint8_t kInvalid = 0xFF; |
| 18 | + |
| 19 | +uint8_t SixBits(uint8_t c) { |
| 20 | + if (c >= 'A' && c <= 'Z') { |
| 21 | + return static_cast<uint8_t>(c - 'A'); |
| 22 | + } |
| 23 | + if (c >= 'a' && c <= 'z') { |
| 24 | + return static_cast<uint8_t>(c - 'a' + 26); |
| 25 | + } |
| 26 | + if (c >= '0' && c <= '9') { |
| 27 | + return static_cast<uint8_t>(c - '0' + 52); |
| 28 | + } |
| 29 | + if (c == '+') { |
| 30 | + return 62; |
| 31 | + } |
| 32 | + if (c == '/') { |
| 33 | + return 63; |
| 34 | + } |
| 35 | + return kInvalid; |
| 36 | +} |
| 37 | + |
| 38 | +bool IsAsciiWhitespace(uint8_t c) { |
| 39 | + return c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == ' '; |
| 40 | +} |
| 41 | + |
| 42 | +// The string's code units as bytes. Fails when any unit is above U+00FF, |
| 43 | +// which neither op can represent. |
| 44 | +bool GetLatin1Bytes(Isolate* isolate, Local<Value> value, |
| 45 | + std::vector<uint8_t>* out) { |
| 46 | + if (!value->IsString()) { |
| 47 | + return false; |
| 48 | + } |
| 49 | + Local<v8::String> str = value.As<v8::String>(); |
| 50 | + if (!str->ContainsOnlyOneByte()) { |
| 51 | + return false; |
| 52 | + } |
| 53 | + const int length = str->Length(); |
| 54 | + out->resize(static_cast<size_t>(length)); |
| 55 | + if (length > 0) { |
| 56 | + str->WriteOneByteV2(isolate, 0, static_cast<uint32_t>(length), out->data()); |
| 57 | + } |
| 58 | + return true; |
| 59 | +} |
| 60 | + |
| 61 | +// btoa: base64-encode the input's code units. |
| 62 | +void BtoaCallback(const FunctionCallbackInfo<Value>& info) { |
| 63 | + Isolate* isolate = info.GetIsolate(); |
| 64 | + std::vector<uint8_t> input; |
| 65 | + if (!GetLatin1Bytes(isolate, info[0], &input)) { |
| 66 | + info.GetReturnValue().SetNull(); |
| 67 | + return; |
| 68 | + } |
| 69 | + |
| 70 | + std::vector<uint8_t> out; |
| 71 | + out.reserve((input.size() + 2) / 3 * 4); |
| 72 | + size_t i = 0; |
| 73 | + for (; i + 3 <= input.size(); i += 3) { |
| 74 | + const uint32_t group = (static_cast<uint32_t>(input[i]) << 16) | |
| 75 | + (static_cast<uint32_t>(input[i + 1]) << 8) | |
| 76 | + input[i + 2]; |
| 77 | + out.push_back(kAlphabet[(group >> 18) & 0x3F]); |
| 78 | + out.push_back(kAlphabet[(group >> 12) & 0x3F]); |
| 79 | + out.push_back(kAlphabet[(group >> 6) & 0x3F]); |
| 80 | + out.push_back(kAlphabet[group & 0x3F]); |
| 81 | + } |
| 82 | + const size_t remaining = input.size() - i; |
| 83 | + if (remaining == 1) { |
| 84 | + const uint32_t group = static_cast<uint32_t>(input[i]) << 16; |
| 85 | + out.push_back(kAlphabet[(group >> 18) & 0x3F]); |
| 86 | + out.push_back(kAlphabet[(group >> 12) & 0x3F]); |
| 87 | + out.push_back('='); |
| 88 | + out.push_back('='); |
| 89 | + } else if (remaining == 2) { |
| 90 | + const uint32_t group = (static_cast<uint32_t>(input[i]) << 16) | |
| 91 | + (static_cast<uint32_t>(input[i + 1]) << 8); |
| 92 | + out.push_back(kAlphabet[(group >> 18) & 0x3F]); |
| 93 | + out.push_back(kAlphabet[(group >> 12) & 0x3F]); |
| 94 | + out.push_back(kAlphabet[(group >> 6) & 0x3F]); |
| 95 | + out.push_back('='); |
| 96 | + } |
| 97 | + |
| 98 | + if (out.empty()) { |
| 99 | + info.GetReturnValue().Set(v8::String::Empty(isolate)); |
| 100 | + return; |
| 101 | + } |
| 102 | + Local<v8::String> result; |
| 103 | + if (v8::String::NewFromOneByte(isolate, out.data(), NewStringType::kNormal, |
| 104 | + static_cast<int>(out.size())) |
| 105 | + .ToLocal(&result)) { |
| 106 | + info.GetReturnValue().Set(result); |
| 107 | + } |
| 108 | +} |
| 109 | + |
| 110 | +// atob: forgiving-base64 decode |
| 111 | +// (https://infra.spec.whatwg.org/#forgiving-base64-decode). |
| 112 | +void AtobCallback(const FunctionCallbackInfo<Value>& info) { |
| 113 | + Isolate* isolate = info.GetIsolate(); |
| 114 | + std::vector<uint8_t> raw; |
| 115 | + if (!GetLatin1Bytes(isolate, info[0], &raw)) { |
| 116 | + info.GetReturnValue().SetNull(); |
| 117 | + return; |
| 118 | + } |
| 119 | + |
| 120 | + std::vector<uint8_t> data; |
| 121 | + data.reserve(raw.size()); |
| 122 | + for (uint8_t c : raw) { |
| 123 | + if (!IsAsciiWhitespace(c)) { |
| 124 | + data.push_back(c); |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + if (data.size() % 4 == 0) { |
| 129 | + size_t strip = 0; |
| 130 | + while (strip < 2 && !data.empty() && data.back() == '=') { |
| 131 | + data.pop_back(); |
| 132 | + strip++; |
| 133 | + } |
| 134 | + } |
| 135 | + if (data.size() % 4 == 1) { |
| 136 | + info.GetReturnValue().SetNull(); |
| 137 | + return; |
| 138 | + } |
| 139 | + |
| 140 | + std::vector<uint8_t> out; |
| 141 | + out.reserve(data.size() / 4 * 3 + 2); |
| 142 | + uint32_t accumulator = 0; |
| 143 | + uint32_t bits = 0; |
| 144 | + for (uint8_t c : data) { |
| 145 | + const uint8_t value = SixBits(c); |
| 146 | + if (value == kInvalid) { |
| 147 | + info.GetReturnValue().SetNull(); |
| 148 | + return; |
| 149 | + } |
| 150 | + accumulator = (accumulator << 6) | value; |
| 151 | + bits += 6; |
| 152 | + if (bits >= 8) { |
| 153 | + bits -= 8; |
| 154 | + out.push_back(static_cast<uint8_t>((accumulator >> bits) & 0xFF)); |
| 155 | + } |
| 156 | + } |
| 157 | + |
| 158 | + if (out.empty()) { |
| 159 | + info.GetReturnValue().Set(v8::String::Empty(isolate)); |
| 160 | + return; |
| 161 | + } |
| 162 | + Local<v8::String> result; |
| 163 | + if (v8::String::NewFromOneByte(isolate, out.data(), NewStringType::kNormal, |
| 164 | + static_cast<int>(out.size())) |
| 165 | + .ToLocal(&result)) { |
| 166 | + info.GetReturnValue().Set(result); |
| 167 | + } |
| 168 | +} |
| 169 | + |
| 170 | +} // namespace |
| 171 | + |
| 172 | +Local<Object> Base64::CreateBinding(Local<Context> context) { |
| 173 | + Isolate* isolate = v8::Isolate::GetCurrent(); |
| 174 | + Local<Object> binding = Object::New(isolate); |
| 175 | + tns::SetMethodNoSideEffect(context, binding, "btoa", BtoaCallback); |
| 176 | + tns::SetMethodNoSideEffect(context, binding, "atob", AtobCallback); |
| 177 | + return binding; |
| 178 | +} |
| 179 | + |
| 180 | +} // namespace tns |
0 commit comments