Bug 1279240 - move path parsing of commandline handlers for mimetypes/protocols to nsILocalFileWin, r=froydnj draft
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Fri, 30 Sep 2016 17:18:41 +0100
changeset 419543 949b1c175da40c8ad4a1d9711dfef497e38c8192
parent 419085 9bf900b1ef8f5ced6c7929fa74c704ca244bf177
child 419544 034a164e2a5ea2ec371c1ab91490866f3bb1fa84
push id30959
push userbmo:gijskruitbosch+bugs@gmail.com
push dateFri, 30 Sep 2016 16:32:26 +0000
reviewersfroydnj
bugs1279240
milestone52.0a1
Bug 1279240 - move path parsing of commandline handlers for mimetypes/protocols to nsILocalFileWin, r=froydnj MozReview-Commit-ID: 4CENm3iqGUH
uriloader/exthandler/win/nsMIMEInfoWin.cpp
uriloader/exthandler/win/nsOSHelperAppService.cpp
uriloader/exthandler/win/nsOSHelperAppService.h
xpcom/io/nsILocalFileWin.idl
xpcom/io/nsLocalFileWin.cpp
xpcom/io/nsLocalFileWin.h
xpcom/tests/unit/test_windows_cmdline_file.js
xpcom/tests/unit/xpcshell.ini
--- a/uriloader/exthandler/win/nsMIMEInfoWin.cpp
+++ b/uriloader/exthandler/win/nsMIMEInfoWin.cpp
@@ -1,29 +1,28 @@
 /* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  *
  * 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 "nsArrayEnumerator.h"
 #include "nsCOMArray.h"
-#include "nsIFile.h"
+#include "nsLocalFile.h"
 #include "nsMIMEInfoWin.h"
 #include "nsNetUtil.h"
 #include <windows.h>
 #include <shellapi.h>
 #include "nsAutoPtr.h"
 #include "nsIMutableArray.h"
 #include "nsTArray.h"
 #include "shlobj.h"
 #include "windows.h"
 #include "nsIWindowsRegKey.h"
 #include "nsIProcess.h"
-#include "nsOSHelperAppService.h"
 #include "nsUnicharUtils.h"
 #include "nsITextToSubURI.h"
 #include "nsVariant.h"
 #include "mozilla/UniquePtrExtensions.h"
 
 #define RUNDLL32_EXE L"\\rundll32.exe"
 
 
@@ -343,17 +342,17 @@ bool nsMIMEInfoWin::GetAppsVerbCommandHa
   if (NS_FAILED(rv)) 
     return false;
 
   nsAutoString appFilesystemCommand;
   if (NS_SUCCEEDED(appKey->ReadStringValue(EmptyString(), 
                                            appFilesystemCommand))) {
     
     // Expand environment vars, clean up any misc.
-    if (!nsOSHelperAppService::CleanupCmdHandlerPath(appFilesystemCommand))
+    if (!nsLocalFile::CleanupCmdHandlerPath(appFilesystemCommand))
       return false;
     
     applicationPath = appFilesystemCommand;
     return true;
   }
   return false;
 }
 
@@ -488,17 +487,17 @@ bool nsMIMEInfoWin::GetProgIDVerbCommand
                              nsIWindowsRegKey::ACCESS_QUERY_VALUE);
   if (NS_FAILED(rv))
     return false;
 
   nsAutoString appFilesystemCommand;
   if (NS_SUCCEEDED(appKey->ReadStringValue(EmptyString(), appFilesystemCommand))) {
     
     // Expand environment vars, clean up any misc.
-    if (!nsOSHelperAppService::CleanupCmdHandlerPath(appFilesystemCommand))
+    if (!nsLocalFile::CleanupCmdHandlerPath(appFilesystemCommand))
       return false;
     
     applicationPath = appFilesystemCommand;
     return true;
   }
   return false;
 }
 
--- a/uriloader/exthandler/win/nsOSHelperAppService.cpp
+++ b/uriloader/exthandler/win/nsOSHelperAppService.cpp
@@ -8,21 +8,21 @@
 #include "nsOSHelperAppService.h"
 #include "nsISupports.h"
 #include "nsString.h"
 #include "nsXPIDLString.h"
 #include "nsIURL.h"
 #include "nsIMIMEInfo.h"
 #include "nsMIMEInfoWin.h"
 #include "nsMimeTypes.h"
-#include "nsILocalFileWin.h"
 #include "nsIProcess.h"
 #include "plstr.h"
 #include "nsAutoPtr.h"
 #include "nsNativeCharsetUtils.h"
+#include "nsLocalFile.h"
 #include "nsIWindowsRegKey.h"
 #include "mozilla/UniquePtrExtensions.h"
 #include "mozilla/WindowsVersion.h"
 
 // shellapi.h is needed to build with WIN32_LEAN_AND_MEAN
 #include <shellapi.h>
 #include <shlwapi.h>
 
@@ -281,124 +281,16 @@ nsOSHelperAppService::typeFromExtEquals(
   nsAutoString type;
   rv = regKey->ReadStringValue(NS_LITERAL_STRING("Content Type"), type);
   if (NS_SUCCEEDED(rv))
      eq = type.EqualsASCII(aType);
 
   return eq;
 }
 
-// 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
-  // 2) C:\Program Files\Company\some.dll
-  // 3) C:\Windows\some.dll,-foo -bar
-  // 4) C:\Windows\some.cpl,-foo -bar
-
-  int32_t lastCommaPos = aPath.RFindChar(',');
-  if (lastCommaPos != kNotFound)
-    aPath.Truncate(lastCommaPos);
-
-  aPath.Append(' ');
-
-  // case insensitive
-  uint32_t index = aPath.Find(".exe ", true);
-  if (index == kNotFound)
-    index = aPath.Find(".dll ", true);
-  if (index == kNotFound)
-    index = aPath.Find(".cpl ", true);
-
-  if (index != kNotFound)
-    aPath.Truncate(index + 4);
-  aPath.Trim(" ", true, true);
-}
-
-// Strip the windows host process bootstrap executable rundll32.exe
-// from a handler's command string if it exists.
-static void StripRundll32(nsString& aCommandString)
-{
-  // Example rundll formats:
-  // C:\Windows\System32\rundll32.exe "path to dll"
-  // rundll32.exe "path to dll"
-  // C:\Windows\System32\rundll32.exe "path to dll", var var
-  // rundll32.exe "path to dll", var var
-
-  NS_NAMED_LITERAL_STRING(rundllSegment, "rundll32.exe ");
-  NS_NAMED_LITERAL_STRING(rundllSegmentShort, "rundll32 ");
-
-  // case insensitive
-  int32_t strLen = rundllSegment.Length();
-  int32_t index = aCommandString.Find(rundllSegment, true);
-  if (index == kNotFound) {
-    strLen = rundllSegmentShort.Length();
-    index = aCommandString.Find(rundllSegmentShort, true);
-  }
-
-  if (index != kNotFound) {
-    uint32_t rundllSegmentLength = index + strLen;
-    aCommandString.Cut(0, rundllSegmentLength);
-  }
-}
-
-// Returns the fully qualified path to an application handler based on
-// a parameterized command string. Note this routine should not be used
-// to launch the associated application as it strips parameters and
-// rundll.exe from the string. Designed for retrieving display information
-// on a particular handler.   
-/* static */ bool nsOSHelperAppService::CleanupCmdHandlerPath(nsAString& aCommandHandler)
-{
-  nsAutoString handlerCommand(aCommandHandler);
-
-  // Straight command path:
-  //
-  // %SystemRoot%\system32\NOTEPAD.EXE var
-  // "C:\Program Files\iTunes\iTunes.exe" var var
-  // C:\Program Files\iTunes\iTunes.exe var var
-  //
-  // Example rundll handlers:
-  //
-  // rundll32.exe "%ProgramFiles%\Win...ery\PhotoViewer.dll", var var
-  // rundll32.exe "%ProgramFiles%\Windows Photo Gallery\PhotoViewer.dll"
-  // C:\Windows\System32\rundll32.exe "path to dll", var var
-  // %SystemRoot%\System32\rundll32.exe "%ProgramFiles%\Win...ery\Photo
-  //    Viewer.dll", var var
-
-  // Expand environment variables so we have full path strings.
-  uint32_t bufLength = ::ExpandEnvironmentStringsW(handlerCommand.get(),
-                                                   L"", 0);
-  if (bufLength == 0) // Error
-    return false;
-
-  auto destination = mozilla::MakeUniqueFallible<wchar_t[]>(bufLength);
-  if (!destination)
-    return false;
-  if (!::ExpandEnvironmentStringsW(handlerCommand.get(), destination.get(),
-                                   bufLength))
-    return false;
-
-  handlerCommand.Assign(destination.get());
-
-  // Remove quotes around paths
-  handlerCommand.StripChars("\"");
-
-  // Strip windows host process bootstrap so we can get to the actual
-  // handler.
-  StripRundll32(handlerCommand);
-
-  // Trim any command parameters so that we have a native path we can
-  // initialize a local file with.
-  CleanupHandlerPath(handlerCommand);
-
-  aCommandHandler.Assign(handlerCommand);
-  return true;
-}
-
 // The "real" name of a given helper app (as specified by the path to the 
 // executable file held in various registry keys) is stored n the VERSIONINFO
 // block in the file's resources. We need to find the path to the executable
 // and then retrieve the "FileDescription" field value from the file. 
 nsresult
 nsOSHelperAppService::GetDefaultAppInfo(const nsAString& aAppInfo,
                                         nsAString& aDefaultDescription, 
                                         nsIFile** aDefaultApplication)
