Bug 1536796 - P2 - Handle disable string paring in nsLocalFileWin; draft
authorTom Tung <ttung@mozilla.com>
Fri, 27 Mar 2020 10:17:52 +0000
changeset 2725578 d74941fa0d4b4f61612b1e96a446407200c61dd2
parent 2725577 05a67e26eb99a98f30bdafebf73f463c75a60432
child 2725579 44440be59aab79e748d0b43ebd0d641e09bc07d4
push id510269
push userreviewbot
push dateFri, 27 Mar 2020 10:18:13 +0000
treeherdertry@44440be59aab [default view] [failures only]
bugs1536796
milestone76.0a1
Bug 1536796 - P2 - Handle disable string paring in nsLocalFileWin; Summary: In the Windows API, the maximum length for a path is MAX_PATH in general. However, the Windows API has many functions that also have Unicode versions to permit an extended-length path for a maximum total path length of 32,767 characters. To specify an extended-length path, use the "\\?\" prefix. A path component which ends with a dot is not allowed for Windows API. However, using the "\\?\" prefix can also resolved this issue. This patch aims to fix the issues which are mentioned above by prepending the prefix to the path of nsLocalFile if mDisableStringParsing is set to true. Differential Revision: https://phabricator.services.mozilla.com/D67014 Test Plan: Reviewers: Subscribers: Bug #: 1536796 Differential Diff: PHID-DIFF-4bjdqkq6ib35dfcnyvdz
xpcom/io/FilePreferences.h
xpcom/io/nsLocalFileWin.cpp
xpcom/io/nsLocalFileWin.h
--- a/xpcom/io/FilePreferences.h
+++ b/xpcom/io/FilePreferences.h
@@ -14,16 +14,18 @@ void InitDirectoriesWhitelist();
 bool IsBlockedUNCPath(const nsAString& aFilePath);
 
 #ifdef XP_WIN
 bool IsAllowedPath(const nsAString& aFilePath);
 #else
 bool IsAllowedPath(const nsACString& aFilePath);
 #endif
 
+extern const char kPathSeparator;
+
 namespace testing {
 
 void SetBlockUNCPaths(bool aBlock);
 void AddDirectoryToWhitelist(nsAString const& aPath);
 bool NormalizePath(nsAString const& aPath, nsAString& aNormalized);
 
 }  // namespace testing
 
--- a/xpcom/io/nsLocalFileWin.cpp
+++ b/xpcom/io/nsLocalFileWin.cpp
@@ -54,16 +54,17 @@
 
 #include "mozIDOMWindow.h"
 #include "nsPIDOMWindow.h"
 #include "nsIWidget.h"
 #include "mozilla/ShellHeaderOnlyUtils.h"
 #include "mozilla/WidgetUtils.h"
 
 using namespace mozilla;
+using mozilla::FilePreferences::kPathSeparator;
 
 #define CHECK_mWorkingPath()                                     \
   do {                                                           \
     if (mWorkingPath.IsEmpty()) return NS_ERROR_NOT_INITIALIZED; \
   } while (0)
 
 // CopyFileEx only supports unbuffered I/O in Windows Vista and above
 #ifndef COPY_FILE_NO_BUFFERING
@@ -73,16 +74,42 @@ using namespace mozilla;
 #ifndef FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
 #  define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000
 #endif
 
 #ifndef DRIVE_REMOTE
 #  define DRIVE_REMOTE 4
 #endif
 
+constexpr auto kDevicePathSpecifier = NS_LITERAL_STRING("\\\\?\\");
+
+namespace {
+
+bool StartsWithDiskDesignator(const nsAString& aAbsolutePath) {
+  // aPath can only be (in regular expression):
+  // UNC path: \\.*
+  // A single backslash: \.*
+  // A disk designator with a backslash: ${Drive}:\.*
+  return aAbsolutePath.Length() >= 3 && aAbsolutePath.CharAt(1) == L':' &&
+         aAbsolutePath.CharAt(2) == kPathSeparator;
+}
+
+void EnsureUseDOSDevicePathSyntax(nsIFile* aFile,
+                                  bool aUseDOSDevicePathSyntax) {
+  MOZ_ASSERT(aFile);
+
+  if (aUseDOSDevicePathSyntax) {
+    nsCOMPtr<nsILocalFileWin> file = do_QueryInterface(aFile);
+    MOZ_ASSERT(file);
+    file->SetUseDOSDevicePathSyntax(true);
+  }
+}
+
+}  // anonymous namespace
+
 static HWND GetMostRecentNavigatorHWND() {
   nsresult rv;
   nsCOMPtr<nsIWindowMediator> winMediator(
       do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv));
   if (NS_FAILED(rv)) {
     return nullptr;
   }
 
