nsHttpAuthCache.cpp (13585B)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 // HttpLog.h should generally be included first 7 #include "HttpLog.h" 8 9 #include "nsHttpAuthCache.h" 10 11 #include <algorithm> 12 13 #include "nsString.h" 14 #include "nsCRT.h" 15 #include "nsIObserverService.h" 16 #include "mozilla/Services.h" 17 #include "mozilla/DebugOnly.h" 18 #include "nsNetUtil.h" 19 20 namespace mozilla { 21 namespace net { 22 23 static inline void GetAuthKey(const nsACString& scheme, const nsACString& host, 24 int32_t port, nsACString const& originSuffix, 25 nsCString& key) { 26 key.Truncate(); 27 key.Append(originSuffix); 28 key.Append(':'); 29 key.Append(scheme); 30 key.AppendLiteral("://"); 31 key.Append(host); 32 key.Append(':'); 33 key.AppendInt(port); 34 } 35 36 //----------------------------------------------------------------------------- 37 // nsHttpAuthCache <public> 38 //----------------------------------------------------------------------------- 39 NS_IMPL_ISUPPORTS(nsHttpAuthCache, nsIHttpAuthCache) 40 41 NS_IMETHODIMP 42 nsHttpAuthCache::GetEntries(nsTArray<RefPtr<nsIHttpAuthEntry>>& aEntries) { 43 for (auto iter = mDB.Iter(); !iter.Done(); iter.Next()) { 44 nsHttpAuthNode* node = iter.Data().get(); 45 for (auto& entry : node->mList) { 46 auto* tmp = entry.get(); 47 aEntries.AppendElement(tmp); 48 } 49 } 50 51 return NS_OK; 52 } 53 54 NS_IMETHODIMP 55 nsHttpAuthCache::ClearEntry(nsIHttpAuthEntry* aEntry) { 56 NS_ENSURE_ARG_POINTER(aEntry); 57 58 for (auto iter = mDB.Iter(); !iter.Done(); iter.Next()) { 59 nsHttpAuthNode* node = iter.Data().get(); 60 61 for (auto& entry : node->mList) { 62 if (entry.get() == aEntry) { 63 node->mList.RemoveElement(entry); 64 if (node->EntryCount() == 0) { 65 iter.Remove(); 66 } 67 return NS_OK; 68 } 69 } 70 } 71 return NS_ERROR_NOT_AVAILABLE; 72 } 73 74 nsHttpAuthCache::nsHttpAuthCache() : mDB(128) { 75 LOG(("nsHttpAuthCache::nsHttpAuthCache %p", this)); 76 77 nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService(); 78 if (obsSvc) { 79 obsSvc->AddObserver(this, "clear-origin-attributes-data", true); 80 } 81 } 82 83 nsHttpAuthCache::~nsHttpAuthCache() { 84 LOG(("nsHttpAuthCache::~nsHttpAuthCache %p", this)); 85 86 ClearAll(); 87 nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService(); 88 if (obsSvc) { 89 obsSvc->RemoveObserver(this, "clear-origin-attributes-data"); 90 } 91 } 92 93 nsresult nsHttpAuthCache::GetAuthEntryForPath(const nsACString& scheme, 94 const nsACString& host, 95 int32_t port, 96 const nsACString& path, 97 nsACString const& originSuffix, 98 RefPtr<nsHttpAuthEntry>& entry) { 99 LOG(("nsHttpAuthCache::GetAuthEntryForPath %p [path=%s]\n", this, 100 path.BeginReading())); 101 102 nsAutoCString key; 103 nsHttpAuthNode* node = LookupAuthNode(scheme, host, port, originSuffix, key); 104 if (!node) return NS_ERROR_NOT_AVAILABLE; 105 106 entry = node->LookupEntryByPath(path); 107 LOG((" returning %p", entry.get())); 108 return entry ? NS_OK : NS_ERROR_NOT_AVAILABLE; 109 } 110 111 nsresult nsHttpAuthCache::GetAuthEntryForDomain(const nsACString& scheme, 112 const nsACString& host, 113 int32_t port, 114 const nsACString& realm, 115 nsACString const& originSuffix, 116 RefPtr<nsHttpAuthEntry>& entry) 117 118 { 119 LOG(("nsHttpAuthCache::GetAuthEntryForDomain %p [realm=%s]\n", this, 120 realm.BeginReading())); 121 122 nsAutoCString key; 123 nsHttpAuthNode* node = LookupAuthNode(scheme, host, port, originSuffix, key); 124 if (!node) return NS_ERROR_NOT_AVAILABLE; 125 126 entry = node->LookupEntryByRealm(realm); 127 LOG((" returning %p", entry.get())); 128 return entry ? NS_OK : NS_ERROR_NOT_AVAILABLE; 129 } 130 131 nsresult nsHttpAuthCache::SetAuthEntry( 132 const nsACString& scheme, const nsACString& host, int32_t port, 133 const nsACString& path, const nsACString& realm, const nsACString& creds, 134 const nsACString& challenge, nsACString const& originSuffix, 135 const nsHttpAuthIdentity* ident, nsISupports* metadata) { 136 nsresult rv; 137 138 LOG(("nsHttpAuthCache::SetAuthEntry %p [realm=%s path=%s metadata=%p]\n", 139 this, realm.BeginReading(), path.BeginReading(), metadata)); 140 141 nsAutoCString key; 142 nsHttpAuthNode* node = LookupAuthNode(scheme, host, port, originSuffix, key); 143 144 if (!node) { 145 // create a new entry node and set the given entry 146 auto node = UniquePtr<nsHttpAuthNode>(new nsHttpAuthNode); 147 LOG((" new nsHttpAuthNode %p for key='%s'", node.get(), key.get())); 148 rv = node->SetAuthEntry(path, realm, creds, challenge, ident, metadata); 149 if (NS_FAILED(rv)) { 150 return rv; 151 } 152 153 mDB.InsertOrUpdate(key, std::move(node)); 154 return NS_OK; 155 } 156 157 return node->SetAuthEntry(path, realm, creds, challenge, ident, metadata); 158 } 159 160 void nsHttpAuthCache::ClearAuthEntry(const nsACString& scheme, 161 const nsACString& host, int32_t port, 162 const nsACString& realm, 163 nsACString const& originSuffix) { 164 nsAutoCString key; 165 GetAuthKey(scheme, host, port, originSuffix, key); 166 LOG(("nsHttpAuthCache::ClearAuthEntry %p key='%s'\n", this, key.get())); 167 mDB.Remove(key); 168 } 169 170 void nsHttpAuthCache::ClearAll() { 171 LOG(("nsHttpAuthCache::ClearAll %p\n", this)); 172 mDB.Clear(); 173 } 174 175 //----------------------------------------------------------------------------- 176 // nsHttpAuthCache <private> 177 //----------------------------------------------------------------------------- 178 179 nsHttpAuthNode* nsHttpAuthCache::LookupAuthNode(const nsACString& scheme, 180 const nsACString& host, 181 int32_t port, 182 nsACString const& originSuffix, 183 nsCString& key) { 184 GetAuthKey(scheme, host, port, originSuffix, key); 185 nsHttpAuthNode* result = mDB.Get(key); 186 187 LOG(("nsHttpAuthCache::LookupAuthNode %p key='%s' found node=%p", this, 188 key.get(), result)); 189 return result; 190 } 191 192 NS_IMETHODIMP 193 nsHttpAuthCache::Observe(nsISupports* subject, const char* topic, 194 const char16_t* data_unicode) { 195 OriginAttributesPattern pattern; 196 if (!pattern.Init(nsDependentString(data_unicode))) { 197 NS_ERROR("Cannot parse origin attributes pattern"); 198 return NS_ERROR_FAILURE; 199 } 200 201 ClearOriginData(pattern); 202 return NS_OK; 203 } 204 205 void nsHttpAuthCache::ClearOriginData(OriginAttributesPattern const& pattern) { 206 LOG(("nsHttpAuthCache::ClearOriginData %p", this)); 207 208 for (auto iter = mDB.Iter(); !iter.Done(); iter.Next()) { 209 const nsACString& key = iter.Key(); 210 211 // Extract the origin attributes suffix from the key. 212 int32_t colon = key.FindChar(':'); 213 MOZ_ASSERT(colon != kNotFound); 214 nsDependentCSubstring oaSuffix = StringHead(key, colon); 215 216 // Build the OriginAttributes object of it... 217 OriginAttributes oa; 218 DebugOnly<bool> rv = oa.PopulateFromSuffix(oaSuffix); 219 MOZ_ASSERT(rv); 220 221 // ...and match it against the given pattern. 222 if (pattern.Matches(oa)) { 223 iter.Remove(); 224 } 225 } 226 } 227 228 void nsHttpAuthCache::CollectKeys(nsTArray<nsCString>& aValue) { 229 AppendToArray(aValue, mDB.Keys()); 230 } 231 232 //----------------------------------------------------------------------------- 233 // nsHttpAuthIdentity 234 //----------------------------------------------------------------------------- 235 236 void nsHttpAuthIdentity::Clear() { 237 mUser.Truncate(); 238 mPass.Truncate(); 239 mDomain.Truncate(); 240 } 241 242 bool nsHttpAuthIdentity::Equals(const nsHttpAuthIdentity& ident) const { 243 // we could probably optimize this with a single loop, but why bother? 244 return mUser == ident.mUser && mPass == ident.mPass && 245 mDomain == ident.mDomain; 246 } 247 248 NS_IMPL_ISUPPORTS(AuthIdentity, nsIHttpAuthIdentity) 249 250 NS_IMETHODIMP 251 AuthIdentity::GetDomain(nsAString& aDomain) { 252 aDomain = mIdent.Domain(); 253 return NS_OK; 254 } 255 256 NS_IMETHODIMP 257 AuthIdentity::GetUser(nsAString& aUser) { 258 aUser = mIdent.User(); 259 return NS_OK; 260 } 261 262 NS_IMETHODIMP 263 AuthIdentity::GetPassword(nsAString& aPassword) { 264 aPassword = mIdent.Password(); 265 return NS_OK; 266 } 267 268 //----------------------------------------------------------------------------- 269 // nsHttpAuthEntry 270 //----------------------------------------------------------------------------- 271 NS_IMPL_ISUPPORTS(nsHttpAuthEntry, nsIHttpAuthEntry) 272 273 NS_IMETHODIMP 274 nsHttpAuthEntry::GetRealm(nsACString& aRealm) { 275 aRealm = mRealm; 276 return NS_OK; 277 } 278 279 NS_IMETHODIMP 280 nsHttpAuthEntry::GetCreds(nsACString& aCreds) { 281 aCreds = mCreds; 282 return NS_OK; 283 } 284 285 NS_IMETHODIMP 286 nsHttpAuthEntry::GetChallenge(nsACString& aChallenge) { 287 aChallenge = mChallenge; 288 return NS_OK; 289 } 290 291 NS_IMETHODIMP 292 nsHttpAuthEntry::GetDomain(nsAString& aDomain) { 293 aDomain = mIdent.Domain(); 294 return NS_OK; 295 } 296 297 NS_IMETHODIMP 298 nsHttpAuthEntry::GetUser(nsAString& aUser) { 299 aUser = mIdent.User(); 300 return NS_OK; 301 } 302 303 NS_IMETHODIMP 304 nsHttpAuthEntry::GetPassword(nsAString& aPass) { 305 aPass = mIdent.Password(); 306 return NS_OK; 307 } 308 309 NS_IMETHODIMP 310 nsHttpAuthEntry::GetIdentity(nsIHttpAuthIdentity** aIdentity) { 311 NS_ENSURE_ARG_POINTER(aIdentity); 312 RefPtr<nsIHttpAuthIdentity> ident = new AuthIdentity(mIdent); 313 ident.forget(aIdentity); 314 return NS_OK; 315 } 316 317 nsresult nsHttpAuthEntry::AddPath(const nsACString& aPath) { 318 for (const auto& p : mPaths) { 319 if (StringBeginsWith(aPath, p)) { 320 return NS_OK; // subpath already exists in the list 321 } 322 } 323 324 mPaths.AppendElement(aPath); 325 return NS_OK; 326 } 327 328 nsresult nsHttpAuthEntry::Set(const nsACString& path, const nsACString& realm, 329 const nsACString& creds, const nsACString& chall, 330 const nsHttpAuthIdentity* ident, 331 nsISupports* metadata) { 332 if (ident) { 333 mIdent = *ident; 334 } else if (mIdent.IsEmpty()) { 335 // If we are not given an identity and our cached identity has not been 336 // initialized yet (so is currently empty), initialize it now by 337 // filling it with nulls. We need to do that because consumers expect 338 // that mIdent is initialized after this function returns. 339 mIdent.Clear(); 340 } 341 342 nsresult rv = AddPath(path); 343 if (NS_FAILED(rv)) { 344 return rv; 345 } 346 347 mRealm = realm; 348 mCreds = creds; 349 mChallenge = chall; 350 mMetaData = metadata; 351 352 return NS_OK; 353 } 354 355 //----------------------------------------------------------------------------- 356 // nsHttpAuthNode 357 //----------------------------------------------------------------------------- 358 359 nsHttpAuthNode::nsHttpAuthNode() { 360 LOG(("Creating nsHttpAuthNode @%p\n", this)); 361 } 362 363 nsHttpAuthNode::~nsHttpAuthNode() { 364 LOG(("Destroying nsHttpAuthNode @%p\n", this)); 365 366 mList.Clear(); 367 } 368 369 nsHttpAuthEntry* nsHttpAuthNode::LookupEntryByPath(const nsACString& aPath) { 370 // look for an entry that either matches or contains this directory. 371 // ie. we'll give out credentials if the given directory is a sub- 372 // directory of an existing entry. 373 for (uint32_t i = 0; i < mList.Length(); ++i) { 374 const auto& entry = mList[i]; 375 376 for (const auto& entryPath : entry->mPaths) { 377 // proxy auth entries have no path, so require exact match on 378 // empty path string. 379 if (entryPath.IsEmpty()) { 380 if (aPath.IsEmpty()) { 381 return entry.get(); 382 } 383 } else if (StringBeginsWith(aPath, entryPath)) { 384 return entry.get(); 385 } 386 } 387 } 388 return nullptr; 389 } 390 391 nsHttpAuthNode::EntryList::const_iterator nsHttpAuthNode::LookupEntryItrByRealm( 392 const nsACString& realm) const { 393 return std::find_if(mList.cbegin(), mList.cend(), [&realm](const auto& val) { 394 return realm.Equals(val->Realm()); 395 }); 396 } 397 398 nsHttpAuthEntry* nsHttpAuthNode::LookupEntryByRealm(const nsACString& realm) { 399 auto itr = LookupEntryItrByRealm(realm); 400 if (itr != mList.cend()) { 401 return itr->get(); 402 } 403 404 return nullptr; 405 } 406 407 nsresult nsHttpAuthNode::SetAuthEntry(const nsACString& path, 408 const nsACString& realm, 409 const nsACString& creds, 410 const nsACString& challenge, 411 const nsHttpAuthIdentity* ident, 412 nsISupports* metadata) { 413 // look for an entry with a matching realm 414 RefPtr<nsHttpAuthEntry> entry = LookupEntryByRealm(realm); 415 if (!entry) { 416 // We want the latest identity be at the begining of the list so that 417 // the newest working credentials are sent first on new requests. 418 // Changing a realm is sometimes used to "timeout" authrozization. 419 entry = new nsHttpAuthEntry(path, realm, creds, challenge, ident, metadata); 420 mList.InsertElementAt(0, entry); 421 } else { 422 // update the entry... 423 nsresult rv = entry->Set(path, realm, creds, challenge, ident, metadata); 424 NS_ENSURE_SUCCESS(rv, rv); 425 } 426 427 return NS_OK; 428 } 429 430 void nsHttpAuthNode::ClearAuthEntry(const nsACString& realm) { 431 auto idx = LookupEntryItrByRealm(realm); 432 if (idx != mList.cend()) { 433 mList.RemoveElementAt(idx); 434 } 435 } 436 437 } // namespace net 438 } // namespace mozilla