Commit f90932ae authored by Beth Rennie's avatar Beth Rennie
Browse files

Bug 1736331 - Add IOUtils methods for dealing with macOS extended filesystem attributes r=Gijs

parent de949e8a
Loading
Loading
Loading
Loading
+42 −1
Original line number Diff line number Diff line
@@ -235,8 +235,49 @@ namespace IOUtils {
   * @return A promise that resolves is the attributes were set successfully.
   */
  Promise<void> setWindowsAttributes(DOMString path, optional WindowsFileAttributes attrs = {});
#elif defined(XP_MACOSX)
  /**
   * Return whether or not the file has a specific extended attribute.
   *
   * @param path An absolute path.
   * @param attr The attribute to check for.
   *
   * @return A promise that resolves to whether or not the file has an extended
   *         attribute, or rejects with an error.
   */
  Promise<boolean> hasMacXAttr(DOMString path, UTF8String attr);
  /**
   * Return the value of an extended attribute for a file.
   *
   * @param path An absolute path.
   * @param attr The attribute to get the value of.
   *
   * @return A promise that resolves to the value of the extended attribute, or
   *         rejects with an error.
   */
  Promise<Uint8Array> getMacXAttr(DOMString path, UTF8String attr);
  /**
   * Set the extended attribute on a file.
   *
   * @param path  An absolute path.
   * @param attr  The attribute to set.
   * @param value The value of the attribute to set.
   *
   * @return A promise that resolves to whether or not the file has an extended
   *         attribute, or rejects with an error.
   */
  Promise<void> setMacXAttr(DOMString path, UTF8String attr, Uint8Array value);
  /**
   * Delete the extended attribute on a file.
   *
   * @param path An absolute path.
   * @param attr The attribute to delete.
   *
   * @return A promise that resolves if the attribute was deleted, or rejects
   *         with an error.
   */
  Promise<void> delMacXAttr(DOMString path, UTF8String attr);
#endif

};

[Exposed=Window]
+165 −1
Original line number Diff line number Diff line
@@ -59,6 +59,8 @@

#if defined(XP_WIN)
#  include "nsILocalFileWin.h"
#elif defined(XP_MACOSX)
#  include "nsILocalFileMac.h"
#endif

#define REJECT_IF_INIT_PATH_FAILED(_file, _path, _promise)            \
@@ -156,6 +158,9 @@ template <typename T>
static void ResolveJSPromise(Promise* aPromise, T&& aValue) {
  if constexpr (std::is_same_v<T, Ok>) {
    aPromise->MaybeResolveWithUndefined();
  } else if constexpr (std::is_same_v<T, nsTArray<uint8_t>>) {
    TypedArrayCreator<Uint8Array> array(aValue);
    aPromise->MaybeResolve(array);
  } else {
    aPromise->MaybeResolve(std::forward<T>(aValue));
  }
@@ -242,6 +247,9 @@ static void RejectJSPromise(Promise* aPromise, const IOUtils::IOError& aError) {
      aPromise->MaybeRejectWithDataError(
          errMsg.refOr("Argument is not allowed"_ns));
      break;
    case NS_ERROR_NOT_AVAILABLE:
      aPromise->MaybeRejectWithNotFoundError(errMsg.refOr("Unavailable"_ns));
      break;
    case NS_ERROR_ABORT:
      aPromise->MaybeRejectWithAbortError(errMsg.refOr("Operation aborted"_ns));
      break;
@@ -777,6 +785,74 @@ already_AddRefed<Promise> IOUtils::SetWindowsAttributes(
  });
}

#elif defined(XP_MACOSX)

/* static */
already_AddRefed<Promise> IOUtils::HasMacXAttr(GlobalObject& aGlobal,
                                               const nsAString& aPath,
                                               const nsACString& aAttr) {
  return WithPromiseAndState(aGlobal, [&](Promise* promise, auto& state) {
    nsCOMPtr<nsIFile> file = new nsLocalFile();
    REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);

    DispatchAndResolve<nsTArray<uint8_t>>(
        state->mEventQueue, promise,
        [file = std::move(file), attr = nsCString(aAttr)]() {
          return HasMacXAttrSync(file, attr);
        });
  });
}

/* static */
already_AddRefed<Promise> IOUtils::GetMacXAttr(GlobalObject& aGlobal,
                                               const nsAString& aPath,
                                               const nsACString& aAttr) {
  return WithPromiseAndState(aGlobal, [&](Promise* promise, auto& state) {
    nsCOMPtr<nsIFile> file = new nsLocalFile();
    REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);

    DispatchAndResolve<nsTArray<uint8_t>>(
        state->mEventQueue, promise,
        [file = std::move(file), attr = nsCString(aAttr)]() {
          return GetMacXAttrSync(file, attr);
        });
  });
}

