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 <QRegularExpression>
14 #include <QString>
15 #include <QStringList>
16 #include <QDir>
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[] = {
47  0x00, 0x00, 0x00, 0x00, 0x40, 0x03, 0x00, 0x50,
48  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10
49  }; // &()<>|
50 
51  return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
52 }
53 
54 inline static bool isSpecialChar(ushort c)
55 {
56  // Chars that should be quoted (TM). This includes:
57  // - control chars & space
58  // - the shell meta chars &()<>^|
59  // - the potential separators ,;=
60  static const uchar iqm[] = {
61  0xff, 0xff, 0xff, 0xff, 0x41, 0x13, 0x00, 0x78,
62  0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10
63  };
64 
65  return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
66 }
67 
68 inline static bool isWhiteSpace(ushort c)
69 {
70  return c == ' ' || c == '\t';
71 }
72 
73 QStringList KShell::splitArgs(const QString &_args, Options flags, Errors *err)
74 {
75  QString args(_args);
76  QStringList ret;
77 
78  const QLatin1Char bs('\\'), dq('\"');
79 
80  if (flags & AbortOnMeta) {
81  args.remove(PERCENT_ESCAPE);
82  if (args.indexOf(QLatin1Char('%')) >= 0) {
83  if (err) {
84  *err = FoundMeta;
85  }
86  return QStringList();
87  }
88 
89  args = _args;
90  args.replace(PERCENT_ESCAPE, QLatin1String("%"));
91 
92  if (!args.isEmpty() && args[0].unicode() == '@') {
93  args.remove(0, 1);
94  }
95 
96  for (int p = 0; p < args.length(); p++) {
97  ushort c = args[p].unicode();
98  if (c == '^') {
99  args.remove(p, 1);
100  } else if (c == '"') {
101  while (++p < args.length() && args[p].unicode() != '"')
102  ;
103  } else if (isMetaChar(c)) {
104  if (err) {
105  *err = FoundMeta;
106  }
107  return QStringList();
108  }
109  }
110  }
111 
112  if (err) {
113  *err = NoError;
114  }
115 
116  int p = 0;
117  const int length = args.length();
118  forever {
119  while (p < length && isWhiteSpace(args[p].unicode()))
120  {
121  ++p;
122  }
123  if (p == length)
124  {
125  return ret;
126  }
127 
128  QString arg;
129  bool inquote = false;
130  forever {
131  bool copy = true; // copy this char
132  int bslashes = 0; // number of preceding backslashes to insert
133  while (p < length && args[p] == bs)
134  {
135  ++p;
136  ++bslashes;
137  }
138  if (p < length && args[p] == dq)
139  {
140  if (bslashes % 2 == 0) {
141  // Even number of backslashes, so the quote is not escaped.
142  if (inquote) {
143  if (p + 1 < length && args[p + 1] == dq) {
144  // Two consecutive quotes make a literal quote.
145  // This is not documented on MSDN.
146  ++p;
147  } else {
148  // Closing quote
149  copy = false;
150  inquote = !inquote;
151  }
152  } else {
153  // Opening quote
154  copy = false;
155  inquote = !inquote;
156  }
157  }
158  bslashes /= 2;
159  }
160 
161  while (--bslashes >= 0)
162  {
163  arg.append(bs);
164  }
165 
166  if (p == length || (!inquote && isWhiteSpace(args[p].unicode())))
167  {
168  ret.append(arg);
169  if (inquote) {
170  if (err) {
171  *err = BadQuoting;
172  }
173  return QStringList();
174  }
175  break;
176  }
177 
178  if (copy)
179  {
180  arg.append(args[p]);
181  }
182  ++p;
183  }
184  }
185  //not reached
186 }
187 
188 QString KShell::quoteArgInternal(const QString &arg, bool _inquote)
189 {
190  // Escape quotes, preceding backslashes are doubled. Surround with quotes.
191  // Note that cmd does not understand quote escapes in quoted strings,
192  // so the quoting needs to be "suspended".
193  const QLatin1Char bs('\\'), dq('\"');
194  QString ret;
195  bool inquote = _inquote;
196  int bslashes = 0;
197  for (int p = 0; p < arg.length(); p++) {
198  if (arg[p] == bs) {
199  bslashes++;
200  } else if (arg[p] == dq) {
201  if (inquote) {
202  ret.append(dq);
203  inquote = false;
204  }
205  for (; bslashes; bslashes--) {
206  ret.append(QLatin1String("\\\\"));
207  }
208  ret.append(QLatin1String("\\^\""));
209  } else {
210  if (!inquote) {
211  ret.append(dq);
212  inquote = true;
213  }
214  for (; bslashes; bslashes--) {
215  ret.append(bs);
216  }
217  ret.append(arg[p]);
218  }
219  }
220  ret.replace(QLatin1Char('%'), PERCENT_ESCAPE);
221  if (bslashes) {
222  // Ensure that we don't have directly trailing backslashes,
223  // so concatenating with another string won't cause surprises.
224  if (!inquote && !_inquote) {
225  ret.append(dq);
226  }
227  for (; bslashes; bslashes--) {
228  ret.append(QLatin1String("\\\\"));
229  }
230  ret.append(dq);
231  if (inquote && _inquote) {
232  ret.append(dq);
233  }
234  } else if (inquote != _inquote) {
235  ret.append(dq);
236  }
237  return ret;
238 }
239 
240 QString KShell::quoteArg(const QString &arg)
241 {
242  if (arg.isEmpty()) {
243  return QStringLiteral("\"\"");
244  }
245 
246  // Ensure that we don't have directly trailing backslashes,
247  // so concatenating with another string won't cause surprises.
248  if (arg.endsWith(QLatin1Char('\\'))) {
249  return quoteArgInternal(arg, false);
250  }
251 
252  for (int x = arg.length() - 1; x >= 0; --x)
253  if (isSpecialChar(arg[x].unicode())) {
254  return quoteArgInternal(arg, false);
255  }
256 
257  // Escape quotes. Preceding backslashes are doubled.
258  // Note that the remaining string is not quoted.
259  QString ret(arg);
260  ret.replace(QRegularExpression(QStringLiteral("(\\\\*)\"")), QStringLiteral("\\1\\1\\^\""));
261  ret.replace(QLatin1Char('%'), PERCENT_ESCAPE);
262  return ret;
263 }
264 
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:58
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-2020 The KDE developers.
Generated on Wed May 27 2020 23:06:03 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.