@@ -477,38 +369,28 @@ nsOSHelperAppService::GetDefaultAppInfo(
                           nsIWindowsRegKey::ACCESS_QUERY_VALUE);
         NS_ENSURE_SUCCESS(rv, rv);
         rv = chkKey->ReadStringValue(EmptyString(), handlerCommand);
         NS_ENSURE_SUCCESS(rv, rv);
       }
     }
   }
 
-  if (!CleanupCmdHandlerPath(handlerCommand))
-    return NS_ERROR_FAILURE;
-
   // XXX FIXME: If this fails, the UI will display the full command
   // string.
   // There are some rare cases this can happen - ["url.dll" -foo]
   // for example won't resolve correctly to the system dir. The 
   // subsequent launch of the helper app will work though.
-  nsCOMPtr<nsIFile> lf;
-  NS_NewLocalFile(handlerCommand, true, getter_AddRefs(lf));
-  if (!lf)
-    return NS_ERROR_FILE_NOT_FOUND;
+  nsCOMPtr<nsILocalFileWin> lf = new nsLocalFile();
+  rv = lf->InitWithCommandLine(handlerCommand);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  nsILocalFileWin* lfw = nullptr;
-  CallQueryInterface(lf, &lfw);
-
-  if (lfw) {
-    // The "FileDescription" field contains the actual name of the application.
-    lfw->GetVersionInfoField("FileDescription", aDefaultDescription);
-    // QI addref'ed for us.
-    *aDefaultApplication = lfw;
-  }
+  // The "FileDescription" field contains the actual name of the application.
+  lf->GetVersionInfoField("FileDescription", aDefaultDescription);
+  lf.forget(aDefaultApplication);
 
   return NS_OK;
 }
 
 already_AddRefed<nsMIMEInfoWin> nsOSHelperAppService::GetByExtension(const nsAFlatString& aFileExt, const char *aTypeHint)
 {
   if (aFileExt.IsEmpty())
     return nullptr;
--- a/uriloader/exthandler/win/nsOSHelperAppService.h
+++ b/uriloader/exthandler/win/nsOSHelperAppService.h
@@ -41,19 +41,16 @@ public:
                                           bool *found,
                                           nsIHandlerInfo **_retval);
 
   /** Get the string value of a registry value and store it in result.
    * @return true on success, false on failure
    */
   static bool GetValueString(HKEY hKey, const char16_t* pValueName, nsAString& result);
 
