michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // HttpLog.h should generally be included first michael@0: #include "HttpLog.h" michael@0: michael@0: #include "nsHttpAuthCache.h" michael@0: michael@0: #include michael@0: michael@0: #include "mozilla/Attributes.h" michael@0: #include "nsString.h" michael@0: #include "nsCRT.h" michael@0: #include "mozIApplicationClearPrivateDataParams.h" michael@0: #include "nsIObserverService.h" michael@0: #include "mozilla/Services.h" michael@0: #include "nsNetUtil.h" michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: static inline void michael@0: GetAuthKey(const char *scheme, const char *host, int32_t port, uint32_t appId, bool inBrowserElement, nsCString &key) michael@0: { michael@0: key.Truncate(); michael@0: key.AppendInt(appId); michael@0: key.Append(':'); michael@0: key.AppendInt(inBrowserElement); michael@0: key.Append(':'); michael@0: key.Append(scheme); michael@0: key.AppendLiteral("://"); michael@0: key.Append(host); michael@0: key.Append(':'); michael@0: key.AppendInt(port); michael@0: } michael@0: michael@0: // return true if the two strings are equal or both empty. an empty string michael@0: // is either null or zero length. michael@0: static bool michael@0: StrEquivalent(const char16_t *a, const char16_t *b) michael@0: { michael@0: static const char16_t emptyStr[] = {0}; michael@0: michael@0: if (!a) michael@0: a = emptyStr; michael@0: if (!b) michael@0: b = emptyStr; michael@0: michael@0: return nsCRT::strcmp(a, b) == 0; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpAuthCache michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsHttpAuthCache::nsHttpAuthCache() michael@0: : mDB(nullptr) michael@0: , mObserver(new AppDataClearObserver(MOZ_THIS_IN_INITIALIZER_LIST())) michael@0: { michael@0: nsCOMPtr obsSvc = services::GetObserverService(); michael@0: if (obsSvc) { michael@0: obsSvc->AddObserver(mObserver, "webapps-clear-data", false); michael@0: } michael@0: } michael@0: michael@0: nsHttpAuthCache::~nsHttpAuthCache() michael@0: { michael@0: if (mDB) michael@0: ClearAll(); michael@0: nsCOMPtr obsSvc = services::GetObserverService(); michael@0: if (obsSvc) { michael@0: obsSvc->RemoveObserver(mObserver, "webapps-clear-data"); michael@0: mObserver->mOwner = nullptr; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpAuthCache::Init() michael@0: { michael@0: NS_ENSURE_TRUE(!mDB, NS_ERROR_ALREADY_INITIALIZED); michael@0: michael@0: LOG(("nsHttpAuthCache::Init\n")); michael@0: michael@0: mDB = PL_NewHashTable(128, (PLHashFunction) PL_HashString, michael@0: (PLHashComparator) PL_CompareStrings, michael@0: (PLHashComparator) 0, &gHashAllocOps, this); michael@0: if (!mDB) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpAuthCache::GetAuthEntryForPath(const char *scheme, michael@0: const char *host, michael@0: int32_t port, michael@0: const char *path, michael@0: uint32_t appId, michael@0: bool inBrowserElement, michael@0: nsHttpAuthEntry **entry) michael@0: { michael@0: LOG(("nsHttpAuthCache::GetAuthEntryForPath [key=%s://%s:%d path=%s]\n", michael@0: scheme, host, port, path)); michael@0: michael@0: nsAutoCString key; michael@0: nsHttpAuthNode *node = LookupAuthNode(scheme, host, port, appId, inBrowserElement, key); michael@0: if (!node) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *entry = node->LookupEntryByPath(path); michael@0: return *entry ? NS_OK : NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpAuthCache::GetAuthEntryForDomain(const char *scheme, michael@0: const char *host, michael@0: int32_t port, michael@0: const char *realm, michael@0: uint32_t appId, michael@0: bool inBrowserElement, michael@0: nsHttpAuthEntry **entry) michael@0: michael@0: { michael@0: LOG(("nsHttpAuthCache::GetAuthEntryForDomain [key=%s://%s:%d realm=%s]\n", michael@0: scheme, host, port, realm)); michael@0: michael@0: nsAutoCString key; michael@0: nsHttpAuthNode *node = LookupAuthNode(scheme, host, port, appId, inBrowserElement, key); michael@0: if (!node) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *entry = node->LookupEntryByRealm(realm); michael@0: return *entry ? NS_OK : NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpAuthCache::SetAuthEntry(const char *scheme, michael@0: const char *host, michael@0: int32_t port, michael@0: const char *path, michael@0: const char *realm, michael@0: const char *creds, michael@0: const char *challenge, michael@0: uint32_t appId, michael@0: bool inBrowserElement, michael@0: const nsHttpAuthIdentity *ident, michael@0: nsISupports *metadata) michael@0: { michael@0: nsresult rv; michael@0: michael@0: LOG(("nsHttpAuthCache::SetAuthEntry [key=%s://%s:%d realm=%s path=%s metadata=%x]\n", michael@0: scheme, host, port, realm, path, metadata)); michael@0: michael@0: if (!mDB) { michael@0: rv = Init(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: michael@0: nsAutoCString key; michael@0: nsHttpAuthNode *node = LookupAuthNode(scheme, host, port, appId, inBrowserElement, key); michael@0: michael@0: if (!node) { michael@0: // create a new entry node and set the given entry michael@0: node = new nsHttpAuthNode(); michael@0: if (!node) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: rv = node->SetAuthEntry(path, realm, creds, challenge, ident, metadata); michael@0: if (NS_FAILED(rv)) michael@0: delete node; michael@0: else michael@0: PL_HashTableAdd(mDB, strdup(key.get()), node); michael@0: return rv; michael@0: } michael@0: michael@0: return node->SetAuthEntry(path, realm, creds, challenge, ident, metadata); michael@0: } michael@0: michael@0: void michael@0: nsHttpAuthCache::ClearAuthEntry(const char *scheme, michael@0: const char *host, michael@0: int32_t port, michael@0: const char *realm, michael@0: uint32_t appId, michael@0: bool inBrowserElement) michael@0: { michael@0: if (!mDB) michael@0: return; michael@0: michael@0: nsAutoCString key; michael@0: GetAuthKey(scheme, host, port, appId, inBrowserElement, key); michael@0: PL_HashTableRemove(mDB, key.get()); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpAuthCache::ClearAll() michael@0: { michael@0: LOG(("nsHttpAuthCache::ClearAll\n")); michael@0: michael@0: if (mDB) { michael@0: PL_HashTableDestroy(mDB); michael@0: mDB = 0; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpAuthCache michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsHttpAuthNode * michael@0: nsHttpAuthCache::LookupAuthNode(const char *scheme, michael@0: const char *host, michael@0: int32_t port, michael@0: uint32_t appId, michael@0: bool inBrowserElement, michael@0: nsCString &key) michael@0: { michael@0: if (!mDB) michael@0: return nullptr; michael@0: michael@0: GetAuthKey(scheme, host, port, appId, inBrowserElement, key); michael@0: michael@0: return (nsHttpAuthNode *) PL_HashTableLookup(mDB, key.get()); michael@0: } michael@0: michael@0: void * michael@0: nsHttpAuthCache::AllocTable(void *self, size_t size) michael@0: { michael@0: return malloc(size); michael@0: } michael@0: michael@0: void michael@0: nsHttpAuthCache::FreeTable(void *self, void *item) michael@0: { michael@0: free(item); michael@0: } michael@0: michael@0: PLHashEntry * michael@0: nsHttpAuthCache::AllocEntry(void *self, const void *key) michael@0: { michael@0: return (PLHashEntry *) malloc(sizeof(PLHashEntry)); michael@0: } michael@0: michael@0: void michael@0: nsHttpAuthCache::FreeEntry(void *self, PLHashEntry *he, unsigned flag) michael@0: { michael@0: if (flag == HT_FREE_VALUE) { michael@0: // this would only happen if PL_HashTableAdd were to replace an michael@0: // existing entry in the hash table, but we _always_ do a lookup michael@0: // before adding a new entry to avoid this case. michael@0: NS_NOTREACHED("should never happen"); michael@0: } michael@0: else if (flag == HT_FREE_ENTRY) { michael@0: // three wonderful flavors of freeing memory ;-) michael@0: delete (nsHttpAuthNode *) he->value; michael@0: free((char *) he->key); michael@0: free(he); michael@0: } michael@0: } michael@0: michael@0: PLHashAllocOps nsHttpAuthCache::gHashAllocOps = michael@0: { michael@0: nsHttpAuthCache::AllocTable, michael@0: nsHttpAuthCache::FreeTable, michael@0: nsHttpAuthCache::AllocEntry, michael@0: nsHttpAuthCache::FreeEntry michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsHttpAuthCache::AppDataClearObserver, nsIObserver) michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpAuthCache::AppDataClearObserver::Observe(nsISupports *subject, michael@0: const char * topic, michael@0: const char16_t * data_unicode) michael@0: { michael@0: NS_ENSURE_TRUE(mOwner, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: nsCOMPtr params = michael@0: do_QueryInterface(subject); michael@0: if (!params) { michael@0: NS_ERROR("'webapps-clear-data' notification's subject should be a mozIApplicationClearPrivateDataParams"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: uint32_t appId; michael@0: bool browserOnly; michael@0: michael@0: nsresult rv = params->GetAppId(&appId); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = params->GetBrowserOnly(&browserOnly); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: MOZ_ASSERT(appId != NECKO_UNKNOWN_APP_ID); michael@0: mOwner->ClearAppData(appId, browserOnly); michael@0: return NS_OK; michael@0: } michael@0: michael@0: static int michael@0: RemoveEntriesForApp(PLHashEntry *entry, int32_t number, void *arg) michael@0: { michael@0: nsDependentCString key(static_cast(entry->key)); michael@0: nsAutoCString* prefix = static_cast(arg); michael@0: if (StringBeginsWith(key, *prefix)) { michael@0: return HT_ENUMERATE_NEXT | HT_ENUMERATE_REMOVE; michael@0: } michael@0: return HT_ENUMERATE_NEXT; michael@0: } michael@0: michael@0: void michael@0: nsHttpAuthCache::ClearAppData(uint32_t appId, bool browserOnly) michael@0: { michael@0: if (!mDB) { michael@0: return; michael@0: } michael@0: nsAutoCString keyPrefix; michael@0: keyPrefix.AppendInt(appId); michael@0: keyPrefix.Append(':'); michael@0: if (browserOnly) { michael@0: keyPrefix.AppendInt(browserOnly); michael@0: keyPrefix.Append(':'); michael@0: } michael@0: PL_HashTableEnumerateEntries(mDB, RemoveEntriesForApp, &keyPrefix); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpAuthIdentity michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: nsHttpAuthIdentity::Set(const char16_t *domain, michael@0: const char16_t *user, michael@0: const char16_t *pass) michael@0: { michael@0: char16_t *newUser, *newPass, *newDomain; michael@0: michael@0: int domainLen = domain ? NS_strlen(domain) : 0; michael@0: int userLen = user ? NS_strlen(user) : 0; michael@0: int passLen = pass ? NS_strlen(pass) : 0; michael@0: michael@0: int len = userLen + 1 + passLen + 1 + domainLen + 1; michael@0: newUser = (char16_t *) malloc(len * sizeof(char16_t)); michael@0: if (!newUser) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: if (user) michael@0: memcpy(newUser, user, userLen * sizeof(char16_t)); michael@0: newUser[userLen] = 0; michael@0: michael@0: newPass = &newUser[userLen + 1]; michael@0: if (pass) michael@0: memcpy(newPass, pass, passLen * sizeof(char16_t)); michael@0: newPass[passLen] = 0; michael@0: michael@0: newDomain = &newPass[passLen + 1]; michael@0: if (domain) michael@0: memcpy(newDomain, domain, domainLen * sizeof(char16_t)); michael@0: newDomain[domainLen] = 0; michael@0: michael@0: // wait until the end to clear member vars in case input params michael@0: // reference our members! michael@0: if (mUser) michael@0: free(mUser); michael@0: mUser = newUser; michael@0: mPass = newPass; michael@0: mDomain = newDomain; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsHttpAuthIdentity::Clear() michael@0: { michael@0: if (mUser) { michael@0: free(mUser); michael@0: mUser = nullptr; michael@0: mPass = nullptr; michael@0: mDomain = nullptr; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsHttpAuthIdentity::Equals(const nsHttpAuthIdentity &ident) const michael@0: { michael@0: // we could probably optimize this with a single loop, but why bother? michael@0: return StrEquivalent(mUser, ident.mUser) && michael@0: StrEquivalent(mPass, ident.mPass) && michael@0: StrEquivalent(mDomain, ident.mDomain); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpAuthEntry michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsHttpAuthEntry::~nsHttpAuthEntry() michael@0: { michael@0: if (mRealm) michael@0: free(mRealm); michael@0: michael@0: while (mRoot) { michael@0: nsHttpAuthPath *ap = mRoot; michael@0: mRoot = mRoot->mNext; michael@0: free(ap); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpAuthEntry::AddPath(const char *aPath) michael@0: { michael@0: // null path matches empty path michael@0: if (!aPath) michael@0: aPath = ""; michael@0: michael@0: nsHttpAuthPath *tempPtr = mRoot; michael@0: while (tempPtr) { michael@0: const char *curpath = tempPtr->mPath; michael@0: if (strncmp(aPath, curpath, strlen(curpath)) == 0) michael@0: return NS_OK; // subpath already exists in the list michael@0: michael@0: tempPtr = tempPtr->mNext; michael@0: michael@0: } michael@0: michael@0: //Append the aPath michael@0: nsHttpAuthPath *newAuthPath; michael@0: int newpathLen = strlen(aPath); michael@0: newAuthPath = (nsHttpAuthPath *) malloc(sizeof(nsHttpAuthPath) + newpathLen); michael@0: if (!newAuthPath) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: memcpy(newAuthPath->mPath, aPath, newpathLen+1); michael@0: newAuthPath->mNext = nullptr; michael@0: michael@0: if (!mRoot) michael@0: mRoot = newAuthPath; //first entry michael@0: else michael@0: mTail->mNext = newAuthPath; // Append newAuthPath michael@0: michael@0: //update the tail pointer. michael@0: mTail = newAuthPath; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpAuthEntry::Set(const char *path, michael@0: const char *realm, michael@0: const char *creds, michael@0: const char *chall, michael@0: const nsHttpAuthIdentity *ident, michael@0: nsISupports *metadata) michael@0: { michael@0: char *newRealm, *newCreds, *newChall; michael@0: michael@0: int realmLen = realm ? strlen(realm) : 0; michael@0: int credsLen = creds ? strlen(creds) : 0; michael@0: int challLen = chall ? strlen(chall) : 0; michael@0: michael@0: int len = realmLen + 1 + credsLen + 1 + challLen + 1; michael@0: newRealm = (char *) malloc(len); michael@0: if (!newRealm) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: if (realm) michael@0: memcpy(newRealm, realm, realmLen); michael@0: newRealm[realmLen] = 0; michael@0: michael@0: newCreds = &newRealm[realmLen + 1]; michael@0: if (creds) michael@0: memcpy(newCreds, creds, credsLen); michael@0: newCreds[credsLen] = 0; michael@0: michael@0: newChall = &newCreds[credsLen + 1]; michael@0: if (chall) michael@0: memcpy(newChall, chall, challLen); michael@0: newChall[challLen] = 0; michael@0: michael@0: nsresult rv = NS_OK; michael@0: if (ident) { michael@0: rv = mIdent.Set(*ident); michael@0: } michael@0: else if (mIdent.IsEmpty()) { michael@0: // If we are not given an identity and our cached identity has not been michael@0: // initialized yet (so is currently empty), initialize it now by michael@0: // filling it with nulls. We need to do that because consumers expect michael@0: // that mIdent is initialized after this function returns. michael@0: rv = mIdent.Set(nullptr, nullptr, nullptr); michael@0: } michael@0: if (NS_FAILED(rv)) { michael@0: free(newRealm); michael@0: return rv; michael@0: } michael@0: michael@0: rv = AddPath(path); michael@0: if (NS_FAILED(rv)) { michael@0: free(newRealm); michael@0: return rv; michael@0: } michael@0: michael@0: // wait until the end to clear member vars in case input params michael@0: // reference our members! michael@0: if (mRealm) michael@0: free(mRealm); michael@0: michael@0: mRealm = newRealm; michael@0: mCreds = newCreds; michael@0: mChallenge = newChall; michael@0: mMetaData = metadata; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpAuthNode michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsHttpAuthNode::nsHttpAuthNode() michael@0: { michael@0: LOG(("Creating nsHttpAuthNode @%x\n", this)); michael@0: } michael@0: michael@0: nsHttpAuthNode::~nsHttpAuthNode() michael@0: { michael@0: LOG(("Destroying nsHttpAuthNode @%x\n", this)); michael@0: michael@0: mList.Clear(); michael@0: } michael@0: michael@0: nsHttpAuthEntry * michael@0: nsHttpAuthNode::LookupEntryByPath(const char *path) michael@0: { michael@0: nsHttpAuthEntry *entry; michael@0: michael@0: // null path matches empty path michael@0: if (!path) michael@0: path = ""; michael@0: michael@0: // look for an entry that either matches or contains this directory. michael@0: // ie. we'll give out credentials if the given directory is a sub- michael@0: // directory of an existing entry. michael@0: for (uint32_t i=0; iRootPath(); michael@0: while (authPath) { michael@0: const char *entryPath = authPath->mPath; michael@0: // proxy auth entries have no path, so require exact match on michael@0: // empty path string. michael@0: if (entryPath[0] == '\0') { michael@0: if (path[0] == '\0') michael@0: return entry; michael@0: } michael@0: else if (strncmp(path, entryPath, strlen(entryPath)) == 0) michael@0: return entry; michael@0: michael@0: authPath = authPath->mNext; michael@0: } michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: nsHttpAuthEntry * michael@0: nsHttpAuthNode::LookupEntryByRealm(const char *realm) michael@0: { michael@0: nsHttpAuthEntry *entry; michael@0: michael@0: // null realm matches empty realm michael@0: if (!realm) michael@0: realm = ""; michael@0: michael@0: // look for an entry that matches this realm michael@0: uint32_t i; michael@0: for (i=0; iRealm()) == 0) michael@0: return entry; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpAuthNode::SetAuthEntry(const char *path, michael@0: const char *realm, michael@0: const char *creds, michael@0: const char *challenge, michael@0: const nsHttpAuthIdentity *ident, michael@0: nsISupports *metadata) michael@0: { michael@0: // look for an entry with a matching realm michael@0: nsHttpAuthEntry *entry = LookupEntryByRealm(realm); michael@0: if (!entry) { michael@0: entry = new nsHttpAuthEntry(path, realm, creds, challenge, ident, metadata); michael@0: if (!entry) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: mList.AppendElement(entry); michael@0: } michael@0: else { michael@0: // update the entry... michael@0: entry->Set(path, realm, creds, challenge, ident, metadata); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsHttpAuthNode::ClearAuthEntry(const char *realm) michael@0: { michael@0: nsHttpAuthEntry *entry = LookupEntryByRealm(realm); michael@0: if (entry) { michael@0: mList.RemoveElement(entry); // double search OK michael@0: } michael@0: } michael@0: michael@0: } // namespace mozilla::net michael@0: } // namespace mozilla