KCoreAddons

kshell_win.cpp
1/*
2 This file is part of the KDE libraries
3
4 SPDX-FileCopyrightText: 2007 Bernhard Loos <nhuh.put@web.de>
5 SPDX-FileCopyrightText: 2007, 2008 Oswald Buddenhagen <ossi@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "kshell.h"
11#include "kshell_p.h"
12
13#include <QDir>
14#include <QRegularExpression>
15#include <QString>
16#include <QStringList>
17
18/*
19 * A short introduction into cmd semantics:
20 * - Variable expansion is done first, without regard to *any* escaping -
21 * if something looks like an existing variable, it is replaced.
22 * - Then follows regular tokenization by the shell. &, &&, | and || are
23 * command delimiters. ( and ) are command grouping operators; they are
24 * recognized only a the start resp. end of a command; mismatched )s are
25 * an error if any (s are present. <, > are just like under UNIX - they can
26 * appear *anywhere* in a command, perform their function and are cut out.
27 * @ at the start of a command is eaten (local echo off - no function as
28 * far as cmd /c is concerned). : at the start of a command declares a label,
29 * which effectively means the remainder of the line is a comment - note that
30 * command separators are not recognized past that point.
31 * ^ is the escape char for everything including itself.
32 * cmd ignores *all* special chars between double quotes, so there is no
33 * way to escape the closing quote. Note that the quotes are *not* removed
34 * from the resulting command line.
35 * - Then follows delayed variable expansion if it is enabled and at least
36 * one exclamation mark is present. This involves another layer of ^
37 * escaping, regardless of quotes. (Win2k+)
38 * - Then follows argument splitting as described in
39 * http://msdn2.microsoft.com/en-us/library/ms880421.aspx .
40 * Note that this is done by the called application and therefore might
41 * be subject to completely different semantics, in fact.
42 */
43
44inline static bool isMetaChar(ushort c)
45{
46 static const uchar iqm[] = {0x00, 0x00, 0x00, 0x00, 0x40, 0x03, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10}; // &()<>|
47
48 return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
49}
50
51inline static bool isSpecialChar(ushort c)
52{
53 // Chars that should be quoted (TM). This includes:
54 // - control chars & space
55 // - the shell meta chars &()<>^|
56 // - the potential separators ,;=
57 static const uchar iqm[] = {0xff, 0xff, 0xff, 0xff, 0x41, 0x13, 0x00, 0x78, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10};
58
59 return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
60}
61
62inline static bool isWhiteSpace(ushort c)
63{
64 return c == ' ' || c == '\t';
65}
66
67QStringList KShell::splitArgs(const QString &_args, Options flags, Errors *err)
68{
69 QString args(_args);
70 QStringList ret;
71
72 const QLatin1Char bs('\\'), dq('\"');
73
74 if (flags & AbortOnMeta) {
75 args.remove(PERCENT_ESCAPE);
76 if (args.indexOf(QLatin1Char('%')) >= 0) {
77 if (err) {
78 *err = FoundMeta;
79 }
80 return QStringList();
81 }
82
83 args = _args;
84 args.replace(PERCENT_ESCAPE, QLatin1String("%"));
85
86 if (!args.isEmpty() && args[0].unicode() == '@') {
87 args.remove(0, 1);
88 }
89
90 for (int p = 0; p < args.length(); p++) {
91 ushort c = args[p].unicode();
92 if (c == '^') {
93 args.remove(p, 1);
94 } else if (c == '"') {
95 while (++p < args.length() && args[p].unicode() != '"')
96 ;
97 } else if (isMetaChar(c)) {
98 if (err) {
99 *err = FoundMeta;
100 }
101 return QStringList();
102 }
103 }
104 }
105
106 if (err) {
107 *err = NoError;
108 }
109
110 int p = 0;
111 const int length = args.length();
112 for (;;) {
113 while (p < length && isWhiteSpace(args[p].unicode())) {
114 ++p;
115 }
116 if (p == length) {
117 return ret;
118 }
119
120 QString arg;
121 bool inquote = false;
122 for (;;) {
123 bool copy = true; // copy this char
124 int bslashes = 0; // number of preceding backslashes to insert
125 while (p < length && args[p] == bs) {
126 ++p;
127 ++bslashes;
128 }
129 if (p < length && args[p] == dq) {
130 if (bslashes % 2 == 0) {
131 // Even number of backslashes, so the quote is not escaped.
132 if (inquote) {
133 if (p + 1 < length && args[p + 1] == dq) {
134 // Two consecutive quotes make a literal quote.
135 // This is not documented on MSDN.
136 ++p;
137 } else {
138 // Closing quote
139 copy = false;
140 inquote = !inquote;
141 }
142 } else {
143 // Opening quote
144 copy = false;
145 inquote = !inquote;
146 }
147 }
148 bslashes /= 2;
149 }
150
151 while (--bslashes >= 0) {
152 arg.append(bs);
153 }
154
155 if (p == length || (!inquote && isWhiteSpace(args[p].unicode()))) {
156 ret.append(arg);
157 if (inquote) {
158 if (err) {
159 *err = BadQuoting;
160 }
161 return QStringList();
162 }
163 break;
164 }
165
166 if (copy) {
167 arg.append(args[p]);
168 }
169 ++p;
170 }
171 }
172 // not reached
173}
174
175QString KShell::quoteArgInternal(const QString &arg, bool _inquote)
176{
177 // Escape quotes, preceding backslashes are doubled. Surround with quotes.
178 // Note that cmd does not understand quote escapes in quoted strings,
179 // so the quoting needs to be "suspended".
180 const QLatin1Char bs('\\'), dq('\"');
181 QString ret;
182 bool inquote = _inquote;
183 int bslashes = 0;
184 for (int p = 0; p < arg.length(); p++) {
185 if (arg[p] == bs) {
186 bslashes++;
187 } else if (arg[p] == dq) {
188 if (inquote) {
189 ret.append(dq);
190 inquote = false;
191 }
192 for (; bslashes; bslashes--) {
193 ret.append(QLatin1String("\\\\"));
194 }
195 ret.append(QLatin1String("\\^\""));
196 } else {
197 if (!inquote) {
198 ret.append(dq);
199 inquote = true;
200 }
201 for (; bslashes; bslashes--) {
202 ret.append(bs);
203 }
204 ret.append(arg[p]);
205 }
206 }
207 ret.replace(QLatin1Char('%'), PERCENT_ESCAPE);
208 if (bslashes) {
209 // Ensure that we don't have directly trailing backslashes,
210 // so concatenating with another string won't cause surprises.
211 if (!inquote && !_inquote) {
212 ret.append(dq);
213 }
214 for (; bslashes; bslashes--) {
215 ret.append(QLatin1String("\\\\"));
216 }
217 ret.append(dq);
218 if (inquote && _inquote) {
219 ret.append(dq);
220 }
221 } else if (inquote != _inquote) {
222 ret.append(dq);
223 }
224 return ret;
225}
226
228{
229 if (arg.isEmpty()) {
230 return QStringLiteral("\"\"");
231 }
232
233 // Ensure that we don't have directly trailing backslashes,
234 // so concatenating with another string won't cause surprises.
235 if (arg.endsWith(QLatin1Char('\\'))) {
236 return quoteArgInternal(arg, false);
237 }
238
239 for (int x = arg.length() - 1; x >= 0; --x)
240 if (isSpecialChar(arg[x].unicode())) {
241 return quoteArgInternal(arg, false);
242 }
243
244 // Escape quotes. Preceding backslashes are doubled.
245 // Note that the remaining string is not quoted.
246 QString ret(arg);
247 ret.replace(QRegularExpression(QStringLiteral("(\\\\*)\"")), QStringLiteral("\\1\\1\\^\""));
248 ret.replace(QLatin1Char('%'), PERCENT_ESCAPE);
249 return ret;
250}
KCOREADDONS_EXPORT QStringList splitArgs(const QString &cmd, Options flags=NoOptions, Errors *err=nullptr)
Splits cmd according to system shell word splitting and quoting rules.
KCOREADDONS_EXPORT QString quoteArg(const QString &arg)
Quotes arg according to system shell rules.
@ BadQuoting
Indicates a parsing error, like an unterminated quoted string.
Definition kshell.h:87
@ FoundMeta
The AbortOnMeta flag was set and an unhandled shell meta character was encountered.
Definition kshell.h:93
const QList< QKeySequence > & copy()
void append(QList< T > &&value)
QString & append(QChar ch)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
const QChar * unicode() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:13:31 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.