Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/dom/system/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 19 kB image not shown  

Quelle  PathUtils.cpp   Sprache: C

 
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 */


#include "PathUtils.h"

#include "mozilla/ClearOnShutdown.h"
#include "mozilla/DataMutex.h"
#include "mozilla/ErrorNames.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Maybe.h"
#include "mozilla/MozPromise.h"
#include "mozilla/RefPtr.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/Span.h"
#include "mozilla/Try.h"
#include "mozilla/dom/DOMParser.h"
#include "mozilla/dom/PathUtilsBinding.h"
#include "mozilla/dom/Promise.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsCOMPtr.h"
#include "nsDirectoryServiceDefs.h"
#include "nsDirectoryServiceUtils.h"
#include "nsIFile.h"
#include "nsIGlobalObject.h"
#include "nsLocalFile.h"
#include "nsNetUtil.h"
#include "nsString.h"
#include "nsURLHelper.h"
#include "xpcpublic.h"

namespace mozilla::dom {

static constexpr auto ERROR_EMPTY_PATH =
    "PathUtils does not support empty paths"_ns;
static constexpr auto ERROR_INITIALIZE_PATH = "Could not initialize path"_ns;
static constexpr auto ERROR_GET_PARENT = "Could not get parent path"_ns;
static constexpr auto ERROR_JOIN = "Could not append to path"_ns;

static constexpr auto COLON = ": "_ns;

static void ThrowError(ErrorResult& aErr, const nsresult aResult,
                       const nsCString& aMessage) {
  nsAutoCStringN<32> errName;
  GetErrorName(aResult, errName);

  nsAutoCStringN<256> formattedMsg;
  formattedMsg.Append(aMessage);
  formattedMsg.Append(COLON);
  formattedMsg.Append(errName);

  switch (aResult) {
    case NS_ERROR_FILE_UNRECOGNIZED_PATH:
      aErr.ThrowOperationError(formattedMsg);
      break;

    case NS_ERROR_FILE_ACCESS_DENIED:
      aErr.ThrowInvalidAccessError(formattedMsg);
      break;

    case NS_ERROR_FAILURE:
    default:
      aErr.ThrowUnknownError(formattedMsg);
      break;
  }
}

static bool DoWindowsPathCheck() {
#ifdef XP_WIN
#  ifdef DEBUG
  return true;
#  else   // DEBUG
  return xpc::IsInAutomation();
#  endif  // DEBUG
#else     // XP_WIN
  return false;
#endif    // XP_WIN
}

/* static */
nsresult PathUtils::InitFileWithPath(nsIFile* aFile, const nsAString& aPath) {
  if (DoWindowsPathCheck()) {
    MOZ_RELEASE_ASSERT(!aPath.Contains(u'/'),
                       "Windows paths cannot include forward slashes");
  }

  MOZ_ASSERT(aFile);
  return aFile->InitWithPath(aPath);
}

MOZ_RUNINIT StaticDataMutex<Maybe<PathUtils::DirectoryCache>>
    PathUtils::sDirCache{"sDirCache"};

/**
 * Return the leaf name, including leading path separators in the case of
 * Windows UNC drive paths.
 *
 * @param aFile The file whose leaf name is to be returned.
 * @param aResult The string to hold the resulting leaf name.
 * @param aParent The pre-computed parent of |aFile|. If not provided, it will
 *                be computed.
 */

static nsresult GetLeafNamePreservingRoot(nsIFile* aFile, nsString& aResult,
                                          nsIFile* aParent = nullptr) {
  MOZ_ASSERT(aFile);

  nsCOMPtr<nsIFile> parent = aParent;
  if (!parent) {
    MOZ_TRY(aFile->GetParent(getter_AddRefs(parent)));
  }

  if (parent) {
    return aFile->GetLeafName(aResult);
  }

  // We have reached the root path. On Windows, the leafname for a UNC path
  // will not have the leading backslashes, so we need to use the entire path
  // here:
  //
  // * for a UNIX root path (/) this will be /;
  // * for a Windows drive path (e.g., C:), this will be the drive path (C:);
  //   and
  // * for a Windows UNC server path (e.g., \\\\server), this will be the full
  //   server path (\\\\server).
  return aFile->GetPath(aResult);
}

void PathUtils::Filename(const GlobalObject&, const nsAString& aPath,
                         nsString& aResult, ErrorResult& aErr) {
  if (aPath.IsEmpty()) {
    aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
    return;
  }

  nsCOMPtr<nsIFile> path = new nsLocalFile();
  if (nsresult rv = InitFileWithPath(path, aPath); NS_FAILED(rv)) {
    ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
    return;
  }

  if (nsresult rv = GetLeafNamePreservingRoot(path, aResult); NS_FAILED(rv)) {
    ThrowError(aErr, rv, "Could not get leaf name of path"_ns);
    return;
  }
}

void PathUtils::Parent(const GlobalObject&, const nsAString& aPath,
                       const int32_t aDepth, nsString& aResult,
                       ErrorResult& aErr) {
  if (aPath.IsEmpty()) {
    aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
    return;
  }

  nsCOMPtr<nsIFile> path = new nsLocalFile();
  if (nsresult rv = InitFileWithPath(path, aPath); NS_FAILED(rv)) {
    ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
    return;
  }

  if (aDepth <= 0) {
    aErr.ThrowNotSupportedError("A depth of at least 1 is required");
    return;
  }

  nsCOMPtr<nsIFile> parent;
  for (int32_t i = 0; path && i < aDepth; i++) {
    if (nsresult rv = path->GetParent(getter_AddRefs(parent)); NS_FAILED(rv)) {
      ThrowError(aErr, rv, ERROR_GET_PARENT);
      return;
    }
    path = parent;
  }

  if (parent) {
    MOZ_ALWAYS_SUCCEEDS(parent->GetPath(aResult));
  } else {
    aResult = VoidString();
  }
}

void PathUtils::Join(const GlobalObject&, const Sequence<nsString>& aComponents,
                     nsString& aResult, ErrorResult& aErr) {
  nsCOMPtr<nsIFile> path = Join(Span(aComponents), aErr);
  if (aErr.Failed()) {
    return;
  }

  MOZ_ALWAYS_SUCCEEDS(path->GetPath(aResult));
}

already_AddRefed<nsIFile> PathUtils::Join(
    const Span<const nsString>& aComponents, ErrorResult& aErr) {
  if (aComponents.IsEmpty() || aComponents[0].IsEmpty()) {
    aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
    return nullptr;
  }

  nsCOMPtr<nsIFile> path = new nsLocalFile();
  if (nsresult rv = InitFileWithPath(path, aComponents[0]); NS_FAILED(rv)) {
    ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
    return nullptr;
  }

  const auto components = Span<const nsString>(aComponents).Subspan(1);
  for (const auto& component : components) {
    if (nsresult rv = path->Append(component); NS_FAILED(rv)) {
      ThrowError(aErr, rv, ERROR_JOIN);
      return nullptr;
    }
  }

  return path.forget();
}

void PathUtils::JoinRelative(const GlobalObject&, const nsAString& aBasePath,
                             const nsAString& aRelativePath, nsString& aResult,
                             ErrorResult& aErr) {
  if (aRelativePath.IsEmpty()) {
    aResult = aBasePath;
    return;
  }

  nsCOMPtr<nsIFile> path = new nsLocalFile();
  if (nsresult rv = InitFileWithPath(path, aBasePath); NS_FAILED(rv)) {
    ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
    return;
  }

  if (nsresult rv = path->AppendRelativePath(aRelativePath); NS_FAILED(rv)) {
    ThrowError(aErr, rv, ERROR_JOIN);
    return;
  }

  MOZ_ALWAYS_SUCCEEDS(path->GetPath(aResult));
}

void PathUtils::ToExtendedWindowsPath(const GlobalObject&,
                                      const nsAString& aPath, nsString& aResult,
                                      ErrorResult& aErr) {
#ifndef XP_WIN
  aErr.ThrowNotAllowedError("Operation is windows specific"_ns);
  return;
#else
  if (aPath.IsEmpty()) {
    aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
    return;
  }

  const nsAString& str1 = Substring(aPath, 1, 1);
  const nsAString& str2 = Substring(aPath, 2, aPath.Length() - 2);

  bool isUNC = aPath.Length() >= 2 &&
               (aPath.First() == '\\' || aPath.First() == '/') &&
               (str1.EqualsLiteral("\\") || str1.EqualsLiteral("/"));

  constexpr auto pathPrefix = u"\\\\?\\"_ns;
  const nsAString& uncPath = pathPrefix + u"UNC\\"_ns + str2;
  const nsAString& normalPath = pathPrefix + aPath;

  nsCOMPtr<nsIFile> path = new nsLocalFile();
  if (nsresult rv = InitFileWithPath(path, isUNC ? uncPath : normalPath);
      NS_FAILED(rv)) {
    ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
    return;
  }

  MOZ_ALWAYS_SUCCEEDS(path->GetPath(aResult));
#endif
}

void PathUtils::Normalize(const GlobalObject&, const nsAString& aPath,
                          nsString& aResult, ErrorResult& aErr) {
  if (aPath.IsEmpty()) {
    aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
    return;
  }

  nsCOMPtr<nsIFile> path = new nsLocalFile();
  if (nsresult rv = InitFileWithPath(path, aPath); NS_FAILED(rv)) {
    ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
    return;
  }

  if (nsresult rv = path->Normalize(); NS_FAILED(rv)) {
    ThrowError(aErr, rv, "Could not normalize path"_ns);
    return;
  }

  MOZ_ALWAYS_SUCCEEDS(path->GetPath(aResult));
}

void PathUtils::Split(const GlobalObject&, const nsAString& aPath,
                      nsTArray<nsString>& aResult, ErrorResult& aErr) {
  if (aPath.IsEmpty()) {
    aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
    return;
  }

  nsCOMPtr<nsIFile> path = new nsLocalFile();
  if (nsresult rv = InitFileWithPath(path, aPath); NS_FAILED(rv)) {
    ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
    return;
  }

  while (path) {
    auto* component = aResult.EmplaceBack(fallible);
    if (!component) {
      aErr.Throw(NS_ERROR_OUT_OF_MEMORY);
      return;
    }

    nsCOMPtr<nsIFile> parent;
    if (nsresult rv = path->GetParent(getter_AddRefs(parent)); NS_FAILED(rv)) {
      ThrowError(aErr, rv, ERROR_GET_PARENT);
      return;
    }

    // GetLeafPreservingRoot cannot fail if we pass it a parent path.
    MOZ_ALWAYS_SUCCEEDS(GetLeafNamePreservingRoot(path, *component, parent));

    path = parent;
  }

  aResult.Reverse();
}

void PathUtils::SplitRelative(const GlobalObject& aGlobal,
                              const nsAString& aPath,
                              const SplitRelativeOptions& aOptions,
                              nsTArray<nsString>& aResult, ErrorResult& aErr) {
  if (aPath.IsEmpty()) {
    aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
    return;
  }

  if (DoWindowsPathCheck()) {
    MOZ_RELEASE_ASSERT(!aPath.Contains(u'/'),
                       "Windows paths cannot include forward slashes");
  }

  if (IsAbsolute(aGlobal, aPath)) {
    aErr.ThrowNotAllowedError(
        "PathUtils.splitRelative requires a relative path"_ns);
    return;
  }

#ifdef XP_WIN
  constexpr auto SEPARATOR = u'\\';
#else
  constexpr auto SEPARATOR = u'/';
#endif

  constexpr auto PARENT = u".."_ns;
  constexpr auto CURRENT = u"."_ns;

  for (const nsAString& pathComponent :
       nsCharSeparatedTokenizerTemplate<NS_TokenizerIgnoreNothing>{aPath,
                                                                   SEPARATOR}
           .ToRange()) {
    if (!aOptions.mAllowEmpty && pathComponent.IsEmpty()) {
      aErr.ThrowNotAllowedError(
          "PathUtils.splitRelative: Empty directory components (\"\") not "
          "allowed by options");
      return;
    }

    if (!aOptions.mAllowParentDir && pathComponent == PARENT) {
      aErr.ThrowNotAllowedError(
          "PathUtils.splitRelative: Parent directory components (\"..\") not "
          "allowed by options");
      return;
    }

    if (!aOptions.mAllowCurrentDir && pathComponent == CURRENT) {
      aErr.ThrowNotAllowedError(
          "PathUtils.splitRelative: Current directory components (\".\") not "
          "allowed by options");
      return;
    }

    aResult.AppendElement(pathComponent);
  }
}

void PathUtils::ToFileURI(const GlobalObject&, const nsAString& aPath,
                          nsCString& aResult, ErrorResult& aErr) {
  if (aPath.IsEmpty()) {
    aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
    return;
  }

  nsCOMPtr<nsIFile> path = new nsLocalFile();
  if (nsresult rv = InitFileWithPath(path, aPath); NS_FAILED(rv)) {
    ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
    return;
  }

  if (nsresult rv = net_GetURLSpecFromActualFile(path, aResult);
      NS_FAILED(rv)) {
    ThrowError(aErr, rv, "Could not retrieve URI spec"_ns);
    return;
  }
}

bool PathUtils::IsAbsolute(const GlobalObject&, const nsAString& aPath) {
  nsCOMPtr<nsIFile> path = new nsLocalFile();
  nsresult rv = InitFileWithPath(path, aPath);
  return NS_SUCCEEDED(rv);
}

void PathUtils::GetProfileDirSync(const GlobalObject&, nsString& aResult,
                                  ErrorResult& aErr) {
  MOZ_ASSERT(NS_IsMainThread());

  auto guard = sDirCache.Lock();
  DirectoryCache::Ensure(guard.ref())
      .GetDirectorySync(aResult, aErr, DirectoryCache::Directory::Profile);
}
void PathUtils::GetLocalProfileDirSync(const GlobalObject&, nsString& aResult,
                                       ErrorResult& aErr) {
  MOZ_ASSERT(NS_IsMainThread());

  auto guard = sDirCache.Lock();
  DirectoryCache::Ensure(guard.ref())
      .GetDirectorySync(aResult, aErr, DirectoryCache::Directory::LocalProfile);
}
void PathUtils::GetTempDirSync(const GlobalObject&, nsString& aResult,
                               ErrorResult& aErr) {
  MOZ_ASSERT(NS_IsMainThread());

  auto guard = sDirCache.Lock();
  DirectoryCache::Ensure(guard.ref())
      .GetDirectorySync(aResult, aErr, DirectoryCache::Directory::Temp);
}

void PathUtils::GetXulLibraryPathSync(const GlobalObject&, nsString& aResult,
                                      ErrorResult& aErr) {
  MOZ_ASSERT(NS_IsMainThread());

  auto guard = sDirCache.Lock();
  DirectoryCache::Ensure(guard.ref())
      .GetDirectorySync(aResult, aErr, DirectoryCache::Directory::XulLibrary);
}

already_AddRefed<Promise> PathUtils::GetProfileDirAsync(
    const GlobalObject& aGlobal, ErrorResult& aErr) {
  MOZ_ASSERT(!NS_IsMainThread());

  auto guard = sDirCache.Lock();
  return DirectoryCache::Ensure(guard.ref())
      .GetDirectoryAsync(aGlobal, aErr, DirectoryCache::Directory::Profile);
}

already_AddRefed<Promise> PathUtils::GetLocalProfileDirAsync(
    const GlobalObject& aGlobal, ErrorResult& aErr) {
  MOZ_ASSERT(!NS_IsMainThread());

  auto guard = sDirCache.Lock();
  return DirectoryCache::Ensure(guard.ref())
      .GetDirectoryAsync(aGlobal, aErr,
                         DirectoryCache::Directory::LocalProfile);
}

already_AddRefed<Promise> PathUtils::GetTempDirAsync(
    const GlobalObject& aGlobal, ErrorResult& aErr) {
  MOZ_ASSERT(!NS_IsMainThread());

  auto guard = sDirCache.Lock();
  return DirectoryCache::Ensure(guard.ref())
      .GetDirectoryAsync(aGlobal, aErr, DirectoryCache::Directory::Temp);
}

already_AddRefed<Promise> PathUtils::GetXulLibraryPathAsync(
    const GlobalObject& aGlobal, ErrorResult& aErr) {
  MOZ_ASSERT(!NS_IsMainThread());

  auto guard = sDirCache.Lock();
  return DirectoryCache::Ensure(guard.ref())
      .GetDirectoryAsync(aGlobal, aErr, DirectoryCache::Directory::XulLibrary);
}

PathUtils::DirectoryCache::DirectoryCache() {
  for (auto& dir : mDirectories) {
    dir.SetIsVoid(true);
  }
}

PathUtils::DirectoryCache& PathUtils::DirectoryCache::Ensure(
    Maybe<PathUtils::DirectoryCache>& aCache) {
  if (aCache.isNothing()) {
    aCache.emplace();

    auto clearAtShutdown = []() {
      RunOnShutdown([]() {
        auto cache = PathUtils::sDirCache.Lock();
        cache->reset();
      });
    };

    if (NS_IsMainThread()) {
      clearAtShutdown();
    } else {
      NS_DispatchToMainThread(
          NS_NewRunnableFunction(__func__, std::move(clearAtShutdown)));
    }
  }

  return aCache.ref();
}

void PathUtils::DirectoryCache::GetDirectorySync(
    nsString& aResult, ErrorResult& aErr, const Directory aRequestedDir) {
  MOZ_RELEASE_ASSERT(aRequestedDir < Directory::Count);

  if (nsresult rv = PopulateDirectoriesImpl(aRequestedDir); NS_FAILED(rv)) {
    nsAutoCStringN<32> errorName;
    GetErrorName(rv, errorName);

    nsAutoCStringN<256> msg;
    msg.Append("Could not retrieve directory "_ns);
    msg.Append(kDirectoryNames[aRequestedDir]);
    msg.Append(COLON);
    msg.Append(errorName);

    aErr.ThrowUnknownError(msg);
    return;
  }

  aResult = mDirectories[aRequestedDir];
}

already_AddRefed<Promise> PathUtils::DirectoryCache::GetDirectoryAsync(
    const GlobalObject& aGlobal, ErrorResult& aErr,
    const Directory aRequestedDir) {
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
  RefPtr<Promise> promise = Promise::Create(global, aErr);
  if (aErr.Failed()) {
    return nullptr;
  }

  if (RefPtr<PopulateDirectoriesPromise> p =
          PopulateDirectories(aRequestedDir)) {
    p->Then(
        GetCurrentSerialEventTarget(), __func__,
        [promise, aRequestedDir](const Ok&) {
          auto cache = PathUtils::sDirCache.Lock();
          cache.ref()->ResolveWithDirectory(promise, aRequestedDir);
        },
        [promise](const nsresult& aRv) { promise->MaybeReject(aRv); });
  } else {
    ResolveWithDirectory(promise, aRequestedDir);
  }

  return promise.forget();
}

void PathUtils::DirectoryCache::ResolveWithDirectory(
    Promise* aPromise, const Directory aRequestedDir) {
  MOZ_RELEASE_ASSERT(aRequestedDir < Directory::Count);
  MOZ_RELEASE_ASSERT(!mDirectories[aRequestedDir].IsVoid());
  aPromise->MaybeResolve(mDirectories[aRequestedDir]);
}

already_AddRefed<PathUtils::DirectoryCache::PopulateDirectoriesPromise>
PathUtils::DirectoryCache::PopulateDirectories(
    const PathUtils::DirectoryCache::Directory aRequestedDir) {
  MOZ_RELEASE_ASSERT(aRequestedDir < Directory::Count);

  // If we have already resolved the requested directory, we can return
  // immediately.
  // Otherwise, if we have already fired off a request to populate the entry,
  // so we can return the corresponding promise immediately. caller will queue
  // a Thenable onto that promise to resolve/reject the request.
  if (!mDirectories[aRequestedDir].IsVoid()) {
    return nullptr;
  }
  if (!mPromises[aRequestedDir].IsEmpty()) {
    return mPromises[aRequestedDir].Ensure(__func__);
  }

  RefPtr<PopulateDirectoriesPromise> promise =
      mPromises[aRequestedDir].Ensure(__func__);

  if (NS_IsMainThread()) {
    nsresult rv = PopulateDirectoriesImpl(aRequestedDir);
    ResolvePopulateDirectoriesPromise(rv, aRequestedDir);
  } else {
    nsCOMPtr<nsIRunnable> runnable =
        NS_NewRunnableFunction(__func__, [aRequestedDir]() {
          auto cache = PathUtils::sDirCache.Lock();
          nsresult rv = cache.ref()->PopulateDirectoriesImpl(aRequestedDir);
          cache.ref()->ResolvePopulateDirectoriesPromise(rv, aRequestedDir);
        });
    NS_DispatchToMainThread(runnable.forget());
  }

  return promise.forget();
}

void PathUtils::DirectoryCache::ResolvePopulateDirectoriesPromise(
    nsresult aRv, const PathUtils::DirectoryCache::Directory aRequestedDir) {
  MOZ_RELEASE_ASSERT(aRequestedDir < Directory::Count);

  if (NS_SUCCEEDED(aRv)) {
    mPromises[aRequestedDir].Resolve(Ok{}, __func__);
  } else {
    mPromises[aRequestedDir].Reject(aRv, __func__);
  }
}

nsresult PathUtils::DirectoryCache::PopulateDirectoriesImpl(
    const PathUtils::DirectoryCache::Directory aRequestedDir) {
  MOZ_RELEASE_ASSERT(NS_IsMainThread());
  MOZ_RELEASE_ASSERT(aRequestedDir < Directory::Count);

  if (!mDirectories[aRequestedDir].IsVoid()) {
    // In between when this promise was dispatched to the main thread and now,
    // the directory cache has had this entry populated (via the
    // on-main-thread sync method).
    return NS_OK;
  }

  nsCOMPtr<nsIFile> path;

  MOZ_TRY(NS_GetSpecialDirectory(kDirectoryNames[aRequestedDir],
                                 getter_AddRefs(path)));
  MOZ_TRY(path->GetPath(mDirectories[aRequestedDir]));

  return NS_OK;
}

}  // namespace mozilla::dom

96%


¤ Dauer der Verarbeitung: 0.17 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.