Bug 1673019 - Get and set file permissions in IOUtils r=emalysz
authorBarret Rennie <barret@brennie.ca>
Thu, 03 Dec 2020 05:37:45 +0000
changeset 3400955 485de8c93ac1df3ab411466bd536c7c70ce5c9df
parent 3400954 856ffced8cdf188ffd55857a5090f081511d4430
child 3400956 75dc030b69c6d888b5ab08962aa01e1d346318a8
push id629647
push userreviewbot
push dateThu, 03 Dec 2020 10:10:02 +0000
treeherdertry@2196998144da [default view] [failures only]
reviewersemalysz
bugs1673019
milestone85.0a1
Bug 1673019 - Get and set file permissions in IOUtils r=emalysz Differential Revision: https://phabricator.services.mozilla.com/D97689
dom/chrome-webidl/IOUtils.webidl
dom/system/IOUtils.cpp
dom/system/IOUtils.h
dom/system/tests/ioutils/chrome.ini
dom/system/tests/ioutils/test_ioutils_set_permissions.html
--- a/dom/chrome-webidl/IOUtils.webidl
+++ b/dom/chrome-webidl/IOUtils.webidl
@@ -150,16 +150,32 @@ namespace IOUtils {
    *
    * @param path An absolute file path.
    *
    * @return Resolves with a sequence of absolute file paths representing the
    *         children of the directory at |path|, otherwise rejects with a
    *         DOMException.
    */
   Promise<sequence<DOMString>> getChildren(DOMString path);
+  /**
+   * Set the permissions of the file at |path|.
+   *
+   * Windows does not make a distinction between user, group, and other
+   * permissions like UNICES do. If a permission flag is set for any of user,
+   * group, or other has a permission, then all users will have that
+   * permission. Additionally, Windows does not support setting the
+   * "executable" permission.
+   *
+   * @param path        An absolute file path
+   * @param permissions The UNIX file mode representing the permissions.
+   *
+   * @return Resolves if the permissions were set successfully, otherwise
+   *         rejects with a DOMException.
+   */
+  Promise<void> setPermissions(DOMString path, unsigned long permissions);
 };
 
 /**
  * Options to be passed to the |IOUtils.readUTF8| method.
  */
 dictionary ReadUTF8Options {
   /**
    * If true, this option indicates that the file to be read is compressed with
@@ -300,9 +316,17 @@ dictionary FileInfo {
 
   /**
    * The timestamp of file creation, represented in milliseconds since Epoch
    * (1970-01-01T00:00:00.000Z).
    *
    * This is only available on MacOS and Windows.
    */
   long long creationTime;
+  /**
+   * The permissions of the file, expressed as a UNIX file mode.
+   *
+   * NB: Windows does not make a distinction between user, group, and other
+   * permissions like UNICES do. The user, group, and other parts will always
+   * be identical on Windows.
+   */
+  unsigned long permissions;
 };
--- a/dom/system/IOUtils.cpp
+++ b/dom/system/IOUtils.cpp
@@ -126,16 +126,19 @@ MOZ_MUST_USE inline bool ToJSValue(
   info.mPath.Construct(aInternalFileInfo.mPath);
   info.mType.Construct(aInternalFileInfo.mType);
   info.mSize.Construct(aInternalFileInfo.mSize);
   info.mLastModified.Construct(aInternalFileInfo.mLastModified);
 
   if (aInternalFileInfo.mCreationTime.isSome()) {
     info.mCreationTime.Construct(aInternalFileInfo.mCreationTime.ref());
   }
+
+  info.mPermissions.Construct(aInternalFileInfo.mPermissions);
+
   return ToJSValue(aCx, info, aValue);
 }
 
 #ifdef XP_WIN
 constexpr char PathSeparator = u'\\';
 #else
 constexpr char PathSeparator = u'/';
 #endif
