Bug 1689598 r=tkikuchi,mhowell,dmajor,valentin
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Tue, 02 Feb 2021 12:36:56 +0000
changeset 565586 ca17538f31ff62a0c6769ff0cd8d2af317a4549e
parent 565585 1d13f4438c9ef7a42f23c2fbd2cbd8005245d61b
child 565587 87d8d318c0b77280fdfe4b9a829049e743013b5f
push id38164
push usermalexandru@mozilla.com
push dateTue, 02 Feb 2021 21:48:09 +0000
treeherdermozilla-central@5ff587e0026f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstkikuchi, mhowell, dmajor, valentin
bugs1689598
milestone87.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 1689598 r=tkikuchi,mhowell,dmajor,valentin Differential Revision: https://phabricator.services.mozilla.com/D103523
docshell/base/URIFixup.jsm
xpcom/io/nsLocalFileWin.cpp
--- a/docshell/base/URIFixup.jsm
+++ b/docshell/base/URIFixup.jsm
@@ -38,16 +38,23 @@ XPCOMUtils.defineLazyServiceGetter(
 
 XPCOMUtils.defineLazyServiceGetter(
   this,
   "defaultProtocolHandler",
   "@mozilla.org/network/protocol;1?name=default",
   "nsIProtocolHandler"
 );
 
+XPCOMUtils.defineLazyServiceGetter(
+  this,
+  "fileProtocolHandler",
+  "@mozilla.org/network/protocol;1?name=file",
+  "nsIFileProtocolHandler"
+);
+
 XPCOMUtils.defineLazyPreferenceGetter(
   this,
   "fixupSchemeTypos",
   "browser.fixup.typo.scheme",
   true
 );
 XPCOMUtils.defineLazyPreferenceGetter(
   this,
@@ -817,17 +824,19 @@ function fileURIFixup(uriString) {
     attemptFixup = uriString.startsWith("/");
   }
   if (attemptFixup) {
     try {
       // Test if this is a valid path by trying to create a local file
       // object. The URL of that is returned if successful.
       let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
       file.initWithPath(uriString);
-      return Services.io.newFileURI(file);
+      return Services.io.newURI(
+        fileProtocolHandler.getURLSpecFromActualFile(file)
+      );
     } catch (ex) {
       // Not a file uri.
     }
   }
   return null;
 }
 
 /**
--- a/xpcom/io/nsLocalFileWin.cpp
+++ b/xpcom/io/nsLocalFileWin.cpp
@@ -1,16 +1,17 @@
 /* -*- 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 "mozilla/ArrayUtils.h"
 #include "mozilla/DebugOnly.h"
+#include "mozilla/TextUtils.h"
 #include "mozilla/UniquePtrExtensions.h"
 #include "mozilla/Utf8.h"
 
 #include "nsCOMPtr.h"
 #include "nsMemory.h"
 #include "GeckoProfiler.h"
 
 #include "nsLocalFile.h"
@@ -306,16 +307,119 @@ static __int64 MyFileSeek64(HANDLE aHand
   li.LowPart = SetFilePointer(aHandle, li.LowPart, &li.HighPart, aMoveMethod);
   if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) {
     li.QuadPart = -1;
   }
 
   return li.QuadPart;
 }
 
+// Check whether a path is a volume root. Expects paths to be \-terminated.
+static bool IsRootPath(const nsAString& aPath) {
+  // Easy cases first:
+  if (aPath.Last() != L'\\') {
+    return false;
+  }
+  if (StringEndsWith(aPath, u":\\"_ns)) {
+    return true;
+  }
+
+  nsAString::const_iterator begin, end;
+  aPath.BeginReading(begin);
+  aPath.EndReading(end);
+  // We know we've got a trailing slash, skip that:
+  end--;
+  // Find the next last slash:
+  if (RFindInReadable(u"\\"_ns, begin, end)) {
+    // Reset iterator:
+    aPath.EndReading(end);
+    end--;
+    auto lastSegment = Substring(++begin, end);
+    if (lastSegment.IsEmpty()) {
+      return false;
+    }
+
+    // Check if we end with e.g. "c$", a drive letter in UNC or network shares
+    if (lastSegment.Last() == L'$' && lastSegment.Length() == 2 &&
+        IsAsciiAlpha(lastSegment.First())) {
+      return true;
+    }
+    // Volume GUID paths:
+    if (StringBeginsWith(lastSegment, u"Volume{"_ns) &&
+        lastSegment.Last() == L'}') {
+      return true;
+    }
+  }
+  return false;
+}
+
+static auto kSpecialNTFSFilesInRoot = {
+    u"$MFT"_ns,     u"$MFTMirr"_ns, u"$LogFile"_ns, u"$Volume"_ns,
+    u"$AttrDef"_ns, u"$Bitmap"_ns,  u"$Boot"_ns,    u"$BadClus"_ns,
+    u"$Secure"_ns,  u"$UpCase"_ns,  u"$Extend"_ns};
+static bool IsSpecialNTFSPath(const nsAString& aFilePath) {
+  nsAString::const_iterator begin, end;
+  aFilePath.BeginReading(begin);
+  aFilePath.EndReading(end);
+  auto iter = begin;
+  // Early exit if there's no '$' (common case)
+  if (!FindCharInReadable(L'$', iter, end)) {
+    return false;
+  }
+
+  iter = begin;
+  // Any use of ':$' is illegal in filenames anyway; while we support some
+  // ADS stuff (ie ":Zone.Identifier"), none of them use the ':$' syntax:
+  if (FindInReadable(u":$"_ns, iter, end)) {
+    return true;
+  }
+
+  auto normalized = mozilla::MakeUniqueFallible<wchar_t[]>(MAX_PATH);
+  if (!normalized) {
+    return true;
+  }
+  auto flatPath = PromiseFlatString(aFilePath);
+  auto fullPathRV =
+      GetFullPathNameW(flatPath.get(), MAX_PATH - 1, normalized.get(), nullptr);
+  if (fullPathRV == 0 || fullPathRV > MAX_PATH - 1) {
+    return false;
+  }
+
+  nsString normalizedPath(normalized.get());
+  normalizedPath.BeginReading(begin);
+  normalizedPath.EndReading(end);
+  iter = begin;
+  auto kDelimiters = u"\\:"_ns;
+  while (iter != end && FindCharInReadable(L'$', iter, end)) {
+    for (auto str : kSpecialNTFSFilesInRoot) {
+      if (StringBeginsWith(Substring(iter, end), str,
+                           nsCaseInsensitiveStringComparator)) {
+        // If we're enclosed by separators or the beginning/end of the string,
+        // this is one of the special files. Check if we're on a volume root.
+        auto iterCopy = iter;
+        iterCopy.advance(str.Length());
+        // We check for both \ and : here because the filename could be
+        // followd by a colon and a stream name/type, which shouldn't affect
+        // our check:
+        if (iterCopy == end || kDelimiters.Contains(*iterCopy)) {
+          iterCopy = iter;
+          // At the start of this path component, we don't need to care about
+          // colons: we would have caught those in the check for `:$` above.
+          if (iterCopy == begin || *(--iterCopy) == L'\\') {
+            return IsRootPath(Substring(begin, iter));
+          }
+        }
+      }
+    }
+    iter++;
+  }
+
+  return false;
+}
+
 //-----------------------------------------------------------------------------
 // We need the following three definitions to make |OpenFile| convert a file
 // handle to an NSPR file descriptor correctly when |O_APPEND| flag is
 // specified. It is defined in a private header of NSPR (primpl.h) we can't
 // include. As a temporary workaround until we decide how to extend
 // |PR_ImportFile|, we define it here. Currently, |_PR_HAVE_PEEK_BUFFER|
 // and |PR_STRICT_ADDR_LEN| are not defined for the 'w95'-dependent portion
 // of NSPR so that fields of |PRFilePrivate| #ifdef'd by them are not copied.
@@ -916,16 +1020,20 @@ nsLocalFile::InitWithPath(const nsAStrin
   if (secondChar == L':') {
     // Make sure we have a valid drive, later code assumes the drive letter
     // is a single char a-z or A-Z.
     if (PathGetDriveNumberW(aFilePath.Data()) == -1) {
       return NS_ERROR_FILE_UNRECOGNIZED_PATH;
     }
   }
 
+  if (IsSpecialNTFSPath(aFilePath)) {
+    return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+  }
+
   mWorkingPath = aFilePath;
   // kill any trailing '\'
   if (mWorkingPath.Last() == L'\\') {
     mWorkingPath.Truncate(mWorkingPath.Length() - 1);
   }
 
   // Bug 1626514: make sure that we don't end up with multiple prefixes.
 
@@ -1231,16 +1339,22 @@ nsresult nsLocalFile::AppendInternal(con
     return NS_ERROR_FILE_UNRECOGNIZED_PATH;
   }
 
   MakeDirty();
 
   mWorkingPath.Append('\\');
   mWorkingPath.Append(aNode);
 
+  if (IsSpecialNTFSPath(mWorkingPath)) {
+    // Revert changes to mWorkingPath:
+    mWorkingPath.SetLength(mWorkingPath.Length() - aNode.Length() - 1);
+    return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+  }
+
   return NS_OK;
 }
 
 nsresult nsLocalFile::OpenNSPRFileDescMaybeShareDelete(int32_t aFlags,
                                                        int32_t aMode,
                                                        bool aShareDelete,
                                                        PRFileDesc** aResult) {
   return OpenFile(mWorkingPath, aFlags, aMode, aShareDelete, aResult);
@@ -1437,20 +1551,27 @@ nsLocalFile::SetLeafName(const nsAString
   MakeDirty();
 
   if (mWorkingPath.IsEmpty()) {
     return NS_ERROR_FILE_UNRECOGNIZED_PATH;
   }
 
   // cannot use nsCString::RFindChar() due to 0x5c problem
   int32_t offset = mWorkingPath.RFindChar(L'\\');
+  nsString newDir;
   if (offset) {
-    mWorkingPath.Truncate(offset + 1);
+    newDir = Substring(mWorkingPath, 0, offset + 1) + aLeafName;
+  } else {
+    newDir = mWorkingPath + aLeafName;
   }
-  mWorkingPath.Append(aLeafName);
+  if (IsSpecialNTFSPath(newDir)) {
+    return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+  }
+
+  mWorkingPath.Assign(newDir);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsLocalFile::GetPath(nsAString& aResult) {
   MOZ_ASSERT_IF(
       mUseDOSDevicePathSyntax,