Compare commits

...

29 Commits

Author SHA1 Message Date
Sefa Ilkimen
516aa6b61d release v2.0.0 2018-09-03 16:03:46 +02:00
Sefa Ilkimen
a6bf9041a5 add missing "updatecert" task to travis config 2018-09-03 15:25:11 +02:00
Sefa Ilkimen
c41fc11888 SSL certs must be places in "www/certificates"; project root folder is not scanned for certs anymore 2018-09-03 15:02:26 +02:00
Sefa Ilkimen
91515d30bd add test for gzipped content 2018-09-03 14:18:22 +02:00
Sefa Ilkimen
c8638ff204 exclude test certs from repository 2018-09-03 14:17:22 +02:00
Sefa Ilkimen
0acf2e2574 fix test #34 "should pin SSL cert correctly (GET)" 2018-09-03 13:52:09 +02:00
Sefa Ilkimen
f4674028b5 Merge pull request #118 from 0verrfl0w/ios-networking-indicator
Ios networking indicator
2018-07-23 15:18:45 +02:00
Florian Leitmann
5919d90698 Merge remote-tracking branch 'upstream/master' into ios-networking-indicator 2018-05-31 17:29:12 +02:00
Sefa Ilkimen
27e959800a remove deprecated AngularJS integration service 2018-05-27 20:33:02 +02:00
Sefa Ilkimen
96f45d7274 feature #103: implement HTTP SSL cert modes 2018-05-27 19:30:42 +02:00
Florian Leitmann
3dbd07c9df Merge branch 'master' into ios-networking-indicator 2018-05-27 14:01:04 +02:00
Florian Leitmann
cb597c0d30 Added SDNetworkActivityIndicator library and integrated it in plugin.xml definition as well as CordovaHttpPlugin.m
This library allows to show the network activity indicator on iOS devices, to indicate native networking
It uses a counter to hide the indicator depending on whether all request have been fulfilled or not
2018-05-27 12:45:52 +02:00
Sefa Ilkimen
60189a68b3 update httpbin.org SSL cert 2018-05-25 18:58:52 +02:00
Sefa Ilkimen
8e4bfdbc70 remove explicit host name verification setting on Android 2018-05-25 18:45:10 +02:00
Sefa Ilkimen
2ae4c7cf39 refactor cert handling for Android (preparing for v2 changes) 2018-04-11 03:24:27 +02:00
Sefa Ilkimen
32fdf49d31 fix misleading examples in README 2018-04-08 23:59:11 +02:00
Sefa Ilkimen
db0f233737 update README (missing url property in callback signature) 2018-03-21 22:35:31 +01:00
Sefa Ilkimen
2263950de9 release v1.11.1 2018-03-21 13:18:57 +01:00
Sefa Ilkimen
2a9d3296cc update httpbin.org SSL cert 2018-03-21 12:56:11 +01:00
Sefa Ilkimen
55dd751cec fix #92: headers not deserialized on platform "browser" 2018-03-16 03:04:03 +01:00
Sefa Ilkimen
af239d3194 release v1.11.0 2018-03-02 01:09:23 +01:00
Sefa Ilkimen
291e8fe547 browser: fix content type header 2018-03-02 01:00:22 +01:00
Sefa Ilkimen
8b3466e3c3 - update readme
- update version number
2018-03-02 00:46:01 +01:00
Sefa Ilkimen
73e76f6333 feature #11: implement support for "browser" platform 2018-03-01 23:45:54 +01:00
Sefa Ilkimen
3fcac64597 update OkHttp dependency 2018-03-01 16:16:02 +01:00
Sefa Ilkimen
4a437f9435 fix docu for "disableRedirect" 2018-02-28 04:08:32 +01:00
Sefa Ilkimen
2d15b86cb5 add documentation for feature #77 2018-02-28 04:05:13 +01:00
Sefa Ilkimen
63d859ad54 - implement all HTTP operations as shorthand functions of "sendRequest"
- add some more type checking
2018-02-28 03:24:27 +01:00
Sefa Ilkimen
6b9ed72b9f WIP: new API which allows overriding global options 2018-02-28 01:31:35 +01:00
30 changed files with 998 additions and 661 deletions

1
.gitignore vendored
View File

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

View File

@@ -30,6 +30,7 @@ install:
script:
- npm run testjs
- npm run updatecert
- travis_wait scripts/build-test-app.sh --$TARGET_PLATFORM --emulator &&
scripts/upload-artifact.sh --$TARGET_PLATFORM &&
scripts/test-app.sh --$TARGET_PLATFORM --emulator;

View File

@@ -1,5 +1,22 @@
# Changelog
## 2.0.0
- Feature #103: implement HTTP SSL cert modes
- :warning: **Breaking Change**: Removed AngularJS (v1) integration service
- :warning: **Breaking Change**: Removed "enableSSLPinning" and "acceptAllCerts", use "setSSLCertMode" instead
- :warning: **Breaking Change**: Certificates must be placed in "www/certificates" folder
## 1.11.1
- Fixed #92: headers not deserialized on platform "browser"
## 1.11.0
- Feature #77: allow overriding global settings for each single request
- Feature #11: add support for "browser" platform
## 1.10.2
- Fixed #78: overriding header "Content-Type" not working on Android

144
README.md
View File

