diff options
Diffstat (limited to 'WordPress/src/main/java/org/wordpress/android/networking/WPDelayedHurlStack.java')
-rw-r--r-- | WordPress/src/main/java/org/wordpress/android/networking/WPDelayedHurlStack.java | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/WordPress/src/main/java/org/wordpress/android/networking/WPDelayedHurlStack.java b/WordPress/src/main/java/org/wordpress/android/networking/WPDelayedHurlStack.java new file mode 100644 index 000000000..b0afaec72 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/networking/WPDelayedHurlStack.java @@ -0,0 +1,287 @@ +package org.wordpress.android.networking; + +import android.content.Context; +import android.util.Base64; + +import com.android.volley.AuthFailureError; +import com.android.volley.Request; +import com.android.volley.Request.Method; +import com.android.volley.toolbox.HttpStack; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.ProtocolVersion; +import org.apache.http.StatusLine; +import org.apache.http.entity.BasicHttpEntity; +import org.apache.http.message.BasicHeader; +import org.apache.http.message.BasicHttpResponse; +import org.apache.http.message.BasicStatusLine; +import org.wordpress.android.WordPress; +import org.wordpress.android.models.Blog; +import org.wordpress.android.models.AccountHelper; +import org.wordpress.android.util.AppLog; +import org.wordpress.android.util.AppLog.T; +import org.wordpress.android.util.UrlUtils; +import org.wordpress.android.util.WPUrlUtils; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.security.GeneralSecurityException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; + +/** + * An {@link HttpStack} based on the code of {@link com.android.volley.toolbox.HurlStack} that internally + * uses a {@link HttpURLConnection}. + * + * This implementation of {@link HttpStack} internally initializes {@link SelfSignedSSLCertsManager} in a secondary + * thread since initialization could take a few seconds. + */ +public class WPDelayedHurlStack implements HttpStack { + private static final String HEADER_CONTENT_TYPE = "Content-Type"; + + private SSLSocketFactory mSslSocketFactory; + private final Blog mCurrentBlog; + private final Context mCtx; + private final Object monitor = new Object(); + + public WPDelayedHurlStack(final Context ctx, final Blog currentBlog) { + mCurrentBlog = currentBlog; + mCtx = ctx; + + // initializes SelfSignedSSLCertsManager in a separate thread. + Thread sslContextInitializer = new Thread() { + @Override + public void run() { + try { + TrustManager[] trustAllowedCerts = new TrustManager[]{ + new WPTrustManager(SelfSignedSSLCertsManager.getInstance(ctx).getLocalKeyStore()) + }; + SSLContext context = SSLContext.getInstance("SSL"); + context.init(null, trustAllowedCerts, new SecureRandom()); + mSslSocketFactory = context.getSocketFactory(); + } catch (NoSuchAlgorithmException e) { + AppLog.e(T.API, e); + } catch (KeyManagementException e) { + AppLog.e(T.API, e); + } catch (GeneralSecurityException e) { + AppLog.e(T.API, e); + } catch (IOException e) { + AppLog.e(T.API, e); + } + } + }; + sslContextInitializer.start(); + } + + + private static boolean hasAuthorizationHeader(Request request) { + try { + if (request.getHeaders() != null && request.getHeaders().containsKey("Authorization")) { + return true; + } + } catch (AuthFailureError e) { + // nope + } + + return false; + } + + @Override + public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) + throws IOException, AuthFailureError { + if (request.getUrl() != null) { + if (!WPUrlUtils.isWordPressCom(request.getUrl()) && mCurrentBlog != null + && mCurrentBlog.hasValidHTTPAuthCredentials()) { + String creds = String.format("%s:%s", mCurrentBlog.getHttpuser(), mCurrentBlog.getHttppassword()); + String auth = "Basic " + Base64.encodeToString(creds.getBytes(), Base64.DEFAULT); + additionalHeaders.put("Authorization", auth); + } + + /** + * Add the Authorization header to access private WP.com files. + * + * Note: Additional headers have precedence over request headers, so add Authorization only it it's not already + * available in the request. + * + */ + if (WPUrlUtils.safeToAddWordPressComAuthToken(request.getUrl()) && mCtx != null + && AccountHelper.isSignedInWordPressDotCom() && !hasAuthorizationHeader(request)) { + additionalHeaders.put("Authorization", "Bearer " + AccountHelper.getDefaultAccount().getAccessToken()); + } + } + + additionalHeaders.put("User-Agent", WordPress.getUserAgent()); + + String url = request.getUrl(); + + // Ensure that an HTTPS request is made to wpcom when Authorization is set. + if (additionalHeaders.containsKey("Authorization") || hasAuthorizationHeader(request)) { + url = UrlUtils.makeHttps(url); + } + + HashMap<String, String> map = new HashMap<String, String>(); + map.putAll(request.getHeaders()); + map.putAll(additionalHeaders); + + URL parsedUrl = new URL(url); + HttpURLConnection connection = openConnection(parsedUrl, request); + for (String headerName : map.keySet()) { + connection.addRequestProperty(headerName, map.get(headerName)); + } + setConnectionParametersForRequest(connection, request); + // Initialize HttpResponse with data from the HttpURLConnection. + ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); + int responseCode = connection.getResponseCode(); + if (responseCode == -1) { + // -1 is returned by getResponseCode() if the response code could not be retrieved. + // Signal to the caller that something was wrong with the connection. + throw new IOException("Could not retrieve response code from HttpUrlConnection."); + } + StatusLine responseStatus = new BasicStatusLine(protocolVersion, + connection.getResponseCode(), connection.getResponseMessage()); + BasicHttpResponse response = new BasicHttpResponse(responseStatus); + response.setEntity(entityFromConnection(connection)); + for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) { + if (header.getKey() != null) { + Header h = new BasicHeader(header.getKey(), header.getValue().get(0)); + response.addHeader(h); + } + } + return response; + } + + /** + * Initializes an {@link HttpEntity} from the given {@link HttpURLConnection}. + * @param connection + * @return an HttpEntity populated with data from <code>connection</code>. + */ + private static HttpEntity entityFromConnection(HttpURLConnection connection) { + BasicHttpEntity entity = new BasicHttpEntity(); + InputStream inputStream; + try { + inputStream = connection.getInputStream(); + } catch (IOException ioe) { + inputStream = connection.getErrorStream(); + } + entity.setContent(inputStream); + entity.setContentLength(connection.getContentLength()); + entity.setContentEncoding(connection.getContentEncoding()); + entity.setContentType(connection.getContentType()); + return entity; + } + + /** + * Create an {@link HttpURLConnection} for the specified {@code url}. + */ + protected HttpURLConnection createConnection(URL url) throws IOException { + // Check that the custom SslSocketFactory is not null on HTTPS connections + if (UrlUtils.isHttps(url) && !WPUrlUtils.isWordPressCom(url) + && !WPUrlUtils.isGravatar(url)) { + // WordPress.com doesn't need the custom mSslSocketFactory + synchronized (monitor) { + while (mSslSocketFactory == null) { + try { + monitor.wait(500); + } catch (InterruptedException e) { + // we can't do much here. + } + } + } + } + + return (HttpURLConnection) url.openConnection(); + } + + /** + * Opens an {@link HttpURLConnection} with parameters. + * @param url + * @return an open connection + * @throws IOException + */ + private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException { + HttpURLConnection connection = createConnection(url); + + int timeoutMs = request.getTimeoutMs(); + connection.setConnectTimeout(timeoutMs); + connection.setReadTimeout(timeoutMs); + connection.setUseCaches(false); + connection.setDoInput(true); + + // use caller-provided custom SslSocketFactory, if any, for HTTPS + if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) { + ((HttpsURLConnection) connection).setSSLSocketFactory(mSslSocketFactory); + } + + return connection; + } + + @SuppressWarnings("deprecation") + /* package */ static void setConnectionParametersForRequest(HttpURLConnection connection, + Request<?> request) throws IOException, AuthFailureError { + switch (request.getMethod()) { + case Method.DEPRECATED_GET_OR_POST: + // This is the deprecated way that needs to be handled for backwards compatibility. + // If the request's post body is null, then the assumption is that the request is + // GET. Otherwise, it is assumed that the request is a POST. + byte[] postBody = request.getPostBody(); + if (postBody != null) { + // Prepare output. There is no need to set Content-Length explicitly, + // since this is handled by HttpURLConnection using the size of the prepared + // output stream. + connection.setDoOutput(true); + connection.setRequestMethod("POST"); + connection.addRequestProperty(HEADER_CONTENT_TYPE, + request.getPostBodyContentType()); + DataOutputStream out = new DataOutputStream(connection.getOutputStream()); + out.write(postBody); + out.close(); + } + break; + case Method.GET: + // Not necessary to set the request method because connection defaults to GET but + // being explicit here. + connection.setRequestMethod("GET"); + break; + case Method.DELETE: + connection.setRequestMethod("DELETE"); + break; + case Method.POST: + connection.setRequestMethod("POST"); + addBodyIfExists(connection, request); + break; + case Method.PUT: + connection.setRequestMethod("PUT"); + addBodyIfExists(connection, request); + break; + default: + throw new IllegalStateException("Unknown method type."); + } + } + + private static void addBodyIfExists(HttpURLConnection connection, Request<?> request) + throws IOException, AuthFailureError { + byte[] body = request.getBody(); + if (body != null) { + connection.setDoOutput(true); + connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType()); + DataOutputStream out = new DataOutputStream(connection.getOutputStream()); + out.write(body); + out.close(); + } + } +} |