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
* the encoded bytes
@ -204,7 +224,7 @@ function verifyHeaders(headers) {
function dearmor(input) {
return new Promise(async (resolve, reject) => {
try {
const reSplit = /^-----[^-]+-----$/;
const reSplit = /^-----[^-]+-----$/m;
const reEmptyLine = /^[ \f\r\t\u00a0\u2000-\u200a\u202f\u205f\u3000]*$/;
let type;
@ -213,21 +233,17 @@ function dearmor(input) {
let headersDone;
let text = [];
let textDone;
let resolved = false;
let checksum;
let data = base64.decode(stream.transformPair(input, async (readable, writable) => {
const reader = stream.getReader(readable);
const writer = stream.getWriter(writable);
while (true) {
if (resolved) await writer.ready;
try {
const lineUntrimmed = await reader.readLine();
if (lineUntrimmed === undefined) {
try {
while (true) {
let line = await reader.readLine();
if (line === undefined) {
throw new Error('Misformed armored text');
}
// remove trailing whitespace at end of lines
// remove leading whitespace for compat with older versions of OpenPGP.js
const line = lineUntrimmed.trim();
line = line.replace(/[\t\r\n ]+$/, '');
if (!type) {
if (reSplit.test(line)) {
type = getType(line);
@ -243,13 +259,13 @@ function dearmor(input) {
headersDone = true;
if (textDone || type !== 2) {
resolve({ text, data, headers, type });
resolved = true;
break;
}
}
} else if (!textDone && type === 2) {
if (!reSplit.test(line)) {
// Reverse dash-escaping for msg
text.push(util.removeTrailingSpaces(lineUntrimmed.replace(/^- /, '').replace(/[\r\n]+$/, '')));
text.push(line.replace(/^- /, ''));
} else {
text = text.join('\r\n');
textDone = true;
@ -257,26 +273,40 @@ function dearmor(input) {
lastHeaders = [];
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) => {

View File

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

View File

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

View File

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

View File

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