js: make non-payload data more encoding-tolerant
[gpgme.git] / lang / js / src / Helpers.js
1 /* gpgme.js - Javascript integration for gpgme
2  * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
3  *
4  * This file is part of GPGME.
5  *
6  * GPGME is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as
8  * published by the Free Software Foundation; either version 2.1 of
9  * the License, or (at your option) any later version.
10  *
11  * GPGME is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this program; if not, see <http://www.gnu.org/licenses/>.
18  * SPDX-License-Identifier: LGPL-2.1+
19  *
20  * Author(s):
21  *     Maximilian Krambach <mkrambach@intevation.de>
22  */
23
24 import { gpgme_error } from './Errors';
25
26 /**
27  * Tries to return an array of fingerprints, either from input fingerprints or
28  * from Key objects (openpgp Keys or GPGME_Keys are both accepted).
29  *
30  * @param {Object | Array<Object> | String | Array<String>} input
31  * @returns {Array<String>} Array of fingerprints, or an empty array
32  */
33 export function toKeyIdArray (input){
34     if (!input){
35         return [];
36     }
37     if (!Array.isArray(input)){
38         input = [input];
39     }
40     let result = [];
41     for (let i=0; i < input.length; i++){
42         if (typeof (input[i]) === 'string'){
43             if (isFingerprint(input[i]) === true){
44                 result.push(input[i]);
45             } else {
46                 // MSG_NOT_A_FPR is just a console warning if warning enabled
47                 // in src/Errors.js
48                 gpgme_error('MSG_NOT_A_FPR');
49             }
50         } else if (typeof (input[i]) === 'object'){
51             let fpr = '';
52             if (input[i].hasOwnProperty('fingerprint')){
53                 fpr = input[i].fingerprint;
54             } else if (input[i].hasOwnProperty('primaryKey') &&
55                 input[i].primaryKey.hasOwnProperty('getFingerprint')){
56                 fpr = input[i].primaryKey.getFingerprint();
57             }
58             if (isFingerprint(fpr) === true){
59                 result.push(fpr);
60             } else {
61                 gpgme_error('MSG_NOT_A_FPR');
62             }
63         } else {
64             return gpgme_error('PARAM_WRONG');
65         }
66     }
67     if (result.length === 0){
68         return [];
69     } else {
70         return result;
71     }
72 }
73
74 /**
75  * Check if values are valid hexadecimal values of a specified length
76  * @param {String} key input value.
77  * @param {int} len the expected length of the value
78  * @returns {Boolean} true if value passes test
79  * @private
80  */
81 function hextest (key, len){
82     if (!key || typeof (key) !== 'string'){
83         return false;
84     }
85     if (key.length !== len){
86         return false;
87     }
88     let regexp= /^[0-9a-fA-F]*$/i;
89     return regexp.test(key);
90 }
91
92 /**
93  * check if the input is a valid Fingerprint
94  *      (Hex string with a length of 40 characters)
95  * @param {String} value to check
96  * @returns {Boolean} true if value passes test
97  */
98 export function isFingerprint (value){
99     return hextest(value, 40);
100 }
101
102 /**
103  * check if the input is a valid gnupg long ID (Hex string with a length of 16
104  * characters)
105  * @param {String} value to check
106  * @returns {Boolean} true if value passes test
107  */
108 export function isLongId (value){
109     return hextest(value, 16);
110 }
111
112 /**
113  * Recursively decodes input (utf8) to output (utf-16; javascript) strings
114  * @param {Object | Array | String} property
115  */
116 export function decode (property){
117     if (typeof property === 'string'){
118         try {
119             return decodeURIComponent(escape(property));
120         }
121         catch (error){
122             if (error instanceof URIError) {
123                 return property;
124             }
125         }
126     } else if (Array.isArray(property)){
127         let res = [];
128         for (let arr=0; arr < property.length; arr++){
129             res.push(decode(property[arr]));
130         }
131         return res;
132     } else if (typeof property === 'object'){
133         const keys = Object.keys(property);
134         if (keys.length){
135             let res = {};
136             for (let k=0; k < keys.length; k++ ){
137                 res[keys[k]] = decode(property[keys[k]]);
138             }
139             return res;
140         }
141         return property;
142     }
143     return property;
144 }
145
146 /**
147  * Turns a base64 encoded string into an uint8 array
148  * @param {String} base64 encoded String
149  * @returns {Uint8Array}
150  * adapted from https://gist.github.com/borismus/1032746
151  */
152 export function atobArray (base64) {
153     if (typeof (base64) !== 'string'){
154         throw gpgme_error('DECODE_FAIL');
155     }
156     const raw = window.atob(base64);
157     const rawLength = raw.length;
158     let array = new Uint8Array(new ArrayBuffer(rawLength));
159     for (let i = 0; i < rawLength; i++) {
160         array[i] = raw.charCodeAt(i);
161     }
162     return array;
163 }
164
165 /**
166  * Turns a Uint8Array into an utf8-String
167  * @param {*} array Uint8Array
168  * @returns {String}
169  * Taken and slightly adapted from
170  *  http://www.onicos.com/staff/iz/amuse/javascript/expert/utf.txt
171  * (original header:
172  *   utf.js - UTF-8 <=> UTF-16 convertion
173  *
174  *   Copyright (C) 1999 Masanao Izumo <iz@onicos.co.jp>
175  *   Version: 1.0
176  *   LastModified: Dec 25 1999
177  *   This library is free.  You can redistribute it and/or modify it.
178  *  )
179  */
180 export function Utf8ArrayToStr (array) {
181     let out, i, len, c, char2, char3;
182     out = '';
183     len = array.length;
184     i = 0;
185     if (array instanceof Uint8Array === false){
186         throw gpgme_error('DECODE_FAIL');
187     }
188     while (i < len) {
189         c = array[i++];
190         switch (c >> 4) {
191         case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
192             // 0xxxxxxx
193             out += String.fromCharCode(c);
194             break;
195         case 12: case 13:
196             // 110x xxxx   10xx xxxx
197             char2 = array[i++];
198             out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
199             break;
200         case 14:
201             // 1110 xxxx  10xx xxxx  10xx xxxx
202             char2 = array[i++];
203             char3 = array[i++];
204             out += String.fromCharCode(((c & 0x0F) << 12) |
205                             ((char2 & 0x3F) << 6) |
206                             ((char3 & 0x3F) << 0));
207             break;
208         default:
209             break;
210         }
211     }
212     return out;
213 }