mirror of
https://github.com/silkimen/cordova-plugin-advanced-http.git
synced 2026-07-26 00:00:14 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1719334d39 | ||
|
|
8db15c6c96 | ||
|
|
e58a398c33 | ||
|
|
a637d70ec1 | ||
|
|
6f40ed5cd8 | ||
|
|
8b09e159fa | ||
|
|
1d5642c8ae | ||
|
|
a580ce2067 | ||
|
|
35dc0205aa | ||
|
|
2dbf0d452a | ||
|
|
8a9381ffaa | ||
|
|
f1c95ef6fd | ||
|
|
6877fbdb65 | ||
|
|
a52e243e5f | ||
|
|
9c34f47624 | ||
|
|
3f1ee62088 | ||
|
|
c73b15b620 | ||
|
|
86698ffdcc | ||
|
|
4a396c28e5 | ||
|
|
53224ed875 | ||
|
|
3adec712c9 | ||
|
|
a49bcac81b | ||
|
|
2f01d48abb | ||
|
|
53df68f154 | ||
|
|
441268586b |
+1
-3
@@ -13,9 +13,7 @@ cache:
|
||||
- node_modules
|
||||
|
||||
addons:
|
||||
sauce_connect:
|
||||
username: silkimen
|
||||
jwt: RZHC8x5ggrXdu1xzhhHmQDwege9s3putJXY1ZG8+MNMaBTLfLBWzFsHuR6Lvz+XnrAT/CTGG957kw6ikSaV6VEkGvtO91irZoJyShjPq2hBbn2CaEbo91Eq8iNYaRG+4FVMxoHa6xpF/HtvOLQdIkURc/lzLj/BOFMmtV/d3adLDcryQZuYB211iN4d6exGHMQe8uvJdkCy4ac85guhF8KmAELyaVa0rx0m6gNaszWU9BJ8Rn+4HJbJtfpQcU1bJN43Dx8forRU/Com53K+KFtDKPUdBHZnuENrZrQ+8RU59vdLrHwsCvP9elJFkcX9xk/r45oUGXhqVV3vMyaqTa9YUO772rhgKuAi4TqiaR51H58KWqynp7wOZwL5FqJs0KzL5rNDDIoKKrklnOidS4RAUKXCi1CUNXwDdOo4Kd96cYOIHqpwa7Beci8CtVlzd/49yE9/AXnZYVwp10uKZvJJdliAOHhVEAtCIp8PzHOOPYJsXvUcEU4ORWWWpusBAz6wFiLOL4d/CAHNj6wDFliRexBrv/Wunu5ifva4oVJWrtAtyGDtsX8kBkf8OcIdy/aPUIIBrbW1sapjPFymMGeL/KkSZRjEy7KjFZKZh9L7sJE56sq+IUremFbcyaX1E3rho6Cs3ongLaKhL9f9vz8EFs7//w8pNlNS9EUdOU68=
|
||||
sauce_connect: true
|
||||
|
||||
before_install:
|
||||
- export LANG=en_US.UTF-8
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
## 1.10.2
|
||||
|
||||
- Fixed #78: overriding header "Content-Type" not working on Android
|
||||
- Fixed #79: PATCH operation not working on Android API level 19 and older (thanks chax)
|
||||
- Fixed #83: App crashes on error during download operation on iOS (thanks troyanskiy)
|
||||
- Fixed #76: upload sequence is not respecting order of operations needed by some sites (thanks Johny101)
|
||||
|
||||
- :warning: **Deprecation**: AngularJS service is deprecated now and will be removed anytime soon
|
||||
|
||||
## 1.10.1
|
||||
|
||||
- Fixed #71: does not encode query string in URL correctly on Android
|
||||
- Fixed #72: app crashes if response encoding is not UTF-8 (thanks jkfb)
|
||||
|
||||
## 1.10.0
|
||||
|
||||
- Feature #34: add new serializer "utf8" sending utf-8 encoded plain text (thanks robertocapuano)
|
||||
|
||||
@@ -32,11 +32,17 @@ cordova plugin add cordova-plugin-advanced-http
|
||||
|
||||
## Usage
|
||||
|
||||
### Without AngularJS
|
||||
### Plain Cordova
|
||||
|
||||
This plugin registers a global object located at `cordova.plugin.http`.
|
||||
|
||||
### With AngularJS
|
||||
### With Ionic-native wrapper
|
||||
|
||||
Check the [Ionic docs](https://ionicframework.com/docs/native/http/) for how to use this plugin with Ionic-native.
|
||||
|
||||
### With AngularJS (Deprecated)
|
||||
|
||||
:warning: *This feature is deprecated and will be removed anytime soon.* :warning:
|
||||
|
||||
This plugin creates a cordovaHTTP service inside of a cordovaHTTP module. You must load the module when you create your app's module.
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cordova-plugin-advanced-http",
|
||||
"version": "1.10.0",
|
||||
"version": "1.10.2",
|
||||
"description": "Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning",
|
||||
"scripts": {
|
||||
"testandroid": "./scripts/build-test-app.sh --android --emulator && ./scripts/test-app.sh --android --emulator",
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-advanced-http" version="1.10.0">
|
||||
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-advanced-http" version="1.10.2">
|
||||
<name>Advanced HTTP plugin</name>
|
||||
<description>
|
||||
Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning
|
||||
@@ -56,6 +56,7 @@
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</config-file>
|
||||
<source-file src="src/android/com/github/kevinsawicki/http/HttpRequest.java" target-dir="src/com/github/kevinsawicki/http"/>
|
||||
<source-file src="src/android/com/github/kevinsawicki/http/OkConnectionFactory.java" target-dir="src/com/github/kevinsawicki/http"/>
|
||||
<source-file src="src/android/com/github/kevinsawicki/http/TLSSocketFactory.java" target-dir="src/com/github/kevinsawicki/http"/>
|
||||
<source-file src="src/android/com/synconset/cordovahttp/CordovaHttp.java" target-dir="src/com/synconset/cordovahttp"/>
|
||||
<source-file src="src/android/com/synconset/cordovahttp/CordovaHttpDelete.java" target-dir="src/com/synconset/cordovahttp"/>
|
||||
@@ -67,5 +68,6 @@
|
||||
<source-file src="src/android/com/synconset/cordovahttp/CordovaHttpPut.java" target-dir="src/com/synconset/cordovahttp"/>
|
||||
<source-file src="src/android/com/synconset/cordovahttp/CordovaHttpPatch.java" target-dir="src/com/synconset/cordovahttp"/>
|
||||
<source-file src="src/android/com/synconset/cordovahttp/CordovaHttpUpload.java" target-dir="src/com/synconset/cordovahttp"/>
|
||||
<framework src="com.squareup.okhttp3:okhttp-urlconnection:3.9.1"/>
|
||||
</platform>
|
||||
</plugin>
|
||||
@@ -3,6 +3,11 @@ set -e
|
||||
|
||||
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"/..
|
||||
|
||||
if [ $CI == "true" ] && ([ -z $SAUCE_USERNAME ] || [ -z $SAUCE_ACCESS_KEY ]); then
|
||||
echo "Skipping CI tests, because Saucelabs credentials are not set.";
|
||||
exit 0;
|
||||
fi
|
||||
|
||||
pushd $ROOT
|
||||
./node_modules/.bin/mocha ./test/app-mocha-specs/test.js "$@"
|
||||
popd
|
||||
|
||||
@@ -5,6 +5,11 @@ PLATFORM=$([[ "${@#--android}" = "$@" ]] && echo "ios" || echo "android")
|
||||
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"/..
|
||||
TEMP=$ROOT/temp
|
||||
|
||||
if [ -z $SAUCE_USERNAME ] || [ -z $SAUCE_ACCESS_KEY ]; then
|
||||
echo "Skipping uploading artifact, because Saucelabs credentials are not set.";
|
||||
exit 0;
|
||||
fi
|
||||
|
||||
if [ $PLATFORM = "android" ]; then
|
||||
curl -u $SAUCE_USERNAME:$SAUCE_ACCESS_KEY \
|
||||
-X POST \
|
||||
|
||||
@@ -92,6 +92,8 @@ import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
/**
|
||||
* A fluid interface for making HTTP requests using an underlying
|
||||
* {@link HttpURLConnection} (or sub-class).
|
||||
@@ -106,6 +108,11 @@ public class HttpRequest {
|
||||
*/
|
||||
public static final String CHARSET_UTF8 = "UTF-8";
|
||||
|
||||
/**
|
||||
* 'ISO-8859-1' charset name
|
||||
*/
|
||||
public static final String CHARSET_LATIN1 = "ISO-8859-1";
|
||||
|
||||
/**
|
||||
* 'application/x-www-form-urlencoded' content type header value
|
||||
*/
|
||||
@@ -363,26 +370,35 @@ public class HttpRequest {
|
||||
}
|
||||
|
||||
private static StringBuilder addParam(final Object key, Object value,
|
||||
final StringBuilder result) {
|
||||
final StringBuilder result) throws HttpRequestException {
|
||||
return addParam(key, value, result, CHARSET_UTF8);
|
||||
}
|
||||
|
||||
private static StringBuilder addParam(final Object key, Object value,
|
||||
final StringBuilder result, String charset) throws HttpRequestException {
|
||||
if (value != null && value.getClass().isArray())
|
||||
value = arrayToList(value);
|
||||
|
||||
if (value instanceof Iterable<?>) {
|
||||
Iterator<?> iterator = ((Iterable<?>) value).iterator();
|
||||
while (iterator.hasNext()) {
|
||||
result.append(key);
|
||||
result.append("[]=");
|
||||
Object element = iterator.next();
|
||||
if (element != null)
|
||||
result.append(element);
|
||||
if (iterator.hasNext())
|
||||
result.append("&");
|
||||
try {
|
||||
if (value instanceof Iterable<?>) {
|
||||
Iterator<?> iterator = ((Iterable<?>) value).iterator();
|
||||
while (iterator.hasNext()) {
|
||||
result.append(URLEncoder.encode(key.toString(), charset));
|
||||
result.append("[]=");
|
||||
Object element = iterator.next();
|
||||
if (element != null)
|
||||
result.append(URLEncoder.encode(element.toString(), charset));
|
||||
if (iterator.hasNext())
|
||||
result.append("&");
|
||||
}
|
||||
} else {
|
||||
result.append(URLEncoder.encode(key.toString(), charset));
|
||||
result.append("=");
|
||||
if (value != null)
|
||||
result.append(URLEncoder.encode(value.toString(), charset));
|
||||
}
|
||||
} else {
|
||||
result.append(key);
|
||||
result.append("=");
|
||||
if (value != null)
|
||||
result.append(value);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new HttpRequestException(e);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -412,15 +428,7 @@ public class HttpRequest {
|
||||
* A {@link ConnectionFactory} which uses the built-in
|
||||
* {@link URL#openConnection()}
|
||||
*/
|
||||
ConnectionFactory DEFAULT = new ConnectionFactory() {
|
||||
public HttpURLConnection create(URL url) throws IOException {
|
||||
return (HttpURLConnection) url.openConnection();
|
||||
}
|
||||
|
||||
public HttpURLConnection create(URL url, Proxy proxy) throws IOException {
|
||||
return (HttpURLConnection) url.openConnection(proxy);
|
||||
}
|
||||
};
|
||||
ConnectionFactory DEFAULT = new OkConnectionFactory();
|
||||
}
|
||||
|
||||
private static ConnectionFactory CONNECTION_FACTORY = ConnectionFactory.DEFAULT;
|
||||
@@ -1929,16 +1937,24 @@ public class HttpRequest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response body as a {@link String} and set it as the value of the
|
||||
* Get the response body as ByteBuffer and set it as the value of the
|
||||
* given reference.
|
||||
*
|
||||
* @param output
|
||||
* @return this request
|
||||
* @throws HttpRequestException
|
||||
*/
|
||||
public HttpRequest body(final AtomicReference<String> output) throws HttpRequestException {
|
||||
output.set(body());
|
||||
return this;
|
||||
public HttpRequest body(final AtomicReference<ByteBuffer> output) throws HttpRequestException {
|
||||
final ByteArrayOutputStream outputStream = byteStream();
|
||||
|
||||
try {
|
||||
copy(buffer(), outputStream);
|
||||
output.set(ByteBuffer.wrap(outputStream.toByteArray()));
|
||||
|
||||
return this;
|
||||
} catch (IOException e) {
|
||||
throw new HttpRequestException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1955,7 +1971,6 @@ public class HttpRequest {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the response body empty?
|
||||
*
|
||||
@@ -2529,6 +2544,16 @@ public class HttpRequest {
|
||||
return header(HEADER_ACCEPT_CHARSET, acceptCharset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the 'Accept-Charset' header to given values
|
||||
*
|
||||
* @param acceptCharsets
|
||||
* @return this request
|
||||
*/
|
||||
public HttpRequest acceptCharset(final String[] acceptCharsets) {
|
||||
return header(HEADER_ACCEPT_CHARSET, TextUtils.join(", ", acceptCharsets));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the 'Content-Encoding' header from the response
|
||||
*
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.github.kevinsawicki.http;
|
||||
|
||||
import okhttp3.OkUrlFactory;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URLStreamHandler;
|
||||
import java.net.Proxy;
|
||||
|
||||
|
||||
public class OkConnectionFactory implements HttpRequest.ConnectionFactory {
|
||||
|
||||
protected OkHttpClient okHttpClient = new OkHttpClient();
|
||||
|
||||
public HttpURLConnection create(URL url) {
|
||||
OkUrlFactory okUrlFactory = new OkUrlFactory(okHttpClient);
|
||||
return (HttpURLConnection) okUrlFactory.open(url);
|
||||
}
|
||||
|
||||
public HttpURLConnection create(URL url, Proxy proxy) {
|
||||
OkHttpClient okHttpClientWithProxy = okHttpClient.newBuilder().proxy(proxy).build();
|
||||
OkUrlFactory okUrlFactory = new OkUrlFactory(okHttpClientWithProxy);
|
||||
return (HttpURLConnection) okUrlFactory.open(url);
|
||||
}
|
||||
}
|
||||
@@ -11,45 +11,45 @@ import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
public class TLSSocketFactory extends SSLSocketFactory {
|
||||
|
||||
private SSLSocketFactory internalSSLSocketFactory;
|
||||
private SSLSocketFactory delegate;
|
||||
|
||||
public TLSSocketFactory(SSLContext context) {
|
||||
internalSSLSocketFactory = context.getSocketFactory();
|
||||
delegate = context.getSocketFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return internalSSLSocketFactory.getDefaultCipherSuites();
|
||||
return delegate.getDefaultCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return internalSSLSocketFactory.getSupportedCipherSuites();
|
||||
return delegate.getSupportedCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
|
||||
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
|
||||
return enableTLSOnSocket(delegate.createSocket(s, host, port, autoClose));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
|
||||
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
|
||||
return enableTLSOnSocket(delegate.createSocket(host, port));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
|
||||
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
|
||||
return enableTLSOnSocket(delegate.createSocket(host, port, localHost, localPort));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
|
||||
return enableTLSOnSocket(delegate.createSocket(host, port));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
|
||||
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
|
||||
return enableTLSOnSocket(delegate.createSocket(address, port, localAddress, localPort));
|
||||
}
|
||||
|
||||
private Socket enableTLSOnSocket(Socket socket) {
|
||||
|
||||
@@ -12,6 +12,14 @@ import org.json.JSONObject;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.nio.charset.MalformedInputException;
|
||||
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -19,6 +27,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.Iterator;
|
||||
|
||||
import android.text.TextUtils;
|
||||
@@ -28,7 +37,7 @@ import com.github.kevinsawicki.http.HttpRequest.HttpRequestException;
|
||||
|
||||
abstract class CordovaHttp {
|
||||
protected static final String TAG = "CordovaHTTP";
|
||||
protected static final String CHARSET = "UTF-8";
|
||||
protected static final String[] ACCEPTED_CHARSETS = new String[] { HttpRequest.CHARSET_UTF8, HttpRequest.CHARSET_LATIN1 };
|
||||
|
||||
private static AtomicBoolean sslPinning = new AtomicBoolean(false);
|
||||
private static AtomicBoolean acceptAllCerts = new AtomicBoolean(false);
|
||||
@@ -43,12 +52,7 @@ abstract class CordovaHttp {
|
||||
private CallbackContext callbackContext;
|
||||
|
||||
public CordovaHttp(String urlString, Object params, JSONObject headers, int timeout, CallbackContext callbackContext) {
|
||||
this.urlString = urlString;
|
||||
this.params = params;
|
||||
this.serializerName = "default";
|
||||
this.headers = headers;
|
||||
this.timeoutInMilliseconds = timeout;
|
||||
this.callbackContext = callbackContext;
|
||||
this(urlString, params, "default", headers, timeout, callbackContext);
|
||||
}
|
||||
|
||||
public CordovaHttp(String urlString, Object params, String serializerName, JSONObject headers, int timeout, CallbackContext callbackContext) {
|
||||
@@ -140,19 +144,12 @@ abstract class CordovaHttp {
|
||||
return request;
|
||||
}
|
||||
|
||||
protected HttpRequest setupDataSerializer(HttpRequest request) throws JSONException, Exception {
|
||||
if (new String("json").equals(this.getSerializerName())) {
|
||||
protected void setupDataSerializer(HttpRequest request) throws JSONException, Exception {
|
||||
if ("json".equals(this.getSerializerName())) {
|
||||
request.contentType(request.CONTENT_TYPE_JSON, request.CHARSET_UTF8);
|
||||
request.send(this.getParamsObject().toString());
|
||||
} else if (new String("utf8").equals(this.getSerializerName())) {
|
||||
} else if ("utf8".equals(this.getSerializerName())) {
|
||||
request.contentType("text/plain", request.CHARSET_UTF8);
|
||||
request.send(this.getParamsMap().get("text").toString());
|
||||
} else
|
||||
{
|
||||
request.form(this.getParamsMap());
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
protected void respondWithError(int status, String msg) {
|
||||
@@ -226,31 +223,85 @@ abstract class CordovaHttp {
|
||||
protected void prepareRequest(HttpRequest request) throws HttpRequestException, JSONException {
|
||||
this.setupRedirect(request);
|
||||
this.setupSecurity(request);
|
||||
|
||||
request.readTimeout(this.getRequestTimeout());
|
||||
request.acceptCharset(CHARSET);
|
||||
request.acceptCharset(ACCEPTED_CHARSETS);
|
||||
request.headers(this.getHeadersMap());
|
||||
request.uncompress(true);
|
||||
}
|
||||
|
||||
protected void prepareRequestBody(HttpRequest request) throws JSONException, Exception {
|
||||
if ("json".equals(this.getSerializerName())) {
|
||||
request.send(this.getParamsObject().toString());
|
||||
} else if ("utf8".equals(this.getSerializerName())) {
|
||||
request.send(this.getParamsMap().get("text").toString());
|
||||
} else {
|
||||
request.form(this.getParamsMap());
|
||||
}
|
||||
}
|
||||
|
||||
private CharsetDecoder createCharsetDecoder(final String charsetName) {
|
||||
return Charset.forName(charsetName).newDecoder()
|
||||
.onMalformedInput(CodingErrorAction.REPORT)
|
||||
.onUnmappableCharacter(CodingErrorAction.REPORT);
|
||||
}
|
||||
|
||||
private String decodeBody(AtomicReference<ByteBuffer> rawOutput, String charsetName)
|
||||
throws CharacterCodingException, MalformedInputException {
|
||||
|
||||
if (charsetName == null) {
|
||||
return tryDecodeByteBuffer(rawOutput);
|
||||
}
|
||||
|
||||
return decodeByteBuffer(rawOutput, charsetName);
|
||||
}
|
||||
|
||||
private String tryDecodeByteBuffer(AtomicReference<ByteBuffer> rawOutput)
|
||||
throws CharacterCodingException, MalformedInputException {
|
||||
|
||||
for (int i = 0; i < ACCEPTED_CHARSETS.length - 1; i++) {
|
||||
try {
|
||||
return decodeByteBuffer(rawOutput, ACCEPTED_CHARSETS[i]);
|
||||
} catch (MalformedInputException e) {
|
||||
continue;
|
||||
} catch (CharacterCodingException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return decodeBody(rawOutput, ACCEPTED_CHARSETS[ACCEPTED_CHARSETS.length - 1]);
|
||||
}
|
||||
|
||||
private String decodeByteBuffer(AtomicReference<ByteBuffer> rawOutput, String charsetName)
|
||||
throws CharacterCodingException, MalformedInputException {
|
||||
|
||||
return createCharsetDecoder(charsetName).decode(rawOutput.get()).toString();
|
||||
}
|
||||
|
||||
protected void returnResponseObject(HttpRequest request) throws HttpRequestException {
|
||||
try {
|
||||
JSONObject response = new JSONObject();
|
||||
int code = request.code();
|
||||
String body = request.body(CHARSET);
|
||||
AtomicReference<ByteBuffer> rawOutputReference = new AtomicReference<ByteBuffer>();
|
||||
|
||||
request.body(rawOutputReference);
|
||||
response.put("status", code);
|
||||
response.put("url", request.url().toString());
|
||||
this.addResponseHeaders(request, response);
|
||||
|
||||
if (code >= 200 && code < 300) {
|
||||
response.put("data", body);
|
||||
response.put("data", decodeBody(rawOutputReference, request.charset()));
|
||||
this.getCallbackContext().success(response);
|
||||
} else {
|
||||
response.put("error", body);
|
||||
response.put("error", decodeBody(rawOutputReference, request.charset()));
|
||||
this.getCallbackContext().error(response);
|
||||
}
|
||||
} catch(JSONException e) {
|
||||
this.respondWithError("There was an error generating the response");
|
||||
} catch(MalformedInputException e) {
|
||||
this.respondWithError("Could not decode response data due to malformed data");
|
||||
} catch(CharacterCodingException e) {
|
||||
this.respondWithError("Could not decode response data due to invalid or unknown charset encoding");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,41 +26,12 @@ class CordovaHttpDelete extends CordovaHttp implements Runnable {
|
||||
try {
|
||||
HttpRequest request = HttpRequest.delete(this.getUrlString(), this.getParamsMap(), false);
|
||||
|
||||
request.readTimeout(this.getRequestTimeout());
|
||||
this.setupRedirect(request);
|
||||
this.setupSecurity(request);
|
||||
request.acceptCharset(CHARSET);
|
||||
request.headers(this.getHeadersMap());
|
||||
request.uncompress(true);
|
||||
|
||||
int code = request.code();
|
||||
String body = request.body(CHARSET);
|
||||
JSONObject response = new JSONObject();
|
||||
|
||||
this.addResponseHeaders(request, response);
|
||||
response.put("status", code);
|
||||
|
||||
if (code >= 200 && code < 300) {
|
||||
response.put("data", body);
|
||||
this.getCallbackContext().success(response);
|
||||
} else {
|
||||
response.put("error", body);
|
||||
this.getCallbackContext().error(response);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
this.respondWithError("There was an error generating the response");
|
||||
this.prepareRequest(request);
|
||||
this.returnResponseObject(request);
|
||||
} catch (HttpRequestException e) {
|
||||
if (e.getCause() instanceof UnknownHostException) {
|
||||
this.respondWithError(0, "The host could not be resolved");
|
||||
} else if (e.getCause() instanceof SocketTimeoutException) {
|
||||
this.respondWithError(1, "The request timed out");
|
||||
} else if (e.getCause() instanceof SSLHandshakeException) {
|
||||
this.respondWithError("SSL handshake failed");
|
||||
} else {
|
||||
this.respondWithError("There was an error with the request: " + e.getMessage());
|
||||
}
|
||||
this.handleHttpRequestException(e);
|
||||
} catch (Exception e) {
|
||||
this.respondWithError(-1, e.getMessage());
|
||||
this.respondWithError(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,9 @@ class CordovaHttpPatch extends CordovaHttp implements Runnable {
|
||||
try {
|
||||
HttpRequest request = HttpRequest.patch(this.getUrlString());
|
||||
|
||||
this.prepareRequest(request);
|
||||
this.setupDataSerializer(request);
|
||||
this.prepareRequest(request);
|
||||
this.prepareRequestBody(request);
|
||||
this.returnResponseObject(request);
|
||||
} catch (HttpRequestException e) {
|
||||
this.handleHttpRequestException(e);
|
||||
|
||||
@@ -25,8 +25,9 @@ class CordovaHttpPost extends CordovaHttp implements Runnable {
|
||||
try {
|
||||
HttpRequest request = HttpRequest.post(this.getUrlString());
|
||||
|
||||
this.prepareRequest(request);
|
||||
this.setupDataSerializer(request);
|
||||
this.prepareRequest(request);
|
||||
this.prepareRequestBody(request);
|
||||
this.returnResponseObject(request);
|
||||
} catch (HttpRequestException e) {
|
||||
this.handleHttpRequestException(e);
|
||||
|
||||
@@ -25,8 +25,9 @@ class CordovaHttpPut extends CordovaHttp implements Runnable {
|
||||
try {
|
||||
HttpRequest request = HttpRequest.put(this.getUrlString());
|
||||
|
||||
this.prepareRequest(request);
|
||||
this.setupDataSerializer(request);
|
||||
this.prepareRequest(request);
|
||||
this.prepareRequestBody(request);
|
||||
this.returnResponseObject(request);
|
||||
} catch (HttpRequestException e) {
|
||||
this.handleHttpRequestException(e);
|
||||
|
||||
@@ -54,8 +54,6 @@ class CordovaHttpUpload extends CordovaHttp implements Runnable {
|
||||
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
||||
String mimeType = mimeTypeMap.getMimeTypeFromExtension(ext);
|
||||
|
||||
request.part(this.name, filename, mimeType, new File(uri));
|
||||
|
||||
Set<?> set = (Set<?>)this.getParamsMap().entrySet();
|
||||
Iterator<?> i = set.iterator();
|
||||
|
||||
@@ -72,6 +70,8 @@ class CordovaHttpUpload extends CordovaHttp implements Runnable {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
request.part(this.name, filename, mimeType, new File(uri));
|
||||
|
||||
this.returnResponseObject(request);
|
||||
} catch (URISyntaxException e) {
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
- (void)handleSuccess:(NSMutableDictionary*)dictionary withResponse:(NSHTTPURLResponse*)response andData:(id)data {
|
||||
if (response != nil) {
|
||||
[dictionary setValue:response.URL.absoluteString forKey:@"url"];
|
||||
[dictionary setObject:[NSNumber numberWithInt:response.statusCode] forKey:@"status"];
|
||||
[dictionary setObject:[NSNumber numberWithInt:(int)response.statusCode] forKey:@"status"];
|
||||
[dictionary setObject:[self copyHeaderFields:response.allHeaderFields] forKey:@"headers"];
|
||||
}
|
||||
|
||||
@@ -67,9 +67,11 @@
|
||||
- (void)handleError:(NSMutableDictionary*)dictionary withResponse:(NSHTTPURLResponse*)response error:(NSError*)error {
|
||||
if (response != nil) {
|
||||
[dictionary setValue:response.URL.absoluteString forKey:@"url"];
|
||||
[dictionary setObject:[NSNumber numberWithInt:response.statusCode] forKey:@"status"];
|
||||
[dictionary setObject:[NSNumber numberWithInt:(int)response.statusCode] forKey:@"status"];
|
||||
[dictionary setObject:[self copyHeaderFields:response.allHeaderFields] forKey:@"headers"];
|
||||
[dictionary setObject:[[NSString alloc] initWithData:(NSData *)error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] encoding:NSUTF8StringEncoding] forKey:@"error"];
|
||||
if (error.userInfo[AFNetworkingOperationFailingURLResponseBodyKey]) {
|
||||
[dictionary setObject:error.userInfo[AFNetworkingOperationFailingURLResponseBodyKey] forKey:@"error"];
|
||||
}
|
||||
} else {
|
||||
[dictionary setObject:[self getStatusCode:error] forKey:@"status"];
|
||||
[dictionary setObject:[error localizedDescription] forKey:@"error"];
|
||||
@@ -515,6 +517,7 @@
|
||||
} failure:^(NSURLSessionTask *task, NSError *error) {
|
||||
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
|
||||
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
|
||||
[dictionary setObject:@"There was an error downloading the file" forKey:@"error"];
|
||||
|
||||
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
|
||||
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
||||
|
||||
@@ -5,4 +5,6 @@
|
||||
|
||||
+ (instancetype)serializer;
|
||||
|
||||
@end
|
||||
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseBodyKey;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,32 +1,126 @@
|
||||
#import "TextResponseSerializer.h"
|
||||
|
||||
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
|
||||
if ([error.domain isEqualToString:domain] && error.code == code) {
|
||||
return YES;
|
||||
} else if (error.userInfo[NSUnderlyingErrorKey]) {
|
||||
return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
|
||||
}
|
||||
NSString * const AFNetworkingOperationFailingURLResponseBodyKey = @"com.alamofire.serialization.response.error.body";
|
||||
NSStringEncoding const SupportedEncodings[6] = { NSUTF8StringEncoding, NSWindowsCP1252StringEncoding, NSISOLatin1StringEncoding, NSISOLatin2StringEncoding, NSASCIIStringEncoding, NSUnicodeStringEncoding };
|
||||
|
||||
return NO;
|
||||
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
|
||||
if (!error) {
|
||||
return underlyingError;
|
||||
}
|
||||
|
||||
if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
|
||||
return error;
|
||||
}
|
||||
|
||||
NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
|
||||
mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;
|
||||
|
||||
return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
|
||||
}
|
||||
|
||||
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
|
||||
if ([error.domain isEqualToString:domain] && error.code == code) {
|
||||
return YES;
|
||||
} else if (error.userInfo[NSUnderlyingErrorKey]) {
|
||||
return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
@implementation TextResponseSerializer
|
||||
|
||||
+ (instancetype)serializer {
|
||||
TextResponseSerializer *serializer = [[self alloc] init];
|
||||
return serializer;
|
||||
TextResponseSerializer *serializer = [[self alloc] init];
|
||||
return serializer;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
self = [super init];
|
||||
|
||||
if (!self) {
|
||||
return nil;
|
||||
if (!self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
self.acceptableContentTypes = nil;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString*)decodeResponseData:(NSData*)rawResponseData withEncoding:(CFStringEncoding)cfEncoding {
|
||||
NSStringEncoding nsEncoding;
|
||||
NSString* decoded = nil;
|
||||
|
||||
if (cfEncoding != kCFStringEncodingInvalidId) {
|
||||
nsEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
|
||||
}
|
||||
|
||||
for (int i = 0; i < sizeof(SupportedEncodings) / sizeof(NSStringEncoding) && !decoded; ++i) {
|
||||
if (cfEncoding == kCFStringEncodingInvalidId || nsEncoding == SupportedEncodings[i]) {
|
||||
decoded = [[NSString alloc] initWithData:rawResponseData encoding:SupportedEncodings[i]];
|
||||
}
|
||||
}
|
||||
|
||||
return decoded;
|
||||
}
|
||||
|
||||
- (CFStringEncoding) getEncoding:(NSURLResponse *)response {
|
||||
CFStringEncoding encoding = kCFStringEncodingInvalidId;
|
||||
|
||||
if (response.textEncodingName) {
|
||||
encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
|
||||
}
|
||||
|
||||
return encoding;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
|
||||
data:(NSData *)data
|
||||
decoded:(NSString **)decoded
|
||||
error:(NSError * __autoreleasing *)error
|
||||
{
|
||||
BOOL responseIsValid = YES;
|
||||
NSError *validationError = nil;
|
||||
|
||||
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
if (data) {
|
||||
*decoded = [self decodeResponseData:data withEncoding:[self getEncoding:response]];
|
||||
}
|
||||
|
||||
self.acceptableContentTypes = nil;
|
||||
if (data && !*decoded) {
|
||||
NSMutableDictionary *mutableUserInfo = [@{
|
||||
NSURLErrorFailingURLErrorKey:[response URL],
|
||||
AFNetworkingOperationFailingURLResponseErrorKey: response,
|
||||
AFNetworkingOperationFailingURLResponseDataErrorKey: data,
|
||||
AFNetworkingOperationFailingURLResponseBodyKey: @"Could not decode response data due to invalid or unknown charset encoding",
|
||||
} mutableCopy];
|
||||
|
||||
return self;
|
||||
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
|
||||
responseIsValid = NO;
|
||||
} else if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
|
||||
NSMutableDictionary *mutableUserInfo = [@{
|
||||
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],
|
||||
NSURLErrorFailingURLErrorKey: [response URL],
|
||||
AFNetworkingOperationFailingURLResponseErrorKey: response,
|
||||
} mutableCopy];
|
||||
|
||||
if (data) {
|
||||
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
|
||||
mutableUserInfo[AFNetworkingOperationFailingURLResponseBodyKey] = *decoded;
|
||||
}
|
||||
|
||||
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
|
||||
responseIsValid = NO;
|
||||
}
|
||||
}
|
||||
|
||||
if (error && !responseIsValid) {
|
||||
*error = validationError;
|
||||
}
|
||||
|
||||
return responseIsValid;
|
||||
}
|
||||
|
||||
#pragma mark - AFURLResponseSerialization
|
||||
@@ -35,25 +129,15 @@ static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger co
|
||||
data:(NSData *)data
|
||||
error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
|
||||
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
|
||||
return nil;
|
||||
}
|
||||
NSString* decoded = nil;
|
||||
|
||||
if (![self validateResponse:(NSHTTPURLResponse *)response data:data decoded:&decoded error:error]) {
|
||||
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization.
|
||||
// See https://github.com/rails/rails/issues/1742
|
||||
NSStringEncoding stringEncoding = self.stringEncoding;
|
||||
if (response.textEncodingName) {
|
||||
CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
|
||||
if (encoding != kCFStringEncodingInvalidId) {
|
||||
stringEncoding = CFStringConvertEncodingToNSStringEncoding(encoding);
|
||||
}
|
||||
}
|
||||
|
||||
NSString *responseString = [[NSString alloc] initWithData:data encoding:stringEncoding];
|
||||
|
||||
return responseString;
|
||||
return decoded;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Binary file not shown.
@@ -1,12 +1,15 @@
|
||||
const hooks = {
|
||||
onBeforeEachTest: function(done) {
|
||||
cordova.plugin.http.clearCookies();
|
||||
cordova.plugin.http.acceptAllCerts(false, done, done);
|
||||
cordova.plugin.http.acceptAllCerts(false, function() {
|
||||
cordova.plugin.http.enableSSLPinning(false, done, done);
|
||||
}, done);
|
||||
}
|
||||
};
|
||||
|
||||
const helpers = {
|
||||
acceptAllCerts: function(done) { cordova.plugin.http.acceptAllCerts(true, done, done); },
|
||||
enableSSLPinning: function(done) { cordova.plugin.http.enableSSLPinning(true, 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')); },
|
||||
@@ -384,7 +387,7 @@ const tests = [
|
||||
cookies.mySecondCookie.should.be.equal('mySecondValue');
|
||||
}
|
||||
},{
|
||||
description: 'should send UTF-8 encoded raw string correctly (POST)',
|
||||
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) {
|
||||
@@ -392,7 +395,74 @@ const tests = [
|
||||
},
|
||||
validationFunc: function(driver, result) {
|
||||
result.type.should.be.equal('resolved');
|
||||
JSON.parse(result.data.data).data.should.eql('this is a test string');
|
||||
JSON.parse(result.data.data).data.should.be.equal('this is a test string');
|
||||
}
|
||||
},{
|
||||
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) {
|
||||
cordova.plugin.http.get('http://httpbin.org/get', { 'query param': 'and value with spaces' }, {}, resolve, reject);
|
||||
},
|
||||
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');
|
||||
}
|
||||
},{
|
||||
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) {
|
||||
cordova.plugin.http.get('http://www.columbia.edu/kermit/latin1.html', {}, {}, resolve, reject);
|
||||
},
|
||||
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');
|
||||
}
|
||||
},{
|
||||
description: 'should return empty body string correctly (GET)',
|
||||
expected: 'resolved: {"status": 200, "data": "" ...',
|
||||
func: function(resolve, reject) {
|
||||
cordova.plugin.http.get('http://httpbin.org/stream/0', {}, {}, resolve, reject);
|
||||
},
|
||||
validationFunc: function(driver, result) {
|
||||
result.type.should.be.equal('resolved');
|
||||
result.data.data.should.be.equal('');
|
||||
}
|
||||
},{
|
||||
description: 'should pin SSL cert correctly (GET)',
|
||||
expected: 'resolved: {"status": 200 ...',
|
||||
before: helpers.enableSSLPinning,
|
||||
func: function(resolve, reject) {
|
||||
cordova.plugin.http.get('https://httpbin.org', {}, {}, resolve, reject);
|
||||
},
|
||||
validationFunc: function(driver, result) {
|
||||
result.type.should.be.equal('resolved');
|
||||
}
|
||||
},{
|
||||
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) {
|
||||
result.type.should.be.equal('resolved');
|
||||
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) {
|
||||
result.type.should.be.equal('resolved');
|
||||
JSON.parse(result.data.data).headers['Content-Type'].should.be.equal('text/plain');
|
||||
}
|
||||
},{
|
||||
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) {
|
||||
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');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user