client_side_detection_service.cc revision 4a5e2dc747d50c653511c68ccb2cfbfb740bd5a7
1// Copyright (c) 2010 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/safe_browsing/client_side_detection_service.h"
6
7#include "base/command_line.h"
8#include "base/file_path.h"
9#include "base/file_util_proxy.h"
10#include "base/logging.h"
11#include "base/message_loop.h"
12#include "base/platform_file.h"
13#include "base/ref_counted_memory.h"
14#include "base/scoped_ptr.h"
15#include "base/stl_util-inl.h"
16#include "base/task.h"
17#include "chrome/browser/browser_thread.h"
18#include "chrome/browser/safe_browsing/csd.pb.h"
19#include "chrome/common/net/http_return.h"
20#include "chrome/common/net/url_fetcher.h"
21#include "chrome/common/net/url_request_context_getter.h"
22#include "gfx/codec/png_codec.h"
23#include "googleurl/src/gurl.h"
24#include "net/base/load_flags.h"
25#include "net/url_request/url_request_status.h"
26#include "third_party/skia/include/core/SkBitmap.h"
27
28namespace safe_browsing {
29
30const char ClientSideDetectionService::kClientReportPhishingUrl[] =
31    "https://sb-ssl.google.com/safebrowsing/clientreport/phishing";
32const char ClientSideDetectionService::kClientModelUrl[] =
33    "https://ssl.gstatic.com/safebrowsing/csd/client_model_v0.pb";
34
35ClientSideDetectionService::ClientSideDetectionService(
36    const FilePath& model_path,
37    URLRequestContextGetter* request_context_getter)
38    : model_path_(model_path),
39      model_status_(UNKNOWN_STATUS),
40      model_file_(base::kInvalidPlatformFileValue),
41      model_fetcher_(NULL),
42      ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
43      ALLOW_THIS_IN_INITIALIZER_LIST(callback_factory_(this)),
44      request_context_getter_(request_context_getter) {
45}
46
47ClientSideDetectionService::~ClientSideDetectionService() {
48  method_factory_.RevokeAll();
49  STLDeleteContainerPairPointers(client_phishing_reports_.begin(),
50                                 client_phishing_reports_.end());
51  client_phishing_reports_.clear();
52  STLDeleteElements(&open_callbacks_);
53  CloseModelFile();
54}
55
56/* static */
57ClientSideDetectionService* ClientSideDetectionService::Create(
58    const FilePath& model_path,
59    URLRequestContextGetter* request_context_getter) {
60  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
61  scoped_ptr<ClientSideDetectionService> service(
62      new ClientSideDetectionService(model_path, request_context_getter));
63  // We try to open the model file right away and start fetching it if
64  // it does not already exist on disk.
65  base::FileUtilProxy::CreateOrOpenCallback* cb =
66      service.get()->callback_factory_.NewCallback(
67          &ClientSideDetectionService::OpenModelFileDone);
68  if (!base::FileUtilProxy::CreateOrOpen(
69          BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE),
70          model_path,
71          base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ,
72          cb)) {
73    delete cb;
74    return NULL;
75  }
76  return service.release();
77}
78
79void ClientSideDetectionService::GetModelFile(OpenModelDoneCallback* callback) {
80  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
81  MessageLoop::current()->PostTask(
82      FROM_HERE,
83      method_factory_.NewRunnableMethod(
84          &ClientSideDetectionService::StartGetModelFile, callback));
85}
86
87void ClientSideDetectionService::SendClientReportPhishingRequest(
88    const GURL& phishing_url,
89    double score,
90    SkBitmap thumbnail,
91    ClientReportPhishingRequestCallback* callback) {
92  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
93  MessageLoop::current()->PostTask(
94      FROM_HERE,
95      method_factory_.NewRunnableMethod(
96          &ClientSideDetectionService::StartClientReportPhishingRequest,
97          phishing_url, score, thumbnail, callback));
98}
99
100void ClientSideDetectionService::OnURLFetchComplete(
101    const URLFetcher* source,
102    const GURL& url,
103    const URLRequestStatus& status,
104    int response_code,
105    const ResponseCookies& cookies,
106    const std::string& data) {
107  if (source == model_fetcher_) {
108    HandleModelResponse(source, url, status, response_code, cookies, data);
109    // The fetcher object will be invalid after this method returns.
110    model_fetcher_ = NULL;
111  } else if (client_phishing_reports_.find(source) !=
112             client_phishing_reports_.end()) {
113    HandlePhishingVerdict(source, url, status, response_code, cookies, data);
114  } else {
115    NOTREACHED();
116  }
117  delete source;
118}
119
120void ClientSideDetectionService::SetModelStatus(ModelStatus status) {
121  DCHECK_NE(READY_STATUS, model_status_);
122  model_status_ = status;
123  if (READY_STATUS == status || ERROR_STATUS == status) {
124    for (size_t i = 0; i < open_callbacks_.size(); ++i) {
125      open_callbacks_[i]->Run(model_file_);
126    }
127    STLDeleteElements(&open_callbacks_);
128  } else {
129    NOTREACHED();
130  }
131}
132
133void ClientSideDetectionService::OpenModelFileDone(
134    base::PlatformFileError error_code,
135    base::PassPlatformFile file,
136    bool created) {
137  DCHECK(!created);
138  if (base::PLATFORM_FILE_OK == error_code) {
139    // The model file already exists.  There is no need to fetch the model.
140    model_file_ = file.ReleaseValue();
141    SetModelStatus(READY_STATUS);
142  } else if (base::PLATFORM_FILE_ERROR_NOT_FOUND == error_code) {
143    // We need to fetch the model since it does not exist yet.
144    model_fetcher_ = URLFetcher::Create(0 /* ID is not used */,
145                                        GURL(kClientModelUrl),
146                                        URLFetcher::GET,
147                                        this);
148    model_fetcher_->set_request_context(request_context_getter_.get());
149    model_fetcher_->Start();
150  } else {
151    // It is not clear what we should do in this case.  For now we simply fail.
152    // Hopefully, we'll be able to read the model during the next browser
153    // restart.
154    SetModelStatus(ERROR_STATUS);
155  }
156}
157
158void ClientSideDetectionService::CreateModelFileDone(
159    base::PlatformFileError error_code,
160    base::PassPlatformFile file,
161    bool created) {
162  model_file_ = file.ReleaseValue();
163  base::FileUtilProxy::ReadWriteCallback* cb = callback_factory_.NewCallback(
164      &ClientSideDetectionService::WriteModelFileDone);
165  if (!created ||
166      base::PLATFORM_FILE_OK != error_code ||
167      !base::FileUtilProxy::Write(
168          BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE),
169          model_file_,
170          0 /* offset */, tmp_model_string_->data(), tmp_model_string_->size(),
171          cb)) {
172    delete cb;
173    // An error occurred somewhere.  We close the model file if necessary and
174    // then run all the pending callbacks giving them an invalid model file.
175    CloseModelFile();
176    SetModelStatus(ERROR_STATUS);
177  }
178}
179
180void ClientSideDetectionService::WriteModelFileDone(
181    base::PlatformFileError error_code,
182    int bytes_written) {
183  if (base::PLATFORM_FILE_OK == error_code) {
184    SetModelStatus(READY_STATUS);
185  } else {
186    // TODO(noelutz): maybe we should retry writing the model since we
187    // did already fetch the model?
188    CloseModelFile();
189    SetModelStatus(ERROR_STATUS);
190  }
191  // Delete the model string that we kept around while we were writing the
192  // string to disk - we don't need it anymore.
193  tmp_model_string_.reset();
194}
195
196void ClientSideDetectionService::CloseModelFile() {
197  if (model_file_ != base::kInvalidPlatformFileValue) {
198    base::FileUtilProxy::Close(
199        BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE),
200        model_file_,
201        NULL);
202  }
203  model_file_ = base::kInvalidPlatformFileValue;
204}
205
206void ClientSideDetectionService::StartGetModelFile(
207    OpenModelDoneCallback* callback) {
208  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
209  if (UNKNOWN_STATUS == model_status_) {
210    // Store the callback which will be called once we know the status of the
211    // model file.
212    open_callbacks_.push_back(callback);
213  } else {
214    // The model is either in READY or ERROR state which means we can
215    // call the callback right away.
216    callback->Run(model_file_);
217    delete callback;
218  }
219}
220
221void ClientSideDetectionService::StartClientReportPhishingRequest(
222    const GURL& phishing_url,
223    double score,
224    SkBitmap thumbnail,
225    ClientReportPhishingRequestCallback* callback) {
226  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
227  scoped_ptr<ClientReportPhishingRequestCallback> cb(callback);
228  // The server expects an encoded PNG image.
229  scoped_refptr<RefCountedBytes> thumbnail_data(new RefCountedBytes);
230  SkAutoLockPixels lock(thumbnail);
231  if (!thumbnail.readyToDraw() ||
232      !gfx::PNGCodec::EncodeBGRASkBitmap(thumbnail,
233                                         true /* discard_transparency */,
234                                         &thumbnail_data->data)) {
235    cb->Run(phishing_url, false);
236    return;
237  }
238
239  ClientPhishingRequest request;
240  request.set_url(phishing_url.spec());
241  request.set_client_score(static_cast<float>(score));
242  request.set_snapshot(reinterpret_cast<const char*>(thumbnail_data->front()),
243                       thumbnail_data->size());
244  std::string request_data;
245  if (!request.SerializeToString(&request_data)) {
246    // For consistency, we always call the callback asynchronously, rather than
247    // directly from this method.
248    LOG(ERROR) << "Unable to serialize the CSD request. Proto file changed?";
249    cb->Run(phishing_url, false);
250    return;
251  }
252
253  URLFetcher* fetcher = URLFetcher::Create(0 /* ID is not used */,
254                                           GURL(kClientReportPhishingUrl),
255                                           URLFetcher::POST,
256                                           this);
257
258  // Remember which callback and URL correspond to the current fetcher object.
259  ClientReportInfo* info = new ClientReportInfo;
260  info->callback.swap(cb);  // takes ownership of the callback.
261  info->phishing_url = phishing_url;
262  client_phishing_reports_[fetcher] = info;
263
264  fetcher->set_load_flags(net::LOAD_DISABLE_CACHE);
265  fetcher->set_request_context(request_context_getter_.get());
266  fetcher->set_upload_data("application/octet-stream", request_data);
267  fetcher->Start();
268}
269
270void ClientSideDetectionService::HandleModelResponse(
271    const URLFetcher* source,
272    const GURL& url,
273    const URLRequestStatus& status,
274    int response_code,
275    const ResponseCookies& cookies,
276    const std::string& data) {
277  if (status.is_success() && RC_REQUEST_OK == response_code) {
278    // Copy the model because it has to be accessible after this function
279    // returns.  Once we have written the model to a file we will delete the
280    // temporary model string. TODO(noelutz): don't store the model to disk if
281    // it's invalid.
282    tmp_model_string_.reset(new std::string(data));
283    base::FileUtilProxy::CreateOrOpenCallback* cb =
284        callback_factory_.NewCallback(
285            &ClientSideDetectionService::CreateModelFileDone);
286    if (!base::FileUtilProxy::CreateOrOpen(
287            BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE),
288            model_path_,
289            base::PLATFORM_FILE_CREATE_ALWAYS |
290            base::PLATFORM_FILE_WRITE |
291            base::PLATFORM_FILE_READ,
292            cb)) {
293      delete cb;
294      SetModelStatus(ERROR_STATUS);
295    }
296  } else {
297    SetModelStatus(ERROR_STATUS);
298  }
299}
300
301void ClientSideDetectionService::HandlePhishingVerdict(
302    const URLFetcher* source,
303    const GURL& url,
304    const URLRequestStatus& status,
305    int response_code,
306    const ResponseCookies& cookies,
307    const std::string& data) {
308  ClientPhishingResponse response;
309  scoped_ptr<ClientReportInfo> info(client_phishing_reports_[source]);
310  if (status.is_success() && RC_REQUEST_OK == response_code  &&
311      response.ParseFromString(data)) {
312    info->callback->Run(info->phishing_url, response.phishy());
313  } else {
314    DLOG(ERROR) << "Unable to get the server verdict for URL: "
315                << info->phishing_url;
316    info->callback->Run(info->phishing_url, false);
317  }
318  client_phishing_reports_.erase(source);
319}
320
321}  // namespace safe_browsing
322