forked from Pissandshittium/pissandshittium
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhttp_auth_cache.cc
443 lines (392 loc) · 15.7 KB
/
http_auth_cache.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/http/http_auth_cache.h"
#include <list>
#include <map>
#include "base/logging.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "url/gurl.h"
#include "url/scheme_host_port.h"
#include "url/url_constants.h"
namespace {
// Helper to find the containing directory of path. In RFC 2617 this is what
// they call the "last symbolic element in the absolute path".
// Examples:
// "/foo/bar.txt" --> "/foo/"
// "/foo/" --> "/foo/"
std::string GetParentDirectory(const std::string& path) {
std::string::size_type last_slash = path.rfind("/");
if (last_slash == std::string::npos) {
// No slash (absolute paths always start with slash, so this must be
// the proxy case which uses empty string).
DCHECK(path.empty());
return path;
}
return path.substr(0, last_slash + 1);
}
// Return true if |path| is a subpath of |container|. In other words, is
// |container| an ancestor of |path|?
bool IsEnclosingPath(const std::string& container, const std::string& path) {
DCHECK(container.empty() || *(container.end() - 1) == '/');
return ((container.empty() && path.empty()) ||
(!container.empty() && path.starts_with(container)));
}
#if DCHECK_IS_ON()
// Debug helper to check that |scheme_host_port| arguments are properly formed.
void CheckSchemeHostPortIsValid(const url::SchemeHostPort& scheme_host_port) {
DCHECK(scheme_host_port.IsValid());
DCHECK(scheme_host_port.scheme() == url::kHttpScheme ||
scheme_host_port.scheme() == url::kHttpsScheme ||
scheme_host_port.scheme() == url::kWsScheme ||
scheme_host_port.scheme() == url::kWssScheme);
}
// Debug helper to check that |path| arguments are properly formed.
// (should be absolute path, or empty string).
void CheckPathIsValid(const std::string& path) {
DCHECK(path.empty() || path[0] == '/');
}
#endif
// Functor used by std::erase_if.
struct IsEnclosedBy {
explicit IsEnclosedBy(const std::string& path) : path(path) { }
bool operator() (const std::string& x) const {
return IsEnclosingPath(path, x);
}
// This field is not a raw_ref<> because it was filtered by the rewriter for:
// #constexpr-ctor-field-initializer
RAW_PTR_EXCLUSION const std::string& path;
};
} // namespace
namespace net {
HttpAuthCache::HttpAuthCache(
bool key_server_entries_by_network_anonymization_key)
: key_server_entries_by_network_anonymization_key_(
key_server_entries_by_network_anonymization_key) {}
HttpAuthCache::~HttpAuthCache() = default;
void HttpAuthCache::SetKeyServerEntriesByNetworkAnonymizationKey(
bool key_server_entries_by_network_anonymization_key) {
if (key_server_entries_by_network_anonymization_key_ ==
key_server_entries_by_network_anonymization_key) {
return;
}
key_server_entries_by_network_anonymization_key_ =
key_server_entries_by_network_anonymization_key;
std::erase_if(entries_, [](EntryMap::value_type& entry_map_pair) {
return entry_map_pair.first.target == HttpAuth::AUTH_SERVER;
});
}
// Performance: O(logN+n), where N is the total number of entries, n is the
// number of realm entries for the given SchemeHostPort, target, and with a
// matching NetworkAnonymizationKey.
HttpAuthCache::Entry* HttpAuthCache::Lookup(
const url::SchemeHostPort& scheme_host_port,
HttpAuth::Target target,
const std::string& realm,
HttpAuth::Scheme scheme,
const NetworkAnonymizationKey& network_anonymization_key) {
EntryMap::iterator entry_it = LookupEntryIt(
scheme_host_port, target, realm, scheme, network_anonymization_key);
if (entry_it == entries_.end())
return nullptr;
return &(entry_it->second);
}
// Performance: O(logN+n*m), where N is the total number of entries, n is the
// number of realm entries for the given SchemeHostPort, target, and
// NetworkAnonymizationKey, m is the number of path entries per realm. Both n
// and m are expected to be small; m is kept small because AddPath() only keeps
// the shallowest entry.
HttpAuthCache::Entry* HttpAuthCache::LookupByPath(
const url::SchemeHostPort& scheme_host_port,
HttpAuth::Target target,
const NetworkAnonymizationKey& network_anonymization_key,
const std::string& path) {
#if DCHECK_IS_ON()
CheckSchemeHostPortIsValid(scheme_host_port);
CheckPathIsValid(path);
#endif
// RFC 2617 section 2:
// A client SHOULD assume that all paths at or deeper than the depth of
// the last symbolic element in the path field of the Request-URI also are
// within the protection space ...
std::string parent_dir = GetParentDirectory(path);
// Linear scan through the <scheme, realm> entries for the given
// SchemeHostPort.
auto entry_range = entries_.equal_range(
EntryMapKey(scheme_host_port, target, network_anonymization_key,
key_server_entries_by_network_anonymization_key_));
auto best_match_it = entries_.end();
size_t best_match_length = 0;
for (auto it = entry_range.first; it != entry_range.second; ++it) {
size_t len = 0;
auto& entry = it->second;
DCHECK(entry.scheme_host_port() == scheme_host_port);
if (entry.HasEnclosingPath(parent_dir, &len) &&
(best_match_it == entries_.end() || len > best_match_length)) {
best_match_it = it;
best_match_length = len;
}
}
if (best_match_it != entries_.end()) {
Entry& best_match_entry = best_match_it->second;
best_match_entry.last_use_time_ticks_ = tick_clock_->NowTicks();
return &best_match_entry;
}
return nullptr;
}
HttpAuthCache::Entry* HttpAuthCache::Add(
const url::SchemeHostPort& scheme_host_port,
HttpAuth::Target target,
const std::string& realm,
HttpAuth::Scheme scheme,
const NetworkAnonymizationKey& network_anonymization_key,
const std::string& auth_challenge,
const AuthCredentials& credentials,
const std::string& path) {
#if DCHECK_IS_ON()
CheckSchemeHostPortIsValid(scheme_host_port);
CheckPathIsValid(path);
#endif
base::TimeTicks now_ticks = tick_clock_->NowTicks();
// Check for existing entry (we will re-use it if present).
HttpAuthCache::Entry* entry = Lookup(scheme_host_port, target, realm, scheme,
network_anonymization_key);
if (!entry) {
// Failsafe to prevent unbounded memory growth of the cache.
//
// Data was collected in June of 2019, before entries were keyed on either
// HttpAuth::Target or NetworkAnonymizationKey. That data indicated that the
// eviction rate was at around 0.05%. I.e. 0.05% of the time the number of
// entries in the cache exceed kMaxNumRealmEntries. The evicted entry is
// roughly half an hour old (median), and it's been around 25 minutes since
// its last use (median).
if (entries_.size() >= kMaxNumRealmEntries) {
DLOG(WARNING) << "Num auth cache entries reached limit -- evicting";
EvictLeastRecentlyUsedEntry();
}
entry =
&(entries_
.insert({EntryMapKey(
scheme_host_port, target, network_anonymization_key,
key_server_entries_by_network_anonymization_key_),
Entry()})
->second);
entry->scheme_host_port_ = scheme_host_port;
entry->realm_ = realm;
entry->scheme_ = scheme;
entry->creation_time_ticks_ = now_ticks;
entry->creation_time_ = clock_->Now();
}
DCHECK_EQ(scheme_host_port, entry->scheme_host_port_);
DCHECK_EQ(realm, entry->realm_);
DCHECK_EQ(scheme, entry->scheme_);
entry->auth_challenge_ = auth_challenge;
entry->credentials_ = credentials;
entry->nonce_count_ = 1;
entry->AddPath(path);
entry->last_use_time_ticks_ = now_ticks;
return entry;
}
HttpAuthCache::Entry::Entry(const Entry& other) = default;
HttpAuthCache::Entry::~Entry() = default;
void HttpAuthCache::Entry::UpdateStaleChallenge(
const std::string& auth_challenge) {
auth_challenge_ = auth_challenge;
nonce_count_ = 1;
}
bool HttpAuthCache::Entry::IsEqualForTesting(const Entry& other) const {
if (scheme_host_port() != other.scheme_host_port())
return false;
if (realm() != other.realm())
return false;
if (scheme() != other.scheme())
return false;
if (auth_challenge() != other.auth_challenge())
return false;
if (!credentials().Equals(other.credentials()))
return false;
std::set<std::string> lhs_paths(paths_.begin(), paths_.end());
std::set<std::string> rhs_paths(other.paths_.begin(), other.paths_.end());
if (lhs_paths != rhs_paths)
return false;
return true;
}
HttpAuthCache::Entry::Entry() = default;
void HttpAuthCache::Entry::AddPath(const std::string& path) {
std::string parent_dir = GetParentDirectory(path);
if (!HasEnclosingPath(parent_dir, nullptr)) {
// Remove any entries that have been subsumed by the new entry.
std::erase_if(paths_, IsEnclosedBy(parent_dir));
// Failsafe to prevent unbounded memory growth of the cache.
//
// Data collected on June of 2019 indicate that when we get here, the list
// of paths has reached the 10 entry maximum around 1% of the time.
if (paths_.size() >= kMaxNumPathsPerRealmEntry) {
DLOG(WARNING) << "Num path entries for " << scheme_host_port()
<< " has grown too large -- evicting";
paths_.pop_back();
}
// Add new path.
paths_.push_front(parent_dir);
}
}
bool HttpAuthCache::Entry::HasEnclosingPath(const std::string& dir,
size_t* path_len) {
DCHECK(GetParentDirectory(dir) == dir);
for (PathList::iterator it = paths_.begin(); it != paths_.end(); ++it) {
if (IsEnclosingPath(*it, dir)) {
// No element of paths_ may enclose any other element.
// Therefore this path is the tightest bound. Important because
// the length returned is used to determine the cache entry that
// has the closest enclosing path in LookupByPath().
if (path_len)
*path_len = it->length();
// Move the found path up by one place so that more frequently used paths
// migrate towards the beginning of the list of paths.
if (it != paths_.begin())
std::iter_swap(it, std::prev(it));
return true;
}
}
return false;
}
bool HttpAuthCache::Remove(
const url::SchemeHostPort& scheme_host_port,
HttpAuth::Target target,
const std::string& realm,
HttpAuth::Scheme scheme,
const NetworkAnonymizationKey& network_anonymization_key,
const AuthCredentials& credentials) {
EntryMap::iterator entry_it = LookupEntryIt(
scheme_host_port, target, realm, scheme, network_anonymization_key);
if (entry_it == entries_.end())
return false;
Entry& entry = entry_it->second;
if (credentials.Equals(entry.credentials())) {
entries_.erase(entry_it);
return true;
}
return false;
}
void HttpAuthCache::ClearEntriesAddedBetween(
base::Time begin_time,
base::Time end_time,
base::RepeatingCallback<bool(const GURL&)> url_matcher) {
if (begin_time.is_min() && end_time.is_max() && !url_matcher) {
ClearAllEntries();
return;
}
std::erase_if(entries_, [begin_time, end_time,
url_matcher](EntryMap::value_type& entry_map_pair) {
Entry& entry = entry_map_pair.second;
return entry.creation_time_ >= begin_time &&
entry.creation_time_ < end_time &&
(url_matcher ? url_matcher.Run(entry.scheme_host_port().GetURL())
: true);
});
}
void HttpAuthCache::ClearAllEntries() {
entries_.clear();
}
bool HttpAuthCache::UpdateStaleChallenge(
const url::SchemeHostPort& scheme_host_port,
HttpAuth::Target target,
const std::string& realm,
HttpAuth::Scheme scheme,
const NetworkAnonymizationKey& network_anonymization_key,
const std::string& auth_challenge) {
HttpAuthCache::Entry* entry = Lookup(scheme_host_port, target, realm, scheme,
network_anonymization_key);
if (!entry)
return false;
entry->UpdateStaleChallenge(auth_challenge);
entry->last_use_time_ticks_ = tick_clock_->NowTicks();
return true;
}
void HttpAuthCache::CopyProxyEntriesFrom(const HttpAuthCache& other) {
for (auto it = other.entries_.begin(); it != other.entries_.end(); ++it) {
const Entry& e = it->second;
// Skip non-proxy entries.
if (it->first.target != HttpAuth::AUTH_PROXY)
continue;
// Sanity check - proxy entries should have an empty
// NetworkAnonymizationKey.
DCHECK(NetworkAnonymizationKey() == it->first.network_anonymization_key);
// Add an Entry with one of the original entry's paths.
DCHECK(e.paths_.size() > 0);
Entry* entry = Add(e.scheme_host_port(), it->first.target, e.realm(),
e.scheme(), it->first.network_anonymization_key,
e.auth_challenge(), e.credentials(), e.paths_.back());
// Copy all other paths.
for (auto it2 = std::next(e.paths_.rbegin()); it2 != e.paths_.rend(); ++it2)
entry->AddPath(*it2);
// Copy nonce count (for digest authentication).
entry->nonce_count_ = e.nonce_count_;
}
}
HttpAuthCache::EntryMapKey::EntryMapKey(
const url::SchemeHostPort& scheme_host_port,
HttpAuth::Target target,
const NetworkAnonymizationKey& network_anonymization_key,
bool key_server_entries_by_network_anonymization_key)
: scheme_host_port(scheme_host_port),
target(target),
network_anonymization_key(
target == HttpAuth::AUTH_SERVER &&
key_server_entries_by_network_anonymization_key
? network_anonymization_key
: NetworkAnonymizationKey()) {}
HttpAuthCache::EntryMapKey::~EntryMapKey() = default;
bool HttpAuthCache::EntryMapKey::operator<(const EntryMapKey& other) const {
return std::tie(scheme_host_port, target, network_anonymization_key) <
std::tie(other.scheme_host_port, other.target,
other.network_anonymization_key);
}
size_t HttpAuthCache::GetEntriesSizeForTesting() {
return entries_.size();
}
HttpAuthCache::EntryMap::iterator HttpAuthCache::LookupEntryIt(
const url::SchemeHostPort& scheme_host_port,
HttpAuth::Target target,
const std::string& realm,
HttpAuth::Scheme scheme,
const NetworkAnonymizationKey& network_anonymization_key) {
#if DCHECK_IS_ON()
CheckSchemeHostPortIsValid(scheme_host_port);
#endif
// Linear scan through the <scheme, realm> entries for the given
// SchemeHostPort and NetworkAnonymizationKey.
auto entry_range = entries_.equal_range(
EntryMapKey(scheme_host_port, target, network_anonymization_key,
key_server_entries_by_network_anonymization_key_));
for (auto it = entry_range.first; it != entry_range.second; ++it) {
Entry& entry = it->second;
DCHECK(entry.scheme_host_port() == scheme_host_port);
if (entry.scheme() == scheme && entry.realm() == realm) {
entry.last_use_time_ticks_ = tick_clock_->NowTicks();
return it;
}
}
return entries_.end();
}
// Linear scan through all entries to find least recently used entry (by oldest
// |last_use_time_ticks_| and evict it from |entries_|.
void HttpAuthCache::EvictLeastRecentlyUsedEntry() {
DCHECK(entries_.size() == kMaxNumRealmEntries);
base::TimeTicks now_ticks = tick_clock_->NowTicks();
EntryMap::iterator oldest_entry_it = entries_.end();
base::TimeTicks oldest_last_use_time_ticks = now_ticks;
for (auto it = entries_.begin(); it != entries_.end(); ++it) {
Entry& entry = it->second;
if (entry.last_use_time_ticks_ < oldest_last_use_time_ticks ||
oldest_entry_it == entries_.end()) {
oldest_entry_it = it;
oldest_last_use_time_ticks = entry.last_use_time_ticks_;
}
}
DCHECK(oldest_entry_it != entries_.end());
entries_.erase(oldest_entry_it);
}
} // namespace net