package org.itsnat.droid.impl.browser; import org.apache.http.Header; import org.apache.http.HeaderElement; import org.apache.http.HttpEntity; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpOptions; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpTrace; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.utils.URLEncodedUtils; 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.ssl.SSLSocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.http.message.BasicNameValuePair; import org.apache.http.params.HttpParams; import org.apache.http.protocol.HttpContext; import org.itsnat.droid.ItsNatDroidException; import org.itsnat.droid.ItsNatDroidServerResponseException; import org.itsnat.droid.impl.util.MiscUtil; import org.itsnat.droid.impl.util.NameValue; import org.itsnat.droid.impl.util.StringUtil; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.Socket; import java.net.SocketTimeoutException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLEncoder; import java.net.UnknownHostException; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.zip.GZIPInputStream; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; /** * Created by jmarranz on 23/05/14. */ public class HttpUtil { public static HttpRequestResultOKImpl httpGet(String url,HttpRequestData httpRequestData,List<NameValue> paramList,String overrideMime) { return httpAction("GET",url,httpRequestData,paramList,overrideMime); } public static HttpRequestResultOKImpl httpPost(String url,HttpRequestData httpRequestData,List<NameValue> paramList,String overrideMime) { return httpAction("POST", url, httpRequestData, paramList, overrideMime); } public static HttpRequestResultOKImpl httpAction(String method,String url,HttpRequestData httpRequestData,List<NameValue> paramList,String overrideMime) { URI uri; try { uri = new URI(url); } catch (URISyntaxException ex) { throw new ItsNatDroidException(ex); } HttpParams httpParams = httpRequestData.getHttpParams(); int connTimeout = httpRequestData.getConnectTimeout(); int readTimeout = httpRequestData.getReadTimeout(); httpParams.setIntParameter("http.connection.timeout", connTimeout); httpParams.setIntParameter("http.socket.timeout", readTimeout); HttpClient httpClient = createHttpClient(uri.getScheme(),httpRequestData.isSslSelfSignedAllowed(),httpParams); HttpUriRequest httpUriRequest = null; method = method.toUpperCase(); // Se especifica que sea en mayúsculas pero por si acaso if ("POST".equals(method) || "PUT".equals(method)) // PATCH no está implementado (sería HttpPatch) { if ("POST".equals(method)) httpUriRequest = new HttpPost(url); else // PUT httpUriRequest = new HttpPut(url); // http://stackoverflow.com/questions/3649814/android-httpput-example-code // httpUriRequest.setHeader("Content-Type", "application/x-www-form-urlencoded"); try { List<NameValuePair> paramListApache = new ArrayList<NameValuePair>(paramList.size()); // Creo que no se admite que sea nulo if (paramList != null) { for (NameValue nameValue : paramList) { String name = nameValue.getName(); String value = nameValue.getValue().toString(); paramListApache.add(new BasicNameValuePair(name, value)); } } ((HttpEntityEnclosingRequest)httpUriRequest).setEntity(new UrlEncodedFormEntity(paramListApache,"UTF-8")); } catch (UnsupportedEncodingException ex) { throw new ItsNatDroidException(ex); } } else { if (paramList != null && paramList.size() > 0) { List<NameValuePair> paramListApache = new ArrayList<NameValuePair>(paramList.size()); for(NameValue nameValue : paramList) { String name = nameValue.getName(); String value = nameValue.getValue().toString(); paramListApache.add(new BasicNameValuePair(name,value)); } // http://stackoverflow.com/questions/2959316/how-to-add-parameters-to-a-http-get-request-in-android String paramString = URLEncodedUtils.format(paramListApache, "UTF-8"); int pos = url.lastIndexOf('?'); if (pos != -1) // Tiene ? { if (!url.endsWith("?")) url += '&'; // Tiene parámetros } else // No tiene ? { url += '?'; } url += paramString; } if ("GET".equals(method)) { httpUriRequest = new HttpGet(url); } else if ("DELETE".equals(method)) { httpUriRequest = new HttpDelete(url); } else if ("HEAD".equals(method)) { httpUriRequest = new HttpHead(url); } else if ("OPTIONS".equals(method)) { httpUriRequest = new HttpOptions(url); } else if ("TRACE".equals(method)) { httpUriRequest = new HttpTrace(url); } else throw new ItsNatDroidException("Unsupported HTTP method: " + method); } HttpResponse httpResponse = execute(httpClient,httpUriRequest,httpRequestData.getHttpContext(),httpRequestData.getRequestPropertyMap()); return processResponse(url,httpResponse,httpRequestData.getHttpFileCache(),overrideMime); } private static HttpResponse execute(HttpClient httpClient,HttpUriRequest httpUriRequest,HttpContext httpContext,RequestPropertyMap requestPropertyMap) { try { // Para evitar cacheados (en el caso de GET) por si acaso // http://stackoverflow.com/questions/49547/making-sure-a-web-page-is-not-cached-across-all-browsers httpUriRequest.setHeader("If-Modified-Since","Wed, 15 Nov 1995 00:00:00 GMT"); httpUriRequest.setHeader("Cache-Control","no-store,no-httpFileCache,must-revalidate"); httpUriRequest.setHeader("Pragma", "no-httpFileCache"); // HTTP 1.0. httpUriRequest.setHeader("Expires", "0"); // Proxies. for(Map.Entry<String,List<String>> header : requestPropertyMap.getPropertyMap().entrySet()) { String name = header.getKey(); List<String> valueList = header.getValue(); for(String value : valueList) httpUriRequest.addHeader(name, value); } HttpResponse response = httpClient.execute(httpUriRequest, httpContext); return response; } catch(SocketTimeoutException ex) { throw new ItsNatDroidException(ex); } // Hay un caso en que se ejecuta fireEventMonitors de forma específica al detectar esta excepción catch(ClientProtocolException ex) { throw new ItsNatDroidException(ex); } catch(IOException ex) { throw new ItsNatDroidException(ex); } } private static HttpRequestResultOKImpl processResponse(String url,HttpResponse httpResponse,HttpFileCache httpFileCache,String overrideMime) { // Get hold of the response entity HttpEntity entity = httpResponse.getEntity(); // If the response does not enclose an entity, there is no need // to worry about connection release if (entity == null) throw MiscUtil.internalError(); // null es muy raro incluso en caso de error String[] mimeTypeRes = new String[1]; String[] encodingRes = new String[1]; getMimeTypeEncoding(httpResponse, mimeTypeRes, encodingRes); if (!StringUtil.isEmpty(overrideMime)) mimeTypeRes[0] = overrideMime; InputStream input; try { input = entity.getContent(); // Interesa incluso cuando hay error (statusCode != 200) porque obtenemos el texto del error Header contentEncoding = httpResponse.getFirstHeader("Content-Encoding"); if (contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip")) { // http://stackoverflow.com/questions/1573391/android-http-communication-should-use-accept-encoding-gzip input = new GZIPInputStream(input); } } catch (IOException ex) { throw new ItsNatDroidException(ex); } HttpRequestResultImpl result = HttpRequestResultImpl.createHttpRequestResult(url,httpResponse,input, httpFileCache, mimeTypeRes[0], encodingRes[0]); if (result instanceof HttpRequestResultFailImpl) { throw new ItsNatDroidServerResponseException(result); } return (HttpRequestResultOKImpl)result; } public static HttpClient createHttpClient(String scheme,boolean sslSelfSignedAllowed,HttpParams httpParams) { if (sslSelfSignedAllowed && scheme.equals("https")) return getHttpClientSSLSelfSignedAllowed(httpParams); else return getHttpClientThreadSafe(httpParams); } public static HttpClient getHttpClientSSLSelfSignedAllowed(HttpParams params) { // URLs para probar: "https://www.pcwebshop.co.uk/" "https://mms.nw.ru/" try { KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustStore.load(null, null); SSLSocketFactory sf = new SSLSocketFactoryForSelfSigned(trustStore); sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); //HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); //HttpProtocolParams.setContentCharset(params, HTTP.UTF_8); SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); registry.register(new Scheme("https", sf, 443)); ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry); return new DefaultHttpClient(ccm, params); } catch (KeyStoreException ex) { throw new ItsNatDroidException(ex); } catch (NoSuchAlgorithmException ex) { throw new ItsNatDroidException(ex); } catch (IOException ex) { throw new ItsNatDroidException(ex); } catch (CertificateException ex) { throw new ItsNatDroidException(ex); } catch (KeyManagementException ex) { throw new ItsNatDroidException(ex); } catch (UnrecoverableKeyException ex) { throw new ItsNatDroidException(ex); } } private static DefaultHttpClient getHttpClientThreadSafe(HttpParams httpParams) { // Evitamos el error aleatorio: java.lang.IllegalStateException: No wrapped connection // http://www.androider.me/2013/11/solve-one-issue-because-of-thread-safe.html // https://groups.google.com/forum/#!topic/android-developers/GUnCMjCnKKQ // http://stackoverflow.com/questions/10795591/releasing-connection-in-android DefaultHttpClient client = new DefaultHttpClient(httpParams); ClientConnectionManager mgr = client.getConnectionManager(); HttpParams params = client.getParams(); client = new DefaultHttpClient(new ThreadSafeClientConnManager(params,mgr.getSchemeRegistry()), params); return client; } private static void getMimeTypeEncoding(HttpResponse httpResponse, String[] mimeType, String[] encoding) { Header[] contentTypes = httpResponse.getHeaders("Content-Type"); // Internamente ignora mayúsculas y minúsculas, no hay que preocuparse if (contentTypes != null && contentTypes.length > 0) { // Ej: Content-Type: android/layout;charset=UTF-8 HeaderElement[] elems = contentTypes[0].getElements(); // https://hc.apache.org/httpclient-3.x/apidocs/org/apache/commons/httpclient/HeaderElement.html for (HeaderElement elem : elems) { mimeType[0] = elem.getName(); NameValuePair[] params = elem.getParameters(); for (NameValuePair param : params) { String name = param.getName(); if (name.equalsIgnoreCase("charset")) { encoding[0] = param.getValue(); break; } } if (encoding != null) break; } } if (mimeType[0] == null) mimeType[0] = "android/layout"; // Por si acaso if (encoding[0] == null) encoding[0] = "UTF-8"; // Por si acaso } private static String formURLQuery(String url,List<NameValue> paramList,boolean get) { if (paramList != null && paramList.size() > 0) { StringBuilder query = new StringBuilder(); if (get) { int pos = url.indexOf('?'); if (pos != -1) // Tiene ? { if (!url.endsWith("?")) query.append('&'); // Tiene ya parámetros y tenemos que añadir más } } int i = 0; for(NameValue param : paramList) { try { String name = param.getName(); Object value = param.getValue(); query.append(URLEncoder.encode(name, "UTF-8")); query.append("="); if (value != null) query.append(URLEncoder.encode(value.toString(), "UTF-8")); if (i < paramList.size() - 1) query.append('&'); // Hay más parámetros i++; } catch (UnsupportedEncodingException ex) { throw new ItsNatDroidException(ex); } } return query.toString(); } else { return ""; } } public static String composeAbsoluteURL(String src,String pageURLStr) { String absURL; URI uri; try { uri = new URI(src); } catch (URISyntaxException ex) { throw new ItsNatDroidException(ex); } String scheme = uri.getScheme(); if (scheme == null) { if (src.equals("")) // Ejemplo: <item name="android:paddingRight">@remote:dimen/:test_dimen_paddingRight</item> referenciado en un archivo XML values { absURL = pageURLStr; } else if (src.startsWith("/")) { // Path absoluto, tenemos que formar: scheme://authority + src URL pageURL; try { pageURL = new URL(pageURLStr); } catch (MalformedURLException ex) { throw new ItsNatDroidException(ex); } absURL = pageURL.getProtocol() + "://" + pageURL.getAuthority() + src; } else { int pos = pageURLStr.lastIndexOf('/'); if (pos < pageURLStr.length() - 1) // El / no está en el final pageURLStr = pageURLStr.substring(0, pos + 1); // Quitamos así el servlet, el JSP etc que generó la página // Ahora pageURLStr termina en '/' absURL = pageURLStr.substring(0, pos + 1) + src; } } else { // Path absoluto, nada que componer if (!scheme.equals("http") && !scheme.equals("https")) throw new ItsNatDroidException("Scheme not supported: " + scheme); absURL = src; } return absURL; } public static String getBasePathOfURL(String urlStr) { URL u = null; try { u = new URL(urlStr); } catch (MalformedURLException ex) { throw new ItsNatDroidException(ex); } // Vale, sí, este código está basado en el código fuente de java.net.URLStreamHandler.toExternalForm() // pre-compute length of StringBuilder int len = u.getProtocol().length() + 1; if (u.getAuthority() != null && u.getAuthority().length() > 0) len += 2 + u.getAuthority().length(); if (u.getPath() != null) { len += u.getPath().length(); } /* if (u.getQuery() != null) { len += 1 + u.getQuery().length(); } if (u.getRef() != null) len += 1 + u.getRef().length(); */ StringBuilder result = new StringBuilder(len); result.append(u.getProtocol()); result.append(":"); if (u.getAuthority() != null && u.getAuthority().length() > 0) { result.append("//"); result.append(u.getAuthority()); } if (u.getPath() != null) { result.append(u.getPath()); } /* if (u.getQuery() != null) { result.append('?'); result.append(u.getQuery()); } if (u.getRef() != null) { result.append("#"); result.append(u.getRef()); } */ return result.toString(); } private static class SSLSocketFactoryForSelfSigned extends SSLSocketFactory { private SSLContext sslContext = SSLContext.getInstance("TLS"); public SSLSocketFactoryForSelfSigned(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { super(truststore); TrustManager tm = new X509TrustManager() { public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public X509Certificate[] getAcceptedIssuers() { return null; } }; sslContext.init(null, new TrustManager[] { tm }, null); } @Override public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); } @Override public Socket createSocket() throws IOException { return sslContext.getSocketFactory().createSocket(); } } private static String convertStreamToString_NO_SE_USA(InputStream is) { /* * To convert the InputStream to String we use the BufferedReader.readLine() * method. We iterate until the BufferedReader return null which means * there's no more data to read. Each line will appended to a StringBuilder * and returned as String. */ BufferedReader reader = new BufferedReader(new InputStreamReader(is)); StringBuilder sb = new StringBuilder(); String line = null; try { while ((line = reader.readLine()) != null) { sb.append(line + "\n"); } } catch (IOException ex) { throw new ItsNatDroidException(ex); } finally { try { is.close(); } catch (IOException ex) { throw new ItsNatDroidException(ex); } } return sb.toString(); } }