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