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 | |
17 | static 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 | |
31 | inline static bool isQuoteMeta(QChar cUnicode) |
32 | { |
33 | char c = cUnicode.toLatin1(); |
34 | return c == '\\' || c == '\'' || c == '"' || c == '$'; |
35 | } |
36 | |
37 | inline 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 | |
46 | QStringList 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 | |
268 | okret: |
269 | if (err) { |
270 | *err = NoError; |
271 | } |
272 | return ret; |
273 | |
274 | quoteerr: |
275 | if (err) { |
276 | *err = BadQuoting; |
277 | } |
278 | return QStringList(); |
279 | |
280 | metaerr: |
281 | if (err) { |
282 | *err = FoundMeta; |
283 | } |
284 | return QStringList(); |
285 | } |
286 | |
287 | inline 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 | |
295 | QString 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 | |