js: encrypt improvement and decrypt method
[gpgme.git] / lang / js / src / Connection.js
1 import { GPGME_Message } from "./Message";
2
3 /* gpgme.js - Javascript integration for gpgme
4  * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
5  *
6  * This file is part of GPGME.
7  *
8  * GPGME is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU Lesser General Public License as
10  * published by the Free Software Foundation; either version 2.1 of
11  * the License, or (at your option) any later version.
12  *
13  * GPGME is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this program; if not, see <http://www.gnu.org/licenses/>.
20  * SPDX-License-Identifier: LGPL-2.1+
21  */
22
23 /**
24  * A connection port will be opened for each communication between gpgmejs and
25  * gnupg. It should be alive as long as there are additional messages to be
26  * expected.
27  */
28 import { permittedOperations } from './permittedOperations'
29
30 export class Connection{
31
32     /**
33      * Opens and closes a port. Thus, it is made sure that the connection can
34      * be used.
35      * THIS BEHAVIOUR MAY CHANGE!
36      * discussion is to keep a port alive as long as the context stays the same
37      *
38      * TODO returns nothing, but triggers exceptions if not successfull
39      */
40     constructor(){
41         this._connection = chrome.runtime.connectNative('gpgmejson');
42         if (!this._connection){
43             if (chrome.runtime.lastError){
44                 throw('NO_CONNECT_RLE');
45             } else {
46                 throw('NO_CONNECT');
47             }
48         }
49         this._flags = {}; // TODO general config
50     }
51
52     /**
53      * Immediately closes the open port
54      */
55     disconnect() {
56         if (this._connection){
57             this._connection.disconnect();
58         }
59     }
60
61     /**
62      * Sends a message and resolves with the answer.
63      * @param {GPGME_Message} message
64      * @returns {Promise<Object>} the gnupg answer, or rejection with error
65      * information
66      * TODO: better/more consistent error information
67      */
68     post(message){
69         if (!message || !message instanceof GPGME_Message){
70             return Promise.reject('ERR_NO_MSG');
71         }
72         // let timeout = 5000; //TODO config
73         let me = this;
74         return new Promise(function(resolve, reject){
75             let answer = new Answer(message.op);
76             let listener = function(msg) {
77                 if (!msg){
78                     me._connection.onMessage.removeListener(listener)
79                     reject('EMPTY_ANSWER');
80                 } else if (msg.type === "error"){
81                     me._connection.onMessage.removeListener(listener)
82                     reject(msg.msg);
83                 } else {
84                     answer.add(msg);
85                     if (msg.more === true){
86                         me._connection.postMessage({'op': 'getmore'});
87                     } else {
88                         me._connection.onMessage.removeListener(listener)
89                         resolve(answer.message);
90                     }
91                 }
92             };
93
94             me._connection.onMessage.addListener(listener);
95             me._connection.postMessage(message);
96             //TBD: needs to be aware if there is a pinentry pending
97             // setTimeout(
98             //     function(){
99             //         me.disconnect();
100             //         reject('TIMEOUT');
101             //     }, timeout);
102         });
103      }
104 };
105
106 /**
107  * A class for answer objects, checking and processing the return messages of
108  * the nativeMessaging communication
109  * @param {String} operation The operation, to look up validity of return keys
110  */
111 class Answer{
112
113     constructor(operation){
114         this.operation = operation;
115     }
116
117     /**
118      *
119      * @param {Object} msg The message as received with nativeMessaging
120      * TODO: "error" and "more" handling are not in here, but in post()
121      */
122     add(msg){
123         if (this._response === undefined){
124             this._response = {};
125         }
126         let messageKeys = Object.keys(msg);
127         let poa = permittedOperations[this.operation].answer;
128         for (let i= 0; i < messageKeys.length; i++){
129             let key = messageKeys[i];
130             switch (key) {
131                 case 'type':
132                     if ( msg.type !== 'error' && poa.type.indexOf(msg.type) < 0){
133                         console.log( 'unexpected answer type: ' + msg.type);
134                         throw('UNEXPECTED_TYPE');
135
136                     }
137                     break;
138                 case 'more':
139                     break;
140                 default:
141                     //data should be concatenated
142                     if (poa.data.indexOf(key) >= 0){
143                         if (!this._response.hasOwnProperty(key)){
144                             this._response[key] = '';
145                         }
146                         this._response[key] = this._response[key].concat(msg[key]);
147                     }
148                     //params should not change through the message
149                     else if (poa.params.indexOf(key) >= 0){
150                         if (!this._response.hasOwnProperty(key)){
151                             this._response[key] = msg[key];
152                         }
153                         else if (this._response[key] !== msg[key]){
154                                 throw('UNEXPECTED_TYPE');
155                         }
156                     }
157                     //infos may be json objects etc. Not yet defined.
158                     // Pushing them into arrays for now
159                     else if (poa.infos.indexOf(key) >= 0){
160                         if (!this._response.hasOwnProperty(key)){
161                             this._response[key] = [];
162                         }
163                         this._response.push(msg[key]);
164                     }
165                     else {
166                         console.log('unexpected answer parameter: ' + key);
167                         throw('UNEXPECTED_PARAM');
168                     }
169                     break;
170             }
171         }
172     }
173
174     /**
175      * Returns the assembled message. TODO: does not care yet if completed.
176      */
177     get message(){
178         return this._response;
179     }
180 }