OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/safe_browsing/download_protection_service.h" | 5 #include "chrome/browser/safe_browsing/download_protection_service.h" |
6 | 6 |
7 #include "base/bind.h" | 7 #include "base/bind.h" |
8 #include "base/memory/scoped_ptr.h" | 8 #include "base/memory/scoped_ptr.h" |
9 #include "base/metrics/histogram.h" | 9 #include "base/metrics/histogram.h" |
10 #include "base/stl_util.h" | 10 #include "base/stl_util.h" |
| 11 #include "base/string_util.h" |
11 #include "chrome/browser/safe_browsing/safe_browsing_service.h" | 12 #include "chrome/browser/safe_browsing/safe_browsing_service.h" |
| 13 #include "chrome/browser/safe_browsing/signature_util.h" |
12 #include "chrome/common/net/http_return.h" | 14 #include "chrome/common/net/http_return.h" |
13 #include "chrome/common/safe_browsing/csd.pb.h" | 15 #include "chrome/common/safe_browsing/csd.pb.h" |
14 #include "content/browser/browser_thread.h" | 16 #include "content/browser/browser_thread.h" |
| 17 #include "content/browser/download/download_item.h" |
15 #include "content/public/common/url_fetcher.h" | 18 #include "content/public/common/url_fetcher.h" |
| 19 #include "content/public/common/url_fetcher_delegate.h" |
16 #include "net/base/load_flags.h" | 20 #include "net/base/load_flags.h" |
17 #include "net/url_request/url_request_context_getter.h" | 21 #include "net/url_request/url_request_context_getter.h" |
18 #include "net/url_request/url_request_status.h" | 22 #include "net/url_request/url_request_status.h" |
19 | 23 |
20 namespace safe_browsing { | 24 namespace safe_browsing { |
21 | 25 |
22 const char DownloadProtectionService::kDownloadRequestUrl[] = | 26 const char DownloadProtectionService::kDownloadRequestUrl[] = |
23 "https://sb-ssl.google.com/safebrowsing/clientreport/download"; | 27 "https://sb-ssl.google.com/safebrowsing/clientreport/download"; |
24 | 28 |
| 29 namespace { |
| 30 bool IsBinaryFile(const FilePath& file) { |
| 31 return (file.MatchesExtension(FILE_PATH_LITERAL(".exe")) || |
| 32 file.MatchesExtension(FILE_PATH_LITERAL(".cab")) || |
| 33 file.MatchesExtension(FILE_PATH_LITERAL(".msi"))); |
| 34 } |
| 35 } // namespace |
| 36 |
25 DownloadProtectionService::DownloadInfo::DownloadInfo() | 37 DownloadProtectionService::DownloadInfo::DownloadInfo() |
26 : total_bytes(0), user_initiated(false) {} | 38 : total_bytes(0), user_initiated(false) {} |
27 | 39 |
28 DownloadProtectionService::DownloadInfo::~DownloadInfo() {} | 40 DownloadProtectionService::DownloadInfo::~DownloadInfo() {} |
29 | 41 |
30 DownloadProtectionService::DownloadProtectionService( | 42 // static |
31 SafeBrowsingService* sb_service, | 43 DownloadProtectionService::DownloadInfo |
32 net::URLRequestContextGetter* request_context_getter) | 44 DownloadProtectionService::DownloadInfo::FromDownloadItem( |
33 : sb_service_(sb_service), | 45 const DownloadItem& item) { |
34 request_context_getter_(request_context_getter), | 46 DownloadInfo download_info; |
35 enabled_(false) {} | 47 download_info.local_file = item.full_path(); |
36 | 48 download_info.download_url_chain = item.url_chain(); |
37 DownloadProtectionService::~DownloadProtectionService() { | 49 download_info.referrer_url = item.referrer_url(); |
38 STLDeleteContainerPairFirstPointers(download_requests_.begin(), | 50 // TODO(bryner): Fill in the hash (we shouldn't compute it again) |
39 download_requests_.end()); | 51 download_info.total_bytes = item.total_bytes(); |
40 download_requests_.clear(); | 52 // TODO(bryner): Populate user_initiated |
| 53 return download_info; |
41 } | 54 } |
42 | 55 |
43 void DownloadProtectionService::SetEnabled(bool enabled) { | 56 class DownloadProtectionService::CheckClientDownloadRequest |
44 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 57 : public base::RefCountedThreadSafe< |
45 BrowserThread::PostTask( | 58 DownloadProtectionService::CheckClientDownloadRequest, |
46 BrowserThread::IO, | 59 BrowserThread::DeleteOnUIThread>, |
47 FROM_HERE, | 60 public content::URLFetcherDelegate { |
48 base::Bind(&DownloadProtectionService::SetEnabledOnIOThread, | 61 public: |
49 this, enabled)); | 62 CheckClientDownloadRequest(const DownloadInfo& info, |
50 } | 63 const CheckDownloadCallback& callback, |
| 64 DownloadProtectionService* service, |
| 65 SafeBrowsingService* sb_service) |
| 66 : info_(info), |
| 67 callback_(callback), |
| 68 service_(service), |
| 69 sb_service_(sb_service), |
| 70 pingback_enabled_(service_->enabled()) { |
| 71 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 72 } |
51 | 73 |
52 void DownloadProtectionService::SetEnabledOnIOThread(bool enabled) { | 74 void Start() { |
53 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 75 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
54 if (enabled == enabled_) { | 76 // TODO(noelutz): implement some cache to make sure we don't issue the same |
55 return; | 77 // request over and over again if a user downloads the same binary multiple |
56 } | 78 // times. |
57 enabled_ = enabled; | 79 if (info_.download_url_chain.empty()) { |
58 if (!enabled_) { | 80 RecordStats(REASON_INVALID_URL); |
59 for (DownloadRequests::iterator it = download_requests_.begin(); | 81 PostFinishTask(SAFE); |
60 it != download_requests_.end(); ++it) { | |
61 it->second.Run(SAFE); | |
62 } | |
63 STLDeleteContainerPairFirstPointers(download_requests_.begin(), | |
64 download_requests_.end()); | |
65 download_requests_.clear(); | |
66 } | |
67 } | |
68 | |
69 void DownloadProtectionService::OnURLFetchComplete( | |
70 const content::URLFetcher* source) { | |
71 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
72 scoped_ptr<const content::URLFetcher> s(source); | |
73 if (download_requests_.find(source) != download_requests_.end()) { | |
74 CheckDownloadCallback callback = download_requests_[source]; | |
75 download_requests_.erase(source); | |
76 if (!enabled_) { | |
77 // SafeBrowsing got disabled. We can't do anything. Note: the request | |
78 // object will be deleted. | |
79 RecordStats(REASON_SB_DISABLED); | |
80 return; | 82 return; |
81 } | 83 } |
| 84 const GURL& final_url = info_.download_url_chain.back(); |
| 85 if (!final_url.is_valid() || final_url.is_empty() || |
| 86 !final_url.SchemeIs("http")) { |
| 87 RecordStats(REASON_INVALID_URL); |
| 88 PostFinishTask(SAFE); |
| 89 return; // For now we only support HTTP download URLs. |
| 90 } |
| 91 |
| 92 if (!IsBinaryFile(info_.local_file)) { |
| 93 RecordStats(REASON_NOT_BINARY_FILE); |
| 94 PostFinishTask(SAFE); |
| 95 return; |
| 96 } |
| 97 |
| 98 // Compute features from the file contents. Note that we record histograms |
| 99 // based on the result, so this runs regardless of whether the pingbacks |
| 100 // are enabled. Since we do blocking I/O, this happens on the file thread. |
| 101 BrowserThread::PostTask( |
| 102 BrowserThread::FILE, |
| 103 FROM_HERE, |
| 104 base::Bind(&CheckClientDownloadRequest::ExtractFileFeatures, this)); |
| 105 } |
| 106 |
| 107 // Canceling a request will cause us to always report the result as SAFE. |
| 108 // In addition, the DownloadProtectionService will not be notified when the |
| 109 // request finishes, so it must drop its reference after calling Cancel. |
| 110 void Cancel() { |
| 111 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 112 service_ = NULL; |
| 113 if (fetcher_.get()) { |
| 114 // The DownloadProtectionService is going to release its reference, so we |
| 115 // might be destroyed before the URLFetcher completes. Cancel the |
| 116 // fetcher so it does not try to invoke OnURLFetchComplete. |
| 117 FinishRequest(SAFE); |
| 118 fetcher_.reset(); |
| 119 } |
| 120 // Note: If there is no fetcher, then some callback is still holding a |
| 121 // reference to this object. We'll eventually wind up in some method on |
| 122 // the UI thread that will call FinishRequest() and run the callback. |
| 123 } |
| 124 |
| 125 // From the content::URLFetcherDelegate interface. |
| 126 virtual void OnURLFetchComplete(const content::URLFetcher* source) OVERRIDE { |
| 127 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 128 DCHECK_EQ(source, fetcher_.get()); |
| 129 VLOG(2) << "Received a response for URL: " |
| 130 << info_.download_url_chain.back() << ": success=" |
| 131 << source->GetStatus().is_success() << " response_code=" |
| 132 << source->GetResponseCode(); |
82 DownloadCheckResultReason reason = REASON_MAX; | 133 DownloadCheckResultReason reason = REASON_MAX; |
83 reason = REASON_SERVER_PING_FAILED; | 134 reason = REASON_SERVER_PING_FAILED; |
84 if (source->GetStatus().is_success() && | 135 if (source->GetStatus().is_success() && |
85 RC_REQUEST_OK == source->GetResponseCode()) { | 136 RC_REQUEST_OK == source->GetResponseCode()) { |
86 std::string data; | 137 std::string data; |
87 source->GetResponseAsString(&data); | 138 source->GetResponseAsString(&data); |
88 if (data.size() > 0) { | 139 if (data.size() > 0) { |
89 // For now no matter what we'll always say the download is safe. | 140 // For now no matter what we'll always say the download is safe. |
90 // TODO(noelutz): Parse the response body to see exactly what's going | 141 // TODO(noelutz): Parse the response body to see exactly what's going |
91 // on. | 142 // on. |
92 reason = REASON_INVALID_RESPONSE_PROTO; | 143 reason = REASON_INVALID_RESPONSE_PROTO; |
93 } | 144 } |
94 } | 145 } |
| 146 |
| 147 if (reason != REASON_MAX) { |
| 148 RecordStats(reason); |
| 149 } |
| 150 // We don't need the fetcher anymore. |
| 151 fetcher_.reset(); |
| 152 FinishRequest(SAFE); |
| 153 } |
| 154 |
| 155 private: |
| 156 friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>; |
| 157 friend class DeleteTask<CheckClientDownloadRequest>; |
| 158 |
| 159 virtual ~CheckClientDownloadRequest() { |
| 160 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 161 } |
| 162 |
| 163 void ExtractFileFeatures() { |
| 164 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 165 bool is_signed; |
| 166 if (safe_browsing::signature_util::IsSigned(info_.local_file)) { |
| 167 VLOG(2) << "Downloaded a signed binary: " << info_.local_file.value(); |
| 168 is_signed = true; |
| 169 } else { |
| 170 VLOG(2) << "Downloaded an unsigned binary: " << info_.local_file.value(); |
| 171 is_signed = false; |
| 172 } |
| 173 UMA_HISTOGRAM_BOOLEAN("SBClientDownload.SignedBinaryDownload", is_signed); |
| 174 |
| 175 // TODO(noelutz): DownloadInfo should also contain the IP address of every |
| 176 // URL in the redirect chain. We also should check whether the download |
| 177 // URL is hosted on the internal network. |
| 178 BrowserThread::PostTask( |
| 179 BrowserThread::IO, |
| 180 FROM_HERE, |
| 181 base::Bind(&CheckClientDownloadRequest::CheckWhitelists, this)); |
| 182 } |
| 183 |
| 184 void CheckWhitelists() { |
| 185 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 186 DownloadCheckResultReason reason = REASON_MAX; |
| 187 if (!pingback_enabled_ || !sb_service_.get()) { |
| 188 reason = REASON_SB_DISABLED; |
| 189 } else { |
| 190 for (size_t i = 0; i < info_.download_url_chain.size(); ++i) { |
| 191 const GURL& url = info_.download_url_chain[i]; |
| 192 if (url.is_valid() && sb_service_->MatchDownloadWhitelistUrl(url)) { |
| 193 reason = REASON_WHITELISTED_URL; |
| 194 break; |
| 195 } |
| 196 } |
| 197 if (info_.referrer_url.is_valid() && |
| 198 sb_service_->MatchDownloadWhitelistUrl(info_.referrer_url)) { |
| 199 reason = REASON_WHITELISTED_REFERRER; |
| 200 } |
| 201 } |
| 202 if (reason != REASON_MAX) { |
| 203 RecordStats(reason); |
| 204 PostFinishTask(SAFE); |
| 205 return; |
| 206 } |
| 207 |
| 208 // TODO(noelutz): check signature and CA against whitelist. |
| 209 |
| 210 // The URLFetcher is owned by the UI thread, so post a message to |
| 211 // start the pingback. |
95 BrowserThread::PostTask( | 212 BrowserThread::PostTask( |
96 BrowserThread::UI, | 213 BrowserThread::UI, |
97 FROM_HERE, | 214 FROM_HERE, |
98 base::Bind(&DownloadProtectionService::EndCheckClientDownload, | 215 base::Bind(&CheckClientDownloadRequest::SendRequest, this)); |
99 this, SAFE, reason, callback)); | 216 } |
100 } else { | 217 |
101 NOTREACHED(); | 218 void SendRequest() { |
102 } | 219 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
103 } | 220 |
104 | 221 // This is our last chance to check whether the request has been canceled |
105 bool DownloadProtectionService::CheckClientDownload( | 222 // before sending it. |
106 const DownloadInfo& info, | 223 if (!service_) { |
107 const CheckDownloadCallback& callback) { | 224 FinishRequest(SAFE); |
108 // TODO(noelutz): implement some cache to make sure we don't issue the same | 225 return; |
109 // request over and over again if a user downloads the same binary multiple | 226 } |
110 // times. | 227 |
111 if (info.download_url_chain.empty()) { | 228 ClientDownloadRequest request; |
112 RecordStats(REASON_INVALID_URL); | 229 request.set_url(info_.download_url_chain.back().spec()); |
113 return true; | 230 request.mutable_digests()->set_sha256(info_.sha256_hash); |
114 } | 231 request.set_length(info_.total_bytes); |
115 const GURL& final_url = info.download_url_chain.back(); | 232 for (size_t i = 0; i < info_.download_url_chain.size(); ++i) { |
116 if (!final_url.is_valid() || final_url.is_empty() || | 233 ClientDownloadRequest::Resource* resource = request.add_resources(); |
117 !final_url.SchemeIs("http")) { | 234 resource->set_url(info_.download_url_chain[i].spec()); |
118 RecordStats(REASON_INVALID_URL); | 235 if (i == info_.download_url_chain.size() - 1) { |
119 return true; // For now we only support HTTP download URLs. | 236 // The last URL in the chain is the download URL. |
120 } | 237 resource->set_type(ClientDownloadRequest::DOWNLOAD_URL); |
121 // TODO(noelutz): DownloadInfo should also contain the IP address of every | 238 resource->set_referrer(info_.referrer_url.spec()); |
122 // URL in the redirect chain. We also should check whether the download URL | 239 } else { |
123 // is hosted on the internal network. | 240 resource->set_type(ClientDownloadRequest::DOWNLOAD_REDIRECT); |
124 BrowserThread::PostTask( | 241 } |
125 BrowserThread::IO, | 242 // TODO(noelutz): fill out the remote IP addresses. |
126 FROM_HERE, | 243 } |
127 base::Bind(&DownloadProtectionService::StartCheckClientDownload, | 244 request.set_user_initiated(info_.user_initiated); |
128 this, info, callback)); | 245 std::string request_data; |
129 return false; | 246 if (!request.SerializeToString(&request_data)) { |
130 } | 247 RecordStats(REASON_INVALID_REQUEST_PROTO); |
131 | 248 FinishRequest(SAFE); |
132 void DownloadProtectionService::StartCheckClientDownload( | 249 return; |
133 const DownloadInfo& info, | 250 } |
134 const CheckDownloadCallback& callback) { | 251 |
135 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 252 VLOG(2) << "Sending a request for URL: " |
136 if (!enabled_ || !sb_service_.get()) { | 253 << info_.download_url_chain.back(); |
137 // This is a hard fail. We won't even call the callback in this case. | 254 fetcher_.reset(content::URLFetcher::Create(0 /* ID used for testing */, |
138 RecordStats(REASON_SB_DISABLED); | 255 GURL(kDownloadRequestUrl), |
139 return; | 256 content::URLFetcher::POST, |
140 } | 257 this)); |
141 DownloadCheckResultReason reason = REASON_MAX; | 258 fetcher_->SetLoadFlags(net::LOAD_DISABLE_CACHE); |
142 for (size_t i = 0; i < info.download_url_chain.size(); ++i) { | 259 fetcher_->SetRequestContext(service_->request_context_getter_.get()); |
143 if (sb_service_->MatchDownloadWhitelistUrl(info.download_url_chain[i])) { | 260 fetcher_->SetUploadData("application/octet-stream", request_data); |
144 reason = REASON_WHITELISTED_URL; | 261 fetcher_->Start(); |
145 break; | 262 } |
146 } | 263 |
147 } | 264 void PostFinishTask(DownloadCheckResult result) { |
148 if (sb_service_->MatchDownloadWhitelistUrl(info.referrer_url)) { | |
149 reason = REASON_WHITELISTED_REFERRER; | |
150 } | |
151 // TODO(noelutz): check signature and CA against whitelist. | |
152 | |
153 ClientDownloadRequest request; | |
154 request.set_url(info.download_url_chain.back().spec()); | |
155 request.mutable_digests()->set_sha256(info.sha256_hash); | |
156 request.set_length(info.total_bytes); | |
157 for (size_t i = 0; i < info.download_url_chain.size(); ++i) { | |
158 ClientDownloadRequest::Resource* resource = request.add_resources(); | |
159 resource->set_url(info.download_url_chain[i].spec()); | |
160 if (i == info.download_url_chain.size() - 1) { | |
161 // The last URL in the chain is the download URL. | |
162 resource->set_type(ClientDownloadRequest::DOWNLOAD_URL); | |
163 resource->set_referrer(info.referrer_url.spec()); | |
164 } else { | |
165 resource->set_type(ClientDownloadRequest::DOWNLOAD_REDIRECT); | |
166 } | |
167 // TODO(noelutz): fill out the remote IP addresses. | |
168 } | |
169 request.set_user_initiated(info.user_initiated); | |
170 std::string request_data; | |
171 if (!request.SerializeToString(&request_data)) { | |
172 reason = REASON_INVALID_REQUEST_PROTO; | |
173 } | |
174 | |
175 if (reason != REASON_MAX) { | |
176 // We stop here because the download is considered safe. | |
177 BrowserThread::PostTask( | 265 BrowserThread::PostTask( |
178 BrowserThread::UI, | 266 BrowserThread::UI, |
179 FROM_HERE, | 267 FROM_HERE, |
180 base::Bind(&DownloadProtectionService::EndCheckClientDownload, | 268 base::Bind(&CheckClientDownloadRequest::FinishRequest, this, result)); |
181 this, SAFE, reason, callback)); | 269 } |
| 270 |
| 271 void FinishRequest(DownloadCheckResult result) { |
| 272 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 273 if (service_) { |
| 274 callback_.Run(result); |
| 275 service_->RequestFinished(this); |
| 276 } else { |
| 277 callback_.Run(SAFE); |
| 278 } |
| 279 } |
| 280 |
| 281 void RecordStats(DownloadCheckResultReason reason) { |
| 282 UMA_HISTOGRAM_ENUMERATION("SBClientDownload.CheckDownloadStats", |
| 283 reason, |
| 284 REASON_MAX); |
| 285 } |
| 286 |
| 287 DownloadInfo info_; |
| 288 CheckDownloadCallback callback_; |
| 289 // Will be NULL if the request has been canceled. |
| 290 DownloadProtectionService* service_; |
| 291 scoped_refptr<SafeBrowsingService> sb_service_; |
| 292 bool pingback_enabled_; |
| 293 scoped_ptr<content::URLFetcher> fetcher_; |
| 294 |
| 295 DISALLOW_COPY_AND_ASSIGN(CheckClientDownloadRequest); |
| 296 }; |
| 297 |
| 298 DownloadProtectionService::DownloadProtectionService( |
| 299 SafeBrowsingService* sb_service, |
| 300 net::URLRequestContextGetter* request_context_getter) |
| 301 : sb_service_(sb_service), |
| 302 request_context_getter_(request_context_getter), |
| 303 enabled_(false) {} |
| 304 |
| 305 DownloadProtectionService::~DownloadProtectionService() { |
| 306 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 307 CancelPendingRequests(); |
| 308 } |
| 309 |
| 310 void DownloadProtectionService::SetEnabled(bool enabled) { |
| 311 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 312 if (enabled == enabled_) { |
182 return; | 313 return; |
183 } | 314 } |
184 | 315 enabled_ = enabled; |
185 content::URLFetcher* fetcher = content::URLFetcher::Create( | 316 if (!enabled_) { |
186 0 /* ID used for testing */, GURL(kDownloadRequestUrl), | 317 CancelPendingRequests(); |
187 content::URLFetcher::POST, this); | 318 } |
188 download_requests_[fetcher] = callback; | 319 } |
189 fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE); | 320 |
190 fetcher->SetRequestContext(request_context_getter_.get()); | 321 void DownloadProtectionService::CheckClientDownload( |
191 fetcher->SetUploadData("application/octet-stream", request_data); | 322 const DownloadProtectionService::DownloadInfo& info, |
192 fetcher->Start(); | |
193 } | |
194 | |
195 void DownloadProtectionService::EndCheckClientDownload( | |
196 DownloadCheckResult result, | |
197 DownloadCheckResultReason reason, | |
198 const CheckDownloadCallback& callback) { | 323 const CheckDownloadCallback& callback) { |
199 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 324 scoped_refptr<CheckClientDownloadRequest> request( |
200 RecordStats(reason); | 325 new CheckClientDownloadRequest(info, callback, this, sb_service_)); |
201 callback.Run(result); | 326 download_requests_.insert(request); |
202 } | 327 request->Start(); |
203 | 328 } |
204 void DownloadProtectionService::RecordStats(DownloadCheckResultReason reason) { | 329 |
205 UMA_HISTOGRAM_ENUMERATION("SBClientDownload.CheckDownloadStats", | 330 void DownloadProtectionService::CancelPendingRequests() { |
206 reason, | 331 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
207 REASON_MAX); | 332 for (std::set<scoped_refptr<CheckClientDownloadRequest> >::iterator it = |
208 } | 333 download_requests_.begin(); |
| 334 it != download_requests_.end(); ++it) { |
| 335 (*it)->Cancel(); |
| 336 } |
| 337 download_requests_.clear(); |
| 338 } |
| 339 |
| 340 void DownloadProtectionService::RequestFinished( |
| 341 CheckClientDownloadRequest* request) { |
| 342 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 343 std::set<scoped_refptr<CheckClientDownloadRequest> >::iterator it = |
| 344 download_requests_.find(request); |
| 345 DCHECK(it != download_requests_.end()); |
| 346 download_requests_.erase(*it); |
| 347 } |
| 348 |
209 } // namespace safe_browsing | 349 } // namespace safe_browsing |
OLD | NEW |