[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()) {
throw new IllegalArgumentException("Relative URIs are not yet supported by resolveUri.");
}
UriResolver ret = null;
// Check the against the white-list before delegating to plugins.
if (("http".equals(uri.getScheme()) || "https".equals(uri.getScheme())) && !Config.isUrlWhiteListed(uri.toString()))
{
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.
ret = ((org.apache.cordova.PluginManager)pluginManager).resolveUri(uri);
}
// Give plugins a chance to handle the request.
UriResolver resolver = ((org.apache.cordova.PluginManager)pluginManager).resolveUri(uri);
if (resolver == null && !fromWebView) {
resolver = UriResolvers.forUri(uri, cordova.getActivity());
if (resolver == null) {
resolver = new UriResolvers.ErrorUriResolver(uri, "Unresolvable URI");
if (ret == null && !fromWebView) {
ret = UriResolvers.forUri(uri, cordova.getActivity());
if (ret == null) {
ret = UriResolvers.createError("Unresolvable URI: " + uri);
}
}
return resolver;
return ret == null ? null : UriResolvers.makeThreadChecking(ret);
}
}

View File

@ -44,27 +44,34 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
UriResolver uriResolver = appView.resolveUri(Uri.parse(url), true);
if (uriResolver == null && url.startsWith("file:///android_asset/")) {
if (url.contains("?") || url.contains("#") || needsIceCreamSpecialsInAssetUrlFix(url)) {
uriResolver = appView.resolveUri(Uri.parse(url), false);
// 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);
if (uriResolver == null && url.startsWith("file:///android_asset/")) {
if (url.contains("?") || url.contains("#") || needsIceCreamSpecialsInAssetUrlFix(url)) {
uriResolver = appView.resolveUri(Uri.parse(url), false);
}
}
}
if (uriResolver != null) {
try {
InputStream stream = uriResolver.getInputStream();
String mimeType = uriResolver.getMimeType();
// If we don't know how to open this file, let the browser continue loading
return new WebResourceResponse(mimeType, "UTF-8", stream);
} catch (IOException e) {
LOG.e("IceCreamCordovaWebViewClient", "Error occurred while loading a file.", e);
// Results in a 404.
return new WebResourceResponse("text/plain", "UTF-8", null);
if (uriResolver != null) {
try {
InputStream stream = uriResolver.getInputStream();
String mimeType = uriResolver.getMimeType();
// If we don't know how to open this file, let the browser continue loading
return new WebResourceResponse(mimeType, "UTF-8", stream);
} catch (IOException e) {
LOG.e("IceCreamCordovaWebViewClient", "Error occurred while loading a file.", e);
// Results in a 404.
return new WebResourceResponse("text/plain", "UTF-8", null);
}
}
return null;
} finally {
// Tell the Thread-Checking resolve what thread the WebCore thread is.
UriResolvers.webCoreThread = Thread.currentThread();
}
return null;
}
private static boolean needsIceCreamSpecialsInAssetUrlFix(String url) {

View File

@ -31,9 +31,6 @@ import android.net.Uri;
*/
public interface UriResolver {
/** Returns the URI that this instance will resolve. */
Uri getUri();
/**
* Returns the InputStream for the resource.
* 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.res.AssetManager;
import android.net.Uri;
import android.os.Looper;
/*
* UriResolver implementations.
*/
public final class UriResolvers {
static Thread webCoreThread;
private UriResolvers() {}
private static final class FileUriResolver implements UriResolver {
private final Uri uri;
private final File localFile;
private String mimeType;
private File localFile;
FileUriResolver(Uri uri) {
this.uri = uri;
}
public Uri getUri() {
return uri;
localFile = new File(uri.getPath());
}
public InputStream getInputStream() throws IOException {
return new FileInputStream(getLocalFile());
return new FileInputStream(localFile);
}
public OutputStream getOutputStream() throws FileNotFoundException {
return new FileOutputStream(getLocalFile());
return new FileOutputStream(localFile);
}
public String getMimeType() {
if (mimeType == null) {
mimeType = FileHelper.getMimeTypeForExtension(getLocalFile().getName());
mimeType = FileHelper.getMimeTypeForExtension(localFile.getName());
}
return mimeType;
}
public boolean isWritable() {
File f = getLocalFile();
if (f.isDirectory()) {
if (localFile.isDirectory()) {
return false;
}
if (f.exists()) {
return f.canWrite();
if (localFile.exists()) {
return localFile.canWrite();
}
return f.getParentFile().canWrite();
return localFile.getParentFile().canWrite();
}
public File getLocalFile() {
if (localFile == null) {
localFile = new File(uri.getPath());
}
return localFile;
}
}
private static final class AssetUriResolver implements UriResolver {
private final Uri uri;
private final AssetManager assetManager;
private final String assetPath;
private String mimeType;
AssetUriResolver(Uri uri, AssetManager assetManager) {
this.uri = uri;
this.assetManager = assetManager;
this.assetPath = uri.getPath().substring(15);
}
public Uri getUri() {
return uri;
}
public InputStream getInputStream() throws IOException {
return assetManager.open(assetPath);
}
@ -138,10 +126,6 @@ public final class UriResolvers {
this.contentResolver = contentResolver;
}
public Uri getUri() {
return uri;
}
public InputStream getInputStream() throws IOException {
return contentResolver.openInputStream(uri);
}
@ -166,88 +150,108 @@ public final class UriResolvers {
}
}
static final class ErrorUriResolver implements UriResolver {
final Uri uri;
private static final class ErrorUriResolver implements UriResolver {
final String errorMsg;
ErrorUriResolver(Uri uri, String errorMsg) {
this.uri = uri;
ErrorUriResolver(String errorMsg) {
this.errorMsg = errorMsg;
}
@Override
public boolean isWritable() {
return false;
}
@Override
public Uri getUri() {
return uri;
}
@Override
public File getLocalFile() {
return null;
}
@Override
public OutputStream getOutputStream() throws IOException {
throw new FileNotFoundException(errorMsg);
}
@Override
public String getMimeType() {
return null;
}
@Override
public InputStream getInputStream() throws IOException {
throw new FileNotFoundException(errorMsg);
}
}
private static final class ReadOnlyResolver implements UriResolver {
private Uri uri;
private InputStream inputStream;
private String mimeType;
public ReadOnlyResolver(Uri uri, InputStream inputStream, String mimeType) {
this.uri = uri;
this.inputStream = inputStream;
this.mimeType = mimeType;
}
@Override
public boolean isWritable() {
return false;
}
@Override
public Uri getUri() {
return uri;
}
@Override
public File getLocalFile() {
return null;
}
@Override
public OutputStream getOutputStream() throws IOException {
throw new FileNotFoundException("URI is not writable");
}
@Override
public String getMimeType() {
return mimeType;
}
@Override
public InputStream getInputStream() throws IOException {
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) {
return createInline(uri, EncodingUtils.getBytes(response, "UTF-8"), mimeType);
}
@ -260,6 +264,10 @@ public final class UriResolvers {
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(). */
static UriResolver forUri(Uri uri, Context context) {
String scheme = uri.getScheme();
@ -274,4 +282,12 @@ public final class UriResolvers {
}
return null;
}
/* Used only by CordovaWebView.resolveUri(). */
static UriResolver makeThreadChecking(UriResolver resolver) {
if (resolver instanceof ThreadCheckingResolver) {
return resolver;
}
return new ThreadCheckingResolver(resolver);
}
}