@@ -452,16 +455,35 @@ already_AddRefed<Promise> IOUtils::GetCh
 
   RunOnBackgroundThread<nsTArray<nsString>>(
       promise, [file = std::move(file)]() { return GetChildrenSync(file); });
 
   return promise.forget();
 }
 
 /* static */
+already_AddRefed<Promise> IOUtils::SetPermissions(GlobalObject& aGlobal,
+                                                  const nsAString& aPath,
+                                                  const uint32_t aPermissions) {
+  MOZ_ASSERT(XRE_IsParentProcess());
+  RefPtr<Promise> promise = CreateJSPromise(aGlobal);
+  NS_ENSURE_TRUE(!!promise, nullptr);
+
+  nsCOMPtr<nsIFile> file = new nsLocalFile();
+  REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);
+
+  RunOnBackgroundThread<Ok>(
+      promise, [file = std::move(file), permissions = aPermissions]() {
+        return SetPermissionsSync(file, permissions);
+      });
+
+  return promise.forget();
+}
+
+/* static */
 already_AddRefed<nsISerialEventTarget> IOUtils::GetBackgroundEventTarget() {
   if (sShutdownStarted) {
     return nullptr;
   }
 
   auto lockedBackgroundEventTarget = sBackgroundEventTarget.Lock();
   if (!lockedBackgroundEventTarget.ref()) {
     nsCOMPtr<nsISerialEventTarget> et;
@@ -1112,16 +1134,18 @@ Result<IOUtils::InternalFileInfo, IOUtil
   PRTime creationTime = 0;
   if (nsresult rv = aFile->GetCreationTime(&creationTime); NS_SUCCEEDED(rv)) {
     info.mCreationTime.emplace(static_cast<int64_t>(creationTime));
   } else if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
     // This field is only supported on some platforms.
     return Err(IOError(rv));
   }
 
+  MOZ_TRY(aFile->GetPermissions(&info.mPermissions));
+
   return info;
 }
 
 /* static */
 Result<int64_t, IOUtils::IOError> IOUtils::TouchSync(
     nsIFile* aFile, const Maybe<int64_t>& aNewModTime) {
   MOZ_ASSERT(!NS_IsMainThread());
 
@@ -1201,16 +1225,25 @@ Result<nsTArray<nsString>, IOUtils::IOEr
     }
     MOZ_TRY(iter->HasMoreElements(&hasMoreElements));
   }
 
   return children;
 }
 
 /* static */
