Stage 3 Draft / March 25, 2024

Uint8Array to/from base64

Contributing to this Proposal

This proposal is developed on GitHub. A playground is available with examples and a non-production polyfill to allow direct experimentation.

1 Uint8Array.prototype.toBase64 ( [ options ] )

  1. Let O be the this value.
  2. Perform ? ValidateUint8Array(O).
  3. Let opts be ? GetOptionsObject(options).
  4. Let alphabet be ? Get(opts, "alphabet").
  5. If alphabet is undefined, set alphabet to "base64".
  6. If alphabet is neither "base64" nor "base64url", throw a TypeError exception.
  7. Let toEncode be ? GetUint8ArrayBytes(O).
  8. If alphabet is "base64", then
    1. Let outAscii be the sequence of code points which results from encoding toEncode according to the base64 encoding specified in section 4 of RFC 4648. Padding is included.
  9. Else,
    1. Assert: alphabet is "base64url".
    2. Let outAscii be the sequence of code points which results from encoding toEncode according to the base64url encoding specified in section 5 of RFC 4648. Padding is included.
  10. Return CodePointsToString(outAscii).

2 Uint8Array.prototype.toHex ( )

  1. Let O be the this value.
  2. Perform ? ValidateUint8Array(O).
  3. Let toEncode be ? GetUint8ArrayBytes(O).
  4. Let out be the empty String.
  5. For each byte byte of toEncode, do
    1. Let hex be Number::toString(𝔽(byte), 16).
    2. Set hex to StringPad(hex, 2, "0", start).
    3. Set out to the string-concatenation of out and hex.
  6. Return out.

3 Uint8Array.fromBase64 ( string [ , options ] )

  1. If string is not a String, throw a TypeError exception.
  2. Let opts be ? GetOptionsObject(options).
  3. Let alphabet be ? Get(opts, "alphabet").
  4. If alphabet is undefined, set alphabet to "base64".
  5. If alphabet is neither "base64" nor "base64url", throw a TypeError exception.
  6. Let lastChunkHandling be ? Get(opts, "lastChunkHandling").
  7. If lastChunkHandling is undefined, set lastChunkHandling to "loose".
  8. If lastChunkHandling is not one of "loose", "strict", or "stop-before-partial", throw a TypeError exception.
  9. Let result be ? FromBase64(string, alphabet, lastChunkHandling).
  10. Let resultLength be the length of result.[[Bytes]].
  11. Let ta be ? AllocateTypedArray("Uint8Array", %Uint8Array%, "%Uint8Array.prototype%", resultLength).
  12. Set the value at each index of ta.[[ViewedArrayBuffer]].[[ArrayBufferData]] to the value at the corresponding index of result.[[Bytes]].
  13. Return ta.

4 Uint8Array.prototype.setFromBase64 ( string [ , options ] )

  1. Let into be the this value.
  2. Perform ? ValidateUint8Array(into).
  3. If string is not a String, throw a TypeError exception.
  4. Let opts be ? GetOptionsObject(options).
  5. Let alphabet be ? Get(opts, "alphabet").
  6. If alphabet is undefined, set alphabet to "base64".
  7. If alphabet is neither "base64" nor "base64url", throw a TypeError exception.
  8. Let lastChunkHandling be ? Get(opts, "lastChunkHandling").
  9. If lastChunkHandling is undefined, set lastChunkHandling to "loose".
  10. If lastChunkHandling is not one of "loose", "strict", or "stop-before-partial", throw a TypeError exception.
  11. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(into, seq-cst).
  12. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception.
  13. Let byteLength be TypedArrayLength(taRecord).
  14. Let result be ? FromBase64(string, alphabet, lastChunkHandling, byteLength).
  15. Let bytes be result.[[Bytes]].
  16. Let written be the length of bytes.
  17. NOTE: FromBase64 does not invoke any user code, so the ArrayBuffer backing into cannot have been detached or shrunk.
  18. Assert: written ≀ byteLength.
  19. Perform SetUint8ArrayBytes(into, bytes).
  20. Let resultObject be OrdinaryObjectCreate(%Object.prototype%).
  21. Perform ! CreateDataPropertyOrThrow(resultObject, "read", 𝔽(result.[[Read]])).
  22. Perform ! CreateDataPropertyOrThrow(resultObject, "written", 𝔽(written)).
  23. Return resultObject.

