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
18class QString;
19template<typename KT, typename VT>
20class QHash;
21class 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 */
31class KCOREADDONS_EXPORT KMacroExpanderBase
32{
33public:
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 &quot;<tt>case $v in pat)</tt>&quot; syntax. Use
67 * &quot;<tt>case $v in (pat)</tt>&quot; 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 * &quot;<tt>sh -c 'foo \%f'</tt>&quot; is taboo.
79 * &quot;<tt>file=\%f sh -c 'foo "$file"'</tt>&quot; 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 - &quot;<tt>for /f ...</tt>&quot; 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
120protected:
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
153private:
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 */
207class KCOREADDONS_EXPORT KWordMacroExpander : public KMacroExpanderBase
208{
209public:
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
219protected:
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 */
246class KCOREADDONS_EXPORT KCharMacroExpander : public KMacroExpanderBase
247{
248public:
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
258protected:
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 */
278namespace 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 */
300KCOREADDONS_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 */
325KCOREADDONS_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 */
350KCOREADDONS_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 */
379KCOREADDONS_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 */
385KCOREADDONS_EXPORT QString expandMacros(const QString &str, const QHash<QChar, QStringList> &map, QChar c = QLatin1Char('%'));
386KCOREADDONS_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 */
394KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash<QChar, QStringList> &map, QChar c = QLatin1Char('%'));
395KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash<QString, QStringList> &map, QChar c = QLatin1Char('%'));
396}
397
398#endif /* KMACROEXPANDER_H */
399

source code of kcoreaddons/src/lib/text/kmacroexpander.h