• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * btoa() as defined by the HTML5 spec, which mostly just references RFC4648.
3 */
4function mybtoa(s) {
5    // String conversion as required by WebIDL.
6    s = String(s);
7
8    // "The btoa() method must throw an INVALID_CHARACTER_ERR exception if the
9    // method's first argument contains any character whose code point is
10    // greater than U+00FF."
11    for (var i = 0; i < s.length; i++) {
12        if (s.charCodeAt(i) > 255) {
13            return "INVALID_CHARACTER_ERR";
14        }
15    }
16
17    var out = "";
18    for (var i = 0; i < s.length; i += 3) {
19        var groupsOfSix = [undefined, undefined, undefined, undefined];
20        groupsOfSix[0] = s.charCodeAt(i) >> 2;
21        groupsOfSix[1] = (s.charCodeAt(i) & 0x03) << 4;
22        if (s.length > i + 1) {
23            groupsOfSix[1] |= s.charCodeAt(i + 1) >> 4;
24            groupsOfSix[2] = (s.charCodeAt(i + 1) & 0x0f) << 2;
25        }
26        if (s.length > i + 2) {
27            groupsOfSix[2] |= s.charCodeAt(i + 2) >> 6;
28            groupsOfSix[3] = s.charCodeAt(i + 2) & 0x3f;
29        }
30        for (var j = 0; j < groupsOfSix.length; j++) {
31            if (typeof groupsOfSix[j] == "undefined") {
32                out += "=";
33            } else {
34                out += btoaLookup(groupsOfSix[j]);
35            }
36        }
37    }
38    return out;
39}
40
41/**
42 * Lookup table for mybtoa(), which converts a six-bit number into the
43 * corresponding ASCII character.
44 */
45function btoaLookup(idx) {
46    if (idx < 26) {
47        return String.fromCharCode(idx + 'A'.charCodeAt(0));
48    }
49    if (idx < 52) {
50        return String.fromCharCode(idx - 26 + 'a'.charCodeAt(0));
51    }
52    if (idx < 62) {
53        return String.fromCharCode(idx - 52 + '0'.charCodeAt(0));
54    }
55    if (idx == 62) {
56        return '+';
57    }
58    if (idx == 63) {
59        return '/';
60    }
61    // Throw INVALID_CHARACTER_ERR exception here -- won't be hit in the tests.
62}
63
64function btoaException(input) {
65    input = String(input);
66    for (var i = 0; i < input.length; i++) {
67        if (input.charCodeAt(i) > 255) {
68            return true;
69        }
70    }
71    return false;
72}
73
74function testBtoa(input) {
75    // "The btoa() method must throw an INVALID_CHARACTER_ERR exception if the
76    // method's first argument contains any character whose code point is
77    // greater than U+00FF."
78    var normalizedInput = String(input);
79    for (var i = 0; i < normalizedInput.length; i++) {
80        if (normalizedInput.charCodeAt(i) > 255) {
81            assert_throws_dom("InvalidCharacterError", function() { btoa(input); },
82                "Code unit " + i + " has value " + normalizedInput.charCodeAt(i) + ", which is greater than 255");
83            return;
84        }
85    }
86    assert_equals(btoa(input), mybtoa(input));
87    assert_equals(atob(btoa(input)), String(input), "atob(btoa(input)) must be the same as String(input)");
88}
89
90var tests = ["עברית", "", "ab", "abc", "abcd", "abcde",
91    // This one is thrown in because IE9 seems to fail atob(btoa()) on it.  Or
92    // possibly to fail btoa().  I actually can't tell what's happening here,
93    // but it doesn't hurt.
94    "\xff\xff\xc0",
95    // Is your DOM implementation binary-safe?
96    "\0a", "a\0b",
97    // WebIDL tests.
98    undefined, null, 7, 12, 1.5, true, false, NaN, +Infinity, -Infinity, 0, -0,
99    {toString: function() { return "foo" }},
100];
101for (var i = 0; i < 258; i++) {
102    tests.push(String.fromCharCode(i));
103}
104tests.push(String.fromCharCode(10000));
105tests.push(String.fromCharCode(65534));
106tests.push(String.fromCharCode(65535));
107
108// This is supposed to be U+10000.
109tests.push(String.fromCharCode(0xd800, 0xdc00));
110tests = tests.map(
111    function(elem) {
112        var expected = mybtoa(elem);
113        if (expected === "INVALID_CHARACTER_ERR") {
114            return ["btoa("  + format_value(elem) + ") must raise INVALID_CHARACTER_ERR", elem];
115        }
116        return ["btoa(" + format_value(elem) + ") == " + format_value(mybtoa(elem)), elem];
117    }
118);
119
120var everything = "";
121for (var i = 0; i < 256; i++) {
122    everything += String.fromCharCode(i);
123}
124tests.push(["btoa(first 256 code points concatenated)", everything]);
125
126generate_tests(testBtoa, tests);
127
128promise_test(() => fetch("../../../fetch/data-urls/resources/base64.json").then(res => res.json()).then(runAtobTests), "atob() setup.");
129
130const idlTests = [
131  [undefined, null],
132  [null, [158, 233, 101]],
133  [7, null],
134  [12, [215]],
135  [1.5, null],
136  [true, [182, 187]],
137  [false, null],
138  [NaN, [53, 163]],
139  [+Infinity, [34, 119, 226, 158, 43, 114]],
140  [-Infinity, null],
141  [0, null],
142  [-0, null],
143  [{toString: function() { return "foo" }}, [126, 138]],
144  [{toString: function() { return "abcd" }}, [105, 183, 29]]
145];
146
147function runAtobTests(tests) {
148  const allTests = tests.concat(idlTests);
149  for(let i = 0; i < allTests.length; i++) {
150    const input = allTests[i][0],
151          output = allTests[i][1];
152    test(() => {
153      if(output === null) {
154        assert_throws_dom("InvalidCharacterError", () => globalThis.atob(input));
155      } else {
156        const result = globalThis.atob(input);
157        for(let ii = 0; ii < output.length; ii++) {
158          assert_equals(result.charCodeAt(ii), output[ii]);
159        }
160      }
161    }, "atob(" + format_value(input) + ")");
162  }
163}
164