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