/*
* 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();
}
}