| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef COMPONENTS_SAFE_BROWSING_CONTENT_BROWSER_ASYNC_CHECK_TRACKER_H_ |
| #define COMPONENTS_SAFE_BROWSING_CONTENT_BROWSER_ASYNC_CHECK_TRACKER_H_ |
| |
| #include <memory> |
| |
| #include "base/memory/weak_ptr.h" |
| #include "base/observer_list.h" |
| #include "base/observer_list_types.h" |
| #include "base/time/time.h" |
| #include "components/safe_browsing/content/browser/url_checker_holder.h" |
| #include "components/security_interstitials/core/unsafe_resource.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "content/public/browser/web_contents_user_data.h" |
| |
| namespace safe_browsing { |
| |
| class BaseUIManager; |
| |
| // AsyncCheckTracker is responsible for: |
| // * Manage the lifetime of any `UrlCheckerHolder` that is not able to |
| // complete before BrowserUrlLoaderThrottle::WillProcessResponse is called. |
| // * Trigger a warning based on the result from `UrlCheckerHolder` if the |
| // check is completed between BrowserUrlLoaderThrottle::WillProcessResponse and |
| // WebContentsObserver::DidFinishNavigation. If the check is completed before |
| // WillProcessResponse, SafeBrowsingNavigationThrottle will trigger the warning. |
| // If the check is completed after DidFinishNavigation, |
| // BaseUIManager::DisplayBlockingPage will trigger the warning. |
| // * Track and provide the status of navigation that is associated with |
| // UnsafeResource. Other classes can add themselves as an observer and get |
| // notified when certain events happen. |
| // This class should only be called on the UI thread. |
| class AsyncCheckTracker |
| : public content::WebContentsUserData<AsyncCheckTracker>, |
| public content::WebContentsObserver { |
| public: |
| // Interface for observing events on AsyncCheckTracker. |
| class Observer : public base::CheckedObserver { |
| public: |
| // Called when a SB check is completed by AsyncCheckTracker. This is not |
| // called if the check was not handled by AsyncCheckTracker (i.e. the check |
| // is completed before the response body is processed, which is the majority |
| // of cases). |
| virtual void OnAsyncSafeBrowsingCheckCompleted() {} |
| // Notify the observers to unsubscribe before AsyncCheckTracker is |
| // destructed. |
| virtual void OnAsyncSafeBrowsingCheckTrackerDestructed() {} |
| }; |
| |
| // Returns true if the main frame load is pending (i.e. the navigation has not |
| // yet committed). Note that a main frame hit may not be pending, eg. 1) |
| // client side detection happens after the load is committed, or 2) async Safe |
| // Browsing check is enabled. |
| // Caveat: This class only tracks committed navigation ids for a |
| // certain period, so this function may not return the correct result if the |
| // navigation associated with the `resource` is too old. |
| static bool IsMainPageResourceLoadPending( |
| const security_interstitials::UnsafeResource& resource); |
| |
| static bool IsMainPageLoadPending( |
| const security_interstitials::UnsafeResourceLocator& rfh_locator, |
| const std::optional<int64_t>& navigation_id, |
| safe_browsing::SBThreatType threat_type); |
| |
| // Returns the timestamp when the navigation associated with `resource` is |
| // committed. Returns nullopt if the navigation has not committed. |
| // Caveat: This class only tracks committed navigation ids for a |
| // certain period, so this function may not return the correct result if the |
| // navigation associated with the `resource` is too old. |
| static std::optional<base::TimeTicks> GetBlockedPageCommittedTimestamp( |
| const security_interstitials::UnsafeResource& resource); |
| |
| // Returns whether the platform is eligible for its sync checker to check the |
| // allowlist first. Only return true if |
| // * The allowlist check is significantly faster than the local blocklist |
| // check on this platform. AND |
| // * As a risk mitigation, the async checker should still fall back to local |
| // blocklist check if the URL matches the allowlist. |
| static bool IsPlatformEligibleForSyncCheckerCheckAllowlist(); |
| |
| AsyncCheckTracker(const AsyncCheckTracker&) = delete; |
| AsyncCheckTracker& operator=(const AsyncCheckTracker&) = delete; |
| |
| ~AsyncCheckTracker() override; |
| |
| // Takes ownership of `checker`. |
| void TransferUrlChecker(std::unique_ptr<UrlCheckerHolder> checker); |
| |
| // Called by `UrlCheckerHolder` or `BrowserURLLoaderThrottle`, when the check |
| // completes. |
| void PendingCheckerCompleted(int64_t navigation_id, |
| UrlCheckerHolder::OnCompleteCheckResult result); |
| |
| // Returns whether navigation is pending. |
| bool IsNavigationPending(int64_t navigation_id); |
| |
| // Returns the time when the navigation is committed. Returns nullopt if the |
| // navigation has not yet committed. |
| std::optional<base::TimeTicks> GetNavigationCommittedTimestamp( |
| int64_t navigation_id); |
| |
| // Returns whether the additional sync checker should check the allowlist |
| // first. |
| // Checking the allowlist first can reduce the loading latency caused by Safe |
| // Browsing on certain platforms. |
| bool should_sync_checker_check_allowlist() { |
| return should_sync_checker_check_allowlist_; |
| } |
| |
| // content::WebContentsObserver methods: |
| void DidFinishNavigation(content::NavigationHandle* handle) override; |
| |
| void AddObserver(Observer* observer); |
| void RemoveObserver(Observer* observer); |
| |
| size_t PendingCheckersSizeForTesting(); |
| |
| void SetNavigationTimestampsSizeThresholdForTesting(size_t threshold); |
| |
| base::WeakPtr<AsyncCheckTracker> GetWeakPtr(); |
| |
| private: |
| friend class content::WebContentsUserData<AsyncCheckTracker>; |
| friend class SBBrowserUrlLoaderThrottleTestBase; |
| friend class AsyncCheckTrackerTest; |
| friend class SafeBrowsingBlockingPageTestHelper; |
| |
| AsyncCheckTracker(content::WebContents* web_contents, |
| scoped_refptr<BaseUIManager> ui_manager, |
| bool should_sync_checker_check_allowlist); |
| |
| // Deletes the pending checker in `pending_checkers_` that is keyed by |
| // `navigation_id`. Does nothing if `navigation_id` is not found. |
| void MaybeDeleteChecker(int64_t navigation_id); |
| |
| // Deletes all pending checkers in `pending_checkers_` except the checker that |
| // is keyed by `excluded_navigation_id`. |
| void DeletePendingCheckers(std::optional<int64_t> excluded_navigation_id); |
| |
| // Deletes expired timestamps to avoid `committed_navigation_timestamps_` |
| // getting too large. |
| void DeleteExpiredNavigationTimestamps(); |
| |
| // Displays an interstitial if there is unsafe resource associated with |
| // `redirect_chain` and `navigation_id`. |
| void MaybeDisplayBlockingPage(const std::vector<GURL>& redirect_chain, |
| int64_t navigation_id); |
| |
| // Displays an interstitial on `resource`. |
| void DisplayBlockingPage(security_interstitials::UnsafeResource resource); |
| |
| // Sets callback to be used once all checkers are completed. Used only for |
| // tests. |
| void SetOnAllCheckersCompletedForTesting(base::OnceClosure callback); |
| |
| // May call |on_all_checkers_completed_callback_for_testing_| if there are no |
| // |pending_checkers_| remaining. |
| void MaybeCallOnAllCheckersCompletedCallback(); |
| |
| // Used to display a warning. |
| scoped_refptr<BaseUIManager> ui_manager_; |
| |
| // Pending Safe Browsing checkers on the current page, keyed by the |
| // navigation_id. |
| base::flat_map<int64_t, std::unique_ptr<UrlCheckerHolder>> pending_checkers_; |
| |
| // Set to true if interstitial should be shown after DidFinishNavigation is |
| // called. Reset to false after interstitial is triggered. |
| bool show_interstitial_after_finish_navigation_ = false; |
| |
| // Time when a navigation is committed, keyed by the navigation_id. |
| // Whether a navigation id is in the map can be used to determine if a |
| // navigation has committed. The time when the navigation has committed is |
| // used for logging metrics. |
| base::flat_map<int64_t, base::TimeTicks> committed_navigation_timestamps_; |
| |
| // The threshold that will trigger a cleanup on |
| // `committed_navigation_timestamps_`. Overridden in tests. |
| size_t navigation_timestamps_size_threshold_; |
| |
| // Whether sync checker should check the allowlist first. |
| const bool should_sync_checker_check_allowlist_ = false; |
| |
| // A list of observers that are interested in events from this class. |
| base::ObserverList<Observer> observers_; |
| |
| // Callback that is called once all checkers are completed. Used only for |
| // tests. |
| base::OnceClosure on_all_checkers_completed_callback_for_testing_; |
| |
| base::WeakPtrFactory<AsyncCheckTracker> weak_factory_{this}; |
| |
| WEB_CONTENTS_USER_DATA_KEY_DECL(); |
| }; |
| |
| } // namespace safe_browsing |
| |
| #endif // COMPONENTS_SAFE_BROWSING_CONTENT_BROWSER_ASYNC_CHECK_TRACKER_H_ |