1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "ActorsParentCommon.h"
8
9 // local includes
10 #include "DatabaseFileInfo.h"
11 #include "DatabaseFileManager.h"
12 #include "IndexedDatabase.h" // for StructuredCloneFile...
13 #include "IndexedDatabaseInlines.h"
14 #include "IndexedDatabaseManager.h"
15 #include "IndexedDBCommon.h"
16 #include "ReportInternalError.h"
17
18 // global includes
19 #include <stdlib.h>
20 #include <string.h>
21 #include <algorithm>
22 #include <numeric>
23 #include <type_traits>
24 #include "MainThreadUtils.h"
25 #include "SafeRefPtr.h"
26 #include "js/RootingAPI.h"
27 #include "js/StructuredClone.h"
28 #include "mozIStorageConnection.h"
29 #include "mozIStorageStatement.h"
30 #include "mozIStorageValueArray.h"
31 #include "mozilla/Assertions.h"
32 #include "mozilla/CheckedInt.h"
33 #include "mozilla/ClearOnShutdown.h"
34 #include "mozilla/JSObjectHolder.h"
35 #include "mozilla/NullPrincipal.h"
36 #include "mozilla/ProfilerLabels.h"
37 #include "mozilla/RefPtr.h"
38 #include "mozilla/ResultExtensions.h"
39 #include "mozilla/StaticPtr.h"
40 #include "mozilla/Telemetry.h"
41 #include "mozilla/TelemetryScalarEnums.h"
42 #include "mozilla/dom/quota/DecryptingInputStream_impl.h"
43 #include "mozilla/dom/quota/QuotaCommon.h"
44 #include "mozilla/dom/quota/ScopedLogExtraInfo.h"
45 #include "mozilla/fallible.h"
46 #include "mozilla/ipc/BackgroundParent.h"
47 #include "mozilla/mozalloc.h"
48 #include "nsCOMPtr.h"
49 #include "nsCharSeparatedTokenizer.h"
50 #include "nsContentUtils.h"
51 #include "nsDebug.h"
52 #include "nsError.h"
53 #include "nsIInputStream.h"
54 #include "nsIPrincipal.h"
55 #include "nsIXPConnect.h"
56 #include "nsNetUtil.h"
57 #include "nsString.h"
58 #include "nsStringFlags.h"
59 #include "nsXULAppAPI.h"
60 #include "snappy/snappy.h"
61
62 class nsIFile;
63
64 namespace mozilla::dom::indexedDB {
65
66 static_assert(SNAPPY_VERSION == 0x010108);
67
68 using mozilla::ipc::IsOnBackgroundThread;
69
70 namespace {
71
ToStructuredCloneFileType(const char16_t aTag)72 constexpr StructuredCloneFileBase::FileType ToStructuredCloneFileType(
73 const char16_t aTag) {
74 switch (aTag) {
75 case char16_t('-'):
76 return StructuredCloneFileBase::eMutableFile;
77
78 case char16_t('.'):
79 return StructuredCloneFileBase::eStructuredClone;
80
81 case char16_t('/'):
82 return StructuredCloneFileBase::eWasmBytecode;
83
84 case char16_t('\\'):
85 return StructuredCloneFileBase::eWasmCompiled;
86
87 default:
88 return StructuredCloneFileBase::eBlob;
89 }
90 }
91
ToInteger(const nsAString & aStr,nsresult * const aRv)92 int32_t ToInteger(const nsAString& aStr, nsresult* const aRv) {
93 return aStr.ToInteger(aRv);
94 }
95
DeserializeStructuredCloneFile(const DatabaseFileManager & aFileManager,const nsDependentSubstring & aText)96 Result<StructuredCloneFileParent, nsresult> DeserializeStructuredCloneFile(
97 const DatabaseFileManager& aFileManager,
98 const nsDependentSubstring& aText) {
99 MOZ_ASSERT(!aText.IsEmpty());
100
101 const StructuredCloneFileBase::FileType type =
102 ToStructuredCloneFileType(aText.First());
103
104 QM_TRY_INSPECT(
105 const auto& id,
106 ToResultGet<int32_t>(
107 ToInteger, type == StructuredCloneFileBase::eBlob
108 ? aText
109 : static_cast<const nsAString&>(Substring(aText, 1))));
110
111 SafeRefPtr<DatabaseFileInfo> fileInfo = aFileManager.GetFileInfo(id);
112 MOZ_ASSERT(fileInfo);
113 // XXX In bug 1432133, for some reasons DatabaseFileInfo object cannot be
114 // got. This is just a short-term fix, and we are working on finding the real
115 // cause in bug 1519859.
116 if (!fileInfo) {
117 IDB_WARNING(
118 "Corrupt structured clone data detected in IndexedDB. Failing the "
119 "database request. Bug 1519859 will address this problem.");
120 Telemetry::ScalarAdd(Telemetry::ScalarID::IDB_FAILURE_FILEINFO_ERROR, 1);
121
122 return Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
123 }
124
125 return StructuredCloneFileParent{type, std::move(fileInfo)};
126 }
127
128 // This class helps to create only 1 sandbox.
129 class SandboxHolder final {
130 public:
131 NS_INLINE_DECL_REFCOUNTING(SandboxHolder)
132
133 private:
134 friend JSObject* mozilla::dom::indexedDB::GetSandbox(JSContext* aCx);
135
136 ~SandboxHolder() = default;
137
GetOrCreate()138 static SandboxHolder* GetOrCreate() {
139 MOZ_ASSERT(XRE_IsParentProcess());
140 MOZ_ASSERT(NS_IsMainThread());
141
142 static StaticRefPtr<SandboxHolder> sHolder;
143 if (!sHolder) {
144 sHolder = new SandboxHolder();
145 ClearOnShutdown(&sHolder);
146 }
147 return sHolder;
148 }
149
GetSandboxInternal(JSContext * aCx)150 JSObject* GetSandboxInternal(JSContext* aCx) {
151 if (!mSandbox) {
152 nsIXPConnect* const xpc = nsContentUtils::XPConnect();
153 MOZ_ASSERT(xpc, "This should never be null!");
154
155 // Let's use a null principal.
156 const nsCOMPtr<nsIPrincipal> principal =
157 NullPrincipal::CreateWithoutOriginAttributes();
158
159 JS::Rooted<JSObject*> sandbox(aCx);
160 QM_TRY(xpc->CreateSandbox(aCx, principal, sandbox.address()), nullptr);
161
162 mSandbox = new JSObjectHolder(aCx, sandbox);
163 }
164
165 return mSandbox->GetJSObject();
166 }
167
168 RefPtr<JSObjectHolder> mSandbox;
169 };
170
CompressedByteCountForNumber(uint64_t aNumber)171 uint32_t CompressedByteCountForNumber(uint64_t aNumber) {
172 // All bytes have 7 bits available.
173 uint32_t count = 1;
174 while ((aNumber >>= 7)) {
175 count++;
176 }
177
178 return count;
179 }
180
CompressedByteCountForIndexId(IndexOrObjectStoreId aIndexId)181 uint32_t CompressedByteCountForIndexId(IndexOrObjectStoreId aIndexId) {
182 MOZ_ASSERT(aIndexId);
183 MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId),
184 "Overflow!");
185
186 return CompressedByteCountForNumber(uint64_t(aIndexId * 2));
187 }
188
WriteCompressedNumber(uint64_t aNumber,uint8_t ** aIterator)189 void WriteCompressedNumber(uint64_t aNumber, uint8_t** aIterator) {
190 MOZ_ASSERT(aIterator);
191 MOZ_ASSERT(*aIterator);
192
193 uint8_t*& buffer = *aIterator;
194
195 #ifdef DEBUG
196 const uint8_t* const bufferStart = buffer;
197 const uint64_t originalNumber = aNumber;
198 #endif
199
200 while (true) {
201 uint64_t shiftedNumber = aNumber >> 7;
202 if (shiftedNumber) {
203 *buffer++ = uint8_t(0x80 | (aNumber & 0x7f));
204 aNumber = shiftedNumber;
205 } else {
206 *buffer++ = uint8_t(aNumber);
207 break;
208 }
209 }
210
211 MOZ_ASSERT(buffer > bufferStart);
212 MOZ_ASSERT(uint32_t(buffer - bufferStart) ==
213 CompressedByteCountForNumber(originalNumber));
214 }
215
WriteCompressedIndexId(IndexOrObjectStoreId aIndexId,bool aUnique,uint8_t ** aIterator)216 void WriteCompressedIndexId(IndexOrObjectStoreId aIndexId, bool aUnique,
217 uint8_t** aIterator) {
218 MOZ_ASSERT(aIndexId);
219 MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId),
220 "Overflow!");
221 MOZ_ASSERT(aIterator);
222 MOZ_ASSERT(*aIterator);
223
224 const uint64_t indexId = (uint64_t(aIndexId * 2) | (aUnique ? 1 : 0));
225 WriteCompressedNumber(indexId, aIterator);
226 }
227
228 // aOutIndexValues is an output parameter, since its storage is reused.
ReadCompressedIndexDataValuesFromBlob(const Span<const uint8_t> aBlobData,nsTArray<IndexDataValue> * aOutIndexValues)229 nsresult ReadCompressedIndexDataValuesFromBlob(
230 const Span<const uint8_t> aBlobData,
231 nsTArray<IndexDataValue>* aOutIndexValues) {
232 MOZ_ASSERT(!NS_IsMainThread());
233 MOZ_ASSERT(!IsOnBackgroundThread());
234 MOZ_ASSERT(!aBlobData.IsEmpty());
235 MOZ_ASSERT(aOutIndexValues);
236 MOZ_ASSERT(aOutIndexValues->IsEmpty());
237
238 AUTO_PROFILER_LABEL("ReadCompressedIndexDataValuesFromBlob", DOM);
239
240 // XXX Is this check still necessary with a Span? Or should it rather be moved
241 // to the caller?
242 QM_TRY(OkIf(uintptr_t(aBlobData.Elements()) <=
243 UINTPTR_MAX - aBlobData.LengthBytes()),
244 NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
245
246 for (auto remainder = aBlobData; !remainder.IsEmpty();) {
247 QM_TRY_INSPECT((const auto& [indexId, unique, remainderAfterIndexId]),
248 ReadCompressedIndexId(remainder));
249
250 QM_TRY(OkIf(!remainderAfterIndexId.IsEmpty()), NS_ERROR_FILE_CORRUPTED,
251 IDB_REPORT_INTERNAL_ERR_LAMBDA);
252
253 // Read key buffer length.
254 QM_TRY_INSPECT(
255 (const auto& [keyBufferLength, remainderAfterKeyBufferLength]),
256 ReadCompressedNumber(remainderAfterIndexId));
257
258 QM_TRY(OkIf(!remainderAfterKeyBufferLength.IsEmpty()),
259 NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
260
261 QM_TRY(OkIf(keyBufferLength <= uint64_t(UINT32_MAX)),
262 NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
263
264 QM_TRY(OkIf(keyBufferLength <= remainderAfterKeyBufferLength.Length()),
265 NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
266
267 const auto [keyBuffer, remainderAfterKeyBuffer] =
268 remainderAfterKeyBufferLength.SplitAt(keyBufferLength);
269 auto idv =
270 IndexDataValue{indexId, unique, Key{nsCString{AsChars(keyBuffer)}}};
271
272 // Read sort key buffer length.
273 QM_TRY_INSPECT(
274 (const auto& [sortKeyBufferLength, remainderAfterSortKeyBufferLength]),
275 ReadCompressedNumber(remainderAfterKeyBuffer));
276
277 remainder = remainderAfterSortKeyBufferLength;
278 if (sortKeyBufferLength > 0) {
279 QM_TRY(OkIf(!remainder.IsEmpty()), NS_ERROR_FILE_CORRUPTED,
280 IDB_REPORT_INTERNAL_ERR_LAMBDA);
281
282 QM_TRY(OkIf(sortKeyBufferLength <= uint64_t(UINT32_MAX)),
283 NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
284
285 QM_TRY(OkIf(sortKeyBufferLength <= remainder.Length()),
286 NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
287
288 const auto [sortKeyBuffer, remainderAfterSortKeyBuffer] =
289 remainder.SplitAt(sortKeyBufferLength);
290 idv.mLocaleAwarePosition = Key{nsCString{AsChars(sortKeyBuffer)}};
291 remainder = remainderAfterSortKeyBuffer;
292 }
293
294 QM_TRY(OkIf(aOutIndexValues->AppendElement(std::move(idv), fallible)),
295 NS_ERROR_OUT_OF_MEMORY, IDB_REPORT_INTERNAL_ERR_LAMBDA);
296 }
297 aOutIndexValues->Sort();
298
299 return NS_OK;
300 }
301
302 // aOutIndexValues is an output parameter, since its storage is reused.
303 template <typename T>
ReadCompressedIndexDataValuesFromSource(T & aSource,uint32_t aColumnIndex,nsTArray<IndexDataValue> * aOutIndexValues)304 nsresult ReadCompressedIndexDataValuesFromSource(
305 T& aSource, uint32_t aColumnIndex,
306 nsTArray<IndexDataValue>* aOutIndexValues) {
307 MOZ_ASSERT(!NS_IsMainThread());
308 MOZ_ASSERT(!IsOnBackgroundThread());
309 MOZ_ASSERT(aOutIndexValues);
310 MOZ_ASSERT(aOutIndexValues->IsEmpty());
311
312 QM_TRY_INSPECT(const int32_t& columnType,
313 MOZ_TO_RESULT_INVOKE(aSource, GetTypeOfIndex, aColumnIndex));
314
315 switch (columnType) {
316 case mozIStorageStatement::VALUE_TYPE_NULL:
317 return NS_OK;
318
319 case mozIStorageStatement::VALUE_TYPE_BLOB: {
320 // XXX ToResultInvoke does not support multiple output parameters yet, so
321 // we also can't use QM_TRY_UNWRAP/QM_TRY_INSPECT here.
322 const uint8_t* blobData;
323 uint32_t blobDataLength;
324 QM_TRY(aSource.GetSharedBlob(aColumnIndex, &blobDataLength, &blobData));
325
326 QM_TRY(OkIf(blobDataLength), NS_ERROR_FILE_CORRUPTED,
327 IDB_REPORT_INTERNAL_ERR_LAMBDA);
328
329 QM_TRY(ReadCompressedIndexDataValuesFromBlob(
330 Span(blobData, blobDataLength), aOutIndexValues));
331
332 return NS_OK;
333 }
334
335 default:
336 return NS_ERROR_FILE_CORRUPTED;
337 }
338 }
339
340 Result<StructuredCloneReadInfoParent, nsresult>
GetStructuredCloneReadInfoFromBlob(const uint8_t * aBlobData,uint32_t aBlobDataLength,const DatabaseFileManager & aFileManager,const nsAString & aFileIds,const Maybe<CipherKey> & aMaybeKey)341 GetStructuredCloneReadInfoFromBlob(const uint8_t* aBlobData,
342 uint32_t aBlobDataLength,
343 const DatabaseFileManager& aFileManager,
344 const nsAString& aFileIds,
345 const Maybe<CipherKey>& aMaybeKey) {
346 MOZ_ASSERT(!IsOnBackgroundThread());
347
348 AUTO_PROFILER_LABEL("GetStructuredCloneReadInfoFromBlob", DOM);
349
350 const char* const compressed = reinterpret_cast<const char*>(aBlobData);
351 const size_t compressedLength = size_t(aBlobDataLength);
352
353 size_t uncompressedLength;
354 QM_TRY(OkIf(snappy::GetUncompressedLength(compressed, compressedLength,
355 &uncompressedLength)),
356 Err(NS_ERROR_FILE_CORRUPTED));
357
358 AutoTArray<uint8_t, 512> uncompressed;
359 QM_TRY(OkIf(uncompressed.SetLength(uncompressedLength, fallible)),
360 Err(NS_ERROR_OUT_OF_MEMORY));
361
362 char* const uncompressedBuffer =
363 reinterpret_cast<char*>(uncompressed.Elements());
364
365 QM_TRY(OkIf(snappy::RawUncompress(compressed, compressedLength,
366 uncompressedBuffer)),
367 Err(NS_ERROR_FILE_CORRUPTED));
368
369 JSStructuredCloneData data(JS::StructuredCloneScope::DifferentProcess);
370 QM_TRY(OkIf(data.AppendBytes(uncompressedBuffer, uncompressed.Length())),
371 Err(NS_ERROR_OUT_OF_MEMORY));
372
373 nsTArray<StructuredCloneFileParent> files;
374 if (!aFileIds.IsVoid()) {
375 QM_TRY_UNWRAP(files,
376 DeserializeStructuredCloneFiles(aFileManager, aFileIds));
377 }
378
379 return StructuredCloneReadInfoParent{std::move(data), std::move(files),
380 false};
381 }
382
383 Result<StructuredCloneReadInfoParent, nsresult>
GetStructuredCloneReadInfoFromExternalBlob(uint64_t aIntData,const DatabaseFileManager & aFileManager,const nsAString & aFileIds,const Maybe<CipherKey> & aMaybeKey)384 GetStructuredCloneReadInfoFromExternalBlob(
385 uint64_t aIntData, const DatabaseFileManager& aFileManager,
386 const nsAString& aFileIds, const Maybe<CipherKey>& aMaybeKey) {
387 MOZ_ASSERT(!IsOnBackgroundThread());
388
389 AUTO_PROFILER_LABEL("GetStructuredCloneReadInfoFromExternalBlob", DOM);
390
391 nsTArray<StructuredCloneFileParent> files;
392 if (!aFileIds.IsVoid()) {
393 QM_TRY_UNWRAP(files,
394 DeserializeStructuredCloneFiles(aFileManager, aFileIds));
395 }
396
397 // Higher and lower 32 bits described
398 // in ObjectStoreAddOrPutRequestOp::DoDatabaseWork.
399 const uint32_t index = uint32_t(aIntData & UINT32_MAX);
400
401 QM_TRY(OkIf(index < files.Length()), Err(NS_ERROR_UNEXPECTED),
402 [](const auto&) { MOZ_ASSERT(false, "Bad index value!"); });
403
404 if (IndexedDatabaseManager::PreprocessingEnabled()) {
405 return StructuredCloneReadInfoParent{
406 JSStructuredCloneData{JS::StructuredCloneScope::DifferentProcess},
407 std::move(files), true};
408 }
409
410 // XXX Why can there be multiple files, but we use only a single one here?
411 const StructuredCloneFileParent& file = files[index];
412 MOZ_ASSERT(file.Type() == StructuredCloneFileBase::eStructuredClone);
413
414 auto data = JSStructuredCloneData{JS::StructuredCloneScope::DifferentProcess};
415
416 {
417 const nsCOMPtr<nsIFile> nativeFile = file.FileInfo().GetFileForFileInfo();
418 QM_TRY(OkIf(nativeFile), Err(NS_ERROR_FAILURE));
419
420 QM_TRY_INSPECT(
421 const auto& fileInputStream,
422 NS_NewLocalFileInputStream(nativeFile)
423 .andThen([aMaybeKey](auto fileInputStream)
424 -> Result<nsCOMPtr<nsIInputStream>, nsresult> {
425 if (aMaybeKey) {
426 return nsCOMPtr<nsIInputStream>{MakeRefPtr<
427 quota::DecryptingInputStream<IndexedDBCipherStrategy>>(
428 WrapNotNull(std::move(fileInputStream)),
429 kEncryptedStreamBlockSize, *aMaybeKey)};
430 }
431
432 return fileInputStream;
433 }));
434
435 QM_TRY(SnappyUncompressStructuredCloneData(*fileInputStream, data));
436 }
437
438 return StructuredCloneReadInfoParent{std::move(data), std::move(files),
439 false};
440 }
441
442 template <typename T>
443 Result<StructuredCloneReadInfoParent, nsresult>
GetStructuredCloneReadInfoFromSource(T * aSource,uint32_t aDataIndex,uint32_t aFileIdsIndex,const DatabaseFileManager & aFileManager,const Maybe<CipherKey> & aMaybeKey)444 GetStructuredCloneReadInfoFromSource(T* aSource, uint32_t aDataIndex,
445 uint32_t aFileIdsIndex,
446 const DatabaseFileManager& aFileManager,
447 const Maybe<CipherKey>& aMaybeKey) {
448 MOZ_ASSERT(!IsOnBackgroundThread());
449 MOZ_ASSERT(aSource);
450
451 QM_TRY_INSPECT(const int32_t& columnType,
452 MOZ_TO_RESULT_INVOKE(aSource, GetTypeOfIndex, aDataIndex));
453
454 QM_TRY_INSPECT(const bool& isNull,
455 MOZ_TO_RESULT_INVOKE(aSource, GetIsNull, aFileIdsIndex));
456
457 QM_TRY_INSPECT(const nsString& fileIds, ([aSource, aFileIdsIndex, isNull] {
458 return isNull ? Result<nsString, nsresult>{VoidString()}
459 : MOZ_TO_RESULT_INVOKE_TYPED(nsString, aSource,
460 GetString,
461 aFileIdsIndex);
462 }()));
463
464 switch (columnType) {
465 case mozIStorageStatement::VALUE_TYPE_INTEGER: {
466 QM_TRY_INSPECT(const int64_t& intData,
467 MOZ_TO_RESULT_INVOKE(aSource, GetInt64, aDataIndex));
468
469 uint64_t uintData;
470 memcpy(&uintData, &intData, sizeof(uint64_t));
471
472 return GetStructuredCloneReadInfoFromExternalBlob(uintData, aFileManager,
473 fileIds, aMaybeKey);
474 }
475
476 case mozIStorageStatement::VALUE_TYPE_BLOB: {
477 const uint8_t* blobData;
478 uint32_t blobDataLength;
479 QM_TRY(aSource->GetSharedBlob(aDataIndex, &blobDataLength, &blobData));
480
481 return GetStructuredCloneReadInfoFromBlob(
482 blobData, blobDataLength, aFileManager, fileIds, aMaybeKey);
483 }
484
485 default:
486 return Err(NS_ERROR_FILE_CORRUPTED);
487 }
488 }
489
490 } // namespace
491
IndexDataValue()492 IndexDataValue::IndexDataValue() : mIndexId(0), mUnique(false) {
493 MOZ_COUNT_CTOR(IndexDataValue);
494 }
495
496 #ifdef NS_BUILD_REFCNT_LOGGING
IndexDataValue(IndexDataValue && aOther)497 IndexDataValue::IndexDataValue(IndexDataValue&& aOther)
498 : mIndexId(aOther.mIndexId),
499 mPosition(std::move(aOther.mPosition)),
500 mLocaleAwarePosition(std::move(aOther.mLocaleAwarePosition)),
501 mUnique(aOther.mUnique) {
502 MOZ_ASSERT(!aOther.mPosition.IsUnset());
503
504 MOZ_COUNT_CTOR(IndexDataValue);
505 }
506 #endif
507
IndexDataValue(IndexOrObjectStoreId aIndexId,bool aUnique,const Key & aPosition)508 IndexDataValue::IndexDataValue(IndexOrObjectStoreId aIndexId, bool aUnique,
509 const Key& aPosition)
510 : mIndexId(aIndexId), mPosition(aPosition), mUnique(aUnique) {
511 MOZ_ASSERT(!aPosition.IsUnset());
512
513 MOZ_COUNT_CTOR(IndexDataValue);
514 }
515
IndexDataValue(IndexOrObjectStoreId aIndexId,bool aUnique,const Key & aPosition,const Key & aLocaleAwarePosition)516 IndexDataValue::IndexDataValue(IndexOrObjectStoreId aIndexId, bool aUnique,
517 const Key& aPosition,
518 const Key& aLocaleAwarePosition)
519 : mIndexId(aIndexId),
520 mPosition(aPosition),
521 mLocaleAwarePosition(aLocaleAwarePosition),
522 mUnique(aUnique) {
523 MOZ_ASSERT(!aPosition.IsUnset());
524
525 MOZ_COUNT_CTOR(IndexDataValue);
526 }
527
operator ==(const IndexDataValue & aOther) const528 bool IndexDataValue::operator==(const IndexDataValue& aOther) const {
529 if (mIndexId != aOther.mIndexId) {
530 return false;
531 }
532 if (mLocaleAwarePosition.IsUnset()) {
533 return mPosition == aOther.mPosition;
534 }
535 return mLocaleAwarePosition == aOther.mLocaleAwarePosition;
536 }
537
operator <(const IndexDataValue & aOther) const538 bool IndexDataValue::operator<(const IndexDataValue& aOther) const {
539 if (mIndexId == aOther.mIndexId) {
540 if (mLocaleAwarePosition.IsUnset()) {
541 return mPosition < aOther.mPosition;
542 }
543 return mLocaleAwarePosition < aOther.mLocaleAwarePosition;
544 }
545
546 return mIndexId < aOther.mIndexId;
547 }
548
GetSandbox(JSContext * aCx)549 JSObject* GetSandbox(JSContext* aCx) {
550 SandboxHolder* holder = SandboxHolder::GetOrCreate();
551 return holder->GetSandboxInternal(aCx);
552 }
553
554 Result<std::pair<UniqueFreePtr<uint8_t>, uint32_t>, nsresult>
MakeCompressedIndexDataValues(const nsTArray<IndexDataValue> & aIndexValues)555 MakeCompressedIndexDataValues(const nsTArray<IndexDataValue>& aIndexValues) {
556 MOZ_ASSERT(!NS_IsMainThread());
557 MOZ_ASSERT(!IsOnBackgroundThread());
558
559 AUTO_PROFILER_LABEL("MakeCompressedIndexDataValues", DOM);
560
561 const uint32_t arrayLength = aIndexValues.Length();
562 if (!arrayLength) {
563 return std::pair{UniqueFreePtr<uint8_t>{}, 0u};
564 }
565
566 // First calculate the size of the final buffer.
567 const auto blobDataLength = std::accumulate(
568 aIndexValues.cbegin(), aIndexValues.cend(), CheckedUint32(0),
569 [](CheckedUint32 sum, const IndexDataValue& info) {
570 const nsCString& keyBuffer = info.mPosition.GetBuffer();
571 const nsCString& sortKeyBuffer = info.mLocaleAwarePosition.GetBuffer();
572 const uint32_t keyBufferLength = keyBuffer.Length();
573 const uint32_t sortKeyBufferLength = sortKeyBuffer.Length();
574
575 MOZ_ASSERT(!keyBuffer.IsEmpty());
576
577 return sum + CompressedByteCountForIndexId(info.mIndexId) +
578 CompressedByteCountForNumber(keyBufferLength) +
579 CompressedByteCountForNumber(sortKeyBufferLength) +
580 keyBufferLength + sortKeyBufferLength;
581 });
582
583 QM_TRY(OkIf(blobDataLength.isValid()),
584 Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
585 IDB_REPORT_INTERNAL_ERR_LAMBDA);
586
587 UniqueFreePtr<uint8_t> blobData(
588 static_cast<uint8_t*>(malloc(blobDataLength.value())));
589 QM_TRY(OkIf(static_cast<bool>(blobData)), Err(NS_ERROR_OUT_OF_MEMORY),
590 IDB_REPORT_INTERNAL_ERR_LAMBDA);
591
592 uint8_t* blobDataIter = blobData.get();
593
594 for (const IndexDataValue& info : aIndexValues) {
595 const nsCString& keyBuffer = info.mPosition.GetBuffer();
596 const nsCString& sortKeyBuffer = info.mLocaleAwarePosition.GetBuffer();
597 const uint32_t keyBufferLength = keyBuffer.Length();
598 const uint32_t sortKeyBufferLength = sortKeyBuffer.Length();
599
600 WriteCompressedIndexId(info.mIndexId, info.mUnique, &blobDataIter);
601 WriteCompressedNumber(keyBufferLength, &blobDataIter);
602
603 memcpy(blobDataIter, keyBuffer.get(), keyBufferLength);
604 blobDataIter += keyBufferLength;
605
606 WriteCompressedNumber(sortKeyBufferLength, &blobDataIter);
607
608 memcpy(blobDataIter, sortKeyBuffer.get(), sortKeyBufferLength);
609 blobDataIter += sortKeyBufferLength;
610 }
611
612 MOZ_ASSERT(blobDataIter == blobData.get() + blobDataLength.value());
613
614 return std::pair{std::move(blobData), blobDataLength.value()};
615 }
616
ReadCompressedIndexDataValues(mozIStorageStatement & aStatement,uint32_t aColumnIndex,nsTArray<IndexDataValue> & aOutIndexValues)617 nsresult ReadCompressedIndexDataValues(
618 mozIStorageStatement& aStatement, uint32_t aColumnIndex,
619 nsTArray<IndexDataValue>& aOutIndexValues) {
620 return ReadCompressedIndexDataValuesFromSource(aStatement, aColumnIndex,
621 &aOutIndexValues);
622 }
623
624 template <typename T>
ReadCompressedIndexDataValues(T & aValues,uint32_t aColumnIndex)625 Result<IndexDataValuesAutoArray, nsresult> ReadCompressedIndexDataValues(
626 T& aValues, uint32_t aColumnIndex) {
627 return ToResultInvoke<IndexDataValuesAutoArray>(
628 &ReadCompressedIndexDataValuesFromSource<T>, aValues, aColumnIndex);
629 }
630
631 template Result<IndexDataValuesAutoArray, nsresult>
632 ReadCompressedIndexDataValues<mozIStorageValueArray>(mozIStorageValueArray&,
633 uint32_t);
634
635 template Result<IndexDataValuesAutoArray, nsresult>
636 ReadCompressedIndexDataValues<mozIStorageStatement>(mozIStorageStatement&,
637 uint32_t);
638
639 Result<std::tuple<IndexOrObjectStoreId, bool, Span<const uint8_t>>, nsresult>
ReadCompressedIndexId(const Span<const uint8_t> aData)640 ReadCompressedIndexId(const Span<const uint8_t> aData) {
641 QM_TRY_INSPECT((const auto& [indexId, remainder]),
642 ReadCompressedNumber(aData));
643
644 MOZ_ASSERT(UINT64_MAX / 2 >= uint64_t(indexId), "Bad index id!");
645
646 return std::tuple{IndexOrObjectStoreId(indexId >> 1), indexId % 2 == 1,
647 remainder};
648 }
649
650 Result<std::pair<uint64_t, mozilla::Span<const uint8_t>>, nsresult>
ReadCompressedNumber(const Span<const uint8_t> aSpan)651 ReadCompressedNumber(const Span<const uint8_t> aSpan) {
652 uint8_t shiftCounter = 0;
653 uint64_t result = 0;
654
655 const auto end = aSpan.cend();
656
657 const auto newPos =
658 std::find_if(aSpan.cbegin(), end, [&result, &shiftCounter](uint8_t byte) {
659 MOZ_ASSERT(shiftCounter <= 56, "Shifted too many bits!");
660
661 result += (uint64_t(byte & 0x7f) << shiftCounter);
662 shiftCounter += 7;
663
664 return !(byte & 0x80);
665 });
666
667 QM_TRY(OkIf(newPos != end), Err(NS_ERROR_FILE_CORRUPTED), [](const auto&) {
668 MOZ_ASSERT(false);
669 IDB_REPORT_INTERNAL_ERR();
670 });
671
672 return std::pair{result, Span{newPos + 1, end}};
673 }
674
675 Result<StructuredCloneReadInfoParent, nsresult>
GetStructuredCloneReadInfoFromValueArray(mozIStorageValueArray * aValues,uint32_t aDataIndex,uint32_t aFileIdsIndex,const DatabaseFileManager & aFileManager,const Maybe<CipherKey> & aMaybeKey)676 GetStructuredCloneReadInfoFromValueArray(
677 mozIStorageValueArray* aValues, uint32_t aDataIndex, uint32_t aFileIdsIndex,
678 const DatabaseFileManager& aFileManager,
679 const Maybe<CipherKey>& aMaybeKey) {
680 return GetStructuredCloneReadInfoFromSource(
681 aValues, aDataIndex, aFileIdsIndex, aFileManager, aMaybeKey);
682 }
683
684 Result<StructuredCloneReadInfoParent, nsresult>
GetStructuredCloneReadInfoFromStatement(mozIStorageStatement * aStatement,uint32_t aDataIndex,uint32_t aFileIdsIndex,const DatabaseFileManager & aFileManager,const Maybe<CipherKey> & aMaybeKey)685 GetStructuredCloneReadInfoFromStatement(mozIStorageStatement* aStatement,
686 uint32_t aDataIndex,
687 uint32_t aFileIdsIndex,
688 const DatabaseFileManager& aFileManager,
689 const Maybe<CipherKey>& aMaybeKey) {
690 return GetStructuredCloneReadInfoFromSource(
691 aStatement, aDataIndex, aFileIdsIndex, aFileManager, aMaybeKey);
692 }
693
694 Result<nsTArray<StructuredCloneFileParent>, nsresult>
DeserializeStructuredCloneFiles(const DatabaseFileManager & aFileManager,const nsAString & aText)695 DeserializeStructuredCloneFiles(const DatabaseFileManager& aFileManager,
696 const nsAString& aText) {
697 MOZ_ASSERT(!IsOnBackgroundThread());
698
699 nsTArray<StructuredCloneFileParent> result;
700 for (const auto& token :
701 nsCharSeparatedTokenizerTemplate<NS_TokenizerIgnoreNothing>(aText, ' ')
702 .ToRange()) {
703 MOZ_ASSERT(!token.IsEmpty());
704
705 QM_TRY_UNWRAP(auto structuredCloneFile,
706 DeserializeStructuredCloneFile(aFileManager, token));
707
708 result.EmplaceBack(std::move(structuredCloneFile));
709 }
710
711 return result;
712 }
713
ExecuteSimpleSQLSequence(mozIStorageConnection & aConnection,Span<const nsLiteralCString> aSQLCommands)714 nsresult ExecuteSimpleSQLSequence(mozIStorageConnection& aConnection,
715 Span<const nsLiteralCString> aSQLCommands) {
716 for (const auto& aSQLCommand : aSQLCommands) {
717 const auto extraInfo = quota::ScopedLogExtraInfo{
718 quota::ScopedLogExtraInfo::kTagQuery, aSQLCommand};
719
720 QM_TRY(aConnection.ExecuteSimpleSQL(aSQLCommand));
721 }
722
723 return NS_OK;
724 }
725
726 } // namespace mozilla::dom::indexedDB
727