1 // Copyright (c) 2008 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 "net/http/http_auth_cache.h" 6 7 #include "base/logging.h" 8 #include "base/string_util.h" 9 10 namespace { 11 12 // Helper to find the containing directory of path. In RFC 2617 this is what 13 // they call the "last symbolic element in the absolute path". 14 // Examples: 15 // "/foo/bar.txt" --> "/foo/" 16 // "/foo/" --> "/foo/" 17 std::string GetParentDirectory(const std::string& path) { 18 std::string::size_type last_slash = path.rfind("/"); 19 if (last_slash == std::string::npos) { 20 // No slash (absolute paths always start with slash, so this must be 21 // the proxy case which uses empty string). 22 DCHECK(path.empty()); 23 return path; 24 } 25 return path.substr(0, last_slash + 1); 26 } 27 28 // Debug helper to check that |path| arguments are properly formed. 29 // (should be absolute path, or empty string). 30 void CheckPathIsValid(const std::string& path) { 31 DCHECK(path.empty() || path[0] == '/'); 32 } 33 34 // Return true if |path| is a subpath of |container|. In other words, is 35 // |container| an ancestor of |path|? 36 bool IsEnclosingPath(const std::string& container, const std::string& path) { 37 DCHECK(container.empty() || *(container.end() - 1) == '/'); 38 return (container.empty() && path.empty()) || 39 (!container.empty() && StartsWithASCII(path, container, true)); 40 } 41 42 // Debug helper to check that |origin| arguments are properly formed. 43 void CheckOriginIsValid(const GURL& origin) { 44 DCHECK(origin.is_valid()); 45 DCHECK(origin.SchemeIs("http") || origin.SchemeIs("https")); 46 DCHECK(origin.GetOrigin() == origin); 47 } 48 49 // Functor used by remove_if. 50 struct IsEnclosedBy { 51 IsEnclosedBy(const std::string& path) : path(path) { } 52 bool operator() (const std::string& x) { 53 return IsEnclosingPath(path, x); 54 } 55 const std::string& path; 56 }; 57 58 } // namespace 59 60 namespace net { 61 62 // Performance: O(n), where n is the number of realm entries. 63 HttpAuthCache::Entry* HttpAuthCache::LookupByRealm(const GURL& origin, 64 const std::string& realm) { 65 CheckOriginIsValid(origin); 66 67 // Linear scan through the realm entries. 68 for (EntryList::iterator it = entries_.begin(); it != entries_.end(); ++it) { 69 if (it->origin() == origin && it->realm() == realm) 70 return &(*it); 71 } 72 return NULL; // No realm entry found. 73 } 74 75 // Performance: O(n*m), where n is the number of realm entries, m is the number 76 // of path entries per realm. Both n amd m are expected to be small; m is 77 // kept small because AddPath() only keeps the shallowest entry. 78 HttpAuthCache::Entry* HttpAuthCache::LookupByPath(const GURL& origin, 79 const std::string& path) { 80 CheckOriginIsValid(origin); 81 CheckPathIsValid(path); 82 83 // RFC 2617 section 2: 84 // A client SHOULD assume that all paths at or deeper than the depth of 85 // the last symbolic element in the path field of the Request-URI also are 86 // within the protection space ... 87 std::string parent_dir = GetParentDirectory(path); 88 89 // Linear scan through the realm entries. 90 for (EntryList::iterator it = entries_.begin(); it != entries_.end(); ++it) { 91 if (it->origin() == origin && it->HasEnclosingPath(parent_dir)) 92 return &(*it); 93 } 94 return NULL; // No entry found. 95 } 96 97 HttpAuthCache::Entry* HttpAuthCache::Add(const GURL& origin, 98 HttpAuthHandler* handler, 99 const std::wstring& username, 100 const std::wstring& password, 101 const std::string& path) { 102 CheckOriginIsValid(origin); 103 CheckPathIsValid(path); 104 105 // Check for existing entry (we will re-use it if present). 106 HttpAuthCache::Entry* entry = LookupByRealm(origin, handler->realm()); 107 108 if (!entry) { 109 // Failsafe to prevent unbounded memory growth of the cache. 110 if (entries_.size() >= kMaxNumRealmEntries) { 111 LOG(WARNING) << "Num auth cache entries reached limit -- evicting"; 112 entries_.pop_back(); 113 } 114 115 entries_.push_front(Entry()); 116 entry = &entries_.front(); 117 entry->origin_ = origin; 118 } 119 120 entry->username_ = username; 121 entry->password_ = password; 122 entry->handler_ = handler; 123 entry->AddPath(path); 124 125 return entry; 126 } 127 128 void HttpAuthCache::Entry::AddPath(const std::string& path) { 129 std::string parent_dir = GetParentDirectory(path); 130 if (!HasEnclosingPath(parent_dir)) { 131 // Remove any entries that have been subsumed by the new entry. 132 paths_.remove_if(IsEnclosedBy(parent_dir)); 133 134 // Failsafe to prevent unbounded memory growth of the cache. 135 if (paths_.size() >= kMaxNumPathsPerRealmEntry) { 136 LOG(WARNING) << "Num path entries for " << origin() 137 << " has grown too large -- evicting"; 138 paths_.pop_back(); 139 } 140 141 // Add new path. 142 paths_.push_front(parent_dir); 143 } 144 } 145 146 bool HttpAuthCache::Entry::HasEnclosingPath(const std::string& dir) { 147 DCHECK(GetParentDirectory(dir) == dir); 148 for (PathList::const_iterator it = paths_.begin(); it != paths_.end(); 149 ++it) { 150 if (IsEnclosingPath(*it, dir)) 151 return true; 152 } 153 return false; 154 } 155 156 bool HttpAuthCache::Remove(const GURL& origin, 157 const std::string& realm, 158 const std::wstring& username, 159 const std::wstring& password) { 160 for (EntryList::iterator it = entries_.begin(); it != entries_.end(); ++it) { 161 if (it->origin() == origin && it->realm() == realm) { 162 if (username == it->username() && password == it->password()) { 163 entries_.erase(it); 164 return true; 165 } 166 return false; 167 } 168 } 169 return false; 170 } 171 172 } // namespace net 173