• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.android_webview;
6 
7 import android.content.Context;
8 import android.content.res.AssetManager;
9 import android.net.Uri;
10 import android.util.Log;
11 import android.util.TypedValue;
12 
13 import org.chromium.base.CalledByNative;
14 import org.chromium.base.JNINamespace;
15 
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.net.URLConnection;
19 import java.util.List;
20 
21 /**
22  * Implements the Java side of Android URL protocol jobs.
23  * See android_protocol_handler.cc.
24  */
25 @JNINamespace("android_webview")
26 public class AndroidProtocolHandler {
27     private static final String TAG = "AndroidProtocolHandler";
28 
29     // Supported URL schemes. This needs to be kept in sync with
30     // clank/native/framework/chrome/url_request_android_job.cc.
31     private static final String FILE_SCHEME = "file";
32     private static final String CONTENT_SCHEME = "content";
33 
34     /**
35      * Open an InputStream for an Android resource.
36      * @param context The context manager.
37      * @param url The url to load.
38      * @return An InputStream to the Android resource.
39      */
40     @CalledByNative
open(Context context, String url)41     public static InputStream open(Context context, String url) {
42         Uri uri = verifyUrl(url);
43         if (uri == null) {
44             return null;
45         }
46         try {
47             String path = uri.getPath();
48             if (uri.getScheme().equals(FILE_SCHEME)) {
49                 if (path.startsWith(nativeGetAndroidAssetPath())) {
50                     return openAsset(context, uri);
51                 } else if (path.startsWith(nativeGetAndroidResourcePath())) {
52                     return openResource(context, uri);
53                 }
54             } else if (uri.getScheme().equals(CONTENT_SCHEME)) {
55                 return openContent(context, uri);
56             }
57         } catch (Exception ex) {
58             Log.e(TAG, "Error opening inputstream: " + url);
59         }
60         return null;
61     }
62 
getFieldId(Context context, String assetType, String assetName)63     private static int getFieldId(Context context, String assetType, String assetName)
64         throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
65         Class<?> d = context.getClassLoader()
66             .loadClass(context.getPackageName() + ".R$" + assetType);
67         java.lang.reflect.Field field = d.getField(assetName);
68         int id = field.getInt(null);
69         return id;
70     }
71 
getValueType(Context context, int fieldId)72     private static int getValueType(Context context, int fieldId) {
73         TypedValue value = new TypedValue();
74         context.getResources().getValue(fieldId, value, true);
75         return value.type;
76     }
77 
openResource(Context context, Uri uri)78     private static InputStream openResource(Context context, Uri uri) {
79         assert uri.getScheme().equals(FILE_SCHEME);
80         assert uri.getPath() != null;
81         assert uri.getPath().startsWith(nativeGetAndroidResourcePath());
82         // The path must be of the form "/android_res/asset_type/asset_name.ext".
83         List<String> pathSegments = uri.getPathSegments();
84         if (pathSegments.size() != 3) {
85             Log.e(TAG, "Incorrect resource path: " + uri);
86             return null;
87         }
88         String assetPath = pathSegments.get(0);
89         String assetType = pathSegments.get(1);
90         String assetName = pathSegments.get(2);
91         if (!("/" + assetPath + "/").equals(nativeGetAndroidResourcePath())) {
92             Log.e(TAG, "Resource path does not start with " + nativeGetAndroidResourcePath() +
93                   ": " + uri);
94             return null;
95         }
96         // Drop the file extension.
97         assetName = assetName.split("\\.")[0];
98         try {
99             // Use the application context for resolving the resource package name so that we do
100             // not use the browser's own resources. Note that if 'context' here belongs to the
101             // test suite, it does not have a separate application context. In that case we use
102             // the original context object directly.
103             if (context.getApplicationContext() != null) {
104                 context = context.getApplicationContext();
105             }
106             int fieldId = getFieldId(context, assetType, assetName);
107             int valueType = getValueType(context, fieldId);
108             if (valueType == TypedValue.TYPE_STRING) {
109                 return context.getResources().openRawResource(fieldId);
110             } else {
111                 Log.e(TAG, "Asset not of type string: " + uri);
112                 return null;
113             }
114         } catch (ClassNotFoundException e) {
115             Log.e(TAG, "Unable to open resource URL: " + uri, e);
116             return null;
117         } catch (NoSuchFieldException e) {
118             Log.e(TAG, "Unable to open resource URL: " + uri, e);
119             return null;
120         } catch (IllegalAccessException e) {
121             Log.e(TAG, "Unable to open resource URL: " + uri, e);
122             return null;
123         }
124     }
125 
openAsset(Context context, Uri uri)126     private static InputStream openAsset(Context context, Uri uri) {
127         assert uri.getScheme().equals(FILE_SCHEME);
128         assert uri.getPath() != null;
129         assert uri.getPath().startsWith(nativeGetAndroidAssetPath());
130         String path = uri.getPath().replaceFirst(nativeGetAndroidAssetPath(), "");
131         try {
132             AssetManager assets = context.getAssets();
133             return assets.open(path, AssetManager.ACCESS_STREAMING);
134         } catch (IOException e) {
135             Log.e(TAG, "Unable to open asset URL: " + uri);
136             return null;
137         }
138     }
139 
openContent(Context context, Uri uri)140     private static InputStream openContent(Context context, Uri uri) {
141         assert uri.getScheme().equals(CONTENT_SCHEME);
142         try {
143             return context.getContentResolver().openInputStream(uri);
144         } catch (Exception e) {
145             Log.e(TAG, "Unable to open content URL: " + uri);
146             return null;
147         }
148     }
149 
150     /**
151      * Determine the mime type for an Android resource.
152      * @param context The context manager.
153      * @param stream The opened input stream which to examine.
154      * @param url The url from which the stream was opened.
155      * @return The mime type or null if the type is unknown.
156      */
157     @CalledByNative
getMimeType(Context context, InputStream stream, String url)158     public static String getMimeType(Context context, InputStream stream, String url) {
159         Uri uri = verifyUrl(url);
160         if (uri == null) {
161             return null;
162         }
163         try {
164             String path = uri.getPath();
165             // The content URL type can be queried directly.
166             if (uri.getScheme().equals(CONTENT_SCHEME)) {
167                 return context.getContentResolver().getType(uri);
168                 // Asset files may have a known extension.
169             } else if (uri.getScheme().equals(FILE_SCHEME) &&
170                        path.startsWith(nativeGetAndroidAssetPath())) {
171                 String mimeType = URLConnection.guessContentTypeFromName(path);
172                 if (mimeType != null) {
173                     return mimeType;
174                 }
175             }
176         } catch (Exception ex) {
177             Log.e(TAG, "Unable to get mime type" + url);
178             return null;
179         }
180         // Fall back to sniffing the type from the stream.
181         try {
182             return URLConnection.guessContentTypeFromStream(stream);
183         } catch (IOException e) {
184             return null;
185         }
186     }
187 
188     /**
189      * Make sure the given string URL is correctly formed and parse it into a Uri.
190      * @return a Uri instance, or null if the URL was invalid.
191      */
verifyUrl(String url)192     private static Uri verifyUrl(String url) {
193         if (url == null) {
194             return null;
195         }
196         Uri uri = Uri.parse(url);
197         if (uri == null) {
198             Log.e(TAG, "Malformed URL: " + url);
199             return null;
200         }
201         String path = uri.getPath();
202         if (path == null || path.length() == 0) {
203             Log.e(TAG, "URL does not have a path: " + url);
204             return null;
205         }
206         return uri;
207     }
208 
209     /**
210      * Set the context to be used for resolving resource queries.
211      * @param context Context to be used, or null for the default application
212      *                context.
213      */
setResourceContextForTesting(Context context)214     public static void setResourceContextForTesting(Context context) {
215         nativeSetResourceContextForTesting(context);
216     }
217 
nativeSetResourceContextForTesting(Context context)218     private static native void nativeSetResourceContextForTesting(Context context);
nativeGetAndroidAssetPath()219     private static native String nativeGetAndroidAssetPath();
nativeGetAndroidResourcePath()220     private static native String nativeGetAndroidResourcePath();
221 }
222