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