js: change chunksize handling and decoding
-- * the nativeApp now sends all data in one base64-encoded string, which needs reassembly, but in a much easier way now. * there are some new performance problems now, especially with decrypting data
This commit is contained in:
parent
7a072270ac
commit
c072675f3f
@ -152,7 +152,7 @@ describe('Encryption and Decryption', function () {
|
||||
let prm = Gpgmejs.init();
|
||||
prm.then(function (context) {
|
||||
context.encrypt(b64data,
|
||||
inputvalues.encrypt.good.fingerprint).then(
|
||||
inputvalues.encrypt.good.fingerprint, true).then(
|
||||
function (answer) {
|
||||
expect(answer).to.not.be.empty;
|
||||
expect(answer.data).to.be.a('string');
|
||||
@ -185,7 +185,7 @@ describe('Encryption and Decryption', function () {
|
||||
'BEGIN PGP MESSAGE');
|
||||
expect(answer.data).to.include(
|
||||
'END PGP MESSAGE');
|
||||
context.decrypt(answer.data, true).then(
|
||||
context.decrypt(answer.data).then(
|
||||
function (result) {
|
||||
expect(result).to.not.be.empty;
|
||||
expect(result.data).to.be.a('string');
|
||||
@ -196,31 +196,4 @@ describe('Encryption and Decryption', function () {
|
||||
});
|
||||
}).timeout(3000);
|
||||
|
||||
it('Random data, input and output as base64', function (done) {
|
||||
let data = bigBoringString(0.0001);
|
||||
let b64data = btoa(data);
|
||||
let prm = Gpgmejs.init();
|
||||
prm.then(function (context) {
|
||||
context.encrypt(b64data,
|
||||
inputvalues.encrypt.good.fingerprint).then(
|
||||
function (answer) {
|
||||
expect(answer).to.not.be.empty;
|
||||
expect(answer.data).to.be.a('string');
|
||||
|
||||
expect(answer.data).to.include(
|
||||
'BEGIN PGP MESSAGE');
|
||||
expect(answer.data).to.include(
|
||||
'END PGP MESSAGE');
|
||||
context.decrypt(answer.data, true).then(
|
||||
function (result) {
|
||||
expect(result).to.not.be.empty;
|
||||
expect(result.data).to.be.a('string');
|
||||
expect(result.data).to.equal(b64data);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
}).timeout(3000);
|
||||
|
||||
|
||||
});
|
||||
|
@ -24,7 +24,7 @@
|
||||
/* global bigString, inputvalues */
|
||||
|
||||
describe('Long running Encryption/Decryption', function () {
|
||||
for (let i=0; i < 100; i++) {
|
||||
for (let i=0; i < 101; i++) {
|
||||
it('Successful encrypt/decrypt completely random data ' +
|
||||
(i+1) + '/100', function (done) {
|
||||
let prm = Gpgmejs.init();
|
||||
@ -43,30 +43,32 @@ describe('Long running Encryption/Decryption', function () {
|
||||
function(result){
|
||||
expect(result).to.not.be.empty;
|
||||
expect(result.data).to.be.a('string');
|
||||
/*
|
||||
if (result.data.length !== data.length) {
|
||||
// console.log('diff: ' +
|
||||
// (result.data.length - data.length));
|
||||
console.log('diff: ' +
|
||||
(result.data.length - data.length));
|
||||
for (let i=0; i < result.data.length; i++){
|
||||
if (result.data[i] !== data[i]){
|
||||
// console.log('position: ' + i);
|
||||
// console.log('result : ' +
|
||||
// result.data.charCodeAt(i) +
|
||||
// result.data[i-2] +
|
||||
// result.data[i-1] +
|
||||
// result.data[i] +
|
||||
// result.data[i+1] +
|
||||
// result.data[i+2]);
|
||||
// console.log('original: ' +
|
||||
// data.charCodeAt(i) +
|
||||
// data[i-2] +
|
||||
// data[i-1] +
|
||||
// data[i] +
|
||||
// data[i+1] +
|
||||
// data[i+2]);
|
||||
console.log('position: ' + i);
|
||||
console.log('result : ' +
|
||||
result.data.charCodeAt(i) +
|
||||
result.data[i-2] +
|
||||
result.data[i-1] +
|
||||
result.data[i] +
|
||||
result.data[i+1] +
|
||||
result.data[i+2]);
|
||||
console.log('original: ' +
|
||||
data.charCodeAt(i) +
|
||||
data[i-2] +
|
||||
data[i-1] +
|
||||
data[i] +
|
||||
data[i+1] +
|
||||
data[i+2]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
expect(result.data).to.equal(data);
|
||||
done();
|
||||
});
|
||||
|
@ -108,6 +108,7 @@ export class Connection{
|
||||
return Promise.reject(gpgme_error('MSG_INCOMPLETE'));
|
||||
}
|
||||
let me = this;
|
||||
let chunksize = message.chunksize;
|
||||
return new Promise(function(resolve, reject){
|
||||
let answer = new Answer(message);
|
||||
let listener = function(msg) {
|
||||
@ -115,22 +116,27 @@ export class Connection{
|
||||
me._connection.onMessage.removeListener(listener);
|
||||
me._connection.disconnect();
|
||||
reject(gpgme_error('CONN_EMPTY_GPG_ANSWER'));
|
||||
} else if (msg.type === 'error'){
|
||||
me._connection.onMessage.removeListener(listener);
|
||||
me._connection.disconnect();
|
||||
reject(gpgme_error('GNUPG_ERROR', msg.msg));
|
||||
} else {
|
||||
let answer_result = answer.add(msg);
|
||||
let answer_result = answer.collect(msg);
|
||||
if (answer_result !== true){
|
||||
me._connection.onMessage.removeListener(listener);
|
||||
me._connection.disconnect();
|
||||
reject(answer_result);
|
||||
} else if (msg.more === true){
|
||||
me._connection.postMessage({'op': 'getmore'});
|
||||
} else {
|
||||
me._connection.onMessage.removeListener(listener);
|
||||
me._connection.disconnect();
|
||||
resolve(answer.message);
|
||||
if (msg.more === true){
|
||||
me._connection.postMessage({
|
||||
'op': 'getmore',
|
||||
'chunksize': chunksize
|
||||
});
|
||||
} else {
|
||||
me._connection.onMessage.removeListener(listener);
|
||||
me._connection.disconnect();
|
||||
if (answer.message instanceof Error){
|
||||
reject(answer.message);
|
||||
} else {
|
||||
resolve(answer.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -170,19 +176,32 @@ class Answer{
|
||||
|
||||
constructor(message){
|
||||
this.operation = message.operation;
|
||||
this.expected = message.expected;
|
||||
this.expect = message.expect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the information to the answer
|
||||
* @param {Object} msg The message as received with nativeMessaging
|
||||
* returns true if successfull, gpgme_error otherwise
|
||||
*/
|
||||
add(msg){
|
||||
if (this._response === undefined){
|
||||
this._response = {};
|
||||
collect(msg){
|
||||
if (typeof(msg) !== 'object' || !msg.hasOwnProperty('response')) {
|
||||
return gpgme_error('CONN_UNEXPECTED_ANSWER');
|
||||
}
|
||||
let messageKeys = Object.keys(msg);
|
||||
if (this._responseb64 === undefined){
|
||||
//this._responseb64 = [msg.response];
|
||||
this._responseb64 = msg.response;
|
||||
return true;
|
||||
} else {
|
||||
//this._responseb64.push(msg.response);
|
||||
this._responseb64 += msg.response;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
get message(){
|
||||
if (this._responseb64 === undefined){
|
||||
return gpgme_error('CONN_UNEXPECTED_ANSWER');
|
||||
}
|
||||
// let _decodedResponse = JSON.parse(atob(this._responseb64.join('')));
|
||||
let _decodedResponse = JSON.parse(atob(this._responseb64));
|
||||
let _response = {};
|
||||
let messageKeys = Object.keys(_decodedResponse);
|
||||
let poa = permittedOperations[this.operation].answer;
|
||||
if (messageKeys.length === 0){
|
||||
return gpgme_error('CONN_UNEXPECTED_ANSWER');
|
||||
@ -191,80 +210,42 @@ class Answer{
|
||||
let key = messageKeys[i];
|
||||
switch (key) {
|
||||
case 'type':
|
||||
if ( msg.type !== 'error' && poa.type.indexOf(msg.type) < 0){
|
||||
if (_decodedResponse.type === 'error'){
|
||||
return (gpgme_error('GNUPG_ERROR', _decodedResponse.msg));
|
||||
} else if (poa.type.indexOf(_decodedResponse.type) < 0){
|
||||
return gpgme_error('CONN_UNEXPECTED_ANSWER');
|
||||
}
|
||||
break;
|
||||
case 'more':
|
||||
case 'base64':
|
||||
break;
|
||||
case 'msg':
|
||||
if (_decodedResponse.type === 'error'){
|
||||
return (gpgme_error('GNUPG_ERROR', _decodedResponse.msg));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
//data should be concatenated
|
||||
if (poa.data.indexOf(key) >= 0){
|
||||
if (!this._response.hasOwnProperty(key)){
|
||||
this._response[key] = '';
|
||||
}
|
||||
this._response[key] += msg[key];
|
||||
}
|
||||
//params should not change through the message
|
||||
else if (poa.params.indexOf(key) >= 0){
|
||||
if (!this._response.hasOwnProperty(key)){
|
||||
this._response[key] = msg[key];
|
||||
}
|
||||
else if (this._response[key] !== msg[key]){
|
||||
return gpgme_error('CONN_UNEXPECTED_ANSWER',msg[key]);
|
||||
}
|
||||
}
|
||||
//infos may be json objects etc. Not yet defined.
|
||||
// Pushing them into arrays for now
|
||||
else if (poa.infos.indexOf(key) >= 0){
|
||||
if (!this._response.hasOwnProperty(key)){
|
||||
this._response[key] = [];
|
||||
}
|
||||
|
||||
if (Array.isArray(msg[key])) {
|
||||
for (let i=0; i< msg[key].length; i++) {
|
||||
this._response[key].push(msg[key][i]);
|
||||
}
|
||||
} else {
|
||||
this._response[key].push(msg[key]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!poa.data.hasOwnProperty(key)){
|
||||
return gpgme_error('CONN_UNEXPECTED_ANSWER');
|
||||
}
|
||||
if( typeof(_decodedResponse[key]) !== poa.data[key] ){
|
||||
return gpgme_error('CONN_UNEXPECTED_ANSWER');
|
||||
}
|
||||
if (_decodedResponse.base64 === true
|
||||
&& poa.data[key] === 'string'
|
||||
&& this.expect === undefined
|
||||
){
|
||||
_response[key] = decodeURIComponent(
|
||||
atob(_decodedResponse[key]).split('').map(
|
||||
function(c) {
|
||||
return '%' +
|
||||
('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
||||
}).join(''));
|
||||
} else {
|
||||
_response[key] = _decodedResponse[key];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object} the assembled message, original data assumed to be
|
||||
* (javascript-) strings
|
||||
*/
|
||||
get message(){
|
||||
let keys = Object.keys(this._response);
|
||||
let msg = {};
|
||||
let poa = permittedOperations[this.operation].answer;
|
||||
for (let i=0; i < keys.length; i++) {
|
||||
if (poa.data.indexOf(keys[i]) >= 0
|
||||
&& this._response.base64 === true
|
||||
) {
|
||||
msg[keys[i]] = atob(this._response[keys[i]]);
|
||||
if (this.expected === 'base64'){
|
||||
msg[keys[i]] = this._response[keys[i]];
|
||||
} else {
|
||||
msg[keys[i]] = decodeURIComponent(
|
||||
atob(this._response[keys[i]]).split('').map(
|
||||
function(c) {
|
||||
return '%' +
|
||||
('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
||||
}).join(''));
|
||||
}
|
||||
} else {
|
||||
msg[keys[i]] = this._response[keys[i]];
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
return _response;
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,6 @@ export class GPGME_Message {
|
||||
|
||||
constructor(operation){
|
||||
this.operation = operation;
|
||||
this._expected = 'string';
|
||||
}
|
||||
|
||||
set operation (op){
|
||||
@ -59,24 +58,50 @@ export class GPGME_Message {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get operation(){
|
||||
return this._msg.op;
|
||||
}
|
||||
|
||||
set expected(string){
|
||||
if (string === 'base64'){
|
||||
this._expected = 'base64';
|
||||
/**
|
||||
* Set the maximum size of responses from gpgme in bytes. Values allowed
|
||||
* range from 10kB to 1MB. The lower limit is arbitrary, the upper limit
|
||||
* fixed by browsers' nativeMessaging specifications
|
||||
*/
|
||||
set chunksize(value){
|
||||
if (
|
||||
Number.isInteger(value) &&
|
||||
value > 10 * 1024 &&
|
||||
value <= 1024 * 1024
|
||||
){
|
||||
this._chunksize = value;
|
||||
}
|
||||
}
|
||||
get chunksize(){
|
||||
if (this._chunksize === undefined){
|
||||
return 1024 * 1023;
|
||||
} else {
|
||||
return this._chunksize;
|
||||
}
|
||||
}
|
||||
|
||||
get expected() {
|
||||
if (this._expected === 'base64'){
|
||||
return this._expected;
|
||||
/**
|
||||
* If expect is set to 'base64', the response is expected to be base64
|
||||
* encoded binary
|
||||
*/
|
||||
set expect(value){
|
||||
if (value ==='base64'){
|
||||
this._expect = value;
|
||||
}
|
||||
return 'string';
|
||||
}
|
||||
|
||||
get expect(){
|
||||
if ( this._expect === 'base64'){
|
||||
return this._expect;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets a parameter for the message. Note that the operation has to be set
|
||||
* first, to be able to check if the parameter is permittted
|
||||
@ -188,6 +213,7 @@ export class GPGME_Message {
|
||||
*/
|
||||
get message(){
|
||||
if (this.isComplete === true){
|
||||
this._msg.chunksize = this.chunksize;
|
||||
return this._msg;
|
||||
}
|
||||
else {
|
||||
@ -201,10 +227,13 @@ export class GPGME_Message {
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (me.isComplete === true) {
|
||||
let conn = new Connection;
|
||||
if (me._msg.chunksize === undefined){
|
||||
me._msg.chunksize = 1023*1024;
|
||||
}
|
||||
conn.post(me).then(function(response) {
|
||||
resolve(response);
|
||||
}, function(reason) {
|
||||
reject(gpgme_error('GNUPG_ERROR', reason));
|
||||
reject(reason);
|
||||
});
|
||||
}
|
||||
else {
|
||||
|
@ -57,8 +57,8 @@ export class GpgME {
|
||||
* Keys used to encrypt the message
|
||||
* @param {GPGME_Key|String|Array<String>|Array<GPGME_Key>} secretKeys
|
||||
* (optional) Keys used to sign the message
|
||||
* @param {Boolean} base64 (optional) The data is already considered to be
|
||||
* in base64 encoding
|
||||
* @param {Boolean} base64 (optional) The data will be interpreted as
|
||||
* base64 encoded data
|
||||
* @param {Boolean} armor (optional) Request the output as armored block
|
||||
* @param {Boolean} wildcard (optional) If true, recipient information will
|
||||
* not be added to the message
|
||||
@ -109,24 +109,20 @@ export class GpgME {
|
||||
* Decrypt a Message
|
||||
* @param {String|Object} data text/data to be decrypted. Accepts Strings
|
||||
* and Objects with a getText method
|
||||
* @param {Boolean} base64 (optional) Response is expected to be base64
|
||||
* encoded
|
||||
* @returns {Promise<Object>} decrypted message:
|
||||
data: The decrypted data. This may be base64 encoded.
|
||||
data: The decrypted data.
|
||||
base64: Boolean indicating whether data is base64 encoded.
|
||||
mime: A Boolean indicating whether the data is a MIME object.
|
||||
signatures: Array of signature Objects TODO not yet implemented.
|
||||
// should be an object that can tell if all signatures are valid .
|
||||
// should be an object that can tell if all signatures are valid.
|
||||
* @async
|
||||
*/
|
||||
decrypt(data, base64=false){
|
||||
decrypt(data){
|
||||
if (data === undefined){
|
||||
return Promise.reject(gpgme_error('MSG_EMPTY'));
|
||||
}
|
||||
let msg = createMessage('decrypt');
|
||||
if (base64 === true){
|
||||
msg.expected = 'base64';
|
||||
}
|
||||
|
||||
if (msg instanceof Error){
|
||||
return Promise.reject(msg);
|
||||
}
|
||||
@ -165,10 +161,10 @@ export class GpgME {
|
||||
}
|
||||
msg.setParameter('mode', mode);
|
||||
putData(msg, data);
|
||||
if (mode === 'detached') {
|
||||
msg.expected = 'base64';
|
||||
}
|
||||
return new Promise(function(resolve,reject) {
|
||||
if (mode ==='detached'){
|
||||
msg.expect= 'base64';
|
||||
}
|
||||
msg.post().then( function(message) {
|
||||
if (mode === 'clearsign'){
|
||||
resolve({
|
||||
|
@ -37,11 +37,8 @@
|
||||
5000 ms would be too short
|
||||
answer: <Object>
|
||||
type: <String< The content type of answer expected
|
||||
data: Array<String> The payload property of the answer. May be
|
||||
partial and in need of concatenation
|
||||
params: Array<String> Information that do not change throughout
|
||||
the message
|
||||
infos: Array<*> arbitrary information that may result in a list
|
||||
data: <Object>
|
||||
the properties expected and their type, eg: {'data':'string'}
|
||||
}
|
||||
}
|
||||
*/
|
||||
@ -67,9 +64,6 @@ export const permittedOperations = {
|
||||
allowed: ['string'],
|
||||
array_allowed: true
|
||||
},
|
||||
'chunksize': {
|
||||
allowed: ['number']
|
||||
},
|
||||
'base64': {
|
||||
allowed: ['boolean']
|
||||
},
|
||||
@ -101,9 +95,10 @@ export const permittedOperations = {
|
||||
},
|
||||
answer: {
|
||||
type: ['ciphertext'],
|
||||
data: ['data'],
|
||||
params: ['base64'],
|
||||
infos: []
|
||||
data: {
|
||||
'data': 'string',
|
||||
'base64':'boolean'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -119,18 +114,18 @@ export const permittedOperations = {
|
||||
allowed: ['string'],
|
||||
allowed_data: ['cms', 'openpgp']
|
||||
},
|
||||
'chunksize': {
|
||||
allowed: ['number'],
|
||||
},
|
||||
'base64': {
|
||||
allowed: ['boolean']
|
||||
}
|
||||
},
|
||||
answer: {
|
||||
type: ['plaintext'],
|
||||
data: ['data'],
|
||||
params: ['base64', 'mime'],
|
||||
infos: ['signatures']
|
||||
data: {
|
||||
'data': 'string',
|
||||
'base64': 'boolean',
|
||||
'mime': 'boolean',
|
||||
'signatures': 'object'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -149,9 +144,6 @@ export const permittedOperations = {
|
||||
allowed: ['string'],
|
||||
allowed_data: ['cms', 'openpgp']
|
||||
},
|
||||
'chunksize': {
|
||||
allowed: ['number'],
|
||||
},
|
||||
'sender': {
|
||||
allowed: ['string'],
|
||||
},
|
||||
@ -169,10 +161,11 @@ export const permittedOperations = {
|
||||
},
|
||||
answer: {
|
||||
type: ['signature', 'ciphertext'],
|
||||
data: ['data'], // Unless armor mode is used a Base64 encoded binary
|
||||
// signature. In armor mode a string with an armored
|
||||
// OpenPGP or a PEM message.
|
||||
params: ['base64']
|
||||
data: {
|
||||
'data': 'string',
|
||||
'base64':'boolean'
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
@ -186,9 +179,6 @@ export const permittedOperations = {
|
||||
allowed: ['string'],
|
||||
allowed_data: ['cms', 'openpgp']
|
||||
},
|
||||
'chunksize': {
|
||||
allowed: ['number'],
|
||||
},
|
||||
'secret': {
|
||||
allowed: ['boolean']
|
||||
},
|
||||
@ -220,9 +210,10 @@ export const permittedOperations = {
|
||||
},
|
||||
answer: {
|
||||
type: ['keys'],
|
||||
data: [],
|
||||
params: ['base64'],
|
||||
infos: ['keys']
|
||||
data: {
|
||||
'base64': 'boolean',
|
||||
'keys': 'object'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -233,9 +224,6 @@ export const permittedOperations = {
|
||||
allowed: ['string'],
|
||||
allowed_data: ['cms', 'openpgp']
|
||||
},
|
||||
'chunksize': {
|
||||
allowed: ['number'],
|
||||
},
|
||||
'keys': {
|
||||
allowed: ['string'],
|
||||
array_allowed: true
|
||||
@ -259,8 +247,10 @@ export const permittedOperations = {
|
||||
},
|
||||
answer: {
|
||||
type: ['keys'],
|
||||
data: ['data'],
|
||||
params: ['base64']
|
||||
data: {
|
||||
'data': 'string',
|
||||
'base64': 'boolean'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -280,10 +270,10 @@ export const permittedOperations = {
|
||||
},
|
||||
},
|
||||
answer: {
|
||||
infos: ['result'],
|
||||
type: [],
|
||||
data: [],
|
||||
params: []
|
||||
data: {
|
||||
'result': 'Object'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -299,15 +289,15 @@ export const permittedOperations = {
|
||||
allowed: ['string'],
|
||||
allowed_data: ['cms', 'openpgp']
|
||||
},
|
||||
// 'secret': { not yet implemented
|
||||
// 'secret': { not implemented
|
||||
// allowed: ['boolean']
|
||||
// }
|
||||
|
||||
},
|
||||
answer: {
|
||||
data: [],
|
||||
params:['success'],
|
||||
infos: []
|
||||
data: {
|
||||
'success': 'boolean'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -316,9 +306,10 @@ export const permittedOperations = {
|
||||
optional: {},
|
||||
answer: {
|
||||
type: [''],
|
||||
data: ['gpgme'],
|
||||
infos: ['info'],
|
||||
params:[]
|
||||
data: {
|
||||
'gpgme': 'string',
|
||||
'info': 'object'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -339,7 +339,8 @@ function unittests (){
|
||||
test0.setParameter('keys', hp.validFingerprints);
|
||||
|
||||
expect(test0.message).to.not.be.null;
|
||||
expect(test0.message).to.have.keys('op', 'data', 'keys');
|
||||
expect(test0.message).to.have.keys('op', 'data', 'keys',
|
||||
'chunksize');
|
||||
expect(test0.message.op).to.equal('encrypt');
|
||||
expect(test0.message.data).to.equal(
|
||||
mp.valid_encrypt_data);
|
||||
|
Loading…
Reference in New Issue
Block a user