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