GetFilesHelper.cpp (16414B)
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 "GetFilesHelper.h" 8 9 #include "FileSystemUtils.h" 10 #include "mozilla/dom/ContentChild.h" 11 #include "mozilla/dom/ContentParent.h" 12 #include "mozilla/dom/Directory.h" 13 #include "mozilla/dom/FileBlobImpl.h" 14 #include "mozilla/dom/IPCBlobUtils.h" 15 #include "mozilla/dom/Promise.h" 16 #include "mozilla/dom/UnionTypes.h" 17 #include "mozilla/ipc/IPCStreamUtils.h" 18 #include "nsNetCID.h" 19 #include "nsProxyRelease.h" 20 21 namespace mozilla::dom { 22 23 // This class is used in the DTOR of GetFilesHelper to release resources in the 24 // correct thread. 25 class GetFilesHelper::ReleaseRunnable final : public Runnable { 26 public: 27 static void MaybeReleaseOnMainThread( 28 nsTArray<PromiseAdapter>&& aPromises, 29 nsTArray<RefPtr<GetFilesCallback>>&& aCallbacks) { 30 if (NS_IsMainThread()) { 31 return; 32 } 33 34 RefPtr<ReleaseRunnable> runnable = 35 new ReleaseRunnable(std::move(aPromises), std::move(aCallbacks)); 36 FileSystemUtils::DispatchRunnable(nullptr, runnable.forget()); 37 } 38 39 NS_IMETHOD 40 Run() override { 41 MOZ_ASSERT(NS_IsMainThread()); 42 mPromises.Clear(); 43 mCallbacks.Clear(); 44 return NS_OK; 45 } 46 47 private: 48 ReleaseRunnable(nsTArray<PromiseAdapter>&& aPromises, 49 nsTArray<RefPtr<GetFilesCallback>>&& aCallbacks) 50 : Runnable("dom::ReleaseRunnable"), 51 mPromises(std::move(aPromises)), 52 mCallbacks(std::move(aCallbacks)) {} 53 54 nsTArray<PromiseAdapter> mPromises; 55 nsTArray<RefPtr<GetFilesCallback>> mCallbacks; 56 }; 57 58 /////////////////////////////////////////////////////////////////////////////// 59 // PromiseAdapter 60 61 GetFilesHelper::PromiseAdapter::PromiseAdapter( 62 MozPromiseAndGlobal&& aMozPromise) 63 : mPromise(std::move(aMozPromise)) {} 64 GetFilesHelper::PromiseAdapter::PromiseAdapter(Promise* aDomPromise) 65 : mPromise(RefPtr{aDomPromise}) {} 66 GetFilesHelper::PromiseAdapter::~PromiseAdapter() { Clear(); } 67 68 void GetFilesHelper::PromiseAdapter::Clear() { 69 mPromise = AsVariant(RefPtr<Promise>(nullptr)); 70 } 71 72 nsIGlobalObject* GetFilesHelper::PromiseAdapter::GetGlobalObject() { 73 return mPromise.match( 74 [](RefPtr<Promise>& aDomPromise) { 75 return aDomPromise ? aDomPromise->GetGlobalObject() : nullptr; 76 }, 77 [](MozPromiseAndGlobal& aMozPromiseAndGlobal) { 78 return aMozPromiseAndGlobal.mGlobal.get(); 79 }); 80 } 81 82 void GetFilesHelper::PromiseAdapter::Resolve(nsTArray<RefPtr<File>>&& aFiles) { 83 mPromise.match( 84 [&aFiles](RefPtr<Promise>& aDomPromise) { 85 if (aDomPromise) { 86 aDomPromise->MaybeResolve(Sequence(std::move(aFiles))); 87 } 88 }, 89 [&aFiles](MozPromiseAndGlobal& aMozPromiseAndGlobal) { 90 aMozPromiseAndGlobal.mMozPromise->Resolve(std::move(aFiles), __func__); 91 }); 92 } 93 94 void GetFilesHelper::PromiseAdapter::Reject(nsresult aError) { 95 mPromise.match( 96 [&aError](RefPtr<Promise>& aDomPromise) { 97 if (aDomPromise) { 98 aDomPromise->MaybeReject(aError); 99 } 100 }, 101 [&aError](MozPromiseAndGlobal& aMozPromiseAndGlobal) { 102 aMozPromiseAndGlobal.mMozPromise->Reject(aError, __func__); 103 }); 104 } 105 106 /////////////////////////////////////////////////////////////////////////////// 107 // GetFilesHelper Base class 108 109 already_AddRefed<GetFilesHelper> GetFilesHelper::Create( 110 const nsTArray<OwningFileOrDirectory>& aFilesOrDirectory, 111 bool aRecursiveFlag, ErrorResult& aRv) { 112 RefPtr<GetFilesHelper> helper; 113 114 if (XRE_IsParentProcess()) { 115 helper = new GetFilesHelper(aRecursiveFlag); 116 } else { 117 helper = new GetFilesHelperChild(aRecursiveFlag); 118 } 119 120 // Most callers will have at most one directory 121 AutoTArray<nsString, 1> directoryPaths; 122 123 for (uint32_t i = 0; i < aFilesOrDirectory.Length(); ++i) { 124 const OwningFileOrDirectory& data = aFilesOrDirectory[i]; 125 if (data.IsFile()) { 126 if (!helper->mTargetBlobImplArray.AppendElement(data.GetAsFile()->Impl(), 127 fallible)) { 128 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 129 return nullptr; 130 } 131 } else { 132 MOZ_ASSERT(data.IsDirectory()); 133 134 RefPtr<Directory> directory = data.GetAsDirectory(); 135 MOZ_ASSERT(directory); 136 137 nsString directoryPath; 138 aRv = directory->GetFullRealPath(directoryPath); 139 directoryPaths.AppendElement(std::move(directoryPath)); 140 if (NS_WARN_IF(aRv.Failed())) { 141 return nullptr; 142 } 143 } 144 } 145 146 // No directories to explore. 147 if (directoryPaths.IsEmpty()) { 148 helper->mListingCompleted = true; 149 return helper.forget(); 150 } 151 152 MOZ_ASSERT(helper->mTargetBlobImplArray.IsEmpty()); 153 helper->SetDirectoryPaths(std::move(directoryPaths)); 154 155 helper->Work(aRv); 156 if (NS_WARN_IF(aRv.Failed())) { 157 return nullptr; 158 } 159 160 return helper.forget(); 161 } 162 163 GetFilesHelper::GetFilesHelper(bool aRecursiveFlag) 164 : Runnable("GetFilesHelper"), 165 GetFilesHelperBase(aRecursiveFlag), 166 mListingCompleted(false), 167 mErrorResult(NS_OK), 168 mMutex("GetFilesHelper::mMutex"), 169 mCanceled(false) {} 170 171 GetFilesHelper::~GetFilesHelper() { 172 ReleaseRunnable::MaybeReleaseOnMainThread(std::move(mPromises), 173 std::move(mCallbacks)); 174 } 175 176 void GetFilesHelper::AddPromiseInternal(PromiseAdapter&& aPromise) { 177 // Still working. 178 if (!mListingCompleted) { 179 mPromises.AppendElement(std::move(aPromise)); 180 return; 181 } 182 183 MOZ_ASSERT(mPromises.IsEmpty()); 184 ResolveOrRejectPromise(std::move(aPromise)); 185 } 186 187 void GetFilesHelper::AddPromise(Promise* aPromise) { 188 MOZ_ASSERT(aPromise); 189 AddPromiseInternal(PromiseAdapter(aPromise)); 190 } 191 192 void GetFilesHelper::AddMozPromise(MozPromiseType* aPromise, 193 nsIGlobalObject* aGlobal) { 194 MOZ_ASSERT(aPromise); 195 MOZ_ASSERT(aGlobal); 196 AddPromiseInternal(PromiseAdapter(MozPromiseAndGlobal{aPromise, aGlobal})); 197 } 198 199 void GetFilesHelper::AddCallback(GetFilesCallback* aCallback) { 200 MOZ_ASSERT(aCallback); 201 202 // Still working. 203 if (!mListingCompleted) { 204 mCallbacks.AppendElement(aCallback); 205 return; 206 } 207 208 MOZ_ASSERT(mCallbacks.IsEmpty()); 209 RunCallback(aCallback); 210 } 211 212 void GetFilesHelper::Unlink() { 213 mPromises.Clear(); 214 mCallbacks.Clear(); 215 216 { 217 MutexAutoLock lock(mMutex); 218 mCanceled = true; 219 } 220 221 Cancel(); 222 } 223 224 void GetFilesHelper::Traverse(nsCycleCollectionTraversalCallback& aCb) { 225 for (auto&& promiseAdapter : mPromises) { 226 promiseAdapter.Traverse(aCb); 227 } 228 } 229 230 void GetFilesHelper::PromiseAdapter::Traverse( 231 nsCycleCollectionTraversalCallback& aCb) { 232 mPromise.match( 233 [&aCb](RefPtr<Promise>& aDomPromise) { 234 if (aDomPromise) { 235 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mDomPromise"); 236 aCb.NoteNativeChild(aDomPromise, 237 NS_CYCLE_COLLECTION_PARTICIPANT(Promise)); 238 } 239 }, 240 [&aCb](MozPromiseAndGlobal& aMozPromiseAndGlobal) { 241 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mGlobal"); 242 aCb.NoteXPCOMChild(aMozPromiseAndGlobal.mGlobal); 243 }); 244 } 245 246 void GetFilesHelper::Work(ErrorResult& aRv) { 247 nsCOMPtr<nsIEventTarget> target = 248 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); 249 MOZ_ASSERT(target); 250 251 aRv = target->Dispatch(this, NS_DISPATCH_NORMAL); 252 } 253 254 NS_IMETHODIMP 255 GetFilesHelper::Run() { 256 MOZ_ASSERT(!mDirectoryPaths.IsEmpty()); 257 MOZ_ASSERT(!mListingCompleted); 258 259 // First step is to retrieve the list of file paths. 260 // This happens in the I/O thread. 261 if (!NS_IsMainThread()) { 262 RunIO(); 263 264 // If this operation has been canceled, we don't have to go back to 265 // main-thread. 266 if (IsCanceled()) { 267 return NS_OK; 268 } 269 270 RefPtr<Runnable> runnable = this; 271 return FileSystemUtils::DispatchRunnable(nullptr, runnable.forget()); 272 } 273 274 // We are here, but we should not do anything on this thread because, in the 275 // meantime, the operation has been canceled. 276 if (IsCanceled()) { 277 return NS_OK; 278 } 279 280 OperationCompleted(); 281 return NS_OK; 282 } 283 284 void GetFilesHelper::OperationCompleted() { 285 // We mark the operation as completed here. 286 mListingCompleted = true; 287 288 // Let's process the pending promises. 289 auto promises = std::move(mPromises); 290 for (uint32_t i = 0; i < promises.Length(); ++i) { 291 ResolveOrRejectPromise(std::move(promises[i])); 292 } 293 294 // Let's process the pending callbacks. 295 nsTArray<RefPtr<GetFilesCallback>> callbacks = std::move(mCallbacks); 296 297 for (uint32_t i = 0; i < callbacks.Length(); ++i) { 298 RunCallback(callbacks[i]); 299 } 300 } 301 302 void GetFilesHelper::RunIO() { 303 MOZ_ASSERT(!NS_IsMainThread()); 304 MOZ_ASSERT(!mDirectoryPaths.IsEmpty()); 305 MOZ_ASSERT(!mListingCompleted); 306 307 for (const auto& directoryPath : mDirectoryPaths) { 308 nsCOMPtr<nsIFile> file; 309 mErrorResult = NS_NewLocalFile(directoryPath, getter_AddRefs(file)); 310 if (NS_WARN_IF(NS_FAILED(mErrorResult))) { 311 return; 312 } 313 314 nsAutoString leafName; 315 mErrorResult = file->GetLeafName(leafName); 316 if (NS_WARN_IF(NS_FAILED(mErrorResult))) { 317 return; 318 } 319 320 nsAutoString domPath; 321 domPath.AssignLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL); 322 domPath.Append(leafName); 323 324 mErrorResult = ExploreDirectory(domPath, file); 325 if (NS_FAILED(mErrorResult)) { 326 break; 327 } 328 } 329 } 330 331 nsresult GetFilesHelperBase::ExploreDirectory(const nsAString& aDOMPath, 332 nsIFile* aFile) { 333 MOZ_ASSERT(!NS_IsMainThread()); 334 MOZ_ASSERT(aFile); 335 336 // We check if this operation has to be terminated at each recursion. 337 if (IsCanceled()) { 338 return NS_OK; 339 } 340 341 nsCOMPtr<nsIDirectoryEnumerator> entries; 342 nsresult rv = aFile->GetDirectoryEntries(getter_AddRefs(entries)); 343 if (NS_WARN_IF(NS_FAILED(rv))) { 344 return rv; 345 } 346 347 for (;;) { 348 nsCOMPtr<nsIFile> currFile; 349 if (NS_WARN_IF(NS_FAILED(entries->GetNextFile(getter_AddRefs(currFile)))) || 350 !currFile) { 351 break; 352 } 353 bool isLink, isSpecial, isFile, isDir; 354 if (NS_WARN_IF(NS_FAILED(currFile->IsSymlink(&isLink)) || 355 NS_FAILED(currFile->IsSpecial(&isSpecial))) || 356 isSpecial || 357 // Although we allow explicit individual selection of symlinks via the 358 // file picker, we do not process symlinks in directory traversal. Our 359 // specific policy decision is documented at 360 // https://bugzilla.mozilla.org/show_bug.cgi?id=1813299#c20 361 isLink) { 362 continue; 363 } 364 365 if (NS_WARN_IF(NS_FAILED(currFile->IsFile(&isFile)) || 366 NS_FAILED(currFile->IsDirectory(&isDir))) || 367 !(isFile || isDir)) { 368 continue; 369 } 370 371 // The new domPath 372 nsAutoString domPath; 373 domPath.Assign(aDOMPath); 374 if (!aDOMPath.EqualsLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL)) { 375 domPath.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL); 376 } 377 378 nsAutoString leafName; 379 if (NS_WARN_IF(NS_FAILED(currFile->GetLeafName(leafName)))) { 380 continue; 381 } 382 domPath.Append(leafName); 383 384 if (isFile) { 385 RefPtr<BlobImpl> blobImpl = new FileBlobImpl(currFile); 386 blobImpl->SetDOMPath(domPath); 387 388 if (!mTargetBlobImplArray.AppendElement(blobImpl, fallible)) { 389 return NS_ERROR_OUT_OF_MEMORY; 390 } 391 392 continue; 393 } 394 395 MOZ_ASSERT(isDir); 396 if (!mRecursiveFlag) { 397 continue; 398 } 399 400 // Recursive. 401 rv = ExploreDirectory(domPath, currFile); 402 if (NS_WARN_IF(NS_FAILED(rv))) { 403 return rv; 404 } 405 } 406 407 return NS_OK; 408 } 409 410 void GetFilesHelper::ResolveOrRejectPromise(PromiseAdapter&& aPromise) { 411 MOZ_ASSERT(NS_IsMainThread()); 412 MOZ_ASSERT(mListingCompleted); 413 414 nsTArray<RefPtr<File>> files; 415 416 if (NS_SUCCEEDED(mErrorResult)) { 417 for (uint32_t i = 0; i < mTargetBlobImplArray.Length(); ++i) { 418 RefPtr<File> domFile = 419 File::Create(aPromise.GetGlobalObject(), mTargetBlobImplArray[i]); 420 if (NS_WARN_IF(!domFile)) { 421 mErrorResult = NS_ERROR_FAILURE; 422 files.Clear(); 423 break; 424 } 425 426 if (!files.AppendElement(domFile, fallible)) { 427 mErrorResult = NS_ERROR_OUT_OF_MEMORY; 428 files.Clear(); 429 break; 430 } 431 } 432 } 433 434 // Error propagation. 435 if (NS_FAILED(mErrorResult)) { 436 aPromise.Reject(mErrorResult); 437 return; 438 } 439 440 aPromise.Resolve(std::move(files)); 441 } 442 443 void GetFilesHelper::RunCallback(GetFilesCallback* aCallback) { 444 MOZ_ASSERT(NS_IsMainThread()); 445 MOZ_ASSERT(mListingCompleted); 446 MOZ_ASSERT(aCallback); 447 448 aCallback->Callback(mErrorResult, mTargetBlobImplArray); 449 } 450 451 /////////////////////////////////////////////////////////////////////////////// 452 // GetFilesHelperChild class 453 454 void GetFilesHelperChild::Work(ErrorResult& aRv) { 455 ContentChild* cc = ContentChild::GetSingleton(); 456 if (NS_WARN_IF(!cc)) { 457 aRv.Throw(NS_ERROR_FAILURE); 458 return; 459 } 460 461 aRv = nsID::GenerateUUIDInPlace(mUUID); 462 if (NS_WARN_IF(aRv.Failed())) { 463 return; 464 } 465 466 mPendingOperation = true; 467 cc->CreateGetFilesRequest(std::move(mDirectoryPaths), mRecursiveFlag, mUUID, 468 this); 469 } 470 471 void GetFilesHelperChild::Cancel() { 472 if (!mPendingOperation) { 473 return; 474 } 475 476 ContentChild* cc = ContentChild::GetSingleton(); 477 if (NS_WARN_IF(!cc)) { 478 return; 479 } 480 481 mPendingOperation = false; 482 cc->DeleteGetFilesRequest(mUUID, this); 483 } 484 485 bool GetFilesHelperChild::AppendBlobImpl(BlobImpl* aBlobImpl) { 486 MOZ_ASSERT(mPendingOperation); 487 MOZ_ASSERT(aBlobImpl); 488 MOZ_ASSERT(aBlobImpl->IsFile()); 489 490 return mTargetBlobImplArray.AppendElement(aBlobImpl, fallible); 491 } 492 493 void GetFilesHelperChild::Finished(nsresult aError) { 494 MOZ_ASSERT(mPendingOperation); 495 MOZ_ASSERT(NS_SUCCEEDED(mErrorResult)); 496 497 mPendingOperation = false; 498 mErrorResult = aError; 499 500 OperationCompleted(); 501 } 502 503 /////////////////////////////////////////////////////////////////////////////// 504 // GetFilesHelperParent class 505 506 class GetFilesHelperParentCallback final : public GetFilesCallback { 507 public: 508 explicit GetFilesHelperParentCallback(GetFilesHelperParent* aParent) 509 : mParent(aParent) { 510 MOZ_ASSERT(aParent); 511 } 512 513 void Callback(nsresult aStatus, 514 const FallibleTArray<RefPtr<BlobImpl>>& aBlobImpls) override { 515 if (NS_FAILED(aStatus)) { 516 mParent->mContentParent->SendGetFilesResponseAndForget( 517 mParent->mUUID, GetFilesResponseFailure(aStatus)); 518 return; 519 } 520 521 GetFilesResponseSuccess success; 522 523 nsTArray<IPCBlob>& ipcBlobs = success.blobs(); 524 ipcBlobs.SetLength(aBlobImpls.Length()); 525 526 for (uint32_t i = 0; i < aBlobImpls.Length(); ++i) { 527 nsresult rv = IPCBlobUtils::Serialize(aBlobImpls[i], ipcBlobs[i]); 528 if (NS_WARN_IF(NS_FAILED(rv))) { 529 mParent->mContentParent->SendGetFilesResponseAndForget( 530 mParent->mUUID, GetFilesResponseFailure(NS_ERROR_OUT_OF_MEMORY)); 531 return; 532 } 533 } 534 535 mParent->mContentParent->SendGetFilesResponseAndForget(mParent->mUUID, 536 success); 537 } 538 539 private: 540 // Raw pointer because this callback is kept alive by this parent object. 541 GetFilesHelperParent* mParent; 542 }; 543 544 GetFilesHelperParent::GetFilesHelperParent(const nsID& aUUID, 545 ContentParent* aContentParent, 546 bool aRecursiveFlag) 547 : GetFilesHelper(aRecursiveFlag), 548 mContentParent(aContentParent), 549 mUUID(aUUID) {} 550 551 GetFilesHelperParent::~GetFilesHelperParent() { 552 NS_ReleaseOnMainThread("GetFilesHelperParent::mContentParent", 553 mContentParent.forget()); 554 } 555 556 /* static */ 557 already_AddRefed<GetFilesHelperParent> GetFilesHelperParent::Create( 558 const nsID& aUUID, nsTArray<nsString>&& aDirectoryPaths, 559 bool aRecursiveFlag, ContentParent* aContentParent, ErrorResult& aRv) { 560 MOZ_ASSERT(aContentParent); 561 562 RefPtr<GetFilesHelperParent> helper = 563 new GetFilesHelperParent(aUUID, aContentParent, aRecursiveFlag); 564 helper->SetDirectoryPaths(std::move(aDirectoryPaths)); 565 566 helper->Work(aRv); 567 if (NS_WARN_IF(aRv.Failed())) { 568 return nullptr; 569 } 570 571 RefPtr<GetFilesHelperParentCallback> callback = 572 new GetFilesHelperParentCallback(helper); 573 helper->AddCallback(callback); 574 575 return helper.forget(); 576 } 577 578 } // namespace mozilla::dom