Optimize base64 encoding and decoding
This commit is contained in:
parent
15202d9d40
commit
2ff4fbb0e8
|
@ -319,7 +319,7 @@ function dearmor(input) {
|
||||||
});
|
});
|
||||||
const writer = stream.getWriter(writable);
|
const writer = stream.getWriter(writable);
|
||||||
try {
|
try {
|
||||||
const checksumVerifiedString = await checksumVerified;
|
const checksumVerifiedString = (await checksumVerified).replace('\r\n', '');
|
||||||
if (checksum !== checksumVerifiedString && (checksum || config.checksum_required)) {
|
if (checksum !== checksumVerifiedString && (checksum || config.checksum_required)) {
|
||||||
throw new Error("Ascii armor integrity check on message failed: '" + checksum + "' should be '" +
|
throw new Error("Ascii armor integrity check on message failed: '" + checksum + "' should be '" +
|
||||||
checksumVerifiedString + "'");
|
checksumVerifiedString + "'");
|
||||||
|
@ -362,14 +362,14 @@ function armor(messagetype, body, partindex, parttotal, customComment) {
|
||||||
result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n");
|
result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n");
|
||||||
result.push(addheader(customComment));
|
result.push(addheader(customComment));
|
||||||
result.push(base64.encode(body));
|
result.push(base64.encode(body));
|
||||||
result.push("\r\n=", getCheckSum(bodyClone), "\r\n");
|
result.push("=", getCheckSum(bodyClone));
|
||||||
result.push("-----END PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n");
|
result.push("-----END PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n");
|
||||||
break;
|
break;
|
||||||
case enums.armor.multipart_last:
|
case enums.armor.multipart_last:
|
||||||
result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "-----\r\n");
|
result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "-----\r\n");
|
||||||
result.push(addheader(customComment));
|
result.push(addheader(customComment));
|
||||||
result.push(base64.encode(body));
|
result.push(base64.encode(body));
|
||||||
result.push("\r\n=", getCheckSum(bodyClone), "\r\n");
|
result.push("=", getCheckSum(bodyClone));
|
||||||
result.push("-----END PGP MESSAGE, PART " + partindex + "-----\r\n");
|
result.push("-----END PGP MESSAGE, PART " + partindex + "-----\r\n");
|
||||||
break;
|
break;
|
||||||
case enums.armor.signed:
|
case enums.armor.signed:
|
||||||
|
@ -379,35 +379,35 @@ function armor(messagetype, body, partindex, parttotal, customComment) {
|
||||||
result.push("\r\n-----BEGIN PGP SIGNATURE-----\r\n");
|
result.push("\r\n-----BEGIN PGP SIGNATURE-----\r\n");
|
||||||
result.push(addheader(customComment));
|
result.push(addheader(customComment));
|
||||||
result.push(base64.encode(body));
|
result.push(base64.encode(body));
|
||||||
result.push("\r\n=", getCheckSum(bodyClone), "\r\n");
|
result.push("=", getCheckSum(bodyClone));
|
||||||
result.push("-----END PGP SIGNATURE-----\r\n");
|
result.push("-----END PGP SIGNATURE-----\r\n");
|
||||||
break;
|
break;
|
||||||
case enums.armor.message:
|
case enums.armor.message:
|
||||||
result.push("-----BEGIN PGP MESSAGE-----\r\n");
|
result.push("-----BEGIN PGP MESSAGE-----\r\n");
|
||||||
result.push(addheader(customComment));
|
result.push(addheader(customComment));
|
||||||
result.push(base64.encode(body));
|
result.push(base64.encode(body));
|
||||||
result.push("\r\n=", getCheckSum(bodyClone), "\r\n");
|
result.push("=", getCheckSum(bodyClone));
|
||||||
result.push("-----END PGP MESSAGE-----\r\n");
|
result.push("-----END PGP MESSAGE-----\r\n");
|
||||||
break;
|
break;
|
||||||
case enums.armor.public_key:
|
case enums.armor.public_key:
|
||||||
result.push("-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n");
|
result.push("-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n");
|
||||||
result.push(addheader(customComment));
|
result.push(addheader(customComment));
|
||||||
result.push(base64.encode(body));
|
result.push(base64.encode(body));
|
||||||
result.push("\r\n=", getCheckSum(bodyClone), "\r\n");
|
result.push("=", getCheckSum(bodyClone));
|
||||||
result.push("-----END PGP PUBLIC KEY BLOCK-----\r\n");
|
result.push("-----END PGP PUBLIC KEY BLOCK-----\r\n");
|
||||||
break;
|
break;
|
||||||
case enums.armor.private_key:
|
case enums.armor.private_key:
|
||||||
result.push("-----BEGIN PGP PRIVATE KEY BLOCK-----\r\n");
|
result.push("-----BEGIN PGP PRIVATE KEY BLOCK-----\r\n");
|
||||||
result.push(addheader(customComment));
|
result.push(addheader(customComment));
|
||||||
result.push(base64.encode(body));
|
result.push(base64.encode(body));
|
||||||
result.push("\r\n=", getCheckSum(bodyClone), "\r\n");
|
result.push("=", getCheckSum(bodyClone));
|
||||||
result.push("-----END PGP PRIVATE KEY BLOCK-----\r\n");
|
result.push("-----END PGP PRIVATE KEY BLOCK-----\r\n");
|
||||||
break;
|
break;
|
||||||
case enums.armor.signature:
|
case enums.armor.signature:
|
||||||
result.push("-----BEGIN PGP SIGNATURE-----\r\n");
|
result.push("-----BEGIN PGP SIGNATURE-----\r\n");
|
||||||
result.push(addheader(customComment));
|
result.push(addheader(customComment));
|
||||||
result.push(base64.encode(body));
|
result.push(base64.encode(body));
|
||||||
result.push("\r\n=", getCheckSum(bodyClone), "\r\n");
|
result.push("=", getCheckSum(bodyClone));
|
||||||
result.push("-----END PGP SIGNATURE-----\r\n");
|
result.push("-----END PGP SIGNATURE-----\r\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,121 +13,84 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @requires web-stream-tools
|
* @requires web-stream-tools
|
||||||
|
* @requires util
|
||||||
* @module encoding/base64
|
* @module encoding/base64
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import stream from 'web-stream-tools';
|
import stream from 'web-stream-tools';
|
||||||
|
import util from '../util';
|
||||||
|
|
||||||
const b64s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; // Standard radix-64
|
const Buffer = util.getNodeBuffer();
|
||||||
const b64u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; // URL-safe radix-64
|
|
||||||
|
|
||||||
const b64toByte = [];
|
let encodeChunk;
|
||||||
for (let i = 0; i < b64s.length; i++) {
|
let decodeChunk;
|
||||||
b64toByte[b64s.charCodeAt(i)] = i;
|
if (Buffer) {
|
||||||
|
encodeChunk = buf => Buffer.from(buf).toString('base64');
|
||||||
|
decodeChunk = str => {
|
||||||
|
const b = Buffer.from(str, 'base64');
|
||||||
|
return new Uint8Array(b.buffer, b.byteOffset, b.byteLength);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
encodeChunk = buf => btoa(util.Uint8Array_to_str(buf));
|
||||||
|
decodeChunk = str => util.str_to_Uint8Array(atob(str));
|
||||||
}
|
}
|
||||||
b64toByte[b64u.charCodeAt(62)] = 62;
|
|
||||||
b64toByte[b64u.charCodeAt(63)] = 63;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert binary array to radix-64
|
* Convert binary array to radix-64
|
||||||
* @param {Uint8Array | ReadableStream<Uint8Array>} t Uint8Array to convert
|
* @param {Uint8Array | ReadableStream<Uint8Array>} data Uint8Array to convert
|
||||||
* @param {bool} u if true, output is URL-safe
|
|
||||||
* @returns {String | ReadableStream<String>} radix-64 version of input string
|
* @returns {String | ReadableStream<String>} radix-64 version of input string
|
||||||
* @static
|
* @static
|
||||||
*/
|
*/
|
||||||
function s2r(t, u = false) {
|
function encode(data) {
|
||||||
// TODO check btoa alternative
|
let buf = new Uint8Array();
|
||||||
const b64 = u ? b64u : b64s;
|
return stream.transform(data, value => {
|
||||||
let a;
|
buf = util.concatUint8Array([buf, value]);
|
||||||
let c;
|
|
||||||
|
|
||||||
let l = 0;
|
|
||||||
let s = 0;
|
|
||||||
|
|
||||||
return stream.transform(t, value => {
|
|
||||||
const r = [];
|
const r = [];
|
||||||
const tl = value.length;
|
const bytesPerLine = 45; // 60 chars per line * (3 bytes / 4 chars of base64).
|
||||||
for (let n = 0; n < tl; n++) {
|
const lines = Math.floor(buf.length / bytesPerLine);
|
||||||
if (l && (l % 60) === 0 && !u) {
|
const bytes = lines * bytesPerLine;
|
||||||
r.push("\r\n");
|
const encoded = encodeChunk(buf.subarray(0, bytes));
|
||||||
}
|
for (let i = 0; i < lines; i++) {
|
||||||
c = value[n];
|
r.push(encoded.substr(i * 60, 60));
|
||||||
if (s === 0) {
|
r.push('\r\n');
|
||||||
r.push(b64.charAt((c >> 2) & 63));
|
|
||||||
a = (c & 3) << 4;
|
|
||||||
} else if (s === 1) {
|
|
||||||
r.push(b64.charAt(a | ((c >> 4) & 15)));
|
|
||||||
a = (c & 15) << 2;
|
|
||||||
} else if (s === 2) {
|
|
||||||
r.push(b64.charAt(a | ((c >> 6) & 3)));
|
|
||||||
l += 1;
|
|
||||||
if ((l % 60) === 0 && !u) {
|
|
||||||
r.push("\r\n");
|
|
||||||
}
|
|
||||||
r.push(b64.charAt(c & 63));
|
|
||||||
}
|
|
||||||
l += 1;
|
|
||||||
s += 1;
|
|
||||||
if (s === 3) {
|
|
||||||
s = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
buf = buf.subarray(bytes);
|
||||||
return r.join('');
|
return r.join('');
|
||||||
}, () => {
|
}, () => (buf.length ? encodeChunk(buf) + '\r\n' : ''));
|
||||||
const r = [];
|
|
||||||
if (s > 0) {
|
|
||||||
r.push(b64.charAt(a));
|
|
||||||
l += 1;
|
|
||||||
if ((l % 60) === 0 && !u) {
|
|
||||||
r.push("\r\n");
|
|
||||||
}
|
|
||||||
if (!u) {
|
|
||||||
r.push('=');
|
|
||||||
l += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (s === 1 && !u) {
|
|
||||||
if ((l % 60) === 0 && !u) {
|
|
||||||
r.push("\r\n");
|
|
||||||
}
|
|
||||||
r.push('=');
|
|
||||||
}
|
|
||||||
return r.join('');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert radix-64 to binary array
|
* Convert radix-64 to binary array
|
||||||
* @param {String | ReadableStream<String>} t radix-64 string to convert
|
* @param {String | ReadableStream<String>} data radix-64 string to convert
|
||||||
* @returns {Uint8Array | ReadableStream<Uint8Array>} binary array version of input string
|
* @returns {Uint8Array | ReadableStream<Uint8Array>} binary array version of input string
|
||||||
* @static
|
* @static
|
||||||
*/
|
*/
|
||||||
function r2s(t) {
|
function decode(data) {
|
||||||
// TODO check atob alternative
|
let buf = '';
|
||||||
let c;
|
return stream.transform(data, value => {
|
||||||
|
buf += value;
|
||||||
|
|
||||||
let s = 0;
|
// Count how many whitespace characters there are in buf
|
||||||
let a = 0;
|
let spaces = 0;
|
||||||
|
const spacechars = [' ', '\t', '\r', '\n'];
|
||||||
return stream.transform(t, value => {
|
for (let i = 0; i < spacechars.length; i++) {
|
||||||
const tl = value.length;
|
const spacechar = spacechars[i];
|
||||||
const r = new Uint8Array(Math.ceil(0.75 * tl));
|
for (let pos = buf.indexOf(spacechar); pos !== -1; pos = buf.indexOf(spacechar, pos + 1)) {
|
||||||
let index = 0;
|
spaces++;
|
||||||
for (let n = 0; n < tl; n++) {
|
|
||||||
c = b64toByte[value.charCodeAt(n)];
|
|
||||||
if (c >= 0) {
|
|
||||||
if (s) {
|
|
||||||
r[index++] = a | ((c >> (6 - s)) & 255);
|
|
||||||
}
|
}
|
||||||
s = (s + 2) & 7;
|
|
||||||
a = (c << s) & 255;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r.subarray(0, index);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
// Backtrack until we have 4n non-whitespace characters
|
||||||
encode: s2r,
|
// that we can safely base64-decode
|
||||||
decode: r2s
|
let length = buf.length;
|
||||||
};
|
for (; length > 0 && (length - spaces) % 4 !== 0; length--) {
|
||||||
|
if (spacechars.includes(buf[length])) spaces--;
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoded = decodeChunk(buf.substr(0, length));
|
||||||
|
buf = buf.substr(length);
|
||||||
|
return decoded;
|
||||||
|
}, () => decodeChunk(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { encode, decode };
|
||||||
|
|
|
@ -250,7 +250,11 @@ export default {
|
||||||
* @returns {String} Base-64 encoded string
|
* @returns {String} Base-64 encoded string
|
||||||
*/
|
*/
|
||||||
Uint8Array_to_b64: function (bytes, url) {
|
Uint8Array_to_b64: function (bytes, url) {
|
||||||
return b64.encode(bytes, url).replace(/[\r\n]/g, '');
|
let encoded = b64.encode(bytes).replace(/[\r\n]/g, '');
|
||||||
|
if (url) {
|
||||||
|
encoded = encoded.replace(/[+]/g, '-').replace(/[/]/g, '_').replace(/[=]/g, '');
|
||||||
|
}
|
||||||
|
return encoded;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -381,7 +381,7 @@ function tests() {
|
||||||
const msgAsciiArmored = encrypted.data;
|
const msgAsciiArmored = encrypted.data;
|
||||||
const message = await openpgp.message.readArmored(openpgp.stream.transform(msgAsciiArmored, value => {
|
const message = await openpgp.message.readArmored(openpgp.stream.transform(msgAsciiArmored, value => {
|
||||||
value += '';
|
value += '';
|
||||||
if (value === '\n=' || value.length === 4) return; // Remove checksum
|
if (value === '=' || value.length === 6) return; // Remove checksum
|
||||||
const newlineIndex = value.indexOf('\r\n', 500);
|
const newlineIndex = value.indexOf('\r\n', 500);
|
||||||
if (value.length > 1000) return value.slice(0, newlineIndex - 1) + (value[newlineIndex - 1] === 'a' ? 'b' : 'a') + value.slice(newlineIndex);
|
if (value.length > 1000) return value.slice(0, newlineIndex - 1) + (value[newlineIndex - 1] === 'a' ? 'b' : 'a') + value.slice(newlineIndex);
|
||||||
return value;
|
return value;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user