| 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();
|
| +}
|
|
|