1 | // Copyright (C) 2022 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
3 | |
4 | #ifndef SHELLQUOTE_SHARED_H |
5 | #define SHELLQUOTE_SHARED_H |
6 | |
7 | #include <QDir> |
8 | #include <QRegularExpression> |
9 | #include <QString> |
10 | |
11 | // Copy-pasted from qmake/library/ioutil.cpp |
12 | inline static bool hasSpecialChars(const QString &arg, const uchar (&iqm)[16]) |
13 | { |
14 | for (int x = arg.size() - 1; x >= 0; --x) { |
15 | ushort c = arg.unicode()[x].unicode(); |
16 | if ((c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)))) |
17 | return true; |
18 | } |
19 | return false; |
20 | } |
21 | |
22 | static QString shellQuoteUnix(const QString &arg) |
23 | { |
24 | // Chars that should be quoted (TM). This includes: |
25 | static const uchar iqm[] = { |
26 | 0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8, |
27 | 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78 |
28 | }; // 0-32 \'"$`<>|;&(){}*?#!~[] |
29 | |
30 | if (!arg.size()) |
31 | return QLatin1String("\"\"" ); |
32 | |
33 | QString ret(arg); |
34 | if (hasSpecialChars(arg: ret, iqm)) { |
35 | ret.replace(c: QLatin1Char('\''), after: QLatin1String("'\\''" )); |
36 | ret.prepend(c: QLatin1Char('\'')); |
37 | ret.append(c: QLatin1Char('\'')); |
38 | } |
39 | return ret; |
40 | } |
41 | |
42 | static QString shellQuoteWin(const QString &arg) |
43 | { |
44 | // Chars that should be quoted (TM). This includes: |
45 | // - control chars & space |
46 | // - the shell meta chars "&()<>^| |
47 | // - the potential separators ,;= |
48 | static const uchar iqm[] = { |
49 | 0xff, 0xff, 0xff, 0xff, 0x45, 0x13, 0x00, 0x78, |
50 | 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10 |
51 | }; |
52 | |
53 | if (!arg.size()) |
54 | return QLatin1String("\"\"" ); |
55 | |
56 | QString ret(arg); |
57 | if (hasSpecialChars(arg: ret, iqm)) { |
58 | // Quotes are escaped and their preceding backslashes are doubled. |
59 | // It's impossible to escape anything inside a quoted string on cmd |
60 | // level, so the outer quoting must be "suspended". |
61 | ret.replace(re: QRegularExpression(QLatin1String("(\\\\*)\"" )), after: QLatin1String("\"\\1\\1\\^\"\"" )); |
62 | // The argument must not end with a \ since this would be interpreted |
63 | // as escaping the quote -- rather put the \ behind the quote: e.g. |
64 | // rather use "foo"\ than "foo\" |
65 | int i = ret.size(); |
66 | while (i > 0 && ret.at(i: i - 1) == QLatin1Char('\\')) |
67 | --i; |
68 | ret.insert(i, c: QLatin1Char('"')); |
69 | ret.prepend(c: QLatin1Char('"')); |
70 | } |
71 | return ret; |
72 | } |
73 | |
74 | static QString shellQuote(const QString &arg) |
75 | { |
76 | if (QDir::separator() == QLatin1Char('\\')) |
77 | return shellQuoteWin(arg); |
78 | else |
79 | return shellQuoteUnix(arg); |
80 | } |
81 | |
82 | #endif // SHELLQUOTE_SHARED_H |
83 | |