KCoreAddons

kmacroexpander_unix.cpp
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(arg.unicode()[i])) {
58 QChar q(QLatin1Char('\''));
59 return q + QString(arg).replace(q, 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(QLatin1Char(' '));
71 }
72 ret.append(quoteArg(*it));
73 }
74 return ret;
75}
76
78{
79 int len;
80 int pos2;
81 ushort ec = d->escapechar.unicode();
82 State state = {noquote, 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, rst))) {
95 goto nohit;
96 }
97 } else {
98 if (!(len = expandPlainMacro(str, pos, rst))) {
99 goto nohit;
100 }
101 }
102 if (len < 0) {
103 pos -= len;
104 continue;
105 }
106 if (state.dquote) {
107 rsts = rst.join(QLatin1Char(' '));
108 const static QRegularExpression regex(QStringLiteral("([$`\"\\\\])"));
109 rsts.replace(regex, QStringLiteral("\\\\1"));
110 } else if (state.current == dollarquote) {
111 rsts = rst.join(QLatin1Char(' '));
112 const static QRegularExpression regex(QStringLiteral("(['\\\\])"));
113 rsts.replace(regex, QStringLiteral("\\\\1"));
114 } else if (state.current == singlequote) {
115 rsts = rst.join(QLatin1Char(' '));
116 rsts.replace(QLatin1Char('\''), QLatin1String("'\\''"));
117 } else {
118 if (rst.isEmpty()) {
119 str.remove(pos, len);
120 continue;
121 } else {
122 rsts = joinArgs(rst);
123 }
124 }
125 rst.clear();
126 str.replace(pos, len, 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(state);
146 if (str.unicode()[pos + 1].unicode() == '(') {
147 Save sav = {str, pos + 2};
148 ostack.push(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(state);
158 state.current = subst;
159 } else if (!state.dquote) {
160 if (cc == '\'') {
161 sstack.push(state);
162 state.current = dollarquote;
163 } else if (cc == '"') {
164 sstack.push(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(pos, 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(pos2 - 1, 1);
186 continue;
187 }
188 }
189 pos2++;
190 }
191 str[pos2] = QLatin1Char(')');
192 sstack.push(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(state);
203 state.current = singlequote;
204 }
205 } else if (cc == '"') {
206 if (!state.dquote) {
207 sstack.push(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(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(state);
244 state.current = paren;
245 } else if (cc == '{') {
246 sstack.push(state);
247 state.current = KMacroExpander::group;
248 }
249 pos++;
250 }
251 return sstack.empty();
252}
virtual int expandPlainMacro(const QString &str, int pos, QStringList &ret)
This function is called for every single char within the string if the escape char is QChar::null.
virtual int expandEscapedMacro(const QString &str, int pos, QStringList &ret)
This function is called every time the escape char is found if it is not QChar::null.
bool expandMacrosShellQuote(QString &str, int &pos)
Perform safe macro expansion (substitution) on a string for use in shell commands.
A group of functions providing macro expansion (substitution) in strings, optionally with quoting app...
char16_t & unicode()
iterator begin()
void clear()
bool empty() const const
iterator end()
bool isEmpty() const const
void push(const T &t)
T & top()
QString & append(QChar ch)
bool isEmpty() const const
qsizetype length() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
const QChar * unicode() const const
QString join(QChar separator) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:04:24 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.