Skip to content
This repository has been archived by the owner on Feb 9, 2018. It is now read-only.

Commit

Permalink
Handle blogs under subfolders + comprehensive unit tests for various …
Browse files Browse the repository at this point in the history
…kinds of

blog URLs.

Fixes #161.
  • Loading branch information
vickychijwani committed Feb 8, 2017
1 parent e2fd415 commit 4a33dc0
Show file tree
Hide file tree
Showing 10 changed files with 391 additions and 122 deletions.
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,5 @@ dependencies {
testCompile "junit:junit:4.12"
testCompile "org.hamcrest:hamcrest-library:1.3"
testCompile "org.json:json:20140107" // don't depend on Android's JSONObject: http://stackoverflow.com/a/30759769/504611
testCompile "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okhttpVersion"
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
package me.vickychijwani.spectre;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.support.annotation.NonNull;

import com.squareup.leakcanary.LeakCanary;

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;

import io.palaima.debugdrawer.DebugDrawer;
import io.palaima.debugdrawer.commons.BuildModule;
Expand All @@ -21,6 +14,7 @@
import io.palaima.debugdrawer.okhttp3.OkHttp3Module;
import io.palaima.debugdrawer.picasso.PicassoModule;
import io.palaima.debugdrawer.scalpel.ScalpelModule;
import me.vickychijwani.spectre.network.UnsafeHttpClientFactory;

public class DebugSpectreApplication extends SpectreApplication {

Expand All @@ -43,13 +37,8 @@ protected void initOkHttpClient() {
if (mOkHttpClient != null) {
return;
}
super.initOkHttpClient();

mOkHttpClient = mOkHttpClient.newBuilder()
// trust all SSL certs, for TESTING ONLY!
.hostnameVerifier((hostname, session) -> true)
.sslSocketFactory(getUnsafeSslSocketFactory(), TrustEveryoneManager.getInstance())
.build();
File cacheDir = createCacheDir(this);
mOkHttpClient = new UnsafeHttpClientFactory().create(cacheDir);
}

@Override
Expand All @@ -64,48 +53,4 @@ public void addDebugDrawer(@NonNull Activity activity) {
).build();
}

private SSLSocketFactory getUnsafeSslSocketFactory() {
try {
// Create a trust manager that does not validate certificate chains
final TrustManager[] trustAllCerts = new TrustManager[] { TrustEveryoneManager.getInstance() };
// Install the all-trusting trust manager
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
// Create an ssl socket factory with our all-trusting manager
return sslContext.getSocketFactory();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

// private stuff
private static class TrustEveryoneManager implements X509TrustManager {

private static TrustEveryoneManager sInstance = null;

public static TrustEveryoneManager getInstance() {
if (sInstance == null) {
sInstance = new TrustEveryoneManager();
}
return sInstance;
}

private TrustEveryoneManager() {}

@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType)
throws CertificateException {}

@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType)
throws CertificateException {}

@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package me.vickychijwani.spectre.network;

import android.annotation.SuppressLint;
import android.support.annotation.Nullable;

import java.io.File;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import okhttp3.OkHttpClient;

public class UnsafeHttpClientFactory extends ProductionHttpClientFactory {

private final X509TrustManager mGullibleTrustManager;

public UnsafeHttpClientFactory() {
mGullibleTrustManager = new GullibleX509TrustManager();
}

@Override
public OkHttpClient create(@Nullable File cacheDir) {
return super.create(cacheDir).newBuilder()
// trust all SSL certs, for TESTING ONLY!
.hostnameVerifier((hostname, session) -> true)
.sslSocketFactory(getUnsafeSslSocketFactory(), mGullibleTrustManager)
.build();
}

private SSLSocketFactory getUnsafeSslSocketFactory() {
try {
// Create a trust manager that does not validate certificate chains
final TrustManager[] trustAllCerts = new TrustManager[] { mGullibleTrustManager };
// Install the all-trusting trust manager
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
// Create an ssl socket factory with our all-trusting manager
return sslContext.getSocketFactory();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

private static class GullibleX509TrustManager implements X509TrustManager {

@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType)
throws CertificateException {}

@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType)
throws CertificateException {}

@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}

}

}
59 changes: 15 additions & 44 deletions app/src/main/java/me/vickychijwani/spectre/SpectreApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.os.Build;
import android.os.StatFs;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

import com.crashlytics.android.Crashlytics;
Expand All @@ -17,7 +16,6 @@
import com.tsengvn.typekit.Typekit;

import java.io.File;
import java.util.concurrent.TimeUnit;

import io.fabric.sdk.android.Fabric;
import io.realm.Realm;
Expand All @@ -27,8 +25,8 @@
import me.vickychijwani.spectre.event.BusProvider;
import me.vickychijwani.spectre.model.DatabaseMigration;
import me.vickychijwani.spectre.network.NetworkService;
import me.vickychijwani.spectre.network.ProductionHttpClientFactory;
import me.vickychijwani.spectre.util.NetworkUtils;
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import retrofit.RetrofitError;

Expand All @@ -37,10 +35,8 @@ public class SpectreApplication extends Application {
private static final String TAG = "SpectreApplication";
private static SpectreApplication sInstance;

private static final String IMAGE_CACHE_PATH = "images";
private static final int MIN_DISK_CACHE_SIZE = 5 * 1024 * 1024; // in bytes
private static final int MAX_DISK_CACHE_SIZE = 50 * 1024 * 1024; // in bytes
private static final int CONNECTION_TIMEOUT = 10 * 1000; // in milliseconds
// this is named "images" but it actually caches all HTTP responses
private static final String HTTP_CACHE_PATH = "images";

protected OkHttpClient mOkHttpClient = null;
protected Picasso mPicasso = null;
Expand Down Expand Up @@ -93,15 +89,8 @@ protected void initOkHttpClient() {
if (mOkHttpClient != null) {
return;
}
File cacheDir = createCacheDir(this, IMAGE_CACHE_PATH);
long size = calculateDiskCacheSize(cacheDir);
Cache cache = new Cache(cacheDir, size);
mOkHttpClient = new OkHttpClient.Builder()
.cache(cache)
.connectTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
.readTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
.writeTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
.build();
File cacheDir = createCacheDir(this);
mOkHttpClient = new ProductionHttpClientFactory().create(cacheDir);
}

@SuppressWarnings("WeakerAccess")
Expand Down Expand Up @@ -130,36 +119,18 @@ public void addDebugDrawer(@NonNull Activity activity) {
// no-op, overridden in debug build
}

private static long calculateDiskCacheSize(File dir) {
long size = MIN_DISK_CACHE_SIZE;
try {
StatFs statFs = new StatFs(dir.getAbsolutePath());
long available;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
available = statFs.getBlockCountLong() * statFs.getBlockSizeLong();
} else {
// checked at runtime
//noinspection deprecation
available = statFs.getBlockCount() * statFs.getBlockSize();
}
// Target 2% of the total space.
size = available / 50;
} catch (IllegalArgumentException ignored) {
}
// Bound inside min/max size for disk cache.
return Math.max(Math.min(size, MAX_DISK_CACHE_SIZE), MIN_DISK_CACHE_SIZE);
}

private static File createCacheDir(Context context, String path) {
@Nullable protected static File createCacheDir(Context context) {
File cacheDir = context.getApplicationContext().getExternalCacheDir();
if (cacheDir == null)
if (cacheDir == null) {
cacheDir = context.getApplicationContext().getCacheDir();
File cache = new File(cacheDir, path);
if (!cache.exists()) {
//noinspection ResultOfMethodCallIgnored
cache.mkdirs();
}
return cache;

File cache = new File(cacheDir, HTTP_CACHE_PATH);
if (cache.exists() || cache.mkdirs()) {
return cache;
} else {
return null;
}
}

@Subscribe
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package me.vickychijwani.spectre.network;

import android.support.annotation.Nullable;

import java.io.File;

import okhttp3.OkHttpClient;

public interface HttpClientFactory {

OkHttpClient create(@Nullable File cacheDir);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package me.vickychijwani.spectre.network;

import android.os.Build;
import android.os.StatFs;
import android.support.annotation.Nullable;

import java.io.File;
import java.util.concurrent.TimeUnit;

import okhttp3.Cache;
import okhttp3.OkHttpClient;

public class ProductionHttpClientFactory implements HttpClientFactory {

private static final int MIN_DISK_CACHE_SIZE = 5 * 1024 * 1024; // in bytes
private static final int MAX_DISK_CACHE_SIZE = 50 * 1024 * 1024; // in bytes
private static final int CONNECTION_TIMEOUT = 10 * 1000; // in milliseconds

/**
*
* @param cacheDir - directory for the HTTP cache, disabled if null
* @return an HTTP client intended for production use
*/
@Override
public OkHttpClient create(@Nullable File cacheDir) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (cacheDir != null) {
long size = calculateDiskCacheSize(cacheDir);
builder.cache(new Cache(cacheDir, size));
}
return builder.connectTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
.readTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
.writeTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
.build();
}

private static long calculateDiskCacheSize(File dir) {
long size = MIN_DISK_CACHE_SIZE;
try {
StatFs statFs = new StatFs(dir.getAbsolutePath());
long available;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
available = statFs.getBlockCountLong() * statFs.getBlockSizeLong();
} else {
// checked at runtime
//noinspection deprecation
available = statFs.getBlockCount() * statFs.getBlockSize();
}
// Target 2% of the total space.
size = available / 50;
} catch (IllegalArgumentException ignored) {
}
// Bound inside min/max size for disk cache.
return Math.max(Math.min(size, MAX_DISK_CACHE_SIZE), MIN_DISK_CACHE_SIZE);
}

}
Loading

0 comments on commit 4a33dc0

Please sign in to comment.