Skip to content

Commit 3a25a29

Browse files
committed
Make the package compatible with browser targets natively
Remove the need for a polyfill by providing an implementation that uses web APIs.
1 parent c372d0b commit 3a25a29

File tree

5 files changed

+409
-339
lines changed

5 files changed

+409
-339
lines changed

lib/browser-buffer-ops.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const replacementCharacterBuffer = new Uint8Array([0xef, 0xbf, 0xbd]);
2+
3+
const base64js = require('base64-js');
4+
5+
module.exports = {
6+
fromBase64: (b64str) => {
7+
try {
8+
return base64js.toByteArray(b64str);
9+
} catch (e) {
10+
return new Uint8Array();
11+
}
12+
},
13+
14+
toUtf8: TextEncoder.prototype.encode.bind(new TextEncoder()),
15+
fromUtf8: TextDecoder.prototype.decode.bind(new TextDecoder()),
16+
fromAscii: TextDecoder.prototype.decode.bind(new TextDecoder('ascii')),
17+
18+
allocByteBuffer: (length) => {
19+
return new Uint8Array(length);
20+
},
21+
22+
includesReplacementCharacter: (haystack) => {
23+
const needle = replacementCharacterBuffer;
24+
if (haystack.length < needle.length) {
25+
return false;
26+
}
27+
let fromIndex = 0;
28+
while (fromIndex != haystack.length - 3) {
29+
const foundFirst = haystack[fromIndex] === needle[0];
30+
const foundSecond = haystack[fromIndex + 1] === needle[1];
31+
const foundThird = haystack[fromIndex + 2] === needle[2];
32+
33+
if (foundFirst && foundSecond && foundThird) {
34+
return true;
35+
} else {
36+
fromIndex += 1;
37+
}
38+
}
39+
40+
return false;
41+
},
42+
};

lib/node-buffer-ops.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const replacementCharacterBuffer = new Uint8Array([0xef, 0xbf, 0xbd]);
2+
3+
module.exports = {
4+
fromBase64: (b64str) => {
5+
return Buffer.from(b64str, 'base64');
6+
},
7+
8+
toUtf8: (str) => {
9+
return Buffer.from(str, 'utf-8');
10+
},
11+
fromUtf8: String,
12+
fromAscii: (buffer) => {
13+
return buffer.toString('ascii');
14+
},
15+
16+
allocByteBuffer: Buffer.alloc,
17+
18+
includesReplacementCharacter: (haystack) => {
19+
return haystack.includes(replacementCharacterBuffer);
20+
},
21+
};

lib/rfc2047.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const isUtf8RegExp = /^utf-?8$/i;
44
const isLatin1RegExp = /^(?:iso-8859-1|latin1|us-ascii)$/i;
5+
const bufferOps = require('./node-buffer-ops');
56
const rfc2047 = (module.exports = {});
67

78
let iconv;
@@ -22,13 +23,13 @@ function stringify(obj) {
2223
return obj;
2324
} else if (obj === null || typeof obj === 'undefined') {
2425
return '';
26+
} else if (obj instanceof Uint8Array) {
27+
return bufferOps.fromUtf8(obj);
2528
} else {
2629
return String(obj);
2730
}
2831
}
2932

30-
const replacementCharacterBuffer = Buffer.from('�');
31-
3233
function decodeBuffer(encodedText, encoding) {
3334
if (encoding === 'q') {
3435
encodedText = encodedText.replace(/_/g, ' ');
@@ -42,7 +43,7 @@ function decodeBuffer(encodedText, encoding) {
4243
numValidlyEncodedBytes += 1;
4344
}
4445
}
45-
const buffer = Buffer.alloc(
46+
const buffer = bufferOps.allocByteBuffer(
4647
encodedText.length - numValidlyEncodedBytes * 2
4748
);
4849
let j = 0;
@@ -62,7 +63,7 @@ function decodeBuffer(encodedText, encoding) {
6263
}
6364
return buffer;
6465
} else {
65-
return Buffer.from(encodedText, 'base64');
66+
return bufferOps.fromBase64(encodedText);
6667
}
6768
}
6869

@@ -95,23 +96,23 @@ function decodeEncodedWord(encodedText, encoding, charset) {
9596
converter = new iconv.Iconv('iso-8859-1', 'utf-8//TRANSLIT');
9697
}
9798
try {
98-
return converter.convert(buffer).toString('utf-8');
99+
return bufferOps.fromUtf8(converter.convert(buffer));
99100
} catch (e2) {}
100101
} else if (isUtf8RegExp.test(charset)) {
101-
const decoded = buffer.toString('utf-8');
102+
const decoded = bufferOps.fromUtf8(buffer);
102103
if (
103104
!/\ufffd/.test(decoded) ||
104-
buffer.includes(replacementCharacterBuffer)
105+
bufferOps.includesReplacementCharacter(buffer)
105106
) {
106107
return decoded;
107108
}
108109
} else if (isLatin1RegExp.test(charset)) {
109-
return buffer.toString('ascii');
110+
return bufferOps.fromAscii(buffer);
110111
} else if (iconvLite && iconvLite.encodingExists(charset)) {
111112
decoded = iconvLite.decode(buffer, charset);
112113
if (
113114
!/\ufffd/.test(decoded) ||
114-
buffer.includes(replacementCharacterBuffer)
115+
bufferOps.includesReplacementCharacter(buffer)
115116
) {
116117
return decoded;
117118
}
@@ -264,7 +265,7 @@ rfc2047.encode = (text) => {
264265
const charset = 'utf-8';
265266
// Around 25% faster than encodeURIComponent(token.replace(/ /g, "_")).replace(/%/g, "="):
266267
const encodedWordBody = bufferToQuotedPrintableString(
267-
Buffer.from(token, 'utf-8')
268+
bufferOps.toUtf8(token)
268269
);
269270
if (previousTokenWasEncodedWord) {
270271
result += ' ';

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@
5353
"iconv-lite": "0.4.5"
5454
},
5555
"browser": {
56-
"iconv": false
56+
"iconv": false,
57+
"lib/node-buffer-ops.js": "lib/browser-buffer-ops.js"
5758
},
5859
"nyc": {
5960
"include": [

0 commit comments

Comments
 (0)