Bug 1279240 - move path parsing of commandline handlers for mimetypes/protocols to nsILocalFileWin, r=froydnj
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Fri, 30 Sep 2016 17:18:41 +0100
changeset 316001 cc17d0140e11af85d6ee80b56193ddfcc52c02b0
parent 316000 29ae0ea36daf8acc580b52092cbfc6614fd43e02
child 316002 2fe60d27c7805da856f286548e1d20e4413b56ca
push id32722
push usergijskruitbosch@gmail.com
push dateFri, 30 Sep 2016 16:36:30 +0000
treeherderautoland@2fe60d27c780 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1279240
milestone52.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 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"