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