diff --git a/framework/src/org/apache/cordova/CordovaPlugin.java b/framework/src/org/apache/cordova/CordovaPlugin.java
index 22c87030..e947fcc5 100644
--- a/framework/src/org/apache/cordova/CordovaPlugin.java
+++ b/framework/src/org/apache/cordova/CordovaPlugin.java
@@ -22,7 +22,6 @@ import org.apache.cordova.CordovaArgs;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.CallbackContext;
-import org.apache.cordova.UriResolver;
import org.json.JSONArray;
import org.json.JSONException;
@@ -165,13 +164,12 @@ public class CordovaPlugin {
}
/**
- * Hook for overriding the default URI handling mechanism.
- * Applies to WebView requests as well as requests made by plugins.
+ * Hook for redirecting requests. Applies to WebView requests as well as requests made by plugins.
*/
- public UriResolver resolveUri(Uri uri) {
+ public Uri remapUri(Uri uri) {
return null;
}
-
+
/**
* Called when the WebView does a top-level navigation or refreshes.
*
diff --git a/framework/src/org/apache/cordova/CordovaResourceApi.java b/framework/src/org/apache/cordova/CordovaResourceApi.java
new file mode 100644
index 00000000..b891b51a
--- /dev/null
+++ b/framework/src/org/apache/cordova/CordovaResourceApi.java
@@ -0,0 +1,347 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+package org.apache.cordova;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.AssetManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Looper;
+import android.util.Base64;
+import android.util.Base64InputStream;
+
+import com.squareup.okhttp.OkHttpClient;
+
+import org.apache.http.util.EncodingUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.channels.FileChannel;
+
+public class CordovaResourceApi {
+ @SuppressWarnings("unused")
+ private static final String LOG_TAG = "CordovaResourceApi";
+
+ public static final int URI_TYPE_FILE = 0;
+ public static final int URI_TYPE_ASSET = 1;
+ public static final int URI_TYPE_CONTENT = 2;
+ public static final int URI_TYPE_RESOURCE = 3;
+ public static final int URI_TYPE_DATA = 4;
+ public static final int URI_TYPE_HTTP = 5;
+ public static final int URI_TYPE_HTTPS = 6;
+ public static final int URI_TYPE_UNKNOWN = -1;
+
+ private static final String[] LOCAL_FILE_PROJECTION = { "_data" };
+
+ // Creating this is light-weight.
+ private static OkHttpClient httpClient = new OkHttpClient();
+
+ static Thread webCoreThread;
+
+ private final AssetManager assetManager;
+ private final ContentResolver contentResolver;
+ private final PluginManager pluginManager;
+ private boolean threadCheckingEnabled = true;
+
+
+ public CordovaResourceApi(Context context, PluginManager pluginManager) {
+ this.contentResolver = context.getContentResolver();
+ this.assetManager = context.getAssets();
+ this.pluginManager = pluginManager;
+ }
+
+ public void setThreadCheckingEnabled(boolean value) {
+ threadCheckingEnabled = value;
+ }
+
+ public boolean isThreadCheckingEnabled() {
+ return threadCheckingEnabled;
+ }
+
+ public static int getUriType(Uri uri) {
+ assertNonRelative(uri);
+ String scheme = uri.getScheme();
+ if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
+ return URI_TYPE_CONTENT;
+ }
+ if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
+ return URI_TYPE_RESOURCE;
+ }
+ if (ContentResolver.SCHEME_FILE.equals(scheme)) {
+ if (uri.getPath().startsWith("/android_asset/")) {
+ return URI_TYPE_ASSET;
+ }
+ return URI_TYPE_FILE;
+ }
+ if ("data".equals(scheme)) {
+ return URI_TYPE_DATA;
+ }
+ if ("http".equals(scheme)) {
+ return URI_TYPE_HTTP;
+ }
+ if ("https".equals(scheme)) {
+ return URI_TYPE_HTTPS;
+ }
+ return URI_TYPE_UNKNOWN;
+ }
+
+ public Uri remapUri(Uri uri) {
+ assertNonRelative(uri);
+ Uri pluginUri = pluginManager.remapUri(uri);
+ return pluginUri != null ? pluginUri : uri;
+ }
+
+ public String remapPath(String path) {
+ return remapUri(Uri.fromFile(new File(path))).getPath();
+ }
+
+ /**
+ * Returns a File that points to the resource, or null if the resource
+ * is not on the local filesystem.
+ */
+ public File mapUriToFile(Uri uri) {
+ assertBackgroundThread();
+ switch (getUriType(uri)) {
+ case URI_TYPE_FILE:
+ return new File(uri.getPath());
+ case URI_TYPE_CONTENT: {
+ Cursor cursor = contentResolver.query(uri, LOCAL_FILE_PROJECTION, null, null, null);
+ if (cursor != null) {
+ try {
+ int columnIndex = cursor.getColumnIndex(LOCAL_FILE_PROJECTION[0]);
+ if (columnIndex != -1 && cursor.getCount() > 0) {
+ cursor.moveToFirst();
+ String realPath = cursor.getString(columnIndex);
+ if (realPath != null) {
+ return new File(realPath);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Opens a stream to the givne URI, also providing the MIME type & length.
+ * @return Never returns null.
+ * @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
+ * resolved before being passed into this function.
+ * @throws Throws an IOException if the URI cannot be opened.
+ */
+ public OpenForReadResult openForRead(Uri uri) throws IOException {
+ assertBackgroundThread();
+ switch (getUriType(uri)) {
+ case URI_TYPE_FILE: {
+ FileInputStream inputStream = new FileInputStream(uri.getPath());
+ String mimeType = FileHelper.getMimeTypeForExtension(uri.getPath());
+ long length = inputStream.getChannel().size();
+ return new OpenForReadResult(uri, inputStream, mimeType, length, null);
+ }
+ case URI_TYPE_ASSET: {
+ String assetPath = uri.getPath().substring(15);
+ AssetFileDescriptor assetFd = null;
+ InputStream inputStream;
+ long length = -1;
+ try {
+ assetFd = assetManager.openFd(assetPath);
+ inputStream = assetFd.createInputStream();
+ length = assetFd.getLength();
+ } catch (FileNotFoundException e) {
+ // Will occur if the file is compressed.
+ inputStream = assetManager.open(assetPath);
+ }
+ String mimeType = FileHelper.getMimeTypeForExtension(assetPath);
+ return new OpenForReadResult(uri, inputStream, mimeType, length, assetFd);
+ }
+ case URI_TYPE_CONTENT:
+ case URI_TYPE_RESOURCE: {
+ String mimeType = contentResolver.getType(uri);
+ AssetFileDescriptor assetFd = contentResolver.openAssetFileDescriptor(uri, "r");
+ InputStream inputStream = assetFd.createInputStream();
+ long length = assetFd.getLength();
+ return new OpenForReadResult(uri, inputStream, mimeType, length, assetFd);
+ }
+ case URI_TYPE_DATA: {
+ OpenForReadResult ret = readDataUri(uri);
+ if (ret == null) {
+ break;
+ }
+ return ret;
+ }
+ case URI_TYPE_HTTP:
+ case URI_TYPE_HTTPS: {
+ HttpURLConnection conn = httpClient.open(new URL(uri.toString()));
+ conn.setDoInput(true);
+ String mimeType = conn.getHeaderField("Content-Type");
+ int length = conn.getContentLength();
+ InputStream inputStream = conn.getInputStream();
+ return new OpenForReadResult(uri, inputStream, mimeType, length, null);
+ }
+ }
+ throw new FileNotFoundException("URI not supported by CordovaResourceApi: " + uri);
+ }
+
+ public OutputStream openOutputStream(Uri uri) throws IOException {
+ return openOutputStream(uri, false);
+ }
+
+ /**
+ * Opens a stream to the given URI.
+ * @return Never returns null.
+ * @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
+ * resolved before being passed into this function.
+ * @throws Throws an IOException if the URI cannot be opened.
+ */
+ public OutputStream openOutputStream(Uri uri, boolean append) throws IOException {
+ assertBackgroundThread();
+ switch (getUriType(uri)) {
+ case URI_TYPE_FILE: {
+ File localFile = new File(uri.getPath());
+ File parent = localFile.getParentFile();
+ if (parent != null) {
+ parent.mkdirs();
+ }
+ return new FileOutputStream(localFile, append);
+ }
+ case URI_TYPE_CONTENT:
+ case URI_TYPE_RESOURCE: {
+ AssetFileDescriptor assetFd = contentResolver.openAssetFileDescriptor(uri, append ? "wa" : "w");
+ return assetFd.createOutputStream();
+ }
+ }
+ throw new FileNotFoundException("URI not supported by CordovaResourceApi: " + uri);
+ }
+
+ public HttpURLConnection createHttpConnection(Uri uri) throws IOException {
+ assertBackgroundThread();
+ return httpClient.open(new URL(uri.toString()));
+ }
+
+ // Copies the input to the output in the most efficient manner possible.
+ // Closes both streams.
+ public void copyResource(OpenForReadResult input, OutputStream outputStream) throws IOException {
+ assertBackgroundThread();
+ try {
+ InputStream inputStream = input.inputStream;
+ if (inputStream instanceof FileInputStream && outputStream instanceof FileOutputStream) {
+ FileChannel inChannel = ((FileInputStream)input.inputStream).getChannel();
+ FileChannel outChannel = ((FileOutputStream)outputStream).getChannel();
+ long offset = 0;
+ long length = input.length;
+ if (input.assetFd != null) {
+ offset = input.assetFd.getStartOffset();
+ }
+ outChannel.transferFrom(inChannel, offset, length);
+ } else {
+ final int BUFFER_SIZE = 8192;
+ byte[] buffer = new byte[BUFFER_SIZE];
+
+ for (;;) {
+ int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
+
+ if (bytesRead <= 0) {
+ break;
+ }
+ outputStream.write(buffer, 0, bytesRead);
+ }
+ }
+ } finally {
+ input.inputStream.close();
+ if (outputStream != null) {
+ outputStream.close();
+ }
+ }
+ }
+
+ public void copyResource(Uri sourceUri, OutputStream outputStream) throws IOException {
+ copyResource(openForRead(sourceUri), outputStream);
+ }
+
+
+ private void assertBackgroundThread() {
+ if (threadCheckingEnabled) {
+ 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.");
+ }
+ }
+ }
+
+ private OpenForReadResult readDataUri(Uri uri) {
+ String uriAsString = uri.toString().substring(5);
+ int commaPos = uriAsString.indexOf(',');
+ if (commaPos == -1) {
+ return null;
+ }
+ String[] mimeParts = uriAsString.substring(0, commaPos).split(";");
+ String contentType = null;
+ boolean base64 = false;
+ if (mimeParts.length > 0) {
+ contentType = mimeParts[0];
+ }
+ for (int i = 1; i < mimeParts.length; ++i) {
+ if ("base64".equalsIgnoreCase(mimeParts[i])) {
+ base64 = true;
+ }
+ }
+ String dataPartAsString = uriAsString.substring(commaPos + 1);
+ byte[] data = base64 ? Base64.decode(dataPartAsString, Base64.DEFAULT) : EncodingUtils.getBytes(dataPartAsString, "UTF-8");
+ InputStream inputStream = new ByteArrayInputStream(data);
+ return new OpenForReadResult(uri, inputStream, contentType, data.length, null);
+ }
+
+ private static void assertNonRelative(Uri uri) {
+ if (!uri.isAbsolute()) {
+ throw new IllegalArgumentException("Relative URIs are not supported.");
+ }
+ }
+
+ public static final class OpenForReadResult {
+ public final Uri uri;
+ public final InputStream inputStream;
+ public final String mimeType;
+ public final long length;
+ public final AssetFileDescriptor assetFd;
+
+ OpenForReadResult(Uri uri, InputStream inputStream, String mimeType, long length, AssetFileDescriptor assetFd) {
+ this.uri = uri;
+ this.inputStream = inputStream;
+ this.mimeType = mimeType;
+ this.length = length;
+ this.assetFd = assetFd;
+ }
+ }
+}
diff --git a/framework/src/org/apache/cordova/CordovaWebView.java b/framework/src/org/apache/cordova/CordovaWebView.java
index 648b1f88..b97b03e0 100755
--- a/framework/src/org/apache/cordova/CordovaWebView.java
+++ b/framework/src/org/apache/cordova/CordovaWebView.java
@@ -97,6 +97,8 @@ public class CordovaWebView extends WebView {
private ActivityResult mResult = null;
+ private CordovaResourceApi resourceApi;
+
class ActivityResult {
int request;
@@ -307,6 +309,7 @@ public class CordovaWebView extends WebView {
pluginManager = new PluginManager(this, this.cordova);
jsMessageQueue = new NativeToJsMessageQueue(this, cordova);
exposedJsApi = new ExposedJsApi(pluginManager, jsMessageQueue);
+ resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);
exposeJsInterface();
}
@@ -957,37 +960,7 @@ public class CordovaWebView extends WebView {
mResult = new ActivityResult(requestCode, resultCode, intent);
}
- /**
- * Resolves the given URI, giving plugins a chance to re-route or customly handle the URI.
- * A white-list rejection will be returned if the URI does not pass the white-list.
- * @return Never returns null.
- * @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
- * resolved before being passed into this function.
- */
- public UriResolver resolveUri(Uri uri) {
- return resolveUri(uri, false);
- }
-
- UriResolver resolveUri(Uri uri, boolean fromWebView) {
- 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);
- ret = UriResolvers.createError("Whitelist rejection for: " + uri);
- } else {
- // Give plugins a chance to handle the request.
- ret = ((org.apache.cordova.PluginManager)pluginManager).resolveUri(uri);
- }
- if (ret == null && !fromWebView) {
- ret = UriResolvers.forUri(uri, cordova.getActivity());
- if (ret == null) {
- ret = UriResolvers.createError("Unresolvable URI: " + uri);
- }
- }
- return ret == null ? null : UriResolvers.makeThreadChecking(ret);
+ public CordovaResourceApi getResourceApi() {
+ return resourceApi;
}
}
diff --git a/framework/src/org/apache/cordova/CordovaWebViewClient.java b/framework/src/org/apache/cordova/CordovaWebViewClient.java
index c49611e6..d782aa04 100755
--- a/framework/src/org/apache/cordova/CordovaWebViewClient.java
+++ b/framework/src/org/apache/cordova/CordovaWebViewClient.java
@@ -48,7 +48,7 @@ import android.webkit.WebViewClient;
*/
public class CordovaWebViewClient extends WebViewClient {
- private static final String TAG = "Cordova";
+ private static final String TAG = "CordovaWebViewClient";
private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/";
CordovaInterface cordova;
CordovaWebView appView;
diff --git a/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java b/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java
index c23d5807..317acc2f 100644
--- a/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java
+++ b/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java
@@ -19,20 +19,22 @@
package org.apache.cordova;
import java.io.IOException;
-import java.io.InputStream;
+import org.apache.cordova.CordovaResourceApi.OpenForReadResult;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.LOG;
import android.annotation.TargetApi;
import android.net.Uri;
import android.os.Build;
+import android.util.Log;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
+ private static final String TAG = "IceCreamCordovaWebViewClient";
public IceCreamCordovaWebViewClient(CordovaInterface cordova) {
super(cordova);
@@ -44,38 +46,44 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
- // Disable checks during shouldInterceptRequest since there is no way to avoid IO here :(.
- UriResolvers.webCoreThread = null;
+ // Tell the Thread-Checking resolve what thread the WebCore thread is.
+ CordovaResourceApi.webCoreThread = Thread.currentThread();
+ Log.e("WHAAAA", "FOOD " + CordovaResourceApi.webCoreThread);
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);
- }
+ // Check the against the white-list.
+ if ((url.startsWith("http:") || url.startsWith("https:")) && !Config.isUrlWhiteListed(url)) {
+ LOG.w(TAG, "URL blocked by whitelist: " + url);
+ // Results in a 404.
+ return new WebResourceResponse("text/plain", "UTF-8", null);
}
+
+ CordovaResourceApi resourceApi = appView.getResourceApi();
+ Uri origUri = Uri.parse(url);
+ // Allow plugins to intercept WebView requests.
+ Uri remappedUri = resourceApi.remapUri(origUri);
- 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 (!origUri.equals(remappedUri) || needsSpecialsInAssetUrlFix(origUri)) {
+ OpenForReadResult result = resourceApi.openForRead(remappedUri);
+ return new WebResourceResponse(result.mimeType, "UTF-8", result.inputStream);
}
+ // If we don't need to special-case the request, let the browser load it.
return null;
- } finally {
- // Tell the Thread-Checking resolve what thread the WebCore thread is.
- UriResolvers.webCoreThread = Thread.currentThread();
+ } 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);
}
}
+
+ private static boolean needsSpecialsInAssetUrlFix(Uri uri) {
+ if (CordovaResourceApi.getUriType(uri) != CordovaResourceApi.URI_TYPE_ASSET) {
+ return false;
+ }
+ if (uri.getQuery() != null || uri.getFragment() != null) {
+ return true;
+ }
- private static boolean needsIceCreamSpecialsInAssetUrlFix(String url) {
- if (!url.contains("%20")){
+ if (!uri.toString().contains("%")) {
return false;
}
@@ -83,8 +91,7 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
case android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH:
case android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1:
return true;
- default:
- return false;
}
+ return false;
}
}
diff --git a/framework/src/org/apache/cordova/PluginManager.java b/framework/src/org/apache/cordova/PluginManager.java
index 1f32ba6b..2db9d56e 100755
--- a/framework/src/org/apache/cordova/PluginManager.java
+++ b/framework/src/org/apache/cordova/PluginManager.java
@@ -26,7 +26,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.apache.cordova.CordovaArgs;
import org.apache.cordova.CordovaWebView;
-import org.apache.cordova.UriResolver;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.CordovaPlugin;
@@ -40,7 +39,6 @@ import android.content.res.XmlResourceParser;
import android.net.Uri;
import android.util.Log;
-import android.webkit.WebResourceResponse;
/**
* PluginManager is exposed to JavaScript in the Cordova WebView.
@@ -407,10 +405,10 @@ public class PluginManager {
LOG.e(TAG, "=====================================================================================");
}
- UriResolver resolveUri(Uri uri) {
+ Uri remapUri(Uri uri) {
for (PluginEntry entry : this.entries.values()) {
if (entry.plugin != null) {
- UriResolver ret = entry.plugin.resolveUri(uri);
+ Uri ret = entry.plugin.remapUri(uri);
if (ret != null) {
return ret;
}
diff --git a/framework/src/org/apache/cordova/UriResolver.java b/framework/src/org/apache/cordova/UriResolver.java
deleted file mode 100644
index b3bfa4d4..00000000
--- a/framework/src/org/apache/cordova/UriResolver.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied. See the License for the
- specific language governing permissions and limitations
- under the License.
-*/
-package org.apache.cordova;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/*
- * Interface for a class that can resolve URIs.
- * See CordovaUriResolver for an example.
- */
-public abstract class UriResolver {
-
- /**
- * Returns the InputStream for the resource.
- * Throws an exception if it cannot be read.
- * Never returns null.
- */
- public abstract InputStream getInputStream() throws IOException;
-
- /**
- * Returns the MIME type of the resource.
- * Returns null if the MIME type cannot be determined (e.g. content: that doesn't exist).
- */
- public abstract String getMimeType();
-
- /** Returns whether the resource is writable. */
- public abstract boolean isWritable();
-
- /**
- * Returns a File that points to the resource, or null if the resource
- * is not on the local file system.
- */
- public abstract File getLocalFile();
-
- /**
- * Returns the OutputStream for the resource.
- * Throws an exception if it cannot be written to.
- * Never returns null.
- */
- public OutputStream getOutputStream() throws IOException {
- throw new IOException("Writing is not suppported");
- }
-
- /**
- * Returns the length of the input stream, or -1 if it is not computable.
- */
- public long computeLength() throws IOException {
- return -1;
- }
-}
diff --git a/framework/src/org/apache/cordova/UriResolvers.java b/framework/src/org/apache/cordova/UriResolvers.java
deleted file mode 100644
index 294fc6bf..00000000
--- a/framework/src/org/apache/cordova/UriResolvers.java
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied. See the License for the
- specific language governing permissions and limitations
- under the License.
-*/
-package org.apache.cordova;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import org.apache.cordova.FileHelper;
-import org.apache.http.util.EncodingUtils;
-
-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 long computeSizeFromResolver(UriResolver resolver) throws IOException {
- InputStream inputStream = resolver.getInputStream();
- if (inputStream instanceof FileInputStream) {
- return ((FileInputStream)inputStream).getChannel().size();
- }
- if (inputStream instanceof ByteArrayInputStream) {
- return ((ByteArrayInputStream)inputStream).available();
- }
- return -1;
- }
-
- private static final class FileUriResolver extends UriResolver {
- private final File localFile;
- private String mimeType;
- private FileInputStream cachedInputStream;
-
- FileUriResolver(Uri uri) {
- localFile = new File(uri.getPath());
- }
-
- public InputStream getInputStream() throws IOException {
- if (cachedInputStream == null) {
- cachedInputStream = new FileInputStream(localFile);
- }
- return cachedInputStream;
- }
-
- public OutputStream getOutputStream() throws FileNotFoundException {
- File parent = localFile.getParentFile();
- if (parent != null) {
- localFile.getParentFile().mkdirs();
- }
- return new FileOutputStream(localFile);
- }
-
- public String getMimeType() {
- if (mimeType == null) {
- mimeType = FileHelper.getMimeTypeForExtension(localFile.getName());
- }
- return mimeType;
- }
-
- public boolean isWritable() {
- if (localFile.isDirectory()) {
- return false;
- }
- if (localFile.exists()) {
- return localFile.canWrite();
- }
- return localFile.getParentFile().canWrite();
- }
-
- public File getLocalFile() {
- return localFile;
- }
-
- public long computeLength() throws IOException {
- return localFile.length();
- }
- }
-
- private static final class AssetUriResolver extends UriResolver {
- private final AssetManager assetManager;
- private final String assetPath;
- private String mimeType;
- private InputStream cachedInputStream;
-
- AssetUriResolver(Uri uri, AssetManager assetManager) {
- this.assetManager = assetManager;
- this.assetPath = uri.getPath().substring(15);
- }
-
- public InputStream getInputStream() throws IOException {
- if (cachedInputStream == null) {
- cachedInputStream = assetManager.open(assetPath);
- }
- return cachedInputStream;
- }
-
- public OutputStream getOutputStream() throws FileNotFoundException {
- throw new FileNotFoundException("URI not writable.");
- }
-
- public String getMimeType() {
- if (mimeType == null) {
- mimeType = FileHelper.getMimeTypeForExtension(assetPath);
- }
- return mimeType;
- }
-
- public boolean isWritable() {
- return false;
- }
-
- public File getLocalFile() {
- return null;
- }
-
- public long computeLength() throws IOException {
- return computeSizeFromResolver(this);
- }
- }
-
- private static final class ContentUriResolver extends UriResolver {
- private final Uri uri;
- private final ContentResolver contentResolver;
- private String mimeType;
- private InputStream cachedInputStream;
-
- ContentUriResolver(Uri uri, ContentResolver contentResolver) {
- this.uri = uri;
- this.contentResolver = contentResolver;
- }
-
- public InputStream getInputStream() throws IOException {
- if (cachedInputStream == null) {
- cachedInputStream = contentResolver.openInputStream(uri);
- }
- return cachedInputStream;
- }
-
- public OutputStream getOutputStream() throws FileNotFoundException {
- return contentResolver.openOutputStream(uri);
- }
-
- public String getMimeType() {
- if (mimeType == null) {
- mimeType = contentResolver.getType(uri);
- }
- return mimeType;
- }
-
- public boolean isWritable() {
- return uri.getScheme().equals(ContentResolver.SCHEME_CONTENT);
- }
-
- public File getLocalFile() {
- return null;
- }
-
- public long computeLength() throws IOException {
- return computeSizeFromResolver(this);
- }
- }
-
- private static final class ErrorUriResolver extends UriResolver {
- final String errorMsg;
-
- ErrorUriResolver(String errorMsg) {
- this.errorMsg = errorMsg;
- }
-
- public boolean isWritable() {
- return false;
- }
-
- public File getLocalFile() {
- return null;
- }
-
- public OutputStream getOutputStream() throws IOException {
- throw new FileNotFoundException(errorMsg);
- }
-
- public String getMimeType() {
- return null;
- }
-
- public InputStream getInputStream() throws IOException {
- throw new FileNotFoundException(errorMsg);
- }
- }
-
- private static final class ReadOnlyResolver extends UriResolver {
- private InputStream inputStream;
- private String mimeType;
-
- public ReadOnlyResolver(Uri uri, InputStream inputStream, String mimeType) {
- this.inputStream = inputStream;
- this.mimeType = mimeType;
- }
-
- public boolean isWritable() {
- return false;
- }
-
- public File getLocalFile() {
- return null;
- }
-
- public OutputStream getOutputStream() throws IOException {
- throw new FileNotFoundException("URI is not writable");
- }
-
- public String getMimeType() {
- return mimeType;
- }
-
- public InputStream getInputStream() throws IOException {
- return inputStream;
- }
-
- public long computeLength() throws IOException {
- return computeSizeFromResolver(this);
- }
- }
-
- private static final class ThreadCheckingResolver extends 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 long computeLength() throws IOException {
- checkThread();
- return delegate.computeLength();
- }
- }
-
- public static UriResolver createInline(Uri uri, String response, String mimeType) {
- return createInline(uri, EncodingUtils.getBytes(response, "UTF-8"), mimeType);
- }
-
- public static UriResolver createInline(Uri uri, byte[] response, String mimeType) {
- return new ReadOnlyResolver(uri, new ByteArrayInputStream(response), mimeType);
- }
-
- public static UriResolver createReadOnly(Uri uri, InputStream inputStream, String 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(). */
- static UriResolver forUri(Uri uri, Context context) {
- String scheme = uri.getScheme();
- if (ContentResolver.SCHEME_CONTENT.equals(scheme) || ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
- return new ContentUriResolver(uri, context.getContentResolver());
- }
- if (ContentResolver.SCHEME_FILE.equals(scheme)) {
- if (uri.getPath().startsWith("/android_asset/")) {
- return new AssetUriResolver(uri, context.getAssets());
- }
- return new FileUriResolver(uri);
- }
- return null;
- }
-
- /* Used only by CordovaWebView.resolveUri(). */
- static UriResolver makeThreadChecking(UriResolver resolver) {
- if (resolver instanceof ThreadCheckingResolver) {
- return resolver;
- }
- return new ThreadCheckingResolver(resolver);
- }
-}
diff --git a/test/AndroidManifest.xml b/test/AndroidManifest.xml
index f6c840eb..04ef3c6e 100755
--- a/test/AndroidManifest.xml
+++ b/test/AndroidManifest.xml
@@ -45,7 +45,7 @@
-
+
{
-
- public UriResolversTest()
- {
- super(CordovaWebViewTestActivity.class);
- }
-
- CordovaWebView cordovaWebView;
- private CordovaWebViewTestActivity activity;
- String execPayload;
- Integer execStatus;
-
- protected void setUp() throws Exception {
- super.setUp();
- activity = this.getActivity();
- cordovaWebView = activity.cordovaWebView;
- cordovaWebView.pluginManager.addService(new PluginEntry("UriResolverTestPlugin1", new CordovaPlugin() {
- @Override
- public UriResolver resolveUri(Uri uri) {
- if ("plugin-uri".equals(uri.getScheme())) {
- return cordovaWebView.resolveUri(uri.buildUpon().scheme("file").build());
- }
- return null;
- }
- public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
- synchronized (UriResolversTest.this) {
- execPayload = args.getString(0);
- execStatus = args.getInt(1);
- UriResolversTest.this.notify();
- }
- return true;
- }
- }));
- cordovaWebView.pluginManager.addService(new PluginEntry("UriResolverTestPlugin2", new CordovaPlugin() {
- @Override
- public UriResolver resolveUri(Uri uri) {
- if (uri.getQueryParameter("pluginRewrite") != null) {
- return UriResolvers.createInline(uri, "pass", "my/mime");
- }
- return null;
- }
- }));
- }
-
- private Uri createTestImageContentUri() {
- Bitmap imageBitmap = BitmapFactory.decodeResource(activity.getResources(), R.drawable.icon);
- String stored = MediaStore.Images.Media.insertImage(activity.getContentResolver(),
- imageBitmap, "app-icon", "desc");
- return Uri.parse(stored);
- }
-
- private void performResolverTest(Uri uri, String expectedMimeType, File expectedLocalFile,
- boolean expectedIsWritable,
- boolean expectRead, boolean expectWrite) throws IOException {
- UriResolver resolver = cordovaWebView.resolveUri(uri);
- assertEquals(expectedLocalFile, resolver.getLocalFile());
- assertEquals(expectedMimeType, resolver.getMimeType());
- if (expectedIsWritable) {
- assertTrue(resolver.isWritable());
- } else {
- assertFalse(resolver.isWritable());
- }
- try {
- resolver.getInputStream().read();
- if (!expectRead) {
- fail("Expected getInputStream to throw.");
- }
- } catch (IOException e) {
- if (expectRead) {
- throw e;
- }
- }
- try {
- resolver.getOutputStream().write(123);
- if (!expectWrite) {
- fail("Expected getOutputStream to throw.");
- }
- } catch (IOException e) {
- if (expectWrite) {
- throw e;
- }
- }
- }
-
- public void testValidContentUri() throws IOException
- {
- Uri contentUri = createTestImageContentUri();
- performResolverTest(contentUri, "image/jpeg", null, true, true, true);
- }
-
- public void testInvalidContentUri() throws IOException
- {
- Uri contentUri = Uri.parse("content://media/external/images/media/999999999");
- performResolverTest(contentUri, null, null, true, false, false);
- }
-
- public void testValidAssetUri() throws IOException
- {
- Uri assetUri = Uri.parse("file:///android_asset/www/index.html?foo#bar"); // Also check for stripping off ? and # correctly.
- performResolverTest(assetUri, "text/html", null, false, true, false);
- }
-
- public void testInvalidAssetUri() throws IOException
- {
- Uri assetUri = Uri.parse("file:///android_asset/www/missing.html");
- performResolverTest(assetUri, "text/html", null, false, false, false);
- }
-
- public void testFileUriToExistingFile() throws IOException
- {
- File f = File.createTempFile("te s t", ".txt"); // Also check for dealing with spaces.
- try {
- Uri fileUri = Uri.parse(f.toURI().toString() + "?foo#bar"); // Also check for stripping off ? and # correctly.
- performResolverTest(fileUri, "text/plain", f, true, true, true);
- } finally {
- f.delete();
- }
- }
-
- public void testFileUriToMissingFile() throws IOException
- {
- File f = new File(Environment.getExternalStorageDirectory() + "/somefilethatdoesntexist");
- Uri fileUri = Uri.parse(f.toURI().toString());
- try {
- performResolverTest(fileUri, null, f, true, false, true);
- } finally {
- f.delete();
- }
- }
-
- public void testFileUriToMissingFileWithMissingParent() throws IOException
- {
- File f = new File(Environment.getExternalStorageDirectory() + "/somedirthatismissing/somefilethatdoesntexist");
- Uri fileUri = Uri.parse(f.toURI().toString());
- performResolverTest(fileUri, null, f, false, false, false);
- }
-
- public void testUnrecognizedUri() throws IOException
- {
- Uri uri = Uri.parse("somescheme://foo");
- performResolverTest(uri, null, null, false, false, false);
- }
-
- public void testRelativeUri()
- {
- try {
- cordovaWebView.resolveUri(Uri.parse("/foo"));
- fail("Should have thrown for relative URI 1.");
- } catch (Throwable t) {
- }
- try {
- cordovaWebView.resolveUri(Uri.parse("//foo/bar"));
- fail("Should have thrown for relative URI 2.");
- } catch (Throwable t) {
- }
- try {
- cordovaWebView.resolveUri(Uri.parse("foo.png"));
- fail("Should have thrown for relative URI 3.");
- } catch (Throwable t) {
- }
- }
-
- public void testPluginOverrides1() throws IOException
- {
- Uri uri = Uri.parse("plugin-uri://foohost/android_asset/www/index.html");
- performResolverTest(uri, "text/html", null, false, true, false);
- }
-
- public void testPluginOverrides2() throws IOException
- {
- Uri uri = Uri.parse("plugin-uri://foohost/android_asset/www/index.html?pluginRewrite=yes");
- performResolverTest(uri, "my/mime", null, false, true, false);
- }
-
- public void testWhitelistRejection() throws IOException
- {
- Uri uri = Uri.parse("http://foohost.com/");
- performResolverTest(uri, null, null, false, false, false);
- }
-
- public void testWebViewRequestIntercept() throws IOException
- {
- cordovaWebView.sendJavascript(
- "var x = new XMLHttpRequest;\n" +
- "x.open('GET', 'file://foo?pluginRewrite=1', false);\n" +
- "x.send();\n" +
- "cordova.require('cordova/exec')(null,null,'UriResolverTestPlugin1', 'foo', [x.responseText, x.status])");
- execPayload = null;
- execStatus = null;
- try {
- synchronized (this) {
- this.wait(2000);
- }
- } catch (InterruptedException e) {
- }
- assertEquals("pass", execPayload);
- assertEquals(execStatus.intValue(), 200);
- }
-
- public void testWebViewWhiteListRejection() throws IOException
- {
- cordovaWebView.sendJavascript(
- "var x = new XMLHttpRequest;\n" +
- "x.open('GET', 'http://foo/bar', false);\n" +
- "x.send();\n" +
- "cordova.require('cordova/exec')(null,null,'UriResolverTestPlugin1', 'foo', [x.responseText, x.status])");
- execPayload = null;
- execStatus = null;
- try {
- synchronized (this) {
- this.wait(2000);
- }
- } catch (InterruptedException e) {
- }
- assertEquals("", execPayload);
- assertEquals(execStatus.intValue(), 404);
- }
-}