Chromium Code Reviews
|
| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2011 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 // Implementation of the MalwareDetails class. | |
| 6 | |
| 7 #include "chrome/browser/safe_browsing/malware_details.h" | |
| 8 | |
| 9 #include "base/callback.h" | |
| 10 #include "base/lazy_instance.h" | |
| 11 #include "base/md5.h" | |
| 12 #include "chrome/browser/net/chrome_url_request_context.h" | |
| 13 #include "chrome/browser/profiles/profile.h" | |
| 14 #include "chrome/browser/safe_browsing/malware_details_cache.h" | |
| 15 #include "chrome/browser/safe_browsing/safe_browsing_service.h" | |
| 16 #include "chrome/browser/safe_browsing/report.pb.h" | |
| 17 #include "content/browser/browser_thread.h" | |
| 18 #include "net/base/io_buffer.h" | |
| 19 #include "net/disk_cache/disk_cache.h" | |
| 20 #include "net/http/http_cache.h" | |
| 21 #include "net/http/http_response_headers.h" | |
| 22 #include "net/http/http_response_info.h" | |
| 23 #include "net/url_request/url_request_context_getter.h" | |
| 24 | |
| 25 using safe_browsing::ClientMalwareReportRequest; | |
| 26 | |
| 27 // Only send small files for now, a better strategy would use the size | |
| 28 // of the whole report and the user's bandwidth. | |
| 29 static const int kMaxBodySizeBytes = 1024; | |
| 30 | |
| 31 MalwareDetailsCacheCollector::MalwareDetailsCacheCollector() | |
| 32 : cache_state_(STATE_NONE), | |
| 33 ALLOW_THIS_IN_INITIALIZER_LIST( | |
| 34 cache_callback_(this , &MalwareDetailsCacheCollector::CacheLoop)), | |
| 35 ALLOW_THIS_IN_INITIALIZER_LIST( | |
| 36 entry_callback_( | |
| 37 new net::CancelableCompletionCallback< | |
| 38 MalwareDetailsCacheCollector>( | |
| 39 this, &MalwareDetailsCacheCollector::CacheLoop))) { | |
| 40 } | |
| 41 | |
| 42 MalwareDetailsCacheCollector::~MalwareDetailsCacheCollector() { | |
| 43 } | |
| 44 | |
| 45 void MalwareDetailsCacheCollector::StartCacheCollection( | |
| 46 net::URLRequestContextGetter* request_context_getter, | |
| 47 safe_browsing::ResourceMap* resources, | |
| 48 int* result, | |
| 49 Task* callback) { | |
| 50 // Start the data collection from the HTTP cache. | |
| 51 DVLOG(1) << "Getting disk_cache data for all urls..."; | |
| 52 request_context_getter_ = request_context_getter; | |
| 53 resources_ = resources; | |
| 54 resources_it_ = resources_->begin(); | |
| 55 cache_state_ = STATE_OPEN_CACHE; | |
| 56 result_ = result; | |
| 57 callback_ = callback; | |
| 58 | |
| 59 AddRef(); // The cache_callback_ calls are asynchronous and not refcounted. | |
| 60 | |
| 61 // Post a task in the message loop, so the callers don't need to | |
| 62 // check if we call their callback immediately. | |
| 63 BrowserThread::PostTask( | |
| 64 BrowserThread::IO, FROM_HERE, | |
| 65 NewRunnableMethod( | |
| 66 this, &MalwareDetailsCacheCollector::CacheLoop, net::OK)); | |
| 67 } | |
| 68 | |
| 69 bool MalwareDetailsCacheCollector::InProgress() { | |
| 70 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 71 return cache_state_ != STATE_NONE; | |
| 72 } | |
| 73 | |
| 74 // The IO thread loop. Called once the DOM details are callected, | |
| 75 // and then is passed to all the cache lookups as a callback. | |
| 76 void MalwareDetailsCacheCollector::CacheLoop(int result) { | |
| 77 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 78 DCHECK(cache_state_ != STATE_NONE); | |
| 79 | |
| 80 int rv = result; | |
| 81 do { | |
| 82 DVLOG(1) << "cache_state_:" << cache_state_; | |
| 83 CacheState state = cache_state_; | |
| 84 cache_state_ = STATE_NONE; | |
| 85 switch (state) { | |
| 86 case STATE_OPEN_CACHE: | |
| 87 rv = DoOpenCache(); | |
| 88 break; | |
| 89 case STATE_OPEN_CACHE_COMPLETE: | |
| 90 rv = DoOpenCacheComplete(rv); | |
| 91 break; | |
| 92 case STATE_OPEN_ENTRY: | |
| 93 rv = DoOpenEntry(); | |
| 94 break; | |
| 95 case STATE_OPEN_ENTRY_COMPLETE: | |
| 96 rv = DoOpenEntryComplete(rv); | |
| 97 break; | |
| 98 case STATE_READ_RESPONSE: | |
| 99 rv = DoReadResponse(); | |
| 100 break; | |
| 101 case STATE_READ_RESPONSE_COMPLETE: | |
| 102 rv = DoReadResponseComplete(rv); | |
| 103 break; | |
| 104 case STATE_READ_DATA: | |
| 105 rv = DoReadData(); | |
| 106 break; | |
| 107 case STATE_READ_DATA_COMPLETE: | |
| 108 rv = DoReadDataComplete(rv); | |
| 109 break; | |
| 110 default: | |
| 111 NOTREACHED() << "Bad state:" << state; | |
| 112 rv = net::ERR_FAILED; | |
| 113 break; | |
| 114 } | |
| 115 } while (rv != net::ERR_IO_PENDING && cache_state_ != STATE_NONE); | |
| 116 | |
| 117 if (rv != net::ERR_IO_PENDING) { | |
| 118 DVLOG(1) << "CacheIO done."; | |
| 119 DoCacheDone(rv); | |
| 120 } | |
| 121 } | |
| 122 | |
| 123 net::URLRequestContext* MalwareDetailsCacheCollector::GetURLRequestContext() { | |
| 124 if (!request_context_getter_) { | |
| 125 DVLOG(1) << "Missing request context getter"; | |
| 126 return NULL; | |
| 127 } | |
| 128 return request_context_getter_->GetURLRequestContext(); | |
| 129 } | |
| 130 | |
| 131 int MalwareDetailsCacheCollector::DoOpenCache() { | |
| 132 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 133 cache_state_ = STATE_OPEN_CACHE_COMPLETE; | |
| 134 | |
| 135 net::URLRequestContext* context = GetURLRequestContext(); | |
| 136 if (!context) { | |
| 137 DVLOG(1) << "Could not get URLRequestContext"; | |
| 138 cache_state_ = STATE_NONE; | |
| 139 return net::ERR_FAILED; | |
| 140 } | |
| 141 | |
| 142 if (!context->http_transaction_factory()) { | |
| 143 DVLOG(1) << "Could not get HTTP transaction factory"; | |
| 144 cache_state_ = STATE_NONE; | |
| 145 return net::ERR_FAILED; | |
| 146 } | |
| 147 | |
| 148 net::HttpCache* http_cache = | |
| 149 context->http_transaction_factory()->GetCache(); | |
| 150 if (!http_cache) { | |
| 151 DVLOG(1) << "Could not get http cache"; | |
| 152 cache_state_ = STATE_NONE; | |
| 153 return net::ERR_FAILED; | |
| 154 } | |
| 155 | |
| 156 return http_cache->GetBackend(&cache_, &cache_callback_); | |
| 157 } | |
| 158 | |
| 159 int MalwareDetailsCacheCollector::DoOpenCacheComplete(int result) { | |
| 160 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 161 cache_state_ = STATE_OPEN_ENTRY; | |
| 162 | |
| 163 if (result == net::ERR_FAILED || !cache_) { | |
| 164 DVLOG(1) << "Could not get disk cache"; | |
| 165 cache_state_ = STATE_NONE; | |
| 166 return net::ERR_FAILED; | |
| 167 } | |
| 168 | |
| 169 return net::OK; | |
| 170 } | |
| 171 | |
| 172 int MalwareDetailsCacheCollector::DoOpenEntry() { | |
| 173 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 174 cache_state_ = STATE_OPEN_ENTRY_COMPLETE; | |
| 175 | |
| 176 if (resources_it_ == resources_->end()) { | |
| 177 cache_state_ = STATE_NONE; | |
| 178 return net::OK; | |
| 179 } | |
| 180 | |
| 181 entry_ = NULL; // Reset the entry. | |
| 182 // resources_it_ is an iterator to a map, key = URL, value = | |
| 183 // ClientMalwareReportRequest::Resource. | |
| 184 return cache_->OpenEntry(resources_it_->first, &entry_, &cache_callback_); | |
| 185 } | |
| 186 | |
| 187 int MalwareDetailsCacheCollector::DoOpenEntryComplete(int rv) { | |
| 188 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 189 if (rv != net::OK || !entry_) { | |
| 190 DVLOG(1) << "Error opening entry, or entry not found: Advancing."; | |
| 191 return AdvanceEntry(); | |
| 192 } | |
| 193 | |
| 194 cache_state_ = STATE_READ_RESPONSE; | |
| 195 return net::OK; | |
| 196 } | |
| 197 | |
| 198 int MalwareDetailsCacheCollector::DoReadResponse() { | |
| 199 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 200 cache_state_ = STATE_READ_RESPONSE_COMPLETE; | |
| 201 | |
| 202 // net::HttpCache::kResponseInfoIndex = 0. | |
|
mattm
2011/04/12 01:47:26
Do you know if there is a reason why the enum wher
panayiotis
2011/04/12 17:23:10
I don't know :/
| |
| 203 buf_len_ = entry_->GetDataSize(0); | |
| 204 if (!buf_len_) { | |
| 205 DVLOG(1) << "Empty response"; | |
| 206 return AdvanceEntry(); | |
| 207 } | |
| 208 | |
| 209 buf_ = new net::IOBuffer(buf_len_); | |
| 210 return entry_->ReadData(0, 0, buf_, buf_len_, entry_callback_); | |
| 211 } | |
| 212 | |
| 213 // Here, |result| is the length of the buffer read. | |
| 214 int MalwareDetailsCacheCollector::DoReadResponseComplete(int result) { | |
| 215 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 216 cache_state_ = STATE_READ_DATA; | |
| 217 | |
| 218 if (!result || result != buf_len_) { | |
| 219 DVLOG(1) << "Empty buffer or buffer length mismatch."; | |
| 220 // Reset buf_ and buf_len_. | |
| 221 buf_ = NULL; | |
| 222 buf_len_ = 0; | |
| 223 return AdvanceEntry(); | |
| 224 } | |
| 225 | |
| 226 linked_ptr<ClientMalwareReportRequest::Resource> | |
| 227 pb_resource = resources_it_->second; | |
| 228 net::HttpResponseInfo response; | |
| 229 bool truncated; | |
| 230 if (net::HttpCache::ParseResponseInfo(buf_->data(), buf_len_, &response, | |
| 231 &truncated)) { | |
| 232 if (!truncated) { | |
| 233 if (response.headers) { | |
| 234 ClientMalwareReportRequest::HTTPResponse* pb_response = | |
| 235 pb_resource->mutable_response(); | |
| 236 pb_response->mutable_firstline()->set_code( | |
| 237 response.headers->response_code()); | |
| 238 void* iter = NULL; | |
| 239 std::string name, value; | |
| 240 while (response.headers->EnumerateHeaderLines(&iter, &name, &value)) { | |
| 241 ClientMalwareReportRequest::HTTPHeader* pb_header = | |
| 242 pb_response->add_headers(); | |
| 243 pb_header->set_name(name); | |
| 244 // Strip any Set-Cookie headers. | |
|
mattm
2011/04/12 01:47:26
Is this for privacy reasons? Seems like it should
panayiotis
2011/04/12 17:23:10
Update the doc.
| |
| 245 if (LowerCaseEqualsASCII(name, "set-cookie")) { | |
| 246 pb_header->set_value(""); | |
| 247 } else { | |
| 248 pb_header->set_value(value); | |
| 249 } | |
| 250 } | |
| 251 } else { | |
| 252 DVLOG(1) << "Missing response headers."; | |
| 253 } | |
| 254 } else { | |
| 255 DVLOG(1) << "Response truncated"; | |
| 256 } | |
| 257 } else { | |
| 258 DVLOG(1) << "Could not parse HTTP cache entry"; | |
| 259 } | |
| 260 | |
| 261 // Reset buf_ and buf_len_. | |
| 262 buf_ = NULL; | |
| 263 buf_len_ = 0; | |
| 264 return net::OK; | |
| 265 } | |
| 266 | |
| 267 int MalwareDetailsCacheCollector::DoReadData() { | |
| 268 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 269 cache_state_ = STATE_READ_DATA_COMPLETE; | |
| 270 | |
| 271 // net::HttpCache::kResponseContentIndex = 1. | |
| 272 buf_len_ = entry_->GetDataSize(1); | |
| 273 if (!buf_len_) { | |
| 274 DVLOG(1) << "Empty data"; | |
| 275 return AdvanceEntry(); | |
| 276 } | |
| 277 | |
| 278 buf_ = new net::IOBuffer(buf_len_); | |
| 279 return entry_->ReadData(1, 0, buf_, buf_len_, entry_callback_); | |
|
mattm
2011/04/12 01:47:26
I don't quite grok the disk cache interface, but i
panayiotis
2011/04/12 17:23:10
Do you mean that the entries we lookup could be sp
mattm
2011/04/12 22:18:12
I was refering to ReadSparseData in disk_cache.h.
| |
| 280 } | |
| 281 | |
| 282 // Here, |result| is the length of the buffer read. | |
| 283 int MalwareDetailsCacheCollector::DoReadDataComplete(int result) { | |
| 284 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 285 | |
| 286 if (result && result == buf_len_) { | |
| 287 linked_ptr<ClientMalwareReportRequest::Resource> | |
| 288 pb_resource = resources_it_->second; | |
| 289 ClientMalwareReportRequest::HTTPResponse* pb_response = | |
| 290 pb_resource->mutable_response(); | |
| 291 if (buf_len_ <= kMaxBodySizeBytes) { // Only send small bodies for now. | |
| 292 pb_response->set_body(buf_->data(), buf_len_); | |
| 293 } | |
| 294 pb_response->set_bodylength(buf_len_); | |
| 295 MD5Digest digest; | |
| 296 MD5Sum(buf_->data(), buf_len_, &digest); | |
| 297 pb_response->set_bodydigest(MD5DigestToBase16(digest)); | |
| 298 } | |
| 299 | |
| 300 // Reset buf_ and buf_len. | |
| 301 buf_ = NULL; | |
| 302 buf_len_ = 0; | |
| 303 return AdvanceEntry(); | |
| 304 } | |
| 305 | |
| 306 int MalwareDetailsCacheCollector::AdvanceEntry() { | |
| 307 // Close this entry, advance to the next one. Create a task so we don't | |
| 308 // take over the IO thread for too long. | |
| 309 cache_state_ = STATE_OPEN_ENTRY; | |
| 310 ++resources_it_; | |
| 311 if (entry_) { | |
| 312 entry_->Close(); | |
| 313 entry_ = NULL; | |
| 314 } | |
| 315 BrowserThread::PostTask( | |
| 316 BrowserThread::IO, FROM_HERE, | |
| 317 NewRunnableMethod( | |
| 318 this, &MalwareDetailsCacheCollector::CacheLoop, net::OK)); | |
| 319 | |
| 320 return net::ERR_IO_PENDING; | |
| 321 } | |
| 322 | |
| 323 void MalwareDetailsCacheCollector::DoCacheDone(int result) { | |
| 324 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 325 *result_ = result; | |
| 326 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, callback_); | |
| 327 | |
| 328 Release(); | |
| 329 // We are now deleted. | |
| 330 } | |
| OLD | NEW |