Plugins/HttpsProxy/https-proxy.js

1       /*
2        * Sybase PhoneGap HTTPS proxy
3        *
4        * https-proxy.js
5        * This file will not be regenerated, so it is possible to modify it, but it
6        * is not recommended.
7        *
8        * Copyright (c) 2013 Sybase Inc. All rights reserved.
9        */
10       
11      /**
12       * Holds PhoneGap HTTP(S) proxy JavaScript
13       * @namespace
14       */
15      (function (window, undefined) {
16          if (!window.HttpsConnection) {
17              window.HttpsConnection = {};
18          }
19          
20          var HttpsConnection = window.HttpsConnection;
21          
22         /**
23          * Constant definitions for registration methods
24          */
25          
26         /**
27          * Constant indicating the operation failed with unknown error. Used in {@link anonymous.sendRequestErrorCBParameter}
28          * @memberOf HttpsConnection
29          * @type number 
30          */
31          HttpsConnection.ERR_UNKNOWN = -1;
32         /**
33          * Constant indicating the operation has invalid parameter. Used in {@link anonymous.sendRequestErrorCBParameter}
34          * @memberOf HttpsConnection
35          * @type number 
36          */
37          HttpsConnection.ERR_INVALID_PARAMETER_VALUE = -2;
38         /**
39          * Constant indicating the operation failed because of missing parameter. Used in {@link anonymous.sendRequestErrorCBParameter}
40          * @memberOf HttpsConnection
41          * @type number 
42          */
43          HttpsConnection.ERR_MISSING_PARAMETER = -3;
44         /**
45          * Constant indicating there is no such cordova action for the current service. Used in {@link anonymous.sendRequestErrorCBParameter}
46          * @memberOf HttpsConnection
47          * @type number 
48          */
49          HttpsConnection.ERR_NO_SUCH_ACTION = -100;
50         /**
51          * Constant indicating certificate from file keystore is not supported on current platform. Used in {@link anonymous.sendRequestErrorCBParameter}
52          * @memberOf HttpsConnection
53          * @type number 
54          */
55          HttpsConnection.ERR_FILE_CERTIFICATE_SOURCE_UNSUPPORTED    = -101;
56         /**
57          * Constant indicating certificate from system keystore is not supported on current platform. Used in {@link anonymous.sendRequestErrorCBParameter}
58          * @memberOf HttpsConnection
59          * @type number 
60          */
61          HttpsConnection.ERR_SYSTEM_CERTIFICATE_SOURCE_UNSUPPORTED = -102;
62         /**
63          * Constant indicating certificate from Afaria server is not supported on current platform. Used in {@link anonymous.sendRequestErrorCBParameter}
64          * @memberOf HttpsConnection
65          * @type number 
66          */
67          HttpsConnection.ERR_AFARIA_CERTIFICATE_SOURCE_UNSUPPORTED = -103;
68         /**
69          * Constant indicating the certificate with given alias could not be found. Used in {@link anonymous.sendRequestErrorCBParameter}
70          * @memberOf HttpsConnection
71          * @type number 
72          */
73          HttpsConnection.ERR_CERTIFICATE_ALIAS_NOT_FOUND = -104;
74         /**
75          * Constant indicating the certificate file could not be found. Used in {@link anonymous.sendRequestErrorCBParameter}
76          * @memberOf HttpsConnection
77          * @type number 
78          */
79          HttpsConnection.ERR_CERTIFICATE_FILE_NOT_EXIST = -105;
80         /**
81          * Constant indicating incorrect certificate file format. Used in {@link anonymous.sendRequestErrorCBParameter}
82          * @memberOf HttpsConnection
83          * @type number 
84          */
85          HttpsConnection.ERR_CERTIFICATE_INVALID_FILE_FORMAT = -106;
86         /**
87          * Constant indicating failed in getting certificate. Used in {@link anonymous.sendRequestErrorCBParameter}
88          * @memberOf HttpsConnection
89          * @type number 
90          */
91          HttpsConnection.ERR_GET_CERTIFICATE_FAILED = -107;
92         /**
93          * Constant indicating the provided certificate failed validation on server side. Used in {@link anonymous.sendRequestErrorCBParameter}
94          * @memberOf HttpsConnection
95          * @type number 
96          */
97          HttpsConnection.ERR_CLIENT_CERTIFICATE_VALIDATION = -108;
98         /**
99          * Constant indicating the server certificate failed validation on client side. Used in {@link anonymous.sendRequestErrorCB}
100         * @memberOf HttpsConnection
101         * @type number 
102         */
103         HttpsConnection.ERR_SERVER_CERTIFICATE_VALIDATION = -109;
104         /**
105         * Constant indicating the server request failed. Used in {@link anonymous.sendRequestErrorCB}
106         * @memberOf HttpsConnection
107         * @type number 
108         */
109         HttpsConnection.ERR_SERVER_REQUEST_FAILED = -110;
110        /**
111         * Constant indicating timeout error while connecting to the server. Used in {@link anonymous.sendRequestErrorCB}
112         * @memberOf HttpsConnection
113         * @type number 
114         */
115         HttpsConnection.ERR_HTTP_TIMEOUT = -120;
116         
117         /**
118          * Create certificate source description object for certificates from a keystore file. 
119          * <b> Not supported on Blackberry platform </b>
120          * @class
121          * @public
122          * @memberOf HttpsConnection
123          * @param {string} Path Path of the keystore file. For iOS client, it first tries to load the 
124          *                 relative file path from application's Documents folder; if it fails, then tries
125          *                 to load the file path from application's main bundle. In addition, before trying 
126          *                 to load the certificate from file system, iOS client first checks whether the 
127          *                 specified certificate key already exists in the key store, if so, it just loads 
128          *                 the existing certificate from key store, instead of loading the certificate from 
129          *                 file system.
130          * @param {string} Password Password of the keystore.
131          * @param {string} CertificateKey An unique key that will be used to locate the certificate. 
132          */
133         HttpsConnection.CertificateFromFile = function (Path, Password, CertificateKey) {
134             this.Source = "FILE";
135             this.Path = Path;
136             this.Password = Password;
137             this.CertificateKey = CertificateKey;
138         };
139         
140         /**
141          * Create certificate source description object for certificates from Afaria.
142          * @class
143          * @memberOf HttpsConnection
144          * @public
145          * @param {string} CN Common Name (CN) for CA/SCEP protocol. For iOS, the retrieved certificate is 
146          *                    stored in the key store with the common name as the certificate key, the 
147          *                    following requests for the same common name will just load the saved certificate 
148          *                    from key store, instead of sending a new request to Afaria server. 
149          * @param {string} [ChallengeCode] Challenge code for CA/SCEP protocol.
150          */
151         HttpsConnection.CertificateFromAfaria = function (CN, ChallengeCode) {
152             this.Source = "AFARIA";
153             this.CN = CN;
154             this.ChallengeCode = ChallengeCode;
155         };
156         
157         /**
158          * Create certificate source description object for certificates from system keystore (Keystore in BB, Keychain in iOS and Android). 
159          * The certificateKey is not used on the BB platform. BB will prompt the user to select a certificate if a certificate was not already
160          * used for the server connection.
161          * @class
162          * @memberOf HttpsConnection
163          * @public
164          * @param {string} CertificateKey An unique key that will be used to locate the certificate. Not used in BB platform.
165          */
166         HttpsConnection.CertificateFromStore = function (CertificateKey) {
167             this.Source = "SYSTEM";
168             this.CertificateKey = CertificateKey;
169         };
170         
171         HttpsConnection.MSG_MISSING_PARAMETER = "Missing a required parameter: ";
172         HttpsConnection.MSG_INVALID_PARAMETER_VALUE = "Invalid Parameter Value for parameter: ";
173         
174         /**
175          * @private
176          */
177         HttpsConnection.validateCertSource = function(certSource, errorCB) {
178             if (!certSource) {
179                 // The certificate is not present, so just ignore it.
180                 return true;
181             }
182                 
183             // errorCB required.
184             // First check this one. We may need it to return errors
185             if (errorCB && (typeof errorCB !== "function")) {
186                 console.log("HttpsConnection Error: errorCB is not a function");
187                 return false;
188             }
189             
190             try    {
191                 // First check whether it is an object
192                 if (typeof certSource !== "object") {
193                     errorCB({errorCode : HttpsConnection.ERR_INVALID_PARAMETER_VALUE, description : HttpsConnection.MSG_INVALID_PARAMETER_VALUE + "certSource"});
194                     return false;
195                 }
196                 
197                 if (certSource.Source === "FILE") {
198                     if (!certSource.Path) {
199                         errorCB({errorCode : HttpsConnection.ERR_INVALID_PARAMETER_VALUE, description : HttpsConnection.MSG_MISSING_PARAMETER + "keystore path"});
200                         return false;
201                     }
202                     
203                     if (typeof certSource.Path !== "string") {
204                         errorCB({errorCode : HttpsConnection.ERR_INVALID_PARAMETER_VALUE, description : HttpsConnection.MSG_INVALID_PARAMETER_VALUE + "keystore path"});
205                         return false;
206                     }
207                     
208                     if (!certSource.Password) {
209                         errorCB({errorCode : HttpsConnection.ERR_INVALID_PARAMETER_VALUE, description : HttpsConnection.MSG_MISSING_PARAMETER + "keystore password"});
210                         return false;
211                     }
212                     
213                     if (typeof certSource.Password !== "string") {
214                         errorCB({errorCode : HttpsConnection.ERR_INVALID_PARAMETER_VALUE, description : HttpsConnection.MSG_INVALID_PARAMETER_VALUE + "keystore password"});
215                         return false;
216                     }
217                     
218                     if (!certSource.CertificateKey) {
219                         errorCB({errorCode : HttpsConnection.ERR_INVALID_PARAMETER_VALUE, description : HttpsConnection.MSG_MISSING_PARAMETER + "certificate key"});
220                         return false;
221                     }
222                     
223                     if (typeof certSource.CertificateKey !== "string") {
224                         errorCB({errorCode : HttpsConnection.ERR_INVALID_PARAMETER_VALUE, description : HttpsConnection.MSG_INVALID_PARAMETER_VALUE + "certificate key"});
225                         return false;
226                     }
227                 } else if (certSource.Source === "SYSTEM") {
228                     if (!certSource.CertificateKey) {
229                         errorCB({errorCode : HttpsConnection.ERR_INVALID_PARAMETER_VALUE, description : HttpsConnection.MSG_MISSING_PARAMETER + "certificate key"});
230                         return false;
231                     }
232                     
233                     if (typeof certSource.CertificateKey !== "string") {
234                         errorCB({errorCode : HttpsConnection.ERR_INVALID_PARAMETER_VALUE, description : HttpsConnection.MSG_INVALID_PARAMETER_VALUE + "certificate key"});
235                         return false;
236                     }
237                 } else if (certSource.Source === "AFARIA") {
238                     if (!certSource.CN) {
239                         errorCB({errorCode : HttpsConnection.ERR_INVALID_PARAMETER_VALUE, description : HttpsConnection.MSG_MISSING_PARAMETER + "common name"});
240                         return false;
241                     }
242                     
243                     if (typeof certSource.CN !== "string") {
244                         errorCB({errorCode : HttpsConnection.ERR_INVALID_PARAMETER_VALUE, description : HttpsConnection.MSG_INVALID_PARAMETER_VALUE + "common name"});
245                         return false;
246                     }
247                     
248                     if (!certSource.ChallengeCode) {
249                         errorCB({errorCode : HttpsConnection.ERR_INVALID_PARAMETER_VALUE, description : HttpsConnection.MSG_MISSING_PARAMETER + "Afaria challenge code"});
250                         return false;
251                     }
252                     
253                     if (typeof certSource.ChallengeCode !== "string") {
254                         errorCB({errorCode : HttpsConnection.ERR_INVALID_PARAMETER_VALUE, description : HttpsConnection.MSG_INVALID_PARAMETER_VALUE + "Afaria challenge code"});
255                         return false;
256                     }
257                 } else {
258                     errorCB({errorCode : HttpsConnection.ERR_INVALID_PARAMETER_VALUE, description : HttpsConnection.MSG_INVALID_PARAMETER_VALUE + "certSource"});
259                     return false;
260                 }
261                     
262                 return true;
263             } catch (ex) {
264                 errorCB({errorCode : HttpsConnection.ERR_INVALID_PARAMETER_VALUE, description : HttpsConnection.MSG_INVALID_PARAMETER_VALUE + "certSource"});
265             }
266         };
267         
268         /**
269          * Send a HTTP(S) request to a remote server.
270          * @memberOf HttpsConnection
271          * @public
272          * @param {string} method Standard HTTP request method name.
273          * @param {string} url The http url with format http(s)://[user:password]@hostname[:port]/path.
274          * @param {Object} header HTTP header to be sent to server. This is an Object. Can be null.
275          * @param {string} requestBody Data to be sent to server with the request. It’s a string value. Can be null.
276          * @param {anonymous.sendRequestSuccessCB} successCB Callback method upon success.
277          * @param {anonymous.sendRequestErrorCB} errorCB Callback method upon failure.
278          * @param {string} [user] User ID for basic authentication.
279          * @param {string} [password] User password for basic authentication.
280          * @param {number} [timeout] Timeout setting in seconds.
281          * @param {Object} [certSource] Certificate description object. It can be one of {@link HttpsConnection.CertificateFromFile},
282          * {@link HttpsConnection.CertificateFromStore}, or {@link HttpsConnection.CertificateFromAfaria}.
283          * @return {anonymous.abort} A JavaScript function object to cancel the operation.
284          * @example
285          * // To send a post request to server, call the method
286          * HttpsConnection.sendRequest("POST", "http://www.google.com", null, "THIS IS THE BODY", function (data) {
287          *        alert("Status: " + JSON.stringify(data.status));
288          *        alert("Headers: " + JSON.stringify(data.headers));
289          *        alert("Response: " + JSON.stringify(data.response));
290          *        }, function (data) {
291          *        alert("Failed: " + JSON.stringify(data));}};
292          * // To send a post request to server with headers, call the method
293          * HttpsConnection.sendRequest("POST", url, {HeaderName : "Header value"}, "THIS IS THE BODY", successCB, errorCB);
294          * // To send a post request to server with basic authentication, call the method
295          * HttpsConnection.sendRequest("POST", url, headers, "THIS IS THE BODY", successCB, errorCB, "username", "password");
296          * // To send a post request to server with mutual authentication, call the method
297          * HttpsConnection.sendRequest("POST", "https://hostname", headers, "THIS IS THE BODY", successCB, errorCB, null, 
298     	 *     null, 0, new CertificateFromFile("/mnt/sdcard/my.keystore", "password", "mykey"));
299          */
300         HttpsConnection.sendRequest = function (method, url, header, requestBody, successCB, errorCB, user, password, timeout, certSource){
301     
302             // errorCB required.
303             // First check this one. We may need it to return errors
304             if (!errorCB || (typeof errorCB !== "function")) {
305                 console.log("HttpsConnection Error: errorCB is not a function");
306                 // if error callback is invalid, throw an exception to notify the caller
307                 throw new Error("HttpsConnection Error: errorCB is not a function");
308                 return;
309             }
310             
311             // method required
312             if (!method) {
313                 console.log("HttpsConnection Error: method is required");
314                 errorCB({errorCode : HttpsConnection.ERR_MISSING_PARAMETER, description : HttpsConnection.MSG_MISSING_PARAMETER + "method"});
315                 return;
316             }
317     
318             // We only support GET, POST, HEAD, PUT, DELETE method
319             if (method !== "GET" && method !== "POST" && method !== "HEAD" && method !== "PUT" && method !== "DELETE") {
320                 console.log("Invalid Parameter Value for parameter: " + method);
321                 errorCB({errorCode : HttpsConnection.ERR_INVALID_PARAMETER_VALUE, description : HttpsConnection.MSG_INVALID_PARAMETER_VALUE + "method"});
322                 return;
323             }
324             
325             // url required
326             if (!url) {
327                 console.log("HttpsConnection Error: url is required");
328                 errorCB({errorCode : HttpsConnection.ERR_MISSING_PARAMETER, description : HttpsConnection.MSG_MISSING_PARAMETER + "url"});
329                 return;
330             }
331     
332             // successCB required
333             if (!successCB) {
334                 console.log("HttpsConnection Error: successCB is required");
335                 errorCB({errorCode : HttpsConnection.ERR_MISSING_PARAMETER, description : HttpsConnection.MSG_MISSING_PARAMETER + "successCB"});
336                 return;
337             }
338     
339             if (typeof successCB !== "function") {
340                 console.log("HttpsConnection Error: successCB is not a function");
341                 errorCB({errorCode : HttpsConnection.ERR_INVALID_PARAMETER_VALUE, description : HttpsConnection.MSG_INVALID_PARAMETER_VALUE + "successCB"});
342                 return;
343             }
344             
345             if (user && typeof user !== "string") {
346                 errorCB({errorCode : HttpsConnection.ERR_INVALID_PARAMETER_VALUE, description : HttpsConnection.MSG_INVALID_PARAMETER_VALUE + "user"});
347                 return;
348             }
349             
350             if (password && typeof password !== "string") {
351                 errorCB({errorCode : HttpsConnection.ERR_INVALID_PARAMETER_VALUE, description : HttpsConnection.MSG_INVALID_PARAMETER_VALUE + "password"});
352                 return;
353             }
354             
355             if (timeout && typeof timeout !== "number") {
356                 errorCB({errorCode : HttpsConnection.ERR_INVALID_PARAMETER_VALUE, description : HttpsConnection.MSG_INVALID_PARAMETER_VALUE + "timeout"});
357                 return;
358             }
359     
360             if (!HttpsConnection.validateCertSource(certSource, errorCB)) {
361                 return;
362             }
363     
364             try {
365                 var client = new HttpsConnection.Client(method, url, header, requestBody, successCB, errorCB, user, password, timeout, certSource);
366                 return client.send();
367             } catch (ex){
368                 errorCB({errorCode : HttpsConnection.ERR_UNKNOWN, description : ex.message});
369             }
370         };
371         
372             /**
373          * Send a HTTP(S) GET request to a remote server.
374          * @memberOf HttpsConnection
375          * @public
376          * @param {string} url The http url with format http(s)://[user:password]@hostname[:port]/path.
377          * @param {Object} header HTTP header to be sent to server. This is an Object. Can be null.
378          * @param {string} requestBody Data to be sent to server with the request. It’s a string value. Can be null.
379          * @param {anonymous.sendRequestSuccessCB} successCB Callback method upon success.
380          * @param {anonymous.sendRequestErrorCB} [errorCB] Callback method upon failure.
381          * @param {string} [user] User ID for basic authentication.
382          * @param {string} [password] User password for basic authentication.
383          * @param {number} [timeout] Timeout setting in seconds.
384          * @param {Object} [certSource] Certificate description object. It can be one of {@link HttpsConnection.CertificateFromFile},
385          * {@link HttpsConnection.CertificateFromStore}, or {@link HttpsConnection.CertificateFromAfaria}.
386          * @return {anonymous.abort} A JavaScript function object to cancel the operation.
387          * @example
388          * // To send a get request to server, call the method
389          * HttpsConnection.get("http://www.google.com", null, function (data) {
390          *            alert("Status: " + JSON.stringify(data.status));
391          *            alert("Headers: " + JSON.stringify(data.headers));
392          *          if (data.responseText){
393          *                 alert("Response: " + JSON.stringify(data.responseText));
394          *          }
395          *        }, 
396          *      function (error) {
397          *             alert("Failed: " + JSON.stringify(error));
398          *      }};
399          * // To send a get request to server with headers, call the method
400          * HttpsConnection.get(url, {HeaderName : "Header value"}, successCB, errorCB);
401          * // To send a get request to server with basic authentication, call the method
402          * HttpsConnection.get(url, headers, successCB, errorCB, "username", "password");
403          * // To send a get request to server with mutual authentication, call the method
404          * HttpsConnection.get("https://hostname", headers, successCB, errorCB, null, null, 0, 
405     	 *     new CertificateFromFile("/mnt/sdcard/my.p12", "password", "mykey"));
406          */
407         HttpsConnection.get = function (url, header, successCB, errorCB, user, password, timeout, certSource){
408             return HttpsConnection.sendRequest("GET", url, header, null, successCB, errorCB, user, password, timeout, certSource);
409         };
410     
411         /**
412          * Delete cached certificate from keychain. iOS client will always try the cached certificate first if it is available before requesting the certificate from
413          * afaria server or loading the certificate from file system. In case the cached certificate is no longer valid, use this method to delete it from keychain
414          * <b> Only supported by iOS platform </b>
415          * @memberOf HttpsConnection
416          * @public
417          * @param {anonymous.sendRequestSuccessCB} successCB Callback method upon success.
418          * @param {anonymous.sendRequestErrorCB} [errorCB] Callback method upon failure.
419          * @param {string} certificateKey The key of the certificate to be deleted.
420          */
421          HttpsConnection.deleteCertificateFromStore = function (successCB, errorCB, certificateKey) {
422             cordova.exec(successCB, errorCB, "HttpsProxy", "deleteCertificateFromStore", [certificateKey]);
423          };
424      
425     
426         /**
427          * @private
428          */
429         HttpsConnection.Client = function ( method, url, header, requestBody, successCB, errorCB, user, password, timeout, certSource ) 
430         {
431           //ios plugin parameter does not support object type, convert Header and CertSource to JSON string
432           if (device.platform === "iOS" || (device.platform && device.platform.indexOf("iP") === 0 ))
433           {
434              if (header) {
435                 header = JSON.stringify(header);
436              }
437              if (certSource) {
438                 certSource = JSON.stringify(certSource);
439              }
440            }
441      
442             this.Method = method;
443             this.Url = url;
444             this.Header = header;
445             this.RequestBody = requestBody;
446             this.SuccessCB = successCB;
447             this.ErrorCB = errorCB;
448             this.User = user;
449             this.Password = password;
450             this.Timeout = timeout;
451             this.CertSource = certSource;
452             this.IsAbort = false;
453             
454             this.abort = function () 
455             {
456                 this.IsAbort = true;
457             };
458     
459             this.send = function () 
460             {
461                 var args = [this.Method, this.Url, this.Header, this.RequestBody, this.User, this.Password, this.Timeout, this.CertSource];
462     
463                 var me = this;
464                 
465                 var successCallBack = function(data) 
466                 {
467                     if (me.IsAbort === true) 
468                     {
469                         return;
470                     }
471                     
472                     successCB(data);
473                 };
474                 
475                 var errorCallBack = function(data) 
476                 {
477                     if (me.IsAbort === true) 
478                     {
479                         return;
480                     }
481                     
482                     errorCB(data);
483                 };
484     
485                 cordova.exec(successCallBack, errorCallBack, "HttpsProxy", "sendRequest", args);
486                 
487                 return this.abort;
488             };
489         };
490     
491     })(this);
492     
493     /**
494      * Used to group anonymous objects and callback functions used as method parameters. Methods and fields in this
495      * namespace cannot be instantiated. Used for API docs generation only.
496      * @namespace
497      */
498     anonymous = (typeof anonymous === "undefined" || !anonymous) ? {} : anonymous;      // SUP 'namespace'
499     
500     /**
501      * Callback function that will be invoked HttpsConnection.get()/sendRequest() succeeded.
502      *
503      * @name anonymous.sendRequestSuccessCB
504      *
505      * @param {anonymous.sendRequestSuccessCBParameter} data The response data object.
506      * @function
507      */
508     
509     /**
510      * Callback function that will be invoked HttpsConnection.get()/sendRequest() failed.
511      *
512      * @name anonymous.sendRequestErrorCB
513      * @param {anonymous.sendRequestErrorCBParameter} data The error object. 
514      * @function
515      */
516      
517      /**
518       * Object used in {@link anonymous.sendRequestSuccessCB} function.
519       * @class
520       * @name anonymous.sendRequestSuccessCBParameter
521       * @property {number} status The HTTP status code
522       * @property {object} headers An object that contains headerKey = value pairs.
523       * @property {string} [responseText] The text response. This parameter is present only if the response is a text response.
524       * @property {string} [responseBase64] Base64 encoded representation of the binary response. This parameter is included only
525       *                                  if the response is a binary response. 
526       * @property {object} [clientError] An optional object that contains the authentication error. It is an object of {@link anonymous.sendRequestErrorCBParameter}.
527       */
528       
529       /**
530       * Object used in {@link anonymous.sendRequestErrorCB} function.
531       * @class
532       * @name anonymous.sendRequestErrorCBParameter
533       * @property {number} errorCode Predefined error code
534       * @property {string} description The description of the error
535       * @property {number} [nativeErrorCode] The native error code reported from Afaria, device, etc (optional)
536       */
537     
538     
539     /**
540      * JavaScript function to abort the HTTP(S) request
541      *
542      * @name anonymous.abort
543      *
544      * @function
545      */
546