зеркало из https://github.com/mozilla/gecko-dev.git
741 строка
25 KiB
C++
741 строка
25 KiB
C++
/* -*- 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 "ActorsParentCommon.h"
|
|
|
|
// local includes
|
|
#include "DatabaseFileInfo.h"
|
|
#include "DatabaseFileManager.h"
|
|
#include "IndexedDatabase.h" // for StructuredCloneFile...
|
|
#include "IndexedDatabaseInlines.h"
|
|
#include "IndexedDatabaseManager.h"
|
|
#include "IndexedDBCommon.h"
|
|
#include "ReportInternalError.h"
|
|
|
|
// global includes
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <algorithm>
|
|
#include <numeric>
|
|
#include <type_traits>
|
|
#include "MainThreadUtils.h"
|
|
#include "SafeRefPtr.h"
|
|
#include "js/RootingAPI.h"
|
|
#include "js/StructuredClone.h"
|
|
#include "mozIStorageConnection.h"
|
|
#include "mozIStorageStatement.h"
|
|
#include "mozIStorageValueArray.h"
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/CheckedInt.h"
|
|
#include "mozilla/ClearOnShutdown.h"
|
|
#include "mozilla/JSObjectHolder.h"
|
|
#include "mozilla/NullPrincipal.h"
|
|
#include "mozilla/ProfilerLabels.h"
|
|
#include "mozilla/RefPtr.h"
|
|
#include "mozilla/ResultExtensions.h"
|
|
#include "mozilla/StaticPtr.h"
|
|
#include "mozilla/Telemetry.h"
|
|
#include "mozilla/TelemetryScalarEnums.h"
|
|
#include "mozilla/dom/quota/DecryptingInputStream_impl.h"
|
|
#include "mozilla/dom/quota/QuotaCommon.h"
|
|
#include "mozilla/dom/quota/ResultExtensions.h"
|
|
#include "mozilla/dom/quota/ScopedLogExtraInfo.h"
|
|
#include "mozilla/fallible.h"
|
|
#include "mozilla/ipc/BackgroundParent.h"
|
|
#include "mozilla/mozalloc.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsCharSeparatedTokenizer.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsDebug.h"
|
|
#include "nsError.h"
|
|
#include "nsIInputStream.h"
|
|
#include "nsIPrincipal.h"
|
|
#include "nsIXPConnect.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsString.h"
|
|
#include "nsStringFlags.h"
|
|
#include "nsXULAppAPI.h"
|
|
#include "snappy/snappy.h"
|
|
|
|
class nsIFile;
|
|
|
|
namespace mozilla::dom::indexedDB {
|
|
|
|
static_assert(SNAPPY_VERSION == 0x010108);
|
|
|
|
using mozilla::ipc::IsOnBackgroundThread;
|
|
|
|
const nsLiteralString kJournalDirectoryName = u"journals"_ns;
|
|
|
|
namespace {
|
|
|
|
constexpr StructuredCloneFileBase::FileType ToStructuredCloneFileType(
|
|
const char16_t aTag) {
|
|
switch (aTag) {
|
|
case char16_t('-'):
|
|
return StructuredCloneFileBase::eMutableFile;
|
|
|
|
case char16_t('.'):
|
|
return StructuredCloneFileBase::eStructuredClone;
|
|
|
|
case char16_t('/'):
|
|
return StructuredCloneFileBase::eWasmBytecode;
|
|
|
|
case char16_t('\\'):
|
|
return StructuredCloneFileBase::eWasmCompiled;
|
|
|
|
default:
|
|
return StructuredCloneFileBase::eBlob;
|
|
}
|
|
}
|
|
|
|
int32_t ToInteger(const nsAString& aStr, nsresult* const aRv) {
|
|
return aStr.ToInteger(aRv);
|
|
}
|
|
|
|
Result<StructuredCloneFileParent, nsresult> DeserializeStructuredCloneFile(
|
|
const DatabaseFileManager& aFileManager,
|
|
const nsDependentSubstring& aText) {
|
|
MOZ_ASSERT(!aText.IsEmpty());
|
|
|
|
const StructuredCloneFileBase::FileType type =
|
|
ToStructuredCloneFileType(aText.First());
|
|
|
|
QM_TRY_INSPECT(const auto& id,
|
|
MOZ_TO_RESULT_GET_TYPED(
|
|
int32_t, ToInteger,
|
|
type == StructuredCloneFileBase::eBlob
|
|
? aText
|
|
: static_cast<const nsAString&>(Substring(aText, 1))));
|
|
|
|
SafeRefPtr<DatabaseFileInfo> fileInfo = aFileManager.GetFileInfo(id);
|
|
MOZ_ASSERT(fileInfo);
|
|
// XXX In bug 1432133, for some reasons DatabaseFileInfo object cannot be
|
|
// got. This is just a short-term fix, and we are working on finding the real
|
|
// cause in bug 1519859.
|
|
if (!fileInfo) {
|
|
IDB_WARNING(
|
|
"Corrupt structured clone data detected in IndexedDB. Failing the "
|
|
"database request. Bug 1519859 will address this problem.");
|
|
Telemetry::ScalarAdd(Telemetry::ScalarID::IDB_FAILURE_FILEINFO_ERROR, 1);
|
|
|
|
return Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
|
}
|
|
|
|
return StructuredCloneFileParent{type, std::move(fileInfo)};
|
|
}
|
|
|
|
// This class helps to create only 1 sandbox.
|
|
class SandboxHolder final {
|
|
public:
|
|
NS_INLINE_DECL_REFCOUNTING(SandboxHolder)
|
|
|
|
private:
|
|
friend JSObject* mozilla::dom::indexedDB::GetSandbox(JSContext* aCx);
|
|
|
|
~SandboxHolder() = default;
|
|
|
|
static SandboxHolder* GetOrCreate() {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
static StaticRefPtr<SandboxHolder> sHolder;
|
|
if (!sHolder) {
|
|
sHolder = new SandboxHolder();
|
|
ClearOnShutdown(&sHolder);
|
|
}
|
|
return sHolder;
|
|
}
|
|
|
|
JSObject* GetSandboxInternal(JSContext* aCx) {
|
|
if (!mSandbox) {
|
|
nsIXPConnect* const xpc = nsContentUtils::XPConnect();
|
|
MOZ_ASSERT(xpc, "This should never be null!");
|
|
|
|
// Let's use a null principal.
|
|
const nsCOMPtr<nsIPrincipal> principal =
|
|
NullPrincipal::CreateWithoutOriginAttributes();
|
|
|
|
JS::Rooted<JSObject*> sandbox(aCx);
|
|
QM_TRY(
|
|
MOZ_TO_RESULT(xpc->CreateSandbox(aCx, principal, sandbox.address())),
|
|
nullptr);
|
|
|
|
mSandbox = new JSObjectHolder(aCx, sandbox);
|
|
}
|
|
|
|
return mSandbox->GetJSObject();
|
|
}
|
|
|
|
RefPtr<JSObjectHolder> mSandbox;
|
|
};
|
|
|
|
uint32_t CompressedByteCountForNumber(uint64_t aNumber) {
|
|
// All bytes have 7 bits available.
|
|
uint32_t count = 1;
|
|
while ((aNumber >>= 7)) {
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
uint32_t CompressedByteCountForIndexId(IndexOrObjectStoreId aIndexId) {
|
|
MOZ_ASSERT(aIndexId);
|
|
MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId),
|
|
"Overflow!");
|
|
|
|
return CompressedByteCountForNumber(uint64_t(aIndexId * 2));
|
|
}
|
|
|
|
void WriteCompressedNumber(uint64_t aNumber, uint8_t** aIterator) {
|
|
MOZ_ASSERT(aIterator);
|
|
MOZ_ASSERT(*aIterator);
|
|
|
|
uint8_t*& buffer = *aIterator;
|
|
|
|
#ifdef DEBUG
|
|
const uint8_t* const bufferStart = buffer;
|
|
const uint64_t originalNumber = aNumber;
|
|
#endif
|
|
|
|
while (true) {
|
|
uint64_t shiftedNumber = aNumber >> 7;
|
|
if (shiftedNumber) {
|
|
*buffer++ = uint8_t(0x80 | (aNumber & 0x7f));
|
|
aNumber = shiftedNumber;
|
|
} else {
|
|
*buffer++ = uint8_t(aNumber);
|
|
break;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(buffer > bufferStart);
|
|
MOZ_ASSERT(uint32_t(buffer - bufferStart) ==
|
|
CompressedByteCountForNumber(originalNumber));
|
|
}
|
|
|
|
void WriteCompressedIndexId(IndexOrObjectStoreId aIndexId, bool aUnique,
|
|
uint8_t** aIterator) {
|
|
MOZ_ASSERT(aIndexId);
|
|
MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId),
|
|
"Overflow!");
|
|
MOZ_ASSERT(aIterator);
|
|
MOZ_ASSERT(*aIterator);
|
|
|
|
const uint64_t indexId = (uint64_t(aIndexId * 2) | (aUnique ? 1 : 0));
|
|
WriteCompressedNumber(indexId, aIterator);
|
|
}
|
|
|
|
// aOutIndexValues is an output parameter, since its storage is reused.
|
|
nsresult ReadCompressedIndexDataValuesFromBlob(
|
|
const Span<const uint8_t> aBlobData,
|
|
nsTArray<IndexDataValue>* aOutIndexValues) {
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(!aBlobData.IsEmpty());
|
|
MOZ_ASSERT(aOutIndexValues);
|
|
MOZ_ASSERT(aOutIndexValues->IsEmpty());
|
|
|
|
AUTO_PROFILER_LABEL("ReadCompressedIndexDataValuesFromBlob", DOM);
|
|
|
|
// XXX Is this check still necessary with a Span? Or should it rather be moved
|
|
// to the caller?
|
|
QM_TRY(OkIf(uintptr_t(aBlobData.Elements()) <=
|
|
UINTPTR_MAX - aBlobData.LengthBytes()),
|
|
NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
|
|
|
|
for (auto remainder = aBlobData; !remainder.IsEmpty();) {
|
|
QM_TRY_INSPECT((const auto& [indexId, unique, remainderAfterIndexId]),
|
|
ReadCompressedIndexId(remainder));
|
|
|
|
QM_TRY(OkIf(!remainderAfterIndexId.IsEmpty()), NS_ERROR_FILE_CORRUPTED,
|
|
IDB_REPORT_INTERNAL_ERR_LAMBDA);
|
|
|
|
// Read key buffer length.
|
|
QM_TRY_INSPECT(
|
|
(const auto& [keyBufferLength, remainderAfterKeyBufferLength]),
|
|
ReadCompressedNumber(remainderAfterIndexId));
|
|
|
|
QM_TRY(OkIf(!remainderAfterKeyBufferLength.IsEmpty()),
|
|
NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
|
|
|
|
QM_TRY(OkIf(keyBufferLength <= uint64_t(UINT32_MAX)),
|
|
NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
|
|
|
|
QM_TRY(OkIf(keyBufferLength <= remainderAfterKeyBufferLength.Length()),
|
|
NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
|
|
|
|
const auto [keyBuffer, remainderAfterKeyBuffer] =
|
|
remainderAfterKeyBufferLength.SplitAt(keyBufferLength);
|
|
auto idv =
|
|
IndexDataValue{indexId, unique, Key{nsCString{AsChars(keyBuffer)}}};
|
|
|
|
// Read sort key buffer length.
|
|
QM_TRY_INSPECT(
|
|
(const auto& [sortKeyBufferLength, remainderAfterSortKeyBufferLength]),
|
|
ReadCompressedNumber(remainderAfterKeyBuffer));
|
|
|
|
remainder = remainderAfterSortKeyBufferLength;
|
|
if (sortKeyBufferLength > 0) {
|
|
QM_TRY(OkIf(!remainder.IsEmpty()), NS_ERROR_FILE_CORRUPTED,
|
|
IDB_REPORT_INTERNAL_ERR_LAMBDA);
|
|
|
|
QM_TRY(OkIf(sortKeyBufferLength <= uint64_t(UINT32_MAX)),
|
|
NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
|
|
|
|
QM_TRY(OkIf(sortKeyBufferLength <= remainder.Length()),
|
|
NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
|
|
|
|
const auto [sortKeyBuffer, remainderAfterSortKeyBuffer] =
|
|
remainder.SplitAt(sortKeyBufferLength);
|
|
idv.mLocaleAwarePosition = Key{nsCString{AsChars(sortKeyBuffer)}};
|
|
remainder = remainderAfterSortKeyBuffer;
|
|
}
|
|
|
|
QM_TRY(OkIf(aOutIndexValues->AppendElement(std::move(idv), fallible)),
|
|
NS_ERROR_OUT_OF_MEMORY, IDB_REPORT_INTERNAL_ERR_LAMBDA);
|
|
}
|
|
aOutIndexValues->Sort();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// aOutIndexValues is an output parameter, since its storage is reused.
|
|
template <typename T>
|
|
nsresult ReadCompressedIndexDataValuesFromSource(
|
|
T& aSource, uint32_t aColumnIndex,
|
|
nsTArray<IndexDataValue>* aOutIndexValues) {
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aOutIndexValues);
|
|
MOZ_ASSERT(aOutIndexValues->IsEmpty());
|
|
|
|
QM_TRY_INSPECT(
|
|
const int32_t& columnType,
|
|
MOZ_TO_RESULT_INVOKE_MEMBER(aSource, GetTypeOfIndex, aColumnIndex));
|
|
|
|
switch (columnType) {
|
|
case mozIStorageStatement::VALUE_TYPE_NULL:
|
|
return NS_OK;
|
|
|
|
case mozIStorageStatement::VALUE_TYPE_BLOB: {
|
|
// XXX ToResultInvoke does not support multiple output parameters yet, so
|
|
// we also can't use QM_TRY_UNWRAP/QM_TRY_INSPECT here.
|
|
const uint8_t* blobData;
|
|
uint32_t blobDataLength;
|
|
QM_TRY(MOZ_TO_RESULT(
|
|
aSource.GetSharedBlob(aColumnIndex, &blobDataLength, &blobData)));
|
|
|
|
QM_TRY(OkIf(blobDataLength), NS_ERROR_FILE_CORRUPTED,
|
|
IDB_REPORT_INTERNAL_ERR_LAMBDA);
|
|
|
|
QM_TRY(MOZ_TO_RESULT(ReadCompressedIndexDataValuesFromBlob(
|
|
Span(blobData, blobDataLength), aOutIndexValues)));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
default:
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
}
|
|
|
|
Result<StructuredCloneReadInfoParent, nsresult>
|
|
GetStructuredCloneReadInfoFromBlob(const uint8_t* aBlobData,
|
|
uint32_t aBlobDataLength,
|
|
const DatabaseFileManager& aFileManager,
|
|
const nsAString& aFileIds,
|
|
const Maybe<CipherKey>& aMaybeKey) {
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
|
|
AUTO_PROFILER_LABEL("GetStructuredCloneReadInfoFromBlob", DOM);
|
|
|
|
const char* const compressed = reinterpret_cast<const char*>(aBlobData);
|
|
const size_t compressedLength = size_t(aBlobDataLength);
|
|
|
|
size_t uncompressedLength;
|
|
QM_TRY(OkIf(snappy::GetUncompressedLength(compressed, compressedLength,
|
|
&uncompressedLength)),
|
|
Err(NS_ERROR_FILE_CORRUPTED));
|
|
|
|
// `data` (JSStructuredCloneData) currently uses 4k buffer internally.
|
|
// For performance reasons, it's better to align `uncompressed` with that.
|
|
AutoTArray<uint8_t, 4096> uncompressed;
|
|
QM_TRY(OkIf(uncompressed.SetLength(uncompressedLength, fallible)),
|
|
Err(NS_ERROR_OUT_OF_MEMORY));
|
|
|
|
char* const uncompressedBuffer =
|
|
reinterpret_cast<char*>(uncompressed.Elements());
|
|
|
|
QM_TRY(OkIf(snappy::RawUncompress(compressed, compressedLength,
|
|
uncompressedBuffer)),
|
|
Err(NS_ERROR_FILE_CORRUPTED));
|
|
|
|
JSStructuredCloneData data(JS::StructuredCloneScope::DifferentProcess);
|
|
QM_TRY(OkIf(data.AppendBytes(uncompressedBuffer, uncompressed.Length())),
|
|
Err(NS_ERROR_OUT_OF_MEMORY));
|
|
|
|
nsTArray<StructuredCloneFileParent> files;
|
|
if (!aFileIds.IsVoid()) {
|
|
QM_TRY_UNWRAP(files,
|
|
DeserializeStructuredCloneFiles(aFileManager, aFileIds));
|
|
}
|
|
|
|
return StructuredCloneReadInfoParent{std::move(data), std::move(files),
|
|
false};
|
|
}
|
|
|
|
Result<StructuredCloneReadInfoParent, nsresult>
|
|
GetStructuredCloneReadInfoFromExternalBlob(
|
|
uint64_t aIntData, const DatabaseFileManager& aFileManager,
|
|
const nsAString& aFileIds, const Maybe<CipherKey>& aMaybeKey) {
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
|
|
AUTO_PROFILER_LABEL("GetStructuredCloneReadInfoFromExternalBlob", DOM);
|
|
|
|
nsTArray<StructuredCloneFileParent> files;
|
|
if (!aFileIds.IsVoid()) {
|
|
QM_TRY_UNWRAP(files,
|
|
DeserializeStructuredCloneFiles(aFileManager, aFileIds));
|
|
}
|
|
|
|
// Higher and lower 32 bits described
|
|
// in ObjectStoreAddOrPutRequestOp::DoDatabaseWork.
|
|
const uint32_t index = uint32_t(aIntData & UINT32_MAX);
|
|
|
|
QM_TRY(OkIf(index < files.Length()), Err(NS_ERROR_UNEXPECTED),
|
|
[](const auto&) { MOZ_ASSERT(false, "Bad index value!"); });
|
|
|
|
if (IndexedDatabaseManager::PreprocessingEnabled()) {
|
|
return StructuredCloneReadInfoParent{
|
|
JSStructuredCloneData{JS::StructuredCloneScope::DifferentProcess},
|
|
std::move(files), true};
|
|
}
|
|
|
|
// XXX Why can there be multiple files, but we use only a single one here?
|
|
const StructuredCloneFileParent& file = files[index];
|
|
MOZ_ASSERT(file.Type() == StructuredCloneFileBase::eStructuredClone);
|
|
|
|
auto data = JSStructuredCloneData{JS::StructuredCloneScope::DifferentProcess};
|
|
|
|
{
|
|
const nsCOMPtr<nsIFile> nativeFile = file.FileInfo().GetFileForFileInfo();
|
|
QM_TRY(OkIf(nativeFile), Err(NS_ERROR_FAILURE));
|
|
|
|
QM_TRY_INSPECT(
|
|
const auto& fileInputStream,
|
|
NS_NewLocalFileInputStream(nativeFile)
|
|
.andThen([aMaybeKey](auto fileInputStream)
|
|
-> Result<nsCOMPtr<nsIInputStream>, nsresult> {
|
|
if (aMaybeKey) {
|
|
return nsCOMPtr<nsIInputStream>{MakeRefPtr<
|
|
quota::DecryptingInputStream<IndexedDBCipherStrategy>>(
|
|
WrapNotNull(std::move(fileInputStream)),
|
|
kEncryptedStreamBlockSize, *aMaybeKey)};
|
|
}
|
|
|
|
return fileInputStream;
|
|
}));
|
|
|
|
QM_TRY(MOZ_TO_RESULT(
|
|
SnappyUncompressStructuredCloneData(*fileInputStream, data)));
|
|
}
|
|
|
|
return StructuredCloneReadInfoParent{std::move(data), std::move(files),
|
|
false};
|
|
}
|
|
|
|
template <typename T>
|
|
Result<StructuredCloneReadInfoParent, nsresult>
|
|
GetStructuredCloneReadInfoFromSource(T* aSource, uint32_t aDataIndex,
|
|
uint32_t aFileIdsIndex,
|
|
const DatabaseFileManager& aFileManager,
|
|
const Maybe<CipherKey>& aMaybeKey) {
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aSource);
|
|
|
|
QM_TRY_INSPECT(
|
|
const int32_t& columnType,
|
|
MOZ_TO_RESULT_INVOKE_MEMBER(aSource, GetTypeOfIndex, aDataIndex));
|
|
|
|
QM_TRY_INSPECT(const bool& isNull, MOZ_TO_RESULT_INVOKE_MEMBER(
|
|
aSource, GetIsNull, aFileIdsIndex));
|
|
|
|
QM_TRY_INSPECT(const nsString& fileIds, ([aSource, aFileIdsIndex, isNull] {
|
|
return isNull ? Result<nsString, nsresult>{VoidString()}
|
|
: MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
|
|
nsString, aSource, GetString,
|
|
aFileIdsIndex);
|
|
}()));
|
|
|
|
switch (columnType) {
|
|
case mozIStorageStatement::VALUE_TYPE_INTEGER: {
|
|
QM_TRY_INSPECT(
|
|
const int64_t& intData,
|
|
MOZ_TO_RESULT_INVOKE_MEMBER(aSource, GetInt64, aDataIndex));
|
|
|
|
uint64_t uintData;
|
|
memcpy(&uintData, &intData, sizeof(uint64_t));
|
|
|
|
return GetStructuredCloneReadInfoFromExternalBlob(uintData, aFileManager,
|
|
fileIds, aMaybeKey);
|
|
}
|
|
|
|
case mozIStorageStatement::VALUE_TYPE_BLOB: {
|
|
const uint8_t* blobData;
|
|
uint32_t blobDataLength;
|
|
QM_TRY(MOZ_TO_RESULT(
|
|
aSource->GetSharedBlob(aDataIndex, &blobDataLength, &blobData)));
|
|
|
|
return GetStructuredCloneReadInfoFromBlob(
|
|
blobData, blobDataLength, aFileManager, fileIds, aMaybeKey);
|
|
}
|
|
|
|
default:
|
|
return Err(NS_ERROR_FILE_CORRUPTED);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
IndexDataValue::IndexDataValue() : mIndexId(0), mUnique(false) {
|
|
MOZ_COUNT_CTOR(IndexDataValue);
|
|
}
|
|
|
|
#ifdef NS_BUILD_REFCNT_LOGGING
|
|
IndexDataValue::IndexDataValue(IndexDataValue&& aOther)
|
|
: mIndexId(aOther.mIndexId),
|
|
mPosition(std::move(aOther.mPosition)),
|
|
mLocaleAwarePosition(std::move(aOther.mLocaleAwarePosition)),
|
|
mUnique(aOther.mUnique) {
|
|
MOZ_ASSERT(!aOther.mPosition.IsUnset());
|
|
|
|
MOZ_COUNT_CTOR(IndexDataValue);
|
|
}
|
|
#endif
|
|
|
|
IndexDataValue::IndexDataValue(IndexOrObjectStoreId aIndexId, bool aUnique,
|
|
const Key& aPosition)
|
|
: mIndexId(aIndexId), mPosition(aPosition), mUnique(aUnique) {
|
|
MOZ_ASSERT(!aPosition.IsUnset());
|
|
|
|
MOZ_COUNT_CTOR(IndexDataValue);
|
|
}
|
|
|
|
IndexDataValue::IndexDataValue(IndexOrObjectStoreId aIndexId, bool aUnique,
|
|
const Key& aPosition,
|
|
const Key& aLocaleAwarePosition)
|
|
: mIndexId(aIndexId),
|
|
mPosition(aPosition),
|
|
mLocaleAwarePosition(aLocaleAwarePosition),
|
|
mUnique(aUnique) {
|
|
MOZ_ASSERT(!aPosition.IsUnset());
|
|
|
|
MOZ_COUNT_CTOR(IndexDataValue);
|
|
}
|
|
|
|
bool IndexDataValue::operator==(const IndexDataValue& aOther) const {
|
|
if (mIndexId != aOther.mIndexId) {
|
|
return false;
|
|
}
|
|
if (mLocaleAwarePosition.IsUnset()) {
|
|
return mPosition == aOther.mPosition;
|
|
}
|
|
return mLocaleAwarePosition == aOther.mLocaleAwarePosition;
|
|
}
|
|
|
|
bool IndexDataValue::operator<(const IndexDataValue& aOther) const {
|
|
if (mIndexId == aOther.mIndexId) {
|
|
if (mLocaleAwarePosition.IsUnset()) {
|
|
return mPosition < aOther.mPosition;
|
|
}
|
|
return mLocaleAwarePosition < aOther.mLocaleAwarePosition;
|
|
}
|
|
|
|
return mIndexId < aOther.mIndexId;
|
|
}
|
|
|
|
JSObject* GetSandbox(JSContext* aCx) {
|
|
SandboxHolder* holder = SandboxHolder::GetOrCreate();
|
|
return holder->GetSandboxInternal(aCx);
|
|
}
|
|
|
|
Result<std::pair<UniqueFreePtr<uint8_t>, uint32_t>, nsresult>
|
|
MakeCompressedIndexDataValues(const nsTArray<IndexDataValue>& aIndexValues) {
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
|
|
AUTO_PROFILER_LABEL("MakeCompressedIndexDataValues", DOM);
|
|
|
|
const uint32_t arrayLength = aIndexValues.Length();
|
|
if (!arrayLength) {
|
|
return std::pair{UniqueFreePtr<uint8_t>{}, 0u};
|
|
}
|
|
|
|
// First calculate the size of the final buffer.
|
|
const auto blobDataLength = std::accumulate(
|
|
aIndexValues.cbegin(), aIndexValues.cend(), CheckedUint32(0),
|
|
[](CheckedUint32 sum, const IndexDataValue& info) {
|
|
const nsCString& keyBuffer = info.mPosition.GetBuffer();
|
|
const nsCString& sortKeyBuffer = info.mLocaleAwarePosition.GetBuffer();
|
|
const uint32_t keyBufferLength = keyBuffer.Length();
|
|
const uint32_t sortKeyBufferLength = sortKeyBuffer.Length();
|
|
|
|
MOZ_ASSERT(!keyBuffer.IsEmpty());
|
|
|
|
return sum + CompressedByteCountForIndexId(info.mIndexId) +
|
|
CompressedByteCountForNumber(keyBufferLength) +
|
|
CompressedByteCountForNumber(sortKeyBufferLength) +
|
|
keyBufferLength + sortKeyBufferLength;
|
|
});
|
|
|
|
QM_TRY(OkIf(blobDataLength.isValid()),
|
|
Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
|
|
IDB_REPORT_INTERNAL_ERR_LAMBDA);
|
|
|
|
UniqueFreePtr<uint8_t> blobData(
|
|
static_cast<uint8_t*>(malloc(blobDataLength.value())));
|
|
QM_TRY(OkIf(static_cast<bool>(blobData)), Err(NS_ERROR_OUT_OF_MEMORY),
|
|
IDB_REPORT_INTERNAL_ERR_LAMBDA);
|
|
|
|
uint8_t* blobDataIter = blobData.get();
|
|
|
|
for (const IndexDataValue& info : aIndexValues) {
|
|
const nsCString& keyBuffer = info.mPosition.GetBuffer();
|
|
const nsCString& sortKeyBuffer = info.mLocaleAwarePosition.GetBuffer();
|
|
const uint32_t keyBufferLength = keyBuffer.Length();
|
|
const uint32_t sortKeyBufferLength = sortKeyBuffer.Length();
|
|
|
|
WriteCompressedIndexId(info.mIndexId, info.mUnique, &blobDataIter);
|
|
WriteCompressedNumber(keyBufferLength, &blobDataIter);
|
|
|
|
memcpy(blobDataIter, keyBuffer.get(), keyBufferLength);
|
|
blobDataIter += keyBufferLength;
|
|
|
|
WriteCompressedNumber(sortKeyBufferLength, &blobDataIter);
|
|
|
|
memcpy(blobDataIter, sortKeyBuffer.get(), sortKeyBufferLength);
|
|
blobDataIter += sortKeyBufferLength;
|
|
}
|
|
|
|
MOZ_ASSERT(blobDataIter == blobData.get() + blobDataLength.value());
|
|
|
|
return std::pair{std::move(blobData), blobDataLength.value()};
|
|
}
|
|
|
|
nsresult ReadCompressedIndexDataValues(
|
|
mozIStorageStatement& aStatement, uint32_t aColumnIndex,
|
|
nsTArray<IndexDataValue>& aOutIndexValues) {
|
|
return ReadCompressedIndexDataValuesFromSource(aStatement, aColumnIndex,
|
|
&aOutIndexValues);
|
|
}
|
|
|
|
template <typename T>
|
|
Result<IndexDataValuesAutoArray, nsresult> ReadCompressedIndexDataValues(
|
|
T& aValues, uint32_t aColumnIndex) {
|
|
return MOZ_TO_RESULT_INVOKE_TYPED(IndexDataValuesAutoArray,
|
|
&ReadCompressedIndexDataValuesFromSource<T>,
|
|
aValues, aColumnIndex);
|
|
}
|
|
|
|
template Result<IndexDataValuesAutoArray, nsresult>
|
|
ReadCompressedIndexDataValues<mozIStorageValueArray>(mozIStorageValueArray&,
|
|
uint32_t);
|
|
|
|
template Result<IndexDataValuesAutoArray, nsresult>
|
|
ReadCompressedIndexDataValues<mozIStorageStatement>(mozIStorageStatement&,
|
|
uint32_t);
|
|
|
|
Result<std::tuple<IndexOrObjectStoreId, bool, Span<const uint8_t>>, nsresult>
|
|
ReadCompressedIndexId(const Span<const uint8_t> aData) {
|
|
QM_TRY_INSPECT((const auto& [indexId, remainder]),
|
|
ReadCompressedNumber(aData));
|
|
|
|
MOZ_ASSERT(UINT64_MAX / 2 >= uint64_t(indexId), "Bad index id!");
|
|
|
|
return std::tuple{IndexOrObjectStoreId(indexId >> 1), indexId % 2 == 1,
|
|
remainder};
|
|
}
|
|
|
|
Result<std::pair<uint64_t, mozilla::Span<const uint8_t>>, nsresult>
|
|
ReadCompressedNumber(const Span<const uint8_t> aSpan) {
|
|
uint8_t shiftCounter = 0;
|
|
uint64_t result = 0;
|
|
|
|
const auto end = aSpan.cend();
|
|
|
|
const auto newPos =
|
|
std::find_if(aSpan.cbegin(), end, [&result, &shiftCounter](uint8_t byte) {
|
|
MOZ_ASSERT(shiftCounter <= 56, "Shifted too many bits!");
|
|
|
|
result += (uint64_t(byte & 0x7f) << shiftCounter);
|
|
shiftCounter += 7;
|
|
|
|
return !(byte & 0x80);
|
|
});
|
|
|
|
QM_TRY(OkIf(newPos != end), Err(NS_ERROR_FILE_CORRUPTED), [](const auto&) {
|
|
MOZ_ASSERT(false);
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
});
|
|
|
|
return std::pair{result, Span{newPos + 1, end}};
|
|
}
|
|
|
|
Result<StructuredCloneReadInfoParent, nsresult>
|
|
GetStructuredCloneReadInfoFromValueArray(
|
|
mozIStorageValueArray* aValues, uint32_t aDataIndex, uint32_t aFileIdsIndex,
|
|
const DatabaseFileManager& aFileManager,
|
|
const Maybe<CipherKey>& aMaybeKey) {
|
|
return GetStructuredCloneReadInfoFromSource(
|
|
aValues, aDataIndex, aFileIdsIndex, aFileManager, aMaybeKey);
|
|
}
|
|
|
|
Result<StructuredCloneReadInfoParent, nsresult>
|
|
GetStructuredCloneReadInfoFromStatement(mozIStorageStatement* aStatement,
|
|
uint32_t aDataIndex,
|
|
uint32_t aFileIdsIndex,
|
|
const DatabaseFileManager& aFileManager,
|
|
const Maybe<CipherKey>& aMaybeKey) {
|
|
return GetStructuredCloneReadInfoFromSource(
|
|
aStatement, aDataIndex, aFileIdsIndex, aFileManager, aMaybeKey);
|
|
}
|
|
|
|
Result<nsTArray<StructuredCloneFileParent>, nsresult>
|
|
DeserializeStructuredCloneFiles(const DatabaseFileManager& aFileManager,
|
|
const nsAString& aText) {
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
|
|
nsTArray<StructuredCloneFileParent> result;
|
|
for (const auto& token :
|
|
nsCharSeparatedTokenizerTemplate<NS_TokenizerIgnoreNothing>(aText, ' ')
|
|
.ToRange()) {
|
|
MOZ_ASSERT(!token.IsEmpty());
|
|
|
|
QM_TRY_UNWRAP(auto structuredCloneFile,
|
|
DeserializeStructuredCloneFile(aFileManager, token));
|
|
|
|
result.EmplaceBack(std::move(structuredCloneFile));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
nsresult ExecuteSimpleSQLSequence(mozIStorageConnection& aConnection,
|
|
Span<const nsLiteralCString> aSQLCommands) {
|
|
for (const auto& aSQLCommand : aSQLCommands) {
|
|
const auto extraInfo = quota::ScopedLogExtraInfo{
|
|
quota::ScopedLogExtraInfo::kTagQuery, aSQLCommand};
|
|
|
|
QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(aSQLCommand)));
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace mozilla::dom::indexedDB
|