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