5 Uint8Array.fromHex ( string )

  1. If string is not a String, throw a TypeError exception.
  2. Let result be ? FromHex(string).
  3. Let resultLength be the length of result.[[Bytes]].
  4. Let ta be ? AllocateTypedArray("Uint8Array", %Uint8Array%, "%Uint8Array.prototype%", resultLength).
  5. Set the value at each index of ta.[[ViewedArrayBuffer]].[[ArrayBufferData]] to the value at the corresponding index of result.[[Bytes]].
  6. Return ta.

6 Uint8Array.prototype.setFromHex ( string )

  1. Let into be the this value.
  2. Perform ? ValidateUint8Array(into).
  3. If string is not a String, throw a TypeError exception.
  4. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(into, seq-cst).
  5. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception.
  6. Let byteLength be TypedArrayLength(taRecord).
  7. Let result be ? FromHex(string, byteLength).
  8. Let bytes be result.[[Bytes]].
  9. Let written be the length of bytes.
  10. NOTE: FromHex does not invoke any user code, so the ArrayBuffer backing into cannot have been detached or shrunk.
  11. Assert: written ≀ byteLength.
  12. Perform SetUint8ArrayBytes(into, bytes).
  13. Let resultObject be OrdinaryObjectCreate(%Object.prototype%).
  14. Perform ! CreateDataPropertyOrThrow(resultObject, "read", 𝔽(result.[[Read]])).
  15. Perform ! CreateDataPropertyOrThrow(resultObject, "written", 𝔽(written)).
  16. Return resultObject.

7 ValidateUint8Array ( ta )

The abstract operation ValidateUint8Array takes argument ta (an ECMAScript language value) and returns either a normal completion containing unused or a throw completion. It performs the following steps when called:

  1. Perform ? RequireInternalSlot(ta, [[TypedArrayName]]).
  2. If ta.[[TypedArrayName]] is not "Uint8Array", throw a TypeError exception.
  3. Return unused.

8 GetUint8ArrayBytes ( ta )

The abstract operation GetUint8ArrayBytes takes argument ta (a Uint8Array) and returns either a normal completion containing a List of byte values or a throw completion. It performs the following steps when called:

  1. Let buffer be ta.[[ViewedArrayBuffer]].
  2. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(ta, seq-cst).
  3. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception.
  4. Let len be TypedArrayLength(taRecord).
  5. Let byteOffset be ta.[[ByteOffset]].
  6. Let bytes be a new empty List.
  7. Let index be 0.
  8. Repeat, while index < len,
    1. Let byteIndex be byteOffset + index.
    2. Let byte be ℝ(GetValueFromBuffer(buffer, byteIndex, uint8, true, unordered)).
    3. Append byte to bytes.
    4. Set index to index + 1.
  9. Return bytes.

9 SetUint8ArrayBytes ( into, bytes )

The abstract operation SetUint8ArrayBytes takes arguments into (a Uint8Array) and bytes (a List of byte values) and returns unused. It performs the following steps when called:

  1. Let offset be into.[[ByteOffset]].
  2. Let len be the length of bytes.
  3. Let index be 0.
  4. Repeat, while index < len,
    1. Let byte be bytes[index].
    2. Let byteIndexInBuffer be index + offset.
    3. Perform SetValueInBuffer(into.[[ViewedArrayBuffer]], byteIndexInBuffer, uint8, 𝔽(byte), true, unordered).
    4. Set index to index + 1.

10 Helpers

