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 | #ifndef KMACROEXPANDER_H |
10 | #define KMACROEXPANDER_H |
11 | |
12 | #include <QChar> |
13 | #include <QStringList> |
14 | |
15 | #include <kcoreaddons_export.h> |
16 | #include <memory> |
17 | |
18 | class QString; |
19 | template<typename KT, typename VT> |
20 | class QHash; |
21 | class KMacroExpanderBasePrivate; |
22 | |
23 | /** |
24 | * \class KMacroExpanderBase kmacroexpander.h <KMacroExpander> |
25 | * |
26 | * Abstract base class for the worker classes behind the KMacroExpander namespace |
27 | * and the KCharMacroExpander and KWordMacroExpander classes. |
28 | * |
29 | * @author Oswald Buddenhagen <ossi@kde.org> |
30 | */ |
31 | class KCOREADDONS_EXPORT KMacroExpanderBase |
32 | { |
33 | public: |
34 | /** |
35 | * Constructor. |
36 | * @param c escape char indicating start of macros, or QChar::null for none |
37 | */ |
38 | explicit KMacroExpanderBase(QChar c = QLatin1Char('%')); |
39 | |
40 | /** |
41 | * Destructor. |
42 | */ |
43 | virtual ~KMacroExpanderBase(); |
44 | |
45 | /** |
46 | * Perform safe macro expansion (substitution) on a string. |
47 | * |
48 | * @param str the string in which macros are expanded in-place |
49 | */ |
50 | void expandMacros(QString &str); |
51 | |
52 | // TODO: This documentation is relevant for end-users. Where to put it? |
53 | /** |
54 | * Perform safe macro expansion (substitution) on a string for use |
55 | * in shell commands. |
56 | * |
57 | * <h3>*NIX notes</h3> |
58 | * |
59 | * Explicitly supported shell constructs: |
60 | * \ '' "" $'' $"" {} () $(()) ${} $() `` |
61 | * |
62 | * Implicitly supported shell constructs: |
63 | * (()) |
64 | * |
65 | * Unsupported shell constructs that will cause problems: |
66 | * Shortened "<tt>case $v in pat)</tt>" syntax. Use |
67 | * "<tt>case $v in (pat)</tt>" instead. |
68 | * |
69 | * The rest of the shell (incl. bash) syntax is simply ignored, |
70 | * as it is not expected to cause problems. |
71 | * |
72 | * Note that bash contains a bug which makes macro expansion within |
73 | * double quoted substitutions (<tt>"${VAR:-%macro}"</tt>) inherently |
74 | * insecure. |
75 | * |
76 | * For security reasons, @em never put expandos in command line arguments |
77 | * that are shell commands by themselves - |
78 | * "<tt>sh -c 'foo \%f'</tt>" is taboo. |
79 | * "<tt>file=\%f sh -c 'foo "$file"'</tt>" is OK. |
80 | * |
81 | * <h3>Windows notes</h3> |
82 | * |
83 | * All quoting syntax supported by KShell is supported here as well. |
84 | * Additionally, command grouping via parentheses is recognized - note |
85 | * however, that the parser is much stricter about unquoted parentheses |
86 | * than cmd itself. |
87 | * The rest of the cmd syntax is simply ignored, as it is not expected |
88 | * to cause problems - do not use commands that embed other commands, |
89 | * though - "<tt>for /f ...</tt>" is taboo. |
90 | * |
91 | * @param str the string in which macros are expanded in-place |
92 | * @param pos the position inside the string at which parsing/substitution |
93 | * should start, and upon exit where processing stopped |
94 | * @return false if the string could not be parsed and therefore no safe |
95 | * substitution was possible. Note that macros will have been processed |
96 | * up to the point where the error occurred. An unmatched closing paren |
97 | * or brace outside any shell construct is @em not an error (unlike in |
98 | * the function below), but still prematurely terminates processing. |
99 | */ |
100 | bool expandMacrosShellQuote(QString &str, int &pos); |
101 | |
102 | /** |
103 | * Same as above, but always starts at position 0, and unmatched closing |
104 | * parens and braces are treated as errors. |
105 | */ |
106 | bool expandMacrosShellQuote(QString &str); |
107 | |
108 | /** |
109 | * Set the macro escape character. |
110 | * @param c escape char indicating start of macros, or QChar::null if none |
111 | */ |
112 | void setEscapeChar(QChar c); |
113 | |
114 | /** |
115 | * Obtain the macro escape character. |
116 | * @return escape char indicating start of macros, or QChar::null if none |
117 | */ |
118 | QChar escapeChar() const; |
119 | |
120 | protected: |
121 | /** |
122 | * This function is called for every single char within the string if |
123 | * the escape char is QChar::null. It should determine whether the |
124 | * string starting at @p pos within @p str is a valid macro and return |
125 | * the substitution value for it if so. |
126 | * @param str the input string |
127 | * @param pos the offset within @p str |
128 | * @param ret return value: the string to substitute for the macro |
129 | * @return If greater than zero, the number of chars at @p pos in @p str |
130 | * to substitute with @p ret (i.e., a valid macro was found). If less |
131 | * than zero, subtract this value from @p pos (to skip a macro, i.e., |
132 | * substitute it with itself). If zero, no macro starts at @p pos. |
133 | */ |
134 | virtual int expandPlainMacro(const QString &str, int pos, QStringList &ret); |
135 | |
136 | /** |
137 | * This function is called every time the escape char is found if it is |
138 | * not QChar::null. It should determine whether the |
139 | * string starting at @p pos witin @p str is a valid macro and return |
140 | * the substitution value for it if so. |
141 | * @param str the input string |
142 | * @param pos the offset within @p str. Note that this is the position of |
143 | * the occurrence of the escape char |
144 | * @param ret return value: the string to substitute for the macro |
145 | * @return If greater than zero, the number of chars at @p pos in @p str |
146 | * to substitute with @p ret (i.e., a valid macro was found). If less |
147 | * than zero, subtract this value from @p pos (to skip a macro, i.e., |
148 | * substitute it with itself). If zero, scanning continues as if no |
149 | * escape char was encountered at all. |
150 | */ |
151 | virtual int expandEscapedMacro(const QString &str, int pos, QStringList &ret); |
152 | |
153 | private: |
154 | std::unique_ptr<KMacroExpanderBasePrivate> const d; |
155 | }; |
156 | |
157 | /** |
158 | * \class KWordMacroExpander kmacroexpander.h <KMacroExpanderBase> |
159 | * |
160 | * Abstract base class for simple word macro substitutors. Use this instead of |
161 | * the functions in the KMacroExpander namespace if speculatively pre-filling |
162 | * the substitution map would be too expensive. |
163 | * |
164 | * A typical application: |
165 | * |
166 | * \code |
167 | * class MyClass { |
168 | * ... |
169 | * private: |
170 | * QString m_str; |
171 | * ... |
172 | * friend class MyExpander; |
173 | * }; |
174 | * |
175 | * class MyExpander : public KWordMacroExpander { |
176 | * public: |
177 | * MyExpander( MyClass *_that ) : KWordMacroExpander(), that( _that ) {} |
178 | * protected: |
179 | * virtual bool expandMacro( const QString &str, QStringList &ret ); |
180 | * private: |
181 | * MyClass *that; |
182 | * }; |
183 | * |
184 | * bool MyExpander::expandMacro( const QString &str, QStringList &ret ) |
185 | * { |
186 | * if (str == "macro") { |
187 | * ret += complexOperation( that->m_str ); |
188 | * return true; |
189 | * } |
190 | * return false; |
191 | * } |
192 | * |
193 | * ... MyClass::...(...) |
194 | * { |
195 | * QString str; |
196 | * ... |
197 | * MyExpander mx( this ); |
198 | * mx.expandMacrosShellQuote( str ); |
199 | * ... |
200 | * } |
201 | * \endcode |
202 | * |
203 | * Alternatively MyClass could inherit from KWordMacroExpander directly. |
204 | * |
205 | * @author Oswald Buddenhagen <ossi@kde.org> |
206 | */ |
207 | class KCOREADDONS_EXPORT KWordMacroExpander : public KMacroExpanderBase |
208 | { |
209 | public: |
210 | /** |
211 | * Constructor. |
212 | * @param c escape char indicating start of macros, or QChar::null for none |
213 | */ |
214 | explicit KWordMacroExpander(QChar c = QLatin1Char('%')) |
215 | : KMacroExpanderBase(c) |
216 | { |
217 | } |
218 | |
219 | protected: |
220 | /** \internal Not to be called or reimplemented. */ |
221 | int expandPlainMacro(const QString &str, int pos, QStringList &ret) override; |
222 | /** \internal Not to be called or reimplemented. */ |
223 | int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override; |
224 | |
225 | /** |
226 | * Return substitution list @p ret for string macro @p str. |
227 | * @param str the macro to expand |
228 | * @param ret return variable reference. It is guaranteed to be empty |
229 | * when expandMacro is entered. |
230 | * @return @c true iff @p str was a recognized macro name |
231 | */ |
232 | virtual bool expandMacro(const QString &str, QStringList &ret) = 0; |
233 | }; |
234 | |
235 | /** |
236 | * \class KCharMacroExpander kmacroexpander.h <KMacroExpanderBase> |
237 | * |
238 | * Abstract base class for single char macro substitutors. Use this instead of |
239 | * the functions in the KMacroExpander namespace if speculatively pre-filling |
240 | * the substitution map would be too expensive. |
241 | * |
242 | * See KWordMacroExpander for a sample application. |
243 | * |
244 | * @author Oswald Buddenhagen <ossi@kde.org> |
245 | */ |
246 | class KCOREADDONS_EXPORT KCharMacroExpander : public KMacroExpanderBase |
247 | { |
248 | public: |
249 | /** |
250 | * Constructor. |
251 | * @param c escape char indicating start of macros, or QChar::null for none |
252 | */ |
253 | explicit KCharMacroExpander(QChar c = QLatin1Char('%')) |
254 | : KMacroExpanderBase(c) |
255 | { |
256 | } |
257 | |
258 | protected: |
259 | /** \internal Not to be called or reimplemented. */ |
260 | int expandPlainMacro(const QString &str, int pos, QStringList &ret) override; |
261 | /** \internal Not to be called or reimplemented. */ |
262 | int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override; |
263 | |
264 | /** |
265 | * Return substitution list @p ret for single-character macro @p chr. |
266 | * @param chr the macro to expand |
267 | * @param ret return variable reference. It is guaranteed to be empty |
268 | * when expandMacro is entered. |
269 | * @return @c true iff @p chr was a recognized macro name |
270 | */ |
271 | virtual bool expandMacro(QChar chr, QStringList &ret) = 0; |
272 | }; |
273 | |
274 | /** |
275 | * A group of functions providing macro expansion (substitution) in strings, |
276 | * optionally with quoting appropriate for shell execution. |
277 | */ |
278 | namespace KMacroExpander |
279 | { |
280 | /** |
281 | * Perform safe macro expansion (substitution) on a string. |
282 | * The escape char must be quoted with itself to obtain its literal |
283 | * representation in the resulting string. |
284 | * |
285 | * @param str The string to expand |
286 | * @param map map with substitutions |
287 | * @param c escape char indicating start of macro, or QChar::null if none |
288 | * @return the string with all valid macros expanded |
289 | * |
290 | * \code |
291 | * // Code example |
292 | * QHash<QChar,QString> map; |
293 | * map.insert('u', "/tmp/myfile.txt"); |
294 | * map.insert('n', "My File"); |
295 | * QString s = "%% Title: %u:%n"; |
296 | * s = KMacroExpander::expandMacros(s, map); |
297 | * // s is now "% Title: /tmp/myfile.txt:My File"; |
298 | * \endcode |
299 | */ |
300 | KCOREADDONS_EXPORT QString expandMacros(const QString &str, const QHash<QChar, QString> &map, QChar c = QLatin1Char('%')); |
301 | |
302 | /** |
303 | * Perform safe macro expansion (substitution) on a string for use |
304 | * in shell commands. |
305 | * The escape char must be quoted with itself to obtain its literal |
306 | * representation in the resulting string. |
307 | * |
308 | * @param str The string to expand |
309 | * @param map map with substitutions |
310 | * @param c escape char indicating start of macro, or QChar::null if none |
311 | * @return the string with all valid macros expanded, or a null string |
312 | * if a shell syntax error was detected in the command |
313 | * |
314 | * \code |
315 | * // Code example |
316 | * QHash<QChar,QString> map; |
317 | * map.insert('u', "/tmp/myfile.txt"); |
318 | * map.insert('n', "My File"); |
319 | * QString s = "kwrite --qwindowtitle %n %u"; |
320 | * s = KMacroExpander::expandMacrosShellQuote(s, map); |
321 | * // s is now "kwrite --qwindowtitle 'My File' '/tmp/myfile.txt'"; |
322 | * system(QFile::encodeName(s)); |
323 | * \endcode |
324 | */ |
325 | KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash<QChar, QString> &map, QChar c = QLatin1Char('%')); |
326 | |
327 | /** |
328 | * Perform safe macro expansion (substitution) on a string. |
329 | * The escape char must be quoted with itself to obtain its literal |
330 | * representation in the resulting string. |
331 | * Macro names can consist of chars in the range [A-Za-z0-9_]; |
332 | * use braces to delimit macros from following words starting |
333 | * with these chars, or to use other chars for macro names. |
334 | * |
335 | * @param str The string to expand |
336 | * @param map map with substitutions |
337 | * @param c escape char indicating start of macro, or QChar::null if none |
338 | * @return the string with all valid macros expanded |
339 | * |
340 | * \code |
341 | * // Code example |
342 | * QHash<QString,QString> map; |
343 | * map.insert("url", "/tmp/myfile.txt"); |
344 | * map.insert("name", "My File"); |
345 | * QString s = "Title: %{url}-%name"; |
346 | * s = KMacroExpander::expandMacros(s, map); |
347 | * // s is now "Title: /tmp/myfile.txt-My File"; |
348 | * \endcode |
349 | */ |
350 | KCOREADDONS_EXPORT QString expandMacros(const QString &str, const QHash<QString, QString> &map, QChar c = QLatin1Char('%')); |
351 | |
352 | /** |
353 | * Perform safe macro expansion (substitution) on a string for use |
354 | * in shell commands. See KMacroExpanderBase::expandMacrosShellQuote() |
355 | * for the exact semantics. |
356 | * The escape char must be quoted with itself to obtain its literal |
357 | * representation in the resulting string. |
358 | * Macro names can consist of chars in the range [A-Za-z0-9_]; |
359 | * use braces to delimit macros from following words starting |
360 | * with these chars, or to use other chars for macro names. |
361 | * |
362 | * @param str The string to expand |
363 | * @param map map with substitutions |
364 | * @param c escape char indicating start of macro, or QChar::null if none |
365 | * @return the string with all valid macros expanded, or a null string |
366 | * if a shell syntax error was detected in the command |
367 | * |
368 | * \code |
369 | * // Code example |
370 | * QHash<QString,QString> map; |
371 | * map.insert("url", "/tmp/myfile.txt"); |
372 | * map.insert("name", "My File"); |
373 | * QString s = "kwrite --qwindowtitle %name %{url}"; |
374 | * s = KMacroExpander::expandMacrosShellQuote(s, map); |
375 | * // s is now "kwrite --qwindowtitle 'My File' '/tmp/myfile.txt'"; |
376 | * system(QFile::encodeName(s)); |
377 | * \endcode |
378 | */ |
379 | KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash<QString, QString> &map, QChar c = QLatin1Char('%')); |
380 | |
381 | /** |
382 | * Same as above, except that the macros expand to string lists that |
383 | * are simply join(" ")ed together. |
384 | */ |
385 | KCOREADDONS_EXPORT QString expandMacros(const QString &str, const QHash<QChar, QStringList> &map, QChar c = QLatin1Char('%')); |
386 | KCOREADDONS_EXPORT QString expandMacros(const QString &str, const QHash<QString, QStringList> &map, QChar c = QLatin1Char('%')); |
387 | |
388 | /** |
389 | * Same as above, except that the macros expand to string lists. |
390 | * If the macro appears inside a quoted string, the list is simply |
391 | * join(" ")ed together; otherwise every element expands to a separate |
392 | * quoted string. |
393 | */ |
394 | KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash<QChar, QStringList> &map, QChar c = QLatin1Char('%')); |
395 | KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash<QString, QStringList> &map, QChar c = QLatin1Char('%')); |
396 | } |
397 | |
398 | #endif /* KMACROEXPANDER_H */ |
399 | |