Home | History | Annotate | Download | only in native
      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
    318