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