/* static */
already_AddRefed<Promise> IOUtils::SetMacXAttr(GlobalObject& aGlobal,
                                               const nsAString& aPath,
                                               const nsACString& aAttr,
                                               const Uint8Array& aValue) {
  return WithPromiseAndState(aGlobal, [&](Promise* promise, auto& state) {
    nsCOMPtr<nsIFile> file = new nsLocalFile();
    REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);

    nsTArray<uint8_t> value(aValue.Data(), aValue.Length());

    DispatchAndResolve<Ok>(state->mEventQueue, promise,
                           [file = std::move(file), attr = nsCString(aAttr),
                            value = std::move(value)] {
                             return SetMacXAttrSync(file, attr, value);
                           });
  });
}

/* static */
already_AddRefed<Promise> IOUtils::DelMacXAttr(GlobalObject& aGlobal,
                                               const nsAString& aPath,
                                               const nsACString& aAttr) {
  return WithPromiseAndState(aGlobal, [&](Promise* promise, auto& state) {
    nsCOMPtr<nsIFile> file = new nsLocalFile();
    REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);

    DispatchAndResolve<Ok>(state->mEventQueue, promise,
                           [file = std::move(file), attr = nsCString(aAttr)] {
                             return DelMacXAttrSync(file, attr);
                           });
  });
}

#endif

/* static */
@@ -1538,7 +1614,95 @@ Result<Ok, IOUtils::IOError> IOUtils::SetWindowsAttributesSync(
  return Ok{};
}

#endif  // XP_WIN
#elif defined(XP_MACOSX)

/* static */
Result<nsTArray<uint8_t>, IOUtils::IOError> IOUtils::GetMacXAttrSync(
    nsIFile* aFile, const nsCString& aAttr) {
  MOZ_ASSERT(!NS_IsMainThread());

  nsCOMPtr<nsILocalFileMac> file = do_QueryInterface(aFile);
  MOZ_ASSERT(file);

  bool hasAttr;
  if (nsresult rv = file->HasXAttr(aAttr, &hasAttr); NS_FAILED(rv)) {
    return Err(IOError(rv).WithMessage(
        "Could not read the extended attribute `%s' from the file `%s'",
        aAttr.get(), aFile->HumanReadablePath().get()));
  }

  return hasAttr;
}

/* static */
Result<nsTArray<uint8_t>, IOUtils::IOError> IOUtils::GetMacXAttrSync(
    nsIFile* aFile, const nsCString& aAttr) {
  MOZ_ASSERT(!NS_IsMainThread());

  nsCOMPtr<nsILocalFileMac> file = do_QueryInterface(aFile);
  MOZ_ASSERT(file);

  nsTArray<uint8_t> value;
  if (nsresult rv = file->GetXAttr(aAttr, value); NS_FAILED(rv)) {
    auto err = IOError(rv);

    if (rv == NS_ERROR_NOT_AVAILABLE) {
      return Err(err.WithMessage(
          "The file `%s' does not have an extended attribute `%s'",
          aFile->HumanReadablePath().get(), aAttr.get()));
    }

    return Err(err.WithMessage(
        "Could not read the extended attribute `%s' from the file `%s'",
        aAttr.get(), aFile->HumanReadablePath().get()));
  }

  return value;
}

/* static */
Result<Ok, IOUtils::IOError> IOUtils::SetMacXAttrSync(
    nsIFile* aFile, const nsCString& aAttr, const nsTArray<uint8_t>& aValue) {
  MOZ_ASSERT(!NS_IsMainThread());

  nsCOMPtr<nsILocalFileMac> file = do_QueryInterface(aFile);
  MOZ_ASSERT(file);

  if (nsresult rv = file->SetXAttr(aAttr, aValue); NS_FAILED(rv)) {
    return Err(IOError(rv).WithMessage(
        "Could not set extended attribute `%s' on file `%s'", aAttr.get(),
        aFile->HumanReadablePath().get()));
  }

  return Ok{};
}

/* static */
Result<Ok, IOUtils::IOError> IOUtils::DelMacXAttrSync(nsIFile* aFile,
                                                      const nsCString& aAttr) {
  MOZ_ASSERT(!NS_IsMainThread());

  nsCOMPtr<nsILocalFileMac> file = do_QueryInterface(aFile);
  MOZ_ASSERT(file);

  if (nsresult rv = file->DelXAttr(aAttr); NS_FAILED(rv)) {
    auto err = IOError(rv);

    if (rv == NS_ERROR_NOT_AVAILABLE) {
      return Err(err.WithMessage(
          "The file `%s' does not have an extended attribute `%s'",
          aFile->HumanReadablePath().get(), aAttr.get()));
    }

    return Err(IOError(rv).WithMessage(
        "Could not delete extended attribute `%s' on file `%s'", aAttr.get(),
        aFile->HumanReadablePath().get()));
  }

  return Ok{};
}

#endif

