|  | // Copyright 2016 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 "components/safe_browsing/android/safe_browsing_api_handler_bridge.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <string> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/android/jni_array.h" | 
|  | #include "base/android/jni_string.h" | 
|  | #include "base/bind.h" | 
|  | #include "base/bind_helpers.h" | 
|  | #include "base/containers/flat_set.h" | 
|  | #include "base/feature_list.h" | 
|  | #include "base/metrics/histogram_macros.h" | 
|  | #include "base/task_scheduler/post_task.h" | 
|  | #include "base/task_scheduler/task_traits.h" | 
|  | #include "base/threading/scoped_blocking_call.h" | 
|  | #include "base/time/time.h" | 
|  | #include "base/timer/elapsed_timer.h" | 
|  | #include "base/trace_event/trace_event.h" | 
|  | #include "components/safe_browsing/android/safe_browsing_api_handler_util.h" | 
|  | #include "components/safe_browsing/db/v4_protocol_manager_util.h" | 
|  | #include "components/safe_browsing/features.h" | 
|  | #include "content/public/browser/browser_thread.h" | 
|  | #include "jni/SafeBrowsingApiBridge_jni.h" | 
|  |  | 
|  | using base::android::AttachCurrentThread; | 
|  | using base::android::ConvertJavaStringToUTF8; | 
|  | using base::android::ConvertUTF8ToJavaString; | 
|  | using base::android::JavaParamRef; | 
|  | using base::android::ScopedJavaLocalRef; | 
|  | using base::android::ToJavaIntArray; | 
|  | using content::BrowserThread; | 
|  |  | 
|  | namespace safe_browsing { | 
|  |  | 
|  | namespace { | 
|  | void RunCallbackOnIOThread( | 
|  | std::unique_ptr<SafeBrowsingApiHandler::URLCheckCallbackMeta> callback, | 
|  | SBThreatType threat_type, | 
|  | const ThreatMetadata& metadata) { | 
|  | BrowserThread::PostTask( | 
|  | BrowserThread::IO, FROM_HERE, | 
|  | base::BindOnce(std::move(*callback), threat_type, metadata)); | 
|  | } | 
|  |  | 
|  | void ReportUmaResult(safe_browsing::UmaRemoteCallResult result) { | 
|  | UMA_HISTOGRAM_ENUMERATION("SB2.RemoteCall.Result", result, | 
|  | safe_browsing::UMA_STATUS_MAX_VALUE); | 
|  | } | 
|  |  | 
|  | // Convert a SBThreatType to a Java threat type.  We only support a few. | 
|  | int SBThreatTypeToJavaThreatType(const SBThreatType& sb_threat_type) { | 
|  | switch (sb_threat_type) { | 
|  | case SB_THREAT_TYPE_URL_PHISHING: | 
|  | return safe_browsing::JAVA_THREAT_TYPE_SOCIAL_ENGINEERING; | 
|  | case SB_THREAT_TYPE_URL_MALWARE: | 
|  | return safe_browsing::JAVA_THREAT_TYPE_POTENTIALLY_HARMFUL_APPLICATION; | 
|  | case SB_THREAT_TYPE_URL_UNWANTED: | 
|  | return safe_browsing::JAVA_THREAT_TYPE_UNWANTED_SOFTWARE; | 
|  | case SB_THREAT_TYPE_SUBRESOURCE_FILTER: | 
|  | return safe_browsing::JAVA_THREAT_TYPE_SUBRESOURCE_FILTER; | 
|  | default: | 
|  | NOTREACHED(); | 
|  | return 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Convert a vector of SBThreatTypes to JavaIntArray of Java threat types. | 
|  | ScopedJavaLocalRef<jintArray> SBThreatTypeSetToJavaArray( | 
|  | JNIEnv* env, | 
|  | const SBThreatTypeSet& threat_types) { | 
|  | DCHECK_LT(0u, threat_types.size()); | 
|  | int int_threat_types[threat_types.size()]; | 
|  | int* itr = &int_threat_types[0]; | 
|  | for (auto threat_type : threat_types) { | 
|  | *itr++ = SBThreatTypeToJavaThreatType(threat_type); | 
|  | } | 
|  | return ToJavaIntArray(env, int_threat_types, threat_types.size()); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // Java->Native call, invoked when a check is done. | 
|  | //   |callback_id| is an int form of pointer to a URLCheckCallbackMeta | 
|  | //                 that will be called and then deleted here. | 
|  | //   |result_status| is one of those from SafeBrowsingApiHandler.java | 
|  | //   |metadata| is a JSON string classifying the threat if there is one. | 
|  | // | 
|  | //   Careful note: this can be called on multiple threads, so make sure there is | 
|  | //   nothing thread unsafe happening here. | 
|  | void JNI_SafeBrowsingApiBridge_OnUrlCheckDone( | 
|  | JNIEnv* env, | 
|  | const JavaParamRef<jclass>& context, | 
|  | jlong callback_id, | 
|  | jint result_status, | 
|  | const JavaParamRef<jstring>& metadata, | 
|  | jlong check_delta) { | 
|  | DCHECK(callback_id); | 
|  | UMA_HISTOGRAM_COUNTS_10M("SB2.RemoteCall.CheckDelta", check_delta); | 
|  |  | 
|  | const std::string metadata_str = | 
|  | (metadata ? ConvertJavaStringToUTF8(env, metadata) : ""); | 
|  |  | 
|  | TRACE_EVENT1("safe_browsing", "SafeBrowsingApiHandlerBridge::OnUrlCheckDone", | 
|  | "metadata", metadata_str); | 
|  |  | 
|  | DVLOG(1) << "OnURLCheckDone invoked for check " << callback_id | 
|  | << " with status=" << result_status << " and metadata=[" | 
|  | << metadata_str << "]"; | 
|  |  | 
|  | // Convert java long long int to c++ pointer, take ownership. | 
|  | std::unique_ptr<SafeBrowsingApiHandler::URLCheckCallbackMeta> callback( | 
|  | reinterpret_cast<SafeBrowsingApiHandlerBridge::URLCheckCallbackMeta*>( | 
|  | callback_id)); | 
|  |  | 
|  | if (result_status != RESULT_STATUS_SUCCESS) { | 
|  | if (result_status == RESULT_STATUS_TIMEOUT) { | 
|  | ReportUmaResult(UMA_STATUS_TIMEOUT); | 
|  | VLOG(1) << "Safe browsing API call timed-out"; | 
|  | } else { | 
|  | DCHECK_EQ(result_status, RESULT_STATUS_INTERNAL_ERROR); | 
|  | ReportUmaResult(UMA_STATUS_INTERNAL_ERROR); | 
|  | } | 
|  | RunCallbackOnIOThread(std::move(callback), SB_THREAT_TYPE_SAFE, | 
|  | ThreatMetadata()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Shortcut for safe, so we don't have to parse JSON. | 
|  | if (metadata_str == "{}") { | 
|  | ReportUmaResult(UMA_STATUS_SAFE); | 
|  | RunCallbackOnIOThread(std::move(callback), SB_THREAT_TYPE_SAFE, | 
|  | ThreatMetadata()); | 
|  | } else { | 
|  | // Unsafe, assuming we can parse the JSON. | 
|  | SBThreatType worst_threat; | 
|  | ThreatMetadata threat_metadata; | 
|  | ReportUmaResult( | 
|  | ParseJsonFromGMSCore(metadata_str, &worst_threat, &threat_metadata)); | 
|  | if (worst_threat != SB_THREAT_TYPE_SAFE) { | 
|  | DVLOG(1) << "Check " << callback_id << " was a MATCH"; | 
|  | } | 
|  |  | 
|  | RunCallbackOnIOThread(std::move(callback), worst_threat, threat_metadata); | 
|  | } | 
|  | } | 
|  |  | 
|  | // | 
|  | // SafeBrowsingApiHandlerBridge | 
|  | // | 
|  | SafeBrowsingApiHandlerBridge::SafeBrowsingApiHandlerBridge() {} | 
|  |  | 
|  | SafeBrowsingApiHandlerBridge::~SafeBrowsingApiHandlerBridge() { | 
|  | if (api_task_runner_) | 
|  | api_task_runner_->DeleteSoon(FROM_HERE, core_.release()); | 
|  | } | 
|  |  | 
|  | void SafeBrowsingApiHandlerBridge::Initialize() { | 
|  | DCHECK(!core_); | 
|  | core_ = std::make_unique<Core>(); | 
|  | api_task_runner_ = base::CreateSequencedTaskRunnerWithTraits( | 
|  | {base::MayBlock(), base::TaskPriority::USER_VISIBLE}); | 
|  | } | 
|  |  | 
|  | void SafeBrowsingApiHandlerBridge::StartURLCheck( | 
|  | std::unique_ptr<SafeBrowsingApiHandler::URLCheckCallbackMeta> callback, | 
|  | const GURL& url, | 
|  | const SBThreatTypeSet& threat_types) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::IO); | 
|  | // Initialize on the first URL check, when the feature list API is ready to be | 
|  | // used. | 
|  | if (!core_) | 
|  | Initialize(); | 
|  |  | 
|  | // Note: it turns out in practice that dispatching the IPC to Google Play | 
|  | // Services can be quite expensive in terms of wall time, often due to thread | 
|  | // descheduling. Since this task runs in an extremely performance critical | 
|  | // place (it blocks navigation and subresource requests), dispatch it on a | 
|  | // worker thread. In high percentiles it seems like the dispatching can take | 
|  | // >100ms, so use base::MayBlock even though we aren't technically doing | 
|  | // blocking IO. | 
|  | if (!api_task_runner_) { | 
|  | core_->StartURLCheck(std::move(callback), url, threat_types); | 
|  | return; | 
|  | } | 
|  | // Unretained is safe because the task to delete |core_| will be sequenced | 
|  | // after any task posted here. | 
|  | api_task_runner_->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce(&SafeBrowsingApiHandlerBridge::Core::StartURLCheck, | 
|  | base::Unretained(core_.get()), std::move(callback), url, | 
|  | threat_types)); | 
|  | } | 
|  |  | 
|  | SafeBrowsingApiHandlerBridge::Core::Core() { | 
|  | // The sequence checker is constructed on a different sequence from where it | 
|  | // is used. | 
|  | DETACH_FROM_SEQUENCE(sequence_checker_); | 
|  | } | 
|  |  | 
|  | SafeBrowsingApiHandlerBridge::Core::~Core() = default; | 
|  |  | 
|  | bool SafeBrowsingApiHandlerBridge::Core::CheckApiIsSupported() { | 
|  | DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
|  | if (!checked_api_support_) { | 
|  | DVLOG(1) << "Checking API support."; | 
|  | j_api_handler_ = base::android::ScopedJavaGlobalRef<jobject>( | 
|  | Java_SafeBrowsingApiBridge_create(AttachCurrentThread())); | 
|  | checked_api_support_ = true; | 
|  | } | 
|  | return j_api_handler_.obj() != nullptr; | 
|  | } | 
|  |  | 
|  | void SafeBrowsingApiHandlerBridge::Core::StartURLCheck( | 
|  | std::unique_ptr<URLCheckCallbackMeta> callback, | 
|  | const GURL& url, | 
|  | const SBThreatTypeSet& threat_types) { | 
|  | DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
|  | TRACE_EVENT0("safe_browsing", | 
|  | "SafeBrowsingApiHandlerBridge::StartURLCheckAsync"); | 
|  | if (!CheckApiIsSupported()) { | 
|  | // Mark all requests as safe. Only users who have an old, broken GMSCore or | 
|  | // have sideloaded Chrome w/o PlayStore should land here. | 
|  | RunCallbackOnIOThread(std::move(callback), SB_THREAT_TYPE_SAFE, | 
|  | ThreatMetadata()); | 
|  | ReportUmaResult(UMA_STATUS_UNSUPPORTED); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Save the address on the heap so we can pass it through JNI. The unique ptr | 
|  | // releases ownership, we will re-own this callback when the response is | 
|  | // received in JNI_SafeBrowsingApiBridge_OnUrlCheckDone. | 
|  | intptr_t callback_id = reinterpret_cast<intptr_t>(callback.release()); | 
|  |  | 
|  | DVLOG(1) << "Starting check " << callback_id << " for URL " << url; | 
|  |  | 
|  | DCHECK(!threat_types.empty()); | 
|  |  | 
|  | JNIEnv* env = AttachCurrentThread(); | 
|  | ScopedJavaLocalRef<jstring> j_url = ConvertUTF8ToJavaString(env, url.spec()); | 
|  | ScopedJavaLocalRef<jintArray> j_threat_types = | 
|  | SBThreatTypeSetToJavaArray(env, threat_types); | 
|  |  | 
|  | // Increase parallelism by indicating that the lookup may block. Only the long | 
|  | // tail of these calls block for more than 10ms, which is the current | 
|  | // threshold for increasing worker capacity. | 
|  | base::ScopedBlockingCall scoped_blocking_call(base::BlockingType::MAY_BLOCK); | 
|  | base::ElapsedTimer check_timer; | 
|  | Java_SafeBrowsingApiBridge_startUriLookup(env, j_api_handler_, callback_id, | 
|  | j_url, j_threat_types); | 
|  | UMA_HISTOGRAM_COUNTS_10M("SB2.RemoteCall.CheckDispatchTime", | 
|  | check_timer.Elapsed().InMicroseconds()); | 
|  | } | 
|  |  | 
|  | }  // namespace safe_browsing |