mirror of
https://github.com/silkimen/cordova-plugin-advanced-http.git
synced 2026-02-22 00:00:04 +08:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bf39650e1 | ||
|
|
d444363a8e | ||
|
|
13976bbe43 | ||
|
|
70c8b9bb32 | ||
|
|
4b964e8b7c | ||
|
|
56f8b41f4f | ||
|
|
e1e720fe2d | ||
|
|
a1b6ea94f0 | ||
|
|
d977392a49 | ||
|
|
8d28f4ab80 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,5 @@
|
||||
node_modules/**
|
||||
test/app-template/www/certificates/*.cer
|
||||
test/e2e-app-template/www/certificates/*.cer
|
||||
tags
|
||||
.zedstate
|
||||
npm-debug.log
|
||||
|
||||
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,5 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## 2.0.8
|
||||
|
||||
- Fixed #198: cookie header is always passed even if there is no cookie
|
||||
- Fixed #201: browser implementation is broken due to broken dependency
|
||||
- Fixed #197: iOS crashes when multiple request are done simultaneously (reverted a8e3637)
|
||||
- Fixed #189: error code mappings are not precise
|
||||
- Fixed #200: compatibility with Java 6 is broken due to string switch on Android
|
||||
|
||||
- :warning: **Deprecation**: Deprecated "setSSLCertMode" in favor of "setServerTrustMode"
|
||||
|
||||
## 2.0.7
|
||||
|
||||
- Fixed #195: URLs are double-encoded on Android
|
||||
|
||||
29
README.md
29
README.md
@@ -128,13 +128,13 @@ cordova.plugin.http.clearCookies();
|
||||
## Asynchronous Functions
|
||||
These functions all take success and error callbacks as their last 2 arguments.
|
||||
|
||||
### setSSLCertMode<a name="setSSLCertMode"></a>
|
||||
Set SSL Cert handling mode, being one of the following values:
|
||||
### setServerTrustMode<a name="setServerTrustMode"></a>
|
||||
Set server trust mode, being one of the following values:
|
||||
|
||||
* `default`: default SSL cert handling using system's CA certs
|
||||
* `default`: default SSL trustship and hostname verification handling using system's CA certs
|
||||
* `legacy`: use legacy default behavior (< 2.0.3), excluding user installed CA certs (only for Android)
|
||||
* `nocheck`: disable SSL cert checking, trusting all certs (meant to be used only for testing purposes)
|
||||
* `pinned`: trust only provided certs
|
||||
* `nocheck`: disable SSL certificate checking and hostname verification, trusting all certs (meant to be used only for testing purposes)
|
||||
* `pinned`: trust only provided certificates
|
||||
|
||||
To use SSL pinning you must include at least one `.cer` SSL certificate in your app project. You can pin to your server certificate or to one of the issuing CA certificates. Include your certificate in the `www/certificates` folder. All `.cer` files found there will be loaded automatically.
|
||||
|
||||
@@ -142,32 +142,38 @@ To use SSL pinning you must include at least one `.cer` SSL certificate in your
|
||||
|
||||
```js
|
||||
// enable SSL pinning
|
||||
cordova.plugin.http.setSSLCertMode('pinned', function() {
|
||||
cordova.plugin.http.setServerTrustMode('pinned', function() {
|
||||
console.log('success!');
|
||||
}, function() {
|
||||
console.log('error :(');
|
||||
});
|
||||
|
||||
// use system's default CA certs
|
||||
cordova.plugin.http.setSSLCertMode('default', function() {
|
||||
cordova.plugin.http.setServerTrustMode('default', function() {
|
||||
console.log('success!');
|
||||
}, function() {
|
||||
console.log('error :(');
|
||||
});
|
||||
|
||||
// disable SSL cert checking, only meant for testing purposes, do NOT use in production!
|
||||
cordova.plugin.http.setSSLCertMode('nocheck', function() {
|
||||
cordova.plugin.http.setServerTrustMode('nocheck', function() {
|
||||
console.log('success!');
|
||||
}, function() {
|
||||
console.log('error :(');
|
||||
});
|
||||
```
|
||||
|
||||
### setSSLCertMode (deprecated)
|
||||
This function was deprecated in 2.0.8. Use ["setServerTrustMode"](#setServerTrustMode) instead.
|
||||
|
||||
### enableSSLPinning (obsolete)
|
||||
This function was removed in 2.0.0. Use ["setSSLCertMode"](#setSSLCertMode) to enable SSL pinning (mode "pinned").
|
||||
This function was removed in 2.0.0. Use ["setServerTrustMode"](#setServerTrustMode) to enable SSL pinning (mode "pinned").
|
||||
|
||||
### acceptAllCerts (obsolete)
|
||||
This function was removed in 2.0.0. Use ["setSSLCertMode"](#setSSLCertMode) to disable checking certs (mode "nocheck").
|
||||
This function was removed in 2.0.0. Use ["setServerTrustMode"](#setServerTrustMode) to disable checking certs (mode "nocheck").
|
||||
|
||||
### validateDomainName (obsolete)
|
||||
This function was removed in v1.6.2. Domain name validation is disabled automatically when you set server trust mode to "nocheck".
|
||||
|
||||
### disableRedirect
|
||||
If set to `true`, it won't follow redirects automatically. This defaults to false.
|
||||
@@ -180,9 +186,6 @@ cordova.plugin.http.disableRedirect(true, function() {
|
||||
});
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cordova-plugin-advanced-http",
|
||||
"version": "2.0.7",
|
||||
"version": "2.0.8",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cordova-plugin-advanced-http",
|
||||
"version": "2.0.7",
|
||||
"version": "2.0.8",
|
||||
"description": "Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning",
|
||||
"scripts": {
|
||||
"updatecert": "node ./scripts/update-test-cert.js",
|
||||
@@ -8,7 +8,7 @@
|
||||
"testandroid": "npm run updatecert && ./scripts/build-test-app.sh --android --emulator && ./scripts/test-app.sh --android --emulator",
|
||||
"testios": "npm run updatecert && ./scripts/build-test-app.sh --ios --emulator && ./scripts/test-app.sh --ios --emulator",
|
||||
"testapp": "npm run testandroid && npm run testios",
|
||||
"testjs": "mocha ./test/js-mocha-specs.js",
|
||||
"testjs": "mocha ./test/js-specs.js",
|
||||
"test": "npm run testjs && npm run testapp",
|
||||
"release": "npm run test && ./scripts/release.sh"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-advanced-http" version="2.0.7">
|
||||
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-advanced-http" version="2.0.8">
|
||||
<name>Advanced HTTP plugin</name>
|
||||
<description>
|
||||
Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning
|
||||
@@ -11,6 +11,7 @@
|
||||
<js-module src="www/cookie-handler.js" name="cookie-handler"/>
|
||||
<js-module src="www/global-configs.js" name="global-configs"/>
|
||||
<js-module src="www/helpers.js" name="helpers"/>
|
||||
<js-module src="www/js-util.js" name="js-util"/>
|
||||
<js-module src="www/local-storage-store.js" name="local-storage-store"/>
|
||||
<js-module src="www/lodash.js" name="lodash"/>
|
||||
<js-module src="www/messages.js" name="messages"/>
|
||||
@@ -59,19 +60,21 @@
|
||||
<config-file target="AndroidManifest.xml" parent="/manifest">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</config-file>
|
||||
<source-file src="src/android/com/silkimen/cordovahttp/CordovaClientAuth.java" target-dir="src/com/silkimen/cordovahttp"/>
|
||||
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpBase.java" target-dir="src/com/silkimen/cordovahttp"/>
|
||||
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpDownload.java" target-dir="src/com/silkimen/cordovahttp"/>
|
||||
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpOperation.java" target-dir="src/com/silkimen/cordovahttp"/>
|
||||
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java" target-dir="src/com/silkimen/cordovahttp"/>
|
||||
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpResponse.java" target-dir="src/com/silkimen/cordovahttp"/>
|
||||
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpUpload.java" target-dir="src/com/silkimen/cordovahttp"/>
|
||||
<source-file src="src/android/com/silkimen/http/HostnameVerifierFactory.java" target-dir="src/com/silkimen/http"/>
|
||||
<source-file src="src/android/com/silkimen/cordovahttp/CordovaServerTrust.java" target-dir="src/com/silkimen/cordovahttp"/>
|
||||
<source-file src="src/android/com/silkimen/http/HttpBodyDecoder.java" target-dir="src/com/silkimen/http"/>
|
||||
<source-file src="src/android/com/silkimen/http/HttpRequest.java" target-dir="src/com/silkimen/http"/>
|
||||
<source-file src="src/android/com/silkimen/http/JsonUtils.java" target-dir="src/com/silkimen/http"/>
|
||||
<source-file src="src/android/com/silkimen/http/KeyChainKeyManager.java" target-dir="src/com/silkimen/http"/>
|
||||
<source-file src="src/android/com/silkimen/http/OkConnectionFactory.java" target-dir="src/com/silkimen/http"/>
|
||||
<source-file src="src/android/com/silkimen/http/TLSConfiguration.java" target-dir="src/com/silkimen/http"/>
|
||||
<source-file src="src/android/com/silkimen/http/TLSSocketFactory.java" target-dir="src/com/silkimen/http"/>
|
||||
<source-file src="src/android/com/silkimen/http/TrustManagersFactory.java" target-dir="src/com/silkimen/http"/>
|
||||
<framework src="com.squareup.okhttp3:okhttp-urlconnection:3.10.0"/>
|
||||
</platform>
|
||||
<platform name="browser">
|
||||
|
||||
@@ -35,10 +35,11 @@ while :; do
|
||||
shift
|
||||
done
|
||||
|
||||
printf 'Building test app for %s\n' $PLATFORM
|
||||
rm -rf $ROOT/temp
|
||||
mkdir $ROOT/temp
|
||||
cp -r $ROOT/test/app-template/. $ROOT/temp/
|
||||
cp $ROOT/test/app-test-definitions.js $ROOT/temp/www/
|
||||
cp -r $ROOT/test/e2e-app-template/. $ROOT/temp/
|
||||
cp $ROOT/test/e2e-specs.js $ROOT/temp/www/
|
||||
rsync -ax --exclude node_modules --exclude scripts --exclude temp --exclude test $ROOT/. $WORKINGCOPY
|
||||
cd $ROOT/temp
|
||||
$CDV prepare
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
while getopts a:b: option; do
|
||||
case "${option}" in
|
||||
a) API_LEVEL=${OPTARG};;
|
||||
b) BUILD_TOOLS_VERSION=${OPTARG};;
|
||||
esac
|
||||
done
|
||||
|
||||
curl http://dl.google.com/android/android-sdk_r24.4-macosx.zip -o android-sdk-macosx.zip
|
||||
tar -xvf android-sdk-macosx.zip
|
||||
|
||||
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter platform-tools
|
||||
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter build-tools-${BUILD_TOOLS_VERSION}
|
||||
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter android-${API_LEVEL}
|
||||
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter extra-android-support
|
||||
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter extra-android-m2repository
|
||||
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter extra-google-m2repository
|
||||
@@ -8,6 +8,7 @@ if [ $CI == "true" ] && ([ -z $SAUCE_USERNAME ] || [ -z $SAUCE_ACCESS_KEY ]); th
|
||||
exit 0;
|
||||
fi
|
||||
|
||||
printf 'Running e2e tests\n'
|
||||
pushd $ROOT
|
||||
./node_modules/.bin/mocha ./test/app-mocha-specs/test.js "$@"
|
||||
./node_modules/.bin/mocha ./test/e2e-tooling/test.js "$@"
|
||||
popd
|
||||
|
||||
@@ -3,7 +3,7 @@ const https = require('https');
|
||||
const path = require('path');
|
||||
|
||||
const SOURCE_HOST = 'httpbin.org';
|
||||
const TARGET_PATH = path.join(__dirname, '../test/app-template/www/certificates/httpbin.org.cer');
|
||||
const TARGET_PATH = path.join(__dirname, '../test/e2e-app-template/www/certificates/httpbin.org.cer');
|
||||
|
||||
const getCert = hostname => new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
|
||||
72
src/android/com/silkimen/cordovahttp/CordovaClientAuth.java
Normal file
72
src/android/com/silkimen/cordovahttp/CordovaClientAuth.java
Normal file
@@ -0,0 +1,72 @@
|
||||
package com.silkimen.cordovahttp;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.security.KeyChain;
|
||||
import android.security.KeyChainAliasCallback;
|
||||
import android.util.Log;
|
||||
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
|
||||
import org.apache.cordova.CallbackContext;
|
||||
|
||||
import com.silkimen.http.KeyChainKeyManager;
|
||||
import com.silkimen.http.TLSConfiguration;
|
||||
|
||||
class CordovaClientAuth implements Runnable, KeyChainAliasCallback {
|
||||
private static final String TAG = "Cordova-Plugin-HTTP";
|
||||
|
||||
private String mode;
|
||||
private String filePath;
|
||||
private Activity activity;
|
||||
private Context context;
|
||||
private TLSConfiguration tlsConfiguration;
|
||||
private CallbackContext callbackContext;
|
||||
|
||||
public CordovaClientAuth(final String mode, final String filePath, final Activity activity, final Context context,
|
||||
final TLSConfiguration configContainer, final CallbackContext callbackContext) {
|
||||
|
||||
this.mode = mode;
|
||||
this.filePath = filePath;
|
||||
this.activity = activity;
|
||||
this.tlsConfiguration = configContainer;
|
||||
this.context = context;
|
||||
this.callbackContext = callbackContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if ("systemstore".equals(this.mode)) {
|
||||
KeyChain.choosePrivateKeyAlias(this.activity, this, null, null, null, -1, null);
|
||||
} else if ("file".equals(this.mode)) {
|
||||
this.callbackContext.error("Not implemented, yet");
|
||||
} else {
|
||||
this.tlsConfiguration.setKeyManagers(null);
|
||||
this.callbackContext.success();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void alias(final String alias) {
|
||||
try {
|
||||
if (alias == null) {
|
||||
throw new Exception("Couldn't get a consent for private key access");
|
||||
}
|
||||
|
||||
PrivateKey key = KeyChain.getPrivateKey(this.context, alias);
|
||||
X509Certificate[] chain = KeyChain.getCertificateChain(this.context, alias);
|
||||
KeyManager keyManager = new KeyChainKeyManager(alias, key, chain);
|
||||
|
||||
this.tlsConfiguration.setKeyManagers(new KeyManager[] { keyManager });
|
||||
|
||||
this.callbackContext.success();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Couldn't load private key and certificate pair for authentication", e);
|
||||
this.callbackContext.error("Couldn't load private key and certificate pair for authentication");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.silkimen.cordovahttp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import java.net.SocketTimeoutException;
|
||||
@@ -7,15 +8,14 @@ import java.net.UnknownHostException;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.SSLException;
|
||||
|
||||
import com.silkimen.http.HttpBodyDecoder;
|
||||
import com.silkimen.http.HttpRequest;
|
||||
import com.silkimen.http.HttpRequest.HttpRequestException;
|
||||
import com.silkimen.http.JsonUtils;
|
||||
import com.silkimen.http.OkConnectionFactory;
|
||||
import com.silkimen.http.TLSConfiguration;
|
||||
|
||||
import org.apache.cordova.CallbackContext;
|
||||
|
||||
@@ -34,13 +34,11 @@ abstract class CordovaHttpBase implements Runnable {
|
||||
protected JSONObject headers;
|
||||
protected int timeout;
|
||||
protected boolean followRedirects;
|
||||
protected SSLSocketFactory customSSLSocketFactory;
|
||||
protected HostnameVerifier customHostnameVerifier;
|
||||
protected TLSConfiguration tlsConfiguration;
|
||||
protected CallbackContext callbackContext;
|
||||
|
||||
public CordovaHttpBase(String method, String url, String serializer, Object data, JSONObject headers, int timeout,
|
||||
boolean followRedirects, SSLSocketFactory customSSLSocketFactory, HostnameVerifier customHostnameVerifier,
|
||||
CallbackContext callbackContext) {
|
||||
boolean followRedirects, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
|
||||
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
@@ -49,22 +47,19 @@ abstract class CordovaHttpBase implements Runnable {
|
||||
this.headers = headers;
|
||||
this.timeout = timeout;
|
||||
this.followRedirects = followRedirects;
|
||||
this.customSSLSocketFactory = customSSLSocketFactory;
|
||||
this.customHostnameVerifier = customHostnameVerifier;
|
||||
this.tlsConfiguration = tlsConfiguration;
|
||||
this.callbackContext = callbackContext;
|
||||
}
|
||||
|
||||
public CordovaHttpBase(String method, String url, JSONObject headers, int timeout,
|
||||
boolean followRedirects, SSLSocketFactory customSSLSocketFactory, HostnameVerifier customHostnameVerifier,
|
||||
CallbackContext callbackContext) {
|
||||
public CordovaHttpBase(String method, String url, JSONObject headers, int timeout, boolean followRedirects,
|
||||
TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
|
||||
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.headers = headers;
|
||||
this.timeout = timeout;
|
||||
this.followRedirects = followRedirects;
|
||||
this.customSSLSocketFactory = customSSLSocketFactory;
|
||||
this.customHostnameVerifier = customHostnameVerifier;
|
||||
this.tlsConfiguration = tlsConfiguration;
|
||||
this.callbackContext = callbackContext;
|
||||
}
|
||||
|
||||
@@ -78,10 +73,10 @@ abstract class CordovaHttpBase implements Runnable {
|
||||
this.sendBody(request);
|
||||
this.processResponse(request, response);
|
||||
} catch (HttpRequestException e) {
|
||||
if (e.getCause() instanceof SSLHandshakeException) {
|
||||
if (e.getCause() instanceof SSLException) {
|
||||
response.setStatus(-2);
|
||||
response.setErrorMessage("SSL handshake failed: " + e.getMessage());
|
||||
Log.w(TAG, "SSL handshake failed", e);
|
||||
response.setErrorMessage("TLS connection could not be established: " + e.getMessage());
|
||||
Log.w(TAG, "TLS connection could not be established", e);
|
||||
} else if (e.getCause() instanceof UnknownHostException) {
|
||||
response.setStatus(-3);
|
||||
response.setErrorMessage("Host could not be resolved: " + e.getMessage());
|
||||
@@ -116,20 +111,18 @@ abstract class CordovaHttpBase implements Runnable {
|
||||
return new HttpRequest(this.url, this.method);
|
||||
}
|
||||
|
||||
protected void prepareRequest(HttpRequest request) throws JSONException {
|
||||
protected void prepareRequest(HttpRequest request) throws JSONException, IOException {
|
||||
request.followRedirects(this.followRedirects);
|
||||
request.readTimeout(this.timeout);
|
||||
request.acceptCharset("UTF-8");
|
||||
request.uncompress(true);
|
||||
request.setConnectionFactory(new OkConnectionFactory());
|
||||
|
||||
if (this.customHostnameVerifier != null) {
|
||||
request.setHostnameVerifier(this.customHostnameVerifier);
|
||||
if (this.tlsConfiguration.getHostnameVerifier() != null) {
|
||||
request.setHostnameVerifier(this.tlsConfiguration.getHostnameVerifier());
|
||||
}
|
||||
|
||||
if (this.customSSLSocketFactory != null) {
|
||||
request.setSSLSocketFactory(this.customSSLSocketFactory);
|
||||
}
|
||||
request.setSSLSocketFactory(this.tlsConfiguration.getTLSSocketFactory());
|
||||
|
||||
// setup content type before applying headers, so user can override it
|
||||
this.setContentType(request);
|
||||
@@ -138,16 +131,12 @@ abstract class CordovaHttpBase implements Runnable {
|
||||
}
|
||||
|
||||
protected void setContentType(HttpRequest request) {
|
||||
switch (this.serializer) {
|
||||
case "json":
|
||||
if ("json".equals(this.serializer)) {
|
||||
request.contentType("application/json", "UTF-8");
|
||||
break;
|
||||
case "utf8":
|
||||
} else if ("utf8".equals(this.serializer)) {
|
||||
request.contentType("text/plain", "UTF-8");
|
||||
break;
|
||||
case "urlencoded":
|
||||
} else if ("urlencoded".equals(this.serializer)) {
|
||||
// intentionally left blank, because content type is set in HttpRequest.form()
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,16 +145,12 @@ abstract class CordovaHttpBase implements Runnable {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (this.serializer) {
|
||||
case "json":
|
||||
if ("json".equals(this.serializer)) {
|
||||
request.send(this.data.toString());
|
||||
break;
|
||||
case "utf8":
|
||||
} else if ("utf8".equals(this.serializer)) {
|
||||
request.send(((JSONObject) this.data).getString("text"));
|
||||
break;
|
||||
case "urlencoded":
|
||||
} else if ("urlencoded".equals(this.serializer)) {
|
||||
request.form(JsonUtils.getObjectMap((JSONObject) this.data));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
import com.silkimen.http.HttpRequest;
|
||||
import com.silkimen.http.TLSConfiguration;
|
||||
|
||||
import org.apache.cordova.CallbackContext;
|
||||
import org.apache.cordova.file.FileUtils;
|
||||
@@ -15,12 +16,10 @@ import org.json.JSONObject;
|
||||
class CordovaHttpDownload extends CordovaHttpBase {
|
||||
private String filePath;
|
||||
|
||||
public CordovaHttpDownload(String url, JSONObject headers, String filePath, int timeout,
|
||||
boolean followRedirects, SSLSocketFactory customSSLSocketFactory, HostnameVerifier customHostnameVerifier,
|
||||
CallbackContext callbackContext) {
|
||||
public CordovaHttpDownload(String url, JSONObject headers, String filePath, int timeout, boolean followRedirects,
|
||||
TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
|
||||
|
||||
super("GET", url, headers, timeout, followRedirects, customSSLSocketFactory, customHostnameVerifier,
|
||||
callbackContext);
|
||||
super("GET", url, headers, timeout, followRedirects, tlsConfiguration, callbackContext);
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,23 +3,21 @@ package com.silkimen.cordovahttp;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
import com.silkimen.http.TLSConfiguration;
|
||||
|
||||
import org.apache.cordova.CallbackContext;
|
||||
import org.json.JSONObject;
|
||||
|
||||
class CordovaHttpOperation extends CordovaHttpBase {
|
||||
public CordovaHttpOperation(String method, String url, String serializer, Object data, JSONObject headers,
|
||||
int timeout, boolean followRedirects, SSLSocketFactory customSSLSocketFactory,
|
||||
HostnameVerifier customHostnameVerifier, CallbackContext callbackContext) {
|
||||
int timeout, boolean followRedirects, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
|
||||
|
||||
super(method, url, serializer, data, headers, timeout, followRedirects, customSSLSocketFactory,
|
||||
customHostnameVerifier, callbackContext);
|
||||
super(method, url, serializer, data, headers, timeout, followRedirects, tlsConfiguration, callbackContext);
|
||||
}
|
||||
|
||||
public CordovaHttpOperation(String method, String url, JSONObject headers, int timeout, boolean followRedirects,
|
||||
SSLSocketFactory customSSLSocketFactory, HostnameVerifier customHostnameVerifier,
|
||||
CallbackContext callbackContext) {
|
||||
TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
|
||||
|
||||
super(method, url, headers, timeout, followRedirects, customSSLSocketFactory, customHostnameVerifier,
|
||||
callbackContext);
|
||||
super(method, url, headers, timeout, followRedirects, tlsConfiguration, callbackContext);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,8 @@
|
||||
package com.silkimen.cordovahttp;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStore.TrustedCertificateEntry;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.Certificate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
|
||||
import com.silkimen.http.HostnameVerifierFactory;
|
||||
import com.silkimen.http.TLSSocketFactory;
|
||||
import com.silkimen.http.TrustManagersFactory;
|
||||
import com.silkimen.http.TLSConfiguration;
|
||||
|
||||
import org.apache.cordova.CallbackContext;
|
||||
import org.apache.cordova.CordovaInterface;
|
||||
@@ -31,26 +12,32 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
import android.util.Log;
|
||||
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
public class CordovaHttpPlugin extends CordovaPlugin {
|
||||
private static final String TAG = "Cordova-Plugin-HTTP";
|
||||
|
||||
private final TrustManagersFactory trustManagersFactory = new TrustManagersFactory();
|
||||
private final HostnameVerifierFactory hostnameVerifierFactory = new HostnameVerifierFactory();
|
||||
|
||||
private boolean followRedirects = true;
|
||||
private SSLSocketFactory customSSLSocketFactory;
|
||||
private HostnameVerifier customHostnameVerifier;
|
||||
private TLSConfiguration tlsConfiguration;
|
||||
|
||||
@Override
|
||||
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
|
||||
super.initialize(cordova, webView);
|
||||
|
||||
this.tlsConfiguration = new TLSConfiguration();
|
||||
|
||||
try {
|
||||
this.customSSLSocketFactory = this.createSocketFactory(
|
||||
this.trustManagersFactory.getPinnedTrustManagers(this.getCertsFromKeyStore("AndroidCAStore")));
|
||||
KeyStore store = KeyStore.getInstance("AndroidCAStore");
|
||||
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
|
||||
|
||||
store.load(null);
|
||||
tmf.init(store);
|
||||
|
||||
this.tlsConfiguration.setHostnameVerifier(null);
|
||||
this.tlsConfiguration.setTrustManagers(tmf.getTrustManagers());
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "An error occured while loading system's CA certificates", e);
|
||||
}
|
||||
@@ -64,28 +51,29 @@ public class CordovaHttpPlugin extends CordovaPlugin {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "get":
|
||||
if ("get".equals(action)) {
|
||||
return this.executeHttpRequestWithoutData(action, args, callbackContext);
|
||||
case "post":
|
||||
return this.executeHttpRequestWithData(action, args, callbackContext);
|
||||
case "put":
|
||||
return this.executeHttpRequestWithData(action, args, callbackContext);
|
||||
case "patch":
|
||||
return this.executeHttpRequestWithData(action, args, callbackContext);
|
||||
case "head":
|
||||
} else if ("head".equals(action)) {
|
||||
return this.executeHttpRequestWithoutData(action, args, callbackContext);
|
||||
case "delete":
|
||||
} else if ("delete".equals(action)) {
|
||||
return this.executeHttpRequestWithoutData(action, args, callbackContext);
|
||||
case "uploadFile":
|
||||
} else if ("post".equals(action)) {
|
||||
return this.executeHttpRequestWithData(action, args, callbackContext);
|
||||
} else if ("put".equals(action)) {
|
||||
return this.executeHttpRequestWithData(action, args, callbackContext);
|
||||
} else if ("patch".equals(action)) {
|
||||
return this.executeHttpRequestWithData(action, args, callbackContext);
|
||||
} else if ("uploadFile".equals(action)) {
|
||||
return this.uploadFile(args, callbackContext);
|
||||
case "downloadFile":
|
||||
} else if ("downloadFile".equals(action)) {
|
||||
return this.downloadFile(args, callbackContext);
|
||||
case "setSSLCertMode":
|
||||
return this.setSSLCertMode(args, callbackContext);
|
||||
case "disableRedirect":
|
||||
} else if ("setServerTrustMode".equals(action)) {
|
||||
return this.setServerTrustMode(args, callbackContext);
|
||||
} else if ("setClientAuthMode".equals(action)) {
|
||||
return this.setClientAuthMode(args, callbackContext);
|
||||
} else if ("disableRedirect".equals(action)) {
|
||||
return this.disableRedirect(args, callbackContext);
|
||||
default:
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -98,7 +86,7 @@ public class CordovaHttpPlugin extends CordovaPlugin {
|
||||
int timeout = args.getInt(2) * 1000;
|
||||
|
||||
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, headers, timeout,
|
||||
this.followRedirects, this.customSSLSocketFactory, this.customHostnameVerifier, callbackContext);
|
||||
this.followRedirects, this.tlsConfiguration, callbackContext);
|
||||
|
||||
cordova.getThreadPool().execute(request);
|
||||
|
||||
@@ -115,7 +103,7 @@ public class CordovaHttpPlugin extends CordovaPlugin {
|
||||
int timeout = args.getInt(4) * 1000;
|
||||
|
||||
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, serializer, data, headers,
|
||||
timeout, this.followRedirects, this.customSSLSocketFactory, this.customHostnameVerifier, callbackContext);
|
||||
timeout, this.followRedirects, this.tlsConfiguration, callbackContext);
|
||||
|
||||
cordova.getThreadPool().execute(request);
|
||||
|
||||
@@ -129,8 +117,8 @@ public class CordovaHttpPlugin extends CordovaPlugin {
|
||||
String uploadName = args.getString(3);
|
||||
int timeout = args.getInt(4) * 1000;
|
||||
|
||||
CordovaHttpUpload upload = new CordovaHttpUpload(url, headers, filePath, uploadName, timeout,
|
||||
this.followRedirects, this.customSSLSocketFactory, this.customHostnameVerifier, callbackContext);
|
||||
CordovaHttpUpload upload = new CordovaHttpUpload(url, headers, filePath, uploadName, timeout, this.followRedirects,
|
||||
this.tlsConfiguration, callbackContext);
|
||||
|
||||
cordova.getThreadPool().execute(upload);
|
||||
|
||||
@@ -143,42 +131,28 @@ public class CordovaHttpPlugin extends CordovaPlugin {
|
||||
String filePath = args.getString(2);
|
||||
int timeout = args.getInt(3) * 1000;
|
||||
|
||||
CordovaHttpDownload download = new CordovaHttpDownload(url, headers, filePath, timeout,
|
||||
this.followRedirects, this.customSSLSocketFactory, this.customHostnameVerifier, callbackContext);
|
||||
CordovaHttpDownload download = new CordovaHttpDownload(url, headers, filePath, timeout, this.followRedirects,
|
||||
this.tlsConfiguration, callbackContext);
|
||||
|
||||
cordova.getThreadPool().execute(download);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean setSSLCertMode(final JSONArray args, final CallbackContext callbackContext) {
|
||||
try {
|
||||
switch (args.getString(0)) {
|
||||
case "legacy":
|
||||
this.customHostnameVerifier = null;
|
||||
this.customSSLSocketFactory = null;
|
||||
break;
|
||||
case "nocheck":
|
||||
this.customHostnameVerifier = this.hostnameVerifierFactory.getNoOpVerifier();
|
||||
this.customSSLSocketFactory = this.createSocketFactory(this.trustManagersFactory.getNoopTrustManagers());
|
||||
break;
|
||||
case "pinned":
|
||||
this.customHostnameVerifier = null;
|
||||
this.customSSLSocketFactory = this.createSocketFactory(
|
||||
this.trustManagersFactory.getPinnedTrustManagers(this.getCertsFromBundle("www/certificates")));
|
||||
break;
|
||||
default:
|
||||
this.customHostnameVerifier = null;
|
||||
this.customSSLSocketFactory = this.createSocketFactory(
|
||||
this.trustManagersFactory.getPinnedTrustManagers(this.getCertsFromKeyStore("AndroidCAStore")));
|
||||
break;
|
||||
}
|
||||
private boolean setServerTrustMode(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
|
||||
CordovaServerTrust runnable = new CordovaServerTrust(args.getString(0), this.cordova.getActivity(),
|
||||
this.tlsConfiguration, callbackContext);
|
||||
|
||||
callbackContext.success();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "An error occured while configuring SSL cert mode", e);
|
||||
callbackContext.error("An error occured while configuring SSL cert mode");
|
||||
}
|
||||
cordova.getThreadPool().execute(runnable);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean setClientAuthMode(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
|
||||
CordovaClientAuth runnable = new CordovaClientAuth(args.getString(0), args.getString(1), this.cordova.getActivity(),
|
||||
this.cordova.getContext(), this.tlsConfiguration, callbackContext);
|
||||
|
||||
cordova.getThreadPool().execute(runnable);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -190,59 +164,4 @@ public class CordovaHttpPlugin extends CordovaPlugin {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private ArrayList<Certificate> getCertsFromKeyStore(String storeType) throws GeneralSecurityException, IOException {
|
||||
ArrayList<Certificate> certList = new ArrayList<Certificate>();
|
||||
KeyStore keyStore = KeyStore.getInstance(storeType);
|
||||
keyStore.load(null);
|
||||
|
||||
Enumeration<String> aliases = keyStore.aliases();
|
||||
|
||||
while (aliases.hasMoreElements()) {
|
||||
String alias = aliases.nextElement();
|
||||
TrustedCertificateEntry certEntry = (TrustedCertificateEntry) keyStore.getEntry(alias, null);
|
||||
Certificate cert = certEntry.getTrustedCertificate();
|
||||
certList.add(cert);
|
||||
}
|
||||
|
||||
return certList;
|
||||
}
|
||||
|
||||
private ArrayList<Certificate> getCertsFromBundle(String path) throws GeneralSecurityException, IOException {
|
||||
AssetManager assetManager = cordova.getActivity().getAssets();
|
||||
String[] files = assetManager.list(path);
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
ArrayList<Certificate> certList = new ArrayList<Certificate>();
|
||||
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
int index = files[i].lastIndexOf('.');
|
||||
|
||||
if (index == -1 || !files[i].substring(index).equals(".cer")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
certList.add(cf.generateCertificate(assetManager.open(path + "/" + files[i])));
|
||||
}
|
||||
|
||||
return certList;
|
||||
}
|
||||
|
||||
private SSLSocketFactory createSocketFactory(TrustManager[] trustManagers) throws IOException {
|
||||
try {
|
||||
SSLContext context = SSLContext.getInstance("TLS");
|
||||
|
||||
/* @TODO implement custom KeyManager */
|
||||
context.init(null, trustManagers, new SecureRandom());
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT < 20) {
|
||||
return new TLSSocketFactory(context);
|
||||
} else {
|
||||
return context.getSocketFactory();
|
||||
}
|
||||
} catch (GeneralSecurityException e) {
|
||||
IOException ioException = new IOException("Security exception occured while configuring SSL context");
|
||||
ioException.initCause(e);
|
||||
throw ioException;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import java.net.URI;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
import com.silkimen.http.TLSConfiguration;
|
||||
|
||||
import org.apache.cordova.CallbackContext;
|
||||
import org.json.JSONObject;
|
||||
|
||||
@@ -17,12 +19,10 @@ class CordovaHttpUpload extends CordovaHttpBase {
|
||||
private String filePath;
|
||||
private String uploadName;
|
||||
|
||||
public CordovaHttpUpload(String url, JSONObject headers, String filePath, String uploadName,
|
||||
int timeout, boolean followRedirects, SSLSocketFactory customSSLSocketFactory,
|
||||
HostnameVerifier customHostnameVerifier, CallbackContext callbackContext) {
|
||||
public CordovaHttpUpload(String url, JSONObject headers, String filePath, String uploadName, int timeout,
|
||||
boolean followRedirects, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
|
||||
|
||||
super("POST", url, headers, timeout, followRedirects, customSSLSocketFactory, customHostnameVerifier,
|
||||
callbackContext);
|
||||
super("POST", url, headers, timeout, followRedirects, tlsConfiguration, callbackContext);
|
||||
this.filePath = filePath;
|
||||
this.uploadName = uploadName;
|
||||
}
|
||||
|
||||
124
src/android/com/silkimen/cordovahttp/CordovaServerTrust.java
Normal file
124
src/android/com/silkimen/cordovahttp/CordovaServerTrust.java
Normal file
@@ -0,0 +1,124 @@
|
||||
package com.silkimen.cordovahttp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import com.silkimen.http.TLSConfiguration;
|
||||
|
||||
import org.apache.cordova.CallbackContext;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.util.Log;
|
||||
import android.content.res.AssetManager;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
class CordovaServerTrust implements Runnable {
|
||||
private static final String TAG = "Cordova-Plugin-HTTP";
|
||||
|
||||
private final TrustManager[] noOpTrustManagers;
|
||||
private final HostnameVerifier noOpVerifier;
|
||||
|
||||
private String mode;
|
||||
private Activity activity;
|
||||
private TLSConfiguration tlsConfiguration;
|
||||
private CallbackContext callbackContext;
|
||||
|
||||
public CordovaServerTrust(final String mode, final Activity activity, final TLSConfiguration configContainer,
|
||||
final CallbackContext callbackContext) {
|
||||
|
||||
this.mode = mode;
|
||||
this.activity = activity;
|
||||
this.tlsConfiguration = configContainer;
|
||||
this.callbackContext = callbackContext;
|
||||
|
||||
this.noOpTrustManagers = new TrustManager[] { new X509TrustManager() {
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[0];
|
||||
}
|
||||
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) {
|
||||
// intentionally left blank
|
||||
}
|
||||
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType) {
|
||||
// intentionally left blank
|
||||
}
|
||||
} };
|
||||
|
||||
this.noOpVerifier = new HostnameVerifier() {
|
||||
public boolean verify(String hostname, SSLSession session) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if ("legacy".equals(this.mode)) {
|
||||
this.tlsConfiguration.setHostnameVerifier(null);
|
||||
this.tlsConfiguration.setTrustManagers(null);
|
||||
} else if ("nocheck".equals(this.mode)) {
|
||||
this.tlsConfiguration.setHostnameVerifier(this.noOpVerifier);
|
||||
this.tlsConfiguration.setTrustManagers(this.noOpTrustManagers);
|
||||
} else if ("pinned".equals(this.mode)) {
|
||||
this.tlsConfiguration.setHostnameVerifier(null);
|
||||
this.tlsConfiguration.setTrustManagers(this.getTrustManagers(this.getCertsFromBundle("www/certificates")));
|
||||
} else {
|
||||
this.tlsConfiguration.setHostnameVerifier(null);
|
||||
this.tlsConfiguration.setTrustManagers(this.getTrustManagers(this.getCertsFromKeyStore("AndroidCAStore")));
|
||||
}
|
||||
|
||||
callbackContext.success();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "An error occured while configuring SSL cert mode", e);
|
||||
callbackContext.error("An error occured while configuring SSL cert mode");
|
||||
}
|
||||
}
|
||||
|
||||
private TrustManager[] getTrustManagers(KeyStore store) throws GeneralSecurityException {
|
||||
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
|
||||
tmf.init(store);
|
||||
|
||||
return tmf.getTrustManagers();
|
||||
}
|
||||
|
||||
private KeyStore getCertsFromBundle(String path) throws GeneralSecurityException, IOException {
|
||||
AssetManager assetManager = this.activity.getAssets();
|
||||
String[] files = assetManager.list(path);
|
||||
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
String keyStoreType = KeyStore.getDefaultType();
|
||||
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
|
||||
|
||||
keyStore.load(null, null);
|
||||
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
int index = files[i].lastIndexOf('.');
|
||||
|
||||
if (index == -1 || !files[i].substring(index).equals(".cer")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
keyStore.setCertificateEntry("CA" + i, cf.generateCertificate(assetManager.open(path + "/" + files[i])));
|
||||
}
|
||||
|
||||
return keyStore;
|
||||
}
|
||||
|
||||
private KeyStore getCertsFromKeyStore(String storeType) throws GeneralSecurityException, IOException {
|
||||
KeyStore store = KeyStore.getInstance(storeType);
|
||||
store.load(null);
|
||||
|
||||
return store;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.silkimen.http;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLSession;
|
||||
|
||||
public class HostnameVerifierFactory {
|
||||
private final HostnameVerifier noOpVerifier;
|
||||
|
||||
public HostnameVerifierFactory() {
|
||||
this.noOpVerifier = new HostnameVerifier() {
|
||||
public boolean verify(String hostname, SSLSession session) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public HostnameVerifier getNoOpVerifier() {
|
||||
return this.noOpVerifier;
|
||||
}
|
||||
}
|
||||
57
src/android/com/silkimen/http/KeyChainKeyManager.java
Normal file
57
src/android/com/silkimen/http/KeyChainKeyManager.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package com.silkimen.http;
|
||||
|
||||
import android.content.Context;
|
||||
import android.security.KeyChain;
|
||||
|
||||
import java.net.Socket;
|
||||
import java.security.Principal;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.X509ExtendedKeyManager;
|
||||
|
||||
public class KeyChainKeyManager extends X509ExtendedKeyManager {
|
||||
private final String alias;
|
||||
private final X509Certificate[] chain;
|
||||
private final PrivateKey key;
|
||||
|
||||
public KeyChainKeyManager(String alias, PrivateKey key, X509Certificate[] chain) {
|
||||
this.alias = alias;
|
||||
this.key = key;
|
||||
this.chain = chain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {
|
||||
return this.alias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getCertificateChain(String alias) {
|
||||
return chain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrivateKey getPrivateKey(String alias) {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
|
||||
// not a client SSLSocket callback
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String[] getClientAliases(String keyType, Principal[] issuers) {
|
||||
// not a client SSLSocket callback
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String[] getServerAliases(String keyType, Principal[] issuers) {
|
||||
// not a client SSLSocket callback
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
69
src/android/com/silkimen/http/TLSConfiguration.java
Normal file
69
src/android/com/silkimen/http/TLSConfiguration.java
Normal file
@@ -0,0 +1,69 @@
|
||||
package com.silkimen.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import com.silkimen.http.TLSSocketFactory;
|
||||
|
||||
public class TLSConfiguration {
|
||||
private TrustManager[] trustManagers;
|
||||
private KeyManager[] keyManagers;
|
||||
private HostnameVerifier hostnameVerifier;
|
||||
|
||||
private SSLSocketFactory socketFactory;
|
||||
|
||||
public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
|
||||
this.hostnameVerifier = hostnameVerifier;
|
||||
}
|
||||
|
||||
public void setKeyManagers(KeyManager[] keyManagers) {
|
||||
this.keyManagers = keyManagers;
|
||||
this.socketFactory = null;
|
||||
}
|
||||
|
||||
public void setTrustManagers(TrustManager[] trustManagers) {
|
||||
this.trustManagers = trustManagers;
|
||||
this.socketFactory = null;
|
||||
}
|
||||
|
||||
public HostnameVerifier getHostnameVerifier() {
|
||||
return this.hostnameVerifier;
|
||||
}
|
||||
|
||||
public SSLSocketFactory getTLSSocketFactory() throws IOException {
|
||||
if (this.socketFactory != null) {
|
||||
return this.socketFactory;
|
||||
}
|
||||
|
||||
try {
|
||||
SSLContext context = SSLContext.getInstance("TLS");
|
||||
|
||||
context.init(this.keyManagers, this.trustManagers, new SecureRandom());
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT < 20) {
|
||||
this.socketFactory = new TLSSocketFactory(context);
|
||||
} else {
|
||||
this.socketFactory = context.getSocketFactory();
|
||||
}
|
||||
|
||||
return this.socketFactory;
|
||||
} catch (GeneralSecurityException e) {
|
||||
IOException ioException = new IOException("Security exception occured while configuring TLS context");
|
||||
ioException.initCause(e);
|
||||
throw ioException;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,8 @@ public class TLSSocketFactory extends SSLSocketFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
|
||||
return enableTLSOnSocket(delegate.createSocket(s, host, port, autoClose));
|
||||
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
|
||||
return enableTLSOnSocket(delegate.createSocket(socket, host, port, autoClose));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
package com.silkimen.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
public class TrustManagersFactory {
|
||||
private final TrustManager[] noOpTrustManager;
|
||||
|
||||
public TrustManagersFactory() {
|
||||
this.noOpTrustManager = new TrustManager[] { new X509TrustManager() {
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[0];
|
||||
}
|
||||
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) {
|
||||
// intentionally left blank
|
||||
}
|
||||
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType) {
|
||||
// intentionally left blank
|
||||
}
|
||||
} };
|
||||
}
|
||||
|
||||
public TrustManager[] getNoopTrustManagers() {
|
||||
return this.noOpTrustManager;
|
||||
}
|
||||
|
||||
public TrustManager[] getPinnedTrustManagers(ArrayList<Certificate> pinnedCerts) throws IOException {
|
||||
if (pinnedCerts == null || pinnedCerts.size() == 0) {
|
||||
throw new IOException("You must add at least 1 certificate in order to pin to certificates");
|
||||
}
|
||||
|
||||
try {
|
||||
String keyStoreType = KeyStore.getDefaultType();
|
||||
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
|
||||
keyStore.load(null, null);
|
||||
|
||||
for (int i = 0; i < pinnedCerts.size(); i++) {
|
||||
keyStore.setCertificateEntry("CA" + i, pinnedCerts.get(i));
|
||||
}
|
||||
|
||||
// Create a TrustManager that trusts the CAs in our KeyStore
|
||||
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
|
||||
tmf.init(keyStore);
|
||||
|
||||
return tmf.getTrustManagers();
|
||||
} catch (GeneralSecurityException e) {
|
||||
IOException ioException = new IOException("Security exception configuring SSL trust managers");
|
||||
ioException.initCause(e);
|
||||
|
||||
throw ioException;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/browser/cordova-http-plugin.js
vendored
15
src/browser/cordova-http-plugin.js
vendored
@@ -1,7 +1,7 @@
|
||||
var pluginId = module.id.slice(0, module.id.lastIndexOf('.'));
|
||||
|
||||
var cordovaProxy = require('cordova/exec/proxy');
|
||||
var helpers = require(pluginId + '.helpers');
|
||||
var jsUtil = require(pluginId + '.js-util');
|
||||
|
||||
function serializeJsonData(data) {
|
||||
try {
|
||||
@@ -29,7 +29,7 @@ function serializeParams(params) {
|
||||
if (params === null) return '';
|
||||
|
||||
return Object.keys(params).map(function(key) {
|
||||
if (helpers.getTypeOf(params[key]) === 'Array') {
|
||||
if (jsUtil.getTypeOf(params[key]) === 'Array') {
|
||||
return serializeArray(key, params[key]);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ function createXhrSuccessObject(xhr) {
|
||||
return {
|
||||
url: xhr.responseURL,
|
||||
status: xhr.status,
|
||||
data: helpers.getTypeOf(xhr.responseText) === 'String' ? xhr.responseText : xhr.response,
|
||||
data: jsUtil.getTypeOf(xhr.responseText) === 'String' ? xhr.responseText : xhr.response,
|
||||
headers: deserializeResponseHeaders(xhr.getAllResponseHeaders())
|
||||
};
|
||||
}
|
||||
@@ -65,7 +65,7 @@ function createXhrFailureObject(xhr) {
|
||||
var obj = {};
|
||||
|
||||
obj.headers = xhr.getAllResponseHeaders();
|
||||
obj.error = helpers.getTypeOf(xhr.responseText) === 'String' ? xhr.responseText : xhr.response;
|
||||
obj.error = jsUtil.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;
|
||||
@@ -184,8 +184,11 @@ var browserInterface = {
|
||||
downloadFile: function (success, failure, opts) {
|
||||
return failure('advanced-http: function "downloadFile" not supported on browser platform');
|
||||
},
|
||||
setSSLCertMode: function (success, failure, opts) {
|
||||
return failure('advanced-http: function "setSSLCertMode" not supported on browser platform');
|
||||
setServerTrustMode: function (success, failure, opts) {
|
||||
return failure('advanced-http: function "setServerTrustMode" not supported on browser platform');
|
||||
},
|
||||
setClientAuthMode: function (success, failure, opts) {
|
||||
return failure('advanced-http: function "setClientAuthMode" not supported on browser platform');
|
||||
},
|
||||
disableRedirect: function (success, failure, opts) {
|
||||
return failure('advanced-http: function "disableRedirect" not supported on browser platform');
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
@interface CordovaHttpPlugin : CDVPlugin
|
||||
|
||||
- (void)setSSLCertMode:(CDVInvokedUrlCommand*)command;
|
||||
- (void)setServerTrustMode:(CDVInvokedUrlCommand*)command;
|
||||
- (void)disableRedirect:(CDVInvokedUrlCommand*)command;
|
||||
- (void)post:(CDVInvokedUrlCommand*)command;
|
||||
- (void)get:(CDVInvokedUrlCommand*)command;
|
||||
|
||||
@@ -20,12 +20,10 @@
|
||||
@implementation CordovaHttpPlugin {
|
||||
AFSecurityPolicy *securityPolicy;
|
||||
bool redirect;
|
||||
AFHTTPSessionManager *manager;
|
||||
}
|
||||
|
||||
- (void)pluginInitialize {
|
||||
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
|
||||
manager = [AFHTTPSessionManager manager];
|
||||
redirect = true;
|
||||
}
|
||||
|
||||
@@ -106,8 +104,12 @@
|
||||
case -1009:
|
||||
// no connection
|
||||
return [NSNumber numberWithInt:-6];
|
||||
case -1202:
|
||||
// untrusted SSL certificate
|
||||
case -1200: // secure connection failed
|
||||
case -1201: // certificate has bad date
|
||||
case -1202: // certificate untrusted
|
||||
case -1203: // certificate has unknown root
|
||||
case -1204: // certificate is not yet valid
|
||||
// configuring SSL failed
|
||||
return [NSNumber numberWithInt:-2];
|
||||
default:
|
||||
return [NSNumber numberWithInt:-1];
|
||||
@@ -126,7 +128,7 @@
|
||||
return headerFieldsCopy;
|
||||
}
|
||||
|
||||
- (void)setSSLCertMode:(CDVInvokedUrlCommand*)command {
|
||||
- (void)setServerTrustMode:(CDVInvokedUrlCommand*)command {
|
||||
NSString *certMode = [command.arguments objectAtIndex:0];
|
||||
|
||||
if ([certMode isEqualToString: @"default"] || [certMode isEqualToString: @"legacy"]) {
|
||||
@@ -162,6 +164,7 @@
|
||||
}
|
||||
|
||||
- (void)get:(CDVInvokedUrlCommand*)command {
|
||||
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
|
||||
manager.securityPolicy = securityPolicy;
|
||||
|
||||
NSString *url = [command.arguments objectAtIndex:0];
|
||||
@@ -201,6 +204,7 @@
|
||||
}
|
||||
|
||||
- (void)head:(CDVInvokedUrlCommand*)command {
|
||||
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
|
||||
manager.securityPolicy = securityPolicy;
|
||||
|
||||
NSString *url = [command.arguments objectAtIndex:0];
|
||||
@@ -240,6 +244,7 @@
|
||||
}
|
||||
|
||||
- (void)delete:(CDVInvokedUrlCommand*)command {
|
||||
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
|
||||
manager.securityPolicy = securityPolicy;
|
||||
|
||||
NSString *url = [command.arguments objectAtIndex:0];
|
||||
@@ -279,6 +284,7 @@
|
||||
}
|
||||
|
||||
- (void)post:(CDVInvokedUrlCommand*)command {
|
||||
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
|
||||
manager.securityPolicy = securityPolicy;
|
||||
|
||||
NSString *url = [command.arguments objectAtIndex:0];
|
||||
@@ -320,6 +326,7 @@
|
||||
}
|
||||
|
||||
- (void)put:(CDVInvokedUrlCommand*)command {
|
||||
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
|
||||
manager.securityPolicy = securityPolicy;
|
||||
|
||||
NSString *url = [command.arguments objectAtIndex:0];
|
||||
@@ -361,6 +368,7 @@
|
||||
}
|
||||
|
||||
- (void)patch:(CDVInvokedUrlCommand*)command {
|
||||
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
|
||||
manager.securityPolicy = securityPolicy;
|
||||
|
||||
NSString *url = [command.arguments objectAtIndex:0];
|
||||
@@ -402,6 +410,7 @@
|
||||
}
|
||||
|
||||
- (void)uploadFile:(CDVInvokedUrlCommand*)command {
|
||||
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
|
||||
manager.securityPolicy = securityPolicy;
|
||||
|
||||
NSString *url = [command.arguments objectAtIndex:0];
|
||||
@@ -456,6 +465,7 @@
|
||||
}
|
||||
|
||||
- (void)downloadFile:(CDVInvokedUrlCommand*)command {
|
||||
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
|
||||
manager.securityPolicy = securityPolicy;
|
||||
|
||||
NSString *url = [command.arguments objectAtIndex:0];
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<button id="nextBtn">Run next test</button>
|
||||
|
||||
<script type="text/javascript" src="cordova.js"></script>
|
||||
<script type="text/javascript" src="app-test-definitions.js"></script>
|
||||
<script type="text/javascript" src="e2e-specs.js"></script>
|
||||
<script type="text/javascript" src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,14 +1,14 @@
|
||||
const hooks = {
|
||||
onBeforeEachTest: function(done) {
|
||||
cordova.plugin.http.clearCookies();
|
||||
helpers.setDefaultCertMode(done);
|
||||
helpers.setDefaultServerTrustMode(done);
|
||||
}
|
||||
};
|
||||
|
||||
const helpers = {
|
||||
setDefaultCertMode: function(done) { cordova.plugin.http.setSSLCertMode('default', done, done); },
|
||||
setNoCheckCertMode: function(done) { cordova.plugin.http.setSSLCertMode('nocheck', done, done); },
|
||||
setPinnedCertMode: function(done) { cordova.plugin.http.setSSLCertMode('pinned', done, done); },
|
||||
setDefaultServerTrustMode: function(done) { cordova.plugin.http.setServerTrustMode('default', done, done); },
|
||||
setNoCheckServerTrustMode: function(done) { cordova.plugin.http.setServerTrustMode('nocheck', done, done); },
|
||||
setPinnedServerTrustMode: function(done) { cordova.plugin.http.setServerTrustMode('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')); },
|
||||
@@ -38,7 +38,7 @@ const helpers = {
|
||||
};
|
||||
|
||||
const messageFactory = {
|
||||
sslTrustAnchor: function() { return 'SSL handshake failed: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.' },
|
||||
sslTrustAnchor: function() { return 'TLS connection could not be established: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.' },
|
||||
invalidCertificate: function(domain) { return 'The certificate for this server is invalid. You might be connecting to a server that is pretending to be “' + domain + '” which could put your confidential information at risk.' }
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ const tests = [
|
||||
{
|
||||
description: 'should accept bad cert (GET)',
|
||||
expected: 'resolved: {"status":200, ...',
|
||||
before: helpers.setNoCheckCertMode,
|
||||
before: helpers.setNoCheckServerTrustMode,
|
||||
func: function(resolve, reject) { cordova.plugin.http.get('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
|
||||
validationFunc: function(driver, result) {
|
||||
result.type.should.be.equal('resolved');
|
||||
@@ -101,7 +101,7 @@ const tests = [
|
||||
{
|
||||
description: 'should accept bad cert (PUT)',
|
||||
expected: 'rejected: {"status":405, ... // will be rejected because PUT is not allowed',
|
||||
before: helpers.setNoCheckCertMode,
|
||||
before: helpers.setNoCheckServerTrustMode,
|
||||
func: function(resolve, reject) { cordova.plugin.http.put('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
|
||||
validationFunc: function(driver, result) {
|
||||
result.type.should.be.equal('rejected');
|
||||
@@ -111,7 +111,7 @@ const tests = [
|
||||
{
|
||||
description: 'should accept bad cert (POST)',
|
||||
expected: 'rejected: {"status":405, ... // will be rejected because POST is not allowed',
|
||||
before: helpers.setNoCheckCertMode,
|
||||
before: helpers.setNoCheckServerTrustMode,
|
||||
func: function(resolve, reject) { cordova.plugin.http.post('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
|
||||
validationFunc: function(driver, result) {
|
||||
result.type.should.be.equal('rejected');
|
||||
@@ -121,7 +121,7 @@ const tests = [
|
||||
{
|
||||
description: 'should accept bad cert (PATCH)',
|
||||
expected: 'rejected: {"status":405, ... // will be rejected because PATCH is not allowed',
|
||||
before: helpers.setNoCheckCertMode,
|
||||
before: helpers.setNoCheckServerTrustMode,
|
||||
func: function(resolve, reject) { cordova.plugin.http.patch('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
|
||||
validationFunc: function(driver, result) {
|
||||
result.type.should.be.equal('rejected');
|
||||
@@ -131,7 +131,7 @@ const tests = [
|
||||
{
|
||||
description: 'should accept bad cert (DELETE)',
|
||||
expected: 'rejected: {"status":405, ... // will be rejected because DELETE is not allowed',
|
||||
before: helpers.setNoCheckCertMode,
|
||||
before: helpers.setNoCheckServerTrustMode,
|
||||
func: function(resolve, reject) { cordova.plugin.http.delete('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
|
||||
validationFunc: function(driver, result) {
|
||||
result.type.should.be.equal('rejected');
|
||||
@@ -141,7 +141,7 @@ const tests = [
|
||||
{
|
||||
description: 'should fetch data from http://httpbin.org/ (GET)',
|
||||
expected: 'resolved: {"status":200, ...',
|
||||
before: helpers.setNoCheckCertMode,
|
||||
before: helpers.setNoCheckServerTrustMode,
|
||||
func: function(resolve, reject) { cordova.plugin.http.get('http://httpbin.org/', {}, {}, resolve, reject); },
|
||||
validationFunc: function(driver, result) {
|
||||
result.type.should.be.equal('resolved');
|
||||
@@ -383,8 +383,7 @@ const tests = [
|
||||
JSON
|
||||
.parse(result.data.data)
|
||||
.headers
|
||||
.Cookie
|
||||
.should.be.equal('');
|
||||
.should.not.have.property('Cookie');
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -468,7 +467,7 @@ const tests = [
|
||||
{
|
||||
description: 'should pin SSL cert correctly (GET)',
|
||||
expected: 'resolved: {"status": 200 ...',
|
||||
before: helpers.setPinnedCertMode,
|
||||
before: helpers.setPinnedServerTrustMode,
|
||||
func: function(resolve, reject) {
|
||||
cordova.plugin.http.get('https://httpbin.org', {}, {}, resolve, reject);
|
||||
},
|
||||
@@ -480,7 +479,7 @@ const tests = [
|
||||
{
|
||||
description: 'should reject when pinned cert does not match received server cert (GET)',
|
||||
expected: 'rejected: {"status": -2 ...',
|
||||
before: helpers.setPinnedCertMode,
|
||||
before: helpers.setPinnedServerTrustMode,
|
||||
func: function(resolve, reject) {
|
||||
cordova.plugin.http.get('https://sha512.badssl.com/', {}, {}, resolve, reject);
|
||||
},
|
||||
@@ -4,7 +4,7 @@ const wd = require('wd');
|
||||
const apps = require('./helpers/apps');
|
||||
const caps = Object.assign({}, require('./helpers/caps'));
|
||||
const serverConfig = require('./helpers/server');
|
||||
const testDefinitions = require('../app-test-definitions');
|
||||
const testDefinitions = require('../e2e-specs');
|
||||
const pkgjson = require('../../package.json');
|
||||
|
||||
describe('Advanced HTTP', function() {
|
||||
@@ -10,12 +10,13 @@ describe('Advanced HTTP public interface', function () {
|
||||
const getDependenciesBlueprint = () => {
|
||||
const messages = require('../www/messages');
|
||||
const globalConfigs = require('../www/global-configs');
|
||||
const jsUtil = require('../www/js-util');
|
||||
const ToughCookie = require('../www/umd-tough-cookie');
|
||||
const lodash = require('../www/lodash');
|
||||
const WebStorageCookieStore = require('../www/local-storage-store')(ToughCookie, lodash);
|
||||
const cookieHandler = require('../www/cookie-handler')(null, ToughCookie, WebStorageCookieStore);
|
||||
const helpers = require('../www/helpers')(cookieHandler, messages);
|
||||
const urlUtil = require('../www/url-util')(helpers);
|
||||
const helpers = require('../www/helpers')(jsUtil, cookieHandler, messages);
|
||||
const urlUtil = require('../www/url-util')(jsUtil);
|
||||
|
||||
return { exec: noop, cookieHandler, urlUtil: urlUtil, helpers, globalConfigs };
|
||||
};
|
||||
@@ -131,8 +132,8 @@ describe('Advanced HTTP public interface', function () {
|
||||
});
|
||||
|
||||
describe('URL util', function () {
|
||||
const helpers = require('../www/helpers')(null, null);
|
||||
const util = require('../www/url-util')(helpers);
|
||||
const jsUtil = require('../www/js-util');
|
||||
const util = require('../www/url-util')(jsUtil);
|
||||
|
||||
it('parses URL with protocol, hostname and path correctly', () => {
|
||||
util.parseUrl('http://ilkimen.net/test').should.include({
|
||||
@@ -234,3 +235,30 @@ describe('URL util', function () {
|
||||
.should.equal('http://ilkimen.net/?myParam=myValue¶m1=value1#myHash');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Common helpers', function () {
|
||||
describe('mergeHeaders(globalHeaders, localHeaders)', function () {
|
||||
const init = require('../www/helpers');
|
||||
init.debug = true;
|
||||
|
||||
const helpers = init(null, null, null);
|
||||
|
||||
it('merges empty header sets correctly', () => {
|
||||
helpers.mergeHeaders({}, {}).should.eql({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCookieHeader(url)', function () {
|
||||
it('resolves cookie header correctly when no cookie is set #198', () => {
|
||||
const helpers = require('../www/helpers')(null, { getCookieString: () => '' }, null);
|
||||
|
||||
helpers.getCookieHeader('http://ilkimen.net').should.eql({});
|
||||
});
|
||||
|
||||
it('resolves cookie header correctly when a cookie is set', () => {
|
||||
const helpers = require('../www/helpers')(null, { getCookieString: () => 'cookie=value' }, null);
|
||||
|
||||
helpers.getCookieHeader('http://ilkimen.net').should.eql({ Cookie: 'cookie=value' });
|
||||
});
|
||||
});
|
||||
})
|
||||
@@ -7,12 +7,13 @@ var pluginId = module.id.slice(0, module.id.lastIndexOf('.'));
|
||||
var exec = require('cordova/exec');
|
||||
var messages = require(pluginId + '.messages');
|
||||
var globalConfigs = require(pluginId + '.global-configs');
|
||||
var jsUtil = require(pluginId + '.js-util');
|
||||
var ToughCookie = require(pluginId + '.tough-cookie');
|
||||
var lodash = require(pluginId + '.lodash');
|
||||
var WebStorageCookieStore = require(pluginId + '.local-storage-store')(ToughCookie, lodash);
|
||||
var cookieHandler = require(pluginId + '.cookie-handler')(window.localStorage, ToughCookie, WebStorageCookieStore);
|
||||
var helpers = require(pluginId + '.helpers')(cookieHandler, messages);
|
||||
var urlUtil = require(pluginId + '.url-util')(helpers);
|
||||
var helpers = require(pluginId + '.helpers')(jsUtil, cookieHandler, messages);
|
||||
var urlUtil = require(pluginId + '.url-util')(jsUtil);
|
||||
var publicInterface = require(pluginId + '.public-interface')(exec, cookieHandler, urlUtil, helpers, globalConfigs);
|
||||
|
||||
module.exports = publicInterface;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
module.exports = function init(cookieHandler, messages) {
|
||||
module.exports = function init(jsUtil, cookieHandler, messages) {
|
||||
var validSerializers = ['urlencoded', 'json', 'utf8'];
|
||||
var validCertModes = ['default', 'nocheck', 'pinned', 'legacy'];
|
||||
var validClientAuthModes = ['none', 'systemstore', 'file'];
|
||||
var validHttpMethods = ['get', 'put', 'post', 'patch', 'head', 'delete', 'upload', 'download'];
|
||||
|
||||
return {
|
||||
var interface = {
|
||||
b64EncodeUnicode: b64EncodeUnicode,
|
||||
getTypeOf: getTypeOf,
|
||||
checkSerializer: checkSerializer,
|
||||
checkSSLCertMode: checkSSLCertMode,
|
||||
checkClientAuthMode: checkClientAuthMode,
|
||||
checkForBlacklistedHeaderKey: checkForBlacklistedHeaderKey,
|
||||
checkForInvalidHeaderValue: checkForInvalidHeaderValue,
|
||||
injectCookieHandler: injectCookieHandler,
|
||||
@@ -18,6 +19,24 @@ module.exports = function init(cookieHandler, messages) {
|
||||
handleMissingOptions: handleMissingOptions
|
||||
};
|
||||
|
||||
// expose all functions for testing purposes
|
||||
if (init.debug) {
|
||||
interface.mergeHeaders = mergeHeaders;
|
||||
interface.checkForValidStringValue = checkForValidStringValue;
|
||||
interface.checkKeyValuePairObject = checkKeyValuePairObject;
|
||||
interface.checkHttpMethod = checkHttpMethod;
|
||||
interface.checkTimeoutValue = checkTimeoutValue;
|
||||
interface.checkHeadersObject = checkHeadersObject;
|
||||
interface.checkParamsObject = checkParamsObject;
|
||||
interface.resolveCookieString = resolveCookieString;
|
||||
interface.createFileEntry = createFileEntry;
|
||||
interface.getCookieHeader = getCookieHeader;
|
||||
interface.getMatchingHostHeaders = getMatchingHostHeaders;
|
||||
interface.getAllowedDataTypes = getAllowedDataTypes;
|
||||
}
|
||||
|
||||
return interface;
|
||||
|
||||
// Thanks Mozilla: https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_.22Unicode_Problem.22
|
||||
function b64EncodeUnicode(str) {
|
||||
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
|
||||
@@ -41,7 +60,7 @@ module.exports = function init(cookieHandler, messages) {
|
||||
}
|
||||
|
||||
function checkForValidStringValue(list, value, onInvalidValueMessage) {
|
||||
if (getTypeOf(value) !== 'String') {
|
||||
if (jsUtil.getTypeOf(value) !== 'String') {
|
||||
throw new Error(onInvalidValueMessage + ' ' + list.join(', '));
|
||||
}
|
||||
|
||||
@@ -55,14 +74,14 @@ module.exports = function init(cookieHandler, messages) {
|
||||
}
|
||||
|
||||
function checkKeyValuePairObject(obj, allowedChildren, onInvalidValueMessage) {
|
||||
if (getTypeOf(obj) !== 'Object') {
|
||||
if (jsUtil.getTypeOf(obj) !== 'Object') {
|
||||
throw new Error(onInvalidValueMessage);
|
||||
}
|
||||
|
||||
var keys = Object.keys(obj);
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (allowedChildren.indexOf(getTypeOf(obj[keys[i]])) === -1) {
|
||||
if (allowedChildren.indexOf(jsUtil.getTypeOf(obj[keys[i]])) === -1) {
|
||||
throw new Error(onInvalidValueMessage);
|
||||
}
|
||||
}
|
||||
@@ -82,6 +101,10 @@ module.exports = function init(cookieHandler, messages) {
|
||||
return checkForValidStringValue(validCertModes, mode, messages.INVALID_SSL_CERT_MODE);
|
||||
}
|
||||
|
||||
function checkClientAuthMode(mode) {
|
||||
return checkForValidStringValue(validClientAuthModes, mode, messages.INVALID_CLIENT_AUTH_MODE);
|
||||
}
|
||||
|
||||
function checkForBlacklistedHeaderKey(key) {
|
||||
if (key.toLowerCase() === 'cookie') {
|
||||
throw new Error(messages.ADDING_COOKIES_NOT_SUPPORTED);
|
||||
@@ -91,7 +114,7 @@ module.exports = function init(cookieHandler, messages) {
|
||||
}
|
||||
|
||||
function checkForInvalidHeaderValue(value) {
|
||||
if (getTypeOf(value) !== 'String') {
|
||||
if (jsUtil.getTypeOf(value) !== 'String') {
|
||||
throw new Error(messages.INVALID_HEADERS_VALUE);
|
||||
}
|
||||
|
||||
@@ -99,7 +122,7 @@ module.exports = function init(cookieHandler, messages) {
|
||||
}
|
||||
|
||||
function checkTimeoutValue(timeout) {
|
||||
if (getTypeOf(timeout) !== 'Number' || timeout < 0) {
|
||||
if (jsUtil.getTypeOf(timeout) !== 'Number' || timeout < 0) {
|
||||
throw new Error(messages.INVALID_TIMEOUT_VALUE);
|
||||
}
|
||||
|
||||
@@ -153,7 +176,13 @@ module.exports = function init(cookieHandler, messages) {
|
||||
}
|
||||
|
||||
function getCookieHeader(url) {
|
||||
return { Cookie: cookieHandler.getCookieString(url) };
|
||||
var cookieString = cookieHandler.getCookieString(url);
|
||||
|
||||
if (cookieString.length) {
|
||||
return { Cookie: cookieHandler.getCookieString(url) };
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
function getMatchingHostHeaders(url, headersList) {
|
||||
@@ -174,30 +203,6 @@ module.exports = function init(cookieHandler, messages) {
|
||||
return mergedHeaders;
|
||||
}
|
||||
|
||||
// typeof is not working reliably in JS
|
||||
function getTypeOf(object) {
|
||||
switch (Object.prototype.toString.call(object)) {
|
||||
case '[object Array]':
|
||||
return 'Array';
|
||||
case '[object Boolean]':
|
||||
return 'Boolean';
|
||||
case '[object Function]':
|
||||
return 'Function';
|
||||
case '[object Null]':
|
||||
return 'Null';
|
||||
case '[object Number]':
|
||||
return 'Number';
|
||||
case '[object Object]':
|
||||
return 'Object';
|
||||
case '[object String]':
|
||||
return 'String';
|
||||
case '[object Undefined]':
|
||||
return 'Undefined';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
function getAllowedDataTypes(dataSerializer) {
|
||||
switch (dataSerializer) {
|
||||
case 'utf8':
|
||||
@@ -210,7 +215,7 @@ module.exports = function init(cookieHandler, messages) {
|
||||
}
|
||||
|
||||
function getProcessedData(data, dataSerializer) {
|
||||
var currentDataType = getTypeOf(data);
|
||||
var currentDataType = jsUtil.getTypeOf(data);
|
||||
var allowedDataTypes = getAllowedDataTypes(dataSerializer);
|
||||
|
||||
if (allowedDataTypes.indexOf(currentDataType) === -1) {
|
||||
@@ -225,11 +230,11 @@ module.exports = function init(cookieHandler, messages) {
|
||||
}
|
||||
|
||||
function handleMissingCallbacks(successFn, failFn) {
|
||||
if (getTypeOf(successFn) !== 'Function') {
|
||||
if (jsUtil.getTypeOf(successFn) !== 'Function') {
|
||||
throw new Error(messages.MANDATORY_SUCCESS);
|
||||
}
|
||||
|
||||
if (getTypeOf(failFn) !== 'Function') {
|
||||
if (jsUtil.getTypeOf(failFn) !== 'Function') {
|
||||
throw new Error(messages.MANDATORY_FAIL);
|
||||
}
|
||||
}
|
||||
@@ -243,7 +248,7 @@ module.exports = function init(cookieHandler, messages) {
|
||||
timeout: checkTimeoutValue(options.timeout || globals.timeout),
|
||||
headers: checkHeadersObject(options.headers || {}),
|
||||
params: checkParamsObject(options.params || {}),
|
||||
data: getTypeOf(options.data) === 'Undefined' ? null : options.data,
|
||||
data: jsUtil.getTypeOf(options.data) === 'Undefined' ? null : options.data,
|
||||
filePath: options.filePath || '',
|
||||
name: options.name || ''
|
||||
};
|
||||
|
||||
26
www/js-util.js
Normal file
26
www/js-util.js
Normal file
@@ -0,0 +1,26 @@
|
||||
module.exports = {
|
||||
// typeof is not working reliably in JS
|
||||
getTypeOf: function (object) {
|
||||
switch (Object.prototype.toString.call(object)) {
|
||||
case '[object Array]':
|
||||
return 'Array';
|
||||
case '[object Boolean]':
|
||||
return 'Boolean';
|
||||
case '[object Function]':
|
||||
return 'Function';
|
||||
case '[object Null]':
|
||||
return 'Null';
|
||||
case '[object Number]':
|
||||
return 'Number';
|
||||
case '[object Object]':
|
||||
return 'Object';
|
||||
case '[object String]':
|
||||
return 'String';
|
||||
case '[object Undefined]':
|
||||
return 'Undefined';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ module.exports = {
|
||||
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_CLIENT_AUTH_MODE: 'advanced-http: invalid client certificate authentication 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'
|
||||
|
||||
@@ -12,7 +12,10 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf
|
||||
getCookieString: getCookieString,
|
||||
getRequestTimeout: getRequestTimeout,
|
||||
setRequestTimeout: setRequestTimeout,
|
||||
setSSLCertMode: setSSLCertMode,
|
||||
// for being backward compatible
|
||||
setSSLCertMode: setServerTrustMode,
|
||||
setServerTrustMode: setServerTrustMode,
|
||||
setClientAuthMode: setClientAuthMode,
|
||||
disableRedirect: disableRedirect,
|
||||
sendRequest: sendRequest,
|
||||
post: post,
|
||||
@@ -88,11 +91,34 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf
|
||||
globalConfigs.timeout = timeout;
|
||||
}
|
||||
|
||||
function setSSLCertMode(mode, success, failure) {
|
||||
return exec(success, failure, 'CordovaHttpPlugin', 'setSSLCertMode', [helpers.checkSSLCertMode(mode)]);
|
||||
function setServerTrustMode(mode, success, failure) {
|
||||
helpers.handleMissingCallbacks(success, failure);
|
||||
|
||||
return exec(success, failure, 'CordovaHttpPlugin', 'setServerTrustMode', [helpers.checkSSLCertMode(mode)]);
|
||||
}
|
||||
|
||||
function setClientAuthMode() {
|
||||
// filePath is an optional param
|
||||
var mode = arguments[0];
|
||||
var success = arguments[1];
|
||||
var failure = arguments[2];
|
||||
var filePath = null;
|
||||
|
||||
if (arguments.length === 4) {
|
||||
mode = arguments[0];
|
||||
filePath = arguments[1];
|
||||
success = arguments[2];
|
||||
failure = arguments[3];
|
||||
}
|
||||
|
||||
helpers.handleMissingCallbacks(success, failure);
|
||||
|
||||
return exec(success, failure, 'CordovaHttpPlugin', 'setClientAuthMode', [helpers.checkClientAuthMode(mode), filePath]);
|
||||
}
|
||||
|
||||
function disableRedirect(disable, success, failure) {
|
||||
helpers.handleMissingCallbacks(success, failure);
|
||||
|
||||
return exec(success, failure, 'CordovaHttpPlugin', 'disableRedirect', [!!disable]);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = function init(helpers) {
|
||||
module.exports = function init(jsUtil) {
|
||||
return {
|
||||
parseUrl: parseUrl,
|
||||
appendQueryParamsString: appendQueryParamsString,
|
||||
@@ -48,10 +48,10 @@ module.exports = function init(helpers) {
|
||||
|
||||
var identifier = parentKey.length ? parentKey + '[' + key + ']' : key;
|
||||
|
||||
if (helpers.getTypeOf(object[key]) === 'Array') {
|
||||
if (jsUtil.getTypeOf(object[key]) === 'Array') {
|
||||
parts.push(serializeArray(identifier, object[key], encode));
|
||||
continue;
|
||||
} else if (helpers.getTypeOf(object[key]) === 'Object') {
|
||||
} else if (jsUtil.getTypeOf(object[key]) === 'Object') {
|
||||
parts.push(serializeObject(identifier, object[key], encode));
|
||||
continue;
|
||||
}
|
||||
@@ -66,10 +66,10 @@ module.exports = function init(helpers) {
|
||||
var parts = [];
|
||||
|
||||
for (var i = 0; i < array.length; ++i) {
|
||||
if (helpers.getTypeOf(array[i]) === 'Array') {
|
||||
if (jsUtil.getTypeOf(array[i]) === 'Array') {
|
||||
parts.push(serializeArray(parentKey + '[]', array[i], encode));
|
||||
continue;
|
||||
} else if (helpers.getTypeOf(array[i]) === 'Object') {
|
||||
} else if (jsUtil.getTypeOf(array[i]) === 'Object') {
|
||||
parts.push(serializeObject(parentKey + '[]' + array[i], encode));
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user