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 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 void AppendResponseHeaders( 81 JNIEnv* env, 82 net::HttpResponseHeaders* headers) OVERRIDE; 83 84 virtual ~AndroidStreamReaderURLRequestJobDelegateImpl(); 85 }; 86 87 class 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 96 class 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. 112 class ContentSchemeRequestInterceptor : public AndroidRequestInterceptorBase { 113 public: 114 ContentSchemeRequestInterceptor(); 115 virtual bool ShouldHandleRequest( 116 const net::URLRequest* request) const OVERRIDE; 117 }; 118 119 static 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 132 AndroidStreamReaderURLRequestJobDelegateImpl:: 133 AndroidStreamReaderURLRequestJobDelegateImpl() {} 134 135 AndroidStreamReaderURLRequestJobDelegateImpl:: 136 ~AndroidStreamReaderURLRequestJobDelegateImpl() { 137 } 138 139 scoped_ptr<InputStream> 140 AndroidStreamReaderURLRequestJobDelegateImpl::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 161 void AndroidStreamReaderURLRequestJobDelegateImpl::OnInputStreamOpenFailed( 162 net::URLRequest* request, 163 bool* restart) { 164 DCHECK(!HasRequestPreviouslyFailed(request)); 165 MarkRequestAsFailed(request); 166 *restart = true; 167 } 168 169 bool 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 196 bool 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 205 void AndroidStreamReaderURLRequestJobDelegateImpl::AppendResponseHeaders( 206 JNIEnv* env, 207 net::HttpResponseHeaders* headers) { 208 // no-op 209 } 210 211 // AndroidRequestInterceptorBase ---------------------------------------------- 212 213 net::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 243 AssetFileRequestInterceptor::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 252 AssetFileRequestInterceptor::~AssetFileRequestInterceptor() { 253 } 254 255 bool 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 271 ContentSchemeRequestInterceptor::ContentSchemeRequestInterceptor() { 272 } 273 274 bool ContentSchemeRequestInterceptor::ShouldHandleRequest( 275 const net::URLRequest* request) const { 276 return request->url().SchemeIs(android_webview::kContentScheme); 277 } 278 279 } // namespace 280 281 namespace android_webview { 282 283 bool RegisterAndroidProtocolHandler(JNIEnv* env) { 284 return RegisterNativesImpl(env); 285 } 286 287 // static 288 scoped_ptr<net::URLRequestInterceptor> 289 CreateContentSchemeRequestInterceptor() { 290 return make_scoped_ptr<net::URLRequestInterceptor>( 291 new ContentSchemeRequestInterceptor()); 292 } 293 294 // static 295 scoped_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. 307 static 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 316 static jstring GetAndroidAssetPath(JNIEnv* env, jclass /*clazz*/) { 317 // OK to release, JNI binding. 318 return ConvertUTF8ToJavaString( 319 env, android_webview::kAndroidAssetPath).Release(); 320 } 321 322 static 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