/*
* Copyright (C) 2010 Nullbyte <http://nullbyte.eu>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package eu.nullbyte.android.urllib;
import com.liato.bankdroid.legacy.BuildConfig;
import com.liato.bankdroid.legacy.R;
import com.liato.bankdroid.utils.ExceptionUtils;
import org.apache.http.ConnectionReuseStrategy;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.NoHttpResponseException;
import org.apache.http.ProtocolException;
import org.apache.http.client.AuthenticationHandler;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.RedirectHandler;
import org.apache.http.client.RequestDirector;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.UserTokenHandler;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.routing.HttpRoutePlanner;
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.ssl.SSLSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.DefaultRedirectHandler;
import org.apache.http.impl.client.DefaultRequestDirector;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.HttpRequestExecutor;
import org.apache.http.util.EntityUtils;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Build;
import android.preference.PreferenceManager;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import timber.log.Timber;
public class Urllib {
private final static int MAX_RETRIES = 5;
public final static String DEFAULT_USER_AGENT
= "Mozilla/5.0 (Linux; U; Android 2.1; en-us; Nexus One Build/ERD62) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17";
private String userAgent = null;
private DefaultHttpClient httpclient;
private HttpContext mHttpContext;
private String currentURI;
private String charset = HTTP.UTF_8;
private HashMap<String, String> headers;
private Context mContext;
private CertPinningSSLSocketFactory mSSLSocketFactory;
public Urllib(Context context) {
this(context, null);
}
public Urllib(Context context, Certificate[] pins) {
this(context, null, pins);
}
public Urllib(Context context, ClientCertificate clientCert, Certificate[] pins) {
mContext = context;
this.headers = new HashMap<String, String>();
userAgent = createUserAgentString();
HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, this.charset);
params.setBooleanParameter("http.protocol.expect-continue", false);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean trustSystemKeystore = prefs.getBoolean("debug_mode", false) && prefs
.getBoolean("no_cert_pinning", false);
try {
mSSLSocketFactory = new CertPinningSSLSocketFactory(clientCert, pins);
registry.register(new Scheme("https",
pins != null && !trustSystemKeystore ? mSSLSocketFactory
: SSLSocketFactory.getSocketFactory(), 443));
} catch (UnrecoverableKeyException | KeyManagementException | KeyStoreException | NoSuchAlgorithmException e) {
Timber.w(e, "Urllib: SSLSocketFactory error");
}
ClientConnectionManager manager = new ThreadSafeClientConnManager(params, registry);
httpclient = new BankdroidHttpClient(manager, params);
mHttpContext = new BasicHttpContext();
}
public String open(String url) throws ClientProtocolException, IOException {
try {
return this.open(url, new ArrayList<NameValuePair>());
} catch (IOException e) {
ExceptionUtils.blameBankdroid(e);
throw e;
}
}
public String post(String url) throws ClientProtocolException, IOException {
return this.open(url, new ArrayList<NameValuePair>(), true);
}
public String open(String url, List<NameValuePair> postData)
throws ClientProtocolException, IOException {
try {
return open(url, postData, false);
} catch (IOException e) {
ExceptionUtils.blameBankdroid(e);
throw e;
}
}
public String open(String url, List<NameValuePair> postData, boolean forcePost)
throws ClientProtocolException, IOException {
HttpEntity entity = openAsHttpResponse(url, postData, forcePost).getEntity();
if (entity == null) {
return "";
}
return EntityUtils.toString(entity);
}
public HttpResponse openAsHttpResponse(String url, List<NameValuePair> postData,
boolean forcePost) throws ClientProtocolException, IOException {
HttpEntity entity = (postData == null || postData.isEmpty()) && !forcePost ? null
: new UrlEncodedFormEntity(postData, this.charset);
try {
return openAsHttpResponse(url, entity, forcePost);
} catch (IOException e) {
ExceptionUtils.blameBankdroid(e);
throw e;
}
}
public HttpResponse openAsHttpResponse(String url, boolean forcePost)
throws ClientProtocolException, IOException {
return openAsHttpResponse(url, Collections.<NameValuePair>emptyList(), forcePost);
}
public HttpResponse openAsHttpResponse(String url, HttpEntity entity, boolean forcePost)
throws ClientProtocolException, IOException {
try {
if ((entity == null) && !forcePost) {
return openAsHttpResponse(url, entity, HttpMethod.GET);
} else {
return openAsHttpResponse(url, entity, HttpMethod.POST);
}
} catch (IOException e) {
ExceptionUtils.blameBankdroid(e);
throw e;
}
}
public HttpResponse openAsHttpResponse(String url, HttpMethod method)
throws ClientProtocolException, IOException {
return openAsHttpResponse(url, null, method);
}
public HttpResponse openAsHttpResponse(String url, HttpEntity entity, HttpMethod method)
throws ClientProtocolException, IOException {
this.currentURI = url;
HttpResponse response;
String[] headerKeys = (String[]) this.headers.keySet().toArray(new String[headers.size()]);
String[] headerVals = (String[]) this.headers.values().toArray(new String[headers.size()]);
HttpUriRequest request;
switch (method) {
case GET:
request = new HttpGet(url);
break;
case POST:
request = new HttpPost(url);
((HttpPost) request).setEntity(entity);
break;
case PUT:
request = new HttpPut(url);
((HttpPut) request).setEntity(entity);
break;
default:
request = new HttpGet(url);
}
if (userAgent != null) {
request.addHeader("User-Agent", userAgent);
}
for (int i = 0; i < headerKeys.length; i++) {
request.addHeader(headerKeys[i], headerVals[i]);
}
HttpRequestRetryHandler retryHandler = new HttpRequestRetryHandler() {
public boolean retryRequest(IOException exception, int executionCount,
HttpContext context) {
// retry a max of 5 times
if (executionCount >= MAX_RETRIES) {
return false;
}
if (exception instanceof NoHttpResponseException) {
return true;
} else if (exception instanceof ClientProtocolException) {
return true;
}
return false;
}
};
httpclient.setHttpRequestRetryHandler(retryHandler);
response = httpclient.execute(request, mHttpContext);
//HttpUriRequest currentReq = (HttpUriRequest)mHttpContext.getAttribute(ExecutionContext.HTTP_REQUEST);
//HttpHost currentHost = (HttpHost)mHttpContext.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
//this.currentURI = currentHost.toURI() + currentReq.getURI();
this.currentURI = request.getURI().toString();
return response;
}
public InputStream openStream(String url) throws ClientProtocolException, IOException {
return openStream(url, (HttpEntity) null, false);
}
public HttpEntity toEntity(List<NameValuePair> postData) {
if (postData != null && !postData.isEmpty()) {
try {
return new UrlEncodedFormEntity(postData, this.charset);
} catch (UnsupportedEncodingException e) {
Timber.w(e, "Error converting NameValuePair list to HttpEntity");
}
}
return null;
}
public InputStream openStream(String url, List<NameValuePair> postData, boolean forcePost)
throws ClientProtocolException, IOException {
return openStream(url, toEntity(postData), forcePost);
}
public InputStream openStream(String url, String postData, boolean forcePost)
throws ClientProtocolException, IOException {
try {
return openStream(url, postData != null ? new StringEntity(postData, this.charset) : null,
forcePost);
} catch (IOException e) {
ExceptionUtils.blameBankdroid(e);
throw e;
}
}
public InputStream openStream(String url, HttpEntity postData, boolean forcePost)
throws ClientProtocolException, IOException {
this.currentURI = url;
String[] headerKeys = (String[]) this.headers.keySet().toArray(new String[headers.size()]);
String[] headerVals = (String[]) this.headers.values().toArray(new String[headers.size()]);
HttpUriRequest request;
if (!forcePost && postData == null) {
request = new HttpGet(url);
} else {
request = new HttpPost(url);
((HttpPost) request).setEntity(postData);
}
if (userAgent != null) {
request.addHeader("User-Agent", userAgent);
}
for (int i = 0; i < headerKeys.length; i++) {
request.addHeader(headerKeys[i], headerVals[i]);
}
this.currentURI = request.getURI().toString();
HttpResponse response = httpclient.execute(request);
HttpEntity entity = response.getEntity();
return entity.getContent();
}
public void close() {
httpclient.getConnectionManager().shutdown();
}
public HttpContext getHttpContext() {
return mHttpContext;
}
public String getCurrentURI() {
return currentURI;
}
public DefaultHttpClient getHttpclient() {
return httpclient;
}
public void setContentCharset(String charset) {
this.charset = charset;
HttpProtocolParams.setContentCharset(httpclient.getParams(), this.charset);
}
public void setAllowCircularRedirects(boolean allow) {
httpclient.getParams().setBooleanParameter("http.protocol.allow-circular-redirects", allow);
}
public void addHeader(String key, String value) {
this.headers.put(key, value);
}
public void setKeepAliveTimeout(final int seconds) {
httpclient.setKeepAliveStrategy(new ConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(HttpResponse response, HttpContext arg1) {
return seconds;
}
});
}
public String removeHeader(String key) {
return this.headers.remove(key);
}
public void clearHeaders() {
this.headers.clear();
}
public HashMap<String, String> getHeaders() {
return this.headers;
}
public void setFollowRedirects(boolean follow) {
httpclient
.setRedirectHandler(follow ? new DefaultRedirectHandler() : new RedirectHandler() {
public URI getLocationURI(HttpResponse response,
HttpContext context) throws ProtocolException {
return null;
}
public boolean isRedirectRequested(HttpResponse response,
HttpContext context) {
return false;
}
});
}
public void setUserAgent(String userAgent) {
this.userAgent = userAgent;
}
private String createUserAgentString() {
String appName = mContext.getString(R.string.app_name);
Configuration config = mContext.getResources().getConfiguration();
return String
.format("%1$s/%2$s (%3$s; U; Android %4$s; %5$s-%6$s; %10$s Build/%7$s; %8$s) %9$s %10$s"
, appName
, BuildConfig.VERSION_NAME
, System.getProperty("os.name", "Linux")
, Build.VERSION.RELEASE
, config.locale.getLanguage().toLowerCase()
, config.locale.getCountry().toLowerCase()
, Build.ID
, Build.BRAND
, Build.MANUFACTURER
, Build.MODEL);
}
private void updateSocketFactoryHost(HttpHost host) {
if (mSSLSocketFactory != null && host != null) {
mSSLSocketFactory.setHost(host.getHostName());
}
}
class BankdroidHttpClient extends DefaultHttpClient {
BankdroidHttpClient(ClientConnectionManager conman, HttpParams params) {
super(conman, params);
}
@Override
public <T> T execute(HttpHost target, HttpRequest request,
ResponseHandler<? extends T> responseHandler)
throws IOException, ClientProtocolException {
updateSocketFactoryHost(target);
return super.execute(target, request, responseHandler);
}
@Override
public <T> T execute(HttpHost target, HttpRequest request,
ResponseHandler<? extends T> responseHandler, HttpContext context)
throws IOException, ClientProtocolException {
updateSocketFactoryHost(target);
return super.execute(target, request, responseHandler, context);
}
@Override
protected RequestDirector createClientRequestDirector(HttpRequestExecutor requestExec,
ClientConnectionManager conman, ConnectionReuseStrategy reustrat,
ConnectionKeepAliveStrategy kastrat, HttpRoutePlanner rouplan,
HttpProcessor httpProcessor, HttpRequestRetryHandler retryHandler,
RedirectHandler redirectHandler, AuthenticationHandler targetAuthHandler,
AuthenticationHandler proxyAuthHandler, UserTokenHandler stateHandler,
HttpParams params) {
return new DefaultishRequestDirector(requestExec, conman, reustrat, kastrat, rouplan,
httpProcessor, retryHandler, redirectHandler, targetAuthHandler,
proxyAuthHandler, stateHandler, params);
}
}
class DefaultishRequestDirector extends DefaultRequestDirector {
public DefaultishRequestDirector(HttpRequestExecutor requestExec,
ClientConnectionManager conman, ConnectionReuseStrategy reustrat,
ConnectionKeepAliveStrategy kastrat, HttpRoutePlanner rouplan,
HttpProcessor httpProcessor, HttpRequestRetryHandler retryHandler,
RedirectHandler redirectHandler, AuthenticationHandler targetAuthHandler,
AuthenticationHandler proxyAuthHandler, UserTokenHandler userTokenHandler,
HttpParams params) {
super(requestExec, conman, reustrat, kastrat, rouplan, httpProcessor, retryHandler,
redirectHandler, targetAuthHandler, proxyAuthHandler, userTokenHandler, params);
}
@Override
public HttpResponse execute(HttpHost target, HttpRequest request, HttpContext context)
throws HttpException, IOException {
updateSocketFactoryHost(target);
return super.execute(target, request, context);
}
}
}