WIP: implementing X509 client cert authentication (android "buffer" mode)

This commit is contained in:
Sefa Ilkimen
2019-04-15 03:18:59 +02:00
parent 620ce3f81c
commit 4f3ff9097f
14 changed files with 451 additions and 180 deletions

1
.gitignore vendored
View File

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

View File

@@ -3,7 +3,7 @@
"version": "2.0.9",
"description": "Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning",
"scripts": {
"updatecert": "node ./scripts/update-test-cert.js",
"updatecert": "node ./scripts/update-e2e-server-cert.js && node ./scripts/update-e2e-client-cert.js",
"buildbrowser": "./scripts/build-test-app.sh --browser",
"testandroid": "npm run updatecert && ./scripts/build-test-app.sh --android --emulator && ./scripts/test-app.sh --android --emulator",
"testios": "npm run updatecert && ./scripts/build-test-app.sh --ios --emulator && ./scripts/test-app.sh --ios --emulator",

View File

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

View File

@@ -30,13 +30,11 @@ const getCert = hostname => new Promise((resolve, reject) => {
req.end();
});
console.log(`Updating test certificate from ${SOURCE_HOST}`);
console.log(`Updating server certificate from ${SOURCE_HOST}`);
getCert(SOURCE_HOST)
.then(cert => {
fs.writeFileSync(TARGET_PATH, cert.raw);
})
.then(cert => fs.writeFileSync(TARGET_PATH, cert.raw))
.catch(error => {
console.error(`Updating test cert failed: ${error}`);
console.error(`Updating server certificate failed: ${error}`);
process.exit(1);
});

View File

@@ -6,11 +6,15 @@ import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URI;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import org.apache.cordova.CallbackContext;
@@ -21,17 +25,22 @@ class CordovaClientAuth implements Runnable, KeyChainAliasCallback {
private static final String TAG = "Cordova-Plugin-HTTP";
private String mode;
private String filePath;
private String aliasString;
private byte[] rawPkcs;
private String pkcsPassword;
private Activity activity;
private Context context;
private TLSConfiguration tlsConfiguration;
private CallbackContext callbackContext;
public CordovaClientAuth(final String mode, final String filePath, final Activity activity, final Context context,
final TLSConfiguration configContainer, final CallbackContext callbackContext) {
public CordovaClientAuth(final String mode, final String aliasString, final byte[] rawPkcs,
final String pkcsPassword, final Activity activity, final Context context, final TLSConfiguration configContainer,
final CallbackContext callbackContext) {
this.mode = mode;
this.filePath = filePath;
this.aliasString = aliasString;
this.rawPkcs = rawPkcs;
this.pkcsPassword = pkcsPassword;
this.activity = activity;
this.tlsConfiguration = configContainer;
this.context = context;
@@ -41,15 +50,45 @@ class CordovaClientAuth implements Runnable, KeyChainAliasCallback {
@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");
this.loadFromSystemStore();
} else if ("buffer".equals(this.mode)) {
this.loadFromBuffer();
} else {
this.tlsConfiguration.setKeyManagers(null);
this.callbackContext.success();
this.disableClientAuth();
}
}
private void loadFromSystemStore() {
if (this.aliasString == null) {
KeyChain.choosePrivateKeyAlias(this.activity, this, null, null, null, -1, null);
} else {
this.alias(this.aliasString);
}
}
private void loadFromBuffer() {
try {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(keyManagerFactoryAlgorithm);
ByteArrayInputStream stream = new ByteArrayInputStream(this.rawPkcs);
keyStore.load(stream, this.pkcsPassword.toCharArray());
keyManagerFactory.init(keyStore, this.pkcsPassword.toCharArray());
this.tlsConfiguration.setKeyManagers(keyManagerFactory.getKeyManagers());
this.callbackContext.success();
} catch (Exception e) {
Log.e(TAG, "Couldn't load given PKCS12 container for authentication", e);
this.callbackContext.error("Couldn't load given PKCS12 container for authentication");
}
}
private void disableClientAuth() {
this.tlsConfiguration.setKeyManagers(null);
this.callbackContext.success();
}
@Override
public void alias(final String alias) {
try {
@@ -63,10 +102,12 @@ class CordovaClientAuth implements Runnable, KeyChainAliasCallback {
this.tlsConfiguration.setKeyManagers(new KeyManager[] { keyManager });
this.callbackContext.success();
this.callbackContext.success(alias);
} 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");
Log.e(TAG, "Couldn't load private key and certificate pair with given alias \"" + alias + "\" for authentication",
e);
this.callbackContext.error(
"Couldn't load private key and certificate pair with given alias \"" + alias + "\" for authentication");
}
}
}

View File

@@ -13,6 +13,7 @@ import org.json.JSONException;
import org.json.JSONObject;
import android.util.Log;
import android.util.Base64;
import javax.net.ssl.TrustManagerFactory;
@@ -149,8 +150,11 @@ public class CordovaHttpPlugin extends CordovaPlugin {
}
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.getActivity().getApplicationContext(), this.tlsConfiguration, callbackContext);
byte[] pkcs = args.isNull(2) ? null : Base64.decode(args.getString(2), Base64.DEFAULT);
CordovaClientAuth runnable = new CordovaClientAuth(args.getString(0), args.isNull(1) ? null : args.getString(1),
pkcs, args.getString(3), this.cordova.getActivity(), this.cordova.getActivity().getApplicationContext(),
this.tlsConfiguration, callbackContext);
cordova.getThreadPool().execute(runnable);

View File

@@ -2,19 +2,13 @@ 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;

View File

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

View File

@@ -1,24 +1,42 @@
const hooks = {
onBeforeEachTest: function(done) {
onBeforeEachTest: function (resolve, reject) {
cordova.plugin.http.clearCookies();
helpers.setDefaultServerTrustMode(done);
helpers.setDefaultServerTrustMode(function () {
// @TODO: not ready yet
// helpers.setNoneClientAuthMode(resolve, reject);
resolve();
}, reject);
}
};
const helpers = {
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')); },
getWithXhr: function(done, url) {
setDefaultServerTrustMode: function (resolve, reject) { cordova.plugin.http.setServerTrustMode('default', resolve, reject); },
setNoCheckServerTrustMode: function (resolve, reject) { cordova.plugin.http.setServerTrustMode('nocheck', resolve, reject); },
setPinnedServerTrustMode: function (resolve, reject) { cordova.plugin.http.setServerTrustMode('pinned', resolve, reject); },
setNoneClientAuthMode: function (resolve, reject) { cordova.plugin.http.setClientAuthMode('none', resolve, reject); },
setBufferClientAuthMode: function (resolve, reject) {
helpers.getWithXhr(function(pkcs) {
cordova.plugin.http.setClientAuthMode('buffer', {
rawPkcs: pkcs,
pkcsPassword: 'badssl.com'
}, resolve, reject);
}, './certificates/badssl-client-cert.pkcs', 'arraybuffer');
},
setJsonSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('json')); },
setUtf8StringSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('utf8')); },
setUrlEncodedSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('urlencoded')); },
getWithXhr: function (done, url, type) {
var xhr = new XMLHttpRequest();
xhr.addEventListener('load', function() {
done(this.responseText);
xhr.addEventListener('load', function () {
if (!type || type === 'text') {
done(this.responseText);
} else {
done(this.response);
}
});
xhr.responseType = type;
xhr.open('GET', url);
xhr.send();
},
@@ -26,7 +44,7 @@ const helpers = {
window.resolveLocalFileSystemURL(cordova.file.cacheDirectory, function (directoryEntry) {
directoryEntry.getFile(fileName, { create: true, exclusive: false }, function (fileEntry) {
fileEntry.createWriter(function (fileWriter) {
var blob = new Blob([ content ], { type: 'text/plain' });
var blob = new Blob([content], { type: 'text/plain' });
fileWriter.onwriteend = done;
fileWriter.onerror = done;
@@ -38,16 +56,16 @@ const helpers = {
};
const messageFactory = {
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.' }
sslTrustAnchor: function () { return 'TLS connection could not be established: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.' },
invalidCertificate: function (domain) { return 'The certificate for this server is invalid. You might be connecting to a server that is pretending to be “' + domain + '” which could put your confidential information at risk.' }
}
const tests = [
{
description: 'should reject self signed cert (GET)',
expected: 'rejected: {"status":-2, ...',
func: function(resolve, reject) { cordova.plugin.http.get('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function(driver, result, targetInfo) {
func: function (resolve, reject) { cordova.plugin.http.get('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function (driver, result, targetInfo) {
result.type.should.be.equal('rejected');
result.data.should.be.eql({ status: -2, error: targetInfo.isAndroid ? messageFactory.sslTrustAnchor() : messageFactory.invalidCertificate('self-signed.badssl.com') });
}
@@ -55,8 +73,8 @@ const tests = [
{
description: 'should reject self signed cert (PUT)',
expected: 'rejected: {"status":-2, ...',
func: function(resolve, reject) { cordova.plugin.http.put('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result, targetInfo) {
func: function (resolve, reject) { cordova.plugin.http.put('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result, targetInfo) {
result.type.should.be.equal('rejected');
result.data.should.be.eql({ status: -2, error: targetInfo.isAndroid ? messageFactory.sslTrustAnchor() : messageFactory.invalidCertificate('self-signed.badssl.com') });
}
@@ -64,8 +82,8 @@ const tests = [
{
description: 'should reject self signed cert (POST)',
expected: 'rejected: {"status":-2, ...',
func: function(resolve, reject) { cordova.plugin.http.post('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result, targetInfo) {
func: function (resolve, reject) { cordova.plugin.http.post('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result, targetInfo) {
result.type.should.be.equal('rejected');
result.data.should.be.eql({ status: -2, error: targetInfo.isAndroid ? messageFactory.sslTrustAnchor() : messageFactory.invalidCertificate('self-signed.badssl.com') });
}
@@ -73,8 +91,8 @@ const tests = [
{
description: 'should reject self signed cert (PATCH)',
expected: 'rejected: {"status":-2, ...',
func: function(resolve, reject) { cordova.plugin.http.patch('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result, targetInfo) {
func: function (resolve, reject) { cordova.plugin.http.patch('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result, targetInfo) {
result.type.should.be.equal('rejected');
result.data.should.be.eql({ status: -2, error: targetInfo.isAndroid ? messageFactory.sslTrustAnchor() : messageFactory.invalidCertificate('self-signed.badssl.com') });
}
@@ -82,8 +100,8 @@ const tests = [
{
description: 'should reject self signed cert (DELETE)',
expected: 'rejected: {"status":-2, ...',
func: function(resolve, reject) { cordova.plugin.http.delete('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function(driver, result, targetInfo) {
func: function (resolve, reject) { cordova.plugin.http.delete('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function (driver, result, targetInfo) {
result.type.should.be.equal('rejected');
result.data.should.be.eql({ status: -2, error: targetInfo.isAndroid ? messageFactory.sslTrustAnchor() : messageFactory.invalidCertificate('self-signed.badssl.com') });
}
@@ -92,8 +110,8 @@ const tests = [
description: 'should accept bad cert (GET)',
expected: 'resolved: {"status":200, ...',
before: helpers.setNoCheckServerTrustMode,
func: function(resolve, reject) { cordova.plugin.http.get('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.get('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.should.include({ status: 200 });
}
@@ -102,8 +120,8 @@ const tests = [
description: 'should accept bad cert (PUT)',
expected: 'rejected: {"status":405, ... // will be rejected because PUT is not allowed',
before: helpers.setNoCheckServerTrustMode,
func: function(resolve, reject) { cordova.plugin.http.put('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.put('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
result.data.should.include({ status: 405 });
}
@@ -112,8 +130,8 @@ const tests = [
description: 'should accept bad cert (POST)',
expected: 'rejected: {"status":405, ... // will be rejected because POST is not allowed',
before: helpers.setNoCheckServerTrustMode,
func: function(resolve, reject) { cordova.plugin.http.post('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.post('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
result.data.should.include({ status: 405 });
}
@@ -122,8 +140,8 @@ const tests = [
description: 'should accept bad cert (PATCH)',
expected: 'rejected: {"status":405, ... // will be rejected because PATCH is not allowed',
before: helpers.setNoCheckServerTrustMode,
func: function(resolve, reject) { cordova.plugin.http.patch('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.patch('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
result.data.should.include({ status: 405 });
}
@@ -132,8 +150,8 @@ const tests = [
description: 'should accept bad cert (DELETE)',
expected: 'rejected: {"status":405, ... // will be rejected because DELETE is not allowed',
before: helpers.setNoCheckServerTrustMode,
func: function(resolve, reject) { cordova.plugin.http.delete('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.delete('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
result.data.should.include({ status: 405 });
}
@@ -142,8 +160,8 @@ const tests = [
description: 'should fetch data from http://httpbin.org/ (GET)',
expected: 'resolved: {"status":200, ...',
before: helpers.setNoCheckServerTrustMode,
func: function(resolve, reject) { cordova.plugin.http.get('http://httpbin.org/', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.get('http://httpbin.org/', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.should.include({ status: 200 });
}
@@ -152,8 +170,8 @@ const tests = [
description: 'should send JSON object correctly (POST)',
expected: 'resolved: {"status": 200, "data": "{\\"json\\":\\"test\\": \\"testString\\"}\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).json.should.eql({ test: 'testString' });
}
@@ -162,8 +180,8 @@ const tests = [
description: 'should send JSON object correctly (PUT)',
expected: 'resolved: {"status": 200, "data": "{\\"json\\":\\"test\\": \\"testString\\"}\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.put('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.put('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).json.should.eql({ test: 'testString' });
}
@@ -172,8 +190,8 @@ const tests = [
description: 'should send JSON object correctly (PATCH)',
expected: 'resolved: {"status": 200, "data": "{\\"json\\":\\"test\\": \\"testString\\"}\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.patch('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.patch('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).json.should.eql({ test: 'testString' });
}
@@ -182,39 +200,39 @@ const tests = [
description: 'should send JSON array correctly (POST) #26',
expected: 'resolved: {"status": 200, "data": "[ 1, 2, 3 ]\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', [ 1, 2, 3 ], {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', [1, 2, 3], {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).json.should.eql([ 1, 2, 3 ]);
JSON.parse(result.data.data).json.should.eql([1, 2, 3]);
}
},
{
description: 'should send JSON array correctly (PUT) #26',
expected: 'resolved: {"status": 200, "data": "[ 1, 2, 3 ]\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.put('http://httpbin.org/anything', [ 1, 2, 3 ], {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.put('http://httpbin.org/anything', [1, 2, 3], {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).json.should.eql([ 1, 2, 3 ]);
JSON.parse(result.data.data).json.should.eql([1, 2, 3]);
}
},
{
description: 'should send JSON array correctly (PATCH) #26',
expected: 'resolved: {"status": 200, "data": "[ 1, 2, 3 ]\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.patch('http://httpbin.org/anything', [ 1, 2, 3 ], {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.patch('http://httpbin.org/anything', [1, 2, 3], {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.data.should.be.a('string');
JSON.parse(result.data.data).json.should.eql([ 1, 2, 3 ]);
JSON.parse(result.data.data).json.should.eql([1, 2, 3]);
}
},
{
description: 'should send url encoded data correctly (POST) #41',
expected: 'resolved: {"status": 200, "data": "{\\"form\\":\\"test\\": \\"testString\\"}\" ...',
before: helpers.setUrlEncodedSerializer,
func: function(resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).form.should.eql({ test: 'testString' });
}
@@ -223,8 +241,8 @@ const tests = [
description: 'should send url encoded data correctly (PUT)',
expected: 'resolved: {"status": 200, "data": "{\\"form\\":\\"test\\": \\"testString\\"}\" ...',
before: helpers.setUrlEncodedSerializer,
func: function(resolve, reject) { cordova.plugin.http.put('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.put('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).form.should.eql({ test: 'testString' });
}
@@ -233,8 +251,8 @@ const tests = [
description: 'should send url encoded data correctly (PATCH)',
expected: 'resolved: {"status": 200, "data": "{\\"form\\":\\"test\\": \\"testString\\"}\" ...',
before: helpers.setUrlEncodedSerializer,
func: function(resolve, reject) { cordova.plugin.http.patch('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.patch('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).form.should.eql({ test: 'testString' });
}
@@ -242,8 +260,8 @@ const tests = [
{
description: 'should resolve correct URL after redirect (GET) #33',
expected: 'resolved: {"status": 200, url: "http://httpbin.org/anything", ...',
func: function(resolve, reject) { cordova.plugin.http.get('http://httpbin.org/redirect-to?url=http://httpbin.org/anything', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.get('http://httpbin.org/redirect-to?url=http://httpbin.org/anything', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.url.should.be.equal('http://httpbin.org/anything');
}
@@ -251,12 +269,12 @@ const tests = [
{
description: 'should download a file from given URL to given path in local filesystem',
expected: 'resolved: {"content": "<?xml version=\'1.0\' encoding=\'us-ascii\'?>\\n\\n<!-- A SAMPLE set of slides -->" ...',
func: function(resolve, reject) {
func: function (resolve, reject) {
var sourceUrl = 'http://httpbin.org/xml';
var targetPath = cordova.file.cacheDirectory + 'test.xml';
cordova.plugin.http.downloadFile(sourceUrl, {}, {}, targetPath, function(entry) {
helpers.getWithXhr(function(content) {
cordova.plugin.http.downloadFile(sourceUrl, {}, {}, targetPath, function (entry) {
helpers.getWithXhr(function (content) {
resolve({
sourceUrl: sourceUrl,
targetPath: targetPath,
@@ -267,7 +285,7 @@ const tests = [
}, targetPath);
}, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.name.should.be.equal('test.xml');
result.data.content.should.be.equal("<?xml version='1.0' encoding='us-ascii'?>\n\n<!-- A SAMPLE set of slides -->\n\n<slideshow \n title=\"Sample Slide Show\"\n date=\"Date of publication\"\n author=\"Yours Truly\"\n >\n\n <!-- TITLE SLIDE -->\n <slide type=\"all\">\n <title>Wake up to WonderWidgets!</title>\n </slide>\n\n <!-- OVERVIEW -->\n <slide type=\"all\">\n <title>Overview</title>\n <item>Why <em>WonderWidgets</em> are great</item>\n <item/>\n <item>Who <em>buys</em> WonderWidgets</item>\n </slide>\n\n</slideshow>");
@@ -276,17 +294,17 @@ const tests = [
{
description: 'should upload a file from given path in local filesystem to given URL #27',
expected: 'resolved: {"status": 200, "data": "files": {"test-file.txt": "I am a dummy file. I am used ...',
func: function(resolve, reject) {
func: function (resolve, reject) {
var fileName = 'test-file.txt';
var fileContent = 'I am a dummy file. I am used for testing purposes!';
var sourcePath = cordova.file.cacheDirectory + fileName;
var targetUrl = 'http://httpbin.org/post';
helpers.writeToFile(function() {
helpers.writeToFile(function () {
cordova.plugin.http.uploadFile(targetUrl, {}, {}, sourcePath, fileName, resolve, reject);
}, fileName, fileContent);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
var fileName = 'test-file.txt';
var fileContent = 'I am a dummy file. I am used for testing purposes!';
@@ -302,10 +320,10 @@ const tests = [
{
description: 'should encode HTTP array params correctly (GET) #45',
expected: 'resolved: {"status": 200, "data": "{\\"url\\":\\"http://httpbin.org/get?myArray[]=val1&myArray[]=val2&myArray[]=val3\\"}\" ...',
func: function(resolve, reject) {
cordova.plugin.http.get('http://httpbin.org/get', { myArray: [ 'val1', 'val2', 'val3' ], myString: 'testString' }, {}, resolve, reject);
func: function (resolve, reject) {
cordova.plugin.http.get('http://httpbin.org/get', { myArray: ['val1', 'val2', 'val3'], myString: 'testString' }, {}, resolve, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.data.should.be.a('string');
@@ -318,10 +336,10 @@ const tests = [
{
description: 'should throw on non-string values in local header object #54',
expected: 'throwed: {"message": "advanced-http: header values must be strings"}',
func: function(resolve, reject) {
func: function (resolve, reject) {
cordova.plugin.http.get('http://httpbin.org/get', {}, { myTestHeader: 1 }, resolve, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('throwed');
result.message.should.be.equal('advanced-http: header values must be strings');
}
@@ -329,10 +347,10 @@ const tests = [
{
description: 'should throw an error while setting non-string value as global header #54',
expected: 'throwed: "advanced-http: header values must be strings"',
func: function(resolve, reject) {
func: function (resolve, reject) {
cordova.plugin.http.setHeader('myTestHeader', 2);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('throwed');
result.message.should.be.equal('advanced-http: header values must be strings');
}
@@ -340,10 +358,10 @@ const tests = [
{
description: 'should accept content-type "application/xml" #58',
expected: 'resolved: {"status": 200, ...',
func: function(resolve, reject) {
func: function (resolve, reject) {
cordova.plugin.http.get('http://httpbin.org/xml', {}, {}, resolve, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.status.should.be.equal(200);
}
@@ -351,12 +369,12 @@ const tests = [
{
description: 'should send programmatically set cookies correctly (GET)',
expected: 'resolved: {"status": 200, ...',
func: function(resolve, reject) {
func: function (resolve, reject) {
cordova.plugin.http.setCookie('http://httpbin.org/get', 'myCookie=myValue');
cordova.plugin.http.setCookie('http://httpbin.org/get', 'mySecondCookie=mySecondValue');
cordova.plugin.http.get('http://httpbin.org/get', {}, {}, resolve, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.data.should.be.a('string');
@@ -370,13 +388,13 @@ const tests = [
{
description: 'should not send any cookies after running "clearCookies" (GET) #59',
expected: 'resolved: {"status": 200, "data": "{\"headers\": {\"Cookie\": \"\"...',
func: function(resolve, reject) {
func: function (resolve, reject) {
cordova.plugin.http.setCookie('http://httpbin.org/get', 'myCookie=myValue');
cordova.plugin.http.setCookie('http://httpbin.org/get', 'mySecondCookie=mySecondValue');
cordova.plugin.http.clearCookies();
cordova.plugin.http.get('http://httpbin.org/get', {}, {}, resolve, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.data.should.be.a('string');
@@ -389,15 +407,15 @@ const tests = [
{
description: 'should send programmatically set cookies correctly (DOWNLOAD) #57',
expected: 'resolved: {"content":{"cookies":{"myCookie":"myValue ...',
func: function(resolve, reject) {
func: function (resolve, reject) {
var sourceUrl = 'http://httpbin.org/cookies';
var targetPath = cordova.file.cacheDirectory + 'cookies.json';
cordova.plugin.http.setCookie('http://httpbin.org/get', 'myCookie=myValue');
cordova.plugin.http.setCookie('http://httpbin.org/get', 'mySecondCookie=mySecondValue');
cordova.plugin.http.downloadFile(sourceUrl, {}, {}, targetPath, function(entry) {
helpers.getWithXhr(function(content) {
cordova.plugin.http.downloadFile(sourceUrl, {}, {}, targetPath, function (entry) {
helpers.getWithXhr(function (content) {
resolve({
sourceUrl: sourceUrl,
targetPath: targetPath,
@@ -408,7 +426,7 @@ const tests = [
}, targetPath);
}, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.name.should.be.equal('cookies.json');
result.data.content.should.be.a('string');
@@ -423,10 +441,10 @@ const tests = [
description: 'should send UTF-8 encoded raw string correctly (POST) #34',
expected: 'resolved: {"status": 200, "data": "{\\"data\\": \\"this is a test string\\"...',
before: helpers.setUtf8StringSerializer,
func: function(resolve, reject) {
func: function (resolve, reject) {
cordova.plugin.http.post('http://httpbin.org/anything', 'this is a test string', {}, resolve, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).data.should.be.equal('this is a test string');
}
@@ -434,10 +452,10 @@ const tests = [
{
description: 'should encode spaces in query string (params object) correctly (GET) #71',
expected: 'resolved: {"status": 200, "data": "{\\"args\\": \\"query param\\": \\"and value with spaces\\"...',
func: function(resolve, reject) {
func: function (resolve, reject) {
cordova.plugin.http.get('http://httpbin.org/get', { 'query param': 'and value with spaces' }, {}, resolve, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).args['query param'].should.be.equal('and value with spaces');
}
@@ -445,10 +463,10 @@ const tests = [
{
description: 'should decode latin1 (iso-8859-1) encoded body correctly (GET) #72',
expected: 'resolved: {"status": 200, "data": "<!DOCTYPE HTML PUBLIC \\"-//W3C//DTD HTML 4.01 Transitional//EN\\"> ...',
func: function(resolve, reject) {
func: function (resolve, reject) {
cordova.plugin.http.get('http://www.columbia.edu/kermit/latin1.html', {}, {}, resolve, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.data.should.include('[¡] 161 10/01 241 A1 INVERTED EXCLAMATION MARK\n[¢] 162 10/02 242 A2 CENT SIGN');
}
@@ -456,10 +474,10 @@ const tests = [
{
description: 'should return empty body string correctly (GET)',
expected: 'resolved: {"status": 200, "data": "" ...',
func: function(resolve, reject) {
func: function (resolve, reject) {
cordova.plugin.http.get('http://httpbin.org/stream/0', {}, {}, resolve, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.data.should.be.equal('');
}
@@ -468,10 +486,10 @@ const tests = [
description: 'should pin SSL cert correctly (GET)',
expected: 'resolved: {"status": 200 ...',
before: helpers.setPinnedServerTrustMode,
func: function(resolve, reject) {
func: function (resolve, reject) {
cordova.plugin.http.get('https://httpbin.org', {}, {}, resolve, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.status.should.be.equal(200);
}
@@ -480,10 +498,10 @@ const tests = [
description: 'should reject when pinned cert does not match received server cert (GET)',
expected: 'rejected: {"status": -2 ...',
before: helpers.setPinnedServerTrustMode,
func: function(resolve, reject) {
func: function (resolve, reject) {
cordova.plugin.http.get('https://sha512.badssl.com/', {}, {}, resolve, reject);
},
validationFunc: function(driver, result, targetInfo) {
validationFunc: function (driver, result, targetInfo) {
result.type.should.be.equal('rejected');
result.data.should.be.eql({ status: -2, error: targetInfo.isAndroid ? messageFactory.sslTrustAnchor() : messageFactory.invalidCertificate('sha512.badssl.com') });
}
@@ -492,18 +510,18 @@ const tests = [
description: 'should send deeply structured JSON object correctly (POST) #65',
expected: 'resolved: {"status": 200, "data": "{\\"data\\": \\"{\\\\"outerObj\\\\":{\\\\"innerStr\\\\":\\\\"testString\\\\",\\\\"innerArr\\\\":[1,2,3]}}\\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', { outerObj: { innerStr: 'testString', innerArr: [1, 2, 3] }}, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', { outerObj: { innerStr: 'testString', innerArr: [1, 2, 3] } }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).json.should.eql({ outerObj: { innerStr: 'testString', innerArr: [1, 2, 3] }});
JSON.parse(result.data.data).json.should.eql({ outerObj: { innerStr: 'testString', innerArr: [1, 2, 3] } });
}
},
{
description: 'should override header "content-type" correctly (POST) #78',
expected: 'resolved: {"status": 200, "headers": "{\\"Content-Type\\": \\"text/plain\\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', {}, { 'Content-Type': 'text/plain' }, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', {}, { 'Content-Type': 'text/plain' }, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).headers['Content-Type'].should.be.equal('text/plain');
}
@@ -511,8 +529,8 @@ const tests = [
{
description: 'should handle error during file download correctly (DOWNLOAD) #83',
expected: 'rejected: {"status": 403, "error": "There was an error downloading the file" ...',
func: function(resolve, reject) { cordova.plugin.http.downloadFile('http://httpbin.org/status/403', {}, {}, cordova.file.tempDirectory + 'testfile.txt', resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.downloadFile('http://httpbin.org/status/403', {}, {}, cordova.file.tempDirectory + 'testfile.txt', resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
result.data.status.should.be.equal(403);
result.data.error.should.be.equal('There was an error downloading the file');
@@ -521,8 +539,8 @@ const tests = [
{
description: 'should handle gzip encoded response correctly',
expected: 'resolved: {"status": 200, "headers": "{\\"Content-Encoding\\": \\"gzip\\" ...',
func: function(resolve, reject) { cordova.plugin.http.get('http://httpbin.org/gzip', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.get('http://httpbin.org/gzip', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.status.should.be.equal(200);
JSON.parse(result.data.data).gzipped.should.be.equal(true);
@@ -532,8 +550,8 @@ const tests = [
description: 'should send empty string correctly',
expected: 'resolved: {"status": 200, "data": "{\\"json\\":\\"\\" ...',
before: helpers.setUtf8StringSerializer,
func: function(resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', '', {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', '', {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).data.should.be.equal('');
}
@@ -542,8 +560,8 @@ const tests = [
description: 'shouldn\'t escape forward slashes #184',
expected: 'resolved: {"status": 200, "data": "{\\"json\\":\\"/\\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', { testString: '/' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', { testString: '/' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).json.testString.should.be.equal('/');
}
@@ -551,8 +569,8 @@ const tests = [
{
description: 'should not double encode spaces in url path #195',
expected: 'resolved: {"status": 200, "data": "{\\"url\\":\\"https://httpbin.org/anything/containing spaces in url\\" ...',
func: function(resolve, reject) { cordova.plugin.http.get('https://httpbin.org/anything/containing%20spaces%20in%20url', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.get('https://httpbin.org/anything/containing%20spaces%20in%20url', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).url.should.be.equal('https://httpbin.org/anything/containing spaces in url');
}
@@ -560,8 +578,8 @@ const tests = [
{
description: 'should encode spaces in url query correctly',
expected: 'resolved: {"status": 200, "data": "{\\"url\\":\\"https://httpbin.org/anything?query key=very long query value with spaces\\" ...',
func: function(resolve, reject) { cordova.plugin.http.get('https://httpbin.org/anything', { 'query key': 'very long query value with spaces' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
func: function (resolve, reject) { cordova.plugin.http.get('https://httpbin.org/anything', { 'query key': 'very long query value with spaces' }, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).url.should.be.equal('https://httpbin.org/anything?query key=very long query value with spaces');
}
@@ -569,12 +587,12 @@ const tests = [
{
description: 'should download a file from given HTTPS URL to given path in local filesystem #197',
expected: 'resolved: {"content": "<?xml version=\'1.0\' encoding=\'us-ascii\'?>\\n\\n<!-- A SAMPLE set of slides -->" ...',
func: function(resolve, reject) {
func: function (resolve, reject) {
var sourceUrl = 'https://httpbin.org/xml';
var targetPath = cordova.file.cacheDirectory + 'test.xml';
cordova.plugin.http.downloadFile(sourceUrl, {}, {}, targetPath, function(entry) {
helpers.getWithXhr(function(content) {
cordova.plugin.http.downloadFile(sourceUrl, {}, {}, targetPath, function (entry) {
helpers.getWithXhr(function (content) {
resolve({
sourceUrl: sourceUrl,
targetPath: targetPath,
@@ -585,12 +603,23 @@ const tests = [
}, targetPath);
}, reject);
},
validationFunc: function(driver, result) {
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.name.should.be.equal('test.xml');
result.data.content.should.be.equal("<?xml version='1.0' encoding='us-ascii'?>\n\n<!-- A SAMPLE set of slides -->\n\n<slideshow \n title=\"Sample Slide Show\"\n date=\"Date of publication\"\n author=\"Yours Truly\"\n >\n\n <!-- TITLE SLIDE -->\n <slide type=\"all\">\n <title>Wake up to WonderWidgets!</title>\n </slide>\n\n <!-- OVERVIEW -->\n <slide type=\"all\">\n <title>Overview</title>\n <item>Why <em>WonderWidgets</em> are great</item>\n <item/>\n <item>Who <em>buys</em> WonderWidgets</item>\n </slide>\n\n</slideshow>");
}
}
},
// @TODO: not ready yet
// {
// description: 'should authenticate correctly when client cert auth is configured with a PKCS12 container',
// expected: 'resolved: {"status": 200, ...',
// before: helpers.setBufferClientAuthMode,
// func: function (resolve, reject) { cordova.plugin.http.get('https://client.badssl.com/', {}, {}, resolve, reject); },
// validationFunc: function (driver, result) {
// result.type.should.be.equal('resolved');
// result.data.data.should.include('TLS handshake');
// }
// }
];
if (typeof module !== 'undefined' && module.exports) {

View File

@@ -261,4 +261,69 @@ describe('Common helpers', function () {
helpers.getCookieHeader('http://ilkimen.net').should.eql({ Cookie: 'cookie=value' });
});
});
describe('checkClientAuthOptions()', function () {
const jsUtil = require('../www/js-util');
const messages = require('../www/messages');
const helpers = require('../www/helpers')(jsUtil, null, messages);
it('returns options object with empty values when mode is "none" and no options are given', () => {
helpers.checkClientAuthOptions('none').should.eql({
alias: null,
pkcsPath: '',
pkcsPassword: ''
});
});
it('returns options object with empty values when mode is "none" and random options are given', () => {
helpers.checkClientAuthOptions('none', {
alias: 'myAlias',
pkcsPath: 'myPath'
}).should.eql({
alias: null,
pkcsPath: '',
pkcsPassword: ''
});
});
it('throws an error when mode is "systemstore" and alias is not a string or undefined', () => {
(() => helpers.checkClientAuthOptions('systemstore', { alias: 1 }))
.should.throw(messages.INVALID_CLIENT_AUTH_ALIAS);
(() => helpers.checkClientAuthOptions('systemstore', { alias: undefined }))
.should.not.throw();
});
it('returns an object with null alias when mode is "systemstore" and no options object is given', () => {
helpers.checkClientAuthOptions('systemstore').should.eql({
alias: null,
pkcsPath: '',
pkcsPassword: ''
});
});
it('throws an error when mode is "file" and pkcsPath is not a string', () => {
(() => helpers.checkClientAuthOptions('file', {
pkcsPath: undefined,
pkcsPassword: 'password'
})).should.throw(messages.INVALID_CLIENT_AUTH_PKCS_PATH);
(() => helpers.checkClientAuthOptions('file', {
pkcsPath: 1,
pkcsPassword: 'password'
})).should.throw(messages.INVALID_CLIENT_AUTH_PKCS_PATH);
});
it('throws an error when mode is "file" and pkcsPassword is not a string', () => {
(() => helpers.checkClientAuthOptions('file', {
pkcsPath: 'path',
pkcsPassword: undefined
})).should.throw(messages.INVALID_CLIENT_AUTH_PKCS_PASSWORD);
(() => helpers.checkClientAuthOptions('file', {
pkcsPath: 'path',
pkcsPassword: 1
})).should.throw(messages.INVALID_CLIENT_AUTH_PKCS_PASSWORD);
});
});
})

View File

@@ -1,7 +1,7 @@
module.exports = function init(jsUtil, cookieHandler, messages) {
var validSerializers = ['urlencoded', 'json', 'utf8'];
var validCertModes = ['default', 'nocheck', 'pinned', 'legacy'];
var validClientAuthModes = ['none', 'systemstore', 'file'];
var validClientAuthModes = ['none', 'systemstore', 'buffer'];
var validHttpMethods = ['get', 'put', 'post', 'patch', 'head', 'delete', 'upload', 'download'];
var interface = {
@@ -9,6 +9,7 @@ module.exports = function init(jsUtil, cookieHandler, messages) {
checkSerializer: checkSerializer,
checkSSLCertMode: checkSSLCertMode,
checkClientAuthMode: checkClientAuthMode,
checkClientAuthOptions: checkClientAuthOptions,
checkForBlacklistedHeaderKey: checkForBlacklistedHeaderKey,
checkForInvalidHeaderValue: checkForInvalidHeaderValue,
injectCookieHandler: injectCookieHandler,
@@ -105,6 +106,54 @@ module.exports = function init(jsUtil, cookieHandler, messages) {
return checkForValidStringValue(validClientAuthModes, mode, messages.INVALID_CLIENT_AUTH_MODE);
}
function checkClientAuthOptions(mode, options) {
options = options || {};
// none
if (mode === validClientAuthModes[0]) {
return {
alias: null,
rawPkcs: null,
pkcsPassword: ''
};
}
if (jsUtil.getTypeOf(options) !== 'Object') {
throw new Error(messages.INVALID_CLIENT_AUTH_OPTIONS);
}
// systemstore
if (mode === validClientAuthModes[1]) {
if (jsUtil.getTypeOf(options.alias) !== 'String'
&& jsUtil.getTypeOf(options.alias) !== 'Undefined') {
throw new Error(messages.INVALID_CLIENT_AUTH_ALIAS);
}
return {
alias: jsUtil.getTypeOf(options.alias) === 'Undefined' ? null : options.alias,
rawPkcs: null,
pkcsPassword: ''
};
}
// buffer
if (mode === validClientAuthModes[2]) {
if (jsUtil.getTypeOf(options.rawPkcs) !== 'ArrayBuffer') {
throw new Error(messages.INVALID_CLIENT_AUTH_RAW_PKCS);
}
if (jsUtil.getTypeOf(options.pkcsPassword) !== 'String') {
throw new Error(messages.INVALID_CLIENT_AUTH_PKCS_PASSWORD);
}
return {
alias: null,
rawPkcs: options.rawPkcs,
pkcsPassword: options.pkcsPassword
}
}
}
function checkForBlacklistedHeaderKey(key) {
if (key.toLowerCase() === 'cookie') {
throw new Error(messages.ADDING_COOKIES_NOT_SUPPORTED);

View File

@@ -4,6 +4,8 @@ module.exports = {
switch (Object.prototype.toString.call(object)) {
case '[object Array]':
return 'Array';
case '[object ArrayBuffer]':
return 'ArrayBuffer';
case '[object Boolean]':
return 'Boolean';
case '[object Function]':

View File

@@ -7,6 +7,10 @@ module.exports = {
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_CLIENT_AUTH_OPTIONS: 'advanced-http: invalid client certificate authentication options, needs to be an object',
INVALID_CLIENT_AUTH_ALIAS: 'advanced-http: invalid client certificate alias, needs to be a string or undefined',
INVALID_CLIENT_AUTH_RAW_PKCS: 'advanced-http: invalid PKCS12 container, needs to be an array buffer',
INVALID_CLIENT_AUTH_PKCS_PASSWORD: 'advanced-http: invalid PKCS12 container password, needs to be a string',
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'

View File

@@ -98,22 +98,23 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf
}
function setClientAuthMode() {
// filePath is an optional param
var mode = arguments[0];
var options = null;
var success = arguments[1];
var failure = arguments[2];
var filePath = null;
if (arguments.length === 4) {
mode = arguments[0];
filePath = arguments[1];
options = arguments[1];
success = arguments[2];
failure = arguments[3];
}
mode = helpers.checkClientAuthMode(mode);
options = helpers.checkClientAuthOptions(mode, options);
helpers.handleMissingCallbacks(success, failure);
return exec(success, failure, 'CordovaHttpPlugin', 'setClientAuthMode', [helpers.checkClientAuthMode(mode), filePath]);
return exec(success, failure, 'CordovaHttpPlugin', 'setClientAuthMode', [mode, options.alias, options.rawPkcs, options.pkcsPassword]);
}
function disableRedirect(disable, success, failure) {