mirror of
https://github.com/pocoproject/poco.git
synced 2024-12-16 19:54:38 +01:00
Merge pull request #1243 from tonyabbott/develop
GH #1222 Escape command line arguments passed to Process::launch() on…
This commit is contained in:
commit
3b51cbdfb6
@ -107,14 +107,66 @@ void ProcessImpl::timesImpl(long& userTime, long& kernelTime)
|
||||
}
|
||||
|
||||
|
||||
static bool argNeedsEscaping(const std::string& arg)
|
||||
{
|
||||
bool containsQuotableChar = arg.npos != arg.find_first_of(" \t\n\v\"");
|
||||
// Assume args that start and end with quotes are already quoted and do not require further quoting.
|
||||
// There is probably code out there written before launch() escaped the arguments that does its own
|
||||
// escaping of arguments. This ensures we do not interfere with those arguments.
|
||||
bool isAlreadyQuoted = arg.size() > 1 && '\"' == arg[0] && '\"' == arg[arg.size() - 1];
|
||||
return containsQuotableChar && !isAlreadyQuoted;
|
||||
}
|
||||
|
||||
|
||||
// Based on code from https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
|
||||
static std::string escapeArg(const std::string& arg)
|
||||
{
|
||||
if (argNeedsEscaping(arg))
|
||||
{
|
||||
std::string quotedArg("\"");
|
||||
for (auto it = arg.begin(); ; ++it)
|
||||
{
|
||||
unsigned backslashCount = 0;
|
||||
while (it != arg.end() && '\\' == *it)
|
||||
{
|
||||
++it;
|
||||
++backslashCount;
|
||||
}
|
||||
|
||||
if (it == arg.end())
|
||||
{
|
||||
quotedArg.append(2 * backslashCount, '\\');
|
||||
break;
|
||||
}
|
||||
else if ('"' == *it)
|
||||
{
|
||||
quotedArg.append(2 * backslashCount + 1, '\\');
|
||||
quotedArg.push_back('"');
|
||||
}
|
||||
else
|
||||
{
|
||||
quotedArg.append(backslashCount, '\\');
|
||||
quotedArg.push_back(*it);
|
||||
}
|
||||
}
|
||||
quotedArg.push_back('"');
|
||||
return quotedArg;
|
||||
}
|
||||
else
|
||||
{
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ProcessHandleImpl* ProcessImpl::launchImpl(const std::string& command, const ArgsImpl& args, const std::string& initialDirectory, Pipe* inPipe, Pipe* outPipe, Pipe* errPipe, const EnvImpl& env)
|
||||
{
|
||||
std::string commandLine = command;
|
||||
for (ArgsImpl::const_iterator it = args.begin(); it != args.end(); ++it)
|
||||
{
|
||||
commandLine.append(" ");
|
||||
commandLine.append(*it);
|
||||
}
|
||||
commandLine.append(escapeArg(*it));
|
||||
}
|
||||
|
||||
STARTUPINFOA startupInfo;
|
||||
GetStartupInfoA(&startupInfo); // take defaults from current process
|
||||
|
@ -108,13 +108,65 @@ void ProcessImpl::timesImpl(long& userTime, long& kernelTime)
|
||||
}
|
||||
|
||||
|
||||
static bool argNeedsEscaping(const std::string& arg)
|
||||
{
|
||||
bool containsQuotableChar = arg.npos != arg.find_first_of(" \t\n\v\"");
|
||||
// Assume args that start and end with quotes are already quoted and do not require further quoting.
|
||||
// There is probably code out there written before launch() escaped the arguments that does its own
|
||||
// escaping of arguments. This ensures we do not interfere with those arguments.
|
||||
bool isAlreadyQuoted = arg.size() > 1 && '\"' == arg[0] && '\"' == arg[arg.size() - 1];
|
||||
return containsQuotableChar && !isAlreadyQuoted;
|
||||
}
|
||||
|
||||
|
||||
// Based on code from https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
|
||||
static std::string escapeArg(const std::string& arg)
|
||||
{
|
||||
if (argNeedsEscaping(arg))
|
||||
{
|
||||
std::string quotedArg("\"");
|
||||
for (auto it = arg.begin(); ; ++it)
|
||||
{
|
||||
unsigned backslashCount = 0;
|
||||
while (it != arg.end() && '\\' == *it)
|
||||
{
|
||||
++it;
|
||||
++backslashCount;
|
||||
}
|
||||
|
||||
if (it == arg.end())
|
||||
{
|
||||
quotedArg.append(2 * backslashCount, '\\');
|
||||
break;
|
||||
}
|
||||
else if ('"' == *it)
|
||||
{
|
||||
quotedArg.append(2 * backslashCount + 1, '\\');
|
||||
quotedArg.push_back('"');
|
||||
}
|
||||
else
|
||||
{
|
||||
quotedArg.append(backslashCount, '\\');
|
||||
quotedArg.push_back(*it);
|
||||
}
|
||||
}
|
||||
quotedArg.push_back('"');
|
||||
return quotedArg;
|
||||
}
|
||||
else
|
||||
{
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ProcessHandleImpl* ProcessImpl::launchImpl(const std::string& command, const ArgsImpl& args, const std::string& initialDirectory, Pipe* inPipe, Pipe* outPipe, Pipe* errPipe, const EnvImpl& env)
|
||||
{
|
||||
std::string commandLine = command;
|
||||
for (ArgsImpl::const_iterator it = args.begin(); it != args.end(); ++it)
|
||||
{
|
||||
commandLine.append(" ");
|
||||
commandLine.append(*it);
|
||||
commandLine.append(escapeArg(*it));
|
||||
}
|
||||
|
||||
std::wstring ucommandLine;
|
||||
|
@ -147,6 +147,66 @@ void ProcessTest::testLaunchEnv()
|
||||
}
|
||||
|
||||
|
||||
void ProcessTest::testLaunchArgs()
|
||||
{
|
||||
#if !defined(_WIN32_WCE)
|
||||
std::string name("TestApp");
|
||||
std::string cmd;
|
||||
|
||||
#if defined(POCO_OS_FAMILY_UNIX)
|
||||
cmd = "./";
|
||||
cmd += name;
|
||||
#else
|
||||
cmd = name;
|
||||
#endif
|
||||
|
||||
std::vector<std::string> args;
|
||||
args.push_back("-echo-args");
|
||||
args.push_back("simple");
|
||||
args.push_back("with space");
|
||||
args.push_back("with\ttab");
|
||||
args.push_back("with\vverticaltab");
|
||||
// can't test newline here because TestApp -echo-args uses newline to separate the echoed args
|
||||
//args.push_back("with\nnewline");
|
||||
args.push_back("with \" quotes");
|
||||
args.push_back("ends with \"quotes\"");
|
||||
args.push_back("\"starts\" with quotes");
|
||||
args.push_back("\"");
|
||||
args.push_back("\\");
|
||||
args.push_back("c:\\program files\\ends with backslash\\");
|
||||
args.push_back("\"already quoted \\\" \\\\\"");
|
||||
Pipe outPipe;
|
||||
ProcessHandle ph = Process::launch(cmd, args, 0, &outPipe, 0);
|
||||
PipeInputStream istr(outPipe);
|
||||
std::string receivedArg;
|
||||
int c = istr.get();
|
||||
int argNumber = 1;
|
||||
while (c != -1)
|
||||
{
|
||||
if ('\n' == c)
|
||||
{
|
||||
assert(argNumber < args.size());
|
||||
std::string expectedArg = args[argNumber];
|
||||
if (expectedArg.npos != expectedArg.find("already quoted")) {
|
||||
expectedArg = "already quoted \" \\";
|
||||
}
|
||||
assert(receivedArg == expectedArg);
|
||||
++argNumber;
|
||||
receivedArg = "";
|
||||
}
|
||||
else if ('\r' != c)
|
||||
{
|
||||
receivedArg += (char)c;
|
||||
}
|
||||
c = istr.get();
|
||||
}
|
||||
assert(argNumber == args.size());
|
||||
int rc = ph.wait();
|
||||
assert(rc == args.size());
|
||||
#endif // !defined(_WIN32_WCE)
|
||||
}
|
||||
|
||||
|
||||
void ProcessTest::testIsRunning()
|
||||
{
|
||||
#if !defined(_WIN32_WCE)
|
||||
@ -234,6 +294,7 @@ CppUnit::Test* ProcessTest::suite()
|
||||
CppUnit_addTest(pSuite, ProcessTest, testLaunchRedirectIn);
|
||||
CppUnit_addTest(pSuite, ProcessTest, testLaunchRedirectOut);
|
||||
CppUnit_addTest(pSuite, ProcessTest, testLaunchEnv);
|
||||
CppUnit_addTest(pSuite, ProcessTest, testLaunchArgs);
|
||||
CppUnit_addTest(pSuite, ProcessTest, testIsRunning);
|
||||
CppUnit_addTest(pSuite, ProcessTest, testIsRunningAllowsForTermination);
|
||||
CppUnit_addTest(pSuite, ProcessTest, testSignalExitCode);
|
||||
|
@ -30,6 +30,7 @@ public:
|
||||
void testLaunchRedirectIn();
|
||||
void testLaunchRedirectOut();
|
||||
void testLaunchEnv();
|
||||
void testLaunchArgs();
|
||||
void testIsRunning();
|
||||
void testIsRunningAllowsForTermination();
|
||||
void testSignalExitCode();
|
||||
|
@ -52,6 +52,13 @@ int main(int argc, char** argv)
|
||||
std::signal(SIGINT, SIG_DFL);
|
||||
std::raise(SIGINT);
|
||||
}
|
||||
else if (arg == "-echo-args")
|
||||
{
|
||||
for (int i = 2; i < argc; ++i)
|
||||
{
|
||||
std::cout << argv[i] << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
return argc - 1;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user