10.1 SkipAsciiWhitespace ( string, index )

The abstract operation SkipAsciiWhitespace takes arguments string (a string) and index (a non-negative integer) and returns a non-negative integer. It performs the following steps when called:

  1. Let length be the length of string.
  2. Repeat, while index < length,
    1. Let char be the code unit at index index of string.
    2. If char is neither 0x0009 (TAB), 0x000A (LF), 0x000C (FF), 0x000D (CR), nor 0x0020 (SPACE), then
      1. Return index.
    3. Set index to index + 1.
  3. Return index.

10.2 DecodeBase64Chunk ( chunk [ , throwOnExtraBits ] )

The abstract operation DecodeBase64Chunk takes argument chunk (a string) and optional argument throwOnExtraBits (a boolean) and returns either a normal completion containing a List of byte values, or a throw completion.

The standard base64 alphabet is the String whose elements are the code units corresponding to every letter and number in the Unicode Basic Latin block along with "+" and "/"; that is, it is "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".

  1. Let chunkLength be the length of chunk.
  2. If chunkLength is 2, then
    1. Set chunk to the string-concatenation of chunk and "AA".
  3. Else if chunkLength is 3, then
    1. Set chunk to the string-concatenation of chunk and "A".
  4. Else,
    1. Assert: chunkLength is 4.
  5. Let byteSequence be the unique sequence of 3 bytes resulting from decoding chunk as base64 (such that applying the base64 encoding specified in section 4 of RFC 4648 to byteSequence would produce chunk).
  6. Let bytes be a List whose elements are the elements of byteSequence, in order.
  7. If chunkLength is 2, then
    1. Assert: throwOnExtraBits is present.
    2. If throwOnExtraBits is true and bytes[1] β‰  0, then
      1. Throw a SyntaxError exception.
    3. Return Β« bytes[0] Β».
  8. Else if chunkLength is 3, then
    1. Assert: throwOnExtraBits is present.
    2. If throwOnExtraBits is true and bytes[2] β‰  0, then
      1. Throw a SyntaxError exception.
    3. Return Β« bytes[0], bytes[1] Β».
  9. Else,
    1. Return bytes.

10.3 FromBase64 ( string, alphabet, lastChunkHandling [ , maxLength ] )

