OLD | NEW |
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 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 | 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 "net/base/sdch_manager.h" | 5 #include "net/base/sdch_manager.h" |
6 | 6 |
7 #include "base/base64.h" | 7 #include "base/base64.h" |
8 #include "base/logging.h" | 8 #include "base/logging.h" |
9 #include "base/metrics/histogram.h" | 9 #include "base/metrics/histogram.h" |
10 #include "base/sha2.h" | 10 #include "base/sha2.h" |
11 #include "base/string_number_conversions.h" | 11 #include "base/string_number_conversions.h" |
12 #include "base/string_util.h" | 12 #include "base/string_util.h" |
13 #include "net/base/registry_controlled_domain.h" | 13 #include "net/base/registry_controlled_domain.h" |
14 #include "net/url_request/url_request_http_job.h" | 14 #include "net/url_request/url_request_http_job.h" |
15 | 15 |
16 using base::Time; | 16 using base::Time; |
17 using base::TimeDelta; | 17 using base::TimeDelta; |
18 | 18 |
19 //------------------------------------------------------------------------------ | 19 //------------------------------------------------------------------------------ |
20 // static | 20 // static |
21 const size_t SdchManager::kMaxDictionarySize = 1000000; | 21 const size_t SdchManager::kMaxDictionarySize = 1000000; |
22 | 22 |
23 // static | 23 // static |
24 const size_t SdchManager::kMaxDictionaryCount = 20; | 24 const size_t SdchManager::kMaxDictionaryCount = 20; |
25 | 25 |
26 // static | 26 // static |
27 SdchManager* SdchManager::global_; | 27 SdchManager* SdchManager::global_; |
28 | 28 |
| 29 //------------------------------------------------------------------------------ |
| 30 SdchManager::Dictionary::Dictionary(const std::string& dictionary_text, |
| 31 size_t offset, const std::string& client_hash, const GURL& gurl, |
| 32 const std::string& domain, const std::string& path, const Time& expiration, |
| 33 const std::set<int> ports) |
| 34 : text_(dictionary_text, offset), |
| 35 client_hash_(client_hash), |
| 36 url_(gurl), |
| 37 domain_(domain), |
| 38 path_(path), |
| 39 expiration_(expiration), |
| 40 ports_(ports) { |
| 41 } |
| 42 |
| 43 SdchManager::Dictionary::~Dictionary() { |
| 44 } |
| 45 |
| 46 bool SdchManager::Dictionary::CanAdvertise(const GURL& target_url) { |
| 47 if (!SdchManager::Global()->IsInSupportedDomain(target_url)) |
| 48 return false; |
| 49 /* The specific rules of when a dictionary should be advertised in an |
| 50 Avail-Dictionary header are modeled after the rules for cookie scoping. The |
| 51 terms "domain-match" and "pathmatch" are defined in RFC 2965 [6]. A |
| 52 dictionary may be advertised in the Avail-Dictionaries header exactly when |
| 53 all of the following are true: |
| 54 1. The server's effective host name domain-matches the Domain attribute of |
| 55 the dictionary. |
| 56 2. If the dictionary has a Port attribute, the request port is one of the |
| 57 ports listed in the Port attribute. |
| 58 3. The request URI path-matches the path header of the dictionary. |
| 59 4. The request is not an HTTPS request. |
| 60 */ |
| 61 if (!DomainMatch(target_url, domain_)) |
| 62 return false; |
| 63 if (!ports_.empty() && 0 == ports_.count(target_url.EffectiveIntPort())) |
| 64 return false; |
| 65 if (path_.size() && !PathMatch(target_url.path(), path_)) |
| 66 return false; |
| 67 if (target_url.SchemeIsSecure()) |
| 68 return false; |
| 69 if (Time::Now() > expiration_) |
| 70 return false; |
| 71 return true; |
| 72 } |
| 73 |
| 74 //------------------------------------------------------------------------------ |
| 75 // Security functions restricting loads and use of dictionaries. |
| 76 |
29 // static | 77 // static |
30 SdchManager* SdchManager::Global() { | 78 bool SdchManager::Dictionary::CanSet(const std::string& domain, |
31 return global_; | 79 const std::string& path, |
| 80 const std::set<int> ports, |
| 81 const GURL& dictionary_url) { |
| 82 if (!SdchManager::Global()->IsInSupportedDomain(dictionary_url)) |
| 83 return false; |
| 84 /* |
| 85 A dictionary is invalid and must not be stored if any of the following are |
| 86 true: |
| 87 1. The dictionary has no Domain attribute. |
| 88 2. The effective host name that derives from the referer URL host name does |
| 89 not domain-match the Domain attribute. |
| 90 3. The Domain attribute is a top level domain. |
| 91 4. The referer URL host is a host domain name (not IP address) and has the |
| 92 form HD, where D is the value of the Domain attribute, and H is a string |
| 93 that contains one or more dots. |
| 94 5. If the dictionary has a Port attribute and the referer URL's port was not |
| 95 in the list. |
| 96 */ |
| 97 |
| 98 // TODO(jar): Redirects in dictionary fetches might plausibly be problematic, |
| 99 // and hence the conservative approach is to not allow any redirects (if there |
| 100 // were any... then don't allow the dictionary to be set). |
| 101 |
| 102 if (domain.empty()) { |
| 103 SdchErrorRecovery(DICTIONARY_MISSING_DOMAIN_SPECIFIER); |
| 104 return false; // Domain is required. |
| 105 } |
| 106 if (net::RegistryControlledDomainService::GetDomainAndRegistry(domain).size() |
| 107 == 0) { |
| 108 SdchErrorRecovery(DICTIONARY_SPECIFIES_TOP_LEVEL_DOMAIN); |
| 109 return false; // domain was a TLD. |
| 110 } |
| 111 if (!Dictionary::DomainMatch(dictionary_url, domain)) { |
| 112 SdchErrorRecovery(DICTIONARY_DOMAIN_NOT_MATCHING_SOURCE_URL); |
| 113 return false; |
| 114 } |
| 115 |
| 116 std::string referrer_url_host = dictionary_url.host(); |
| 117 size_t postfix_domain_index = referrer_url_host.rfind(domain); |
| 118 // See if it is indeed a postfix, or just an internal string. |
| 119 if (referrer_url_host.size() == postfix_domain_index + domain.size()) { |
| 120 // It is a postfix... so check to see if there's a dot in the prefix. |
| 121 size_t end_of_host_index = referrer_url_host.find_first_of('.'); |
| 122 if (referrer_url_host.npos != end_of_host_index && |
| 123 end_of_host_index < postfix_domain_index) { |
| 124 SdchErrorRecovery(DICTIONARY_REFERER_URL_HAS_DOT_IN_PREFIX); |
| 125 return false; |
| 126 } |
| 127 } |
| 128 |
| 129 if (!ports.empty() |
| 130 && 0 == ports.count(dictionary_url.EffectiveIntPort())) { |
| 131 SdchErrorRecovery(DICTIONARY_PORT_NOT_MATCHING_SOURCE_URL); |
| 132 return false; |
| 133 } |
| 134 return true; |
32 } | 135 } |
33 | 136 |
34 // static | 137 // static |
35 void SdchManager::SdchErrorRecovery(ProblemCodes problem) { | 138 bool SdchManager::Dictionary::CanUse(const GURL& referring_url) { |
36 UMA_HISTOGRAM_ENUMERATION("Sdch3.ProblemCodes_4", problem, MAX_PROBLEM_CODE); | 139 if (!SdchManager::Global()->IsInSupportedDomain(referring_url)) |
| 140 return false; |
| 141 /* |
| 142 1. The request URL's host name domain-matches the Domain attribute of the |
| 143 dictionary. |
| 144 2. If the dictionary has a Port attribute, the request port is one of the |
| 145 ports listed in the Port attribute. |
| 146 3. The request URL path-matches the path attribute of the dictionary. |
| 147 4. The request is not an HTTPS request. |
| 148 */ |
| 149 if (!DomainMatch(referring_url, domain_)) { |
| 150 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_DOMAIN); |
| 151 return false; |
| 152 } |
| 153 if (!ports_.empty() |
| 154 && 0 == ports_.count(referring_url.EffectiveIntPort())) { |
| 155 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PORT_LIST); |
| 156 return false; |
| 157 } |
| 158 if (path_.size() && !PathMatch(referring_url.path(), path_)) { |
| 159 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PATH); |
| 160 return false; |
| 161 } |
| 162 if (referring_url.SchemeIsSecure()) { |
| 163 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_SCHEME); |
| 164 return false; |
| 165 } |
| 166 |
| 167 // TODO(jar): Remove overly restrictive failsafe test (added per security |
| 168 // review) when we have a need to be more general. |
| 169 if (!referring_url.SchemeIs("http")) { |
| 170 SdchErrorRecovery(ATTEMPT_TO_DECODE_NON_HTTP_DATA); |
| 171 return false; |
| 172 } |
| 173 |
| 174 return true; |
| 175 } |
| 176 |
| 177 bool SdchManager::Dictionary::PathMatch(const std::string& path, |
| 178 const std::string& restriction) { |
| 179 /* Must be either: |
| 180 1. P2 is equal to P1 |
| 181 2. P2 is a prefix of P1 and either the final character in P2 is "/" or the |
| 182 character following P2 in P1 is "/". |
| 183 */ |
| 184 if (path == restriction) |
| 185 return true; |
| 186 size_t prefix_length = restriction.size(); |
| 187 if (prefix_length > path.size()) |
| 188 return false; // Can't be a prefix. |
| 189 if (0 != path.compare(0, prefix_length, restriction)) |
| 190 return false; |
| 191 return restriction[prefix_length - 1] == '/' || path[prefix_length] == '/'; |
37 } | 192 } |
38 | 193 |
39 // static | 194 // static |
40 void SdchManager::ClearBlacklistings() { | 195 bool SdchManager::Dictionary::DomainMatch(const GURL& gurl, |
41 Global()->blacklisted_domains_.clear(); | 196 const std::string& restriction) { |
42 Global()->exponential_blacklist_count.clear(); | 197 // TODO(jar): This is not precisely a domain match definition. |
43 } | 198 return gurl.DomainIs(restriction.data(), restriction.size()); |
44 | |
45 // static | |
46 void SdchManager::ClearDomainBlacklisting(const std::string& domain) { | |
47 Global()->blacklisted_domains_.erase(StringToLowerASCII(domain)); | |
48 } | |
49 | |
50 // static | |
51 int SdchManager::BlackListDomainCount(const std::string& domain) { | |
52 if (Global()->blacklisted_domains_.end() == | |
53 Global()->blacklisted_domains_.find(domain)) | |
54 return 0; | |
55 return Global()->blacklisted_domains_[StringToLowerASCII(domain)]; | |
56 } | |
57 | |
58 // static | |
59 int SdchManager::BlacklistDomainExponential(const std::string& domain) { | |
60 if (Global()->exponential_blacklist_count.end() == | |
61 Global()->exponential_blacklist_count.find(domain)) | |
62 return 0; | |
63 return Global()->exponential_blacklist_count[StringToLowerASCII(domain)]; | |
64 } | 199 } |
65 | 200 |
66 //------------------------------------------------------------------------------ | 201 //------------------------------------------------------------------------------ |
67 SdchManager::SdchManager() : sdch_enabled_(false) { | 202 SdchManager::SdchManager() : sdch_enabled_(false) { |
68 DCHECK(!global_); | 203 DCHECK(!global_); |
69 global_ = this; | 204 global_ = this; |
70 } | 205 } |
71 | 206 |
72 SdchManager::~SdchManager() { | 207 SdchManager::~SdchManager() { |
73 DCHECK(global_ == this); | 208 DCHECK(global_ == this); |
74 while (!dictionaries_.empty()) { | 209 while (!dictionaries_.empty()) { |
75 DictionaryMap::iterator it = dictionaries_.begin(); | 210 DictionaryMap::iterator it = dictionaries_.begin(); |
76 it->second->Release(); | 211 it->second->Release(); |
77 dictionaries_.erase(it->first); | 212 dictionaries_.erase(it->first); |
78 } | 213 } |
79 global_ = NULL; | 214 global_ = NULL; |
80 } | 215 } |
81 | 216 |
82 // static | 217 // static |
83 void SdchManager::Shutdown() { | 218 void SdchManager::Shutdown() { |
84 if (!global_ ) | 219 if (!global_ ) |
85 return; | 220 return; |
86 global_->fetcher_.reset(NULL); | 221 global_->fetcher_.reset(NULL); |
87 } | 222 } |
88 | 223 |
89 // static | 224 // static |
| 225 SdchManager* SdchManager::Global() { |
| 226 return global_; |
| 227 } |
| 228 |
| 229 // static |
| 230 void SdchManager::SdchErrorRecovery(ProblemCodes problem) { |
| 231 UMA_HISTOGRAM_ENUMERATION("Sdch3.ProblemCodes_4", problem, MAX_PROBLEM_CODE); |
| 232 } |
| 233 |
| 234 void SdchManager::EnableSdchSupport(const std::string& domain) { |
| 235 // We presume that there is a SDCH manager instance. |
| 236 global_->supported_domain_ = domain; |
| 237 global_->sdch_enabled_ = true; |
| 238 } |
| 239 |
| 240 // static |
90 void SdchManager::BlacklistDomain(const GURL& url) { | 241 void SdchManager::BlacklistDomain(const GURL& url) { |
91 if (!global_ ) | 242 if (!global_ ) |
92 return; | 243 return; |
93 global_->SetAllowLatencyExperiment(url, false); | 244 global_->SetAllowLatencyExperiment(url, false); |
94 | 245 |
95 std::string domain(StringToLowerASCII(url.host())); | 246 std::string domain(StringToLowerASCII(url.host())); |
96 int count = global_->blacklisted_domains_[domain]; | 247 int count = global_->blacklisted_domains_[domain]; |
97 if (count > 0) | 248 if (count > 0) |
98 return; // Domain is already blacklisted. | 249 return; // Domain is already blacklisted. |
99 | 250 |
(...skipping 10 matching lines...) Expand all Loading... |
110 void SdchManager::BlacklistDomainForever(const GURL& url) { | 261 void SdchManager::BlacklistDomainForever(const GURL& url) { |
111 if (!global_ ) | 262 if (!global_ ) |
112 return; | 263 return; |
113 global_->SetAllowLatencyExperiment(url, false); | 264 global_->SetAllowLatencyExperiment(url, false); |
114 | 265 |
115 std::string domain(StringToLowerASCII(url.host())); | 266 std::string domain(StringToLowerASCII(url.host())); |
116 global_->exponential_blacklist_count[domain] = INT_MAX; | 267 global_->exponential_blacklist_count[domain] = INT_MAX; |
117 global_->blacklisted_domains_[domain] = INT_MAX; | 268 global_->blacklisted_domains_[domain] = INT_MAX; |
118 } | 269 } |
119 | 270 |
120 void SdchManager::EnableSdchSupport(const std::string& domain) { | 271 // static |
121 // We presume that there is a SDCH manager instance. | 272 void SdchManager::ClearBlacklistings() { |
122 global_->supported_domain_ = domain; | 273 Global()->blacklisted_domains_.clear(); |
123 global_->sdch_enabled_ = true; | 274 Global()->exponential_blacklist_count.clear(); |
| 275 } |
| 276 |
| 277 // static |
| 278 void SdchManager::ClearDomainBlacklisting(const std::string& domain) { |
| 279 Global()->blacklisted_domains_.erase(StringToLowerASCII(domain)); |
| 280 } |
| 281 |
| 282 // static |
| 283 int SdchManager::BlackListDomainCount(const std::string& domain) { |
| 284 if (Global()->blacklisted_domains_.end() == |
| 285 Global()->blacklisted_domains_.find(domain)) |
| 286 return 0; |
| 287 return Global()->blacklisted_domains_[StringToLowerASCII(domain)]; |
| 288 } |
| 289 |
| 290 // static |
| 291 int SdchManager::BlacklistDomainExponential(const std::string& domain) { |
| 292 if (Global()->exponential_blacklist_count.end() == |
| 293 Global()->exponential_blacklist_count.find(domain)) |
| 294 return 0; |
| 295 return Global()->exponential_blacklist_count[StringToLowerASCII(domain)]; |
124 } | 296 } |
125 | 297 |
126 bool SdchManager::IsInSupportedDomain(const GURL& url) { | 298 bool SdchManager::IsInSupportedDomain(const GURL& url) { |
127 if (!sdch_enabled_ ) | 299 if (!sdch_enabled_ ) |
128 return false; | 300 return false; |
129 if (!supported_domain_.empty() && | 301 if (!supported_domain_.empty() && |
130 !url.DomainIs(supported_domain_.data(), supported_domain_.size())) | 302 !url.DomainIs(supported_domain_.data(), supported_domain_.size())) |
131 return false; // It is not the singular supported domain. | 303 return false; // It is not the singular supported domain. |
132 | 304 |
133 if (blacklisted_domains_.empty()) | 305 if (blacklisted_domains_.empty()) |
134 return true; | 306 return true; |
135 | 307 |
136 std::string domain(StringToLowerASCII(url.host())); | 308 std::string domain(StringToLowerASCII(url.host())); |
137 DomainCounter::iterator it = blacklisted_domains_.find(domain); | 309 DomainCounter::iterator it = blacklisted_domains_.find(domain); |
138 if (blacklisted_domains_.end() == it) | 310 if (blacklisted_domains_.end() == it) |
139 return true; | 311 return true; |
140 | 312 |
141 int count = it->second - 1; | 313 int count = it->second - 1; |
142 if (count > 0) | 314 if (count > 0) |
143 blacklisted_domains_[domain] = count; | 315 blacklisted_domains_[domain] = count; |
144 else | 316 else |
145 blacklisted_domains_.erase(domain); | 317 blacklisted_domains_.erase(domain); |
146 SdchErrorRecovery(DOMAIN_BLACKLIST_INCLUDES_TARGET); | 318 SdchErrorRecovery(DOMAIN_BLACKLIST_INCLUDES_TARGET); |
147 return false; | 319 return false; |
148 } | 320 } |
149 | 321 |
| 322 void SdchManager::FetchDictionary(const GURL& request_url, |
| 323 const GURL& dictionary_url) { |
| 324 if (SdchManager::Global()->CanFetchDictionary(request_url, dictionary_url) && |
| 325 fetcher_.get()) |
| 326 fetcher_->Schedule(dictionary_url); |
| 327 } |
| 328 |
150 bool SdchManager::CanFetchDictionary(const GURL& referring_url, | 329 bool SdchManager::CanFetchDictionary(const GURL& referring_url, |
151 const GURL& dictionary_url) const { | 330 const GURL& dictionary_url) const { |
152 /* The user agent may retrieve a dictionary from the dictionary URL if all of | 331 /* The user agent may retrieve a dictionary from the dictionary URL if all of |
153 the following are true: | 332 the following are true: |
154 1 The dictionary URL host name matches the referrer URL host name | 333 1 The dictionary URL host name matches the referrer URL host name |
155 2 The dictionary URL host name domain matches the parent domain of the | 334 2 The dictionary URL host name domain matches the parent domain of the |
156 referrer URL host name | 335 referrer URL host name |
157 3 The parent domain of the referrer URL host name is not a top level | 336 3 The parent domain of the referrer URL host name is not a top level |
158 domain | 337 domain |
159 4 The dictionary URL is not an HTTPS URL. | 338 4 The dictionary URL is not an HTTPS URL. |
(...skipping 12 matching lines...) Expand all Loading... |
172 // TODO(jar): Remove this failsafe conservative hack which is more restrictive | 351 // TODO(jar): Remove this failsafe conservative hack which is more restrictive |
173 // than current SDCH spec when needed, and justified by security audit. | 352 // than current SDCH spec when needed, and justified by security audit. |
174 if (!referring_url.SchemeIs("http")) { | 353 if (!referring_url.SchemeIs("http")) { |
175 SdchErrorRecovery(DICTIONARY_SELECTED_FROM_NON_HTTP); | 354 SdchErrorRecovery(DICTIONARY_SELECTED_FROM_NON_HTTP); |
176 return false; | 355 return false; |
177 } | 356 } |
178 | 357 |
179 return true; | 358 return true; |
180 } | 359 } |
181 | 360 |
182 void SdchManager::FetchDictionary(const GURL& request_url, | |
183 const GURL& dictionary_url) { | |
184 if (SdchManager::Global()->CanFetchDictionary(request_url, dictionary_url) && | |
185 fetcher_.get()) | |
186 fetcher_->Schedule(dictionary_url); | |
187 } | |
188 | |
189 bool SdchManager::AddSdchDictionary(const std::string& dictionary_text, | 361 bool SdchManager::AddSdchDictionary(const std::string& dictionary_text, |
190 const GURL& dictionary_url) { | 362 const GURL& dictionary_url) { |
191 std::string client_hash; | 363 std::string client_hash; |
192 std::string server_hash; | 364 std::string server_hash; |
193 GenerateHash(dictionary_text, &client_hash, &server_hash); | 365 GenerateHash(dictionary_text, &client_hash, &server_hash); |
194 if (dictionaries_.find(server_hash) != dictionaries_.end()) { | 366 if (dictionaries_.find(server_hash) != dictionaries_.end()) { |
195 SdchErrorRecovery(DICTIONARY_ALREADY_LOADED); | 367 SdchErrorRecovery(DICTIONARY_ALREADY_LOADED); |
196 return false; // Already loaded. | 368 return false; // Already loaded. |
197 } | 369 } |
198 | 370 |
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
310 ++count; | 482 ++count; |
311 if (!list->empty()) | 483 if (!list->empty()) |
312 list->append(","); | 484 list->append(","); |
313 list->append(it->second->client_hash()); | 485 list->append(it->second->client_hash()); |
314 } | 486 } |
315 // Watch to see if we have corrupt or numerous dictionaries. | 487 // Watch to see if we have corrupt or numerous dictionaries. |
316 if (count > 0) | 488 if (count > 0) |
317 UMA_HISTOGRAM_COUNTS("Sdch3.Advertisement_Count", count); | 489 UMA_HISTOGRAM_COUNTS("Sdch3.Advertisement_Count", count); |
318 } | 490 } |
319 | 491 |
320 SdchManager::Dictionary::Dictionary(const std::string& dictionary_text, | |
321 size_t offset, const std::string& client_hash, const GURL& gurl, | |
322 const std::string& domain, const std::string& path, const Time& expiration, | |
323 const std::set<int> ports) | |
324 : text_(dictionary_text, offset), | |
325 client_hash_(client_hash), | |
326 url_(gurl), | |
327 domain_(domain), | |
328 path_(path), | |
329 expiration_(expiration), | |
330 ports_(ports) { | |
331 } | |
332 | |
333 SdchManager::Dictionary::~Dictionary() { | |
334 } | |
335 | |
336 // static | 492 // static |
337 void SdchManager::GenerateHash(const std::string& dictionary_text, | 493 void SdchManager::GenerateHash(const std::string& dictionary_text, |
338 std::string* client_hash, std::string* server_hash) { | 494 std::string* client_hash, std::string* server_hash) { |
339 char binary_hash[32]; | 495 char binary_hash[32]; |
340 base::SHA256HashString(dictionary_text, binary_hash, sizeof(binary_hash)); | 496 base::SHA256HashString(dictionary_text, binary_hash, sizeof(binary_hash)); |
341 | 497 |
342 std::string first_48_bits(&binary_hash[0], 6); | 498 std::string first_48_bits(&binary_hash[0], 6); |
343 std::string second_48_bits(&binary_hash[6], 6); | 499 std::string second_48_bits(&binary_hash[6], 6); |
344 UrlSafeBase64Encode(first_48_bits, client_hash); | 500 UrlSafeBase64Encode(first_48_bits, client_hash); |
345 UrlSafeBase64Encode(second_48_bits, server_hash); | 501 UrlSafeBase64Encode(second_48_bits, server_hash); |
346 | 502 |
347 DCHECK_EQ(server_hash->length(), 8u); | 503 DCHECK_EQ(server_hash->length(), 8u); |
348 DCHECK_EQ(client_hash->length(), 8u); | 504 DCHECK_EQ(client_hash->length(), 8u); |
349 } | 505 } |
350 | 506 |
351 // static | |
352 void SdchManager::UrlSafeBase64Encode(const std::string& input, | |
353 std::string* output) { | |
354 // Since this is only done during a dictionary load, and hashes are only 8 | |
355 // characters, we just do the simple fixup, rather than rewriting the encoder. | |
356 base::Base64Encode(input, output); | |
357 for (size_t i = 0; i < output->size(); ++i) { | |
358 switch (output->data()[i]) { | |
359 case '+': | |
360 (*output)[i] = '-'; | |
361 continue; | |
362 case '/': | |
363 (*output)[i] = '_'; | |
364 continue; | |
365 default: | |
366 continue; | |
367 } | |
368 } | |
369 } | |
370 | |
371 //------------------------------------------------------------------------------ | |
372 // Security functions restricting loads and use of dictionaries. | |
373 | |
374 // static | |
375 bool SdchManager::Dictionary::CanSet(const std::string& domain, | |
376 const std::string& path, | |
377 const std::set<int> ports, | |
378 const GURL& dictionary_url) { | |
379 if (!SdchManager::Global()->IsInSupportedDomain(dictionary_url)) | |
380 return false; | |
381 /* | |
382 A dictionary is invalid and must not be stored if any of the following are | |
383 true: | |
384 1. The dictionary has no Domain attribute. | |
385 2. The effective host name that derives from the referer URL host name does | |
386 not domain-match the Domain attribute. | |
387 3. The Domain attribute is a top level domain. | |
388 4. The referer URL host is a host domain name (not IP address) and has the | |
389 form HD, where D is the value of the Domain attribute, and H is a string | |
390 that contains one or more dots. | |
391 5. If the dictionary has a Port attribute and the referer URL's port was not | |
392 in the list. | |
393 */ | |
394 | |
395 // TODO(jar): Redirects in dictionary fetches might plausibly be problematic, | |
396 // and hence the conservative approach is to not allow any redirects (if there | |
397 // were any... then don't allow the dictionary to be set). | |
398 | |
399 if (domain.empty()) { | |
400 SdchErrorRecovery(DICTIONARY_MISSING_DOMAIN_SPECIFIER); | |
401 return false; // Domain is required. | |
402 } | |
403 if (net::RegistryControlledDomainService::GetDomainAndRegistry(domain).size() | |
404 == 0) { | |
405 SdchErrorRecovery(DICTIONARY_SPECIFIES_TOP_LEVEL_DOMAIN); | |
406 return false; // domain was a TLD. | |
407 } | |
408 if (!Dictionary::DomainMatch(dictionary_url, domain)) { | |
409 SdchErrorRecovery(DICTIONARY_DOMAIN_NOT_MATCHING_SOURCE_URL); | |
410 return false; | |
411 } | |
412 | |
413 std::string referrer_url_host = dictionary_url.host(); | |
414 size_t postfix_domain_index = referrer_url_host.rfind(domain); | |
415 // See if it is indeed a postfix, or just an internal string. | |
416 if (referrer_url_host.size() == postfix_domain_index + domain.size()) { | |
417 // It is a postfix... so check to see if there's a dot in the prefix. | |
418 size_t end_of_host_index = referrer_url_host.find_first_of('.'); | |
419 if (referrer_url_host.npos != end_of_host_index && | |
420 end_of_host_index < postfix_domain_index) { | |
421 SdchErrorRecovery(DICTIONARY_REFERER_URL_HAS_DOT_IN_PREFIX); | |
422 return false; | |
423 } | |
424 } | |
425 | |
426 if (!ports.empty() | |
427 && 0 == ports.count(dictionary_url.EffectiveIntPort())) { | |
428 SdchErrorRecovery(DICTIONARY_PORT_NOT_MATCHING_SOURCE_URL); | |
429 return false; | |
430 } | |
431 return true; | |
432 } | |
433 | |
434 // static | |
435 bool SdchManager::Dictionary::CanUse(const GURL& referring_url) { | |
436 if (!SdchManager::Global()->IsInSupportedDomain(referring_url)) | |
437 return false; | |
438 /* | |
439 1. The request URL's host name domain-matches the Domain attribute of the | |
440 dictionary. | |
441 2. If the dictionary has a Port attribute, the request port is one of the | |
442 ports listed in the Port attribute. | |
443 3. The request URL path-matches the path attribute of the dictionary. | |
444 4. The request is not an HTTPS request. | |
445 */ | |
446 if (!DomainMatch(referring_url, domain_)) { | |
447 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_DOMAIN); | |
448 return false; | |
449 } | |
450 if (!ports_.empty() | |
451 && 0 == ports_.count(referring_url.EffectiveIntPort())) { | |
452 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PORT_LIST); | |
453 return false; | |
454 } | |
455 if (path_.size() && !PathMatch(referring_url.path(), path_)) { | |
456 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PATH); | |
457 return false; | |
458 } | |
459 if (referring_url.SchemeIsSecure()) { | |
460 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_SCHEME); | |
461 return false; | |
462 } | |
463 | |
464 // TODO(jar): Remove overly restrictive failsafe test (added per security | |
465 // review) when we have a need to be more general. | |
466 if (!referring_url.SchemeIs("http")) { | |
467 SdchErrorRecovery(ATTEMPT_TO_DECODE_NON_HTTP_DATA); | |
468 return false; | |
469 } | |
470 | |
471 return true; | |
472 } | |
473 | |
474 bool SdchManager::Dictionary::CanAdvertise(const GURL& target_url) { | |
475 if (!SdchManager::Global()->IsInSupportedDomain(target_url)) | |
476 return false; | |
477 /* The specific rules of when a dictionary should be advertised in an | |
478 Avail-Dictionary header are modeled after the rules for cookie scoping. The | |
479 terms "domain-match" and "pathmatch" are defined in RFC 2965 [6]. A | |
480 dictionary may be advertised in the Avail-Dictionaries header exactly when | |
481 all of the following are true: | |
482 1. The server's effective host name domain-matches the Domain attribute of | |
483 the dictionary. | |
484 2. If the dictionary has a Port attribute, the request port is one of the | |
485 ports listed in the Port attribute. | |
486 3. The request URI path-matches the path header of the dictionary. | |
487 4. The request is not an HTTPS request. | |
488 */ | |
489 if (!DomainMatch(target_url, domain_)) | |
490 return false; | |
491 if (!ports_.empty() && 0 == ports_.count(target_url.EffectiveIntPort())) | |
492 return false; | |
493 if (path_.size() && !PathMatch(target_url.path(), path_)) | |
494 return false; | |
495 if (target_url.SchemeIsSecure()) | |
496 return false; | |
497 if (Time::Now() > expiration_) | |
498 return false; | |
499 return true; | |
500 } | |
501 | |
502 bool SdchManager::Dictionary::PathMatch(const std::string& path, | |
503 const std::string& restriction) { | |
504 /* Must be either: | |
505 1. P2 is equal to P1 | |
506 2. P2 is a prefix of P1 and either the final character in P2 is "/" or the | |
507 character following P2 in P1 is "/". | |
508 */ | |
509 if (path == restriction) | |
510 return true; | |
511 size_t prefix_length = restriction.size(); | |
512 if (prefix_length > path.size()) | |
513 return false; // Can't be a prefix. | |
514 if (0 != path.compare(0, prefix_length, restriction)) | |
515 return false; | |
516 return restriction[prefix_length - 1] == '/' || path[prefix_length] == '/'; | |
517 } | |
518 | |
519 // static | |
520 bool SdchManager::Dictionary::DomainMatch(const GURL& gurl, | |
521 const std::string& restriction) { | |
522 // TODO(jar): This is not precisely a domain match definition. | |
523 return gurl.DomainIs(restriction.data(), restriction.size()); | |
524 } | |
525 | |
526 //------------------------------------------------------------------------------ | 507 //------------------------------------------------------------------------------ |
527 // Methods for supporting latency experiments. | 508 // Methods for supporting latency experiments. |
528 | 509 |
529 bool SdchManager::AllowLatencyExperiment(const GURL& url) const { | 510 bool SdchManager::AllowLatencyExperiment(const GURL& url) const { |
530 return allow_latency_experiment_.end() != | 511 return allow_latency_experiment_.end() != |
531 allow_latency_experiment_.find(url.host()); | 512 allow_latency_experiment_.find(url.host()); |
532 } | 513 } |
533 | 514 |
534 void SdchManager::SetAllowLatencyExperiment(const GURL& url, bool enable) { | 515 void SdchManager::SetAllowLatencyExperiment(const GURL& url, bool enable) { |
535 if (enable) { | 516 if (enable) { |
536 allow_latency_experiment_.insert(url.host()); | 517 allow_latency_experiment_.insert(url.host()); |
537 return; | 518 return; |
538 } | 519 } |
539 ExperimentSet::iterator it = allow_latency_experiment_.find(url.host()); | 520 ExperimentSet::iterator it = allow_latency_experiment_.find(url.host()); |
540 if (allow_latency_experiment_.end() == it) | 521 if (allow_latency_experiment_.end() == it) |
541 return; // It was already erased, or never allowed. | 522 return; // It was already erased, or never allowed. |
542 SdchErrorRecovery(LATENCY_TEST_DISALLOWED); | 523 SdchErrorRecovery(LATENCY_TEST_DISALLOWED); |
543 allow_latency_experiment_.erase(it); | 524 allow_latency_experiment_.erase(it); |
544 } | 525 } |
| 526 |
| 527 // static |
| 528 void SdchManager::UrlSafeBase64Encode(const std::string& input, |
| 529 std::string* output) { |
| 530 // Since this is only done during a dictionary load, and hashes are only 8 |
| 531 // characters, we just do the simple fixup, rather than rewriting the encoder. |
| 532 base::Base64Encode(input, output); |
| 533 for (size_t i = 0; i < output->size(); ++i) { |
| 534 switch (output->data()[i]) { |
| 535 case '+': |
| 536 (*output)[i] = '-'; |
| 537 continue; |
| 538 case '/': |
| 539 (*output)[i] = '_'; |
| 540 continue; |
| 541 default: |
| 542 continue; |
| 543 } |
| 544 } |
| 545 } |
OLD | NEW |