| OLD | NEW |
| (Empty) | |
| 1 <!doctype html> |
| 2 <!-- Originally developed by Aryeh Gregor, funded by Google. Copyright belongs |
| 3 to Google. --> |
| 4 <title>atob()/btoa() tests</title> |
| 5 <meta charset=utf-8> |
| 6 <div id=log></div> |
| 7 <script src=../../../../../resources/testharness.js></script> |
| 8 <script src=../../../../../resources/testharnessreport.js></script> |
| 9 <script> |
| 10 /** |
| 11 * btoa() as defined by the HTML5 spec, which mostly just references RFC4648. |
| 12 */ |
| 13 function mybtoa(s) { |
| 14 // String conversion as required by WebIDL. |
| 15 s = String(s); |
| 16 |
| 17 // "The btoa() method must throw an INVALID_CHARACTER_ERR exception if the |
| 18 // method's first argument contains any character whose code point is |
| 19 // greater than U+00FF." |
| 20 for (var i = 0; i < s.length; i++) { |
| 21 if (s.charCodeAt(i) > 255) { |
| 22 return "INVALID_CHARACTER_ERR"; |
| 23 } |
| 24 } |
| 25 |
| 26 var out = ""; |
| 27 for (var i = 0; i < s.length; i += 3) { |
| 28 var groupsOfSix = [undefined, undefined, undefined, undefined]; |
| 29 groupsOfSix[0] = s.charCodeAt(i) >> 2; |
| 30 groupsOfSix[1] = (s.charCodeAt(i) & 0x03) << 4; |
| 31 if (s.length > i + 1) { |
| 32 groupsOfSix[1] |= s.charCodeAt(i + 1) >> 4; |
| 33 groupsOfSix[2] = (s.charCodeAt(i + 1) & 0x0f) << 2; |
| 34 } |
| 35 if (s.length > i + 2) { |
| 36 groupsOfSix[2] |= s.charCodeAt(i + 2) >> 6; |
| 37 groupsOfSix[3] = s.charCodeAt(i + 2) & 0x3f; |
| 38 } |
| 39 for (var j = 0; j < groupsOfSix.length; j++) { |
| 40 if (typeof groupsOfSix[j] == "undefined") { |
| 41 out += "="; |
| 42 } else { |
| 43 out += btoaLookup(groupsOfSix[j]); |
| 44 } |
| 45 } |
| 46 } |
| 47 return out; |
| 48 } |
| 49 |
| 50 /** |
| 51 * Lookup table for mybtoa(), which converts a six-bit number into the |
| 52 * corresponding ASCII character. |
| 53 */ |
| 54 function btoaLookup(idx) { |
| 55 if (idx < 26) { |
| 56 return String.fromCharCode(idx + 'A'.charCodeAt(0)); |
| 57 } |
| 58 if (idx < 52) { |
| 59 return String.fromCharCode(idx - 26 + 'a'.charCodeAt(0)); |
| 60 } |
| 61 if (idx < 62) { |
| 62 return String.fromCharCode(idx - 52 + '0'.charCodeAt(0)); |
| 63 } |
| 64 if (idx == 62) { |
| 65 return '+'; |
| 66 } |
| 67 if (idx == 63) { |
| 68 return '/'; |
| 69 } |
| 70 // Throw INVALID_CHARACTER_ERR exception here -- won't be hit in the tests. |
| 71 } |
| 72 |
| 73 /** |
| 74 * Implementation of atob() according to the HTML spec, except that instead of |
| 75 * throwing INVALID_CHARACTER_ERR we return null. |
| 76 */ |
| 77 function myatob(input) { |
| 78 // WebIDL requires DOMStrings to just be converted using ECMAScript |
| 79 // ToString, which in our case amounts to calling String(). |
| 80 input = String(input); |
| 81 |
| 82 // "Remove all space characters from input." |
| 83 input = input.replace(/[ \t\n\f\r]/g, ""); |
| 84 |
| 85 // "If the length of input divides by 4 leaving no remainder, then: if |
| 86 // input ends with one or two U+003D EQUALS SIGN (=) characters, remove |
| 87 // them from input." |
| 88 if (input.length % 4 == 0 && /==?$/.test(input)) { |
| 89 input = input.replace(/==?$/, ""); |
| 90 } |
| 91 |
| 92 // "If the length of input divides by 4 leaving a remainder of 1, throw an |
| 93 // INVALID_CHARACTER_ERR exception and abort these steps." |
| 94 // |
| 95 // "If input contains a character that is not in the following list of |
| 96 // characters and character ranges, throw an INVALID_CHARACTER_ERR |
| 97 // exception and abort these steps: |
| 98 // |
| 99 // U+002B PLUS SIGN (+) |
| 100 // U+002F SOLIDUS (/) |
| 101 // U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9) |
| 102 // U+0041 LATIN CAPITAL LETTER A to U+005A LATIN CAPITAL LETTER Z |
| 103 // U+0061 LATIN SMALL LETTER A to U+007A LATIN SMALL LETTER Z" |
| 104 if (input.length % 4 == 1 |
| 105 || !/^[+/0-9A-Za-z]*$/.test(input)) { |
| 106 return null; |
| 107 } |
| 108 |
| 109 // "Let output be a string, initially empty." |
| 110 var output = ""; |
| 111 |
| 112 // "Let buffer be a buffer that can have bits appended to it, initially |
| 113 // empty." |
| 114 // |
| 115 // We append bits via left-shift and or. accumulatedBits is used to track |
| 116 // when we've gotten to 24 bits. |
| 117 var buffer = 0; |
| 118 var accumulatedBits = 0; |
| 119 |
| 120 // "While position does not point past the end of input, run these |
| 121 // substeps:" |
| 122 for (var i = 0; i < input.length; i++) { |
| 123 // "Find the character pointed to by position in the first column of |
| 124 // the following table. Let n be the number given in the second cell of |
| 125 // the same row." |
| 126 // |
| 127 // "Append to buffer the six bits corresponding to number, most |
| 128 // significant bit first." |
| 129 // |
| 130 // atobLookup() implements the table from the spec. |
| 131 buffer <<= 6; |
| 132 buffer |= atobLookup(input[i]); |
| 133 |
| 134 // "If buffer has accumulated 24 bits, interpret them as three 8-bit |
| 135 // big-endian numbers. Append the three characters with code points |
| 136 // equal to those numbers to output, in the same order, and then empty |
| 137 // buffer." |
| 138 accumulatedBits += 6; |
| 139 if (accumulatedBits == 24) { |
| 140 output += String.fromCharCode((buffer & 0xff0000) >> 16); |
| 141 output += String.fromCharCode((buffer & 0xff00) >> 8); |
| 142 output += String.fromCharCode(buffer & 0xff); |
| 143 buffer = accumulatedBits = 0; |
| 144 } |
| 145 |
| 146 // "Advance position by one character." |
| 147 } |
| 148 |
| 149 // "If buffer is not empty, it contains either 12 or 18 bits. If it |
| 150 // contains 12 bits, discard the last four and interpret the remaining |
| 151 // eight as an 8-bit big-endian number. If it contains 18 bits, discard the |
| 152 // last two and interpret the remaining 16 as two 8-bit big-endian numbers. |
| 153 // Append the one or two characters with code points equal to those one or |
| 154 // two numbers to output, in the same order." |
| 155 if (accumulatedBits == 12) { |
| 156 buffer >>= 4; |
| 157 output += String.fromCharCode(buffer); |
| 158 } else if (accumulatedBits == 18) { |
| 159 buffer >>= 2; |
| 160 output += String.fromCharCode((buffer & 0xff00) >> 8); |
| 161 output += String.fromCharCode(buffer & 0xff); |
| 162 } |
| 163 |
| 164 // "Return output." |
| 165 return output; |
| 166 } |
| 167 |
| 168 /** |
| 169 * A lookup table for atob(), which converts an ASCII character to the |
| 170 * corresponding six-bit number. |
| 171 */ |
| 172 function atobLookup(chr) { |
| 173 if (/[A-Z]/.test(chr)) { |
| 174 return chr.charCodeAt(0) - "A".charCodeAt(0); |
| 175 } |
| 176 if (/[a-z]/.test(chr)) { |
| 177 return chr.charCodeAt(0) - "a".charCodeAt(0) + 26; |
| 178 } |
| 179 if (/[0-9]/.test(chr)) { |
| 180 return chr.charCodeAt(0) - "0".charCodeAt(0) + 52; |
| 181 } |
| 182 if (chr == "+") { |
| 183 return 62; |
| 184 } |
| 185 if (chr == "/") { |
| 186 return 63; |
| 187 } |
| 188 // Throw exception; should not be hit in tests |
| 189 } |
| 190 |
| 191 function btoaException(input) { |
| 192 input = String(input); |
| 193 for (var i = 0; i < input.length; i++) { |
| 194 if (input.charCodeAt(i) > 255) { |
| 195 return true; |
| 196 } |
| 197 } |
| 198 return false; |
| 199 } |
| 200 |
| 201 function testBtoa(input) { |
| 202 // "The btoa() method must throw an INVALID_CHARACTER_ERR exception if the |
| 203 // method's first argument contains any character whose code point is |
| 204 // greater than U+00FF." |
| 205 var normalizedInput = String(input); |
| 206 for (var i = 0; i < normalizedInput.length; i++) { |
| 207 if (normalizedInput.charCodeAt(i) > 255) { |
| 208 assert_throws("InvalidCharacterError", function() { btoa(input); }, |
| 209 "Code unit " + i + " has value " + normalizedInput.charCodeAt(i)
+ ", which is greater than 255"); |
| 210 return; |
| 211 } |
| 212 } |
| 213 assert_equals(btoa(input), mybtoa(input)); |
| 214 assert_equals(atob(btoa(input)), String(input), "atob(btoa(input)) must be t
he same as String(input)"); |
| 215 } |
| 216 |
| 217 var tests = ["עברית", "", "ab", "abc", "abcd", "abcde", |
| 218 // This one is thrown in because IE9 seems to fail atob(btoa()) on it. Or |
| 219 // possibly to fail btoa(). I actually can't tell what's happening here, |
| 220 // but it doesn't hurt. |
| 221 "\xff\xff\xc0", |
| 222 // Is your DOM implementation binary-safe? |
| 223 "\0a", "a\0b", |
| 224 // WebIDL tests. |
| 225 undefined, null, 7, 12, 1.5, true, false, NaN, +Infinity, -Infinity, 0, -0, |
| 226 {toString: function() { return "foo" }}, |
| 227 ]; |
| 228 for (var i = 0; i < 258; i++) { |
| 229 tests.push(String.fromCharCode(i)); |
| 230 } |
| 231 tests.push(String.fromCharCode(10000)); |
| 232 tests.push(String.fromCharCode(65534)); |
| 233 tests.push(String.fromCharCode(65535)); |
| 234 |
| 235 // This is supposed to be U+10000. |
| 236 tests.push(String.fromCharCode(0xd800, 0xdc00)); |
| 237 tests = tests.map( |
| 238 function(elem) { |
| 239 var expected = mybtoa(elem); |
| 240 if (expected === "INVALID_CHARACTER_ERR") { |
| 241 return ["btoa(" + format_value(elem) + ") must raise INVALID_CHARAC
TER_ERR", elem]; |
| 242 } |
| 243 return ["btoa(" + format_value(elem) + ") == " + format_value(mybtoa(ele
m)), elem]; |
| 244 } |
| 245 ); |
| 246 |
| 247 var everything = ""; |
| 248 for (var i = 0; i < 256; i++) { |
| 249 everything += String.fromCharCode(i); |
| 250 } |
| 251 tests.push(["btoa(first 256 code points concatenated)", everything]); |
| 252 |
| 253 generate_tests(testBtoa, tests); |
| 254 |
| 255 function testAtob(input) { |
| 256 var expected = myatob(input); |
| 257 if (expected === null) { |
| 258 assert_throws("InvalidCharacterError", function() { atob(input) }); |
| 259 return; |
| 260 } |
| 261 |
| 262 assert_equals(atob(input), expected); |
| 263 } |
| 264 |
| 265 var tests = ["", "abcd", " abcd", "abcd ", " abcd===", "abcd=== ", |
| 266 "abcd ===", "a", "ab", "abc", "abcde", String.fromCharCode(0xd800, 0xdc00), |
| 267 "=", "==", "===", "====", "=====", |
| 268 "a=", "a==", "a===", "a====", "a=====", |
| 269 "ab=", "ab==", "ab===", "ab====", "ab=====", |
| 270 "abc=", "abc==", "abc===", "abc====", "abc=====", |
| 271 "abcd=", "abcd==", "abcd===", "abcd====", "abcd=====", |
| 272 "abcde=", "abcde==", "abcde===", "abcde====", "abcde=====", |
| 273 "=a", "=a=", "a=b", "a=b=", "ab=c", "ab=c=", "abc=d", "abc=d=", |
| 274 // With whitespace |
| 275 "ab\tcd", "ab\ncd", "ab\fcd", "ab\rcd", "ab cd", "ab\u00a0cd", |
| 276 "ab\t\n\f\r cd", " \t\n\f\r ab\t\n\f\r cd\t\n\f\r ", |
| 277 "ab\t\n\f\r =\t\n\f\r =\t\n\f\r ", |
| 278 // Test if any bits are set at the end. These should all be fine, since |
| 279 // they end with A, which becomes 0: |
| 280 "A", "/A", "//A", "///A", "////A", |
| 281 // These are all bad, since they end in / (= 63, all bits set) but their |
| 282 // length isn't a multiple of four characters, so they can't be output by |
| 283 // btoa(). Thus one might expect some UAs to throw exceptions or otherwise |
| 284 // object, since they could never be output by btoa(), so they're good to |
| 285 // test. |
| 286 "/", "A/", "AA/", "AAAA/", |
| 287 // But this one is possible: |
| 288 "AAA/", |
| 289 // Binary-safety tests |
| 290 "\0nonsense", "abcd\0nonsense", |
| 291 // WebIDL tests |
| 292 undefined, null, 7, 12, 1.5, true, false, NaN, +Infinity, -Infinity, 0, -0, |
| 293 {toString: function() { return "foo" }}, |
| 294 {toString: function() { return "abcd" }}, |
| 295 ]; |
| 296 tests = tests.map( |
| 297 function(elem) { |
| 298 if (myatob(elem) === null) { |
| 299 return ["atob(" + format_value(elem) + ") must raise InvalidCharacte
rError", elem]; |
| 300 } |
| 301 return ["atob(" + format_value(elem) + ") == " + format_value(myatob(ele
m)), elem]; |
| 302 } |
| 303 ); |
| 304 |
| 305 generate_tests(testAtob, tests); |
| 306 </script> |
| OLD | NEW |