// Copyright (c) 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "android_webview/browser/aw_contents_client_bridge.h" #include #include #include "android_webview/browser_jni_headers/AwContentsClientBridge_jni.h" #include "android_webview/common/devtools_instrumentation.h" #include "android_webview/grit/components_strings.h" #include "base/android/jni_android.h" #include "base/android/jni_array.h" #include "base/android/jni_string.h" #include "base/memory/raw_ptr.h" #include "base/memory/ref_counted.h" #include "base/task/current_thread.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/client_certificate_delegate.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/web_contents.h" #include "net/cert/x509_certificate.h" #include "net/cert/x509_util.h" #include "net/http/http_response_headers.h" #include "net/ssl/ssl_cert_request_info.h" #include "net/ssl/ssl_client_cert_type.h" #include "net/ssl/ssl_platform_key_android.h" #include "net/ssl/ssl_private_key.h" #include "ui/base/l10n/l10n_util.h" #include "url/gurl.h" using base::android::AttachCurrentThread; using base::android::ConvertJavaStringToUTF16; using base::android::ConvertUTF8ToJavaString; using base::android::ConvertUTF16ToJavaString; using base::android::HasException; using base::android::JavaRef; using base::android::ScopedJavaLocalRef; using base::android::ToJavaArrayOfStrings; using content::BrowserThread; using content::WebContents; using std::vector; namespace android_webview { namespace { const void* const kAwContentsClientBridge = &kAwContentsClientBridge; // This class is invented so that the UserData registry that we inject the // AwContentsClientBridge object does not own and destroy it. class UserData : public base::SupportsUserData::Data { public: static AwContentsClientBridge* GetContents( content::WebContents* web_contents) { if (!web_contents) return NULL; UserData* data = static_cast( web_contents->GetUserData(kAwContentsClientBridge)); return data ? data->contents_.get() : NULL; } explicit UserData(AwContentsClientBridge* ptr) : contents_(ptr) {} UserData(const UserData&) = delete; UserData& operator=(const UserData&) = delete; private: raw_ptr contents_; }; } // namespace AwContentsClientBridge::HttpErrorInfo::HttpErrorInfo() : status_code(0) {} AwContentsClientBridge::HttpErrorInfo::~HttpErrorInfo() {} // static void AwContentsClientBridge::Associate(WebContents* web_contents, AwContentsClientBridge* handler) { web_contents->SetUserData(kAwContentsClientBridge, std::make_unique(handler)); } // static void AwContentsClientBridge::Dissociate(WebContents* web_contents) { web_contents->RemoveUserData(kAwContentsClientBridge); } // static AwContentsClientBridge* AwContentsClientBridge::FromWebContents( WebContents* web_contents) { return UserData::GetContents(web_contents); } AwContentsClientBridge::AwContentsClientBridge(JNIEnv* env, const JavaRef& obj) : java_ref_(env, obj) { DCHECK(obj); Java_AwContentsClientBridge_setNativeContentsClientBridge( env, obj, reinterpret_cast(this)); } AwContentsClientBridge::~AwContentsClientBridge() { JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef obj = java_ref_.get(env); if (obj) { // Clear the weak reference from the java peer to the native object since // it is possible that java object lifetime can exceed the AwContens. Java_AwContentsClientBridge_setNativeContentsClientBridge(env, obj, 0); } } void AwContentsClientBridge::AllowCertificateError(int cert_error, net::X509Certificate* cert, const GURL& request_url, CertErrorCallback callback, bool* cancel_request) { DCHECK_CURRENTLY_ON(BrowserThread::UI); JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef obj = java_ref_.get(env); if (!obj) return; base::StringPiece der_string = net::x509_util::CryptoBufferAsStringPiece(cert->cert_buffer()); ScopedJavaLocalRef jcert = base::android::ToJavaByteArray( env, reinterpret_cast(der_string.data()), der_string.length()); ScopedJavaLocalRef jurl( ConvertUTF8ToJavaString(env, request_url.spec())); // We need to add the callback before making the call to java side, // as it may do a synchronous callback prior to returning. int request_id = pending_cert_error_callbacks_.Add( std::make_unique(std::move(callback))); *cancel_request = !Java_AwContentsClientBridge_allowCertificateError( env, obj, cert_error, jcert, jurl, request_id); // if the request is cancelled, then cancel the stored callback if (*cancel_request) { pending_cert_error_callbacks_.Remove(request_id); } } void AwContentsClientBridge::ProceedSslError(JNIEnv* env, const JavaRef& obj, jboolean proceed, jint id) { DCHECK_CURRENTLY_ON(BrowserThread::UI); CertErrorCallback* callback = pending_cert_error_callbacks_.Lookup(id); if (!callback || callback->is_null()) { LOG(WARNING) << "Ignoring unexpected ssl error proceed callback"; return; } std::move(*callback).Run( proceed ? content::CERTIFICATE_REQUEST_RESULT_TYPE_CONTINUE : content::CERTIFICATE_REQUEST_RESULT_TYPE_CANCEL); pending_cert_error_callbacks_.Remove(id); } // This method is inspired by SelectClientCertificate() in // components/browser_ui/client_certificate/android/ // ssl_client_certificate_request.cc void AwContentsClientBridge::SelectClientCertificate( net::SSLCertRequestInfo* cert_request_info, std::unique_ptr delegate) { DCHECK_CURRENTLY_ON(BrowserThread::UI); JNIEnv* env = base::android::AttachCurrentThread(); ScopedJavaLocalRef obj = java_ref_.get(env); if (!obj) return; // Build the |key_types| JNI parameter, as a String[] std::vector key_types; for (size_t i = 0; i < cert_request_info->cert_key_types.size(); ++i) { switch (cert_request_info->cert_key_types[i]) { case net::CLIENT_CERT_RSA_SIGN: key_types.push_back("RSA"); break; case net::CLIENT_CERT_ECDSA_SIGN: key_types.push_back("ECDSA"); break; default: // Ignore unknown types. break; } } ScopedJavaLocalRef key_types_ref = base::android::ToJavaArrayOfStrings(env, key_types); if (!key_types_ref) { LOG(ERROR) << "Could not create key types array (String[])"; return; } // Build the |encoded_principals| JNI parameter, as a byte[][] ScopedJavaLocalRef principals_ref = base::android::ToJavaArrayOfByteArray( env, cert_request_info->cert_authorities); if (!principals_ref) { LOG(ERROR) << "Could not create principals array (byte[][])"; return; } // Build the |host_name| and |port| JNI parameters, as a String and // a jint. ScopedJavaLocalRef host_name_ref = base::android::ConvertUTF8ToJavaString( env, cert_request_info->host_and_port.host()); int request_id = pending_client_cert_request_delegates_.Add(std::move(delegate)); Java_AwContentsClientBridge_selectClientCertificate( env, obj, request_id, key_types_ref, principals_ref, host_name_ref, cert_request_info->host_and_port.port()); } // This method is inspired by OnSystemRequestCompletion() in // components/browser_ui/client_certificate/android/ // ssl_client_certificate_request.cc void AwContentsClientBridge::ProvideClientCertificateResponse( JNIEnv* env, const JavaRef& obj, int request_id, const JavaRef& encoded_chain_ref, const JavaRef& private_key_ref) { DCHECK_CURRENTLY_ON(BrowserThread::UI); std::unique_ptr delegate = pending_client_cert_request_delegates_.Replace(request_id, nullptr); pending_client_cert_request_delegates_.Remove(request_id); DCHECK(delegate); if (!encoded_chain_ref || !private_key_ref) { LOG(ERROR) << "No client certificate selected"; delegate->ContinueWithCertificate(nullptr, nullptr); return; } // Convert the encoded chain to a vector of strings. std::vector encoded_chain_strings; if (encoded_chain_ref) { base::android::JavaArrayOfByteArrayToStringVector(env, encoded_chain_ref, &encoded_chain_strings); } std::vector encoded_chain; for (size_t i = 0; i < encoded_chain_strings.size(); ++i) encoded_chain.push_back(encoded_chain_strings[i]); // Create the X509Certificate object from the encoded chain. scoped_refptr client_cert( net::X509Certificate::CreateFromDERCertChain(encoded_chain)); if (!client_cert.get()) { LOG(ERROR) << "Could not decode client certificate chain"; return; } // Create an SSLPrivateKey wrapper for the private key JNI reference. scoped_refptr private_key = net::WrapJavaPrivateKey(client_cert.get(), private_key_ref); if (!private_key) { LOG(ERROR) << "Could not create OpenSSL wrapper for private key"; return; } delegate->ContinueWithCertificate(std::move(client_cert), std::move(private_key)); } void AwContentsClientBridge::RunJavaScriptDialog( content::JavaScriptDialogType dialog_type, const GURL& origin_url, const std::u16string& message_text, const std::u16string& default_prompt_text, content::JavaScriptDialogManager::DialogClosedCallback callback) { DCHECK_CURRENTLY_ON(BrowserThread::UI); JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef obj = java_ref_.get(env); if (!obj) { std::move(callback).Run(false, std::u16string()); return; } int callback_id = pending_js_dialog_callbacks_.Add( std::make_unique( std::move(callback))); ScopedJavaLocalRef jurl( ConvertUTF8ToJavaString(env, origin_url.spec())); ScopedJavaLocalRef jmessage( ConvertUTF16ToJavaString(env, message_text)); switch (dialog_type) { case content::JAVASCRIPT_DIALOG_TYPE_ALERT: { devtools_instrumentation::ScopedEmbedderCallbackTask("onJsAlert"); Java_AwContentsClientBridge_handleJsAlert(env, obj, jurl, jmessage, callback_id); break; } case content::JAVASCRIPT_DIALOG_TYPE_CONFIRM: { devtools_instrumentation::ScopedEmbedderCallbackTask("onJsConfirm"); Java_AwContentsClientBridge_handleJsConfirm(env, obj, jurl, jmessage, callback_id); break; } case content::JAVASCRIPT_DIALOG_TYPE_PROMPT: { ScopedJavaLocalRef jdefault_value( ConvertUTF16ToJavaString(env, default_prompt_text)); devtools_instrumentation::ScopedEmbedderCallbackTask("onJsPrompt"); Java_AwContentsClientBridge_handleJsPrompt(env, obj, jurl, jmessage, jdefault_value, callback_id); break; } default: NOTREACHED(); } } void AwContentsClientBridge::RunBeforeUnloadDialog( const GURL& origin_url, content::JavaScriptDialogManager::DialogClosedCallback callback) { DCHECK_CURRENTLY_ON(BrowserThread::UI); JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef obj = java_ref_.get(env); if (!obj) { std::move(callback).Run(false, std::u16string()); return; } const std::u16string message_text = l10n_util::GetStringUTF16(IDS_BEFOREUNLOAD_MESSAGEBOX_MESSAGE); int callback_id = pending_js_dialog_callbacks_.Add( std::make_unique( std::move(callback))); ScopedJavaLocalRef jurl( ConvertUTF8ToJavaString(env, origin_url.spec())); ScopedJavaLocalRef jmessage( ConvertUTF16ToJavaString(env, message_text)); devtools_instrumentation::ScopedEmbedderCallbackTask("onJsBeforeUnload"); Java_AwContentsClientBridge_handleJsBeforeUnload(env, obj, jurl, jmessage, callback_id); } bool AwContentsClientBridge::ShouldOverrideUrlLoading( const std::u16string& url, bool has_user_gesture, bool is_redirect, bool is_outermost_main_frame, bool* ignore_navigation) { *ignore_navigation = false; JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef obj = java_ref_.get(env); if (!obj) return true; ScopedJavaLocalRef jurl = ConvertUTF16ToJavaString(env, url); devtools_instrumentation::ScopedEmbedderCallbackTask( "shouldOverrideUrlLoading"); *ignore_navigation = Java_AwContentsClientBridge_shouldOverrideUrlLoading( env, obj, jurl, has_user_gesture, is_redirect, is_outermost_main_frame); if (HasException(env)) { // Tell the chromium message loop to not perform any tasks after the current // one - we want to make sure we return to Java cleanly without first making // any new JNI calls. base::CurrentUIThread::Get()->Abort(); // If we crashed we don't want to continue the navigation. *ignore_navigation = true; return false; } return true; } bool AwContentsClientBridge::SendBrowseIntent(const std::u16string& url) { JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef obj = java_ref_.get(env); if (!obj) return false; ScopedJavaLocalRef jurl = ConvertUTF16ToJavaString(env, url); return Java_AwContentsClientBridge_sendBrowseIntent(env, obj, jurl); } void AwContentsClientBridge::NewDownload(const GURL& url, const std::string& user_agent, const std::string& content_disposition, const std::string& mime_type, int64_t content_length) { DCHECK_CURRENTLY_ON(BrowserThread::UI); JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef obj = java_ref_.get(env); if (!obj) return; ScopedJavaLocalRef jstring_url = ConvertUTF8ToJavaString(env, url.spec()); ScopedJavaLocalRef jstring_user_agent = ConvertUTF8ToJavaString(env, user_agent); ScopedJavaLocalRef jstring_content_disposition = ConvertUTF8ToJavaString(env, content_disposition); ScopedJavaLocalRef jstring_mime_type = ConvertUTF8ToJavaString(env, mime_type); Java_AwContentsClientBridge_newDownload( env, obj, jstring_url, jstring_user_agent, jstring_content_disposition, jstring_mime_type, content_length); } void AwContentsClientBridge::NewLoginRequest(const std::string& realm, const std::string& account, const std::string& args) { DCHECK_CURRENTLY_ON(BrowserThread::UI); JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef obj = java_ref_.get(env); if (!obj) return; ScopedJavaLocalRef jrealm = ConvertUTF8ToJavaString(env, realm); ScopedJavaLocalRef jargs = ConvertUTF8ToJavaString(env, args); ScopedJavaLocalRef jaccount; if (!account.empty()) jaccount = ConvertUTF8ToJavaString(env, account); Java_AwContentsClientBridge_newLoginRequest(env, obj, jrealm, jaccount, jargs); } void AwContentsClientBridge::OnReceivedError( const AwWebResourceRequest& request, int error_code, bool safebrowsing_hit, bool should_omit_notifications_for_safebrowsing_hit) { DCHECK(request.is_renderer_initiated.has_value()); DCHECK_CURRENTLY_ON(BrowserThread::UI); JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef obj = java_ref_.get(env); if (!obj) return; ScopedJavaLocalRef jstring_description = ConvertUTF8ToJavaString(env, net::ErrorToString(error_code)); AwWebResourceRequest::AwJavaWebResourceRequest java_web_resource_request; AwWebResourceRequest::ConvertToJava(env, request, &java_web_resource_request); Java_AwContentsClientBridge_onReceivedError( env, obj, java_web_resource_request.jurl, request.is_outermost_main_frame, request.has_user_gesture, *request.is_renderer_initiated, java_web_resource_request.jmethod, java_web_resource_request.jheader_names, java_web_resource_request.jheader_values, error_code, jstring_description, safebrowsing_hit, should_omit_notifications_for_safebrowsing_hit); } void AwContentsClientBridge::OnSafeBrowsingHit( const AwWebResourceRequest& request, const safe_browsing::SBThreatType& threat_type, SafeBrowsingActionCallback callback) { int request_id = safe_browsing_callbacks_.Add( std::make_unique(std::move(callback))); DCHECK_CURRENTLY_ON(BrowserThread::UI); JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef obj = java_ref_.get(env); if (!obj) return; AwWebResourceRequest::AwJavaWebResourceRequest java_web_resource_request; AwWebResourceRequest::ConvertToJava(env, request, &java_web_resource_request); Java_AwContentsClientBridge_onSafeBrowsingHit( env, obj, java_web_resource_request.jurl, request.is_outermost_main_frame, request.has_user_gesture, java_web_resource_request.jmethod, java_web_resource_request.jheader_names, java_web_resource_request.jheader_values, static_cast(threat_type), request_id); } void AwContentsClientBridge::OnReceivedHttpError( const AwWebResourceRequest& request, std::unique_ptr http_error_info) { DCHECK_CURRENTLY_ON(BrowserThread::UI); JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef obj = java_ref_.get(env); if (!obj) return; AwWebResourceRequest::AwJavaWebResourceRequest java_web_resource_request; AwWebResourceRequest::ConvertToJava(env, request, &java_web_resource_request); ScopedJavaLocalRef jstring_mime_type = ConvertUTF8ToJavaString(env, http_error_info->mime_type); ScopedJavaLocalRef jstring_encoding = ConvertUTF8ToJavaString(env, http_error_info->encoding); ScopedJavaLocalRef jstring_reason = ConvertUTF8ToJavaString(env, http_error_info->status_text); ScopedJavaLocalRef jstringArray_response_header_names = ToJavaArrayOfStrings(env, http_error_info->response_header_names); ScopedJavaLocalRef jstringArray_response_header_values = ToJavaArrayOfStrings(env, http_error_info->response_header_values); Java_AwContentsClientBridge_onReceivedHttpError( env, obj, java_web_resource_request.jurl, request.is_outermost_main_frame, request.has_user_gesture, java_web_resource_request.jmethod, java_web_resource_request.jheader_names, java_web_resource_request.jheader_values, jstring_mime_type, jstring_encoding, http_error_info->status_code, jstring_reason, jstringArray_response_header_names, jstringArray_response_header_values); } // static std::unique_ptr AwContentsClientBridge::ExtractHttpErrorInfo( const net::HttpResponseHeaders* response_headers) { auto http_error_info = std::make_unique(); { size_t headers_iterator = 0; std::string header_name, header_value; while (response_headers->EnumerateHeaderLines( &headers_iterator, &header_name, &header_value)) { http_error_info->response_header_names.push_back(header_name); http_error_info->response_header_values.push_back(header_value); } } response_headers->GetMimeTypeAndCharset(&http_error_info->mime_type, &http_error_info->encoding); http_error_info->status_code = response_headers->response_code(); http_error_info->status_text = response_headers->GetStatusText(); return http_error_info; } void AwContentsClientBridge::ConfirmJsResult(JNIEnv* env, const JavaRef&, int id, const JavaRef& prompt) { DCHECK_CURRENTLY_ON(BrowserThread::UI); content::JavaScriptDialogManager::DialogClosedCallback* callback = pending_js_dialog_callbacks_.Lookup(id); if (!callback) { LOG(WARNING) << "Unexpected JS dialog confirm. " << id; return; } std::u16string prompt_text; if (prompt) { prompt_text = ConvertJavaStringToUTF16(env, prompt); } std::move(*callback).Run(true, prompt_text); pending_js_dialog_callbacks_.Remove(id); } void AwContentsClientBridge::TakeSafeBrowsingAction(JNIEnv*, const JavaRef&, int action, bool reporting, int request_id) { DCHECK_CURRENTLY_ON(BrowserThread::UI); auto* callback = safe_browsing_callbacks_.Lookup(request_id); if (!callback) { LOG(WARNING) << "Unexpected TakeSafeBrowsingAction. " << request_id; return; } std::move(*callback).Run( static_cast(action), reporting); safe_browsing_callbacks_.Remove(request_id); } void AwContentsClientBridge::CancelJsResult(JNIEnv*, const JavaRef&, int id) { DCHECK_CURRENTLY_ON(BrowserThread::UI); content::JavaScriptDialogManager::DialogClosedCallback* callback = pending_js_dialog_callbacks_.Lookup(id); if (!callback) { LOG(WARNING) << "Unexpected JS dialog cancel. " << id; return; } std::move(*callback).Run(false, std::u16string()); pending_js_dialog_callbacks_.Remove(id); } } // namespace android_webview