Bug 1746052, don't allow Windows reserved filenames when sanitizing filenames. Move MangleTextToValidFileName to nsLocalFileWin and rename it to CheckForReservedFileName, r=Gijs
☠☠ backed out by 15cebc1bbdef ☠ ☠
authorNeil Deakin <neil@mozilla.com>
Tue, 03 May 2022 19:44:27 +0000
changeset 616004 30de4b77f24203072f4a612dce0f6c55750b4c57
parent 616003 ebc6720fdab3b528fe21fbc368ac02b07899765a
child 616005 b0ef7c68abcfe37cf4e0324efee92c49a5fde6a5
push id39645
push userimoraru@mozilla.com
push dateWed, 04 May 2022 03:39:47 +0000
treeherdermozilla-central@7fafa8a52cb0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs
bugs1746052
milestone102.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 1746052, don't allow Windows reserved filenames when sanitizing filenames. Move MangleTextToValidFileName to nsLocalFileWin and rename it to CheckForReservedFileName, r=Gijs Differential Revision: https://phabricator.services.mozilla.com/D138737
netwerk/mime/nsIMIMEService.idl
uriloader/exthandler/nsExternalHelperAppService.cpp
widget/windows/nsDataObj.cpp
xpcom/io/nsLocalFileWin.cpp
xpcom/io/nsLocalFileWin.h
--- a/netwerk/mime/nsIMIMEService.idl
+++ b/netwerk/mime/nsIMIMEService.idl
@@ -163,16 +163,18 @@ interface nsIMIMEService : nsISupports {
      *    replaced by spaces (*, :, etc)
      *  - Bidi related marks are replaced by underscores (_)
      *  - Whitespace and periods are removed from the beginning and end.
      *  - Unless VALIDATE_DONT_COLLAPSE_WHITESPACE is specified, multiple
      *    consecutive whitespace characters are collapsed to a single space
      *    character, either ' ' or an ideographic space 0x3000 if present.
      *  - Unless VALIDATE_DONT_TRUNCATE is specified, the filename is truncated
      *    to a maximum length, preserving the extension if possible.
+     *  - Some filenames are invalid on certain platforms. These are replaced if
+     *    possible.
      *
      * If the VALIDATE_NO_DEFAULT_FILENAME flag is not specified, and after the
      * rules above are applied, the resulting filename is empty, a default
      * filename is used.
      *
      * If the VALIDATE_ALLOW_EMPTY flag is specified, an empty string may be
      * returned only if the filename could not be determined or was blank.
      *
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -100,16 +100,17 @@
 #include "nsXULAppAPI.h"
 #include "nsPIDOMWindow.h"
 #include "ExternalHelperAppChild.h"
 
 #include "mozilla/dom/nsHTTPSOnlyUtils.h"
 
 #ifdef XP_WIN
 #  include "nsWindowsHelpers.h"
+#  include "nsLocalFile.h"
 #endif
 
 #include "mozilla/Components.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ipc/URIUtils.h"
 
 using namespace mozilla;
@@ -3445,16 +3446,20 @@ nsExternalHelperAppService::ValidateFile
         if (modify != ModifyExtension_Ignore && !extension.IsEmpty()) {
           fileName.AppendLiteral(".");
           fileName.Append(NS_ConvertUTF8toUTF16(extension));
         }
       }
     }
   }
 
