/** * 2011 Foxykeep (http://datadroid.foxykeep.com) * <p> * Licensed under the Beerware License : <br /> * As long as you retain this notice you can do whatever you want with this stuff. If we meet some * day, and you think this stuff is worth it, you can buy me a beer in return */ package com.foxykeep.datadroid.internal.network; import com.foxykeep.datadroid.exception.ConnectionException; import com.foxykeep.datadroid.network.NetworkConnection.ConnectionResult; import com.foxykeep.datadroid.network.NetworkConnection.Method; import com.foxykeep.datadroid.network.UserAgentUtils; import com.foxykeep.datadroid.util.DataDroidLog; import android.content.Context; import android.util.Base64; import android.util.Log; import org.apache.http.HttpStatus; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HTTP; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.HashMap; import java.util.Map.Entry; 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; /** * Implementation of the network connection. * * @author Foxykeep */ public final class NetworkConnectionImpl { private static final String TAG = NetworkConnectionImpl.class.getSimpleName(); private static final String ACCEPT_CHARSET_HEADER = "Accept-Charset"; private static final String ACCEPT_ENCODING_HEADER = "Accept-Encoding"; private static final String AUTHORIZATION_HEADER = "Authorization"; private static final String LOCATION_HEADER = "Location"; private static final String UTF8_CHARSET = "UTF-8"; // Default connection and socket timeout of 60 seconds. Tweak to taste. private static final int OPERATION_TIMEOUT = 60 * 1000; private NetworkConnectionImpl() { // No public constructor } /** * Call the webservice using the given parameters to construct the request and return the * result. * * @param context The context to use for this operation. Used to generate the user agent if * needed. * @param urlValue The webservice URL. * @param method The request method to use. * @param parameterList The parameters to add to the request. * @param headerMap The headers to add to the request. * @param isGzipEnabled Whether the request will use gzip compression if available on the * server. * @param userAgent The user agent to set in the request. If null, a default Android one will be * created. * @param postText The POSTDATA text to add in the request. * @param credentials The credentials to use for authentication. * @param isSslValidationEnabled Whether the request will validate the SSL certificates. * @return The result of the webservice call. */ public static ConnectionResult execute(Context context, String urlValue, Method method, ArrayList<BasicNameValuePair> parameterList, HashMap<String, String> headerMap, boolean isGzipEnabled, String userAgent, String postText, UsernamePasswordCredentials credentials, boolean isSslValidationEnabled) throws ConnectionException { HttpURLConnection connection = null; try { // Prepare the request information if (userAgent == null) { userAgent = UserAgentUtils.get(context); } if (headerMap == null) { headerMap = new HashMap<String, String>(); } headerMap.put(HTTP.USER_AGENT, userAgent); if (isGzipEnabled) { headerMap.put(ACCEPT_ENCODING_HEADER, "gzip"); } headerMap.put(ACCEPT_CHARSET_HEADER, UTF8_CHARSET); if (credentials != null) { headerMap.put(AUTHORIZATION_HEADER, createAuthenticationHeader(credentials)); } StringBuilder paramBuilder = new StringBuilder(); if (parameterList != null && !parameterList.isEmpty()) { for (int i = 0, size = parameterList.size(); i < size; i++) { BasicNameValuePair parameter = parameterList.get(i); paramBuilder.append(URLEncoder.encode(parameter.getName(), UTF8_CHARSET)); paramBuilder.append("="); paramBuilder.append(URLEncoder.encode(parameter.getValue(), UTF8_CHARSET)); paramBuilder.append("&"); } } // Log the request if (DataDroidLog.canLog(Log.DEBUG)) { DataDroidLog.d(TAG, "Request url: " + urlValue); DataDroidLog.d(TAG, "Method: " + method.toString()); if (parameterList != null && !parameterList.isEmpty()) { DataDroidLog.d(TAG, "Parameters:"); for (int i = 0, size = parameterList.size(); i < size; i++) { BasicNameValuePair parameter = parameterList.get(i); String message = "- " + parameter.getName() + " = " + parameter.getValue(); DataDroidLog.d(TAG, message); } } if (postText != null) { DataDroidLog.d(TAG, "Post body: " + postText); } if (headerMap != null && !headerMap.isEmpty()) { DataDroidLog.d(TAG, "Headers:"); for (Entry<String, String> header : headerMap.entrySet()) { DataDroidLog.d(TAG, "- " + header.getKey() + " = " + header.getValue()); } } } // Create the connection object URL url = null; String outputText = null; switch (method) { case GET: case DELETE: url = new URL(urlValue + "?" + paramBuilder.toString()); connection = (HttpURLConnection) url.openConnection(); break; case PUT: case POST: url = new URL(urlValue); connection = (HttpURLConnection) url.openConnection(); connection.setDoOutput(true); if (paramBuilder.length() > 0) { outputText = paramBuilder.toString(); headerMap.put(HTTP.CONTENT_TYPE, "application/x-www-form-urlencoded"); headerMap.put(HTTP.CONTENT_LEN, String.valueOf(outputText.getBytes().length)); } else if (postText != null) { outputText = postText; } break; } // Set the request method connection.setRequestMethod(method.toString()); // If it's an HTTPS request and the SSL Validation is disabled if (url.getProtocol().equals("https") && !isSslValidationEnabled) { HttpsURLConnection httpsConnection = (HttpsURLConnection) connection; httpsConnection.setSSLSocketFactory(getAllHostsValidSocketFactory()); httpsConnection.setHostnameVerifier(getAllHostsValidVerifier()); } // Add the headers if (!headerMap.isEmpty()) { for (Entry<String, String> header : headerMap.entrySet()) { connection.addRequestProperty(header.getKey(), header.getValue()); } } // Set the connection and read timeout connection.setConnectTimeout(OPERATION_TIMEOUT); connection.setReadTimeout(OPERATION_TIMEOUT); // Set the outputStream content for POST and PUT requests if ((method == Method.POST || method == Method.PUT) && outputText != null) { OutputStream output = null; try { output = connection.getOutputStream(); output.write(outputText.getBytes()); } finally { if (output != null) { try { output.close(); } catch (IOException e) { // Already catching the first IOException so nothing to do here. } } } } String contentEncoding = connection.getHeaderField(HTTP.CONTENT_ENCODING); int responseCode = connection.getResponseCode(); boolean isGzip = contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip"); DataDroidLog.d(TAG, "Response code: " + responseCode); if (responseCode == HttpStatus.SC_MOVED_PERMANENTLY) { String redirectionUrl = connection.getHeaderField(LOCATION_HEADER); throw new ConnectionException("New location : " + redirectionUrl, redirectionUrl); } InputStream errorStream = connection.getErrorStream(); if (errorStream != null) { String error = convertStreamToString(errorStream, isGzip); throw new ConnectionException(error, responseCode); } String body = convertStreamToString(connection.getInputStream(), isGzip); if (DataDroidLog.canLog(Log.VERBOSE)) { DataDroidLog.v(TAG, "Response body: "); int pos = 0; int bodyLength = body.length(); while (pos < bodyLength) { DataDroidLog.v(TAG, body.substring(pos, Math.min(bodyLength - 1, pos + 200))); pos = pos + 200; } } return new ConnectionResult(connection.getHeaderFields(), body); } catch (IOException e) { DataDroidLog.e(TAG, "IOException", e); throw new ConnectionException(e); } catch (KeyManagementException e) { DataDroidLog.e(TAG, "KeyManagementException", e); throw new ConnectionException(e); } catch (NoSuchAlgorithmException e) { DataDroidLog.e(TAG, "NoSuchAlgorithmException", e); throw new ConnectionException(e); } finally { if (connection != null) { connection.disconnect(); } } } private static String createAuthenticationHeader(UsernamePasswordCredentials credentials) { StringBuilder sb = new StringBuilder(); sb.append(credentials.getUserName()).append(":").append(credentials.getPassword()); return "Basic " + Base64.encodeToString(sb.toString().getBytes(), Base64.NO_WRAP); } private static SSLSocketFactory sAllHostsValidSocketFactory; private static SSLSocketFactory getAllHostsValidSocketFactory() throws NoSuchAlgorithmException, KeyManagementException { if (sAllHostsValidSocketFactory == null) { TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(X509Certificate[] certs, String authType) {} public void checkServerTrusted(X509Certificate[] certs, String authType) {} } }; SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new java.security.SecureRandom()); sAllHostsValidSocketFactory = sc.getSocketFactory(); } return sAllHostsValidSocketFactory; } private static HostnameVerifier sAllHostsValidVerifier; private static HostnameVerifier getAllHostsValidVerifier() { if (sAllHostsValidVerifier == null) { sAllHostsValidVerifier = new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; } }; } return sAllHostsValidVerifier; } private static String convertStreamToString(InputStream is, boolean isGzipEnabled) throws IOException { InputStream cleanedIs = is; if (isGzipEnabled) { cleanedIs = new GZIPInputStream(is); } BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(cleanedIs, UTF8_CHARSET)); StringBuilder sb = new StringBuilder(); for (String line; (line = reader.readLine()) != null;) { sb.append(line); sb.append("\n"); } return sb.toString(); } finally { if (reader != null) { reader.close(); } cleanedIs.close(); if (isGzipEnabled) { is.close(); } } } }