aboutsummaryrefslogtreecommitdiff
path: root/WordPress/src/main/java/org/wordpress/android/networking
diff options
context:
space:
mode:
authorChris Warrington <cmw@google.com>2016-10-18 12:29:21 +0100
committerChris Warrington <cmw@google.com>2016-10-18 12:34:18 +0100
commite3780081075c01aa1dff6d1f373cb43192b33e68 (patch)
treefb734615933a39f3d009210dc0d1457160479b35 /WordPress/src/main/java/org/wordpress/android/networking
parent7e05eb7e57827eddc885570bc00aed8a50320dbf (diff)
parent025b8b226c8d8edba2b309ca878572f40512eca7 (diff)
downloadgradle-perf-android-medium-e3780081075c01aa1dff6d1f373cb43192b33e68.tar.gz
Change-Id: I63f5e16d09297c48432192761b840310935eb903
Diffstat (limited to 'WordPress/src/main/java/org/wordpress/android/networking')
-rw-r--r--WordPress/src/main/java/org/wordpress/android/networking/ConnectionChangeReceiver.java70
-rw-r--r--WordPress/src/main/java/org/wordpress/android/networking/GravatarApi.java130
-rw-r--r--WordPress/src/main/java/org/wordpress/android/networking/OAuthAuthenticator.java35
-rw-r--r--WordPress/src/main/java/org/wordpress/android/networking/OAuthAuthenticatorFactory.java12
-rw-r--r--WordPress/src/main/java/org/wordpress/android/networking/OAuthAuthenticatorFactoryAbstract.java5
-rw-r--r--WordPress/src/main/java/org/wordpress/android/networking/OAuthAuthenticatorFactoryDefault.java7
-rw-r--r--WordPress/src/main/java/org/wordpress/android/networking/SSLCertsViewActivity.java42
-rw-r--r--WordPress/src/main/java/org/wordpress/android/networking/SelfSignedSSLCertsManager.java267
-rw-r--r--WordPress/src/main/java/org/wordpress/android/networking/StreamingRequest.java41
-rw-r--r--WordPress/src/main/java/org/wordpress/android/networking/WPDelayedHurlStack.java287
-rw-r--r--WordPress/src/main/java/org/wordpress/android/networking/WPTrustManager.java119
11 files changed, 1015 insertions, 0 deletions
diff --git a/WordPress/src/main/java/org/wordpress/android/networking/ConnectionChangeReceiver.java b/WordPress/src/main/java/org/wordpress/android/networking/ConnectionChangeReceiver.java
new file mode 100644
index 000000000..238f10fc6
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/networking/ConnectionChangeReceiver.java
@@ -0,0 +1,70 @@
+package org.wordpress.android.networking;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+
+import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.AppLog.T;
+import org.wordpress.android.util.NetworkUtils;
+
+import de.greenrobot.event.EventBus;
+
+/*
+ * global network connection change receiver - declared in the manifest to monitor
+ * android.net.conn.CONNECTIVITY_CHANGE
+ */
+public class ConnectionChangeReceiver extends BroadcastReceiver {
+ private static boolean mIsFirstReceive = true;
+ private static boolean mWasConnected = true;
+ private static boolean mIsEnabled = false; // this value must be synchronized with the ConnectionChangeReceiver
+ // state in our AndroidManifest
+
+ public static class ConnectionChangeEvent {
+ private final boolean mIsConnected;
+ public ConnectionChangeEvent(boolean isConnected) {
+ mIsConnected = isConnected;
+ }
+ public boolean isConnected() {
+ return mIsConnected;
+ }
+ }
+
+ /*
+ * note that onReceive occurs when anything about the connection has changed, not just
+ * when the connection has been lost or restated, so it can happen quite often when the
+ * user is on the move. for this reason we only fire the event the first time onReceive
+ * is called, and afterwards only when we know connection availability has changed
+ */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ boolean isConnected = NetworkUtils.isNetworkAvailable(context);
+ if (mIsFirstReceive || isConnected != mWasConnected) {
+ postConnectionChangeEvent(isConnected);
+ }
+ }
+
+ private static void postConnectionChangeEvent(boolean isConnected) {
+ AppLog.i(T.UTILS, "Connection status changed, isConnected=" + isConnected);
+ mWasConnected = isConnected;
+ mIsFirstReceive = false;
+ EventBus.getDefault().post(new ConnectionChangeEvent(isConnected));
+ }
+
+ public static void setEnabled(Context context, boolean enabled) {
+ if (mIsEnabled == enabled) {
+ return;
+ }
+ mIsEnabled = enabled;
+ AppLog.i(T.UTILS, "ConnectionChangeReceiver.setEnabled " + enabled);
+ int flag = (enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+ ComponentName component = new ComponentName(context, ConnectionChangeReceiver.class);
+ context.getPackageManager().setComponentEnabledSetting(component, flag, PackageManager.DONT_KILL_APP);
+ if (mIsEnabled) {
+ postConnectionChangeEvent(NetworkUtils.isNetworkAvailable(context));
+ }
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/networking/GravatarApi.java b/WordPress/src/main/java/org/wordpress/android/networking/GravatarApi.java
new file mode 100644
index 000000000..0ed52a0a8
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/networking/GravatarApi.java
@@ -0,0 +1,130 @@
+package org.wordpress.android.networking;
+
+import org.wordpress.android.analytics.AnalyticsTracker;
+import org.wordpress.android.models.AccountHelper;
+import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.CrashlyticsUtils;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.Interceptor;
+import okhttp3.MultipartBody;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+
+public class GravatarApi {
+ public static final String API_BASE_URL = "https://api.gravatar.com/v1/";
+
+ public interface GravatarUploadListener {
+ void onSuccess();
+ void onError();
+ }
+
+ private static OkHttpClient createClient(final String restEndpointUrl) {
+ OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
+
+ //// uncomment the following line to add logcat logging
+ //httpClientBuilder.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));
+
+ // add oAuth token usage
+ httpClientBuilder.addInterceptor(new Interceptor() {
+ @Override
+ public Response intercept(Interceptor.Chain chain) throws IOException {
+ Request original = chain.request();
+
+ String siteId = AuthenticatorRequest.extractSiteIdFromUrl(restEndpointUrl, original.url()
+ .toString());
+ String token = OAuthAuthenticator.getAccessToken(siteId);
+
+ Request.Builder requestBuilder = original.newBuilder()
+ .header("Authorization", "Bearer " + token)
+ .method(original.method(), original.body());
+
+ Request request = requestBuilder.build();
+ return chain.proceed(request);
+ }
+ });
+
+ return httpClientBuilder.build();
+ }
+
+ public static Request prepareGravatarUpload(String email, File file) {
+ return new Request.Builder()
+ .url(API_BASE_URL + "upload-image")
+ .post(new MultipartBody.Builder()
+ .setType(MultipartBody.FORM)
+ .addFormDataPart("account", email)
+ .addFormDataPart("filedata", file.getName(), new StreamingRequest(file))
+ .build())
+ .build();
+ }
+
+ public static void uploadGravatar(final File file, final GravatarUploadListener gravatarUploadListener) {
+ Request request = prepareGravatarUpload(AccountHelper.getDefaultAccount().getEmail(), file);
+
+ createClient(API_BASE_URL).newCall(request).enqueue(
+ new Callback() {
+ @Override
+ public void onResponse(Call call, final Response response) throws IOException {
+ if (!response.isSuccessful()) {
+ Map<String, Object> properties = new HashMap<>();
+ properties.put("network_response_code", response.code());
+
+ // response's body can only be read once so, keep it in a local variable
+ String responseBody;
+
+ try {
+ responseBody = response.body().string();
+ } catch (IOException e) {
+ responseBody = "null";
+ }
+ properties.put("network_response_body", responseBody);
+
+ AnalyticsTracker.track(AnalyticsTracker.Stat.ME_GRAVATAR_UPLOAD_UNSUCCESSFUL,
+ properties);
+ AppLog.w(AppLog.T.API, "Network call unsuccessful trying to upload Gravatar: " +
+ responseBody);
+ }
+
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ if (response.isSuccessful()) {
+ gravatarUploadListener.onSuccess();
+ } else {
+ gravatarUploadListener.onError();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onFailure(okhttp3.Call call, final IOException e) {
+ Map<String, Object> properties = new HashMap<>();
+ properties.put("network_exception_class", e != null ? e.getClass().getCanonicalName() : "null");
+ properties.put("network_exception_message", e != null ? e.getMessage() : "null");
+ AnalyticsTracker.track(AnalyticsTracker.Stat.ME_GRAVATAR_UPLOAD_EXCEPTION, properties);
+ CrashlyticsUtils.logException(e, CrashlyticsUtils.ExceptionType.SPECIFIC,
+ AppLog.T.API, "Network call failure trying to upload Gravatar!");
+ AppLog.w(AppLog.T.API, "Network call failure trying to upload Gravatar!" + (e != null ?
+ e.getMessage() : "null"));
+
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ gravatarUploadListener.onError();
+ }
+ });
+ }
+ });
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/networking/OAuthAuthenticator.java b/WordPress/src/main/java/org/wordpress/android/networking/OAuthAuthenticator.java
new file mode 100644
index 000000000..326ca9064
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/networking/OAuthAuthenticator.java
@@ -0,0 +1,35 @@
+package org.wordpress.android.networking;
+
+import org.wordpress.android.WordPress;
+import org.wordpress.android.models.Blog;
+import org.wordpress.android.models.AccountHelper;
+import org.wordpress.android.util.StringUtils;
+
+public class OAuthAuthenticator implements Authenticator {
+ public static String getAccessToken(final String siteId) {
+ String token = AccountHelper.getDefaultAccount().getAccessToken();
+
+ if (siteId != null) {
+ // Get the token for a Jetpack site if needed
+ Blog blog = WordPress.wpDB.getBlogForDotComBlogId(siteId);
+
+ if (blog != null) {
+ String jetpackToken = blog.getApi_key();
+
+ // valid OAuth tokens are 64 chars
+ if (jetpackToken != null && jetpackToken.length() == 64 && !blog.isDotcomFlag()) {
+ token = jetpackToken;
+ }
+ }
+ }
+
+ return token;
+ }
+
+ @Override
+ public void authenticate(final AuthenticatorRequest request) {
+ String siteId = request.getSiteId();
+ String token = getAccessToken(siteId);
+ request.sendWithAccessToken(StringUtils.notNullStr(token));
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/networking/OAuthAuthenticatorFactory.java b/WordPress/src/main/java/org/wordpress/android/networking/OAuthAuthenticatorFactory.java
new file mode 100644
index 000000000..5dc68edb6
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/networking/OAuthAuthenticatorFactory.java
@@ -0,0 +1,12 @@
+package org.wordpress.android.networking;
+
+public class OAuthAuthenticatorFactory {
+ private static OAuthAuthenticatorFactoryAbstract sFactory;
+
+ public static OAuthAuthenticator instantiate() {
+ if (sFactory == null) {
+ sFactory = new OAuthAuthenticatorFactoryDefault();
+ }
+ return sFactory.make();
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/networking/OAuthAuthenticatorFactoryAbstract.java b/WordPress/src/main/java/org/wordpress/android/networking/OAuthAuthenticatorFactoryAbstract.java
new file mode 100644
index 000000000..85cff768c
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/networking/OAuthAuthenticatorFactoryAbstract.java
@@ -0,0 +1,5 @@
+package org.wordpress.android.networking;
+
+public interface OAuthAuthenticatorFactoryAbstract {
+ public OAuthAuthenticator make();
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/networking/OAuthAuthenticatorFactoryDefault.java b/WordPress/src/main/java/org/wordpress/android/networking/OAuthAuthenticatorFactoryDefault.java
new file mode 100644
index 000000000..4687d1dca
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/networking/OAuthAuthenticatorFactoryDefault.java
@@ -0,0 +1,7 @@
+package org.wordpress.android.networking;
+
+public class OAuthAuthenticatorFactoryDefault implements OAuthAuthenticatorFactoryAbstract {
+ public OAuthAuthenticator make() {
+ return new OAuthAuthenticator();
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/networking/SSLCertsViewActivity.java b/WordPress/src/main/java/org/wordpress/android/networking/SSLCertsViewActivity.java
new file mode 100644
index 000000000..c6914b889
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/networking/SSLCertsViewActivity.java
@@ -0,0 +1,42 @@
+package org.wordpress.android.networking;
+
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+
+import org.wordpress.android.R;
+import org.wordpress.android.ui.WebViewActivity;
+
+/**
+ * Display details of a SSL cert
+ */
+public class SSLCertsViewActivity extends WebViewActivity {
+ public static final String CERT_DETAILS_KEYS = "CertDetails";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setTitle(getResources().getText(R.string.ssl_certificate_details));
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(false);
+ }
+ }
+
+ @Override
+ protected void loadContent() {
+ Bundle extras = getIntent().getExtras();
+ if (extras != null && extras.containsKey(CERT_DETAILS_KEYS)) {
+ String certDetails = extras.getString(CERT_DETAILS_KEYS);
+ StringBuilder sb = new StringBuilder("<html><body>");
+ sb.append(certDetails);
+ sb.append("</body></html>");
+ mWebView.loadDataWithBaseURL(null, sb.toString(), "text/html", "utf-8", null);
+ }
+ }
+
+ @Override
+ protected void configureWebView() {
+ mWebView.getSettings().setDefaultTextEncodingName("utf-8");
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/networking/SelfSignedSSLCertsManager.java b/WordPress/src/main/java/org/wordpress/android/networking/SelfSignedSSLCertsManager.java
new file mode 100644
index 000000000..172530d51
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/networking/SelfSignedSSLCertsManager.java
@@ -0,0 +1,267 @@
+package org.wordpress.android.networking;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.net.http.SslCertificate;
+import android.os.Bundle;
+
+import org.wordpress.android.BuildConfig;
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.ui.ActivityLauncher;
+import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.AppLog.T;
+import org.wordpress.android.util.GenericCallback;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+
+import javax.security.auth.x500.X500Principal;
+
+public class SelfSignedSSLCertsManager {
+ private static SelfSignedSSLCertsManager sInstance;
+ private File mLocalTrustStoreFile;
+ private KeyStore mLocalKeyStore;
+ // Used to hold the last self-signed certificate chain that doesn't pass trusting
+ private X509Certificate[] mLastFailureChain;
+
+ private SelfSignedSSLCertsManager(Context ctx) throws IOException, GeneralSecurityException {
+ mLocalTrustStoreFile = new File(ctx.getFilesDir(), "self_signed_certs_truststore.bks");
+ createLocalKeyStoreFile();
+ mLocalKeyStore = loadTrustStore(ctx);
+ }
+
+ public static void askForSslTrust(final Context ctx, final GenericCallback<Void> certificateTrusted) {
+ AlertDialog.Builder alert = new AlertDialog.Builder(ctx);
+ alert.setTitle(ctx.getString(R.string.ssl_certificate_error));
+ alert.setMessage(ctx.getString(R.string.ssl_certificate_ask_trust));
+ alert.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ SelfSignedSSLCertsManager selfSignedSSLCertsManager;
+ try {
+ selfSignedSSLCertsManager = SelfSignedSSLCertsManager.getInstance(ctx);
+ X509Certificate[] certificates = selfSignedSSLCertsManager.getLastFailureChain();
+ AppLog.i(T.NUX, "Add the following certificate to our Certificate Manager: " +
+ Arrays.toString(certificates));
+ selfSignedSSLCertsManager.addCertificates(certificates);
+ } catch (GeneralSecurityException e) {
+ AppLog.e(T.API, e);
+ } catch (IOException e) {
+ AppLog.e(T.API, e);
+ }
+ if (certificateTrusted != null) {
+ certificateTrusted.callback(null);
+ }
+ }
+ }
+ );
+ alert.setNeutralButton(R.string.ssl_certificate_details, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ ActivityLauncher.viewSSLCerts(ctx);
+ }
+ });
+ alert.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ });
+ alert.show();
+ }
+
+ public static synchronized SelfSignedSSLCertsManager getInstance(Context ctx)
+ throws IOException, GeneralSecurityException {
+ if (sInstance == null) {
+ sInstance = new SelfSignedSSLCertsManager(ctx);
+ }
+ return sInstance;
+ }
+
+ public void addCertificates(X509Certificate[] certs) throws IOException, GeneralSecurityException {
+ if (certs == null || certs.length == 0) {
+ return;
+ }
+
+ for (X509Certificate cert : certs) {
+ String alias = hashName(cert.getSubjectX500Principal());
+ mLocalKeyStore.setCertificateEntry(alias, cert);
+ }
+ saveTrustStore();
+ // reset the Volley queue Otherwise new certs are not used
+ WordPress.setupVolleyQueue();
+ }
+
+ public void addCertificate(X509Certificate cert) throws IOException, GeneralSecurityException {
+ if (cert == null) {
+ return;
+ }
+
+ String alias = hashName(cert.getSubjectX500Principal());
+ mLocalKeyStore.setCertificateEntry(alias, cert);
+ saveTrustStore();
+ }
+
+ public KeyStore getLocalKeyStore() {
+ return mLocalKeyStore;
+ }
+
+ private KeyStore loadTrustStore(Context ctx) throws IOException, GeneralSecurityException {
+ KeyStore localTrustStore = KeyStore.getInstance("BKS");
+ InputStream in = new FileInputStream(mLocalTrustStoreFile);
+ try {
+ localTrustStore.load(in, BuildConfig.DB_SECRET.toCharArray());
+ } finally {
+ in.close();
+ }
+ return localTrustStore;
+ }
+
+ private void saveTrustStore() throws IOException, GeneralSecurityException {
+ FileOutputStream out = null;
+ try {
+ out = new FileOutputStream(mLocalTrustStoreFile);
+ mLocalKeyStore.store(out, BuildConfig.DB_SECRET.toCharArray());
+ } finally {
+ if (out!=null){
+ try {
+ out.close();
+ } catch (IOException e) {
+ AppLog.e(T.UTILS, e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Create an empty trust store file if missing
+ */
+ private void createLocalKeyStoreFile() throws GeneralSecurityException, IOException {
+ if (!mLocalTrustStoreFile.exists()) {
+ FileOutputStream out = null;
+ try {
+ out = new FileOutputStream(mLocalTrustStoreFile);
+ KeyStore localTrustStore = KeyStore.getInstance("BKS");
+ localTrustStore.load(null, BuildConfig.DB_SECRET.toCharArray());
+ localTrustStore.store(out, BuildConfig.DB_SECRET.toCharArray());
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ AppLog.e(T.UTILS, e);
+ }
+ }
+ }
+ }
+ }
+
+ public void emptyLocalKeyStoreFile() {
+ if (mLocalTrustStoreFile.exists()) {
+ mLocalTrustStoreFile.delete();
+ }
+ try {
+ createLocalKeyStoreFile();
+ } catch (GeneralSecurityException e) {
+ AppLog.e(T.API, "Cannot create/initialize local Keystore", e);
+ } catch (IOException e) {
+ AppLog.e(T.API, "Cannot create/initialize local Keystore", e);
+ }
+ }
+
+ private static String hashName(X500Principal principal) {
+ try {
+ byte[] digest = MessageDigest.getInstance("MD5").digest(principal.getEncoded());
+ String result = Integer.toString(leInt(digest), 16);
+ if (result.length() > 8) {
+ StringBuilder buff = new StringBuilder();
+ int padding = 8 - result.length();
+ for (int i = 0; i < padding; i++) {
+ buff.append("0");
+ }
+ buff.append(result);
+
+ return buff.toString();
+ }
+
+ return result;
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ private static int leInt(byte[] bytes) {
+ int offset = 0;
+ return ((bytes[offset++] & 0xff) << 0)
+ | ((bytes[offset++] & 0xff) << 8)
+ | ((bytes[offset++] & 0xff) << 16)
+ | ((bytes[offset] & 0xff) << 24);
+ }
+
+ public X509Certificate[] getLastFailureChain() {
+ return mLastFailureChain;
+ }
+
+ public void setLastFailureChain(X509Certificate[] lastFaiulreChain) {
+ mLastFailureChain = lastFaiulreChain;
+ }
+
+ public String getLastFailureChainDescription() {
+ return (mLastFailureChain == null || mLastFailureChain.length == 0) ? "" : mLastFailureChain[0].toString();
+ }
+
+ public boolean isCertificateTrusted(SslCertificate cert){
+ if (cert==null)
+ return false;
+
+ Bundle bundle = SslCertificate.saveState(cert);
+ X509Certificate x509Certificate;
+ byte[] bytes = bundle.getByteArray("x509-certificate");
+ if (bytes == null) {
+ AppLog.e(T.API, "Cannot load the SSLCertificate bytes from the bundle!");
+ x509Certificate = null;
+ } else {
+ try {
+ CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ Certificate certX509 = certFactory.generateCertificate(new ByteArrayInputStream(bytes));
+ x509Certificate = (X509Certificate) certX509;
+ } catch (CertificateException e) {
+ AppLog.e(T.API, "Cannot generate the X509Certificate with the bytes provided", e);
+ x509Certificate = null;
+ }
+ }
+
+ return isCertificateTrusted(x509Certificate);
+ }
+
+ public boolean isCertificateTrusted(X509Certificate x509Certificate){
+ if (x509Certificate==null)
+ return false;
+
+ // Now I have an X509Certificate I can pass to an X509TrustManager for validation.
+ try {
+ String certificateAlias = this.getLocalKeyStore().getCertificateAlias(x509Certificate);
+ if(certificateAlias != null ) {
+ AppLog.w(T.API, "Current certificate " + x509Certificate.getSubjectDN().getName() +" is in KeyStore.");
+ return true;
+ }
+ } catch (KeyStoreException e) {
+ AppLog.e(T.API, "Cannot check if the certificate is in KeyStore. Seems that Keystore is not initialized.", e);
+ }
+
+ AppLog.w(T.API, "Current certificate " + x509Certificate.getSubjectDN().getName() +" is NOT in KeyStore.");
+ return false;
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/networking/StreamingRequest.java b/WordPress/src/main/java/org/wordpress/android/networking/StreamingRequest.java
new file mode 100644
index 000000000..60c6880fe
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/networking/StreamingRequest.java
@@ -0,0 +1,41 @@
+package org.wordpress.android.networking;
+
+import java.io.File;
+import java.io.IOException;
+
+import okhttp3.MediaType;
+import okhttp3.RequestBody;
+import okhttp3.internal.Util;
+import okio.BufferedSink;
+import okio.Okio;
+import okio.Source;
+
+public class StreamingRequest extends RequestBody {
+ public static final int CHUNK_SIZE = 2048;
+
+ private final File mFile;
+
+ public StreamingRequest(File file) {
+ mFile = file;
+ }
+
+ @Override
+ public MediaType contentType() {
+ return MediaType.parse("multipart/form-data");
+ }
+
+ @Override
+ public void writeTo(BufferedSink sink) throws IOException {
+ Source source = null;
+ try {
+ source = Okio.source(mFile);
+
+ while (source.read(sink.buffer(), CHUNK_SIZE) != -1) {
+ sink.flush();
+ }
+ } finally {
+ Util.closeQuietly(source);
+ }
+ }
+};
+
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();
+ }
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/networking/WPTrustManager.java b/WordPress/src/main/java/org/wordpress/android/networking/WPTrustManager.java
new file mode 100644
index 000000000..8f7bb9104
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/networking/WPTrustManager.java
@@ -0,0 +1,119 @@
+package org.wordpress.android.networking;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.AppLog.T;
+
+public class WPTrustManager implements X509TrustManager {
+ private X509TrustManager defaultTrustManager;
+ private X509TrustManager localTrustManager;
+ private X509Certificate[] acceptedIssuers;
+
+ public WPTrustManager(KeyStore localKeyStore) {
+ try {
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init((KeyStore) null);
+
+ defaultTrustManager = findX509TrustManager(tmf);
+ if (defaultTrustManager == null) {
+ throw new IllegalStateException("Couldn't find X509TrustManager");
+ }
+
+ localTrustManager = new LocalStoreX509TrustManager(localKeyStore);
+
+ List<X509Certificate> allIssuers = new ArrayList<X509Certificate>();
+ Collections.addAll(allIssuers, defaultTrustManager.getAcceptedIssuers());
+ Collections.addAll(allIssuers, localTrustManager.getAcceptedIssuers());
+ acceptedIssuers = allIssuers.toArray(new X509Certificate[allIssuers.size()]);
+ } catch (GeneralSecurityException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ private static X509TrustManager findX509TrustManager(TrustManagerFactory tmf) {
+ TrustManager tms[] = tmf.getTrustManagers();
+ for (int i = 0; i < tms.length; i++) {
+ if (tms[i] instanceof X509TrustManager) {
+ return (X509TrustManager) tms[i];
+ }
+ }
+ return null;
+ }
+
+
+ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ try {
+ defaultTrustManager.checkClientTrusted(chain, authType);
+ } catch (CertificateException ce) {
+ localTrustManager.checkClientTrusted(chain, authType);
+ }
+ }
+
+ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ try {
+ defaultTrustManager.checkServerTrusted(chain, authType);
+ } catch (CertificateException ce) {
+ localTrustManager.checkServerTrusted(chain, authType);
+ }
+ }
+
+ public X509Certificate[] getAcceptedIssuers() {
+ return acceptedIssuers;
+ }
+
+ static class LocalStoreX509TrustManager implements X509TrustManager {
+ private X509TrustManager trustManager;
+
+ LocalStoreX509TrustManager(KeyStore localKeyStore) {
+ try {
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init(localKeyStore);
+
+ trustManager = findX509TrustManager(tmf);
+ if (trustManager == null) {
+ throw new IllegalStateException("Couldn't find X509TrustManager");
+ }
+ } catch (GeneralSecurityException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ trustManager.checkClientTrusted(chain, authType);
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ try {
+ trustManager.checkServerTrusted(chain, authType);
+ } catch (CertificateException e) {
+ AppLog.e(T.API, "Cannot trust the certificate with the local trust manager...", e);
+ try {
+ SelfSignedSSLCertsManager.getInstance(null).setLastFailureChain(chain);
+ } catch (GeneralSecurityException e1) {
+ } catch (IOException e1) {
+ }
+ throw e;
+ }
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return trustManager.getAcceptedIssuers();
+ }
+ }
+} \ No newline at end of file