This file is part of the KDE libraries
SPDX-FileCopyrightText: 2003, 2007 Oswald Buddenhagen <ossi@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kshell.h"
#include "kshell_p.h"
#include <kuser.h>
#include <QChar>
#include <QStringList>
static int fromHex(QChar cUnicode)
{
char c = cUnicode.toLatin1();
if (c >= '0' && c <= '9') {
return c - '0';
} else if (c >= 'A' && c <= 'F') {
return c - 'A' + 10;
} else if (c >= 'a' && c <= 'f') {
return c - 'a' + 10;
}
return -1;
}
inline static bool isQuoteMeta(QChar cUnicode)
{
char c = cUnicode.toLatin1();
return c == '\\' || c == '\'' || c == '"' || c == '$';
}
inline static bool isMeta(QChar cUnicode)
{
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)));
}
QStringList KShell::splitArgs(const QString &args, Options flags, Errors *err)
{
QStringList ret;
bool firstword = flags & AbortOnMeta;
for (int pos = 0;;) {
QChar c;
do {
if (pos >= args.length()) {
goto okret;
}
c = args.unicode()[pos++];
} while (c == QLatin1Char(' '));
QString cret;
if ((flags & TildeExpand) && c == QLatin1Char('~')) {
int opos = pos;
for (;; pos++) {
if (pos >= args.length()) {
break;
}
c = args.unicode()[pos];
if (c == QLatin1Char('/') || c == QLatin1Char(' ')) {
break;
}
if (isQuoteMeta(c)) {
pos = opos;
c = QLatin1Char('~');
goto notilde;
}
if ((flags & AbortOnMeta) && isMeta(c)) {
goto metaerr;
}
}
QString ccret = homeDir(args.mid(opos, pos - opos));
if (ccret.isEmpty()) {
pos = opos;
c = QLatin1Char('~');
goto notilde;
}
if (pos >= args.length()) {
ret += ccret;
goto okret;
}
pos++;
if (c == QLatin1Char(' ')) {
ret += ccret;
firstword = false;
continue;
}
cret = ccret;
}
if (firstword) {
if (c == QLatin1Char('_')
|| (c >= QLatin1Char('A') && c <= QLatin1Char('Z'))
|| (c >= QLatin1Char('a') && c <= QLatin1Char('z'))) {
int pos2 = pos;
QChar cc;
do {
if (pos2 >= args.length()) {
ret += args.mid(pos - 1);
goto okret;
}
cc = args.unicode()[pos2++];
} while (cc == QLatin1Char('_')
|| (cc >= QLatin1Char('A') && cc <= QLatin1Char('Z'))
|| (cc >= QLatin1Char('a') && cc <= QLatin1Char('z'))
|| (cc >= QLatin1Char('0') && cc <= QLatin1Char('9')));
if (cc == QLatin1Char('=')) {
goto metaerr;
}
}
}
notilde:
do {
if (c == QLatin1Char('\'')) {
int spos = pos;
do {
if (pos >= args.length()) {
goto quoteerr;
}
c = args.unicode()[pos++];
} while (c != QLatin1Char('\''));
cret += QStringView(args).mid(spos, pos - spos - 1);
} else if (c == QLatin1Char('"')) {
for (;;) {
if (pos >= args.length()) {
goto quoteerr;
}
c = args.unicode()[pos++];
if (c == QLatin1Char('"')) {
break;
}
if (c == QLatin1Char('\\')) {
if (pos >= args.length()) {
goto quoteerr;
}
c = args.unicode()[pos++];
if (c != QLatin1Char('"')
&& c != QLatin1Char('\\')
&& !((flags & AbortOnMeta) && (c == QLatin1Char('$') || c == QLatin1Char('`')))) {
cret += QLatin1Char('\\');
}
} else if ((flags & AbortOnMeta) && (c == QLatin1Char('$') || c == QLatin1Char('`'))) {
goto metaerr;
}
cret += c;
}
} else if (c == QLatin1Char('$') && pos < args.length() && args.unicode()[pos] == QLatin1Char('\'')) {
pos++;
for (;;) {
if (pos >= args.length()) {
goto quoteerr;
}
c = args.unicode()[pos++];
if (c == QLatin1Char('\'')) {
break;
}
if (c == QLatin1Char('\\')) {
if (pos >= args.length()) {
goto quoteerr;
}
c = args.unicode()[pos++];
switch (c.toLatin1()) {
case 'a':
cret += QLatin1Char('\a');
break;
case 'b':
cret += QLatin1Char('\b');
break;
case 'e':
cret += QLatin1Char('\033');
break;
case 'f':
cret += QLatin1Char('\f');
break;
case 'n':
cret += QLatin1Char('\n');
break;
case 'r':
cret += QLatin1Char('\r');
break;
case 't':
cret += QLatin1Char('\t');
break;
case '\\':
cret += QLatin1Char('\\');
break;
case '\'':
cret += QLatin1Char('\'');
break;
case 'c':
if (pos >= args.length()) {
goto quoteerr;
}
cret += QChar::fromLatin1(args.unicode()[pos++].toLatin1() & 31);
break;
case 'x': {
if (pos >= args.length()) {
goto quoteerr;
}
int hv = fromHex(args.unicode()[pos++]);
if (hv < 0) {
goto quoteerr;
}
if (pos < args.length()) {
int hhv = fromHex(args.unicode()[pos]);
if (hhv > 0) {
hv = hv * 16 + hhv;
pos++;
}
cret += QChar(hv);
}
break;
}
default:
if (c.toLatin1() >= '0' && c.toLatin1() <= '7') {
char cAscii = c.toLatin1();
int hv = cAscii - '0';
for (int i = 0; i < 2; i++) {
if (pos >= args.length()) {
break;
}
c = args.unicode()[pos];
if (c.toLatin1() < '0' || c.toLatin1() > '7') {
break;
}
hv = hv * 8 + (c.toLatin1() - '0');
pos++;
}
cret += QChar(hv);
} else {
cret += QLatin1Char('\\');
cret += c;
}
break;
}
} else {
cret += c;
}
}
} else {
if (c == QLatin1Char('\\')) {
if (pos >= args.length()) {
goto quoteerr;
}
c = args.unicode()[pos++];
} else if ((flags & AbortOnMeta) && isMeta(c)) {
goto metaerr;
}
cret += c;
}
if (pos >= args.length()) {
break;
}
c = args.unicode()[pos++];
} while (c != QLatin1Char(' '));
ret += cret;
firstword = false;
}
okret:
if (err) {
*err = NoError;
}
return ret;
quoteerr:
if (err) {
*err = BadQuoting;
}
return QStringList();
metaerr:
if (err) {
*err = FoundMeta;
}
return QStringList();
}
inline static bool isSpecial(QChar cUnicode)
{
static const uchar iqm[] = {0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78};
uint c = cUnicode.unicode();
return ((c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))));
}
QString KShell::quoteArg(const QString &arg)
{
if (!arg.length()) {
return QStringLiteral("''");
}
for (int i = 0; i < arg.length(); i++) {
if (isSpecial(arg.unicode()[i])) {
QChar q(QLatin1Char('\''));
return q + QString(arg).replace(q, QLatin1String("'\\''")) + q;
}
}
return arg;
}