Bug 1209924 - Implement a general filtering mechanism for Directory::GetFilesAndDirectories, and add filtering of sensitive files/directories. r=baku
authorJonathan Watt <jwatt@jwatt.org>
Fri, 09 Oct 2015 15:59:40 +0100
changeset 308889 e603b842873168711235805167aee442831f6a5f
parent 308888 4b98f3a5667b1b0c30f8e1ac16d6ce14593fac81
child 308890 f7db313181973ea6d8ec75d2bc23bcc0e8b68bd6
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1209924
milestone45.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1209924 - Implement a general filtering mechanism for Directory::GetFilesAndDirectories, and add filtering of sensitive files/directories. r=baku
dom/events/DataTransfer.cpp
dom/filesystem/Directory.cpp
dom/filesystem/Directory.h
dom/filesystem/GetDirectoryListingTask.cpp
dom/filesystem/GetDirectoryListingTask.h
dom/html/HTMLInputElement.cpp
dom/ipc/PContent.ipdl
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -905,17 +905,19 @@ DataTransfer::GetFilesAndDirectories(Err
         mFiles->Item(i)->GetMozFullPathInternal(path, aRv);
         if (aRv.Failed()) {
           return nullptr;
         }
         int32_t leafSeparatorIndex = path.RFind(FILE_PATH_SEPARATOR);
         nsDependentSubstring dirname = Substring(path, 0, leafSeparatorIndex);
         nsDependentSubstring basename = Substring(path, leafSeparatorIndex);
         fs = MakeOrReuseFileSystem(dirname, fs, window);
-        filesAndDirsSeq[i].SetAsDirectory() = new Directory(fs, basename);
+        RefPtr<Directory> directory = new Directory(fs, basename);
+        directory->SetContentFilters(NS_LITERAL_STRING("filter-out-sensitive"));
+        filesAndDirsSeq[i].SetAsDirectory() = directory;
       } else {
         filesAndDirsSeq[i].SetAsFile() = mFiles->Item(i);
       }
     }
   }
 
   p->MaybeResolve(filesAndDirsSeq);
 
--- a/dom/filesystem/Directory.cpp
+++ b/dom/filesystem/Directory.cpp
@@ -237,25 +237,31 @@ Directory::GetPath(nsAString& aRetval) c
 
 already_AddRefed<Promise>
 Directory::GetFilesAndDirectories()
 {
   nsresult error = NS_OK;
   nsString realPath;
   ErrorResult rv;
   RefPtr<GetDirectoryListingTask> task =
-    new GetDirectoryListingTask(mFileSystem, mPath, rv);
+    new GetDirectoryListingTask(mFileSystem, mPath, mFilters, rv);
   if (NS_WARN_IF(rv.Failed())) {
     return nullptr;
   }
   task->SetError(error);
   FileSystemPermissionRequest::RequestForTask(task);
   return task->GetPromise();
 }
 
+void
+Directory::SetContentFilters(const nsAString& aFilters)
+{
+  mFilters = aFilters;
+}
+
 FileSystemBase*
 Directory::GetFileSystem() const
 {
   return mFileSystem.get();
 }
 
 bool
 Directory::DOMPathToRealPath(const nsAString& aPath, nsAString& aRealPath) const
--- a/dom/filesystem/Directory.h
+++ b/dom/filesystem/Directory.h
@@ -82,16 +82,41 @@ public:
   void
   GetPath(nsAString& aRetval) const;
 
   already_AddRefed<Promise>
   GetFilesAndDirectories();
 
   // =========== End WebIDL bindings.============
 
+  /**
+   * Sets a semi-colon separated list of filters to filter-in or filter-out
+   * certain types of files when the contents of this directory are requested
+   * via a GetFilesAndDirectories() call.
+   *
+   * Currently supported keywords:
+   *
+   *   * filter-out-sensitive
+   *       This keyword filters out files or directories that we don't wish to
+   *       make available to Web content because we are concerned that there is
+   *       a risk that users may unwittingly give Web content access to them
+   *       and suffer undesirable consequences.  The details of what is
+   *       filtered out can be found in GetDirectoryListingTask::Work.
+   *
+   * In future, we will likely support filtering based on filename extensions
+   * (for example, aFilters could be "*.jpg; *.jpeg; *.gif"), but that isn't
+   * supported yet.  Once supported, files that don't match a specified
+   * extension (if any are specified) would be filtered out.  This
+   * functionality would allow us to apply the 'accept' attribute from
+   * <input type=file directory accept="..."> to the results of a directory
+   * picker operation.
+   */
+  void
+  SetContentFilters(const nsAString& aFilters);
+
   FileSystemBase*
   GetFileSystem() const;
 private:
   ~Directory();
 
   static bool
   IsValidRelativePath(const nsString& aPath);
 
