1+ // Adapted from abab
2+ //
3+
4+ const keystr =
5+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' ;
6+
7+ /**
8+ * Implementation of atob() according to the HTML and Infra specs, except that
9+ * instead of throwing INVALID_CHARACTER_ERR we return null.
10+ */
11+ function atob ( data ) {
12+ if ( arguments . length === 0 ) {
13+ throw new TypeError ( '1 argument required, but only 0 present.' ) ;
14+ }
15+
16+ // Web IDL requires DOMStrings to just be converted using ECMAScript
17+ // ToString, which in our case amounts to using a template literal.
18+ data = `${ data } ` ;
19+ // "Remove all ASCII whitespace from data."
20+ data = data . replace ( / [ \t \n \f \r ] / g, '' ) ;
21+
22+ // "If data's length divides by 4 leaving no remainder, then: if data ends
23+ // with one or two U+003D (=) code points, then remove them from data."
24+ if ( data . length % 4 === 0 ) {
25+ data = data . replace ( / = = ? $ / , '' ) ;
26+ }
27+
28+ // "If data's length divides by 4 leaving a remainder of 1, then return
29+ // failure."
30+ //
31+ // "If data contains a code point that is not one of
32+ //
33+ // U+002B (+)
34+ // U+002F (/)
35+ // ASCII alphanumeric
36+ //
37+ // then return failure."
38+ if ( data . length % 4 === 1 || / [ ^ + / 0 - 9 A - Z a - z ] / . test ( data ) ) {
39+ throw new DOMException (
40+ 'Failed to decode base64: invalid character' ,
41+ 'InvalidCharacterError'
42+ ) ;
43+ }
44+
45+ // "Let output be an empty byte sequence."
46+ let output = '' ;
47+ // "Let buffer be an empty buffer that can have bits appended to it."
48+ //
49+ // We append bits via left-shift and or. accumulatedBits is used to track
50+ // when we've gotten to 24 bits.
51+ let buffer = 0 ;
52+ let accumulatedBits = 0 ;
53+
54+ // "Let position be a position variable for data, initially pointing at the
55+ // start of data."
56+ //
57+ // "While position does not point past the end of data:"
58+ for ( let i = 0 ; i < data . length ; i ++ ) {
59+ // "Find the code point pointed to by position in the second column of
60+ // Table 1: The Base 64 Alphabet of RFC 4648. Let n be the number given in
61+ // the first cell of the same row.
62+ //
63+ // "Append to buffer the six bits corresponding to n, most significant bit
64+ // first."
65+ //
66+ // atobLookup() implements the table from RFC 4648.
67+ buffer <<= 6 ;
68+ buffer |= atobLookup ( data [ i ] ) ;
69+ accumulatedBits += 6 ;
70+
71+ // "If buffer has accumulated 24 bits, interpret them as three 8-bit
72+ // big-endian numbers. Append three bytes with values equal to those
73+ // numbers to output, in the same order, and then empty buffer."
74+ if ( accumulatedBits === 24 ) {
75+ output += String . fromCharCode ( ( buffer & 0xff0000 ) >> 16 ) ;
76+ output += String . fromCharCode ( ( buffer & 0xff00 ) >> 8 ) ;
77+ output += String . fromCharCode ( buffer & 0xff ) ;
78+ buffer = accumulatedBits = 0 ;
79+ }
80+ // "Advance position by 1."
81+ }
82+
83+ // "If buffer is not empty, it contains either 12 or 18 bits. If it contains
84+ // 12 bits, then discard the last four and interpret the remaining eight as
85+ // an 8-bit big-endian number. If it contains 18 bits, then discard the last
86+ // two and interpret the remaining 16 as two 8-bit big-endian numbers. Append
87+ // the one or two bytes with values equal to those one or two numbers to
88+ // output, in the same order."
89+ if ( accumulatedBits === 12 ) {
90+ buffer >>= 4 ;
91+ output += String . fromCharCode ( buffer ) ;
92+ } else if ( accumulatedBits === 18 ) {
93+ buffer >>= 2 ;
94+ output += String . fromCharCode ( ( buffer & 0xff00 ) >> 8 ) ;
95+ output += String . fromCharCode ( buffer & 0xff ) ;
96+ }
97+
98+ // "Return output."
99+ return output ;
100+ }
101+
102+ /**
103+ * A lookup table for atob(), which converts an ASCII character to the
104+ * corresponding six-bit number.
105+ */
106+ function atobLookup ( chr ) {
107+ const index = keystr . indexOf ( chr ) ;
108+
109+ // Throw exception if character is not in the lookup string; should not be hit in tests
110+ return index < 0 ? undefined : index ;
111+ }
112+
113+
114+ /**
115+ * btoa() as defined by the HTML and Infra specs, which mostly just references
116+ * RFC 4648.
117+ */
118+ function btoa ( s ) {
119+ if ( arguments . length === 0 ) {
120+ throw new TypeError ( '1 argument required, but only 0 present.' ) ;
121+ }
122+
123+ let i ;
124+
125+ // String conversion as required by Web IDL.
126+ s = `${ s } ` ;
127+
128+ // "The btoa() method must throw an "InvalidCharacterError" DOMException if
129+ // data contains any character whose code point is greater than U+00FF."
130+ for ( i = 0 ; i < s . length ; i ++ ) {
131+ if ( s . charCodeAt ( i ) > 255 ) {
132+ throw new DOMException (
133+ 'The string to be encoded contains characters outside of the Latin1 range.' ,
134+ 'InvalidCharacterError'
135+ ) ;
136+ }
137+ }
138+
139+ let out = '' ;
140+
141+ for ( i = 0 ; i < s . length ; i += 3 ) {
142+ const groupsOfSix = [ undefined , undefined , undefined , undefined ] ;
143+
144+ groupsOfSix [ 0 ] = s . charCodeAt ( i ) >> 2 ;
145+ groupsOfSix [ 1 ] = ( s . charCodeAt ( i ) & 0x03 ) << 4 ;
146+
147+ if ( s . length > i + 1 ) {
148+ groupsOfSix [ 1 ] |= s . charCodeAt ( i + 1 ) >> 4 ;
149+ groupsOfSix [ 2 ] = ( s . charCodeAt ( i + 1 ) & 0x0f ) << 2 ;
150+ }
151+
152+ if ( s . length > i + 2 ) {
153+ groupsOfSix [ 2 ] |= s . charCodeAt ( i + 2 ) >> 6 ;
154+ groupsOfSix [ 3 ] = s . charCodeAt ( i + 2 ) & 0x3f ;
155+ }
156+
157+ for ( let j = 0 ; j < groupsOfSix . length ; j ++ ) {
158+ if ( typeof groupsOfSix [ j ] === 'undefined' ) {
159+ out += '=' ;
160+ } else {
161+ out += btoaLookup ( groupsOfSix [ j ] ) ;
162+ }
163+ }
164+ }
165+
166+ return out ;
167+ }
168+
169+ /**
170+ * Lookup table for btoa(), which converts a six-bit number into the
171+ * corresponding ASCII character.
172+ */
173+ function btoaLookup ( index ) {
174+ if ( index >= 0 && index < 64 ) {
175+ return keystr [ index ] ;
176+ }
177+
178+ // Throw INVALID_CHARACTER_ERR exception here -- won't be hit in the tests.
179+ return undefined ;
180+ }
181+
182+
183+ globalThis . atob = atob ;
184+ globalThis . btoa = btoa ;
0 commit comments