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
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "mozilla/dom/IndexedDatabase.h"
8 #include "IndexedDatabaseInlines.h"
9 
10 #include "IDBDatabase.h"
11 #include "IDBMutableFile.h"
12 
13 #include "mozilla/dom/FileBlobImpl.h"
14 #include "mozilla/dom/StructuredCloneTags.h"
15 #include "mozilla/dom/WorkerPrivate.h"
16 #include "mozilla/dom/WorkerScope.h"
17 #include "MainThreadUtils.h"
18 #include "jsapi.h"
19 #include "nsIFile.h"
20 #include "nsIGlobalObject.h"
21 #include "nsQueryObject.h"
22 #include "nsString.h"
23 
24 namespace mozilla::dom::indexedDB {
25 namespace {
26 struct MOZ_STACK_CLASS MutableFileData final {
27   nsString type;
28   nsString name;
29 
30   MOZ_COUNTED_DEFAULT_CTOR(MutableFileData)
31 
32   MOZ_COUNTED_DTOR(MutableFileData)
33 };
34 
35 struct MOZ_STACK_CLASS BlobOrFileData final {
36   uint32_t tag = 0;
37   uint64_t size = 0;
38   nsString type;
39   nsString name;
40   int64_t lastModifiedDate = INT64_MAX;
41 
42   MOZ_COUNTED_DEFAULT_CTOR(BlobOrFileData)
43 
44   MOZ_COUNTED_DTOR(BlobOrFileData)
45 };
46 
47 struct MOZ_STACK_CLASS WasmModuleData final {
48   uint32_t bytecodeIndex;
49   uint32_t compiledIndex;
50   uint32_t flags;
51 
WasmModuleDatamozilla::dom::indexedDB::__anon5a3e21f70111::WasmModuleData52   explicit WasmModuleData(uint32_t aFlags)
53       : bytecodeIndex(0), compiledIndex(0), flags(aFlags) {
54     MOZ_COUNT_CTOR(WasmModuleData);
55   }
56 
57   MOZ_COUNTED_DTOR(WasmModuleData)
58 };
59 
StructuredCloneReadString(JSStructuredCloneReader * aReader,nsCString & aString)60 bool StructuredCloneReadString(JSStructuredCloneReader* aReader,
61                                nsCString& aString) {
62   uint32_t length;
63   if (!JS_ReadBytes(aReader, &length, sizeof(uint32_t))) {
64     NS_WARNING("Failed to read length!");
65     return false;
66   }
67   length = NativeEndian::swapFromLittleEndian(length);
68 
69   if (!aString.SetLength(length, fallible)) {
70     NS_WARNING("Out of memory?");
71     return false;
72   }
73   char* const buffer = aString.BeginWriting();
74 
75   if (!JS_ReadBytes(aReader, buffer, length)) {
76     NS_WARNING("Failed to read type!");
77     return false;
78   }
79 
80   return true;
81 }
82 
ReadFileHandle(JSStructuredCloneReader * aReader,MutableFileData * aRetval)83 bool ReadFileHandle(JSStructuredCloneReader* aReader,
84                     MutableFileData* aRetval) {
85   static_assert(SCTAG_DOM_MUTABLEFILE == 0xFFFF8004, "Update me!");
86   MOZ_ASSERT(aReader && aRetval);
87 
88   nsCString type;
89   if (!StructuredCloneReadString(aReader, type)) {
90     return false;
91   }
92   CopyUTF8toUTF16(type, aRetval->type);
93 
94   nsCString name;
95   if (!StructuredCloneReadString(aReader, name)) {
96     return false;
97   }
98   CopyUTF8toUTF16(name, aRetval->name);
99 
100   return true;
101 }
102 
ReadBlobOrFile(JSStructuredCloneReader * aReader,uint32_t aTag,BlobOrFileData * aRetval)103 bool ReadBlobOrFile(JSStructuredCloneReader* aReader, uint32_t aTag,
104                     BlobOrFileData* aRetval) {
105   static_assert(SCTAG_DOM_BLOB == 0xffff8001 &&
106                     SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE == 0xffff8002 &&
107                     SCTAG_DOM_FILE == 0xffff8005,
108                 "Update me!");
109 
110   MOZ_ASSERT(aReader);
111   MOZ_ASSERT(aTag == SCTAG_DOM_FILE ||
112              aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE ||
113              aTag == SCTAG_DOM_BLOB);
114   MOZ_ASSERT(aRetval);
115 
116   aRetval->tag = aTag;
117 
118   uint64_t size;
119   if (NS_WARN_IF(!JS_ReadBytes(aReader, &size, sizeof(uint64_t)))) {
120     return false;
121   }
122 
123   aRetval->size = NativeEndian::swapFromLittleEndian(size);
124 
125   nsCString type;
126   if (NS_WARN_IF(!StructuredCloneReadString(aReader, type))) {
127     return false;
128   }
129 
130   CopyUTF8toUTF16(type, aRetval->type);
131 
132   // Blobs are done.
133   if (aTag == SCTAG_DOM_BLOB) {
134     return true;
135   }
136 
137   MOZ_ASSERT(aTag == SCTAG_DOM_FILE ||
138              aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE);
139 
140   int64_t lastModifiedDate;
141   if (aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE) {
142     lastModifiedDate = INT64_MAX;
143   } else {
144     if (NS_WARN_IF(!JS_ReadBytes(aReader, &lastModifiedDate,
145                                  sizeof(lastModifiedDate)))) {
146       return false;
147     }
148     lastModifiedDate = NativeEndian::swapFromLittleEndian(lastModifiedDate);
149   }
150 
151   aRetval->lastModifiedDate = lastModifiedDate;
152 
153   nsCString name;
154   if (NS_WARN_IF(!StructuredCloneReadString(aReader, name))) {
155     return false;
156   }
157 
158   CopyUTF8toUTF16(name, aRetval->name);
159 
160   return true;
161 }
162 
ReadWasmModule(JSStructuredCloneReader * aReader,WasmModuleData * aRetval)163 bool ReadWasmModule(JSStructuredCloneReader* aReader, WasmModuleData* aRetval) {
164   static_assert(SCTAG_DOM_WASM_MODULE == 0xFFFF8006, "Update me!");
165   MOZ_ASSERT(aReader && aRetval);
166 
167   uint32_t bytecodeIndex;
168   uint32_t compiledIndex;
169   if (NS_WARN_IF(!JS_ReadUint32Pair(aReader, &bytecodeIndex, &compiledIndex))) {
170     return false;
171   }
172 
173   aRetval->bytecodeIndex = bytecodeIndex;
174   aRetval->compiledIndex = compiledIndex;
175 
176   return true;
177 }
178 
179 template <typename StructuredCloneFile>
180 class ValueDeserializationHelper;
181 
182 class ValueDeserializationHelperBase {
183  public:
CreateAndWrapWasmModule(JSContext * aCx,const StructuredCloneFileBase & aFile,const WasmModuleData & aData,JS::MutableHandle<JSObject * > aResult)184   static bool CreateAndWrapWasmModule(JSContext* aCx,
185                                       const StructuredCloneFileBase& aFile,
186                                       const WasmModuleData& aData,
187                                       JS::MutableHandle<JSObject*> aResult) {
188     MOZ_ASSERT(aCx);
189     MOZ_ASSERT(aFile.Type() == StructuredCloneFileBase::eWasmBytecode);
190 
191     // Both on the parent and child side, just create a plain object here,
192     // support for de-serialization of WebAssembly.Modules has been removed in
193     // bug 1561876. Full removal is tracked in bug 1487479.
194 
195     JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
196     if (NS_WARN_IF(!obj)) {
197       return false;
198     }
199 
200     aResult.set(obj);
201     return true;
202   }
203 
204   template <typename StructuredCloneFile>
CreateAndWrapBlobOrFile(JSContext * aCx,IDBDatabase * aDatabase,const StructuredCloneFile & aFile,const BlobOrFileData & aData,JS::MutableHandle<JSObject * > aResult)205   static bool CreateAndWrapBlobOrFile(JSContext* aCx, IDBDatabase* aDatabase,
206                                       const StructuredCloneFile& aFile,
207                                       const BlobOrFileData& aData,
208                                       JS::MutableHandle<JSObject*> aResult) {
209     MOZ_ASSERT(aCx);
210     MOZ_ASSERT(aData.tag == SCTAG_DOM_FILE ||
211                aData.tag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE ||
212                aData.tag == SCTAG_DOM_BLOB);
213     MOZ_ASSERT(aFile.Type() == StructuredCloneFileBase::eBlob);
214 
215     const auto blob = ValueDeserializationHelper<StructuredCloneFile>::GetBlob(
216         aCx, aDatabase, aFile);
217     if (NS_WARN_IF(!blob)) {
218       return false;
219     }
220 
221     if (aData.tag == SCTAG_DOM_BLOB) {
222       blob->Impl()->SetLazyData(VoidString(), aData.type, aData.size,
223                                 INT64_MAX);
224       MOZ_ASSERT(!blob->IsFile());
225 
226       // XXX The comment below is somewhat confusing, since it seems to imply
227       // that this branch is only executed when called from ActorsParent, but
228       // it's executed from both the parent and the child side code.
229 
230       // ActorsParent sends here a kind of half blob and half file wrapped into
231       // a DOM File object. DOM File and DOM Blob are a WebIDL wrapper around a
232       // BlobImpl object. SetLazyData() has just changed the BlobImpl to be a
233       // Blob (see the previous assert), but 'blob' still has the WebIDL DOM
234       // File wrapping.
235       // Before exposing it to content, we must recreate a DOM Blob object.
236 
237       const RefPtr<Blob> exposedBlob =
238           Blob::Create(blob->GetParentObject(), blob->Impl());
239       if (NS_WARN_IF(!exposedBlob)) {
240         return false;
241       }
242 
243       return WrapAsJSObject(aCx, exposedBlob, aResult);
244     }
245 
246     blob->Impl()->SetLazyData(aData.name, aData.type, aData.size,
247                               aData.lastModifiedDate * PR_USEC_PER_MSEC);
248 
249     MOZ_ASSERT(blob->IsFile());
250     const RefPtr<File> file = blob->ToFile();
251     MOZ_ASSERT(file);
252 
253     return WrapAsJSObject(aCx, file, aResult);
254   }
255 };
256 
257 template <>
258 class ValueDeserializationHelper<StructuredCloneFileParent>
259     : public ValueDeserializationHelperBase {
260  public:
CreateAndWrapMutableFile(JSContext * aCx,StructuredCloneFileParent & aFile,const MutableFileData & aData,JS::MutableHandle<JSObject * > aResult)261   static bool CreateAndWrapMutableFile(JSContext* aCx,
262                                        StructuredCloneFileParent& aFile,
263                                        const MutableFileData& aData,
264                                        JS::MutableHandle<JSObject*> aResult) {
265     MOZ_ASSERT(aCx);
266     MOZ_ASSERT(aFile.Type() == StructuredCloneFileBase::eBlob);
267 
268     // We are in an IDB SQLite schema upgrade where we don't care about a real
269     // 'MutableFile', but we just care of having a proper |mType| flag.
270 
271     aFile.MutateType(StructuredCloneFileBase::eMutableFile);
272 
273     // Just make a dummy object.
274     JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
275 
276     if (NS_WARN_IF(!obj)) {
277       return false;
278     }
279 
280     aResult.set(obj);
281     return true;
282   }
283 
GetBlob(JSContext * aCx,IDBDatabase * aDatabase,const StructuredCloneFileParent & aFile)284   static RefPtr<Blob> GetBlob(JSContext* aCx, IDBDatabase* aDatabase,
285                               const StructuredCloneFileParent& aFile) {
286     // This is chrome code, so there is no parent, but still we want to set a
287     // correct parent for the new File object.
288     const auto global = [aDatabase, aCx]() -> nsCOMPtr<nsIGlobalObject> {
289       if (NS_IsMainThread()) {
290         if (aDatabase && aDatabase->GetParentObject()) {
291           return aDatabase->GetParentObject();
292         }
293         return xpc::CurrentNativeGlobal(aCx);
294       }
295       const WorkerPrivate* const workerPrivate =
296           GetCurrentThreadWorkerPrivate();
297       MOZ_ASSERT(workerPrivate);
298 
299       WorkerGlobalScope* const globalScope = workerPrivate->GlobalScope();
300       MOZ_ASSERT(globalScope);
301 
302       return do_QueryObject(globalScope);
303     }();
304 
305     MOZ_ASSERT(global);
306 
307     // We do not have an mBlob but do have a DatabaseFileInfo.
308     //
309     // If we are creating an index, we do need a real-looking Blob/File instance
310     // because the index's key path can reference their properties.  Rather than
311     // create a fake-looking object, create a real Blob.
312     //
313     // If we are in a schema upgrade, we don't strictly need that, but we do not
314     // need to optimize for that, and create it anyway.
315     const nsCOMPtr<nsIFile> file = aFile.FileInfo().GetFileForFileInfo();
316     if (!file) {
317       return nullptr;
318     }
319 
320     const auto impl = MakeRefPtr<FileBlobImpl>(file);
321     impl->SetFileId(aFile.FileInfo().Id());
322     return File::Create(global, impl);
323   }
324 };
325 
326 template <>
327 class ValueDeserializationHelper<StructuredCloneFileChild>
328     : public ValueDeserializationHelperBase {
329  public:
CreateAndWrapMutableFile(JSContext * aCx,StructuredCloneFileChild & aFile,const MutableFileData & aData,JS::MutableHandle<JSObject * > aResult)330   static bool CreateAndWrapMutableFile(JSContext* aCx,
331                                        StructuredCloneFileChild& aFile,
332                                        const MutableFileData& aData,
333                                        JS::MutableHandle<JSObject*> aResult) {
334     MOZ_ASSERT(aCx);
335     MOZ_ASSERT(aFile.Type() == StructuredCloneFileBase::eMutableFile);
336 
337     // If either MutableFile is disabled (via a pref) and we don't have a
338     // mutable file here, or we are on a DOM worker and MutableFile is not
339     // supported on workers, return false to indicate that.
340     if (!aFile.HasMutableFile() || !NS_IsMainThread()) {
341       return false;
342     }
343 
344     aFile.MutableMutableFile().SetLazyData(aData.name, aData.type);
345 
346     return WrapAsJSObject(aCx, aFile.MutableMutableFile(), aResult);
347   }
348 
GetBlob(JSContext * aCx,IDBDatabase * aDatabase,const StructuredCloneFileChild & aFile)349   static RefPtr<Blob> GetBlob(JSContext* aCx, IDBDatabase* aDatabase,
350                               const StructuredCloneFileChild& aFile) {
351     if (aFile.HasBlob()) {
352       return aFile.BlobPtr();
353     }
354 
355     MOZ_CRASH("Expected a StructuredCloneFile with a Blob");
356   }
357 };
358 
359 }  // namespace
360 
361 template <typename StructuredCloneReadInfo>
CommonStructuredCloneReadCallback(JSContext * aCx,JSStructuredCloneReader * aReader,const JS::CloneDataPolicy & aCloneDataPolicy,uint32_t aTag,uint32_t aData,StructuredCloneReadInfo * aCloneReadInfo,IDBDatabase * aDatabase)362 JSObject* CommonStructuredCloneReadCallback(
363     JSContext* aCx, JSStructuredCloneReader* aReader,
364     const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag, uint32_t aData,
365     StructuredCloneReadInfo* aCloneReadInfo, IDBDatabase* aDatabase) {
366   // We need to statically assert that our tag values are what we expect
367   // so that if people accidentally change them they notice.
368   static_assert(SCTAG_DOM_BLOB == 0xffff8001 &&
369                     SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE == 0xffff8002 &&
370                     SCTAG_DOM_MUTABLEFILE == 0xffff8004 &&
371                     SCTAG_DOM_FILE == 0xffff8005 &&
372                     SCTAG_DOM_WASM_MODULE == 0xffff8006,
373                 "You changed our structured clone tag values and just ate "
374                 "everyone's IndexedDB data.  I hope you are happy.");
375 
376   using StructuredCloneFile =
377       typename StructuredCloneReadInfo::StructuredCloneFile;
378 
379   if (aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE ||
380       aTag == SCTAG_DOM_BLOB || aTag == SCTAG_DOM_FILE ||
381       aTag == SCTAG_DOM_MUTABLEFILE || aTag == SCTAG_DOM_WASM_MODULE) {
382     JS::Rooted<JSObject*> result(aCx);
383 
384     if (aTag == SCTAG_DOM_WASM_MODULE) {
385       WasmModuleData data(aData);
386       if (NS_WARN_IF(!ReadWasmModule(aReader, &data))) {
387         return nullptr;
388       }
389 
390       MOZ_ASSERT(data.compiledIndex == data.bytecodeIndex + 1);
391       MOZ_ASSERT(!data.flags);
392 
393       const auto& files = aCloneReadInfo->Files();
394       if (data.bytecodeIndex >= files.Length() ||
395           data.compiledIndex >= files.Length()) {
396         MOZ_ASSERT(false, "Bad index value!");
397         return nullptr;
398       }
399 
400       const auto& file = files[data.bytecodeIndex];
401 
402       if (NS_WARN_IF(!ValueDeserializationHelper<StructuredCloneFile>::
403                          CreateAndWrapWasmModule(aCx, file, data, &result))) {
404         return nullptr;
405       }
406 
407       return result;
408     }
409 
410     if (aData >= aCloneReadInfo->Files().Length()) {
411       MOZ_ASSERT(false, "Bad index value!");
412       return nullptr;
413     }
414 
415     auto& file = aCloneReadInfo->MutableFile(aData);
416 
417     if (aTag == SCTAG_DOM_MUTABLEFILE) {
418       MutableFileData data;
419       if (NS_WARN_IF(!ReadFileHandle(aReader, &data))) {
420         return nullptr;
421       }
422 
423       if (NS_WARN_IF(!ValueDeserializationHelper<StructuredCloneFile>::
424                          CreateAndWrapMutableFile(aCx, file, data, &result))) {
425         return nullptr;
426       }
427 
428       return result;
429     }
430 
431     BlobOrFileData data;
432     if (NS_WARN_IF(!ReadBlobOrFile(aReader, aTag, &data))) {
433       return nullptr;
434     }
435 
436     if (NS_WARN_IF(!ValueDeserializationHelper<
437                    StructuredCloneFile>::CreateAndWrapBlobOrFile(aCx, aDatabase,
438                                                                  file, data,
439                                                                  &result))) {
440       return nullptr;
441     }
442 
443     return result;
444   }
445 
446   return StructuredCloneHolder::ReadFullySerializableObjects(aCx, aReader,
447                                                              aTag);
448 }
449 
450 template JSObject* CommonStructuredCloneReadCallback(
451     JSContext* aCx, JSStructuredCloneReader* aReader,
452     const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag, uint32_t aData,
453     StructuredCloneReadInfoChild* aCloneReadInfo, IDBDatabase* aDatabase);
454 
455 template JSObject* CommonStructuredCloneReadCallback(
456     JSContext* aCx, JSStructuredCloneReader* aReader,
457     const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag, uint32_t aData,
458     StructuredCloneReadInfoParent* aCloneReadInfo, IDBDatabase* aDatabase);
459 }  // namespace mozilla::dom::indexedDB
460