@@ -103,14 +128,15 @@ private:
   DOMPathToRealPath(const nsAString& aPath, nsAString& aRealPath) const;
 
   already_AddRefed<Promise>
   RemoveInternal(const StringOrFileOrDirectory& aPath, bool aRecursive,
                  ErrorResult& aRv);
 
   RefPtr<FileSystemBase> mFileSystem;
   nsString mPath;
+  nsString mFilters;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Directory_h
--- a/dom/filesystem/GetDirectoryListingTask.cpp
+++ b/dom/filesystem/GetDirectoryListingTask.cpp
@@ -1,56 +1,60 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "GetDirectoryListingTask.h"
 
+#include "HTMLSplitOnSpacesTokenizer.h"
 #include "js/Value.h"
 #include "mozilla/dom/Directory.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FileSystemBase.h"
 #include "mozilla/dom/FileSystemUtils.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ipc/BlobChild.h"
 #include "mozilla/dom/ipc/BlobParent.h"
 #include "nsIFile.h"
 #include "nsStringGlue.h"
 
 namespace mozilla {
 namespace dom {
 
 GetDirectoryListingTask::GetDirectoryListingTask(FileSystemBase* aFileSystem,
                                                  const nsAString& aTargetPath,
+                                                 const nsAString& aFilters,
                                                  ErrorResult& aRv)
   : FileSystemTaskBase(aFileSystem)
   , mTargetRealPath(aTargetPath)
+  , mFilters(aFilters)
 {
   MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
   MOZ_ASSERT(aFileSystem);
   nsCOMPtr<nsIGlobalObject> globalObject =
     do_QueryInterface(aFileSystem->GetWindow());
   if (!globalObject) {
     return;
   }
   mPromise = Promise::Create(globalObject, aRv);
 }
 
 GetDirectoryListingTask::GetDirectoryListingTask(FileSystemBase* aFileSystem,
                                                  const FileSystemGetDirectoryListingParams& aParam,
                                                  FileSystemRequestParent* aParent)
   : FileSystemTaskBase(aFileSystem, aParam, aParent)
+  , mTargetRealPath(aParam.realPath())
+  , mFilters(aParam.filters())
 {
   MOZ_ASSERT(XRE_IsParentProcess(),
              "Only call from parent process!");
   MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
   MOZ_ASSERT(aFileSystem);
-  mTargetRealPath = aParam.realPath();
 }
 
 GetDirectoryListingTask::~GetDirectoryListingTask()
 {
   MOZ_ASSERT(!mPromise || NS_IsMainThread(),
              "mPromise should be released on main thread!");
 }
 
@@ -60,17 +64,18 @@ GetDirectoryListingTask::GetPromise()
   MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
   return RefPtr<Promise>(mPromise).forget();
 }
 
 FileSystemParams
 GetDirectoryListingTask::GetRequestParams(const nsString& aFileSystem) const
 {
   MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
-  return FileSystemGetDirectoryListingParams(aFileSystem, mTargetRealPath);
+  return FileSystemGetDirectoryListingParams(aFileSystem, mTargetRealPath,
+                                             mFilters);
 }
 
 FileSystemResponseValue
 GetDirectoryListingTask::GetSuccessRequestResult() const
 {
   MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
 
   InfallibleTArray<PBlobParent*> blobs;
@@ -150,16 +155,30 @@ GetDirectoryListingTask::Work()
   }
 
   nsCOMPtr<nsISimpleEnumerator> entries;
   rv = dir->GetDirectoryEntries(getter_AddRefs(entries));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  bool filterOutSensitive = false;
+  {
+    HTMLSplitOnSpacesTokenizer tokenizer(mFilters, ';');
+    nsAutoString token;
+    while (tokenizer.hasMoreTokens()) {
+      token = tokenizer.nextToken();
+      if (token.EqualsLiteral("filter-out-sensitive")) {
+        filterOutSensitive = true;
+      } else {
+        MOZ_CRASH("Unrecognized filter");
+      }
+    }
+  }
+
   for (;;) {
     bool hasMore = false;
     if (NS_WARN_IF(NS_FAILED(entries->HasMoreElements(&hasMore))) || !hasMore) {
       break;
     }
     nsCOMPtr<nsISupports> supp;
     if (NS_WARN_IF(NS_FAILED(entries->GetNext(getter_AddRefs(supp))))) {
       break;
@@ -174,16 +193,31 @@ GetDirectoryListingTask::Work()
         isLink || isSpecial) {
       continue;
     };
     if (NS_WARN_IF(NS_FAILED(currFile->IsFile(&isFile)) ||
                    NS_FAILED(currFile->IsDirectory(&isDir))) ||
         !(isFile || isDir)) {
       continue;
     }
+
+    if (filterOutSensitive) {
+      bool isHidden;
+      if (NS_WARN_IF(NS_FAILED(currFile->IsHidden(&isHidden))) || isHidden) {
+        continue;
+      }
+      nsAutoString leafName;
+      if (NS_WARN_IF(NS_FAILED(currFile->GetLeafName(leafName)))) {
+        continue;
+      }
+      if (leafName[0] == char16_t('.')) {
+        continue;
+      }
+    }
+
     BlobImplFile* impl = new BlobImplFile(currFile);
     impl->LookupAndCacheIsDirectory();
     mTargetBlobImpls.AppendElement(impl);
   }
   return NS_OK;
 }
 
 void