The abstract operation FromBase64 takes arguments string (a string), alphabet ("base64" or "base64url"), and lastChunkHandling ("loose", "strict", or "stop-before-partial") and optional argument maxLength (a non-negative integer) and returns either a normal completion containing a Record with fields [[Read]] (an integral Number) and [[Bytes]] (a List of byte values), or a throw completion. It performs the following steps when called:

  1. If maxLength is not present, then
    1. Let maxLength be 253 - 1.
    2. NOTE: Because the input is a string, the length of strings is limited to 253 - 1 characters, and the output requires no more bytes than the input has characters, this limit can never be reached. However, it is editorially convenient to use a finite value here.
  2. NOTE: The order of validation and decoding in the algorithm below is not observable. Implementations are encouraged to perform them in whatever order is most efficient, possibly interleaving validation with decoding, as long as the behaviour is observably equivalent.
  3. If maxLength is 0, then
    1. Return the Record { [[Read]]: 0, [[Bytes]]: Β« Β» }.
  4. Let read be 0.
  5. Let bytes be Β« Β».
  6. Let chunk be the empty String.
  7. Let chunkLength be 0.
  8. Let index be 0.
  9. Let length be the length of string.
  10. Repeat,
    1. Set index to SkipAsciiWhitespace(string, index).
    2. If index = length, then
      1. If chunkLength > 0, then
        1. If lastChunkHandling is "stop-before-partial", then
          1. Return the Record { [[Read]]: read, [[Bytes]]: bytes }.
        2. Else if lastChunkHandling is "loose", then
          1. If chunkLength is 1, then
            1. Throw a SyntaxError exception.
          2. Set bytes to the list-concatenation of bytes and ! DecodeBase64Chunk(chunk, false).
        3. Else,
          1. Assert: lastChunkHandling is "strict".
          2. Throw a SyntaxError exception.
      2. Return the Record { [[Read]]: length, [[Bytes]]: bytes }.
    3. Let char be the substring of string from index to index + 1.
    4. Set index to index + 1.
    5. If char is "=", then
      1. If chunkLength < 2, then
        1. Throw a SyntaxError exception.
      2. Set index to SkipAsciiWhitespace(string, index).
      3. If chunkLength = 2, then
        1. If index = length, then
          1. If lastChunkHandling is "stop-before-partial", then
            1. Return the Record { [[Read]]: read, [[Bytes]]: bytes }.
          2. Throw a SyntaxError exception.
        2. Set char to the substring of string from index to index + 1.
        3. If char is "=", then
          1. Set index to SkipAsciiWhitespace(string, index + 1).
      4. If index < length, then
        1. Throw a SyntaxError exception.
      5. If lastChunkHandling is "strict", let throwOnExtraBits be true.
      6. Else, let throwOnExtraBits be false.
      7. Set bytes to the list-concatenation of bytes and ? DecodeBase64Chunk(chunk, throwOnExtraBits).
      8. Return the Record { [[Read]]: length, [[Bytes]]: bytes }.
    6. If alphabet is "base64url", then
      1. If char is either "+" or "/", throw a SyntaxError exception.
      2. Else if char is "-", set char to "+".
      3. Else if char is "_", set char to "/".
    7. If the sole code unit of char is not an element of the standard base64 alphabet, throw a SyntaxError exception.
    8. Let remaining be maxLength - the length of bytes.
    9. If remaining = 1 and chunkLength = 2, or if remaining = 2 and chunkLength = 3, then
      1. Return the Record { [[Read]]: read, [[Bytes]]: bytes }.
    10. Set chunk to the string-concatenation of chunk and char.
    11. Set chunkLength to the length of chunk.
    12. If chunkLength = 4, then
      1. Set bytes to the list-concatenation of bytes and ! DecodeBase64Chunk(chunk).
      2. Set chunk to the empty String.
      3. Set chunkLength to 0.
      4. Set read to index.
      5. If the length of bytes = maxLength, then
        1. Return the Record { [[Read]]: read, [[Bytes]]: bytes }.

10.4 FromHex ( string [ , maxLength ] )

The abstract operation FromHex takes argument string (a string) and optional argument maxLength (a non-negative integer) and returns either a normal completion containing a Record with fields [[Read]] (an integral Number) and [[Bytes]] (a List of byte values), or a throw completion. It performs the following steps when called:

  1. If maxLength is not present, let maxLength be 253 - 1.
  2. Let length be the length of string.
  3. If length modulo 2 is not 0, throw a SyntaxError exception.
  4. Let bytes be Β« Β».
  5. Let index be 0.
  6. Repeat, while index < length and the length of bytes < maxLength,
    1. Let hexits be the substring of string from index to index + 2.
    2. If hexits contains any code units which are not in "0123456789abcdefABCDEF", throw a SyntaxError exception.
    3. Set index to index + 2.
    4. Let byte be the integer value represented by hexits in base-16 notation, using the letters A-F and a-f for digits with values 10 through 15.
    5. Append byte to bytes.
  7. Return the Record { [[Read]]: index, [[Bytes]]: bytes }.

10.5 GetOptionsObject ( options )

  1. If options is undefined, then
    1. Return OrdinaryObjectCreate(null).
  2. If Type(options) is Object, then
    1. Return options.
  3. Throw a TypeError exception.

A Bibliography

NOTE: We need to add RFC 4648 to the bibliography as part of landing this upstream.

  1. RFC 4648 β€œThe Base16, Base32, and Base64 Data Encodings”, available at <https://datatracker.ietf.org/doc/html/rfc4648>