1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 <stdlib.h>
12
13 #include "mozilla/Attributes.h"
14 #include "nsString.h"
15 #include "nsCRT.h"
16 #include "nsIObserverService.h"
17 #include "mozilla/Services.h"
18 #include "mozilla/DebugOnly.h"
19 #include "nsNetUtil.h"
20
21 namespace mozilla {
22 namespace net {
23
GetAuthKey(const char * scheme,const char * host,int32_t port,nsACString const & originSuffix,nsCString & key)24 static inline void GetAuthKey(const char *scheme, const char *host,
25 int32_t port, nsACString const &originSuffix,
26 nsCString &key) {
27 key.Truncate();
28 key.Append(originSuffix);
29 key.Append(':');
30 key.Append(scheme);
31 key.AppendLiteral("://");
32 key.Append(host);
33 key.Append(':');
34 key.AppendInt(port);
35 }
36
37 // return true if the two strings are equal or both empty. an empty string
38 // is either null or zero length.
StrEquivalent(const char16_t * a,const char16_t * b)39 static bool StrEquivalent(const char16_t *a, const char16_t *b) {
40 static const char16_t emptyStr[] = {0};
41
42 if (!a) a = emptyStr;
43 if (!b) b = emptyStr;
44
45 return nsCRT::strcmp(a, b) == 0;
46 }
47
48 //-----------------------------------------------------------------------------
49 // nsHttpAuthCache <public>
50 //-----------------------------------------------------------------------------
51
nsHttpAuthCache()52 nsHttpAuthCache::nsHttpAuthCache()
53 : mDB(128), mObserver(new OriginClearObserver(this)) {
54 nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
55 if (obsSvc) {
56 obsSvc->AddObserver(mObserver, "clear-origin-attributes-data", false);
57 }
58 }
59
~nsHttpAuthCache()60 nsHttpAuthCache::~nsHttpAuthCache() {
61 DebugOnly<nsresult> rv = ClearAll();
62 MOZ_ASSERT(NS_SUCCEEDED(rv));
63 nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
64 if (obsSvc) {
65 obsSvc->RemoveObserver(mObserver, "clear-origin-attributes-data");
66 mObserver->mOwner = nullptr;
67 }
68 }
69
GetAuthEntryForPath(const char * scheme,const char * host,int32_t port,const char * path,nsACString const & originSuffix,nsHttpAuthEntry ** entry)70 nsresult nsHttpAuthCache::GetAuthEntryForPath(const char *scheme,
71 const char *host, int32_t port,
72 const char *path,
73 nsACString const &originSuffix,
74 nsHttpAuthEntry **entry) {
75 LOG(("nsHttpAuthCache::GetAuthEntryForPath [key=%s://%s:%d path=%s]\n",
76 scheme, host, port, path));
77
78 nsAutoCString key;
79 nsHttpAuthNode *node = LookupAuthNode(scheme, host, port, originSuffix, key);
80 if (!node) return NS_ERROR_NOT_AVAILABLE;
81
82 *entry = node->LookupEntryByPath(path);
83 return *entry ? NS_OK : NS_ERROR_NOT_AVAILABLE;
84 }
85
GetAuthEntryForDomain(const char * scheme,const char * host,int32_t port,const char * realm,nsACString const & originSuffix,nsHttpAuthEntry ** entry)86 nsresult nsHttpAuthCache::GetAuthEntryForDomain(const char *scheme,
87 const char *host, int32_t port,
88 const char *realm,
89 nsACString const &originSuffix,
90 nsHttpAuthEntry **entry)
91
92 {
93 LOG(("nsHttpAuthCache::GetAuthEntryForDomain [key=%s://%s:%d realm=%s]\n",
94 scheme, host, port, realm));
95
96 nsAutoCString key;
97 nsHttpAuthNode *node = LookupAuthNode(scheme, host, port, originSuffix, key);
98 if (!node) return NS_ERROR_NOT_AVAILABLE;
99
100 *entry = node->LookupEntryByRealm(realm);
101 return *entry ? NS_OK : NS_ERROR_NOT_AVAILABLE;
102 }
103
SetAuthEntry(const char * scheme,const char * host,int32_t port,const char * path,const char * realm,const char * creds,const char * challenge,nsACString const & originSuffix,const nsHttpAuthIdentity * ident,nsISupports * metadata)104 nsresult nsHttpAuthCache::SetAuthEntry(const char *scheme, const char *host,
105 int32_t port, const char *path,
106 const char *realm, const char *creds,
107 const char *challenge,
108 nsACString const &originSuffix,
109 const nsHttpAuthIdentity *ident,
110 nsISupports *metadata) {
111 nsresult rv;
112
113 LOG(
114 ("nsHttpAuthCache::SetAuthEntry [key=%s://%s:%d realm=%s path=%s "
115 "metadata=%p]\n",
116 scheme, host, port, realm, path, metadata));
117
118 nsAutoCString key;
119 nsHttpAuthNode *node = LookupAuthNode(scheme, host, port, originSuffix, key);
120
121 if (!node) {
122 // create a new entry node and set the given entry
123 node = new nsHttpAuthNode();
124 rv = node->SetAuthEntry(path, realm, creds, challenge, ident, metadata);
125 if (NS_FAILED(rv))
126 delete node;
127 else
128 mDB.Put(key, node);
129 return rv;
130 }
131
132 return node->SetAuthEntry(path, realm, creds, challenge, ident, metadata);
133 }
134
ClearAuthEntry(const char * scheme,const char * host,int32_t port,const char * realm,nsACString const & originSuffix)135 void nsHttpAuthCache::ClearAuthEntry(const char *scheme, const char *host,
136 int32_t port, const char *realm,
137 nsACString const &originSuffix) {
138 nsAutoCString key;
139 GetAuthKey(scheme, host, port, originSuffix, key);
140 mDB.Remove(key);
141 }
142
ClearAll()143 nsresult nsHttpAuthCache::ClearAll() {
144 LOG(("nsHttpAuthCache::ClearAll\n"));
145 mDB.Clear();
146 return NS_OK;
147 }
148
149 //-----------------------------------------------------------------------------
150 // nsHttpAuthCache <private>
151 //-----------------------------------------------------------------------------
152
LookupAuthNode(const char * scheme,const char * host,int32_t port,nsACString const & originSuffix,nsCString & key)153 nsHttpAuthNode *nsHttpAuthCache::LookupAuthNode(const char *scheme,
154 const char *host, int32_t port,
155 nsACString const &originSuffix,
156 nsCString &key) {
157 GetAuthKey(scheme, host, port, originSuffix, key);
158 return mDB.Get(key);
159 }
160
NS_IMPL_ISUPPORTS(nsHttpAuthCache::OriginClearObserver,nsIObserver)161 NS_IMPL_ISUPPORTS(nsHttpAuthCache::OriginClearObserver, nsIObserver)
162
163 NS_IMETHODIMP
164 nsHttpAuthCache::OriginClearObserver::Observe(nsISupports *subject,
165 const char *topic,
166 const char16_t *data_unicode) {
167 NS_ENSURE_TRUE(mOwner, NS_ERROR_NOT_AVAILABLE);
168
169 OriginAttributesPattern pattern;
170 if (!pattern.Init(nsDependentString(data_unicode))) {
171 NS_ERROR("Cannot parse origin attributes pattern");
172 return NS_ERROR_FAILURE;
173 }
174
175 mOwner->ClearOriginData(pattern);
176 return NS_OK;
177 }
178
ClearOriginData(OriginAttributesPattern const & pattern)179 void nsHttpAuthCache::ClearOriginData(OriginAttributesPattern const &pattern) {
180 for (auto iter = mDB.Iter(); !iter.Done(); iter.Next()) {
181 const nsACString &key = iter.Key();
182
183 // Extract the origin attributes suffix from the key.
184 int32_t colon = key.FindChar(':');
185 MOZ_ASSERT(colon != kNotFound);
186 nsDependentCSubstring oaSuffix = StringHead(key, colon);
187
188 // Build the OriginAttributes object of it...
189 OriginAttributes oa;
190 DebugOnly<bool> rv = oa.PopulateFromSuffix(oaSuffix);
191 MOZ_ASSERT(rv);
192
193 // ...and match it against the given pattern.
194 if (pattern.Matches(oa)) {
195 iter.Remove();
196 }
197 }
198 }
199
200 //-----------------------------------------------------------------------------
201 // nsHttpAuthIdentity
202 //-----------------------------------------------------------------------------
203
Set(const char16_t * domain,const char16_t * user,const char16_t * pass)204 nsresult nsHttpAuthIdentity::Set(const char16_t *domain, const char16_t *user,
205 const char16_t *pass) {
206 char16_t *newUser, *newPass, *newDomain;
207
208 int domainLen = domain ? NS_strlen(domain) : 0;
209 int userLen = user ? NS_strlen(user) : 0;
210 int passLen = pass ? NS_strlen(pass) : 0;
211
212 int len = userLen + 1 + passLen + 1 + domainLen + 1;
213 newUser = (char16_t *)malloc(len * sizeof(char16_t));
214 if (!newUser) return NS_ERROR_OUT_OF_MEMORY;
215
216 if (user) memcpy(newUser, user, userLen * sizeof(char16_t));
217 newUser[userLen] = 0;
218
219 newPass = &newUser[userLen + 1];
220 if (pass) memcpy(newPass, pass, passLen * sizeof(char16_t));
221 newPass[passLen] = 0;
222
223 newDomain = &newPass[passLen + 1];
224 if (domain) memcpy(newDomain, domain, domainLen * sizeof(char16_t));
225 newDomain[domainLen] = 0;
226
227 // wait until the end to clear member vars in case input params
228 // reference our members!
229 if (mUser) free(mUser);
230 mUser = newUser;
231 mPass = newPass;
232 mDomain = newDomain;
233 return NS_OK;
234 }
235
Clear()236 void nsHttpAuthIdentity::Clear() {
237 if (mUser) {
238 free(mUser);
239 mUser = nullptr;
240 mPass = nullptr;
241 mDomain = nullptr;
242 }
243 }
244
Equals(const nsHttpAuthIdentity & ident) const245 bool nsHttpAuthIdentity::Equals(const nsHttpAuthIdentity &ident) const {
246 // we could probably optimize this with a single loop, but why bother?
247 return StrEquivalent(mUser, ident.mUser) &&
248 StrEquivalent(mPass, ident.mPass) &&
249 StrEquivalent(mDomain, ident.mDomain);
250 }
251
252 //-----------------------------------------------------------------------------
253 // nsHttpAuthEntry
254 //-----------------------------------------------------------------------------
255
~nsHttpAuthEntry()256 nsHttpAuthEntry::~nsHttpAuthEntry() {
257 if (mRealm) free(mRealm);
258
259 while (mRoot) {
260 nsHttpAuthPath *ap = mRoot;
261 mRoot = mRoot->mNext;
262 free(ap);
263 }
264 }
265
AddPath(const char * aPath)266 nsresult nsHttpAuthEntry::AddPath(const char *aPath) {
267 // null path matches empty path
268 if (!aPath) aPath = "";
269
270 nsHttpAuthPath *tempPtr = mRoot;
271 while (tempPtr) {
272 const char *curpath = tempPtr->mPath;
273 if (strncmp(aPath, curpath, strlen(curpath)) == 0)
274 return NS_OK; // subpath already exists in the list
275
276 tempPtr = tempPtr->mNext;
277 }
278
279 // Append the aPath
280 nsHttpAuthPath *newAuthPath;
281 int newpathLen = strlen(aPath);
282 newAuthPath = (nsHttpAuthPath *)malloc(sizeof(nsHttpAuthPath) + newpathLen);
283 if (!newAuthPath) return NS_ERROR_OUT_OF_MEMORY;
284
285 memcpy(newAuthPath->mPath, aPath, newpathLen + 1);
286 newAuthPath->mNext = nullptr;
287
288 if (!mRoot)
289 mRoot = newAuthPath; // first entry
290 else
291 mTail->mNext = newAuthPath; // Append newAuthPath
292
293 // update the tail pointer.
294 mTail = newAuthPath;
295 return NS_OK;
296 }
297
Set(const char * path,const char * realm,const char * creds,const char * chall,const nsHttpAuthIdentity * ident,nsISupports * metadata)298 nsresult nsHttpAuthEntry::Set(const char *path, const char *realm,
299 const char *creds, const char *chall,
300 const nsHttpAuthIdentity *ident,
301 nsISupports *metadata) {
302 char *newRealm, *newCreds, *newChall;
303
304 int realmLen = realm ? strlen(realm) : 0;
305 int credsLen = creds ? strlen(creds) : 0;
306 int challLen = chall ? strlen(chall) : 0;
307
308 int len = realmLen + 1 + credsLen + 1 + challLen + 1;
309 newRealm = (char *)malloc(len);
310 if (!newRealm) return NS_ERROR_OUT_OF_MEMORY;
311
312 if (realm) memcpy(newRealm, realm, realmLen);
313 newRealm[realmLen] = 0;
314
315 newCreds = &newRealm[realmLen + 1];
316 if (creds) memcpy(newCreds, creds, credsLen);
317 newCreds[credsLen] = 0;
318
319 newChall = &newCreds[credsLen + 1];
320 if (chall) memcpy(newChall, chall, challLen);
321 newChall[challLen] = 0;
322
323 nsresult rv = NS_OK;
324 if (ident) {
325 rv = mIdent.Set(*ident);
326 } else if (mIdent.IsEmpty()) {
327 // If we are not given an identity and our cached identity has not been
328 // initialized yet (so is currently empty), initialize it now by
329 // filling it with nulls. We need to do that because consumers expect
330 // that mIdent is initialized after this function returns.
331 rv = mIdent.Set(nullptr, nullptr, nullptr);
332 }
333 if (NS_FAILED(rv)) {
334 free(newRealm);
335 return rv;
336 }
337
338 rv = AddPath(path);
339 if (NS_FAILED(rv)) {
340 free(newRealm);
341 return rv;
342 }
343
344 // wait until the end to clear member vars in case input params
345 // reference our members!
346 if (mRealm) free(mRealm);
347
348 mRealm = newRealm;
349 mCreds = newCreds;
350 mChallenge = newChall;
351 mMetaData = metadata;
352
353 return NS_OK;
354 }
355
356 //-----------------------------------------------------------------------------
357 // nsHttpAuthNode
358 //-----------------------------------------------------------------------------
359
nsHttpAuthNode()360 nsHttpAuthNode::nsHttpAuthNode() {
361 LOG(("Creating nsHttpAuthNode @%p\n", this));
362 }
363
~nsHttpAuthNode()364 nsHttpAuthNode::~nsHttpAuthNode() {
365 LOG(("Destroying nsHttpAuthNode @%p\n", this));
366
367 mList.Clear();
368 }
369
LookupEntryByPath(const char * path)370 nsHttpAuthEntry *nsHttpAuthNode::LookupEntryByPath(const char *path) {
371 nsHttpAuthEntry *entry;
372
373 // null path matches empty path
374 if (!path) path = "";
375
376 // look for an entry that either matches or contains this directory.
377 // ie. we'll give out credentials if the given directory is a sub-
378 // directory of an existing entry.
379 for (uint32_t i = 0; i < mList.Length(); ++i) {
380 entry = mList[i];
381 nsHttpAuthPath *authPath = entry->RootPath();
382 while (authPath) {
383 const char *entryPath = authPath->mPath;
384 // proxy auth entries have no path, so require exact match on
385 // empty path string.
386 if (entryPath[0] == '\0') {
387 if (path[0] == '\0') return entry;
388 } else if (strncmp(path, entryPath, strlen(entryPath)) == 0)
389 return entry;
390
391 authPath = authPath->mNext;
392 }
393 }
394 return nullptr;
395 }
396
LookupEntryByRealm(const char * realm)397 nsHttpAuthEntry *nsHttpAuthNode::LookupEntryByRealm(const char *realm) {
398 nsHttpAuthEntry *entry;
399
400 // null realm matches empty realm
401 if (!realm) realm = "";
402
403 // look for an entry that matches this realm
404 uint32_t i;
405 for (i = 0; i < mList.Length(); ++i) {
406 entry = mList[i];
407 if (strcmp(realm, entry->Realm()) == 0) return entry;
408 }
409 return nullptr;
410 }
411
SetAuthEntry(const char * path,const char * realm,const char * creds,const char * challenge,const nsHttpAuthIdentity * ident,nsISupports * metadata)412 nsresult nsHttpAuthNode::SetAuthEntry(const char *path, const char *realm,
413 const char *creds, const char *challenge,
414 const nsHttpAuthIdentity *ident,
415 nsISupports *metadata) {
416 // look for an entry with a matching realm
417 nsHttpAuthEntry *entry = LookupEntryByRealm(realm);
418 if (!entry) {
419 entry = new nsHttpAuthEntry(path, realm, creds, challenge, ident, metadata);
420 if (!entry) return NS_ERROR_OUT_OF_MEMORY;
421
422 // We want the latest identity be at the begining of the list so that
423 // the newest working credentials are sent first on new requests.
424 // Changing a realm is sometimes used to "timeout" authrozization.
425 mList.InsertElementAt(0, entry);
426 } else {
427 // update the entry...
428 nsresult rv = entry->Set(path, realm, creds, challenge, ident, metadata);
429 NS_ENSURE_SUCCESS(rv, rv);
430 }
431
432 return NS_OK;
433 }
434
ClearAuthEntry(const char * realm)435 void nsHttpAuthNode::ClearAuthEntry(const char *realm) {
436 nsHttpAuthEntry *entry = LookupEntryByRealm(realm);
437 if (entry) {
438 mList.RemoveElement(entry); // double search OK
439 }
440 }
441
442 } // namespace net
443 } // namespace mozilla
444