Upstream version 11.39.256.0
[platform/framework/web/crosswalk.git] / src / xwalk / runtime / browser / android / net / android_protocol_handler.cc
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 #include "xwalk/runtime/browser/android/net/android_protocol_handler.h"
6
7 #include <string>
8
9 #include "base/android/jni_android.h"
10 #include "base/android/jni_string.h"
11 #include "base/android/jni_weak_ref.h"
12 #include "base/strings/string_util.h"
13 #include "content/public/common/url_constants.h"
14 #include "jni/AndroidProtocolHandler_jni.h"
15 #include "net/base/io_buffer.h"
16 #include "net/base/mime_util.h"
17 #include "net/base/net_errors.h"
18 #include "net/base/net_util.h"
19 #include "net/http/http_util.h"
20 #include "net/url_request/url_request.h"
21 #include "net/url_request/url_request_intercepting_job_factory.h"
22 #include "net/url_request/url_request_interceptor.h"
23 #include "url/gurl.h"
24 #include "xwalk/runtime/browser/android/net/android_stream_reader_url_request_job.h"
25 #include "xwalk/runtime/browser/android/net/input_stream_impl.h"
26 #include "xwalk/runtime/browser/android/net/url_constants.h"
27 #include "xwalk/runtime/browser/xwalk_browser_context.h"
28 #include "xwalk/runtime/browser/xwalk_runner.h"
29
30 using base::android::AttachCurrentThread;
31 using base::android::ClearException;
32 using base::android::ConvertUTF8ToJavaString;
33 using base::android::ScopedJavaGlobalRef;
34 using base::android::ScopedJavaLocalRef;
35 using xwalk::InputStream;
36 using xwalk::InputStreamImpl;
37
38 namespace {
39
40 // Override resource context for reading resource and asset files. Used for
41 // testing.
42 JavaObjectWeakGlobalRef* g_resource_context = NULL;
43
44 void ResetResourceContext(JavaObjectWeakGlobalRef* ref) {
45   if (g_resource_context)
46     delete g_resource_context;
47
48   g_resource_context = ref;
49 }
50
51 void* kPreviouslyFailedKey = &kPreviouslyFailedKey;
52
53 void MarkRequestAsFailed(net::URLRequest* request) {
54   request->SetUserData(kPreviouslyFailedKey,
55                        new base::SupportsUserData::Data());
56 }
57
58 bool HasRequestPreviouslyFailed(net::URLRequest* request) {
59   return request->GetUserData(kPreviouslyFailedKey) != NULL;
60 }
61
62 class AndroidStreamReaderURLRequestJobDelegateImpl
63     : public AndroidStreamReaderURLRequestJob::Delegate {
64  public:
65   AndroidStreamReaderURLRequestJobDelegateImpl();
66
67   virtual scoped_ptr<InputStream> OpenInputStream(
68       JNIEnv* env,
69       const GURL& url) OVERRIDE;
70
71   virtual void OnInputStreamOpenFailed(net::URLRequest* request,
72                                        bool* restart) OVERRIDE;
73
74   virtual bool GetMimeType(JNIEnv* env,
75                            net::URLRequest* request,
76                            InputStream* stream,
77                            std::string* mime_type) OVERRIDE;
78
79   virtual bool GetCharset(JNIEnv* env,
80                           net::URLRequest* request,
81                           InputStream* stream,
82                           std::string* charset) OVERRIDE;
83
84   virtual bool GetPackageName(JNIEnv* env,
85                               std::string* name) OVERRIDE;
86
87   virtual ~AndroidStreamReaderURLRequestJobDelegateImpl();
88 };
89
90 class AndroidRequestInterceptorBase : public net::URLRequestInterceptor {
91  public:
92   virtual net::URLRequestJob* MaybeInterceptRequest(
93       net::URLRequest* request,
94       net::NetworkDelegate* network_delegate) const OVERRIDE;
95
96   virtual bool ShouldHandleRequest(const net::URLRequest* request) const = 0;
97 };
98
99 class AssetFileRequestInterceptor : public AndroidRequestInterceptorBase {
100  public:
101   AssetFileRequestInterceptor();
102
103   virtual ~AssetFileRequestInterceptor() OVERRIDE;
104   virtual bool ShouldHandleRequest(
105       const net::URLRequest* request) const OVERRIDE;
106
107  private:
108   // file:///android_asset/
109   const std::string asset_prefix_;
110   // file:///android_res/
111   const std::string resource_prefix_;
112 };
113
114 // Protocol handler for app:// scheme requests.
115 class AppSchemeRequestInterceptor : public AndroidRequestInterceptorBase {
116  public:
117   AppSchemeRequestInterceptor();
118     virtual bool ShouldHandleRequest(
119       const net::URLRequest* request) const OVERRIDE;
120 };
121
122 // Protocol handler for content:// scheme requests.
123 class ContentSchemeRequestInterceptor : public AndroidRequestInterceptorBase {
124  public:
125   ContentSchemeRequestInterceptor();
126   virtual bool ShouldHandleRequest(
127       const net::URLRequest* request) const OVERRIDE;
128 };
129
130 static ScopedJavaLocalRef<jobject> GetResourceContext(JNIEnv* env) {
131   if (g_resource_context)
132     return g_resource_context->get(env);
133   ScopedJavaLocalRef<jobject> context;
134   // We have to reset as GetApplicationContext() returns a jobject with a
135   // global ref. The constructor that takes a jobject would expect a local ref
136   // and would assert.
137   context.Reset(env, base::android::GetApplicationContext());
138   return context;
139 }
140
141 // AndroidStreamReaderURLRequestJobDelegateImpl -------------------------------
142
143 AndroidStreamReaderURLRequestJobDelegateImpl::
144     AndroidStreamReaderURLRequestJobDelegateImpl() {}
145
146 AndroidStreamReaderURLRequestJobDelegateImpl::
147 ~AndroidStreamReaderURLRequestJobDelegateImpl() {
148 }
149
150 scoped_ptr<InputStream>
151 AndroidStreamReaderURLRequestJobDelegateImpl::OpenInputStream(
152     JNIEnv* env, const GURL& url) {
153   DCHECK(url.is_valid());
154   DCHECK(env);
155
156   // Open the input stream.
157   ScopedJavaLocalRef<jstring> jurl =
158       ConvertUTF8ToJavaString(env, url.spec());
159   ScopedJavaLocalRef<jobject> stream =
160       xwalk::Java_AndroidProtocolHandler_open(
161           env,
162           GetResourceContext(env).obj(),
163           jurl.obj());
164
165   // Check and clear pending exceptions.
166   if (ClearException(env) || stream.is_null()) {
167     DLOG(ERROR) << "Unable to open input stream for Android URL";
168     return scoped_ptr<InputStream>();
169   }
170   return make_scoped_ptr<InputStream>(new InputStreamImpl(stream));
171 }
172
173 void AndroidStreamReaderURLRequestJobDelegateImpl::OnInputStreamOpenFailed(
174     net::URLRequest* request,
175     bool* restart) {
176   DCHECK(!HasRequestPreviouslyFailed(request));
177   MarkRequestAsFailed(request);
178   *restart = true;
179 }
180
181 bool AndroidStreamReaderURLRequestJobDelegateImpl::GetMimeType(
182     JNIEnv* env,
183     net::URLRequest* request,
184     xwalk::InputStream* stream,
185     std::string* mime_type) {
186   DCHECK(env);
187   DCHECK(request);
188   DCHECK(mime_type);
189
190   // Query the mime type from the Java side. It is possible for the query to
191   // fail, as the mime type cannot be determined for all supported schemes.
192   ScopedJavaLocalRef<jstring> url =
193       ConvertUTF8ToJavaString(env, request->url().spec());
194   const InputStreamImpl* stream_impl =
195       InputStreamImpl::FromInputStream(stream);
196   ScopedJavaLocalRef<jstring> returned_type =
197       xwalk::Java_AndroidProtocolHandler_getMimeType(
198           env,
199           GetResourceContext(env).obj(),
200           stream_impl->jobj(), url.obj());
201   if (ClearException(env) || returned_type.is_null())
202     return false;
203
204   *mime_type = base::android::ConvertJavaStringToUTF8(returned_type);
205   return true;
206 }
207
208 bool AndroidStreamReaderURLRequestJobDelegateImpl::GetCharset(
209     JNIEnv* env,
210     net::URLRequest* request,
211     xwalk::InputStream* stream,
212     std::string* charset) {
213   // TODO(shouqun): We should probably be getting this from the managed side.
214   return false;
215 }
216
217 bool AndroidStreamReaderURLRequestJobDelegateImpl::GetPackageName(
218     JNIEnv* env,
219     std::string* name) {
220   ScopedJavaLocalRef<jstring> returned_name =
221       xwalk::Java_AndroidProtocolHandler_getPackageName(
222           env,
223           GetResourceContext(env).obj());
224
225   if (ClearException(env) || returned_name.is_null())
226     return false;
227
228   *name = base::android::ConvertJavaStringToUTF8(returned_name);
229   return true;
230 }
231
232 // AndroidRequestInterceptorBase ----------------------------------------------
233
234 net::URLRequestJob* AndroidRequestInterceptorBase::MaybeInterceptRequest(
235     net::URLRequest* request,
236     net::NetworkDelegate* network_delegate) const {
237   if (!ShouldHandleRequest(request))
238     return NULL;
239
240   // For WebViewClassic compatibility this job can only accept URLs that can be
241   // opened. URLs that cannot be opened should be resolved by the next handler.
242   //
243   // If a request is initially handled here but the job fails due to it being
244   // unable to open the InputStream for that request the request is marked as
245   // previously failed and restarted.
246   // Restarting a request involves creating a new job for that request. This
247   // handler will ignore requests know to have previously failed to 1) prevent
248   // an infinite loop, 2) ensure that the next handler in line gets the
249   // opportunity to create a job for the request.
250   if (HasRequestPreviouslyFailed(request))
251     return NULL;
252
253   scoped_ptr<AndroidStreamReaderURLRequestJobDelegateImpl> reader_delegate(
254       new AndroidStreamReaderURLRequestJobDelegateImpl());
255
256   xwalk::XWalkBrowserContext* browser_context =
257       xwalk::XWalkRunner::GetInstance()->browser_context();
258   std::string content_security_policy = browser_context->GetCSPString();
259
260   return new AndroidStreamReaderURLRequestJob(
261       request,
262       network_delegate,
263       reader_delegate.PassAs<AndroidStreamReaderURLRequestJob::Delegate>(),
264       content_security_policy);
265 }
266
267 // AssetFileRequestInterceptor ----------------------------------------------
268
269 AssetFileRequestInterceptor::AssetFileRequestInterceptor()
270     : asset_prefix_(std::string(url::kFileScheme) +
271                     std::string(url::kStandardSchemeSeparator) +
272                     xwalk::kAndroidAssetPath),
273       resource_prefix_(std::string(url::kFileScheme) +
274                        std::string(url::kStandardSchemeSeparator) +
275                        xwalk::kAndroidResourcePath) {
276 }
277
278 AssetFileRequestInterceptor::~AssetFileRequestInterceptor() {
279 }
280
281 bool AssetFileRequestInterceptor::ShouldHandleRequest(
282     const net::URLRequest* request) const {
283   if (!request->url().SchemeIsFile())
284     return false;
285
286   const std::string& url = request->url().spec();
287   if (!StartsWithASCII(url, asset_prefix_, /*case_sensitive=*/ true) &&
288       !StartsWithASCII(url, resource_prefix_, /*case_sensitive=*/ true)) {
289     return false;
290   }
291
292   return true;
293 }
294
295 // ContentSchemeRequestInterceptor
296 ContentSchemeRequestInterceptor::ContentSchemeRequestInterceptor() {
297 }
298
299 bool ContentSchemeRequestInterceptor::ShouldHandleRequest(
300     const net::URLRequest* request) const {
301   return request->url().SchemeIs(xwalk::kContentScheme);
302 }
303
304 // AppSchemeRequestInterceptor
305 AppSchemeRequestInterceptor::AppSchemeRequestInterceptor() {
306 }
307
308 bool AppSchemeRequestInterceptor::ShouldHandleRequest(
309     const net::URLRequest* request) const {
310   return request->url().SchemeIs(xwalk::kAppScheme);
311 }
312
313 }  // namespace
314
315 namespace xwalk {
316
317 bool RegisterAndroidProtocolHandler(JNIEnv* env) {
318   return RegisterNativesImpl(env);
319 }
320
321 // static
322 scoped_ptr<net::URLRequestInterceptor>
323 CreateContentSchemeRequestInterceptor() {
324   return make_scoped_ptr<net::URLRequestInterceptor>(
325       new ContentSchemeRequestInterceptor());
326 }
327
328 // static
329 scoped_ptr<net::URLRequestInterceptor> CreateAssetFileRequestInterceptor() {
330   return scoped_ptr<net::URLRequestInterceptor>(
331       new AssetFileRequestInterceptor());
332 }
333
334 // static
335 scoped_ptr<net::URLRequestInterceptor> CreateAppSchemeRequestInterceptor() {
336   return make_scoped_ptr<net::URLRequestInterceptor>(
337       new AppSchemeRequestInterceptor());
338 }
339
340
341 // Set a context object to be used for resolving resource queries. This can
342 // be used to override the default application context and redirect all
343 // resource queries to a specific context object, e.g., for the purposes of
344 // testing.
345 //
346 // |context| should be a android.content.Context instance or NULL to enable
347 // the use of the standard application context.
348 static void SetResourceContextForTesting(JNIEnv* env, jclass /*clazz*/,
349                                          jobject context) {
350   if (context) {
351     ResetResourceContext(new JavaObjectWeakGlobalRef(env, context));
352   } else {
353     ResetResourceContext(NULL);
354   }
355 }
356
357 static jstring GetAndroidAssetPath(JNIEnv* env, jclass /*clazz*/) {
358   // OK to release, JNI binding.
359   return ConvertUTF8ToJavaString(
360       env, xwalk::kAndroidAssetPath).Release();
361 }
362
363 static jstring GetAndroidResourcePath(JNIEnv* env, jclass /*clazz*/) {
364   // OK to release, JNI binding.
365   return ConvertUTF8ToJavaString(
366       env, xwalk::kAndroidResourcePath).Release();
367 }
368
369 }  // namespace xwalk