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

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