IndexedDatabase.cpp (18539B)
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 9 #include "IDBDatabase.h" 10 #include "IndexedDatabaseInlines.h" 11 #include "MainThreadUtils.h" 12 #include "jsapi.h" 13 #include "mozilla/dom/FileBlobImpl.h" 14 #include "mozilla/dom/FileList.h" 15 #include "mozilla/dom/StructuredCloneTags.h" 16 #include "mozilla/dom/URLSearchParams.h" 17 #include "mozilla/dom/WorkerPrivate.h" 18 #include "mozilla/dom/WorkerScope.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 52 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 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 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 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 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: 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> 205 static already_AddRefed<File> CreateUnwrappedFile( 206 JSContext* aCx, IDBDatabase* aDatabase, const StructuredCloneFile& aFile, 207 const BlobOrFileData& aData) { 208 MOZ_ASSERT(aCx); 209 MOZ_ASSERT(aData.tag == SCTAG_DOM_FILE || 210 aData.tag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE); 211 MOZ_ASSERT(aFile.Type() == StructuredCloneFileBase::eBlob); 212 213 const auto blob = ValueDeserializationHelper<StructuredCloneFile>::GetBlob( 214 aCx, aDatabase, aFile); 215 if (NS_WARN_IF(!blob)) { 216 return nullptr; 217 } 218 219 blob->Impl()->SetLazyData(aData.name, aData.type, aData.size, 220 aData.lastModifiedDate * PR_USEC_PER_MSEC); 221 222 MOZ_ASSERT(blob->IsFile()); 223 RefPtr<File> file = blob->ToFile(); 224 MOZ_ASSERT(file); 225 226 return file.forget(); 227 } 228 229 template <typename StructuredCloneFile> 230 static bool CreateAndWrapBlobOrFile(JSContext* aCx, IDBDatabase* aDatabase, 231 const StructuredCloneFile& aFile, 232 const BlobOrFileData& aData, 233 JS::MutableHandle<JSObject*> aResult) { 234 MOZ_ASSERT(aCx); 235 MOZ_ASSERT(aData.tag == SCTAG_DOM_FILE || 236 aData.tag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE || 237 aData.tag == SCTAG_DOM_BLOB); 238 MOZ_ASSERT(aFile.Type() == StructuredCloneFileBase::eBlob); 239 240 if (aData.tag == SCTAG_DOM_FILE || 241 aData.tag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE) { 242 RefPtr<File> file = 243 ValueDeserializationHelper<StructuredCloneFile>::CreateUnwrappedFile( 244 aCx, aDatabase, aFile, aData); 245 return WrapAsJSObject(aCx, file, aResult); 246 } 247 248 const auto blob = ValueDeserializationHelper<StructuredCloneFile>::GetBlob( 249 aCx, aDatabase, aFile); 250 if (NS_WARN_IF(!blob)) { 251 return false; 252 } 253 254 MOZ_ASSERT(aData.tag == SCTAG_DOM_BLOB); 255 blob->Impl()->SetLazyData(VoidString(), aData.type, aData.size, INT64_MAX); 256 MOZ_ASSERT(!blob->IsFile()); 257 258 // XXX The comment below is somewhat confusing, since it seems to imply 259 // that this branch is only executed when called from ActorsParent, but 260 // it's executed from both the parent and the child side code. 261 262 // ActorsParent sends here a kind of half blob and half file wrapped into 263 // a DOM File object. DOM File and DOM Blob are a WebIDL wrapper around a 264 // BlobImpl object. SetLazyData() has just changed the BlobImpl to be a 265 // Blob (see the previous assert), but 'blob' still has the WebIDL DOM 266 // File wrapping. 267 // Before exposing it to content, we must recreate a DOM Blob object. 268 269 const RefPtr<Blob> exposedBlob = 270 Blob::Create(blob->GetParentObject(), blob->Impl()); 271 if (NS_WARN_IF(!exposedBlob)) { 272 return false; 273 } 274 275 return WrapAsJSObject(aCx, exposedBlob, aResult); 276 } 277 }; 278 279 template <> 280 class ValueDeserializationHelper<StructuredCloneFileParent> 281 : public ValueDeserializationHelperBase { 282 public: 283 static bool CreateAndWrapMutableFile(JSContext* aCx, 284 StructuredCloneFileParent& aFile, 285 const MutableFileData& aData, 286 JS::MutableHandle<JSObject*> aResult) { 287 MOZ_ASSERT(aCx); 288 MOZ_ASSERT(aFile.Type() == StructuredCloneFileBase::eBlob); 289 290 // We are in an IDB SQLite schema upgrade where we don't care about a real 291 // 'MutableFile', but we just care of having a proper |mType| flag. 292 293 aFile.MutateType(StructuredCloneFileBase::eMutableFile); 294 295 // Just make a dummy object. 296 JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx)); 297 298 if (NS_WARN_IF(!obj)) { 299 return false; 300 } 301 302 aResult.set(obj); 303 return true; 304 } 305 306 static RefPtr<Blob> GetBlob(JSContext* aCx, IDBDatabase* aDatabase, 307 const StructuredCloneFileParent& aFile) { 308 // This is chrome code, so there is no parent, but still we want to set a 309 // correct parent for the new File object. 310 const auto global = [aDatabase, aCx]() -> nsCOMPtr<nsIGlobalObject> { 311 if (NS_IsMainThread()) { 312 if (aDatabase && aDatabase->GetParentObject()) { 313 return aDatabase->GetParentObject(); 314 } 315 return xpc::CurrentNativeGlobal(aCx); 316 } 317 const WorkerPrivate* const workerPrivate = 318 GetCurrentThreadWorkerPrivate(); 319 MOZ_ASSERT(workerPrivate); 320 321 WorkerGlobalScope* const globalScope = workerPrivate->GlobalScope(); 322 MOZ_ASSERT(globalScope); 323 324 return do_QueryObject(globalScope); 325 }(); 326 327 MOZ_ASSERT(global); 328 329 // We do not have an mBlob but do have a DatabaseFileInfo. 330 // 331 // If we are creating an index, we do need a real-looking Blob/File instance 332 // because the index's key path can reference their properties. Rather than 333 // create a fake-looking object, create a real Blob. 334 // 335 // If we are in a schema upgrade, we don't strictly need that, but we do not 336 // need to optimize for that, and create it anyway. 337 const nsCOMPtr<nsIFile> file = aFile.FileInfo().GetFileForFileInfo(); 338 if (!file) { 339 return nullptr; 340 } 341 342 const auto impl = MakeRefPtr<FileBlobImpl>(file); 343 impl->SetFileId(aFile.FileInfo().Id()); 344 return File::Create(global, impl); 345 } 346 }; 347 348 template <> 349 class ValueDeserializationHelper<StructuredCloneFileChild> 350 : public ValueDeserializationHelperBase { 351 public: 352 static bool CreateAndWrapMutableFile(JSContext* aCx, 353 StructuredCloneFileChild& aFile, 354 const MutableFileData& aData, 355 JS::MutableHandle<JSObject*> aResult) { 356 MOZ_ASSERT(aCx); 357 MOZ_ASSERT(aFile.Type() == StructuredCloneFileBase::eMutableFile); 358 359 return false; 360 } 361 362 static RefPtr<Blob> GetBlob(JSContext* aCx, IDBDatabase* aDatabase, 363 const StructuredCloneFileChild& aFile) { 364 if (aFile.HasBlob()) { 365 return aFile.BlobPtr(); 366 } 367 368 MOZ_CRASH("Expected a StructuredCloneFile with a Blob"); 369 } 370 }; 371 372 } // namespace 373 374 template <typename StructuredCloneReadInfo> 375 JSObject* CommonStructuredCloneReadCallback( 376 JSContext* aCx, JSStructuredCloneReader* aReader, 377 const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag, uint32_t aData, 378 StructuredCloneReadInfo* aCloneReadInfo, IDBDatabase* aDatabase) { 379 // We need to statically assert that our tag values are what we expect 380 // so that if people accidentally change them they notice. 381 static_assert(SCTAG_DOM_BLOB == 0xffff8001 && 382 SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE == 0xffff8002 && 383 SCTAG_DOM_MUTABLEFILE == 0xffff8004 && 384 SCTAG_DOM_FILE == 0xffff8005 && 385 SCTAG_DOM_WASM_MODULE == 0xffff8006 && 386 SCTAG_DOM_URLSEARCHPARAMS == 0xffff8014 && 387 SCTAG_DOM_FILELIST == 0xffff8003, 388 "You changed our structured clone tag values and just ate " 389 "everyone's IndexedDB data. I hope you are happy."); 390 391 if (aTag == SCTAG_DOM_URLSEARCHPARAMS) { 392 // Protect the result from a moving GC in ~RefPtr. 393 JS::Rooted<JSObject*> result(aCx); 394 395 { 396 // Scope for the RefPtr below. 397 398 nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); 399 if (!global) { 400 return nullptr; 401 } 402 403 RefPtr<URLSearchParams> params = new URLSearchParams(global); 404 405 uint32_t paramCount; 406 uint32_t zero; 407 if (!JS_ReadUint32Pair(aReader, ¶mCount, &zero)) { 408 return nullptr; 409 } 410 411 nsAutoString key; 412 nsAutoString value; 413 for (uint32_t index = 0; index < paramCount; index++) { 414 if (!StructuredCloneHolder::ReadString(aReader, key) || 415 !StructuredCloneHolder::ReadString(aReader, value)) { 416 return nullptr; 417 } 418 params->Append(NS_ConvertUTF16toUTF8(key), 419 NS_ConvertUTF16toUTF8(value)); 420 } 421 422 if (!WrapAsJSObject(aCx, params, &result)) { 423 return nullptr; 424 } 425 } 426 427 return result; 428 } 429 430 using StructuredCloneFile = 431 typename StructuredCloneReadInfo::StructuredCloneFile; 432 433 if (aTag == SCTAG_DOM_FILELIST) { 434 const auto& files = aCloneReadInfo->Files(); 435 436 uint32_t fileListLength = aData; 437 438 if (fileListLength > files.Length()) { 439 MOZ_ASSERT(false, "Bad file list length value!"); 440 441 return nullptr; 442 } 443 444 // We need to ensure that all RAII smart pointers which may trigger GC are 445 // destroyed on return prior to this JS::Rooted being destroyed and 446 // unrooting the pointer. This scope helps make this intent more explicit. 447 JS::Rooted<JSObject*> obj(aCx); 448 { 449 nsCOMPtr<nsIGlobalObject> global = xpc::CurrentNativeGlobal(aCx); 450 if (!global) { 451 MOZ_ASSERT(false, "Could not access global!"); 452 453 return nullptr; 454 } 455 456 RefPtr<FileList> fileList = new FileList(global); 457 458 for (uint32_t i = 0u; i < fileListLength; ++i) { 459 uint32_t tag = UINT32_MAX; 460 uint32_t index = UINT32_MAX; 461 if (!JS_ReadUint32Pair(aReader, &tag, &index)) { 462 return nullptr; 463 } 464 465 if (tag != SCTAG_DOM_FILE) { 466 MOZ_ASSERT(false, "Unexpected tag!"); 467 468 return nullptr; 469 } 470 471 if (uint64_t(index) >= files.Length()) { 472 MOZ_ASSERT(false, "Bad index!"); 473 474 return nullptr; 475 } 476 477 BlobOrFileData data; 478 if (NS_WARN_IF(!ReadBlobOrFile(aReader, tag, &data))) { 479 return nullptr; 480 } 481 482 auto& fileObj = aCloneReadInfo->MutableFile(index); 483 484 RefPtr<File> file = ValueDeserializationHelper< 485 StructuredCloneFile>::CreateUnwrappedFile(aCx, aDatabase, fileObj, 486 data); 487 if (!file) { 488 MOZ_ASSERT(false, "Could not deserialize file!"); 489 490 return nullptr; 491 } 492 493 if (!fileList->Append(file)) { 494 MOZ_ASSERT(false, "Could not extend filelist!"); 495 496 return nullptr; 497 } 498 } 499 500 if (!WrapAsJSObject(aCx, fileList, &obj)) { 501 return nullptr; 502 } 503 } 504 505 return obj; 506 } 507 508 if (aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE || 509 aTag == SCTAG_DOM_BLOB || aTag == SCTAG_DOM_FILE || 510 aTag == SCTAG_DOM_MUTABLEFILE || aTag == SCTAG_DOM_WASM_MODULE) { 511 JS::Rooted<JSObject*> result(aCx); 512 513 if (aTag == SCTAG_DOM_WASM_MODULE) { 514 WasmModuleData data(aData); 515 if (NS_WARN_IF(!ReadWasmModule(aReader, &data))) { 516 return nullptr; 517 } 518 519 MOZ_ASSERT(data.compiledIndex == data.bytecodeIndex + 1); 520 MOZ_ASSERT(!data.flags); 521 522 const auto& files = aCloneReadInfo->Files(); 523 if (data.bytecodeIndex >= files.Length() || 524 data.compiledIndex >= files.Length()) { 525 MOZ_ASSERT(false, "Bad index value!"); 526 return nullptr; 527 } 528 529 const auto& file = files[data.bytecodeIndex]; 530 531 if (NS_WARN_IF(!ValueDeserializationHelper<StructuredCloneFile>:: 532 CreateAndWrapWasmModule(aCx, file, data, &result))) { 533 return nullptr; 534 } 535 536 return result; 537 } 538 539 if (aData >= aCloneReadInfo->Files().Length()) { 540 MOZ_ASSERT(false, "Bad index value!"); 541 return nullptr; 542 } 543 544 auto& file = aCloneReadInfo->MutableFile(aData); 545 546 if (aTag == SCTAG_DOM_MUTABLEFILE) { 547 MutableFileData data; 548 if (NS_WARN_IF(!ReadFileHandle(aReader, &data))) { 549 return nullptr; 550 } 551 552 if (NS_WARN_IF(!ValueDeserializationHelper<StructuredCloneFile>:: 553 CreateAndWrapMutableFile(aCx, file, data, &result))) { 554 return nullptr; 555 } 556 557 return result; 558 } 559 560 BlobOrFileData data; 561 if (NS_WARN_IF(!ReadBlobOrFile(aReader, aTag, &data))) { 562 return nullptr; 563 } 564 565 if (NS_WARN_IF(!ValueDeserializationHelper< 566 StructuredCloneFile>::CreateAndWrapBlobOrFile(aCx, aDatabase, 567 file, data, 568 &result))) { 569 return nullptr; 570 } 571 572 return result; 573 } 574 575 return StructuredCloneHolder::ReadFullySerializableObjects(aCx, aReader, aTag, 576 true); 577 } 578 579 template JSObject* CommonStructuredCloneReadCallback( 580 JSContext* aCx, JSStructuredCloneReader* aReader, 581 const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag, uint32_t aData, 582 StructuredCloneReadInfoChild* aCloneReadInfo, IDBDatabase* aDatabase); 583 584 template JSObject* CommonStructuredCloneReadCallback( 585 JSContext* aCx, JSStructuredCloneReader* aReader, 586 const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag, uint32_t aData, 587 StructuredCloneReadInfoParent* aCloneReadInfo, IDBDatabase* aDatabase); 588 } // namespace mozilla::dom::indexedDB