@@ -6,7 +6,8 @@ Cordova Advanced HTTP
[![Build Status](https://travis-ci.org/silkimen/cordova-plugin-advanced-http.svg?branch=master)](https://travis-ci.org/silkimen/cordova-plugin-advanced-http)
Cordova / Phonegap plugin for communicating with HTTP servers. Supports iOS and Android.
Cordova / Phonegap plugin for communicating with HTTP servers. Supports iOS, Android and [Browser](#browserSupport).
This is a fork of [Wymsee's Cordova-HTTP plugin](https://github.com/wymsee/cordova-HTTP).
## Advantages over Javascript requests
@@ -40,19 +41,6 @@ This plugin registers a global object located at `cordova.plugin.http`.
Check the [Ionic docs](https://ionicframework.com/docs/native/http/) for how to use this plugin with Ionic-native.
### With AngularJS (Deprecated)
:warning: *This feature is deprecated and will be removed anytime soon.* :warning:
This plugin creates a cordovaHTTP service inside of a cordovaHTTP module. You must load the module when you create your app's module.
```js
var app = angular.module('myApp', ['ngRoute', 'ngAnimate', 'cordovaHTTP']);
```
You can then inject the cordovaHTTP service into your controllers. The functions can then be used identically to the examples shown below except that instead of accepting success and failure callback functions, each function returns a promise. For more information on promises in AngularJS read the [AngularJS docs](http://docs.angularjs.org/api/ng/service/$q). For more info on promises in general check out this article on [html5rocks](http://www.html5rocks.com/en/tutorials/es6/promises/). Make sure that you load cordova.js or phonegap.js after AngularJS is loaded.
## Synchronous Functions
### getBasicAuthHeader
@@ -93,14 +81,7 @@ cordova.plugin.http.setHeader('www.example.com', 'Header', 'Value');
cordova.plugin.http.setHeader('www.example.com:8080', 'Header', 'Value');
```
### disableRedirect
If set to `true`, it won't follow redirects automatically. This is a global setting.
```js
cordova.plugin.http.disableRedirect(true);
```
### setDataSerializer
### setDataSerializer<a name="setDataSerializer"></a>
Set the data serializer which will be used for all future PATCH, POST and PUT requests. Takes a string representing the name of the serializer.
```js
@@ -147,34 +128,59 @@ cordova.plugin.http.clearCookies();
## Asynchronous Functions
These functions all take success and error callbacks as their last 2 arguments.
### enableSSLPinning
Enable or disable SSL pinning. This defaults to false.
### setSSLCertMode<a name="setSSLCertMode"></a>
Set SSL Cert handling mode, being one of the following values:
To use SSL pinning you must include at least one .cer SSL certificate in your app project. You can pin to your server certificate or to one of the issuing CA certificates. For ios include your certificate in the root level of your bundle (just add the .cer file to your project/target at the root level). For android include your certificate in your project's platforms/android/assets folder. In both cases all .cer files found will be loaded automatically. If you only have a .pem certificate see this [stackoverflow answer](http://stackoverflow.com/a/16583429/3182729). You want to convert it to a DER encoded certificate with a .cer extension.
* `default`: default SSL cert handling using system's CA certs
* `nocheck`: disable SSL cert checking, trusting all certs (meant to be used only for testing purposes)
* `pinned`: trust only provided certs
As an alternative, you can store your .cer files in the www/certificates folder.
To use SSL pinning you must include at least one `.cer` SSL certificate in your app project. You can pin to your server certificate or to one of the issuing CA certificates. Include your certificate in the `www/certificates` folder. All `.cer` files found there will be loaded automatically.
:warning: Your certificate must be DER encoded! If you only have a PEM enoceded certificate see this [stackoverflow answer](http://stackoverflow.com/a/16583429/3182729). You want to convert it to a DER encoded certificate with a .cer extension.
```js
cordova.plugin.http.enableSSLPinning(true, function() {
// enable SSL pinning
cordova.plugin.http.setSSLCertMode('pinned', function() {
console.log('success!');
}, function() {
console.log('error :(');
});
// use system's default CA certs
cordova.plugin.http.setSSLCertMode('default', function() {
console.log('success!');
}, function() {
console.log('error :(');
});
// disable SSL cert checking, only meant for testing purposes, do NOT use in production!
cordova.plugin.http.setSSLCertMode('nocheck', function() {
console.log('success!');
}, function() {
console.log('error :(');
});
```
### acceptAllCerts
Accept all SSL certificates. Or disable accepting all certificates. This defaults to false.
### enableSSLPinning (obsolete)
This function was removed in 2.0.0. Use ["setSSLCertMode"](#setSSLCertMode) to enable SSL pinning (mode "pinned").
### acceptAllCerts (obsolete)
This function was removed in 2.0.0. Use ["setSSLCertMode"](#setSSLCertMode) to disable checking certs (mode "nocheck").
### disableRedirect
If set to `true`, it won't follow redirects automatically. This defaults to false.
```js
cordova.plugin.http.acceptAllCerts(true, function() {
cordova.plugin.http.disableRedirect(true, function() {
console.log('success!');
}, function() {
console.log('error :(');
});
```
### validateDomainName
This function was removed in v1.6.2. Domain name validation is disabled automatically when you enable "acceptAllCerts".
### validateDomainName (obsolete)
This function was removed in v1.6.2. Domain name validation is disabled automatically when you set SSL cert mode to "nocheck".
### removeCookies
Remove all cookies associated with a given URL.
@@ -183,11 +189,47 @@ Remove all cookies associated with a given URL.
cordova.plugin.http.removeCookies(url, callback);
```
### sendRequest
Execute a HTTP request. Takes a URL and an options object. This is the internally used implementation of the following shorthand functions ([post](#post), [get](#get), [put](#put), [patch](#patch), [delete](#delete), [head](#head), [uploadFile](#uploadFile) and [downloadFile](#downloadFile)). You can use this function, if you want to override global settings for each single request.
The options object contains following keys:
* `method`: HTTP method to be used, defaults to `get`, needs to be one of the following values:
* `get`, `post`, `put`, `patch`, `head`, `delete`, `upload`, `download`
* `data`: payload to be send to the server (only applicable on `post`, `put` or `patch` methods)
* `params`: query params to be appended to the URL (only applicable on `get`, `head`, `delete`, `upload` or `download` methods)
* `serializer`: data serializer to be used (only applicable on `post`, `put` or `patch` methods), defaults to global serializer value, see [setDataSerializer](#setDataSerializer) for supported values
* `timeout`: timeout value for the request in seconds, defaults to global timeout value
* `headers`: headers object (key value pair), will be merged with global values
* `filePath`: filePath to be used during upload and download see [uploadFile](#uploadFile) and [downloadFile](#downloadFile) for detailed information
* `name`: name to be used during upload see [uploadFile](#uploadFile) for detailed information
Here's a quick example:
```js
const options = {
method: 'post',
data: { id: 12, message: 'test' },
headers: { Authorization: 'OAuth2: token' }
};
cordova.plugin.http.sendRequest('https://google.com/', options, function(response) {
// prints 200
console.log(response.status);
}, function(response) {
// prints 403
console.log(response.status);
//prints Permission denied
console.log(response.error);
});
```
### post<a name="post"></a>
Execute a POST request. Takes a URL, data, and headers.
#### success
The success function receives a response object with 3 properties: status, data, and headers. **status** is the HTTP response code as numeric value. **data** is the response from the server as a string. **headers** is an object with the headers. The keys of the returned object are the header names and the values are the respective header values. All header names are lowercase.
The success function receives a response object with 4 properties: status, data, url, and headers. **status** is the HTTP response code as numeric value. **data** is the response from the server as a string. **url** is the final URL obtained after any redirects as a string. **headers** is an object with the headers. The keys of the returned object are the header names and the values are the respective header values. All header names are lowercase.
Here's a quick example:
@@ -195,6 +237,7 @@ Here's a quick example:
{
status: 200,
data: '{"id": 12, "message": "test"}',
url: 'http://example.net/rest'
headers: {
'content-length': '247'
}
@@ -227,7 +270,7 @@ cordova.plugin.http.post('https://google.com/', {
```
#### failure
The error function receives a response object with 3 properties: status, error and headers. **status** is the HTTP response code as numeric value. **error** is the error response from the server as a string. **headers** is an object with the headers. The keys of the returned object are the header names and the values are the respective header values. All header names are lowercase.
The error function receives a response object with 4 properties: status, error, url, and headers (url and headers being optional). **status** is the HTTP response code as numeric value. **error** is the error response from the server as a string. **url** is the final URL obtained after any redirects as a string. **headers** is an object with the headers. The keys of the returned object are the header names and the values are the respective header values. All header names are lowercase.
Here's a quick example:
@@ -235,18 +278,19 @@ Here's a quick example:
{
status: 403,
error: 'Permission denied',
url: 'http://example.net/noperm'
headers: {
'content-length': '247'
}
}
```
### get
### get<a name="get"></a>
Execute a GET request. Takes a URL, parameters, and headers. See the [post](#post) documentation for details on what is returned on success and failure.
```js
cordova.plugin.http.get('https://google.com/', {
id: 12,
id: '12',
message: 'test'
}, { Authorization: 'OAuth2: token' }, function(response) {
console.log(response.status);
@@ -255,24 +299,24 @@ cordova.plugin.http.get('https://google.com/', {
});
```
### put
### put<a name="put"></a>
Execute a PUT request. Takes a URL, data, and headers. See the [post](#post) documentation for details on what is returned on success and failure.
### patch
### patch<a name="patch"></a>
Execute a PATCH request. Takes a URL, data, and headers. See the [post](#post) documentation for details on what is returned on success and failure.
### delete
### delete<a name="delete"></a>
Execute a DELETE request. Takes a URL, parameters, and headers. See the [post](#post) documentation for details on what is returned on success and failure.
### head
### head<a name="head"></a>
Execute a HEAD request. Takes a URL, parameters, and headers. See the [post](#post) documentation for details on what is returned on success and failure.
### uploadFile
### uploadFile<a name="uploadFile"></a>
Uploads a file saved on the device. Takes a URL, parameters, headers, filePath, and the name of the parameter to pass the file along as. See the [post](#post) documentation for details on what is returned on success and failure.
```js
cordova.plugin.http.uploadFile("https://google.com/", {
id: 12,
id: '12',
message: 'test'
}, { Authorization: 'OAuth2: token' }, 'file:///somepicture.jpg', 'picture', function(response) {
console.log(response.status);
@@ -281,12 +325,12 @@ cordova.plugin.http.uploadFile("https://google.com/", {
});
```
### downloadFile
### downloadFile<a name="downloadFile"></a>
Downloads a file and saves it to the device. Takes a URL, parameters, headers, and a filePath. See [post](#post) documentation for details on what is returned on failure. On success this function returns a cordova [FileEntry object](http://cordova.apache.org/docs/en/3.3.0/cordova_file_file.md.html#FileEntry).
```js
cordova.plugin.http.downloadFile("https://google.com/", {
id: 12,
id: '12',
message: 'test'
}, { Authorization: 'OAuth2: token' }, 'file:///somepicture.jpg', function(entry) {
// prints the filename
@@ -299,6 +343,18 @@ cordova.plugin.http.downloadFile("https://google.com/", {
});
```
## Browser support<a name="browserSupport"></a>
This plugin supports a very restricted set of functions on the browser platform.
It's meant for testing purposes, not for production grade usage.
Following features are *not* supported:
* Manipulating Cookies
* Uploading and Downloading files
* Pinning SSL certificate
* Disabling SSL certificate check
* Disabling transparently following redirects (HTTP codes 3xx)
## Libraries

View File

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

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="1.10.2">
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-advanced-http" version="2.0.0">
<name>Advanced HTTP plugin</name>
<description>
Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning
@@ -13,7 +13,6 @@
<js-module src="www/messages.js" name="messages"/>
<js-module src="www/local-storage-store.js" name="local-storage-store"/>
<js-module src="www/cookie-handler.js" name="cookie-handler"/>
<js-module src="www/angular-integration.js" name="angular-integration"/>
<js-module src="www/helpers.js" name="helpers"/>
<js-module src="www/advanced-http.js" name="http">
<clobbers target="cordova.plugin.http"/>
@@ -34,6 +33,7 @@
<header-file src="src/ios/AFNetworking/AFURLRequestSerialization.h"/>
<header-file src="src/ios/AFNetworking/AFURLResponseSerialization.h"/>
<header-file src="src/ios/AFNetworking/AFURLSessionManager.h"/>
<header-file src="src/ios/SDNetworkActivityIndicator/SDNetworkActivityIndicator.h"/>
<source-file src="src/ios/CordovaHttpPlugin.m"/>
<source-file src="src/ios/TextResponseSerializer.m"/>
<source-file src="src/ios/TextRequestSerializer.m"/>
@@ -43,6 +43,7 @@
<source-file src="src/ios/AFNetworking/AFURLRequestSerialization.m"/>
<source-file src="src/ios/AFNetworking/AFURLResponseSerialization.m"/>
<source-file src="src/ios/AFNetworking/AFURLSessionManager.m"/>
<source-file src="src/ios/SDNetworkActivityIndicator/SDNetworkActivityIndicator.m"/>
<framework src="Security.framework"/>
<framework src="SystemConfiguration.framework"/>
</platform>
@@ -68,6 +69,16 @@
<source-file src="src/android/com/synconset/cordovahttp/CordovaHttpPut.java" target-dir="src/com/synconset/cordovahttp"/>
<source-file src="src/android/com/synconset/cordovahttp/CordovaHttpPatch.java" target-dir="src/com/synconset/cordovahttp"/>
<source-file src="src/android/com/synconset/cordovahttp/CordovaHttpUpload.java" target-dir="src/com/synconset/cordovahttp"/>
<framework src="com.squareup.okhttp3:okhttp-urlconnection:3.9.1"/>
<framework src="com.squareup.okhttp3:okhttp-urlconnection:3.10.0"/>
</platform>
<platform name="browser">
<config-file target="config.xml" parent="/*">
<feature name="CordovaHttpPlugin">
<param name="browser-package" value="CordovaHttpPlugin"/>
</feature>
</config-file>
<js-module src="src/browser/cordova-http-plugin.js" name="http-proxy">
<runs/>
</js-module>
</platform>
</plugin>

View File

@@ -1,11 +1,39 @@
#!/usr/bin/env bash
set -e
PLATFORM=$([[ "${@#--android}" = "$@" ]] && echo "ios" || echo "android")
TARGET=$([[ "${@#--device}" = "$@" ]] && echo "emulator" || echo "device")
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"/..
CDV=$ROOT/node_modules/.bin/cordova
PLATFORM=ios
TARGET=emulator
while :; do
case $1 in
--android)
PLATFORM=android
;;
--browser)
PLATFORM=browser
;;
--ios)
PLATFORM=ios
;;
--device)
TARGET=device
;;
--emulator)
TARGET=emulator
;;
-?*)
printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2
;;
*)
break
esac
shift
done
rm -rf $ROOT/temp
mkdir $ROOT/temp
cp -r $ROOT/test/app-template/ $ROOT/temp/

View File

@@ -0,0 +1,42 @@
const fs = require('fs');
const https = require('https');
const path = require('path');
const SOURCE_HOST = 'httpbin.org';
const TARGET_PATH = path.join(__dirname, '../test/app-template/www/certificates/httpbin.org.cer');
const getCert = hostname => new Promise((resolve, reject) => {
const options = {
hostname,
agent: false,
rejectUnauthorized: false,
ciphers: 'ALL'
};
const req = https.get(options, response => {
const certificate = response.socket.getPeerCertificate();
if (certificate === null) {
return reject({ message: 'The website did not provide a certificate' });
}
resolve(certificate);
});
req.on('error', error => {
return reject(error)
});
req.end();
});
console.log(`Updating test certificate from ${SOURCE_HOST}`);
getCert(SOURCE_HOST)
.then(cert => {
fs.writeFileSync(TARGET_PATH, cert.raw);
})
.catch(error => {
console.error(`Updating test cert failed: ${error}`);
process.exit(1);
});

View File

@@ -263,6 +263,12 @@ public class HttpRequest {
*/
public static final String PARAM_CHARSET = "charset";
public static final String CERT_MODE_DEFAULT = "default";
public static final String CERT_MODE_PINNED = "pinned";
public static final String CERT_MODE_TRUSTALL = "trustall";
private static final String BOUNDARY = "00content0boundary00";
private static final String CONTENT_TYPE_MULTIPART = "multipart/form-data; boundary="
@@ -272,13 +278,13 @@ public class HttpRequest {
private static final String[] EMPTY_STRINGS = new String[0];
private static SSLSocketFactory PINNED_FACTORY;
private static SSLSocketFactory SOCKET_FACTORY;
private static SSLSocketFactory TRUSTED_FACTORY;
private static String CURRENT_CERT_MODE = CERT_MODE_DEFAULT;
private static ArrayList<Certificate> PINNED_CERTS;
private static HostnameVerifier TRUSTED_VERIFIER;
private static HostnameVerifier HOSTNAME_VERIFIER;
private static String getValidCharset(final String charset) {
if (charset != null && charset.length() > 0)
@@ -287,63 +293,97 @@ public class HttpRequest {
return CHARSET_UTF8;
}
private static SSLSocketFactory getPinnedFactory()
throws HttpRequestException {
if (PINNED_FACTORY != null) {
return PINNED_FACTORY;
} else {
IOException e = new IOException("You must add at least 1 certificate in order to pin to certificates");
throw new HttpRequestException(e);
/**
* Configure SSL cert handling for all future HTTPS connections
*
* @param mode
*/
public static void setSSLCertMode(String mode) {
try {
if (mode == CERT_MODE_TRUSTALL) {
SOCKET_FACTORY = createSocketFactory(getNoopTrustManagers());
HOSTNAME_VERIFIER = getTrustedVerifier();
} else if (mode == CERT_MODE_PINNED) {
SOCKET_FACTORY = createSocketFactory(getPinnedTrustManagers());
HOSTNAME_VERIFIER = null;
} else {
SOCKET_FACTORY = null;
HOSTNAME_VERIFIER = null;
}
CURRENT_CERT_MODE = mode;
} catch(IOException e) {
throw new HttpRequestException(e);
}
}
private static SSLSocketFactory getTrustedFactory()
throws HttpRequestException {
if (TRUSTED_FACTORY == null) {
final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] chain, String authType) {
// Intentionally left blank
}
public void checkServerTrusted(X509Certificate[] chain, String authType) {
// Intentionally left blank
}
} };
try {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, trustAllCerts, new SecureRandom());
if (android.os.Build.VERSION.SDK_INT < 20) {
TRUSTED_FACTORY = new TLSSocketFactory(context);
} else {
TRUSTED_FACTORY = context.getSocketFactory();
}
} catch (GeneralSecurityException e) {
IOException ioException = new IOException(
"Security exception configuring SSL context");
ioException.initCause(e);
throw new HttpRequestException(ioException);
}
private static TrustManager[] getPinnedTrustManagers() throws IOException {
if (PINNED_CERTS == null) {
throw new IOException("You must add at least 1 certificate in order to pin to certificates");
}
return TRUSTED_FACTORY;
try {
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
for (int i = 0; i < PINNED_CERTS.size(); i++) {
keyStore.setCertificateEntry("CA" + i, PINNED_CERTS.get(i));
}
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
return tmf.getTrustManagers();
} catch (GeneralSecurityException e) {
IOException ioException = new IOException("Security exception configuring SSL trust managers");
ioException.initCause(e);
throw new HttpRequestException(ioException);
}
}
private static TrustManager[] getNoopTrustManagers() {
return new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] chain, String authType) {
// Intentionally left blank
}
public void checkServerTrusted(X509Certificate[] chain, String authType) {
// Intentionally left blank
}
}};
}
private static SSLSocketFactory createSocketFactory(TrustManager[] trustManagers)
throws HttpRequestException {
try {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, trustManagers, new SecureRandom());
if (android.os.Build.VERSION.SDK_INT < 20) {
return new TLSSocketFactory(context);
} else {
return context.getSocketFactory();
}
} catch (GeneralSecurityException e) {
IOException ioException = new IOException("Security exception configuring SSL context");
ioException.initCause(e);
throw new HttpRequestException(ioException);
}
}
private static HostnameVerifier getTrustedVerifier() {
if (TRUSTED_VERIFIER == null)
TRUSTED_VERIFIER = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
return TRUSTED_VERIFIER;
return new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
}
private static StringBuilder addPathSeparator(final String baseUrl,
@@ -453,32 +493,15 @@ public class HttpRequest {
* @throws IOException
*/
public static void addCert(Certificate ca) throws GeneralSecurityException, IOException {
if (PINNED_CERTS == null) {
PINNED_CERTS = new ArrayList<Certificate>();
}
PINNED_CERTS.add(ca);
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
if (PINNED_CERTS == null) {
PINNED_CERTS = new ArrayList<Certificate>();
}
for (int i = 0; i < PINNED_CERTS.size(); i++) {
keyStore.setCertificateEntry("CA" + i, PINNED_CERTS.get(i));
}
PINNED_CERTS.add(ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
// Create an SSLContext that uses our TrustManager
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
if (android.os.Build.VERSION.SDK_INT < 20) {
PINNED_FACTORY = new TLSSocketFactory(sslContext);
} else {
PINNED_FACTORY = sslContext.getSocketFactory();
}
if (CURRENT_CERT_MODE == CERT_MODE_PINNED) {
SOCKET_FACTORY = createSocketFactory(getPinnedTrustManagers());
}
}
/**
@@ -1632,6 +1655,7 @@ public class HttpRequest {
throw new HttpRequestException(e);
}
this.requestMethod = method;
this.setupSecurity();
}
/**
@@ -1645,6 +1669,23 @@ public class HttpRequest {
throws HttpRequestException {
this.url = url;
this.requestMethod = method;
this.setupSecurity();
}
private void setupSecurity() {
final HttpURLConnection connection = getConnection();
if (!(connection instanceof HttpsURLConnection)) {
return;
}
if (SOCKET_FACTORY != null) {
((HttpsURLConnection) connection).setSSLSocketFactory(SOCKET_FACTORY);
}
if (HOSTNAME_VERIFIER != null) {
((HttpsURLConnection) connection).setHostnameVerifier(HOSTNAME_VERIFIER);
}
}
private Proxy createProxy() {
@@ -3351,58 +3392,6 @@ public class HttpRequest {
return this;
}
/**
* Configure HTTPS connection to trust only certain certificates
* <p>
* This method throws an exception if the current request is not a HTTPS request
*
* @return this request
* @throws HttpRequestException
*/
public HttpRequest pinToCerts() throws HttpRequestException {
final HttpURLConnection connection = getConnection();
if (connection instanceof HttpsURLConnection) {
((HttpsURLConnection) connection).setSSLSocketFactory(getPinnedFactory());
} else {
IOException e = new IOException("You must use a https url to use ssl pinning");
throw new HttpRequestException(e);
}
return this;
}
/**
* Configure HTTPS connection to trust all certificates
* <p>
* This method does nothing if the current request is not a HTTPS request
*
* @return this request
* @throws HttpRequestException
*/
public HttpRequest trustAllCerts() throws HttpRequestException {
final HttpURLConnection connection = getConnection();
if (connection instanceof HttpsURLConnection)
((HttpsURLConnection) connection)
.setSSLSocketFactory(getTrustedFactory());
return this;
}
/**
* Configure HTTPS connection to trust all hosts using a custom
* {@link HostnameVerifier} that always returns <code>true</code> for each
* host verified
* <p>
* This method does nothing if the current request is not a HTTPS request
*
* @return this request
*/
public HttpRequest trustAllHosts() {
final HttpURLConnection connection = getConnection();
if (connection instanceof HttpsURLConnection)
((HttpsURLConnection) connection)
.setHostnameVerifier(getTrustedVerifier());
return this;
}
/**
* Get the {@link URL} of this request's connection
*

View File

@@ -38,10 +38,6 @@ import com.github.kevinsawicki.http.HttpRequest.HttpRequestException;
abstract class CordovaHttp {
protected static final String TAG = "CordovaHTTP";
protected static final String[] ACCEPTED_CHARSETS = new String[] { HttpRequest.CHARSET_UTF8, HttpRequest.CHARSET_LATIN1 };
private static AtomicBoolean sslPinning = new AtomicBoolean(false);
private static AtomicBoolean acceptAllCerts = new AtomicBoolean(false);
private static AtomicBoolean validateDomainName = new AtomicBoolean(true);
private static AtomicBoolean disableRedirect = new AtomicBoolean(false);
private String urlString;
@@ -64,24 +60,6 @@ abstract class CordovaHttp {
this.callbackContext = callbackContext;
}
public static void enableSSLPinning(boolean enable) {
sslPinning.set(enable);
if (enable) {
acceptAllCerts.set(false);
}
}
public static void acceptAllCerts(boolean accept) {
acceptAllCerts.set(accept);
if (accept) {
sslPinning.set(false);
}
}
public static void validateDomainName(boolean accept) {
validateDomainName.set(accept);
}
public static void disableRedirect(boolean disable) {
disableRedirect.set(disable);
}
@@ -122,20 +100,6 @@ abstract class CordovaHttp {
return this.callbackContext;
}
protected HttpRequest setupSecurity(HttpRequest request) {
if (acceptAllCerts.get()) {
request.trustAllCerts();
}
if (!validateDomainName.get()) {
request.trustAllHosts();
}
if (sslPinning.get()) {
request.pinToCerts();
}
return request;
}
protected HttpRequest setupRedirect(HttpRequest request) {
if (disableRedirect.get()) {
request.followRedirects(false);
@@ -222,7 +186,6 @@ abstract class CordovaHttp {
protected void prepareRequest(HttpRequest request) throws HttpRequestException, JSONException {
this.setupRedirect(request);
this.setupSecurity(request);
request.readTimeout(this.getRequestTimeout());
request.acceptCharset(ACCEPTED_CHARSETS);

View File

@@ -86,21 +86,25 @@ public class CordovaHttpPlugin extends CordovaPlugin {
CordovaHttpHead head = new CordovaHttpHead(urlString, params, headers, timeoutInMilliseconds, callbackContext);
cordova.getThreadPool().execute(head);
} else if (action.equals("enableSSLPinning")) {
try {
boolean enable = args.getBoolean(0);
this.enableSSLPinning(enable);
callbackContext.success();
} catch(Exception e) {
e.printStackTrace();
callbackContext.error("There was an error setting up ssl pinning");
}
} else if (action.equals("acceptAllCerts")) {
boolean accept = args.getBoolean(0);
} else if (action.equals("setSSLCertMode")) {
String mode = args.getString(0);
CordovaHttp.acceptAllCerts(accept);
CordovaHttp.validateDomainName(!accept);
callbackContext.success();
if (mode.equals("default")) {
HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_DEFAULT);
callbackContext.success();
} else if (mode.equals("nocheck")) {
HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_TRUSTALL);
callbackContext.success();
} else if (mode.equals("pinned")) {
try {
this.loadSSLCerts();
HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_PINNED);
callbackContext.success();
} catch(Exception e) {
e.printStackTrace();
callbackContext.error("There was an error setting up ssl pinning");
}
}
} else if (action.equals("uploadFile")) {
String urlString = args.getString(0);
Object params = args.get(1);
@@ -121,49 +125,33 @@ public class CordovaHttpPlugin extends CordovaPlugin {
cordova.getThreadPool().execute(download);
} else if (action.equals("disableRedirect")) {
boolean disable = args.getBoolean(0);
CordovaHttp.disableRedirect(disable);
callbackContext.success();
boolean disable = args.getBoolean(0);
CordovaHttp.disableRedirect(disable);
callbackContext.success();
} else {
return false;
}
return true;
}
private void enableSSLPinning(boolean enable) throws GeneralSecurityException, IOException {
if (enable) {
AssetManager assetManager = cordova.getActivity().getAssets();
String[] files = assetManager.list("");
int index;
ArrayList<String> cerFiles = new ArrayList<String>();
for (int i = 0; i < files.length; i++) {
index = files[i].lastIndexOf('.');
if (index != -1) {
if (files[i].substring(index).equals(".cer")) {
cerFiles.add(files[i]);
}
}
}
private void loadSSLCerts() throws GeneralSecurityException, IOException {
AssetManager assetManager = cordova.getActivity().getAssets();
String[] files = assetManager.list("www/certificates");
ArrayList<String> cerFiles = new ArrayList<String>();
// scan the www/certificates folder for .cer files as well
files = assetManager.list("www/certificates");
for (int i = 0; i < files.length; i++) {
index = files[i].lastIndexOf('.');
if (index != -1) {
if (files[i].substring(index).equals(".cer")) {
cerFiles.add("www/certificates/" + files[i]);
}
}
for (int i = 0; i < files.length; i++) {
int index = files[i].lastIndexOf('.');
if (index != -1) {
if (files[i].substring(index).equals(".cer")) {
cerFiles.add("www/certificates/" + files[i]);
}
}
}
for (int i = 0; i < cerFiles.size(); i++) {
InputStream in = cordova.getActivity().getAssets().open(cerFiles.get(i));
InputStream caInput = new BufferedInputStream(in);
HttpRequest.addCert(caInput);
}
CordovaHttp.enableSSLPinning(true);
} else {
CordovaHttp.enableSSLPinning(false);
for (int i = 0; i < cerFiles.size(); i++) {
InputStream in = cordova.getActivity().getAssets().open(cerFiles.get(i));
InputStream caInput = new BufferedInputStream(in);
HttpRequest.addCert(caInput);
}
}
}

181
src/browser/cordova-http-plugin.js vendored Normal file
View File

@@ -0,0 +1,181 @@
var pluginId = module.id.slice(0, module.id.lastIndexOf('.'));
var cordovaProxy = require('cordova/exec/proxy');
var helpers = require(pluginId + '.helpers');
function serializeJsonData(data) {
try {
return JSON.stringify(data);
} catch (err) {
return null;
}
}
function serializePrimitive(key, value) {
if (value === null || value === undefined) {
return encodeURIComponent(key) + '=';
}
return encodeURIComponent(key) + '=' + encodeURIComponent(value);
}
function serializeArray(key, values) {
return values.map(function(value) {
return encodeURIComponent(key) + '[]=' + encodeURIComponent(value);
}).join('&');
}
function serializeParams(params) {
if (params === null) return '';
return Object.keys(params).map(function(key) {
if (helpers.getTypeOf(params[key]) === 'Array') {
return serializeArray(key, params[key]);
}
return serializePrimitive(key, params[key]);
}).join('&');
}
function deserializeResponseHeaders(headers) {
var headerMap = {};
var arr = headers.trim().split(/[\r\n]+/);
arr.forEach(function (line) {
var parts = line.split(': ');
var header = parts.shift().toLowerCase();
var value = parts.join(': ');
headerMap[header] = value;
});
return headerMap;
}
function createXhrSuccessObject(xhr) {
return {
url: xhr.responseURL,
status: xhr.status,
data: helpers.getTypeOf(xhr.responseText) === 'String' ? xhr.responseText : xhr.response,
headers: deserializeResponseHeaders(xhr.getAllResponseHeaders())
};
}
function createXhrFailureObject(xhr) {
var obj = {};
obj.headers = xhr.getAllResponseHeaders();
obj.error = helpers.getTypeOf(xhr.responseText) === 'String' ? xhr.responseText : xhr.response;
obj.error = obj.error || 'advanced-http: please check browser console for error messages';
if (xhr.responseURL) obj.url = xhr.responseURL;
if (xhr.status) obj.status = xhr.status;
return obj;
}
function setHeaders(xhr, headers) {
Object.keys(headers).forEach(function(key) {
if (key === 'Cookie') return;
xhr.setRequestHeader(key, headers[key]);
});
}
function sendRequest(method, withData, opts, success, failure) {
var data = withData ? opts[1] : null;
var params = withData ? null : serializeParams(opts[1]);
var serializer = withData ? opts[2] : null;
var headers = withData ? opts[3] : opts[2];
var timeout = withData ? opts[4] : opts[3];
var url = params ? opts[0] + '?' + params : opts[0];
var processedData = null;
var xhr = new XMLHttpRequest();
xhr.open(method, url);
if (headers.Cookie && headers.Cookie.length > 0) {
return failure('advanced-http: custom cookies not supported on browser platform');
}
switch (serializer) {
case 'json':
xhr.setRequestHeader('Content-Type', 'application/json; charset=utf8');
processedData = serializeJsonData(data);
if (processedData === null) {
return failure('advanced-http: failed serializing data');
}
break;
case 'utf8':
xhr.setRequestHeader('Content-Type', 'text/plain; charset=utf8');
processedData = data.text;
break;
case 'urlencoded':
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
processedData = serializeParams(data);
break;
}
xhr.timeout = timeout * 1000;
setHeaders(xhr, headers);
xhr.onerror = xhr.ontimeout = function () {
return failure(createXhrFailureObject(xhr));
};
xhr.onload = function () {
if (xhr.readyState !== xhr.DONE) return;
if (xhr.status < 200 || xhr.status > 299) {
return failure(createXhrFailureObject(xhr));
}
return success(createXhrSuccessObject(xhr));
};
xhr.send(processedData);
}
var browserInterface = {
post: function (success, failure, opts) {
return sendRequest('post', true, opts, success, failure);
},
get: function (success, failure, opts) {
return sendRequest('get', false, opts, success, failure);
},
put: function (success, failure, opts) {
return sendRequest('put', true, opts, success, failure);
},
patch: function (success, failure, opts) {
return sendRequest('patch', true, opts, success, failure);
},
delete: function (success, failure, opts) {
return sendRequest('delete', false, opts, success, failure);
},
head: function (success, failure, opts) {
return sendRequest('head', false, opts, success, failure);
},
uploadFile: function (success, failure, opts) {
return failure('advanced-http: function "uploadFile" not supported on browser platform');
},
downloadFile: function (success, failure, opts) {
return failure('advanced-http: function "downloadFile" not supported on browser platform');
},
enableSSLPinning: function (success, failure, opts) {
return failure('advanced-http: function "enableSSLPinning" not supported on browser platform');
},
acceptAllCerts: function (success, failure, opts) {
return failure('advanced-http: function "acceptAllCerts" not supported on browser platform');
},
disableRedirect: function (success, failure, opts) {
return failure('advanced-http: function "disableRedirect" not supported on browser platform');
}
};
module.exports = browserInterface;
cordovaProxy.add('CordovaHttpPlugin', browserInterface);

View File

@@ -156,16 +156,9 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
@implementation AFSecurityPolicy
+ (NSSet *)certificatesInBundle:(NSBundle *)bundle {
NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];
NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"www/certificates"];
NSMutableSet *certificates = [NSMutableSet setWithCapacity:[paths count]];
for (NSString *path in paths) {
NSData *certificateData = [NSData dataWithContentsOfFile:path];
[certificates addObject:certificateData];
}
// also add certs from www/certificates
paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"www/certificates"];
for (NSString *path in paths) {
NSData *certificateData = [NSData dataWithContentsOfFile:path];
[certificates addObject:certificateData];
@@ -284,13 +277,13 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
// obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA)
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}
return NO;
}
case AFSSLPinningModePublicKey: {
@@ -307,7 +300,7 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
return trustedPublicKeyCount > 0;
}
}
return NO;
}

View File

@@ -4,8 +4,7 @@
@interface CordovaHttpPlugin : CDVPlugin
- (void)enableSSLPinning:(CDVInvokedUrlCommand*)command;
- (void)acceptAllCerts:(CDVInvokedUrlCommand*)command;
- (void)setSSLCertMode:(CDVInvokedUrlCommand*)command;
- (void)disableRedirect:(CDVInvokedUrlCommand*)command;
- (void)post:(CDVInvokedUrlCommand*)command;
- (void)get:(CDVInvokedUrlCommand*)command;

View File

@@ -3,6 +3,7 @@
#import "TextResponseSerializer.h"
#import "TextRequestSerializer.h"
#import "AFHTTPSessionManager.h"
#import "SDNetworkActivityIndicator.h"
@interface CordovaHttpPlugin()
@@ -120,23 +121,31 @@
return headerFieldsCopy;
}
- (void)setTimeout:(NSTimeInterval)timeout forManager:(AFHTTPSessionManager*)manager {
[manager.requestSerializer setTimeoutInterval:timeout];
}
- (void)setSSLCertMode:(CDVInvokedUrlCommand*)command {
NSString *certMode = [command.arguments objectAtIndex:0];
- (void)enableSSLPinning:(CDVInvokedUrlCommand*)command {
bool enable = [[command.arguments objectAtIndex:0] boolValue];
if (enable) {
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
} else {
if ([certMode isEqualToString: @"default"]) {
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
securityPolicy.allowInvalidCertificates = NO;
securityPolicy.validatesDomainName = YES;
} else if ([certMode isEqualToString: @"nocheck"]) {
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
securityPolicy.allowInvalidCertificates = YES;
securityPolicy.validatesDomainName = NO;
} else if ([certMode isEqualToString: @"pinned"]) {
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
securityPolicy.allowInvalidCertificates = NO;
securityPolicy.validatesDomainName = YES;
}
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
- (void)setTimeout:(NSTimeInterval)timeout forManager:(AFHTTPSessionManager*)manager {
[manager.requestSerializer setTimeoutInterval:timeout];
}
- (void)disableRedirect:(CDVInvokedUrlCommand*)command {
CDVPluginResult* pluginResult = nil;
bool disable = [[command.arguments objectAtIndex:0] boolValue];
@@ -147,17 +156,6 @@
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
- (void)acceptAllCerts:(CDVInvokedUrlCommand*)command {
CDVPluginResult* pluginResult = nil;
bool allow = [[command.arguments objectAtIndex:0] boolValue];
securityPolicy.allowInvalidCertificates = allow;
securityPolicy.validatesDomainName = !allow;
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
- (void)post:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
@@ -175,6 +173,7 @@
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
[manager POST:url parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject) {
@@ -183,15 +182,18 @@
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
} failure:^(NSURLSessionTask *task, NSError *error) {
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];
}];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[self handleException:exception withCommand:command];
}
}
@@ -213,6 +215,7 @@
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
[manager GET:url parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject) {
@@ -221,15 +224,18 @@
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
} failure:^(NSURLSessionTask *task, NSError *error) {
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];
}];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[self handleException:exception withCommand:command];
}
}
@@ -251,6 +257,7 @@
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
[manager PUT:url parameters:parameters success:^(NSURLSessionTask *task, id responseObject) {
@@ -259,15 +266,18 @@
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
} failure:^(NSURLSessionTask *task, NSError *error) {
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];
}];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[self handleException:exception withCommand:command];
}
}
@@ -289,6 +299,7 @@
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
[manager PATCH:url parameters:parameters success:^(NSURLSessionTask *task, id responseObject) {
@@ -297,15 +308,18 @@
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
} failure:^(NSURLSessionTask *task, NSError *error) {
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];
}];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[self handleException:exception withCommand:command];
}
}
@@ -326,6 +340,7 @@
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
[manager DELETE:url parameters:parameters success:^(NSURLSessionTask *task, id responseObject) {
@@ -334,15 +349,18 @@
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
} failure:^(NSURLSessionTask *task, NSError *error) {
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];
}];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[self handleException:exception withCommand:command];
}
}
@@ -361,6 +379,7 @@
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
[manager HEAD:url parameters:parameters success:^(NSURLSessionTask *task) {
@@ -370,15 +389,18 @@
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
} failure:^(NSURLSessionTask *task, NSError *error) {
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];
}];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[self handleException:exception withCommand:command];
}
}
@@ -402,6 +424,7 @@
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
[manager POST:url parameters:parameters constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
@@ -413,6 +436,7 @@
[dictionary setObject:@"Could not add file to post body." forKey:@"error"];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
return;
}
} progress:nil success:^(NSURLSessionTask *task, id responseObject) {
@@ -421,15 +445,18 @@
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
} failure:^(NSURLSessionTask *task, NSError *error) {
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];
}];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[self handleException:exception withCommand:command];
}
}
@@ -455,6 +482,7 @@
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
[manager GET:url parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject) {
@@ -495,6 +523,7 @@
}
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
return;
}
NSData *data = (NSData *)responseObject;
@@ -504,6 +533,7 @@
[dictionary setObject:@"Could not write the data to the given filePath." forKey:@"error"];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
return;
}
@@ -514,6 +544,7 @@
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
} failure:^(NSURLSessionTask *task, NSError *error) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
@@ -521,9 +552,11 @@
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
}];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[self handleException:exception withCommand:command];
}
}

View File

@@ -0,0 +1,20 @@
Copyright (c) 2010 Olivier Poitrey <rs@dailymotion.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,52 @@
# SDNetworkActivityIndicator
Handle showing / hiding of the iOS network activity indicator to allow multiple concurrent threads to show / hide the indicator such that the indicator remains visible until all the requests have completed and requested the indicator to be hidden.
## Requirements
* iOS 5.0 or later.
* ARC memory management.
## Installation
The easiest way to install it is by copying the following files to your project:
* SDNetworkActivityIndicator.h
* SDNetworkActivityIndicator.m
## Usage
* When you start a network activity (will show the network activity indicator):
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
* When you finish a network activity (will hide the network activity indicator only if the number of calls to `stopActivity` matches the number of calls to `startActivity`):
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
* To hide the network activity indicator regardless of whether all activities have finished (without having to call `stopActivity` for each `startActivity` called):
[[SDNetworkActivityIndicator sharedActivityIndicator] stopAllActivity];
## License
Copyright (c) 2010 Olivier Poitrey <rs@dailymotion.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,18 @@
/*
* This file is part of the SDNetworkActivityIndicator package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
@interface SDNetworkActivityIndicator : NSObject
+ (id)sharedActivityIndicator;
- (void)startActivity;
- (void)stopActivity;
- (void)stopAllActivity;
@end

View File

@@ -0,0 +1,70 @@
/*
* This file is part of the SDNetworkActivityIndicator package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDNetworkActivityIndicator.h"
@interface SDNetworkActivityIndicator()
{
@private NSUInteger counter;
}
@end
@implementation SDNetworkActivityIndicator
+ (instancetype) sharedActivityIndicator
{
static id _sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
- (id)init
{
if ((self = [super init]))
{
counter = 0;
}
return self;
}
- (void)startActivity
{
@synchronized(self)
{
counter++;
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
}
}
- (void)stopActivity
{
@synchronized(self)
{
if (counter > 0 && --counter == 0)
{
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}
}
}
- (void)stopAllActivity
{
@synchronized(self)
{
counter = 0;
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}
}
@end

View File

@@ -83,10 +83,10 @@ describe('Advanced HTTP', function() {
}));
testDefinitions.tests.forEach((definition, index) => {
it(definition.description, function() {
it(index + ': ' + definition.description, function() {
return clickNext()
.then(() => validateTestIndex(index))
.then(() => validateTestTitle(this.test.title))
.then(() => validateTestTitle(definition.description))
.then(() => waitToBeFinished(definition.timeout || 10000))
.then(() => validateResult(definition))
});

View File

@@ -23,6 +23,7 @@
<allow-intent href="itms-apps:*" />
</platform>
<engine name="android" spec="6.2.3" />
<engine name="browser" spec="5.0.0" />
<engine name="ios" spec="4.4.0" />
<plugin name="cordova-plugin-file" spec="4.3.3" />
</widget>

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:;">
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com https://self-signed.badssl.com http://httpbin.org http://www.columbia.edu 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:;">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">

View File

@@ -1,15 +1,14 @@
const hooks = {
onBeforeEachTest: function(done) {
cordova.plugin.http.clearCookies();
cordova.plugin.http.acceptAllCerts(false, function() {
cordova.plugin.http.enableSSLPinning(false, done, done);
}, done);
helpers.setDefaultCertMode(done);
}
};
const helpers = {
acceptAllCerts: function(done) { cordova.plugin.http.acceptAllCerts(true, done, done); },
enableSSLPinning: function(done) { cordova.plugin.http.enableSSLPinning(true, done, done); },
setDefaultCertMode: function(done) { cordova.plugin.http.setSSLCertMode('default', done, done); },
setNoCheckCertMode: function(done) { cordova.plugin.http.setSSLCertMode('nocheck', done, done); },
setPinnedCertMode: function(done) { cordova.plugin.http.setSSLCertMode('pinned', done, done); },
setJsonSerializer: function(done) { done(cordova.plugin.http.setDataSerializer('json')); },
setUtf8StringSerializer: function(done) { done(cordova.plugin.http.setDataSerializer('utf8')); },
setUrlEncodedSerializer: function(done) { done(cordova.plugin.http.setDataSerializer('urlencoded')); },
@@ -82,7 +81,7 @@ const tests = [
},{
description: 'should accept bad cert (GET)',
expected: 'resolved: {"status":200, ...',
before: helpers.acceptAllCerts,
before: helpers.setNoCheckCertMode,
func: function(resolve, reject) { cordova.plugin.http.get('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
@@ -91,7 +90,7 @@ const tests = [
},{
description: 'should accept bad cert (PUT)',
expected: 'rejected: {"status":405, ... // will be rejected because PUT is not allowed',
before: helpers.acceptAllCerts,
before: helpers.setNoCheckCertMode,
func: function(resolve, reject) { cordova.plugin.http.put('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('rejected');
@@ -100,7 +99,7 @@ const tests = [
},{
description: 'should accept bad cert (POST)',
expected: 'rejected: {"status":405, ... // will be rejected because POST is not allowed',
before: helpers.acceptAllCerts,
before: helpers.setNoCheckCertMode,
func: function(resolve, reject) { cordova.plugin.http.post('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('rejected');
@@ -109,7 +108,7 @@ const tests = [
},{
description: 'should accept bad cert (PATCH)',
expected: 'rejected: {"status":405, ... // will be rejected because PATCH is not allowed',
before: helpers.acceptAllCerts,
before: helpers.setNoCheckCertMode,
func: function(resolve, reject) { cordova.plugin.http.patch('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('rejected');
@@ -118,7 +117,7 @@ const tests = [
},{
description: 'should accept bad cert (DELETE)',
expected: 'rejected: {"status":405, ... // will be rejected because DELETE is not allowed',
before: helpers.acceptAllCerts,
before: helpers.setNoCheckCertMode,
func: function(resolve, reject) { cordova.plugin.http.delete('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('rejected');
@@ -127,7 +126,7 @@ const tests = [
},{
description: 'should fetch data from http://httpbin.org/ (GET)',
expected: 'resolved: {"status":200, ...',
before: helpers.acceptAllCerts,
before: helpers.setNoCheckCertMode,
func: function(resolve, reject) { cordova.plugin.http.get('http://httpbin.org/', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
@@ -288,14 +287,14 @@ const tests = [
.should.be.equal('http://httpbin.org/get?myArray[]=val1&myArray[]=val2&myArray[]=val3&myString=testString');
}
},{
description: 'should reject non-string values in local header object #54',
expected: 'rejected: {"status": 0, "error": "advanced-http: header values must be strings" ...',
description: 'should throw on non-string values in local header object #54',
expected: 'throwed: {"message": "advanced-http: header values must be strings"}',
func: function(resolve, reject) {
cordova.plugin.http.get('http://httpbin.org/get', {}, { myTestHeader: 1 }, resolve, reject);
},
validationFunc: function(driver, result) {
result.type.should.be.equal('rejected');
result.data.error.should.be.equal('advanced-http: header values must be strings');
result.type.should.be.equal('throwed');
result.message.should.be.equal('advanced-http: header values must be strings');
}
},{
description: 'should throw an error while setting non-string value as global header #54',
@@ -430,12 +429,24 @@ const tests = [
},{
description: 'should pin SSL cert correctly (GET)',
expected: 'resolved: {"status": 200 ...',
before: helpers.enableSSLPinning,
before: helpers.setPinnedCertMode,
func: function(resolve, reject) {
cordova.plugin.http.get('https://httpbin.org', {}, {}, resolve, reject);
},
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
result.data.status.should.be.equal(200);
}
},{
description: 'should reject when pinned cert does not match received server cert (GET)',
expected: 'rejected: {"status": -1 ...',
before: helpers.setPinnedCertMode,
func: function(resolve, reject) {
cordova.plugin.http.get('https://sha512.badssl.com/', {}, {}, resolve, reject);
},
validationFunc: function(driver, result, targetInfo) {
result.type.should.be.equal('rejected');
result.data.should.be.eql({ status: -1, error: targetInfo.isAndroid ? 'SSL handshake failed' : 'cancelled' });
}
},{
description: 'should send deeply structured JSON object correctly (POST) #65',
@@ -464,6 +475,15 @@ const tests = [
result.data.status.should.be.equal(403);
result.data.error.should.be.equal('There was an error downloading the file');
}
},{
description: 'should handle gzip encoded response correctly',
expected: 'resolved: {"status": 200, "headers": "{\\"Content-Encoding\\": \\"gzip\\" ...',
func: function(resolve, reject) { cordova.plugin.http.get('http://httpbin.org/gzip', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
result.data.status.should.be.equal(200);
JSON.parse(result.data.data).gzipped.should.be.equal(true);
}
}
];

View File

@@ -27,9 +27,7 @@ describe('Advanced HTTP www interface', function() {
mock('cordova/exec', noop);
mock(`${PLUGIN_ID}.cookie-handler`, {});
mock(`${HELPERS_ID}.cookie-handler`, {});
mock(`${PLUGIN_ID}.messages`, require('../www/messages'));
mock(`${HELPERS_ID}.messages`, require('../www/messages'));
mock(`${PLUGIN_ID}.angular-integration`, { registerService: noop });
loadHttp();
});

View File

@@ -1,238 +1,125 @@
/*global angular*/
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* Modified by Andrew Stephan for Sync OnSet
* Modified by Sefa Ilkimen
*/
/*
* An HTTP Plugin for PhoneGap.
* A native HTTP Plugin for Cordova / PhoneGap.
*/
var pluginId = module.id.slice(0, module.id.lastIndexOf('.'));
var exec = require('cordova/exec');
var angularIntegration = require(pluginId +'.angular-integration');
var cookieHandler = require(pluginId + '.cookie-handler');
var helpers = require(pluginId + '.helpers');
var messages = require(pluginId + '.messages');
var internals = {
var globalConfigs = {
headers: {},
dataSerializer: 'urlencoded',
timeoutInSeconds: 60.0,
serializer: 'urlencoded',
timeout: 60.0,
};
var publicInterface = {
getBasicAuthHeader: function (username, password) {
return {'Authorization': 'Basic ' + helpers.b64EncodeUnicode(username + ':' + password)};
},
useBasicAuth: function (username, password) {
this.setHeader('*', 'Authorization', 'Basic ' + helpers.b64EncodeUnicode(username + ':' + password));
},
getHeaders: function (host) {
return internals.headers[host || '*'] || null;
},
setHeader: function () {
// this one is for being backward compatible
var host = '*';
var header = arguments[0];
var value = arguments[1];
getBasicAuthHeader: function (username, password) {
return {'Authorization': 'Basic ' + helpers.b64EncodeUnicode(username + ':' + password)};
},
useBasicAuth: function (username, password) {
this.setHeader('*', 'Authorization', 'Basic ' + helpers.b64EncodeUnicode(username + ':' + password));
},
getHeaders: function (host) {
return globalConfigs.headers[host || '*'] || null;
},
setHeader: function () {
// this one is for being backward compatible
var host = '*';
var header = arguments[0];
var value = arguments[1];
if (arguments.length === 3) {
host = arguments[0];
header = arguments[1];
value = arguments[2];
}
if (header.toLowerCase() === 'cookie') {
throw new Error(messages.ADDING_COOKIES_NOT_SUPPORTED);
}
if (helpers.getTypeOf(value) !== 'String') {
throw new Error(messages.HEADER_VALUE_MUST_BE_STRING);
}
internals.headers[host] = internals.headers[host] || {};
internals.headers[host][header] = value;
},
getDataSerializer: function () {
return internals.dataSerializer;
},
setDataSerializer: function (serializer) {
internals.dataSerializer = helpers.checkSerializer(serializer);
},
setCookie: function (url, cookie, options) {
cookieHandler.setCookie(url, cookie, options);
},
clearCookies: function () {
cookieHandler.clearCookies();
},
removeCookies: function (url, callback) {
cookieHandler.removeCookies(url, callback);
},
getCookieString: function (url) {
return cookieHandler.getCookieString(url);
},
getRequestTimeout: function () {
return internals.timeoutInSeconds;
},
setRequestTimeout: function (timeout) {
internals.timeoutInSeconds = timeout;
},
enableSSLPinning: function (enable, success, failure) {
return exec(success, failure, 'CordovaHttpPlugin', 'enableSSLPinning', [enable]);
},
acceptAllCerts: function (allow, success, failure) {
return exec(success, failure, 'CordovaHttpPlugin', 'acceptAllCerts', [allow]);
},
disableRedirect: function (disable, success, failure) {
return exec(success, failure, 'CordovaHttpPlugin', 'disableRedirect', [disable]);
},
validateDomainName: function (validate, success, failure) {
failure(messages.DEPRECATED_VDN);
},
post: function (url, data, headers, success, failure) {
helpers.handleMissingCallbacks(success, failure);
data = helpers.getProcessedData(data, internals.dataSerializer);
headers = helpers.getMergedHeaders(url, headers, internals.headers);
if (!helpers.checkHeaders(headers)) {
return helpers.onInvalidHeader(failure);
}
var onSuccess = helpers.injectCookieHandler(url, success);
var onFail = helpers.injectCookieHandler(url, failure);
return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'post', [url, data, internals.dataSerializer, headers, internals.timeoutInSeconds]);
},
get: function (url, params, headers, success, failure) {
helpers.handleMissingCallbacks(success, failure);
params = params || {};
headers = helpers.getMergedHeaders(url, headers, internals.headers);
if (!helpers.checkHeaders(headers)) {
return helpers.onInvalidHeader(failure);
}
var onSuccess = helpers.injectCookieHandler(url, success);
var onFail = helpers.injectCookieHandler(url, failure);
return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'get', [url, params, headers, internals.timeoutInSeconds]);
},
put: function (url, data, headers, success, failure) {
helpers.handleMissingCallbacks(success, failure);
data = helpers.getProcessedData(data, internals.dataSerializer);
headers = helpers.getMergedHeaders(url, headers, internals.headers);
if (!helpers.checkHeaders(headers)) {
return helpers.onInvalidHeader(failure);
}
var onSuccess = helpers.injectCookieHandler(url, success);
var onFail = helpers.injectCookieHandler(url, failure);
return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'put', [url, data, internals.dataSerializer, headers, internals.timeoutInSeconds]);
},
patch: function (url, data, headers, success, failure) {
helpers.handleMissingCallbacks(success, failure);
data = helpers.getProcessedData(data, internals.dataSerializer);
headers = helpers.getMergedHeaders(url, headers, internals.headers);
if (!helpers.checkHeaders(headers)) {
return helpers.onInvalidHeader(failure);
}
var onSuccess = helpers.injectCookieHandler(url, success);
var onFail = helpers.injectCookieHandler(url, failure);
return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'patch', [url, data, internals.dataSerializer, headers, internals.timeoutInSeconds]);
},
delete: function (url, params, headers, success, failure) {
helpers.handleMissingCallbacks(success, failure);
params = params || {};
headers = helpers.getMergedHeaders(url, headers, internals.headers);
if (!helpers.checkHeaders(headers)) {
return helpers.onInvalidHeader(failure);
}
var onSuccess = helpers.injectCookieHandler(url, success);
var onFail = helpers.injectCookieHandler(url, failure);
return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'delete', [url, params, headers, internals.timeoutInSeconds]);
},
head: function (url, params, headers, success, failure) {
helpers.handleMissingCallbacks(success, failure);
params = params || {};
headers = helpers.getMergedHeaders(url, headers, internals.headers);
if (!helpers.checkHeaders(headers)) {
return helpers.onInvalidHeader(failure);
}
var onSuccess = helpers.injectCookieHandler(url, success);
var onFail = helpers.injectCookieHandler(url, failure);
return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'head', [url, params, headers, internals.timeoutInSeconds]);
},
uploadFile: function (url, params, headers, filePath, name, success, failure) {
helpers.handleMissingCallbacks(success, failure);
params = params || {};
headers = helpers.getMergedHeaders(url, headers, internals.headers);
if (!helpers.checkHeaders(headers)) {
return helpers.onInvalidHeader(failure);
}
var onSuccess = helpers.injectCookieHandler(url, success);
var onFail = helpers.injectCookieHandler(url, failure);
return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'uploadFile', [url, params, headers, filePath, name, internals.timeoutInSeconds]);
},
downloadFile: function (url, params, headers, filePath, success, failure) {
helpers.handleMissingCallbacks(success, failure);
params = params || {};
headers = helpers.getMergedHeaders(url, headers, internals.headers);
if (!helpers.checkHeaders(headers)) {
return helpers.onInvalidHeader(failure);
}
var onSuccess = helpers.injectCookieHandler(url, helpers.injectFileEntryHandler(success));
var onFail = helpers.injectCookieHandler(url, failure);
return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'downloadFile', [url, params, headers, filePath, internals.timeoutInSeconds]);
if (arguments.length === 3) {
host = arguments[0];
header = arguments[1];
value = arguments[2];
}
helpers.checkForBlacklistedHeaderKey(header);
helpers.checkForInvalidHeaderValue(value);
globalConfigs.headers[host] = globalConfigs.headers[host] || {};
globalConfigs.headers[host][header] = value;
},
getDataSerializer: function () {
return globalConfigs.serializer;
},
setDataSerializer: function (serializer) {
globalConfigs.serializer = helpers.checkSerializer(serializer);
},
setCookie: function (url, cookie, options) {
cookieHandler.setCookie(url, cookie, options);
},
clearCookies: function () {
cookieHandler.clearCookies();
},
removeCookies: function (url, callback) {
cookieHandler.removeCookies(url, callback);
},
getCookieString: function (url) {
return cookieHandler.getCookieString(url);
},
getRequestTimeout: function () {
return globalConfigs.timeout;
},
setRequestTimeout: function (timeout) {
globalConfigs.timeout = timeout;
},
setSSLCertMode: function (mode, success, failure) {
return exec(success, failure, 'CordovaHttpPlugin', 'setSSLCertMode', [ helpers.checkSSLCertMode(mode) ]);
},
disableRedirect: function (disable, success, failure) {
return exec(success, failure, 'CordovaHttpPlugin', 'disableRedirect', [ !!disable ]);
},
sendRequest: function (url, options, success, failure) {
helpers.handleMissingCallbacks(success, failure);
options = helpers.handleMissingOptions(options, globalConfigs);
var headers = helpers.getMergedHeaders(url, options.headers, globalConfigs.headers);
var onSuccess = helpers.injectCookieHandler(url, success);
var onFail = helpers.injectCookieHandler(url, failure);
switch(options.method) {
case 'post':
case 'put':
case 'patch':
var data = helpers.getProcessedData(options.data, options.serializer);
return exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [ url, data, options.serializer, headers, options.timeout ]);
case 'upload':
return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'uploadFile', [ url, options.params, headers, options.filePath, options.name, options.timeout ]);
case 'download':
var onDownloadSuccess = helpers.injectCookieHandler(url, helpers.injectFileEntryHandler(success));
return exec(onDownloadSuccess, onFail, 'CordovaHttpPlugin', 'downloadFile', [ url, options.params, headers, options.filePath, options.timeout ]);
default:
return exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [ url, options.params, headers, options.timeout ]);
}
},
post: function (url, data, headers, success, failure) {
return publicInterface.sendRequest(url, { method: 'post', data: data, headers: headers }, success, failure);
},
get: function (url, params, headers, success, failure) {
return publicInterface.sendRequest(url, { method: 'get', params: params, headers: headers }, success, failure);
},
put: function (url, data, headers, success, failure) {
return publicInterface.sendRequest(url, { method: 'put', data: data, headers: headers }, success, failure);
},
patch: function (url, data, headers, success, failure) {
return publicInterface.sendRequest(url, { method: 'patch', data: data, headers: headers }, success, failure);
},
delete: function (url, params, headers, success, failure) {
return publicInterface.sendRequest(url, { method: 'delete', params: params, headers: headers }, success, failure);
},
head: function (url, params, headers, success, failure) {
return publicInterface.sendRequest(url, { method: 'head', params: params, headers: headers }, success, failure);
},
uploadFile: function (url, params, headers, filePath, name, success, failure) {
return publicInterface.sendRequest(url, { method: 'upload', params: params, headers: headers, filePath: filePath, name: name }, success, failure);
},
downloadFile: function (url, params, headers, filePath, success, failure) {
return publicInterface.sendRequest(url, { method: 'download', params: params, headers: headers, filePath: filePath }, success, failure);
}
};
angularIntegration.registerService(publicInterface);
module.exports = publicInterface;

View File

@@ -1,96 +0,0 @@
function registerService(http) {
if (typeof angular === 'undefined') return;
angular.module('cordovaHTTP', []).factory('cordovaHTTP', function ($timeout, $q) {
function makePromise(fn, args, async) {
var deferred = $q.defer();
var success = function (response) {
if (async) {
$timeout(function () {
deferred.resolve(response);
});
} else {
deferred.resolve(response);
}
};
var fail = function (response) {
if (async) {
$timeout(function () {
deferred.reject(response);
});
} else {
deferred.reject(response);
}
};
args.push(success);
args.push(fail);
fn.apply(http, args);
return deferred.promise;
}
var cordovaHTTP = {
getBasicAuthHeader: http.getBasicAuthHeader,
useBasicAuth: function (username, password) {
return http.useBasicAuth(username, password);
},
setHeader: function (host, header, value) {
return http.setHeader(host, header, value);
},
setDataSerializer: function (serializer) {
return http.setDataSerializer(serializer);
},
clearCookies: function () {
return http.clearCookies();
},
removeCookies: function (url) {
return http.removeCookies(url);
},
setRequestTimeout: function (timeout) {
return http.setRequestTimeout(timeout);
},
enableSSLPinning: function (enable) {
return makePromise(http.enableSSLPinning, [enable]);
},
acceptAllCerts: function (allow) {
return makePromise(http.acceptAllCerts, [allow]);
},
disableRedirect: function(disable) {
return makePromise(http.disableRedirect, [disable]);
},
validateDomainName: function (validate) {
return makePromise(http.validateDomainName, [validate]);
},
post: function (url, data, headers) {
return makePromise(http.post, [url, data, headers], true);
},
get: function (url, params, headers) {
return makePromise(http.get, [url, params, headers], true);
},
put: function (url, data, headers) {
return makePromise(http.put, [url, data, headers], true);
},
delete: function (url, params, headers) {
return makePromise(http.delete, [url, params, headers], true);
},
head: function (url, params, headers) {
return makePromise(http.head, [url, params, headers], true);
},
uploadFile: function (url, params, headers, filePath, name) {
return makePromise(http.uploadFile, [url, params, headers, filePath, name], true);
},
downloadFile: function (url, params, headers, filePath) {
return makePromise(http.downloadFile, [url, params, headers, filePath], true);
}
};
return cordovaHTTP;
});
}
module.exports = {
registerService: registerService
};

View File

@@ -3,18 +3,22 @@ var cookieHandler = require(pluginId + '.cookie-handler');
var messages = require(pluginId + '.messages');
var validSerializers = [ 'urlencoded', 'json', 'utf8' ];
var validCertModes = [ 'default', 'nocheck', 'pinned' ];
var validHttpMethods = [ 'get', 'put', 'post', 'patch', 'head', 'delete', 'upload', 'download' ];
module.exports = {
b64EncodeUnicode: b64EncodeUnicode,
getTypeOf: getTypeOf,
checkHeaders: checkHeaders,
onInvalidHeader: onInvalidHeader,
checkSerializer: checkSerializer,
checkSSLCertMode: checkSSLCertMode,
checkForBlacklistedHeaderKey: checkForBlacklistedHeaderKey,
checkForInvalidHeaderValue: checkForInvalidHeaderValue,
injectCookieHandler: injectCookieHandler,
injectFileEntryHandler: injectFileEntryHandler,
getMergedHeaders: getMergedHeaders,
getProcessedData: getProcessedData,
handleMissingCallbacks: handleMissingCallbacks
handleMissingCallbacks: handleMissingCallbacks,
handleMissingOptions: handleMissingOptions
};
// Thanks Mozilla: https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_.22Unicode_Problem.22
@@ -39,38 +43,78 @@ function mergeHeaders(globalHeaders, localHeaders) {
return localHeaders;
}
function checkHeaders(headers) {
var keys = Object.keys(headers);
var key;
function checkForValidStringValue(list, value, onInvalidValueMessage) {
if (getTypeOf(value) !== 'String') {
throw new Error(onInvalidValueMessage + ' ' + list.join(', '));
}
value = value.trim().toLowerCase();
if (list.indexOf(value) === -1) {
throw new Error(onInvalidValueMessage + ' ' + list.join(', '));
}
return value;
}
function checkKeyValuePairObject(obj, allowedChildren, onInvalidValueMessage) {
if (getTypeOf(obj) !== 'Object') {
throw new Error(onInvalidValueMessage);
}
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
key = keys[i];
if (getTypeOf(headers[key]) !== 'String') {
return false;
if (allowedChildren.indexOf(getTypeOf(obj[keys[i]])) === -1) {
throw new Error(onInvalidValueMessage);
}
}
return true;
return obj;
}
function onInvalidHeader(handler) {
handler({
status: 0,
error: messages.HEADER_VALUE_MUST_BE_STRING,
headers: {}
});
function checkHttpMethod(method) {
return checkForValidStringValue(validHttpMethods, method, messages.INVALID_HTTP_METHOD);
}
function checkSerializer(serializer) {
serializer = serializer || '';
serializer = serializer.trim().toLowerCase();
return checkForValidStringValue(validSerializers, serializer, messages.INVALID_DATA_SERIALIZER);
}
if (validSerializers.indexOf(serializer) > -1) {
return serializer;
function checkSSLCertMode(mode) {
return checkForValidStringValue(validCertModes, mode, messages.INVALID_SSL_CERT_MODE);
}
function checkForBlacklistedHeaderKey(key) {
if (key.toLowerCase() === 'cookie') {
throw new Error(messages.ADDING_COOKIES_NOT_SUPPORTED);
}
return serializer[0];
return key;
}
function checkForInvalidHeaderValue(value) {
if (getTypeOf(value) !== 'String') {
throw new Error(messages.INVALID_HEADERS_VALUE);
}
return value;
}
function checkTimeoutValue(timeout) {
if (getTypeOf(timeout) !== 'Number' || timeout < 0) {
throw new Error(messages.INVALID_TIMEOUT_VALUE);
}
return timeout;
}
function checkHeadersObject(headers) {
return checkKeyValuePairObject(headers, [ 'String' ], messages.INVALID_HEADERS_VALUE);
}
function checkParamsObject(params) {
return checkKeyValuePairObject(params, [ 'String', 'Array' ], messages.INVALID_PARAMS_VALUE);
}
function resolveCookieString(headers) {
@@ -194,3 +238,18 @@ function handleMissingCallbacks(successFn, failFn) {
throw new Error(messages.MANDATORY_FAIL);
}
}
function handleMissingOptions(options, globals) {
options = options || {};
return {
method: checkHttpMethod(options.method || validHttpMethods[0]),
serializer: checkSerializer(options.serializer || globals.serializer),
timeout: checkTimeoutValue(options.timeout || globals.timeout),
headers: checkHeadersObject(options.headers || {}),
params: checkParamsObject(options.params || {}),
data: options.data || null,
filePath: options.filePath || '',
name: options.name || ''
};
}

View File

@@ -1,8 +1,12 @@
module.exports = {
ADDING_COOKIES_NOT_SUPPORTED: 'advanced-http: "setHeader" does not support adding cookies, please use "setCookie" function instead',
DATA_TYPE_MISMATCH: 'advanced-http: "data" argument supports only following data types:',
DEPRECATED_VDN: 'advanced-http: "validateDomainName" is no more supported, please see change log for further info',
HEADER_VALUE_MUST_BE_STRING: 'advanced-http: header values must be strings',
MANDATORY_SUCCESS: 'advanced-http: missing mandatory "onSuccess" callback function',
MANDATORY_FAIL: 'advanced-http: missing mandatory "onFail" callback function'
MANDATORY_FAIL: 'advanced-http: missing mandatory "onFail" callback function',
INVALID_HTTP_METHOD: 'advanced-http: invalid HTTP method, supported methods are:',
INVALID_DATA_SERIALIZER: 'advanced-http: invalid serializer, supported serializers are:',
INVALID_SSL_CERT_MODE: 'advanced-http: invalid SSL cert mode, supported modes are:',
INVALID_HEADERS_VALUE: 'advanced-http: header values must be strings',
INVALID_TIMEOUT_VALUE: 'advanced-http: invalid timeout value, needs to be a positive numeric value',
INVALID_PARAMS_VALUE: 'advanced-http: invalid params object, needs to be an object with strings'
};