Bug 1837267 - introduce XPCOM interface nsIDataStorage for DataStorage r=jschanck,necko-reviewers,kershaw

Differential Revision: https://phabricator.services.mozilla.com/D180267
This commit is contained in:
Dana Keeler 2023-06-15 20:24:07 +00:00
parent 4d8a23d28f
commit 2edebb46f1
16 changed files with 694 additions and 590 deletions

View File

@ -201,7 +201,7 @@ void AltSvcMapping::ProcessHeader(
}
}
AltSvcMapping::AltSvcMapping(DataStorage* storage, int32_t epoch,
AltSvcMapping::AltSvcMapping(nsIDataStorage* storage, int32_t epoch,
const nsACString& originScheme,
const nsACString& originHost, int32_t originPort,
const nsACString& username, bool privateBrowsing,
@ -294,8 +294,9 @@ int32_t AltSvcMapping::TTL() { return mExpiresAt - NowInSeconds(); }
void AltSvcMapping::SyncString(const nsCString& str) {
MOZ_ASSERT(NS_IsMainThread());
mStorage->Put(HashKey(), str,
mPrivate ? DataStorage_Private : DataStorage_Persistent);
(void)mStorage->Put(HashKey(), str,
mPrivate ? nsIDataStorage::DataType::Private
: nsIDataStorage::DataType::Persistent);
}
void AltSvcMapping::Sync() {
@ -316,8 +317,9 @@ void AltSvcMapping::Sync() {
return;
}
mStorage->Put(HashKey(), value,
mPrivate ? DataStorage_Private : DataStorage_Persistent);
(void)mStorage->Put(HashKey(), value,
mPrivate ? nsIDataStorage::DataType::Private
: nsIDataStorage::DataType::Persistent);
}
void AltSvcMapping::SetValidated(bool val) {
@ -403,7 +405,7 @@ void AltSvcMapping::Serialize(nsCString& out) {
// Add code to serialize new members here!
}
AltSvcMapping::AltSvcMapping(DataStorage* storage, int32_t epoch,
AltSvcMapping::AltSvcMapping(nsIDataStorage* storage, int32_t epoch,
const nsCString& str)
: mStorage(storage), mStorageEpoch(epoch) {
mValidated = false;
@ -869,20 +871,22 @@ void AltSvcCache::EnsureStorageInited() {
auto initTask = [&]() {
MOZ_ASSERT(NS_IsMainThread());
// DataStorage gives synchronous access to a memory based hash table
// nsIDataStorage gives synchronous access to a memory based hash table
// that is backed by disk where those writes are done asynchronously
// on another thread
mStorage = DataStorage::Get(DataStorageClass::AlternateServices);
if (!mStorage) {
nsCOMPtr<nsIDataStorageManager> dataStorageManager(
do_GetService("@mozilla.org/security/datastoragemanager;1"));
if (!dataStorageManager) {
LOG(("AltSvcCache::EnsureStorageInited WARN NO STORAGE MANAGER\n"));
return;
}
nsresult rv = dataStorageManager->Get(
nsIDataStorageManager::AlternateServices, getter_AddRefs(mStorage));
if (NS_FAILED(rv) || !mStorage) {
LOG(("AltSvcCache::EnsureStorageInited WARN NO STORAGE\n"));
return;
}
if (NS_FAILED(mStorage->Init())) {
mStorage = nullptr;
} else {
initialized = true;
}
initialized = true;
mStorageEpoch = NowInSeconds();
};
@ -910,50 +914,70 @@ already_AddRefed<AltSvcMapping> AltSvcCache::LookupMapping(
return nullptr;
}
if (NS_IsMainThread() && !mStorage->IsReady()) {
LOG(("AltSvcCache::LookupMapping %p skip when storage is not ready\n",
this));
return nullptr;
if (NS_IsMainThread()) {
bool isReady;
nsresult rv = mStorage->IsReady(&isReady);
if (NS_FAILED(rv)) {
LOG(("AltSvcCache::LookupMapping %p mStorage->IsReady failed\n", this));
return nullptr;
}
if (!isReady) {
LOG(("AltSvcCache::LookupMapping %p skip when storage is not ready\n",
this));
return nullptr;
}
}
nsCString val(mStorage->Get(
key, privateBrowsing ? DataStorage_Private : DataStorage_Persistent));
if (val.IsEmpty()) {
nsAutoCString val;
nsresult rv =
mStorage->Get(key,
privateBrowsing ? nsIDataStorage::DataType::Private
: nsIDataStorage::DataType::Persistent,
val);
if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
LOG(("AltSvcCache::LookupMapping %p mStorage->Get failed \n", this));
return nullptr;
}
if (rv == NS_ERROR_NOT_AVAILABLE || val.IsEmpty()) {
LOG(("AltSvcCache::LookupMapping %p MISS\n", this));
return nullptr;
}
RefPtr<AltSvcMapping> rv = new AltSvcMapping(mStorage, mStorageEpoch, val);
if (!rv->Validated() && (rv->StorageEpoch() != mStorageEpoch)) {
RefPtr<AltSvcMapping> mapping =
new AltSvcMapping(mStorage, mStorageEpoch, val);
if (!mapping->Validated() && (mapping->StorageEpoch() != mStorageEpoch)) {
// this was an in progress validation abandoned in a different session
// rare edge case will not detect session change - that's ok as only impact
// will be loss of alt-svc to this origin for this session.
LOG(("AltSvcCache::LookupMapping %p invalid hit - MISS\n", this));
mStorage->Remove(
key, rv->Private() ? DataStorage_Private : DataStorage_Persistent);
(void)mStorage->Remove(key, mapping->Private()
? nsIDataStorage::DataType::Private
: nsIDataStorage::DataType::Persistent);
return nullptr;
}
if (rv->IsHttp3() &&
if (mapping->IsHttp3() &&
(!nsHttpHandler::IsHttp3Enabled() ||
!gHttpHandler->IsHttp3VersionSupported(rv->NPNToken()) ||
gHttpHandler->IsHttp3Excluded(rv->AlternateHost()))) {
!gHttpHandler->IsHttp3VersionSupported(mapping->NPNToken()) ||
gHttpHandler->IsHttp3Excluded(mapping->AlternateHost()))) {
// If Http3 is disabled or the version not supported anymore, remove the
// mapping.
mStorage->Remove(
key, rv->Private() ? DataStorage_Private : DataStorage_Persistent);
(void)mStorage->Remove(key, mapping->Private()
? nsIDataStorage::DataType::Private
: nsIDataStorage::DataType::Persistent);
return nullptr;
}
if (rv->TTL() <= 0) {
if (mapping->TTL() <= 0) {
LOG(("AltSvcCache::LookupMapping %p expired hit - MISS\n", this));
mStorage->Remove(
key, rv->Private() ? DataStorage_Private : DataStorage_Persistent);
(void)mStorage->Remove(key, mapping->Private()
? nsIDataStorage::DataType::Private
: nsIDataStorage::DataType::Persistent);
return nullptr;
}
MOZ_ASSERT(rv->Private() == privateBrowsing);
LOG(("AltSvcCache::LookupMapping %p HIT %p\n", this, rv.get()));
return rv.forget();
MOZ_ASSERT(mapping->Private() == privateBrowsing);
LOG(("AltSvcCache::LookupMapping %p HIT %p\n", this, mapping.get()));
return mapping.forget();
}
// This is only used for testing!
@ -1255,18 +1279,26 @@ void AltSvcCache::ClearHostMapping(nsHttpConnectionInfo* ci) {
void AltSvcCache::ClearAltServiceMappings() {
MOZ_ASSERT(NS_IsMainThread());
if (mStorage) {
mStorage->Clear();
(void)mStorage->Clear();
}
}
nsresult AltSvcCache::GetAltSvcCacheKeys(nsTArray<nsCString>& value) {
MOZ_ASSERT(NS_IsMainThread());
if (gHttpHandler->AllowAltSvc() && mStorage) {
nsTArray<DataStorageItem> items;
mStorage->GetAll(&items);
nsTArray<RefPtr<nsIDataStorageItem>> items;
nsresult rv = mStorage->GetAll(items);
if (NS_FAILED(rv)) {
return rv;
}
for (const auto& item : items) {
value.AppendElement(item.key);
nsAutoCString key;
rv = item->GetKey(key);
if (NS_FAILED(rv)) {
return rv;
}
value.AppendElement(key);
}
}
return NS_OK;

View File

@ -23,9 +23,9 @@ https://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-06
#ifndef mozilla_net_AlternateServices_h
#define mozilla_net_AlternateServices_h
#include "mozilla/DataStorage.h"
#include "nsRefPtrHashtable.h"
#include "nsString.h"
#include "nsIDataStorage.h"
#include "nsIInterfaceRequestor.h"
#include "nsIStreamListener.h"
#include "nsISpeculativeConnect.h"
@ -47,7 +47,7 @@ class AltSvcMapping {
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AltSvcMapping)
private: // ctor from ProcessHeader
AltSvcMapping(DataStorage* storage, int32_t storageEpoch,
AltSvcMapping(nsIDataStorage* storage, int32_t storageEpoch,
const nsACString& originScheme, const nsACString& originHost,
int32_t originPort, const nsACString& username,
bool privateBrowsing, uint32_t expiresAt,
@ -56,7 +56,7 @@ class AltSvcMapping {
const OriginAttributes& originAttributes, bool aIsHttp3);
public:
AltSvcMapping(DataStorage* storage, int32_t storageEpoch,
AltSvcMapping(nsIDataStorage* storage, int32_t storageEpoch,
const nsCString& str);
static void ProcessHeader(
@ -107,7 +107,7 @@ class AltSvcMapping {
private:
virtual ~AltSvcMapping() = default;
void SyncString(const nsCString& str);
RefPtr<DataStorage> mStorage;
nsCOMPtr<nsIDataStorage> mStorage;
int32_t mStorageEpoch;
void Serialize(nsCString& out);
@ -200,7 +200,7 @@ class AltSvcCache {
void ClearHostMapping(const nsACString& host, int32_t port,
const OriginAttributes& originAttributes);
void ClearHostMapping(nsHttpConnectionInfo* ci);
DataStorage* GetStoragePtr() { return mStorage.get(); }
nsIDataStorage* GetStoragePtr() { return mStorage.get(); }
int32_t StorageEpoch() { return mStorageEpoch; }
nsresult GetAltSvcCacheKeys(nsTArray<nsCString>& value);
@ -208,7 +208,7 @@ class AltSvcCache {
void EnsureStorageInited();
already_AddRefed<AltSvcMapping> LookupMapping(const nsCString& key,
bool privateBrowsing);
RefPtr<DataStorage> mStorage;
nsCOMPtr<nsIDataStorage> mStorage;
int32_t mStorageEpoch{0};
};

View File

@ -20,7 +20,6 @@
#include "nsAppDirectoryServiceDefs.h"
#include "nsDirectoryServiceUtils.h"
#include "nsIFileStreams.h"
#include "nsIMemoryReporter.h"
#include "nsIObserverService.h"
#include "nsISafeOutputStream.h"
#include "nsISerialEventTarget.h"
@ -49,38 +48,73 @@ static const int64_t sOneDayInMicroseconds =
namespace mozilla {
class DataStorageMemoryReporter final : public nsIMemoryReporter {
MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
~DataStorageMemoryReporter() = default;
NS_IMPL_ISUPPORTS(DataStorageManager, nsIDataStorageManager)
NS_IMPL_ISUPPORTS(DataStorageItem, nsIDataStorageItem)
NS_IMPL_ISUPPORTS(DataStorage, nsIDataStorage, nsIMemoryReporter, nsIObserver)
public:
NS_DECL_ISUPPORTS
NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
nsISupports* aData, bool aAnonymize) final {
nsTArray<nsString> fileNames;
#define DATA_STORAGE(_) \
fileNames.AppendElement(NS_LITERAL_STRING_FROM_CSTRING(#_ ".txt"));
#include "mozilla/DataStorageList.h"
#undef DATA_STORAGE
for (const auto& file : fileNames) {
RefPtr<DataStorage> ds = DataStorage::GetFromRawFileName(file);
size_t amount = ds->SizeOfIncludingThis(MallocSizeOf);
nsPrintfCString path("explicit/data-storage/%s",
NS_ConvertUTF16toUTF8(file).get());
Unused << aHandleReport->Callback(
""_ns, path, KIND_HEAP, UNITS_BYTES, amount,
"Memory used by PSM data storage cache."_ns, aData);
}
return NS_OK;
NS_IMETHODIMP
DataStorageManager::Get(nsIDataStorageManager::DataStorage aDataStorage,
nsIDataStorage** aResult) {
if (!NS_IsMainThread()) {
return NS_ERROR_NOT_SAME_THREAD;
}
};
nsAutoString filename;
switch (aDataStorage) {
case nsIDataStorageManager::AlternateServices:
if (mAlternateServicesCreated) {
return NS_ERROR_ALREADY_INITIALIZED;
}
mAlternateServicesCreated = true;
filename.Assign(u"AlternateServices.txt"_ns);
break;
case nsIDataStorageManager::ClientAuthRememberList:
if (mClientAuthRememberListCreated) {
return NS_ERROR_ALREADY_INITIALIZED;
}
mClientAuthRememberListCreated = true;
filename.Assign(u"ClientAuthRememberList.txt"_ns);
break;
case nsIDataStorageManager::SiteSecurityServiceState:
if (mSiteSecurityServiceStateCreated) {
return NS_ERROR_ALREADY_INITIALIZED;
}
mSiteSecurityServiceStateCreated = true;
filename.Assign(u"SiteSecurityServiceState.txt"_ns);
break;
default:
return NS_ERROR_INVALID_ARG;
}
RefPtr<mozilla::DataStorage> dataStorage(new mozilla::DataStorage(filename));
nsresult rv = dataStorage->Init();
if (NS_FAILED(rv)) {
return rv;
}
nsCOMPtr<nsIMemoryReporter> memoryReporter(dataStorage.get());
RegisterStrongMemoryReporter(memoryReporter);
*aResult = dataStorage.forget().take();
return NS_OK;
}
NS_IMPL_ISUPPORTS(DataStorageMemoryReporter, nsIMemoryReporter)
NS_IMETHODIMP
DataStorageItem::GetKey(nsACString& aKey) {
aKey.Assign(key);
return NS_OK;
}
NS_IMPL_ISUPPORTS(DataStorage, nsIObserver)
NS_IMETHODIMP
DataStorageItem::GetValue(nsACString& aValue) {
aValue.Assign(value);
return NS_OK;
}
mozilla::StaticAutoPtr<DataStorage::DataStorages> DataStorage::sDataStorages;
NS_IMETHODIMP
DataStorageItem::GetType(nsIDataStorage::DataType* aType) {
if (!aType) {
return NS_ERROR_INVALID_ARG;
}
*aType = type;
return NS_OK;
}
DataStorage::DataStorage(const nsString& aFilename)
: mMutex("DataStorage::mMutex"),
@ -92,42 +126,6 @@ DataStorage::DataStorage(const nsString& aFilename)
mReady(false),
mFilename(aFilename) {}
// static
already_AddRefed<DataStorage> DataStorage::Get(DataStorageClass aFilename) {
switch (aFilename) {
#define DATA_STORAGE(_) \
case DataStorageClass::_: \
return GetFromRawFileName(NS_LITERAL_STRING_FROM_CSTRING(#_ ".txt"));
#include "mozilla/DataStorageList.h"
#undef DATA_STORAGE
default:
MOZ_ASSERT_UNREACHABLE("Invalid DataStorage type passed?");
return nullptr;
}
}
// static
already_AddRefed<DataStorage> DataStorage::GetFromRawFileName(
const nsString& aFilename) {
MOZ_ASSERT(NS_IsMainThread());
if (!sDataStorages) {
sDataStorages = new DataStorages();
ClearOnShutdown(&sDataStorages);
}
return do_AddRef(sDataStorages->LookupOrInsertWith(
aFilename, [&] { return RefPtr{new DataStorage(aFilename)}; }));
}
size_t DataStorage::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) {
MutexAutoLock lock(mMutex);
size_t sizeOfExcludingThis =
mPersistentDataTable.ShallowSizeOfExcludingThis(aMallocSizeOf) +
mTemporaryDataTable.ShallowSizeOfExcludingThis(aMallocSizeOf) +
mPrivateDataTable.ShallowSizeOfExcludingThis(aMallocSizeOf) +
mFilename.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
return aMallocSizeOf(this) + sizeOfExcludingThis;
}
nsresult DataStorage::Init() {
// Don't access the observer service or preferences off the main thread.
if (!NS_IsMainThread()) {
@ -157,15 +155,6 @@ nsresult DataStorage::Init() {
mInitCalled = true;
static bool memoryReporterRegistered = false;
if (!memoryReporterRegistered) {
nsresult rv = RegisterStrongMemoryReporter(new DataStorageMemoryReporter());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
memoryReporterRegistered = true;
}
nsCOMPtr<nsISerialEventTarget> target;
nsresult rv = NS_CreateBackgroundTaskQueue(
"DataStorage::mBackgroundTaskQueue", getter_AddRefs(target));
@ -316,8 +305,8 @@ DataStorage::Reader::Run() {
// The value must not contain '\n' and must have a length no more than 1024.
// The length limits are to prevent unbounded memory and disk usage.
/* static */
nsresult DataStorage::ValidateKeyAndValue(const nsCString& aKey,
const nsCString& aValue) {
nsresult DataStorage::ValidateKeyAndValue(const nsACString& aKey,
const nsACString& aValue) {
if (aKey.IsEmpty()) {
return NS_ERROR_INVALID_ARG;
}
@ -439,9 +428,14 @@ nsresult DataStorage::AsyncReadData(const MutexAutoLock& /*aProofOfLock*/) {
return NS_OK;
}
bool DataStorage::IsReady() {
NS_IMETHODIMP
DataStorage::IsReady(bool* aReady) {
if (!aReady) {
return NS_ERROR_INVALID_ARG;
}
MonitorAutoLock readyLock(mReadyMonitor);
return mReady;
*aReady = mReady;
return NS_OK;
}
void DataStorage::WaitForReady() {
@ -454,14 +448,16 @@ void DataStorage::WaitForReady() {
MOZ_ASSERT(mReady);
}
nsCString DataStorage::Get(const nsCString& aKey, DataStorageType aType) {
NS_IMETHODIMP
DataStorage::Get(const nsACString& aKey, nsIDataStorage::DataType aType,
nsACString& aValue) {
WaitForReady();
MutexAutoLock lock(mMutex);
Entry entry;
bool foundValue = GetInternal(aKey, &entry, aType, lock);
if (!foundValue) {
return ""_ns;
return NS_ERROR_NOT_AVAILABLE;
}
// If we're here, we found a value. Maybe update its score.
@ -469,11 +465,12 @@ nsCString DataStorage::Get(const nsCString& aKey, DataStorageType aType) {
PutInternal(aKey, entry, aType, lock);
}
return entry.mValue;
aValue.Assign(entry.mValue);
return NS_OK;
}
bool DataStorage::GetInternal(const nsCString& aKey, Entry* aEntry,
DataStorageType aType,
bool DataStorage::GetInternal(const nsACString& aKey, Entry* aEntry,
nsIDataStorage::DataType aType,
const MutexAutoLock& aProofOfLock) {
DataStorageTable& table = GetTableForType(aType, aProofOfLock);
bool foundValue = table.Get(aKey, aEntry);
@ -481,40 +478,41 @@ bool DataStorage::GetInternal(const nsCString& aKey, Entry* aEntry,
}
DataStorage::DataStorageTable& DataStorage::GetTableForType(
DataStorageType aType, const MutexAutoLock& /*aProofOfLock*/) {
nsIDataStorage::DataType aType, const MutexAutoLock& /*aProofOfLock*/) {
switch (aType) {
case DataStorage_Persistent:
case nsIDataStorage::DataType::Persistent:
return mPersistentDataTable;
case DataStorage_Temporary:
case nsIDataStorage::DataType::Temporary:
return mTemporaryDataTable;
case DataStorage_Private:
case nsIDataStorage::DataType::Private:
return mPrivateDataTable;
}
MOZ_CRASH("given bad DataStorage storage type");
}
void DataStorage::ReadAllFromTable(DataStorageType aType,
nsTArray<DataStorageItem>* aItems,
void DataStorage::ReadAllFromTable(nsIDataStorage::DataType aType,
nsTArray<RefPtr<nsIDataStorageItem>>& aItems,
const MutexAutoLock& aProofOfLock) {
for (auto iter = GetTableForType(aType, aProofOfLock).Iter(); !iter.Done();
iter.Next()) {
DataStorageItem* item = aItems->AppendElement();
item->key = iter.Key();
item->value = iter.Data().mValue;
item->type = aType;
nsCOMPtr<nsIDataStorageItem> item(
new DataStorageItem(iter.Key(), iter.Data().mValue, aType));
aItems.AppendElement(item);
}
}
void DataStorage::GetAll(nsTArray<DataStorageItem>* aItems) {
NS_IMETHODIMP
DataStorage::GetAll(nsTArray<RefPtr<nsIDataStorageItem>>& aItems) {
WaitForReady();
MutexAutoLock lock(mMutex);
aItems->SetCapacity(mPersistentDataTable.Count() +
mTemporaryDataTable.Count() + mPrivateDataTable.Count());
ReadAllFromTable(DataStorage_Persistent, aItems, lock);
ReadAllFromTable(DataStorage_Temporary, aItems, lock);
ReadAllFromTable(DataStorage_Private, aItems, lock);
aItems.SetCapacity(mPersistentDataTable.Count() +
mTemporaryDataTable.Count() + mPrivateDataTable.Count());
ReadAllFromTable(nsIDataStorage::DataType::Persistent, aItems, lock);
ReadAllFromTable(nsIDataStorage::DataType::Temporary, aItems, lock);
ReadAllFromTable(nsIDataStorage::DataType::Private, aItems, lock);
return NS_OK;
}
// Limit the number of entries per table. This is to prevent unbounded
@ -524,7 +522,7 @@ void DataStorage::GetAll(nsTArray<DataStorageItem>* aItems) {
// (this is the same as saying evict the entry with the lowest score,
// except for when there are multiple entries with the lowest score,
// in which case one of them is evicted - which one is not specified).
void DataStorage::MaybeEvictOneEntry(DataStorageType aType,
void DataStorage::MaybeEvictOneEntry(nsIDataStorage::DataType aType,
const MutexAutoLock& aProofOfLock) {
DataStorageTable& table = GetTableForType(aType, aProofOfLock);
if (table.Count() >= sMaxDataEntries) {
@ -552,8 +550,9 @@ void DataStorage::MaybeEvictOneEntry(DataStorageType aType,
}
}
nsresult DataStorage::Put(const nsCString& aKey, const nsCString& aValue,
DataStorageType aType) {
NS_IMETHODIMP
DataStorage::Put(const nsACString& aKey, const nsACString& aValue,
nsIDataStorage::DataType aType) {
WaitForReady();
MutexAutoLock lock(mMutex);
@ -574,14 +573,14 @@ nsresult DataStorage::Put(const nsCString& aKey, const nsCString& aValue,
return PutInternal(aKey, entry, aType, lock);
}
nsresult DataStorage::PutInternal(const nsCString& aKey, Entry& aEntry,
DataStorageType aType,
nsresult DataStorage::PutInternal(const nsACString& aKey, Entry& aEntry,
nsIDataStorage::DataType aType,
const MutexAutoLock& aProofOfLock) {
mMutex.AssertCurrentThreadOwns();
DataStorageTable& table = GetTableForType(aType, aProofOfLock);
table.InsertOrUpdate(aKey, aEntry);
if (aType == DataStorage_Persistent) {
if (aType == nsIDataStorage::DataType::Persistent) {
mPendingWrite = true;
ArmTimer(aProofOfLock);
}
@ -589,17 +588,19 @@ nsresult DataStorage::PutInternal(const nsCString& aKey, Entry& aEntry,
return NS_OK;
}
void DataStorage::Remove(const nsCString& aKey, DataStorageType aType) {
NS_IMETHODIMP
DataStorage::Remove(const nsACString& aKey, nsIDataStorage::DataType aType) {
WaitForReady();
MutexAutoLock lock(mMutex);
DataStorageTable& table = GetTableForType(aType, lock);
table.Remove(aKey);
if (aType == DataStorage_Persistent) {
if (aType == nsIDataStorage::DataType::Persistent) {
mPendingWrite = true;
ArmTimer(lock);
}
return NS_OK;
}
class DataStorage::Writer final : public Runnable {
@ -715,7 +716,8 @@ nsresult DataStorage::AsyncWriteData(const MutexAutoLock& /*aProofOfLock*/) {
return NS_OK;
}
nsresult DataStorage::Clear() {
NS_IMETHODIMP
DataStorage::Clear() {
WaitForReady();
MutexAutoLock lock(mMutex);
mPersistentDataTable.Clear();
@ -781,6 +783,27 @@ void DataStorage::ShutdownTimer() {
}
}
//------------------------------------------------------------
// DataStorage::nsIMemoryReporter
//------------------------------------------------------------
NS_IMETHODIMP
DataStorage::CollectReports(nsIHandleReportCallback* aHandleReport,
nsISupports* aData, bool aAnonymize) {
MutexAutoLock lock(mMutex);
size_t sizeOfExcludingThis =
mPersistentDataTable.ShallowSizeOfExcludingThis(MallocSizeOf) +
mTemporaryDataTable.ShallowSizeOfExcludingThis(MallocSizeOf) +
mPrivateDataTable.ShallowSizeOfExcludingThis(MallocSizeOf) +
mFilename.SizeOfExcludingThisIfUnshared(MallocSizeOf);
size_t amount = MallocSizeOf(this) + sizeOfExcludingThis;
nsPrintfCString path("explicit/data-storage/%s",
NS_ConvertUTF16toUTF8(mFilename).get());
return aHandleReport->Callback(""_ns, path, KIND_HEAP, UNITS_BYTES, amount,
"Memory used by PSM data storage cache."_ns,
aData);
}
//------------------------------------------------------------
// DataStorage::nsIObserver
//------------------------------------------------------------

View File

@ -14,15 +14,14 @@
#include "mozilla/StaticPtr.h"
#include "nsCOMPtr.h"
#include "nsTHashMap.h"
#include "nsIDataStorage.h"
#include "nsIMemoryReporter.h"
#include "nsIObserver.h"
#include "nsITimer.h"
#include "nsRefPtrHashtable.h"
#include "nsString.h"
class psm_DataStorageTest;
namespace mozilla {
class DataStorageMemoryReporter;
class TaskQueue;
/**
@ -77,78 +76,57 @@ class TaskQueue;
* (the length limits are to prevent unbounded disk and memory usage)
*/
/**
* Data that is DataStorage_Persistent is saved on disk. DataStorage_Temporary
* and DataStorage_Private are not saved. DataStorage_Private is meant to
* only be set and accessed from private contexts. It will be cleared upon
* observing the event "last-pb-context-exited".
*/
enum DataStorageType {
DataStorage_Persistent,
DataStorage_Temporary,
DataStorage_Private
};
struct DataStorageItem final {
nsCString key;
nsCString value;
DataStorageType type;
};
enum class DataStorageClass {
#define DATA_STORAGE(_) _,
#include "mozilla/DataStorageList.h"
#undef DATA_STORAGE
};
class DataStorage : public nsIObserver {
class DataStorageManager final : public nsIDataStorageManager {
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIDATASTORAGEMANAGER
private:
~DataStorageManager() = default;
bool mAlternateServicesCreated = false;
bool mClientAuthRememberListCreated = false;
bool mSiteSecurityServiceStateCreated = false;
};
class DataStorageItem final : public nsIDataStorageItem {
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIDATASTORAGEITEM
DataStorageItem(const nsACString& aKey, const nsACString& aValue,
nsIDataStorage::DataType aType)
: key(aKey), value(aValue), type(aType) {}
private:
~DataStorageItem() = default;
nsAutoCString key;
nsAutoCString value;
nsIDataStorage::DataType type;
};
class DataStorage final : public nsIDataStorage,
public nsIMemoryReporter,
public nsIObserver {
MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIDATASTORAGE
NS_DECL_NSIMEMORYREPORTER
NS_DECL_NSIOBSERVER
// If there is a profile directory, there is or will eventually be a file
// by the name specified by aFilename there.
static already_AddRefed<DataStorage> Get(DataStorageClass aFilename);
explicit DataStorage(const nsString& aFilename);
// Initializes the DataStorage. Must be called before using.
nsresult Init();
// Given a key and a type of data, returns a value. Returns an empty string if
// the key is not present for that type of data. If Get is called before the
// "data-storage-ready" event is observed, it will block. NB: It is not
// currently possible to differentiate between missing data and data that is
// the empty string.
nsCString Get(const nsCString& aKey, DataStorageType aType);
// Give a key, value, and type of data, adds an entry as appropriate.
// Updates existing entries.
nsresult Put(const nsCString& aKey, const nsCString& aValue,
DataStorageType aType);
// Given a key and type of data, removes an entry if present.
void Remove(const nsCString& aKey, DataStorageType aType);
// Removes all entries of all types of data.
nsresult Clear();
// Read all of the data items.
void GetAll(nsTArray<DataStorageItem>* aItems);
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
// Return true if this data storage is ready to be used.
bool IsReady();
private:
~DataStorage() = default;
void ArmTimer(const MutexAutoLock& aProofOfLock);
void ShutdownTimer();
private:
explicit DataStorage(const nsString& aFilename);
virtual ~DataStorage() = default;
static already_AddRefed<DataStorage> GetFromRawFileName(
const nsString& aFilename);
friend class ::psm_DataStorageTest;
friend class mozilla::DataStorageMemoryReporter;
class Writer;
class Reader;
@ -176,23 +154,24 @@ class DataStorage : public nsIObserver {
nsresult AsyncWriteData(const MutexAutoLock& aProofOfLock);
nsresult AsyncReadData(const MutexAutoLock& aProofOfLock);
static nsresult ValidateKeyAndValue(const nsCString& aKey,
const nsCString& aValue);
static nsresult ValidateKeyAndValue(const nsACString& aKey,
const nsACString& aValue);
static void TimerCallback(nsITimer* aTimer, void* aClosure);
void NotifyObservers(const char* aTopic);
bool GetInternal(const nsCString& aKey, Entry* aEntry, DataStorageType aType,
bool GetInternal(const nsACString& aKey, Entry* aEntry,
nsIDataStorage::DataType aType,
const MutexAutoLock& aProofOfLock);
nsresult PutInternal(const nsCString& aKey, Entry& aEntry,
DataStorageType aType,
nsresult PutInternal(const nsACString& aKey, Entry& aEntry,
nsIDataStorage::DataType aType,
const MutexAutoLock& aProofOfLock);
void MaybeEvictOneEntry(DataStorageType aType,
void MaybeEvictOneEntry(nsIDataStorage::DataType aType,
const MutexAutoLock& aProofOfLock);
DataStorageTable& GetTableForType(DataStorageType aType,
DataStorageTable& GetTableForType(nsIDataStorage::DataType aType,
const MutexAutoLock& aProofOfLock);
void ReadAllFromTable(DataStorageType aType,
nsTArray<DataStorageItem>* aItems,
void ReadAllFromTable(nsIDataStorage::DataType aType,
nsTArray<RefPtr<nsIDataStorageItem>>& aItems,
const MutexAutoLock& aProofOfLock);
Mutex mMutex; // This mutex protects access to the following members:

View File

@ -1,18 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
// This is the list of well-known PSM DataStorage classes that Gecko uses.
// These are key value data stores that are backed by a simple text-based
// storage in the profile directory.
//
// Please note that it is crucial for performance reasons for the number of
// these classes to remain low. If you need to add to this list, you may
// need to update the algorithm in DataStorage::SetCachedStorageEntries()
// to something faster.
DATA_STORAGE(AlternateServices)
DATA_STORAGE(ClientAuthRememberList)
DATA_STORAGE(SiteSecurityServiceState)

View File

@ -140,4 +140,10 @@ Classes = [
'type': 'mozilla::psm::CRLiteTimestamp',
'headers': ['/security/certverifier/CRLiteTimestamp.h'],
},
{
'cid': '{71b49926-fd4e-43e2-ab8d-d9b049413c0b}',
'contract_ids': ['@mozilla.org/security/datastoragemanager;1'],
'type': 'mozilla::DataStorageManager',
'headers': ['/security/manager/ssl//DataStorage.h'],
},
]

View File

@ -26,6 +26,7 @@ XPIDL_SOURCES += [
"nsIClientAuthRememberService.idl",
"nsIContentSignatureVerifier.idl",
"nsICryptoHash.idl",
"nsIDataStorage.idl",
"nsINSSComponent.idl",
"nsINSSErrorsService.idl",
"nsINSSVersion.idl",
@ -83,8 +84,6 @@ EXPORTS += [
]
EXPORTS.mozilla += [
"DataStorage.h",
"DataStorageList.h",
"PublicSSL.h",
]

View File

@ -7,12 +7,12 @@
#include "nsClientAuthRemember.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/DataStorage.h"
#include "mozilla/RefPtr.h"
#include "nsCRT.h"
#include "nsINSSComponent.h"
#include "nsPrintfCString.h"
#include "nsNSSComponent.h"
#include "nsIDataStorage.h"
#include "nsIObserverService.h"
#include "nsNetUtil.h"
#include "nsPromiseFlatString.h"
@ -70,21 +70,31 @@ nsresult nsClientAuthRememberService::Init() {
return NS_ERROR_NOT_SAME_THREAD;
}
mClientAuthRememberList =
mozilla::DataStorage::Get(DataStorageClass::ClientAuthRememberList);
nsresult rv = mClientAuthRememberList->Init();
if (NS_WARN_IF(NS_FAILED(rv))) {
nsCOMPtr<nsIDataStorageManager> dataStorageManager(
do_GetService("@mozilla.org/security/datastoragemanager;1"));
if (!dataStorageManager) {
return NS_ERROR_FAILURE;
}
nsresult rv =
dataStorageManager->Get(nsIDataStorageManager::ClientAuthRememberList,
getter_AddRefs(mClientAuthRememberList));
if (NS_FAILED(rv)) {
return rv;
}
if (!mClientAuthRememberList) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
NS_IMETHODIMP
nsClientAuthRememberService::ForgetRememberedDecision(const nsACString& key) {
mClientAuthRememberList->Remove(PromiseFlatCString(key),
mozilla::DataStorage_Persistent);
nsresult rv = mClientAuthRememberList->Remove(
PromiseFlatCString(key), nsIDataStorage::DataType::Persistent);
if (NS_FAILED(rv)) {
return rv;
}
nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(NS_NSSCOMPONENT_CID));
if (!nssComponent) {
return NS_ERROR_NOT_AVAILABLE;
@ -95,13 +105,31 @@ nsClientAuthRememberService::ForgetRememberedDecision(const nsACString& key) {
NS_IMETHODIMP
nsClientAuthRememberService::GetDecisions(
nsTArray<RefPtr<nsIClientAuthRememberRecord>>& results) {
nsTArray<DataStorageItem> decisions;
mClientAuthRememberList->GetAll(&decisions);
nsTArray<RefPtr<nsIDataStorageItem>> decisions;
nsresult rv = mClientAuthRememberList->GetAll(decisions);
if (NS_FAILED(rv)) {
return rv;
}
for (const DataStorageItem& decision : decisions) {
if (decision.type == DataStorageType::DataStorage_Persistent) {
for (const auto& decision : decisions) {
nsIDataStorage::DataType type;
rv = decision->GetType(&type);
if (NS_FAILED(rv)) {
return rv;
}
if (type == nsIDataStorage::DataType::Persistent) {
nsAutoCString key;
rv = decision->GetKey(key);
if (NS_FAILED(rv)) {
return rv;
}
nsAutoCString value;
rv = decision->GetValue(value);
if (NS_FAILED(rv)) {
return rv;
}
RefPtr<nsIClientAuthRememberRecord> tmp =
new nsClientAuthRemember(decision.key, decision.value);
new nsClientAuthRemember(key, value);
results.AppendElement(tmp);
}
@ -112,7 +140,10 @@ nsClientAuthRememberService::GetDecisions(
NS_IMETHODIMP
nsClientAuthRememberService::ClearRememberedDecisions() {
mClientAuthRememberList->Clear();
nsresult rv = mClientAuthRememberList->Clear();
if (NS_FAILED(rv)) {
return rv;
}
nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(NS_NSSCOMPONENT_CID));
if (!nssComponent) {
return NS_ERROR_NOT_AVAILABLE;
@ -128,19 +159,40 @@ nsClientAuthRememberService::DeleteDecisionsByHost(
if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
return NS_ERROR_INVALID_ARG;
}
DataStorageType storageType = GetDataStorageType(attrs);
nsIDataStorage::DataType storageType = GetDataStorageType(attrs);
nsTArray<DataStorageItem> decisions;
mClientAuthRememberList->GetAll(&decisions);
nsTArray<RefPtr<nsIDataStorageItem>> decisions;
nsresult rv = mClientAuthRememberList->GetAll(decisions);
if (NS_FAILED(rv)) {
return rv;
}
for (const DataStorageItem& decision : decisions) {
if (decision.type == storageType) {
for (const auto& decision : decisions) {
nsIDataStorage::DataType type;
nsresult rv = decision->GetType(&type);
if (NS_FAILED(rv)) {
return rv;
}
if (type == storageType) {
nsAutoCString key;
rv = decision->GetKey(key);
if (NS_FAILED(rv)) {
return rv;
}
nsAutoCString value;
rv = decision->GetValue(value);
if (NS_FAILED(rv)) {
return rv;
}
RefPtr<nsIClientAuthRememberRecord> tmp =
new nsClientAuthRemember(decision.key, decision.value);
new nsClientAuthRemember(key, value);
nsAutoCString asciiHost;
tmp->GetAsciiHost(asciiHost);
if (asciiHost.Equals(aHostName)) {
mClientAuthRememberList->Remove(decision.key, decision.type);
rv = mClientAuthRememberList->Remove(key, type);
if (NS_FAILED(rv)) {
return rv;
}
}
}
}
@ -240,25 +292,44 @@ void nsClientAuthRememberService::Migrate() {
if (migrated) {
return;
}
nsTArray<DataStorageItem> decisions;
mClientAuthRememberList->GetAll(&decisions);
migrated = true;
nsTArray<RefPtr<nsIDataStorageItem>> decisions;
nsresult rv = mClientAuthRememberList->GetAll(decisions);
if (NS_FAILED(rv)) {
return;
}
for (const auto& decision : decisions) {
if (decision.type != DataStorage_Persistent) {
nsIDataStorage::DataType type;
if (NS_FAILED(decision->GetType(&type))) {
continue;
}
RefPtr<nsClientAuthRemember> entry(
new nsClientAuthRemember(decision.key, decision.value));
if (type != nsIDataStorage::DataType::Persistent) {
continue;
}
nsAutoCString key;
if (NS_FAILED(decision->GetKey(key))) {
continue;
}
nsAutoCString value;
if (NS_FAILED(decision->GetValue(value))) {
continue;
}
RefPtr<nsClientAuthRemember> entry(new nsClientAuthRemember(key, value));
nsAutoCString newKey;
if (NS_FAILED(entry->GetEntryKey(newKey))) {
continue;
}
if (newKey != decision.key) {
mClientAuthRememberList->Remove(decision.key, DataStorage_Persistent);
(void)mClientAuthRememberList->Put(newKey, decision.value,
DataStorage_Persistent);
if (newKey != key) {
if (NS_FAILED(mClientAuthRememberList->Remove(
key, nsIDataStorage::DataType::Persistent))) {
continue;
}
if (NS_FAILED(mClientAuthRememberList->Put(
newKey, value, nsIDataStorage::DataType::Persistent))) {
continue;
}
}
}
migrated = true;
}
NS_IMETHODIMP
@ -285,10 +356,14 @@ nsClientAuthRememberService::HasRememberedDecision(
if (NS_FAILED(rv)) {
return rv;
}
DataStorageType storageType = GetDataStorageType(aOriginAttributes);
nsIDataStorage::DataType storageType = GetDataStorageType(aOriginAttributes);
nsCString listEntry = mClientAuthRememberList->Get(entryKey, storageType);
if (!listEntry.IsEmpty()) {
nsAutoCString listEntry;
rv = mClientAuthRememberList->Get(entryKey, storageType, listEntry);
if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
return rv;
}
if (NS_SUCCEEDED(rv) && !listEntry.IsEmpty()) {
if (!listEntry.Equals(nsClientAuthRemember::SentinelValue)) {
aCertDBKey = listEntry;
}
@ -331,7 +406,7 @@ nsresult nsClientAuthRememberService::AddEntryToList(
if (NS_FAILED(rv)) {
return rv;
}
DataStorageType storageType = GetDataStorageType(aOriginAttributes);
nsIDataStorage::DataType storageType = GetDataStorageType(aOriginAttributes);
nsCString tmpDbKey(aDBKey);
rv = mClientAuthRememberList->Put(entryKey, tmpDbKey, storageType);
@ -354,10 +429,10 @@ bool nsClientAuthRememberService::IsPrivateBrowsingKey(
return OriginAttributes::IsPrivateBrowsing(suffix);
}
DataStorageType nsClientAuthRememberService::GetDataStorageType(
nsIDataStorage::DataType nsClientAuthRememberService::GetDataStorageType(
const OriginAttributes& aOriginAttributes) {
if (aOriginAttributes.mPrivateBrowsingId > 0) {
return DataStorage_Private;
return nsIDataStorage::DataType::Private;
}
return DataStorage_Persistent;
return nsIDataStorage::DataType::Persistent;
}

View File

@ -10,10 +10,10 @@
#include <utility>
#include "mozilla/Attributes.h"
#include "mozilla/DataStorage.h"
#include "mozilla/HashFunctions.h"
#include "mozilla/ReentrantMonitor.h"
#include "nsIClientAuthRememberService.h"
#include "nsIDataStorage.h"
#include "nsIObserver.h"
#include "nsNSSCertificate.h"
#include "nsString.h"
@ -83,10 +83,10 @@ class nsClientAuthRememberService final : public nsIClientAuthRememberService {
protected:
~nsClientAuthRememberService() = default;
static mozilla::DataStorageType GetDataStorageType(
static nsIDataStorage::DataType GetDataStorageType(
const OriginAttributes& aOriginAttributes);
RefPtr<mozilla::DataStorage> mClientAuthRememberList;
nsCOMPtr<nsIDataStorage> mClientAuthRememberList;
nsresult AddEntryToList(const nsACString& aHost,
const OriginAttributes& aOriginAttributes,

View File

@ -0,0 +1,64 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* 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/. */
#include "nsISupports.idl"
interface nsIDataStorage;
interface nsIDataStorageItem;
[scriptable, uuid(71b49926-fd4e-43e2-ab8d-d9b049413c0b)]
interface nsIDataStorageManager : nsISupports {
cenum DataStorage : 8 {
AlternateServices,
ClientAuthRememberList,
SiteSecurityServiceState,
};
nsIDataStorage get(in nsIDataStorageManager_DataStorage dataStorage);
};
[scriptable, uuid(fcbb5ec4-7134-4069-91c6-9378eff51e03)]
interface nsIDataStorage : nsISupports {
/**
* Data that is Persistent is saved on disk. Temporary and Private are not
* saved. Private is meant to only be set and accessed from private contexts.
* It will be cleared upon observing the event "last-pb-context-exited".
*/
cenum DataType : 8 {
Persistent,
Temporary,
Private,
};
// Given a key and a type of data, returns a value. Returns
// NS_ERROR_NOT_AVAILABLE if the key is not present for that type of data. If
// Get is called before the "data-storage-ready" event is observed, it will
// block.
ACString get(in ACString key, in nsIDataStorage_DataType type);
// Give a key, value, and type of data, adds an entry as appropriate.
// Updates existing entries.
void put(in ACString key, in ACString value, in nsIDataStorage_DataType type);
// Given a key and type of data, removes an entry if present.
void remove(in ACString key, in nsIDataStorage_DataType type);
// Removes all entries of all types of data.
void clear();
// Returns true if this data storage is ready to be used.
bool isReady();
// Read all of the data items.
Array<nsIDataStorageItem> getAll();
};
[scriptable, uuid(4501f984-0e3a-4199-a67e-7753649e93f1)]
interface nsIDataStorageItem : nsISupports {
readonly attribute ACString key;
readonly attribute ACString value;
readonly attribute nsIDataStorage_DataType type;
};

View File

@ -13,7 +13,6 @@
#include "mozilla/Tokenizer.h"
#include "mozilla/dom/PContent.h"
#include "mozilla/dom/ToJSValue.h"
#include "nsArrayEnumerator.h"
#include "nsCOMArray.h"
#include "nsIScriptSecurityManager.h"
#include "nsISocketProvider.h"
@ -190,12 +189,20 @@ nsresult nsSiteSecurityService::Init() {
mozilla::Preferences::GetInt("test.currentTimeOffsetSeconds", 0);
mozilla::Preferences::AddStrongObserver(this,
"test.currentTimeOffsetSeconds");
mSiteStateStorage =
mozilla::DataStorage::Get(DataStorageClass::SiteSecurityServiceState);
nsresult rv = mSiteStateStorage->Init();
if (NS_WARN_IF(NS_FAILED(rv))) {
nsCOMPtr<nsIDataStorageManager> dataStorageManager(
do_GetService("@mozilla.org/security/datastoragemanager;1"));
if (!dataStorageManager) {
return NS_ERROR_FAILURE;
}
nsresult rv =
dataStorageManager->Get(nsIDataStorageManager::SiteSecurityServiceState,
getter_AddRefs(mSiteStateStorage));
if (NS_FAILED(rv)) {
return rv;
}
if (!mSiteStateStorage) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
@ -315,21 +322,33 @@ nsresult nsSiteSecurityService::SetHSTSState(
siteState.ToString(stateString);
SSSLOG(("SSS: setting state for %s", hostname.get()));
bool isPrivate = aOriginAttributes.mPrivateBrowsingId > 0;
mozilla::DataStorageType storageType = isPrivate
? mozilla::DataStorage_Private
: mozilla::DataStorage_Persistent;
nsIDataStorage::DataType storageType =
isPrivate ? nsIDataStorage::DataType::Private
: nsIDataStorage::DataType::Persistent;
SSSLOG(("SSS: storing HSTS site entry for %s", hostname.get()));
nsCString value;
GetWithMigration(hostname, aOriginAttributes, storageType, value);
nsAutoCString value;
nsresult rv =
GetWithMigration(hostname, aOriginAttributes, storageType, value);
// If this fails for a reason other than nothing by that key exists,
// propagate the failure.
if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
return rv;
}
// This is an entirely new entry.
if (rv == NS_ERROR_NOT_AVAILABLE) {
nsAutoCString storageKey;
GetStorageKey(hostname, aOriginAttributes, storageKey);
return mSiteStateStorage->Put(storageKey, stateString, storageType);
}
// Otherwise, only update the backing storage if the currently-stored state
// is different. In the case of expiration time, "different" means "is
// different by more than a day".
SiteHSTSState curSiteState(hostname, aOriginAttributes, value);
// Only update the backing storage if the currently-stored state is
// different. In the case of expiration time, "different" means "is different
// by more than a day".
if (curSiteState.mHSTSState != siteState.mHSTSState ||
curSiteState.mHSTSIncludeSubdomains != siteState.mHSTSIncludeSubdomains ||
AbsoluteDifference(curSiteState.mHSTSExpireTime,
siteState.mHSTSExpireTime) > sOneDayInMilliseconds) {
nsresult rv =
rv =
PutWithMigration(hostname, aOriginAttributes, storageType, stateString);
if (NS_FAILED(rv)) {
return rv;
@ -346,9 +365,9 @@ nsresult nsSiteSecurityService::SetHSTSState(
nsresult nsSiteSecurityService::MarkHostAsNotHSTS(
const nsAutoCString& aHost, const OriginAttributes& aOriginAttributes) {
bool isPrivate = aOriginAttributes.mPrivateBrowsingId > 0;
mozilla::DataStorageType storageType = isPrivate
? mozilla::DataStorage_Private
: mozilla::DataStorage_Persistent;
nsIDataStorage::DataType storageType =
isPrivate ? nsIDataStorage::DataType::Private
: nsIDataStorage::DataType::Persistent;
if (GetPreloadStatus(aHost)) {
SSSLOG(("SSS: storing knockout entry for %s", aHost.get()));
SiteHSTSState siteState(aHost, aOriginAttributes, 0,
@ -419,17 +438,31 @@ nsresult nsSiteSecurityService::ResetStateInternal(
return NS_OK;
}
nsTArray<DataStorageItem> items;
mSiteStateStorage->GetAll(&items);
nsTArray<RefPtr<nsIDataStorageItem>> items;
rv = mSiteStateStorage->GetAll(items);
if (NS_FAILED(rv)) {
return rv;
}
for (const auto& item : items) {
static const nsLiteralCString kHPKPKeySuffix = ":HPKP"_ns;
if (StringEndsWith(item.key, kHPKPKeySuffix)) {
mSiteStateStorage->Remove(item.key, DataStorage_Persistent);
nsAutoCString key;
rv = item->GetKey(key);
if (NS_FAILED(rv)) {
return rv;
}
nsAutoCString value;
rv = item->GetValue(value);
if (NS_FAILED(rv)) {
return rv;
}
if (StringEndsWith(key, kHPKPKeySuffix)) {
(void)mSiteStateStorage->Remove(key,
nsIDataStorage::DataType::Persistent);
continue;
}
size_t suffixLength =
StringEndsWith(item.key, kHSTSKeySuffix) ? kHSTSKeySuffix.Length() : 0;
nsCString origin(StringHead(item.key, item.key.Length() - suffixLength));
StringEndsWith(key, kHSTSKeySuffix) ? kHSTSKeySuffix.Length() : 0;
nsCString origin(StringHead(key, key.Length() - suffixLength));
nsAutoCString itemHostname;
OriginAttributes itemOriginAttributes;
if (!itemOriginAttributes.PopulateFromOrigin(origin, itemHostname)) {
@ -460,9 +493,9 @@ nsresult nsSiteSecurityService::ResetStateInternal(
void nsSiteSecurityService::ResetStateForExactDomain(
const nsCString& aHostname, const OriginAttributes& aOriginAttributes) {
bool isPrivate = aOriginAttributes.mPrivateBrowsingId > 0;
mozilla::DataStorageType storageType = isPrivate
? mozilla::DataStorage_Private
: mozilla::DataStorage_Persistent;
nsIDataStorage::DataType storageType =
isPrivate ? nsIDataStorage::DataType::Private
: nsIDataStorage::DataType::Persistent;
RemoveWithMigration(aHostname, aOriginAttributes, storageType);
}
@ -738,48 +771,55 @@ bool nsSiteSecurityService::GetPreloadStatus(const nsACString& aHost,
return found;
}
void nsSiteSecurityService::GetWithMigration(
nsresult nsSiteSecurityService::GetWithMigration(
const nsACString& aHostname, const OriginAttributes& aOriginAttributes,
DataStorageType aDataStorageType, nsCString& aValue) {
nsIDataStorage::DataType aDataStorageType, nsACString& aValue) {
// First see if this entry exists and has already been migrated.
nsAutoCString storageKey;
GetStorageKey(aHostname, aOriginAttributes, storageKey);
aValue = mSiteStateStorage->Get(storageKey, aDataStorageType);
if (!aValue.IsEmpty()) {
return;
nsresult rv = mSiteStateStorage->Get(storageKey, aDataStorageType, aValue);
if (NS_SUCCEEDED(rv)) {
return NS_OK;
}
if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
return rv;
}
// Otherwise, it potentially needs to be migrated, if it's persistent data.
if (aDataStorageType != DataStorage_Persistent) {
return;
if (aDataStorageType != nsIDataStorage::DataType::Persistent) {
return NS_ERROR_NOT_AVAILABLE;
}
nsAutoCString oldStorageKey;
GetOldStorageKey(aHostname, aOriginAttributes, oldStorageKey);
aValue = mSiteStateStorage->Get(oldStorageKey, DataStorage_Persistent);
if (aValue.IsEmpty()) {
return;
rv = mSiteStateStorage->Get(oldStorageKey,
nsIDataStorage::DataType::Persistent, aValue);
if (NS_FAILED(rv)) {
return rv;
}
// If there was a value, remove the old entry, insert a new one with the new
// key, and return the value.
mSiteStateStorage->Remove(oldStorageKey, DataStorage_Persistent);
nsresult rv =
mSiteStateStorage->Put(storageKey, aValue, DataStorage_Persistent);
// DataStorage::Put only fails if the key or value is too long. Since the new
// key is shorter than the old key, and since the old key and value were
// already present in storage, this should never fail.
MOZ_ASSERT(NS_SUCCEEDED(rv));
(void)rv;
rv = mSiteStateStorage->Remove(oldStorageKey,
nsIDataStorage::DataType::Persistent);
if (NS_FAILED(rv)) {
return rv;
}
return mSiteStateStorage->Put(storageKey, aValue,
nsIDataStorage::DataType::Persistent);
}
nsresult nsSiteSecurityService::PutWithMigration(
const nsACString& aHostname, const OriginAttributes& aOriginAttributes,
DataStorageType aDataStorageType, const nsCString& aStateString) {
nsIDataStorage::DataType aDataStorageType, const nsACString& aStateString) {
// Only persistent data needs migrating.
if (aDataStorageType == DataStorage_Persistent) {
if (aDataStorageType == nsIDataStorage::DataType::Persistent) {
// Since the intention is to overwrite the previously-stored data anyway,
// the old entry can be removed.
nsAutoCString oldStorageKey;
GetOldStorageKey(aHostname, aOriginAttributes, oldStorageKey);
mSiteStateStorage->Remove(oldStorageKey, DataStorage_Persistent);
nsresult rv = mSiteStateStorage->Remove(
oldStorageKey, nsIDataStorage::DataType::Persistent);
if (NS_FAILED(rv)) {
return rv;
}
}
nsAutoCString storageKey;
@ -787,19 +827,23 @@ nsresult nsSiteSecurityService::PutWithMigration(
return mSiteStateStorage->Put(storageKey, aStateString, aDataStorageType);
}
void nsSiteSecurityService::RemoveWithMigration(
nsresult nsSiteSecurityService::RemoveWithMigration(
const nsACString& aHostname, const OriginAttributes& aOriginAttributes,
DataStorageType aDataStorageType) {
nsIDataStorage::DataType aDataStorageType) {
// Only persistent data needs migrating.
if (aDataStorageType == DataStorage_Persistent) {
if (aDataStorageType == nsIDataStorage::DataType::Persistent) {
nsAutoCString oldStorageKey;
GetOldStorageKey(aHostname, aOriginAttributes, oldStorageKey);
mSiteStateStorage->Remove(oldStorageKey, DataStorage_Persistent);
nsresult rv = mSiteStateStorage->Remove(
oldStorageKey, nsIDataStorage::DataType::Persistent);
if (NS_FAILED(rv)) {
return rv;
}
}
nsAutoCString storageKey;
GetStorageKey(aHostname, aOriginAttributes, storageKey);
mSiteStateStorage->Remove(storageKey, aDataStorageType);
return mSiteStateStorage->Remove(storageKey, aDataStorageType);
}
// Allows us to determine if we have an HSTS entry for a given host (and, if
@ -808,9 +852,11 @@ void nsSiteSecurityService::RemoveWithMigration(
// the host which we wish to deteming HSTS information on,
// aRequireIncludeSubdomains specifies whether we require includeSubdomains
// to be set on the entry (with the other parameters being as per IsSecureHost).
bool nsSiteSecurityService::HostHasHSTSEntry(
nsresult nsSiteSecurityService::HostHasHSTSEntry(
const nsAutoCString& aHost, bool aRequireIncludeSubdomains,
const OriginAttributes& aOriginAttributes, bool* aResult) {
const OriginAttributes& aOriginAttributes, bool& aHostHasHSTSEntry,
bool* aResult) {
aHostHasHSTSEntry = false;
// First we check for an entry in site security storage. If that entry exists,
// we don't want to check in the preload lists. We only want to use the
// stored value if it is not a knockout entry, however.
@ -818,48 +864,62 @@ bool nsSiteSecurityService::HostHasHSTSEntry(
// on the host, because the knockout entry indicates "we have no information
// regarding the security status of this host".
bool isPrivate = aOriginAttributes.mPrivateBrowsingId > 0;
mozilla::DataStorageType storageType = isPrivate
? mozilla::DataStorage_Private
: mozilla::DataStorage_Persistent;
nsIDataStorage::DataType storageType =
isPrivate ? nsIDataStorage::DataType::Private
: nsIDataStorage::DataType::Persistent;
SSSLOG(("Seeking HSTS entry for %s", aHost.get()));
nsCString value;
GetWithMigration(aHost, aOriginAttributes, storageType, value);
SiteHSTSState siteState(aHost, aOriginAttributes, value);
if (siteState.mHSTSState != SecurityPropertyUnset) {
SSSLOG(("Found HSTS entry for %s", aHost.get()));
bool expired = siteState.IsExpired();
if (!expired) {
SSSLOG(("Entry for %s is not expired", aHost.get()));
if (siteState.mHSTSState == SecurityPropertySet) {
*aResult =
aRequireIncludeSubdomains ? siteState.mHSTSIncludeSubdomains : true;
return true;
nsAutoCString value;
nsresult rv = GetWithMigration(aHost, aOriginAttributes, storageType, value);
// If this fails for a reason other than nothing by that key exists,
// propagate the failure.
if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
return rv;
}
bool checkPreloadList = true;
// If something by that key does exist, decode and process that information.
if (NS_SUCCEEDED(rv)) {
SiteHSTSState siteState(aHost, aOriginAttributes, value);
if (siteState.mHSTSState != SecurityPropertyUnset) {
SSSLOG(("Found HSTS entry for %s", aHost.get()));
bool expired = siteState.IsExpired();
if (!expired) {
SSSLOG(("Entry for %s is not expired", aHost.get()));
if (siteState.mHSTSState == SecurityPropertySet) {
*aResult = aRequireIncludeSubdomains
? siteState.mHSTSIncludeSubdomains
: true;
aHostHasHSTSEntry = true;
return NS_OK;
}
}
}
if (expired) {
SSSLOG(("Entry %s is expired - checking for preload state", aHost.get()));
if (!GetPreloadStatus(aHost)) {
SSSLOG(("No static preload - removing expired entry"));
nsAutoCString storageKey;
GetStorageKey(aHost, aOriginAttributes, storageKey);
mSiteStateStorage->Remove(storageKey, storageType);
if (expired) {
SSSLOG(
("Entry %s is expired - checking for preload state", aHost.get()));
if (!GetPreloadStatus(aHost)) {
SSSLOG(("No static preload - removing expired entry"));
nsAutoCString storageKey;
GetStorageKey(aHost, aOriginAttributes, storageKey);
rv = mSiteStateStorage->Remove(storageKey, storageType);
if (NS_FAILED(rv)) {
return rv;
}
}
}
return NS_OK;
}
return false;
checkPreloadList = false;
}
bool includeSubdomains = false;
// Finally look in the static preload list.
if (siteState.mHSTSState == SecurityPropertyUnset &&
GetPreloadStatus(aHost, &includeSubdomains)) {
if (checkPreloadList && GetPreloadStatus(aHost, &includeSubdomains)) {
SSSLOG(("%s is a preloaded HSTS host", aHost.get()));
*aResult = aRequireIncludeSubdomains ? includeSubdomains : true;
return true;
aHostHasHSTSEntry = true;
}
return false;
return NS_OK;
}
nsresult nsSiteSecurityService::IsSecureHost(
@ -880,7 +940,13 @@ nsresult nsSiteSecurityService::IsSecureHost(
PublicKeyPinningService::CanonicalizeHostname(flatHost.get()));
// First check the exact host.
if (HostHasHSTSEntry(host, false, aOriginAttributes, aResult)) {
bool hostHasHSTSEntry = false;
nsresult rv = HostHasHSTSEntry(host, false, aOriginAttributes,
hostHasHSTSEntry, aResult);
if (NS_FAILED(rv)) {
return rv;
}
if (hostHasHSTSEntry) {
return NS_OK;
}
@ -902,7 +968,13 @@ nsresult nsSiteSecurityService::IsSecureHost(
// that the entry includes subdomains.
nsAutoCString subdomainString(subdomain);
if (HostHasHSTSEntry(subdomainString, true, aOriginAttributes, aResult)) {
hostHasHSTSEntry = false;
rv = HostHasHSTSEntry(subdomainString, true, aOriginAttributes,
hostHasHSTSEntry, aResult);
if (NS_FAILED(rv)) {
return rv;
}
if (hostHasHSTSEntry) {
break;
}

View File

@ -7,9 +7,9 @@
#include "mozilla/BasePrincipal.h"
#include "mozilla/Dafsa.h"
#include "mozilla/DataStorage.h"
#include "mozilla/RefPtr.h"
#include "nsCOMPtr.h"
#include "nsIDataStorage.h"
#include "nsIObserver.h"
#include "nsISiteSecurityService.h"
#include "nsString.h"
@ -126,10 +126,10 @@ class nsSiteSecurityService : public nsISiteSecurityService,
nsISiteSecurityService::ResetStateBy aScope);
void ResetStateForExactDomain(const nsCString& aHostname,
const OriginAttributes& aOriginAttributes);
bool HostHasHSTSEntry(const nsAutoCString& aHost,
bool aRequireIncludeSubdomains,
const OriginAttributes& aOriginAttributes,
bool* aResult);
nsresult HostHasHSTSEntry(const nsAutoCString& aHost,
bool aRequireIncludeSubdomains,
const OriginAttributes& aOriginAttributes,
bool& aHostHasHSTSEntry, bool* aResult);
bool GetPreloadStatus(
const nsACString& aHost,
/*optional out*/ bool* aIncludeSubdomains = nullptr) const;
@ -137,21 +137,21 @@ class nsSiteSecurityService : public nsISiteSecurityService,
const OriginAttributes& aOriginAttributes,
bool* aResult);
void GetWithMigration(const nsACString& aHostname,
const OriginAttributes& aOriginAttributes,
mozilla::DataStorageType aDataStorageType,
nsCString& aValue);
nsresult GetWithMigration(const nsACString& aHostname,
const OriginAttributes& aOriginAttributes,
nsIDataStorage::DataType aDataStorageType,
nsACString& aValue);
nsresult PutWithMigration(const nsACString& aHostname,
const OriginAttributes& aOriginAttributes,
mozilla::DataStorageType aDataStorageType,
const nsCString& aStateString);
void RemoveWithMigration(const nsACString& aHostname,
const OriginAttributes& aOriginAttributes,
mozilla::DataStorageType aDataStorageType);
nsIDataStorage::DataType aDataStorageType,
const nsACString& aStateString);
nsresult RemoveWithMigration(const nsACString& aHostname,
const OriginAttributes& aOriginAttributes,
nsIDataStorage::DataType aDataStorageType);
bool mUsePreloadList;
int64_t mPreloadListTimeOffset;
RefPtr<mozilla::DataStorage> mSiteStateStorage;
nsCOMPtr<nsIDataStorage> mSiteStateStorage;
const mozilla::Dafsa mDafsa;
};

View File

@ -1,201 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include "gtest/gtest.h"
#include "mozilla/DataStorage.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsDirectoryServiceUtils.h"
#include "nsNetUtil.h"
#include "nsPrintfCString.h"
#include "nsStreamUtils.h"
#include "prtime.h"
using namespace mozilla;
class psm_DataStorageTest : public ::testing::Test {
protected:
void SetUp() override {
const ::testing::TestInfo* const testInfo =
::testing::UnitTest::GetInstance()->current_test_info();
NS_ConvertUTF8toUTF16 testName(testInfo->name());
storage = DataStorage::GetFromRawFileName(testName);
storage->Init();
}
RefPtr<DataStorage> storage;
};
constexpr auto testKey = "test"_ns;
constexpr auto testValue = "value"_ns;
constexpr auto privateTestValue = "private"_ns;
TEST_F(psm_DataStorageTest, GetPutRemove) {
// Test Put/Get on Persistent data
EXPECT_EQ(NS_OK, storage->Put(testKey, testValue, DataStorage_Persistent));
// Don't re-use testKey / testValue here, to make sure that this works as
// expected with objects that have the same semantic value but are not
// literally the same object.
nsCString result = storage->Get("test"_ns, DataStorage_Persistent);
EXPECT_STREQ("value", result.get());
// Get on Temporary/Private data with the same key should give nothing
result = storage->Get(testKey, DataStorage_Temporary);
EXPECT_TRUE(result.IsEmpty());
result = storage->Get(testKey, DataStorage_Private);
EXPECT_TRUE(result.IsEmpty());
// Put with Temporary/Private data shouldn't affect Persistent data
constexpr auto temporaryTestValue = "temporary"_ns;
EXPECT_EQ(NS_OK,
storage->Put(testKey, temporaryTestValue, DataStorage_Temporary));
EXPECT_EQ(NS_OK,
storage->Put(testKey, privateTestValue, DataStorage_Private));
result = storage->Get(testKey, DataStorage_Temporary);
EXPECT_STREQ("temporary", result.get());
result = storage->Get(testKey, DataStorage_Private);
EXPECT_STREQ("private", result.get());
result = storage->Get(testKey, DataStorage_Persistent);
EXPECT_STREQ("value", result.get());
// Put of a previously-present key overwrites it (if of the same type)
constexpr auto newValue = "new"_ns;
EXPECT_EQ(NS_OK, storage->Put(testKey, newValue, DataStorage_Persistent));
result = storage->Get(testKey, DataStorage_Persistent);
EXPECT_STREQ("new", result.get());
// Removal should work
storage->Remove(testKey, DataStorage_Temporary);
result = storage->Get(testKey, DataStorage_Temporary);
EXPECT_TRUE(result.IsEmpty());
// But removing one type shouldn't affect the others
result = storage->Get(testKey, DataStorage_Private);
EXPECT_STREQ("private", result.get());
result = storage->Get(testKey, DataStorage_Persistent);
EXPECT_STREQ("new", result.get());
// Test removing the other types as well
storage->Remove(testKey, DataStorage_Private);
result = storage->Get(testKey, DataStorage_Private);
EXPECT_TRUE(result.IsEmpty());
storage->Remove(testKey, DataStorage_Persistent);
result = storage->Get(testKey, DataStorage_Persistent);
EXPECT_TRUE(result.IsEmpty());
}
TEST_F(psm_DataStorageTest, InputValidation) {
// Keys may not have tabs or newlines
EXPECT_EQ(NS_ERROR_INVALID_ARG,
storage->Put("key\thas tab"_ns, testValue, DataStorage_Persistent));
nsCString result = storage->Get("key\thas tab"_ns, DataStorage_Persistent);
EXPECT_TRUE(result.IsEmpty());
EXPECT_EQ(NS_ERROR_INVALID_ARG, storage->Put("key has\nnewline"_ns, testValue,
DataStorage_Persistent));
result = storage->Get("keyhas\nnewline"_ns, DataStorage_Persistent);
EXPECT_TRUE(result.IsEmpty());
// Values may not have newlines
EXPECT_EQ(NS_ERROR_INVALID_ARG, storage->Put(testKey, "value\nhas newline"_ns,
DataStorage_Persistent));
result = storage->Get(testKey, DataStorage_Persistent);
// Values may have tabs
EXPECT_TRUE(result.IsEmpty());
EXPECT_EQ(NS_OK, storage->Put(testKey, "val\thas tab; this is ok"_ns,
DataStorage_Persistent));
result = storage->Get(testKey, DataStorage_Persistent);
EXPECT_STREQ("val\thas tab; this is ok", result.get());
nsCString longKey("a");
for (int i = 0; i < 8; i++) {
longKey.Append(longKey);
}
// A key of length 256 will work
EXPECT_EQ(NS_OK, storage->Put(longKey, testValue, DataStorage_Persistent));
result = storage->Get(longKey, DataStorage_Persistent);
EXPECT_STREQ("value", result.get());
longKey.AppendLiteral("a");
// A key longer than that will not work
EXPECT_EQ(NS_ERROR_INVALID_ARG,
storage->Put(longKey, testValue, DataStorage_Persistent));
result = storage->Get(longKey, DataStorage_Persistent);
EXPECT_TRUE(result.IsEmpty());
nsCString longValue("a");
for (int i = 0; i < 10; i++) {
longValue.Append(longValue);
}
// A value of length 1024 will work
EXPECT_EQ(NS_OK, storage->Put(testKey, longValue, DataStorage_Persistent));
result = storage->Get(testKey, DataStorage_Persistent);
EXPECT_STREQ(longValue.get(), result.get());
longValue.AppendLiteral("a");
// A value longer than that will not work
storage->Remove(testKey, DataStorage_Persistent);
EXPECT_EQ(NS_ERROR_INVALID_ARG,
storage->Put(testKey, longValue, DataStorage_Persistent));
result = storage->Get(testKey, DataStorage_Persistent);
EXPECT_TRUE(result.IsEmpty());
}
TEST_F(psm_DataStorageTest, Eviction) {
// Eviction is on a per-table basis. Tables shouldn't affect each other.
EXPECT_EQ(NS_OK, storage->Put(testKey, testValue, DataStorage_Persistent));
for (int i = 0; i < 1025; i++) {
EXPECT_EQ(NS_OK,
storage->Put(nsPrintfCString("%d", i), nsPrintfCString("%d", i),
DataStorage_Temporary));
nsCString result =
storage->Get(nsPrintfCString("%d", i), DataStorage_Temporary);
EXPECT_STREQ(nsPrintfCString("%d", i).get(), result.get());
}
// We don't know which entry got evicted, but we can count them.
int entries = 0;
for (int i = 0; i < 1025; i++) {
nsCString result =
storage->Get(nsPrintfCString("%d", i), DataStorage_Temporary);
if (!result.IsEmpty()) {
entries++;
}
}
EXPECT_EQ(entries, 1024);
nsCString result = storage->Get(testKey, DataStorage_Persistent);
EXPECT_STREQ("value", result.get());
}
TEST_F(psm_DataStorageTest, ClearPrivateData) {
EXPECT_EQ(NS_OK,
storage->Put(testKey, privateTestValue, DataStorage_Private));
nsCString result = storage->Get(testKey, DataStorage_Private);
EXPECT_STREQ("private", result.get());
storage->Observe(nullptr, "last-pb-context-exited", nullptr);
result = storage->Get(testKey, DataStorage_Private);
EXPECT_TRUE(result.IsEmpty());
}
TEST_F(psm_DataStorageTest, Shutdown) {
EXPECT_EQ(NS_OK, storage->Put(testKey, testValue, DataStorage_Persistent));
nsCString result = storage->Get(testKey, DataStorage_Persistent);
EXPECT_STREQ("value", result.get());
// Get "now" (in days) close to when the data was last touched, so we won't
// get intermittent failures with the day not matching.
int64_t microsecondsPerDay = 24 * 60 * 60 * int64_t(PR_USEC_PER_SEC);
int32_t nowInDays = int32_t(PR_Now() / microsecondsPerDay);
// Simulate shutdown.
storage->Observe(nullptr, "profile-before-change", nullptr);
nsCOMPtr<nsIFile> backingFile;
EXPECT_EQ(NS_OK, NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
getter_AddRefs(backingFile)));
const ::testing::TestInfo* const testInfo =
::testing::UnitTest::GetInstance()->current_test_info();
NS_ConvertUTF8toUTF16 testName(testInfo->name());
EXPECT_EQ(NS_OK, backingFile->Append(testName));
nsCOMPtr<nsIInputStream> fileInputStream;
EXPECT_EQ(NS_OK, NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream),
backingFile));
nsCString data;
EXPECT_EQ(NS_OK, NS_ConsumeStream(fileInputStream, UINT32_MAX, data));
// The data will be of the form 'test\t0\t<days since the epoch>\tvalue'
EXPECT_STREQ(nsPrintfCString("test\t0\t%d\tvalue\n", nowInDays).get(),
data.get());
}

View File

@ -7,7 +7,6 @@
SOURCES += [
"CertDBTest.cpp",
"CoseTest.cpp",
"DataStorageTest.cpp",
"DeserializeCertTest.cpp",
"HMACTest.cpp",
"MD4Test.cpp",

View File

@ -0,0 +1,73 @@
/* 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/. */
"use strict";
do_get_profile(); // must be done before instantiating nsIDataStorageManager
let dataStorageManager = Cc[
"@mozilla.org/security/datastoragemanager;1"
].getService(Ci.nsIDataStorageManager);
let dataStorage = dataStorageManager.get(
Ci.nsIDataStorageManager.ClientAuthRememberList
);
add_task(function test_data_storage() {
// Test putting a simple key/value pair.
dataStorage.put("test", "value", Ci.nsIDataStorage.Persistent);
Assert.equal(dataStorage.get("test", Ci.nsIDataStorage.Persistent), "value");
// Test that getting a value with the same key but of a different type throws.
Assert.throws(
() => dataStorage.get("test", Ci.nsIDataStorage.Temporary),
/NS_ERROR_NOT_AVAILABLE/,
"getting a value of a type that hasn't been set yet should throw"
);
Assert.throws(
() => dataStorage.get("test", Ci.nsIDataStorage.Private),
/NS_ERROR_NOT_AVAILABLE/,
"getting a value of a type that hasn't been set yet should throw"
);
// Put with Temporary/Private data shouldn't affect Persistent data
dataStorage.put("test", "temporary", Ci.nsIDataStorage.Temporary);
Assert.equal(
dataStorage.get("test", Ci.nsIDataStorage.Temporary),
"temporary"
);
dataStorage.put("test", "private", Ci.nsIDataStorage.Private);
Assert.equal(dataStorage.get("test", Ci.nsIDataStorage.Private), "private");
Assert.equal(dataStorage.get("test", Ci.nsIDataStorage.Persistent), "value");
// Put of a previously-present key overwrites it (if of the same type)
dataStorage.put("test", "new", Ci.nsIDataStorage.Persistent);
Assert.equal(dataStorage.get("test", Ci.nsIDataStorage.Persistent), "new");
// Removal should work
dataStorage.remove("test", Ci.nsIDataStorage.Persistent);
Assert.throws(
() => dataStorage.get("test", Ci.nsIDataStorage.Persistent),
/NS_ERROR_NOT_AVAILABLE/,
"getting a removed value should throw"
);
// But removing one type shouldn't affect the others
Assert.equal(
dataStorage.get("test", Ci.nsIDataStorage.Temporary),
"temporary"
);
Assert.equal(dataStorage.get("test", Ci.nsIDataStorage.Private), "private");
// Test removing the other types as well
dataStorage.remove("test", Ci.nsIDataStorage.Temporary);
dataStorage.remove("test", Ci.nsIDataStorage.Private);
Assert.throws(
() => dataStorage.get("test", Ci.nsIDataStorage.Temporary),
/NS_ERROR_NOT_AVAILABLE/,
"getting a removed value should throw"
);
Assert.throws(
() => dataStorage.get("test", Ci.nsIDataStorage.Private),
/NS_ERROR_NOT_AVAILABLE/,
"getting a removed value should throw"
);
});

View File

@ -118,6 +118,7 @@ tags = remote-settings psm
# Requires hard-coded debug-only data
skip-if = !debug
run-sequentially = hardcoded ports
[test_data_storage.js]
[test_db_format_pref_new.js]
# Android always has and always will use the new format, so
# this test doesn't apply.