1/*
2 This file is part of the KDE libraries
3
4 SPDX-FileCopyrightText: 2003, 2007 Oswald Buddenhagen <ossi@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "kshell.h"
10#include "kshell_p.h"
11
12#include <kuser.h>
13
14#include <QChar>
15#include <QStringList>
16
17static int fromHex(QChar cUnicode)
18{
19 char c = cUnicode.toLatin1();
20
21 if (c >= '0' && c <= '9') {
22 return c - '0';
23 } else if (c >= 'A' && c <= 'F') {
24 return c - 'A' + 10;
25 } else if (c >= 'a' && c <= 'f') {
26 return c - 'a' + 10;
27 }
28 return -1;
29}
30
31inline static bool isQuoteMeta(QChar cUnicode)
32{
33 char c = cUnicode.toLatin1();
34 return c == '\\' || c == '\'' || c == '"' || c == '$';
35}
36
37inline static bool isMeta(QChar cUnicode)
38{
39 static const uchar iqm[] = {0x00, 0x00, 0x00, 0x00, 0xdc, 0x07, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x38}; // \'"$`<>|;&(){}*?#[]
40
41 uint c = cUnicode.unicode();
42
43 return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
44}
45
46QStringList KShell::splitArgs(const QString &args, Options flags, Errors *err)
47{
48 QStringList ret;
49 bool firstword = flags & AbortOnMeta;
50
51 for (int pos = 0;;) {
52 QChar c;
53 do {
54 if (pos >= args.length()) {
55 goto okret;
56 }
57 c = args.unicode()[pos++];
58 } while (c == QLatin1Char(' '));
59 QString cret;
60 if ((flags & TildeExpand) && c == QLatin1Char('~')) {
61 int opos = pos;
62 for (;; pos++) {
63 if (pos >= args.length()) {
64 break;
65 }
66 c = args.unicode()[pos];
67 if (c == QLatin1Char('/') || c == QLatin1Char(' ')) {
68 break;
69 }
70 if (isQuoteMeta(cUnicode: c)) {
71 pos = opos;
72 c = QLatin1Char('~');
73 goto notilde;
74 }
75 if ((flags & AbortOnMeta) && isMeta(cUnicode: c)) {
76 goto metaerr;
77 }
78 }
79 QString ccret = homeDir(user: args.mid(position: opos, n: pos - opos));
80 if (ccret.isEmpty()) {
81 pos = opos;
82 c = QLatin1Char('~');
83 goto notilde;
84 }
85 if (pos >= args.length()) {
86 ret += ccret;
87 goto okret;
88 }
89 pos++;
90 if (c == QLatin1Char(' ')) {
91 ret += ccret;
92 firstword = false;
93 continue;
94 }
95 cret = ccret;
96 }
97 // before the notilde label, as a tilde does not match anyway
98 if (firstword) {
99 if (c == QLatin1Char('_') //
100 || (c >= QLatin1Char('A') && c <= QLatin1Char('Z')) //
101 || (c >= QLatin1Char('a') && c <= QLatin1Char('z'))) {
102 int pos2 = pos;
103 QChar cc;
104 do {
105 if (pos2 >= args.length()) {
106 // Exactly one word
107 ret += args.mid(position: pos - 1);
108 goto okret;
109 }
110 cc = args.unicode()[pos2++];
111 } while (cc == QLatin1Char('_') /* clang-format off */
112 || (cc >= QLatin1Char('A') && cc <= QLatin1Char('Z'))
113 || (cc >= QLatin1Char('a') && cc <= QLatin1Char('z'))
114 || (cc >= QLatin1Char('0') && cc <= QLatin1Char('9'))); /* clang-format on */
115 if (cc == QLatin1Char('=')) {
116 goto metaerr;
117 }
118 }
119 }
120 notilde:
121 do {
122 if (c == QLatin1Char('\'')) {
123 int spos = pos;
124 do {
125 if (pos >= args.length()) {
126 goto quoteerr;
127 }
128 c = args.unicode()[pos++];
129 } while (c != QLatin1Char('\''));
130 cret += QStringView(args).mid(pos: spos, n: pos - spos - 1);
131 } else if (c == QLatin1Char('"')) {
132 for (;;) {
133 if (pos >= args.length()) {
134 goto quoteerr;
135 }
136 c = args.unicode()[pos++];
137 if (c == QLatin1Char('"')) {
138 break;
139 }
140 if (c == QLatin1Char('\\')) {
141 if (pos >= args.length()) {
142 goto quoteerr;
143 }
144 c = args.unicode()[pos++];
145 if (c != QLatin1Char('"') //
146 && c != QLatin1Char('\\') //
147 && !((flags & AbortOnMeta) && (c == QLatin1Char('$') || c == QLatin1Char('`')))) {
148 cret += QLatin1Char('\\');
149 }
150 } else if ((flags & AbortOnMeta) && (c == QLatin1Char('$') || c == QLatin1Char('`'))) {
151 goto metaerr;
152 }
153 cret += c;
154 }
155 } else if (c == QLatin1Char('$') && pos < args.length() && args.unicode()[pos] == QLatin1Char('\'')) {
156 pos++;
157 for (;;) {
158 if (pos >= args.length()) {
159 goto quoteerr;
160 }
161 c = args.unicode()[pos++];
162 if (c == QLatin1Char('\'')) {
163 break;
164 }
165 if (c == QLatin1Char('\\')) {
166 if (pos >= args.length()) {
167 goto quoteerr;
168 }
169 c = args.unicode()[pos++];
170 switch (c.toLatin1()) {
171 case 'a':
172 cret += QLatin1Char('\a');
173 break;
174 case 'b':
175 cret += QLatin1Char('\b');
176 break;
177 case 'e':
178 cret += QLatin1Char('\033');
179 break;
180 case 'f':
181 cret += QLatin1Char('\f');
182 break;
183 case 'n':
184 cret += QLatin1Char('\n');
185 break;
186 case 'r':
187 cret += QLatin1Char('\r');
188 break;
189 case 't':
190 cret += QLatin1Char('\t');
191 break;
192 case '\\':
193 cret += QLatin1Char('\\');
194 break;
195 case '\'':
196 cret += QLatin1Char('\'');
197 break;
198 case 'c':
199 if (pos >= args.length()) {
200 goto quoteerr;
201 }
202 cret += QChar::fromLatin1(c: args.unicode()[pos++].toLatin1() & 31);
203 break;
204 case 'x': {
205 if (pos >= args.length()) {
206 goto quoteerr;
207 }
208 int hv = fromHex(cUnicode: args.unicode()[pos++]);
209 if (hv < 0) {
210 goto quoteerr;
211 }
212 if (pos < args.length()) {
213 int hhv = fromHex(cUnicode: args.unicode()[pos]);
214 if (hhv > 0) {
215 hv = hv * 16 + hhv;
216 pos++;
217 }
218 cret += QChar(hv);
219 }
220 break;
221 }
222 default:
223 if (c.toLatin1() >= '0' && c.toLatin1() <= '7') {
224 char cAscii = c.toLatin1();
225 int hv = cAscii - '0';
226 for (int i = 0; i < 2; i++) {
227 if (pos >= args.length()) {
228 break;
229 }
230 c = args.unicode()[pos];
231 if (c.toLatin1() < '0' || c.toLatin1() > '7') {
232 break;
233 }
234 hv = hv * 8 + (c.toLatin1() - '0');
235 pos++;
236 }
237 cret += QChar(hv);
238 } else {
239 cret += QLatin1Char('\\');
240 cret += c;
241 }
242 break;
243 }
244 } else {
245 cret += c;
246 }
247 }
248 } else {
249 if (c == QLatin1Char('\\')) {
250 if (pos >= args.length()) {
251 goto quoteerr;
252 }
253 c = args.unicode()[pos++];
254 } else if ((flags & AbortOnMeta) && isMeta(cUnicode: c)) {
255 goto metaerr;
256 }
257 cret += c;
258 }
259 if (pos >= args.length()) {
260 break;
261 }
262 c = args.unicode()[pos++];
263 } while (c != QLatin1Char(' '));
264 ret += cret;
265 firstword = false;
266 }
267
268okret:
269 if (err) {
270 *err = NoError;
271 }
272 return ret;
273
274quoteerr:
275 if (err) {
276 *err = BadQuoting;
277 }
278 return QStringList();
279
280metaerr:
281 if (err) {
282 *err = FoundMeta;
283 }
284 return QStringList();
285}
286
287inline static bool isSpecial(QChar cUnicode)
288{
289 static const uchar iqm[] = {0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78}; // 0-32 \'"$`<>|;&(){}*?#!~[]
290
291 uint c = cUnicode.unicode();
292 return ((c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))));
293}
294
295QString KShell::quoteArg(const QString &arg)
296{
297 if (!arg.length()) {
298 return QStringLiteral("''");
299 }
300 for (int i = 0; i < arg.length(); i++) {
301 if (isSpecial(cUnicode: arg.unicode()[i])) {
302 QChar q(QLatin1Char('\''));
303 return q + QString(arg).replace(c: q, after: QLatin1String("'\\''")) + q;
304 }
305 }
306 return arg;
307}
308

source code of kcoreaddons/src/lib/util/kshell_unix.cpp