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