| 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.isEmpty()) { |
| 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 | |