js: improve decryption performance

--
* src/Connection.js, src/Helpers.js: performance of decoding incoming
  base64 data was improved to about 4 times the speed by introducing
  two more efficient functions (thanks to rrenkert@intevation.de for
  finding and testing them)

* src/gpgmejs.js: Decrypted data will now return as Uint8Array, if the
  caller does not wish for a decoding. Decoding binary data will return
  invalid data, and a Uint8Array may be desired. This can be indicated
  by using the (new) 'binary' option in decrypt.

* src/Errors.js A new error in case this decoding fails

* src/Message.js, src/Connection.js: expected is change from base64
  to binary, to avoid confusion later on.
This commit is contained in:
Maximilian Krambach 2018-08-22 16:32:31 +02:00
parent 94a0ed361e
commit 129fa919b9
5 changed files with 100 additions and 19 deletions

View File

@ -26,7 +26,7 @@
import { permittedOperations } from './permittedOperations';
import { gpgme_error } from './Errors';
import { GPGME_Message, createMessage } from './Message';
import { decode } from './Helpers';
import { decode, atobArray, Utf8ArrayToStr } from './Helpers';
/**
* A Connection handles the nativeMessaging interaction via a port. As the
@ -223,8 +223,9 @@ class Answer{
}
}
/**
* Returns the base64 encoded answer data with the content verified
* against {@link permittedOperations}.
* Decodes and verifies the base64 encoded answer data. Verified against
* {@link permittedOperations}.
* @returns {Object} The readable gpnupg answer
*/
getMessage (){
if (this._response_b64 === null){
@ -264,14 +265,15 @@ class Answer{
}
if (_decodedResponse.base64 === true
&& poa.data[key] === 'string'
&& this.expected !== 'base64'
){
_response[key] = decodeURIComponent(
atob(_decodedResponse[key]).split('').map(
function (c) {
return '%' +
('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
) {
if (this.expected === 'binary'){
_response[key] = atobArray(_decodedResponse[key]);
_response.binary = true;
} else {
_response[key] = Utf8ArrayToStr(
atobArray(_decodedResponse[key]));
_response.binary = false;
}
} else {
_response[key] = decode(_decodedResponse[key]);
}

View File

@ -103,6 +103,10 @@ export const err_list = {
msg: 'Invalid parameter was found',
type: 'error'
},
'DECODE_FAIL': {
msg: 'Decoding failed due to unexpected data',
type: 'error'
},
'PARAM_IGNORED': {
msg: 'An parameter was set that has no effect in gpgmejs',
type: 'warning'

View File

@ -135,3 +135,72 @@ export function decode (property){
}
return property;
}
/**
* Turns a base64 encoded string into an uint8 array
* @param {String} base64 encoded String
* @returns {Uint8Array}
* adapted from https://gist.github.com/borismus/1032746
*/
export function atobArray (base64) {
if (typeof (base64) !== 'string'){
throw gpgme_error('DECODE_FAIL');
}
const raw = window.atob(base64);
const rawLength = raw.length;
let array = new Uint8Array(new ArrayBuffer(rawLength));
for (let i = 0; i < rawLength; i++) {
array[i] = raw.charCodeAt(i);
}
return array;
}
/**
* Turns a Uint8Array into an utf8-String
* @param {*} array Uint8Array
* @returns {String}
* Taken and slightly adapted from
* http://www.onicos.com/staff/iz/amuse/javascript/expert/utf.txt
* (original header:
* utf.js - UTF-8 <=> UTF-16 convertion
*
* Copyright (C) 1999 Masanao Izumo <iz@onicos.co.jp>
* Version: 1.0
* LastModified: Dec 25 1999
* This library is free. You can redistribute it and/or modify it.
* )
*/
export function Utf8ArrayToStr (array) {
let out, i, len, c, char2, char3;
out = '';
len = array.length;
i = 0;
if (array instanceof Uint8Array === false){
throw gpgme_error('DECODE_FAIL');
}
while (i < len) {
c = array[i++];
switch (c >> 4) {
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
// 0xxxxxxx
out += String.fromCharCode(c);
break;
case 12: case 13:
// 110x xxxx 10xx xxxx
char2 = array[i++];
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
// 1110 xxxx 10xx xxxx 10xx xxxx
char2 = array[i++];
char3 = array[i++];
out += String.fromCharCode(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0));
break;
default:
break;
}
}
return out;
}

View File

@ -64,7 +64,7 @@ export class GPGME_Message {
}
set expected (value){
if (value === 'base64'){
if (value === 'binary'){
this._expected = value;
}
}

View File

@ -30,8 +30,8 @@ import { createSignature } from './Signature';
/**
* @typedef {Object} decrypt_result
* @property {String} data The decrypted data
* @property {Boolean} base64 indicating whether data is base64 encoded.
* @property {String|Uint8Array} data The decrypted data
* @property {Boolean} binary indicating whether data is an Uint8Array.
* @property {Boolean} is_mime (optional) the data claims to be a MIME
* object.
* @property {String} file_name (optional) the original file name
@ -51,7 +51,8 @@ import { createSignature } from './Signature';
/**
* @typedef {Object} encrypt_result The result of an encrypt operation
* @property {String} data The encrypted message
* @property {Boolean} base64 Indicating whether data is base64 encoded.
* @property {Boolean} binary Indicating whether returning payload data is an
* Uint8Array.
*/
/**
@ -174,10 +175,12 @@ export class GpgME {
* Strings and Objects with a getText method
* @param {Boolean} base64 (optional) false if the data is an armored
* block, true if it is base64 encoded binary data
* @param {Boolean} binary (optional) if true, treat the decoded data as
* binary, and return the data as Uint8Array
* @returns {Promise<decrypt_result>} Decrypted Message and information
* @async
*/
decrypt (data, base64=false){
decrypt (data, base64=false, binary){
if (data === undefined){
return Promise.reject(gpgme_error('MSG_EMPTY'));
}
@ -189,11 +192,14 @@ export class GpgME {
if (base64 === true){
msg.setParameter('base64', true);
}
if (binary === true){
msg.expected = 'binary';
}
putData(msg, data);
return new Promise(function (resolve, reject){
msg.post().then(function (result){
let _result = { data: result.data };
_result.base64 = result.base64 ? true: false;
_result.binary = result.binary ? true: false;
if (result.hasOwnProperty('dec_info')){
_result.is_mime = result.dec_info.is_mime ? true: false;
if (result.dec_info.file_name) {
@ -251,7 +257,7 @@ export class GpgME {
putData(msg, data);
return new Promise(function (resolve,reject) {
if (mode ==='detached'){
msg.expected ='base64';
msg.expected ='binary';
}
msg.post().then( function (message) {
if (mode === 'clearsign'){