-  // Removes registry command handler parameters, quotes, and expands environment strings.
-  static bool CleanupCmdHandlerPath(nsAString& aCommandHandler);
-
 protected:
   nsresult GetDefaultAppInfo(const nsAString& aTypeName, nsAString& aDefaultDescription, nsIFile** aDefaultApplication);
   // Lookup a mime info by extension, using an optional type hint
   already_AddRefed<nsMIMEInfoWin> GetByExtension(const nsAFlatString& aFileExt, const char *aTypeHint = nullptr);
   nsresult FindOSMimeInfoForType(const char * aMimeContentType, nsIURI * aURI, char ** aFileExtension, nsIMIMEInfo ** aMIMEInfo);
 
   static nsresult GetMIMEInfoFromRegistry(const nsAFlatString& fileType, nsIMIMEInfo *pInfo);
   /// Looks up the type for the extension aExt and compares it to aType
--- a/xpcom/io/nsILocalFileWin.idl
+++ b/xpcom/io/nsILocalFileWin.idl
@@ -10,16 +10,26 @@
 struct PRFileDesc;
 %}
 
 [ptr] native PRFileDescStar(PRFileDesc);
 
 [scriptable, builtinclass, uuid(e7a3a954-384b-4aeb-a5f7-55626b0de9be)]
 interface nsILocalFileWin : nsILocalFile
 {
+    /**
+     *  initWithCommandLine
+     *
+     *  Initialize this object based on the main app path of a commandline
+     *  handler.
+     *
+     *   @param aCommandLine
+     *       the commandline to parse an app path out of.
+     */
+    void initWithCommandLine(in AString aCommandLine);
    /**
     * getVersionInfoValue
     *
     * Retrieve a metadata field from the file's VERSIONINFO block.
     * Throws NS_ERROR_FAILURE if no value is found, or the value is empty.
     *
     * @param   aField         The field to look up.
     *
--- 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/UniquePtrExtensions.h"
 #include "mozilla/WindowsVersion.h"
 
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsMemory.h"
 #include "GeckoProfiler.h"
 
 #include "nsLocalFile.h"
@@ -1186,16 +1187,138 @@ nsLocalFile::InitWithPath(const nsAStrin
   if (mWorkingPath.Last() == L'\\') {
     mWorkingPath.Truncate(mWorkingPath.Length() - 1);
   }
 
   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
+  // 2) C:\Program Files\Company\some.dll
+  // 3) C:\Windows\some.dll,-foo -bar
+  // 4) C:\Windows\some.cpl,-foo -bar
+
+  int32_t lastCommaPos = aPath.RFindChar(',');
+  if (lastCommaPos != kNotFound)
+    aPath.Truncate(lastCommaPos);
+
+  aPath.Append(' ');
+
+  // case insensitive
+  uint32_t index = aPath.Find(".exe ", true);
+  if (index == kNotFound)
+    index = aPath.Find(".dll ", true);
+  if (index == kNotFound)
+    index = aPath.Find(".cpl ", true);
+
+  if (index != kNotFound)
+    aPath.Truncate(index + 4);
+  aPath.Trim(" ", true, true);
+}
+
+// Strip the windows host process bootstrap executable rundll32.exe
+// from a handler's command string if it exists.
+static void
+StripRundll32(nsString& aCommandString)
+{
+  // Example rundll formats:
+  // C:\Windows\System32\rundll32.exe "path to dll"
+  // rundll32.exe "path to dll"
+  // C:\Windows\System32\rundll32.exe "path to dll", var var
+  // rundll32.exe "path to dll", var var
+
+  NS_NAMED_LITERAL_STRING(rundllSegment, "rundll32.exe ");
+  NS_NAMED_LITERAL_STRING(rundllSegmentShort, "rundll32 ");
+
+  // case insensitive
+  int32_t strLen = rundllSegment.Length();
+  int32_t index = aCommandString.Find(rundllSegment, true);
+  if (index == kNotFound) {
+    strLen = rundllSegmentShort.Length();
+    index = aCommandString.Find(rundllSegmentShort, true);
+  }
+
+  if (index != kNotFound) {
+    uint32_t rundllSegmentLength = index + strLen;
+    aCommandString.Cut(0, rundllSegmentLength);
+  }
+}
+
+// Returns the fully qualified path to an application handler based on
+// a parameterized command string. Note this routine should not be used
+// to launch the associated application as it strips parameters and
+// rundll.exe from the string. Designed for retrieving display information
+// on a particular handler.
+/* static */ bool
+nsLocalFile::CleanupCmdHandlerPath(nsAString& aCommandHandler)
+{
+  nsAutoString handlerCommand(aCommandHandler);
+
+  // Straight command path:
+  //
+  // %SystemRoot%\system32\NOTEPAD.EXE var
+  // "C:\Program Files\iTunes\iTunes.exe" var var
+  // C:\Program Files\iTunes\iTunes.exe var var
+  //
+  // Example rundll handlers:
+  //
+  // rundll32.exe "%ProgramFiles%\Win...ery\PhotoViewer.dll", var var
+  // rundll32.exe "%ProgramFiles%\Windows Photo Gallery\PhotoViewer.dll"
+  // C:\Windows\System32\rundll32.exe "path to dll", var var
+  // %SystemRoot%\System32\rundll32.exe "%ProgramFiles%\Win...ery\Photo
+  //    Viewer.dll", var var
+
+  // Expand environment variables so we have full path strings.
+  uint32_t bufLength = ::ExpandEnvironmentStringsW(handlerCommand.get(),
+                                                   L"", 0);
+  if (bufLength == 0) // Error
+    return false;
+
+  auto destination = mozilla::MakeUniqueFallible<wchar_t[]>(bufLength);
+  if (!destination)
+    return false;
+  if (!::ExpandEnvironmentStringsW(handlerCommand.get(), destination.get(),
+                                   bufLength))
+    return false;
+
+  handlerCommand.Assign(destination.get());
+
+  // Remove quotes around paths
+  handlerCommand.StripChars("\"");
+
+  // Strip windows host process bootstrap so we can get to the actual
+  // handler.
+  StripRundll32(handlerCommand);
+
+  // Trim any command parameters so that we have a native path we can
+  // initialize a local file with.
+  CleanupHandlerPath(handlerCommand);
+
+  aCommandHandler.Assign(handlerCommand);
+  return true;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::InitWithCommandLine(const nsAString& aCommandLine)
+{
+  nsAutoString commandLine(aCommandLine);
+  if (!CleanupCmdHandlerPath(commandLine)) {
+    return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+  }
+  return InitWithPath(commandLine);
+}
+
 NS_IMETHODIMP
 nsLocalFile::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode,
                               PRFileDesc** aResult)
 {
   nsresult rv = OpenNSPRFileDescMaybeShareDelete(aFlags, aMode, false, aResult);
   if (NS_FAILED(rv)) {
     return rv;
   }
--- a/xpcom/io/nsLocalFileWin.h
+++ b/xpcom/io/nsLocalFileWin.h
@@ -52,16 +52,19 @@ public:
 
   // nsIHashable interface
   NS_DECL_NSIHASHABLE
 
 public:
   static void GlobalInit();
   static void GlobalShutdown();
 
+  // Removes registry command handler parameters, quotes, and expands environment strings.
+  static bool CleanupCmdHandlerPath(nsAString& aCommandHandler);
+
 private:
   // CopyMove and CopySingleFile constants for |options| parameter:
   enum CopyFileOption {
     FollowSymlinks          = 1u << 0,
     Move                    = 1u << 1,
     SkipNtfsAclReset        = 1u << 2,
     Rename                  = 1u << 3
   };
new file mode 100644
--- /dev/null
+++ b/xpcom/tests/unit/test_windows_cmdline_file.js
@@ -0,0 +1,21 @@
+let { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+Cu.import("resource://gre/modules/Services.jsm");
+
+let executableFile = Services.dirsvc.get("CurProcD", Ci.nsIFile);
+executableFile.append("xpcshell.exe");
+function run_test() {
+  let quote = '"'; // Windows' cmd processor doesn't actually use single quotes.
+  for (let suffix of ["", " -osint", ` --blah "%PROGRAMFILES%"`]) {
+    let cmdline = quote + executableFile.path + quote + suffix;
+    do_print(`Testing with ${cmdline}`);
+    let f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFileWin);
+    f.initWithCommandLine(cmdline);
+    Assert.equal(f.path, executableFile.path, "Should be able to recover executable path");
+  }
+
+  let f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFileWin);
+  f.initWithCommandLine("%ComSpec% -c echo 'hi'");
+  let cmd = Services.dirsvc.get("SysD", Ci.nsIFile);
+  cmd.append("cmd.exe");
+  Assert.equal(f.path, cmd.path, "Should be able to replace env vars.");
+}
--- a/xpcom/tests/unit/xpcshell.ini
+++ b/xpcom/tests/unit/xpcshell.ini
@@ -62,16 +62,19 @@ skip-if = os == "android"
 fail-if = os == "android"
 [test_systemInfo.js]
 # Bug 902081: test fails consistently on Android 2.2, passes on 4.0
 skip-if = os == "android"
 [test_versioncomparator.js]
 [test_comp_no_aslr.js]
 skip-if = os != "win"
 [test_windows_shortcut.js]
+skip-if = os != "win"
+[test_windows_cmdline_file.js]
+skip-if = os != "win"
 [test_bug745466.js]
 skip-if = os == "win"
 # Bug 676998: test fails consistently on Android
 fail-if = os == "android"
 [test_file_renameTo.js]
 [test_notxpcom_scriptable.js]
 [test_windows_registry.js]
 skip-if = os != "win"