[CB-3384] Make UriResolver assert that IO is not on the UI nor WebCore threads.

This commit is contained in:
Andrew Grieve 2013-07-05 11:44:38 -04:00
parent bf6291020a
commit 99341bce29
4 changed files with 105 additions and 85 deletions

View File

@ -959,22 +959,22 @@ public class CordovaWebView extends WebView {
if (!uri.isAbsolute()) { if (!uri.isAbsolute()) {
throw new IllegalArgumentException("Relative URIs are not yet supported by resolveUri."); throw new IllegalArgumentException("Relative URIs are not yet supported by resolveUri.");
} }
UriResolver ret = null;
// Check the against the white-list before delegating to plugins. // Check the against the white-list before delegating to plugins.
if (("http".equals(uri.getScheme()) || "https".equals(uri.getScheme())) && !Config.isUrlWhiteListed(uri.toString())) if (("http".equals(uri.getScheme()) || "https".equals(uri.getScheme())) && !Config.isUrlWhiteListed(uri.toString()))
{ {
LOG.w(TAG, "resolveUri - URL is not in whitelist: " + uri); LOG.w(TAG, "resolveUri - URL is not in whitelist: " + uri);
return new UriResolvers.ErrorUriResolver(uri, "Whitelist rejection"); ret = UriResolvers.createError("Whitelist rejection for: " + uri);
} } else {
// Give plugins a chance to handle the request. // Give plugins a chance to handle the request.
UriResolver resolver = ((org.apache.cordova.PluginManager)pluginManager).resolveUri(uri); ret = ((org.apache.cordova.PluginManager)pluginManager).resolveUri(uri);
if (resolver == null && !fromWebView) { }
resolver = UriResolvers.forUri(uri, cordova.getActivity()); if (ret == null && !fromWebView) {
if (resolver == null) { ret = UriResolvers.forUri(uri, cordova.getActivity());
resolver = new UriResolvers.ErrorUriResolver(uri, "Unresolvable URI"); if (ret == null) {
ret = UriResolvers.createError("Unresolvable URI: " + uri);
} }
} }
return ret == null ? null : UriResolvers.makeThreadChecking(ret);
return resolver;
} }
} }

View File

