2012-10-02 09:12:39 +02:00
|
|
|
/****************************************************************************
|
2010-10-14 18:05:43 +02:00
|
|
|
**
|
2016-01-15 14:58:39 +01:00
|
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
2010-10-14 18:05:43 +02:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
** This file is part of Qt Creator.
|
2010-10-14 18:05:43 +02:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
** Commercial License Usage
|
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
|
** accordance with the commercial license agreement provided with the
|
|
|
|
|
** Software or, alternatively, in accordance with the terms contained in
|
2016-01-15 14:58:39 +01:00
|
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
2010-10-14 18:05:43 +02:00
|
|
|
**
|
2016-01-15 14:58:39 +01:00
|
|
|
** GNU General Public License Usage
|
|
|
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
|
|
|
** General Public License version 3 as published by the Free Software
|
|
|
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
|
|
|
** included in the packaging of this file. Please review the following
|
|
|
|
|
** information to ensure the GNU General Public License requirements will
|
|
|
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
2010-12-17 17:14:20 +01:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
****************************************************************************/
|
2010-10-14 18:05:43 +02:00
|
|
|
|
|
|
|
|
#include "qtcprocess.h"
|
|
|
|
|
#include "stringutils.h"
|
|
|
|
|
|
2013-05-03 13:52:52 +02:00
|
|
|
#include <utils/qtcassert.h>
|
2014-02-05 10:43:21 +01:00
|
|
|
#include <utils/hostosinfo.h>
|
2013-05-03 13:52:52 +02:00
|
|
|
|
2012-02-15 10:42:41 +01:00
|
|
|
#include <QDir>
|
|
|
|
|
#include <QDebug>
|
|
|
|
|
#include <QCoreApplication>
|
2017-03-09 23:02:32 +01:00
|
|
|
#include <QRegExp>
|
2014-02-05 10:43:21 +01:00
|
|
|
#include <QStack>
|
2011-02-28 10:14:52 +01:00
|
|
|
|
2011-08-03 12:04:46 +02:00
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
#include <qt_windows.h>
|
2019-09-10 23:39:29 +03:00
|
|
|
#else
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <unistd.h>
|
2011-08-03 12:04:46 +02:00
|
|
|
#endif
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
|
|
|
|
|
// The main state of the Unix shell parser
|
|
|
|
|
enum MxQuoting { MxBasic, MxSingleQuote, MxDoubleQuote, MxParen, MxSubst, MxGroup, MxMath };
|
|
|
|
|
|
|
|
|
|
struct MxState
|
|
|
|
|
{
|
|
|
|
|
MxQuoting current;
|
|
|
|
|
// Bizarrely enough, double quoting has an impact on the behavior of some
|
|
|
|
|
// complex expressions within the quoted string.
|
|
|
|
|
bool dquote;
|
|
|
|
|
};
|
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
|
Q_DECLARE_TYPEINFO(MxState, Q_PRIMITIVE_TYPE);
|
|
|
|
|
QT_END_NAMESPACE
|
|
|
|
|
|
|
|
|
|
// Pushed state for the case where a $(()) expansion turns out bogus
|
|
|
|
|
struct MxSave
|
|
|
|
|
{
|
|
|
|
|
QString str;
|
|
|
|
|
int pos, varPos;
|
|
|
|
|
};
|
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
|
Q_DECLARE_TYPEINFO(MxSave, Q_MOVABLE_TYPE);
|
|
|
|
|
QT_END_NAMESPACE
|
|
|
|
|
|
|
|
|
|
namespace Utils {
|
2010-10-14 18:05:43 +02:00
|
|
|
|
2011-03-02 17:13:33 +01:00
|
|
|
/*!
|
|
|
|
|
\class Utils::QtcProcess
|
|
|
|
|
|
2013-06-05 14:29:24 +02:00
|
|
|
\brief The QtcProcess class provides functionality for dealing with
|
|
|
|
|
shell-quoted process arguments.
|
2011-03-02 17:13:33 +01:00
|
|
|
*/
|
2010-10-14 18:05:43 +02:00
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
inline static bool isMetaCharWin(ushort c)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
static const uchar iqm[] = {
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x40, 0x03, 0x00, 0x50,
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10
|
|
|
|
|
}; // &()<>|
|
|
|
|
|
|
|
|
|
|
return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
static void envExpandWin(QString &args, const Environment *env, const QString *pwd)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
static const QString cdName = QLatin1String("CD");
|
|
|
|
|
int off = 0;
|
|
|
|
|
next:
|
|
|
|
|
for (int prev = -1, that;
|
|
|
|
|
(that = args.indexOf(QLatin1Char('%'), off)) >= 0;
|
|
|
|
|
prev = that, off = that + 1) {
|
|
|
|
|
if (prev >= 0) {
|
|
|
|
|
const QString var = args.mid(prev + 1, that - prev - 1).toUpper();
|
|
|
|
|
const QString val = (var == cdName && pwd && !pwd->isEmpty())
|
2019-08-19 14:29:14 +02:00
|
|
|
? QDir::toNativeSeparators(*pwd) : env->expandedValueForKey(var);
|
2010-10-14 18:05:43 +02:00
|
|
|
if (!val.isEmpty()) { // Empty values are impossible, so this is an existence check
|
|
|
|
|
args.replace(prev, that - prev + 1, val);
|
|
|
|
|
off = prev + val.length();
|
|
|
|
|
goto next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
static QtcProcess::Arguments prepareArgsWin(const QString &_args, QtcProcess::SplitError *err,
|
|
|
|
|
const Environment *env, const QString *pwd)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
QString args(_args);
|
|
|
|
|
|
|
|
|
|
if (env) {
|
2014-02-05 10:43:21 +01:00
|
|
|
envExpandWin(args, env, pwd);
|
2010-10-14 18:05:43 +02:00
|
|
|
} else {
|
|
|
|
|
if (args.indexOf(QLatin1Char('%')) >= 0) {
|
|
|
|
|
if (err)
|
2014-02-05 10:43:21 +01:00
|
|
|
*err = QtcProcess::FoundMeta;
|
|
|
|
|
return QtcProcess::Arguments::createWindowsArgs(QString());
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!args.isEmpty() && args.unicode()[0].unicode() == '@')
|
|
|
|
|
args.remove(0, 1);
|
|
|
|
|
|
|
|
|
|
for (int p = 0; p < args.length(); p++) {
|
|
|
|
|
ushort c = args.unicode()[p].unicode();
|
|
|
|
|
if (c == '^') {
|
|
|
|
|
args.remove(p, 1);
|
|
|
|
|
} else if (c == '"') {
|
|
|
|
|
do {
|
|
|
|
|
if (++p == args.length())
|
|
|
|
|
break; // For cmd, this is no error.
|
|
|
|
|
} while (args.unicode()[p].unicode() != '"');
|
2014-02-05 10:43:21 +01:00
|
|
|
} else if (isMetaCharWin(c)) {
|
2010-10-14 18:05:43 +02:00
|
|
|
if (err)
|
2014-02-05 10:43:21 +01:00
|
|
|
*err = QtcProcess::FoundMeta;
|
|
|
|
|
return QtcProcess::Arguments::createWindowsArgs(QString());
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (err)
|
2014-02-05 10:43:21 +01:00
|
|
|
*err = QtcProcess::SplitOk;
|
|
|
|
|
return QtcProcess::Arguments::createWindowsArgs(args);
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
inline static bool isWhiteSpaceWin(ushort c)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
return c == ' ' || c == '\t';
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
static QStringList doSplitArgsWin(const QString &args, QtcProcess::SplitError *err)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
QStringList ret;
|
|
|
|
|
|
|
|
|
|
if (err)
|
|
|
|
|
*err = QtcProcess::SplitOk;
|
|
|
|
|
|
|
|
|
|
int p = 0;
|
|
|
|
|
const int length = args.length();
|
|
|
|
|
forever {
|
|
|
|
|
forever {
|
|
|
|
|
if (p == length)
|
|
|
|
|
return ret;
|
2014-02-05 10:43:21 +01:00
|
|
|
if (!isWhiteSpaceWin(args.unicode()[p].unicode()))
|
2010-10-14 18:05:43 +02:00
|
|
|
break;
|
|
|
|
|
++p;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString arg;
|
|
|
|
|
bool inquote = false;
|
|
|
|
|
forever {
|
|
|
|
|
bool copy = true; // copy this char
|
|
|
|
|
int bslashes = 0; // number of preceding backslashes to insert
|
|
|
|
|
while (p < length && args.unicode()[p] == QLatin1Char('\\')) {
|
|
|
|
|
++p;
|
|
|
|
|
++bslashes;
|
|
|
|
|
}
|
|
|
|
|
if (p < length && args.unicode()[p] == QLatin1Char('"')) {
|
|
|
|
|
if (!(bslashes & 1)) {
|
|
|
|
|
// Even number of backslashes, so the quote is not escaped.
|
|
|
|
|
if (inquote) {
|
|
|
|
|
if (p + 1 < length && args.unicode()[p + 1] == QLatin1Char('"')) {
|
|
|
|
|
// This is not documented on MSDN.
|
|
|
|
|
// Two consecutive quotes make a literal quote. Brain damage:
|
|
|
|
|
// this still closes the quoting, so a 3rd quote is required,
|
|
|
|
|
// which makes the runtime's quoting run out of sync with the
|
|
|
|
|
// shell's one unless the 2nd quote is escaped.
|
|
|
|
|
++p;
|
|
|
|
|
} else {
|
|
|
|
|
// Closing quote
|
|
|
|
|
copy = false;
|
|
|
|
|
}
|
|
|
|
|
inquote = false;
|
|
|
|
|
} else {
|
|
|
|
|
// Opening quote
|
|
|
|
|
copy = false;
|
|
|
|
|
inquote = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
bslashes >>= 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (--bslashes >= 0)
|
|
|
|
|
arg.append(QLatin1Char('\\'));
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
if (p == length || (!inquote && isWhiteSpaceWin(args.unicode()[p].unicode()))) {
|
2010-10-14 18:05:43 +02:00
|
|
|
ret.append(arg);
|
|
|
|
|
if (inquote) {
|
|
|
|
|
if (err)
|
|
|
|
|
*err = QtcProcess::BadQuoting;
|
|
|
|
|
return QStringList();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (copy)
|
|
|
|
|
arg.append(args.unicode()[p]);
|
|
|
|
|
++p;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//not reached
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-02 17:13:33 +01:00
|
|
|
/*!
|
2013-09-06 13:23:11 +02:00
|
|
|
Splits \a _args according to system shell word splitting and quoting rules.
|
2011-03-02 17:13:33 +01:00
|
|
|
|
|
|
|
|
\section1 Unix
|
|
|
|
|
|
|
|
|
|
The behavior is based on the POSIX shell and bash:
|
|
|
|
|
\list
|
2013-09-06 13:23:11 +02:00
|
|
|
\li Whitespace splits tokens.
|
|
|
|
|
\li The backslash quotes the following character.
|
2013-02-06 08:50:23 +01:00
|
|
|
\li A string enclosed in single quotes is not split. No shell meta
|
2011-03-02 17:13:33 +01:00
|
|
|
characters are interpreted.
|
2013-02-06 08:50:23 +01:00
|
|
|
\li A string enclosed in double quotes is not split. Within the string,
|
2011-03-02 17:13:33 +01:00
|
|
|
the backslash quotes shell meta characters - if it is followed
|
|
|
|
|
by a "meaningless" character, the backslash is output verbatim.
|
|
|
|
|
\endlist
|
|
|
|
|
If \a abortOnMeta is \c false, only the splitting and quoting rules apply,
|
|
|
|
|
while other meta characters (substitutions, redirections, etc.) are ignored.
|
|
|
|
|
If \a abortOnMeta is \c true, encounters of unhandled meta characters are
|
|
|
|
|
treated as errors.
|
|
|
|
|
|
2013-09-06 13:23:11 +02:00
|
|
|
If \a err is not NULL, stores a status code at the pointer target. For more
|
|
|
|
|
information, see \l SplitError.
|
|
|
|
|
|
|
|
|
|
If \env is not NULL, performs variable substitution with the
|
|
|
|
|
given environment.
|
|
|
|
|
|
|
|
|
|
Returns a list of unquoted words or an empty list if an error occurred.
|
|
|
|
|
|
2011-03-02 17:13:33 +01:00
|
|
|
\section1 Windows
|
|
|
|
|
|
|
|
|
|
The behavior is defined by the Microsoft C runtime:
|
|
|
|
|
\list
|
2013-09-06 13:23:11 +02:00
|
|
|
\li Whitespace splits tokens.
|
|
|
|
|
\li A string enclosed in double quotes is not split.
|
2011-03-02 17:13:33 +01:00
|
|
|
\list
|
2013-02-06 08:50:23 +01:00
|
|
|
\li 3N double quotes within a quoted string yield N literal quotes.
|
2011-03-02 17:13:33 +01:00
|
|
|
This is not documented on MSDN.
|
|
|
|
|
\endlist
|
2013-09-06 13:23:11 +02:00
|
|
|
\li Backslashes have special semantics if they are followed by a double quote:
|
2011-03-02 17:13:33 +01:00
|
|
|
\list
|
2013-02-06 08:50:23 +01:00
|
|
|
\li 2N backslashes + double quote => N backslashes and begin/end quoting
|
|
|
|
|
\li 2N+1 backslashes + double quote => N backslashes + literal quote
|
2011-03-02 17:13:33 +01:00
|
|
|
\endlist
|
|
|
|
|
\endlist
|
|
|
|
|
Qt and many other implementations comply with this standard, but many do not.
|
|
|
|
|
|
|
|
|
|
If \a abortOnMeta is \c true, cmd shell semantics are applied before
|
|
|
|
|
proceeding with word splitting:
|
|
|
|
|
\list
|
2013-02-06 08:50:23 +01:00
|
|
|
\li Cmd ignores \e all special chars between double quotes.
|
2011-03-28 14:58:12 +02:00
|
|
|
Note that the quotes are \e not removed at this stage - the
|
2011-03-02 17:13:33 +01:00
|
|
|
tokenization rules described above still apply.
|
2013-02-06 08:50:23 +01:00
|
|
|
\li The \c circumflex is the escape char for everything including itself.
|
2011-03-02 17:13:33 +01:00
|
|
|
\endlist
|
|
|
|
|
As the quoting levels are independent from each other and have different
|
|
|
|
|
semantics, you need a command line like \c{"foo "\^"" bar"} to get
|
|
|
|
|
\c{foo " bar}.
|
|
|
|
|
*/
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
|
|
|
|
|
static QStringList splitArgsWin(const QString &_args, bool abortOnMeta,
|
|
|
|
|
QtcProcess::SplitError *err,
|
|
|
|
|
const Environment *env, const QString *pwd)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
if (abortOnMeta) {
|
2014-02-05 10:43:21 +01:00
|
|
|
QtcProcess::SplitError perr;
|
2010-10-14 18:05:43 +02:00
|
|
|
if (!err)
|
|
|
|
|
err = &perr;
|
2014-02-05 10:43:21 +01:00
|
|
|
QString args = prepareArgsWin(_args, &perr, env, pwd).toWindowsArgs();
|
|
|
|
|
if (*err != QtcProcess::SplitOk)
|
2010-10-14 18:05:43 +02:00
|
|
|
return QStringList();
|
2014-02-05 10:43:21 +01:00
|
|
|
return doSplitArgsWin(args, err);
|
2010-10-14 18:05:43 +02:00
|
|
|
} else {
|
|
|
|
|
QString args = _args;
|
|
|
|
|
if (env)
|
2014-02-05 10:43:21 +01:00
|
|
|
envExpandWin(args, env, pwd);
|
|
|
|
|
return doSplitArgsWin(args, err);
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
static bool isMetaUnix(QChar cUnicode)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
static const uchar iqm[] = {
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0xdc, 0x07, 0x00, 0xd8,
|
|
|
|
|
0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x38
|
|
|
|
|
}; // \'"$`<>|;&(){}*?#[]
|
|
|
|
|
|
|
|
|
|
uint c = cUnicode.unicode();
|
|
|
|
|
|
|
|
|
|
return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
static QStringList splitArgsUnix(const QString &args, bool abortOnMeta,
|
|
|
|
|
QtcProcess::SplitError *err,
|
|
|
|
|
const Environment *env, const QString *pwd)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
static const QString pwdName = QLatin1String("PWD");
|
|
|
|
|
QStringList ret;
|
|
|
|
|
|
|
|
|
|
for (int pos = 0; ; ) {
|
|
|
|
|
QChar c;
|
|
|
|
|
do {
|
|
|
|
|
if (pos >= args.length())
|
|
|
|
|
goto okret;
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
} while (c.isSpace());
|
|
|
|
|
QString cret;
|
|
|
|
|
bool hadWord = false;
|
2011-03-28 14:50:32 +02:00
|
|
|
if (c == QLatin1Char('~')) {
|
|
|
|
|
if (pos >= args.length()
|
|
|
|
|
|| args.unicode()[pos].isSpace() || args.unicode()[pos] == QLatin1Char('/')) {
|
|
|
|
|
cret = QDir::homePath();
|
|
|
|
|
hadWord = true;
|
|
|
|
|
goto getc;
|
|
|
|
|
} else if (abortOnMeta) {
|
|
|
|
|
goto metaerr;
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-10-14 18:05:43 +02:00
|
|
|
do {
|
|
|
|
|
if (c == QLatin1Char('\'')) {
|
|
|
|
|
int spos = pos;
|
|
|
|
|
do {
|
|
|
|
|
if (pos >= args.length())
|
|
|
|
|
goto quoteerr;
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
} while (c != QLatin1Char('\''));
|
2017-04-14 10:24:28 +02:00
|
|
|
cret += args.midRef(spos, pos - spos - 1);
|
2010-10-14 18:05:43 +02:00
|
|
|
hadWord = true;
|
|
|
|
|
} else if (c == QLatin1Char('"')) {
|
|
|
|
|
for (;;) {
|
|
|
|
|
if (pos >= args.length())
|
|
|
|
|
goto quoteerr;
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
nextq:
|
|
|
|
|
if (c == QLatin1Char('"'))
|
|
|
|
|
break;
|
|
|
|
|
if (c == QLatin1Char('\\')) {
|
|
|
|
|
if (pos >= args.length())
|
|
|
|
|
goto quoteerr;
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
if (c != QLatin1Char('"') &&
|
|
|
|
|
c != QLatin1Char('\\') &&
|
|
|
|
|
!(abortOnMeta &&
|
|
|
|
|
(c == QLatin1Char('$') ||
|
|
|
|
|
c == QLatin1Char('`'))))
|
|
|
|
|
cret += QLatin1Char('\\');
|
|
|
|
|
} else if (c == QLatin1Char('$') && env) {
|
|
|
|
|
if (pos >= args.length())
|
|
|
|
|
goto quoteerr;
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
bool braced = false;
|
|
|
|
|
if (c == QLatin1Char('{')) {
|
|
|
|
|
if (pos >= args.length())
|
|
|
|
|
goto quoteerr;
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
braced = true;
|
|
|
|
|
}
|
|
|
|
|
QString var;
|
|
|
|
|
while (c.isLetterOrNumber() || c == QLatin1Char('_')) {
|
|
|
|
|
var += c;
|
|
|
|
|
if (pos >= args.length())
|
|
|
|
|
goto quoteerr;
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
}
|
|
|
|
|
if (var == pwdName && pwd && !pwd->isEmpty()) {
|
|
|
|
|
cret += *pwd;
|
|
|
|
|
} else {
|
|
|
|
|
Environment::const_iterator vit = env->constFind(var);
|
|
|
|
|
if (vit == env->constEnd()) {
|
|
|
|
|
if (abortOnMeta)
|
|
|
|
|
goto metaerr; // Assume this is a shell builtin
|
|
|
|
|
} else {
|
2019-08-19 14:29:14 +02:00
|
|
|
cret += env->expandedValueForKey(env->key(vit));
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!braced)
|
|
|
|
|
goto nextq;
|
|
|
|
|
if (c != QLatin1Char('}')) {
|
|
|
|
|
if (abortOnMeta)
|
|
|
|
|
goto metaerr; // Assume this is a complex expansion
|
|
|
|
|
goto quoteerr; // Otherwise it's just garbage
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
} else if (abortOnMeta &&
|
|
|
|
|
(c == QLatin1Char('$') ||
|
|
|
|
|
c == QLatin1Char('`'))) {
|
|
|
|
|
goto metaerr;
|
|
|
|
|
}
|
|
|
|
|
cret += c;
|
|
|
|
|
}
|
|
|
|
|
hadWord = true;
|
|
|
|
|
} else if (c == QLatin1Char('$') && env) {
|
|
|
|
|
if (pos >= args.length())
|
|
|
|
|
goto quoteerr; // Bash just takes it verbatim, but whatever
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
bool braced = false;
|
|
|
|
|
if (c == QLatin1Char('{')) {
|
|
|
|
|
if (pos >= args.length())
|
|
|
|
|
goto quoteerr;
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
braced = true;
|
|
|
|
|
}
|
|
|
|
|
QString var;
|
|
|
|
|
while (c.isLetterOrNumber() || c == QLatin1Char('_')) {
|
|
|
|
|
var += c;
|
|
|
|
|
if (pos >= args.length()) {
|
|
|
|
|
if (braced)
|
|
|
|
|
goto quoteerr;
|
|
|
|
|
c = QLatin1Char(' ');
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
}
|
|
|
|
|
QString val;
|
|
|
|
|
if (var == pwdName && pwd && !pwd->isEmpty()) {
|
|
|
|
|
val = *pwd;
|
|
|
|
|
} else {
|
|
|
|
|
Environment::const_iterator vit = env->constFind(var);
|
|
|
|
|
if (vit == env->constEnd()) {
|
|
|
|
|
if (abortOnMeta)
|
|
|
|
|
goto metaerr; // Assume this is a shell builtin
|
|
|
|
|
} else {
|
2019-08-19 14:29:14 +02:00
|
|
|
val = env->expandedValueForKey(env->key(vit));
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (int i = 0; i < val.length(); i++) {
|
2016-05-18 12:37:39 +02:00
|
|
|
const QChar cc = val.unicode()[i];
|
|
|
|
|
if (cc.unicode() == 9 || cc.unicode() == 10 || cc.unicode() == 32) {
|
2010-10-14 18:05:43 +02:00
|
|
|
if (hadWord) {
|
|
|
|
|
ret += cret;
|
|
|
|
|
cret.clear();
|
|
|
|
|
hadWord = false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
cret += cc;
|
|
|
|
|
hadWord = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!braced)
|
|
|
|
|
goto nextc;
|
|
|
|
|
if (c != QLatin1Char('}')) {
|
|
|
|
|
if (abortOnMeta)
|
|
|
|
|
goto metaerr; // Assume this is a complex expansion
|
|
|
|
|
goto quoteerr; // Otherwise it's just garbage
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (c == QLatin1Char('\\')) {
|
|
|
|
|
if (pos >= args.length())
|
|
|
|
|
goto quoteerr;
|
|
|
|
|
c = args.unicode()[pos++];
|
2014-02-05 10:43:21 +01:00
|
|
|
} else if (abortOnMeta && isMetaUnix(c)) {
|
2010-10-14 18:05:43 +02:00
|
|
|
goto metaerr;
|
|
|
|
|
}
|
|
|
|
|
cret += c;
|
|
|
|
|
hadWord = true;
|
|
|
|
|
}
|
2011-03-28 14:50:32 +02:00
|
|
|
getc:
|
2010-10-14 18:05:43 +02:00
|
|
|
if (pos >= args.length())
|
|
|
|
|
break;
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
nextc: ;
|
|
|
|
|
} while (!c.isSpace());
|
|
|
|
|
if (hadWord)
|
|
|
|
|
ret += cret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
okret:
|
|
|
|
|
if (err)
|
2014-02-05 10:43:21 +01:00
|
|
|
*err = QtcProcess::SplitOk;
|
2010-10-14 18:05:43 +02:00
|
|
|
return ret;
|
|
|
|
|
|
|
|
|
|
quoteerr:
|
|
|
|
|
if (err)
|
2014-02-05 10:43:21 +01:00
|
|
|
*err = QtcProcess::BadQuoting;
|
2010-10-14 18:05:43 +02:00
|
|
|
return QStringList();
|
|
|
|
|
|
|
|
|
|
metaerr:
|
|
|
|
|
if (err)
|
2014-02-05 10:43:21 +01:00
|
|
|
*err = QtcProcess::FoundMeta;
|
2010-10-14 18:05:43 +02:00
|
|
|
return QStringList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline static bool isSpecialCharUnix(ushort c)
|
|
|
|
|
{
|
|
|
|
|
// Chars that should be quoted (TM). This includes:
|
|
|
|
|
static const uchar iqm[] = {
|
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8,
|
|
|
|
|
0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78
|
|
|
|
|
}; // 0-32 \'"$`<>|;&(){}*?#!~[]
|
|
|
|
|
|
|
|
|
|
return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline static bool hasSpecialCharsUnix(const QString &arg)
|
|
|
|
|
{
|
|
|
|
|
for (int x = arg.length() - 1; x >= 0; --x)
|
|
|
|
|
if (isSpecialCharUnix(arg.unicode()[x].unicode()))
|
|
|
|
|
return true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
QStringList QtcProcess::splitArgs(const QString &args, OsType osType,
|
|
|
|
|
bool abortOnMeta, QtcProcess::SplitError *err,
|
|
|
|
|
const Environment *env, const QString *pwd)
|
|
|
|
|
{
|
|
|
|
|
if (osType == OsTypeWindows)
|
|
|
|
|
return splitArgsWin(args, abortOnMeta, err, env, pwd);
|
|
|
|
|
else
|
|
|
|
|
return splitArgsUnix(args, abortOnMeta, err, env, pwd);
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-14 18:05:43 +02:00
|
|
|
QString QtcProcess::quoteArgUnix(const QString &arg)
|
|
|
|
|
{
|
|
|
|
|
if (!arg.length())
|
|
|
|
|
return QString::fromLatin1("''");
|
|
|
|
|
|
|
|
|
|
QString ret(arg);
|
|
|
|
|
if (hasSpecialCharsUnix(ret)) {
|
|
|
|
|
ret.replace(QLatin1Char('\''), QLatin1String("'\\''"));
|
|
|
|
|
ret.prepend(QLatin1Char('\''));
|
|
|
|
|
ret.append(QLatin1Char('\''));
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
static bool isSpecialCharWin(ushort c)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
// Chars that should be quoted (TM). This includes:
|
|
|
|
|
// - control chars & space
|
|
|
|
|
// - the shell meta chars "&()<>^|
|
|
|
|
|
// - the potential separators ,;=
|
|
|
|
|
static const uchar iqm[] = {
|
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0x45, 0x13, 0x00, 0x78,
|
|
|
|
|
0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
static bool hasSpecialCharsWin(const QString &arg)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
for (int x = arg.length() - 1; x >= 0; --x)
|
2014-02-05 10:43:21 +01:00
|
|
|
if (isSpecialCharWin(arg.unicode()[x].unicode()))
|
2010-10-14 18:05:43 +02:00
|
|
|
return true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
static QString quoteArgWin(const QString &arg)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
if (!arg.length())
|
|
|
|
|
return QString::fromLatin1("\"\"");
|
|
|
|
|
|
|
|
|
|
QString ret(arg);
|
2014-02-05 10:43:21 +01:00
|
|
|
if (hasSpecialCharsWin(ret)) {
|
2010-10-14 18:05:43 +02:00
|
|
|
// Quotes are escaped and their preceding backslashes are doubled.
|
|
|
|
|
// It's impossible to escape anything inside a quoted string on cmd
|
|
|
|
|
// level, so the outer quoting must be "suspended".
|
|
|
|
|
ret.replace(QRegExp(QLatin1String("(\\\\*)\"")), QLatin1String("\"\\1\\1\\^\"\""));
|
|
|
|
|
// The argument must not end with a \ since this would be interpreted
|
|
|
|
|
// as escaping the quote -- rather put the \ behind the quote: e.g.
|
|
|
|
|
// rather use "foo"\ than "foo\"
|
2011-06-30 18:44:44 +02:00
|
|
|
int i = ret.length();
|
|
|
|
|
while (i > 0 && ret.at(i - 1) == QLatin1Char('\\'))
|
|
|
|
|
--i;
|
|
|
|
|
ret.insert(i, QLatin1Char('"'));
|
2010-10-14 18:05:43 +02:00
|
|
|
ret.prepend(QLatin1Char('"'));
|
|
|
|
|
}
|
|
|
|
|
// FIXME: Without this, quoting is not foolproof. But it needs support in the process setup, etc.
|
|
|
|
|
//ret.replace('%', QLatin1String("%PERCENT_SIGN%"));
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
QtcProcess::Arguments QtcProcess::prepareArgs(const QString &cmd, SplitError *err, OsType osType,
|
2019-06-14 13:37:20 +02:00
|
|
|
const Environment *env, const QString *pwd, bool abortOnMeta)
|
2014-02-05 10:43:21 +01:00
|
|
|
{
|
|
|
|
|
if (osType == OsTypeWindows)
|
|
|
|
|
return prepareArgsWin(cmd, err, env, pwd);
|
|
|
|
|
else
|
2019-06-14 13:37:20 +02:00
|
|
|
return Arguments::createUnixArgs(splitArgs(cmd, osType, abortOnMeta, err, env, pwd));
|
2014-02-05 10:43:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
QString QtcProcess::quoteArg(const QString &arg, OsType osType)
|
|
|
|
|
{
|
|
|
|
|
if (osType == OsTypeWindows)
|
|
|
|
|
return quoteArgWin(arg);
|
|
|
|
|
else
|
|
|
|
|
return quoteArgUnix(arg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::addArg(QString *args, const QString &arg, OsType osType)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
if (!args->isEmpty())
|
|
|
|
|
*args += QLatin1Char(' ');
|
2014-02-05 10:43:21 +01:00
|
|
|
*args += quoteArg(arg, osType);
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
QString QtcProcess::joinArgs(const QStringList &args, OsType osType)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
QString ret;
|
|
|
|
|
foreach (const QString &arg, args)
|
2014-02-05 10:43:21 +01:00
|
|
|
addArg(&ret, arg, osType);
|
2010-10-14 18:05:43 +02:00
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::addArgs(QString *args, const QString &inArgs)
|
|
|
|
|
{
|
|
|
|
|
if (!inArgs.isEmpty()) {
|
|
|
|
|
if (!args->isEmpty())
|
|
|
|
|
*args += QLatin1Char(' ');
|
|
|
|
|
*args += inArgs;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::addArgs(QString *args, const QStringList &inArgs)
|
|
|
|
|
{
|
|
|
|
|
foreach (const QString &arg, inArgs)
|
|
|
|
|
addArg(args, arg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool QtcProcess::prepareCommand(const QString &command, const QString &arguments,
|
2014-02-05 10:43:21 +01:00
|
|
|
QString *outCmd, Arguments *outArgs, OsType osType,
|
2010-10-14 18:05:43 +02:00
|
|
|
const Environment *env, const QString *pwd)
|
|
|
|
|
{
|
|
|
|
|
QtcProcess::SplitError err;
|
2014-02-05 10:43:21 +01:00
|
|
|
*outArgs = QtcProcess::prepareArgs(arguments, &err, osType, env, pwd);
|
2010-10-14 18:05:43 +02:00
|
|
|
if (err == QtcProcess::SplitOk) {
|
|
|
|
|
*outCmd = command;
|
|
|
|
|
} else {
|
2014-02-05 10:43:21 +01:00
|
|
|
if (osType == OsTypeWindows) {
|
|
|
|
|
*outCmd = QString::fromLatin1(qgetenv("COMSPEC"));
|
|
|
|
|
*outArgs = Arguments::createWindowsArgs(QLatin1String("/v:off /s /c \"")
|
|
|
|
|
+ quoteArg(QDir::toNativeSeparators(command)) + QLatin1Char(' ') + arguments
|
|
|
|
|
+ QLatin1Char('"'));
|
|
|
|
|
} else {
|
|
|
|
|
if (err != QtcProcess::FoundMeta)
|
|
|
|
|
return false;
|
|
|
|
|
*outCmd = QLatin1String("/bin/sh");
|
2017-02-09 14:00:07 +01:00
|
|
|
*outArgs = Arguments::createUnixArgs(
|
2017-02-22 15:09:35 +01:00
|
|
|
QStringList({"-c", (quoteArg(command) + ' ' + arguments)}));
|
2014-02-05 10:43:21 +01:00
|
|
|
}
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-08 13:30:30 +02:00
|
|
|
QtcProcess::QtcProcess(QObject *parent)
|
2018-07-23 10:45:40 +02:00
|
|
|
: QProcess(parent)
|
2016-06-08 13:30:30 +02:00
|
|
|
{
|
|
|
|
|
static int qProcessExitStatusMeta = qRegisterMetaType<QProcess::ExitStatus>();
|
|
|
|
|
static int qProcessProcessErrorMeta = qRegisterMetaType<QProcess::ProcessError>();
|
2019-07-23 10:58:00 +02:00
|
|
|
Q_UNUSED(qProcessExitStatusMeta)
|
|
|
|
|
Q_UNUSED(qProcessProcessErrorMeta)
|
2016-06-08 13:30:30 +02:00
|
|
|
}
|
|
|
|
|
|
2014-05-16 15:57:02 +02:00
|
|
|
void QtcProcess::setUseCtrlCStub(bool enabled)
|
|
|
|
|
{
|
|
|
|
|
// Do not use the stub in debug mode. Activating the stub will shut down
|
|
|
|
|
// Qt Creator otherwise, because they share the same Windows console.
|
|
|
|
|
// See QTCREATORBUG-11995 for details.
|
|
|
|
|
#ifndef QT_DEBUG
|
|
|
|
|
m_useCtrlCStub = enabled;
|
2014-05-20 15:17:32 +02:00
|
|
|
#else
|
|
|
|
|
Q_UNUSED(enabled)
|
2014-05-16 15:57:02 +02:00
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-14 18:05:43 +02:00
|
|
|
void QtcProcess::start()
|
|
|
|
|
{
|
|
|
|
|
Environment env;
|
2014-02-05 10:43:21 +01:00
|
|
|
const OsType osType = HostOsInfo::hostOs();
|
2010-10-14 18:05:43 +02:00
|
|
|
if (m_haveEnv) {
|
2011-02-28 10:14:52 +01:00
|
|
|
if (m_environment.size() == 0)
|
2019-05-28 18:59:45 +02:00
|
|
|
qWarning("QtcProcess::start: Empty environment set when running '%s'.",
|
|
|
|
|
qPrintable(m_commandLine.executable().toString()));
|
2010-10-14 18:05:43 +02:00
|
|
|
env = m_environment;
|
2011-11-23 15:21:07 +01:00
|
|
|
|
2019-08-19 14:29:14 +02:00
|
|
|
QProcess::setProcessEnvironment(env.toProcessEnvironment());
|
2010-10-14 18:05:43 +02:00
|
|
|
} else {
|
|
|
|
|
env = Environment::systemEnvironment();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QString &workDir = workingDirectory();
|
|
|
|
|
QString command;
|
2014-02-05 10:43:21 +01:00
|
|
|
QtcProcess::Arguments arguments;
|
2019-05-28 18:59:45 +02:00
|
|
|
bool success = prepareCommand(m_commandLine.executable().toString(),
|
|
|
|
|
m_commandLine.arguments(),
|
|
|
|
|
&command, &arguments, osType, &env, &workDir);
|
2014-02-05 10:43:21 +01:00
|
|
|
if (osType == OsTypeWindows) {
|
2014-02-19 14:30:07 +01:00
|
|
|
QString args;
|
|
|
|
|
if (m_useCtrlCStub) {
|
2019-09-10 23:39:29 +03:00
|
|
|
if (m_lowPriority)
|
|
|
|
|
addArg(&args, "-nice");
|
|
|
|
|
addArg(&args, QDir::toNativeSeparators(command));
|
2014-02-19 14:30:07 +01:00
|
|
|
command = QCoreApplication::applicationDirPath()
|
|
|
|
|
+ QLatin1String("/qtcreator_ctrlc_stub.exe");
|
2019-09-10 23:39:29 +03:00
|
|
|
} else if (m_lowPriority) {
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
setCreateProcessArgumentsModifier([](CreateProcessArguments *args) {
|
|
|
|
|
args->flags |= BELOW_NORMAL_PRIORITY_CLASS;
|
|
|
|
|
});
|
|
|
|
|
#endif
|
2014-02-19 14:30:07 +01:00
|
|
|
}
|
|
|
|
|
QtcProcess::addArgs(&args, arguments.toWindowsArgs());
|
2010-10-14 18:05:43 +02:00
|
|
|
#ifdef Q_OS_WIN
|
2014-02-05 10:43:21 +01:00
|
|
|
setNativeArguments(args);
|
2010-10-14 18:05:43 +02:00
|
|
|
#endif
|
2014-02-19 14:30:07 +01:00
|
|
|
// Note: Arguments set with setNativeArgs will be appended to the ones
|
|
|
|
|
// passed with start() below.
|
|
|
|
|
QProcess::start(command, QStringList());
|
2014-02-05 10:43:21 +01:00
|
|
|
} else {
|
|
|
|
|
if (!success) {
|
|
|
|
|
setErrorString(tr("Error in command line."));
|
|
|
|
|
// Should be FailedToStart, but we cannot set the process error from the outside,
|
|
|
|
|
// so it would be inconsistent.
|
2016-08-03 19:43:54 +03:00
|
|
|
emit errorOccurred(QProcess::UnknownError);
|
2014-02-05 10:43:21 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
QProcess::start(command, arguments.toUnixArgs());
|
|
|
|
|
}
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
|
2011-08-03 12:04:46 +02:00
|
|
|
#ifdef Q_OS_WIN
|
2013-05-03 13:52:52 +02:00
|
|
|
static BOOL sendMessage(UINT message, HWND hwnd, LPARAM lParam)
|
2011-08-03 12:04:46 +02:00
|
|
|
{
|
|
|
|
|
DWORD dwProcessID;
|
|
|
|
|
GetWindowThreadProcessId(hwnd, &dwProcessID);
|
2011-08-09 13:43:56 +02:00
|
|
|
if ((DWORD)lParam == dwProcessID) {
|
2013-05-03 13:52:52 +02:00
|
|
|
SendNotifyMessage(hwnd, message, 0, 0);
|
2011-08-03 12:04:46 +02:00
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
2013-05-03 13:52:52 +02:00
|
|
|
|
|
|
|
|
BOOL CALLBACK sendShutDownMessageToAllWindowsOfProcess_enumWnd(HWND hwnd, LPARAM lParam)
|
|
|
|
|
{
|
|
|
|
|
static UINT uiShutDownMessage = RegisterWindowMessage(L"qtcctrlcstub_shutdown");
|
|
|
|
|
return sendMessage(uiShutDownMessage, hwnd, lParam);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BOOL CALLBACK sendInterruptMessageToAllWindowsOfProcess_enumWnd(HWND hwnd, LPARAM lParam)
|
|
|
|
|
{
|
|
|
|
|
static UINT uiInterruptMessage = RegisterWindowMessage(L"qtcctrlcstub_interrupt");
|
|
|
|
|
return sendMessage(uiInterruptMessage, hwnd, lParam);
|
|
|
|
|
}
|
2011-08-03 12:04:46 +02:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
void QtcProcess::terminate()
|
|
|
|
|
{
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
if (m_useCtrlCStub)
|
|
|
|
|
EnumWindows(sendShutDownMessageToAllWindowsOfProcess_enumWnd, pid()->dwProcessId);
|
|
|
|
|
else
|
|
|
|
|
#endif
|
|
|
|
|
QProcess::terminate();
|
|
|
|
|
}
|
|
|
|
|
|
2013-05-03 13:52:52 +02:00
|
|
|
void QtcProcess::interrupt()
|
|
|
|
|
{
|
2013-09-12 16:34:34 +02:00
|
|
|
#ifdef Q_OS_WIN
|
2013-05-03 13:52:52 +02:00
|
|
|
QTC_ASSERT(m_useCtrlCStub, return);
|
|
|
|
|
EnumWindows(sendInterruptMessageToAllWindowsOfProcess_enumWnd, pid()->dwProcessId);
|
2013-09-12 16:34:34 +02:00
|
|
|
#endif
|
2013-05-03 13:52:52 +02:00
|
|
|
}
|
2010-10-14 18:05:43 +02:00
|
|
|
|
|
|
|
|
// This function assumes that the resulting string will be quoted.
|
|
|
|
|
// That's irrelevant if it does not contain quotes itself.
|
2014-02-05 10:43:21 +01:00
|
|
|
static int quoteArgInternalWin(QString &ret, int bslashes)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
// Quotes are escaped and their preceding backslashes are doubled.
|
|
|
|
|
// It's impossible to escape anything inside a quoted string on cmd
|
|
|
|
|
// level, so the outer quoting must be "suspended".
|
|
|
|
|
const QChar bs(QLatin1Char('\\')), dq(QLatin1Char('"'));
|
|
|
|
|
for (int p = 0; p < ret.length(); p++) {
|
|
|
|
|
if (ret.at(p) == bs) {
|
|
|
|
|
bslashes++;
|
|
|
|
|
} else {
|
|
|
|
|
if (ret.at(p) == dq) {
|
|
|
|
|
if (bslashes) {
|
|
|
|
|
ret.insert(p, QString(bslashes, bs));
|
|
|
|
|
p += bslashes;
|
|
|
|
|
}
|
|
|
|
|
ret.insert(p, QLatin1String("\"\\^\""));
|
|
|
|
|
p += 4;
|
|
|
|
|
}
|
|
|
|
|
bslashes = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return bslashes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: This documentation is relevant for end-users. Where to put it?
|
|
|
|
|
/**
|
|
|
|
|
* Perform safe macro expansion (substitution) on a string for use
|
|
|
|
|
* in shell commands.
|
|
|
|
|
*
|
|
|
|
|
* \section Unix notes
|
|
|
|
|
*
|
|
|
|
|
* Explicitly supported shell constructs:
|
|
|
|
|
* \\ '' "" {} () $(()) ${} $() ``
|
|
|
|
|
*
|
|
|
|
|
* Implicitly supported shell constructs:
|
|
|
|
|
* (())
|
|
|
|
|
*
|
|
|
|
|
* Unsupported shell constructs that will cause problems:
|
|
|
|
|
* \list
|
|
|
|
|
* \li Shortened \c{case $v in pat)} syntax. Use \c{case $v in (pat)} instead.
|
|
|
|
|
* \li Bash-style \c{$""} and \c{$''} string quoting syntax.
|
|
|
|
|
* \endlist
|
|
|
|
|
*
|
|
|
|
|
* The rest of the shell (incl. bash) syntax is simply ignored,
|
|
|
|
|
* as it is not expected to cause problems.
|
|
|
|
|
*
|
|
|
|
|
* Security considerations:
|
|
|
|
|
* \list
|
|
|
|
|
* \li Backslash-escaping an expando is treated as a quoting error
|
|
|
|
|
* \li Do not put expandos into double quoted substitutions:
|
|
|
|
|
* \badcode
|
|
|
|
|
* "${VAR:-%{macro}}"
|
|
|
|
|
* \endcode
|
|
|
|
|
* \li Do not put expandos into command line arguments which are nested
|
|
|
|
|
* shell commands:
|
|
|
|
|
* \badcode
|
|
|
|
|
* sh -c 'foo \%{file}'
|
|
|
|
|
* \endcode
|
|
|
|
|
* \goodcode
|
|
|
|
|
* file=\%{file} sh -c 'foo "$file"'
|
|
|
|
|
* \endcode
|
|
|
|
|
* \endlist
|
|
|
|
|
*
|
|
|
|
|
* \section Windows notes
|
|
|
|
|
*
|
|
|
|
|
* All quoting syntax supported by splitArgs() is supported here as well.
|
|
|
|
|
* Additionally, command grouping via parentheses is recognized - note
|
|
|
|
|
* however, that the parser is much stricter about unquoted parentheses
|
|
|
|
|
* than cmd itself.
|
|
|
|
|
* The rest of the cmd syntax is simply ignored, as it is not expected
|
|
|
|
|
* to cause problems.
|
|
|
|
|
*
|
|
|
|
|
* Security considerations:
|
|
|
|
|
* \list
|
|
|
|
|
* \li Circumflex-escaping an expando is treated as a quoting error
|
|
|
|
|
* \li Closing double quotes right before expandos and opening double quotes
|
|
|
|
|
* right after expandos are treated as quoting errors
|
|
|
|
|
* \li Do not put expandos into nested commands:
|
|
|
|
|
* \badcode
|
|
|
|
|
* for /f "usebackq" \%v in (`foo \%{file}`) do \@echo \%v
|
|
|
|
|
* \endcode
|
|
|
|
|
* \li A macro's value must not contain anything which may be interpreted
|
|
|
|
|
* as an environment variable expansion. A solution is replacing any
|
|
|
|
|
* percent signs with a fixed string like \c{\%PERCENT_SIGN\%} and
|
|
|
|
|
* injecting \c{PERCENT_SIGN=\%} into the shell's environment.
|
|
|
|
|
* \li Enabling delayed environment variable expansion (cmd /v:on) should have
|
|
|
|
|
* no security implications, but may still wreak havoc due to the
|
|
|
|
|
* need for doubling circumflexes if any exclamation marks are present,
|
|
|
|
|
* and the need to circumflex-escape the exclamation marks themselves.
|
|
|
|
|
* \endlist
|
|
|
|
|
*
|
|
|
|
|
* \param cmd pointer to the string in which macros are expanded in-place
|
|
|
|
|
* \param mx pointer to a macro expander instance
|
|
|
|
|
* \return false if the string could not be parsed and therefore no safe
|
|
|
|
|
* substitution was possible
|
|
|
|
|
*/
|
2014-02-05 10:43:21 +01:00
|
|
|
bool QtcProcess::expandMacros(QString *cmd, AbstractMacroExpander *mx, OsType osType)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
QString str = *cmd;
|
|
|
|
|
if (str.isEmpty())
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
QString rsts;
|
|
|
|
|
int varLen;
|
|
|
|
|
int varPos = 0;
|
|
|
|
|
if (!(varLen = mx->findMacro(str, &varPos, &rsts)))
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
int pos = 0;
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
if (osType == OsTypeWindows) {
|
|
|
|
|
enum { // cmd.exe parsing state
|
|
|
|
|
ShellBasic, // initial state
|
|
|
|
|
ShellQuoted, // double-quoted state => *no* other meta chars are interpreted
|
|
|
|
|
ShellEscaped // circumflex-escaped state => next char is not interpreted
|
|
|
|
|
} shellState = ShellBasic;
|
|
|
|
|
enum { // CommandLineToArgv() parsing state and some more
|
|
|
|
|
CrtBasic, // initial state
|
|
|
|
|
CrtNeedWord, // after empty expando; insert empty argument if whitespace follows
|
|
|
|
|
CrtInWord, // in non-whitespace
|
|
|
|
|
CrtClosed, // previous char closed the double-quoting
|
|
|
|
|
CrtHadQuote, // closed double-quoting after an expando
|
|
|
|
|
// The remaining two need to be numerically higher
|
|
|
|
|
CrtQuoted, // double-quoted state => spaces don't split tokens
|
|
|
|
|
CrtNeedQuote // expando opened quote; close if no expando follows
|
|
|
|
|
} crtState = CrtBasic;
|
|
|
|
|
int bslashes = 0; // previous chars were manual backslashes
|
|
|
|
|
int rbslashes = 0; // trailing backslashes in replacement
|
2010-10-14 18:05:43 +02:00
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
forever {
|
|
|
|
|
if (pos == varPos) {
|
|
|
|
|
if (shellState == ShellEscaped)
|
|
|
|
|
return false; // Circumflex'd quoted expando would be Bad (TM).
|
|
|
|
|
if ((shellState == ShellQuoted) != (crtState == CrtQuoted))
|
|
|
|
|
return false; // CRT quoting out of sync with shell quoting. Ahoy to Redmond.
|
|
|
|
|
rbslashes += bslashes;
|
|
|
|
|
bslashes = 0;
|
|
|
|
|
if (crtState < CrtQuoted) {
|
|
|
|
|
if (rsts.isEmpty()) {
|
|
|
|
|
if (crtState == CrtBasic) {
|
|
|
|
|
// Outside any quoting and the string is empty, so put
|
|
|
|
|
// a pair of quotes. Delaying that is just pedantry.
|
|
|
|
|
crtState = CrtNeedWord;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
2014-02-05 10:43:21 +01:00
|
|
|
if (hasSpecialCharsWin(rsts)) {
|
|
|
|
|
if (crtState == CrtClosed) {
|
|
|
|
|
// Quoted expando right after closing quote. Can't do that.
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
int tbslashes = quoteArgInternalWin(rsts, 0);
|
|
|
|
|
rsts.prepend(QLatin1Char('"'));
|
|
|
|
|
if (rbslashes)
|
|
|
|
|
rsts.prepend(QString(rbslashes, QLatin1Char('\\')));
|
|
|
|
|
crtState = CrtNeedQuote;
|
|
|
|
|
rbslashes = tbslashes;
|
|
|
|
|
} else {
|
|
|
|
|
crtState = CrtInWord; // We know that this string contains no spaces.
|
|
|
|
|
// We know that this string contains no quotes,
|
|
|
|
|
// so the function won't make a mess.
|
|
|
|
|
rbslashes = quoteArgInternalWin(rsts, rbslashes);
|
|
|
|
|
}
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
} else {
|
|
|
|
|
rbslashes = quoteArgInternalWin(rsts, rbslashes);
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
str.replace(pos, varLen, rsts);
|
|
|
|
|
pos += rsts.length();
|
|
|
|
|
varPos = pos;
|
|
|
|
|
if (!(varLen = mx->findMacro(str, &varPos, &rsts))) {
|
|
|
|
|
// Don't leave immediately, as we may be in CrtNeedWord state which could
|
|
|
|
|
// be still resolved, or we may have inserted trailing backslashes.
|
|
|
|
|
varPos = INT_MAX;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
if (crtState == CrtNeedQuote) {
|
|
|
|
|
if (rbslashes) {
|
|
|
|
|
str.insert(pos, QString(rbslashes, QLatin1Char('\\')));
|
|
|
|
|
pos += rbslashes;
|
|
|
|
|
varPos += rbslashes;
|
|
|
|
|
rbslashes = 0;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
str.insert(pos, QLatin1Char('"'));
|
|
|
|
|
pos++;
|
|
|
|
|
varPos++;
|
|
|
|
|
crtState = CrtHadQuote;
|
|
|
|
|
}
|
|
|
|
|
ushort cc = str.unicode()[pos].unicode();
|
|
|
|
|
if (shellState == ShellBasic && cc == '^') {
|
|
|
|
|
shellState = ShellEscaped;
|
2010-10-14 18:05:43 +02:00
|
|
|
} else {
|
2014-02-05 10:43:21 +01:00
|
|
|
if (!cc || cc == ' ' || cc == '\t') {
|
|
|
|
|
if (crtState < CrtQuoted) {
|
|
|
|
|
if (crtState == CrtNeedWord) {
|
|
|
|
|
str.insert(pos, QLatin1String("\"\""));
|
|
|
|
|
pos += 2;
|
|
|
|
|
varPos += 2;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
crtState = CrtBasic;
|
|
|
|
|
}
|
|
|
|
|
if (!cc)
|
|
|
|
|
break;
|
|
|
|
|
bslashes = 0;
|
|
|
|
|
rbslashes = 0;
|
|
|
|
|
} else {
|
|
|
|
|
if (cc == '\\') {
|
|
|
|
|
bslashes++;
|
|
|
|
|
if (crtState < CrtQuoted)
|
|
|
|
|
crtState = CrtInWord;
|
|
|
|
|
} else {
|
|
|
|
|
if (cc == '"') {
|
|
|
|
|
if (shellState != ShellEscaped)
|
|
|
|
|
shellState = (shellState == ShellQuoted) ? ShellBasic : ShellQuoted;
|
|
|
|
|
if (rbslashes) {
|
|
|
|
|
// Offset -1: skip possible circumflex. We have at least
|
|
|
|
|
// one backslash, so a fixed offset is ok.
|
|
|
|
|
str.insert(pos - 1, QString(rbslashes, QLatin1Char('\\')));
|
|
|
|
|
pos += rbslashes;
|
|
|
|
|
varPos += rbslashes;
|
|
|
|
|
}
|
|
|
|
|
if (!(bslashes & 1)) {
|
|
|
|
|
// Even number of backslashes, so the quote is not escaped.
|
|
|
|
|
switch (crtState) {
|
|
|
|
|
case CrtQuoted:
|
|
|
|
|
// Closing quote
|
|
|
|
|
crtState = CrtClosed;
|
|
|
|
|
break;
|
|
|
|
|
case CrtClosed:
|
|
|
|
|
// Two consecutive quotes make a literal quote - and
|
|
|
|
|
// still close quoting. See QtcProcess::quoteArg().
|
|
|
|
|
crtState = CrtInWord;
|
|
|
|
|
break;
|
|
|
|
|
case CrtHadQuote:
|
|
|
|
|
// Opening quote right after quoted expando. Can't do that.
|
|
|
|
|
return false;
|
|
|
|
|
default:
|
|
|
|
|
// Opening quote
|
|
|
|
|
crtState = CrtQuoted;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else if (crtState < CrtQuoted) {
|
2010-10-14 18:05:43 +02:00
|
|
|
crtState = CrtInWord;
|
|
|
|
|
}
|
|
|
|
|
} else if (crtState < CrtQuoted) {
|
|
|
|
|
crtState = CrtInWord;
|
|
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
bslashes = 0;
|
|
|
|
|
rbslashes = 0;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
if (varPos == INT_MAX && !rbslashes)
|
|
|
|
|
break;
|
|
|
|
|
if (shellState == ShellEscaped)
|
|
|
|
|
shellState = ShellBasic;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
pos++;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
} else {
|
|
|
|
|
// !Windows
|
2017-02-22 15:09:35 +01:00
|
|
|
MxState state = {MxBasic, false};
|
2014-02-05 10:43:21 +01:00
|
|
|
QStack<MxState> sstack;
|
|
|
|
|
QStack<MxSave> ostack;
|
|
|
|
|
|
|
|
|
|
while (pos < str.length()) {
|
|
|
|
|
if (pos == varPos) {
|
|
|
|
|
// Our expansion rules trigger in any context
|
|
|
|
|
if (state.dquote) {
|
|
|
|
|
// We are within a double-quoted string. Escape relevant meta characters.
|
|
|
|
|
rsts.replace(QRegExp(QLatin1String("([$`\"\\\\])")), QLatin1String("\\\\1"));
|
|
|
|
|
} else if (state.current == MxSingleQuote) {
|
|
|
|
|
// We are within a single-quoted string. "Suspend" single-quoting and put a
|
|
|
|
|
// single escaped quote for each single quote inside the string.
|
|
|
|
|
rsts.replace(QLatin1Char('\''), QLatin1String("'\\''"));
|
|
|
|
|
} else if (rsts.isEmpty() || hasSpecialCharsUnix(rsts)) {
|
|
|
|
|
// String contains "quote-worthy" characters. Use single quoting - but
|
|
|
|
|
// that choice is arbitrary.
|
|
|
|
|
rsts.replace(QLatin1Char('\''), QLatin1String("'\\''"));
|
|
|
|
|
rsts.prepend(QLatin1Char('\''));
|
|
|
|
|
rsts.append(QLatin1Char('\''));
|
|
|
|
|
} // Else just use the string verbatim.
|
|
|
|
|
str.replace(pos, varLen, rsts);
|
|
|
|
|
pos += rsts.length();
|
|
|
|
|
varPos = pos;
|
|
|
|
|
if (!(varLen = mx->findMacro(str, &varPos, &rsts)))
|
2010-10-14 18:05:43 +02:00
|
|
|
break;
|
2014-02-05 10:43:21 +01:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
ushort cc = str.unicode()[pos].unicode();
|
|
|
|
|
if (state.current == MxSingleQuote) {
|
|
|
|
|
// Single quoted context - only the single quote has any special meaning.
|
|
|
|
|
if (cc == '\'')
|
|
|
|
|
state = sstack.pop();
|
|
|
|
|
} else if (cc == '\\') {
|
|
|
|
|
// In any other context, the backslash starts an escape.
|
|
|
|
|
pos += 2;
|
|
|
|
|
if (varPos < pos)
|
|
|
|
|
return false; // Backslash'd quoted expando would be Bad (TM).
|
|
|
|
|
continue;
|
|
|
|
|
} else if (cc == '$') {
|
|
|
|
|
cc = str.unicode()[++pos].unicode();
|
|
|
|
|
if (cc == '(') {
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
if (str.unicode()[pos + 1].unicode() == '(') {
|
|
|
|
|
// $(( starts a math expression. This may also be a $( ( in fact,
|
|
|
|
|
// so we push the current string and offset on a stack so we can retry.
|
2017-02-22 15:09:35 +01:00
|
|
|
MxSave sav = {str, pos + 2, varPos};
|
2014-02-05 10:43:21 +01:00
|
|
|
ostack.push(sav);
|
|
|
|
|
state.current = MxMath;
|
|
|
|
|
pos += 2;
|
2010-10-14 18:05:43 +02:00
|
|
|
continue;
|
2014-02-05 10:43:21 +01:00
|
|
|
} else {
|
|
|
|
|
// $( starts a command substitution. This actually "opens a new context"
|
|
|
|
|
// which overrides surrounding double quoting.
|
|
|
|
|
state.current = MxParen;
|
|
|
|
|
state.dquote = false;
|
|
|
|
|
}
|
|
|
|
|
} else if (cc == '{') {
|
|
|
|
|
// ${ starts a "braced" variable substitution.
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
state.current = MxSubst;
|
|
|
|
|
} // Else assume that a "bare" variable substitution has started
|
|
|
|
|
} else if (cc == '`') {
|
|
|
|
|
// Backticks are evil, as every shell interprets escapes within them differently,
|
|
|
|
|
// which is a danger for the quoting of our own expansions.
|
|
|
|
|
// So we just apply *our* rules (which match bash) and transform it into a POSIX
|
|
|
|
|
// command substitution which has clear semantics.
|
|
|
|
|
str.replace(pos, 1, QLatin1String("$( " )); // add space -> avoid creating $((
|
|
|
|
|
varPos += 2;
|
|
|
|
|
int pos2 = pos += 3;
|
|
|
|
|
forever {
|
|
|
|
|
if (pos2 >= str.length())
|
|
|
|
|
return false; // Syntax error - unterminated backtick expression.
|
|
|
|
|
cc = str.unicode()[pos2].unicode();
|
|
|
|
|
if (cc == '`')
|
|
|
|
|
break;
|
|
|
|
|
if (cc == '\\') {
|
|
|
|
|
cc = str.unicode()[++pos2].unicode();
|
|
|
|
|
if (cc == '$' || cc == '`' || cc == '\\' ||
|
|
|
|
|
(cc == '"' && state.dquote))
|
|
|
|
|
{
|
|
|
|
|
str.remove(pos2 - 1, 1);
|
|
|
|
|
if (varPos >= pos2)
|
|
|
|
|
varPos--;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
pos2++;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
str[pos2] = QLatin1Char(')');
|
2010-10-14 18:05:43 +02:00
|
|
|
sstack.push(state);
|
2014-02-05 10:43:21 +01:00
|
|
|
state.current = MxParen;
|
|
|
|
|
state.dquote = false;
|
|
|
|
|
continue;
|
|
|
|
|
} else if (state.current == MxDoubleQuote) {
|
|
|
|
|
// (Truly) double quoted context - only remaining special char is the closing quote.
|
|
|
|
|
if (cc == '"')
|
2010-10-14 18:05:43 +02:00
|
|
|
state = sstack.pop();
|
2014-02-05 10:43:21 +01:00
|
|
|
} else if (cc == '\'') {
|
|
|
|
|
// Start single quote if we are not in "inherited" double quoted context.
|
|
|
|
|
if (!state.dquote) {
|
2010-10-14 18:05:43 +02:00
|
|
|
sstack.push(state);
|
2014-02-05 10:43:21 +01:00
|
|
|
state.current = MxSingleQuote;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
} else if (cc == '"') {
|
|
|
|
|
// Same for double quoting.
|
|
|
|
|
if (!state.dquote) {
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
state.current = MxDoubleQuote;
|
|
|
|
|
state.dquote = true;
|
|
|
|
|
}
|
|
|
|
|
} else if (state.current == MxSubst) {
|
|
|
|
|
// "Braced" substitution context - only remaining special char is the closing brace.
|
|
|
|
|
if (cc == '}')
|
|
|
|
|
state = sstack.pop();
|
|
|
|
|
} else if (cc == ')') {
|
|
|
|
|
if (state.current == MxMath) {
|
|
|
|
|
if (str.unicode()[pos + 1].unicode() == ')') {
|
|
|
|
|
state = sstack.pop();
|
|
|
|
|
pos += 2;
|
|
|
|
|
} else {
|
|
|
|
|
// False hit: the $(( was a $( ( in fact.
|
|
|
|
|
// ash does not care (and will complain), but bash actually parses it.
|
|
|
|
|
varPos = ostack.top().varPos;
|
|
|
|
|
pos = ostack.top().pos;
|
|
|
|
|
str = ostack.top().str;
|
|
|
|
|
ostack.pop();
|
|
|
|
|
state.current = MxParen;
|
|
|
|
|
state.dquote = false;
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
} else if (state.current == MxParen) {
|
|
|
|
|
state = sstack.pop();
|
|
|
|
|
} else {
|
|
|
|
|
break; // Syntax error - excess closing parenthesis.
|
|
|
|
|
}
|
|
|
|
|
} else if (cc == '}') {
|
|
|
|
|
if (state.current == MxGroup)
|
|
|
|
|
state = sstack.pop();
|
|
|
|
|
else
|
|
|
|
|
break; // Syntax error - excess closing brace.
|
|
|
|
|
} else if (cc == '(') {
|
|
|
|
|
// Context-saving command grouping.
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
state.current = MxParen;
|
|
|
|
|
} else if (cc == '{') {
|
|
|
|
|
// Plain command grouping.
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
state.current = MxGroup;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
pos++;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
// FIXME? May complain if (!sstack.empty()), but we don't really care anyway.
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*cmd = str;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
QString QtcProcess::expandMacros(const QString &str, AbstractMacroExpander *mx, OsType osType)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
QString ret = str;
|
2014-02-05 10:43:21 +01:00
|
|
|
expandMacros(&ret, mx, osType);
|
2010-10-14 18:05:43 +02:00
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-10 23:39:29 +03:00
|
|
|
void QtcProcess::setupChildProcess()
|
|
|
|
|
{
|
|
|
|
|
#if defined Q_OS_UNIX
|
|
|
|
|
// nice value range is -20 to +19 where -20 is highest, 0 default and +19 is lowest
|
|
|
|
|
if (m_lowPriority) {
|
|
|
|
|
errno = 0;
|
|
|
|
|
if (::nice(5) == -1 && errno != 0)
|
|
|
|
|
qWarning("Failed to set nice value. Error: %d", errno);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
QProcess::setupChildProcess();
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-14 18:05:43 +02:00
|
|
|
bool QtcProcess::ArgIterator::next()
|
|
|
|
|
{
|
|
|
|
|
// We delay the setting of m_prev so we can still delete the last argument
|
|
|
|
|
// after we find that there are no more arguments. It's a bit of a hack ...
|
|
|
|
|
int prev = m_pos;
|
|
|
|
|
|
|
|
|
|
m_simple = true;
|
|
|
|
|
m_value.clear();
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
if (m_osType == OsTypeWindows) {
|
|
|
|
|
enum { // cmd.exe parsing state
|
|
|
|
|
ShellBasic, // initial state
|
|
|
|
|
ShellQuoted, // double-quoted state => *no* other meta chars are interpreted
|
|
|
|
|
ShellEscaped // circumflex-escaped state => next char is not interpreted
|
|
|
|
|
} shellState = ShellBasic;
|
|
|
|
|
enum { // CommandLineToArgv() parsing state and some more
|
|
|
|
|
CrtBasic, // initial state
|
|
|
|
|
CrtInWord, // in non-whitespace
|
|
|
|
|
CrtClosed, // previous char closed the double-quoting
|
|
|
|
|
CrtQuoted // double-quoted state => spaces don't split tokens
|
|
|
|
|
} crtState = CrtBasic;
|
|
|
|
|
enum { NoVar, NewVar, FullVar } varState = NoVar; // inside a potential env variable expansion
|
|
|
|
|
int bslashes = 0; // number of preceding backslashes
|
|
|
|
|
|
|
|
|
|
for (;; m_pos++) {
|
|
|
|
|
ushort cc = m_pos < m_str->length() ? m_str->unicode()[m_pos].unicode() : 0;
|
|
|
|
|
if (shellState == ShellBasic && cc == '^') {
|
|
|
|
|
varState = NoVar;
|
|
|
|
|
shellState = ShellEscaped;
|
|
|
|
|
} else if ((shellState == ShellBasic && isMetaCharWin(cc)) || !cc) { // A "bit" simplistic ...
|
|
|
|
|
// We ignore crtQuote state here. Whatever ...
|
|
|
|
|
doReturn:
|
|
|
|
|
if (m_simple)
|
|
|
|
|
while (--bslashes >= 0)
|
|
|
|
|
m_value += QLatin1Char('\\');
|
|
|
|
|
else
|
|
|
|
|
m_value.clear();
|
2010-10-14 18:05:43 +02:00
|
|
|
if (crtState != CrtBasic) {
|
2014-02-05 10:43:21 +01:00
|
|
|
m_prev = prev;
|
|
|
|
|
return true;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
return false;
|
2010-10-14 18:05:43 +02:00
|
|
|
} else {
|
2014-02-05 10:43:21 +01:00
|
|
|
if (crtState != CrtQuoted && (cc == ' ' || cc == '\t')) {
|
|
|
|
|
if (crtState != CrtBasic) {
|
|
|
|
|
// We'll lose shellQuote state here. Whatever ...
|
|
|
|
|
goto doReturn;
|
|
|
|
|
}
|
2010-10-14 18:05:43 +02:00
|
|
|
} else {
|
2014-02-05 10:43:21 +01:00
|
|
|
if (cc == '\\') {
|
|
|
|
|
bslashes++;
|
|
|
|
|
if (crtState != CrtQuoted)
|
2010-10-14 18:05:43 +02:00
|
|
|
crtState = CrtInWord;
|
2014-02-05 10:43:21 +01:00
|
|
|
varState = NoVar;
|
2010-10-14 18:05:43 +02:00
|
|
|
} else {
|
2014-02-05 10:43:21 +01:00
|
|
|
if (cc == '"') {
|
|
|
|
|
varState = NoVar;
|
|
|
|
|
if (shellState != ShellEscaped)
|
|
|
|
|
shellState = (shellState == ShellQuoted) ? ShellBasic : ShellQuoted;
|
|
|
|
|
int obslashes = bslashes;
|
|
|
|
|
bslashes >>= 1;
|
|
|
|
|
if (!(obslashes & 1)) {
|
|
|
|
|
// Even number of backslashes, so the quote is not escaped.
|
|
|
|
|
switch (crtState) {
|
|
|
|
|
case CrtQuoted:
|
|
|
|
|
// Closing quote
|
|
|
|
|
crtState = CrtClosed;
|
|
|
|
|
continue;
|
|
|
|
|
case CrtClosed:
|
|
|
|
|
// Two consecutive quotes make a literal quote - and
|
|
|
|
|
// still close quoting. See quoteArg().
|
|
|
|
|
crtState = CrtInWord;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
// Opening quote
|
|
|
|
|
crtState = CrtQuoted;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
} else if (crtState != CrtQuoted) {
|
|
|
|
|
crtState = CrtInWord;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
} else {
|
|
|
|
|
if (cc == '%') {
|
|
|
|
|
if (varState == FullVar) {
|
|
|
|
|
m_simple = false;
|
|
|
|
|
varState = NoVar;
|
|
|
|
|
} else {
|
|
|
|
|
varState = NewVar;
|
|
|
|
|
}
|
|
|
|
|
} else if (varState != NoVar) {
|
|
|
|
|
// This check doesn't really reflect cmd reality, but it is an
|
|
|
|
|
// approximation of what would be sane.
|
|
|
|
|
varState = (cc == '_' || cc == '-' || cc == '.'
|
|
|
|
|
|| QChar(cc).isLetterOrNumber()) ? FullVar : NoVar;
|
2010-10-14 18:05:43 +02:00
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
}
|
|
|
|
|
if (crtState != CrtQuoted)
|
|
|
|
|
crtState = CrtInWord;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
for (; bslashes > 0; bslashes--)
|
|
|
|
|
m_value += QLatin1Char('\\');
|
|
|
|
|
m_value += QChar(cc);
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
if (shellState == ShellEscaped)
|
|
|
|
|
shellState = ShellBasic;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
} else {
|
2017-02-22 15:09:35 +01:00
|
|
|
MxState state = {MxBasic, false};
|
2014-02-05 10:43:21 +01:00
|
|
|
QStack<MxState> sstack;
|
|
|
|
|
QStack<int> ostack;
|
|
|
|
|
bool hadWord = false;
|
|
|
|
|
|
|
|
|
|
for (; m_pos < m_str->length(); m_pos++) {
|
|
|
|
|
ushort cc = m_str->unicode()[m_pos].unicode();
|
|
|
|
|
if (state.current == MxSingleQuote) {
|
|
|
|
|
if (cc == '\'') {
|
|
|
|
|
state = sstack.pop();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
} else if (cc == '\\') {
|
|
|
|
|
if (++m_pos >= m_str->length())
|
|
|
|
|
break;
|
|
|
|
|
cc = m_str->unicode()[m_pos].unicode();
|
|
|
|
|
if (state.dquote && cc != '"' && cc != '\\' && cc != '$' && cc != '`')
|
|
|
|
|
m_value += QLatin1Char('\\');
|
|
|
|
|
} else if (cc == '$') {
|
2010-10-14 18:05:43 +02:00
|
|
|
if (++m_pos >= m_str->length())
|
|
|
|
|
break;
|
2014-02-05 10:43:21 +01:00
|
|
|
cc = m_str->unicode()[m_pos].unicode();
|
|
|
|
|
if (cc == '(') {
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
if (++m_pos >= m_str->length())
|
|
|
|
|
break;
|
|
|
|
|
if (m_str->unicode()[m_pos].unicode() == '(') {
|
|
|
|
|
ostack.push(m_pos);
|
|
|
|
|
state.current = MxMath;
|
|
|
|
|
} else {
|
|
|
|
|
state.dquote = false;
|
|
|
|
|
state.current = MxParen;
|
|
|
|
|
// m_pos too far by one now - whatever.
|
|
|
|
|
}
|
|
|
|
|
} else if (cc == '{') {
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
state.current = MxSubst;
|
2010-10-14 18:05:43 +02:00
|
|
|
} else {
|
|
|
|
|
// m_pos too far by one now - whatever.
|
|
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
m_simple = false;
|
2010-10-14 18:05:43 +02:00
|
|
|
hadWord = true;
|
|
|
|
|
continue;
|
2014-02-05 10:43:21 +01:00
|
|
|
} else if (cc == '`') {
|
|
|
|
|
forever {
|
|
|
|
|
if (++m_pos >= m_str->length()) {
|
|
|
|
|
m_simple = false;
|
|
|
|
|
m_prev = prev;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
cc = m_str->unicode()[m_pos].unicode();
|
|
|
|
|
if (cc == '`')
|
|
|
|
|
break;
|
|
|
|
|
if (cc == '\\')
|
|
|
|
|
m_pos++; // m_pos may be too far by one now - whatever.
|
|
|
|
|
}
|
|
|
|
|
m_simple = false;
|
2010-10-14 18:05:43 +02:00
|
|
|
hadWord = true;
|
|
|
|
|
continue;
|
2014-02-05 10:43:21 +01:00
|
|
|
} else if (state.current == MxDoubleQuote) {
|
|
|
|
|
if (cc == '"') {
|
2010-10-14 18:05:43 +02:00
|
|
|
state = sstack.pop();
|
2014-02-05 10:43:21 +01:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
} else if (cc == '\'') {
|
|
|
|
|
if (!state.dquote) {
|
2010-10-14 18:05:43 +02:00
|
|
|
sstack.push(state);
|
2014-02-05 10:43:21 +01:00
|
|
|
state.current = MxSingleQuote;
|
|
|
|
|
hadWord = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
} else if (cc == '"') {
|
|
|
|
|
if (!state.dquote) {
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
state.dquote = true;
|
|
|
|
|
state.current = MxDoubleQuote;
|
|
|
|
|
hadWord = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
} else if (state.current == MxSubst) {
|
|
|
|
|
if (cc == '}')
|
|
|
|
|
state = sstack.pop();
|
|
|
|
|
continue; // Not simple anyway
|
|
|
|
|
} else if (cc == ')') {
|
|
|
|
|
if (state.current == MxMath) {
|
|
|
|
|
if (++m_pos >= m_str->length())
|
|
|
|
|
break;
|
|
|
|
|
if (m_str->unicode()[m_pos].unicode() == ')') {
|
|
|
|
|
ostack.pop();
|
|
|
|
|
state = sstack.pop();
|
|
|
|
|
} else {
|
|
|
|
|
// false hit: the $(( was a $( ( in fact.
|
|
|
|
|
// ash does not care, but bash does.
|
|
|
|
|
m_pos = ostack.pop();
|
|
|
|
|
state.current = MxParen;
|
|
|
|
|
state.dquote = false;
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
} else if (state.current == MxParen) {
|
|
|
|
|
state = sstack.pop();
|
|
|
|
|
continue;
|
|
|
|
|
} else {
|
|
|
|
|
break;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
#if 0 // MxGroup is impossible, see below.
|
2014-02-05 10:43:21 +01:00
|
|
|
} else if (cc == '}') {
|
|
|
|
|
if (state.current == MxGroup) {
|
|
|
|
|
state = sstack.pop();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2010-10-14 18:05:43 +02:00
|
|
|
#endif
|
2014-02-05 10:43:21 +01:00
|
|
|
} else if (cc == '(') {
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
state.current = MxParen;
|
|
|
|
|
m_simple = false;
|
|
|
|
|
hadWord = true;
|
2015-03-07 20:22:32 +02:00
|
|
|
continue;
|
2010-10-14 18:05:43 +02:00
|
|
|
#if 0 // Should match only at the beginning of a command, which we never have currently.
|
2014-02-05 10:43:21 +01:00
|
|
|
} else if (cc == '{') {
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
state.current = MxGroup;
|
|
|
|
|
m_simple = false;
|
|
|
|
|
hadWord = true;
|
2010-10-14 18:05:43 +02:00
|
|
|
continue;
|
2014-02-05 10:43:21 +01:00
|
|
|
#endif
|
|
|
|
|
} else if (cc == '<' || cc == '>' || cc == '&' || cc == '|' || cc == ';') {
|
|
|
|
|
if (sstack.isEmpty())
|
|
|
|
|
break;
|
|
|
|
|
} else if (cc == ' ' || cc == '\t') {
|
|
|
|
|
if (!hadWord)
|
|
|
|
|
continue;
|
|
|
|
|
if (sstack.isEmpty())
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
m_value += QChar(cc);
|
|
|
|
|
hadWord = true;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
// TODO: Possibly complain here if (!sstack.empty())
|
|
|
|
|
if (!m_simple)
|
|
|
|
|
m_value.clear();
|
|
|
|
|
if (hadWord) {
|
|
|
|
|
m_prev = prev;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::ArgIterator::deleteArg()
|
|
|
|
|
{
|
|
|
|
|
if (!m_prev)
|
|
|
|
|
while (m_pos < m_str->length() && m_str->at(m_pos).isSpace())
|
|
|
|
|
m_pos++;
|
|
|
|
|
m_str->remove(m_prev, m_pos - m_prev);
|
|
|
|
|
m_pos = m_prev;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::ArgIterator::appendArg(const QString &str)
|
|
|
|
|
{
|
|
|
|
|
const QString qstr = quoteArg(str);
|
|
|
|
|
if (!m_pos)
|
|
|
|
|
m_str->insert(0, qstr + QLatin1Char(' '));
|
|
|
|
|
else
|
|
|
|
|
m_str->insert(m_pos, QLatin1Char(' ') + qstr);
|
|
|
|
|
m_pos += qstr.length() + 1;
|
|
|
|
|
}
|
2013-07-12 09:00:12 +02:00
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
QtcProcess::Arguments QtcProcess::Arguments::createWindowsArgs(const QString &args)
|
|
|
|
|
{
|
|
|
|
|
Arguments result;
|
|
|
|
|
result.m_windowsArgs = args;
|
|
|
|
|
result.m_isWindows = true;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QtcProcess::Arguments QtcProcess::Arguments::createUnixArgs(const QStringList &args)
|
|
|
|
|
{
|
|
|
|
|
Arguments result;
|
|
|
|
|
result.m_unixArgs = args;
|
|
|
|
|
result.m_isWindows = false;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString QtcProcess::Arguments::toWindowsArgs() const
|
|
|
|
|
{
|
|
|
|
|
QTC_CHECK(m_isWindows);
|
|
|
|
|
return m_windowsArgs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QStringList QtcProcess::Arguments::toUnixArgs() const
|
|
|
|
|
{
|
|
|
|
|
QTC_CHECK(!m_isWindows);
|
|
|
|
|
return m_unixArgs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString QtcProcess::Arguments::toString() const
|
|
|
|
|
{
|
|
|
|
|
if (m_isWindows)
|
|
|
|
|
return m_windowsArgs;
|
|
|
|
|
else
|
|
|
|
|
return QtcProcess::joinArgs(m_unixArgs, OsTypeLinux);
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-12 09:00:12 +02:00
|
|
|
} // namespace Utils
|