// Copyright (c) 2012 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. #ifndef CHROME_BROWSER_SAFE_BROWSING_PROTOCOL_MANAGER_H_ #define CHROME_BROWSER_SAFE_BROWSING_PROTOCOL_MANAGER_H_ // A class that implements Chrome's interface with the SafeBrowsing protocol. // See https://developers.google.com/safe-browsing/developers_guide_v2 for // protocol details. // // The SafeBrowsingProtocolManager handles formatting and making requests of, // and handling responses from, Google's SafeBrowsing servers. This class uses // The SafeBrowsingProtocolParser class to do the actual parsing. #include <deque> #include <set> #include <string> #include <vector> #include "base/containers/hash_tables.h" #include "base/gtest_prod_util.h" #include "base/memory/scoped_ptr.h" #include "base/threading/non_thread_safe.h" #include "base/time/time.h" #include "base/timer/timer.h" #include "chrome/browser/safe_browsing/chunk_range.h" #include "chrome/browser/safe_browsing/protocol_manager_helper.h" #include "chrome/browser/safe_browsing/protocol_parser.h" #include "chrome/browser/safe_browsing/safe_browsing_util.h" #include "net/url_request/url_fetcher_delegate.h" #include "url/gurl.h" namespace net { class URLFetcher; class URLRequestContextGetter; } // namespace net #if defined(COMPILER_GCC) // Allows us to use URLFetchers in a hash_map with gcc (MSVC is okay without // specifying this). namespace BASE_HASH_NAMESPACE { template<> struct hash<const net::URLFetcher*> { size_t operator()(const net::URLFetcher* fetcher) const { return reinterpret_cast<size_t>(fetcher); } }; } #endif class SBProtocolManagerFactory; class SafeBrowsingProtocolManagerDelegate; class SafeBrowsingProtocolManager : public net::URLFetcherDelegate, public base::NonThreadSafe { public: // FullHashCallback is invoked when GetFullHash completes. // Parameters: // - The vector of full hash results. If empty, indicates that there // were no matches, and that the resource is safe. // - Whether the result can be cached. This may not be the case when // the result did not come from the SB server, for example. typedef base::Callback<void(const std::vector<SBFullHashResult>&, bool)> FullHashCallback; virtual ~SafeBrowsingProtocolManager(); // Makes the passed |factory| the factory used to instantiate // a SafeBrowsingService. Useful for tests. static void RegisterFactory(SBProtocolManagerFactory* factory) { factory_ = factory; } // Create an instance of the safe browsing protocol manager. static SafeBrowsingProtocolManager* Create( SafeBrowsingProtocolManagerDelegate* delegate, net::URLRequestContextGetter* request_context_getter, const SafeBrowsingProtocolConfig& config); // Sets up the update schedule and internal state for making periodic requests // of the Safebrowsing servers. virtual void Initialize(); // net::URLFetcherDelegate interface. virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE; // Retrieve the full hash for a set of prefixes, and invoke the callback // argument when the results are retrieved. The callback may be invoked // synchronously. virtual void GetFullHash(const std::vector<SBPrefix>& prefixes, FullHashCallback callback, bool is_download); // Forces the start of next update after |interval| time. void ForceScheduleNextUpdate(base::TimeDelta interval); // Scheduled update callback. void GetNextUpdate(); // Called by the SafeBrowsingService when our request for a list of all chunks // for each list is done. If database_error is true, that means the protocol // manager shouldn't fetch updates since they can't be written to disk. It // should try again later to open the database. void OnGetChunksComplete(const std::vector<SBListChunkRanges>& list, bool database_error); // The last time we received an update. base::Time last_update() const { return last_update_; } // Setter for additional_query_. To make sure the additional_query_ won't // be changed in the middle of an update, caller (e.g.: SafeBrowsingService) // should call this after callbacks triggered in UpdateFinished() or before // IssueUpdateRequest(). void set_additional_query(const std::string& query) { additional_query_ = query; } const std::string& additional_query() const { return additional_query_; } // Enumerate failures for histogramming purposes. DO NOT CHANGE THE // ORDERING OF THESE VALUES. enum ResultType { // 200 response code means that the server recognized the hash // prefix, while 204 is an empty response indicating that the // server did not recognize it. GET_HASH_STATUS_200, GET_HASH_STATUS_204, // Subset of successful responses which returned no full hashes. // This includes the 204 case, and also 200 responses for stale // prefixes (deleted at the server but yet deleted on the client). GET_HASH_FULL_HASH_EMPTY, // Subset of successful responses for which one or more of the // full hashes matched (should lead to an interstitial). GET_HASH_FULL_HASH_HIT, // Subset of successful responses which weren't empty and have no // matches. It means that there was a prefix collision which was // cleared up by the full hashes. GET_HASH_FULL_HASH_MISS, // Memory space for histograms is determined by the max. ALWAYS // ADD NEW VALUES BEFORE THIS ONE. GET_HASH_RESULT_MAX }; // Record a GetHash result. |is_download| indicates if the get // hash is triggered by download related lookup. static void RecordGetHashResult(bool is_download, ResultType result_type); // Returns whether another update is currently scheduled. bool IsUpdateScheduled() const; protected: // Constructs a SafeBrowsingProtocolManager for |delegate| that issues // network requests using |request_context_getter|. SafeBrowsingProtocolManager( SafeBrowsingProtocolManagerDelegate* delegate, net::URLRequestContextGetter* request_context_getter, const SafeBrowsingProtocolConfig& config); private: FRIEND_TEST_ALL_PREFIXES(SafeBrowsingProtocolManagerTest, TestBackOffTimes); FRIEND_TEST_ALL_PREFIXES(SafeBrowsingProtocolManagerTest, TestChunkStrings); FRIEND_TEST_ALL_PREFIXES(SafeBrowsingProtocolManagerTest, TestGetHashUrl); FRIEND_TEST_ALL_PREFIXES(SafeBrowsingProtocolManagerTest, TestGetHashBackOffTimes); FRIEND_TEST_ALL_PREFIXES(SafeBrowsingProtocolManagerTest, TestNextChunkUrl); FRIEND_TEST_ALL_PREFIXES(SafeBrowsingProtocolManagerTest, TestUpdateUrl); friend class SafeBrowsingServerTest; friend class SBProtocolManagerFactoryImpl; // Internal API for fetching information from the SafeBrowsing servers. The // GetHash requests are higher priority since they can block user requests // so are handled separately. enum SafeBrowsingRequestType { NO_REQUEST = 0, // No requests in progress UPDATE_REQUEST, // Request for redirect URLs BACKUP_UPDATE_REQUEST, // Request for redirect URLs to a backup URL. CHUNK_REQUEST, // Request for a specific chunk }; // Which type of backup update request is being used. enum BackupUpdateReason { BACKUP_UPDATE_REASON_CONNECT, BACKUP_UPDATE_REASON_HTTP, BACKUP_UPDATE_REASON_NETWORK, BACKUP_UPDATE_REASON_MAX, }; // Generates Update URL for querying about the latest set of chunk updates. GURL UpdateUrl() const; // Generates backup Update URL for querying about the latest set of chunk // updates. |url_prefix| is the base prefix to use. GURL BackupUpdateUrl(BackupUpdateReason reason) const; // Generates GetHash request URL for retrieving full hashes. GURL GetHashUrl() const; // Generates URL for reporting safe browsing hits for UMA users. // Composes a ChunkUrl based on input string. GURL NextChunkUrl(const std::string& input) const; // Returns the time for the next update request. If |back_off| is true, // the time returned will increment an error count and return the appriate // next time (see ScheduleNextUpdate below). base::TimeDelta GetNextUpdateInterval(bool back_off); // Worker function for calculating GetHash and Update backoff times (in // seconds). |multiplier| is doubled for each consecutive error between the // 2nd and 5th, and |error_count| is incremented with each call. base::TimeDelta GetNextBackOffInterval(int* error_count, int* multiplier) const; // Manages our update with the next allowable update time. If 'back_off_' is // true, we must decrease the frequency of requests of the SafeBrowsing // service according to section 5 of the protocol specification. // When disable_auto_update_ is set, ScheduleNextUpdate will do nothing. // ForceScheduleNextUpdate has to be called to trigger the update. void ScheduleNextUpdate(bool back_off); // Sends a request for a list of chunks we should download to the SafeBrowsing // servers. In order to format this request, we need to send all the chunk // numbers for each list that we have to the server. Getting the chunk numbers // requires a database query (run on the database thread), and the request // is sent upon completion of that query in OnGetChunksComplete. void IssueUpdateRequest(); // Sends a backup request for a list of chunks to download, when the primary // update request failed. |reason| specifies why the backup is needed. Unlike // the primary IssueUpdateRequest, this does not need to hit the local // SafeBrowsing database since the existing chunk numbers are remembered from // the primary update request. Returns whether the backup request was issued - // this may be false in cases where there is not a prefix specified. bool IssueBackupUpdateRequest(BackupUpdateReason reason); // Sends a request for a chunk to the SafeBrowsing servers. void IssueChunkRequest(); // Formats a string returned from the database into: // "list_name;a:<add_chunk_ranges>:s:<sub_chunk_ranges>\n" static std::string FormatList(const SBListChunkRanges& list); // Runs the protocol parser on received data and update the // SafeBrowsingService with the new content. Returns 'true' on successful // parse, 'false' on error. bool HandleServiceResponse(const GURL& url, const char* data, int length); // Updates internal state for each GetHash response error, assuming that the // current time is |now|. void HandleGetHashError(const base::Time& now); // Helper function for update completion. void UpdateFinished(bool success); void UpdateFinished(bool success, bool back_off); // A callback that runs if we timeout waiting for a response to an update // request. We use this to properly set our update state. void UpdateResponseTimeout(); // Called after the chunks are added to the database. void OnAddChunksComplete(); private: // Map of GetHash requests to parameters which created it. struct FullHashDetails { FullHashDetails(); FullHashDetails(FullHashCallback callback, bool is_download); ~FullHashDetails(); FullHashCallback callback; bool is_download; }; typedef base::hash_map<const net::URLFetcher*, FullHashDetails> HashRequests; // The factory that controls the creation of SafeBrowsingProtocolManager. // This is used by tests. static SBProtocolManagerFactory* factory_; // Our delegate. SafeBrowsingProtocolManagerDelegate* delegate_; // Current active request (in case we need to cancel) for updates or chunks // from the SafeBrowsing service. We can only have one of these outstanding // at any given time unlike GetHash requests, which are tracked separately. scoped_ptr<net::URLFetcher> request_; // The kind of request that is currently in progress. SafeBrowsingRequestType request_type_; // The number of HTTP response errors, used for request backoff timing. int update_error_count_; int gethash_error_count_; // Multipliers which double (max == 8) for each error after the second. int update_back_off_mult_; int gethash_back_off_mult_; // Multiplier between 0 and 1 to spread clients over an interval. float back_off_fuzz_; // The list for which we are make a request. std::string list_name_; // For managing the next earliest time to query the SafeBrowsing servers for // updates. base::TimeDelta next_update_interval_; base::OneShotTimer<SafeBrowsingProtocolManager> update_timer_; // timeout_timer_ is used to interrupt update requests which are taking // too long. base::OneShotTimer<SafeBrowsingProtocolManager> timeout_timer_; // All chunk requests that need to be made. std::deque<ChunkUrl> chunk_request_urls_; HashRequests hash_requests_; // The next scheduled update has special behavior for the first 2 requests. enum UpdateRequestState { FIRST_REQUEST = 0, SECOND_REQUEST, NORMAL_REQUEST }; UpdateRequestState update_state_; // True if the service has been given an add/sub chunk but it hasn't been // added to the database yet. bool chunk_pending_to_write_; // The last time we successfully received an update. base::Time last_update_; // While in GetHash backoff, we can't make another GetHash until this time. base::Time next_gethash_time_; // Current product version sent in each request. std::string version_; // Used for measuring chunk request latency. base::Time chunk_request_start_; // Tracks the size of each update (in bytes). int update_size_; // The safe browsing client name sent in each request. std::string client_name_; // A string that is appended to the end of URLs for download, gethash, // safebrowsing hits and chunk update requests. std::string additional_query_; // The context we use to issue network requests. scoped_refptr<net::URLRequestContextGetter> request_context_getter_; // URL prefix where browser fetches safebrowsing chunk updates, and hashes. std::string url_prefix_; // Backup URL prefixes for updates. std::string backup_url_prefixes_[BACKUP_UPDATE_REASON_MAX]; // The current reason why the backup update request is happening. BackupUpdateReason backup_update_reason_; // Data to POST when doing an update. std::string update_list_data_; // When true, protocol manager will not start an update unless // ForceScheduleNextUpdate() is called. This is set for testing purpose. bool disable_auto_update_; // ID for URLFetchers for testing. int url_fetcher_id_; DISALLOW_COPY_AND_ASSIGN(SafeBrowsingProtocolManager); }; // Interface of a factory to create ProtocolManager. Useful for tests. class SBProtocolManagerFactory { public: SBProtocolManagerFactory() {} virtual ~SBProtocolManagerFactory() {} virtual SafeBrowsingProtocolManager* CreateProtocolManager( SafeBrowsingProtocolManagerDelegate* delegate, net::URLRequestContextGetter* request_context_getter, const SafeBrowsingProtocolConfig& config) = 0; private: DISALLOW_COPY_AND_ASSIGN(SBProtocolManagerFactory); }; // Delegate interface for the SafeBrowsingProtocolManager. class SafeBrowsingProtocolManagerDelegate { public: typedef base::Callback<void(const std::vector<SBListChunkRanges>&, bool)> GetChunksCallback; typedef base::Callback<void(void)> AddChunksCallback; virtual ~SafeBrowsingProtocolManagerDelegate(); // |UpdateStarted()| is called just before the SafeBrowsing update protocol // has begun. virtual void UpdateStarted() = 0; // |UpdateFinished()| is called just after the SafeBrowsing update protocol // has completed. virtual void UpdateFinished(bool success) = 0; // Wipe out the local database. The SafeBrowsing server can request this. virtual void ResetDatabase() = 0; // Retrieve all the local database chunks, and invoke |callback| with the // results. The SafeBrowsingProtocolManagerDelegate must only invoke the // callback if the SafeBrowsingProtocolManager is still alive. Only one call // may be made to GetChunks at a time. virtual void GetChunks(GetChunksCallback callback) = 0; // Add new chunks to the database. Invokes |callback| when complete, but must // call at a later time. virtual void AddChunks(const std::string& list, SBChunkList* chunks, AddChunksCallback callback) = 0; // Delete chunks from the database. virtual void DeleteChunks( std::vector<SBChunkDelete>* delete_chunks) = 0; }; #endif // CHROME_BROWSER_SAFE_BROWSING_PROTOCOL_MANAGER_H_