1/*
2 This file is part of the KDE libraries
3
4 SPDX-FileCopyrightText: 2002-2003 Oswald Buddenhagen <ossi@kde.org>
5 SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "kmacroexpander_p.h"
11
12#include <QRegularExpression>
13#include <QStack>
14#include <QStringList>
15
16namespace KMacroExpander
17{
18enum Quoting {
19 noquote,
20 singlequote,
21 doublequote,
22 dollarquote,
23 paren,
24 subst,
25 group,
26 math,
27};
28typedef struct {
29 Quoting current;
30 bool dquote;
31} State;
32typedef struct {
33 QString str;
34 int pos;
35} Save;
36
37}
38
39using namespace KMacroExpander;
40
41// #pragma message("TODO: Import these methods into Qt")
42
43inline static bool isSpecial(QChar cUnicode)
44{
45 static const uchar iqm[] = {0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78}; // 0-32 \'"$`<>|;&(){}*?#!~[]
46
47 uint c = cUnicode.unicode();
48 return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
49}
50
51static QString quoteArg(const QString &arg)
52{
53 if (!arg.length()) {
54 return QStringLiteral("''");
55 }
56 for (int i = 0; i < arg.length(); i++) {
57 if (isSpecial(cUnicode: arg.unicode()[i])) {
58 QChar q(QLatin1Char('\''));
59 return q + QString(arg).replace(c: q, after: QLatin1String("'\\''")) + q;
60 }
61 }
62 return arg;
63}
64
65static QString joinArgs(const QStringList &args)
66{
67 QString ret;
68 for (QStringList::ConstIterator it = args.begin(); it != args.end(); ++it) {
69 if (!ret.isEmpty()) {
70 ret.append(c: QLatin1Char(' '));
71 }
72 ret.append(s: quoteArg(arg: *it));
73 }
74 return ret;
75}
76
77bool KMacroExpanderBase::expandMacrosShellQuote(QString &str, int &pos)
78{
79 int len;
80 int pos2;
81 ushort ec = d->escapechar.unicode();
82 State state = {.current: noquote, .dquote: false};
83 QStack<State> sstack;
84 QStack<Save> ostack;
85 QStringList rst;
86 QString rsts;
87
88 while (pos < str.length()) {
89 ushort cc = str.unicode()[pos].unicode();
90 if (ec != 0) {
91 if (cc != ec) {
92 goto nohit;
93 }
94 if (!(len = expandEscapedMacro(str, pos, ret&: rst))) {
95 goto nohit;
96 }
97 } else {
98 if (!(len = expandPlainMacro(str, pos, ret&: rst))) {
99 goto nohit;
100 }
101 }
102 if (len < 0) {
103 pos -= len;
104 continue;
105 }
106 if (state.dquote) {
107 rsts = rst.join(sep: QLatin1Char(' '));
108 const static QRegularExpression regex(QStringLiteral("([$`\"\\\\])"));
109 rsts.replace(re: regex, QStringLiteral("\\\\1"));
110 } else if (state.current == dollarquote) {
111 rsts = rst.join(sep: QLatin1Char(' '));
112 const static QRegularExpression regex(QStringLiteral("(['\\\\])"));
113 rsts.replace(re: regex, QStringLiteral("\\\\1"));
114 } else if (state.current == singlequote) {
115 rsts = rst.join(sep: QLatin1Char(' '));
116 rsts.replace(c: QLatin1Char('\''), after: QLatin1String("'\\''"));
117 } else {
118 if (rst.isEmpty()) {
119 str.remove(i: pos, len);
120 continue;
121 } else {
122 rsts = joinArgs(args: rst);
123 }
124 }
125 rst.clear();
126 str.replace(i: pos, len, after: rsts);
127 pos += rsts.length();
128 continue;
129 nohit:
130 if (state.current == singlequote) {
131 if (cc == '\'') {
132 state = sstack.pop();
133 }
134 } else if (cc == '\\') {
135 // always swallow the char -> prevent anomalies due to expansion
136 pos += 2;
137 continue;
138 } else if (state.current == dollarquote) {
139 if (cc == '\'') {
140 state = sstack.pop();
141 }
142 } else if (cc == '$') {
143 cc = str.unicode()[++pos].unicode();
144 if (cc == '(') {
145 sstack.push(t: state);
146 if (str.unicode()[pos + 1].unicode() == '(') {
147 Save sav = {.str: str, .pos: pos + 2};
148 ostack.push(t: sav);
149 state.current = math;
150 pos += 2;
151 continue;
152 } else {
153 state.current = paren;
154 state.dquote = false;
155 }
156 } else if (cc == '{') {
157 sstack.push(t: state);
158 state.current = subst;
159 } else if (!state.dquote) {
160 if (cc == '\'') {
161 sstack.push(t: state);
162 state.current = dollarquote;
163 } else if (cc == '"') {
164 sstack.push(t: state);
165 state.current = doublequote;
166 state.dquote = true;
167 }
168 }
169 // always swallow the char -> prevent anomalies due to expansion
170 } else if (cc == '`') {
171 str.replace(i: pos, len: 1, QStringLiteral("$( ")); // add space -> avoid creating $((
172 pos2 = pos += 3;
173 for (;;) {
174 if (pos2 >= str.length()) {
175 pos = pos2;
176 return false;
177 }
178 cc = str.unicode()[pos2].unicode();
179 if (cc == '`') {
180 break;
181 }
182 if (cc == '\\') {
183 cc = str.unicode()[++pos2].unicode();
184 if (cc == '$' || cc == '`' || cc == '\\' || (cc == '"' && state.dquote)) {
185 str.remove(i: pos2 - 1, len: 1);
186 continue;
187 }
188 }
189 pos2++;
190 }
191 str[pos2] = QLatin1Char(')');
192 sstack.push(t: state);
193 state.current = paren;
194 state.dquote = false;
195 continue;
196 } else if (state.current == doublequote) {
197 if (cc == '"') {
198 state = sstack.pop();
199 }
200 } else if (cc == '\'') {
201 if (!state.dquote) {
202 sstack.push(t: state);
203 state.current = singlequote;
204 }
205 } else if (cc == '"') {
206 if (!state.dquote) {
207 sstack.push(t: state);
208 state.current = doublequote;
209 state.dquote = true;
210 }
211 } else if (state.current == subst) {
212 if (cc == '}') {
213 state = sstack.pop();
214 }
215 } else if (cc == ')') {
216 if (state.current == math) {
217 if (str.unicode()[pos + 1].unicode() == ')') {
218 state = sstack.pop();
219 pos += 2;
220 } else {
221 // false hit: the $(( was a $( ( in fact
222 // ash does not care, but bash does
223 pos = ostack.top().pos;
224 str = ostack.top().str;
225 ostack.pop();
226 state.current = paren;
227 state.dquote = false;
228 sstack.push(t: state);
229 }
230 continue;
231 } else if (state.current == paren) {
232 state = sstack.pop();
233 } else {
234 break;
235 }
236 } else if (cc == '}') {
237 if (state.current == KMacroExpander::group) {
238 state = sstack.pop();
239 } else {
240 break;
241 }
242 } else if (cc == '(') {
243 sstack.push(t: state);
244 state.current = paren;
245 } else if (cc == '{') {
246 sstack.push(t: state);
247 state.current = KMacroExpander::group;
248 }
249 pos++;
250 }
251 return sstack.empty();
252}
253

source code of kcoreaddons/src/lib/text/kmacroexpander_unix.cpp