diff --git a/plugin.xml b/plugin.xml index cfeff2b..e027bf0 100644 --- a/plugin.xml +++ b/plugin.xml @@ -56,19 +56,23 @@ + - - - + + + + + @@ -81,4 +85,4 @@ - \ No newline at end of file + diff --git a/src/android/com/github/kevinsawicki/http/HttpRequest.java b/src/android/com/github/kevinsawicki/http/HttpRequest.java index 237f236..935c972 100644 --- a/src/android/com/github/kevinsawicki/http/HttpRequest.java +++ b/src/android/com/github/kevinsawicki/http/HttpRequest.java @@ -88,6 +88,7 @@ import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.KeyManager; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; @@ -364,7 +365,7 @@ public class HttpRequest { throws HttpRequestException { try { SSLContext context = SSLContext.getInstance("TLS"); - context.init(null, trustManagers, new SecureRandom()); + context.init(keyManagers, trustManagers, new SecureRandom()); if (android.os.Build.VERSION.SDK_INT < 20) { return new TLSSocketFactory(context); diff --git a/src/android/com/github/kevinsawicki/http/OkConnectionFactory.java b/src/android/com/github/kevinsawicki/http/OkConnectionFactory.java deleted file mode 100644 index fb529a6..0000000 --- a/src/android/com/github/kevinsawicki/http/OkConnectionFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -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); - } -} \ No newline at end of file diff --git a/src/android/com/silkimen/cordovahttp/CordovaHttpRequest.java b/src/android/com/silkimen/cordovahttp/CordovaHttpRequest.java new file mode 100644 index 0000000..a8fe13f --- /dev/null +++ b/src/android/com/silkimen/cordovahttp/CordovaHttpRequest.java @@ -0,0 +1,154 @@ +package com.silkimen.cordovahttp; + +import java.io.ByteArrayOutputStream; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; + +import javax.net.ssl.SSLHandshakeException; + +import com.silkimen.http.HttpBodyDecoder; +import com.silkimen.http.HttpRequest; +import com.silkimen.http.HttpRequest.HttpRequestException; +import com.silkimen.http.HttpResponse; +import com.silkimen.http.JsonUtils; + +import org.apache.cordova.CallbackContext; +import org.json.JSONException; +import org.json.JSONObject; + +import android.util.Log; + +public class CordovaHttpRequest implements Runnable { + private String method; + private String url; + private String serializer = "none"; + private Object data; + private JSONObject params; + private JSONObject headers; + private int timeout; + private CallbackContext callbackContext; + + public CordovaHttpRequest(String method, String url, String serializer, Object data, JSONObject headers, + int timeout, CallbackContext callbackContext) { + + this.method = method; + this.url = url; + this.serializer = serializer; + this.data = data; + this.headers = headers; + this.timeout = timeout; + this.callbackContext = callbackContext; + } + + public CordovaHttpRequest(String method, String url, JSONObject params, JSONObject headers, int timeout, + CallbackContext callbackContext) { + + this.method = method; + this.url = url; + this.params = params; + this.headers = headers; + this.timeout = timeout; + this.callbackContext = callbackContext; + } + + @Override + public void run() { + HttpResponse response = new HttpResponse(); + + try { + String processedUrl = HttpRequest.encode(HttpRequest.append(this.url, JsonUtils.getObjectMap(this.params))); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + HttpRequest request = new HttpRequest(processedUrl, this.method) + .followRedirects(true /* @TODO */) + .readTimeout(this.timeout) + .acceptCharset("UTF-8") + .uncompress(true); + + // setup content type before applying headers, so user can override it + this.setContentType(request) + .headers(JsonUtils.getStringMap(this.headers)); + + this.sendBody(request) + .receive(outputStream); + + ByteBuffer rawOutput = ByteBuffer.wrap(outputStream.toByteArray()); + String decodedBody = HttpBodyDecoder.decodeBody(rawOutput, request.charset()); + + response.setStatus(request.code()); + response.setUrl(request.url().toString()); + response.setHeaders(request.headers()); + + if (request.code() >= 200 && request.code() < 300) { + response.setBody(decodedBody); + this.callbackContext.success(response.toJSON()); + } else { + response.setErrorMessage(decodedBody); + this.callbackContext.error(response.toJSON()); + } + } catch (HttpRequestException e) { + if (e.getCause() instanceof SSLHandshakeException) { + response.setStatus(-2); + response.setErrorMessage("SSL handshake failed: " + e.getMessage()); + Log.w("Cordova-Plugin-HTTP", "SSL handshake failed", e); + } else if (e.getCause() instanceof UnknownHostException) { + response.setStatus(-3); + response.setErrorMessage("Host could not be resolved: " + e.getMessage()); + Log.w("Cordova-Plugin-HTTP", "Host could not be resolved", e); + } else if (e.getCause() instanceof SocketTimeoutException) { + response.setStatus(-4); + response.setErrorMessage("Request timed out: " + e.getMessage()); + Log.w("Cordova-Plugin-HTTP", "Request timed out", e); + } else { + response.setStatus(-1); + response.setErrorMessage("There was an error with the request: " + e.getCause().getMessage()); + Log.w("Cordova-Plugin-HTTP", "Generic request error", e); + } + } catch (Exception e) { + response.setStatus(-1); + response.setErrorMessage(e.getMessage()); + Log.e("Cordova-Plugin-HTTP", "An unexpected error occured", e); + } + + try { + if (response.hasFailed()) { + this.callbackContext.error(response.toJSON()); + } else { + this.callbackContext.success(response.toJSON()); + } + } catch (JSONException e) { + Log.e("Cordova-Plugin-HTTP", "An unexpected error occured while processing HTTP response", e); + } + } + + private HttpRequest setContentType(HttpRequest request) { + switch(this.serializer) { + case "json": + return request.contentType("application/json", "UTF-8"); + case "utf8": + return request.contentType("text/plain", "UTF-8"); + case "urlencoded": + // intentionally left blank, because content type is set in HttpRequest.form() + } + + return request; + } + + private HttpRequest sendBody(HttpRequest request) throws JSONException { + if (this.data == null) { + return request; + } + + switch (this.serializer) { + case "json": + return request.send(this.data.toString()); + case "utf8": + return request.send(((JSONObject) this.data).getString("text")); + case "urlencoded": + return request.form(JsonUtils.getObjectMap((JSONObject) this.data)); + } + + return request; + } +} diff --git a/src/android/com/silkimen/http/HostnameVerifierFactory.java b/src/android/com/silkimen/http/HostnameVerifierFactory.java new file mode 100644 index 0000000..8bc4fdc --- /dev/null +++ b/src/android/com/silkimen/http/HostnameVerifierFactory.java @@ -0,0 +1,19 @@ +package com.silkimen.http; + +import javax.net.ssl.HostnameVerifier; + +public class HostnameVerfifierFactory { + private final HostnameVerifier noOpVerififer; + + public HostnameVerifierFactory() { + this.noOpVerififer = new HostnameVerifier() { + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + } + + public HostnameVerifier getNoOpVerifier() { + return this.noOpVerififer; + } +} diff --git a/src/android/com/silkimen/http/HttpBodyDecoder.java b/src/android/com/silkimen/http/HttpBodyDecoder.java new file mode 100644 index 0000000..7aeddc3 --- /dev/null +++ b/src/android/com/silkimen/http/HttpBodyDecoder.java @@ -0,0 +1,48 @@ +package com.silkimen.http; + +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; + +public class HttpBodyDecoder { + private static final String[] ACCEPTED_CHARSETS = new String[] { "UTF-8", "ISO-8859-1" }; + + public static String decodeBody(ByteBuffer rawOutput, String charsetName) + throws CharacterCodingException, MalformedInputException { + + if (charsetName == null) { + return tryDecodeByteBuffer(rawOutput); + } + + return decodeByteBuffer(rawOutput, charsetName); + } + + private static String tryDecodeByteBuffer(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 static String decodeByteBuffer(ByteBuffer rawOutput, String charsetName) + throws CharacterCodingException, MalformedInputException { + + return createCharsetDecoder(charsetName).decode(rawOutput).toString(); + } + + private static CharsetDecoder createCharsetDecoder(String charsetName) { + return Charset.forName(charsetName).newDecoder().onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT); + } +} diff --git a/src/android/com/silkimen/http/HttpRequest.java b/src/android/com/silkimen/http/HttpRequest.java new file mode 100644 index 0000000..7e638bb --- /dev/null +++ b/src/android/com/silkimen/http/HttpRequest.java @@ -0,0 +1,3095 @@ +/* + * Copyright (c) 2014 Kevin Sawicki + * modified by contributors of cordova-plugin-advanced-http + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +package com.silkimen.http; + +import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; +import static java.net.HttpURLConnection.HTTP_CREATED; +import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR; +import static java.net.HttpURLConnection.HTTP_NO_CONTENT; +import static java.net.HttpURLConnection.HTTP_NOT_FOUND; +import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED; +import static java.net.HttpURLConnection.HTTP_OK; +import static java.net.Proxy.Type.HTTP; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.Flushable; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintStream; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.security.AccessController; +import java.security.GeneralSecurityException; +import java.security.PrivilegedAction; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.zip.GZIPInputStream; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +/** + * A fluid interface for making HTTP requests using an underlying + * {@link HttpURLConnection} (or sub-class). + *

+ * Each instance supports making a single request and cannot be reused for + * further requests. + */ +public class HttpRequest { + + /** + * 'UTF-8' charset name + */ + public static final String CHARSET_UTF8 = "UTF-8"; + + /** + * 'application/x-www-form-urlencoded' content type header value + */ + public static final String CONTENT_TYPE_FORM = "application/x-www-form-urlencoded"; + + /** + * 'application/json' content type header value + */ + public static final String CONTENT_TYPE_JSON = "application/json"; + + /** + * 'gzip' encoding header value + */ + public static final String ENCODING_GZIP = "gzip"; + + /** + * 'Accept' header name + */ + public static final String HEADER_ACCEPT = "Accept"; + + /** + * 'Accept-Charset' header name + */ + public static final String HEADER_ACCEPT_CHARSET = "Accept-Charset"; + + /** + * 'Accept-Encoding' header name + */ + public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding"; + + /** + * 'Authorization' header name + */ + public static final String HEADER_AUTHORIZATION = "Authorization"; + + /** + * 'Cache-Control' header name + */ + public static final String HEADER_CACHE_CONTROL = "Cache-Control"; + + /** + * 'Content-Encoding' header name + */ + public static final String HEADER_CONTENT_ENCODING = "Content-Encoding"; + + /** + * 'Content-Length' header name + */ + public static final String HEADER_CONTENT_LENGTH = "Content-Length"; + + /** + * 'Content-Type' header name + */ + public static final String HEADER_CONTENT_TYPE = "Content-Type"; + + /** + * 'Date' header name + */ + public static final String HEADER_DATE = "Date"; + + /** + * 'ETag' header name + */ + public static final String HEADER_ETAG = "ETag"; + + /** + * 'Expires' header name + */ + public static final String HEADER_EXPIRES = "Expires"; + + /** + * 'If-None-Match' header name + */ + public static final String HEADER_IF_NONE_MATCH = "If-None-Match"; + + /** + * 'Last-Modified' header name + */ + public static final String HEADER_LAST_MODIFIED = "Last-Modified"; + + /** + * 'Location' header name + */ + public static final String HEADER_LOCATION = "Location"; + + /** + * 'Proxy-Authorization' header name + */ + public static final String HEADER_PROXY_AUTHORIZATION = "Proxy-Authorization"; + + /** + * 'Referer' header name + */ + public static final String HEADER_REFERER = "Referer"; + + /** + * 'Server' header name + */ + public static final String HEADER_SERVER = "Server"; + + /** + * 'User-Agent' header name + */ + public static final String HEADER_USER_AGENT = "User-Agent"; + + /** + * 'DELETE' request method + */ + public static final String METHOD_DELETE = "DELETE"; + + /** + * 'GET' request method + */ + public static final String METHOD_GET = "GET"; + + /** + * 'HEAD' request method + */ + public static final String METHOD_HEAD = "HEAD"; + + /** + * 'OPTIONS' options method + */ + public static final String METHOD_OPTIONS = "OPTIONS"; + + /** + * 'POST' request method + */ + public static final String METHOD_POST = "POST"; + + /** + * 'PUT' request method + */ + public static final String METHOD_PUT = "PUT"; + + /** + * 'TRACE' request method + */ + public static final String METHOD_TRACE = "TRACE"; + + /** + * 'charset' header value parameter + */ + public static final String PARAM_CHARSET = "charset"; + + private static final String BOUNDARY = "00content0boundary00"; + + private static final String CONTENT_TYPE_MULTIPART = "multipart/form-data; boundary=" + BOUNDARY; + + private static final String CRLF = "\r\n"; + + private static final String[] EMPTY_STRINGS = new String[0]; + + private static String getValidCharset(final String charset) { + if (charset != null && charset.length() > 0) + return charset; + else + return CHARSET_UTF8; + } + + private static StringBuilder addPathSeparator(final String baseUrl, final StringBuilder result) { + // Add trailing slash if the base URL doesn't have any path segments. + // + // The following test is checking for the last slash not being part of + // the protocol to host separator: '://'. + if (baseUrl.indexOf(':') + 2 == baseUrl.lastIndexOf('/')) + result.append('/'); + return result; + } + + private static StringBuilder addParamPrefix(final String baseUrl, final StringBuilder result) { + // Add '?' if missing and add '&' if params already exist in base url + final int queryStart = baseUrl.indexOf('?'); + final int lastChar = result.length() - 1; + if (queryStart == -1) + result.append('?'); + else if (queryStart < lastChar && baseUrl.charAt(lastChar) != '&') + result.append('&'); + return result; + } + + private static StringBuilder addParam(final Object key, Object value, final StringBuilder result) { + 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("&"); + } + } else { + result.append(key); + result.append("="); + if (value != null) + result.append(value); + } + + return result; + } + + /** + * Creates {@link HttpURLConnection HTTP connections} for {@link URL urls}. + */ + public interface ConnectionFactory { + /** + * Open an {@link HttpURLConnection} for the specified {@link URL}. + * + * @throws IOException + */ + HttpURLConnection create(URL url) throws IOException; + + /** + * Open an {@link HttpURLConnection} for the specified {@link URL} and + * {@link Proxy}. + * + * @throws IOException + */ + HttpURLConnection create(URL url, Proxy proxy) throws IOException; + + /** + * 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); + } + }; + } + + private static ConnectionFactory CONNECTION_FACTORY = ConnectionFactory.DEFAULT; + + /** + * Specify the {@link ConnectionFactory} used to create new requests. + */ + public static void setConnectionFactory(final ConnectionFactory connectionFactory) { + if (connectionFactory == null) + CONNECTION_FACTORY = ConnectionFactory.DEFAULT; + else + CONNECTION_FACTORY = connectionFactory; + } + + /** + * Callback interface for reporting upload progress for a request. + */ + public interface UploadProgress { + /** + * Callback invoked as data is uploaded by the request. + * + * @param uploaded The number of bytes already uploaded + * @param total The total number of bytes that will be uploaded or -1 if the + * length is unknown. + */ + void onUpload(long uploaded, long total); + + UploadProgress DEFAULT = new UploadProgress() { + public void onUpload(long uploaded, long total) { + } + }; + } + + /** + *

+ * Encodes and decodes to and from Base64 notation. + *

+ *

+ * I am placing this code in the Public Domain. Do with it as you will. This + * software comes with no guarantees or warranties but with plenty of + * well-wishing instead! Please visit + * http://iharder.net/base64 + * periodically to check for updates or to contribute improvements. + *

+ * + * @author Robert Harder + * @author rob@iharder.net + * @version 2.3.7 + */ + public static class Base64 { + + /** The equals sign (=) as a byte. */ + private final static byte EQUALS_SIGN = (byte) '='; + + /** Preferred encoding. */ + private final static String PREFERRED_ENCODING = "US-ASCII"; + + /** The 64 valid Base64 values. */ + private final static byte[] _STANDARD_ALPHABET = { (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', + (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', + (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', + (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', + (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', + (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', + (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', + (byte) '7', (byte) '8', (byte) '9', (byte) '+', (byte) '/' }; + + /** Defeats instantiation. */ + private Base64() { + } + + /** + *

+ * Encodes up to three bytes of the array source and writes the + * resulting four Base64 bytes to destination. The source and + * destination arrays can be manipulated anywhere along their length by + * specifying srcOffset and destOffset. This method does + * not check to make sure your arrays are large enough to accomodate + * srcOffset + 3 for the source array or + * destOffset + 4 for the destination array. The actual + * number of significant bytes in your array is given by numSigBytes. + *

+ *

+ * This is the lowest level of the encoding methods with all possible + * parameters. + *

+ * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param numSigBytes the number of significant bytes in your array + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @return the destination array + * @since 1.3 + */ + private static byte[] encode3to4(byte[] source, int srcOffset, int numSigBytes, byte[] destination, + int destOffset) { + + byte[] ALPHABET = _STANDARD_ALPHABET; + + int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) + | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) + | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); + + switch (numSigBytes) { + case 3: + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f]; + return destination; + + case 2: + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = EQUALS_SIGN; + return destination; + + case 1: + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = EQUALS_SIGN; + destination[destOffset + 3] = EQUALS_SIGN; + return destination; + + default: + return destination; + } + } + + /** + * Encode string as a byte array in Base64 annotation. + * + * @param string + * @return The Base64-encoded data as a string + */ + public static String encode(String string) { + byte[] bytes; + try { + bytes = string.getBytes(PREFERRED_ENCODING); + } catch (UnsupportedEncodingException e) { + bytes = string.getBytes(); + } + return encodeBytes(bytes); + } + + /** + * Encodes a byte array into Base64 notation. + * + * @param source The data to convert + * @return The Base64-encoded data as a String + * @throws NullPointerException if source array is null + * @throws IllegalArgumentException if source array, offset, or length are + * invalid + * @since 2.0 + */ + public static String encodeBytes(byte[] source) { + return encodeBytes(source, 0, source.length); + } + + /** + * Encodes a byte array into Base64 notation. + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @return The Base64-encoded data as a String + * @throws NullPointerException if source array is null + * @throws IllegalArgumentException if source array, offset, or length are + * invalid + * @since 2.0 + */ + public static String encodeBytes(byte[] source, int off, int len) { + byte[] encoded = encodeBytesToBytes(source, off, len); + try { + return new String(encoded, PREFERRED_ENCODING); + } catch (UnsupportedEncodingException uue) { + return new String(encoded); + } + } + + /** + * Similar to {@link #encodeBytes(byte[], int, int)} but returns a byte array + * instead of instantiating a String. This is more efficient if you're working + * with I/O streams and have large data sets to encode. + * + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @return The Base64-encoded data as a String if there is an error + * @throws NullPointerException if source array is null + * @throws IllegalArgumentException if source array, offset, or length are + * invalid + * @since 2.3.1 + */ + public static byte[] encodeBytesToBytes(byte[] source, int off, int len) { + + if (source == null) + throw new NullPointerException("Cannot serialize a null array."); + + if (off < 0) + throw new IllegalArgumentException("Cannot have negative offset: " + off); + + if (len < 0) + throw new IllegalArgumentException("Cannot have length offset: " + len); + + if (off + len > source.length) + throw new IllegalArgumentException(String + .format("Cannot have offset of %d and length of %d with array of length %d", off, len, source.length)); + + // Bytes needed for actual encoding + int encLen = (len / 3) * 4 + (len % 3 > 0 ? 4 : 0); + + byte[] outBuff = new byte[encLen]; + + int d = 0; + int e = 0; + int len2 = len - 2; + for (; d < len2; d += 3, e += 4) + encode3to4(source, d + off, 3, outBuff, e); + + if (d < len) { + encode3to4(source, d + off, len - d, outBuff, e); + e += 4; + } + + if (e <= outBuff.length - 1) { + byte[] finalOut = new byte[e]; + System.arraycopy(outBuff, 0, finalOut, 0, e); + return finalOut; + } else + return outBuff; + } + } + + /** + * HTTP request exception whose cause is always an {@link IOException} + */ + public static class HttpRequestException extends RuntimeException { + + private static final long serialVersionUID = -1170466989781746231L; + + /** + * Create a new HttpRequestException with the given cause + * + * @param cause + */ + public HttpRequestException(final IOException cause) { + super(cause); + } + + /** + * Get {@link IOException} that triggered this request exception + * + * @return {@link IOException} cause + */ + @Override + public IOException getCause() { + return (IOException) super.getCause(); + } + } + + /** + * Operation that handles executing a callback once complete and handling nested + * exceptions + * + * @param + */ + protected static abstract class Operation implements Callable { + + /** + * Run operation + * + * @return result + * @throws HttpRequestException + * @throws IOException + */ + protected abstract V run() throws HttpRequestException, IOException; + + /** + * Operation complete callback + * + * @throws IOException + */ + protected abstract void done() throws IOException; + + public V call() throws HttpRequestException { + boolean thrown = false; + try { + return run(); + } catch (HttpRequestException e) { + thrown = true; + throw e; + } catch (IOException e) { + thrown = true; + throw new HttpRequestException(e); + } finally { + try { + done(); + } catch (IOException e) { + if (!thrown) + throw new HttpRequestException(e); + } + } + } + } + + /** + * Class that ensures a {@link Closeable} gets closed with proper exception + * handling. + * + * @param + */ + protected static abstract class CloseOperation extends Operation { + + private final Closeable closeable; + + private final boolean ignoreCloseExceptions; + + /** + * Create closer for operation + * + * @param closeable + * @param ignoreCloseExceptions + */ + protected CloseOperation(final Closeable closeable, final boolean ignoreCloseExceptions) { + this.closeable = closeable; + this.ignoreCloseExceptions = ignoreCloseExceptions; + } + + @Override + protected void done() throws IOException { + if (closeable instanceof Flushable) + ((Flushable) closeable).flush(); + if (ignoreCloseExceptions) + try { + closeable.close(); + } catch (IOException e) { + // Ignored + } + else + closeable.close(); + } + } + + /** + * Class that and ensures a {@link Flushable} gets flushed with proper exception + * handling. + * + * @param + */ + protected static abstract class FlushOperation extends Operation { + + private final Flushable flushable; + + /** + * Create flush operation + * + * @param flushable + */ + protected FlushOperation(final Flushable flushable) { + this.flushable = flushable; + } + + @Override + protected void done() throws IOException { + flushable.flush(); + } + } + + /** + * Request output stream + */ + public static class RequestOutputStream extends BufferedOutputStream { + + private final CharsetEncoder encoder; + + /** + * Create request output stream + * + * @param stream + * @param charset + * @param bufferSize + */ + public RequestOutputStream(final OutputStream stream, final String charset, final int bufferSize) { + super(stream, bufferSize); + + encoder = Charset.forName(getValidCharset(charset)).newEncoder(); + } + + /** + * Write string to stream + * + * @param value + * @return this stream + * @throws IOException + */ + public RequestOutputStream write(final String value) throws IOException { + final ByteBuffer bytes = encoder.encode(CharBuffer.wrap(value)); + + super.write(bytes.array(), 0, bytes.limit()); + + return this; + } + } + + /** + * Represents array of any type as list of objects so we can easily iterate over + * it + * + * @param array of elements + * @return list with the same elements + */ + private static List arrayToList(final Object array) { + if (array instanceof Object[]) + return Arrays.asList((Object[]) array); + + List result = new ArrayList(); + // Arrays of the primitive types can't be cast to array of Object, so this: + if (array instanceof int[]) + for (int value : (int[]) array) + result.add(value); + else if (array instanceof boolean[]) + for (boolean value : (boolean[]) array) + result.add(value); + else if (array instanceof long[]) + for (long value : (long[]) array) + result.add(value); + else if (array instanceof float[]) + for (float value : (float[]) array) + result.add(value); + else if (array instanceof double[]) + for (double value : (double[]) array) + result.add(value); + else if (array instanceof short[]) + for (short value : (short[]) array) + result.add(value); + else if (array instanceof byte[]) + for (byte value : (byte[]) array) + result.add(value); + else if (array instanceof char[]) + for (char value : (char[]) array) + result.add(value); + return result; + } + + /** + * Encode the given URL as an ASCII {@link String} + *

+ * This method ensures the path and query segments of the URL are properly + * encoded such as ' ' characters being encoded to '%20' or any UTF-8 characters + * that are non-ASCII. No encoding of URLs is done by default by the + * {@link HttpRequest} constructors and so if URL encoding is needed this method + * should be called before calling the {@link HttpRequest} constructor. + * + * @param url + * @return encoded URL + * @throws HttpRequestException + */ + public static String encode(final CharSequence url) throws HttpRequestException { + URL parsed; + try { + parsed = new URL(url.toString()); + } catch (IOException e) { + throw new HttpRequestException(e); + } + + String host = parsed.getHost(); + int port = parsed.getPort(); + if (port != -1) + host = host + ':' + Integer.toString(port); + + try { + String encoded = new URI(parsed.getProtocol(), host, parsed.getPath(), parsed.getQuery(), null).toASCIIString(); + int paramsStart = encoded.indexOf('?'); + if (paramsStart > 0 && paramsStart + 1 < encoded.length()) + encoded = encoded.substring(0, paramsStart + 1) + encoded.substring(paramsStart + 1).replace("+", "%2B"); + return encoded; + } catch (URISyntaxException e) { + IOException io = new IOException("Parsing URI failed"); + io.initCause(e); + throw new HttpRequestException(io); + } + } + + /** + * Append given map as query parameters to the base URL + *

+ * Each map entry's key will be a parameter name and the value's + * {@link Object#toString()} will be the parameter value. + * + * @param url + * @param params + * @return URL with appended query params + */ + public static String append(final CharSequence url, final Map params) { + final String baseUrl = url.toString(); + if (params == null || params.isEmpty()) + return baseUrl; + + final StringBuilder result = new StringBuilder(baseUrl); + + addPathSeparator(baseUrl, result); + addParamPrefix(baseUrl, result); + + Entry entry; + Iterator iterator = params.entrySet().iterator(); + entry = (Entry) iterator.next(); + addParam(entry.getKey().toString(), entry.getValue(), result); + + while (iterator.hasNext()) { + result.append('&'); + entry = (Entry) iterator.next(); + addParam(entry.getKey().toString(), entry.getValue(), result); + } + + return result.toString(); + } + + /** + * Append given name/value pairs as query parameters to the base URL + *

+ * The params argument is interpreted as a sequence of name/value pairs so the + * given number of params must be divisible by 2. + * + * @param url + * @param params name/value pairs + * @return URL with appended query params + */ + public static String append(final CharSequence url, final Object... params) { + final String baseUrl = url.toString(); + if (params == null || params.length == 0) + return baseUrl; + + if (params.length % 2 != 0) + throw new IllegalArgumentException("Must specify an even number of parameter names/values"); + + final StringBuilder result = new StringBuilder(baseUrl); + + addPathSeparator(baseUrl, result); + addParamPrefix(baseUrl, result); + + addParam(params[0], params[1], result); + + for (int i = 2; i < params.length; i += 2) { + result.append('&'); + addParam(params[i], params[i + 1], result); + } + + return result.toString(); + } + + /** + * Start a 'GET' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest get(final CharSequence url) throws HttpRequestException { + return new HttpRequest(url, METHOD_GET); + } + + /** + * Start a 'GET' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest get(final URL url) throws HttpRequestException { + return new HttpRequest(url, METHOD_GET); + } + + /** + * Start a 'GET' request to the given URL along with the query params + * + * @param baseUrl + * @param params The query parameters to include as part of the baseUrl + * @param encode true to encode the full URL + * + * @see #append(CharSequence, Map) + * @see #encode(CharSequence) + * + * @return request + */ + public static HttpRequest get(final CharSequence baseUrl, final Map params, final boolean encode) { + String url = append(baseUrl, params); + return get(encode ? encode(url) : url); + } + + /** + * Start a 'GET' request to the given URL along with the query params + * + * @param baseUrl + * @param encode true to encode the full URL + * @param params the name/value query parameter pairs to include as part of the + * baseUrl + * + * @see #append(CharSequence, Object...) + * @see #encode(CharSequence) + * + * @return request + */ + public static HttpRequest get(final CharSequence baseUrl, final boolean encode, final Object... params) { + String url = append(baseUrl, params); + return get(encode ? encode(url) : url); + } + + /** + * Start a 'POST' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest post(final CharSequence url) throws HttpRequestException { + return new HttpRequest(url, METHOD_POST); + } + + /** + * Start a 'POST' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest post(final URL url) throws HttpRequestException { + return new HttpRequest(url, METHOD_POST); + } + + /** + * Start a 'POST' request to the given URL along with the query params + * + * @param baseUrl + * @param params the query parameters to include as part of the baseUrl + * @param encode true to encode the full URL + * + * @see #append(CharSequence, Map) + * @see #encode(CharSequence) + * + * @return request + */ + public static HttpRequest post(final CharSequence baseUrl, final Map params, final boolean encode) { + String url = append(baseUrl, params); + return post(encode ? encode(url) : url); + } + + /** + * Start a 'POST' request to the given URL along with the query params + * + * @param baseUrl + * @param encode true to encode the full URL + * @param params the name/value query parameter pairs to include as part of the + * baseUrl + * + * @see #append(CharSequence, Object...) + * @see #encode(CharSequence) + * + * @return request + */ + public static HttpRequest post(final CharSequence baseUrl, final boolean encode, final Object... params) { + String url = append(baseUrl, params); + return post(encode ? encode(url) : url); + } + + /** + * Start a 'PUT' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest put(final CharSequence url) throws HttpRequestException { + return new HttpRequest(url, METHOD_PUT); + } + + /** + * Start a 'PUT' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest put(final URL url) throws HttpRequestException { + return new HttpRequest(url, METHOD_PUT); + } + + /** + * Start a 'PUT' request to the given URL along with the query params + * + * @param baseUrl + * @param params the query parameters to include as part of the baseUrl + * @param encode true to encode the full URL + * + * @see #append(CharSequence, Map) + * @see #encode(CharSequence) + * + * @return request + */ + public static HttpRequest put(final CharSequence baseUrl, final Map params, final boolean encode) { + String url = append(baseUrl, params); + return put(encode ? encode(url) : url); + } + + /** + * Start a 'PUT' request to the given URL along with the query params + * + * @param baseUrl + * @param encode true to encode the full URL + * @param params the name/value query parameter pairs to include as part of the + * baseUrl + * + * @see #append(CharSequence, Object...) + * @see #encode(CharSequence) + * + * @return request + */ + public static HttpRequest put(final CharSequence baseUrl, final boolean encode, final Object... params) { + String url = append(baseUrl, params); + return put(encode ? encode(url) : url); + } + + /** + * Start a 'DELETE' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest delete(final CharSequence url) throws HttpRequestException { + return new HttpRequest(url, METHOD_DELETE); + } + + /** + * Start a 'DELETE' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest delete(final URL url) throws HttpRequestException { + return new HttpRequest(url, METHOD_DELETE); + } + + /** + * Start a 'DELETE' request to the given URL along with the query params + * + * @param baseUrl + * @param params The query parameters to include as part of the baseUrl + * @param encode true to encode the full URL + * + * @see #append(CharSequence, Map) + * @see #encode(CharSequence) + * + * @return request + */ + public static HttpRequest delete(final CharSequence baseUrl, final Map params, final boolean encode) { + String url = append(baseUrl, params); + return delete(encode ? encode(url) : url); + } + + /** + * Start a 'DELETE' request to the given URL along with the query params + * + * @param baseUrl + * @param encode true to encode the full URL + * @param params the name/value query parameter pairs to include as part of the + * baseUrl + * + * @see #append(CharSequence, Object...) + * @see #encode(CharSequence) + * + * @return request + */ + public static HttpRequest delete(final CharSequence baseUrl, final boolean encode, final Object... params) { + String url = append(baseUrl, params); + return delete(encode ? encode(url) : url); + } + + /** + * Start a 'HEAD' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest head(final CharSequence url) throws HttpRequestException { + return new HttpRequest(url, METHOD_HEAD); + } + + /** + * Start a 'HEAD' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest head(final URL url) throws HttpRequestException { + return new HttpRequest(url, METHOD_HEAD); + } + + /** + * Start a 'HEAD' request to the given URL along with the query params + * + * @param baseUrl + * @param params The query parameters to include as part of the baseUrl + * @param encode true to encode the full URL + * + * @see #append(CharSequence, Map) + * @see #encode(CharSequence) + * + * @return request + */ + public static HttpRequest head(final CharSequence baseUrl, final Map params, final boolean encode) { + String url = append(baseUrl, params); + return head(encode ? encode(url) : url); + } + + /** + * Start a 'GET' request to the given URL along with the query params + * + * @param baseUrl + * @param encode true to encode the full URL + * @param params the name/value query parameter pairs to include as part of the + * baseUrl + * + * @see #append(CharSequence, Object...) + * @see #encode(CharSequence) + * + * @return request + */ + public static HttpRequest head(final CharSequence baseUrl, final boolean encode, final Object... params) { + String url = append(baseUrl, params); + return head(encode ? encode(url) : url); + } + + /** + * Start an 'OPTIONS' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest options(final CharSequence url) throws HttpRequestException { + return new HttpRequest(url, METHOD_OPTIONS); + } + + /** + * Start an 'OPTIONS' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest options(final URL url) throws HttpRequestException { + return new HttpRequest(url, METHOD_OPTIONS); + } + + /** + * Start a 'TRACE' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest trace(final CharSequence url) throws HttpRequestException { + return new HttpRequest(url, METHOD_TRACE); + } + + /** + * Start a 'TRACE' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest trace(final URL url) throws HttpRequestException { + return new HttpRequest(url, METHOD_TRACE); + } + + /** + * Set the 'http.keepAlive' property to the given value. + *

+ * This setting will apply to all requests. + * + * @param keepAlive + */ + public static void keepAlive(final boolean keepAlive) { + setProperty("http.keepAlive", Boolean.toString(keepAlive)); + } + + /** + * Set the 'http.maxConnections' property to the given value. + *

+ * This setting will apply to all requests. + * + * @param maxConnections + */ + public static void maxConnections(final int maxConnections) { + setProperty("http.maxConnections", Integer.toString(maxConnections)); + } + + /** + * Set the 'http.proxyHost' and 'https.proxyHost' properties to the given host + * value. + *

+ * This setting will apply to all requests. + * + * @param host + */ + public static void proxyHost(final String host) { + setProperty("http.proxyHost", host); + setProperty("https.proxyHost", host); + } + + /** + * Set the 'http.proxyPort' and 'https.proxyPort' properties to the given port + * number. + *

+ * This setting will apply to all requests. + * + * @param port + */ + public static void proxyPort(final int port) { + final String portValue = Integer.toString(port); + setProperty("http.proxyPort", portValue); + setProperty("https.proxyPort", portValue); + } + + /** + * Set the 'http.nonProxyHosts' property to the given host values. + *

+ * Hosts will be separated by a '|' character. + *

+ * This setting will apply to all requests. + * + * @param hosts + */ + public static void nonProxyHosts(final String... hosts) { + if (hosts != null && hosts.length > 0) { + StringBuilder separated = new StringBuilder(); + int last = hosts.length - 1; + for (int i = 0; i < last; i++) + separated.append(hosts[i]).append('|'); + separated.append(hosts[last]); + setProperty("http.nonProxyHosts", separated.toString()); + } else + setProperty("http.nonProxyHosts", null); + } + + /** + * Set property to given value. + *

+ * Specifying a null value will cause the property to be cleared + * + * @param name + * @param value + * @return previous value + */ + private static String setProperty(final String name, final String value) { + final PrivilegedAction action; + if (value != null) + action = new PrivilegedAction() { + + public String run() { + return System.setProperty(name, value); + } + }; + else + action = new PrivilegedAction() { + + public String run() { + return System.clearProperty(name); + } + }; + return AccessController.doPrivileged(action); + } + + private HttpURLConnection connection = null; + + private final URL url; + + private final String requestMethod; + + private RequestOutputStream output; + + private boolean multipart; + + private boolean form; + + private boolean ignoreCloseExceptions = true; + + private boolean uncompress = false; + + private int bufferSize = 8192; + + private long totalSize = -1; + + private long totalWritten = 0; + + private String httpProxyHost; + + private int httpProxyPort; + + private UploadProgress progress = UploadProgress.DEFAULT; + + /** + * Create HTTP connection wrapper + * + * @param url Remote resource URL. + * @param method HTTP request method (e.g., "GET", "POST"). + * @throws HttpRequestException + */ + public HttpRequest(final CharSequence url, final String method) throws HttpRequestException { + try { + this.url = new URL(url.toString()); + } catch (MalformedURLException e) { + throw new HttpRequestException(e); + } + this.requestMethod = method; + } + + /** + * Create HTTP connection wrapper + * + * @param url Remote resource URL. + * @param method HTTP request method (e.g., "GET", "POST"). + * @throws HttpRequestException + */ + public HttpRequest(final URL url, final String method) throws HttpRequestException { + this.url = url; + this.requestMethod = method; + } + + private Proxy createProxy() { + return new Proxy(HTTP, new InetSocketAddress(httpProxyHost, httpProxyPort)); + } + + private HttpURLConnection createConnection() { + try { + final HttpURLConnection connection; + if (httpProxyHost != null) + connection = CONNECTION_FACTORY.create(url, createProxy()); + else + connection = CONNECTION_FACTORY.create(url); + connection.setRequestMethod(requestMethod); + return connection; + } catch (IOException e) { + throw new HttpRequestException(e); + } + } + + @Override + public String toString() { + return method() + ' ' + url(); + } + + /** + * Get underlying connection + * + * @return connection + */ + public HttpURLConnection getConnection() { + if (connection == null) + connection = createConnection(); + return connection; + } + + /** + * Set whether or not to ignore exceptions that occur from calling + * {@link Closeable#close()} + *

+ * The default value of this setting is true + * + * @param ignore + * @return this request + */ + public HttpRequest ignoreCloseExceptions(final boolean ignore) { + ignoreCloseExceptions = ignore; + return this; + } + + /** + * Get whether or not exceptions thrown by {@link Closeable#close()} are ignored + * + * @return true if ignoring, false if throwing + */ + public boolean ignoreCloseExceptions() { + return ignoreCloseExceptions; + } + + /** + * Get the status code of the response + * + * @return the response code + * @throws HttpRequestException + */ + public int code() throws HttpRequestException { + try { + closeOutput(); + return getConnection().getResponseCode(); + } catch (IOException e) { + throw new HttpRequestException(e); + } + } + + /** + * Set the value of the given {@link AtomicInteger} to the status code of the + * response + * + * @param output + * @return this request + * @throws HttpRequestException + */ + public HttpRequest code(final AtomicInteger output) throws HttpRequestException { + output.set(code()); + return this; + } + + /** + * Is the response code a 200 OK? + * + * @return true if 200, false otherwise + * @throws HttpRequestException + */ + public boolean ok() throws HttpRequestException { + return HTTP_OK == code(); + } + + /** + * Is the response code a 201 Created? + * + * @return true if 201, false otherwise + * @throws HttpRequestException + */ + public boolean created() throws HttpRequestException { + return HTTP_CREATED == code(); + } + + /** + * Is the response code a 204 No Content? + * + * @return true if 204, false otherwise + * @throws HttpRequestException + */ + public boolean noContent() throws HttpRequestException { + return HTTP_NO_CONTENT == code(); + } + + /** + * Is the response code a 500 Internal Server Error? + * + * @return true if 500, false otherwise + * @throws HttpRequestException + */ + public boolean serverError() throws HttpRequestException { + return HTTP_INTERNAL_ERROR == code(); + } + + /** + * Is the response code a 400 Bad Request? + * + * @return true if 400, false otherwise + * @throws HttpRequestException + */ + public boolean badRequest() throws HttpRequestException { + return HTTP_BAD_REQUEST == code(); + } + + /** + * Is the response code a 404 Not Found? + * + * @return true if 404, false otherwise + * @throws HttpRequestException + */ + public boolean notFound() throws HttpRequestException { + return HTTP_NOT_FOUND == code(); + } + + /** + * Is the response code a 304 Not Modified? + * + * @return true if 304, false otherwise + * @throws HttpRequestException + */ + public boolean notModified() throws HttpRequestException { + return HTTP_NOT_MODIFIED == code(); + } + + /** + * Get status message of the response + * + * @return message + * @throws HttpRequestException + */ + public String message() throws HttpRequestException { + try { + closeOutput(); + return getConnection().getResponseMessage(); + } catch (IOException e) { + throw new HttpRequestException(e); + } + } + + /** + * Disconnect the connection + * + * @return this request + */ + public HttpRequest disconnect() { + getConnection().disconnect(); + return this; + } + + /** + * Set chunked streaming mode to the given size + * + * @param size + * @return this request + */ + public HttpRequest chunk(final int size) { + getConnection().setChunkedStreamingMode(size); + return this; + } + + /** + * Set the size used when buffering and copying between streams + *

+ * This size is also used for send and receive buffers created for both char and + * byte arrays + *

+ * The default buffer size is 8,192 bytes + * + * @param size + * @return this request + */ + public HttpRequest bufferSize(final int size) { + if (size < 1) + throw new IllegalArgumentException("Size must be greater than zero"); + bufferSize = size; + return this; + } + + /** + * Get the configured buffer size + *

+ * The default buffer size is 8,192 bytes + * + * @return buffer size + */ + public int bufferSize() { + return bufferSize; + } + + /** + * Set whether or not the response body should be automatically uncompressed + * when read from. + *

+ * This will only affect requests that have the 'Content-Encoding' response + * header set to 'gzip'. + *

+ * This causes all receive methods to use a {@link GZIPInputStream} when + * applicable so that higher level streams and readers can read the data + * uncompressed. + *

+ * Setting this option does not cause any request headers to be set + * automatically so {@link #acceptGzipEncoding()} should be used in conjunction + * with this setting to tell the server to gzip the response. + * + * @param uncompress + * @return this request + */ + public HttpRequest uncompress(final boolean uncompress) { + this.uncompress = uncompress; + return this; + } + + /** + * Create byte array output stream + * + * @return stream + */ + protected ByteArrayOutputStream byteStream() { + final int size = contentLength(); + if (size > 0) + return new ByteArrayOutputStream(size); + else + return new ByteArrayOutputStream(); + } + + /** + * Get response as {@link String} in given character set + *

+ * This will fall back to using the UTF-8 character set if the given charset is + * null + * + * @param charset + * @return string + * @throws HttpRequestException + */ + public String body(final String charset) throws HttpRequestException { + final ByteArrayOutputStream output = byteStream(); + try { + copy(buffer(), output); + return output.toString(getValidCharset(charset)); + } catch (IOException e) { + throw new HttpRequestException(e); + } + } + + /** + * Get response as {@link String} using character set returned from + * {@link #charset()} + * + * @return string + * @throws HttpRequestException + */ + public String body() throws HttpRequestException { + return body(charset()); + } + + /** + * Get the response body as a {@link String} and set it as the value of the + * given reference. + * + * @param output + * @return this request + * @throws HttpRequestException + */ + public HttpRequest body(final AtomicReference output) throws HttpRequestException { + output.set(body()); + return this; + } + + /** + * Get the response body as a {@link String} and set it as the value of the + * given reference. + * + * @param output + * @param charset + * @return this request + * @throws HttpRequestException + */ + public HttpRequest body(final AtomicReference output, final String charset) throws HttpRequestException { + output.set(body(charset)); + return this; + } + + /** + * Is the response body empty? + * + * @return true if the Content-Length response header is 0, false otherwise + * @throws HttpRequestException + */ + public boolean isBodyEmpty() throws HttpRequestException { + return contentLength() == 0; + } + + /** + * Get response as byte array + * + * @return byte array + * @throws HttpRequestException + */ + public byte[] bytes() throws HttpRequestException { + final ByteArrayOutputStream output = byteStream(); + try { + copy(buffer(), output); + } catch (IOException e) { + throw new HttpRequestException(e); + } + return output.toByteArray(); + } + + /** + * Get response in a buffered stream + * + * @see #bufferSize(int) + * @return stream + * @throws HttpRequestException + */ + public BufferedInputStream buffer() throws HttpRequestException { + return new BufferedInputStream(stream(), bufferSize); + } + + /** + * Get stream to response body + * + * @return stream + * @throws HttpRequestException + */ + public InputStream stream() throws HttpRequestException { + InputStream stream; + if (code() < HTTP_BAD_REQUEST) + try { + stream = getConnection().getInputStream(); + } catch (IOException e) { + throw new HttpRequestException(e); + } + else { + stream = getConnection().getErrorStream(); + if (stream == null) + try { + stream = getConnection().getInputStream(); + } catch (IOException e) { + if (contentLength() > 0) + throw new HttpRequestException(e); + else + stream = new ByteArrayInputStream(new byte[0]); + } + } + + if (!uncompress || !ENCODING_GZIP.equals(contentEncoding())) + return stream; + else + try { + return new GZIPInputStream(stream); + } catch (IOException e) { + throw new HttpRequestException(e); + } + } + + /** + * Get reader to response body using given character set. + *

+ * This will fall back to using the UTF-8 character set if the given charset is + * null + * + * @param charset + * @return reader + * @throws HttpRequestException + */ + public InputStreamReader reader(final String charset) throws HttpRequestException { + try { + return new InputStreamReader(stream(), getValidCharset(charset)); + } catch (UnsupportedEncodingException e) { + throw new HttpRequestException(e); + } + } + + /** + * Get reader to response body using the character set returned from + * {@link #charset()} + * + * @return reader + * @throws HttpRequestException + */ + public InputStreamReader reader() throws HttpRequestException { + return reader(charset()); + } + + /** + * Get buffered reader to response body using the given character set r and the + * configured buffer size + * + * + * @see #bufferSize(int) + * @param charset + * @return reader + * @throws HttpRequestException + */ + public BufferedReader bufferedReader(final String charset) throws HttpRequestException { + return new BufferedReader(reader(charset), bufferSize); + } + + /** + * Get buffered reader to response body using the character set returned from + * {@link #charset()} and the configured buffer size + * + * @see #bufferSize(int) + * @return reader + * @throws HttpRequestException + */ + public BufferedReader bufferedReader() throws HttpRequestException { + return bufferedReader(charset()); + } + + /** + * Stream response body to file + * + * @param file + * @return this request + * @throws HttpRequestException + */ + public HttpRequest receive(final File file) throws HttpRequestException { + final OutputStream output; + try { + output = new BufferedOutputStream(new FileOutputStream(file), bufferSize); + } catch (FileNotFoundException e) { + throw new HttpRequestException(e); + } + return new CloseOperation(output, ignoreCloseExceptions) { + + @Override + protected HttpRequest run() throws HttpRequestException, IOException { + return receive(output); + } + }.call(); + } + + /** + * Stream response to given output stream + * + * @param output + * @return this request + * @throws HttpRequestException + */ + public HttpRequest receive(final OutputStream output) throws HttpRequestException { + try { + return copy(buffer(), output); + } catch (IOException e) { + throw new HttpRequestException(e); + } + } + + /** + * Stream response to given print stream + * + * @param output + * @return this request + * @throws HttpRequestException + */ + public HttpRequest receive(final PrintStream output) throws HttpRequestException { + return receive((OutputStream) output); + } + + /** + * Receive response into the given appendable + * + * @param appendable + * @return this request + * @throws HttpRequestException + */ + public HttpRequest receive(final Appendable appendable) throws HttpRequestException { + final BufferedReader reader = bufferedReader(); + return new CloseOperation(reader, ignoreCloseExceptions) { + + @Override + public HttpRequest run() throws IOException { + final CharBuffer buffer = CharBuffer.allocate(bufferSize); + int read; + while ((read = reader.read(buffer)) != -1) { + buffer.rewind(); + appendable.append(buffer, 0, read); + buffer.rewind(); + } + return HttpRequest.this; + } + }.call(); + } + + /** + * Receive response into the given writer + * + * @param writer + * @return this request + * @throws HttpRequestException + */ + public HttpRequest receive(final Writer writer) throws HttpRequestException { + final BufferedReader reader = bufferedReader(); + return new CloseOperation(reader, ignoreCloseExceptions) { + + @Override + public HttpRequest run() throws IOException { + return copy(reader, writer); + } + }.call(); + } + + /** + * Set read timeout on connection to given value + * + * @param timeout + * @return this request + */ + public HttpRequest readTimeout(final int timeout) { + getConnection().setReadTimeout(timeout); + return this; + } + + /** + * Set connect timeout on connection to given value + * + * @param timeout + * @return this request + */ + public HttpRequest connectTimeout(final int timeout) { + getConnection().setConnectTimeout(timeout); + return this; + } + + /** + * Set header name to given value + * + * @param name + * @param value + * @return this request + */ + public HttpRequest header(final String name, final String value) { + getConnection().setRequestProperty(name, value); + return this; + } + + /** + * Set header name to given value + * + * @param name + * @param value + * @return this request + */ + public HttpRequest header(final String name, final Number value) { + return header(name, value != null ? value.toString() : null); + } + + /** + * Set all headers found in given map where the keys are the header names and + * the values are the header values + * + * @param headers + * @return this request + */ + public HttpRequest headers(final Map headers) { + if (!headers.isEmpty()) + for (Entry header : headers.entrySet()) + header(header); + return this; + } + + /** + * Set header to have given entry's key as the name and value as the value + * + * @param header + * @return this request + */ + public HttpRequest header(final Entry header) { + return header(header.getKey(), header.getValue()); + } + + /** + * Get a response header + * + * @param name + * @return response header + * @throws HttpRequestException + */ + public String header(final String name) throws HttpRequestException { + closeOutputQuietly(); + return getConnection().getHeaderField(name); + } + + /** + * Get all the response headers + * + * @return map of response header names to their value(s) + * @throws HttpRequestException + */ + public Map> headers() throws HttpRequestException { + closeOutputQuietly(); + return getConnection().getHeaderFields(); + } + + /** + * Get a date header from the response falling back to returning -1 if the + * header is missing or parsing fails + * + * @param name + * @return date, -1 on failures + * @throws HttpRequestException + */ + public long dateHeader(final String name) throws HttpRequestException { + return dateHeader(name, -1L); + } + + /** + * Get a date header from the response falling back to returning the given + * default value if the header is missing or parsing fails + * + * @param name + * @param defaultValue + * @return date, default value on failures + * @throws HttpRequestException + */ + public long dateHeader(final String name, final long defaultValue) throws HttpRequestException { + closeOutputQuietly(); + return getConnection().getHeaderFieldDate(name, defaultValue); + } + + /** + * Get an integer header from the response falling back to returning -1 if the + * header is missing or parsing fails + * + * @param name + * @return header value as an integer, -1 when missing or parsing fails + * @throws HttpRequestException + */ + public int intHeader(final String name) throws HttpRequestException { + return intHeader(name, -1); + } + + /** + * Get an integer header value from the response falling back to the given + * default value if the header is missing or if parsing fails + * + * @param name + * @param defaultValue + * @return header value as an integer, default value when missing or parsing + * fails + * @throws HttpRequestException + */ + public int intHeader(final String name, final int defaultValue) throws HttpRequestException { + closeOutputQuietly(); + return getConnection().getHeaderFieldInt(name, defaultValue); + } + + /** + * Get all values of the given header from the response + * + * @param name + * @return non-null but possibly empty array of {@link String} header values + */ + public String[] headers(final String name) { + final Map> headers = headers(); + if (headers == null || headers.isEmpty()) + return EMPTY_STRINGS; + + final List values = headers.get(name); + if (values != null && !values.isEmpty()) + return values.toArray(new String[values.size()]); + else + return EMPTY_STRINGS; + } + + /** + * Get parameter with given name from header value in response + * + * @param headerName + * @param paramName + * @return parameter value or null if missing + */ + public String parameter(final String headerName, final String paramName) { + return getParam(header(headerName), paramName); + } + + /** + * Get all parameters from header value in response + *

+ * This will be all key=value pairs after the first ';' that are separated by a + * ';' + * + * @param headerName + * @return non-null but possibly empty map of parameter headers + */ + public Map parameters(final String headerName) { + return getParams(header(headerName)); + } + + /** + * Get parameter values from header value + * + * @param header + * @return parameter value or null if none + */ + protected Map getParams(final String header) { + if (header == null || header.length() == 0) + return Collections.emptyMap(); + + final int headerLength = header.length(); + int start = header.indexOf(';') + 1; + if (start == 0 || start == headerLength) + return Collections.emptyMap(); + + int end = header.indexOf(';', start); + if (end == -1) + end = headerLength; + + Map params = new LinkedHashMap(); + while (start < end) { + int nameEnd = header.indexOf('=', start); + if (nameEnd != -1 && nameEnd < end) { + String name = header.substring(start, nameEnd).trim(); + if (name.length() > 0) { + String value = header.substring(nameEnd + 1, end).trim(); + int length = value.length(); + if (length != 0) + if (length > 2 && '"' == value.charAt(0) && '"' == value.charAt(length - 1)) + params.put(name, value.substring(1, length - 1)); + else + params.put(name, value); + } + } + + start = end + 1; + end = header.indexOf(';', start); + if (end == -1) + end = headerLength; + } + + return params; + } + + /** + * Get parameter value from header value + * + * @param value + * @param paramName + * @return parameter value or null if none + */ + protected String getParam(final String value, final String paramName) { + if (value == null || value.length() == 0) + return null; + + final int length = value.length(); + int start = value.indexOf(';') + 1; + if (start == 0 || start == length) + return null; + + int end = value.indexOf(';', start); + if (end == -1) + end = length; + + while (start < end) { + int nameEnd = value.indexOf('=', start); + if (nameEnd != -1 && nameEnd < end && paramName.equals(value.substring(start, nameEnd).trim())) { + String paramValue = value.substring(nameEnd + 1, end).trim(); + int valueLength = paramValue.length(); + if (valueLength != 0) + if (valueLength > 2 && '"' == paramValue.charAt(0) && '"' == paramValue.charAt(valueLength - 1)) + return paramValue.substring(1, valueLength - 1); + else + return paramValue; + } + + start = end + 1; + end = value.indexOf(';', start); + if (end == -1) + end = length; + } + + return null; + } + + /** + * Get 'charset' parameter from 'Content-Type' response header + * + * @return charset or null if none + */ + public String charset() { + return parameter(HEADER_CONTENT_TYPE, PARAM_CHARSET); + } + + /** + * Set the 'User-Agent' header to given value + * + * @param userAgent + * @return this request + */ + public HttpRequest userAgent(final String userAgent) { + return header(HEADER_USER_AGENT, userAgent); + } + + /** + * Set the 'Referer' header to given value + * + * @param referer + * @return this request + */ + public HttpRequest referer(final String referer) { + return header(HEADER_REFERER, referer); + } + + /** + * Set value of {@link HttpURLConnection#setUseCaches(boolean)} + * + * @param useCaches + * @return this request + */ + public HttpRequest useCaches(final boolean useCaches) { + getConnection().setUseCaches(useCaches); + return this; + } + + /** + * Set the 'Accept-Encoding' header to given value + * + * @param acceptEncoding + * @return this request + */ + public HttpRequest acceptEncoding(final String acceptEncoding) { + return header(HEADER_ACCEPT_ENCODING, acceptEncoding); + } + + /** + * Set the 'Accept-Encoding' header to 'gzip' + * + * @see #uncompress(boolean) + * @return this request + */ + public HttpRequest acceptGzipEncoding() { + return acceptEncoding(ENCODING_GZIP); + } + + /** + * Set the 'Accept-Charset' header to given value + * + * @param acceptCharset + * @return this request + */ + public HttpRequest acceptCharset(final String acceptCharset) { + return header(HEADER_ACCEPT_CHARSET, acceptCharset); + } + + /** + * Get the 'Content-Encoding' header from the response + * + * @return this request + */ + public String contentEncoding() { + return header(HEADER_CONTENT_ENCODING); + } + + /** + * Get the 'Server' header from the response + * + * @return server + */ + public String server() { + return header(HEADER_SERVER); + } + + /** + * Get the 'Date' header from the response + * + * @return date value, -1 on failures + */ + public long date() { + return dateHeader(HEADER_DATE); + } + + /** + * Get the 'Cache-Control' header from the response + * + * @return cache control + */ + public String cacheControl() { + return header(HEADER_CACHE_CONTROL); + } + + /** + * Get the 'ETag' header from the response + * + * @return entity tag + */ + public String eTag() { + return header(HEADER_ETAG); + } + + /** + * Get the 'Expires' header from the response + * + * @return expires value, -1 on failures + */ + public long expires() { + return dateHeader(HEADER_EXPIRES); + } + + /** + * Get the 'Last-Modified' header from the response + * + * @return last modified value, -1 on failures + */ + public long lastModified() { + return dateHeader(HEADER_LAST_MODIFIED); + } + + /** + * Get the 'Location' header from the response + * + * @return location + */ + public String location() { + return header(HEADER_LOCATION); + } + + /** + * Set the 'Authorization' header to given value + * + * @param authorization + * @return this request + */ + public HttpRequest authorization(final String authorization) { + return header(HEADER_AUTHORIZATION, authorization); + } + + /** + * Set the 'Proxy-Authorization' header to given value + * + * @param proxyAuthorization + * @return this request + */ + public HttpRequest proxyAuthorization(final String proxyAuthorization) { + return header(HEADER_PROXY_AUTHORIZATION, proxyAuthorization); + } + + /** + * Set the 'Authorization' header to given values in Basic authentication format + * + * @param name + * @param password + * @return this request + */ + public HttpRequest basic(final String name, final String password) { + return authorization("Basic " + Base64.encode(name + ':' + password)); + } + + /** + * Set the 'Proxy-Authorization' header to given values in Basic authentication + * format + * + * @param name + * @param password + * @return this request + */ + public HttpRequest proxyBasic(final String name, final String password) { + return proxyAuthorization("Basic " + Base64.encode(name + ':' + password)); + } + + /** + * Set the 'If-Modified-Since' request header to the given value + * + * @param ifModifiedSince + * @return this request + */ + public HttpRequest ifModifiedSince(final long ifModifiedSince) { + getConnection().setIfModifiedSince(ifModifiedSince); + return this; + } + + /** + * Set the 'If-None-Match' request header to the given value + * + * @param ifNoneMatch + * @return this request + */ + public HttpRequest ifNoneMatch(final String ifNoneMatch) { + return header(HEADER_IF_NONE_MATCH, ifNoneMatch); + } + + /** + * Set the 'Content-Type' request header to the given value + * + * @param contentType + * @return this request + */ + public HttpRequest contentType(final String contentType) { + return contentType(contentType, null); + } + + /** + * Set the 'Content-Type' request header to the given value and charset + * + * @param contentType + * @param charset + * @return this request + */ + public HttpRequest contentType(final String contentType, final String charset) { + if (charset != null && charset.length() > 0) { + final String separator = "; " + PARAM_CHARSET + '='; + return header(HEADER_CONTENT_TYPE, contentType + separator + charset); + } else + return header(HEADER_CONTENT_TYPE, contentType); + } + + /** + * Get the 'Content-Type' header from the response + * + * @return response header value + */ + public String contentType() { + return header(HEADER_CONTENT_TYPE); + } + + /** + * Get the 'Content-Length' header from the response + * + * @return response header value + */ + public int contentLength() { + return intHeader(HEADER_CONTENT_LENGTH); + } + + /** + * Set the 'Content-Length' request header to the given value + * + * @param contentLength + * @return this request + */ + public HttpRequest contentLength(final String contentLength) { + return contentLength(Integer.parseInt(contentLength)); + } + + /** + * Set the 'Content-Length' request header to the given value + * + * @param contentLength + * @return this request + */ + public HttpRequest contentLength(final int contentLength) { + getConnection().setFixedLengthStreamingMode(contentLength); + return this; + } + + /** + * Set the 'Accept' header to given value + * + * @param accept + * @return this request + */ + public HttpRequest accept(final String accept) { + return header(HEADER_ACCEPT, accept); + } + + /** + * Set the 'Accept' header to 'application/json' + * + * @return this request + */ + public HttpRequest acceptJson() { + return accept(CONTENT_TYPE_JSON); + } + + /** + * Copy from input stream to output stream + * + * @param input + * @param output + * @return this request + * @throws IOException + */ + protected HttpRequest copy(final InputStream input, final OutputStream output) throws IOException { + return new CloseOperation(input, ignoreCloseExceptions) { + + @Override + public HttpRequest run() throws IOException { + final byte[] buffer = new byte[bufferSize]; + int read; + while ((read = input.read(buffer)) != -1) { + output.write(buffer, 0, read); + totalWritten += read; + progress.onUpload(totalWritten, totalSize); + } + return HttpRequest.this; + } + }.call(); + } + + /** + * Copy from reader to writer + * + * @param input + * @param output + * @return this request + * @throws IOException + */ + protected HttpRequest copy(final Reader input, final Writer output) throws IOException { + return new CloseOperation(input, ignoreCloseExceptions) { + + @Override + public HttpRequest run() throws IOException { + final char[] buffer = new char[bufferSize]; + int read; + while ((read = input.read(buffer)) != -1) { + output.write(buffer, 0, read); + totalWritten += read; + progress.onUpload(totalWritten, -1); + } + return HttpRequest.this; + } + }.call(); + } + + /** + * Set the UploadProgress callback for this request + * + * @param callback + * @return this request + */ + public HttpRequest progress(final UploadProgress callback) { + if (callback == null) + progress = UploadProgress.DEFAULT; + else + progress = callback; + return this; + } + + private HttpRequest incrementTotalSize(final long size) { + if (totalSize == -1) + totalSize = 0; + totalSize += size; + return this; + } + + /** + * Close output stream + * + * @return this request + * @throws HttpRequestException + * @throws IOException + */ + protected HttpRequest closeOutput() throws IOException { + progress(null); + if (output == null) + return this; + if (multipart) + output.write(CRLF + "--" + BOUNDARY + "--" + CRLF); + if (ignoreCloseExceptions) + try { + output.close(); + } catch (IOException ignored) { + // Ignored + } + else + output.close(); + output = null; + return this; + } + + /** + * Call {@link #closeOutput()} and re-throw a caught {@link IOException}s as an + * {@link HttpRequestException} + * + * @return this request + * @throws HttpRequestException + */ + protected HttpRequest closeOutputQuietly() throws HttpRequestException { + try { + return closeOutput(); + } catch (IOException e) { + throw new HttpRequestException(e); + } + } + + /** + * Open output stream + * + * @return this request + * @throws IOException + */ + protected HttpRequest openOutput() throws IOException { + if (output != null) + return this; + getConnection().setDoOutput(true); + final String charset = getParam(getConnection().getRequestProperty(HEADER_CONTENT_TYPE), PARAM_CHARSET); + output = new RequestOutputStream(getConnection().getOutputStream(), charset, bufferSize); + return this; + } + + /** + * Start part of a multipart + * + * @return this request + * @throws IOException + */ + protected HttpRequest startPart() throws IOException { + if (!multipart) { + multipart = true; + contentType(CONTENT_TYPE_MULTIPART).openOutput(); + output.write("--" + BOUNDARY + CRLF); + } else + output.write(CRLF + "--" + BOUNDARY + CRLF); + return this; + } + + /** + * Write part header + * + * @param name + * @param filename + * @return this request + * @throws IOException + */ + protected HttpRequest writePartHeader(final String name, final String filename) throws IOException { + return writePartHeader(name, filename, null); + } + + /** + * Write part header + * + * @param name + * @param filename + * @param contentType + * @return this request + * @throws IOException + */ + protected HttpRequest writePartHeader(final String name, final String filename, final String contentType) + throws IOException { + final StringBuilder partBuffer = new StringBuilder(); + partBuffer.append("form-data; name=\"").append(name); + if (filename != null) + partBuffer.append("\"; filename=\"").append(filename); + partBuffer.append('"'); + partHeader("Content-Disposition", partBuffer.toString()); + if (contentType != null) + partHeader(HEADER_CONTENT_TYPE, contentType); + return send(CRLF); + } + + /** + * Write part of a multipart request to the request body + * + * @param name + * @param part + * @return this request + */ + public HttpRequest part(final String name, final String part) { + return part(name, null, part); + } + + /** + * Write part of a multipart request to the request body + * + * @param name + * @param filename + * @param part + * @return this request + * @throws HttpRequestException + */ + public HttpRequest part(final String name, final String filename, final String part) throws HttpRequestException { + return part(name, filename, null, part); + } + + /** + * Write part of a multipart request to the request body + * + * @param name + * @param filename + * @param contentType value of the Content-Type part header + * @param part + * @return this request + * @throws HttpRequestException + */ + public HttpRequest part(final String name, final String filename, final String contentType, final String part) + throws HttpRequestException { + try { + startPart(); + writePartHeader(name, filename, contentType); + output.write(part); + } catch (IOException e) { + throw new HttpRequestException(e); + } + return this; + } + + /** + * Write part of a multipart request to the request body + * + * @param name + * @param part + * @return this request + * @throws HttpRequestException + */ + public HttpRequest part(final String name, final Number part) throws HttpRequestException { + return part(name, null, part); + } + + /** + * Write part of a multipart request to the request body + * + * @param name + * @param filename + * @param part + * @return this request + * @throws HttpRequestException + */ + public HttpRequest part(final String name, final String filename, final Number part) throws HttpRequestException { + return part(name, filename, part != null ? part.toString() : null); + } + + /** + * Write part of a multipart request to the request body + * + * @param name + * @param part + * @return this request + * @throws HttpRequestException + */ + public HttpRequest part(final String name, final File part) throws HttpRequestException { + return part(name, null, part); + } + + /** + * Write part of a multipart request to the request body + * + * @param name + * @param filename + * @param part + * @return this request + * @throws HttpRequestException + */ + public HttpRequest part(final String name, final String filename, final File part) throws HttpRequestException { + return part(name, filename, null, part); + } + + /** + * Write part of a multipart request to the request body + * + * @param name + * @param filename + * @param contentType value of the Content-Type part header + * @param part + * @return this request + * @throws HttpRequestException + */ + public HttpRequest part(final String name, final String filename, final String contentType, final File part) + throws HttpRequestException { + final InputStream stream; + try { + stream = new BufferedInputStream(new FileInputStream(part)); + incrementTotalSize(part.length()); + } catch (IOException e) { + throw new HttpRequestException(e); + } + return part(name, filename, contentType, stream); + } + + /** + * Write part of a multipart request to the request body + * + * @param name + * @param part + * @return this request + * @throws HttpRequestException + */ + public HttpRequest part(final String name, final InputStream part) throws HttpRequestException { + return part(name, null, null, part); + } + + /** + * Write part of a multipart request to the request body + * + * @param name + * @param filename + * @param contentType value of the Content-Type part header + * @param part + * @return this request + * @throws HttpRequestException + */ + public HttpRequest part(final String name, final String filename, final String contentType, final InputStream part) + throws HttpRequestException { + try { + startPart(); + writePartHeader(name, filename, contentType); + copy(part, output); + } catch (IOException e) { + throw new HttpRequestException(e); + } + return this; + } + + /** + * Write a multipart header to the response body + * + * @param name + * @param value + * @return this request + * @throws HttpRequestException + */ + public HttpRequest partHeader(final String name, final String value) throws HttpRequestException { + return send(name).send(": ").send(value).send(CRLF); + } + + /** + * Write contents of file to request body + * + * @param input + * @return this request + * @throws HttpRequestException + */ + public HttpRequest send(final File input) throws HttpRequestException { + final InputStream stream; + try { + stream = new BufferedInputStream(new FileInputStream(input)); + incrementTotalSize(input.length()); + } catch (FileNotFoundException e) { + throw new HttpRequestException(e); + } + return send(stream); + } + + /** + * Write byte array to request body + * + * @param input + * @return this request + * @throws HttpRequestException + */ + public HttpRequest send(final byte[] input) throws HttpRequestException { + if (input != null) + incrementTotalSize(input.length); + return send(new ByteArrayInputStream(input)); + } + + /** + * Write stream to request body + *

+ * The given stream will be closed once sending completes + * + * @param input + * @return this request + * @throws HttpRequestException + */ + public HttpRequest send(final InputStream input) throws HttpRequestException { + try { + openOutput(); + copy(input, output); + } catch (IOException e) { + throw new HttpRequestException(e); + } + return this; + } + + /** + * Write reader to request body + *

+ * The given reader will be closed once sending completes + * + * @param input + * @return this request + * @throws HttpRequestException + */ + public HttpRequest send(final Reader input) throws HttpRequestException { + try { + openOutput(); + } catch (IOException e) { + throw new HttpRequestException(e); + } + final Writer writer = new OutputStreamWriter(output, output.encoder.charset()); + return new FlushOperation(writer) { + + @Override + protected HttpRequest run() throws IOException { + return copy(input, writer); + } + }.call(); + } + + /** + * Write char sequence to request body + *

+ * The charset configured via {@link #contentType(String)} will be used and + * UTF-8 will be used if it is unset. + * + * @param value + * @return this request + * @throws HttpRequestException + */ + public HttpRequest send(final CharSequence value) throws HttpRequestException { + try { + openOutput(); + output.write(value.toString()); + } catch (IOException e) { + throw new HttpRequestException(e); + } + return this; + } + + /** + * Create writer to request output stream + * + * @return writer + * @throws HttpRequestException + */ + public OutputStreamWriter writer() throws HttpRequestException { + try { + openOutput(); + return new OutputStreamWriter(output, output.encoder.charset()); + } catch (IOException e) { + throw new HttpRequestException(e); + } + } + + /** + * Write the values in the map as form data to the request body + *

+ * The pairs specified will be URL-encoded in UTF-8 and sent with the + * 'application/x-www-form-urlencoded' content-type + * + * @param values + * @return this request + * @throws HttpRequestException + */ + public HttpRequest form(final Map values) throws HttpRequestException { + return form(values, CHARSET_UTF8); + } + + /** + * Write the key and value in the entry as form data to the request body + *

+ * The pair specified will be URL-encoded in UTF-8 and sent with the + * 'application/x-www-form-urlencoded' content-type + * + * @param entry + * @return this request + * @throws HttpRequestException + */ + public HttpRequest form(final Entry entry) throws HttpRequestException { + return form(entry, CHARSET_UTF8); + } + + /** + * Write the key and value in the entry as form data to the request body + *

+ * The pair specified will be URL-encoded and sent with the + * 'application/x-www-form-urlencoded' content-type + * + * @param entry + * @param charset + * @return this request + * @throws HttpRequestException + */ + public HttpRequest form(final Entry entry, final String charset) throws HttpRequestException { + return form(entry.getKey(), entry.getValue(), charset); + } + + /** + * Write the name/value pair as form data to the request body + *

+ * The pair specified will be URL-encoded in UTF-8 and sent with the + * 'application/x-www-form-urlencoded' content-type + * + * @param name + * @param value + * @return this request + * @throws HttpRequestException + */ + public HttpRequest form(final Object name, final Object value) throws HttpRequestException { + return form(name, value, CHARSET_UTF8); + } + + /** + * Write the name/value pair as form data to the request body + *

+ * The values specified will be URL-encoded and sent with the + * 'application/x-www-form-urlencoded' content-type + * + * @param name + * @param value + * @param charset + * @return this request + * @throws HttpRequestException + */ + public HttpRequest form(final Object name, final Object value, String charset) throws HttpRequestException { + final boolean first = !form; + if (first) { + contentType(CONTENT_TYPE_FORM, charset); + form = true; + } + charset = getValidCharset(charset); + try { + openOutput(); + if (!first) + output.write('&'); + output.write(URLEncoder.encode(name.toString(), charset)); + output.write('='); + if (value != null) + output.write(URLEncoder.encode(value.toString(), charset)); + } catch (IOException e) { + throw new HttpRequestException(e); + } + return this; + } + + /** + * Write the values in the map as encoded form data to the request body + * + * @param values + * @param charset + * @return this request + * @throws HttpRequestException + */ + public HttpRequest form(final Map values, final String charset) throws HttpRequestException { + if (!values.isEmpty()) + for (Entry entry : values.entrySet()) + form(entry, charset); + return this; + } + + public HttpRequest setSSLSocketFactory(SSLSocketFactory socketFactory) throws HttpRequestException { + final HttpURLConnection connection = getConnection(); + if (connection instanceof HttpsURLConnection) + ((HttpsURLConnection) connection).setSSLSocketFactory(socketFactory); + return this; + } + + public HttpRequest setHostnameVerifier(HostnameVerifier verifier) { + final HttpURLConnection connection = getConnection(); + if (connection instanceof HttpsURLConnection) + ((HttpsURLConnection) connection).setHostnameVerifier(verifier); + return this; + } + + /** + * Get the {@link URL} of this request's connection + * + * @return request URL + */ + public URL url() { + return getConnection().getURL(); + } + + /** + * Get the HTTP method of this request + * + * @return method + */ + public String method() { + return getConnection().getRequestMethod(); + } + + /** + * Configure an HTTP proxy on this connection. Use + * {{@link #proxyBasic(String, String)} if this proxy requires basic + * authentication. + * + * @param proxyHost + * @param proxyPort + * @return this request + */ + public HttpRequest useProxy(final String proxyHost, final int proxyPort) { + if (connection != null) + throw new IllegalStateException( + "The connection has already been created. This method must be called before reading or writing to the request."); + + this.httpProxyHost = proxyHost; + this.httpProxyPort = proxyPort; + return this; + } + + /** + * Set whether or not the underlying connection should follow redirects in the + * response. + * + * @param followRedirects - true fo follow redirects, false to not. + * @return this request + */ + public HttpRequest followRedirects(final boolean followRedirects) { + getConnection().setInstanceFollowRedirects(followRedirects); + return this; + } +} diff --git a/src/android/com/silkimen/http/HttpResponse.java b/src/android/com/silkimen/http/HttpResponse.java new file mode 100644 index 0000000..cab1285 --- /dev/null +++ b/src/android/com/silkimen/http/HttpResponse.java @@ -0,0 +1,80 @@ +package com.silkimen.http; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.json.JSONException; +import org.json.JSONObject; + +import android.text.TextUtils; +import android.util.Log; + +public class HttpResponse { + private int status; + private String url; + private Map> headers; + private String body; + private boolean failed; + private String error; + + public void setStatus(int status) { + this.status = status; + } + + public void setUrl(String url) { + this.url = url; + } + + public void setHeaders(Map> headers) { + this.headers = headers; + } + + public void setBody(String body) { + this.body = body; + } + + public void setErrorMessage(String message) { + this.failed = true; + this.error = message; + } + + public boolean hasFailed() { + return this.failed; + } + + public JSONObject toJSON() throws JSONException { + JSONObject json = new JSONObject(); + + json.put("status", this.status); + json.put("url", this.url); + + if (this.failed) { + json.put("error", this.error); + } else { + json.put("headers", new JSONObject(getFilteredHeaders())); + json.put("data", this.body); + } + + return json; + } + + private Map getFilteredHeaders() throws JSONException { + Map filteredHeaders = new HashMap(); + + if (this.headers == null || this.headers.isEmpty()) { + return filteredHeaders; + } + + for (Map.Entry> entry : this.headers.entrySet()) { + String key = entry.getKey(); + List value = entry.getValue(); + + if ((key != null) && (!value.isEmpty())) { + filteredHeaders.put(key.toLowerCase(), TextUtils.join(", ", value)); + } + } + + return filteredHeaders; + } +} diff --git a/src/android/com/silkimen/http/JsonUtils.java b/src/android/com/silkimen/http/JsonUtils.java new file mode 100644 index 0000000..72f1b48 --- /dev/null +++ b/src/android/com/silkimen/http/JsonUtils.java @@ -0,0 +1,58 @@ +package com.silkimen.http; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +public class JsonUtils { + public static HashMap getStringMap(JSONObject object) throws JSONException { + HashMap map = new HashMap(); + + if (object == null) { + return map; + } + + Iterator i = object.keys(); + + while (i.hasNext()) { + String key = (String) i.next(); + map.put(key, object.getString(key)); + } + return map; + } + + public static HashMap getObjectMap(JSONObject object) throws JSONException { + HashMap map = new HashMap(); + + if (object == null) { + return map; + } + + Iterator i = object.keys(); + + while (i.hasNext()) { + String key = (String) i.next(); + Object value = object.get(key); + + if (value instanceof JSONArray) { + map.put(key, getObjectList((JSONArray) value)); + } else { + map.put(key, object.get(key)); + } + } + return map; + } + + public static ArrayList getObjectList(JSONArray array) throws JSONException { + ArrayList list = new ArrayList(); + + for (int i = 0; i < array.length(); i++) { + list.add(array.get(i)); + } + return list; + } +} diff --git a/src/android/com/silkimen/http/OkConnectionFactory.java b/src/android/com/silkimen/http/OkConnectionFactory.java new file mode 100644 index 0000000..90764b5 --- /dev/null +++ b/src/android/com/silkimen/http/OkConnectionFactory.java @@ -0,0 +1,34 @@ +package com.silkimen.http; + +import okhttp3.OkUrlFactory; +import okhttp3.OkHttpClient; + +/** + * A {@link HttpRequest.ConnectionFactory connection factory} which uses OkHttp. + *

+ * Call {@link HttpRequest#setConnectionFactory(HttpRequest.ConnectionFactory)} + * with an instance of this class to enable. + */ +public class OkConnectionFactory implements HttpRequest.ConnectionFactory { + private final OkHttpClient client; + + public OkConnectionFactory() { + this(new OkHttpClient()); + } + + public OkConnectionFactory(OkHttpClient client) { + if (client == null) { + throw new NullPointerException("Client must not be null."); + } + this.client = client; + } + + public HttpURLConnection create(URL url) throws IOException { + return client.open(url); + } + + public HttpURLConnection create(URL url, Proxy proxy) throws IOException { + throw new UnsupportedOperationException( + "Per-connection proxy is not supported. Use OkHttpClient's setProxy instead."); + } +} diff --git a/src/android/com/silkimen/http/OkConnectionFactory_chax.java b/src/android/com/silkimen/http/OkConnectionFactory_chax.java new file mode 100644 index 0000000..69f1b72 --- /dev/null +++ b/src/android/com/silkimen/http/OkConnectionFactory_chax.java @@ -0,0 +1,25 @@ +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); + } +} diff --git a/src/android/com/silkimen/http/TLSSocketFactory.java b/src/android/com/silkimen/http/TLSSocketFactory.java new file mode 100644 index 0000000..bf3a6ea --- /dev/null +++ b/src/android/com/silkimen/http/TLSSocketFactory.java @@ -0,0 +1,63 @@ +package com.silkimen.http; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +public class TLSSocketFactory extends SSLSocketFactory { + + private SSLSocketFactory delegate; + + public TLSSocketFactory(SSLContext context) { + delegate = context.getSocketFactory(); + } + + @Override + public String[] getDefaultCipherSuites() { + return delegate.getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + return delegate.getSupportedCipherSuites(); + } + + @Override + public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { + return enableTLSOnSocket(delegate.createSocket(s, host, port, autoClose)); + } + + @Override + public Socket createSocket(String host, int port) throws IOException, UnknownHostException { + return enableTLSOnSocket(delegate.createSocket(host, port)); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) + throws IOException, UnknownHostException { + return enableTLSOnSocket(delegate.createSocket(host, port, localHost, localPort)); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + return enableTLSOnSocket(delegate.createSocket(host, port)); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) + throws IOException { + return enableTLSOnSocket(delegate.createSocket(address, port, localAddress, localPort)); + } + + private Socket enableTLSOnSocket(Socket socket) { + if (socket != null && (socket instanceof SSLSocket)) { + ((SSLSocket) socket).setEnabledProtocols(new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" }); + } + return socket; + } +} diff --git a/src/android/com/silkimen/http/TrustManagersFactory.java b/src/android/com/silkimen/http/TrustManagersFactory.java new file mode 100644 index 0000000..943abde --- /dev/null +++ b/src/android/com/silkimen/http/TrustManagersFactory.java @@ -0,0 +1,59 @@ +package com.silkimen.http; + +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.util.ArrayList; + +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +public class TrustManagersFactory { + private final TrustManager[] noOpTrustManager; + + public TrustManagersFactory() { + this.noOpTrustManager = new TrustManager[] { new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) { + // Intentionally left blank + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) { + // Intentionally left blank + } + } }; + } + + public TrustManager[] getNoopTrustManagers() { + return this.noOpTrustManager; + } + + public TrustManager[] getPinnedTrustManagers(ArrayList pinnedCerts) throws IOException { + if (pinnedCerts == null || pinnedCerts.size() == 0) { + throw new IOException("You must add at least 1 certificate in order to pin to certificates"); + } + + try { + String keyStoreType = KeyStore.getDefaultType(); + KeyStore keyStore = KeyStore.getInstance(keyStoreType); + keyStore.load(null, null); + + for (int i = 0; i < pinnedCerts.size(); i++) { + keyStore.setCertificateEntry("CA" + i, pinnedCerts.get(i)); + } + + // Create a TrustManager that trusts the CAs in our KeyStore + String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); + tmf.init(keyStore); + + return tmf.getTrustManagers(); + } catch (GeneralSecurityException e) { + IOException ioException = new IOException("Security exception configuring SSL trust managers"); + ioException.initCause(e); + throw new HttpRequestException(ioException); + } + } +} diff --git a/src/android/com/synconset/cordovahttp/CordovaHttp.java b/src/android/com/synconset/cordovahttp/CordovaHttp.java index 582d6c1..ee14091 100644 --- a/src/android/com/synconset/cordovahttp/CordovaHttp.java +++ b/src/android/com/synconset/cordovahttp/CordovaHttp.java @@ -12,12 +12,10 @@ import org.json.JSONObject; import java.net.SocketTimeoutException; import java.net.UnknownHostException; +import java.io.ByteArrayOutputStream; 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; @@ -32,251 +30,220 @@ import java.util.Iterator; import android.text.TextUtils; -import com.github.kevinsawicki.http.HttpRequest; -import com.github.kevinsawicki.http.HttpRequest.HttpRequestException; +import com.silkimen.http.HttpBodyDecoder; +import com.silkimen.http.HttpRequest; +import com.silkimen.http.HttpRequest.HttpRequestException; abstract class CordovaHttp { - protected static final String TAG = "CordovaHTTP"; - protected static final String[] ACCEPTED_CHARSETS = new String[] { HttpRequest.CHARSET_UTF8, HttpRequest.CHARSET_LATIN1 }; - private static AtomicBoolean disableRedirect = new AtomicBoolean(false); + protected static final String TAG = "CordovaHTTP"; + private static AtomicBoolean disableRedirect = new AtomicBoolean(false); - private String urlString; - private Object params; - private String serializerName; - private JSONObject headers; - private int timeoutInMilliseconds; - private CallbackContext callbackContext; + private String urlString; + private Object params; + private String serializerName; + private JSONObject headers; + private int timeoutInMilliseconds; + private CallbackContext callbackContext; - public CordovaHttp(String urlString, Object params, JSONObject headers, int timeout, CallbackContext callbackContext) { - this(urlString, params, "default", headers, timeout, callbackContext); + public CordovaHttp(String urlString, Object params, JSONObject headers, int timeout, + CallbackContext callbackContext) { + this(urlString, params, "default", headers, timeout, callbackContext); + } + + public CordovaHttp(String urlString, Object params, String serializerName, JSONObject headers, int timeout, + CallbackContext callbackContext) { + this.urlString = urlString; + this.params = params; + this.serializerName = serializerName; + this.headers = headers; + this.timeoutInMilliseconds = timeout; + this.callbackContext = callbackContext; + + + } + + public static void disableRedirect(boolean disable) { + disableRedirect.set(disable); + } + + protected String getUrlString() { + return this.urlString; + } + + protected Object getParamsObject() { + return this.params; + } + + protected String getSerializerName() { + return this.serializerName; + } + + protected HashMap getParamsMap() throws JSONException, Exception { + if (this.params instanceof JSONObject) { + return this.getMapFromJSONObject((JSONObject) this.params); + } else { + throw new Exception("unsupported params type, needs to be a JSON object"); + } + } + + protected JSONObject getHeadersObject() { + return this.headers; + } + + protected HashMap getHeadersMap() throws JSONException { + return this.getStringMapFromJSONObject(this.headers); + } + + protected int getRequestTimeout() { + return this.timeoutInMilliseconds; + } + + protected CallbackContext getCallbackContext() { + return this.callbackContext; + } + + protected HttpRequest setupRedirect(HttpRequest request) { + if (disableRedirect.get()) { + request.followRedirects(false); } - public CordovaHttp(String urlString, Object params, String serializerName, JSONObject headers, int timeout, CallbackContext callbackContext) { - this.urlString = urlString; - this.params = params; - this.serializerName = serializerName; - this.headers = headers; - this.timeoutInMilliseconds = timeout; - this.callbackContext = callbackContext; + return request; + } + + protected void setupDataSerializer(HttpRequest request) throws JSONException, Exception { + if ("json".equals(this.getSerializerName())) { + request.contentType(request.CONTENT_TYPE_JSON, request.CHARSET_UTF8); + } else if ("utf8".equals(this.getSerializerName())) { + request.contentType("text/plain", request.CHARSET_UTF8); } + } - public static void disableRedirect(boolean disable) { - disableRedirect.set(disable); + protected void respondWithError(int status, String msg) { + try { + JSONObject response = new JSONObject(); + response.put("status", status); + response.put("error", msg); + this.callbackContext.error(response); + } catch (JSONException e) { + this.callbackContext.error(msg); } + } - protected String getUrlString() { - return this.urlString; - } + protected void respondWithError(String msg) { + this.respondWithError(-1, msg); + } - protected Object getParamsObject() { - return this.params; - } + protected void addResponseHeaders(HttpRequest request, JSONObject response) throws JSONException { + Map> headers = request.headers(); + Map filteredHeaders = new HashMap(); - protected String getSerializerName() { - return this.serializerName; - } + for (Map.Entry> entry : headers.entrySet()) { + String key = entry.getKey(); + List value = entry.getValue(); - protected HashMap getParamsMap() throws JSONException, Exception { - if (this.params instanceof JSONObject) { - return this.getMapFromJSONObject((JSONObject) this.params); - } else { - throw new Exception("unsupported params type, needs to be a JSON object"); - } - } - - protected JSONObject getHeadersObject() { - return this.headers; - } - - protected HashMap getHeadersMap() throws JSONException { - return this.getStringMapFromJSONObject(this.headers); - } - - protected int getRequestTimeout() { - return this.timeoutInMilliseconds; - } - - protected CallbackContext getCallbackContext() { - return this.callbackContext; - } - - protected HttpRequest setupRedirect(HttpRequest request) { - if (disableRedirect.get()) { - request.followRedirects(false); - } - - return request; - } - - protected void setupDataSerializer(HttpRequest request) throws JSONException, Exception { - if ("json".equals(this.getSerializerName())) { - request.contentType(request.CONTENT_TYPE_JSON, request.CHARSET_UTF8); - } else if ("utf8".equals(this.getSerializerName())) { - request.contentType("text/plain", request.CHARSET_UTF8); + if ((key != null) && (!value.isEmpty())) { + filteredHeaders.put(key.toLowerCase(), TextUtils.join(", ", value)); } } - protected void respondWithError(int status, String msg) { - try { - JSONObject response = new JSONObject(); - response.put("status", status); - response.put("error", msg); - this.callbackContext.error(response); - } catch (JSONException e) { - this.callbackContext.error(msg); - } + response.put("headers", new JSONObject(filteredHeaders)); + } + + protected HashMap getStringMapFromJSONObject(JSONObject object) throws JSONException { + HashMap map = new HashMap(); + Iterator i = object.keys(); + + while (i.hasNext()) { + String key = (String) i.next(); + map.put(key, object.getString(key)); } + return map; + } - protected void respondWithError(String msg) { - this.respondWithError(-1, msg); + protected ArrayList getListFromJSONArray(JSONArray array) throws JSONException { + ArrayList list = new ArrayList(); + + for (int i = 0; i < array.length(); i++) { + list.add(array.get(i)); } + return list; + } - protected void addResponseHeaders(HttpRequest request, JSONObject response) throws JSONException { - Map> headers = request.headers(); - Map filteredHeaders = new HashMap(); + protected HashMap getMapFromJSONObject(JSONObject object) throws JSONException { + HashMap map = new HashMap(); + Iterator i = object.keys(); - for (Map.Entry> entry : headers.entrySet()) { - String key = entry.getKey(); - List value = entry.getValue(); + while (i.hasNext()) { + String key = (String) i.next(); + Object value = object.get(key); - if ((key != null) && (!value.isEmpty())) { - filteredHeaders.put(key.toLowerCase(), TextUtils.join(", ", value)); - } - } - - response.put("headers", new JSONObject(filteredHeaders)); - } - - protected HashMap getStringMapFromJSONObject(JSONObject object) throws JSONException { - HashMap map = new HashMap(); - Iterator i = object.keys(); - - while (i.hasNext()) { - String key = (String)i.next(); - map.put(key, object.getString(key)); - } - return map; - } - - protected ArrayList getListFromJSONArray(JSONArray array) throws JSONException { - ArrayList list = new ArrayList(); - - for (int i = 0; i < array.length(); i++) { - list.add(array.get(i)); - } - return list; - } - - protected HashMap getMapFromJSONObject(JSONObject object) throws JSONException { - HashMap map = new HashMap(); - Iterator i = object.keys(); - - while(i.hasNext()) { - String key = (String)i.next(); - Object value = object.get(key); - - if (value instanceof JSONArray) { - map.put(key, getListFromJSONArray((JSONArray)value)); - } else { - map.put(key, object.get(key)); - } - } - return map; - } - - protected void prepareRequest(HttpRequest request) throws HttpRequestException, JSONException { - this.setupRedirect(request); - - request.readTimeout(this.getRequestTimeout()); - 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()); + if (value instanceof JSONArray) { + map.put(key, getListFromJSONArray((JSONArray) value)); } else { - request.form(this.getParamsMap()); + map.put(key, object.get(key)); } } + return map; + } - private CharsetDecoder createCharsetDecoder(final String charsetName) { - return Charset.forName(charsetName).newDecoder() - .onMalformedInput(CodingErrorAction.REPORT) - .onUnmappableCharacter(CodingErrorAction.REPORT); + protected void prepareRequest(HttpRequest request) throws HttpRequestException, JSONException { + this.setupRedirect(request); + + request.readTimeout(this.getRequestTimeout()); + // 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 String decodeBody(AtomicReference rawOutput, String charsetName) - throws CharacterCodingException, MalformedInputException { + protected void returnResponseObject(HttpRequest request) throws HttpRequestException { + try { + JSONObject response = new JSONObject(); + int code = request.code(); - if (charsetName == null) { - return tryDecodeByteBuffer(rawOutput); - } + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + request.receive(outputStream); + ByteBuffer rawOutput = ByteBuffer.wrap(outputStream.toByteArray()); - return decodeByteBuffer(rawOutput, charsetName); - } + //request.body(rawOutput); + response.put("status", code); + response.put("url", request.url().toString()); + this.addResponseHeaders(request, response); - private String tryDecodeByteBuffer(AtomicReference 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 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(); - AtomicReference rawOutputReference = new AtomicReference(); - - 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", decodeBody(rawOutputReference, request.charset())); - this.getCallbackContext().success(response); - } else { - 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"); - } - } - - protected void handleHttpRequestException(HttpRequestException e) { - if (e.getCause() instanceof UnknownHostException) { - this.respondWithError(0, "The host could not be resolved: " + e.getMessage()); - } else if (e.getCause() instanceof SocketTimeoutException) { - this.respondWithError(1, "The request timed out: " + e.getMessage()); - } else if (e.getCause() instanceof SSLHandshakeException) { - this.respondWithError(-2, "SSL handshake failed: " + e.getMessage()); + if (code >= 200 && code < 300) { + response.put("data", HttpBodyDecoder.decodeBody(rawOutput, request.charset())); + this.getCallbackContext().success(response); } else { - this.respondWithError("There was an error with the request: " + e.getMessage()); + response.put("error", HttpBodyDecoder.decodeBody(rawOutput, 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"); } + } + + protected void handleHttpRequestException(HttpRequestException e) { + if (e.getCause() instanceof UnknownHostException) { + this.respondWithError(0, "The host could not be resolved: " + e.getMessage()); + } else if (e.getCause() instanceof SocketTimeoutException) { + this.respondWithError(1, "The request timed out: " + e.getMessage()); + } else if (e.getCause() instanceof SSLHandshakeException) { + this.respondWithError(-2, "SSL handshake failed: " + e.getMessage()); + } else { + this.respondWithError("There was an error with the request: " + e.getMessage()); + } + } } diff --git a/src/android/com/synconset/cordovahttp/CordovaHttpDelete.java b/src/android/com/synconset/cordovahttp/CordovaHttpDelete.java index 5e2a05f..4857828 100644 --- a/src/android/com/synconset/cordovahttp/CordovaHttpDelete.java +++ b/src/android/com/synconset/cordovahttp/CordovaHttpDelete.java @@ -13,8 +13,8 @@ import org.apache.cordova.CallbackContext; import org.json.JSONException; import org.json.JSONObject; -import com.github.kevinsawicki.http.HttpRequest; -import com.github.kevinsawicki.http.HttpRequest.HttpRequestException; +import com.silkimen.http.HttpRequest; +import com.silkimen.http.HttpRequest.HttpRequestException; class CordovaHttpDelete extends CordovaHttp implements Runnable { public CordovaHttpDelete(String urlString, Object data, JSONObject headers, int timeout, CallbackContext callbackContext) { diff --git a/src/android/com/synconset/cordovahttp/CordovaHttpDownload.java b/src/android/com/synconset/cordovahttp/CordovaHttpDownload.java index 35e9dc9..93ed3dd 100644 --- a/src/android/com/synconset/cordovahttp/CordovaHttpDownload.java +++ b/src/android/com/synconset/cordovahttp/CordovaHttpDownload.java @@ -3,8 +3,8 @@ */ package com.synconset.cordovahttp; -import com.github.kevinsawicki.http.HttpRequest; -import com.github.kevinsawicki.http.HttpRequest.HttpRequestException; +import com.silkimen.http.HttpRequest; +import com.silkimen.http.HttpRequest.HttpRequestException; import java.io.File; import java.net.SocketTimeoutException; diff --git a/src/android/com/synconset/cordovahttp/CordovaHttpGet.java b/src/android/com/synconset/cordovahttp/CordovaHttpGet.java deleted file mode 100644 index a522866..0000000 --- a/src/android/com/synconset/cordovahttp/CordovaHttpGet.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * A HTTP plugin for Cordova / Phonegap - */ -package com.synconset.cordovahttp; - -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; - -import javax.net.ssl.SSLHandshakeException; - -import org.apache.cordova.CallbackContext; - -import org.json.JSONException; -import org.json.JSONObject; - -import com.github.kevinsawicki.http.HttpRequest; -import com.github.kevinsawicki.http.HttpRequest.HttpRequestException; - -class CordovaHttpGet extends CordovaHttp implements Runnable { - public CordovaHttpGet(String urlString, Object params, JSONObject headers, int timeout, CallbackContext callbackContext) { - super(urlString, params, headers, timeout, callbackContext); - } - - @Override - public void run() { - try { - HttpRequest request = HttpRequest.get(this.getUrlString(), this.getParamsMap(), false); - - this.prepareRequest(request); - this.returnResponseObject(request); - } catch (HttpRequestException e) { - this.handleHttpRequestException(e); - } catch (Exception e) { - this.respondWithError(e.getMessage()); - } - } -} diff --git a/src/android/com/synconset/cordovahttp/CordovaHttpHead.java b/src/android/com/synconset/cordovahttp/CordovaHttpHead.java index 6c6d549..224788a 100644 --- a/src/android/com/synconset/cordovahttp/CordovaHttpHead.java +++ b/src/android/com/synconset/cordovahttp/CordovaHttpHead.java @@ -12,8 +12,8 @@ import org.apache.cordova.CallbackContext; import org.json.JSONException; import org.json.JSONObject; -import com.github.kevinsawicki.http.HttpRequest; -import com.github.kevinsawicki.http.HttpRequest.HttpRequestException; +import com.silkimen.http.HttpRequest; +import com.silkimen.http.HttpRequest.HttpRequestException; class CordovaHttpHead extends CordovaHttp implements Runnable { public CordovaHttpHead(String urlString, Object params, JSONObject headers, int timeout, CallbackContext callbackContext) { diff --git a/src/android/com/synconset/cordovahttp/CordovaHttpPatch.java b/src/android/com/synconset/cordovahttp/CordovaHttpPatch.java index b157fb1..e6015cf 100644 --- a/src/android/com/synconset/cordovahttp/CordovaHttpPatch.java +++ b/src/android/com/synconset/cordovahttp/CordovaHttpPatch.java @@ -12,8 +12,8 @@ import org.json.JSONObject; import javax.net.ssl.SSLHandshakeException; -import com.github.kevinsawicki.http.HttpRequest; -import com.github.kevinsawicki.http.HttpRequest.HttpRequestException; +import com.silkimen.http.HttpRequest; +import com.silkimen.http.HttpRequest.HttpRequestException; class CordovaHttpPatch extends CordovaHttp implements Runnable { public CordovaHttpPatch(String urlString, Object params, String serializerName, JSONObject headers, int timeout, CallbackContext callbackContext) { @@ -23,7 +23,8 @@ class CordovaHttpPatch extends CordovaHttp implements Runnable { @Override public void run() { try { - HttpRequest request = HttpRequest.patch(this.getUrlString()); + /* todo */ + HttpRequest request = HttpRequest.get(this.getUrlString()); this.setupDataSerializer(request); this.prepareRequest(request); diff --git a/src/android/com/synconset/cordovahttp/CordovaHttpPlugin.java b/src/android/com/synconset/cordovahttp/CordovaHttpPlugin.java index 280b5dc..3bfbb41 100644 --- a/src/android/com/synconset/cordovahttp/CordovaHttpPlugin.java +++ b/src/android/com/synconset/cordovahttp/CordovaHttpPlugin.java @@ -26,7 +26,8 @@ import org.json.JSONObject; import android.content.res.AssetManager; -import com.github.kevinsawicki.http.HttpRequest; +import com.silkimen.http.HttpRequest; +import com.silkimen.cordovahttp.CordovaHttpRequest; public class CordovaHttpPlugin extends CordovaPlugin { private static final String TAG = "CordovaHTTP"; @@ -36,7 +37,7 @@ public class CordovaHttpPlugin extends CordovaPlugin { super.initialize(cordova, webView); try { - HttpRequest.clearCerts(); + //HttpRequest.clearCerts(); this.pinSSLCertsFromCAStore(); } catch (Exception e) { e.printStackTrace(); @@ -47,72 +48,78 @@ public class CordovaHttpPlugin extends CordovaPlugin { @Override public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException { if (action.equals("post")) { - String urlString = args.getString(0); - Object params = args.get(1); - String serializerName = args.getString(2); + String url = args.getString(0); + Object data = args.get(1); + String serializer = args.getString(2); JSONObject headers = args.getJSONObject(3); - int timeoutInMilliseconds = args.getInt(4) * 1000; - CordovaHttpPost post = new CordovaHttpPost(urlString, params, serializerName, headers, timeoutInMilliseconds, callbackContext); + int timeout = args.getInt(4) * 1000; + + CordovaHttpRequest post = new CordovaHttpRequest("POST", url, serializer, data, headers, timeout, callbackContext); cordova.getThreadPool().execute(post); } else if (action.equals("get")) { - String urlString = args.getString(0); - Object params = args.get(1); + String url = args.getString(0); + JSONObject params = args.getJSONObject(1); JSONObject headers = args.getJSONObject(2); - int timeoutInMilliseconds = args.getInt(3) * 1000; - CordovaHttpGet get = new CordovaHttpGet(urlString, params, headers, timeoutInMilliseconds, callbackContext); + int timeout = args.getInt(3) * 1000; + + CordovaHttpRequest get = new CordovaHttpRequest("GET", url, params, headers, timeout, callbackContext); cordova.getThreadPool().execute(get); } else if (action.equals("put")) { - String urlString = args.getString(0); - Object params = args.get(1); - String serializerName = args.getString(2); + String url = args.getString(0); + Object data = args.get(1); + String serializer = args.getString(2); JSONObject headers = args.getJSONObject(3); - int timeoutInMilliseconds = args.getInt(4) * 1000; - CordovaHttpPut put = new CordovaHttpPut(urlString, params, serializerName, headers, timeoutInMilliseconds, callbackContext); + int timeout = args.getInt(4) * 1000; + + CordovaHttpRequest put = new CordovaHttpRequest("PUT", url, serializer, data, headers, timeout, callbackContext); cordova.getThreadPool().execute(put); } else if (action.equals("patch")) { - String urlString = args.getString(0); - Object params = args.get(1); - String serializerName = args.getString(2); + String url = args.getString(0); + Object data = args.get(1); + String serializer = args.getString(2); JSONObject headers = args.getJSONObject(3); - int timeoutInMilliseconds = args.getInt(4) * 1000; - CordovaHttpPatch patch = new CordovaHttpPatch(urlString, params, serializerName, headers, timeoutInMilliseconds, callbackContext); + int timeout = args.getInt(4) * 1000; + + CordovaHttpRequest patch = new CordovaHttpRequest("PATCH", url, serializer, data, headers, timeout, callbackContext); cordova.getThreadPool().execute(patch); } else if (action.equals("delete")) { - String urlString = args.getString(0); - Object params = args.get(1); + String url = args.getString(0); + JSONObject params = args.getJSONObject(1); JSONObject headers = args.getJSONObject(2); - int timeoutInMilliseconds = args.getInt(3) * 1000; - CordovaHttpDelete delete = new CordovaHttpDelete(urlString, params, headers, timeoutInMilliseconds, callbackContext); + int timeout = args.getInt(3) * 1000; + + CordovaHttpRequest delete = new CordovaHttpRequest("DELETE", url, params, headers, timeout, callbackContext); cordova.getThreadPool().execute(delete); } else if (action.equals("head")) { - String urlString = args.getString(0); - Object params = args.get(1); + String url = args.getString(0); + JSONObject params = args.getJSONObject(1); JSONObject headers = args.getJSONObject(2); - int timeoutInMilliseconds = args.getInt(3) * 1000; - CordovaHttpHead head = new CordovaHttpHead(urlString, params, headers, timeoutInMilliseconds, callbackContext); + int timeout = args.getInt(3) * 1000; + + CordovaHttpRequest head = new CordovaHttpRequest("HEAD", url, params, headers, timeout, callbackContext); cordova.getThreadPool().execute(head); } else if (action.equals("setSSLCertMode")) { String mode = args.getString(0); - HttpRequest.clearCerts(); + //HttpRequest.clearCerts(); if (mode.equals("legacy")) { - HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_DEFAULT); + //HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_DEFAULT); callbackContext.success(); } else if (mode.equals("nocheck")) { - HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_TRUSTALL); + //HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_TRUSTALL); callbackContext.success(); } else if (mode.equals("pinned")) { try { this.loadSSLCertsFromBundle(); - HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_PINNED); + //HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_PINNED); callbackContext.success(); } catch (Exception e) { e.printStackTrace(); @@ -128,22 +135,24 @@ public class CordovaHttpPlugin extends CordovaPlugin { } } } else if (action.equals("uploadFile")) { - String urlString = args.getString(0); + String url = args.getString(0); Object params = args.get(1); JSONObject headers = args.getJSONObject(2); String filePath = args.getString(3); String name = args.getString(4); - int timeoutInMilliseconds = args.getInt(5) * 1000; - CordovaHttpUpload upload = new CordovaHttpUpload(urlString, params, headers, filePath, name, timeoutInMilliseconds, callbackContext); + int timeout = args.getInt(5) * 1000; + + CordovaHttpUpload upload = new CordovaHttpUpload(url, params, headers, filePath, name, timeout, callbackContext); cordova.getThreadPool().execute(upload); } else if (action.equals("downloadFile")) { - String urlString = args.getString(0); + String url = args.getString(0); Object params = args.get(1); JSONObject headers = args.getJSONObject(2); String filePath = args.getString(3); - int timeoutInMilliseconds = args.getInt(4) * 1000; - CordovaHttpDownload download = new CordovaHttpDownload(urlString, params, headers, filePath, timeoutInMilliseconds, callbackContext); + int timeout = args.getInt(4) * 1000; + + CordovaHttpDownload download = new CordovaHttpDownload(url, params, headers, filePath, timeout, callbackContext); cordova.getThreadPool().execute(download); } else if (action.equals("disableRedirect")) { @@ -158,7 +167,7 @@ public class CordovaHttpPlugin extends CordovaPlugin { private void pinSSLCertsFromCAStore() throws GeneralSecurityException, IOException { this.loadSSLCertsFromKeyStore("AndroidCAStore"); - HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_PINNED); + //HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_PINNED); } private void loadSSLCertsFromKeyStore(String storeType) throws GeneralSecurityException, IOException { @@ -170,7 +179,7 @@ public class CordovaHttpPlugin extends CordovaPlugin { String alias = aliases.nextElement(); TrustedCertificateEntry certEntry = (TrustedCertificateEntry) ks.getEntry(alias, null); Certificate cert = certEntry.getTrustedCertificate(); - HttpRequest.addCert(cert); + //HttpRequest.addCert(cert); } } @@ -191,7 +200,7 @@ public class CordovaHttpPlugin extends CordovaPlugin { for (int i = 0; i < cerFiles.size(); i++) { InputStream in = cordova.getActivity().getAssets().open(cerFiles.get(i)); InputStream caInput = new BufferedInputStream(in); - HttpRequest.addCert(caInput); + //HttpRequest.addCert(caInput); } } } diff --git a/src/android/com/synconset/cordovahttp/CordovaHttpPost.java b/src/android/com/synconset/cordovahttp/CordovaHttpPost.java index e42bba6..4696086 100644 --- a/src/android/com/synconset/cordovahttp/CordovaHttpPost.java +++ b/src/android/com/synconset/cordovahttp/CordovaHttpPost.java @@ -12,8 +12,8 @@ import org.json.JSONObject; import javax.net.ssl.SSLHandshakeException; -import com.github.kevinsawicki.http.HttpRequest; -import com.github.kevinsawicki.http.HttpRequest.HttpRequestException; +import com.silkimen.http.HttpRequest; +import com.silkimen.http.HttpRequest.HttpRequestException; class CordovaHttpPost extends CordovaHttp implements Runnable { public CordovaHttpPost(String urlString, Object params, String serializerName, JSONObject headers, int timeout, CallbackContext callbackContext) { diff --git a/src/android/com/synconset/cordovahttp/CordovaHttpPut.java b/src/android/com/synconset/cordovahttp/CordovaHttpPut.java index f75885d..0386b4a 100644 --- a/src/android/com/synconset/cordovahttp/CordovaHttpPut.java +++ b/src/android/com/synconset/cordovahttp/CordovaHttpPut.java @@ -12,8 +12,8 @@ import org.json.JSONObject; import javax.net.ssl.SSLHandshakeException; -import com.github.kevinsawicki.http.HttpRequest; -import com.github.kevinsawicki.http.HttpRequest.HttpRequestException; +import com.silkimen.http.HttpRequest; +import com.silkimen.http.HttpRequest.HttpRequestException; class CordovaHttpPut extends CordovaHttp implements Runnable { public CordovaHttpPut(String urlString, Object data, String serializerName, JSONObject headers, int timeout, CallbackContext callbackContext) { diff --git a/src/android/com/synconset/cordovahttp/CordovaHttpUpload.java b/src/android/com/synconset/cordovahttp/CordovaHttpUpload.java index 033d005..b43794e 100644 --- a/src/android/com/synconset/cordovahttp/CordovaHttpUpload.java +++ b/src/android/com/synconset/cordovahttp/CordovaHttpUpload.java @@ -23,8 +23,8 @@ import javax.net.ssl.SSLHandshakeException; import android.webkit.MimeTypeMap; -import com.github.kevinsawicki.http.HttpRequest; -import com.github.kevinsawicki.http.HttpRequest.HttpRequestException; +import com.silkimen.http.HttpRequest; +import com.silkimen.http.HttpRequest.HttpRequestException; class CordovaHttpUpload extends CordovaHttp implements Runnable { private String filePath; @@ -70,7 +70,7 @@ class CordovaHttpUpload extends CordovaHttp implements Runnable { return; } } - + request.part(this.name, filename, mimeType, new File(uri)); this.returnResponseObject(request);