Don't process armored message data line per line

This cuts down on the overhead of streaming by reducing the amount
of calls to reader.read() and writer.write().
This commit is contained in:
Daniel Huigens 2018-09-02 17:08:20 +02:00
parent b004ddecb2
commit dc722770d0
5 changed files with 81 additions and 49 deletions

View File

@ -192,6 +192,26 @@ function verifyHeaders(headers) {
} }
} }
/**
* Splits a message into two parts, the body and the checksum. This is an internal function
* @param {String} text OpenPGP armored message part
* @returns {Object} An object with attribute "body" containing the body
* and an attribute "checksum" containing the checksum.
*/
function splitChecksum(text) {
let body = text;
let checksum = "";
const lastEquals = text.lastIndexOf("=");
if (lastEquals >= 0 && lastEquals !== text.length - 1) { // '=' as the last char means no checksum
body = text.slice(0, lastEquals);
checksum = text.slice(lastEquals + 1).substr(0, 4);
}
return { body: body, checksum: checksum };
}
/** /**
* DeArmor an OpenPGP armored message; verify the checksum and return * DeArmor an OpenPGP armored message; verify the checksum and return
* the encoded bytes * the encoded bytes
@ -204,7 +224,7 @@ function verifyHeaders(headers) {
function dearmor(input) { function dearmor(input) {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
const reSplit = /^-----[^-]+-----$/; const reSplit = /^-----[^-]+-----$/m;
const reEmptyLine = /^[ \f\r\t\u00a0\u2000-\u200a\u202f\u205f\u3000]*$/; const reEmptyLine = /^[ \f\r\t\u00a0\u2000-\u200a\u202f\u205f\u3000]*$/;
let type; let type;
@ -213,21 +233,17 @@ function dearmor(input) {
let headersDone; let headersDone;
let text = []; let text = [];
let textDone; let textDone;
let resolved = false;
let checksum; let checksum;
let data = base64.decode(stream.transformPair(input, async (readable, writable) => { let data = base64.decode(stream.transformPair(input, async (readable, writable) => {
const reader = stream.getReader(readable); const reader = stream.getReader(readable);
const writer = stream.getWriter(writable); try {
while (true) { while (true) {
if (resolved) await writer.ready; let line = await reader.readLine();
try { if (line === undefined) {
const lineUntrimmed = await reader.readLine();
if (lineUntrimmed === undefined) {
throw new Error('Misformed armored text'); throw new Error('Misformed armored text');
} }
// remove trailing whitespace at end of lines // remove trailing whitespace at end of lines
// remove leading whitespace for compat with older versions of OpenPGP.js line = line.replace(/[\t\r\n ]+$/, '');
const line = lineUntrimmed.trim();
if (!type) { if (!type) {
if (reSplit.test(line)) { if (reSplit.test(line)) {
type = getType(line); type = getType(line);
@ -243,13 +259,13 @@ function dearmor(input) {
headersDone = true; headersDone = true;
if (textDone || type !== 2) { if (textDone || type !== 2) {
resolve({ text, data, headers, type }); resolve({ text, data, headers, type });
resolved = true; break;
} }
} }
} else if (!textDone && type === 2) { } else if (!textDone && type === 2) {
if (!reSplit.test(line)) { if (!reSplit.test(line)) {
// Reverse dash-escaping for msg // Reverse dash-escaping for msg
text.push(util.removeTrailingSpaces(lineUntrimmed.replace(/^- /, '').replace(/[\r\n]+$/, ''))); text.push(line.replace(/^- /, ''));
} else { } else {
text = text.join('\r\n'); text = text.join('\r\n');
textDone = true; textDone = true;
@ -257,26 +273,40 @@ function dearmor(input) {
lastHeaders = []; lastHeaders = [];
headersDone = false; headersDone = false;
} }
} else {
if (!reSplit.test(line)) {
if (line[0] !== '=') {
await writer.write(line);
} else {
checksum = line.substr(1);
}
} else {
await writer.close();
break;
}
} }
} catch(e) {
if (resolved) {
await writer.abort(e);
} else {
reject(e);
}
break;
} }
} catch(e) {
reject(e);
return;
}
const writer = stream.getWriter(writable);
try {
while (true) {
await writer.ready;
const { done, value } = await reader.read();
if (done) {
throw new Error('Misformed armored text');
}
const line = value + '';
if (line.indexOf('=') === -1 && line.indexOf('-') === -1) {
await writer.write(line);
} else {
let remainder = line + await reader.readToEnd();
remainder = remainder.replace(/[\t\r ]+$/mg, '');
const parts = remainder.split(reSplit);
if (parts.length === 1) {
throw new Error('Misformed armored text');
}
const split = splitChecksum(parts[0].slice(0, -1));
checksum = split.checksum;
await writer.write(split.body);
break;
}
}
await writer.ready;
await writer.close();
} catch(e) {
await writer.abort(e);
} }
})); }));
data = stream.transformPair(data, async (readable, writable) => { data = stream.transformPair(data, async (readable, writable) => {

View File

@ -142,8 +142,10 @@ function pako_zlib(constructor, options = {}) {
return function(data) { return function(data) {
const obj = new constructor(options); const obj = new constructor(options);
return stream.transform(data, value => { return stream.transform(data, value => {
obj.push(value, pako.Z_SYNC_FLUSH); if (value.length) {
return obj.result; obj.push(value, pako.Z_SYNC_FLUSH);
return obj.result;
}
}); });
}; };
} }

View File

@ -256,14 +256,13 @@ export default {
packet = await reader.readBytes(packet_length); packet = await reader.readBytes(packet_length);
await callback({ tag, packet }); await callback({ tag, packet });
} }
const { done, value } = await reader.read(); const nextPacket = await reader.peekBytes(2);
if (!done) reader.unshift(value);
if (writer) { if (writer) {
await writer.ready; await writer.ready;
await writer.close(); await writer.close();
} }
if (streaming) await callbackReturned; if (streaming) await callbackReturned;
return done || !value || !value.length; return !nextPacket || !nextPacket.length;
} catch(e) { } catch(e) {
if (writer) { if (writer) {
await writer.abort(e); await writer.abort(e);

View File

@ -304,7 +304,7 @@ describe("ASCII armor", function() {
it('Accept header with trailing whitespace', async function () { it('Accept header with trailing whitespace', async function () {
const privKey = const privKey =
['-----BEGIN PGP PRIVATE KEY BLOCK-----\t \r', ['-----BEGIN PGP PRIVATE KEY BLOCK-----',
'Version: OpenPGP.js v0.3.0', 'Version: OpenPGP.js v0.3.0',
'Comment: https://openpgpjs.org', 'Comment: https://openpgpjs.org',
'', '',
@ -321,7 +321,8 @@ describe("ASCII armor", function() {
'ABMFAlLm1+4JEBD8MASZrpALAhsMAAC3IgD8DnLGbMnpLtrX72RCkPW1ffLq', 'ABMFAlLm1+4JEBD8MASZrpALAhsMAAC3IgD8DnLGbMnpLtrX72RCkPW1ffLq',
'71vlXMJNXvoCeuejiRw=', '71vlXMJNXvoCeuejiRw=',
'=wJNM', '=wJNM',
'-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); '-----END PGP PRIVATE KEY BLOCK-----',
''].join('\t \r\n');
const result = await openpgp.key.readArmored(privKey); const result = await openpgp.key.readArmored(privKey);
expect(result.err).to.not.exist; expect(result.err).to.not.exist;

View File

@ -841,19 +841,19 @@ const wrong_key =
'-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); '-----END PGP PUBLIC KEY BLOCK-----'].join('\n');
const expiredKey = const expiredKey =
`-----BEGIN PGP PRIVATE KEY BLOCK----- `-----BEGIN PGP PRIVATE KEY BLOCK-----
xcA4BAAAAAEBAgCgONc0J8rfO6cJw5YTP38x1ze2tAYIO7EcmRCNYwMkXngb xcA4BAAAAAEBAgCgONc0J8rfO6cJw5YTP38x1ze2tAYIO7EcmRCNYwMkXngb
0Qdzg34Q5RW0rNiR56VB6KElPUhePRPVklLFiIvHABEBAAEAAf9qabYMzsz/ 0Qdzg34Q5RW0rNiR56VB6KElPUhePRPVklLFiIvHABEBAAEAAf9qabYMzsz/
/LeRVZSsTgTljmJTdzd2ambUbpi+vt8MXJsbaWh71vjoLMWSXajaKSPDjVU5 /LeRVZSsTgTljmJTdzd2ambUbpi+vt8MXJsbaWh71vjoLMWSXajaKSPDjVU5
waFNt9kLqwGGGLqpAQD5ZdMH2XzTq6GU9Ka69iZs6Pbnzwdz59Vc3i8hXlUj waFNt9kLqwGGGLqpAQD5ZdMH2XzTq6GU9Ka69iZs6Pbnzwdz59Vc3i8hXlUj
zQEApHargCTsrtvSrm+hK/pN51/BHAy9lxCAw9f2etx+AeMA/RGrijkFZtYt zQEApHargCTsrtvSrm+hK/pN51/BHAy9lxCAw9f2etx+AeMA/RGrijkFZtYt
jeWdv/usXL3mgHvEcJv63N5zcEvDX5X4W1bND3Rlc3QxIDxhQGIuY29tPsJ7 jeWdv/usXL3mgHvEcJv63N5zcEvDX5X4W1bND3Rlc3QxIDxhQGIuY29tPsJ7
BBABCAAvBQIAAAABBQMAAAU5BgsJBwgDAgkQzcF99nGrkAkEFQgKAgMWAgEC BBABCAAvBQIAAAABBQMAAAU5BgsJBwgDAgkQzcF99nGrkAkEFQgKAgMWAgEC
GQECGwMCHgEAABAlAfwPehmLZs+gOhOTTaSslqQ50bl/REjmv42Nyr1ZBlQS GQECGwMCHgEAABAlAfwPehmLZs+gOhOTTaSslqQ50bl/REjmv42Nyr1ZBlQS
DECl1Qu4QyeXin29uEXWiekMpNlZVsEuc8icCw6ABhIZ DECl1Qu4QyeXin29uEXWiekMpNlZVsEuc8icCw6ABhIZ
=/7PI =/7PI
-----END PGP PRIVATE KEY BLOCK-----`; -----END PGP PRIVATE KEY BLOCK-----`;
const multipleBindingSignatures = const multipleBindingSignatures =
`-----BEGIN PGP PUBLIC KEY BLOCK----- `-----BEGIN PGP PUBLIC KEY BLOCK-----