+#ifdef XP_WIN
+  nsLocalFile::CheckForReservedFileName(fileName);
+#endif
+
   // If no filename is present, use a default filename.
   if (!(aFlags & VALIDATE_NO_DEFAULT_FILENAME) &&
       (fileName.Length() == 0 || fileName.RFind(".") == 0)) {
     nsCOMPtr<nsIStringBundleService> stringService =
         mozilla::components::StringBundle::Service();
     if (stringService) {
       nsCOMPtr<nsIStringBundle> bundle;
       if (NS_SUCCEEDED(stringService->CreateBundle(
--- a/widget/windows/nsDataObj.cpp
+++ b/widget/windows/nsDataObj.cpp
@@ -39,16 +39,17 @@
 #include "mozilla/Preferences.h"
 #include "nsContentUtils.h"
 #include "nsIPrincipal.h"
 #include "nsNativeCharsetUtils.h"
 #include "nsMimeTypes.h"
 #include "imgIEncoder.h"
 #include "imgITools.h"
 #include "WinUtils.h"
+#include "nsLocalFile.h"
 
 #include "mozilla/LazyIdleThread.h"
 #include <algorithm>
 
 using namespace mozilla;
 using namespace mozilla::glue;
 using namespace mozilla::widget;
 
@@ -1083,58 +1084,28 @@ nsDataObj ::GetFileContents(FORMATETC& a
   else
     NS_WARNING("Not yet implemented\n");
 
   return res;
 
 }  // GetFileContents
 
 //
-// Given a unicode string, we ensure that it contains only characters which are
-// valid within the file system. Remove all forbidden characters from the name,
-// and completely disallow any title that starts with a forbidden name and
-// extension (e.g. "nul" is invalid, but "nul." and "nul.txt" are also invalid
-// and will cause problems).
-//
-// It would seem that this is more functionality suited to being in nsIFile.
-//
-static void MangleTextToValidFilename(nsString& aText) {
-  static const char* forbiddenNames[] = {
-      "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7",  "COM8",
-      "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6",  "LPT7",
-      "LPT8", "LPT9", "CON",  "PRN",  "AUX",  "NUL",  "CLOCK$"};
-
-  aText.StripChars(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS);
-  aText.CompressWhitespace(true, true);
-  uint32_t nameLen;
-  for (size_t n = 0; n < ArrayLength(forbiddenNames); ++n) {
-    nameLen = (uint32_t)strlen(forbiddenNames[n]);
-    if (aText.EqualsIgnoreCase(forbiddenNames[n], nameLen)) {
-      // invalid name is either the entire string, or a prefix with a period
-      if (aText.Length() == nameLen || aText.CharAt(nameLen) == char16_t('.')) {
-        aText.Truncate();
-        break;
-      }
-    }
-  }
-}
-
-//
 // Given a unicode string, convert it down to a valid local charset filename
 // with the supplied extension. This ensures that we do not cut MBCS characters
 // in the middle.
 //
 // It would seem that this is more functionality suited to being in nsIFile.
 //
 static bool CreateFilenameFromTextA(nsString& aText, const char* aExtension,
                                     char* aFilename, uint32_t aFilenameLen) {
   // ensure that the supplied name doesn't have invalid characters. If
   // a valid mangled filename couldn't be created then it will leave the
   // text empty.
-  MangleTextToValidFilename(aText);
+  nsLocalFile::CheckForReservedFileName(aText);
   if (aText.IsEmpty()) return false;
 
   // repeatably call WideCharToMultiByte as long as the title doesn't fit in the
   // buffer available to us. Continually reduce the length of the source title
   // until the MBCS version will fit in the buffer with room for the supplied
   // extension. Doing it this way ensures that even in MBCS environments there
   // will be a valid MBCS filename of the correct length.
   int maxUsableFilenameLen =
@@ -1156,17 +1127,17 @@ static bool CreateFilenameFromTextA(nsSt
   }
 }
 
 static bool CreateFilenameFromTextW(nsString& aText, const wchar_t* aExtension,
                                     wchar_t* aFilename, uint32_t aFilenameLen) {
   // ensure that the supplied name doesn't have invalid characters. If
   // a valid mangled filename couldn't be created then it will leave the
   // text empty.
-  MangleTextToValidFilename(aText);
+  nsLocalFile::CheckForReservedFileName(aText);
   if (aText.IsEmpty()) return false;
 
   const int extensionLen = wcslen(aExtension);
   if (aText.Length() + extensionLen + 1 > aFilenameLen)
     aText.Truncate(aFilenameLen - extensionLen - 1);
   wcscpy(&aFilename[0], aText.get());
   wcscpy(&aFilename[aText.Length()], aExtension);
   return true;
@@ -2178,17 +2149,17 @@ HRESULT nsDataObj::GetDownloadDetails(ns
     nsAutoCString urlFileName;
     sourceURL->GetFileName(urlFileName);
     NS_UnescapeURL(urlFileName);
     CopyUTF8toUTF16(urlFileName, srcFileName);
   }
   if (srcFileName.IsEmpty()) return E_FAIL;
 
   // make the name safe for the filesystem
-  MangleTextToValidFilename(srcFileName);
+  nsLocalFile::CheckForReservedFileName(srcFileName);
 
   sourceURI.swap(*aSourceURI);
   aFilename = srcFileName;
   return S_OK;
 }
 
 HRESULT nsDataObj::GetFileDescriptor_IStreamA(FORMATETC& aFE, STGMEDIUM& aSTG) {
   HGLOBAL fileGroupDescHandle =
--- a/xpcom/io/nsLocalFileWin.cpp
+++ b/xpcom/io/nsLocalFileWin.cpp
@@ -186,16 +186,36 @@ nsresult nsLocalFile::RevealFile(const n
 
     CoTaskMemFree(dir);
     CoTaskMemFree(item);
   }
 
   return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
 }
 
+// static
+void nsLocalFile::CheckForReservedFileName(nsString& aFileName) {
+  static const char* forbiddenNames[] = {
+      "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7",  "COM8",
+      "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6",  "LPT7",
+      "LPT8", "LPT9", "CON",  "PRN",  "AUX",  "NUL",  "CLOCK$"};
+
+  uint32_t nameLen;
+  for (size_t n = 0; n < ArrayLength(forbiddenNames); ++n) {
+    nameLen = (uint32_t)strlen(forbiddenNames[n]);
+    if (aFileName.EqualsIgnoreCase(forbiddenNames[n], nameLen)) {
+      // invalid name is either the entire string, or a prefix with a period
+      if (aFileName.Length() == nameLen ||
+          aFileName.CharAt(nameLen) == char16_t('.')) {
+        aFileName.Truncate();
+      }
+    }
+  }
+}
+
 class nsDriveEnumerator : public nsSimpleEnumerator,
                           public nsIDirectoryEnumerator {
  public:
   explicit nsDriveEnumerator(bool aUseDOSDevicePathSyntax);
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSISIMPLEENUMERATOR
   NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::)
   nsresult Init();
--- a/xpcom/io/nsLocalFileWin.h
+++ b/xpcom/io/nsLocalFileWin.h
@@ -44,16 +44,20 @@ class nsLocalFile final : public nsILoca
 
  public:
   // Removes registry command handler parameters, quotes, and expands
   // environment strings.
   static bool CleanupCmdHandlerPath(nsAString& aCommandHandler);
   // Called off the main thread to open the window revealing the file
   static nsresult RevealFile(const nsString& aResolvedPath);
 
+  // Checks if the filename is one of the windows reserved filenames
+  // (com1, com2, etc...) and truncates the string if so.
+  static void CheckForReservedFileName(nsString& aFileName);
+
  private:
   // CopyMove and CopySingleFile constants for |options| parameter:
   enum CopyFileOption {
     FollowSymlinks = 1u << 0,
     Move = 1u << 1,
     SkipNtfsAclReset = 1u << 2,
     Rename = 1u << 3
   };