Skip to content

Commit aefa951

Browse files
Encode strings as UTF-8 before/after converting to base64 (#362)
* Add test that catches error * Even simpler test * Steal goog.crypt UTF8 encoding * [AUTOMATED]: Prettier Code Styling * More test cases
1 parent a6b6689 commit aefa951

File tree

2 files changed

+78
-22
lines changed

2 files changed

+78
-22
lines changed

packages/util/src/crypt.ts

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,34 @@
1515
*/
1616

1717
const stringToByteArray = function(str) {
18-
var output = [],
18+
// TODO(user): Use native implementations if/when available
19+
var out = [],
1920
p = 0;
2021
for (var i = 0; i < str.length; i++) {
2122
var c = str.charCodeAt(i);
22-
while (c > 255) {
23-
output[p++] = c & 255;
24-
c >>= 8;
23+
if (c < 128) {
24+
out[p++] = c;
25+
} else if (c < 2048) {
26+
out[p++] = (c >> 6) | 192;
27+
out[p++] = (c & 63) | 128;
28+
} else if (
29+
(c & 0xfc00) == 0xd800 &&
30+
i + 1 < str.length &&
31+
(str.charCodeAt(i + 1) & 0xfc00) == 0xdc00
32+
) {
33+
// Surrogate Pair
34+
c = 0x10000 + ((c & 0x03ff) << 10) + (str.charCodeAt(++i) & 0x03ff);
35+
out[p++] = (c >> 18) | 240;
36+
out[p++] = ((c >> 12) & 63) | 128;
37+
out[p++] = ((c >> 6) & 63) | 128;
38+
out[p++] = (c & 63) | 128;
39+
} else {
40+
out[p++] = (c >> 12) | 224;
41+
out[p++] = ((c >> 6) & 63) | 128;
42+
out[p++] = (c & 63) | 128;
2543
}
26-
output[p++] = c;
2744
}
28-
return output;
45+
return out;
2946
};
3047

3148
/**
@@ -35,23 +52,36 @@ const stringToByteArray = function(str) {
3552
* @return {string} Stringification of the array.
3653
*/
3754
const byteArrayToString = function(bytes) {
38-
var CHUNK_SIZE = 8192;
39-
40-
// Special-case the simple case for speed's sake.
41-
if (bytes.length < CHUNK_SIZE) {
42-
return String.fromCharCode.apply(null, bytes);
43-
}
44-
45-
// The remaining logic splits conversion by chunks since
46-
// Function#apply() has a maximum parameter count.
47-
// See discussion: http://goo.gl/LrWmZ9
48-
49-
var str = '';
50-
for (var i = 0; i < bytes.length; i += CHUNK_SIZE) {
51-
var chunk = bytes.slice(i, i + CHUNK_SIZE);
52-
str += String.fromCharCode.apply(null, chunk);
55+
// TODO(user): Use native implementations if/when available
56+
var out = [],
57+
pos = 0,
58+
c = 0;
59+
while (pos < bytes.length) {
60+
var c1 = bytes[pos++];
61+
if (c1 < 128) {
62+
out[c++] = String.fromCharCode(c1);
63+
} else if (c1 > 191 && c1 < 224) {
64+
var c2 = bytes[pos++];
65+
out[c++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63));
66+
} else if (c1 > 239 && c1 < 365) {
67+
// Surrogate Pair
68+
var c2 = bytes[pos++];
69+
var c3 = bytes[pos++];
70+
var c4 = bytes[pos++];
71+
var u =
72+
(((c1 & 7) << 18) | ((c2 & 63) << 12) | ((c3 & 63) << 6) | (c4 & 63)) -
73+
0x10000;
74+
out[c++] = String.fromCharCode(0xd800 + (u >> 10));
75+
out[c++] = String.fromCharCode(0xdc00 + (u & 1023));
76+
} else {
77+
var c2 = bytes[pos++];
78+
var c3 = bytes[pos++];
79+
out[c++] = String.fromCharCode(
80+
((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)
81+
);
82+
}
5383
}
54-
return str;
84+
return out.join('');
5585
};
5686

5787
// Static lookup maps, lazily populated by init_()

packages/util/test/base64.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Copyright 2017 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import { assert } from 'chai';
17+
import { base64Encode, base64Decode } from '../src/crypt';
18+
19+
describe('base64', () => {
20+
it('bijective', () => {
21+
let cases = [ '$', '¢', '€', '𐍈' ]; // 1, 2, 3, and 4 byte characters
22+
cases.forEach(str => {
23+
assert.strictEqual(base64Decode(base64Encode(str)), str);
24+
});
25+
});
26+
});

0 commit comments

Comments
 (0)