@ -44,6 +44,9 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
@Override @Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) { public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
// Disable checks during shouldInterceptRequest since there is no way to avoid IO here :(.
UriResolvers.webCoreThread = null;
try {
UriResolver uriResolver = appView.resolveUri(Uri.parse(url), true); UriResolver uriResolver = appView.resolveUri(Uri.parse(url), true);
if (uriResolver == null && url.startsWith("file:///android_asset/")) { if (uriResolver == null && url.startsWith("file:///android_asset/")) {
@ -65,6 +68,10 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
} }
} }
return null; return null;
} finally {
// Tell the Thread-Checking resolve what thread the WebCore thread is.
UriResolvers.webCoreThread = Thread.currentThread();
}
} }
private static boolean needsIceCreamSpecialsInAssetUrlFix(String url) { private static boolean needsIceCreamSpecialsInAssetUrlFix(String url) {

View File

@ -31,9 +31,6 @@ import android.net.Uri;
*/ */
public interface UriResolver { public interface UriResolver {
/** Returns the URI that this instance will resolve. */
Uri getUri();
/** /**
* Returns the InputStream for the resource. * Returns the InputStream for the resource.
* Throws an exception if it cannot be read. * Throws an exception if it cannot be read.

View File

@ -34,76 +34,64 @@ import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.net.Uri; import android.net.Uri;
import android.os.Looper;
/* /*
* UriResolver implementations. * UriResolver implementations.
*/ */
public final class UriResolvers { public final class UriResolvers {
static Thread webCoreThread;
private UriResolvers() {} private UriResolvers() {}
private static final class FileUriResolver implements UriResolver { private static final class FileUriResolver implements UriResolver {
private final Uri uri; private final File localFile;
private String mimeType; private String mimeType;
private File localFile;
FileUriResolver(Uri uri) { FileUriResolver(Uri uri) {
this.uri = uri; localFile = new File(uri.getPath());
}
public Uri getUri() {
return uri;
} }
public InputStream getInputStream() throws IOException { public InputStream getInputStream() throws IOException {
return new FileInputStream(getLocalFile()); return new FileInputStream(localFile);
} }
public OutputStream getOutputStream() throws FileNotFoundException { public OutputStream getOutputStream() throws FileNotFoundException {
return new FileOutputStream(getLocalFile()); return new FileOutputStream(localFile);
} }
public String getMimeType() { public String getMimeType() {
if (mimeType == null) { if (mimeType == null) {
mimeType = FileHelper.getMimeTypeForExtension(getLocalFile().getName()); mimeType = FileHelper.getMimeTypeForExtension(localFile.getName());
} }
return mimeType; return mimeType;
} }
public boolean isWritable() { public boolean isWritable() {
File f = getLocalFile(); if (localFile.isDirectory()) {
if (f.isDirectory()) {
return false; return false;
} }
if (f.exists()) { if (localFile.exists()) {
return f.canWrite(); return localFile.canWrite();
} }
return f.getParentFile().canWrite(); return localFile.getParentFile().canWrite();
} }
public File getLocalFile() { public File getLocalFile() {
if (localFile == null) {
localFile = new File(uri.getPath());
}
return localFile; return localFile;
} }
} }
private static final class AssetUriResolver implements UriResolver { private static final class AssetUriResolver implements UriResolver {
private final Uri uri;
private final AssetManager assetManager; private final AssetManager assetManager;
private final String assetPath; private final String assetPath;
private String mimeType; private String mimeType;
AssetUriResolver(Uri uri, AssetManager assetManager) { AssetUriResolver(Uri uri, AssetManager assetManager) {
this.uri = uri;
this.assetManager = assetManager; this.assetManager = assetManager;
this.assetPath = uri.getPath().substring(15); this.assetPath = uri.getPath().substring(15);
} }
public Uri getUri() {
return uri;
}
public InputStream getInputStream() throws IOException { public InputStream getInputStream() throws IOException {
return assetManager.open(assetPath); return assetManager.open(assetPath);
} }
@ -138,10 +126,6 @@ public final class UriResolvers {
this.contentResolver = contentResolver; this.contentResolver = contentResolver;
} }
public Uri getUri() {
return uri;
}
public InputStream getInputStream() throws IOException { public InputStream getInputStream() throws IOException {
return contentResolver.openInputStream(uri); return contentResolver.openInputStream(uri);
} }
@ -166,88 +150,108 @@ public final class UriResolvers {
} }
} }
static final class ErrorUriResolver implements UriResolver { private static final class ErrorUriResolver implements UriResolver {
final Uri uri;
final String errorMsg; final String errorMsg;
ErrorUriResolver(Uri uri, String errorMsg) { ErrorUriResolver(String errorMsg) {
this.uri = uri;
this.errorMsg = errorMsg; this.errorMsg = errorMsg;
} }
@Override
public boolean isWritable() { public boolean isWritable() {
return false; return false;
} }
@Override
public Uri getUri() {
return uri;
}
@Override
public File getLocalFile() { public File getLocalFile() {
return null; return null;
} }
@Override
public OutputStream getOutputStream() throws IOException { public OutputStream getOutputStream() throws IOException {
throw new FileNotFoundException(errorMsg); throw new FileNotFoundException(errorMsg);
} }
@Override
public String getMimeType() { public String getMimeType() {
return null; return null;
} }
@Override
public InputStream getInputStream() throws IOException { public InputStream getInputStream() throws IOException {
throw new FileNotFoundException(errorMsg); throw new FileNotFoundException(errorMsg);
} }
} }
private static final class ReadOnlyResolver implements UriResolver { private static final class ReadOnlyResolver implements UriResolver {
private Uri uri;
private InputStream inputStream; private InputStream inputStream;
private String mimeType; private String mimeType;
public ReadOnlyResolver(Uri uri, InputStream inputStream, String mimeType) { public ReadOnlyResolver(Uri uri, InputStream inputStream, String mimeType) {
this.uri = uri;
this.inputStream = inputStream; this.inputStream = inputStream;
this.mimeType = mimeType; this.mimeType = mimeType;
} }
@Override
public boolean isWritable() { public boolean isWritable() {
return false; return false;
} }
@Override
public Uri getUri() {
return uri;
}
@Override
public File getLocalFile() { public File getLocalFile() {
return null; return null;
} }
@Override
public OutputStream getOutputStream() throws IOException { public OutputStream getOutputStream() throws IOException {
throw new FileNotFoundException("URI is not writable"); throw new FileNotFoundException("URI is not writable");
} }
@Override
public String getMimeType() { public String getMimeType() {
return mimeType; return mimeType;
} }
@Override
public InputStream getInputStream() throws IOException { public InputStream getInputStream() throws IOException {
return inputStream; return inputStream;
} }
} }
private static final class ThreadCheckingResolver implements UriResolver {
final UriResolver delegate;
ThreadCheckingResolver(UriResolver delegate) {
this.delegate = delegate;
}
private static void checkThread() {
Thread curThread = Thread.currentThread();
if (curThread == Looper.getMainLooper().getThread()) {
throw new IllegalStateException("Do not perform IO operations on the UI thread. Use CordovaInterface.getThreadPool() instead.");
}
if (curThread == webCoreThread) {
throw new IllegalStateException("Tried to perform an IO operation on the WebCore thread. Use CordovaInterface.getThreadPool() instead.");
}
}
public boolean isWritable() {
checkThread();
return delegate.isWritable();
}
public File getLocalFile() {
checkThread();
return delegate.getLocalFile();
}
public OutputStream getOutputStream() throws IOException {
checkThread();
return delegate.getOutputStream();
}
public String getMimeType() {
checkThread();
return delegate.getMimeType();
}
public InputStream getInputStream() throws IOException {
checkThread();
return delegate.getInputStream();
}
}
public static UriResolver createInline(Uri uri, String response, String mimeType) { public static UriResolver createInline(Uri uri, String response, String mimeType) {
return createInline(uri, EncodingUtils.getBytes(response, "UTF-8"), mimeType); return createInline(uri, EncodingUtils.getBytes(response, "UTF-8"), mimeType);
} }
@ -260,6 +264,10 @@ public final class UriResolvers {
return new ReadOnlyResolver(uri, inputStream, mimeType); return new ReadOnlyResolver(uri, inputStream, mimeType);
} }
public static UriResolver createError(String errorMsg) {
return new ErrorUriResolver(errorMsg);
}
/* Package-private to force clients to go through CordovaWebView.resolveUri(). */ /* Package-private to force clients to go through CordovaWebView.resolveUri(). */
static UriResolver forUri(Uri uri, Context context) { static UriResolver forUri(Uri uri, Context context) {
String scheme = uri.getScheme(); String scheme = uri.getScheme();
@ -274,4 +282,12 @@ public final class UriResolvers {
} }
return null; return null;
} }
/* Used only by CordovaWebView.resolveUri(). */
static UriResolver makeThreadChecking(UriResolver resolver) {
if (resolver instanceof ThreadCheckingResolver) {
return resolver;
}
return new ThreadCheckingResolver(resolver);
}
} }