/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_net_Dictionary_h
#define mozilla_net_Dictionary_h
#include "nsCOMPtr.h"
#include "nsICacheEntry.h"
#include "nsICacheEntryOpenCallback.h"
#include "nsICacheStorageService.h"
#include "nsICacheStorageVisitor.h"
#include "nsICryptoHash.h"
#include "nsIInterfaceRequestor.h"
#include "nsIObserver.h"
#include "nsIStreamListener.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Vector.h"
#include "nsString.h"
#include "nsTArray.h"
#include <vector>
#include "mozilla/dom/RequestBinding.h"
#include "mozilla/TimeStamp.h"
#include "nsTHashMap.h"
#include "nsHashKeys.h"
#include "mozilla/net/urlpattern_glue.h"
class nsICacheStorage;
class nsIIOService;
class nsILoadContextInfo;
// Version of metadata entries we expect
static const uint32_t METADATA_DICTIONARY_VERSION =
1;
#define META_DICTIONARY_PREFIX
"dict:"_ns
namespace mozilla {
namespace net {
class nsHttpChannel;
class DictionaryOrigin;
// Outstanding requests that offer this dictionary will hold a reference to it.
// If it's replaced (or removed) during the request, we would a) read the data
// into memory* b) unlink this from the origin in the memory cache.
//
// * or we wait for read-into-memory to finish, if we start reading entries
// when we send the request.
//
// When creating an entry from incoming data, we'll create it with no hash
// initially until the full data has arrived, then update the Hash.
class DictionaryCacheEntry final : public nsICacheEntryOpenCallback,
public nsIStreamListener {
friend class DictionaryOrigin;
private:
~DictionaryCacheEntry();
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSICACHEENTRYOPENCALLBACK
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
explicit DictionaryCacheEntry(
const char* aKey);
DictionaryCacheEntry(
const nsACString& aKey,
const nsACString& aPattern,
nsTArray<nsCString>& aMatchDest,
const nsACString& aId,
uint32_t aExpiration =
0,
const Maybe<nsCString>& aHash = Nothing());
static void ConvertMatchDestToEnumArray(
const nsTArray<nsCString>& aMatchDest,
nsTArray<dom::RequestDestination>& aMatchEnums);
// returns true if the pattern for the dictionary matches the path given
bool Match(
const nsACString& aFilePath, ExtContentPolicyType aType,
uint32_t aNow, uint32_t& aLongest);
// This will fail if the cache entry is no longer available.
// Start reading the cache entry into memory and call completion
// function when done
nsresult Prefetch(nsILoadContextInfo* aLoadContextInfo,
bool& aShouldSuspend,
const std::function<
void(nsresult)>& aFunc);
nsCString GetHash()
const;
bool HasHash();
void SetHash(
const nsACString& aHash);
void WriteOnHash();
void SetOrigin(DictionaryOrigin* aOrigin) { mOrigin = aOrigin; }
const nsCString& GetId()
const {
return mId; }
// keep track of requests that may need the data
void InUse();
void UseCompleted();
bool IsReading()
const;
void SetReplacement(DictionaryCacheEntry* aEntry, DictionaryOrigin* aOrigin) {
mReplacement = aEntry;
mOrigin = aOrigin;
if (mReplacement) {
mReplacement->mShouldSuspend = true;
mReplacement->mBlocked = true;
}
}
bool ShouldSuspendUntilCacheRead()
const {
return mShouldSuspend; }
// aFunc is called when we have finished reading a dictionary from the
// cache, or we have no users waiting for cache data (cancelled, etc)
void CallbackOnCacheRead(
const std::function<
void(nsresult)>& aFunc);
const nsACString& GetURI()
const {
return mURI; }
const Vector<uint8_t>& GetDictionary()
const;
// Clear dictionary data for testing (forces reload from cache on next
// prefetch)
void ClearDataForTesting();
// Accumulate a hash while saving a file being received to the cache
void AccumulateHash(
const char* aBuf, int32_t aCount);
void FinishHash();
void FinishHashOnMainThread();
// return a pointer to the data and length
uint8_t* DictionaryData(size_t* aLength)
const;
bool DictionaryReady()
const;
size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
const {
// XXX
return mallocSizeOf(this);
}
static nsresult ReadCacheData(nsIInputStream* aInStream,
void* aClosure,
const char* aFromSegment, uint32_t aToOffset,
uint32_t aCount, uint32_t* aWriteCount);
void CleanupOnCacheData(nsresult result);
void MakeMetadataEntry(nsCString& aNewValue);
nsresult Write(nsICacheEntry* aEntry);
nsresult RemoveEntry(nsICacheEntry* aCacheEntry);
// Parse metadata from DictionaryOrigin
bool ParseMetadata(
const char* aSrc);
void CopyFrom(DictionaryCacheEntry* aOther) {
mURI = aOther->mURI;
mPattern = aOther->mPattern;
mId = aOther->mId;
mMatchDest = aOther->mMatchDest;
// XXX mType = aOther->mType;
}
void UnblockAddEntry(DictionaryOrigin* aOrigin);
const nsCString& GetPattern()
const {
return mPattern; }
void AppendMatchDest(nsACString& aDest)
const;
private:
// URI (without ref) for the dictionary
nsCString mURI;
// Expiration time, or 0 for none (default)
uint32_t mExpiration{
0};
nsCString mPattern;
nsCString mId;
// max length 1024
CopyableTArray<dom::RequestDestination> mMatchDest;
// dcb and dcz use type 'raw'. We're allowed to ignore types we don't
// understand, so we can fail to record a dictionary with type != 'raw'
// nsCString mType;
// Cached parsed URLPattern for performance
Maybe<UrlPatternGlue> mCachedPattern;
// SHA-256 hash value - only written/read on MainThread (after
// pending->active) Written by FinishHash() on MainThread, immutable after
// that
nsCString mHash;
uint32_t mUsers{
0};
// active requests using this entry
// In-memory copy - only accessed on MainThread after validation
// Populated on MainThread after hash validation succeeds
Vector<uint8_t> mDictionaryData;
// Publishes mDictionaryData to reader threads. ReleaseAcquire so that
// the non-atomic mDictionaryData write is visible before readers see true.
Atomic<
bool, ReleaseAcquire> mDictionaryDataComplete{
false};
// Temporary buffer for accumulating dictionary data during cache reads
// Only accessed by cache I/O thread during stream callbacks (serialized)
Vector<uint8_t> mPendingDictionaryData;
// for accumulating SHA-256 hash values for dictionaries
// Only used on main thread (MOZ_ASSERT in AccumulateHash/FinishHash)
nsCOMPtr<nsICryptoHash> mCrypto;
// Structure to track prefetch callbacks with their private browsing status
struct PrefetchRequest {
std::function<
void(nsresult)> callback;
bool isPrivateBrowsing;
};
// Callbacks when prefetch is complete - only accessed on MainThread
// std::vector instead of TArray because it has a std::function ptr in it
std::vector<PrefetchRequest> mWaitingPrefetch;
// If we need to Write() an entry before we know the hash, remember the origin
// here (creates a temporary cycle). Clear on StopRequest
RefPtr<DictionaryOrigin> mOrigin;
// Simple state flags accessed from multiple threads
Atomic<
bool, Relaxed> mStopReceived{
false};
Atomic<
bool, Relaxed> mNotCached{
false};
// If set, a new entry wants to replace us, and we have active decoding users.
// When we finish reading data into this entry for decoding, do 2 things:
// Remove our entry from origin->mEntries (so no future requests find this,
// and un-Suspend the new channel so it can start saving data into the cache.
RefPtr<DictionaryCacheEntry> mReplacement;
// We should suspend until the ond entry has been read
bool mShouldSuspend{
false};
// We're blocked from taking over for the old entry for now
bool mBlocked{
false};
// Set during Prefetch in OnCacheEntryAvailable if the stored response
// headers still contain Content-Encoding. Non-empty means data on disk
// is likely still compressed (decompressor wasn't applied before save).
nsCString mStoredContentEncoding;
};
// XXX Do we want to pre-read dictionaries into RAM at startup (lazily)?
// If we have all dictionaries stored in the cache, we don't need to do
// lookups to find if an origin has dictionaries or not, and we don't need to
// store empty entries (and LRU them). Downside would be if there are a LOT of
// origins with dictionaries, which may eventually happen, it would use more
// memory for rarely used origins. We could have a limit for dictionaries, and
// above that switch to partial caching and empty entries for origins without.
class DictionaryCache;
class DictionaryOriginReader final : public nsICacheEntryOpenCallback,
public nsIStreamListener {
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSICACHEENTRYOPENCALLBACK
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
DictionaryOriginReader() =
default;
void Start(
bool aCreate, DictionaryOrigin* aOrigin, nsACString& aKey, nsIURI* aURI,
ExtContentPolicyType aType, DictionaryCache* aCache,
const std::function<nsresult(
bool, DictionaryCacheEntry*)>& aCallback);
void FinishMatch();
private:
~DictionaryOriginReader() =
default;
RefPtr<DictionaryOrigin> mOrigin;
nsCOMPtr<nsIURI> mURI;
ExtContentPolicyType mType = ExtContentPolicyType::TYPE_INVALID;
std::function<nsresult(
bool, DictionaryCacheEntry*)> mCallback;
RefPtr<DictionaryCache> mCache;
};
// using DictCacheList = AutoCleanLinkedList<RefPtr<DictionaryCacheEntry>>;
using DictCacheList = nsTArray<RefPtr<DictionaryCacheEntry>>;
// XXX if we want to have a parallel LRU list for pushing origins out of memory,
// add this: public LinkedListElement<RefPtr<DictionaryOrigin>>,
class DictionaryOrigin : public nsICacheEntryMetaDataVisitor {
friend class DictionaryCache;
friend class DictionaryOriginReader;
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSICACHEENTRYMETADATAVISITOR
DictionaryOrigin(
const nsACString& aOrigin, nsICacheEntry* aEntry)
: mOrigin(aOrigin), mEntry(aEntry) {}
void SetCacheEntry(nsICacheEntry* aEntry);
nsresult Write(DictionaryCacheEntry* aDictEntry);
already_AddRefed<DictionaryCacheEntry> AddEntry(
DictionaryCacheEntry* aDictEntry,
bool aNewEntry);
nsresult RemoveEntry(
const nsACString& aKey);
void RemoveEntry(DictionaryCacheEntry* aEntry);
DictionaryCacheEntry* Match(
const nsACString& path,
ExtContentPolicyType aType);
void FinishAddEntry(DictionaryCacheEntry* aEntry);
void DumpEntries();
void Clear();
bool IsEmpty()
const {
return mEntries.IsEmpty() && mPendingEntries.IsEmpty() &&
mPendingRemove.IsEmpty() && mWaitingCacheRead.IsEmpty();
}
private:
virtual ~DictionaryOrigin() =
default;
nsCString mOrigin;
nsCOMPtr<nsICacheEntry> mEntry;
DictCacheList mEntries;
// Dictionaries currently being received. Once these get a Hash, move to
// mEntries
DictCacheList mPendingEntries;
// Dictionaries removed from mEntries but waiting to be removed from the
// Cache metadata
DictCacheList mPendingRemove;
// Write out all entries once we have a cacheentry
bool mDeferredWrites{
false};
// readers that are waiting for this origin's metadata to be read
nsTArray<RefPtr<DictionaryOriginReader>> mWaitingCacheRead;
};
// singleton class
class DictionaryCache final : public nsIObserver {
private:
DictionaryCache() {
nsresult rv = Init();
(
void)rv;
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
}
~DictionaryCache() =
default;
friend class DictionaryOriginReader;
friend class DictionaryCacheEntry;
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIOBSERVER
static already_AddRefed<DictionaryCache> GetInstance();
nsresult Init();
static void Shutdown();
nsresult AddEntry(nsIURI* aURI,
const nsACString& aKey,
const nsACString& aPattern, nsTArray<nsCString>& aMatchDest,
const nsACString& aId,
const Maybe<nsCString>& aHash,
bool aNewEntry, uint32_t aExpiration,
DictionaryCacheEntry** aDictEntry);
already_AddRefed<DictionaryCacheEntry> AddEntry(
nsIURI* aURI,
bool aNewEntry, DictionaryCacheEntry* aDictEntry);
static void RemoveDictionaryOMT(
const nsACString& aKey);
// remove the entire origin (should be empty!)
static void RemoveOriginFor(
const nsACString& aKey);
// Remove a dictionary if it exists for the key given
static void RemoveDictionary(
const nsACString& aKey);
// Remove an origin for the origin given
void RemoveOrigin(
const nsACString& aOrigin);
nsresult RemoveEntry(nsIURI* aURI,
const nsACString& aKey);
static void RemoveDictionariesForOrigin(nsIURI* aURI);
static void RemoveAllDictionaries();
// Clears all ports at host
void Clear();
// Corrupt the hash of a dictionary entry for testing
void CorruptHashForTesting(
const nsACString& aURI);
// Clear the dictionary data while keeping the entry (for testing)
void ClearDictionaryDataForTesting(
const nsACString& aURI);
// return an entry
void GetDictionaryFor(
nsIURI* aURI, ExtContentPolicyType aType, nsHttpChannel* aChan,
void (*aSuspend)(nsHttpChannel*),
const std::function<nsresult(
bool, DictionaryCacheEntry*)>& aCallback);
size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
const {
// XXX
return mallocSizeOf(this);
}
private:
void RemoveOriginForInternal(
const nsACString& aKey);
static StaticRefPtr<nsICacheStorage> sCacheStorage;
static Atomic<
bool, Relaxed> sShutdown;
// In-memory cache of dictionary entries. HashMap, keyed by origin, of
// Linked list (LRU order) of valid dictionaries for the origin.
// We keep empty entries in there to avoid hitting the disk cache to find out
// if there are dictionaries for an origin.
// Static assertions fire if we try to have a LinkedList directly in an
// nsTHashMap
nsTHashMap<nsCStringHashKey, RefPtr<DictionaryOrigin>> mDictionaryCache;
};
}
// namespace net
}
// namespace mozilla
#endif // mozilla_net_Dictionary_h