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