michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 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: #ifndef nsCookieService_h__ michael@0: #define nsCookieService_h__ michael@0: michael@0: #include "nsICookieService.h" michael@0: #include "nsICookieManager.h" michael@0: #include "nsICookieManager2.h" michael@0: #include "nsIObserver.h" michael@0: #include "nsWeakReference.h" michael@0: michael@0: #include "nsCookie.h" michael@0: #include "nsString.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsHashKeys.h" michael@0: #include "nsIMemoryReporter.h" michael@0: #include "nsTHashtable.h" michael@0: #include "mozIStorageStatement.h" michael@0: #include "mozIStorageAsyncStatement.h" michael@0: #include "mozIStoragePendingStatement.h" michael@0: #include "mozIStorageConnection.h" michael@0: #include "mozIStorageRow.h" michael@0: #include "mozIStorageCompletionCallback.h" michael@0: #include "mozIStorageStatementCallback.h" michael@0: michael@0: #include "mozilla/MemoryReporting.h" michael@0: michael@0: class nsICookiePermission; michael@0: class nsIEffectiveTLDService; michael@0: class nsIIDNService; michael@0: class nsIPrefBranch; michael@0: class nsIObserverService; michael@0: class nsIURI; michael@0: class nsIChannel; michael@0: class nsIArray; michael@0: class mozIStorageService; michael@0: class mozIThirdPartyUtil; michael@0: class ReadCookieDBListener; michael@0: michael@0: struct nsCookieAttributes; michael@0: struct nsListIter; michael@0: struct nsEnumerationData; michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: class CookieServiceParent; michael@0: } michael@0: } michael@0: michael@0: // hash key class michael@0: class nsCookieKey : public PLDHashEntryHdr michael@0: { michael@0: public: michael@0: typedef const nsCookieKey& KeyType; michael@0: typedef const nsCookieKey* KeyTypePointer; michael@0: michael@0: nsCookieKey() michael@0: {} michael@0: michael@0: nsCookieKey(const nsCString &baseDomain, uint32_t appId, bool inBrowser) michael@0: : mBaseDomain(baseDomain) michael@0: , mAppId(appId) michael@0: , mInBrowserElement(inBrowser) michael@0: {} michael@0: michael@0: nsCookieKey(KeyTypePointer other) michael@0: : mBaseDomain(other->mBaseDomain) michael@0: , mAppId(other->mAppId) michael@0: , mInBrowserElement(other->mInBrowserElement) michael@0: {} michael@0: michael@0: nsCookieKey(KeyType other) michael@0: : mBaseDomain(other.mBaseDomain) michael@0: , mAppId(other.mAppId) michael@0: , mInBrowserElement(other.mInBrowserElement) michael@0: {} michael@0: michael@0: ~nsCookieKey() michael@0: {} michael@0: michael@0: bool KeyEquals(KeyTypePointer other) const michael@0: { michael@0: return mBaseDomain == other->mBaseDomain && michael@0: mAppId == other->mAppId && michael@0: mInBrowserElement == other->mInBrowserElement; michael@0: } michael@0: michael@0: static KeyTypePointer KeyToPointer(KeyType aKey) michael@0: { michael@0: return &aKey; michael@0: } michael@0: michael@0: static PLDHashNumber HashKey(KeyTypePointer aKey) michael@0: { michael@0: // TODO: more efficient way to generate hash? michael@0: nsAutoCString temp(aKey->mBaseDomain); michael@0: temp.Append("#"); michael@0: temp.Append(aKey->mAppId); michael@0: temp.Append("#"); michael@0: temp.Append(aKey->mInBrowserElement ? 1 : 0); michael@0: return mozilla::HashString(temp); michael@0: } michael@0: michael@0: size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; michael@0: michael@0: enum { ALLOW_MEMMOVE = true }; michael@0: michael@0: nsCString mBaseDomain; michael@0: uint32_t mAppId; michael@0: bool mInBrowserElement; michael@0: }; michael@0: michael@0: // Inherit from nsCookieKey so this can be stored in nsTHashTable michael@0: // TODO: why aren't we using nsClassHashTable? michael@0: class nsCookieEntry : public nsCookieKey michael@0: { michael@0: public: michael@0: // Hash methods michael@0: typedef nsTArray< nsRefPtr > ArrayType; michael@0: typedef ArrayType::index_type IndexType; michael@0: michael@0: nsCookieEntry(KeyTypePointer aKey) michael@0: : nsCookieKey(aKey) michael@0: {} michael@0: michael@0: nsCookieEntry(const nsCookieEntry& toCopy) michael@0: { michael@0: // if we end up here, things will break. nsTHashtable shouldn't michael@0: // allow this, since we set ALLOW_MEMMOVE to true. michael@0: NS_NOTREACHED("nsCookieEntry copy constructor is forbidden!"); michael@0: } michael@0: michael@0: ~nsCookieEntry() michael@0: {} michael@0: michael@0: inline ArrayType& GetCookies() { return mCookies; } michael@0: michael@0: size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; michael@0: michael@0: private: michael@0: ArrayType mCookies; michael@0: }; michael@0: michael@0: // encapsulates a (key, nsCookie) tuple for temporary storage purposes. michael@0: struct CookieDomainTuple michael@0: { michael@0: nsCookieKey key; michael@0: nsRefPtr cookie; michael@0: michael@0: size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; michael@0: }; michael@0: michael@0: // encapsulates in-memory and on-disk DB states, so we can michael@0: // conveniently switch state when entering or exiting private browsing. michael@0: struct DBState MOZ_FINAL michael@0: { michael@0: DBState() : cookieCount(0), cookieOldestTime(INT64_MAX), corruptFlag(OK) michael@0: { michael@0: } michael@0: michael@0: private: michael@0: // Private destructor, to discourage deletion outside of Release(): michael@0: ~DBState() michael@0: { michael@0: } michael@0: michael@0: public: michael@0: NS_INLINE_DECL_REFCOUNTING(DBState) michael@0: michael@0: size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; michael@0: michael@0: // State of the database connection. michael@0: enum CorruptFlag { michael@0: OK, // normal michael@0: CLOSING_FOR_REBUILD, // corruption detected, connection closing michael@0: REBUILDING // close complete, rebuilding database from memory michael@0: }; michael@0: michael@0: nsTHashtable hostTable; michael@0: uint32_t cookieCount; michael@0: int64_t cookieOldestTime; michael@0: nsCOMPtr cookieFile; michael@0: nsCOMPtr dbConn; michael@0: nsCOMPtr stmtInsert; michael@0: nsCOMPtr stmtDelete; michael@0: nsCOMPtr stmtUpdate; michael@0: CorruptFlag corruptFlag; michael@0: michael@0: // Various parts representing asynchronous read state. These are useful michael@0: // while the background read is taking place. michael@0: nsCOMPtr syncConn; michael@0: nsCOMPtr stmtReadDomain; michael@0: nsCOMPtr pendingRead; michael@0: // The asynchronous read listener. This is a weak ref (storage has ownership) michael@0: // since it may need to outlive the DBState's database connection. michael@0: ReadCookieDBListener* readListener; michael@0: // An array of (baseDomain, cookie) tuples representing data read in michael@0: // asynchronously. This is merged into hostTable once read is complete. michael@0: nsTArray hostArray; michael@0: // A hashset of baseDomains read in synchronously, while the async read is michael@0: // in flight. This is used to keep track of which data in hostArray is stale michael@0: // when the time comes to merge. michael@0: nsTHashtable readSet; michael@0: michael@0: // DB completion handlers. michael@0: nsCOMPtr insertListener; michael@0: nsCOMPtr updateListener; michael@0: nsCOMPtr removeListener; michael@0: nsCOMPtr closeListener; michael@0: }; michael@0: michael@0: // these constants represent a decision about a cookie based on user prefs. michael@0: enum CookieStatus michael@0: { michael@0: STATUS_ACCEPTED, michael@0: STATUS_ACCEPT_SESSION, michael@0: STATUS_REJECTED, michael@0: // STATUS_REJECTED_WITH_ERROR indicates the cookie should be rejected because michael@0: // of an error (rather than something the user can control). this is used for michael@0: // notification purposes, since we only want to notify of rejections where michael@0: // the user can do something about it (e.g. whitelist the site). michael@0: STATUS_REJECTED_WITH_ERROR michael@0: }; michael@0: michael@0: // Result codes for TryInitDB() and Read(). michael@0: enum OpenDBResult michael@0: { michael@0: RESULT_OK, michael@0: RESULT_RETRY, michael@0: RESULT_FAILURE michael@0: }; michael@0: michael@0: /****************************************************************************** michael@0: * nsCookieService: michael@0: * class declaration michael@0: ******************************************************************************/ michael@0: michael@0: class nsCookieService : public nsICookieService michael@0: , public nsICookieManager2 michael@0: , public nsIObserver michael@0: , public nsSupportsWeakReference michael@0: , public nsIMemoryReporter michael@0: { michael@0: private: michael@0: size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; michael@0: michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIOBSERVER michael@0: NS_DECL_NSICOOKIESERVICE michael@0: NS_DECL_NSICOOKIEMANAGER michael@0: NS_DECL_NSICOOKIEMANAGER2 michael@0: NS_DECL_NSIMEMORYREPORTER michael@0: michael@0: nsCookieService(); michael@0: virtual ~nsCookieService(); michael@0: static nsICookieService* GetXPCOMSingleton(); michael@0: nsresult Init(); michael@0: michael@0: /** michael@0: * Start watching the observer service for messages indicating that an app has michael@0: * been uninstalled. When an app is uninstalled, we get the cookie service michael@0: * (thus instantiating it, if necessary) and clear all the cookies for that michael@0: * app. michael@0: */ michael@0: static void AppClearDataObserverInit(); michael@0: michael@0: protected: michael@0: void PrefChanged(nsIPrefBranch *aPrefBranch); michael@0: void InitDBStates(); michael@0: OpenDBResult TryInitDB(bool aDeleteExistingDB); michael@0: nsresult CreateTable(); michael@0: void CloseDBStates(); michael@0: void CleanupCachedStatements(); michael@0: void CleanupDefaultDBConnection(); michael@0: void HandleDBClosed(DBState* aDBState); michael@0: void HandleCorruptDB(DBState* aDBState); michael@0: void RebuildCorruptDB(DBState* aDBState); michael@0: OpenDBResult Read(); michael@0: template nsCookie* GetCookieFromRow(T &aRow); michael@0: void AsyncReadComplete(); michael@0: void CancelAsyncRead(bool aPurgeReadSet); michael@0: void EnsureReadDomain(const nsCookieKey &aKey); michael@0: void EnsureReadComplete(); michael@0: nsresult NormalizeHost(nsCString &aHost); michael@0: nsresult GetBaseDomain(nsIURI *aHostURI, nsCString &aBaseDomain, bool &aRequireHostMatch); michael@0: nsresult GetBaseDomainFromHost(const nsACString &aHost, nsCString &aBaseDomain); michael@0: nsresult GetCookieStringCommon(nsIURI *aHostURI, nsIChannel *aChannel, bool aHttpBound, char** aCookie); michael@4: void GetCookieStringInternal(nsIURI *aHostURI, bool aIsForeign, bool aHttpBound, uint32_t aAppId, bool aInBrowserElement, bool aIsPrivate, nsCString &aOrigin, nsCString &aCookie); michael@0: nsresult SetCookieStringCommon(nsIURI *aHostURI, const char *aCookieHeader, const char *aServerTime, nsIChannel *aChannel, bool aFromHttp); michael@0: void SetCookieStringInternal(nsIURI *aHostURI, bool aIsForeign, nsDependentCString &aCookieHeader, const nsCString &aServerTime, bool aFromHttp, uint32_t aAppId, bool aInBrowserElement, bool aIsPrivate, nsIChannel* aChannel); michael@4: bool SetCookieInternal(nsIURI *aHostURI, const nsCookieKey& aKey, bool aRequireHostMatch, const nsCString &aOrigin, CookieStatus aStatus, nsDependentCString &aCookieHeader, int64_t aServerTime, bool aFromHttp, nsIChannel* aChannel); michael@0: void AddInternal(const nsCookieKey& aKey, nsCookie *aCookie, int64_t aCurrentTimeInUsec, nsIURI *aHostURI, const char *aCookieHeader, bool aFromHttp); michael@0: void RemoveCookieFromList(const nsListIter &aIter, mozIStorageBindingParamsArray *aParamsArray = nullptr); michael@0: void AddCookieToList(const nsCookieKey& aKey, nsCookie *aCookie, DBState *aDBState, mozIStorageBindingParamsArray *aParamsArray, bool aWriteToDB = true); michael@0: void UpdateCookieInList(nsCookie *aCookie, int64_t aLastAccessed, mozIStorageBindingParamsArray *aParamsArray); michael@0: static bool GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter, nsASingleFragmentCString::const_char_iterator &aEndIter, nsDependentCSubstring &aTokenString, nsDependentCSubstring &aTokenValue, bool &aEqualsFound); michael@0: static bool ParseAttributes(nsDependentCString &aCookieHeader, nsCookieAttributes &aCookie); michael@0: bool RequireThirdPartyCheck(); michael@0: CookieStatus CheckPrefs(nsIURI *aHostURI, bool aIsForeign, bool aRequireHostMatch, const char *aCookieHeader); michael@0: bool CheckDomain(nsCookieAttributes &aCookie, nsIURI *aHostURI, const nsCString &aBaseDomain, bool aRequireHostMatch); michael@0: static bool CheckPath(nsCookieAttributes &aCookie, nsIURI *aHostURI); michael@0: static bool GetExpiry(nsCookieAttributes &aCookie, int64_t aServerTime, int64_t aCurrentTime); michael@0: void RemoveAllFromMemory(); michael@0: already_AddRefed PurgeCookies(int64_t aCurrentTimeInUsec); michael@4: bool FindCookie(const nsCookieKey& aKey, const nsAFlatCString &aOrigin, const nsAFlatCString &aHost, const nsAFlatCString &aName, const nsAFlatCString &aPath, nsListIter &aIter); michael@0: static void FindStaleCookie(nsCookieEntry *aEntry, int64_t aCurrentTime, nsListIter &aIter); michael@0: void NotifyRejected(nsIURI *aHostURI); michael@0: void NotifyThirdParty(nsIURI *aHostURI, bool aAccepted, nsIChannel *aChannel); michael@0: void NotifyChanged(nsISupports *aSubject, const char16_t *aData); michael@0: void NotifyPurged(nsICookie2* aCookie); michael@0: already_AddRefed CreatePurgeList(nsICookie2* aCookie); michael@0: michael@0: /** michael@0: * This method is used to iterate the cookie hash table and select the ones michael@0: * that are part of a specific app. michael@0: */ michael@0: static PLDHashOperator GetCookiesForApp(nsCookieEntry* entry, void* arg); michael@0: michael@0: /** michael@0: * This method is a helper that allows calling nsICookieManager::Remove() michael@0: * with appId/inBrowserElement parameters. michael@0: * NOTE: this could be added to a public interface if we happen to need it. michael@0: */ michael@0: nsresult Remove(const nsACString& aHost, uint32_t aAppId, michael@0: bool aInBrowserElement, const nsACString& aName, michael@0: const nsACString& aPath, bool aBlocked); michael@0: michael@0: protected: michael@0: // cached members. michael@0: nsCOMPtr mObserverService; michael@0: nsCOMPtr mPermissionService; michael@0: nsCOMPtr mThirdPartyUtil; michael@0: nsCOMPtr mTLDService; michael@0: nsCOMPtr mIDNService; michael@0: nsCOMPtr mStorageService; michael@0: michael@0: // we have two separate DB states: one for normal browsing and one for michael@0: // private browsing, switching between them on a per-cookie-request basis. michael@0: // this state encapsulates both the in-memory table and the on-disk DB. michael@0: // note that the private states' dbConn should always be null - we never michael@0: // want to be dealing with the on-disk DB when in private browsing. michael@0: DBState *mDBState; michael@0: nsRefPtr mDefaultDBState; michael@0: nsRefPtr mPrivateDBState; michael@0: michael@0: // cached prefs michael@0: uint8_t mCookieBehavior; // BEHAVIOR_{ACCEPT, REJECTFOREIGN, REJECT, LIMITFOREIGN} michael@0: bool mThirdPartySession; michael@0: uint16_t mMaxNumberOfCookies; michael@0: uint16_t mMaxCookiesPerHost; michael@0: int64_t mCookiePurgeAge; michael@0: michael@0: // friends! michael@0: friend PLDHashOperator purgeCookiesCallback(nsCookieEntry *aEntry, void *aArg); michael@0: friend class DBListenerErrorHandler; michael@0: friend class ReadCookieDBListener; michael@0: friend class CloseCookieDBListener; michael@0: michael@0: static nsCookieService* GetSingleton(); michael@0: friend class mozilla::net::CookieServiceParent; michael@0: }; michael@0: michael@0: #endif // nsCookieService_h__