// Copyright (c) 2011 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. // // Helper class which handles communication with the SafeBrowsing backends for // client-side phishing detection. This class can be used to get a file // descriptor to the client-side phishing model and also to send a ping back to // Google to verify if a particular site is really phishing or not. // // This class is not thread-safe and expects all calls to GetModelFile() and // SendClientReportPhishingRequest() to be made on the UI thread. We also // expect that the calling thread runs a message loop and that there is a FILE // thread running to execute asynchronous file operations. #ifndef CHROME_BROWSER_SAFE_BROWSING_CLIENT_SIDE_DETECTION_SERVICE_H_ #define CHROME_BROWSER_SAFE_BROWSING_CLIENT_SIDE_DETECTION_SERVICE_H_ #pragma once #include <map> #include <queue> #include <string> #include <utility> #include <vector> #include "base/basictypes.h" #include "base/callback.h" #include "base/file_path.h" #include "base/gtest_prod_util.h" #include "base/memory/linked_ptr.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_callback_factory.h" #include "base/memory/scoped_ptr.h" #include "base/platform_file.h" #include "base/task.h" #include "base/time.h" #include "chrome/common/net/url_fetcher.h" #include "googleurl/src/gurl.h" #include "net/base/net_util.h" namespace net { class URLRequestContextGetter; class URLRequestStatus; } // namespace net namespace safe_browsing { class ClientPhishingRequest; class ClientSideDetectionService : public URLFetcher::Delegate { public: typedef Callback1<base::PlatformFile>::Type OpenModelDoneCallback; typedef Callback2<GURL /* phishing URL */, bool /* is phishing */>::Type ClientReportPhishingRequestCallback; virtual ~ClientSideDetectionService(); // Creates a client-side detection service and starts fetching the client-side // detection model if necessary. The model will be stored in |model_path|. // The caller takes ownership of the object. This function may return NULL. static ClientSideDetectionService* Create( const FilePath& model_path, net::URLRequestContextGetter* request_context_getter); // From the URLFetcher::Delegate interface. virtual void OnURLFetchComplete(const URLFetcher* source, const GURL& url, const net::URLRequestStatus& status, int response_code, const ResponseCookies& cookies, const std::string& data); // Gets the model file descriptor once the model is ready and stored // on disk. If there was an error the callback is called and the // platform file is set to kInvalidPlatformFileValue. The // ClientSideDetectionService takes ownership of the |callback|. // The callback is always called after GetModelFile() returns and on the // same thread as GetModelFile() was called. void GetModelFile(OpenModelDoneCallback* callback); // Sends a request to the SafeBrowsing servers with the ClientPhishingRequest. // The URL scheme of the |url()| in the request should be HTTP. This method // takes ownership of the |verdict| as well as the |callback| and calls the // the callback once the result has come back from the server or if an error // occurs during the fetch. If an error occurs the phishing verdict will // always be false. The callback is always called after // SendClientReportPhishingRequest() returns and on the same thread as // SendClientReportPhishingRequest() was called. virtual void SendClientReportPhishingRequest( ClientPhishingRequest* verdict, ClientReportPhishingRequestCallback* callback); // Returns true if the given IP address string falls within a private // (unroutable) network block. Pages which are hosted on these IP addresses // are exempt from client-side phishing detection. This is called by the // ClientSideDetectionHost prior to sending the renderer a // SafeBrowsingMsg_StartPhishingDetection IPC. // // ip_address should be a dotted IPv4 address, or an unbracketed IPv6 // address. virtual bool IsPrivateIPAddress(const std::string& ip_address) const; // Returns true and sets is_phishing if url is in the cache and valid. virtual bool GetValidCachedResult(const GURL& url, bool* is_phishing); // Returns true if the url is in the cache. virtual bool IsInCache(const GURL& url); // Returns true if we have sent more than kMaxReportsPerInterval in the last // kReportsInterval. virtual bool OverReportLimit(); protected: // Use Create() method to create an instance of this object. ClientSideDetectionService( const FilePath& model_path, net::URLRequestContextGetter* request_context_getter); private: friend class ClientSideDetectionServiceTest; enum ModelStatus { // It's unclear whether or not the model was already fetched. UNKNOWN_STATUS, // Model is fetched and is stored on disk. READY_STATUS, // Error occured during fetching or writing. ERROR_STATUS, }; // CacheState holds all information necessary to respond to a caller without // actually making a HTTP request. struct CacheState { bool is_phishing; base::Time timestamp; CacheState(bool phish, base::Time time); }; typedef std::map<GURL, linked_ptr<CacheState> > PhishingCache; // A tuple of (IP address block, prefix size) representing a private // IP address range. typedef std::pair<net::IPAddressNumber, size_t> AddressRange; static const char kClientReportPhishingUrl[]; static const char kClientModelUrl[]; static const int kMaxReportsPerInterval; static const base::TimeDelta kReportsInterval; static const base::TimeDelta kNegativeCacheInterval; static const base::TimeDelta kPositiveCacheInterval; // Sets the model status and invokes all the pending callbacks in // |open_callbacks_| with the current |model_file_| as parameter. void SetModelStatus(ModelStatus status); // Called once the initial open() of the model file is done. If the file // exists we're done and we can call all the pending callbacks. If the // file doesn't exist this method will asynchronously fetch the model // from the server by invoking StartFetchingModel(). void OpenModelFileDone(base::PlatformFileError error_code, base::PassPlatformFile file, bool created); // Callback that is invoked once the attempt to create the model // file on disk is done. If the file was created successfully we // start writing the model to disk (asynchronously). Otherwise, we // give up and send an invalid platform file to all the pending callbacks. void CreateModelFileDone(base::PlatformFileError error_code, base::PassPlatformFile file, bool created); // Callback is invoked once we're done writing the model file to disk. // If everything went well then |model_file_| is a valid platform file // that can be sent to all the pending callbacks. If an error occurs // we give up and send an invalid platform file to all the pending callbacks. void WriteModelFileDone(base::PlatformFileError error_code, int bytes_written); // Helper function which closes the |model_file_| if necessary. void CloseModelFile(); // Starts sending the request to the client-side detection frontends. // This method takes ownership of both pointers. void StartClientReportPhishingRequest( ClientPhishingRequest* verdict, ClientReportPhishingRequestCallback* callback); // Starts getting the model file. void StartGetModelFile(OpenModelDoneCallback* callback); // Called by OnURLFetchComplete to handle the response from fetching the // model. void HandleModelResponse(const URLFetcher* source, const GURL& url, const net::URLRequestStatus& status, int response_code, const ResponseCookies& cookies, const std::string& data); // Called by OnURLFetchComplete to handle the server response from // sending the client-side phishing request. void HandlePhishingVerdict(const URLFetcher* source, const GURL& url, const net::URLRequestStatus& status, int response_code, const ResponseCookies& cookies, const std::string& data); // Invalidate cache results which are no longer useful. void UpdateCache(); // Get the number of phishing reports that we have sent over kReportsInterval int GetNumReports(); // Initializes the |private_networks_| vector with the network blocks // that we consider non-public IP addresses. Returns true on success. bool InitializePrivateNetworks(); FilePath model_path_; ModelStatus model_status_; base::PlatformFile model_file_; scoped_ptr<URLFetcher> model_fetcher_; scoped_ptr<std::string> tmp_model_string_; std::vector<OpenModelDoneCallback*> open_callbacks_; // Map of client report phishing request to the corresponding callback that // has to be invoked when the request is done. struct ClientReportInfo; std::map<const URLFetcher*, ClientReportInfo*> client_phishing_reports_; // Cache of completed requests. Used to satisfy requests for the same urls // as long as the next request falls within our caching window (which is // determined by kNegativeCacheInterval and kPositiveCacheInterval). The // size of this cache is limited by kMaxReportsPerDay * // ceil(InDays(max(kNegativeCacheInterval, kPositiveCacheInterval))). // TODO(gcasto): Serialize this so that it doesn't reset on browser restart. PhishingCache cache_; // Timestamp of when we sent a phishing request. Used to limit the number // of phishing requests that we send in a day. // TODO(gcasto): Serialize this so that it doesn't reset on browser restart. std::queue<base::Time> phishing_report_times_; // Used to asynchronously call the callbacks for GetModelFile and // SendClientReportPhishingRequest. ScopedRunnableMethodFactory<ClientSideDetectionService> method_factory_; // The client-side detection service object (this) might go away before some // of the callbacks are done (e.g., asynchronous file operations). The // callback factory will revoke all pending callbacks if this goes away to // avoid a crash. base::ScopedCallbackFactory<ClientSideDetectionService> callback_factory_; // The context we use to issue network requests. scoped_refptr<net::URLRequestContextGetter> request_context_getter_; // The network blocks that we consider private IP address ranges. std::vector<AddressRange> private_networks_; DISALLOW_COPY_AND_ASSIGN(ClientSideDetectionService); }; } // namepsace safe_browsing #endif // CHROME_BROWSER_SAFE_BROWSING_CLIENT_SIDE_DETECTION_SERVICE_H_