/* * This class was copied from this Stackoverflow Q&A: * http://stackoverflow.com/questions/2253061/secure-http-post-in-android/2253280#2253280 * Thanks go to MattC! */ package org.acra.util; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.SocketTimeoutException; import java.net.URL; import java.net.URLEncoder; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.util.Map; import android.content.Context; import org.acra.ACRA; import org.acra.sender.HttpSender.Method; import org.acra.sender.HttpSender.Type; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.HttpClient; import org.apache.http.client.HttpRequestRetryHandler; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.params.ClientPNames; import org.apache.http.client.params.CookiePolicy; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.scheme.SocketFactory; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.entity.StringEntity; import org.apache.http.impl.auth.BasicScheme; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.SingleClientConnManager; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; import static org.acra.ACRA.LOG_TAG; public final class HttpRequest { private static class SocketTimeOutRetryHandler implements HttpRequestRetryHandler { private final HttpParams httpParams; private final int maxNrRetries; /** * @param httpParams * HttpParams that will be used in the HttpRequest. * @param maxNrRetries * Max number of times to retry Request on failure due to * SocketTimeOutException. */ private SocketTimeOutRetryHandler(HttpParams httpParams, int maxNrRetries) { this.httpParams = httpParams; this.maxNrRetries = maxNrRetries; } @Override public boolean retryRequest(IOException exception, int executionCount, HttpContext context) { if (exception instanceof SocketTimeoutException) { if (executionCount <= maxNrRetries) { if (httpParams != null) { final int newSocketTimeOut = HttpConnectionParams.getSoTimeout(httpParams) * 2; HttpConnectionParams.setSoTimeout(httpParams, newSocketTimeOut); ACRA.log.d(LOG_TAG, "SocketTimeOut - increasing time out to " + newSocketTimeOut + " millis and trying again"); } else { ACRA.log.d(LOG_TAG, "SocketTimeOut - no HttpParams, cannot increase time out. Trying again with current settings"); } return true; } ACRA.log.d(LOG_TAG, "SocketTimeOut but exceeded max number of retries : " + maxNrRetries); } return false; // To change body of implemented methods use File | // Settings | File Templates. } } private String login; private String password; private int connectionTimeOut = 3000; private int socketTimeOut = 3000; private int maxNrRetries = 3; private Map<String,String> headers; public void setLogin(String login) { this.login = login; } public void setPassword(String password) { this.password = password; } public void setConnectionTimeOut(int connectionTimeOut) { this.connectionTimeOut = connectionTimeOut; } public void setSocketTimeOut(int socketTimeOut) { this.socketTimeOut = socketTimeOut; } public void setHeaders(Map<String,String> headers) { this.headers = headers; } /** * The default number of retries is 3. * * @param maxNrRetries * Max number of times to retry Request on failure due to * SocketTimeOutException. */ public void setMaxNrRetries(int maxNrRetries) { this.maxNrRetries = maxNrRetries; } /** * Posts to a URL. * * * @param context Android context for which to create the SocketFactory. * @param url URL to which to post. * @param content Map of parameters to post to a URL. * @throws IOException if the data cannot be posted. */ public void send(Context context, URL url, Method method, String content, Type type) throws IOException { final HttpClient httpClient = getHttpClient(context); final HttpEntityEnclosingRequestBase httpRequest = getHttpRequest(url, method, content, type); ACRA.log.d(LOG_TAG, "Sending request to " + url); if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Http " + method.name() + " content : "); if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, content); HttpResponse response = null; try { response = httpClient.execute(httpRequest, new BasicHttpContext()); if (response != null) { final StatusLine statusLine = response.getStatusLine(); if (statusLine != null) { final String statusCode = Integer.toString(response.getStatusLine().getStatusCode()); if (!statusCode.equals("409") // 409 return code means that the // report has been received // already. So we can discard it. && !statusCode.equals("403") // a 403 error code is an explicit data validation refusal // from the server. The request must not be repeated. // Discard it. && (statusCode.startsWith("4") || statusCode.startsWith("5"))) { if (ACRA.DEV_LOGGING) { ACRA.log.d(LOG_TAG, "Could not send HttpPost : " + httpRequest); ACRA.log.d(LOG_TAG, "HttpResponse Status : " + (statusLine != null ? statusLine.getStatusCode() : "NoStatusLine#noCode")); final String respContent = EntityUtils.toString(response.getEntity()); ACRA.log.d(LOG_TAG, "HttpResponse Content : " + respContent.substring(0, Math.min(respContent.length(), 200))); } throw new IOException("Host returned error code " + statusCode); } } if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "HttpResponse Status : " + (statusLine != null ? statusLine.getStatusCode() : "NoStatusLine#noCode")); final String respContent = EntityUtils.toString(response.getEntity()); if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "HttpResponse Content : " + respContent.substring(0, Math.min(respContent.length(), 200))); } else { if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "HTTP no Response!!"); } } finally { if (response != null) { response.getEntity().consumeContent(); } } } /** * @return HttpClient to use with this HttpRequest. */ private HttpClient getHttpClient(Context context) { final HttpParams httpParams = new BasicHttpParams(); httpParams.setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.RFC_2109); HttpConnectionParams.setConnectionTimeout(httpParams, connectionTimeOut); HttpConnectionParams.setSoTimeout(httpParams, socketTimeOut); HttpConnectionParams.setSocketBufferSize(httpParams, 8192); final SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("http", new PlainSocketFactory(), 80)); if (ACRA.getConfig().disableSSLCertValidation()) { registry.register(new Scheme("https", (new FakeSocketFactory()), 443)); } else if (ACRA.getConfig().keyStore() != null) { try { SSLSocketFactory sf = new SSLSocketFactory(ACRA.getConfig().keyStore()); sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER); registry.register(new Scheme("https", sf, 443)); } catch (KeyManagementException e) { registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443)); } catch (UnrecoverableKeyException e) { registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443)); } catch (NoSuchAlgorithmException e) { registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443)); } catch (KeyStoreException e) { registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443)); } } else { final HttpsSocketFactoryFactory factory = ACRA.getConfig().getHttpSocketFactoryFactory(); final SocketFactory socketFactory = factory.create(context); registry.register(new Scheme("https", socketFactory, 443)); } final ClientConnectionManager clientConnectionManager = new SingleClientConnManager(httpParams, registry); final DefaultHttpClient httpClient = new DefaultHttpClient(clientConnectionManager, httpParams); final HttpRequestRetryHandler retryHandler = new SocketTimeOutRetryHandler(httpParams, maxNrRetries); httpClient.setHttpRequestRetryHandler(retryHandler); return httpClient; } /** * @return Credentials to use with this HttpRequest or null if no * credentials were supplied. */ private UsernamePasswordCredentials getCredentials() { if (login != null || password != null) { return new UsernamePasswordCredentials(login, password); } return null; } private HttpEntityEnclosingRequestBase getHttpRequest(URL url, Method method, String content, Type type) throws UnsupportedEncodingException, UnsupportedOperationException { final HttpEntityEnclosingRequestBase httpRequest; switch (method) { case POST: httpRequest = new HttpPost(url.toString()); break; case PUT: httpRequest = new HttpPut(url.toString()); break; default: throw new UnsupportedOperationException("Unknown method: " + method.name()); } final UsernamePasswordCredentials creds = getCredentials(); if (creds != null) { httpRequest.addHeader(BasicScheme.authenticate(creds, "UTF-8", false)); } httpRequest.setHeader("User-Agent", "Android"); httpRequest .setHeader("Accept", "text/html,application/xml,application/json,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"); httpRequest.setHeader("Content-Type", type.getContentType()); if(headers != null) { for (final String header : headers.keySet()) { final String value = headers.get(header); httpRequest.setHeader(header, value); } } httpRequest.setEntity(new StringEntity(content, "UTF-8")); return httpRequest; } /** * Converts a Map of parameters into a URL encoded Sting. * * @param parameters * Map of parameters to convert. * @return URL encoded String representing the parameters. * @throws UnsupportedEncodingException * if one of the parameters couldn't be converted to UTF-8. */ public static String getParamsAsFormString(Map<?, ?> parameters) throws UnsupportedEncodingException { final StringBuilder dataBfr = new StringBuilder(); for (final Object key : parameters.keySet()) { if (dataBfr.length() != 0) { dataBfr.append('&'); } final Object preliminaryValue = parameters.get(key); final Object value = (preliminaryValue == null) ? "" : preliminaryValue; dataBfr.append(URLEncoder.encode(key.toString(), "UTF-8")); dataBfr.append('='); dataBfr.append(URLEncoder.encode(value.toString(), "UTF-8")); } return dataBfr.toString(); } }