/* static */
void IOUtils::GetProfileBeforeChange(GlobalObject& aGlobal,
+25 −0
Original line number Diff line number Diff line
@@ -131,6 +131,20 @@ class IOUtils final {
  static already_AddRefed<Promise> SetWindowsAttributes(
      GlobalObject& aGlobal, const nsAString& aPath,
      const mozilla::dom::WindowsFileAttributes& aAttrs);
#elif defined(XP_MACOSX)
  static already_AddRefed<Promise> HasMacXAttr(GlobalObject& aGlobal,
                                               const nsAString& aPath,
                                               const nsACString& aAttr);
  static already_AddRefed<Promise> GetMacXAttr(GlobalObject& aGlobal,
                                               const nsAString& aPath,
                                               const nsACString& aAttr);
  static already_AddRefed<Promise> SetMacXAttr(GlobalObject& aGlobal,
                                               const nsAString& aPath,
                                               const nsACString& aAttr,
                                               const Uint8Array& aValue);
  static already_AddRefed<Promise> DelMacXAttr(GlobalObject& aGlobal,
                                               const nsAString& aPath,
                                               const nsACString& aAttr);
#endif

  static void GetProfileBeforeChange(GlobalObject& aGlobal,
@@ -401,6 +415,17 @@ class IOUtils final {
   */
  static Result<Ok, IOError> SetWindowsAttributesSync(
      nsIFile* aFile, const uint32_t aSetAttrs, const uint32_t aClearAttrs);
#elif defined(XP_MACOSX)
  static Result<bool, IOError> HasMacXAttrSync(GlobalObject& aGlobal,
                                               nsIFile* aFile,
                                               const nsCString& aAttr);
  static Result<nsTArray<uint8_t>, IOError> GetMacXAttrSync(
      nsIFile* aFile, const nsCString& aAttr);
  static Result<Ok, IOError> SetMacXAttrSync(nsIFile* aFile,
                                             const nsCString& aAttr,
                                             const nsTArray<uint8_t>& aValue);
  static Result<Ok, IOError> DelMacXAttrSync(nsIFile* aFile,
                                             const nsCString& aAttr);
#endif

  enum class EventQueueStatus {
+2 −0
Original line number Diff line number Diff line
@@ -6,6 +6,8 @@ support-files =
[test_ioutils.html]
[test_ioutils_copy_move.html]
[test_ioutils_dir_iteration.html]
[test_ioutils_mac_xattr.html]
skip-if = (os != "mac")
[test_ioutils_mkdir.html]
[test_ioutils_read_write.html]
[test_ioutils_read_write_json.html]
+85 −0
Original line number Diff line number Diff line
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>

<head>
  <meta charset="utf-8">
  <title>Test the IOUtils file I/O API</title>
  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
  <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
  <script src="file_ioutils_test_fixtures.js"></script>
  <script>
    "use strict";

    const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");
    const { FileUtils } = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");

    const ATTR = "bogus.attr";

    add_task(async function getSetWindowsAttributes() {
      const bogus = new TextEncoder().encode("bogus");

      const tmpDir = PathUtils.join(await PathUtils.getTempDir(), "ioutils-macos-xattr.tmp.d");
      await createDir(tmpDir)

      const path = PathUtils.join(tmpDir, "file.tmp");
      await IOUtils.writeUTF8(path, "");

      ok(
        !await IOUtils.hasMacXAttr(path, ATTR),
        "File does not have an extended attribute at creation"
      );

      info("Testing getting an attribute that does not exist");
      await Assert.rejects(
        IOUtils.getMacXAttr(path, ATTR),
        /NotFoundError: The file `.+' does not have an extended attribute/,
        "IOUtils::getMacXAttr rejects when the attribute does not exist"
      );

      info("Testing setting an attribute");
      await IOUtils.setMacXAttr(ATTR, new TextEncoder().encode("bogus"));
      ok(
        await IOUtils.hasMacXAttr(path, ATTR),
        "File has extended attribute after setting"
      );

      {
        info("Testing getting an attribute")
        const value = await IOUtils.getMacXAttr(ATTR);
        Assert.equal(value, bogus, "Attribute value should match");
      }

      info("Testing removing an attribute");
      await IOUtils.delMacXAttr(ATTR);
      await Assert.rejects(
        IOUtils.getMacXAttr(path, ATTR),
        /NotFoundError: The file `.+' does not have an extended attribute/,
        "IOUtils::delMacXAttr removes the attribute"
      );

      ok(
        await IOUtils.hasMacXAttr(path, ATTR),
        "File does not have extended attribute after removing"
      );

      info("Testing removing an attribute that does not exist");
      await Assert.rejects(
        await IOUtils.delMacXAttr(ATTR),
        /NotFoundError: The file `.+' does not have an extended attribute/,
        "IOUtils::delMacXAttr rejects when the attribute does not exist"
      );

      await cleanup(tmpDir);
    });
  </script>
</head>

<body>
  <p id="display"></p>
  <div id="content" style="display: none"></div>
  <pre id="test"></pre>
</body>

</html>