@@ -159,17 +186,17 @@ nsresult nsLocalFile::RevealFile(const n
   }
 
   return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
 }
 
 class nsDriveEnumerator : public nsSimpleEnumerator,
                           public nsIDirectoryEnumerator {
  public:
-  nsDriveEnumerator();
+  explicit nsDriveEnumerator(bool aUseDOSDevicePathSyntax);
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSISIMPLEENUMERATOR
   NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::)
   nsresult Init();
 
   const nsID& DefaultInterface() override { return NS_GET_IID(nsIFile); }
 
   NS_IMETHOD GetNextFile(nsIFile** aResult) override {
@@ -195,16 +222,17 @@ class nsDriveEnumerator : public nsSimpl
   /* mDrives stores the null-separated drive names.
    * Init sets them.
    * HasMoreElements checks mStartOfCurrentDrive.
    * GetNext advances mStartOfCurrentDrive.
    */
   nsString mDrives;
   nsAString::const_iterator mStartOfCurrentDrive;
   nsAString::const_iterator mEndOfDrivesString;
+  const bool mUseDOSDevicePathSyntax;
 };
 
 //-----------------------------------------------------------------------------
 // static helper functions
 //-----------------------------------------------------------------------------
 
 // |out| must be an allocated buffer of size MAX_PATH
 static nsresult ResolveShellLink(const WCHAR* aIn, WCHAR* aOut) {
@@ -440,22 +468,31 @@ static void FileTimeToPRTime(const FILET
 #else
   *aPrtm = (*aPrtm - _pr_filetime_offset) / 10i64;
 #endif
 }
 
 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} with some
 // changes : PR_GetFileInfo64, _PR_MD_GETFILEINFO64
 static nsresult GetFileInfo(const nsString& aName, PRFileInfo64* aInfo) {
-  WIN32_FILE_ATTRIBUTE_DATA fileData;
-
-  if (aName.IsEmpty() || aName.FindCharInSet(u"?*") != kNotFound) {
+  if (aName.IsEmpty()) {
     return NS_ERROR_INVALID_ARG;
   }
 
+  // Checking u"?*" for the file path excluding the kDevicePathSpecifier.
+  // ToDo: Check if checking "?" for the file path is still needed.
+  const int32_t offset = StringBeginsWith(aName, kDevicePathSpecifier)
+                             ? kDevicePathSpecifier.Length()
+                             : 0;
+
+  if (aName.FindCharInSet(u"?*", offset) != kNotFound) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  WIN32_FILE_ATTRIBUTE_DATA fileData;
   if (!::GetFileAttributesExW(aName.get(), GetFileExInfoStandard, &fileData)) {
     return ConvertWinError(GetLastError());
   }
 
   if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
     aInfo->type = PR_FILE_DIRECTORY;
   } else {
     aInfo->type = PR_FILE_FILE;
@@ -483,19 +520,16 @@ struct nsDir {
 };
 
 static nsresult OpenDir(const nsString& aName, nsDir** aDir) {
   if (NS_WARN_IF(!aDir)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   *aDir = nullptr;
-  if (aName.Length() + 3 >= MAX_PATH) {
-    return NS_ERROR_FILE_NAME_TOO_LONG;
-  }
 
   nsDir* d = new nsDir();
   nsAutoString filename(aName);
 
   // If |aName| ends in a slash or backslash, do not append another backslash.
   if (filename.Last() == L'/' || filename.Last() == L'\\') {
     filename.Append('*');
   } else {
@@ -699,19 +733,23 @@ class nsDirEnumerator final : public nsS
 NS_IMPL_ISUPPORTS_INHERITED(nsDirEnumerator, nsSimpleEnumerator,
                             nsIDirectoryEnumerator)
 
 //-----------------------------------------------------------------------------
 // nsLocalFile <public>
 //-----------------------------------------------------------------------------
 
 nsLocalFile::nsLocalFile()
-    : mDirty(true), mResolveDirty(true), mFollowSymlinks(false) {}
-
-nsLocalFile::nsLocalFile(const nsAString& aFilePath) : mFollowSymlinks(false) {
+    : mDirty(true),
+      mResolveDirty(true),
+      mFollowSymlinks(false),
+      mUseDOSDevicePathSyntax(false) {}
+
+nsLocalFile::nsLocalFile(const nsAString& aFilePath)
+    : mFollowSymlinks(false), mUseDOSDevicePathSyntax(false) {
   InitWithPath(aFilePath);
 }
 
 nsresult nsLocalFile::nsLocalFileConstructor(nsISupports* aOuter,
                                              const nsIID& aIID,
                                              void** aInstancePtr) {
   if (NS_WARN_IF(!aInstancePtr)) {
     return NS_ERROR_INVALID_ARG;
@@ -738,16 +776,17 @@ NS_IMPL_ISUPPORTS(nsLocalFile, nsIFile, 
 //-----------------------------------------------------------------------------
 // nsLocalFile <private>
 //-----------------------------------------------------------------------------
 
 nsLocalFile::nsLocalFile(const nsLocalFile& aOther)
     : mDirty(true),
       mResolveDirty(true),
       mFollowSymlinks(aOther.mFollowSymlinks),
+      mUseDOSDevicePathSyntax(aOther.mUseDOSDevicePathSyntax),
       mWorkingPath(aOther.mWorkingPath) {}
 
 // Resolve the shortcut file from mWorkingPath and write the path
 // it points to into mResolvedPath.
 nsresult nsLocalFile::ResolveShortcut() {
   mResolvedPath.SetLength(MAX_PATH);
   if (mResolvedPath.Length() != MAX_PATH) {
     return NS_ERROR_OUT_OF_MEMORY;
@@ -861,16 +900,23 @@ nsresult nsLocalFile::Resolve() {
     mResolvedPath.Assign(mWorkingPath);
     return rv;
   }
 
   mResolveDirty = false;
   return NS_OK;
 }
 
+void nsLocalFile::EnsureUseDOSDevicePathSyntaxPrefix() {
+  if (mUseDOSDevicePathSyntax && StartsWithDiskDesignator(mWorkingPath)) {
+    mWorkingPath = kDevicePathSpecifier + mWorkingPath;
+    return;
+  }
+}
+
 //-----------------------------------------------------------------------------
 // nsLocalFile::nsIFile
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsLocalFile::Clone(nsIFile** aFile) {
   // Just copy-construct ourselves
   RefPtr<nsLocalFile> file = new nsLocalFile(*this);
@@ -932,16 +978,18 @@ nsLocalFile::InitWithPath(const nsAStrin
   }
 
   mWorkingPath = aFilePath;
   // kill any trailing '\'
   if (mWorkingPath.Last() == L'\\') {
     mWorkingPath.Truncate(mWorkingPath.Length() - 1);
   }
 
+  EnsureUseDOSDevicePathSyntaxPrefix();
+
   return NS_OK;
 }
 
 // Strip a handler command string of its quotes and parameters.
 static void CleanupHandlerPath(nsString& aPath) {
   // Example command strings passed into this routine:
 
   // 1) C:\Program Files\Company\some.exe -foo -bar
@@ -1412,20 +1460,19 @@ nsLocalFile::Normalize() {
         }
       }
     }
 
     // add the current component to the path, including the preceding backslash
     mWorkingPath.Append(pathBuffer + begin - 1, len + 1);
   }
 
-  // kill trailing dots and spaces.
+  // kill trailing spaces.
   int32_t filePathLen = mWorkingPath.Length() - 1;
-  while (filePathLen > 0 && (mWorkingPath[filePathLen] == L' ' ||
-                             mWorkingPath[filePathLen] == L'.')) {
+  while (filePathLen > 0 && mWorkingPath[filePathLen] == L' ') {
     mWorkingPath.Truncate(filePathLen--);
   }
 
   MakeDirty();
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -1463,16 +1510,18 @@ nsLocalFile::SetLeafName(const nsAString
   }
   mWorkingPath.Append(aLeafName);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsLocalFile::GetPath(nsAString& aResult) {
+  MOZ_ASSERT_IF(mUseDOSDevicePathSyntax,
+                !StartsWithDiskDesignator(mWorkingPath));
   aResult = mWorkingPath;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsLocalFile::GetCanonicalPath(nsAString& aResult) {
   EnsureShortPath();
   aResult.Assign(mShortWorkingPath);
@@ -1609,16 +1658,24 @@ nsresult nsLocalFile::CopySingleFile(nsI
     rv = aSourceFile->GetTarget(filePath);
     if (filePath.IsEmpty()) {
       rv = aSourceFile->GetPath(filePath);
     }
   } else {
     rv = aSourceFile->GetPath(filePath);
   }
 
+  MOZ_ASSERT_IF(mUseDOSDevicePathSyntax, !StartsWithDiskDesignator(filePath));
+
+  if (mUseDOSDevicePathSyntax && StartsWithDiskDesignator(destPath)) {
+    MOZ_ASSERT(StringBeginsWith(filePath, kDevicePathSpecifier));
+
+    destPath = kDevicePathSpecifier + destPath;
+  }
+
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   if (FilePreferences::IsBlockedUNCPath(destPath)) {
     return NS_ERROR_FILE_ACCESS_DENIED;
   }
 
@@ -2548,16 +2605,18 @@ nsLocalFile::GetParent(nsIFile** aParent
   nsCOMPtr<nsIFile> localFile;
   nsresult rv =
       NS_NewLocalFile(parentPath, mFollowSymlinks, getter_AddRefs(localFile));
 
   if (NS_FAILED(rv)) {
     return rv;
   }
 
+  EnsureUseDOSDevicePathSyntax(localFile, mUseDOSDevicePathSyntax);
+
   localFile.forget(aParent);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsLocalFile::Exists(bool* aResult) {
   // Check we are correctly initialized.
   CHECK_mWorkingPath();
@@ -2805,18 +2864,37 @@ nsLocalFile::Equals(nsIFile* aInFile, bo
   if (!lf) {
     *aResult = false;
     return NS_OK;
   }
 
   nsAutoString inFilePath;
   lf->GetCanonicalPath(inFilePath);
 
+  bool inUseDOSDevicePathSyntax;
+  lf->GetUseDOSDevicePathSyntax(&inUseDOSDevicePathSyntax);
+
+  nsAutoString shortWorkingPath;
+  if (!inUseDOSDevicePathSyntax && mUseDOSDevicePathSyntax &&
+      StartsWithDiskDesignator(inFilePath)) {
+    MOZ_ASSERT(StringBeginsWith(mShortWorkingPath, kDevicePathSpecifier));
+
+    inFilePath = kDevicePathSpecifier + inFilePath;
+    shortWorkingPath = mShortWorkingPath;
+  } else if (inUseDOSDevicePathSyntax && !mUseDOSDevicePathSyntax &&
+             StartsWithDiskDesignator(mShortWorkingPath)) {
+    MOZ_ASSERT(StringBeginsWith(inFilePath, kDevicePathSpecifier));
+
+    shortWorkingPath = kDevicePathSpecifier + mShortWorkingPath;
+  } else {
+    shortWorkingPath = mShortWorkingPath;
+  }
+
   // Ok : Win9x
-  *aResult = _wcsicmp(mShortWorkingPath.get(), inFilePath.get()) == 0;
+  *aResult = _wcsicmp(shortWorkingPath.get(), inFilePath.get()) == 0;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsLocalFile::Contains(nsIFile* aInFile, bool* aResult) {
   // Check we are correctly initialized.
   CHECK_mWorkingPath();
@@ -2846,16 +2924,19 @@ nsLocalFile::Contains(nsIFile* aInFile, 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsLocalFile::GetTarget(nsAString& aResult) {
   aResult.Truncate();
   Resolve();
 
+  MOZ_ASSERT_IF(mUseDOSDevicePathSyntax,
+                !StartsWithDiskDesignator(mResolvedPath));
+
   aResult = mResolvedPath;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsLocalFile::GetFollowLinks(bool* aFollowLinks) {
   *aFollowLinks = mFollowSymlinks;
   return NS_OK;
@@ -2868,17 +2949,18 @@ nsLocalFile::SetFollowLinks(bool aFollow
 }
 
 NS_IMETHODIMP
 nsLocalFile::GetDirectoryEntriesImpl(nsIDirectoryEnumerator** aEntries) {
   nsresult rv;
 
   *aEntries = nullptr;
   if (mWorkingPath.EqualsLiteral("\\\\.")) {
-    RefPtr<nsDriveEnumerator> drives = new nsDriveEnumerator;
+    RefPtr<nsDriveEnumerator> drives =
+        new nsDriveEnumerator(mUseDOSDevicePathSyntax);
     rv = drives->Init();
     if (NS_FAILED(rv)) {
       return rv;
     }
     drives.forget(aEntries);
     return NS_OK;
   }
 
@@ -2955,16 +3037,17 @@ nsLocalFile::GetUseDOSDevicePathSyntax(b
 
   *aUseDOSDevicePathSyntax = mUseDOSDevicePathSyntax;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsLocalFile::SetUseDOSDevicePathSyntax(bool aUseDOSDevicePathSyntax) {
   mUseDOSDevicePathSyntax = aUseDOSDevicePathSyntax;
+  EnsureUseDOSDevicePathSyntaxPrefix();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsLocalFile::Reveal() {
   // This API should be main thread only
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -3277,17 +3360,18 @@ void nsLocalFile::EnsureShortPath() {
   } else {
     mShortWorkingPath.Assign(mWorkingPath);
   }
 }
 
 NS_IMPL_ISUPPORTS_INHERITED(nsDriveEnumerator, nsSimpleEnumerator,
                             nsIDirectoryEnumerator)
 
-nsDriveEnumerator::nsDriveEnumerator() {}
+nsDriveEnumerator::nsDriveEnumerator(bool aUseDOSDevicePathSyntax)
+    : mUseDOSDevicePathSyntax(aUseDOSDevicePathSyntax) {}
 
 nsDriveEnumerator::~nsDriveEnumerator() {}
 
 nsresult nsDriveEnumerator::Init() {
   /* If the length passed to GetLogicalDriveStrings is smaller
    * than the length of the string it would return, it returns
    * the length required for the string. */
   DWORD length = GetLogicalDriveStringsW(0, 0);
@@ -3323,11 +3407,13 @@ nsDriveEnumerator::GetNext(nsISupports**
   nsAString::const_iterator driveEnd = mStartOfCurrentDrive;
   FindCharInReadable(L'\0', driveEnd, mEndOfDrivesString);
   nsString drive(Substring(mStartOfCurrentDrive, driveEnd));
   mStartOfCurrentDrive = ++driveEnd;
 
   nsIFile* file;
   nsresult rv = NS_NewLocalFile(drive, false, &file);
 
+  EnsureUseDOSDevicePathSyntax(file, mUseDOSDevicePathSyntax);
+
   *aNext = file;
   return rv;
 }
--- a/xpcom/io/nsLocalFileWin.h
+++ b/xpcom/io/nsLocalFileWin.h
@@ -60,17 +60,17 @@ class nsLocalFile final : public nsILoca
 
   nsLocalFile(const nsLocalFile& aOther);
   ~nsLocalFile() {}
 
   bool mDirty;  // cached information can only be used when this is false
   bool mResolveDirty;
   bool mFollowSymlinks;  // should we follow symlinks when working on this file
 
-  bool mUseDOSDevicePathSyntax = false;
+  bool mUseDOSDevicePathSyntax;
 
   // this string will always be in native format!
   nsString mWorkingPath;
 
   // this will be the resolved path of shortcuts, it will *NEVER*
   // be returned to the user
   nsString mResolvedPath;
 
@@ -88,16 +88,21 @@ class nsLocalFile final : public nsILoca
 
   nsresult LookupExtensionIn(const char* const* aExtensionsArray,
                              size_t aArrayLength, bool* aResult);
 
   nsresult ResolveAndStat();
   nsresult Resolve();
   nsresult ResolveShortcut();
 
+  // This function ensures a parsed file path to use the "\\?\" if the
+  // mUseDOSDevicePathSyntax is true and the file path matches ${Disk}:/.*
+  // (regex) syntax.
+  void EnsureUseDOSDevicePathSyntaxPrefix();
+
   void EnsureShortPath();
 
   nsresult CopyMove(nsIFile* aNewParentDir, const nsAString& aNewName,
                     uint32_t aOptions);
   nsresult CopySingleFile(nsIFile* aSource, nsIFile* aDest,
                           const nsAString& aNewName, uint32_t aOptions);
 
   nsresult SetModDate(int64_t aLastModifiedTime, const wchar_t* aFilePath);