KCoreAddons

kshell_win.cpp
1 /*
2  This file is part of the KDE libraries
3 
4  SPDX-FileCopyrightText: 2007 Bernhard Loos <[email protected]>
5  SPDX-FileCopyrightText: 2007, 2008 Oswald Buddenhagen <[email protected]>
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 
44 inline 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 
51 inline 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 
62 inline static bool isWhiteSpace(ushort c)
63 {
64  return c == ' ' || c == '\t';
65 }
66 
67 QStringList 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 
175 QString 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 
227 QString KShell::quoteArg(const QString &arg)
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 }
QString & append(QChar ch)
QString & remove(int position, int n)
KIOCORE_EXPORT CopyJob * copy(const QUrl &src, const QUrl &dest, JobFlags flags=DefaultFlags)
void append(const T &value)
The AbortOnMeta flag was set and an unhandled shell meta character was encountered.
Definition: kshell.h:93
bool isEmpty() const const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
KCOREADDONS_EXPORT QStringList splitArgs(const QString &cmd, Options flags=NoOptions, Errors *err=nullptr)
Splits cmd according to system shell word splitting and quoting rules.
Definition: kshell_unix.cpp:46
KCOREADDONS_EXPORT QString quoteArg(const QString &arg)
Quotes arg according to system shell rules.
QString & replace(int position, int n, QChar after)
const QChar * unicode() const const
Indicates a parsing error, like an unterminated quoted string.
Definition: kshell.h:87
int length() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sun Apr 18 2021 23:02:02 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.