@@ -221,17 +255,20 @@ GetDirectoryListingTask::HandlerCallback
 #ifdef DEBUG
       if (XRE_IsParentProcess()) {
         nsCOMPtr<nsIFile> file = mFileSystem->GetLocalFile(path);
         bool exist;
         file->Exists(&exist);
         MOZ_ASSERT(exist);
       }
 #endif
-      listing[i].SetAsDirectory() = new Directory(mFileSystem, path);
+      RefPtr<Directory> directory = new Directory(mFileSystem, path);
+      // Propogate mFilter onto sub-Directory object:
+      directory->SetContentFilters(mFilters);
+      listing[i].SetAsDirectory() = directory;
     } else {
       listing[i].SetAsFile() = File::Create(mFileSystem->GetWindow(), mTargetBlobImpls[i]);
     }
   }
 
   mPromise->MaybeResolve(listing);
   mPromise = nullptr;
 }
--- a/dom/filesystem/GetDirectoryListingTask.h
+++ b/dom/filesystem/GetDirectoryListingTask.h
@@ -18,16 +18,17 @@ class BlobImpl;
 
 class GetDirectoryListingTask final
   : public FileSystemTaskBase
 {
 public:
   // If aDirectoryOnly is set, we should ensure that the target is a directory.
   GetDirectoryListingTask(FileSystemBase* aFileSystem,
                           const nsAString& aTargetPath,
+                          const nsAString& aFilters,
                           ErrorResult& aRv);
   GetDirectoryListingTask(FileSystemBase* aFileSystem,
                           const FileSystemGetDirectoryListingParams& aParam,
                           FileSystemRequestParent* aParent);
 
   virtual
   ~GetDirectoryListingTask();
 
@@ -50,16 +51,17 @@ protected:
   Work() override;
 
   virtual void
   HandlerCallback() override;
 
 private:
   RefPtr<Promise> mPromise;
   nsString mTargetRealPath;
+  nsString mFilters;
 
   // We cannot store File or Directory objects bacause this object is created
   // on a different thread and File and Directory are not thread-safe.
   nsTArray<RefPtr<BlobImpl>> mTargetBlobImpls;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -4929,18 +4929,24 @@ HTMLInputElement::GetFilesAndDirectories
       if (aRv.Failed()) {
         return nullptr;
       }
       int32_t leafSeparatorIndex = path.RFind(FILE_PATH_SEPARATOR);
       nsDependentSubstring dirname = Substring(path, 0, leafSeparatorIndex);
       fs = MakeOrReuseFileSystem(dirname, fs, window);
       nsAutoString dompath(NS_LITERAL_STRING(FILESYSTEM_DOM_PATH_SEPARATOR));
       dompath.Append(Substring(path, leafSeparatorIndex + 1));
-      filesAndDirsSeq[i].SetAsDirectory() = new Directory(fs, dompath);
+      RefPtr<Directory> directory = new Directory(fs, dompath);
+      // In future we could refactor SetFilePickerFiltersFromAccept to return a
+      // semicolon separated list of file extensions and include that in the
+      // filter string passed here.
+      directory->SetContentFilters(NS_LITERAL_STRING("filter-out-sensitive"));
+      filesAndDirsSeq[i].SetAsDirectory() = directory;
     } else {
+      // This file was directly selected by the user, so don't filter it.
       filesAndDirsSeq[i].SetAsFile() = filesAndDirs[i];
     }
   }
 
   p->MaybeResolve(filesAndDirsSeq);
 
   // Cache the Promise so that repeat getFilesAndDirectories() calls return
   // the same Promise and array of File and Directory objects until the user
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -271,16 +271,26 @@ struct FileSystemCreateFileParams
   FileSystemFileDataValue data;
   bool replace;
 };
 
 struct FileSystemGetDirectoryListingParams
 {
   nsString filesystem;
   nsString realPath;
+  // 'filters' could be an array rather than a semicolon separated string
+  // (we'd then use InfallibleTArray<nsString> internally), but that is
+  // wasteful.  E10s requires us to pass the filters over as a string anyway,
+  // so avoiding using an array avoids serialization on the side passing the
+  // filters.  Since an nsString can share its buffer when copied,
+  // using that instead of InfallibleTArray<nsString> makes copying the filters
+  // around in any given process a bit more efficient too, since copying a
+  // single nsString is cheaper than copying InfallibleTArray member data and
+  // each nsString that it contains.
+  nsString filters;
 };
 
 struct FileSystemGetFileOrDirectoryParams
 {
   nsString filesystem;
   nsString realPath;
 };