KDECore
kshell_win.cpp
Go to the documentation of this file.
00001 /* 00002 This file is part of the KDE libraries 00003 00004 Copyright (c) 2007 Bernhard Loos <nhuh.put@web.de> 00005 Copyright (c) 2007,2008 Oswald Buddenhagen <ossi@kde.org> 00006 00007 This library is free software; you can redistribute it and/or 00008 modify it under the terms of the GNU Library General Public 00009 License as published by the Free Software Foundation; either 00010 version 2 of the License, or (at your option) any later version. 00011 00012 This library is distributed in the hope that it will be useful, 00013 but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00015 Library General Public License for more details. 00016 00017 You should have received a copy of the GNU Library General Public License 00018 along with this library; see the file COPYING.LIB. If not, write to 00019 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00020 Boston, MA 02110-1301, USA. 00021 */ 00022 00023 #include "kshell.h" 00024 #include "kshell_p.h" 00025 00026 #include <kkernel_win.h> 00027 00028 #include <QString> 00029 #include <QStringList> 00030 #include <QtCore/QDir> 00031 00032 /* 00033 * A short introduction into cmd semantics: 00034 * - Variable expansion is done first, without regard to *any* escaping - 00035 * if something looks like an existing variable, it is replaced. 00036 * - Then follows regular tokenization by the shell. &, &&, | and || are 00037 * command delimiters. ( and ) are command grouping operators; they are 00038 * recognized only a the start resp. end of a command; mismatched )s are 00039 * an error if any (s are present. <, > are just like under UNIX - they can 00040 * appear *anywhere* in a command, perform their function and are cut out. 00041 * @ at the start of a command is eaten (local echo off - no function as 00042 * far as cmd /c is concerned). : at the start of a command declares a label, 00043 * which effectively means the remainder of the line is a comment - note that 00044 * command separators are not recognized past that point. 00045 * ^ is the escape char for everything including itself. 00046 * cmd ignores *all* special chars between double quotes, so there is no 00047 * way to escape the closing quote. Note that the quotes are *not* removed 00048 * from the resulting command line. 00049 * - Then follows delayed variable expansion if it is enabled and at least 00050 * one exclamation mark is present. This involves another layer of ^ 00051 * escaping, regardless of quotes. (Win2k+) 00052 * - Then follows argument splitting as described in 00053 * http://msdn2.microsoft.com/en-us/library/ms880421.aspx . 00054 * Note that this is done by the called application and therefore might 00055 * be subject to completely different semantics, in fact. 00056 */ 00057 00058 inline static bool isMetaChar(ushort c) 00059 { 00060 static const uchar iqm[] = { 00061 0x00, 0x00, 0x00, 0x00, 0x40, 0x03, 0x00, 0x50, 00062 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 00063 }; // &()<>| 00064 00065 return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))); 00066 } 00067 00068 inline static bool isSpecialChar(ushort c) 00069 { 00070 // Chars that should be quoted (TM). This includes: 00071 // - control chars & space 00072 // - the shell meta chars &()<>^| 00073 // - the potential separators ,;= 00074 static const uchar iqm[] = { 00075 0xff, 0xff, 0xff, 0xff, 0x41, 0x13, 0x00, 0x78, 00076 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10 00077 }; 00078 00079 return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))); 00080 } 00081 00082 inline static bool isWhiteSpace(ushort c) 00083 { 00084 return c == ' ' || c == '\t'; 00085 } 00086 00087 QStringList KShell::splitArgs(const QString &_args, Options flags, Errors *err) 00088 { 00089 QString args(_args); 00090 QStringList ret; 00091 00092 const QLatin1Char bs('\\'), dq('\"'); 00093 00094 if (flags & AbortOnMeta) { 00095 args.remove(PERCENT_ESCAPE); 00096 if (args.indexOf(QLatin1Char('%')) >= 0) { 00097 if (err) 00098 *err = FoundMeta; 00099 return QStringList(); 00100 } 00101 00102 args = _args; 00103 args.replace(PERCENT_ESCAPE, QLatin1String("%")); 00104 00105 if (!args.isEmpty() && args[0].unicode() == '@') 00106 args.remove(0, 1); 00107 00108 for (int p = 0; p < args.length(); p++) { 00109 ushort c = args[p].unicode(); 00110 if (c == '^') { 00111 args.remove(p, 1); 00112 } else if (c == '"') { 00113 while (++p < args.length() && args[p].unicode() != '"') 00114 ; 00115 } else if (isMetaChar(c)) { 00116 if (err) 00117 *err = FoundMeta; 00118 return QStringList(); 00119 } 00120 } 00121 } 00122 00123 if (err) 00124 *err = NoError; 00125 00126 int p = 0; 00127 const int length = args.length(); 00128 forever { 00129 while (p < length && isWhiteSpace(args[p].unicode())) 00130 ++p; 00131 if (p == length) 00132 return ret; 00133 00134 QString arg; 00135 bool inquote = false; 00136 forever { 00137 bool copy = true; // copy this char 00138 int bslashes = 0; // number of preceding backslashes to insert 00139 while (p < length && args[p] == bs) { 00140 ++p; 00141 ++bslashes; 00142 } 00143 if (p < length && args[p] == dq) { 00144 if (bslashes % 2 == 0) { 00145 // Even number of backslashes, so the quote is not escaped. 00146 if (inquote) { 00147 if (p + 1 < length && args[p + 1] == dq) { 00148 // Two consecutive quotes make a literal quote. 00149 // This is not documented on MSDN. 00150 ++p; 00151 } else { 00152 // Closing quote 00153 copy = false; 00154 inquote = !inquote; 00155 } 00156 } else { 00157 // Opening quote 00158 copy = false; 00159 inquote = !inquote; 00160 } 00161 } 00162 bslashes /= 2; 00163 } 00164 00165 while (--bslashes >= 0) 00166 arg.append(bs); 00167 00168 if (p == length || (!inquote && isWhiteSpace(args[p].unicode()))) { 00169 ret.append(arg); 00170 if (inquote) { 00171 if (err) 00172 *err = BadQuoting; 00173 return QStringList(); 00174 } 00175 break; 00176 } 00177 00178 if (copy) 00179 arg.append(args[p]); 00180 ++p; 00181 } 00182 } 00183 //not reached 00184 } 00185 00186 QString KShell::quoteArgInternal(const QString &arg, bool _inquote) 00187 { 00188 // Escape quotes, preceding backslashes are doubled. Surround with quotes. 00189 // Note that cmd does not understand quote escapes in quoted strings, 00190 // so the quoting needs to be "suspended". 00191 const QLatin1Char bs('\\'), dq('\"'); 00192 QString ret; 00193 bool inquote = _inquote; 00194 int bslashes = 0; 00195 for (int p = 0; p < arg.length(); p++) { 00196 if (arg[p] == bs) { 00197 bslashes++; 00198 } else if (arg[p] == dq) { 00199 if (inquote) { 00200 ret.append(dq); 00201 inquote = false; 00202 } 00203 for (; bslashes; bslashes--) 00204 ret.append(QLatin1String("\\\\")); 00205 ret.append(QLatin1String("\\^\"")); 00206 } else { 00207 if (!inquote) { 00208 ret.append(dq); 00209 inquote = true; 00210 } 00211 for (; bslashes; bslashes--) 00212 ret.append(bs); 00213 ret.append(arg[p]); 00214 } 00215 } 00216 ret.replace(QLatin1Char('%'), PERCENT_ESCAPE); 00217 if (bslashes) { 00218 // Ensure that we don't have directly trailing backslashes, 00219 // so concatenating with another string won't cause surprises. 00220 if (!inquote && !_inquote) 00221 ret.append(dq); 00222 for (; bslashes; bslashes--) 00223 ret.append(QLatin1String("\\\\")); 00224 ret.append(dq); 00225 if (inquote && _inquote) 00226 ret.append(dq); 00227 } else if (inquote != _inquote) { 00228 ret.append(dq); 00229 } 00230 return ret; 00231 } 00232 00233 QString KShell::quoteArg(const QString &arg) 00234 { 00235 if (arg.isEmpty()) 00236 return QString::fromLatin1("\"\""); 00237 00238 // Ensure that we don't have directly trailing backslashes, 00239 // so concatenating with another string won't cause surprises. 00240 if (arg.endsWith(QLatin1Char('\\'))) 00241 return quoteArgInternal(arg, false); 00242 00243 for (int x = arg.length() - 1; x >= 0; --x) 00244 if (isSpecialChar(arg[x].unicode())) 00245 return quoteArgInternal(arg, false); 00246 00247 // Escape quotes. Preceding backslashes are doubled. 00248 // Note that the remaining string is not quoted. 00249 QString ret(arg); 00250 ret.replace(QRegExp(QLatin1String("(\\\\*)\"")), QLatin1String("\\1\\1\\^\"")); 00251 ret.replace(QLatin1Char('%'), PERCENT_ESCAPE); 00252 return ret; 00253 } 00254
KDE 4.6 API Reference