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