Stage 3 Draft / June 11, 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 omitPadding be ToBoolean(? Get(opts, "omitPadding")).
  8. Let toEncode be ? GetUint8ArrayBytes(O).
  9. 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 if and only if omitPadding is false.
  10. 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 if and only if omitPadding is false.
  11. 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. If result.[[Error]] is not none, then
    1. Throw result.[[Error]].
  11. Let resultLength be the length of result.[[Bytes]].
  12. Let ta be ? AllocateTypedArray("Uint8Array", %Uint8Array%, "%Uint8Array.prototype%", resultLength).
  13. Set the value at each index of ta.[[ViewedArrayBuffer]].[[ArrayBufferData]] to the value at the corresponding index of result.[[Bytes]].
  14. 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. If result.[[Error]] is not none, then
    1. Throw result.[[Error]].
  21. Let resultObject be OrdinaryObjectCreate(%Object.prototype%).
  22. Perform ! CreateDataPropertyOrThrow(resultObject, "read", 𝔽(result.[[Read]])).
  23. Perform ! CreateDataPropertyOrThrow(resultObject, "written", 𝔽(written)).
  24. Return resultObject.

5 Uint8Array.fromHex ( string )

  1. If string is not a String, throw a TypeError exception.
  2. Let result be FromHex(string).
  3. If result.[[Error]] is not none, then
    1. Throw result.[[Error]].
  4. Let resultLength be the length of result.[[Bytes]].
  5. Let ta be ? AllocateTypedArray("Uint8Array", %Uint8Array%, "%Uint8Array.prototype%", resultLength).
  6. Set the value at each index of ta.[[ViewedArrayBuffer]].[[ArrayBufferData]] to the value at the corresponding index of result.[[Bytes]].
  7. 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. If result.[[Error]] is not none, then
    1. Throw result.[[Error]].
  14. Let resultObject be OrdinaryObjectCreate(%Object.prototype%).
  15. Perform ! CreateDataPropertyOrThrow(resultObject, "read", 𝔽(result.[[Read]])).
  16. Perform ! CreateDataPropertyOrThrow(resultObject, "written", 𝔽(written)).
  17. 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 a Record with fields [[Read]] (an integral Number), [[Bytes]] (a List of byte values), and [[Error]] (either none 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]]: Β« Β», [[Error]]: none }.
  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, [[Error]]: none }.
        2. Else if lastChunkHandling is "loose", then
          1. If chunkLength is 1, then
            1. Let error be a new SyntaxError exception.
            2. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
          2. Set bytes to the list-concatenation of bytes and ! DecodeBase64Chunk(chunk, false).
        3. Else,
          1. Assert: lastChunkHandling is "strict".
          2. Let error be a new SyntaxError exception.
          3. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
      2. Return the Record { [[Read]]: length, [[Bytes]]: bytes, [[Error]]: none }.
    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. Let error be a new SyntaxError exception.
        2. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
      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, [[Error]]: none }.
          2. Let error be a new SyntaxError exception.
          3. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
        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. Let error be a new SyntaxError exception.
        2. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
      5. If lastChunkHandling is "strict", let throwOnExtraBits be true.
      6. Else, let throwOnExtraBits be false.
      7. Let decodeResult be Completion(DecodeBase64Chunk(chunk, throwOnExtraBits)).
      8. If decodeResult is an abrupt completion, then
        1. Let error be decodeResult.[[Value]].
        2. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
      9. Set bytes to the list-concatenation of bytes and ! decodeResult.
      10. Return the Record { [[Read]]: length, [[Bytes]]: bytes, [[Error]]: none }.
    6. If alphabet is "base64url", then
      1. If char is either "+" or "/", then
        1. Let error be a new SyntaxError exception.
        2. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
      2. Else if char is "-", then
        1. Set char to "+".
      3. Else if char is "_", then
        1. Set char to "/".
    7. If the sole code unit of char is not an element of the standard base64 alphabet, then
      1. Let error be a new SyntaxError exception.
      2. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
    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, [[Error]]: none }.
    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, [[Error]]: none }.

10.4 FromHex ( string [ , maxLength ] )

The abstract operation FromHex takes argument string (a string) and optional argument maxLength (a non-negative integer) and returns a Record with fields [[Read]] (an integral Number), [[Bytes]] (a List of byte values), and [[Error]] (either none 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. Let bytes be Β« Β».
  4. Let read be 0.
  5. If length modulo 2 is not 0, then
    1. Let error be a new SyntaxError exception.
    2. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
  6. Repeat, while read < length and the length of bytes < maxLength,
    1. Let hexits be the substring of string from read to read + 2.
    2. If hexits contains any code units which are not in "0123456789abcdefABCDEF", then
      1. Let error be a new SyntaxError exception.
      2. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
    3. Set read to read + 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]]: read, [[Bytes]]: bytes, [[Error]]: none }.

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>