Upstream version 6.35.131.0
[platform/framework/web/crosswalk.git] / src / xwalk / runtime / android / core / src / org / xwalk / core / AndroidProtocolHandler.java
1 // Copyright (c) 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.xwalk.core;
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 java.io.InputStream;
14 import java.io.IOException;
15 import java.net.URI;
16 import java.net.URISyntaxException;
17 import java.net.URLConnection;
18 import java.util.List;
19
20 import org.chromium.base.CalledByNativeUnchecked;
21 import org.chromium.base.JNINamespace;
22
23 /**
24  * Implements the Java side of Android URL protocol jobs.
25  * See android_protocol_handler.cc.
26  */
27 @JNINamespace("xwalk")
28 class AndroidProtocolHandler {
29     private static final String TAG = "AndroidProtocolHandler";
30
31     // Supported URL schemes. This needs to be kept in sync with
32     // clank/native/framework/chrome/url_request_android_job.cc.
33     public static final String FILE_SCHEME = "file";
34     private static final String CONTENT_SCHEME = "content";
35     public static final String APP_SCHEME = "app";
36     private static final String APP_SRC = "www";
37     private static final String SCHEME_SEPARATOR = "//";
38
39     /**
40      * Open an InputStream for an Android resource.
41      * @param context The context manager.
42      * @param url The url to load.
43      * @return An InputStream to the Android resource.
44      */
45     // TODO(bulach): this should have either a throw clause, or
46     // handle the exception in the java side rather than the native side.
47     @CalledByNativeUnchecked
48     public static InputStream open(Context context, String url) {
49         Uri uri = verifyUrl(url);
50         if (uri == null) {
51             return null;
52         }
53         String path = uri.getPath();
54         if (uri.getScheme().equals(FILE_SCHEME)) {
55             if (path.startsWith(nativeGetAndroidAssetPath())) {
56                 return openAsset(context, uri);
57             } else if (path.startsWith(nativeGetAndroidResourcePath())) {
58                 return openResource(context, uri);
59             }
60         } else if (uri.getScheme().equals(CONTENT_SCHEME)) {
61             return openContent(context, uri);
62         } else if (uri.getScheme().equals(APP_SCHEME)) {
63             // The host should be the same as the lower case of the package
64             // name, otherwise the resource request should be rejected.
65             if (!uri.getHost().equals(context.getPackageName().toLowerCase())) return null;
66
67             // path == "/" or path == ""
68             if (path.length() <= 1) return null;
69
70             return openAsset(context, appUriToFileUri(uri));
71         }
72
73         return null;
74     }
75
76     // Get the asset path of file:///android_asset/* url.
77     public static String getAssetPath(Uri uri) {
78         assert(uri.getScheme().equals(FILE_SCHEME));
79         assert(uri.getPath() != null);
80         assert(uri.getPath().startsWith(nativeGetAndroidAssetPath()));
81         String path = uri.getPath();
82         // Remove duplicate slashes and normalize the URL.
83         path = (new java.io.File(path)).getAbsolutePath();
84         return path.replaceFirst(nativeGetAndroidAssetPath(), "");
85     }
86
87     // Convert app uri to file uri to access the actual files in assets.
88     public static Uri appUriToFileUri(Uri uri) {
89         assert(uri.getScheme().equals(APP_SCHEME));
90         assert(uri.getPath() != null);
91
92         try {
93             URI fileUri = new URI(FILE_SCHEME, SCHEME_SEPARATOR +
94                 nativeGetAndroidAssetPath() + APP_SRC + uri.getPath(), null);
95             return Uri.parse(fileUri.normalize().toString());
96         } catch (URISyntaxException e) {
97             Log.e(TAG, "Unable to convert app URI to file URI: " + uri, e);
98             return null;
99         }
100     }
101
102     static String getUrlContent(Context context, String url) throws IOException {
103         InputStream stream = open(context, url);
104         if (stream == null) {
105             throw new RuntimeException("Failed to open the url: " + url);
106         }
107
108         String content = "";
109         try {
110             final int bufferSize = 1024;
111             byte[] buffer = new byte[bufferSize];
112             int actualSize = 0;
113             while ((actualSize = stream.read(buffer, 0, bufferSize)) > 0) {
114                 content += new String(buffer, 0, actualSize);
115             }
116         } finally {
117             stream.close();
118         }
119         return content;
120     }
121
122     private static int getFieldId(Context context, String assetType, String assetName)
123         throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
124         Class<?> d = context.getClassLoader()
125             .loadClass(context.getPackageName() + ".R$" + assetType);
126         java.lang.reflect.Field field = d.getField(assetName);
127         int id = field.getInt(null);
128         return id;
129     }
130
131     private static int getValueType(Context context, int field_id) {
132       TypedValue value = new TypedValue();
133       context.getResources().getValue(field_id, value, true);
134       return value.type;
135     }
136
137     private static InputStream openResource(Context context, Uri uri) {
138         assert(uri.getScheme().equals(FILE_SCHEME));
139         assert(uri.getPath() != null);
140         assert(uri.getPath().startsWith(nativeGetAndroidResourcePath()));
141         // The path must be of the form "/android_res/asset_type/asset_name.ext".
142         List<String> pathSegments = uri.getPathSegments();
143         if (pathSegments.size() != 3) {
144             Log.e(TAG, "Incorrect resource path: " + uri);
145             return null;
146         }
147         String assetPath = pathSegments.get(0);
148         String assetType = pathSegments.get(1);
149         String assetName = pathSegments.get(2);
150         if (!("/" + assetPath + "/").equals(nativeGetAndroidResourcePath())) {
151             Log.e(TAG, "Resource path does not start with " + nativeGetAndroidResourcePath() +
152                   ": " + uri);
153             return null;
154         }
155         // Drop the file extension.
156         assetName = assetName.split("\\.")[0];
157         try {
158             // Use the application context for resolving the resource package name so that we do
159             // not use the browser's own resources. Note that if 'context' here belongs to the
160             // test suite, it does not have a separate application context. In that case we use
161             // the original context object directly.
162             if (context.getApplicationContext() != null) {
163                 context = context.getApplicationContext();
164             }
165             int field_id = getFieldId(context, assetType, assetName);
166             int value_type = getValueType(context, field_id);
167             if (value_type == TypedValue.TYPE_STRING) {
168                 return context.getResources().openRawResource(field_id);
169             } else {
170                 Log.e(TAG, "Asset not of type string: " + uri);
171                 return null;
172             }
173         } catch (ClassNotFoundException e) {
174             Log.e(TAG, "Unable to open resource URL: " + uri, e);
175             return null;
176         } catch (NoSuchFieldException e) {
177             Log.e(TAG, "Unable to open resource URL: " + uri, e);
178             return null;
179         } catch (IllegalAccessException e) {
180             Log.e(TAG, "Unable to open resource URL: " + uri, e);
181             return null;
182         }
183     }
184
185     private static InputStream openAsset(Context context, Uri uri) {
186         try {
187             AssetManager assets = context.getAssets();
188             return assets.open(getAssetPath(uri), AssetManager.ACCESS_STREAMING);
189         } catch (IOException e) {
190             Log.e(TAG, "Unable to open asset URL: " + uri);
191             return null;
192         }
193     }
194
195     private static InputStream openContent(Context context, Uri uri) {
196         assert(uri.getScheme().equals(CONTENT_SCHEME));
197         try {
198             // We strip the query parameters before opening the stream to
199             // ensure that the URL we try to load exactly matches the URL
200             // we have permission to read.
201             Uri baseUri = stripQueryParameters(uri);
202             return context.getContentResolver().openInputStream(baseUri);
203         } catch (Exception e) {
204             Log.e(TAG, "Unable to open content URL: " + uri);
205             return null;
206         }
207     }
208
209     /**
210      * Determine the mime type for an Android resource.
211      * @param context The context manager.
212      * @param stream The opened input stream which to examine.
213      * @param url The url from which the stream was opened.
214      * @return The mime type or null if the type is unknown.
215      */
216     // TODO(bulach): this should have either a throw clause, or
217     // handle the exception in the java side rather than the native side.
218     @CalledByNativeUnchecked
219     public static String getMimeType(Context context, InputStream stream, String url) {
220         Uri uri = verifyUrl(url);
221         if (uri == null) {
222             return null;
223         }
224         String path = uri.getPath();
225         // The content URL type can be queried directly.
226         if (uri.getScheme().equals(CONTENT_SCHEME)) {
227             return context.getContentResolver().getType(uri);
228         // Asset files may have a known extension.
229         } else if (uri.getScheme().equals(APP_SCHEME) ||
230                    uri.getScheme().equals(FILE_SCHEME) &&
231                    path.startsWith(nativeGetAndroidAssetPath())) {
232             String mimeType = URLConnection.guessContentTypeFromName(path);
233             if (mimeType != null) {
234                 return mimeType;
235             }
236         }
237         // Fall back to sniffing the type from the stream.
238         try {
239             return URLConnection.guessContentTypeFromStream(stream);
240         } catch (IOException e) {
241             return null;
242         }
243     }
244
245     /**
246      * Get the package name of the current Activity.
247      * @param context The context manager.
248      * @return Package name.
249      */
250     @CalledByNativeUnchecked
251     public static String getPackageName(Context context) {
252         // Make sure the context is the application context.
253         // Or it will get the wrong package name in shared mode.
254         return context.getPackageName();
255     }
256
257     /**
258      * Make sure the given string URL is correctly formed and parse it into a Uri.
259      * @return a Uri instance, or null if the URL was invalid.
260      */
261     private static Uri verifyUrl(String url) {
262         if (url == null) {
263             return null;
264         }
265         Uri uri = Uri.parse(url);
266         if (uri == null) {
267             Log.e(TAG, "Malformed URL: " + url);
268             return null;
269         }
270         String path = uri.getPath();
271         if (path == null || path.length() == 0) {
272             Log.e(TAG, "URL does not have a path: " + url);
273             return null;
274         }
275         return uri;
276     }
277
278     /**
279      * Remove query parameters from a Uri.
280      * @param uri The input uri.
281      * @return The given uri without query parameters.
282      */
283     private static Uri stripQueryParameters(Uri uri) {
284         assert(uri.getAuthority() != null);
285         assert(uri.getPath() != null);
286         Uri.Builder builder = new Uri.Builder();
287         builder.scheme(uri.getScheme());
288         builder.encodedAuthority(uri.getAuthority());
289         builder.encodedPath(uri.getPath());
290         return builder.build();
291     }
292
293     /**
294      * Set the context to be used for resolving resource queries.
295      * @param context Context to be used, or null for the default application
296      *                context.
297      */
298     public static void setResourceContextForTesting(Context context) {
299         nativeSetResourceContextForTesting(context);
300     }
301
302     private static native void nativeSetResourceContextForTesting(Context context);
303     private static native String nativeGetAndroidAssetPath();
304     private static native String nativeGetAndroidResourcePath();
305 }