+Result<Ok, IOUtils::IOError> IOUtils::SetPermissionsSync(
+    nsIFile* aFile, const uint32_t aPermissions) {
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  MOZ_TRY(aFile->SetPermissions(aPermissions));
+  return Ok{};
+}
+
+/* static */
 Result<nsTArray<uint8_t>, IOUtils::IOError> IOUtils::MozLZ4::Compress(
     Span<const uint8_t> aUncompressed) {
   nsTArray<uint8_t> result;
   size_t worstCaseSize =
       Compression::LZ4::maxCompressedSize(aUncompressed.Length()) + HEADER_SIZE;
   if (!result.SetCapacity(worstCaseSize, fallible)) {
     return Err(IOError(NS_ERROR_OUT_OF_MEMORY)
                    .WithMessage("Could not allocate buffer to compress data"));
--- a/dom/system/IOUtils.h
+++ b/dom/system/IOUtils.h
@@ -94,16 +94,20 @@ class IOUtils final {
 
   static already_AddRefed<Promise> Touch(
       GlobalObject& aGlobal, const nsAString& aPath,
       const Optional<int64_t>& aModification);
 
   static already_AddRefed<Promise> GetChildren(GlobalObject& aGlobal,
                                                const nsAString& aPath);
 
+  static already_AddRefed<Promise> SetPermissions(GlobalObject& aGlobal,
+                                                  const nsAString& aPath,
+                                                  const uint32_t aPermissions);
+
   static bool IsAbsolutePath(const nsAString& aPath);
 
  private:
   ~IOUtils() = default;
 
   friend class IOUtilsShutdownBlocker;
   struct InternalFileInfo;
   struct InternalWriteAtomicOpts;
@@ -321,16 +325,32 @@ class IOUtils final {
    * Returns the immediate children of the directory at |aFile|, if any.
    *
    * @param aFile The location of the directory.
    *
    * @return An array of absolute paths identifying the children of |aFile|.
    *         If there are no children, an empty array. Otherwise, an error.
    */
   static Result<nsTArray<nsString>, IOError> GetChildrenSync(nsIFile* aFile);
+
+  /**
+   * Set the permissions of the given file.
+   *
+   * Windows does not make a distinction between user, group, and other
+   * permissions like UNICES do. If a permission flag is set for any of user,
+   * group, or other has a permission, then all users will have that
+   * permission.
+   *
+   * @param aFile        The location of the file.
+   * @param aPermissions The permissions to set, as a UNIX file mode.
+   *
+   * @return |Ok| if the permissions were successfully set, or an error.
+   */
+  static Result<Ok, IOError> SetPermissionsSync(nsIFile* aFile,
+                                                const uint32_t aPermissions);
 };
 
 /**
  * An error class used with the |Result| type returned by most private |IOUtils|
  * methods.
  */
 class IOUtils::IOError {
  public:
@@ -377,16 +397,17 @@ class IOUtils::IOError {
  * returning any results to JavaScript.
  */
 struct IOUtils::InternalFileInfo {
   nsString mPath;
   FileType mType;
   uint64_t mSize;
   uint64_t mLastModified;
   Maybe<uint64_t> mCreationTime;
+  uint32_t mPermissions;
 };
 
 /**
  * This is an easier to work with representation of a
  * |mozilla::dom::WriteAtomicOptions| for private use in the |IOUtils|
  * implementation.
  *
  * Because web IDL dictionaries are not easily copy/moveable, this class is
--- a/dom/system/tests/ioutils/chrome.ini
+++ b/dom/system/tests/ioutils/chrome.ini
@@ -7,8 +7,9 @@ support-files =
 [test_ioutils_copy_move.html]
 [test_ioutils_dir_iteration.html]
 [test_ioutils_mkdir.html]
 [test_ioutils_read_write.html]
 [test_ioutils_read_write_utf8.html]
 [test_ioutils_remove.html]
 [test_ioutils_stat_touch.html]
 [test_ioutils_worker.xhtml]
+[test_ioutils_set_permissions.html]
new file mode 100644
--- /dev/null
+++ b/dom/system/tests/ioutils/test_ioutils_set_permissions.html
@@ -0,0 +1,53 @@
+<!-- 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 { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+    add_task(async function test_setPermissions() {
+      const tempDir = await PathUtils.getTempDir();
+      const tempFile = PathUtils.join(tempDir, "setPermissions.tmp");
+
+      await IOUtils.writeAtomicUTF8(tempFile, "");
+      await IOUtils.setPermissions(tempFile, 0o421);
+
+      let stat = await IOUtils.stat(tempFile);
+
+      if (Services.appinfo.OS === "WINNT") {
+        // setPermissions ignores the x bit on Windows.
+        is(stat.permissions, 0o666, "Permissions munged on Windows");
+      } else {
+        is(stat.permissions, 0o421, "Permissions match");
+      }
+
+      await IOUtils.setPermissions(tempFile, 0o400);
+      stat = await IOUtils.stat(tempFile);
+
+      if (Services.appinfo.OS === "WINNT") {
+        is(stat.permissions, 0o444, "Permissions munged on Windows");
+      } else {
+        is(stat.permissions, 0o400, "Permissions match");
+
+        await cleanup(tempFile);
+      }
+    });
+  </script>
+</head>
+
+<body>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test"></pre>
+</body>
+
+</html>