gecko-dev/toolkit/xre/AssembleCmdLine.h
Toshihito Kikuchi 3c6a2a135e Bug 1601905 - Add quotes for a path including whitespaces in ShellExecuteWithIFile. r=aklotz
A fix for Bug 1588975 replaced the call to `LaunchWithIProcess` in
`nsMIMEInfoWin::LaunchWithFile` with the call to `ShellExecuteWithIFile` to use
`mozilla::ShellExecuteByExplorer`.  This caused a regression that if a filepath
contains whitespaces, we pass it to a target application without quoting it
while the old codepath `ShellExecuteWithIFile` quoted a path accordingly in
`assembleCmdLine`.

A proposed fix is to quote a path in the same way as `assembleCmdLine` does.
This patch also moves `assembleCmdLine` to an exported header so that we can add
a unittest to cover both of `assembleCmdLine` and a new function
`assembleSingleArgument.  It would be better to refactor these functions in the
future because many lines are duplicated.

Differential Revision: https://phabricator.services.mozilla.com/D56646

--HG--
extra : moz-landing-system : lando
2019-12-19 15:39:02 +00:00

229 lines
5.8 KiB
C++

/* -*- 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 https://mozilla.org/MPL/2.0/. */
#ifndef mozilla_AssembleCmdLine_h
#define mozilla_AssembleCmdLine_h
#if defined(XP_WIN)
# include "mozilla/UniquePtr.h"
# include <stdlib.h>
# include <windows.h>
# ifdef MOZILLA_INTERNAL_API
# include "nsString.h"
# endif // MOZILLA_INTERNAL_API
namespace mozilla {
// Out param `aWideCmdLine` must be free()d by the caller.
inline int assembleCmdLine(const char* const* aArgv, wchar_t** aWideCmdLine,
UINT aCodePage) {
const char* const* arg;
char* p;
const char* q;
char* cmdLine;
int cmdLineSize;
int numBackslashes;
int i;
int argNeedQuotes;
/*
* Find out how large the command line buffer should be.
*/
cmdLineSize = 0;
for (arg = aArgv; *arg; ++arg) {
/*
* \ and " need to be escaped by a \. In the worst case,
* every character is a \ or ", so the string of length
* may double. If we quote an argument, that needs two ".
* Finally, we need a space between arguments, and
* a null byte at the end of command line.
*/
cmdLineSize += 2 * strlen(*arg) /* \ and " need to be escaped */
+ 2 /* we quote every argument */
+ 1; /* space in between, or final null */
}
p = cmdLine = (char*)malloc(cmdLineSize * sizeof(char));
if (!p) {
return -1;
}
for (arg = aArgv; *arg; ++arg) {
/* Add a space to separates the arguments */
if (arg != aArgv) {
*p++ = ' ';
}
q = *arg;
numBackslashes = 0;
argNeedQuotes = 0;
/* If the argument contains white space, it needs to be quoted. */
if (strpbrk(*arg, " \f\n\r\t\v")) {
argNeedQuotes = 1;
}
if (argNeedQuotes) {
*p++ = '"';
}
while (*q) {
if (*q == '\\') {
numBackslashes++;
q++;
} else if (*q == '"') {
if (numBackslashes) {
/*
* Double the backslashes since they are followed
* by a quote
*/
for (i = 0; i < 2 * numBackslashes; i++) {
*p++ = '\\';
}
numBackslashes = 0;
}
/* To escape the quote */
*p++ = '\\';
*p++ = *q++;
} else {
if (numBackslashes) {
/*
* Backslashes are not followed by a quote, so
* don't need to double the backslashes.
*/
for (i = 0; i < numBackslashes; i++) {
*p++ = '\\';
}
numBackslashes = 0;
}
*p++ = *q++;
}
}
/* Now we are at the end of this argument */
if (numBackslashes) {
/*
* Double the backslashes if we have a quote string
* delimiter at the end.
*/
if (argNeedQuotes) {
numBackslashes *= 2;
}
for (i = 0; i < numBackslashes; i++) {
*p++ = '\\';
}
}
if (argNeedQuotes) {
*p++ = '"';
}
}
*p = '\0';
int numChars = MultiByteToWideChar(aCodePage, 0, cmdLine, -1, nullptr, 0);
*aWideCmdLine = (wchar_t*)malloc(numChars * sizeof(wchar_t));
MultiByteToWideChar(aCodePage, 0, cmdLine, -1, *aWideCmdLine, numChars);
free(cmdLine);
return 0;
}
# ifdef MOZILLA_INTERNAL_API
inline UniquePtr<wchar_t[]> assembleSingleArgument(const nsString& aArg) {
static_assert(sizeof(char16_t) == sizeof(wchar_t),
"char16_t and wchar_t sizes differ");
/*
* \ and " need to be escaped by a \. In the worst case,
* every character is a \ or ", so the string of length
* may double. If we quote an argument, that needs two ".
* Finally, we need a space between arguments, and
* a null byte at the end of command line.
*/
int cmdLineSize = 2 * aArg.Length() /* \ and " need to be escaped */
+ 2 /* we quote every argument */
+ 1; /* space in between, or final null */
auto assembledArg = MakeUnique<wchar_t[]>(cmdLineSize);
if (!assembledArg) {
return nullptr;
}
/* If the argument contains white space, it needs to be quoted. */
int argNeedQuotes = 0;
if (aArg.FindCharInSet(u" \f\n\r\t\v") != kNotFound) {
argNeedQuotes = 1;
}
wchar_t* p = assembledArg.get();
if (argNeedQuotes) {
*p++ = '"';
}
const char16_t* q = aArg.get();
int numBackslashes = 0;
while (*q) {
if (*q == '\\') {
numBackslashes++;
q++;
} else if (*q == '"') {
if (numBackslashes) {
/*
* Double the backslashes since they are followed
* by a quote
*/
for (int i = 0; i < 2 * numBackslashes; i++) {
*p++ = '\\';
}
numBackslashes = 0;
}
/* To escape the quote */
*p++ = '\\';
*p++ = *q++;
} else {
if (numBackslashes) {
/*
* Backslashes are not followed by a quote, so
* don't need to double the backslashes.
*/
for (int i = 0; i < numBackslashes; i++) {
*p++ = '\\';
}
numBackslashes = 0;
}
*p++ = *q++;
}
}
/* Now we are at the end of this argument */
if (numBackslashes) {
/*
* Double the backslashes if we have a quote string
* delimiter at the end.
*/
if (argNeedQuotes) {
numBackslashes *= 2;
}
for (int i = 0; i < numBackslashes; i++) {
*p++ = '\\';
}
}
if (argNeedQuotes) {
*p++ = '"';
}
*p = '\0';
return assembledArg;
}
# endif // MOZILLA_INTERNAL_API
} // namespace mozilla
#endif // defined(XP_WIN)
#endif // mozilla_AssembleCmdLine_h