Compare commits

...

75 Commits

Author SHA1 Message Date
Sefa Ilkimen
6339b9b83d release v3.1.0 2020-10-16 00:58:40 +02:00
Sefa Ilkimen
389534d661 chore: update changelog 2020-10-13 00:46:48 +02:00
Sefa Ilkimen
c10722eca0 Merge branch 'mmig-feature-abort' 2020-10-13 00:23:34 +02:00
Sefa Ilkimen
6a60058fc6 chore: auto fix some indentations 2020-10-13 00:22:42 +02:00
russa
fcedfae1ac FIX do not evaluate test result if test is skipped 2020-10-09 16:51:43 +02:00
russa
aca165b900 added unsupported warning for abort for Android versions < 6 2020-10-08 21:17:26 +02:00
russa
09c2b383ff skip abort-tests if Android version is < 6 2020-10-08 21:10:45 +02:00
russa
6918a2ed15 added support for skipping tests 2020-10-08 21:08:30 +02:00
russa
5b827d500d improve handling for race condition that request finished before adding it to pending requests map 2020-10-08 21:06:56 +02:00
russa
1c27b62148 added configuration for allowing http (cleartext) requests on Android 7 and later (sdk >= 24) 2020-10-08 18:12:59 +02:00
Sefa Ilkimen
f33a911e7e Merge pull request #364 from silkimen/dependabot/npm_and_yarn/bl-4.0.3
chore(deps): bump bl from 4.0.2 to 4.0.3
2020-10-01 02:20:41 +02:00
russa
097faad07a removed unsupported warning for aborting requests on ios from README 2020-09-25 18:59:45 +02:00
russa
62c400c6db added support for aborting requests for ios platform 2020-09-25 18:56:15 +02:00
russa
f823d24438 added tests for abort 2020-09-16 19:51:29 +02:00
russa
64a7148444 FIX use same error message for abort as on android platform 2020-09-16 19:48:23 +02:00
russa
f6736d9150 added usage description for abort() 2020-09-16 18:37:15 +02:00
russa
bc90ae85fb added 'unsupported' feedback for abort() on ios platform 2020-09-16 18:36:55 +02:00
russa
389e860125 added support for abort() on browser platform 2020-09-16 18:35:01 +02:00
russa
2367d264c1 added support for abort() on android platform 2020-09-16 18:05:30 +02:00
russa
269d5d4c8a added abort() function to js-module 2020-09-16 17:44:59 +02:00
dependabot[bot]
b6ee4de379 chore(deps): bump bl from 4.0.2 to 4.0.3
Bumps [bl](https://github.com/rvagg/bl) from 4.0.2 to 4.0.3.
- [Release notes](https://github.com/rvagg/bl/releases)
- [Commits](https://github.com/rvagg/bl/compare/v4.0.2...v4.0.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-02 15:37:00 +00:00
Sefa Ilkimen
5f327dc82a release v3.0.1 2020-08-18 12:52:01 +02:00
Sefa Ilkimen
1639efe8d0 fix: #355 [Bug] [Browser] responseType "json" not working with valid JSON response 2020-08-18 02:16:03 +02:00
Sefa Ilkimen
9bb0c58e35 chore: bump version and update readme 2020-08-17 15:59:23 +02:00
Sefa Ilkimen
57562a0dcf fix: #359 [Bug] [Android] memory leakage leads to app crashes 2020-08-17 03:02:05 +02:00
Sefa Ilkimen
ad4079625e Merge pull request #354 from silkimen/dependabot/npm_and_yarn/lodash-4.17.19
chore(deps): bump lodash from 4.17.15 to 4.17.19
2020-08-17 00:47:29 +02:00
dependabot[bot]
98d3d38e07 chore(deps): bump lodash from 4.17.15 to 4.17.19
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-18 20:24:07 +00:00
Sefa Ilkimen
c748406090 release v3.0.0 2020-07-14 03:13:05 +02:00
Sefa Ilkimen
dc6cf4d45b chore: fix update-plugin-xml.js 2020-07-14 03:12:29 +02:00
Sefa Ilkimen
7661e02598 chore: accept android sdk licenses 2020-07-14 02:39:59 +02:00
Sefa Ilkimen
20ec4bee31 chore: use node 10.x for TravisCI build 2020-07-14 01:33:22 +02:00
Sefa Ilkimen
4eed89e530 chore: update change log 2020-07-14 00:50:49 +02:00
Sefa Ilkimen
f43b2f9f5c Merge pull request #345 from ikosta/master
fix(): default filename for blob
2020-07-07 17:43:46 +02:00
Konstantinos Tsanakas
7b4d37acd9 Update www/ponyfills.js
Co-authored-by: Sefa Ilkimen <silkimen@users.noreply.github.com>
2020-07-07 11:03:36 +02:00
Konstantinos Tsanakas
ca5306cb47 fix(ponyfills): default filename for blob 2020-07-06 15:23:02 +02:00
Konstantinos Tsanakas
c0c1af5ee6 fix(): default filename for blob 2020-06-25 13:36:38 +02:00
Sefa Ilkimen
42e629e34d chore: update readme 2020-06-25 10:35:29 +02:00
Sefa Ilkimen
3bec8dde5f feat: #158 support removing headers which were previously set via "setHeader" 2020-06-25 10:28:43 +02:00
Sefa Ilkimen
8a3bc17810 chore: update dependencies 2020-06-25 09:35:06 +02:00
Sefa Ilkimen
1eb83478dc - deprecate: #297 drop support for Android < 5.1
- bump version
- remove deprecated functions
2020-06-25 07:47:51 +02:00
Sefa Ilkimen
1f0df54111 workaround #344: disable two specs until https://github.com/postmanlabs/httpbin/issues/617 is fixed 2020-06-25 07:09:53 +02:00
Sefa Ilkimen
ef09e466ec release v2.5.1 2020-06-04 01:32:33 +02:00
Sefa Ilkimen
88de0550f4 chore: update README 2020-05-30 18:39:20 +02:00
Sefa Ilkimen
7f680b07ec chore: update changelog 2020-05-29 05:03:12 +02:00
Sefa Ilkimen
a259e7cf8d Merge branch 'clear-cookies-ios'
# Conflicts:
#	test/e2e-specs.js
2020-05-29 05:00:25 +02:00
Sefa Ilkimen
1e3c9e6645 chore: update changelog 2020-05-29 04:22:50 +02:00
Sefa Ilkimen
e0cf458179 Merge branch 'antikalk-master' 2020-05-29 04:15:02 +02:00
Sefa Ilkimen
4f4e7ffa33 fix: broken handling for empty strings 2020-05-29 04:03:20 +02:00
Sefa Ilkimen
65e4a4d4dc fix: e2e spec "should allow empty response body even though responseType is set #334"
- `cordova.plugin.http.get` does not support passing an options object => using `cordova.plugin.http.sendRequest` instead
- but still fails on test "should return empty body string correctly (GET)"
2020-05-29 03:35:20 +02:00
antikalk
fed2b03cc6 Revert "+ dont manipulate response data"
This reverts commit bfc6ba2008.
2020-05-21 22:41:49 +02:00
antikalk
29e0b385de Merge branch 'master' of https://github.com/antikalk/cordova-plugin-advanced-http 2020-05-16 16:55:10 +02:00
antikalk
bfc6ba2008 + dont manipulate response data 2020-05-16 16:54:58 +02:00
Oliver
060aa088f5 Merge branch 'master' into master 2020-05-15 08:32:03 +02:00
antikalk
e775057389 + added e2e test 2020-05-14 22:22:03 +02:00
antikalk
81874c6bc8 + test if empty response data is handled correctly 2020-05-13 09:13:51 +02:00
antikalk
658bbd8b28 + set data to null if response data is not set 2020-05-13 09:12:59 +02:00
antikalk
a1e4be37d4 + early return pattern 2020-05-13 08:07:13 +02:00
antikalk
5be52d78d1 + handle empty success responses 2020-05-12 14:44:22 +02:00
Sefa Ilkimen
0a23e29403 chore: document X.509 client cert feature 2020-05-02 00:48:52 +02:00
Sefa Ilkimen
6009ae82f7 chore: update readme to prevent confusion on request timeout 2020-05-02 00:06:58 +02:00
Sefa Ilkimen
0bfb81c57f chore: fix browserstack testing for android 2020-05-01 23:56:39 +02:00
Sefa Ilkimen
f8f4bdc9df fix: auth challenge block isn't handling server trust settings correctly 2020-03-05 03:23:28 +01:00
Sefa Ilkimen
d84e3995d2 chore: update appium caps 2020-03-05 02:04:48 +01:00
Sefa Ilkimen
f5597dd176 WIP: implement setClientAuthMode()for iOS 2020-03-05 01:36:27 +01:00
Sefa Ilkimen
b25b7db4be release v2.4.1 2020-02-18 01:04:57 +01:00
Sefa Ilkimen
92be8f8e96 fix #248: clearCookies() does not work on iOS 2020-02-03 03:58:09 +01:00
Sefa Ilkimen
9ffdc5d2eb test: implement e2e test for #248 2020-02-03 03:23:50 +01:00
Sefa Ilkimen
23a98ae491 fix #300: FormData object containing null or undefined value is not serialized correctly 2020-02-03 02:46:31 +01:00
Sefa Ilkimen
ab9dad8f46 Merge branch 'antikalk-master' 2020-02-03 01:38:00 +01:00
Sefa Ilkimen
69344b5357 chore: update changelog 2020-02-03 01:37:21 +01:00
Sefa Ilkimen
c88d1b6cc7 test: implement e2e test for #301 2020-02-03 01:30:28 +01:00
antikalk
b6f369b868 fix for issues #220 and #286
When responseType is set to json, the data should be returned as plain json string, not as a base64 encoded string.
2020-01-29 20:14:06 +01:00
Sefa Ilkimen
39fa17e4ed update changelog 2020-01-27 23:57:45 +01:00
Sefa Ilkimen
5a19c6ad06 Merge branch 'fix/#296-multipart-serializer-on-browser-platform' 2020-01-27 23:45:53 +01:00
Sefa Ilkimen
78db1dc516 fix #296: [Bug] [browser] multipart requests are not serialized correctly 2020-01-27 01:29:04 +01:00
33 changed files with 5724 additions and 5689 deletions

View File

@@ -36,17 +36,19 @@ matrix:
android:
components:
- tools
- platform-tools
- build-tools-28.0.3
- android-27
- android-28
- extra-android-support
- extra-android-m2repository
- extra-google-m2repository
before_install:
- export LANG=en_US.UTF-8 &&
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - &&
curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - &&
sudo apt-get install -y nodejs
- yes | sdkmanager --update
install:
- npm install

View File

@@ -1,5 +1,39 @@
# Changelog
## 3.1.0
- Feature #272: add support for aborting requests (thanks russaa)
## 3.0.1
- Fixed #359: memory leakage leads to app crashes on Android
- Fixed #355: responseType "json" not working with valid JSON response on browser (thanks millerg6711)
## 3.0.0
- Feature #158: support removing headers which were previously set via "setHeader"
- Fixed #345: empty file names are not handled correctly (thanks ikosta)
- :warning: **Breaking Change**: Dropped support for Android < 5.1
- :warning: **Breaking Change**: Removed "disableRedirect", use "setFollowRedirect" instead
- :warning: **Breaking Change**: Removed "setSSLCertMode", use "setServerTrustMode" instead
## 2.5.1
- Fixed #334: empty JSON response triggers error even though request is successful (thanks antikalk)
- Fixed #248: clearCookies() does not work on iOS
## 2.5.0
- Feature #56: add support for X.509 client certificate based authentication
## 2.4.1
- Fixed #296: multipart requests are not serialized on browser platform
- Fixed #301: data is not decoded correctly when responseType is "json" (thanks antikalk)
- Fixed #300: FormData object containing null or undefined value is not serialized correctly
## 2.4.0
- Feature #291: add support for sending 'raw' requests (thanks to jachstet-sea and chuchuva)

View File

@@ -16,6 +16,7 @@ This is a fork of [Wymsee's Cordova-HTTP plugin](https://github.com/wymsee/cordo
- SSL / TLS Pinning
- CORS restrictions do not apply
- X.509 client certificate based authentication
- Handling of HTTP code 401 - read more at [Issue CB-2415](https://issues.apache.org/jira/browse/CB-2415)
## Updates
@@ -60,7 +61,7 @@ cordova.plugin.http.useBasicAuth('user', 'password');
```
### setHeader<a name="setHeader"></a>
Set a header for all future requests to a specified host. Takes a hostname, a header and a value (must be a string value).
Set a header for all future requests to a specified host. Takes a hostname, a header and a value (must be a string value or null).
```js
cordova.plugin.http.setHeader('Hostname', 'Header', 'Value');
@@ -114,7 +115,7 @@ This defaults to `urlencoded`. You can also override the default content type he
:warning: `multipart` depends on several Web API standards which need to be supported in your web view. Check out https://github.com/silkimen/cordova-plugin-advanced-http/wiki/Web-APIs-required-for-Multipart-requests for more info.
### setRequestTimeout
Set how long to wait for a request to respond, in seconds.
Set the "read" timeout in seconds. This is the timeout interval to use when waiting for additional data.
```js
cordova.plugin.http.setRequestTimeout(5.0);
@@ -186,11 +187,28 @@ cordova.plugin.http.setServerTrustMode('nocheck', function() {
});
```
### disableRedirect (deprecated)
This function was deprecated in 2.0.9. Use ["setFollowRedirect"](#setFollowRedirect) instead.
### setClientAuthMode<a name="setClientAuthMode"></a>
Configure X.509 client certificate authentication. Takes mode and options. `mode` being one of following values:
### setSSLCertMode (deprecated)
This function was deprecated in 2.0.8. Use ["setServerTrustMode"](#setServerTrustMode) instead.
* `none`: disable client certificate authentication
* `systemstore` (only on Android): use client certificate installed in the Android system store; user will be presented with a list of all installed certificates
* `buffer`: use given client certificate; you will need to provide an options object:
* `rawPkcs`: ArrayBuffer containing raw PKCS12 container with client certificate and private key
* `pkcsPassword`: password of the PKCS container
```js
// enable client auth using PKCS12 container given in ArrayBuffer `myPkcs12ArrayBuffer`
cordova.plugin.http.setClientAuthMode('buffer', {
rawPkcs: myPkcs12ArrayBuffer,
pkcsPassword: 'mySecretPassword'
}, success, fail);
// enable client auth using certificate in system store (only on Android)
cordova.plugin.http.setClientAuthMode('systemstore', {}, success, fail);
// disable client auth
cordova.plugin.http.setClientAuthMode('none', {}, success, fail);
```
### removeCookies
Remove all cookies associated with a given URL.
@@ -213,9 +231,9 @@ The options object contains following keys:
* `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)
* `json` data is treated as JSON and returned as parsed object, returns `undefined` when response body is empty
* `arraybuffer`: data is returned as [ArrayBuffer instance](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer), returns `null` when response body is empty
* `blob`: data is returned as [Blob instance](https://developer.mozilla.org/en-US/docs/Web/API/Blob), returns `null` when response body is empty
* `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
@@ -386,6 +404,46 @@ cordova.plugin.http.downloadFile("https://google.com/", {
});
```
### abort<a name="abort"></a>
Abort a HTTP request. Takes the `requestId` which is returned by [sendRequest](#sendRequest) and its shorthand functions ([post](#post), [get](#get), [put](#put), [patch](#patch), [delete](#delete), [head](#head), [uploadFile](#uploadFile) and [downloadFile](#downloadFile)).
If the request already has finished, the request will finish normally and the abort call result will be `{ aborted: false }`.
If the request is still in progress, the request's `failure` callback will be invoked with response `{ status: -8 }`, and the abort call result `{ aborted: true }`.
:warning: Not supported for Android < 6 (API level < 23). For Android 5.1 and below, calling `abort(reqestId)` will have no effect, i.e. the requests will finish as if the request was not cancelled.
```js
// start a request and get its requestId
var requestId = cordova.plugin.http.downloadFile("https://google.com/", {
id: '12',
message: 'test'
}, { Authorization: 'OAuth2: token' }, 'file:///somepicture.jpg', function(entry) {
// prints the filename
console.log(entry.name);
// prints the filePath
console.log(entry.fullPath);
}, function(response) {
// if request was actually aborted, failure callback with status -8 will be invoked
if(response.status === -8){
console.log('download aborted');
} else {
console.error(response.error);
}
});
//...
// abort request
cordova.plugin.http.abort(requestId, function(result) {
// prints if request was aborted: true | false
console.log(result.aborted);
}, function(response) {
console.error(response.error);
});
```
## Browser support<a name="browserSupport"></a>
This plugin supports a very restricted set of functions on the browser platform.

10084
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "cordova-plugin-advanced-http",
"version": "2.4.0",
"version": "3.1.0",
"description": "Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning",
"scripts": {
"updatecert": "node ./scripts/update-e2e-server-cert.js && node ./scripts/update-e2e-client-cert.js",
@@ -58,15 +58,12 @@
},
"homepage": "https://github.com/silkimen/cordova-plugin-advanced-http#readme",
"devDependencies": {
"chai": "4.1.2",
"chai-as-promised": "7.1.1",
"colors": "1.1.2",
"cordova": "8.1.2",
"mocha": "4.0.0",
"mock-require": "2.0.2",
"mz": "2.7.0",
"chai": "4.2.0",
"colors": "1.4.0",
"cordova": "9.0.0",
"mocha": "8.0.1",
"umd-tough-cookie": "2.4.3",
"wd": "1.4.1",
"xml2js": "0.4.19"
"wd": "1.12.1",
"xml2js": "0.4.23"
}
}

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.4.0">
<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="3.1.0">
<name>Advanced HTTP plugin</name>
<description>
Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning
@@ -74,16 +74,14 @@
<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/cordovahttp/CordovaObservableCallbackContext.java" target-dir="src/com/silkimen/cordovahttp"/>
<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"/>
<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

@@ -3,8 +3,8 @@ set -e
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd ..; pwd )"
if [ $CI == "true" ] && ([ -z $SAUCE_USERNAME ] || [ -z $SAUCE_ACCESS_KEY ]); then
echo "Skipping CI tests, because Saucelabs credentials are not set.";
if [ $CI == "true" ] && ([ -z $SAUCE_USERNAME ] || [ -z $SAUCE_ACCESS_KEY ]) && ([ -z $BROWSERSTACK_USERNAME ] || [ -z $BROWSERSTACK_ACCESS_KEY ]); then
echo "Skipping CI tests, because Saucelabs and BrowserStack credentials are not set.";
exit 0;
fi

View File

@@ -1,5 +1,5 @@
const args = process.argv.slice(2);
const fs = require('mz/fs');
const fs = require('fs');
const path = require('path');
const xml2js = require('xml2js');
const xmlPath = path.join(__dirname, '..', 'plugin.xml');
@@ -22,10 +22,12 @@ const stringify = obj => {
return builder.buildObject(obj);
};
fs.readFile(xmlPath, 'utf-8')
.then(xml => parse(xml))
.then(parsed => {
parsed.plugin.$.version = args[0];
const update = async (version) => {
const xml = fs.readFileSync(xmlPath, 'utf-8');
const parsed = await parse(xml);
return fs.writeFile(xmlPath, stringify(parsed));
});
parsed.plugin.$.version = version;
fs.writeFileSync(xmlPath, stringify(parsed));
};
return update(args[0]);

View File

@@ -5,6 +5,7 @@ import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
@@ -16,11 +17,8 @@ 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;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -40,11 +38,11 @@ abstract class CordovaHttpBase implements Runnable {
protected int timeout;
protected boolean followRedirects;
protected TLSConfiguration tlsConfiguration;
protected CallbackContext callbackContext;
protected CordovaObservableCallbackContext callbackContext;
public CordovaHttpBase(String method, String url, String serializer, Object data, JSONObject headers, int timeout,
boolean followRedirects, String responseType, TLSConfiguration tlsConfiguration,
CallbackContext callbackContext) {
CordovaObservableCallbackContext callbackContext) {
this.method = method;
this.url = url;
@@ -59,7 +57,7 @@ abstract class CordovaHttpBase implements Runnable {
}
public CordovaHttpBase(String method, String url, JSONObject headers, int timeout, boolean followRedirects,
String responseType, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
String responseType, TLSConfiguration tlsConfiguration, CordovaObservableCallbackContext callbackContext) {
this.method = method;
this.url = url;
@@ -75,11 +73,13 @@ abstract class CordovaHttpBase implements Runnable {
public void run() {
CordovaHttpResponse response = new CordovaHttpResponse();
HttpRequest request = null;
try {
HttpRequest request = this.createRequest();
request = this.createRequest();
this.prepareRequest(request);
this.sendBody(request);
this.processResponse(request, response);
request.disconnect();
} catch (HttpRequestException e) {
if (e.getCause() instanceof SSLException) {
response.setStatus(-2);
@@ -94,10 +94,17 @@ abstract class CordovaHttpBase implements Runnable {
response.setErrorMessage("Request timed out: " + e.getMessage());
Log.w(TAG, "Request timed out", e);
} else {
response.setStatus(-1);
response.setErrorMessage("There was an error with the request: " + e.getCause().getMessage());
Log.w(TAG, "Generic request error", e);
String cause = e.getCause().getMessage();
if(e.getCause() instanceof InterruptedIOException && "thread interrupted".equals(cause.toLowerCase())){
this.setAborted(request, response);
} else {
response.setStatus(-1);
response.setErrorMessage("There was an error with the request: " + cause);
Log.w(TAG, "Generic request error", e);
}
}
} catch (InterruptedException ie) {
this.setAborted(request, response);
} catch (Exception e) {
response.setStatus(-1);
response.setErrorMessage(e.getMessage());
@@ -124,7 +131,6 @@ abstract class CordovaHttpBase implements Runnable {
request.readTimeout(this.timeout);
request.acceptCharset("UTF-8");
request.uncompress(true);
HttpRequest.setConnectionFactory(new OkConnectionFactory());
if (this.tlsConfiguration.getHostnameVerifier() != null) {
request.setHostnameVerifier(this.tlsConfiguration.getHostnameVerifier());
@@ -193,7 +199,7 @@ abstract class CordovaHttpBase implements Runnable {
response.setHeaders(request.headers());
if (request.code() >= 200 && request.code() < 300) {
if ("text".equals(this.responseType)) {
if ("text".equals(this.responseType) || "json".equals(this.responseType)) {
String decoded = HttpBodyDecoder.decodeBody(outputStream.toByteArray(), request.charset());
response.setBody(decoded);
} else {
@@ -203,4 +209,17 @@ abstract class CordovaHttpBase implements Runnable {
response.setErrorMessage(HttpBodyDecoder.decodeBody(outputStream.toByteArray(), request.charset()));
}
}
protected void setAborted(HttpRequest request, CordovaHttpResponse response) {
response.setStatus(-8);
response.setErrorMessage("Request was aborted");
if(request != null){
try{
request.disconnect();
} catch(Exception any){
Log.w(TAG, "Failed to close aborted request", any);
}
}
Log.i(TAG, "Request was aborted");
}
}

View File

@@ -9,7 +9,6 @@ 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;
import org.json.JSONObject;
@@ -17,7 +16,7 @@ class CordovaHttpDownload extends CordovaHttpBase {
private String filePath;
public CordovaHttpDownload(String url, JSONObject headers, String filePath, int timeout, boolean followRedirects,
TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
TLSConfiguration tlsConfiguration, CordovaObservableCallbackContext callbackContext) {
super("GET", url, headers, timeout, followRedirects, "text", tlsConfiguration, callbackContext);
this.filePath = filePath;

View File

@@ -5,20 +5,19 @@ 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, String responseType, TLSConfiguration tlsConfiguration,
CallbackContext callbackContext) {
CordovaObservableCallbackContext callbackContext) {
super(method, url, serializer, data, headers, timeout, followRedirects, responseType, tlsConfiguration,
callbackContext);
}
public CordovaHttpOperation(String method, String url, JSONObject headers, int timeout, boolean followRedirects,
String responseType, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
String responseType, TLSConfiguration tlsConfiguration, CordovaObservableCallbackContext callbackContext) {
super(method, url, headers, timeout, followRedirects, responseType, tlsConfiguration, callbackContext);
}

View File

@@ -1,6 +1,10 @@
package com.silkimen.cordovahttp;
import java.security.KeyStore;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.Future;
import com.silkimen.http.TLSConfiguration;
@@ -17,17 +21,22 @@ import android.util.Base64;
import javax.net.ssl.TrustManagerFactory;
public class CordovaHttpPlugin extends CordovaPlugin {
public class CordovaHttpPlugin extends CordovaPlugin implements Observer {
private static final String TAG = "Cordova-Plugin-HTTP";
private TLSConfiguration tlsConfiguration;
private HashMap<Integer, Future<?>> reqMap;
private final Object reqMapLock = new Object();
@Override
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
this.tlsConfiguration = new TLSConfiguration();
this.reqMap = new HashMap<Integer, Future<?>>();
try {
KeyStore store = KeyStore.getInstance("AndroidCAStore");
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
@@ -73,6 +82,8 @@ public class CordovaHttpPlugin extends CordovaPlugin {
return this.setServerTrustMode(args, callbackContext);
} else if ("setClientAuthMode".equals(action)) {
return this.setClientAuthMode(args, callbackContext);
} else if ("abort".equals(action)) {
return this.abort(args, callbackContext);
} else {
return false;
}
@@ -87,10 +98,13 @@ public class CordovaHttpPlugin extends CordovaPlugin {
boolean followRedirect = args.getBoolean(3);
String responseType = args.getString(4);
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, headers, timeout, followRedirect,
responseType, this.tlsConfiguration, callbackContext);
Integer reqId = args.getInt(5);
CordovaObservableCallbackContext observableCallbackContext = new CordovaObservableCallbackContext(callbackContext, reqId);
cordova.getThreadPool().execute(request);
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, headers, timeout, followRedirect,
responseType, this.tlsConfiguration, observableCallbackContext);
startRequest(reqId, observableCallbackContext, request);
return true;
}
@@ -106,10 +120,13 @@ public class CordovaHttpPlugin extends CordovaPlugin {
boolean followRedirect = args.getBoolean(5);
String responseType = args.getString(6);
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, serializer, data, headers,
timeout, followRedirect, responseType, this.tlsConfiguration, callbackContext);
Integer reqId = args.getInt(7);
CordovaObservableCallbackContext observableCallbackContext = new CordovaObservableCallbackContext(callbackContext, reqId);
cordova.getThreadPool().execute(request);
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, serializer, data, headers,
timeout, followRedirect, responseType, this.tlsConfiguration, observableCallbackContext);
startRequest(reqId, observableCallbackContext, request);
return true;
}
@@ -123,10 +140,13 @@ public class CordovaHttpPlugin extends CordovaPlugin {
boolean followRedirect = args.getBoolean(5);
String responseType = args.getString(6);
CordovaHttpUpload upload = new CordovaHttpUpload(url, headers, filePaths, uploadNames, timeout, followRedirect,
responseType, this.tlsConfiguration, this.cordova.getActivity().getApplicationContext(), callbackContext);
Integer reqId = args.getInt(7);
CordovaObservableCallbackContext observableCallbackContext = new CordovaObservableCallbackContext(callbackContext, reqId);
cordova.getThreadPool().execute(upload);
CordovaHttpUpload upload = new CordovaHttpUpload(url, headers, filePaths, uploadNames, timeout, followRedirect,
responseType, this.tlsConfiguration, this.cordova.getActivity().getApplicationContext(), observableCallbackContext);
startRequest(reqId, observableCallbackContext, upload);
return true;
}
@@ -138,14 +158,25 @@ public class CordovaHttpPlugin extends CordovaPlugin {
int timeout = args.getInt(3) * 1000;
boolean followRedirect = args.getBoolean(4);
CordovaHttpDownload download = new CordovaHttpDownload(url, headers, filePath, timeout, followRedirect,
this.tlsConfiguration, callbackContext);
Integer reqId = args.getInt(5);
CordovaObservableCallbackContext observableCallbackContext = new CordovaObservableCallbackContext(callbackContext, reqId);
cordova.getThreadPool().execute(download);
CordovaHttpDownload download = new CordovaHttpDownload(url, headers, filePath, timeout, followRedirect,
this.tlsConfiguration, observableCallbackContext);
startRequest(reqId, observableCallbackContext, download);
return true;
}
private void startRequest(Integer reqId, CordovaObservableCallbackContext observableCallbackContext, CordovaHttpBase request) {
synchronized (reqMapLock) {
observableCallbackContext.setObserver(this);
Future<?> task = cordova.getThreadPool().submit(request);
this.addReq(reqId, task, observableCallbackContext);
}
}
private boolean setServerTrustMode(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
CordovaServerTrust runnable = new CordovaServerTrust(args.getString(0), this.cordova.getActivity(),
this.tlsConfiguration, callbackContext);
@@ -166,4 +197,44 @@ public class CordovaHttpPlugin extends CordovaPlugin {
return true;
}
private boolean abort(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
int reqId = args.getInt(0);
boolean result = false;
// NOTE no synchronized (reqMapLock), since even if the req was already removed from reqMap,
// the worst that would happen calling task.cancel(true) is a result of false
// (i.e. same result as locking & not finding the req in reqMap)
Future<?> task = this.reqMap.get(reqId);
if (task != null && !task.isDone()) {
result = task.cancel(true);
}
callbackContext.success(new JSONObject().put("aborted", result));
return true;
}
private void addReq(final Integer reqId, final Future<?> task, final CordovaObservableCallbackContext observableCallbackContext) {
synchronized (reqMapLock) {
if(!task.isDone()){
this.reqMap.put(reqId, task);
}
}
}
private void removeReq(final Integer reqId) {
synchronized (reqMapLock) {
this.reqMap.remove(reqId);
}
}
@Override
public void update(Observable o, Object arg) {
synchronized (reqMapLock) {
CordovaObservableCallbackContext c = (CordovaObservableCallbackContext) arg;
if (c.getCallbackContext().isFinished()) {
removeReq(c.getRequestId());
}
}
}
}

View File

@@ -17,7 +17,6 @@ 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;
@@ -28,7 +27,7 @@ class CordovaHttpUpload extends CordovaHttpBase {
public CordovaHttpUpload(String url, JSONObject headers, JSONArray filePaths, JSONArray uploadNames, int timeout,
boolean followRedirects, String responseType, TLSConfiguration tlsConfiguration,
Context applicationContext, CallbackContext callbackContext) {
Context applicationContext, CordovaObservableCallbackContext callbackContext) {
super("POST", url, headers, timeout, followRedirects, responseType, tlsConfiguration, callbackContext);
this.filePaths = filePaths;

View File

@@ -0,0 +1,58 @@
package com.silkimen.cordovahttp;
import org.apache.cordova.CallbackContext;
import org.json.JSONObject;
import java.util.Observer;
public class CordovaObservableCallbackContext {
private CallbackContext callbackContext;
private Integer requestId;
private Observer observer;
public CordovaObservableCallbackContext(CallbackContext callbackContext, Integer requestId) {
this.callbackContext = callbackContext;
this.requestId = requestId;
}
public void success(JSONObject message) {
this.callbackContext.success(message);
this.notifyObserver();
}
public void error(JSONObject message) {
this.callbackContext.error(message);
this.notifyObserver();
}
public Integer getRequestId() {
return this.requestId;
}
public CallbackContext getCallbackContext() {
return callbackContext;
}
public Observer getObserver() {
return observer;
}
protected void notifyObserver() {
if(this.observer != null){
this.observer.update(null, this);
}
}
/**
* Set an observer that is notified, when {@link #success(JSONObject)}
* or {@link #error(JSONObject)} are called.
*
* NOTE the observer is notified with
* <pre>observer.update(null, cordovaObservableCallbackContext)</pre>
* @param observer
*/
public void setObserver(Observer observer) {
this.observer = observer;
}
}

View File

@@ -1,26 +0,0 @@
package com.silkimen.http;
import okhttp3.OkHttpClient;
import okhttp3.OkUrlFactory;
import java.net.URL;
import java.net.HttpURLConnection;
import java.net.URLStreamHandler;
import java.net.Proxy;
public class OkConnectionFactory implements HttpRequest.ConnectionFactory {
private final OkHttpClient client = new OkHttpClient();
public HttpURLConnection create(URL url) {
OkUrlFactory urlFactory = new OkUrlFactory(this.client);
return (HttpURLConnection) urlFactory.open(url);
}
public HttpURLConnection create(URL url, Proxy proxy) {
OkHttpClient clientWithProxy = new OkHttpClient.Builder().proxy(proxy).build();
OkUrlFactory urlFactory = new OkUrlFactory(clientWithProxy);
return (HttpURLConnection) urlFactory.open(url);
}
}

View File

@@ -3,6 +3,8 @@ var pluginId = module.id.slice(0, module.id.lastIndexOf('.'));
var cordovaProxy = require('cordova/exec/proxy');
var jsUtil = require(pluginId + '.js-util');
var reqMap = {};
function serializeJsonData(data) {
try {
return JSON.stringify(data);
@@ -20,7 +22,7 @@ function serializePrimitive(key, value) {
}
function serializeArray(key, values) {
return values.map(function(value) {
return values.map(function (value) {
return encodeURIComponent(key) + '[]=' + encodeURIComponent(value);
}).join('&');
}
@@ -28,7 +30,7 @@ function serializeArray(key, values) {
function serializeParams(params) {
if (params === null) return '';
return Object.keys(params).map(function(key) {
return Object.keys(params).map(function (key) {
if (jsUtil.getTypeOf(params[key]) === 'Array') {
return serializeArray(key, params[key]);
}
@@ -37,6 +39,39 @@ function serializeParams(params) {
}).join('&');
}
function decodeB64(dataString) {
var binaryString = atob(dataString);
var bytes = new Uint8Array(binaryString.length);
for (var i = 0; i < binaryString.length; ++i) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}
function processMultipartData(data) {
if (!data) return null;
var fd = new FormData();
for (var i = 0; i < data.buffers.length; ++i) {
var buffer = data.buffers[i];
var name = data.names[i];
var fileName = data.fileNames[i];
var type = data.types[i];
if (fileName) {
fd.append(name, new Blob([decodeB64(buffer)], { type: type }), fileName);
} else {
// we assume it's plain text if no filename was given
fd.append(name, atob(buffer));
}
}
return fd;
}
function deserializeResponseHeaders(headers) {
var headerMap = {};
var arr = headers.trim().split(/[\r\n]+/);
@@ -82,10 +117,17 @@ function createXhrFailureObject(xhr) {
return obj;
}
function injectRequestIdHandler(reqId, cb) {
return function (response) {
delete reqMap[reqId];
cb(response);
}
}
function getHeaderValue(headers, headerName) {
let result = null;
Object.keys(headers).forEach(function(key) {
Object.keys(headers).forEach(function (key) {
if (key.toLowerCase() === headerName.toLowerCase()) {
result = headers[key];
}
@@ -101,7 +143,7 @@ function setDefaultContentType(headers, contentType) {
}
function setHeaders(xhr, headers) {
Object.keys(headers).forEach(function(key) {
Object.keys(headers).forEach(function (key) {
if (key.toLowerCase() === 'cookie') return;
xhr.setRequestHeader(key, headers[key]);
@@ -109,7 +151,7 @@ function setHeaders(xhr, headers) {
}
function sendRequest(method, withData, opts, success, failure) {
var data, serializer, headers, timeout, followRedirect, responseType;
var data, serializer, headers, timeout, followRedirect, responseType, reqId;
var url = opts[0];
if (withData) {
@@ -119,25 +161,31 @@ function sendRequest(method, withData, opts, success, failure) {
timeout = opts[4];
followRedirect = opts[5];
responseType = opts[6];
reqId = opts[7];
} else {
headers = opts[1];
timeout = opts[2];
followRedirect = opts[3];
responseType = opts[4];
reqId = opts[5];
}
var onSuccess = injectRequestIdHandler(reqId, success);
var onFail = injectRequestIdHandler(reqId, failure);
var processedData = null;
var xhr = new XMLHttpRequest();
reqMap[reqId] = xhr;
xhr.open(method, url);
if (headers.Cookie && headers.Cookie.length > 0) {
return failure('advanced-http: custom cookies not supported on browser platform');
return onFail('advanced-http: custom cookies not supported on browser platform');
}
if (!followRedirect) {
return failure('advanced-http: disabling follow redirect not supported on browser platform');
return onFail('advanced-http: disabling follow redirect not supported on browser platform');
}
switch (serializer) {
@@ -146,7 +194,7 @@ function sendRequest(method, withData, opts, success, failure) {
processedData = serializeJsonData(data);
if (processedData === null) {
return failure('advanced-http: failed serializing data');
return onFail('advanced-http: failed serializing data');
}
break;
@@ -161,22 +209,44 @@ function sendRequest(method, withData, opts, success, failure) {
processedData = serializeParams(data);
break;
case 'multipart':
const contentType = getHeaderValue(headers, 'Content-Type');
// intentionally don't set a default content type
// it's set by the browser together with the content disposition string
if (contentType) {
headers['Content-Type'] = contentType;
}
processedData = processMultipartData(data);
break;
case 'raw':
setDefaultContentType(headers, 'application/octet-stream');
processedData = data;
break;
}
// requesting text instead of JSON because it's parsed in the response handler
xhr.responseType = responseType === 'json' ? 'text' : responseType;
xhr.timeout = timeout * 1000;
xhr.responseType = responseType;
setHeaders(xhr, headers);
xhr.onerror = function () {
return failure(createXhrFailureObject(xhr));
return onFail(createXhrFailureObject(xhr));
};
xhr.ontimeout = function () {
return failure({
xhr.onabort = function () {
return onFail({
status: -8,
error: 'Request was aborted',
url: url,
headers: {}
});
};
xhr.ontimeout = function () {
return onFail({
status: -4,
error: 'Request timed out',
url: url,
@@ -188,15 +258,28 @@ function sendRequest(method, withData, opts, success, failure) {
if (xhr.readyState !== xhr.DONE) return;
if (xhr.status < 200 || xhr.status > 299) {
return failure(createXhrFailureObject(xhr));
return onFail(createXhrFailureObject(xhr));
}
return success(createXhrSuccessObject(xhr));
return onSuccess(createXhrSuccessObject(xhr));
};
xhr.send(processedData);
}
function abort(opts, success, failure) {
var reqId = opts[0];
var result = false;
var xhr = reqMap[reqId];
if(xhr && xhr.readyState !== xhr.DONE){
xhr.abort();
result = true;
}
success({aborted: result});
}
var browserInterface = {
get: function (success, failure, opts) {
return sendRequest('get', false, opts, success, failure);
@@ -216,6 +299,9 @@ var browserInterface = {
patch: function (success, failure, opts) {
return sendRequest('patch', true, opts, success, failure);
},
abort: function (success, failure, opts) {
return abort(opts, success, failure);
},
uploadFile: function (success, failure, opts) {
return failure('advanced-http: function "uploadFile" not supported on browser platform');
},

View File

@@ -184,6 +184,8 @@
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block error:&serializationError];
[request setHTTPShouldHandleCookies:NO];
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
@@ -232,6 +234,8 @@
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block error:&serializationError];
[request setHTTPShouldHandleCookies:NO];
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
@@ -293,6 +297,8 @@
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block error:&serializationError];
[request setHTTPShouldHandleCookies:NO];
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
@@ -358,6 +364,8 @@
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
[request setHTTPShouldHandleCookies:NO];
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{

View File

@@ -5,6 +5,7 @@
@interface CordovaHttpPlugin : CDVPlugin
- (void)setServerTrustMode:(CDVInvokedUrlCommand*)command;
- (void)setClientAuthMode:(CDVInvokedUrlCommand*)command;
- (void)post:(CDVInvokedUrlCommand*)command;
- (void)put:(CDVInvokedUrlCommand*)command;
- (void)patch:(CDVInvokedUrlCommand*)command;
@@ -14,5 +15,6 @@
- (void)options:(CDVInvokedUrlCommand*)command;
- (void)uploadFiles:(CDVInvokedUrlCommand*)command;
- (void)downloadFile:(CDVInvokedUrlCommand*)command;
- (void)abort:(CDVInvokedUrlCommand*)command;
@end

View File

@@ -9,6 +9,8 @@
@interface CordovaHttpPlugin()
- (void)addRequest:(NSNumber*)reqId forTask:(NSURLSessionDataTask*)task;
- (void)removeRequest:(NSNumber*)reqId;
- (void)setRequestHeaders:(NSDictionary*)headers forManager:(AFHTTPSessionManager*)manager;
- (void)handleSuccess:(NSMutableDictionary*)dictionary withResponse:(NSHTTPURLResponse*)response andData:(id)data;
- (void)handleError:(NSMutableDictionary*)dictionary withResponse:(NSHTTPURLResponse*)response error:(NSError*)error;
@@ -21,10 +23,21 @@
@implementation CordovaHttpPlugin {
AFSecurityPolicy *securityPolicy;
NSURLCredential *x509Credential;
NSMutableDictionary *reqDict;
}
- (void)pluginInitialize {
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
reqDict = [NSMutableDictionary dictionary];
}
- (void)addRequest:(NSNumber*)reqId forTask:(NSURLSessionDataTask*)task {
[reqDict setObject:task forKey:reqId];
}
- (void)removeRequest:(NSNumber*)reqId {
[reqDict removeObjectForKey:reqId];
}
- (void)setRequestSerializer:(NSString*)serializerName forManager:(AFHTTPSessionManager*)manager {
@@ -39,6 +52,33 @@
}
}
- (void)setupAuthChallengeBlock:(AFHTTPSessionManager*)manager {
[manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(
NSURLSession * _Nonnull session,
NSURLAuthenticationChallenge * _Nonnull challenge,
NSURLCredential * _Nullable __autoreleasing * _Nullable credential
) {
if ([challenge.protectionSpace.authenticationMethod isEqualToString: NSURLAuthenticationMethodServerTrust]) {
*credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (![self->securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
return NSURLSessionAuthChallengeRejectProtectionSpace;
}
if (credential) {
return NSURLSessionAuthChallengeUseCredential;
}
}
if ([challenge.protectionSpace.authenticationMethod isEqualToString: NSURLAuthenticationMethodClientCertificate] && self->x509Credential) {
*credential = self->x509Credential;
return NSURLSessionAuthChallengeUseCredential;
}
return NSURLSessionAuthChallengePerformDefaultHandling;
}];
}
- (void)setRequestHeaders:(NSDictionary*)headers forManager:(AFHTTPSessionManager*)manager {
[headers enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
[manager.requestSerializer setValue:obj forHTTPHeaderField:key];
@@ -62,7 +102,7 @@
}
- (void)setResponseSerializer:(NSString*)responseType forManager:(AFHTTPSessionManager*)manager {
if ([responseType isEqualToString: @"text"]) {
if ([responseType isEqualToString: @"text"] || [responseType isEqualToString: @"json"]) {
manager.responseSerializer = [TextResponseSerializer serializer];
} else {
manager.responseSerializer = [BinaryResponseSerializer serializer];
@@ -83,14 +123,21 @@
}
- (void)handleError:(NSMutableDictionary*)dictionary withResponse:(NSHTTPURLResponse*)response error:(NSError*)error {
bool aborted = error.code == NSURLErrorCancelled;
if(aborted){
[dictionary setObject:[NSNumber numberWithInt:-8] forKey:@"status"];
[dictionary setObject:@"Request was aborted" forKey:@"error"];
}
if (response != nil) {
[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[AFNetworkingOperationFailingURLResponseBodyErrorKey]) {
[dictionary setObject:error.userInfo[AFNetworkingOperationFailingURLResponseBodyErrorKey] forKey:@"error"];
if(!aborted){
[dictionary setObject:[NSNumber numberWithInt:(int)response.statusCode] forKey:@"status"];
if (error.userInfo[AFNetworkingOperationFailingURLResponseBodyErrorKey]) {
[dictionary setObject:error.userInfo[AFNetworkingOperationFailingURLResponseBodyErrorKey] forKey:@"error"];
}
}
} else {
} else if(!aborted) {
[dictionary setObject:[self getStatusCode:error] forKey:@"status"];
[dictionary setObject:[error localizedDescription] forKey:@"error"];
}
@@ -147,15 +194,16 @@
- (void)executeRequestWithoutData:(CDVInvokedUrlCommand*)command withMethod:(NSString*) method {
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];
NSNumber *reqId = [command.arguments objectAtIndex:5];
[self setRequestSerializer: @"default" forManager: manager];
[self setupAuthChallengeBlock: manager];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect:followRedirect forManager:manager];
@@ -166,30 +214,35 @@
@try {
void (^onSuccess)(NSURLSessionTask *, id) = ^(NSURLSessionTask *task, id responseObject) {
[weakSelf removeRequest:reqId];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
// no 'body' for HEAD request, omitting 'data'
if ([method isEqualToString:@"HEAD"]) {
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:nil];
} else {
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject];
}
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
};
void (^onFailure)(NSURLSessionTask *, NSError *) = ^(NSURLSessionTask *task, NSError *error) {
[weakSelf removeRequest:reqId];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
};
[manager downloadTaskWithHTTPMethod:method URLString:url parameters:nil progress:nil success:onSuccess failure:onFailure];
NSURLSessionDataTask *task = [manager downloadTaskWithHTTPMethod:method URLString:url parameters:nil progress:nil success:onSuccess failure:onFailure];
[self addRequest:reqId forTask:task];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
@@ -199,7 +252,6 @@
- (void)executeRequestWithData:(CDVInvokedUrlCommand*)command withMethod:(NSString*)method {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *data = [command.arguments objectAtIndex:1];
@@ -208,8 +260,10 @@
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:4] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:5] boolValue];
NSString *responseType = [command.arguments objectAtIndex:6];
NSNumber *reqId = [command.arguments objectAtIndex:7];
[self setRequestSerializer: serializerName forManager: manager];
[self setupAuthChallengeBlock: manager];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect:followRedirect forManager:manager];
@@ -217,30 +271,32 @@
CordovaHttpPlugin* __weak weakSelf = self;
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
void (^constructBody)(id<AFMultipartFormData>) = ^(id<AFMultipartFormData> formData) {
NSArray *buffers = [data mutableArrayValueForKey:@"buffers"];
NSArray *fileNames = [data mutableArrayValueForKey:@"fileNames"];
NSArray *names = [data mutableArrayValueForKey:@"names"];
NSArray *types = [data mutableArrayValueForKey:@"types"];
NSError *error;
for (int i = 0; i < [buffers count]; ++i) {
NSData *decodedBuffer = [[NSData alloc] initWithBase64EncodedString:[buffers objectAtIndex:i] options:0];
NSString *fileName = [fileNames objectAtIndex:i];
NSString *partName = [names objectAtIndex:i];
NSString *partType = [types objectAtIndex:i];
if (![fileName isEqual:[NSNull null]]) {
[formData appendPartWithFileData:decodedBuffer name:partName fileName:fileName mimeType:partType];
} else {
[formData appendPartWithFormData:decodedBuffer name:[names objectAtIndex:i]];
}
}
if (error) {
[weakSelf removeRequest:reqId];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setObject:[NSNumber numberWithInt:400] forKey:@"status"];
[dictionary setObject:@"Could not add part to multipart request body." forKey:@"error"];
@@ -250,30 +306,36 @@
return;
}
};
void (^onSuccess)(NSURLSessionTask *, id) = ^(NSURLSessionTask *task, id responseObject) {
[weakSelf removeRequest:reqId];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
};
void (^onFailure)(NSURLSessionTask *, NSError *) = ^(NSURLSessionTask *task, NSError *error) {
[weakSelf removeRequest:reqId];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
};
NSURLSessionDataTask *task;
if ([serializerName isEqualToString:@"multipart"]) {
[manager uploadTaskWithHTTPMethod:method URLString:url parameters:nil constructingBodyWithBlock:constructBody progress:nil success:onSuccess failure:onFailure];
task = [manager uploadTaskWithHTTPMethod:method URLString:url parameters:nil constructingBodyWithBlock:constructBody progress:nil success:onSuccess failure:onFailure];
} else {
[manager uploadTaskWithHTTPMethod:method URLString:url parameters:data progress:nil success:onSuccess failure:onFailure];
task = [manager uploadTaskWithHTTPMethod:method URLString:url parameters:data progress:nil success:onSuccess failure:onFailure];
}
[self addRequest:reqId forTask:task];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
@@ -302,6 +364,51 @@
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
- (void)setClientAuthMode:(CDVInvokedUrlCommand*)command {
CDVPluginResult* pluginResult;
NSString *mode = [command.arguments objectAtIndex:0];
if ([mode isEqualToString:@"none"]) {
x509Credential = nil;
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
}
if ([mode isEqualToString:@"systemstore"]) {
NSString *alias = [command.arguments objectAtIndex:1];
// TODO
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"mode 'systemstore' is not supported on iOS"];
}
if ([mode isEqualToString:@"buffer"]) {
CFDataRef container = (__bridge CFDataRef) [command.arguments objectAtIndex:2];
CFStringRef password = (__bridge CFStringRef) [command.arguments objectAtIndex:3];
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef items;
OSStatus securityError = SecPKCS12Import(container, options, &items);
CFRelease(options);
if (securityError != noErr) {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
} else {
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
SecIdentityRef identity = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
self->x509Credential = [NSURLCredential credentialWithIdentity:identity certificates: nil persistence:NSURLCredentialPersistenceForSession];
CFRelease(items);
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
}
}
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
- (void)post:(CDVInvokedUrlCommand*)command {
[self executeRequestWithData: command withMethod:@"POST"];
}
@@ -332,7 +439,6 @@
- (void)uploadFiles:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *headers = [command.arguments objectAtIndex:1];
@@ -341,8 +447,10 @@
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:4] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:5] boolValue];
NSString *responseType = [command.arguments objectAtIndex:6];
NSNumber *reqId = [command.arguments objectAtIndex:7];
[self setRequestHeaders: headers forManager: manager];
[self setupAuthChallengeBlock: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect:followRedirect forManager:manager];
[self setResponseSerializer:responseType forManager:manager];
@@ -351,7 +459,7 @@
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
[manager POST:url parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
NSURLSessionDataTask *task = [manager POST:url parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
NSError *error;
for (int i = 0; i < [filePaths count]; i++) {
NSString *filePath = (NSString *) [filePaths objectAtIndex:i];
@@ -360,6 +468,8 @@
[formData appendPartWithFileURL:fileURL name:uploadName error:&error];
}
if (error) {
[weakSelf removeRequest:reqId];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setObject:[NSNumber numberWithInt:500] forKey:@"status"];
[dictionary setObject:@"Could not add file to post body." forKey:@"error"];
@@ -369,6 +479,8 @@
return;
}
} progress:nil success:^(NSURLSessionTask *task, id responseObject) {
[weakSelf removeRequest:reqId];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject];
@@ -376,6 +488,8 @@
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
} failure:^(NSURLSessionTask *task, NSError *error) {
[weakSelf removeRequest:reqId];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
@@ -383,6 +497,7 @@
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
}];
[self addRequest:reqId forTask:task];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
@@ -392,7 +507,6 @@
- (void)downloadFile:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
NSString *url = [command.arguments objectAtIndex:0];
@@ -400,8 +514,10 @@
NSString *filePath = [command.arguments objectAtIndex: 2];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:3] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:4] boolValue];
NSNumber *reqId = [command.arguments objectAtIndex:5];
[self setRequestHeaders: headers forManager: manager];
[self setupAuthChallengeBlock: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect:followRedirect forManager:manager];
@@ -413,7 +529,8 @@
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
[manager GET:url parameters:nil progress: nil success:^(NSURLSessionTask *task, id responseObject) {
NSURLSessionDataTask *task = [manager GET:url parameters:nil progress: nil success:^(NSURLSessionTask *task, id responseObject) {
[weakSelf removeRequest:reqId];
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
@@ -474,6 +591,7 @@
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
} failure:^(NSURLSessionTask *task, NSError *error) {
[weakSelf removeRequest:reqId];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
[dictionary setObject:@"There was an error downloading the file" forKey:@"error"];
@@ -482,6 +600,7 @@
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
}];
[self addRequest:reqId forTask:task];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
@@ -489,4 +608,32 @@
}
}
- (void)abort:(CDVInvokedUrlCommand*)command {
NSNumber *reqId = [command.arguments objectAtIndex:0];
CDVPluginResult *pluginResult;
bool removed = false;
NSURLSessionDataTask *task = [reqDict objectForKey:reqId];
if(task){
@try{
[task cancel];
removed = true;
} @catch (NSException *exception) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setValue:exception.userInfo forKey:@"error"];
[dictionary setObject:[NSNumber numberWithInt:-1] forKey:@"status"];
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
}
}
if(!pluginResult){
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setObject:[NSNumber numberWithBool:removed] forKey:@"aborted"];
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
}
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
@end

View File

@@ -16,15 +16,19 @@
<allow-intent href="mailto:*" />
<allow-intent href="geo:*" />
<platform name="android">
<edit-config file="app/src/main/AndroidManifest.xml" mode="merge" target="/manifest/application" xmlns:android="http://schemas.android.com/apk/res/android">
<application android:networkSecurityConfig="@xml/network_security_config" />
</edit-config>
<resource-file src="network_security_config.xml" target="app/src/main/res/xml/network_security_config.xml" />
<allow-intent href="market:*" />
</platform>
<platform name="ios">
<allow-intent href="itms:*" />
<allow-intent href="itms-apps:*" />
</platform>
<engine name="android" spec="7.1.0" />
<engine name="browser" spec="5.0.0" />
<engine name="ios" spec="5.0.1" />
<plugin name="cordova-plugin-file" spec="6.0.1" />
<engine name="android" spec="8.1.0" />
<engine name="browser" spec="6.0.0" />
<engine name="ios" spec="5.1.0" />
<plugin name="cordova-plugin-file" spec="6.0.2" />
<preference name="AndroidPersistentFileLocation" value="Internal" />
</widget>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">localhost</domain>
<domain includeSubdomains="true">httpbin.org</domain>
</domain-config>
</network-security-config>

View File

@@ -1,26 +1,31 @@
{
"name": "com.ilkimen.http.demo",
"displayName": "HttpDemo",
"version": "1.0.0",
"description": "A sample Apache Cordova application that demonstrates advanced HTTP plugin.",
"main": "index.js",
"scripts": {
"build": "scripts/build.sh",
"test": "npm run build && scripts/test.sh"
},
"author": "Sefa Ilkimen",
"license": "Apache-2.0",
"dependencies": {
"cordova": "7.0.1",
"cordova-android": "7.1.0",
"cordova-browser": "5.0.0",
"cordova-ios": "5.0.1"
},
"cordova": {
"platforms": [
"android",
"ios"
]
},
"devDependencies": {}
"name": "com.ilkimen.http.demo",
"displayName": "HttpDemo",
"version": "1.0.0",
"description": "A sample Apache Cordova application that demonstrates advanced HTTP plugin.",
"main": "index.js",
"scripts": {
"build": "scripts/build.sh",
"test": "npm run build && scripts/test.sh"
},
"author": "Sefa Ilkimen",
"license": "Apache-2.0",
"dependencies": {
"cordova": "9.0.0",
"cordova-android": "8.1.0",
"cordova-browser": "6.0.0",
"cordova-ios": "5.1.0"
},
"cordova": {
"platforms": [
"android",
"ios"
],
"plugins": {
"cordova-plugin-device": {}
}
},
"devDependencies": {
"cordova-plugin-device": "2.0.3"
}
}

View File

@@ -10,12 +10,12 @@ const app = {
var onlyFlaggedTests = [];
var enabledTests = [];
tests.forEach(function (test) {
if (test.only) {
onlyFlaggedTests.push(test);
}
if (!test.disabled) {
enabledTests.push(test);
}
@@ -50,6 +50,16 @@ const app = {
};
},
skip: function (content) {
document.getElementById('statusInput').value = 'finished';
app.printResult('result - skipped', content);
app.lastResult = {
type: 'skipped',
data: content
};
},
throw: function (error) {
document.getElementById('statusInput').value = 'finished';
app.printResult('result - throwed', error.message);
@@ -126,7 +136,7 @@ const app = {
const execTest = function () {
try {
testDefinition.func(app.resolve, app.reject);
testDefinition.func(app.resolve, app.reject, app.skip);
} catch (error) {
app.throw(error);
}

View File

@@ -2,8 +2,8 @@ const hooks = {
onBeforeEachTest: function (resolve, reject) {
cordova.plugin.http.clearCookies();
helpers.enableFollowingRedirect(function() {
// server trust mode is not supported on brpwser platform
helpers.enableFollowingRedirect(function () {
// server trust mode is not supported on browser platform
if (cordova.platformId === 'browser') {
return resolve();
}
@@ -23,7 +23,7 @@ const helpers = {
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) {
helpers.getWithXhr(function (pkcs) {
cordova.plugin.http.setClientAuthMode('buffer', {
rawPkcs: pkcs,
pkcsPassword: 'badssl.com'
@@ -34,9 +34,9 @@ const helpers = {
setUtf8StringSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('utf8')); },
setUrlEncodedSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('urlencoded')); },
setMultipartSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('multipart')); },
setRawSerializer: function(resolve) { resolve(cordova.plugin.http.setDataSerializer('raw')); },
setRawSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('raw')); },
disableFollowingRedirect: function (resolve) { resolve(cordova.plugin.http.setFollowRedirect(false)); },
enableFollowingRedirect: function(resolve) { resolve(cordova.plugin.http.setFollowRedirect(true)); },
enableFollowingRedirect: function (resolve) { resolve(cordova.plugin.http.setFollowRedirect(true)); },
getWithXhr: function (done, url, type) {
var xhr = new XMLHttpRequest();
@@ -71,7 +71,7 @@ const helpers = {
var byteArray = new Uint8Array(buffer);
for (var i = 0; i < byteArray.length; i++) {
hash = ((hash << 5) - hash) + byteArray[i];
hash = ((hash << 5) - hash) + byteArray[i];
hash |= 0; // Convert to 32bit integer
}
@@ -83,7 +83,16 @@ const helpers = {
}
result.type.should.be.equal(expected);
}
},
isAbortSupported: function () {
if (window.cordova && window.cordova.platformId === 'android') {
var version = device.version; //NOTE will throw error if cordova is present without cordova-plugin-device
var major = parseInt(/^(\d+)(\.|$)/.exec(version)[1], 10);
return isFinite(major) && major >= 6;
}
return true;
},
getAbortDelay: function () { return 10; },
};
const messageFactory = {
@@ -288,25 +297,25 @@ const tests = [
JSON.parse(result.data.data).form.should.eql({ test: 'testString' });
}
},
{
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) {
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 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) {
// 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 -->" ...',
@@ -374,7 +383,7 @@ const tests = [
var targetUrl = 'http://httpbin.org/post';
helpers.writeToFile(function () {
helpers.writeToFile(function() {
helpers.writeToFile(function () {
cordova.plugin.http.uploadFile(targetUrl, {}, {}, [sourcePath, sourcePath2], [fileName, fileName2], resolve, reject);
}, fileName2, fileContent2);
}, fileName, fileContent);
@@ -464,7 +473,7 @@ const tests = [
}
},
{
description: 'should not send any cookies after running "clearCookies" (GET) #59',
description: 'should not send programmatically set cookies after running "clearCookies" (GET) #59',
expected: 'resolved: {"status": 200, "data": "{\"headers\": {\"Cookie\": \"\"...',
func: function (resolve, reject) {
cordova.plugin.http.setCookie('http://httpbin.org/get', 'myCookie=myValue');
@@ -787,7 +796,7 @@ const tests = [
},
{
description: 'should decode error body even if response type is "arraybuffer"',
expected: 'rejected: {"status": 418, ...',
expected: 'rejected: {"status":418, ...',
func: function (resolve, reject) {
var url = 'https://httpbin.org/status/418';
var options = { method: 'get', responseType: 'arraybuffer' };
@@ -801,7 +810,7 @@ const tests = [
},
{
description: 'should serialize FormData instance correctly when it contains string value',
expected: 'resolved: {"status": 200, ...',
expected: 'resolved: {"status":200, ...',
before: helpers.setMultipartSerializer,
func: function (resolve, reject) {
var ponyfills = cordova.plugin.http.ponyfills;
@@ -820,11 +829,11 @@ const tests = [
},
{
description: 'should serialize FormData instance correctly when it contains blob value',
expected: 'resolved: {"status": 200, ...',
expected: 'resolved: {"status":200, ...',
before: helpers.setMultipartSerializer,
func: function (resolve, reject) {
var ponyfills = cordova.plugin.http.ponyfills;
helpers.getWithXhr(function(blob) {
helpers.getWithXhr(function (blob) {
var formData = new ponyfills.FormData();
formData.append('CordovaLogo', blob);
@@ -850,7 +859,7 @@ const tests = [
expected: 'resolved: {"status":200,"data:application/octet-stream;base64,iVBORw0KGgoAAAANSUhEUg ...',
before: helpers.setRawSerializer,
func: function (resolve, reject) {
helpers.getWithXhr(function(buffer) {
helpers.getWithXhr(function (buffer) {
cordova.plugin.http.post('http://httpbin.org/anything', buffer, {}, resolve, reject);
}, './res/cordova_logo.png', 'arraybuffer');
},
@@ -890,18 +899,216 @@ const tests = [
result.data.headers['access-control-allow-origin'].should.be.equal('*');
}
},
{
description: 'should allow empty response body even though responseType is set #334',
expected: 'resolved: {"status":200, ...',
func: function (resolve, reject) {
var url = 'https://httpbin.org/status/200';
var options = { method: 'get', responseType: 'json' };
cordova.plugin.http.sendRequest(url, options, resolve, reject);
},
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
should.equal(null, result.data.data);
}
},
{
description: 'should decode JSON data correctly when response type is "json" #301',
expected: 'resolved: {"status":200,"data":{"slideshow": ... ',
func: function (resolve, reject) {
var url = 'https://httpbin.org/json';
var options = { method: 'get', responseType: 'json' };
cordova.plugin.http.sendRequest(url, options, resolve, reject);
},
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.status.should.be.equal(200);
result.data.data.should.be.an('object');
result.data.data.slideshow.should.be.eql({
author: 'Yours Truly',
date: 'date of publication',
slides: [
{
title: 'Wake up to WonderWidgets!',
type: 'all'
},
{
items: [
'Why <em>WonderWidgets</em> are great',
'Who <em>buys</em> WonderWidgets'
],
title: 'Overview',
type: 'all'
}
],
title: 'Sample Slide Show'
});
}
},
{
description: 'should serialize FormData instance correctly when it contains null or undefined value #300',
expected: 'resolved: {"status":200, ...',
before: helpers.setMultipartSerializer,
func: function (resolve, reject) {
var ponyfills = cordova.plugin.http.ponyfills;
var formData = new ponyfills.FormData();
formData.append('myNullValue', null);
formData.append('myUndefinedValue', undefined);
// 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');
// }
// }
var url = 'https://httpbin.org/anything';
var options = { method: 'post', data: formData };
cordova.plugin.http.sendRequest(url, options, resolve, reject);
},
validationFunc: function (driver, result) {
helpers.checkResult(result, 'resolved');
result.data.status.should.be.equal(200);
JSON.parse(result.data.data).form.should.be.eql({
myNullValue: 'null',
myUndefinedValue: 'undefined'
});
}
},
{
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');
}
},
{
description: 'should not send any cookies after running "clearCookies" (GET) #248',
expected: 'resolved: {"status": 200, "data": "{\"cookies\":{}} ...',
before: helpers.disableFollowingRedirect,
func: function (resolve, reject) {
cordova.plugin.http.get('https://httpbin.org/cookies/set?myCookieKey=myCookieValue', {}, {}, function () {
cordova.plugin.http.clearCookies();
cordova.plugin.http.get('https://httpbin.org/cookies', {}, {}, resolve, reject);
}, function () {
cordova.plugin.http.clearCookies();
cordova.plugin.http.get('https://httpbin.org/cookies', {}, {}, resolve, reject);
});
},
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.status.should.be.equal(200);
JSON.parse(result.data.data).cookies.should.be.eql({});
}
},
{
description: 'should be able to abort (POST)',
expected: 'rejected: {"status":-8, "error": "Request ...}',
before: helpers.setRawSerializer,
func: function (resolve, reject, skip) {
if (!helpers.isAbortSupported()) {
skip();
return;
}
helpers.getWithXhr(function (buffer) {
var reqId = cordova.plugin.http.post('http://httpbin.org/anything', buffer, {}, resolve, reject);
setTimeout(function () {
cordova.plugin.http.abort(reqId);
}, helpers.getAbortDelay());
}, './res/cordova_logo.png', 'arraybuffer');
},
validationFunc: function (driver, result) {
helpers.checkResult(result, 'rejected');
result.data.status.should.be.equal(-8);
}
},
{
description: 'should be able to abort (GET)',
expected: 'rejected: {"status":-8, "error": "Request ...}',
func: function (resolve, reject, skip) {
if (!helpers.isAbortSupported()) {
skip();
return;
}
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
});
};
var reqId = cordova.plugin.http.sendRequest(url, options, success, reject);
setTimeout(function () {
cordova.plugin.http.abort(reqId);
}, helpers.getAbortDelay());
},
validationFunc: function (driver, result) {
helpers.checkResult(result, 'rejected');
result.data.status.should.be.equal(-8);
}
},
{
description: 'should be able to abort downloading a file',
expected: 'rejected: {"status":-8, "error": "Request ...}',
func: function (resolve, reject, skip) {
if (!helpers.isAbortSupported()) {
skip();
return;
}
var sourceUrl = 'http://httpbin.org/xml';
var targetPath = cordova.file.cacheDirectory + 'test.xml';
var reqId = cordova.plugin.http.downloadFile(sourceUrl, {}, {}, targetPath, function (entry) {
helpers.getWithXhr(function (content) {
resolve({
sourceUrl: sourceUrl,
targetPath: targetPath,
fullPath: entry.fullPath,
name: entry.name,
content: content
});
}, targetPath);
}, reject);
setTimeout(function () {
cordova.plugin.http.abort(reqId);
}, helpers.getAbortDelay());
},
validationFunc: function (driver, result) {
helpers.checkResult(result, 'rejected');
result.data.status.should.be.equal(-8);
}
},
{
description: 'should be able to abort uploading a file',
expected: 'rejected: {"status":-8, "error": "Request ...}',
func: function (resolve, reject, skip) {
if (!helpers.isAbortSupported()) {
skip();
return;
}
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 () {
var reqId = cordova.plugin.http.uploadFile(targetUrl, {}, {}, sourcePath, fileName, resolve, reject);
setTimeout(function () {
cordova.plugin.http.abort(reqId);
}, helpers.getAbortDelay());
}, fileName, fileContent);
},
validationFunc: function (driver, result) {
helpers.checkResult(result, 'rejected');
result.data.status.should.be.equal(-8);
}
},
];
if (typeof module !== 'undefined' && module.exports) {

View File

@@ -67,8 +67,8 @@ const configs = {
app: 'HttpTestAppAndroid'
},
browserstackAndroidDevice: {
device: 'Google Nexus 9',
os_version: '5.1',
device: 'Google Nexus 6',
os_version: '5.0',
project: 'HTTP Test App',
autoWebview: true,
app: 'HttpTestAppAndroid'

View File

@@ -1,13 +1,10 @@
const wd = require('wd');
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
const logging = require('./logging');
const capsConfig = require('./caps');
const serverConfig = require('./server');
const testDefinitions = require('../e2e-specs');
chai.use(chaiAsPromised);
chaiAsPromised.transferPromiseness = wd.transferPromiseness;
global.should = chai.should();
describe('Advanced HTTP e2e test suite', function () {
@@ -52,12 +49,19 @@ describe('Advanced HTTP e2e test suite', function () {
});
const defineTestForMocha = (test, index) => {
it(index + ': ' + test.description, async () => {
it(index + ': ' + test.description, async function () {
await clickNext(driver);
await validateTestIndex(driver, index);
await validateTestTitle(driver, test.description);
await waitToBeFinished(driver, test.timeout || 10000);
await validateResult(driver, test.validationFunc, targetInfo);
const skipped = await checkSkipped(driver);
if (skipped) {
this.skip();
} else {
await validateResult(driver, test.validationFunc, targetInfo);
}
});
};
@@ -120,6 +124,11 @@ async function validateResult(driver, validationFunc, targetInfo) {
validationFunc(driver, result, targetInfo);
}
async function checkSkipped(driver) {
const result = await driver.safeExecute('app.lastResult');
return result.type === 'skipped';
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

View File

@@ -1,5 +1,4 @@
const chai = require('chai');
const mock = require('mock-require');
const util = require('util');
const should = chai.should();
@@ -50,6 +49,13 @@ describe('Advanced HTTP public interface', function () {
http.getHeaders('*').myKey.should.equal('myValue');
});
it('clears global headers correctly when value is undefined', () => {
http.setHeader('*', 'myKey', 'myValue');
http.setHeader('*', 'myKey', null);
should.equal(undefined, http.getHeaders('*').myKey);
Object.keys(http.getHeaders('*')).length.should.be.equal(0);
});
it('sets host headers correctly #24', () => {
http.setHeader('www.google.de', 'myKey', 'myValue');
http.getHeaders('www.google.de').myKey.should.equal('myValue');
@@ -419,7 +425,7 @@ describe('Common helpers', function () {
response => response.data.should.be.equal('fakeData')
);
handler({ data: 'fakeData' });
handler({ data: 'fakeData' });
});
it('does not change response data if response type is "text"', () => {
@@ -444,6 +450,17 @@ describe('Common helpers', function () {
handler({ data: JSON.stringify(fakeData) });
});
it('handles empty "json" response correctly', () => {
const emptyData = "";
const helpers = require('../www/helpers')(null, jsUtil, null, messages, null, errorCodes);
const handler = helpers.injectRawResponseHandler(
'json',
response => should.equal(undefined, response.data)
);
handler({ data: emptyData });
});
it('handles response type "arraybuffer" correctly', () => {
const helpers = require('../www/helpers')(null, jsUtil, null, messages, fakeBase64, errorCodes);
const handler = helpers.injectRawResponseHandler(
@@ -454,6 +471,16 @@ describe('Common helpers', function () {
handler({ data: 'myString' });
});
it('handles empty "arraybuffer" response correctly', () => {
const helpers = require('../www/helpers')(null, jsUtil, null, messages, fakeBase64, errorCodes);
const handler = helpers.injectRawResponseHandler(
'arraybuffer',
response => should.equal(null, response.data)
);
handler({ data: '' });
});
it('handles response type "blob" correctly', () => {
const helpers = require('../www/helpers')(null, jsUtil, null, messages, fakeBase64, errorCodes);
const handler = helpers.injectRawResponseHandler(
@@ -465,7 +492,19 @@ describe('Common helpers', function () {
}
);
handler({ data: 'myString', headers: { 'content-type': 'fakeType'} });
handler({ data: 'myString', headers: { 'content-type': 'fakeType' } });
});
it('handles empty "blob" response correctly', () => {
const helpers = require('../www/helpers')(null, jsUtil, null, messages, fakeBase64, errorCodes);
const handler = helpers.injectRawResponseHandler(
'blob',
(response) => {
should.equal(null, response.data)
}
);
handler({ data: '', headers: { 'content-type': 'fakeType' } });
});
it('calls failure callback when post-processing fails', () => {
@@ -546,7 +585,7 @@ describe('Common helpers', function () {
it('processes data correctly when serializer "utf8" is configured', (cb) => {
helpers.processData('myString', 'utf8', (data) => {
data.should.be.eql({text: 'myString'});
data.should.be.eql({ text: 'myString' });
cb();
})
});
@@ -590,8 +629,46 @@ describe('Common helpers', function () {
});
});
it('processes data correctly when serializer "multipart" is configured and form data contains file value (filename set)', (cb) => {
const formData = new FormDataMock();
formData.append('myFile', new BlobMock([testString], { type: 'application/octet-stream' }), 'file.name');
helpers.processData(formData, 'multipart', (data) => {
data.buffers.length.should.be.equal(1);
data.names.length.should.be.equal(1);
data.fileNames.length.should.be.equal(1);
data.types.length.should.be.equal(1);
data.buffers[0].should.be.eql(testStringBase64);
data.names[0].should.be.equal('myFile');
data.fileNames[0].should.be.equal('file.name');
data.types[0].should.be.equal('application/octet-stream');
cb();
});
});
it('processes data correctly when serializer "multipart" is configured and form data contains file value (filename empty)', (cb) => {
const formData = new FormDataMock();
formData.append('myFile', new BlobMock([testString], { type: 'application/octet-stream' }), '');
helpers.processData(formData, 'multipart', (data) => {
data.buffers.length.should.be.equal(1);
data.names.length.should.be.equal(1);
data.fileNames.length.should.be.equal(1);
data.types.length.should.be.equal(1);
data.buffers[0].should.be.eql(testStringBase64);
data.names[0].should.be.equal('myFile');
data.fileNames[0].should.be.equal('');
data.types[0].should.be.equal('application/octet-stream');
cb();
});
});
it('processes data correctly when serializer "raw" is configured', (cb) => {
const byteArray = new Uint8Array([1,2,3]);
const byteArray = new Uint8Array([1, 2, 3]);
helpers.processData(byteArray, 'raw', (data) => {
data.should.be.a('ArrayBuffer');
data.should.be.equal(byteArray.buffer);
@@ -599,6 +676,20 @@ describe('Common helpers', function () {
})
});
});
describe('nextRequestId()', function () {
const helpers = require('../www/helpers')(null, null, null, null, null, null);
it('returns number requestIds', () => {
helpers.nextRequestId().should.be.a('number');
});
it('returns unique requestIds', () => {
const ids = [helpers.nextRequestId(), helpers.nextRequestId(), helpers.nextRequestId()];
const set = new Set(ids);
ids.should.to.deep.equal(Array.from(set));
});
});
});
describe('Dependency Validator', function () {
@@ -647,7 +738,7 @@ describe('Dependency Validator', function () {
describe('checkFormDataInstance()', function () {
it('throws an error if FormData.entries() is not supported on given instance', function () {
const console = new ConsoleMock();
const validator = require('../www/dependency-validator')({ FormData: {}}, console, messages);
const validator = require('../www/dependency-validator')({ FormData: {} }, console, messages);
(() => validator.checkFormDataInstance({})).should.throw(messages.MISSING_FORMDATA_ENTRIES_API);
});
@@ -684,12 +775,12 @@ describe('Ponyfills', function () {
const iterator = new ponyfills.Iterator([]);
iterator.next().should.be.eql({ done: true, value: undefined });
});
it('returns iteration object correctly when end posititon of list is not reached yet', () => {
const iterator = new ponyfills.Iterator([['first', 'this is the first item']]);
iterator.next().should.be.eql({ done: false, value: ['first', 'this is the first item'] });
});
it('returns iteration object correctly when end posititon of list is already reached', () => {
const iterator = new ponyfills.Iterator([['first', 'this is the first item']]);
iterator.next();

View File

@@ -3,7 +3,7 @@ const BlobMock = require('./Blob.mock');
module.exports = class FileMock extends BlobMock {
constructor(blob, fileName) {
super(blob, { type: blob.type });
this._fileName = fileName || '';
this._fileName = fileName !== undefined ? fileName : 'blob';
this.__lastModifiedDate = new Date();
}

View File

@@ -6,4 +6,5 @@ module.exports = {
UNSUPPORTED_URL: -5,
NOT_CONNECTED: -6,
POST_PROCESSING_FAILED: -7,
ABORTED: -8,
};

View File

@@ -5,6 +5,13 @@ module.exports = function init(global, jsUtil, cookieHandler, messages, base64,
var validHttpMethods = ['get', 'put', 'post', 'patch', 'head', 'delete', 'options', 'upload', 'download'];
var validResponseTypes = ['text', 'json', 'arraybuffer', 'blob'];
var nextRequestId = (function(){
var currReqId = 0;
return function nextRequestId() {
return ++currReqId;
}
})();
var interface = {
b64EncodeUnicode: b64EncodeUnicode,
checkClientAuthMode: checkClientAuthMode,
@@ -24,6 +31,7 @@ module.exports = function init(global, jsUtil, cookieHandler, messages, base64,
injectCookieHandler: injectCookieHandler,
injectFileEntryHandler: injectFileEntryHandler,
injectRawResponseHandler: injectRawResponseHandler,
nextRequestId: nextRequestId,
};
// expose all functions for testing purposes
@@ -187,7 +195,9 @@ module.exports = function init(global, jsUtil, cookieHandler, messages, base64,
}
function checkForInvalidHeaderValue(value) {
if (jsUtil.getTypeOf(value) !== 'String') {
var type = jsUtil.getTypeOf(value);
if (type !== 'String' && type !== 'Null') {
throw new Error(messages.INVALID_HEADER_VALUE);
}
@@ -295,20 +305,28 @@ module.exports = function init(global, jsUtil, cookieHandler, messages, base64,
try {
// json
if (responseType === validResponseTypes[1]) {
response.data = JSON.parse(response.data);
response.data = response.data === ''
? undefined
: JSON.parse(response.data);
}
// arraybuffer
else if (responseType === validResponseTypes[2]) {
response.data = base64.toArrayBuffer(response.data);
response.data = response.data === ''
? null
: 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;
if (response.data === '') {
response.data = null;
} else {
var buffer = base64.toArrayBuffer(response.data);
var type = response.headers['content-type'] || '';
var blob = new Blob([buffer], { type: type });
response.data = blob;
}
}
success(response);
@@ -384,7 +402,7 @@ module.exports = function init(global, jsUtil, cookieHandler, messages, base64,
if (allowedInstanceTypes) {
var isCorrectInstanceType = false;
allowedInstanceTypes.forEach(function(type) {
allowedInstanceTypes.forEach(function (type) {
if ((global[type] && data instanceof global[type]) || (ponyfills[type] && data instanceof ponyfills[type])) {
isCorrectInstanceType = true;
}
@@ -440,10 +458,10 @@ module.exports = function init(global, jsUtil, cookieHandler, messages, base64,
if (entry.value[1] instanceof global.Blob || entry.value[1] instanceof global.File) {
var reader = new global.FileReader();
reader.onload = function() {
reader.onload = function () {
result.buffers.push(base64.fromArrayBuffer(reader.result));
result.names.push(entry.value[0]);
result.fileNames.push(entry.value[1].name || 'blob');
result.fileNames.push(entry.value[1].name !== undefined ? entry.value[1].name : 'blob');
result.types.push(entry.value[1].type || '');
processFormDataIterator(iterator, textEncoder, result, onFinished);
};

View File

@@ -11,7 +11,7 @@ module.exports = {
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_HEADER_VALUE: 'advanced-http: invalid header value, needs to be a string or null, <header: string | null>',
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:',

View File

@@ -16,9 +16,9 @@ module.exports = function init(global) {
} else if (global.Blob && value instanceof global.Blob) {
// mimic File instance by adding missing properties
value.lastModifiedDate = new Date();
value.name = filename || '';
value.name = filename !== undefined ? filename : 'blob';
} else {
value = value.toString ? value.toString() : value;
value = String(value);
}
this.__items.push([ name, value ]);
@@ -44,4 +44,4 @@ module.exports = function init(global) {
}
return interface;
};
};

View File

@@ -14,10 +14,6 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf
setRequestTimeout: setRequestTimeout,
getFollowRedirect: getFollowRedirect,
setFollowRedirect: setFollowRedirect,
// @DEPRECATED
disableRedirect: disableRedirect,
// @DEPRECATED
setSSLCertMode: setServerTrustMode,
setServerTrustMode: setServerTrustMode,
setClientAuthMode: setClientAuthMode,
sendRequest: sendRequest,
@@ -30,6 +26,7 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf
options: options,
uploadFile: uploadFile,
downloadFile: downloadFile,
abort: abort,
ErrorCode: errorCodes,
ponyfills: ponyfills
};
@@ -62,7 +59,12 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf
helpers.checkForInvalidHeaderValue(value);
globalConfigs.headers[host] = globalConfigs.headers[host] || {};
globalConfigs.headers[host][header] = value;
if (value === null) {
delete globalConfigs.headers[host][header];
} else {
globalConfigs.headers[host][header] = value;
}
}
function getDataSerializer() {
@@ -105,14 +107,6 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf
globalConfigs.followRedirect = helpers.checkFollowRedirectValue(follow);
}
// @DEPRECATED
function disableRedirect(disable, success, failure) {
helpers.handleMissingCallbacks(success, failure);
setFollowRedirect(!disable);
success();
}
function setServerTrustMode(mode, success, failure) {
helpers.handleMissingCallbacks(success, failure);
@@ -150,23 +144,31 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf
var onFail = helpers.injectCookieHandler(url, failure);
var onSuccess = helpers.injectCookieHandler(url, helpers.injectRawResponseHandler(options.responseType, success, failure));
var reqId = helpers.nextRequestId();
switch (options.method) {
case 'post':
case 'put':
case 'patch':
return helpers.processData(options.data, options.serializer, function(data) {
exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [url, data, options.serializer, headers, options.timeout, options.followRedirect, options.responseType]);
helpers.processData(options.data, options.serializer, function (data) {
exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [url, data, options.serializer, headers, options.timeout, options.followRedirect, options.responseType, reqId]);
});
break;
case 'upload':
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]);
exec(onSuccess, onFail, 'CordovaHttpPlugin', 'uploadFiles', [url, headers, fileOptions.filePaths, fileOptions.names, options.timeout, options.followRedirect, options.responseType, reqId]);
break;
case 'download':
var filePath = helpers.checkDownloadFilePath(options.filePath);
var onDownloadSuccess = helpers.injectCookieHandler(url, helpers.injectFileEntryHandler(success));
return exec(onDownloadSuccess, onFail, 'CordovaHttpPlugin', 'downloadFile', [url, headers, filePath, options.timeout, options.followRedirect]);
exec(onDownloadSuccess, onFail, 'CordovaHttpPlugin', 'downloadFile', [url, headers, filePath, options.timeout, options.followRedirect, reqId]);
break;
default:
return exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [url, headers, options.timeout, options.followRedirect, options.responseType]);
exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [url, headers, options.timeout, options.followRedirect, options.responseType, reqId]);
break;
}
return reqId;
}
function post(url, data, headers, success, failure) {
@@ -205,5 +207,9 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf
return publicInterface.sendRequest(url, { method: 'download', params: params, headers: headers, filePath: filePath }, success, failure);
}
function abort(requestId , success, failure) {
return exec(success, failure, 'CordovaHttpPlugin', 'abort', [requestId]);
}
return publicInterface;
}