Uint8Array.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842
  1. /*
  2. * Copyright (c) 2024, Tim Flynn <trflynn89@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/Base64.h>
  7. #include <AK/StringBuilder.h>
  8. #include <AK/StringUtils.h>
  9. #include <LibJS/Runtime/Temporal/AbstractOperations.h>
  10. #include <LibJS/Runtime/TypedArray.h>
  11. #include <LibJS/Runtime/Uint8Array.h>
  12. #include <LibJS/Runtime/VM.h>
  13. #include <LibJS/Runtime/ValueInlines.h>
  14. namespace JS {
  15. void Uint8ArrayConstructorHelpers::initialize(Realm& realm, Object& constructor)
  16. {
  17. auto& vm = constructor.vm();
  18. static constexpr u8 attr = Attribute::Writable | Attribute::Configurable;
  19. constructor.define_native_function(realm, vm.names.fromBase64, from_base64, 1, attr);
  20. constructor.define_native_function(realm, vm.names.fromHex, from_hex, 1, attr);
  21. }
  22. void Uint8ArrayPrototypeHelpers::initialize(Realm& realm, Object& prototype)
  23. {
  24. auto& vm = prototype.vm();
  25. static constexpr u8 attr = Attribute::Writable | Attribute::Configurable;
  26. prototype.define_native_function(realm, vm.names.toBase64, to_base64, 0, attr);
  27. prototype.define_native_function(realm, vm.names.toHex, to_hex, 0, attr);
  28. prototype.define_native_function(realm, vm.names.setFromBase64, set_from_base64, 1, attr);
  29. prototype.define_native_function(realm, vm.names.setFromHex, set_from_hex, 1, attr);
  30. }
  31. static ThrowCompletionOr<Alphabet> parse_alphabet(VM& vm, Object& options)
  32. {
  33. // Let alphabet be ? Get(opts, "alphabet").
  34. auto alphabet = TRY(options.get(vm.names.alphabet));
  35. // If alphabet is undefined, set alphabet to "base64".
  36. if (alphabet.is_undefined())
  37. return Alphabet::Base64;
  38. // If alphabet is neither "base64" nor "base64url", throw a TypeError exception.
  39. if (alphabet.is_string()) {
  40. if (alphabet.as_string().utf8_string_view() == "base64"sv)
  41. return Alphabet::Base64;
  42. if (alphabet.as_string().utf8_string_view() == "base64url"sv)
  43. return Alphabet::Base64URL;
  44. }
  45. return vm.throw_completion<TypeError>(ErrorType::OptionIsNotValidValue, alphabet, "alphabet"sv);
  46. }
  47. static ThrowCompletionOr<LastChunkHandling> parse_last_chunk_handling(VM& vm, Object& options)
  48. {
  49. // Let lastChunkHandling be ? Get(opts, "lastChunkHandling").
  50. auto last_chunk_handling = TRY(options.get(vm.names.lastChunkHandling));
  51. // If lastChunkHandling is undefined, set lastChunkHandling to "loose".
  52. if (last_chunk_handling.is_undefined())
  53. return LastChunkHandling::Loose;
  54. // If lastChunkHandling is not one of "loose", "strict", or "stop-before-partial", throw a TypeError exception.
  55. if (last_chunk_handling.is_string()) {
  56. if (last_chunk_handling.as_string().utf8_string_view() == "loose"sv)
  57. return LastChunkHandling::Loose;
  58. if (last_chunk_handling.as_string().utf8_string_view() == "strict"sv)
  59. return LastChunkHandling::Strict;
  60. if (last_chunk_handling.as_string().utf8_string_view() == "stop-before-partial"sv)
  61. return LastChunkHandling::StopBeforePartial;
  62. }
  63. return vm.throw_completion<TypeError>(ErrorType::OptionIsNotValidValue, last_chunk_handling, "lastChunkHandling"sv);
  64. }
  65. // 1 Uint8Array.prototype.toBase64 ( [ options ] ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tobase64
  66. JS_DEFINE_NATIVE_FUNCTION(Uint8ArrayPrototypeHelpers::to_base64)
  67. {
  68. auto options_value = vm.argument(0);
  69. // 1. Let O be the this value.
  70. // 2. Perform ? ValidateUint8Array(O).
  71. auto typed_array = TRY(validate_uint8_array(vm));
  72. // 3. Let opts be ? GetOptionsObject(options).
  73. auto* options = TRY(Temporal::get_options_object(vm, options_value));
  74. // 4. Let alphabet be ? Get(opts, "alphabet").
  75. // 5. If alphabet is undefined, set alphabet to "base64".
  76. // 6. If alphabet is neither "base64" nor "base64url", throw a TypeError exception.
  77. auto alphabet = TRY(parse_alphabet(vm, *options));
  78. // 7. Let omitPadding be ToBoolean(? Get(opts, "omitPadding")).
  79. auto omit_padding_value = TRY(options->get(vm.names.omitPadding)).to_boolean();
  80. auto omit_padding = omit_padding_value ? AK::OmitPadding::Yes : AK::OmitPadding::No;
  81. // 8. Let toEncode be ? GetUint8ArrayBytes(O).
  82. auto to_encode = TRY(get_uint8_array_bytes(vm, typed_array));
  83. String out_ascii;
  84. // 9. If alphabet is "base64", then
  85. if (alphabet == Alphabet::Base64) {
  86. // a. Let outAscii be the sequence of code points which results from encoding toEncode according to the base64
  87. // encoding specified in section 4 of RFC 4648. Padding is included if and only if omitPadding is false.
  88. out_ascii = MUST(encode_base64(to_encode, omit_padding));
  89. }
  90. // 10. Else,
  91. else {
  92. // a. Assert: alphabet is "base64url".
  93. // b. Let outAscii be the sequence of code points which results from encoding toEncode according to the base64url
  94. // encoding specified in section 5 of RFC 4648. Padding is included if and only if omitPadding is false.
  95. out_ascii = MUST(encode_base64url(to_encode, omit_padding));
  96. }
  97. // 11. Return CodePointsToString(outAscii).
  98. return PrimitiveString::create(vm, move(out_ascii));
  99. }
  100. // 2 Uint8Array.prototype.toHex ( ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tobase64
  101. JS_DEFINE_NATIVE_FUNCTION(Uint8ArrayPrototypeHelpers::to_hex)
  102. {
  103. // 1. Let O be the this value.
  104. // 2. Perform ? ValidateUint8Array(O).
  105. auto typed_array = TRY(validate_uint8_array(vm));
  106. // 3. Let toEncode be ? GetUint8ArrayBytes(O).
  107. auto to_encode = TRY(get_uint8_array_bytes(vm, typed_array));
  108. // 4. Let out be the empty String.
  109. StringBuilder out;
  110. // 5. For each byte byte of toEncode, do
  111. for (auto byte : to_encode.bytes()) {
  112. // a. Let hex be Number::toString(𝔽(byte), 16).
  113. // b. Set hex to StringPad(hex, 2, "0", START).
  114. // c. Set out to the string-concatenation of out and hex.
  115. out.appendff("{:02x}", byte);
  116. }
  117. // 6. Return out.
  118. return PrimitiveString::create(vm, MUST(out.to_string()));
  119. }
  120. // 3 Uint8Array.fromBase64 ( string [ , options ] ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.frombase64
  121. JS_DEFINE_NATIVE_FUNCTION(Uint8ArrayConstructorHelpers::from_base64)
  122. {
  123. auto& realm = *vm.current_realm();
  124. auto string_value = vm.argument(0);
  125. auto options_value = vm.argument(1);
  126. // 1. If string is not a String, throw a TypeError exception.
  127. if (!string_value.is_string())
  128. return vm.throw_completion<TypeError>(ErrorType::NotAString, string_value);
  129. // 2. Let opts be ? GetOptionsObject(options).
  130. auto* options = TRY(Temporal::get_options_object(vm, options_value));
  131. // 3. Let alphabet be ? Get(opts, "alphabet").
  132. // 4. If alphabet is undefined, set alphabet to "base64".
  133. // 5. If alphabet is neither "base64" nor "base64url", throw a TypeError exception.
  134. auto alphabet = TRY(parse_alphabet(vm, *options));
  135. // 6. Let lastChunkHandling be ? Get(opts, "lastChunkHandling").
  136. // 7. If lastChunkHandling is undefined, set lastChunkHandling to "loose".
  137. // 8. If lastChunkHandling is not one of "loose", "strict", or "stop-before-partial", throw a TypeError exception.
  138. auto last_chunk_handling = TRY(parse_last_chunk_handling(vm, *options));
  139. // 9. Let result be FromBase64(string, alphabet, lastChunkHandling).
  140. auto result = JS::from_base64(vm, string_value.as_string().utf8_string_view(), alphabet, last_chunk_handling);
  141. // 10. If result.[[Error]] is not none, then
  142. if (result.error.has_value()) {
  143. // a. Throw result.[[Error]].
  144. return result.error.release_value();
  145. }
  146. // 11. Let resultLength be the length of result.[[Bytes]].
  147. auto result_length = result.bytes.size();
  148. // 12. Let ta be ? AllocateTypedArray("Uint8Array", %Uint8Array%, "%Uint8Array.prototype%", resultLength).
  149. auto typed_array = TRY(Uint8Array::create(realm, result_length));
  150. // 13. Set the value at each index of ta.[[ViewedArrayBuffer]].[[ArrayBufferData]] to the value at the corresponding
  151. // index of result.[[Bytes]].
  152. auto& array_buffer_data = typed_array->viewed_array_buffer()->buffer();
  153. for (size_t index = 0; index < result_length; ++index)
  154. array_buffer_data[index] = result.bytes[index];
  155. // 14. Return ta.
  156. return typed_array;
  157. }
  158. // 4 Uint8Array.prototype.setFromBase64 ( string [ , options ] ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.setfrombase64
  159. JS_DEFINE_NATIVE_FUNCTION(Uint8ArrayPrototypeHelpers::set_from_base64)
  160. {
  161. auto& realm = *vm.current_realm();
  162. auto string_value = vm.argument(0);
  163. auto options_value = vm.argument(1);
  164. // 1. Let into be the this value.
  165. // 2. Perform ? ValidateUint8Array(into).
  166. auto into = TRY(validate_uint8_array(vm));
  167. // 3. If string is not a String, throw a TypeError exception.
  168. if (!string_value.is_string())
  169. return vm.throw_completion<TypeError>(ErrorType::NotAString, string_value);
  170. // 4. Let opts be ? GetOptionsObject(options).
  171. auto* options = TRY(Temporal::get_options_object(vm, options_value));
  172. // 5. Let alphabet be ? Get(opts, "alphabet").
  173. // 6. If alphabet is undefined, set alphabet to "base64".
  174. // 7. If alphabet is neither "base64" nor "base64url", throw a TypeError exception.
  175. auto alphabet = TRY(parse_alphabet(vm, *options));
  176. // 8. Let lastChunkHandling be ? Get(opts, "lastChunkHandling").
  177. // 9. If lastChunkHandling is undefined, set lastChunkHandling to "loose".
  178. // 10. If lastChunkHandling is not one of "loose", "strict", or "stop-before-partial", throw a TypeError exception.
  179. auto last_chunk_handling = TRY(parse_last_chunk_handling(vm, *options));
  180. // 11. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(into, seq-cst).
  181. auto typed_array_record = make_typed_array_with_buffer_witness_record(into, ArrayBuffer::Order::SeqCst);
  182. // 12. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception.
  183. if (is_typed_array_out_of_bounds(typed_array_record))
  184. return vm.throw_completion<TypeError>(ErrorType::BufferOutOfBounds, "TypedArray"sv);
  185. // 13. Let byteLength be TypedArrayLength(taRecord).
  186. auto byte_length = typed_array_length(typed_array_record);
  187. // 14. Let result be FromBase64(string, alphabet, lastChunkHandling, byteLength).
  188. auto result = JS::from_base64(vm, string_value.as_string().utf8_string_view(), alphabet, last_chunk_handling, byte_length);
  189. // 15. Let bytes be result.[[Bytes]].
  190. auto bytes = move(result.bytes);
  191. // 16. Let written be the length of bytes.
  192. auto written = bytes.size();
  193. // 17. NOTE: FromBase64 does not invoke any user code, so the ArrayBuffer backing into cannot have been detached or shrunk.
  194. // 18. Assert: written ≤ byteLength.
  195. VERIFY(written <= byte_length);
  196. // 19. Perform SetUint8ArrayBytes(into, bytes).
  197. set_uint8_array_bytes(into, bytes);
  198. // 20. If result.[[Error]] is not none, then
  199. if (result.error.has_value()) {
  200. // a. Throw result.[[Error]].
  201. return result.error.release_value();
  202. }
  203. // 21. Let resultObject be OrdinaryObjectCreate(%Object.prototype%).
  204. auto result_object = Object::create(realm, realm.intrinsics().object_prototype());
  205. // 22. Perform ! CreateDataPropertyOrThrow(resultObject, "read", 𝔽(result.[[Read]])).
  206. MUST(result_object->create_data_property(vm.names.read, Value { result.read }));
  207. // 23. Perform ! CreateDataPropertyOrThrow(resultObject, "written", 𝔽(written)).
  208. MUST(result_object->create_data_property(vm.names.written, Value { written }));
  209. // 24. Return resultObject.
  210. return result_object;
  211. }
  212. // 5 Uint8Array.fromHex ( string ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.fromhex
  213. JS_DEFINE_NATIVE_FUNCTION(Uint8ArrayConstructorHelpers::from_hex)
  214. {
  215. auto& realm = *vm.current_realm();
  216. auto string_value = vm.argument(0);
  217. // 1. If string is not a String, throw a TypeError exception.
  218. if (!string_value.is_string())
  219. return vm.throw_completion<TypeError>(ErrorType::NotAString, string_value);
  220. // 2. Let result be FromHex(string).
  221. auto result = JS::from_hex(vm, string_value.as_string().utf8_string_view());
  222. // 3. If result.[[Error]] is not none, then
  223. if (result.error.has_value()) {
  224. // a. Throw result.[[Error]].
  225. return result.error.release_value();
  226. }
  227. // 4. Let resultLength be the length of result.[[Bytes]].
  228. auto result_length = result.bytes.size();
  229. // 5. Let ta be ? AllocateTypedArray("Uint8Array", %Uint8Array%, "%Uint8Array.prototype%", resultLength).
  230. auto typed_array = TRY(Uint8Array::create(realm, result_length));
  231. // 6. Set the value at each index of ta.[[ViewedArrayBuffer]].[[ArrayBufferData]] to the value at the corresponding
  232. // index of result.[[Bytes]].
  233. auto& array_buffer_data = typed_array->viewed_array_buffer()->buffer();
  234. for (size_t index = 0; index < result_length; ++index)
  235. array_buffer_data[index] = result.bytes[index];
  236. // 7. Return ta.
  237. return typed_array;
  238. }
  239. // 6 Uint8Array.prototype.setFromHex ( string ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.setfromhex
  240. JS_DEFINE_NATIVE_FUNCTION(Uint8ArrayPrototypeHelpers::set_from_hex)
  241. {
  242. auto& realm = *vm.current_realm();
  243. auto string_value = vm.argument(0);
  244. // 1. Let into be the this value.
  245. // 2. Perform ? ValidateUint8Array(into).
  246. auto into = TRY(validate_uint8_array(vm));
  247. // 3. If string is not a String, throw a TypeError exception.
  248. if (!string_value.is_string())
  249. return vm.throw_completion<TypeError>(ErrorType::NotAString, string_value);
  250. // 4. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(into, seq-cst).
  251. auto typed_array_record = make_typed_array_with_buffer_witness_record(into, ArrayBuffer::Order::SeqCst);
  252. // 5. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception.
  253. if (is_typed_array_out_of_bounds(typed_array_record))
  254. return vm.throw_completion<TypeError>(ErrorType::BufferOutOfBounds, "TypedArray"sv);
  255. // 6. Let byteLength be TypedArrayLength(taRecord).
  256. auto byte_length = typed_array_length(typed_array_record);
  257. // 7. Let result be FromHex(string, byteLength).
  258. auto result = JS::from_hex(vm, string_value.as_string().utf8_string_view(), byte_length);
  259. // 8. Let bytes be result.[[Bytes]].
  260. auto bytes = move(result.bytes);
  261. // 9. Let written be the length of bytes.
  262. auto written = bytes.size();
  263. // 10. NOTE: FromHex does not invoke any user code, so the ArrayBuffer backing into cannot have been detached or shrunk.
  264. // 11. Assert: written ≤ byteLength.
  265. VERIFY(written <= byte_length);
  266. // 12. Perform SetUint8ArrayBytes(into, bytes).
  267. set_uint8_array_bytes(into, bytes);
  268. // 13. If result.[[Error]] is not none, then
  269. if (result.error.has_value()) {
  270. // a. Throw result.[[Error]].
  271. return result.error.release_value();
  272. }
  273. // 14. Let resultObject be OrdinaryObjectCreate(%Object.prototype%).
  274. auto result_object = Object::create(realm, realm.intrinsics().object_prototype());
  275. // 15. Perform ! CreateDataPropertyOrThrow(resultObject, "read", 𝔽(result.[[Read]])).
  276. MUST(result_object->create_data_property(vm.names.read, Value { result.read }));
  277. // 16. Perform ! CreateDataPropertyOrThrow(resultObject, "written", 𝔽(written)).
  278. MUST(result_object->create_data_property(vm.names.written, Value { written }));
  279. // 17. Return resultObject.
  280. return result_object;
  281. }
  282. // 7 ValidateUint8Array ( ta ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-validateuint8array
  283. ThrowCompletionOr<GC::Ref<TypedArrayBase>> validate_uint8_array(VM& vm)
  284. {
  285. auto this_object = TRY(vm.this_value().to_object(vm));
  286. // 1. Perform ? RequireInternalSlot(ta, [[TypedArrayName]]).
  287. if (!this_object->is_typed_array())
  288. return vm.throw_completion<TypeError>(ErrorType::NotAnObjectOfType, "Uint8Array");
  289. auto& typed_array = static_cast<TypedArrayBase&>(*this_object.ptr());
  290. // 2. If ta.[[TypedArrayName]] is not "Uint8Array", throw a TypeError exception.
  291. if (typed_array.kind() != TypedArrayBase::Kind::Uint8Array)
  292. return vm.throw_completion<TypeError>(ErrorType::NotAnObjectOfType, "Uint8Array");
  293. // 3. Return UNUSED.
  294. return typed_array;
  295. }
  296. // 8 GetUint8ArrayBytes ( ta ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-getuint8arraybytes
  297. ThrowCompletionOr<ByteBuffer> get_uint8_array_bytes(VM& vm, TypedArrayBase const& typed_array)
  298. {
  299. // 1. Let buffer be ta.[[ViewedArrayBuffer]].
  300. // 2. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(ta, SEQ-CST).
  301. auto typed_array_record = make_typed_array_with_buffer_witness_record(typed_array, ArrayBuffer::Order::SeqCst);
  302. // 3. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception.
  303. if (is_typed_array_out_of_bounds(typed_array_record))
  304. return vm.throw_completion<TypeError>(ErrorType::BufferOutOfBounds, "TypedArray"sv);
  305. // 4. Let len be TypedArrayLength(taRecord).
  306. auto length = typed_array_length(typed_array_record);
  307. // 5. Let byteOffset be ta.[[ByteOffset]].
  308. auto byte_offset = typed_array.byte_offset();
  309. // 6. Let bytes be a new empty List.
  310. ByteBuffer bytes;
  311. // 7. Let index be 0.
  312. // 8. Repeat, while index < len,
  313. for (u32 index = 0; index < length; ++index) {
  314. // a. Let byteIndex be byteOffset + index.
  315. auto byte_index = byte_offset + index;
  316. // b. Let byte be ℝ(GetValueFromBuffer(buffer, byteIndex, UINT8, true, UNORDERED)).
  317. auto byte = typed_array.get_value_from_buffer(byte_index, ArrayBuffer::Order::Unordered);
  318. // c. Append byte to bytes.
  319. bytes.append(MUST(byte.to_u8(vm)));
  320. // d. Set index to index + 1.
  321. }
  322. // 9. Return bytes.
  323. return bytes;
  324. }
  325. // 9 SetUint8ArrayBytes ( into, bytes ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-writeuint8arraybytes
  326. void set_uint8_array_bytes(TypedArrayBase& into, ReadonlyBytes bytes)
  327. {
  328. // 1. Let offset be into.[[ByteOffset]].
  329. auto offset = into.byte_offset();
  330. // 2. Let len be the length of bytes.
  331. auto length = bytes.size();
  332. // 3. Let index be 0.
  333. // 4. Repeat, while index < len,
  334. for (u32 index = 0; index < length; ++index) {
  335. // a. Let byte be bytes[index].
  336. auto byte = bytes[index];
  337. // b. Let byteIndexInBuffer be index + offset.
  338. auto byte_index_in_buffer = index + offset;
  339. // c. Perform SetValueInBuffer(into.[[ViewedArrayBuffer]], byteIndexInBuffer, uint8, 𝔽(byte), true, unordered).
  340. into.set_value_in_buffer(byte_index_in_buffer, Value { byte }, ArrayBuffer::Order::Unordered);
  341. // d. Set index to index + 1.
  342. }
  343. }
  344. // 10.1 SkipAsciiWhitespace ( string, index ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-skipasciiwhitespace
  345. static size_t skip_ascii_whitespace(StringView string, size_t index)
  346. {
  347. // 1. Let length be the length of string.
  348. auto length = string.length();
  349. // 2. Repeat, while index < length,
  350. while (index < length) {
  351. // a. Let char be the code unit at index index of string.
  352. auto ch = string[index];
  353. // b. If char is neither 0x0009 (TAB), 0x000A (LF), 0x000C (FF), 0x000D (CR), nor 0x0020 (SPACE), then
  354. if (ch != '\t' && ch != '\n' && ch != '\f' && ch != '\r' && ch != ' ') {
  355. // i. Return index.
  356. return index;
  357. }
  358. // c. Set index to index + 1.
  359. ++index;
  360. }
  361. // 3. Return index.
  362. return index;
  363. }
  364. // 10.2 DecodeBase64Chunk ( chunk [ , throwOnExtraBits ] ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-frombase64
  365. static ThrowCompletionOr<ByteBuffer> decode_base64_chunk(VM& vm, StringBuilder& chunk, Optional<bool> throw_on_extra_bits = {})
  366. {
  367. // 1. Let chunkLength be the length of chunk.
  368. auto chunk_length = chunk.length();
  369. // 2. If chunkLength is 2, then
  370. if (chunk_length == 2) {
  371. // a. Set chunk to the string-concatenation of chunk and "AA".
  372. chunk.append("AA"sv);
  373. }
  374. // 3. Else if chunkLength is 3, then
  375. else if (chunk_length == 3) {
  376. // a. Set chunk to the string-concatenation of chunk and "A".
  377. chunk.append("A"sv);
  378. }
  379. // 4. Else,
  380. else {
  381. // a. Assert: chunkLength is 4.
  382. VERIFY(chunk_length == 4);
  383. }
  384. // 5. Let byteSequence be the unique sequence of 3 bytes resulting from decoding chunk as base64 (such that applying
  385. // the base64 encoding specified in section 4 of RFC 4648 to byteSequence would produce chunk).
  386. // 6. Let bytes be a List whose elements are the elements of byteSequence, in order.
  387. auto bytes = MUST(decode_base64(chunk.string_view()));
  388. // 7. If chunkLength is 2, then
  389. if (chunk_length == 2) {
  390. // a. Assert: throwOnExtraBits is present.
  391. VERIFY(throw_on_extra_bits.has_value());
  392. // b. If throwOnExtraBits is true and bytes[1] ≠ 0, then
  393. if (*throw_on_extra_bits && bytes[1] != 0) {
  394. // i. Throw a SyntaxError exception.
  395. return vm.throw_completion<SyntaxError>("Extra bits found at end of chunk"sv);
  396. }
  397. // c. Return « bytes[0] ».
  398. return MUST(bytes.slice(0, 1));
  399. }
  400. // 8. Else if chunkLength is 3, then
  401. if (chunk_length == 3) {
  402. // a. Assert: throwOnExtraBits is present.
  403. VERIFY(throw_on_extra_bits.has_value());
  404. // b. If throwOnExtraBits is true and bytes[2] ≠ 0, then
  405. if (*throw_on_extra_bits && bytes[2] != 0) {
  406. // i. Throw a SyntaxError exception.
  407. return vm.throw_completion<SyntaxError>("Extra bits found at end of chunk"sv);
  408. }
  409. // c. Return « bytes[0], bytes[1] ».
  410. return MUST(bytes.slice(0, 2));
  411. }
  412. // 9. Else,
  413. // a. Return bytes.
  414. return bytes;
  415. }
  416. // 10.3 FromBase64 ( string, alphabet, lastChunkHandling [ , maxLength ] ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-frombase64
  417. DecodeResult from_base64(VM& vm, StringView string, Alphabet alphabet, LastChunkHandling last_chunk_handling, Optional<size_t> max_length)
  418. {
  419. // FIXME: We can only use simdutf when the last-chunk-handling parameter is "loose". Upstream is planning to implement
  420. // the remaining options. When that is complete, we should be able to remove the slow implementation below. See:
  421. // https://github.com/simdutf/simdutf/issues/440
  422. if (last_chunk_handling == LastChunkHandling::Loose) {
  423. auto output = MUST(ByteBuffer::create_uninitialized(max_length.value_or_lazy_evaluated([&]() {
  424. return AK::size_required_to_decode_base64(string);
  425. })));
  426. auto result = alphabet == Alphabet::Base64
  427. ? AK::decode_base64_into(string, output)
  428. : AK::decode_base64url_into(string, output);
  429. if (result.is_error()) {
  430. auto error = vm.throw_completion<SyntaxError>(result.error().error.string_literal());
  431. return { .read = result.error().valid_input_bytes, .bytes = move(output), .error = move(error) };
  432. }
  433. return { .read = result.value(), .bytes = move(output), .error = {} };
  434. }
  435. // 1. If maxLength is not present, then
  436. if (!max_length.has_value()) {
  437. // a. Let maxLength be 2**53 - 1.
  438. max_length = MAX_ARRAY_LIKE_INDEX;
  439. // b. NOTE: Because the input is a string, the length of strings is limited to 2**53 - 1 characters, and the
  440. // output requires no more bytes than the input has characters, this limit can never be reached. However, it
  441. // is editorially convenient to use a finite value here.
  442. }
  443. // 2. NOTE: The order of validation and decoding in the algorithm below is not observable. Implementations are
  444. // encouraged to perform them in whatever order is most efficient, possibly interleaving validation with decoding,
  445. // as long as the behaviour is observably equivalent.
  446. // 3. If maxLength is 0, then
  447. if (max_length == 0uz) {
  448. // a. Return the Record { [[Read]]: 0, [[Bytes]]: « », [[Error]]: none }.
  449. return { .read = 0, .bytes = {}, .error = {} };
  450. }
  451. // 4. Let read be 0.
  452. size_t read = 0;
  453. // 5. Let bytes be « ».
  454. ByteBuffer bytes;
  455. // 6. Let chunk be the empty String.
  456. StringBuilder chunk;
  457. // 7. Let chunkLength be 0.
  458. size_t chunk_length = 0;
  459. // 8. Let index be 0.
  460. size_t index = 0;
  461. // 9. Let length be the length of string.
  462. auto length = string.length();
  463. // 10. Repeat,
  464. while (true) {
  465. // a. Set index to SkipAsciiWhitespace(string, index).
  466. index = skip_ascii_whitespace(string, index);
  467. // b. If index = length, then
  468. if (index == length) {
  469. // i. If chunkLength > 0, then
  470. if (chunk_length > 0) {
  471. // 1. If lastChunkHandling is "stop-before-partial", then
  472. if (last_chunk_handling == LastChunkHandling::StopBeforePartial) {
  473. // a. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: none }.
  474. return { .read = read, .bytes = move(bytes), .error = {} };
  475. }
  476. // 2. Else if lastChunkHandling is "loose", then
  477. else if (last_chunk_handling == LastChunkHandling::Loose) {
  478. VERIFY_NOT_REACHED();
  479. }
  480. // 3. Else,
  481. else {
  482. // a. Assert: lastChunkHandling is "strict".
  483. VERIFY(last_chunk_handling == LastChunkHandling::Strict);
  484. // b. Let error be a new SyntaxError exception.
  485. auto error = vm.throw_completion<SyntaxError>("Invalid trailing data"sv);
  486. // c. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
  487. return { .read = read, .bytes = move(bytes), .error = move(error) };
  488. }
  489. }
  490. // ii. Return the Record { [[Read]]: length, [[Bytes]]: bytes, [[Error]]: none }.
  491. return { .read = length, .bytes = move(bytes), .error = {} };
  492. }
  493. // c. Let char be the substring of string from index to index + 1.
  494. auto ch = string[index];
  495. // d. Set index to index + 1.
  496. ++index;
  497. // e. If char is "=", then
  498. if (ch == '=') {
  499. // i. If chunkLength < 2, then
  500. if (chunk_length < 2) {
  501. // 1. Let error be a new SyntaxError exception.
  502. auto error = vm.throw_completion<SyntaxError>("Unexpected padding character"sv);
  503. // 2. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
  504. return { .read = read, .bytes = move(bytes), .error = move(error) };
  505. }
  506. // ii. Set index to SkipAsciiWhitespace(string, index).
  507. index = skip_ascii_whitespace(string, index);
  508. // iii. If chunkLength = 2, then
  509. if (chunk_length == 2) {
  510. // 1. If index = length, then
  511. if (index == length) {
  512. // a. If lastChunkHandling is "stop-before-partial", then
  513. if (last_chunk_handling == LastChunkHandling::StopBeforePartial) {
  514. // i. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: none }.
  515. return { .read = read, .bytes = move(bytes), .error = {} };
  516. }
  517. // b. Let error be a new SyntaxError exception.
  518. auto error = vm.throw_completion<SyntaxError>("Incomplete number of padding characters"sv);
  519. // c. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
  520. return { .read = read, .bytes = move(bytes), .error = move(error) };
  521. }
  522. // 2. Set char to the substring of string from index to index + 1.
  523. ch = string[index];
  524. // 3. If char is "=", then
  525. if (ch == '=') {
  526. // a. Set index to SkipAsciiWhitespace(string, index + 1).
  527. index = skip_ascii_whitespace(string, index + 1);
  528. }
  529. }
  530. // iv. If index < length, then
  531. if (index < length) {
  532. // 1. Let error be a new SyntaxError exception.
  533. auto error = vm.throw_completion<SyntaxError>("Unexpected padding character"sv);
  534. // 2. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
  535. return { .read = read, .bytes = move(bytes), .error = move(error) };
  536. }
  537. // v. If lastChunkHandling is "strict", let throwOnExtraBits be true.
  538. // vi. Else, let throwOnExtraBits be false.
  539. auto throw_on_extra_bits = last_chunk_handling == LastChunkHandling::Strict;
  540. // vii. Let decodeResult be Completion(DecodeBase64Chunk(chunk, throwOnExtraBits)).
  541. auto decode_result = decode_base64_chunk(vm, chunk, throw_on_extra_bits);
  542. // viii. If decodeResult is an abrupt completion, then
  543. if (decode_result.is_error()) {
  544. // 1. Let error be decodeResult.[[Value]].
  545. auto error = decode_result.release_error();
  546. // 2. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
  547. return { .read = read, .bytes = move(bytes), .error = move(error) };
  548. }
  549. // ix. Set bytes to the list-concatenation of bytes and ! decodeResult.
  550. bytes.append(decode_result.release_value());
  551. // x. Return the Record { [[Read]]: length, [[Bytes]]: bytes, [[Error]]: none }.
  552. return { .read = length, .bytes = move(bytes), .error = {} };
  553. }
  554. // f. If alphabet is "base64url", then
  555. if (alphabet == Alphabet::Base64URL) {
  556. // i. If char is either "+" or "/", then
  557. if (ch == '+' || ch == '/') {
  558. // 1. Let error be a new SyntaxError exception.
  559. auto error = vm.throw_completion<SyntaxError>(MUST(String::formatted("Invalid character '{}'"sv, ch)));
  560. // 2. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
  561. return { .read = read, .bytes = move(bytes), .error = move(error) };
  562. }
  563. // ii. Else if char is "-", then
  564. else if (ch == '-') {
  565. // 1. Set char to "+".
  566. ch = '+';
  567. }
  568. // iii. Else if char is "_", then
  569. else if (ch == '_') {
  570. // 1. Set char to "/".
  571. ch = '/';
  572. }
  573. }
  574. // g. If the sole code unit of char is not an element of the standard base64 alphabet, then
  575. static constexpr auto standard_base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"sv;
  576. if (!standard_base64_alphabet.contains(ch)) {
  577. // i. Let error be a new SyntaxError exception.
  578. auto error = vm.throw_completion<SyntaxError>(MUST(String::formatted("Invalid character '{}'"sv, ch)));
  579. // ii. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
  580. return { .read = read, .bytes = move(bytes), .error = move(error) };
  581. }
  582. // h. Let remaining be maxLength - the length of bytes.
  583. auto remaining = *max_length - bytes.size();
  584. // i. If remaining = 1 and chunkLength = 2, or if remaining = 2 and chunkLength = 3, then
  585. if ((remaining == 1 && chunk_length == 2) || (remaining == 2 && chunk_length == 3)) {
  586. // i. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: none }.
  587. return { .read = read, .bytes = move(bytes), .error = {} };
  588. }
  589. // j. Set chunk to the string-concatenation of chunk and char.
  590. chunk.append(ch);
  591. // k. Set chunkLength to the length of chunk.
  592. chunk_length = chunk.length();
  593. // l. If chunkLength = 4, then
  594. if (chunk_length == 4) {
  595. // i. Set bytes to the list-concatenation of bytes and ! DecodeBase64Chunk(chunk).
  596. bytes.append(MUST(decode_base64_chunk(vm, chunk)));
  597. // ii. Set chunk to the empty String.
  598. chunk.clear();
  599. // iii. Set chunkLength to 0.
  600. chunk_length = 0;
  601. // iv. Set read to index.
  602. read = index;
  603. // v. If the length of bytes = maxLength, then
  604. if (bytes.size() == max_length) {
  605. // 1. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: none }.
  606. return { .read = read, .bytes = move(bytes), .error = {} };
  607. }
  608. }
  609. }
  610. }
  611. // 10.4 FromHex ( string [ , maxLength ] ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-fromhex
  612. DecodeResult from_hex(VM& vm, StringView string, Optional<size_t> max_length)
  613. {
  614. // 1. If maxLength is not present, let maxLength be 2**53 - 1.
  615. if (!max_length.has_value())
  616. max_length = MAX_ARRAY_LIKE_INDEX;
  617. // 2. Let length be the length of string.
  618. auto length = string.length();
  619. // 3. Let bytes be « ».
  620. ByteBuffer bytes;
  621. // 4. Let read be 0.
  622. size_t read = 0;
  623. // 5. If length modulo 2 is not 0, then
  624. if (length % 2 != 0) {
  625. // a. Let error be a new SyntaxError exception.
  626. auto error = vm.throw_completion<SyntaxError>("Hex string must have an even length"sv);
  627. // b. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
  628. return { .read = read, .bytes = move(bytes), .error = move(error) };
  629. }
  630. // 6. Repeat, while read < length and the length of bytes < maxLength,
  631. while (read < length && bytes.size() < *max_length) {
  632. // a. Let hexits be the substring of string from read to read + 2.
  633. auto hexits = string.substring_view(read, 2);
  634. // d. Let byte be the integer value represented by hexits in base-16 notation, using the letters A-F and a-f
  635. // for digits with values 10 through 15.
  636. // NOTE: We do this early so that we don't have to effectively parse hexits twice.
  637. auto byte = AK::StringUtils::convert_to_uint_from_hex<u8>(hexits, AK::TrimWhitespace::No);
  638. // b. If hexits contains any code units which are not in "0123456789abcdefABCDEF", then
  639. if (!byte.has_value()) {
  640. // i. Let error be a new SyntaxError exception.
  641. auto error = vm.throw_completion<SyntaxError>("Hex string must only contain hex characters"sv);
  642. // ii. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
  643. return { .read = read, .bytes = move(bytes), .error = move(error) };
  644. }
  645. // c. Set read to read + 2.
  646. read += 2;
  647. // e. Append byte to bytes.
  648. bytes.append(*byte);
  649. }
  650. // 7. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: none }.
  651. return { .read = read, .bytes = move(bytes), .error = {} };
  652. }
  653. }