Index: chrome/android/java/src/org/chromium/chrome/browser/AndroidProtocolAdapter.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/AndroidProtocolAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/AndroidProtocolAdapter.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d9134a7fe5038e8a3060a39391c848c46bb3e80a |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/AndroidProtocolAdapter.java |
@@ -0,0 +1,233 @@ |
+// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.chrome.browser; |
+ |
+import android.content.Context; |
+import android.content.res.AssetManager; |
+import android.net.Uri; |
+import android.util.Log; |
+import android.util.TypedValue; |
+ |
+import java.io.InputStream; |
+import java.io.IOException; |
+import java.net.URLConnection; |
+import java.util.List; |
+ |
+import org.chromium.base.CalledByNativeUnchecked; |
+ |
+/** |
+ * Implements the Java side of Android URL protocol jobs. |
+ * See android_protocol_adapter.cc. |
+ */ |
+public class AndroidProtocolAdapter { |
+ private static final String TAG = "AndroidProtocolAdapter"; |
+ |
+ // Supported URL schemes. This needs to be kept in sync with |
+ // clank/native/framework/chrome/url_request_android_job.cc. |
+ private static final String FILE_SCHEME = "file"; |
+ private static final String CONTENT_SCHEME = "content"; |
+ |
+ /** |
+ * Open an InputStream for an Android resource. |
+ * @param context The context manager. |
+ * @param url The url to load. |
+ * @return An InputStream to the Android resource. |
+ */ |
+ // TODO(bulach): this should have either a throw clause, or |
+ // handle the exception in the java side rather than the native side. |
+ @CalledByNativeUnchecked |
+ public static InputStream open(Context context, String url) { |
+ Uri uri = verifyUrl(url); |
+ if (uri == null) { |
+ return null; |
+ } |
+ String path = uri.getPath(); |
+ if (uri.getScheme().equals(FILE_SCHEME)) { |
+ if (path.startsWith(nativeGetAndroidAssetPath())) { |
+ return openAsset(context, uri); |
+ } else if (path.startsWith(nativeGetAndroidResourcePath())) { |
+ return openResource(context, uri); |
+ } |
+ } else if (uri.getScheme().equals(CONTENT_SCHEME)) { |
+ return openContent(context, uri); |
+ } |
+ return null; |
+ } |
+ |
+ private static int getFieldId(Context context, String assetType, String assetName) |
+ throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { |
+ Class<?> d = context.getClassLoader() |
+ .loadClass(context.getPackageName() + ".R$" + assetType); |
+ java.lang.reflect.Field field = d.getField(assetName); |
+ int id = field.getInt(null); |
+ return id; |
+ } |
+ |
+ private static int getValueType(Context context, int field_id) { |
+ TypedValue value = new TypedValue(); |
+ context.getResources().getValue(field_id, value, true); |
+ return value.type; |
+ } |
+ |
+ private static InputStream openResource(Context context, Uri uri) { |
+ assert(uri.getScheme().equals(FILE_SCHEME)); |
+ assert(uri.getPath() != null); |
+ assert(uri.getPath().startsWith(nativeGetAndroidResourcePath())); |
+ // The path must be of the form "/android_res/asset_type/asset_name.ext". |
+ List<String> pathSegments = uri.getPathSegments(); |
+ if (pathSegments.size() != 3) { |
+ Log.e(TAG, "Incorrect resource path: " + uri); |
+ return null; |
+ } |
+ String assetPath = pathSegments.get(0); |
+ String assetType = pathSegments.get(1); |
+ String assetName = pathSegments.get(2); |
+ if (!("/" + assetPath + "/").equals(nativeGetAndroidResourcePath())) { |
+ Log.e(TAG, "Resource path does not start with " + nativeGetAndroidResourcePath() + |
+ ": " + uri); |
+ return null; |
+ } |
+ // Drop the file extension. |
+ assetName = assetName.split("\\.")[0]; |
+ try { |
+ // Use the application context for resolving the resource package name so that we do |
+ // not use the browser's own resources. Note that if 'context' here belongs to the |
+ // test suite, it does not have a separate application context. In that case we use |
+ // the original context object directly. |
+ if (context.getApplicationContext() != null) { |
+ context = context.getApplicationContext(); |
+ } |
+ int field_id = getFieldId(context, assetType, assetName); |
+ int value_type = getValueType(context, field_id); |
+ if (value_type == TypedValue.TYPE_STRING) { |
+ return context.getResources().openRawResource(field_id); |
+ } else { |
+ Log.e(TAG, "Asset not of type string: " + uri); |
+ return null; |
+ } |
+ } catch (ClassNotFoundException e) { |
+ Log.e(TAG, "Unable to open resource URL: " + uri, e); |
+ return null; |
+ } catch (NoSuchFieldException e) { |
+ Log.e(TAG, "Unable to open resource URL: " + uri, e); |
+ return null; |
+ } catch (IllegalAccessException e) { |
+ Log.e(TAG, "Unable to open resource URL: " + uri, e); |
+ return null; |
+ } |
+ } |
+ |
+ private static InputStream openAsset(Context context, Uri uri) { |
+ assert(uri.getScheme().equals(FILE_SCHEME)); |
+ assert(uri.getPath() != null); |
+ assert(uri.getPath().startsWith(nativeGetAndroidAssetPath())); |
+ String path = uri.getPath().replaceFirst(nativeGetAndroidAssetPath(), ""); |
+ try { |
+ AssetManager assets = context.getAssets(); |
+ return assets.open(path, AssetManager.ACCESS_STREAMING); |
+ } catch (IOException e) { |
+ Log.e(TAG, "Unable to open asset URL: " + uri); |
+ return null; |
+ } |
+ } |
+ |
+ private static InputStream openContent(Context context, Uri uri) { |
+ assert(uri.getScheme().equals(CONTENT_SCHEME)); |
+ try { |
+ // We strip the query parameters before opening the stream to |
+ // ensure that the URL we try to load exactly matches the URL |
+ // we have permission to read. |
+ Uri baseUri = stripQueryParameters(uri); |
+ return context.getContentResolver().openInputStream(baseUri); |
+ } catch (Exception e) { |
+ Log.e(TAG, "Unable to open content URL: " + uri); |
+ return null; |
+ } |
+ } |
+ |
+ /** |
+ * Determine the mime type for an Android resource. |
+ * @param context The context manager. |
+ * @param stream The opened input stream which to examine. |
+ * @param url The url from which the stream was opened. |
+ * @return The mime type or null if the type is unknown. |
+ */ |
+ // TODO(bulach): this should have either a throw clause, or |
+ // handle the exception in the java side rather than the native side. |
+ @CalledByNativeUnchecked |
+ public static String getMimeType(Context context, InputStream stream, String url) { |
+ Uri uri = verifyUrl(url); |
+ if (uri == null) { |
+ return null; |
+ } |
+ String path = uri.getPath(); |
+ // The content URL type can be queried directly. |
+ if (uri.getScheme().equals(CONTENT_SCHEME)) { |
+ return context.getContentResolver().getType(uri); |
+ // Asset files may have a known extension. |
+ } else if (uri.getScheme().equals(FILE_SCHEME) && |
+ path.startsWith(nativeGetAndroidAssetPath())) { |
+ String mimeType = URLConnection.guessContentTypeFromName(path); |
+ if (mimeType != null) { |
+ return mimeType; |
+ } |
+ } |
+ // Fall back to sniffing the type from the stream. |
+ try { |
+ return URLConnection.guessContentTypeFromStream(stream); |
+ } catch (IOException e) { |
+ return null; |
+ } |
+ } |
+ |
+ /** |
+ * Make sure the given string URL is correctly formed and parse it into a Uri. |
+ * @return a Uri instance, or null if the URL was invalid. |
+ */ |
+ private static Uri verifyUrl(String url) { |
+ if (url == null) { |
+ return null; |
+ } |
+ Uri uri = Uri.parse(url); |
+ if (uri == null) { |
+ Log.e(TAG, "Malformed URL: " + url); |
+ return null; |
+ } |
+ String path = uri.getPath(); |
+ if (path == null || path.length() == 0) { |
+ Log.e(TAG, "URL does not have a path: " + url); |
+ return null; |
+ } |
+ return uri; |
+ } |
+ |
+ /** |
+ * Remove query parameters from a Uri. |
+ * @param uri The input uri. |
+ * @return The given uri without query parameters. |
+ */ |
+ private static Uri stripQueryParameters(Uri uri) { |
+ assert(uri.getAuthority() != null); |
+ assert(uri.getPath() != null); |
+ Uri.Builder builder = new Uri.Builder(); |
+ builder.scheme(uri.getScheme()); |
+ builder.encodedAuthority(uri.getAuthority()); |
+ builder.encodedPath(uri.getPath()); |
+ return builder.build(); |
+ } |
+ |
+ /** |
+ * Set the context to be used for resolving resource queries. |
+ * @param context Context to be used, or null for the default application |
+ * context. |
+ */ |
+ public static void setResourceContextForTesting(Context context) { |
+ nativeSetResourceContextForTesting(context); |
+ } |
+ |
+ private static native void nativeSetResourceContextForTesting(Context context); |
+ private static native String nativeGetAndroidAssetPath(); |
+ private static native String nativeGetAndroidResourcePath(); |
+} |