This file is part of the KDE libraries
SPDX-FileCopyrightText: 2007 Bernhard Loos <nhuh.put@web.de>
SPDX-FileCopyrightText: 2007, 2008 Oswald Buddenhagen <ossi@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kshell.h"
#include "kshell_p.h"
#include <QDir>
#include <QRegularExpression>
#include <QString>
#include <QStringList>
* A short introduction into cmd semantics:
* - Variable expansion is done first, without regard to *any* escaping -
* if something looks like an existing variable, it is replaced.
* - Then follows regular tokenization by the shell. &, &&, | and || are
* command delimiters. ( and ) are command grouping operators; they are
* recognized only a the start resp. end of a command; mismatched )s are
* an error if any (s are present. <, > are just like under UNIX - they can
* appear *anywhere* in a command, perform their function and are cut out.
* @ at the start of a command is eaten (local echo off - no function as
* far as cmd /c is concerned). : at the start of a command declares a label,
* which effectively means the remainder of the line is a comment - note that
* command separators are not recognized past that point.
* ^ is the escape char for everything including itself.
* cmd ignores *all* special chars between double quotes, so there is no
* way to escape the closing quote. Note that the quotes are *not* removed
* from the resulting command line.
* - Then follows delayed variable expansion if it is enabled and at least
* one exclamation mark is present. This involves another layer of ^
* escaping, regardless of quotes. (Win2k+)
* - Then follows argument splitting as described in
* http://msdn2.microsoft.com/en-us/library/ms880421.aspx .
* Note that this is done by the called application and therefore might
* be subject to completely different semantics, in fact.
*/
inline static bool isMetaChar(ushort c)
{
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)));
}
inline static bool isSpecialChar(ushort c)
{
static const uchar iqm[] = {0xff, 0xff, 0xff, 0xff, 0x41, 0x13, 0x00, 0x78, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10};
return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
}
inline static bool isWhiteSpace(ushort c)
{
return c == ' ' || c == '\t';
}
QStringList KShell::splitArgs(const QString &_args, Options flags, Errors *err)
{
QString args(_args);
QStringList ret;
const QLatin1Char bs('\\'), dq('\"');
if (flags & AbortOnMeta) {
args.remove(PERCENT_ESCAPE);
if (args.indexOf(QLatin1Char('%')) >= 0) {
if (err) {
*err = FoundMeta;
}
return QStringList();
}
args = _args;
args.replace(PERCENT_ESCAPE, QLatin1String("%"));
if (!args.isEmpty() && args[0].unicode() == '@') {
args.remove(0, 1);
}
for (int p = 0; p < args.length(); p++) {
ushort c = args[p].unicode();
if (c == '^') {
args.remove(p, 1);
} else if (c == '"') {
while (++p < args.length() && args[p].unicode() != '"')
;
} else if (isMetaChar(c)) {
if (err) {
*err = FoundMeta;
}
return QStringList();
}
}
}
if (err) {
*err = NoError;
}
int p = 0;
const int length = args.length();
for (;;) {
while (p < length && isWhiteSpace(args[p].unicode())) {
++p;
}
if (p == length) {
return ret;
}
QString arg;
bool inquote = false;
for (;;) {
bool copy = true;
int bslashes = 0;
while (p < length && args[p] == bs) {
++p;
++bslashes;
}
if (p < length && args[p] == dq) {
if (bslashes % 2 == 0) {
if (inquote) {
if (p + 1 < length && args[p + 1] == dq) {
++p;
} else {
copy = false;
inquote = !inquote;
}
} else {
copy = false;
inquote = !inquote;
}
}
bslashes /= 2;
}
while (--bslashes >= 0) {
arg.append(bs);
}
if (p == length || (!inquote && isWhiteSpace(args[p].unicode()))) {
ret.append(arg);
if (inquote) {
if (err) {
*err = BadQuoting;
}
return QStringList();
}
break;
}
if (copy) {
arg.append(args[p]);
}
++p;
}
}
}
QString KShell::quoteArgInternal(const QString &arg, bool _inquote)
{
const QLatin1Char bs('\\'), dq('\"');
QString ret;
bool inquote = _inquote;
int bslashes = 0;
for (int p = 0; p < arg.length(); p++) {
if (arg[p] == bs) {
bslashes++;
} else if (arg[p] == dq) {
if (inquote) {
ret.append(dq);
inquote = false;
}
for (; bslashes; bslashes--) {
ret.append(QLatin1String("\\\\"));
}
ret.append(QLatin1String("\\^\""));
} else {
if (!inquote) {
ret.append(dq);
inquote = true;
}
for (; bslashes; bslashes--) {
ret.append(bs);
}
ret.append(arg[p]);
}
}
ret.replace(QLatin1Char('%'), PERCENT_ESCAPE);
if (bslashes) {
if (!inquote && !_inquote) {
ret.append(dq);
}
for (; bslashes; bslashes--) {
ret.append(QLatin1String("\\\\"));
}
ret.append(dq);
if (inquote && _inquote) {
ret.append(dq);
}
} else if (inquote != _inquote) {
ret.append(dq);
}
return ret;
}
QString KShell::quoteArg(const QString &arg)
{
if (arg.isEmpty()) {
return QStringLiteral("\"\"");
}
if (arg.endsWith(QLatin1Char('\\'))) {
return quoteArgInternal(arg, false);
}
for (int x = arg.length() - 1; x >= 0; --x)
if (isSpecialChar(arg[x].unicode())) {
return quoteArgInternal(arg, false);
}
QString ret(arg);
ret.replace(QRegularExpression(QStringLiteral("(\\\\*)\"")), QStringLiteral("\\1\\1\\^\""));
ret.replace(QLatin1Char('%'), PERCENT_ESCAPE);
return ret;
}