Compare commits

..

51 Commits

Author SHA1 Message Date
Sefa Ilkimen
cc3e70771c release v2.2.0 2019-09-29 23:39:40 +02:00
Sefa Ilkimen
9e892119cc feat(#127): adding multiple file upload
- implement multi uploading files for ios
- update readme
2019-09-29 21:43:58 +02:00
Sefa Ilkimen
f93f69e0aa feat(#127): adding multiple file upload
- remove www interface function "uploadFiles" as it confuses more than it helps
- implement multi uploading files for android
- add e2e spec
2019-09-29 20:37:36 +02:00
Sefa Ilkimen
4ace394464 feat(#127): adding multiple file upload
- fix missing option params
2019-09-27 13:25:14 +02:00
Sefa Ilkimen
ae3e821639 feat(#127): adding multiple file upload
- prepare www interface
- implement options validation
- implement specs for js interface
- improve error messages
2019-09-26 16:37:19 +02:00
Sefa Ilkimen
e17768041c fix: missing file reference in plugin.xml 2019-09-26 04:44:40 +02:00
Sefa Ilkimen
34a559bc6e feature #253: add support for response type "json" 2019-09-26 04:31:08 +02:00
Sefa Ilkimen
2ce1fc407d feature #239: add enumeration style object for error codes 2019-09-26 03:00:30 +02:00
Sefa Ilkimen
4b73c3762c Merge pull request #243 from joellimberg/patch-1
Readme typo fix: ”plugin.” → “plugin.http.”
2019-07-30 23:46:05 +02:00
Joel Limberg
5a9c176bca Readme typo fix: ”plugin.” → “plugin.http.” 2019-07-26 22:07:46 +03:00
Sefa Ilkimen
40aa944d0f fix #231: remove broken link 2019-07-23 23:02:34 +02:00
Sefa Ilkimen
c7feb7aca3 improve readme 2019-07-23 22:39:56 +02:00
Sefa Ilkimen
680105175b Update issue templates 2019-06-25 02:26:16 +02:00
Sefa Ilkimen
07811d0380 Update issue templates 2019-06-25 02:25:20 +02:00
Sefa Ilkimen
026e8589c2 Update issue templates 2019-06-24 17:49:08 +02:00
Sefa Ilkimen
e1592001af Update issue templates 2019-06-24 17:36:30 +02:00
Sefa Ilkimen
f057b126ba release v2.1.1 2019-06-14 17:01:30 +02:00
Sefa Ilkimen
2b567cdf32 Fix #224: responseType "arraybuffer" and "blob" not working on browser platform 2019-06-14 16:43:36 +02:00
Sefa Ilkimen
d99c7bb154 release v2.1.0 2019-06-14 02:57:16 +02:00
Sefa Ilkimen
336a56be1c update changelog 2019-06-14 02:56:59 +02:00
Sefa Ilkimen
8b962cc350 Merge pull request #205 from BeMyEye/master
add OKHTTP_VERSION plugin variable
2019-06-14 02:37:52 +02:00
Sefa Ilkimen
13bf4666b0 feature #171: support for responseType "blob" 2019-06-14 02:29:45 +02:00
Sefa Ilkimen
1fc3d6230c - update readme
- implement response type support for browser platform
2019-06-14 00:52:27 +02:00
Sefa Ilkimen
85346e0381 Merge branch 'binary_response'
# Conflicts:
#	test/e2e-specs.js
2019-06-13 16:44:35 +02:00
Sefa Ilkimen
c9f8c8b66a release v2.0.11 2019-06-13 16:12:50 +02:00
Sefa Ilkimen
9b26a0f031 fix #221: headers are not set on Android when request fails due to non-success status code 2019-06-13 16:00:26 +02:00
Sefa Ilkimen
298c031433 release v2.0.10 2019-06-13 00:20:01 +02:00
Sefa Ilkimen
eab6acf85c - update changelog
- increment version
2019-06-13 00:12:09 +02:00
Sefa Ilkimen
8cf0d21a7a add response type "arraybuffer" support for iOS 2019-06-12 23:43:40 +02:00
Sefa Ilkimen
f91727e14a add response type "arraybuffer" support for android 2019-06-12 23:01:15 +02:00
Sefa Ilkimen
7b485507dc - use appium 1.9.1 for saucelabs
- update readme
2019-06-03 13:49:51 +02:00
Sefa Ilkimen
03b0abb74e using updated appium version for saucelabs 2019-06-03 13:32:45 +02:00
Sefa Ilkimen
c3d60c37bf fix #218: headers are used as params on browser platform 2019-06-03 11:59:35 +02:00
Sefa Ilkimen
54ef8ae169 release v2.0.9 2019-04-15 18:35:51 +02:00
Sefa Ilkimen
c83940a8f6 deprecate "disableRedirect" in favor of "setFollowRedirect" 2019-04-15 18:09:34 +02:00
Sefa Ilkimen
ccabbf2a29 - add some specs
- prepare new "follow redirect" config interface
2019-04-15 04:23:03 +02:00
Sefa Ilkimen
7ba0852698 fix JS specs 2019-04-15 03:59:47 +02:00
Sefa Ilkimen
4f3ff9097f WIP: implementing X509 client cert authentication (android "buffer" mode) 2019-04-15 03:18:59 +02:00
Sefa Ilkimen
620ce3f81c Fix #204: broken support for cordova-android < 7.0 2019-04-15 02:47:14 +02:00
cvaliere
6e68bf4dfe Merge pull request #1 from BeMyEye/use-preference-OKHTTP_VERSION
use OKHTTP_VERSION for okhttp-urlconnection
2019-04-11 17:35:14 +02:00
Kevin Simon
87f0f3600c use OKHTTP_VERSION for okhttp-urlconnection 2019-04-11 17:24:27 +02:00
Sefa Ilkimen
0bf39650e1 release v2.0.8 2019-04-10 12:06:17 +02:00
Sefa Ilkimen
d444363a8e - fix #198: Cookie header is always passed even if there is no cookie
- some cleanup
2019-04-09 17:25:55 +02:00
Sefa Ilkimen
13976bbe43 Update change log and version number 2019-04-08 20:16:24 +02:00
Sefa Ilkimen
70c8b9bb32 Fix #201: browser implementation broken due to broken dependency 2019-04-08 20:12:36 +02:00
Sefa Ilkimen
4b964e8b7c Fix #197: iOS crashes on multiple HTTPS downloadFile() calls (reverting a8e3637) 2019-04-08 19:14:39 +02:00
Sefa Ilkimen
56f8b41f4f Fix #189: error code mappings for TLS handshake problems are not precise 2019-04-08 17:58:39 +02:00
Sefa Ilkimen
e1e720fe2d Add missing handler on browser platform 2019-04-08 17:31:38 +02:00
Sefa Ilkimen
a1b6ea94f0 Fix #200: remove string switch and use ugly if ... else 😢 to be compatible with Java target 6 2019-04-05 16:48:18 +02:00
Sefa Ilkimen
d977392a49 - some cleanup
- deprecate "setSSLCertMode" in favor of "setServerTrustMode"
2019-04-05 16:26:04 +02:00
Sefa Ilkimen
8d28f4ab80 WIP: implementing X509 client cert authentication 2019-04-05 05:22:34 +02:00
61 changed files with 2340 additions and 961 deletions

33
.github/ISSUE_TEMPLATE/--bug-report.md vendored Normal file
View File

@@ -0,0 +1,33 @@
---
name: "\U0001F41BBug report"
about: Create a report to help us improve
title: "[Bug] [platform] your issue title"
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is and what you expected to happen.
**System info**
- affected HTTP plugin version: [e.g. 2.1.1]
- affected platform(s) and version(s): [e.g. iOS 12.2]
- affected device(s): [e.g. iPhone 8]
- cordova version: [e.g. 6.5.0]
- cordova platform version(s): [e.g. android 7.0.0, browser 5.0.3]
**Are you using ionic-native-wrapper?**
- ionic-native-wrapper version: [e.g. 5.8.0]
- did you check [ionic-native issue tracker](https://github.com/ionic-team/ionic-native/issues) for your problem?
**Minimum viable code to reproduce**
If applicable, add formatted sample coding to help explain your problem.
e.g.:
```js
cordova.plugin.http.setDataSerializer('urlencoded');
```
**Screenshots**
If applicable, add screenshots to help explain your problem.

View File

@@ -0,0 +1,21 @@
---
name: "\U0001F680Feature request"
about: Suggest an idea for this project
title: "[Feature]"
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. e.g. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen. If applicable, add formatted sample coding to help explain your idea.
```js
// do some fancy stuff here
```
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

View File

@@ -0,0 +1,18 @@
---
name: "\U0001F914Support question"
about: Ask the community
title: ''
labels: question
assignees: ''
---
**You've got questions?**
We primarily use GitHub as an issue tracker; for usage and support questions, please check out these resources below. Thanks! 😁
* README.md: https://github.com/silkimen/cordova-plugin-advanced-http/blob/master/README.md
* StackOverflow: https://stackoverflow.com/questions/tagged/cordova-plugin-advanced-http using the tag `cordova-plugin-advanced-http`
* Wiki: https://github.com/silkimen/cordova-plugin-advanced-http/wiki
And don't forget: If you get help, help others. Good karma rulez!

3
.gitignore vendored
View File

@@ -1,5 +1,6 @@
node_modules/**
test/app-template/www/certificates/*.cer
test/e2e-app-template/www/certificates/*.cer
test/e2e-app-template/www/certificates/*.pkcs
tags
.zedstate
npm-debug.log

View File

@@ -1,5 +1,45 @@
# Changelog
## 2.2.0
- Feature #239: add enumeration style object for error codes
- Feature #253: add support for response type "json"
- Feature #127: add multiple file upload (thanks SDA SE Open Industry Solutions and nilswitschel)
## 2.1.1
- Fixed #224: response type "arraybuffer" and "blob" not working on browser platform
## 2.1.0
- Feature #216: Support for response type `arraybuffer`
- Feature #171: Support for response type `blob`
- Feature #205: Add preference for configuring OKHTTP version (thanks RougeCiel)
## 2.0.11
- Fixed #221: headers not set on Android when request fails due to non-success status code
## 2.0.10
- Fixed #218: headers are used as params on browser platform
## 2.0.9
- Fixed #204: broken support for cordova-android < 7.0
- :warning: **Deprecation**: Deprecated "disableRedirect" in favor of "setFollowRedirect"
## 2.0.8
- Fixed #198: cookie header is always passed even if there is no cookie
- Fixed #201: browser implementation is broken due to broken dependency
- Fixed #197: iOS crashes when multiple request are done simultaneously (reverted a8e3637)
- Fixed #189: error code mappings are not precise
- Fixed #200: compatibility with Java 6 is broken due to string switch on Android
- :warning: **Deprecation**: Deprecated "setSSLCertMode" in favor of "setServerTrustMode"
## 2.0.7
- Fixed #195: URLs are double-encoded on Android

View File

@@ -14,7 +14,7 @@ This is a fork of [Wymsee's Cordova-HTTP plugin](https://github.com/wymsee/cordo
- Background threading - all requests are done in a background thread.
- Handling of HTTP code 401 - read more at [Issue CB-2415](https://issues.apache.org/jira/browse/CB-2415).
- SSL Pinning - read more at [LumberBlog](http://blog.lumberlabs.com/2012/04/why-app-developers-should-care-about.html).
- SSL Pinning
## Updates
@@ -93,7 +93,7 @@ You can choose one of these:
* `json`: send data as JSON encoded content in body (content type "application/json")
* `utf8`: send data as plain UTF8 encoded string in body (content type "plain/text")
You can also override the default content type headers by specifying your own headers (see [setHeader](#setHeader)).
This defaults to `urlencoded`. You can also override the default content type headers by specifying your own headers (see [setHeader](#setHeader)).
__Caution__: `urlencoded` does not support serializing deep structures whereas `json` does.
@@ -104,6 +104,13 @@ Set how long to wait for a request to respond, in seconds.
cordova.plugin.http.setRequestTimeout(5.0);
```
### setFollowRedirect<a name="setFollowRedirect"></a>
Configure if it should follow redirects automatically. This defaults to true.
```js
cordova.plugin.http.setFollowRedirect(true);
```
### getCookieString
Returns saved cookies (as string) matching given URL.
@@ -128,13 +135,13 @@ cordova.plugin.http.clearCookies();
## Asynchronous Functions
These functions all take success and error callbacks as their last 2 arguments.
### setSSLCertMode<a name="setSSLCertMode"></a>
Set SSL Cert handling mode, being one of the following values:
### setServerTrustMode<a name="setServerTrustMode"></a>
Set server trust mode, being one of the following values:
* `default`: default SSL cert handling using system's CA certs
* `default`: default SSL trustship and hostname verification handling using system's CA certs
* `legacy`: use legacy default behavior (< 2.0.3), excluding user installed CA certs (only for Android)
* `nocheck`: disable SSL cert checking, trusting all certs (meant to be used only for testing purposes)
* `pinned`: trust only provided certs
* `nocheck`: disable SSL certificate checking and hostname verification, trusting all certs (meant to be used only for testing purposes)
* `pinned`: trust only provided certificates
To use SSL pinning you must include at least one `.cer` SSL certificate in your app project. You can pin to your server certificate or to one of the issuing CA certificates. Include your certificate in the `www/certificates` folder. All `.cer` files found there will be loaded automatically.
@@ -142,46 +149,41 @@ To use SSL pinning you must include at least one `.cer` SSL certificate in your
```js
// enable SSL pinning
cordova.plugin.http.setSSLCertMode('pinned', function() {
cordova.plugin.http.setServerTrustMode('pinned', function() {
console.log('success!');
}, function() {
console.log('error :(');
});
// use system's default CA certs
cordova.plugin.http.setSSLCertMode('default', function() {
cordova.plugin.http.setServerTrustMode('default', function() {
console.log('success!');
}, function() {
console.log('error :(');
});
// disable SSL cert checking, only meant for testing purposes, do NOT use in production!
cordova.plugin.http.setSSLCertMode('nocheck', function() {
cordova.plugin.http.setServerTrustMode('nocheck', function() {
console.log('success!');
}, function() {
console.log('error :(');
});
```
### disableRedirect (deprecated)
This function was deprecated in 2.0.9. Use ["setFollowRedirect"](#setFollowRedirect) instead.
### setSSLCertMode (deprecated)
This function was deprecated in 2.0.8. Use ["setServerTrustMode"](#setServerTrustMode) instead.
### enableSSLPinning (obsolete)
This function was removed in 2.0.0. Use ["setSSLCertMode"](#setSSLCertMode) to enable SSL pinning (mode "pinned").
This function was removed in 2.0.0. Use ["setServerTrustMode"](#setServerTrustMode) to enable SSL pinning (mode "pinned").
### acceptAllCerts (obsolete)
This function was removed in 2.0.0. Use ["setSSLCertMode"](#setSSLCertMode) to disable checking certs (mode "nocheck").
### disableRedirect
If set to `true`, it won't follow redirects automatically. This defaults to false.
```js
cordova.plugin.http.disableRedirect(true, function() {
console.log('success!');
}, function() {
console.log('error :(');
});
```
This function was removed in 2.0.0. Use ["setServerTrustMode"](#setServerTrustMode) to disable checking certs (mode "nocheck").
### validateDomainName (obsolete)
This function was removed in v1.6.2. Domain name validation is disabled automatically when you set SSL cert mode to "nocheck".
This function was removed in v1.6.2. Domain name validation is disabled automatically when you set server trust mode to "nocheck".
### removeCookies
Remove all cookies associated with a given URL.
@@ -190,8 +192,10 @@ Remove all cookies associated with a given URL.
cordova.plugin.http.removeCookies(url, callback);
```
### sendRequest
Execute a HTTP request. Takes a URL and an options object. This is the internally used implementation of the following shorthand functions ([post](#post), [get](#get), [put](#put), [patch](#patch), [delete](#delete), [head](#head), [uploadFile](#uploadFile) and [downloadFile](#downloadFile)). You can use this function, if you want to override global settings for each single request.
### sendRequest<a name="sendRequest"></a>
Execute a HTTP request. Takes a URL and an options object. This is the internally used implementation of the following shorthand functions ([post](#post), [get](#get), [put](#put), [patch](#patch), [delete](#delete), [head](#head), [uploadFile](#uploadFile) and [downloadFile](#downloadFile)). You can use this function, if you want to override global settings for each single request. Check the documentation of the respective shorthand function for details on what is returned on success and failure.
:warning: You need to encode the base URL yourself if it contains special characters like whitespaces. You can use `encodeURI()` for this purpose.
The options object contains following keys:
@@ -200,10 +204,16 @@ The options object contains following keys:
* `data`: payload to be send to the server (only applicable on `post`, `put` or `patch` methods)
* `params`: query params to be appended to the URL (only applicable on `get`, `head`, `delete`, `upload` or `download` methods)
* `serializer`: data serializer to be used (only applicable on `post`, `put` or `patch` methods), defaults to global serializer value, see [setDataSerializer](#setDataSerializer) for supported values
* `responseType`: expected response type, defaults to `text`, needs to be one of the following values:
* `text`: data is returned as decoded string, use this for all kinds of string responses (e.g. XML, HTML, plain text, etc.)
* `json` data is treated as JSON and returned as parsed object
* `arraybuffer`: data is returned as [ArrayBuffer instance](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)
* `blob`: data is returned as [Blob instance](https://developer.mozilla.org/en-US/docs/Web/API/Blob)
* `timeout`: timeout value for the request in seconds, defaults to global timeout value
* `followRedirect`: enable or disable automatically following redirects
* `headers`: headers object (key value pair), will be merged with global values
* `filePath`: filePath to be used during upload and download see [uploadFile](#uploadFile) and [downloadFile](#downloadFile) for detailed information
* `name`: name to be used during upload see [uploadFile](#uploadFile) for detailed information
* `filePath`: file path(s) to be used during upload and download see [uploadFile](#uploadFile) and [downloadFile](#downloadFile) for detailed information
* `name`: name(s) to be used during upload see [uploadFile](#uploadFile) for detailed information
Here's a quick example:
@@ -229,6 +239,18 @@ cordova.plugin.http.sendRequest('https://google.com/', options, function(respons
### post<a name="post"></a>
Execute a POST request. Takes a URL, data, and headers.
```js
cordova.plugin.http.post('https://google.com/', {
test: 'testString'
}, {
Authorization: 'OAuth2: token'
}, function(response) {
console.log(response.status);
}, function(response) {
console.error(response.error);
});
```
#### success
The success function receives a response object with 4 properties: status, data, url, and headers. **status** is the HTTP response code as numeric value. **data** is the response from the server as a string. **url** is the final URL obtained after any redirects as a string. **headers** is an object with the headers. The keys of the returned object are the header names and the values are the respective header values. All header names are lowercase.
@@ -271,7 +293,7 @@ cordova.plugin.http.post('https://google.com/', {
```
#### failure
The error function receives a response object with 4 properties: status, error, url, and headers (url and headers being optional). **status** is the HTTP response code as numeric value. **error** is the error response from the server as a string. **url** is the final URL obtained after any redirects as a string. **headers** is an object with the headers. The keys of the returned object are the header names and the values are the respective header values. All header names are lowercase.
The error function receives a response object with 4 properties: status, error, url, and headers (url and headers being optional). **status** is a HTTP response code or an internal error code. Positive values are HTTP status codes whereas negative values do represent internal error codes. **error** is the error response from the server as a string or an internal error message. **url** is the final URL obtained after any redirects as a string. **headers** is an object with the headers. The keys of the returned object are the header names and the values are the respective header values. All header names are lowercase.
Here's a quick example:
@@ -286,6 +308,8 @@ Here's a quick example:
}
```
:warning: An enumeration style object is exposed as `cordova.plugin.http.ErrorCode`. You can use it to check against internal error codes.
### get<a name="get"></a>
Execute a GET request. Takes a URL, parameters, and headers. See the [post](#post) documentation for details on what is returned on success and failure.
@@ -313,13 +337,21 @@ Execute a DELETE request. Takes a URL, parameters, and headers. See the [post]
Execute a HEAD request. Takes a URL, parameters, and headers. See the [post](#post) documentation for details on what is returned on success and failure.
### uploadFile<a name="uploadFile"></a>
Uploads a file saved on the device. Takes a URL, parameters, headers, filePath, and the name of the parameter to pass the file along as. See the [post](#post) documentation for details on what is returned on success and failure.
Uploads one or more file(s) saved on the device. Takes a URL, parameters, headers, filePath(s), and the name(s) of the parameter to pass the file along as. See the [post](#post) documentation for details on what is returned on success and failure.
```js
// e.g. for single file
const filePath = 'file:///somepicture.jpg';
const name = 'picture';
// e.g. for multiple files
const filePath = ['file:///somepicture.jpg', 'file:///somedocument.doc'];
const name = ['picture', 'document'];
cordova.plugin.http.uploadFile("https://google.com/", {
id: '12',
message: 'test'
}, { Authorization: 'OAuth2: token' }, 'file:///somepicture.jpg', 'picture', function(response) {
}, { Authorization: 'OAuth2: token' }, filePath, name, function(response) {
console.log(response.status);
}, function(response) {
console.error(response.error);

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "cordova-plugin-advanced-http",
"version": "2.0.7",
"version": "2.2.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,14 +1,14 @@
{
"name": "cordova-plugin-advanced-http",
"version": "2.0.7",
"version": "2.2.0",
"description": "Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning",
"scripts": {
"updatecert": "node ./scripts/update-test-cert.js",
"updatecert": "node ./scripts/update-e2e-server-cert.js && node ./scripts/update-e2e-client-cert.js",
"buildbrowser": "./scripts/build-test-app.sh --browser",
"testandroid": "npm run updatecert && ./scripts/build-test-app.sh --android --emulator && ./scripts/test-app.sh --android --emulator",
"testios": "npm run updatecert && ./scripts/build-test-app.sh --ios --emulator && ./scripts/test-app.sh --ios --emulator",
"testapp": "npm run testandroid && npm run testios",
"testjs": "mocha ./test/js-mocha-specs.js",
"testjs": "mocha ./test/js-specs.js",
"test": "npm run testjs && npm run testapp",
"release": "npm run test && ./scripts/release.sh"
},

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-advanced-http" version="2.0.7">
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-advanced-http" version="2.2.0">
<name>Advanced HTTP plugin</name>
<description>
Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning
@@ -9,8 +9,10 @@
</engines>
<dependency id="cordova-plugin-file" version=">=2.0.0"/>
<js-module src="www/cookie-handler.js" name="cookie-handler"/>
<js-module src="www/error-codes.js" name="error-codes"/>
<js-module src="www/global-configs.js" name="global-configs"/>
<js-module src="www/helpers.js" name="helpers"/>
<js-module src="www/js-util.js" name="js-util"/>
<js-module src="www/local-storage-store.js" name="local-storage-store"/>
<js-module src="www/lodash.js" name="lodash"/>
<js-module src="www/messages.js" name="messages"/>
@@ -27,6 +29,7 @@
</feature>
</config-file>
<header-file src="src/ios/CordovaHttpPlugin.h"/>
<header-file src="src/ios/BinaryResponseSerializer.h"/>
<header-file src="src/ios/TextResponseSerializer.h"/>
<header-file src="src/ios/TextRequestSerializer.h"/>
<header-file src="src/ios/AFNetworking/AFHTTPSessionManager.h"/>
@@ -38,6 +41,7 @@
<header-file src="src/ios/AFNetworking/AFURLSessionManager.h"/>
<header-file src="src/ios/SDNetworkActivityIndicator/SDNetworkActivityIndicator.h"/>
<source-file src="src/ios/CordovaHttpPlugin.m"/>
<source-file src="src/ios/BinaryResponseSerializer.m"/>
<source-file src="src/ios/TextResponseSerializer.m"/>
<source-file src="src/ios/TextRequestSerializer.m"/>
<source-file src="src/ios/AFNetworking/AFHTTPSessionManager.m"/>
@@ -59,20 +63,23 @@
<config-file target="AndroidManifest.xml" parent="/manifest">
<uses-permission android:name="android.permission.INTERNET"/>
</config-file>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaClientAuth.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpBase.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpDownload.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpOperation.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpResponse.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpUpload.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/http/HostnameVerifierFactory.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaServerTrust.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/http/HttpBodyDecoder.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/HttpRequest.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/JsonUtils.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/KeyChainKeyManager.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/OkConnectionFactory.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/TLSConfiguration.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/TLSSocketFactory.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/TrustManagersFactory.java" target-dir="src/com/silkimen/http"/>
<framework src="com.squareup.okhttp3:okhttp-urlconnection:3.10.0"/>
<preference name="OKHTTP_VERSION" default="3.10.0"/>
<framework src="com.squareup.okhttp3:okhttp-urlconnection:$OKHTTP_VERSION"/>
</platform>
<platform name="browser">
<config-file target="config.xml" parent="/*">

View File

@@ -35,10 +35,11 @@ while :; do
shift
done
printf 'Building test app for %s\n' $PLATFORM
rm -rf $ROOT/temp
mkdir $ROOT/temp
cp -r $ROOT/test/app-template/. $ROOT/temp/
cp $ROOT/test/app-test-definitions.js $ROOT/temp/www/
cp -r $ROOT/test/e2e-app-template/. $ROOT/temp/
cp $ROOT/test/e2e-specs.js $ROOT/temp/www/
rsync -ax --exclude node_modules --exclude scripts --exclude temp --exclude test $ROOT/. $WORKINGCOPY
cd $ROOT/temp
$CDV prepare

View File

@@ -1,19 +0,0 @@
#!/usr/bin/env bash
set -e
while getopts a:b: option; do
case "${option}" in
a) API_LEVEL=${OPTARG};;
b) BUILD_TOOLS_VERSION=${OPTARG};;
esac
done
curl http://dl.google.com/android/android-sdk_r24.4-macosx.zip -o android-sdk-macosx.zip
tar -xvf android-sdk-macosx.zip
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter platform-tools
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter build-tools-${BUILD_TOOLS_VERSION}
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter android-${API_LEVEL}
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter extra-android-support
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter extra-android-m2repository
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter extra-google-m2repository

View File

@@ -8,6 +8,7 @@ if [ $CI == "true" ] && ([ -z $SAUCE_USERNAME ] || [ -z $SAUCE_ACCESS_KEY ]); th
exit 0;
fi
printf 'Running e2e tests\n'
pushd $ROOT
./node_modules/.bin/mocha ./test/app-mocha-specs/test.js "$@"
./node_modules/.bin/mocha ./test/e2e-tooling/test.js "$@"
popd

View File

@@ -0,0 +1,29 @@
const fs = require('fs');
const https = require('https');
const path = require('path');
const SOURCE_URL = 'https://badssl.com/certs/badssl.com-client.p12';
const TARGET_PATH = path.join(__dirname, '../test/e2e-app-template/www/certificates/badssl-client-cert.pkcs');
const downloadPkcsContainer = (source, target) => new Promise((resolve, reject) => {
const file = fs.createWriteStream(target);
const req = https.get(source, response => {
response.pipe(file)
resolve(target);
});
req.on('error', error => {
return reject(error)
});
req.end();
});
console.log(`Updating client certificate from ${SOURCE_URL}`);
downloadPkcsContainer(SOURCE_URL, TARGET_PATH)
.catch(error => {
console.error(`Updating client certificate failed: ${error}`);
process.exit(1);
});

View File

@@ -3,7 +3,7 @@ const https = require('https');
const path = require('path');
const SOURCE_HOST = 'httpbin.org';
const TARGET_PATH = path.join(__dirname, '../test/app-template/www/certificates/httpbin.org.cer');
const TARGET_PATH = path.join(__dirname, '../test/e2e-app-template/www/certificates/httpbin.org.cer');
const getCert = hostname => new Promise((resolve, reject) => {
const options = {
@@ -30,13 +30,11 @@ const getCert = hostname => new Promise((resolve, reject) => {
req.end();
});
console.log(`Updating test certificate from ${SOURCE_HOST}`);
console.log(`Updating server certificate from ${SOURCE_HOST}`);
getCert(SOURCE_HOST)
.then(cert => {
fs.writeFileSync(TARGET_PATH, cert.raw);
})
.then(cert => fs.writeFileSync(TARGET_PATH, cert.raw))
.catch(error => {
console.error(`Updating test cert failed: ${error}`);
console.error(`Updating server certificate failed: ${error}`);
process.exit(1);
});

View File

@@ -0,0 +1,113 @@
package com.silkimen.cordovahttp;
import android.app.Activity;
import android.content.Context;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URI;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import org.apache.cordova.CallbackContext;
import com.silkimen.http.KeyChainKeyManager;
import com.silkimen.http.TLSConfiguration;
class CordovaClientAuth implements Runnable, KeyChainAliasCallback {
private static final String TAG = "Cordova-Plugin-HTTP";
private String mode;
private String aliasString;
private byte[] rawPkcs;
private String pkcsPassword;
private Activity activity;
private Context context;
private TLSConfiguration tlsConfiguration;
private CallbackContext callbackContext;
public CordovaClientAuth(final String mode, final String aliasString, final byte[] rawPkcs,
final String pkcsPassword, final Activity activity, final Context context, final TLSConfiguration configContainer,
final CallbackContext callbackContext) {
this.mode = mode;
this.aliasString = aliasString;
this.rawPkcs = rawPkcs;
this.pkcsPassword = pkcsPassword;
this.activity = activity;
this.tlsConfiguration = configContainer;
this.context = context;
this.callbackContext = callbackContext;
}
@Override
public void run() {
if ("systemstore".equals(this.mode)) {
this.loadFromSystemStore();
} else if ("buffer".equals(this.mode)) {
this.loadFromBuffer();
} else {
this.disableClientAuth();
}
}
private void loadFromSystemStore() {
if (this.aliasString == null) {
KeyChain.choosePrivateKeyAlias(this.activity, this, null, null, null, -1, null);
} else {
this.alias(this.aliasString);
}
}
private void loadFromBuffer() {
try {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(keyManagerFactoryAlgorithm);
ByteArrayInputStream stream = new ByteArrayInputStream(this.rawPkcs);
keyStore.load(stream, this.pkcsPassword.toCharArray());
keyManagerFactory.init(keyStore, this.pkcsPassword.toCharArray());
this.tlsConfiguration.setKeyManagers(keyManagerFactory.getKeyManagers());
this.callbackContext.success();
} catch (Exception e) {
Log.e(TAG, "Couldn't load given PKCS12 container for authentication", e);
this.callbackContext.error("Couldn't load given PKCS12 container for authentication");
}
}
private void disableClientAuth() {
this.tlsConfiguration.setKeyManagers(null);
this.callbackContext.success();
}
@Override
public void alias(final String alias) {
try {
if (alias == null) {
throw new Exception("Couldn't get a consent for private key access");
}
PrivateKey key = KeyChain.getPrivateKey(this.context, alias);
X509Certificate[] chain = KeyChain.getCertificateChain(this.context, alias);
KeyManager keyManager = new KeyChainKeyManager(alias, key, chain);
this.tlsConfiguration.setKeyManagers(new KeyManager[] { keyManager });
this.callbackContext.success(alias);
} catch (Exception e) {
Log.e(TAG, "Couldn't load private key and certificate pair with given alias \"" + alias + "\" for authentication",
e);
this.callbackContext.error(
"Couldn't load private key and certificate pair with given alias \"" + alias + "\" for authentication");
}
}
}

View File

@@ -1,5 +1,6 @@
package com.silkimen.cordovahttp;
import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.net.SocketTimeoutException;
@@ -7,15 +8,14 @@ import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.SSLException;
import com.silkimen.http.HttpBodyDecoder;
import com.silkimen.http.HttpRequest;
import com.silkimen.http.HttpRequest.HttpRequestException;
import com.silkimen.http.JsonUtils;
import com.silkimen.http.OkConnectionFactory;
import com.silkimen.http.TLSConfiguration;
import org.apache.cordova.CallbackContext;
@@ -30,16 +30,16 @@ abstract class CordovaHttpBase implements Runnable {
protected String method;
protected String url;
protected String serializer = "none";
protected String responseType;
protected Object data;
protected JSONObject headers;
protected int timeout;
protected boolean followRedirects;
protected SSLSocketFactory customSSLSocketFactory;
protected HostnameVerifier customHostnameVerifier;
protected TLSConfiguration tlsConfiguration;
protected CallbackContext callbackContext;
public CordovaHttpBase(String method, String url, String serializer, Object data, JSONObject headers, int timeout,
boolean followRedirects, SSLSocketFactory customSSLSocketFactory, HostnameVerifier customHostnameVerifier,
boolean followRedirects, String responseType, TLSConfiguration tlsConfiguration,
CallbackContext callbackContext) {
this.method = method;
@@ -49,22 +49,21 @@ abstract class CordovaHttpBase implements Runnable {
this.headers = headers;
this.timeout = timeout;
this.followRedirects = followRedirects;
this.customSSLSocketFactory = customSSLSocketFactory;
this.customHostnameVerifier = customHostnameVerifier;
this.responseType = responseType;
this.tlsConfiguration = tlsConfiguration;
this.callbackContext = callbackContext;
}
public CordovaHttpBase(String method, String url, JSONObject headers, int timeout,
boolean followRedirects, SSLSocketFactory customSSLSocketFactory, HostnameVerifier customHostnameVerifier,
CallbackContext callbackContext) {
public CordovaHttpBase(String method, String url, JSONObject headers, int timeout, boolean followRedirects,
String responseType, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
this.method = method;
this.url = url;
this.headers = headers;
this.timeout = timeout;
this.followRedirects = followRedirects;
this.customSSLSocketFactory = customSSLSocketFactory;
this.customHostnameVerifier = customHostnameVerifier;
this.responseType = responseType;
this.tlsConfiguration = tlsConfiguration;
this.callbackContext = callbackContext;
}
@@ -78,10 +77,10 @@ abstract class CordovaHttpBase implements Runnable {
this.sendBody(request);
this.processResponse(request, response);
} catch (HttpRequestException e) {
if (e.getCause() instanceof SSLHandshakeException) {
if (e.getCause() instanceof SSLException) {
response.setStatus(-2);
response.setErrorMessage("SSL handshake failed: " + e.getMessage());
Log.w(TAG, "SSL handshake failed", e);
response.setErrorMessage("TLS connection could not be established: " + e.getMessage());
Log.w(TAG, "TLS connection could not be established", e);
} else if (e.getCause() instanceof UnknownHostException) {
response.setStatus(-3);
response.setErrorMessage("Host could not be resolved: " + e.getMessage());
@@ -116,20 +115,18 @@ abstract class CordovaHttpBase implements Runnable {
return new HttpRequest(this.url, this.method);
}
protected void prepareRequest(HttpRequest request) throws JSONException {
protected void prepareRequest(HttpRequest request) throws JSONException, IOException {
request.followRedirects(this.followRedirects);
request.readTimeout(this.timeout);
request.acceptCharset("UTF-8");
request.uncompress(true);
request.setConnectionFactory(new OkConnectionFactory());
if (this.customHostnameVerifier != null) {
request.setHostnameVerifier(this.customHostnameVerifier);
if (this.tlsConfiguration.getHostnameVerifier() != null) {
request.setHostnameVerifier(this.tlsConfiguration.getHostnameVerifier());
}
if (this.customSSLSocketFactory != null) {
request.setSSLSocketFactory(this.customSSLSocketFactory);
}
request.setSSLSocketFactory(this.tlsConfiguration.getTLSSocketFactory());
// setup content type before applying headers, so user can override it
this.setContentType(request);
@@ -138,16 +135,12 @@ abstract class CordovaHttpBase implements Runnable {
}
protected void setContentType(HttpRequest request) {
switch (this.serializer) {
case "json":
if ("json".equals(this.serializer)) {
request.contentType("application/json", "UTF-8");
break;
case "utf8":
} else if ("utf8".equals(this.serializer)) {
request.contentType("text/plain", "UTF-8");
break;
case "urlencoded":
} else if ("urlencoded".equals(this.serializer)) {
// intentionally left blank, because content type is set in HttpRequest.form()
break;
}
}
@@ -156,16 +149,12 @@ abstract class CordovaHttpBase implements Runnable {
return;
}
switch (this.serializer) {
case "json":
if ("json".equals(this.serializer)) {
request.send(this.data.toString());
break;
case "utf8":
} else if ("utf8".equals(this.serializer)) {
request.send(((JSONObject) this.data).getString("text"));
break;
case "urlencoded":
} else if ("urlencoded".equals(this.serializer)) {
request.form(JsonUtils.getObjectMap((JSONObject) this.data));
break;
}
}
@@ -173,17 +162,19 @@ abstract class CordovaHttpBase implements Runnable {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
request.receive(outputStream);
ByteBuffer rawOutput = ByteBuffer.wrap(outputStream.toByteArray());
String decodedBody = HttpBodyDecoder.decodeBody(rawOutput, request.charset());
response.setStatus(request.code());
response.setUrl(request.url().toString());
response.setHeaders(request.headers());
if (request.code() >= 200 && request.code() < 300) {
response.setBody(decodedBody);
if ("text".equals(this.responseType)) {
String decoded = HttpBodyDecoder.decodeBody(outputStream.toByteArray(), request.charset());
response.setBody(decoded);
} else {
response.setData(outputStream.toByteArray());
}
} else {
response.setErrorMessage(decodedBody);
response.setErrorMessage(HttpBodyDecoder.decodeBody(outputStream.toByteArray(), request.charset()));
}
}
}

View File

@@ -7,6 +7,7 @@ import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
import com.silkimen.http.HttpRequest;
import com.silkimen.http.TLSConfiguration;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.file.FileUtils;
@@ -15,12 +16,10 @@ import org.json.JSONObject;
class CordovaHttpDownload extends CordovaHttpBase {
private String filePath;
public CordovaHttpDownload(String url, JSONObject headers, String filePath, int timeout,
boolean followRedirects, SSLSocketFactory customSSLSocketFactory, HostnameVerifier customHostnameVerifier,
CallbackContext callbackContext) {
public CordovaHttpDownload(String url, JSONObject headers, String filePath, int timeout, boolean followRedirects,
TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
super("GET", url, headers, timeout, followRedirects, customSSLSocketFactory, customHostnameVerifier,
callbackContext);
super("GET", url, headers, timeout, followRedirects, "text", tlsConfiguration, callbackContext);
this.filePath = filePath;
}

View File

@@ -3,23 +3,23 @@ package com.silkimen.cordovahttp;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
import com.silkimen.http.TLSConfiguration;
import org.apache.cordova.CallbackContext;
import org.json.JSONObject;
class CordovaHttpOperation extends CordovaHttpBase {
public CordovaHttpOperation(String method, String url, String serializer, Object data, JSONObject headers,
int timeout, boolean followRedirects, SSLSocketFactory customSSLSocketFactory,
HostnameVerifier customHostnameVerifier, CallbackContext callbackContext) {
int timeout, boolean followRedirects, String responseType, TLSConfiguration tlsConfiguration,
CallbackContext callbackContext) {
super(method, url, serializer, data, headers, timeout, followRedirects, customSSLSocketFactory,
customHostnameVerifier, callbackContext);
super(method, url, serializer, data, headers, timeout, followRedirects, responseType, tlsConfiguration,
callbackContext);
}
public CordovaHttpOperation(String method, String url, JSONObject headers, int timeout, boolean followRedirects,
SSLSocketFactory customSSLSocketFactory, HostnameVerifier customHostnameVerifier,
CallbackContext callbackContext) {
String responseType, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
super(method, url, headers, timeout, followRedirects, customSSLSocketFactory, customHostnameVerifier,
callbackContext);
super(method, url, headers, timeout, followRedirects, responseType, tlsConfiguration, callbackContext);
}
}

View File

@@ -1,27 +1,8 @@
package com.silkimen.cordovahttp;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.cert.CertificateFactory;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStore.TrustedCertificateEntry;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import com.silkimen.http.HostnameVerifierFactory;
import com.silkimen.http.TLSSocketFactory;
import com.silkimen.http.TrustManagersFactory;
import com.silkimen.http.TLSConfiguration;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
@@ -31,26 +12,32 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.res.AssetManager;
import android.util.Log;
import android.util.Base64;
import javax.net.ssl.TrustManagerFactory;
public class CordovaHttpPlugin extends CordovaPlugin {
private static final String TAG = "Cordova-Plugin-HTTP";
private final TrustManagersFactory trustManagersFactory = new TrustManagersFactory();
private final HostnameVerifierFactory hostnameVerifierFactory = new HostnameVerifierFactory();
private boolean followRedirects = true;
private SSLSocketFactory customSSLSocketFactory;
private HostnameVerifier customHostnameVerifier;
private TLSConfiguration tlsConfiguration;
@Override
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
this.tlsConfiguration = new TLSConfiguration();
try {
this.customSSLSocketFactory = this.createSocketFactory(
this.trustManagersFactory.getPinnedTrustManagers(this.getCertsFromKeyStore("AndroidCAStore")));
KeyStore store = KeyStore.getInstance("AndroidCAStore");
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
store.load(null);
tmf.init(store);
this.tlsConfiguration.setHostnameVerifier(null);
this.tlsConfiguration.setTrustManagers(tmf.getTrustManagers());
} catch (Exception e) {
Log.e(TAG, "An error occured while loading system's CA certificates", e);
}
@@ -64,28 +51,27 @@ public class CordovaHttpPlugin extends CordovaPlugin {
return false;
}
switch (action) {
case "get":
if ("get".equals(action)) {
return this.executeHttpRequestWithoutData(action, args, callbackContext);
case "post":
return this.executeHttpRequestWithData(action, args, callbackContext);
case "put":
return this.executeHttpRequestWithData(action, args, callbackContext);
case "patch":
return this.executeHttpRequestWithData(action, args, callbackContext);
case "head":
} else if ("head".equals(action)) {
return this.executeHttpRequestWithoutData(action, args, callbackContext);
case "delete":
} else if ("delete".equals(action)) {
return this.executeHttpRequestWithoutData(action, args, callbackContext);
case "uploadFile":
return this.uploadFile(args, callbackContext);
case "downloadFile":
} else if ("post".equals(action)) {
return this.executeHttpRequestWithData(action, args, callbackContext);
} else if ("put".equals(action)) {
return this.executeHttpRequestWithData(action, args, callbackContext);
} else if ("patch".equals(action)) {
return this.executeHttpRequestWithData(action, args, callbackContext);
} else if ("uploadFiles".equals(action)) {
return this.uploadFiles(args, callbackContext);
} else if ("downloadFile".equals(action)) {
return this.downloadFile(args, callbackContext);
case "setSSLCertMode":
return this.setSSLCertMode(args, callbackContext);
case "disableRedirect":
return this.disableRedirect(args, callbackContext);
default:
} else if ("setServerTrustMode".equals(action)) {
return this.setServerTrustMode(args, callbackContext);
} else if ("setClientAuthMode".equals(action)) {
return this.setClientAuthMode(args, callbackContext);
} else {
return false;
}
}
@@ -96,9 +82,11 @@ public class CordovaHttpPlugin extends CordovaPlugin {
String url = args.getString(0);
JSONObject headers = args.getJSONObject(1);
int timeout = args.getInt(2) * 1000;
boolean followRedirect = args.getBoolean(3);
String responseType = args.getString(4);
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, headers, timeout,
this.followRedirects, this.customSSLSocketFactory, this.customHostnameVerifier, callbackContext);
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, headers, timeout, followRedirect,
responseType, this.tlsConfiguration, callbackContext);
cordova.getThreadPool().execute(request);
@@ -113,24 +101,28 @@ public class CordovaHttpPlugin extends CordovaPlugin {
String serializer = args.getString(2);
JSONObject headers = args.getJSONObject(3);
int timeout = args.getInt(4) * 1000;
boolean followRedirect = args.getBoolean(5);
String responseType = args.getString(6);
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, serializer, data, headers,
timeout, this.followRedirects, this.customSSLSocketFactory, this.customHostnameVerifier, callbackContext);
timeout, followRedirect, responseType, this.tlsConfiguration, callbackContext);
cordova.getThreadPool().execute(request);
return true;
}
private boolean uploadFile(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
private boolean uploadFiles(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String url = args.getString(0);
JSONObject headers = args.getJSONObject(1);
String filePath = args.getString(2);
String uploadName = args.getString(3);
JSONArray filePaths = args.getJSONArray(2);
JSONArray uploadNames = args.getJSONArray(3);
int timeout = args.getInt(4) * 1000;
boolean followRedirect = args.getBoolean(5);
String responseType = args.getString(6);
CordovaHttpUpload upload = new CordovaHttpUpload(url, headers, filePath, uploadName, timeout,
this.followRedirects, this.customSSLSocketFactory, this.customHostnameVerifier, callbackContext);
CordovaHttpUpload upload = new CordovaHttpUpload(url, headers, filePaths, uploadNames, timeout, followRedirect,
responseType, this.tlsConfiguration, this.cordova.getActivity().getApplicationContext(), callbackContext);
cordova.getThreadPool().execute(upload);
@@ -142,107 +134,34 @@ public class CordovaHttpPlugin extends CordovaPlugin {
JSONObject headers = args.getJSONObject(1);
String filePath = args.getString(2);
int timeout = args.getInt(3) * 1000;
boolean followRedirect = args.getBoolean(4);
CordovaHttpDownload download = new CordovaHttpDownload(url, headers, filePath, timeout,
this.followRedirects, this.customSSLSocketFactory, this.customHostnameVerifier, callbackContext);
CordovaHttpDownload download = new CordovaHttpDownload(url, headers, filePath, timeout, followRedirect,
this.tlsConfiguration, callbackContext);
cordova.getThreadPool().execute(download);
return true;
}
private boolean setSSLCertMode(final JSONArray args, final CallbackContext callbackContext) {
try {
switch (args.getString(0)) {
case "legacy":
this.customHostnameVerifier = null;
this.customSSLSocketFactory = null;
break;
case "nocheck":
this.customHostnameVerifier = this.hostnameVerifierFactory.getNoOpVerifier();
this.customSSLSocketFactory = this.createSocketFactory(this.trustManagersFactory.getNoopTrustManagers());
break;
case "pinned":
this.customHostnameVerifier = null;
this.customSSLSocketFactory = this.createSocketFactory(
this.trustManagersFactory.getPinnedTrustManagers(this.getCertsFromBundle("www/certificates")));
break;
default:
this.customHostnameVerifier = null;
this.customSSLSocketFactory = this.createSocketFactory(
this.trustManagersFactory.getPinnedTrustManagers(this.getCertsFromKeyStore("AndroidCAStore")));
break;
}
private boolean setServerTrustMode(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
CordovaServerTrust runnable = new CordovaServerTrust(args.getString(0), this.cordova.getActivity(),
this.tlsConfiguration, callbackContext);
callbackContext.success();
} catch (Exception e) {
Log.e(TAG, "An error occured while configuring SSL cert mode", e);
callbackContext.error("An error occured while configuring SSL cert mode");
}
cordova.getThreadPool().execute(runnable);
return true;
}
private boolean disableRedirect(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
this.followRedirects = !args.getBoolean(0);
private boolean setClientAuthMode(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
byte[] pkcs = args.isNull(2) ? null : Base64.decode(args.getString(2), Base64.DEFAULT);
callbackContext.success();
CordovaClientAuth runnable = new CordovaClientAuth(args.getString(0), args.isNull(1) ? null : args.getString(1),
pkcs, args.getString(3), this.cordova.getActivity(), this.cordova.getActivity().getApplicationContext(),
this.tlsConfiguration, callbackContext);
cordova.getThreadPool().execute(runnable);
return true;
}
private ArrayList<Certificate> getCertsFromKeyStore(String storeType) throws GeneralSecurityException, IOException {
ArrayList<Certificate> certList = new ArrayList<Certificate>();
KeyStore keyStore = KeyStore.getInstance(storeType);
keyStore.load(null);
Enumeration<String> aliases = keyStore.aliases();
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
TrustedCertificateEntry certEntry = (TrustedCertificateEntry) keyStore.getEntry(alias, null);
Certificate cert = certEntry.getTrustedCertificate();
certList.add(cert);
}
return certList;
}
private ArrayList<Certificate> getCertsFromBundle(String path) throws GeneralSecurityException, IOException {
AssetManager assetManager = cordova.getActivity().getAssets();
String[] files = assetManager.list(path);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
ArrayList<Certificate> certList = new ArrayList<Certificate>();
for (int i = 0; i < files.length; i++) {
int index = files[i].lastIndexOf('.');
if (index == -1 || !files[i].substring(index).equals(".cer")) {
continue;
}
certList.add(cf.generateCertificate(assetManager.open(path + "/" + files[i])));
}
return certList;
}
private SSLSocketFactory createSocketFactory(TrustManager[] trustManagers) throws IOException {
try {
SSLContext context = SSLContext.getInstance("TLS");
/* @TODO implement custom KeyManager */
context.init(null, trustManagers, new SecureRandom());
if (android.os.Build.VERSION.SDK_INT < 20) {
return new TLSSocketFactory(context);
} else {
return context.getSocketFactory();
}
} catch (GeneralSecurityException e) {
IOException ioException = new IOException("Security exception occured while configuring SSL context");
ioException.initCause(e);
throw ioException;
}
}
}

View File

@@ -1,5 +1,7 @@
package com.silkimen.cordovahttp;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -9,15 +11,18 @@ import org.json.JSONObject;
import android.text.TextUtils;
import android.util.Log;
import android.util.Base64;
class CordovaHttpResponse {
private int status;
private String url;
private Map<String, List<String>> headers;
private String body;
private byte[] rawData;
private JSONObject fileEntry;
private boolean hasFailed;
private boolean isFileOperation;
private boolean isRawResponse;
private String error;
public void setStatus(int status) {
@@ -36,6 +41,11 @@ class CordovaHttpResponse {
this.body = body;
}
public void setData(byte[] rawData) {
this.isRawResponse = true;
this.rawData = rawData;
}
public void setFileEntry(JSONObject entry) {
this.isFileOperation = true;
this.fileEntry = entry;
@@ -56,13 +66,17 @@ class CordovaHttpResponse {
json.put("status", this.status);
json.put("url", this.url);
if (this.headers != null && !this.headers.isEmpty()) {
json.put("headers", new JSONObject(getFilteredHeaders()));
}
if (this.hasFailed) {
json.put("error", this.error);
} else if (this.isFileOperation) {
json.put("headers", new JSONObject(getFilteredHeaders()));
json.put("file", this.fileEntry);
} else if (this.isRawResponse) {
json.put("data", Base64.encodeToString(this.rawData, Base64.DEFAULT));
} else {
json.put("headers", new JSONObject(getFilteredHeaders()));
json.put("data", this.body);
}
@@ -72,10 +86,6 @@ class CordovaHttpResponse {
private Map<String, String> getFilteredHeaders() throws JSONException {
Map<String, String> filteredHeaders = new HashMap<String, String>();
if (this.headers == null || this.headers.isEmpty()) {
return filteredHeaders;
}
for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) {
String key = entry.getKey();
List<String> value = entry.getValue();

View File

@@ -1,43 +1,92 @@
package com.silkimen.cordovahttp;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.OpenableColumns;
import android.webkit.MimeTypeMap;
import com.silkimen.http.HttpRequest;
import com.silkimen.http.TLSConfiguration;
import java.io.File;
import java.io.InputStream;
import java.net.URI;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
import org.json.JSONObject;
class CordovaHttpUpload extends CordovaHttpBase {
private String filePath;
private String uploadName;
private JSONArray filePaths;
private JSONArray uploadNames;
private Context applicationContext;
public CordovaHttpUpload(String url, JSONObject headers, String filePath, String uploadName,
int timeout, boolean followRedirects, SSLSocketFactory customSSLSocketFactory,
HostnameVerifier customHostnameVerifier, CallbackContext callbackContext) {
public CordovaHttpUpload(String url, JSONObject headers, JSONArray filePaths, JSONArray uploadNames, int timeout,
boolean followRedirects, String responseType, TLSConfiguration tlsConfiguration,
Context applicationContext, CallbackContext callbackContext) {
super("POST", url, headers, timeout, followRedirects, customSSLSocketFactory, customHostnameVerifier,
callbackContext);
this.filePath = filePath;
this.uploadName = uploadName;
super("POST", url, headers, timeout, followRedirects, responseType, tlsConfiguration, callbackContext);
this.filePaths = filePaths;
this.uploadNames = uploadNames;
this.applicationContext = applicationContext;
}
@Override
protected void sendBody(HttpRequest request) throws Exception {
int filenameIndex = this.filePath.lastIndexOf('/');
String filename = this.filePath.substring(filenameIndex + 1);
for (int i = 0; i < this.filePaths.length(); ++i) {
String uploadName = this.uploadNames.getString(i);
String filePath = this.filePaths.getString(i);
int extIndex = this.filePath.lastIndexOf('.');
String ext = this.filePath.substring(extIndex + 1);
Uri fileUri = Uri.parse(filePath);
// File Scheme
if (ContentResolver.SCHEME_FILE.equals(fileUri.getScheme())) {
File file = new File(new URI(filePath));
String fileName = file.getName().trim();
String mimeType = this.getMimeTypeFromFileName(fileName);
request.part(uploadName, fileName, mimeType, file);
}
// Content Scheme
if (ContentResolver.SCHEME_CONTENT.equals(fileUri.getScheme())) {
InputStream inputStream = this.applicationContext.getContentResolver().openInputStream(fileUri);
String fileName = this.getFileNameFromContentScheme(fileUri, this.applicationContext).trim();
String mimeType = this.getMimeTypeFromFileName(fileName);
request.part(uploadName, fileName, mimeType, inputStream);
}
}
}
private String getFileNameFromContentScheme(Uri contentSchemeUri, Context applicationContext) {
Cursor returnCursor = applicationContext.getContentResolver().query(contentSchemeUri, null, null, null, null);
if (returnCursor == null || !returnCursor.moveToFirst()) {
return null;
}
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
String fileName = returnCursor.getString(nameIndex);
returnCursor.close();
return fileName;
}
private String getMimeTypeFromFileName(String fileName) {
if (fileName == null || !fileName.contains(".")) {
return null;
}
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
String mimeType = mimeTypeMap.getMimeTypeFromExtension(ext);
int extIndex = fileName.lastIndexOf('.') + 1;
String extension = fileName.substring(extIndex).toLowerCase();
request.part(this.uploadName, filename, mimeType, new File(new URI(this.filePath)));
return mimeTypeMap.getMimeTypeFromExtension(extension);
}
}

View File

@@ -0,0 +1,124 @@
package com.silkimen.cordovahttp;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import com.silkimen.http.TLSConfiguration;
import org.apache.cordova.CallbackContext;
import android.app.Activity;
import android.util.Log;
import android.content.res.AssetManager;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
class CordovaServerTrust implements Runnable {
private static final String TAG = "Cordova-Plugin-HTTP";
private final TrustManager[] noOpTrustManagers;
private final HostnameVerifier noOpVerifier;
private String mode;
private Activity activity;
private TLSConfiguration tlsConfiguration;
private CallbackContext callbackContext;
public CordovaServerTrust(final String mode, final Activity activity, final TLSConfiguration configContainer,
final CallbackContext callbackContext) {
this.mode = mode;
this.activity = activity;
this.tlsConfiguration = configContainer;
this.callbackContext = callbackContext;
this.noOpTrustManagers = new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] chain, String authType) {
// intentionally left blank
}
public void checkServerTrusted(X509Certificate[] chain, String authType) {
// intentionally left blank
}
} };
this.noOpVerifier = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
}
@Override
public void run() {
try {
if ("legacy".equals(this.mode)) {
this.tlsConfiguration.setHostnameVerifier(null);
this.tlsConfiguration.setTrustManagers(null);
} else if ("nocheck".equals(this.mode)) {
this.tlsConfiguration.setHostnameVerifier(this.noOpVerifier);
this.tlsConfiguration.setTrustManagers(this.noOpTrustManagers);
} else if ("pinned".equals(this.mode)) {
this.tlsConfiguration.setHostnameVerifier(null);
this.tlsConfiguration.setTrustManagers(this.getTrustManagers(this.getCertsFromBundle("www/certificates")));
} else {
this.tlsConfiguration.setHostnameVerifier(null);
this.tlsConfiguration.setTrustManagers(this.getTrustManagers(this.getCertsFromKeyStore("AndroidCAStore")));
}
callbackContext.success();
} catch (Exception e) {
Log.e(TAG, "An error occured while configuring SSL cert mode", e);
callbackContext.error("An error occured while configuring SSL cert mode");
}
}
private TrustManager[] getTrustManagers(KeyStore store) throws GeneralSecurityException {
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(store);
return tmf.getTrustManagers();
}
private KeyStore getCertsFromBundle(String path) throws GeneralSecurityException, IOException {
AssetManager assetManager = this.activity.getAssets();
String[] files = assetManager.list(path);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
for (int i = 0; i < files.length; i++) {
int index = files[i].lastIndexOf('.');
if (index == -1 || !files[i].substring(index).equals(".cer")) {
continue;
}
keyStore.setCertificateEntry("CA" + i, cf.generateCertificate(assetManager.open(path + "/" + files[i])));
}
return keyStore;
}
private KeyStore getCertsFromKeyStore(String storeType) throws GeneralSecurityException, IOException {
KeyStore store = KeyStore.getInstance(storeType);
store.load(null);
return store;
}
}

View File

@@ -1,20 +0,0 @@
package com.silkimen.http;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
public class HostnameVerifierFactory {
private final HostnameVerifier noOpVerifier;
public HostnameVerifierFactory() {
this.noOpVerifier = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
}
public HostnameVerifier getNoOpVerifier() {
return this.noOpVerifier;
}
}

View File

@@ -10,21 +10,28 @@ import java.nio.charset.MalformedInputException;
public class HttpBodyDecoder {
private static final String[] ACCEPTED_CHARSETS = new String[] { "UTF-8", "ISO-8859-1" };
public static String decodeBody(ByteBuffer rawOutput, String charsetName)
public static String decodeBody(byte[] body, String charsetName)
throws CharacterCodingException, MalformedInputException {
return decodeBody(ByteBuffer.wrap(body), charsetName);
}
public static String decodeBody(ByteBuffer body, String charsetName)
throws CharacterCodingException, MalformedInputException {
if (charsetName == null) {
return tryDecodeByteBuffer(rawOutput);
return tryDecodeByteBuffer(body);
} else {
return decodeByteBuffer(body, charsetName);
}
return decodeByteBuffer(rawOutput, charsetName);
}
private static String tryDecodeByteBuffer(ByteBuffer rawOutput) throws CharacterCodingException, MalformedInputException {
private static String tryDecodeByteBuffer(ByteBuffer buffer)
throws CharacterCodingException, MalformedInputException {
for (int i = 0; i < ACCEPTED_CHARSETS.length - 1; i++) {
try {
return decodeByteBuffer(rawOutput, ACCEPTED_CHARSETS[i]);
return decodeByteBuffer(buffer, ACCEPTED_CHARSETS[i]);
} catch (MalformedInputException e) {
continue;
} catch (CharacterCodingException e) {
@@ -32,13 +39,13 @@ public class HttpBodyDecoder {
}
}
return decodeBody(rawOutput, ACCEPTED_CHARSETS[ACCEPTED_CHARSETS.length - 1]);
return decodeBody(buffer, ACCEPTED_CHARSETS[ACCEPTED_CHARSETS.length - 1]);
}
private static String decodeByteBuffer(ByteBuffer rawOutput, String charsetName)
private static String decodeByteBuffer(ByteBuffer buffer, String charsetName)
throws CharacterCodingException, MalformedInputException {
return createCharsetDecoder(charsetName).decode(rawOutput).toString();
return createCharsetDecoder(charsetName).decode(buffer).toString();
}
private static CharsetDecoder createCharsetDecoder(String charsetName) {

View File

@@ -0,0 +1,57 @@
package com.silkimen.http;
import android.content.Context;
import android.security.KeyChain;
import java.net.Socket;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509ExtendedKeyManager;
public class KeyChainKeyManager extends X509ExtendedKeyManager {
private final String alias;
private final X509Certificate[] chain;
private final PrivateKey key;
public KeyChainKeyManager(String alias, PrivateKey key, X509Certificate[] chain) {
this.alias = alias;
this.key = key;
this.chain = chain;
}
@Override
public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {
return this.alias;
}
@Override
public X509Certificate[] getCertificateChain(String alias) {
return chain;
}
@Override
public PrivateKey getPrivateKey(String alias) {
return key;
}
@Override
public final String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
// not a client SSLSocket callback
throw new UnsupportedOperationException();
}
@Override
public final String[] getClientAliases(String keyType, Principal[] issuers) {
// not a client SSLSocket callback
throw new UnsupportedOperationException();
}
@Override
public final String[] getServerAliases(String keyType, Principal[] issuers) {
// not a client SSLSocket callback
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,63 @@
package com.silkimen.http;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import com.silkimen.http.TLSSocketFactory;
public class TLSConfiguration {
private TrustManager[] trustManagers;
private KeyManager[] keyManagers;
private HostnameVerifier hostnameVerifier;
private SSLSocketFactory socketFactory;
public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
this.hostnameVerifier = hostnameVerifier;
}
public void setKeyManagers(KeyManager[] keyManagers) {
this.keyManagers = keyManagers;
this.socketFactory = null;
}
public void setTrustManagers(TrustManager[] trustManagers) {
this.trustManagers = trustManagers;
this.socketFactory = null;
}
public HostnameVerifier getHostnameVerifier() {
return this.hostnameVerifier;
}
public SSLSocketFactory getTLSSocketFactory() throws IOException {
if (this.socketFactory != null) {
return this.socketFactory;
}
try {
SSLContext context = SSLContext.getInstance("TLS");
context.init(this.keyManagers, this.trustManagers, new SecureRandom());
if (android.os.Build.VERSION.SDK_INT < 20) {
this.socketFactory = new TLSSocketFactory(context);
} else {
this.socketFactory = context.getSocketFactory();
}
return this.socketFactory;
} catch (GeneralSecurityException e) {
IOException ioException = new IOException("Security exception occured while configuring TLS context");
ioException.initCause(e);
throw ioException;
}
}
}

View File

@@ -28,8 +28,8 @@ public class TLSSocketFactory extends SSLSocketFactory {
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return enableTLSOnSocket(delegate.createSocket(s, host, port, autoClose));
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
return enableTLSOnSocket(delegate.createSocket(socket, host, port, autoClose));
}
@Override

View File

@@ -1,64 +0,0 @@
package com.silkimen.http;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
public class TrustManagersFactory {
private final TrustManager[] noOpTrustManager;
public TrustManagersFactory() {
this.noOpTrustManager = new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] chain, String authType) {
// intentionally left blank
}
public void checkServerTrusted(X509Certificate[] chain, String authType) {
// intentionally left blank
}
} };
}
public TrustManager[] getNoopTrustManagers() {
return this.noOpTrustManager;
}
public TrustManager[] getPinnedTrustManagers(ArrayList<Certificate> pinnedCerts) throws IOException {
if (pinnedCerts == null || pinnedCerts.size() == 0) {
throw new IOException("You must add at least 1 certificate in order to pin to certificates");
}
try {
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
for (int i = 0; i < pinnedCerts.size(); i++) {
keyStore.setCertificateEntry("CA" + i, pinnedCerts.get(i));
}
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
return tmf.getTrustManagers();
} catch (GeneralSecurityException e) {
IOException ioException = new IOException("Security exception configuring SSL trust managers");
ioException.initCause(e);
throw ioException;
}
}
}

View File

@@ -1,7 +1,7 @@
var pluginId = module.id.slice(0, module.id.lastIndexOf('.'));
var cordovaProxy = require('cordova/exec/proxy');
var helpers = require(pluginId + '.helpers');
var jsUtil = require(pluginId + '.js-util');
function serializeJsonData(data) {
try {
@@ -29,7 +29,7 @@ function serializeParams(params) {
if (params === null) return '';
return Object.keys(params).map(function(key) {
if (helpers.getTypeOf(params[key]) === 'Array') {
if (jsUtil.getTypeOf(params[key]) === 'Array') {
return serializeArray(key, params[key]);
}
@@ -52,11 +52,19 @@ function deserializeResponseHeaders(headers) {
return headerMap;
}
function getResponseData(xhr) {
if (xhr.responseType !== 'text' || jsUtil.getTypeOf(xhr.responseText) !== 'String') {
return xhr.response;
}
return xhr.responseText;
}
function createXhrSuccessObject(xhr) {
return {
url: xhr.responseURL,
status: xhr.status,
data: helpers.getTypeOf(xhr.responseText) === 'String' ? xhr.responseText : xhr.response,
data: getResponseData(xhr),
headers: deserializeResponseHeaders(xhr.getAllResponseHeaders())
};
}
@@ -65,7 +73,7 @@ function createXhrFailureObject(xhr) {
var obj = {};
obj.headers = xhr.getAllResponseHeaders();
obj.error = helpers.getTypeOf(xhr.responseText) === 'String' ? xhr.responseText : xhr.response;
obj.error = getResponseData(xhr);
obj.error = obj.error || 'advanced-http: please check browser console for error messages';
if (xhr.responseURL) obj.url = xhr.responseURL;
@@ -101,12 +109,23 @@ function setHeaders(xhr, headers) {
}
function sendRequest(method, withData, opts, success, failure) {
var data = withData ? opts[1] : null;
var params = withData ? null : serializeParams(opts[1]);
var serializer = withData ? opts[2] : null;
var headers = withData ? opts[3] : opts[2];
var timeout = withData ? opts[4] : opts[3];
var url = params ? opts[0] + '?' + params : opts[0];
var data, serializer, headers, timeout, followRedirect, responseType;
var url = opts[0];
if (withData) {
data = opts[1];
serializer = opts[2];
headers = opts[3];
timeout = opts[4];
followRedirect = opts[5];
responseType = opts[6];
} else {
headers = opts[1];
timeout = opts[2];
followRedirect = opts[3];
responseType = opts[4];
}
var processedData = null;
var xhr = new XMLHttpRequest();
@@ -117,6 +136,10 @@ function sendRequest(method, withData, opts, success, failure) {
return failure('advanced-http: custom cookies not supported on browser platform');
}
if (!followRedirect) {
return failure('advanced-http: disabling follow redirect not supported on browser platform');
}
switch (serializer) {
case 'json':
setDefaultContentType(headers, 'application/json; charset=utf8');
@@ -140,6 +163,7 @@ function sendRequest(method, withData, opts, success, failure) {
}
xhr.timeout = timeout * 1000;
xhr.responseType = responseType;
setHeaders(xhr, headers);
xhr.onerror = xhr.ontimeout = function () {
@@ -160,35 +184,38 @@ function sendRequest(method, withData, opts, success, failure) {
}
var browserInterface = {
post: function (success, failure, opts) {
return sendRequest('post', true, opts, success, failure);
},
get: function (success, failure, opts) {
return sendRequest('get', false, opts, success, failure);
},
head: function (success, failure, opts) {
return sendRequest('head', false, opts, success, failure);
},
delete: function (success, failure, opts) {
return sendRequest('delete', false, opts, success, failure);
},
post: function (success, failure, opts) {
return sendRequest('post', true, opts, success, failure);
},
put: function (success, failure, opts) {
return sendRequest('put', true, opts, success, failure);
},
patch: function (success, failure, opts) {
return sendRequest('patch', true, opts, success, failure);
},
delete: function (success, failure, opts) {
return sendRequest('delete', false, opts, success, failure);
},
head: function (success, failure, opts) {
return sendRequest('head', false, opts, success, failure);
},
uploadFile: function (success, failure, opts) {
return failure('advanced-http: function "uploadFile" not supported on browser platform');
},
uploadFiles: function (success, failure, opts) {
return failure('advanced-http: function "uploadFiles" not supported on browser platform');
},
downloadFile: function (success, failure, opts) {
return failure('advanced-http: function "downloadFile" not supported on browser platform');
},
setSSLCertMode: function (success, failure, opts) {
return failure('advanced-http: function "setSSLCertMode" not supported on browser platform');
setServerTrustMode: function (success, failure, opts) {
return failure('advanced-http: function "setServerTrustMode" not supported on browser platform');
},
disableRedirect: function (success, failure, opts) {
return failure('advanced-http: function "disableRedirect" not supported on browser platform');
setClientAuthMode: function (success, failure, opts) {
return failure('advanced-http: function "setClientAuthMode" not supported on browser platform');
}
};

View File

@@ -308,4 +308,11 @@ FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseErrorK
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey;
/**
`AFNetworkingOperationFailingURLResponseBodyErrorKey`
The corresponding value is an `NSString` containing the decoded error message.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseBodyErrorKey;
NS_ASSUME_NONNULL_END

View File

@@ -34,6 +34,7 @@
NSString * const AFURLResponseSerializationErrorDomain = @"com.alamofire.error.serialization.response";
NSString * const AFNetworkingOperationFailingURLResponseErrorKey = @"com.alamofire.serialization.response.error.response";
NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey = @"com.alamofire.serialization.response.error.data";
NSString * const AFNetworkingOperationFailingURLResponseBodyErrorKey = @"com.alamofire.serialization.response.error.body";
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
if (!error) {
@@ -525,7 +526,7 @@ static NSLock* imageLock = nil;
dispatch_once(&onceToken, ^{
imageLock = [[NSLock alloc] init];
});
[imageLock lock];
image = [UIImage imageWithData:data];
[imageLock unlock];
@@ -539,7 +540,7 @@ static UIImage * AFImageWithDataAtScale(NSData *data, CGFloat scale) {
if (image.images) {
return image;
}
return [[UIImage alloc] initWithCGImage:[image CGImage] scale:scale orientation:image.imageOrientation];
}

View File

@@ -0,0 +1,8 @@
#import <Foundation/Foundation.h>
#import "AFURLResponseSerialization.h"
@interface BinaryResponseSerializer : AFHTTPResponseSerializer
+ (instancetype)serializer;
@end

View File

@@ -0,0 +1,126 @@
#import "BinaryResponseSerializer.h"
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
if (!error) {
return underlyingError;
}
if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
return error;
}
NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;
return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
if ([error.domain isEqualToString:domain] && error.code == code) {
return YES;
} else if (error.userInfo[NSUnderlyingErrorKey]) {
return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
}
return NO;
}
@implementation BinaryResponseSerializer
+ (instancetype)serializer {
BinaryResponseSerializer *serializer = [[self alloc] init];
return serializer;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.acceptableContentTypes = nil;
return self;
}
- (NSString*)decodeResponseData:(NSData*)rawResponseData withEncoding:(CFStringEncoding)cfEncoding {
NSStringEncoding nsEncoding;
NSString* decoded = nil;
if (cfEncoding != kCFStringEncodingInvalidId) {
nsEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
NSStringEncoding supportedEncodings[6] = {
NSUTF8StringEncoding, NSWindowsCP1252StringEncoding, NSISOLatin1StringEncoding,
NSISOLatin2StringEncoding, NSASCIIStringEncoding, NSUnicodeStringEncoding
};
for (int i = 0; i < sizeof(supportedEncodings) / sizeof(NSStringEncoding) && !decoded; ++i) {
if (cfEncoding == kCFStringEncodingInvalidId || nsEncoding == supportedEncodings[i]) {
decoded = [[NSString alloc] initWithData:rawResponseData encoding:supportedEncodings[i]];
}
}
return decoded;
}
- (CFStringEncoding) getEncoding:(NSURLResponse *)response {
CFStringEncoding encoding = kCFStringEncodingInvalidId;
if (response.textEncodingName) {
encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
}
return encoding;
}
#pragma mark -
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError * __autoreleasing *)error
{
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],
NSURLErrorFailingURLErrorKey: [response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
} mutableCopy];
if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
// trying to decode error message in body
mutableUserInfo[AFNetworkingOperationFailingURLResponseBodyErrorKey] = [self decodeResponseData:data withEncoding:[self getEncoding:response]];
}
if (error) {
*error = [NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo];
}
return NO;
}
}
return YES;
}
#pragma mark - AFURLResponseSerialization
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
return nil;
}
}
return [data base64EncodedStringWithOptions:0];
}
@end

View File

@@ -4,14 +4,13 @@
@interface CordovaHttpPlugin : CDVPlugin
- (void)setSSLCertMode:(CDVInvokedUrlCommand*)command;
- (void)disableRedirect:(CDVInvokedUrlCommand*)command;
- (void)setServerTrustMode:(CDVInvokedUrlCommand*)command;
- (void)post:(CDVInvokedUrlCommand*)command;
- (void)get:(CDVInvokedUrlCommand*)command;
- (void)put:(CDVInvokedUrlCommand*)command;
- (void)patch:(CDVInvokedUrlCommand*)command;
- (void)delete:(CDVInvokedUrlCommand*)command;
- (void)uploadFile:(CDVInvokedUrlCommand*)command;
- (void)uploadFiles:(CDVInvokedUrlCommand*)command;
- (void)downloadFile:(CDVInvokedUrlCommand*)command;
@end

View File

@@ -1,5 +1,6 @@
#import "CordovaHttpPlugin.h"
#import "CDVFile.h"
#import "BinaryResponseSerializer.h"
#import "TextResponseSerializer.h"
#import "TextRequestSerializer.h"
#import "AFHTTPSessionManager.h"
@@ -13,20 +14,16 @@
- (NSNumber*)getStatusCode:(NSError*) error;
- (NSMutableDictionary*)copyHeaderFields:(NSDictionary*)headerFields;
- (void)setTimeout:(NSTimeInterval)timeout forManager:(AFHTTPSessionManager*)manager;
- (void)setRedirect:(AFHTTPSessionManager*)manager;
- (void)setRedirect:(bool)redirect forManager:(AFHTTPSessionManager*)manager;
@end
@implementation CordovaHttpPlugin {
AFSecurityPolicy *securityPolicy;
bool redirect;
AFHTTPSessionManager *manager;
}
- (void)pluginInitialize {
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
manager = [AFHTTPSessionManager manager];
redirect = true;
}
- (void)setRequestSerializer:(NSString*)serializerName forManager:(AFHTTPSessionManager*)manager {
@@ -45,9 +42,11 @@
}];
}
- (void)setRedirect:(AFHTTPSessionManager*)manager {
[manager setTaskWillPerformHTTPRedirectionBlock:^NSURLRequest * _Nonnull(NSURLSession * _Nonnull session, NSURLSessionTask * _Nonnull task, NSURLResponse * _Nonnull response, NSURLRequest * _Nonnull request) {
if (redirect) {
- (void)setRedirect:(bool)followRedirect forManager:(AFHTTPSessionManager*)manager {
[manager setTaskWillPerformHTTPRedirectionBlock:^NSURLRequest * _Nonnull(NSURLSession * _Nonnull session,
NSURLSessionTask * _Nonnull task, NSURLResponse * _Nonnull response, NSURLRequest * _Nonnull request) {
if (followRedirect) {
return request;
} else {
return nil;
@@ -55,6 +54,19 @@
}];
}
- (void)setTimeout:(NSTimeInterval)timeout forManager:(AFHTTPSessionManager*)manager {
[manager.requestSerializer setTimeoutInterval:timeout];
}
- (void)setResponseSerializer:(NSString*)responseType forManager:(AFHTTPSessionManager*)manager {
if ([responseType isEqualToString: @"text"]) {
manager.responseSerializer = [TextResponseSerializer serializer];
} else {
manager.responseSerializer = [BinaryResponseSerializer serializer];
}
}
- (void)handleSuccess:(NSMutableDictionary*)dictionary withResponse:(NSHTTPURLResponse*)response andData:(id)data {
if (response != nil) {
[dictionary setValue:response.URL.absoluteString forKey:@"url"];
@@ -72,8 +84,8 @@
[dictionary setValue:response.URL.absoluteString forKey:@"url"];
[dictionary setObject:[NSNumber numberWithInt:(int)response.statusCode] forKey:@"status"];
[dictionary setObject:[self copyHeaderFields:response.allHeaderFields] forKey:@"headers"];
if (error.userInfo[AFNetworkingOperationFailingURLResponseBodyKey]) {
[dictionary setObject:error.userInfo[AFNetworkingOperationFailingURLResponseBodyKey] forKey:@"error"];
if (error.userInfo[AFNetworkingOperationFailingURLResponseBodyErrorKey]) {
[dictionary setObject:error.userInfo[AFNetworkingOperationFailingURLResponseBodyErrorKey] forKey:@"error"];
}
} else {
[dictionary setObject:[self getStatusCode:error] forKey:@"status"];
@@ -106,8 +118,12 @@
case -1009:
// no connection
return [NSNumber numberWithInt:-6];
case -1202:
// untrusted SSL certificate
case -1200: // secure connection failed
case -1201: // certificate has bad date
case -1202: // certificate untrusted
case -1203: // certificate has unknown root
case -1204: // certificate is not yet valid
// configuring SSL failed
return [NSNumber numberWithInt:-2];
default:
return [NSNumber numberWithInt:-1];
@@ -126,7 +142,7 @@
return headerFieldsCopy;
}
- (void)setSSLCertMode:(CDVInvokedUrlCommand*)command {
- (void)setServerTrustMode:(CDVInvokedUrlCommand*)command {
NSString *certMode = [command.arguments objectAtIndex:0];
if ([certMode isEqualToString: @"default"] || [certMode isEqualToString: @"legacy"]) {
@@ -147,34 +163,23 @@
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
- (void)setTimeout:(NSTimeInterval)timeout forManager:(AFHTTPSessionManager*)manager {
[manager.requestSerializer setTimeoutInterval:timeout];
}
- (void)disableRedirect:(CDVInvokedUrlCommand*)command {
CDVPluginResult* pluginResult = nil;
bool disable = [[command.arguments objectAtIndex:0] boolValue];
redirect = !disable;
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
- (void)get:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *headers = [command.arguments objectAtIndex:1];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:2] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:3] boolValue];
NSString *responseType = [command.arguments objectAtIndex:4];
[self setRequestSerializer: @"default" forManager: manager];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect: manager];
[self setRedirect:followRedirect forManager:manager];
[self setResponseSerializer:responseType forManager:manager];
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
@@ -201,18 +206,20 @@
}
- (void)head:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *headers = [command.arguments objectAtIndex:1];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:2] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:3] boolValue];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect: manager];
[self setRedirect:followRedirect forManager:manager];
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
@@ -240,19 +247,22 @@
}
- (void)delete:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *headers = [command.arguments objectAtIndex:1];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:2] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:3] boolValue];
NSString *responseType = [command.arguments objectAtIndex:4];
[self setRequestSerializer: @"default" forManager: manager];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect: manager];
[self setRedirect:followRedirect forManager:manager];
[self setResponseSerializer:responseType forManager:manager];
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
@@ -279,6 +289,7 @@
}
- (void)post:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
@@ -286,14 +297,16 @@
NSString *serializerName = [command.arguments objectAtIndex:2];
NSDictionary *headers = [command.arguments objectAtIndex:3];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:4] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:5] boolValue];
NSString *responseType = [command.arguments objectAtIndex:6];
[self setRequestSerializer: serializerName forManager: manager];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect: manager];
[self setRedirect:followRedirect forManager:manager];
[self setResponseSerializer:responseType forManager:manager];
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
@@ -320,6 +333,7 @@
}
- (void)put:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
@@ -327,14 +341,16 @@
NSString *serializerName = [command.arguments objectAtIndex:2];
NSDictionary *headers = [command.arguments objectAtIndex:3];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:4] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:5] boolValue];
NSString *responseType = [command.arguments objectAtIndex:6];
[self setRequestSerializer: serializerName forManager: manager];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect: manager];
[self setRedirect:followRedirect forManager:manager];
[self setResponseSerializer:responseType forManager:manager];
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
@@ -361,6 +377,7 @@
}
- (void)patch:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
@@ -368,14 +385,16 @@
NSString *serializerName = [command.arguments objectAtIndex:2];
NSDictionary *headers = [command.arguments objectAtIndex:3];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:4] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:5] boolValue];
NSString *responseType = [command.arguments objectAtIndex:6];
[self setRequestSerializer: serializerName forManager: manager];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect: manager];
[self setRedirect:followRedirect forManager:manager];
[self setResponseSerializer:responseType forManager:manager];
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
@@ -401,29 +420,35 @@
}
}
- (void)uploadFile:(CDVInvokedUrlCommand*)command {
- (void)uploadFiles:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *headers = [command.arguments objectAtIndex:1];
NSString *filePath = [command.arguments objectAtIndex: 2];
NSString *name = [command.arguments objectAtIndex: 3];
NSArray *filePaths = [command.arguments objectAtIndex: 2];
NSArray *names = [command.arguments objectAtIndex: 3];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:4] doubleValue];
NSURL *fileURL = [NSURL URLWithString: filePath];
bool followRedirect = [[command.arguments objectAtIndex:5] boolValue];
NSString *responseType = [command.arguments objectAtIndex:6];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect: manager];
[self setRedirect:followRedirect forManager:manager];
[self setResponseSerializer:responseType forManager:manager];
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
[manager POST:url parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
NSError *error;
[formData appendPartWithFileURL:fileURL name:name error:&error];
for (int i = 0; i < [filePaths count]; i++) {
NSString *filePath = (NSString *) [filePaths objectAtIndex:i];
NSString *uploadName = (NSString *) [names objectAtIndex:i];
NSURL *fileURL = [NSURL URLWithString: filePath];
[formData appendPartWithFileURL:fileURL name:uploadName error:&error];
}
if (error) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setObject:[NSNumber numberWithInt:500] forKey:@"status"];
@@ -456,23 +481,25 @@
}
- (void)downloadFile:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *headers = [command.arguments objectAtIndex:1];
NSString *filePath = [command.arguments objectAtIndex: 2];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:3] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:4] boolValue];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect: manager];
[self setRedirect:followRedirect forManager:manager];
if ([filePath hasPrefix:@"file://"]) {
filePath = [filePath substringFromIndex:7];
}
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {

View File

@@ -5,6 +5,4 @@
+ (instancetype)serializer;
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseBodyKey;
@end

View File

@@ -1,8 +1,5 @@
#import "TextResponseSerializer.h"
NSString * const AFNetworkingOperationFailingURLResponseBodyKey = @"com.alamofire.serialization.response.error.body";
NSStringEncoding const SupportedEncodings[6] = { NSUTF8StringEncoding, NSWindowsCP1252StringEncoding, NSISOLatin1StringEncoding, NSISOLatin2StringEncoding, NSASCIIStringEncoding, NSUnicodeStringEncoding };
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
if (!error) {
return underlyingError;
@@ -55,9 +52,14 @@ static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger co
nsEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
for (int i = 0; i < sizeof(SupportedEncodings) / sizeof(NSStringEncoding) && !decoded; ++i) {
if (cfEncoding == kCFStringEncodingInvalidId || nsEncoding == SupportedEncodings[i]) {
decoded = [[NSString alloc] initWithData:rawResponseData encoding:SupportedEncodings[i]];
NSStringEncoding supportedEncodings[6] = {
NSUTF8StringEncoding, NSWindowsCP1252StringEncoding, NSISOLatin1StringEncoding,
NSISOLatin2StringEncoding, NSASCIIStringEncoding, NSUnicodeStringEncoding
};
for (int i = 0; i < sizeof(supportedEncodings) / sizeof(NSStringEncoding) && !decoded; ++i) {
if (cfEncoding == kCFStringEncodingInvalidId || nsEncoding == supportedEncodings[i]) {
decoded = [[NSString alloc] initWithData:rawResponseData encoding:supportedEncodings[i]];
}
}
@@ -94,7 +96,7 @@ static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger co
NSURLErrorFailingURLErrorKey:[response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
AFNetworkingOperationFailingURLResponseDataErrorKey: data,
AFNetworkingOperationFailingURLResponseBodyKey: @"Could not decode response data due to invalid or unknown charset encoding",
AFNetworkingOperationFailingURLResponseBodyErrorKey: @"Could not decode response data due to invalid or unknown charset encoding",
} mutableCopy];
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
@@ -108,7 +110,7 @@ static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger co
if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
mutableUserInfo[AFNetworkingOperationFailingURLResponseBodyKey] = *decoded;
mutableUserInfo[AFNetworkingOperationFailingURLResponseBodyErrorKey] = *decoded;
}
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);

View File

@@ -1,12 +0,0 @@
const wd = require("wd");
require('colors');
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const should = chai.should();
chaiAsPromised.transferPromiseness = wd.transferPromiseness;
exports.should = should;

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com https://self-signed.badssl.com http://httpbin.org http://www.columbia.edu 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:;">
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com https://self-signed.badssl.com http://httpbin.org http://www.columbia.edu 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content: blob:;">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
@@ -15,7 +15,7 @@
<button id="nextBtn">Run next test</button>
<script type="text/javascript" src="cordova.js"></script>
<script type="text/javascript" src="app-test-definitions.js"></script>
<script type="text/javascript" src="e2e-specs.js"></script>
<script type="text/javascript" src="index.js"></script>
</body>
</html>

View File

@@ -3,17 +3,17 @@ const app = {
lastResult: null,
initialize: function() {
initialize: function () {
document.getElementById('nextBtn').addEventListener('click', app.onNextBtnClick);
},
printResult: function(prefix, content) {
printResult: function (prefix, content) {
const text = prefix + ': ' + JSON.stringify(content);
document.getElementById('resultTextarea').value += text;
},
reject: function(content) {
reject: function (content) {
document.getElementById('statusInput').value = 'finished';
app.printResult('result - rejected', content);
@@ -23,7 +23,7 @@ const app = {
};
},
resolve: function(content) {
resolve: function (content) {
document.getElementById('statusInput').value = 'finished';
app.printResult('result - resolved', content);
@@ -33,7 +33,7 @@ const app = {
};
},
throw: function(error) {
throw: function (error) {
document.getElementById('statusInput').value = 'finished';
app.printResult('result - throwed', error.message);
@@ -43,11 +43,11 @@ const app = {
};
},
getResult: function(cb) {
getResult: function (cb) {
cb(app.lastResult);
},
runTest: function(index) {
runTest: function (index) {
const testDefinition = tests[index];
const titleText = app.testIndex + ': ' + testDefinition.description;
const expectedText = 'expected - ' + testDefinition.expected;
@@ -57,32 +57,68 @@ const app = {
document.getElementById('resultTextarea').value = '';
document.getElementById('descriptionLbl').innerText = titleText;
try {
testDefinition.func(app.resolve, app.reject);
} catch (error) {
app.throw(error);
}
},
const onSuccessFactory = function (cbChain) {
return function () {
cbChain.shift()(cbChain);
}
};
onBeforeTest: function(testIndex, cb) {
app.lastResult = null;
const onFailFactory = function (prefix) {
return function (errorMessage) {
app.reject(prefix + ': ' + errorMessage);
}
};
if (hooks && hooks.onBeforeEachTest) {
return hooks.onBeforeEachTest(function() {
const testDefinition = tests[testIndex];
const onThrowedHandler = function (prefix, error) {
app.throw(new Error(prefix + ': ' + error.message));
};
if (testDefinition.before) {
testDefinition.before(cb);
} else {
cb();
const execBeforeEachTest = function (cbChain) {
const prefix = 'in before each hook';
try {
if (!hooks || !hooks.onBeforeEachTest) {
return onSuccessFactory(cbChain)();
}
});
} else {
cb();
}
hooks.onBeforeEachTest(
onSuccessFactory(cbChain),
onFailFactory(prefix)
);
} catch (error) {
onThrowedHandler(prefix, error);
}
};
const execBeforeTest = function (cbChain) {
const prefix = 'in before hook';
try {
if (!testDefinition.before) {
return onSuccessFactory(cbChain)();
}
testDefinition.before(
onSuccessFactory(cbChain),
onFailFactory(prefix)
);
} catch (error) {
onThrowedHandler(prefix, error);
}
};
const execTest = function () {
try {
testDefinition.func(app.resolve, app.reject);
} catch (error) {
app.throw(error);
}
};
onSuccessFactory([execBeforeEachTest, execBeforeTest, execTest])();
},
onFinishedAllTests: function() {
onFinishedAllTests: function () {
const titleText = 'No more tests';
const expectedText = 'You have run all available tests.';
@@ -91,13 +127,11 @@ const app = {
document.getElementById('descriptionLbl').innerText = titleText;
},
onNextBtnClick: function() {
onNextBtnClick: function () {
app.testIndex += 1;
if (app.testIndex < tests.length) {
app.onBeforeTest(app.testIndex, function() {
app.runTest(app.testIndex);
});
app.runTest(app.testIndex);
} else {
app.onFinishedAllTests();
}

View File

@@ -1,24 +1,52 @@
const hooks = {
onBeforeEachTest: function(done) {
onBeforeEachTest: function (resolve, reject) {
cordova.plugin.http.clearCookies();
helpers.setDefaultCertMode(done);
helpers.enableFollowingRedirect(function() {
// server trust mode is not supported on brpwser platform
if (cordova.platformId === 'browser') {
return resolve();
}
helpers.setDefaultServerTrustMode(function () {
// @TODO: not ready yet
// helpers.setNoneClientAuthMode(resolve, reject);
resolve();
}, reject);
});
}
};
const helpers = {
setDefaultCertMode: function(done) { cordova.plugin.http.setSSLCertMode('default', done, done); },
setNoCheckCertMode: function(done) { cordova.plugin.http.setSSLCertMode('nocheck', done, done); },
setPinnedCertMode: function(done) { cordova.plugin.http.setSSLCertMode('pinned', done, done); },
setJsonSerializer: function(done) { done(cordova.plugin.http.setDataSerializer('json')); },
setUtf8StringSerializer: function(done) { done(cordova.plugin.http.setDataSerializer('utf8')); },
setUrlEncodedSerializer: function(done) { done(cordova.plugin.http.setDataSerializer('urlencoded')); },
getWithXhr: function(done, url) {
setDefaultServerTrustMode: function (resolve, reject) { cordova.plugin.http.setServerTrustMode('default', resolve, reject); },
setNoCheckServerTrustMode: function (resolve, reject) { cordova.plugin.http.setServerTrustMode('nocheck', resolve, reject); },
setPinnedServerTrustMode: function (resolve, reject) { cordova.plugin.http.setServerTrustMode('pinned', resolve, reject); },
setNoneClientAuthMode: function (resolve, reject) { cordova.plugin.http.setClientAuthMode('none', resolve, reject); },
setBufferClientAuthMode: function (resolve, reject) {
helpers.getWithXhr(function(pkcs) {
cordova.plugin.http.setClientAuthMode('buffer', {
rawPkcs: pkcs,
pkcsPassword: 'badssl.com'
}, resolve, reject);
}, './certificates/badssl-client-cert.pkcs', 'arraybuffer');
},
setJsonSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('json')); },
setUtf8StringSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('utf8')); },
setUrlEncodedSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('urlencoded')); },
disableFollowingRedirect: function (resolve) { resolve(cordova.plugin.http.setFollowRedirect(false)); },
enableFollowingRedirect: function(resolve) { resolve(cordova.plugin.http.setFollowRedirect(true)); },
getWithXhr: function (done, url, type) {
var xhr = new XMLHttpRequest();
xhr.addEventListener('load', function() {
done(this.responseText);
xhr.addEventListener('load', function () {
if (!type || type === 'text') {
done(this.responseText);
} else {
done(this.response);
}
});
xhr.responseType = type;
xhr.open('GET', url);
xhr.send();
},
@@ -26,7 +54,7 @@ const helpers = {
window.resolveLocalFileSystemURL(cordova.file.cacheDirectory, function (directoryEntry) {
directoryEntry.getFile(fileName, { create: true, exclusive: false }, function (fileEntry) {
fileEntry.createWriter(function (fileWriter) {
var blob = new Blob([ content ], { type: 'text/plain' });
var blob = new Blob([content], { type: 'text/plain' });
fileWriter.onwriteend = done;
fileWriter.onerror = done;
@@ -34,20 +62,32 @@ const helpers = {
}, done);
}, done);
}, done);
},
// adopted from: https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
hashArrayBuffer: function (buffer) {
var hash = 0;
var byteArray = new Uint8Array(buffer);
for (var i = 0; i < byteArray.length; i++) {
hash = ((hash << 5) - hash) + byteArray[i];
hash |= 0; // Convert to 32bit integer
}
return hash;
}
};
const messageFactory = {
sslTrustAnchor: function() { return 'SSL handshake failed: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.' },
invalidCertificate: function(domain) { return 'The certificate for this server is invalid. You might be connecting to a server that is pretending to be “' + domain + '” which could put your confidential information at risk.' }
sslTrustAnchor: function () { return 'TLS connection could not be established: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.' },
invalidCertificate: function (domain) { return 'The certificate for this server is invalid. You might be connecting to a server that is pretending to be “' + domain + '” which could put your confidential information at risk.' }
}
const tests = [
{
description: 'should reject self signed cert (GET)',
expected: 'rejected: {"status":-2, ...',
func: function(resolve, reject) { cordova.plugin.http.get('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function(driver, result, targetInfo) {
func: function (resolve, reject) { cordova.plugin.http.get('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function (driver, result, targetInfo) {
result.type.should.be.equal('rejected');
result.data.should.be.eql({ status: -2, error: targetInfo.isAndroid ? messageFactory.sslTrustAnchor() : messageFactory.invalidCertificate('self-signed.badssl.com') });
}
@@ -55,8 +95,8 @@ const tests = [
{
description: 'should reject self signed cert (PUT)',
expected: 'rejected: {"status":-2, ...',
func: function(resolve, reject) { cordova.plugin.http.put('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result, targetInfo) {
func: function (resolve, reject) { cordova.plugin.http.put('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result, targetInfo) {
result.type.should.be.equal('rejected');
result.data.should.be.eql({ status: -2, error: targetInfo.isAndroid ? messageFactory.sslTrustAnchor() : messageFactory.invalidCertificate('self-signed.badssl.com') });
}
@@ -64,8 +104,8 @@ const tests = [
{
description: 'should reject self signed cert (POST)',
expected: 'rejected: {"status":-2, ...',
func: function(resolve, reject) { cordova.plugin.http.post('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result, targetInfo) {
func: function (resolve, reject) { cordova.plugin.http.post('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result, targetInfo) {
result.type.should.be.equal('rejected');
result.data.should.be.eql({ status: -2, error: targetInfo.isAndroid ? messageFactory.sslTrustAnchor() : messageFactory.invalidCertificate('self-signed.badssl.com') });
}
@@ -73,8 +113,8 @@ const tests = [
{
description: 'should reject self signed cert (PATCH)',
expected: 'rejected: {"status":-2, ...',
func: function(resolve, reject) { cordova.plugin.http.patch('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result, targetInfo) {
func: function (resolve, reject) { cordova.plugin.http.patch('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result, targetInfo) {
result.type.should.be.equal('rejected');
result.data.should.be.eql({ status: -2, error: targetInfo.isAndroid ? messageFactory.sslTrustAnchor() : messageFactory.invalidCertificate('self-signed.badssl.com') });
}
@@ -82,8 +122,8 @@ const tests = [
{
description: 'should reject self signed cert (DELETE)',
expected: 'rejected: {"status":-2, ...',
func: function(resolve, reject) { cordova.plugin.http.delete('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function(driver, result, targetInfo) {
func: function (resolve, reject) { cordova.plugin.http.delete('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function (driver, result, targetInfo) {
result.type.should.be.equal('rejected');
result.data.should.be.eql({ status: -2, error: targetInfo.isAndroid ? messageFactory.sslTrustAnchor() : messageFactory.invalidCertificate('self-signed.badssl.com') });
}
@@ -91,9 +131,9 @@ const tests = [
{
description: 'should accept bad cert (GET)',
expected: 'resolved: {"status":200, ...',
before: helpers.setNoCheckCertMode,
func: function(resolve, reject) { cordova.plugin.http.get('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
before: helpers.setNoCheckServerTrustMode,
func: function (resolve, reject) { cordova.plugin.http.get('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.should.include({ status: 200 });
}
@@ -101,9 +141,9 @@ const tests = [
{
description: 'should accept bad cert (PUT)',
expected: 'rejected: {"status":405, ... // will be rejected because PUT is not allowed',
before: helpers.setNoCheckCertMode,
func: function(resolve, reject) { cordova.plugin.http.put('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
before: helpers.setNoCheckServerTrustMode,
func: function (resolve, reject) { cordova.plugin.http.put('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
result.data.should.include({ status: 405 });
}
@@ -111,9 +151,9 @@ const tests = [
{
description: 'should accept bad cert (POST)',
expected: 'rejected: {"status":405, ... // will be rejected because POST is not allowed',
before: helpers.setNoCheckCertMode,
func: function(resolve, reject) { cordova.plugin.http.post('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
before: helpers.setNoCheckServerTrustMode,
func: function (resolve, reject) { cordova.plugin.http.post('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
result.data.should.include({ status: 405 });
}
@@ -121,9 +161,9 @@ const tests = [
{
description: 'should accept bad cert (PATCH)',
expected: 'rejected: {"status":405, ... // will be rejected because PATCH is not allowed',
before: helpers.setNoCheckCertMode,
func: function(resolve, reject) { cordova.plugin.http.patch('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
before: helpers.setNoCheckServerTrustMode,
func: function (resolve, reject) { cordova.plugin.http.patch('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
result.data.should.include({ status: 405 });
}
@@ -131,9 +171,9 @@ const tests = [
{
description: 'should accept bad cert (DELETE)',
expected: 'rejected: {"status":405, ... // will be rejected because DELETE is not allowed',
before: helpers.setNoCheckCertMode,
func: function(resolve, reject) { cordova.plugin.http.delete('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
before: helpers.setNoCheckServerTrustMode,
func: function (resolve, reject) { cordova.plugin.http.delete('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
result.data.should.include({ status: 405 });
}
@@ -141,9 +181,9 @@ const tests = [
{
description: 'should fetch data from http://httpbin.org/ (GET)',
expected: 'resolved: {"status":200, ...',
before: helpers.setNoCheckCertMode,
func: function(resolve, reject) { cordova.plugin.http.get('http://httpbin.org/', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
before: helpers.setNoCheckServerTrustMode,
func: function (resolve, reject) { cordova.plugin.http.get('http://httpbin.org/', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.should.include({ status: 200 });
}
@@ -152,8 +192,8 @@ const tests = [
description: 'should send JSON object correctly (POST)',
expected: 'resolved: {"status": 200, "data": "{\\"json\\":\\"test\\": \\"testString\\"}\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).json.should.eql({ test: 'testString' });
}
@@ -162,8 +202,8 @@ const tests = [
description: 'should send JSON object correctly (PUT)',
expected: 'resolved: {"status": 200, "data": "{\\"json\\":\\"test\\": \\"testString\\"}\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.put('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.put('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).json.should.eql({ test: 'testString' });
}
@@ -172,8 +212,8 @@ const tests = [
description: 'should send JSON object correctly (PATCH)',
expected: 'resolved: {"status": 200, "data": "{\\"json\\":\\"test\\": \\"testString\\"}\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.patch('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.patch('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).json.should.eql({ test: 'testString' });
}
@@ -182,39 +222,39 @@ const tests = [
description: 'should send JSON array correctly (POST) #26',
expected: 'resolved: {"status": 200, "data": "[ 1, 2, 3 ]\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', [ 1, 2, 3 ], {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', [1, 2, 3], {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).json.should.eql([ 1, 2, 3 ]);
JSON.parse(result.data.data).json.should.eql([1, 2, 3]);
}
},
{
description: 'should send JSON array correctly (PUT) #26',
expected: 'resolved: {"status": 200, "data": "[ 1, 2, 3 ]\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.put('http://httpbin.org/anything', [ 1, 2, 3 ], {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.put('http://httpbin.org/anything', [1, 2, 3], {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).json.should.eql([ 1, 2, 3 ]);
JSON.parse(result.data.data).json.should.eql([1, 2, 3]);
}
},
{
description: 'should send JSON array correctly (PATCH) #26',
expected: 'resolved: {"status": 200, "data": "[ 1, 2, 3 ]\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.patch('http://httpbin.org/anything', [ 1, 2, 3 ], {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.patch('http://httpbin.org/anything', [1, 2, 3], {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.data.should.be.a('string');
JSON.parse(result.data.data).json.should.eql([ 1, 2, 3 ]);
JSON.parse(result.data.data).json.should.eql([1, 2, 3]);
}
},
{
description: 'should send url encoded data correctly (POST) #41',
expected: 'resolved: {"status": 200, "data": "{\\"form\\":\\"test\\": \\"testString\\"}\" ...',
before: helpers.setUrlEncodedSerializer,
func: function(resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).form.should.eql({ test: 'testString' });
}
@@ -223,8 +263,8 @@ const tests = [
description: 'should send url encoded data correctly (PUT)',
expected: 'resolved: {"status": 200, "data": "{\\"form\\":\\"test\\": \\"testString\\"}\" ...',
before: helpers.setUrlEncodedSerializer,
func: function(resolve, reject) { cordova.plugin.http.put('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.put('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).form.should.eql({ test: 'testString' });
}
@@ -233,8 +273,8 @@ const tests = [
description: 'should send url encoded data correctly (PATCH)',
expected: 'resolved: {"status": 200, "data": "{\\"form\\":\\"test\\": \\"testString\\"}\" ...',
before: helpers.setUrlEncodedSerializer,
func: function(resolve, reject) { cordova.plugin.http.patch('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.patch('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).form.should.eql({ test: 'testString' });
}
@@ -242,21 +282,31 @@ const tests = [
{
description: 'should resolve correct URL after redirect (GET) #33',
expected: 'resolved: {"status": 200, url: "http://httpbin.org/anything", ...',
func: function(resolve, reject) { cordova.plugin.http.get('http://httpbin.org/redirect-to?url=http://httpbin.org/anything', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.get('http://httpbin.org/redirect-to?url=http://httpbin.org/anything', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.url.should.be.equal('http://httpbin.org/anything');
}
},
{
description: 'should not follow 302 redirect when following redirects is disabled',
expected: 'rejected: {"status": 302, ...',
before: function(resolve, reject) { cordova.plugin.http.disableRedirect(true, resolve, reject)},
func: function (resolve, reject) { cordova.plugin.http.get('http://httpbin.org/redirect-to?url=http://httpbin.org/anything', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
result.data.status.should.be.equal(302);
}
},
{
description: 'should download a file from given URL to given path in local filesystem',
expected: 'resolved: {"content": "<?xml version=\'1.0\' encoding=\'us-ascii\'?>\\n\\n<!-- A SAMPLE set of slides -->" ...',
func: function(resolve, reject) {
func: function (resolve, reject) {
var sourceUrl = 'http://httpbin.org/xml';
var targetPath = cordova.file.cacheDirectory + 'test.xml';
cordova.plugin.http.downloadFile(sourceUrl, {}, {}, targetPath, function(entry) {
helpers.getWithXhr(function(content) {
cordova.plugin.http.downloadFile(sourceUrl, {}, {}, targetPath, function (entry) {
helpers.getWithXhr(function (content) {
resolve({
sourceUrl: sourceUrl,
targetPath: targetPath,
@@ -267,7 +317,7 @@ const tests = [
}, targetPath);
}, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.name.should.be.equal('test.xml');
result.data.content.should.be.equal("<?xml version='1.0' encoding='us-ascii'?>\n\n<!-- A SAMPLE set of slides -->\n\n<slideshow \n title=\"Sample Slide Show\"\n date=\"Date of publication\"\n author=\"Yours Truly\"\n >\n\n <!-- TITLE SLIDE -->\n <slide type=\"all\">\n <title>Wake up to WonderWidgets!</title>\n </slide>\n\n <!-- OVERVIEW -->\n <slide type=\"all\">\n <title>Overview</title>\n <item>Why <em>WonderWidgets</em> are great</item>\n <item/>\n <item>Who <em>buys</em> WonderWidgets</item>\n </slide>\n\n</slideshow>");
@@ -276,17 +326,17 @@ const tests = [
{
description: 'should upload a file from given path in local filesystem to given URL #27',
expected: 'resolved: {"status": 200, "data": "files": {"test-file.txt": "I am a dummy file. I am used ...',
func: function(resolve, reject) {
func: function (resolve, reject) {
var fileName = 'test-file.txt';
var fileContent = 'I am a dummy file. I am used for testing purposes!';
var sourcePath = cordova.file.cacheDirectory + fileName;
var targetUrl = 'http://httpbin.org/post';
helpers.writeToFile(function() {
helpers.writeToFile(function () {
cordova.plugin.http.uploadFile(targetUrl, {}, {}, sourcePath, fileName, resolve, reject);
}, fileName, fileContent);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
var fileName = 'test-file.txt';
var fileContent = 'I am a dummy file. I am used for testing purposes!';
@@ -299,13 +349,50 @@ const tests = [
.should.be.equal(fileContent);
}
},
{
description: 'should upload multiple files from given paths in local filesystem to given URL #127',
expected: 'resolved: {"status": 200, "data": "files": {"test-file.txt": "I am a dummy file. I am used ...',
func: function (resolve, reject) {
var fileName = 'test-file.txt';
var fileName2 = 'test-file2.txt';
var fileContent = 'I am a dummy file. I am used for testing purposes!';
var fileContent2 = 'I am the second dummy file. I am used for testing purposes!';
var sourcePath = cordova.file.cacheDirectory + fileName;
var sourcePath2 = cordova.file.cacheDirectory + fileName2;
var targetUrl = 'http://httpbin.org/post';
helpers.writeToFile(function () {
helpers.writeToFile(function() {
cordova.plugin.http.uploadFile(targetUrl, {}, {}, [sourcePath, sourcePath2], [fileName, fileName2], resolve, reject);
}, fileName2, fileContent2);
}, fileName, fileContent);
},
validationFunc: function (driver, result) {
var fileName = 'test-file.txt';
var fileName2 = 'test-file2.txt';
var fileContent = 'I am a dummy file. I am used for testing purposes!';
var fileContent2 = 'I am the second dummy file. I am used for testing purposes!';
result.type.should.be.equal('resolved');
result.data.data.should.be.a('string');
var parsed = JSON.parse(result.data.data);
parsed.files[fileName].should.be.equal(fileContent);
parsed.files[fileName2].should.be.equal(fileContent2);
}
},
{
description: 'should encode HTTP array params correctly (GET) #45',
expected: 'resolved: {"status": 200, "data": "{\\"url\\":\\"http://httpbin.org/get?myArray[]=val1&myArray[]=val2&myArray[]=val3\\"}\" ...',
func: function(resolve, reject) {
cordova.plugin.http.get('http://httpbin.org/get', { myArray: [ 'val1', 'val2', 'val3' ], myString: 'testString' }, {}, resolve, reject);
func: function (resolve, reject) {
cordova.plugin.http.get('http://httpbin.org/get', { myArray: ['val1', 'val2', 'val3'], myString: 'testString' }, {}, resolve, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.data.should.be.a('string');
@@ -318,32 +405,32 @@ const tests = [
{
description: 'should throw on non-string values in local header object #54',
expected: 'throwed: {"message": "advanced-http: header values must be strings"}',
func: function(resolve, reject) {
func: function (resolve, reject) {
cordova.plugin.http.get('http://httpbin.org/get', {}, { myTestHeader: 1 }, resolve, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('throwed');
result.message.should.be.equal('advanced-http: header values must be strings');
result.message.should.be.equal(require('../www/messages').TYPE_MISMATCH_HEADERS);
}
},
{
description: 'should throw an error while setting non-string value as global header #54',
expected: 'throwed: "advanced-http: header values must be strings"',
func: function(resolve, reject) {
func: function (resolve, reject) {
cordova.plugin.http.setHeader('myTestHeader', 2);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('throwed');
result.message.should.be.equal('advanced-http: header values must be strings');
result.message.should.be.equal(require('../www/messages').INVALID_HEADER_VALUE);
}
},
{
description: 'should accept content-type "application/xml" #58',
expected: 'resolved: {"status": 200, ...',
func: function(resolve, reject) {
func: function (resolve, reject) {
cordova.plugin.http.get('http://httpbin.org/xml', {}, {}, resolve, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.status.should.be.equal(200);
}
@@ -351,12 +438,12 @@ const tests = [
{
description: 'should send programmatically set cookies correctly (GET)',
expected: 'resolved: {"status": 200, ...',
func: function(resolve, reject) {
func: function (resolve, reject) {
cordova.plugin.http.setCookie('http://httpbin.org/get', 'myCookie=myValue');
cordova.plugin.http.setCookie('http://httpbin.org/get', 'mySecondCookie=mySecondValue');
cordova.plugin.http.get('http://httpbin.org/get', {}, {}, resolve, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.data.should.be.a('string');
@@ -370,35 +457,34 @@ const tests = [
{
description: 'should not send any cookies after running "clearCookies" (GET) #59',
expected: 'resolved: {"status": 200, "data": "{\"headers\": {\"Cookie\": \"\"...',
func: function(resolve, reject) {
func: function (resolve, reject) {
cordova.plugin.http.setCookie('http://httpbin.org/get', 'myCookie=myValue');
cordova.plugin.http.setCookie('http://httpbin.org/get', 'mySecondCookie=mySecondValue');
cordova.plugin.http.clearCookies();
cordova.plugin.http.get('http://httpbin.org/get', {}, {}, resolve, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.data.should.be.a('string');
JSON
.parse(result.data.data)
.headers
.Cookie
.should.be.equal('');
.should.not.have.property('Cookie');
}
},
{
description: 'should send programmatically set cookies correctly (DOWNLOAD) #57',
expected: 'resolved: {"content":{"cookies":{"myCookie":"myValue ...',
func: function(resolve, reject) {
func: function (resolve, reject) {
var sourceUrl = 'http://httpbin.org/cookies';
var targetPath = cordova.file.cacheDirectory + 'cookies.json';
cordova.plugin.http.setCookie('http://httpbin.org/get', 'myCookie=myValue');
cordova.plugin.http.setCookie('http://httpbin.org/get', 'mySecondCookie=mySecondValue');
cordova.plugin.http.downloadFile(sourceUrl, {}, {}, targetPath, function(entry) {
helpers.getWithXhr(function(content) {
cordova.plugin.http.downloadFile(sourceUrl, {}, {}, targetPath, function (entry) {
helpers.getWithXhr(function (content) {
resolve({
sourceUrl: sourceUrl,
targetPath: targetPath,
@@ -409,7 +495,7 @@ const tests = [
}, targetPath);
}, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.name.should.be.equal('cookies.json');
result.data.content.should.be.a('string');
@@ -424,10 +510,10 @@ const tests = [
description: 'should send UTF-8 encoded raw string correctly (POST) #34',
expected: 'resolved: {"status": 200, "data": "{\\"data\\": \\"this is a test string\\"...',
before: helpers.setUtf8StringSerializer,
func: function(resolve, reject) {
func: function (resolve, reject) {
cordova.plugin.http.post('http://httpbin.org/anything', 'this is a test string', {}, resolve, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).data.should.be.equal('this is a test string');
}
@@ -435,10 +521,10 @@ const tests = [
{
description: 'should encode spaces in query string (params object) correctly (GET) #71',
expected: 'resolved: {"status": 200, "data": "{\\"args\\": \\"query param\\": \\"and value with spaces\\"...',
func: function(resolve, reject) {
func: function (resolve, reject) {
cordova.plugin.http.get('http://httpbin.org/get', { 'query param': 'and value with spaces' }, {}, resolve, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).args['query param'].should.be.equal('and value with spaces');
}
@@ -446,10 +532,10 @@ const tests = [
{
description: 'should decode latin1 (iso-8859-1) encoded body correctly (GET) #72',
expected: 'resolved: {"status": 200, "data": "<!DOCTYPE HTML PUBLIC \\"-//W3C//DTD HTML 4.01 Transitional//EN\\"> ...',
func: function(resolve, reject) {
func: function (resolve, reject) {
cordova.plugin.http.get('http://www.columbia.edu/kermit/latin1.html', {}, {}, resolve, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.data.should.include('[¡] 161 10/01 241 A1 INVERTED EXCLAMATION MARK\n[¢] 162 10/02 242 A2 CENT SIGN');
}
@@ -457,10 +543,10 @@ const tests = [
{
description: 'should return empty body string correctly (GET)',
expected: 'resolved: {"status": 200, "data": "" ...',
func: function(resolve, reject) {
func: function (resolve, reject) {
cordova.plugin.http.get('http://httpbin.org/stream/0', {}, {}, resolve, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.data.should.be.equal('');
}
@@ -468,11 +554,11 @@ const tests = [
{
description: 'should pin SSL cert correctly (GET)',
expected: 'resolved: {"status": 200 ...',
before: helpers.setPinnedCertMode,
func: function(resolve, reject) {
before: helpers.setPinnedServerTrustMode,
func: function (resolve, reject) {
cordova.plugin.http.get('https://httpbin.org', {}, {}, resolve, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.status.should.be.equal(200);
}
@@ -480,11 +566,11 @@ const tests = [
{
description: 'should reject when pinned cert does not match received server cert (GET)',
expected: 'rejected: {"status": -2 ...',
before: helpers.setPinnedCertMode,
func: function(resolve, reject) {
before: helpers.setPinnedServerTrustMode,
func: function (resolve, reject) {
cordova.plugin.http.get('https://sha512.badssl.com/', {}, {}, resolve, reject);
},
validationFunc: function(driver, result, targetInfo) {
validationFunc: function (driver, result, targetInfo) {
result.type.should.be.equal('rejected');
result.data.should.be.eql({ status: -2, error: targetInfo.isAndroid ? messageFactory.sslTrustAnchor() : messageFactory.invalidCertificate('sha512.badssl.com') });
}
@@ -493,18 +579,18 @@ const tests = [
description: 'should send deeply structured JSON object correctly (POST) #65',
expected: 'resolved: {"status": 200, "data": "{\\"data\\": \\"{\\\\"outerObj\\\\":{\\\\"innerStr\\\\":\\\\"testString\\\\",\\\\"innerArr\\\\":[1,2,3]}}\\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', { outerObj: { innerStr: 'testString', innerArr: [1, 2, 3] }}, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', { outerObj: { innerStr: 'testString', innerArr: [1, 2, 3] } }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).json.should.eql({ outerObj: { innerStr: 'testString', innerArr: [1, 2, 3] }});
JSON.parse(result.data.data).json.should.eql({ outerObj: { innerStr: 'testString', innerArr: [1, 2, 3] } });
}
},
{
description: 'should override header "content-type" correctly (POST) #78',
expected: 'resolved: {"status": 200, "headers": "{\\"Content-Type\\": \\"text/plain\\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', {}, { 'Content-Type': 'text/plain' }, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', {}, { 'Content-Type': 'text/plain' }, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).headers['Content-Type'].should.be.equal('text/plain');
}
@@ -512,8 +598,8 @@ const tests = [
{
description: 'should handle error during file download correctly (DOWNLOAD) #83',
expected: 'rejected: {"status": 403, "error": "There was an error downloading the file" ...',
func: function(resolve, reject) { cordova.plugin.http.downloadFile('http://httpbin.org/status/403', {}, {}, cordova.file.tempDirectory + 'testfile.txt', resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.downloadFile('http://httpbin.org/status/403', {}, {}, cordova.file.tempDirectory + 'testfile.txt', resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
result.data.status.should.be.equal(403);
result.data.error.should.be.equal('There was an error downloading the file');
@@ -522,8 +608,8 @@ const tests = [
{
description: 'should handle gzip encoded response correctly',
expected: 'resolved: {"status": 200, "headers": "{\\"Content-Encoding\\": \\"gzip\\" ...',
func: function(resolve, reject) { cordova.plugin.http.get('http://httpbin.org/gzip', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.get('http://httpbin.org/gzip', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.status.should.be.equal(200);
JSON.parse(result.data.data).gzipped.should.be.equal(true);
@@ -533,8 +619,8 @@ const tests = [
description: 'should send empty string correctly',
expected: 'resolved: {"status": 200, "data": "{\\"json\\":\\"\\" ...',
before: helpers.setUtf8StringSerializer,
func: function(resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', '', {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', '', {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).data.should.be.equal('');
}
@@ -543,8 +629,8 @@ const tests = [
description: 'shouldn\'t escape forward slashes #184',
expected: 'resolved: {"status": 200, "data": "{\\"json\\":\\"/\\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', { testString: '/' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', { testString: '/' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).json.testString.should.be.equal('/');
}
@@ -552,8 +638,8 @@ const tests = [
{
description: 'should not double encode spaces in url path #195',
expected: 'resolved: {"status": 200, "data": "{\\"url\\":\\"https://httpbin.org/anything/containing spaces in url\\" ...',
func: function(resolve, reject) { cordova.plugin.http.get('https://httpbin.org/anything/containing%20spaces%20in%20url', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.get('https://httpbin.org/anything/containing%20spaces%20in%20url', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).url.should.be.equal('https://httpbin.org/anything/containing spaces in url');
}
@@ -561,8 +647,8 @@ const tests = [
{
description: 'should encode spaces in url query correctly',
expected: 'resolved: {"status": 200, "data": "{\\"url\\":\\"https://httpbin.org/anything?query key=very long query value with spaces\\" ...',
func: function(resolve, reject) { cordova.plugin.http.get('https://httpbin.org/anything', { 'query key': 'very long query value with spaces' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.get('https://httpbin.org/anything', { 'query key': 'very long query value with spaces' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).url.should.be.equal('https://httpbin.org/anything?query key=very long query value with spaces');
}
@@ -570,12 +656,12 @@ const tests = [
{
description: 'should download a file from given HTTPS URL to given path in local filesystem #197',
expected: 'resolved: {"content": "<?xml version=\'1.0\' encoding=\'us-ascii\'?>\\n\\n<!-- A SAMPLE set of slides -->" ...',
func: function(resolve, reject) {
func: function (resolve, reject) {
var sourceUrl = 'https://httpbin.org/xml';
var targetPath = cordova.file.cacheDirectory + 'test.xml';
cordova.plugin.http.downloadFile(sourceUrl, {}, {}, targetPath, function(entry) {
helpers.getWithXhr(function(content) {
cordova.plugin.http.downloadFile(sourceUrl, {}, {}, targetPath, function (entry) {
helpers.getWithXhr(function (content) {
resolve({
sourceUrl: sourceUrl,
targetPath: targetPath,
@@ -586,12 +672,135 @@ const tests = [
}, targetPath);
}, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.name.should.be.equal('test.xml');
result.data.content.should.be.equal("<?xml version='1.0' encoding='us-ascii'?>\n\n<!-- A SAMPLE set of slides -->\n\n<slideshow \n title=\"Sample Slide Show\"\n date=\"Date of publication\"\n author=\"Yours Truly\"\n >\n\n <!-- TITLE SLIDE -->\n <slide type=\"all\">\n <title>Wake up to WonderWidgets!</title>\n </slide>\n\n <!-- OVERVIEW -->\n <slide type=\"all\">\n <title>Overview</title>\n <item>Why <em>WonderWidgets</em> are great</item>\n <item/>\n <item>Who <em>buys</em> WonderWidgets</item>\n </slide>\n\n</slideshow>");
}
},
{
description: 'should return header object when request failed due to non-success response from server #221',
expected: 'rejected:',
func: function (resolve, reject) { cordova.plugin.http.get('https://httpbin.org/status/418', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
result.data.headers.should.be.an('object');
}
},
{
description: 'should return status code when request failed due to non-success response from server',
expected: 'rejected:',
func: function (resolve, reject) { cordova.plugin.http.get('https://httpbin.org/status/418', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
result.data.status.should.be.equal(418);
}
},
{
description: 'should return url string when request failed due to non-success response from server',
expected: 'rejected:',
func: function (resolve, reject) { cordova.plugin.http.get('https://httpbin.org/status/418', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
result.data.url.should.be.equal('https://httpbin.org/status/418');
}
},
{
description: 'shouldn\'t return header object when request failed before receiving response from server',
expected: 'rejected:',
func: function (resolve, reject) { cordova.plugin.http.get('https://not_existing_url', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
should.equal(result.data.headers, undefined);
}
},
{
description: 'should return status code when request failed before receiving response from server',
expected: 'rejected:',
func: function (resolve, reject) { cordova.plugin.http.get('https://not_existing_url', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
result.data.status.should.be.a('number');
}
},
{
description: 'shouldn\'t return url string when request failed before receiving response from server',
expected: 'rejected:',
func: function (resolve, reject) { cordova.plugin.http.get('https://not_existing_url', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
should.equal(result.data.url, undefined);
}
},
{
description: 'should fetch binary correctly when response type is "arraybuffer"',
expected: 'resolved: {"isArrayBuffer:true,"hash":-1032603775,"byteLength":35588}',
func: function (resolve, reject) {
var url = 'https://httpbin.org/image/jpeg';
var options = { method: 'get', responseType: 'arraybuffer' };
var success = function (response) {
resolve({
isArrayBuffer: response.data.constructor === ArrayBuffer,
hash: helpers.hashArrayBuffer(response.data),
byteLength: response.data.byteLength
});
};
cordova.plugin.http.sendRequest(url, options, success, reject);
},
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.isArrayBuffer.should.be.equal(true);
result.data.hash.should.be.equal(-1032603775);
result.data.byteLength.should.be.equal(35588);
}
},
{
description: 'should fetch binary correctly when response type is "blob"',
expected: 'resolved: {"isBlob":true,byteLength":35588}',
func: function (resolve, reject) {
var url = 'https://httpbin.org/image/jpeg';
var options = { method: 'get', responseType: 'blob' };
var success = function (response) {
resolve({
isBlob: response.data.constructor === Blob,
type: response.data.type,
byteLength: response.data.size
});
};
cordova.plugin.http.sendRequest(url, options, success, reject);
},
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.isBlob.should.be.equal(true);
result.data.type.should.be.equal('image/jpeg');
result.data.byteLength.should.be.equal(35588);
}
},
{
description: 'should decode error body even if response type is "arraybuffer"',
expected: 'rejected: {"status": 418, ...',
func: function (resolve, reject) {
var url = 'https://httpbin.org/status/418';
var options = { method: 'get', responseType: 'arraybuffer' };
cordova.plugin.http.sendRequest(url, options, resolve, reject);
},
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
result.data.status.should.be.equal(418);
result.data.error.should.be.equal("\n -=[ teapot ]=-\n\n _...._\n .' _ _ `.\n | .\"` ^ `\". _,\n \\_;`\"---\"`|//\n | ;/\n \\_ _/\n `\"\"\"`\n");
}
}
// @TODO: not ready yet
// {
// description: 'should authenticate correctly when client cert auth is configured with a PKCS12 container',
// expected: 'resolved: {"status": 200, ...',
// before: helpers.setBufferClientAuthMode,
// func: function (resolve, reject) { cordova.plugin.http.get('https://client.badssl.com/', {}, {}, resolve, reject); },
// validationFunc: function (driver, result) {
// result.type.should.be.equal('resolved');
// result.data.data.should.include('TLS handshake');
// }
// }
];
if (typeof module !== 'undefined' && module.exports) {

View File

@@ -26,7 +26,7 @@ const local = {
const sauce = {
iosDevice: {
browserName: '',
'appium-version': '1.7.1',
'appium-version': '1.9.1',
platformName: 'iOS',
platformVersion: '10.3',
deviceName: 'iPhone 6',
@@ -35,7 +35,7 @@ const sauce = {
},
iosEmulator: {
browserName: '',
'appium-version': '1.7.1',
'appium-version': '1.9.1',
platformName: 'iOS',
platformVersion: '10.3',
deviceName: 'iPhone Simulator',
@@ -44,7 +44,7 @@ const sauce = {
},
androidEmulator: {
browserName: '',
'appium-version': '1.7.1',
'appium-version': '1.9.1',
platformName: 'Android',
platformVersion: '5.1',
deviceName: 'Android Emulator',

View File

@@ -1,12 +1,18 @@
require('./helpers/setup');
const wd = require('wd');
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
const apps = require('./helpers/apps');
const caps = Object.assign({}, require('./helpers/caps'));
const serverConfig = require('./helpers/server');
const testDefinitions = require('../app-test-definitions');
const testDefinitions = require('../e2e-specs');
const pkgjson = require('../../package.json');
chai.use(chaiAsPromised);
chaiAsPromised.transferPromiseness = wd.transferPromiseness;
global.should = chai.should();
require('colors');
describe('Advanced HTTP', function() {
const isDevice = process.argv.includes('--device');
const isAndroid = process.argv.includes('--android');
@@ -49,7 +55,7 @@ describe('Advanced HTTP', function() {
if (value === 'finished') {
resolve();
} else if (Date.now() > timeoutTimestamp) {
reject('Test function timed out!');
reject(new Error('Test function timed out!'));
} else {
setTimeout(checkIfFinished, 500);
}

View File

@@ -1,236 +0,0 @@
const chai = require('chai');
const mock = require('mock-require');
const should = chai.should();
describe('Advanced HTTP public interface', function () {
let http = {};
const noop = () => { /* intentionally doing nothing */ };
const getDependenciesBlueprint = () => {
const messages = require('../www/messages');
const globalConfigs = require('../www/global-configs');
const ToughCookie = require('../www/umd-tough-cookie');
const lodash = require('../www/lodash');
const WebStorageCookieStore = require('../www/local-storage-store')(ToughCookie, lodash);
const cookieHandler = require('../www/cookie-handler')(null, ToughCookie, WebStorageCookieStore);
const helpers = require('../www/helpers')(cookieHandler, messages);
const urlUtil = require('../www/url-util')(helpers);
return { exec: noop, cookieHandler, urlUtil: urlUtil, helpers, globalConfigs };
};
const loadHttp = (deps) => {
http = require('../www/public-interface')(deps.exec, deps.cookieHandler, deps.urlUtil, deps.helpers, deps.globalConfigs);
};
beforeEach(() => {
// mocked btoa function (base 64 encoding strings)
global.btoa = decoded => new Buffer(decoded).toString('base64');
loadHttp(getDependenciesBlueprint());
});
it('sets global headers correctly with two args (old interface)', () => {
http.setHeader('myKey', 'myValue');
http.getHeaders('*').myKey.should.equal('myValue');
});
it('sets global headers correctly with three args (new interface) #24', () => {
http.setHeader('*', 'myKey', 'myValue');
http.getHeaders('*').myKey.should.equal('myValue');
});
it('sets host headers correctly #24', () => {
http.setHeader('www.google.de', 'myKey', 'myValue');
http.getHeaders('www.google.de').myKey.should.equal('myValue');
});
it('resolves global headers correctly #24', () => {
const deps = getDependenciesBlueprint();
deps.cookieHandler.getCookieString = () => 'fakeCookieString';
deps.exec = (onSuccess, onFail, namespace, method, params) => {
const headers = params[1];
headers.should.eql({
Cookie: 'fakeCookieString',
myKey: 'myValue'
});
};
loadHttp(deps);
http.setHeader('*', 'myKey', 'myValue');
http.get('url', {}, {}, noop, noop);
});
it('resolves host headers correctly (set without port number) #37', () => {
const deps = getDependenciesBlueprint();
deps.cookieHandler.getCookieString = () => 'fakeCookieString';
deps.exec = (onSuccess, onFail, namespace, method, params) => {
const headers = params[1];
headers.should.eql({
Cookie: 'fakeCookieString',
myKey: 'myValue'
});
};
loadHttp(deps);
http.setHeader('www.google.de', 'myKey', 'myValue');
http.get('https://www.google.de/?gws_rd=ssl', {}, {}, noop, noop);
});
it('resolves host headers correctly (set with port number) #37', () => {
const deps = getDependenciesBlueprint();
deps.cookieHandler.getCookieString = () => 'fakeCookieString';
deps.exec = (onSuccess, onFail, namespace, method, params) => {
const headers = params[1];
headers.should.eql({
Cookie: 'fakeCookieString',
myKey: 'myValue'
});
};
loadHttp(deps);
http.setHeader('www.google.de:8080', 'myKey', 'myValue');
http.get('https://www.google.de:8080/?gws_rd=ssl', {}, {}, noop, noop);
});
it('resolves request headers correctly', () => {
const deps = getDependenciesBlueprint();
deps.cookieHandler.getCookieString = () => 'fakeCookieString';
deps.exec = (onSuccess, onFail, namespace, method, params) => {
const headers = params[1];
headers.should.eql({
Cookie: 'fakeCookieString',
myKey: 'myValue'
});
};
loadHttp(deps);
http.get('https://www.google.de/?gws_rd=ssl', {}, { myKey: 'myValue' }, noop, noop);
});
it('sets basic authentication header correctly #36', () => {
http.useBasicAuth('name', 'pass');
http.getHeaders('*').Authorization.should.equal('Basic bmFtZTpwYXNz');
});
it('throws an Error when you try to add a cookie by using "setHeader" #46', () => {
(function () { http.setHeader('*', 'cookie', 'value') }).should.throw();
});
});
describe('URL util', function () {
const helpers = require('../www/helpers')(null, null);
const util = require('../www/url-util')(helpers);
it('parses URL with protocol, hostname and path correctly', () => {
util.parseUrl('http://ilkimen.net/test').should.include({
protocol: 'http:',
host: 'ilkimen.net',
hostname: 'ilkimen.net',
pathname: '/test',
port: '',
search: '',
hash: ''
});
});
it('parses URL with protocol, hostname, port and path correctly', () => {
util.parseUrl('http://ilkimen.net:8080/test').should.include({
protocol: 'http:',
host: 'ilkimen.net:8080',
hostname: 'ilkimen.net',
pathname: '/test',
port: '8080',
search: '',
hash: ''
});
});
it('parses URL with protocol, hostname, port, path and query string correctly', () => {
util.parseUrl('http://ilkimen.net:8080/test?param=value').should.include({
protocol: 'http:',
host: 'ilkimen.net:8080',
hostname: 'ilkimen.net',
pathname: '/test',
port: '8080',
search: '?param=value',
hash: ''
});
});
it('parses URL with protocol, hostname, port, path, query string and hash param correctly', () => {
util.parseUrl('http://ilkimen.net:8080/test?param=value#myHash').should.include({
protocol: 'http:',
host: 'ilkimen.net:8080',
hostname: 'ilkimen.net',
pathname: '/test',
port: '8080',
search: '?param=value',
hash: '#myHash'
});
});
it('serializes query params correctly', () => {
util.serializeQueryParams({
strParam1: 'value with spaces',
strParam2: 'value with special character äöü%',
boolParam: true,
numberParam: 1,
nullParam: null,
}, false).should.equal('strParam1=value with spaces&strParam2=value with special character äöü%&boolParam=true&numberParam=1&nullParam=null');
});
it('serializes query params correctly with URL encoding enabled', () => {
util.serializeQueryParams({
'param 1': 'value with spaces',
'param 2': 'value with special character äöü%&'
}, true).should.equal('param%201=value%20with%20spaces&param%202=value%20with%20special%20character%20%C3%A4%C3%B6%C3%BC%25%26');
});
it('serializes array of query params correctly', () => {
util.serializeQueryParams({
myArray: ['val1', 'val2', 'val3'],
myString: 'testString'
}, false).should.equal('myArray[]=val1&myArray[]=val2&myArray[]=val3&myString=testString');
});
it('serializes deeply structured array of query params correctly', () => {
util.serializeQueryParams({
myArray: [['val1.1', 'val1.2', 'val1.3'], 'val2', 'val3'],
myString: 'testString'
}, false).should.equal('myArray[][]=val1.1&myArray[][]=val1.2&myArray[][]=val1.3&myArray[]=val2&myArray[]=val3&myString=testString');
});
it('serializes deeply structured object of query params correctly', () => {
util.serializeQueryParams({
myObject: { obj1: { 'param1.1': 'val1.1', 'param1.2': 'val1.2' }, param2: 'val2' }
}, false).should.equal('myObject[obj1][param1.1]=val1.1&myObject[obj1][param1.2]=val1.2&myObject[param2]=val2');
});
it('appends query params string correctly to given URL without query parameters', () => {
util.appendQueryParamsString('http://ilkimen.net/', 'param1=value1')
.should.equal('http://ilkimen.net/?param1=value1');
});
it('appends query params string correctly to given URL with existing query parameters', () => {
util.appendQueryParamsString('http://ilkimen.net/?myParam=myValue', 'param1=value1')
.should.equal('http://ilkimen.net/?myParam=myValue&param1=value1');
});
it('appends query params string correctly to given URL with existing query parameters and hash value', () => {
util.appendQueryParamsString('http://ilkimen.net/?myParam=myValue#myHash', 'param1=value1')
.should.equal('http://ilkimen.net/?myParam=myValue&param1=value1#myHash');
});
});

511
test/js-specs.js Normal file
View File

@@ -0,0 +1,511 @@
const chai = require('chai');
const mock = require('mock-require');
const should = chai.should();
describe('Advanced HTTP public interface', function () {
const messages = require('../www/messages');
let http = {};
const noop = () => { /* intentionally doing nothing */ };
const getDependenciesBlueprint = () => {
const globalConfigs = require('../www/global-configs');
const jsUtil = require('../www/js-util');
const ToughCookie = require('../www/umd-tough-cookie');
const lodash = require('../www/lodash');
const errorCodes = require('../www/error-codes');
const WebStorageCookieStore = require('../www/local-storage-store')(ToughCookie, lodash);
const cookieHandler = require('../www/cookie-handler')(null, ToughCookie, WebStorageCookieStore);
const helpers = require('../www/helpers')(jsUtil, cookieHandler, messages, errorCodes);
const urlUtil = require('../www/url-util')(jsUtil);
return { exec: noop, cookieHandler, urlUtil: urlUtil, helpers, globalConfigs, errorCodes };
};
const loadHttp = (deps) => {
http = require('../www/public-interface')(deps.exec, deps.cookieHandler, deps.urlUtil, deps.helpers, deps.globalConfigs, deps.errorCodes);
};
beforeEach(() => {
// mocked btoa function (base 64 encoding strings)
global.btoa = decoded => new Buffer(decoded).toString('base64');
loadHttp(getDependenciesBlueprint());
});
it('sets global headers correctly with two args (old interface)', () => {
http.setHeader('myKey', 'myValue');
http.getHeaders('*').myKey.should.equal('myValue');
});
it('sets global headers correctly with three args (new interface) #24', () => {
http.setHeader('*', 'myKey', 'myValue');
http.getHeaders('*').myKey.should.equal('myValue');
});
it('sets host headers correctly #24', () => {
http.setHeader('www.google.de', 'myKey', 'myValue');
http.getHeaders('www.google.de').myKey.should.equal('myValue');
});
it('resolves global headers correctly #24', () => {
const deps = getDependenciesBlueprint();
deps.cookieHandler.getCookieString = () => 'fakeCookieString';
deps.exec = (onSuccess, onFail, namespace, method, params) => {
const headers = params[1];
headers.should.eql({
Cookie: 'fakeCookieString',
myKey: 'myValue'
});
};
loadHttp(deps);
http.setHeader('*', 'myKey', 'myValue');
http.get('url', {}, {}, noop, noop);
});
it('resolves host headers correctly (set without port number) #37', () => {
const deps = getDependenciesBlueprint();
deps.cookieHandler.getCookieString = () => 'fakeCookieString';
deps.exec = (onSuccess, onFail, namespace, method, params) => {
const headers = params[1];
headers.should.eql({
Cookie: 'fakeCookieString',
myKey: 'myValue'
});
};
loadHttp(deps);
http.setHeader('www.google.de', 'myKey', 'myValue');
http.get('https://www.google.de/?gws_rd=ssl', {}, {}, noop, noop);
});
it('resolves host headers correctly (set with port number) #37', () => {
const deps = getDependenciesBlueprint();
deps.cookieHandler.getCookieString = () => 'fakeCookieString';
deps.exec = (onSuccess, onFail, namespace, method, params) => {
const headers = params[1];
headers.should.eql({
Cookie: 'fakeCookieString',
myKey: 'myValue'
});
};
loadHttp(deps);
http.setHeader('www.google.de:8080', 'myKey', 'myValue');
http.get('https://www.google.de:8080/?gws_rd=ssl', {}, {}, noop, noop);
});
it('resolves request headers correctly', () => {
const deps = getDependenciesBlueprint();
deps.cookieHandler.getCookieString = () => 'fakeCookieString';
deps.exec = (onSuccess, onFail, namespace, method, params) => {
const headers = params[1];
headers.should.eql({
Cookie: 'fakeCookieString',
myKey: 'myValue'
});
};
loadHttp(deps);
http.get('https://www.google.de/?gws_rd=ssl', {}, { myKey: 'myValue' }, noop, noop);
});
it('sets basic authentication header correctly #36', () => {
http.useBasicAuth('name', 'pass');
http.getHeaders('*').Authorization.should.equal('Basic bmFtZTpwYXNz');
});
it('throws an Error when you try to add a cookie by using "setHeader" #46', () => {
(() => { http.setHeader('*', 'cookie', 'value'); }).should.throw(messages.ADDING_COOKIES_NOT_SUPPORTED);
});
it('configures global timeout value correctly with given valid value', () => {
http.setRequestTimeout(10);
http.getRequestTimeout().should.equal(10);
});
it('throws an Error when you try to configure global timeout with a string', () => {
(() => { http.setRequestTimeout('myString'); }).should.throw(messages.INVALID_TIMEOUT_VALUE);
});
it('sets global option for following redirects correctly', () => {
http.setFollowRedirect(false);
http.getFollowRedirect().should.equal(false);
});
it('throws an Error when you try to configure global option for following redirects with a string', () => {
(() => { http.setFollowRedirect('myString'); }).should.throw(messages.INVALID_FOLLOW_REDIRECT_VALUE);
});
it('exposes an enumeration style object with mappings for the error codes', () => {
Object.keys(http.ErrorCode).forEach(key => http.ErrorCode[key].should.be.a('number'));
});
});
describe('URL util', function () {
const jsUtil = require('../www/js-util');
const util = require('../www/url-util')(jsUtil);
it('parses URL with protocol, hostname and path correctly', () => {
util.parseUrl('http://ilkimen.net/test').should.include({
protocol: 'http:',
host: 'ilkimen.net',
hostname: 'ilkimen.net',
pathname: '/test',
port: '',
search: '',
hash: ''
});
});
it('parses URL with protocol, hostname, port and path correctly', () => {
util.parseUrl('http://ilkimen.net:8080/test').should.include({
protocol: 'http:',
host: 'ilkimen.net:8080',
hostname: 'ilkimen.net',
pathname: '/test',
port: '8080',
search: '',
hash: ''
});
});
it('parses URL with protocol, hostname, port, path and query string correctly', () => {
util.parseUrl('http://ilkimen.net:8080/test?param=value').should.include({
protocol: 'http:',
host: 'ilkimen.net:8080',
hostname: 'ilkimen.net',
pathname: '/test',
port: '8080',
search: '?param=value',
hash: ''
});
});
it('parses URL with protocol, hostname, port, path, query string and hash param correctly', () => {
util.parseUrl('http://ilkimen.net:8080/test?param=value#myHash').should.include({
protocol: 'http:',
host: 'ilkimen.net:8080',
hostname: 'ilkimen.net',
pathname: '/test',
port: '8080',
search: '?param=value',
hash: '#myHash'
});
});
it('serializes query params correctly', () => {
util.serializeQueryParams({
strParam1: 'value with spaces',
strParam2: 'value with special character äöü%',
boolParam: true,
numberParam: 1,
nullParam: null,
}, false).should.equal('strParam1=value with spaces&strParam2=value with special character äöü%&boolParam=true&numberParam=1&nullParam=null');
});
it('serializes query params correctly with URL encoding enabled', () => {
util.serializeQueryParams({
'param 1': 'value with spaces',
'param 2': 'value with special character äöü%&'
}, true).should.equal('param%201=value%20with%20spaces&param%202=value%20with%20special%20character%20%C3%A4%C3%B6%C3%BC%25%26');
});
it('serializes array of query params correctly', () => {
util.serializeQueryParams({
myArray: ['val1', 'val2', 'val3'],
myString: 'testString'
}, false).should.equal('myArray[]=val1&myArray[]=val2&myArray[]=val3&myString=testString');
});
it('serializes deeply structured array of query params correctly', () => {
util.serializeQueryParams({
myArray: [['val1.1', 'val1.2', 'val1.3'], 'val2', 'val3'],
myString: 'testString'
}, false).should.equal('myArray[][]=val1.1&myArray[][]=val1.2&myArray[][]=val1.3&myArray[]=val2&myArray[]=val3&myString=testString');
});
it('serializes deeply structured object of query params correctly', () => {
util.serializeQueryParams({
myObject: { obj1: { 'param1.1': 'val1.1', 'param1.2': 'val1.2' }, param2: 'val2' }
}, false).should.equal('myObject[obj1][param1.1]=val1.1&myObject[obj1][param1.2]=val1.2&myObject[param2]=val2');
});
it('appends query params string correctly to given URL without query parameters', () => {
util.appendQueryParamsString('http://ilkimen.net/', 'param1=value1')
.should.equal('http://ilkimen.net/?param1=value1');
});
it('appends query params string correctly to given URL with existing query parameters', () => {
util.appendQueryParamsString('http://ilkimen.net/?myParam=myValue', 'param1=value1')
.should.equal('http://ilkimen.net/?myParam=myValue&param1=value1');
});
it('appends query params string correctly to given URL with existing query parameters and hash value', () => {
util.appendQueryParamsString('http://ilkimen.net/?myParam=myValue#myHash', 'param1=value1')
.should.equal('http://ilkimen.net/?myParam=myValue&param1=value1#myHash');
});
});
describe('Common helpers', function () {
describe('mergeHeaders(globalHeaders, localHeaders)', function () {
const init = require('../www/helpers');
init.debug = true;
const helpers = init(null, null, null);
it('merges empty header sets correctly', () => {
helpers.mergeHeaders({}, {}).should.eql({});
});
it('merges ssimple header sets without collision correctly', () => {
helpers.mergeHeaders({ a: 1 }, { b: 2 }).should.eql({ a: 1, b: 2 });
});
it('merges header sets with collision correctly', () => {
helpers.mergeHeaders({ a: 1 }, { a: 2 }).should.eql({ a: 2 });
});
});
describe('getCookieHeader(url)', function () {
it('resolves cookie header correctly when no cookie is set #198', () => {
const helpers = require('../www/helpers')(null, { getCookieString: () => '' }, null);
helpers.getCookieHeader('http://ilkimen.net').should.eql({});
});
it('resolves cookie header correctly when a cookie is set', () => {
const helpers = require('../www/helpers')(null, { getCookieString: () => 'cookie=value' }, null);
helpers.getCookieHeader('http://ilkimen.net').should.eql({ Cookie: 'cookie=value' });
});
});
describe('checkClientAuthOptions()', function () {
const jsUtil = require('../www/js-util');
const messages = require('../www/messages');
const helpers = require('../www/helpers')(jsUtil, null, messages);
it('returns options object with empty values when mode is "none" and no options are given', () => {
helpers.checkClientAuthOptions('none').should.eql({
alias: null,
rawPkcs: null,
pkcsPassword: ''
});
});
it('returns options object with empty values when mode is "none" and random options are given', () => {
helpers.checkClientAuthOptions('none', {
alias: 'myAlias',
pkcsPath: 'myPath'
}).should.eql({
alias: null,
rawPkcs: null,
pkcsPassword: ''
});
});
it('throws an error when mode is "systemstore" and alias is not a string or undefined', () => {
(() => helpers.checkClientAuthOptions('systemstore', { alias: 1 }))
.should.throw(messages.INVALID_CLIENT_AUTH_ALIAS);
(() => helpers.checkClientAuthOptions('systemstore', { alias: undefined }))
.should.not.throw();
});
it('returns an object with null alias when mode is "systemstore" and no options object is given', () => {
helpers.checkClientAuthOptions('systemstore').should.eql({
alias: null,
rawPkcs: null,
pkcsPassword: ''
});
});
it('throws an error when mode is "buffer" and rawPkcs is not an array buffer', () => {
(() => helpers.checkClientAuthOptions('buffer', {
rawPkcs: undefined,
pkcsPassword: 'password'
})).should.throw(messages.INVALID_CLIENT_AUTH_RAW_PKCS);
(() => helpers.checkClientAuthOptions('buffer', {
pkcsPath: 1,
pkcsPassword: 'password'
})).should.throw(messages.INVALID_CLIENT_AUTH_RAW_PKCS);
});
it('throws an error when mode is "buffer" and pkcsPassword is not a string', () => {
(() => helpers.checkClientAuthOptions('buffer', {
rawPkcs: new ArrayBuffer(),
pkcsPassword: undefined
})).should.throw(messages.INVALID_CLIENT_AUTH_PKCS_PASSWORD);
(() => helpers.checkClientAuthOptions('buffer', {
rawPkcs: new ArrayBuffer(),
pkcsPassword: 1
})).should.throw(messages.INVALID_CLIENT_AUTH_PKCS_PASSWORD);
});
});
describe('handleMissingOptions()', function () {
const jsUtil = require('../www/js-util');
const messages = require('../www/messages');
const helpers = require('../www/helpers')(jsUtil, null, messages);
const mockGlobals = {
headers: {},
serializer: 'urlencoded',
followRedirect: true,
timeout: 60.0,
}
it('adds missing "followRedirect" option correctly', () => {
helpers.handleMissingOptions({}, mockGlobals).should.include({ followRedirect: true });
});
it('throws an error when "followRedirect" option is not a boolean', () => {
(() => helpers.handleMissingOptions({ followRedirect: 1 }, mockGlobals))
.should.throw(messages.INVALID_FOLLOW_REDIRECT_VALUE);
});
});
describe('injectRawResponseHandler()', function () {
const jsUtil = require('../www/js-util');
const messages = require('../www/messages');
const errorCodes = require('../www/error-codes');
const fakeBase64 = { toArrayBuffer: () => 'fakeArrayBuffer' };
global.Blob = function (array, meta) {
this.isFakeBlob = true;
this.array = array;
this.meta = meta;
};
it('does not change response data if it is an ArrayBuffer', () => {
const helpers = require('../www/helpers')(jsUtil, null, messages, null, errorCodes);
const buffer = new ArrayBuffer(5);
const handler = helpers.injectRawResponseHandler(
'arraybuffer',
response => response.data.should.be.equal(buffer)
);
handler({ data: buffer });
});
it('does not change response data if it is a Blob', () => {
const fakeJsUtil = { getTypeOf: () => 'Blob' };
const helpers = require('../www/helpers')(fakeJsUtil, null, messages, null, errorCodes);
const handler = helpers.injectRawResponseHandler(
'blob',
response => response.data.should.be.equal('fakeData')
);
handler({ data: 'fakeData' });
});
it('does not change response data if response type is "text"', () => {
const helpers = require('../www/helpers')(jsUtil, null, messages, null, errorCodes);
const example = 'exampleText';
const handler = helpers.injectRawResponseHandler(
'text',
response => response.data.should.be.equal(example)
);
handler({ data: example });
});
it('handles response type "json" correctly', () => {
const fakeData = { myString: 'bla', myNumber: 10 };
const helpers = require('../www/helpers')(jsUtil, null, messages, null, errorCodes);
const handler = helpers.injectRawResponseHandler(
'json',
response => response.data.should.be.eql(fakeData)
);
handler({ data: JSON.stringify(fakeData) });
});
it('handles response type "arraybuffer" correctly', () => {
const helpers = require('../www/helpers')(jsUtil, null, messages, fakeBase64, errorCodes);
const handler = helpers.injectRawResponseHandler(
'arraybuffer',
response => response.data.should.be.equal('fakeArrayBuffer')
);
handler({ data: 'myString' });
});
it('handles response type "blob" correctly', () => {
const helpers = require('../www/helpers')(jsUtil, null, messages, fakeBase64, errorCodes);
const handler = helpers.injectRawResponseHandler(
'blob',
(response) => {
response.data.isFakeBlob.should.be.equal(true);
response.data.array.should.be.eql(['fakeArrayBuffer']);
response.data.meta.type.should.be.equal('fakeType');
}
);
handler({ data: 'myString', headers: { 'content-type': 'fakeType'} });
});
it('calls failure callback when post-processing fails', () => {
const helpers = require('../www/helpers')(jsUtil, null, messages, fakeBase64, errorCodes);
const handler = helpers.injectRawResponseHandler(
'json',
null,
(response) => {
response.status.should.be.equal(errorCodes.POST_PROCESSING_FAILED);
response.error.should.include('Unexpected token N in JSON at position 0');
}
);
handler({ data: 'NotValidJson' });
});
});
describe('checkUploadFileOptions()', function() {
const jsUtil = require('../www/js-util');
const messages = require('../www/messages');
const helpers = require('../www/helpers')(jsUtil, null, messages, null, null);
it('checks valid file options correctly', () => {
const opts = {
filePaths: ['file://path/to/file.png'],
names: ['ScreenCapture']
};
// string values
helpers.checkUploadFileOptions(opts.filePaths[0], opts.names[0]).should.be.eql(opts);
// string array values
helpers.checkUploadFileOptions(opts.filePaths, opts.names).should.be.eql(opts);
});
it('throws an error when file options are missing', () => {
(() => helpers.checkUploadFileOptions(undefined, ['ScreenCapture'])).should.throw(messages.FILE_PATHS_TYPE_MISMATCH);
(() => helpers.checkUploadFileOptions(['file://path/to/file.png'], undefined)).should.throw(messages.NAMES_TYPE_MISMATCH);
});
it('throws an error when file options contains empty arrays', () => {
(() => helpers.checkUploadFileOptions([], ['ScreenCapture'])).should.throw(messages.EMPTY_FILE_PATHS);
(() => helpers.checkUploadFileOptions(['file://path/to/file.png'], [])).should.throw(messages.EMPTY_NAMES);
});
it('throws an error when file options contains invalid values', () => {
(() => helpers.checkUploadFileOptions([1], ['ScreenCapture'])).should.throw(messages.FILE_PATHS_TYPE_MISMATCH);
(() => helpers.checkUploadFileOptions(['file://path/to/file.png'], [1])).should.throw(messages.NAMES_TYPE_MISMATCH);
});
});
})

View File

@@ -5,14 +5,17 @@
var pluginId = module.id.slice(0, module.id.lastIndexOf('.'));
var exec = require('cordova/exec');
var base64 = require('cordova/base64');
var messages = require(pluginId + '.messages');
var errorCodes = require(pluginId + '.error-codes');
var globalConfigs = require(pluginId + '.global-configs');
var jsUtil = require(pluginId + '.js-util');
var ToughCookie = require(pluginId + '.tough-cookie');
var lodash = require(pluginId + '.lodash');
var WebStorageCookieStore = require(pluginId + '.local-storage-store')(ToughCookie, lodash);
var cookieHandler = require(pluginId + '.cookie-handler')(window.localStorage, ToughCookie, WebStorageCookieStore);
var helpers = require(pluginId + '.helpers')(cookieHandler, messages);
var urlUtil = require(pluginId + '.url-util')(helpers);
var publicInterface = require(pluginId + '.public-interface')(exec, cookieHandler, urlUtil, helpers, globalConfigs);
var helpers = require(pluginId + '.helpers')(jsUtil, cookieHandler, messages, base64, errorCodes);
var urlUtil = require(pluginId + '.url-util')(jsUtil);
var publicInterface = require(pluginId + '.public-interface')(exec, cookieHandler, urlUtil, helpers, globalConfigs, errorCodes);
module.exports = publicInterface;

9
www/error-codes.js Normal file
View File

@@ -0,0 +1,9 @@
module.exports = {
GENERIC: -1,
SSL_EXCEPTION: -2,
SERVER_NOT_FOUND: -3,
TIMEOUT: -4,
UNSUPPORTED_URL: -5,
NOT_CONNECTED: -6,
POST_PROCESSING_FAILED: -7,
};

View File

@@ -1,6 +1,7 @@
var globalConfigs = {
headers: {},
serializer: 'urlencoded',
followRedirect: true,
timeout: 60.0,
};

View File

@@ -1,23 +1,49 @@
module.exports = function init(cookieHandler, messages) {
module.exports = function init(jsUtil, cookieHandler, messages, base64, errorCodes) {
var validSerializers = ['urlencoded', 'json', 'utf8'];
var validCertModes = ['default', 'nocheck', 'pinned', 'legacy'];
var validClientAuthModes = ['none', 'systemstore', 'buffer'];
var validHttpMethods = ['get', 'put', 'post', 'patch', 'head', 'delete', 'upload', 'download'];
var validResponseTypes = ['text', 'json', 'arraybuffer', 'blob'];
return {
var interface = {
b64EncodeUnicode: b64EncodeUnicode,
getTypeOf: getTypeOf,
checkSerializer: checkSerializer,
checkSSLCertMode: checkSSLCertMode,
checkClientAuthMode: checkClientAuthMode,
checkClientAuthOptions: checkClientAuthOptions,
checkDownloadFilePath: checkDownloadFilePath,
checkFollowRedirectValue: checkFollowRedirectValue,
checkForBlacklistedHeaderKey: checkForBlacklistedHeaderKey,
checkForInvalidHeaderValue: checkForInvalidHeaderValue,
injectCookieHandler: injectCookieHandler,
injectFileEntryHandler: injectFileEntryHandler,
checkSerializer: checkSerializer,
checkSSLCertMode: checkSSLCertMode,
checkTimeoutValue: checkTimeoutValue,
checkUploadFileOptions: checkUploadFileOptions,
getMergedHeaders: getMergedHeaders,
getProcessedData: getProcessedData,
handleMissingCallbacks: handleMissingCallbacks,
handleMissingOptions: handleMissingOptions
handleMissingOptions: handleMissingOptions,
injectCookieHandler: injectCookieHandler,
injectFileEntryHandler: injectFileEntryHandler,
injectRawResponseHandler: injectRawResponseHandler,
};
// expose all functions for testing purposes
if (init.debug) {
interface.mergeHeaders = mergeHeaders;
interface.checkForValidStringValue = checkForValidStringValue;
interface.checkKeyValuePairObject = checkKeyValuePairObject;
interface.checkHttpMethod = checkHttpMethod;
interface.checkResponseType = checkResponseType;
interface.checkHeadersObject = checkHeadersObject;
interface.checkParamsObject = checkParamsObject;
interface.resolveCookieString = resolveCookieString;
interface.createFileEntry = createFileEntry;
interface.getCookieHeader = getCookieHeader;
interface.getMatchingHostHeaders = getMatchingHostHeaders;
interface.getAllowedDataTypes = getAllowedDataTypes;
}
return interface;
// Thanks Mozilla: https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_.22Unicode_Problem.22
function b64EncodeUnicode(str) {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
@@ -41,7 +67,7 @@ module.exports = function init(cookieHandler, messages) {
}
function checkForValidStringValue(list, value, onInvalidValueMessage) {
if (getTypeOf(value) !== 'String') {
if (jsUtil.getTypeOf(value) !== 'String') {
throw new Error(onInvalidValueMessage + ' ' + list.join(', '));
}
@@ -55,14 +81,14 @@ module.exports = function init(cookieHandler, messages) {
}
function checkKeyValuePairObject(obj, allowedChildren, onInvalidValueMessage) {
if (getTypeOf(obj) !== 'Object') {
if (jsUtil.getTypeOf(obj) !== 'Object') {
throw new Error(onInvalidValueMessage);
}
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
if (allowedChildren.indexOf(getTypeOf(obj[keys[i]])) === -1) {
if (allowedChildren.indexOf(jsUtil.getTypeOf(obj[keys[i]])) === -1) {
throw new Error(onInvalidValueMessage);
}
}
@@ -70,10 +96,28 @@ module.exports = function init(cookieHandler, messages) {
return obj;
}
function checkArray(array, allowedDataTypes, onInvalidValueMessage) {
if (jsUtil.getTypeOf(array) !== 'Array') {
throw new Error(onInvalidValueMessage);
}
for (var i = 0; i < array.length; ++i) {
if (allowedDataTypes.indexOf(jsUtil.getTypeOf(array[i])) === -1) {
throw new Error(onInvalidValueMessage);
}
}
return array;
}
function checkHttpMethod(method) {
return checkForValidStringValue(validHttpMethods, method, messages.INVALID_HTTP_METHOD);
}
function checkResponseType(type) {
return checkForValidStringValue(validResponseTypes, type, messages.INVALID_RESPONSE_TYPE);
}
function checkSerializer(serializer) {
return checkForValidStringValue(validSerializers, serializer, messages.INVALID_DATA_SERIALIZER);
}
@@ -82,6 +126,58 @@ module.exports = function init(cookieHandler, messages) {
return checkForValidStringValue(validCertModes, mode, messages.INVALID_SSL_CERT_MODE);
}
function checkClientAuthMode(mode) {
return checkForValidStringValue(validClientAuthModes, mode, messages.INVALID_CLIENT_AUTH_MODE);
}
function checkClientAuthOptions(mode, options) {
options = options || {};
// none
if (mode === validClientAuthModes[0]) {
return {
alias: null,
rawPkcs: null,
pkcsPassword: ''
};
}
if (jsUtil.getTypeOf(options) !== 'Object') {
throw new Error(messages.INVALID_CLIENT_AUTH_OPTIONS);
}
// systemstore
if (mode === validClientAuthModes[1]) {
if (jsUtil.getTypeOf(options.alias) !== 'String'
&& jsUtil.getTypeOf(options.alias) !== 'Undefined') {
throw new Error(messages.INVALID_CLIENT_AUTH_ALIAS);
}
return {
alias: jsUtil.getTypeOf(options.alias) === 'Undefined' ? null : options.alias,
rawPkcs: null,
pkcsPassword: ''
};
}
// buffer
if (mode === validClientAuthModes[2]) {
if (jsUtil.getTypeOf(options.rawPkcs) !== 'ArrayBuffer') {
throw new Error(messages.INVALID_CLIENT_AUTH_RAW_PKCS);
}
if (jsUtil.getTypeOf(options.pkcsPassword) !== 'String') {
throw new Error(messages.INVALID_CLIENT_AUTH_PKCS_PASSWORD);
}
return {
alias: null,
rawPkcs: options.rawPkcs,
pkcsPassword: options.pkcsPassword
}
}
}
function checkForBlacklistedHeaderKey(key) {
if (key.toLowerCase() === 'cookie') {
throw new Error(messages.ADDING_COOKIES_NOT_SUPPORTED);
@@ -91,27 +187,68 @@ module.exports = function init(cookieHandler, messages) {
}
function checkForInvalidHeaderValue(value) {
if (getTypeOf(value) !== 'String') {
throw new Error(messages.INVALID_HEADERS_VALUE);
if (jsUtil.getTypeOf(value) !== 'String') {
throw new Error(messages.INVALID_HEADER_VALUE);
}
return value;
}
function checkTimeoutValue(timeout) {
if (getTypeOf(timeout) !== 'Number' || timeout < 0) {
if (jsUtil.getTypeOf(timeout) !== 'Number' || timeout < 0) {
throw new Error(messages.INVALID_TIMEOUT_VALUE);
}
return timeout;
}
function checkFollowRedirectValue(follow) {
if (jsUtil.getTypeOf(follow) !== 'Boolean') {
throw new Error(messages.INVALID_FOLLOW_REDIRECT_VALUE);
}
return follow;
}
function checkHeadersObject(headers) {
return checkKeyValuePairObject(headers, ['String'], messages.INVALID_HEADERS_VALUE);
return checkKeyValuePairObject(headers, ['String'], messages.TYPE_MISMATCH_HEADERS);
}
function checkParamsObject(params) {
return checkKeyValuePairObject(params, ['String', 'Array'], messages.INVALID_PARAMS_VALUE);
return checkKeyValuePairObject(params, ['String', 'Array'], messages.TYPE_MISMATCH_PARAMS);
}
function checkDownloadFilePath(filePath) {
if (!filePath || jsUtil.getTypeOf(filePath) !== 'String') {
throw new Error(messages.INVALID_DOWNLOAD_FILE_PATH);
}
return filePath;
}
function checkUploadFileOptions(filePaths, names) {
if (jsUtil.getTypeOf(filePaths) === 'String') {
filePaths = [filePaths];
}
if (jsUtil.getTypeOf(names) === 'String') {
names = [names];
}
var opts = {
filePaths: checkArray(filePaths, ['String'], messages.TYPE_MISMATCH_FILE_PATHS),
names: checkArray(names, ['String'], messages.TYPE_MISMATCH_NAMES)
};
if (!opts.filePaths.length) {
throw new Error(messages.EMPTY_FILE_PATHS);
}
if (!opts.names.length) {
throw new Error(messages.EMPTY_NAMES);
}
return opts;
}
function resolveCookieString(headers) {
@@ -146,6 +283,46 @@ module.exports = function init(cookieHandler, messages) {
}
}
function injectRawResponseHandler(responseType, success, failure) {
return function (response) {
var dataType = jsUtil.getTypeOf(response.data);
// don't need post-processing if it's already binary type (on browser platform)
if (dataType === 'ArrayBuffer' || dataType === 'Blob') {
return success(response);
}
try {
// json
if (responseType === validResponseTypes[1]) {
response.data = JSON.parse(response.data);
}
// arraybuffer
else if (responseType === validResponseTypes[2]) {
response.data = base64.toArrayBuffer(response.data);
}
// blob
else if (responseType === validResponseTypes[3]) {
var buffer = base64.toArrayBuffer(response.data);
var type = response.headers['content-type'] || '';
var blob = new Blob([ buffer ], { type: type });
response.data = blob;
}
success(response);
} catch (error) {
failure({
status: errorCodes.POST_PROCESSING_FAILED,
error: messages.POST_PROCESSING_FAILED + ' ' + error.message,
url: response.url,
headers: response.headers
});
}
}
}
function injectFileEntryHandler(cb) {
return function (response) {
cb(createFileEntry(response.file));
@@ -153,7 +330,13 @@ module.exports = function init(cookieHandler, messages) {
}
function getCookieHeader(url) {
return { Cookie: cookieHandler.getCookieString(url) };
var cookieString = cookieHandler.getCookieString(url);
if (cookieString.length) {
return { Cookie: cookieHandler.getCookieString(url) };
}
return {};
}
function getMatchingHostHeaders(url, headersList) {
@@ -174,30 +357,6 @@ module.exports = function init(cookieHandler, messages) {
return mergedHeaders;
}
// typeof is not working reliably in JS
function getTypeOf(object) {
switch (Object.prototype.toString.call(object)) {
case '[object Array]':
return 'Array';
case '[object Boolean]':
return 'Boolean';
case '[object Function]':
return 'Function';
case '[object Null]':
return 'Null';
case '[object Number]':
return 'Number';
case '[object Object]':
return 'Object';
case '[object String]':
return 'String';
case '[object Undefined]':
return 'Undefined';
default:
return 'Unknown';
}
}
function getAllowedDataTypes(dataSerializer) {
switch (dataSerializer) {
case 'utf8':
@@ -210,11 +369,11 @@ module.exports = function init(cookieHandler, messages) {
}
function getProcessedData(data, dataSerializer) {
var currentDataType = getTypeOf(data);
var currentDataType = jsUtil.getTypeOf(data);
var allowedDataTypes = getAllowedDataTypes(dataSerializer);
if (allowedDataTypes.indexOf(currentDataType) === -1) {
throw new Error(messages.DATA_TYPE_MISMATCH + ' ' + allowedDataTypes.join(', '));
throw new Error(messages.TYPE_MISMATCH_DATA + ' ' + allowedDataTypes.join(', '));
}
if (dataSerializer === 'utf8') {
@@ -225,11 +384,11 @@ module.exports = function init(cookieHandler, messages) {
}
function handleMissingCallbacks(successFn, failFn) {
if (getTypeOf(successFn) !== 'Function') {
if (jsUtil.getTypeOf(successFn) !== 'Function') {
throw new Error(messages.MANDATORY_SUCCESS);
}
if (getTypeOf(failFn) !== 'Function') {
if (jsUtil.getTypeOf(failFn) !== 'Function') {
throw new Error(messages.MANDATORY_FAIL);
}
}
@@ -238,14 +397,16 @@ module.exports = function init(cookieHandler, messages) {
options = options || {};
return {
data: jsUtil.getTypeOf(options.data) === 'Undefined' ? null : options.data,
filePath: options.filePath,
followRedirect: checkFollowRedirectValue(options.followRedirect || globals.followRedirect),
headers: checkHeadersObject(options.headers || {}),
method: checkHttpMethod(options.method || validHttpMethods[0]),
name: options.name,
params: checkParamsObject(options.params || {}),
responseType: checkResponseType(options.responseType || validResponseTypes[0]),
serializer: checkSerializer(options.serializer || globals.serializer),
timeout: checkTimeoutValue(options.timeout || globals.timeout),
headers: checkHeadersObject(options.headers || {}),
params: checkParamsObject(options.params || {}),
data: getTypeOf(options.data) === 'Undefined' ? null : options.data,
filePath: options.filePath || '',
name: options.name || ''
};
}
};

30
www/js-util.js Normal file
View File

@@ -0,0 +1,30 @@
module.exports = {
// typeof is not working reliably in JS
getTypeOf: function (object) {
switch (Object.prototype.toString.call(object)) {
case '[object Array]':
return 'Array';
case '[object Blob]':
return 'Blob';
case '[object ArrayBuffer]':
return 'ArrayBuffer';
case '[object Boolean]':
return 'Boolean';
case '[object Function]':
return 'Function';
case '[object Null]':
return 'Null';
case '[object Number]':
return 'Number';
case '[object Object]':
return 'Object';
case '[object String]':
return 'String';
case '[object Undefined]':
return 'Undefined';
default:
return 'Unknown';
}
}
}

View File

@@ -1,12 +1,26 @@
module.exports = {
ADDING_COOKIES_NOT_SUPPORTED: 'advanced-http: "setHeader" does not support adding cookies, please use "setCookie" function instead',
DATA_TYPE_MISMATCH: 'advanced-http: "data" argument supports only following data types:',
MANDATORY_SUCCESS: 'advanced-http: missing mandatory "onSuccess" callback function',
MANDATORY_FAIL: 'advanced-http: missing mandatory "onFail" callback function',
INVALID_HTTP_METHOD: 'advanced-http: invalid HTTP method, supported methods are:',
EMPTY_FILE_PATHS: 'advanced-http: "filePaths" option array must not be empty, <filePaths: string[]>',
EMPTY_NAMES: 'advanced-http: "names" option array must not be empty, <names: string[]>',
INVALID_CLIENT_AUTH_ALIAS: 'advanced-http: invalid client certificate alias, needs to be a string or undefined, <alias: string | undefined>',
INVALID_CLIENT_AUTH_MODE: 'advanced-http: invalid client certificate authentication mode, supported modes are:',
INVALID_CLIENT_AUTH_OPTIONS: 'advanced-http: invalid client certificate authentication options, needs to be an dictionary style object',
INVALID_CLIENT_AUTH_PKCS_PASSWORD: 'advanced-http: invalid PKCS12 container password, needs to be a string, <pkcsPassword: string>',
INVALID_CLIENT_AUTH_RAW_PKCS: 'advanced-http: invalid PKCS12 container, needs to be an array buffer, <rawPkcs: ArrayBuffer>',
INVALID_DATA_SERIALIZER: 'advanced-http: invalid serializer, supported serializers are:',
INVALID_DOWNLOAD_FILE_PATH: 'advanced-http: invalid "filePath" value, needs to be a string, <filePath: string>',
INVALID_FOLLOW_REDIRECT_VALUE: 'advanced-http: invalid follow redirect value, needs to be a boolean value, <followRedirect: boolean>',
INVALID_HEADER_VALUE: 'advanced-http: invalid header value, needs to be a string, <header: string>',
INVALID_HTTP_METHOD: 'advanced-http: invalid HTTP method, supported methods are:',
INVALID_RESPONSE_TYPE: 'advanced-http: invalid response type, supported types are:',
INVALID_SSL_CERT_MODE: 'advanced-http: invalid SSL cert mode, supported modes are:',
INVALID_HEADERS_VALUE: 'advanced-http: header values must be strings',
INVALID_TIMEOUT_VALUE: 'advanced-http: invalid timeout value, needs to be a positive numeric value',
INVALID_PARAMS_VALUE: 'advanced-http: invalid params object, needs to be an object with strings'
INVALID_TIMEOUT_VALUE: 'advanced-http: invalid timeout value, needs to be a positive numeric value, <timeout: number>',
MANDATORY_FAIL: 'advanced-http: missing mandatory "onFail" callback function',
MANDATORY_SUCCESS: 'advanced-http: missing mandatory "onSuccess" callback function',
POST_PROCESSING_FAILED: 'advanced-http: an error occured during post processing response:',
TYPE_MISMATCH_DATA: 'advanced-http: "data" option supports only following data types:',
TYPE_MISMATCH_FILE_PATHS: 'advanced-http: "filePaths" option needs to be an string array, <filePaths: string[]>',
TYPE_MISMATCH_HEADERS: 'advanced-http: "headers" option needs to be an dictionary style object with string values, <headers: {[key: string]: string}>',
TYPE_MISMATCH_NAMES: 'advanced-http: "names" option needs to be an string array, <names: string[]>',
TYPE_MISMATCH_PARAMS: 'advanced-http: "params" option needs to be an dictionary style object, <params: {[key: string]: string | string[]}>',
};

View File

@@ -1,5 +1,5 @@
module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConfigs) {
const publicInterface = {
module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConfigs, errorCodes) {
var publicInterface = {
getBasicAuthHeader: getBasicAuthHeader,
useBasicAuth: useBasicAuth,
getHeaders: getHeaders,
@@ -12,8 +12,14 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf
getCookieString: getCookieString,
getRequestTimeout: getRequestTimeout,
setRequestTimeout: setRequestTimeout,
setSSLCertMode: setSSLCertMode,
getFollowRedirect: getFollowRedirect,
setFollowRedirect: setFollowRedirect,
// @DEPRECATED
disableRedirect: disableRedirect,
// @DEPRECATED
setSSLCertMode: setServerTrustMode,
setServerTrustMode: setServerTrustMode,
setClientAuthMode: setClientAuthMode,
sendRequest: sendRequest,
post: post,
get: get,
@@ -22,7 +28,8 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf
delete: del,
head: head,
uploadFile: uploadFile,
downloadFile: downloadFile
downloadFile: downloadFile,
ErrorCode: errorCodes
};
function getBasicAuthHeader(username, password) {
@@ -85,15 +92,49 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf
}
function setRequestTimeout(timeout) {
globalConfigs.timeout = timeout;
globalConfigs.timeout = helpers.checkTimeoutValue(timeout);
}
function setSSLCertMode(mode, success, failure) {
return exec(success, failure, 'CordovaHttpPlugin', 'setSSLCertMode', [helpers.checkSSLCertMode(mode)]);
function getFollowRedirect() {
return globalConfigs.followRedirect;
}
function setFollowRedirect(follow) {
globalConfigs.followRedirect = helpers.checkFollowRedirectValue(follow);
}
// @DEPRECATED
function disableRedirect(disable, success, failure) {
return exec(success, failure, 'CordovaHttpPlugin', 'disableRedirect', [!!disable]);
helpers.handleMissingCallbacks(success, failure);
setFollowRedirect(!disable);
success();
}
function setServerTrustMode(mode, success, failure) {
helpers.handleMissingCallbacks(success, failure);
return exec(success, failure, 'CordovaHttpPlugin', 'setServerTrustMode', [helpers.checkSSLCertMode(mode)]);
}
function setClientAuthMode() {
var mode = arguments[0];
var options = null;
var success = arguments[1];
var failure = arguments[2];
if (arguments.length === 4) {
options = arguments[1];
success = arguments[2];
failure = arguments[3];
}
mode = helpers.checkClientAuthMode(mode);
options = helpers.checkClientAuthOptions(mode, options);
helpers.handleMissingCallbacks(success, failure);
return exec(success, failure, 'CordovaHttpPlugin', 'setClientAuthMode', [mode, options.alias, options.rawPkcs, options.pkcsPassword]);
}
function sendRequest(url, options, success, failure) {
@@ -103,22 +144,25 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf
url = urlUtil.appendQueryParamsString(url, urlUtil.serializeQueryParams(options.params, true));
var headers = helpers.getMergedHeaders(url, options.headers, globalConfigs.headers);
var onSuccess = helpers.injectCookieHandler(url, success);
var onFail = helpers.injectCookieHandler(url, failure);
var onSuccess = helpers.injectCookieHandler(url, helpers.injectRawResponseHandler(options.responseType, success, failure));
switch (options.method) {
case 'post':
case 'put':
case 'patch':
var data = helpers.getProcessedData(options.data, options.serializer);
return exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [url, data, options.serializer, headers, options.timeout]);
return exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [url, data, options.serializer, headers, options.timeout, options.followRedirect, options.responseType]);
case 'upload':
return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'uploadFile', [url, headers, options.filePath, options.name, options.timeout]);
var fileOptions = helpers.checkUploadFileOptions(options.filePath, options.name);
return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'uploadFiles', [url, headers, fileOptions.filePaths, fileOptions.names, options.timeout, options.followRedirect, options.responseType]);
case 'download':
var filePath = helpers.checkDownloadFilePath(options.filePath);
var onDownloadSuccess = helpers.injectCookieHandler(url, helpers.injectFileEntryHandler(success));
return exec(onDownloadSuccess, onFail, 'CordovaHttpPlugin', 'downloadFile', [url, headers, options.filePath, options.timeout]);
return exec(onDownloadSuccess, onFail, 'CordovaHttpPlugin', 'downloadFile', [url, headers, filePath, options.timeout, options.followRedirect]);
default:
return exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [url, headers, options.timeout]);
return exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [url, headers, options.timeout, options.followRedirect, options.responseType]);
}
}

View File

@@ -1,4 +1,4 @@
module.exports = function init(helpers) {
module.exports = function init(jsUtil) {
return {
parseUrl: parseUrl,
appendQueryParamsString: appendQueryParamsString,
@@ -48,10 +48,10 @@ module.exports = function init(helpers) {
var identifier = parentKey.length ? parentKey + '[' + key + ']' : key;
if (helpers.getTypeOf(object[key]) === 'Array') {
if (jsUtil.getTypeOf(object[key]) === 'Array') {
parts.push(serializeArray(identifier, object[key], encode));
continue;
} else if (helpers.getTypeOf(object[key]) === 'Object') {
} else if (jsUtil.getTypeOf(object[key]) === 'Object') {
parts.push(serializeObject(identifier, object[key], encode));
continue;
}
@@ -66,10 +66,10 @@ module.exports = function init(helpers) {
var parts = [];
for (var i = 0; i < array.length; ++i) {
if (helpers.getTypeOf(array[i]) === 'Array') {
if (jsUtil.getTypeOf(array[i]) === 'Array') {
parts.push(serializeArray(parentKey + '[]', array[i], encode));
continue;
} else if (helpers.getTypeOf(array[i]) === 'Object') {
} else if (jsUtil.getTypeOf(array[i]) === 'Object') {
parts.push(serializeObject(parentKey + '[]' + array[i], encode));
continue;
}