tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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