[email protected] | 1519d85 | 2011-10-13 01:32:41 | [diff] [blame] | 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 | #include "chrome/browser/safe_browsing/download_protection_service.h" |
| 6 | |
| 7 | #include "base/bind.h" |
| 8 | #include "base/memory/scoped_ptr.h" |
| 9 | #include "base/metrics/histogram.h" |
| 10 | #include "base/stl_util.h" |
| 11 | #include "chrome/browser/safe_browsing/safe_browsing_service.h" |
| 12 | #include "chrome/common/net/http_return.h" |
| 13 | #include "chrome/common/safe_browsing/csd.pb.h" |
| 14 | #include "content/browser/browser_thread.h" |
| 15 | #include "net/base/load_flags.h" |
| 16 | #include "net/url_request/url_request_context_getter.h" |
| 17 | #include "net/url_request/url_request_status.h" |
| 18 | |
| 19 | namespace safe_browsing { |
| 20 | |
| 21 | const char DownloadProtectionService::kDownloadRequestUrl[] = |
| 22 | "https://sb-ssl.google.com/safebrowsing/clientreport/download"; |
| 23 | |
| 24 | DownloadProtectionService::DownloadInfo::DownloadInfo() |
| 25 | : total_bytes(0), user_initiated(false) {} |
| 26 | |
| 27 | DownloadProtectionService::DownloadInfo::~DownloadInfo() {} |
| 28 | |
| 29 | DownloadProtectionService::DownloadProtectionService( |
| 30 | SafeBrowsingService* sb_service, |
| 31 | net::URLRequestContextGetter* request_context_getter) |
| 32 | : sb_service_(sb_service), |
| 33 | request_context_getter_(request_context_getter), |
| 34 | enabled_(false) {} |
| 35 | |
| 36 | DownloadProtectionService::~DownloadProtectionService() { |
| 37 | STLDeleteContainerPairFirstPointers(download_requests_.begin(), |
| 38 | download_requests_.end()); |
| 39 | download_requests_.clear(); |
| 40 | } |
| 41 | |
| 42 | void DownloadProtectionService::SetEnabled(bool enabled) { |
| 43 | DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 44 | BrowserThread::PostTask( |
| 45 | BrowserThread::IO, |
| 46 | FROM_HERE, |
| 47 | base::Bind(&DownloadProtectionService::SetEnabledOnIOThread, |
| 48 | this, enabled)); |
| 49 | } |
| 50 | |
| 51 | void DownloadProtectionService::SetEnabledOnIOThread(bool enabled) { |
| 52 | DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 53 | if (enabled == enabled_) { |
| 54 | return; |
| 55 | } |
| 56 | enabled_ = enabled; |
| 57 | if (!enabled_) { |
| 58 | for (std::map<const URLFetcher*, CheckDownloadCallback>::iterator it = |
| 59 | download_requests_.begin(); |
| 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 URLFetcher* source, |
| 71 | const GURL& url, |
| 72 | const net::URLRequestStatus& status, |
| 73 | int response_code, |
| 74 | const net::ResponseCookies& cookies, |
| 75 | const std::string& data) { |
| 76 | DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 77 | scoped_ptr<const URLFetcher> s(source); // will delete the URLFetcher object. |
| 78 | if (download_requests_.find(source) != download_requests_.end()) { |
| 79 | CheckDownloadCallback callback = download_requests_[source]; |
| 80 | download_requests_.erase(source); |
| 81 | if (!enabled_) { |
| 82 | // SafeBrowsing got disabled. We can't do anything. Note: the request |
| 83 | // object will be deleted. |
| 84 | RecordStats(REASON_SB_DISABLED); |
| 85 | return; |
| 86 | } |
| 87 | DownloadCheckResultReason reason = REASON_MAX; |
| 88 | if (status.is_success() && |
| 89 | RC_REQUEST_OK == response_code && |
| 90 | data.size() > 0) { |
| 91 | // For now no matter what we'll always say the download is safe. |
| 92 | // TODO(noelutz): Parse the response body to see exactly what's going on. |
| 93 | reason = REASON_INVALID_RESPONSE_PROTO; |
| 94 | } else { |
| 95 | reason = REASON_SERVER_PING_FAILED; |
| 96 | } |
| 97 | BrowserThread::PostTask( |
| 98 | BrowserThread::UI, |
| 99 | FROM_HERE, |
| 100 | base::Bind(&DownloadProtectionService::EndCheckClientDownload, |
| 101 | this, SAFE, reason, callback)); |
| 102 | } else { |
| 103 | NOTREACHED(); |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | bool DownloadProtectionService::CheckClientDownload( |
| 108 | const DownloadInfo& info, |
| 109 | const CheckDownloadCallback& callback) { |
| 110 | // TODO(noelutz): implement some cache to make sure we don't issue the same |
| 111 | // request over and over again if a user downloads the same binary multiple |
| 112 | // times. |
| 113 | if (info.download_url_chain.empty()) { |
| 114 | RecordStats(REASON_INVALID_URL); |
| 115 | return true; |
| 116 | } |
| 117 | const GURL& final_url = info.download_url_chain.back(); |
| 118 | if (!final_url.is_valid() || final_url.is_empty() || |
| 119 | !final_url.SchemeIs("http")) { |
| 120 | RecordStats(REASON_INVALID_URL); |
| 121 | return true; // For now we only support HTTP download URLs. |
| 122 | } |
| 123 | // TODO(noelutz): DownloadInfo should also contain the IP address of every |
| 124 | // URL in the redirect chain. We also should check whether the download URL |
| 125 | // is hosted on the internal network. |
| 126 | BrowserThread::PostTask( |
| 127 | BrowserThread::IO, |
| 128 | FROM_HERE, |
| 129 | base::Bind(&DownloadProtectionService::StartCheckClientDownload, |
| 130 | this, info, callback)); |
| 131 | return false; |
| 132 | } |
| 133 | |
| 134 | void DownloadProtectionService::StartCheckClientDownload( |
| 135 | const DownloadInfo& info, |
| 136 | const CheckDownloadCallback& callback) { |
| 137 | DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 138 | if (!enabled_ || !sb_service_.get()) { |
| 139 | // This is a hard fail. We won't even call the callback in this case. |
| 140 | RecordStats(REASON_SB_DISABLED); |
| 141 | return; |
| 142 | } |
| 143 | DownloadCheckResultReason reason = REASON_MAX; |
| 144 | for (size_t i = 0; i < info.download_url_chain.size(); ++i) { |
| 145 | if (sb_service_->MatchDownloadWhitelistUrl(info.download_url_chain[i])) { |
| 146 | reason = REASON_WHITELISTED_URL; |
| 147 | break; |
| 148 | } |
| 149 | } |
| 150 | if (sb_service_->MatchDownloadWhitelistUrl(info.referrer_url)) { |
| 151 | reason = REASON_WHITELISTED_REFERRER; |
| 152 | } |
| 153 | // TODO(noelutz): check signature and CA against whitelist. |
| 154 | |
| 155 | ClientDownloadRequest request; |
| 156 | request.set_url(info.download_url_chain.back().spec()); |
| 157 | request.mutable_digests()->set_sha256(info.sha256_hash); |
| 158 | request.set_length(info.total_bytes); |
| 159 | for (size_t i = 0; i < info.download_url_chain.size(); ++i) { |
| 160 | ClientDownloadRequest::Resource* resource = request.add_resources(); |
| 161 | resource->set_url(info.download_url_chain[i].spec()); |
| 162 | if (i == info.download_url_chain.size() - 1) { |
| 163 | // The last URL in the chain is the download URL. |
| 164 | resource->set_type(ClientDownloadRequest::DOWNLOAD_URL); |
| 165 | resource->set_referrer(info.referrer_url.spec()); |
| 166 | } else { |
| 167 | resource->set_type(ClientDownloadRequest::DOWNLOAD_REDIRECT); |
| 168 | } |
| 169 | // TODO(noelutz): fill out the remote IP addresses. |
| 170 | } |
| 171 | request.set_user_initiated(info.user_initiated); |
| 172 | std::string request_data; |
| 173 | if (!request.SerializeToString(&request_data)) { |
| 174 | reason = REASON_INVALID_REQUEST_PROTO; |
| 175 | } |
| 176 | |
| 177 | if (reason != REASON_MAX) { |
| 178 | // We stop here because the download is considered safe. |
| 179 | BrowserThread::PostTask( |
| 180 | BrowserThread::UI, |
| 181 | FROM_HERE, |
| 182 | base::Bind(&DownloadProtectionService::EndCheckClientDownload, |
| 183 | this, SAFE, reason, callback)); |
| 184 | return; |
| 185 | } |
| 186 | |
| 187 | URLFetcher* fetcher = URLFetcher::Create(0 /* ID used for testing */, |
| 188 | GURL(kDownloadRequestUrl), |
| 189 | URLFetcher::POST, |
| 190 | this); |
| 191 | download_requests_[fetcher] = callback; |
| 192 | fetcher->set_load_flags(net::LOAD_DISABLE_CACHE); |
| 193 | fetcher->set_request_context(request_context_getter_.get()); |
| 194 | fetcher->set_upload_data("application/octet-stream", request_data); |
| 195 | fetcher->Start(); |
| 196 | } |
| 197 | |
| 198 | void DownloadProtectionService::EndCheckClientDownload( |
| 199 | DownloadCheckResult result, |
| 200 | DownloadCheckResultReason reason, |
| 201 | const CheckDownloadCallback& callback) { |
| 202 | DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 203 | RecordStats(reason); |
| 204 | callback.Run(result); |
| 205 | } |
| 206 | |
| 207 | void DownloadProtectionService::RecordStats(DownloadCheckResultReason reason) { |
| 208 | UMA_HISTOGRAM_ENUMERATION("SBClientDownload.CheckDownloadStats", |
| 209 | reason, |
| 210 | REASON_MAX); |
| 211 | } |
| 212 | } // namespace safe_browsing |