File indexing completed on 2024-04-28 03:53:48

0001 /*
0002     This file is part of the KDE libraries
0003 
0004     SPDX-FileCopyrightText: 2002-2003 Oswald Buddenhagen <ossi@kde.org>
0005     SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 #ifndef KMACROEXPANDER_H
0010 #define KMACROEXPANDER_H
0011 
0012 #include <QChar>
0013 #include <QStringList>
0014 
0015 #include <kcoreaddons_export.h>
0016 #include <memory>
0017 
0018 class QString;
0019 template<typename KT, typename VT>
0020 class QHash;
0021 class KMacroExpanderBasePrivate;
0022 
0023 /**
0024  * \class KMacroExpanderBase kmacroexpander.h <KMacroExpander>
0025  *
0026  * Abstract base class for the worker classes behind the KMacroExpander namespace
0027  * and the KCharMacroExpander and KWordMacroExpander classes.
0028  *
0029  * @author Oswald Buddenhagen <ossi@kde.org>
0030  */
0031 class KCOREADDONS_EXPORT KMacroExpanderBase
0032 {
0033 public:
0034     /**
0035      * Constructor.
0036      * @param c escape char indicating start of macros, or QChar::null for none
0037      */
0038     explicit KMacroExpanderBase(QChar c = QLatin1Char('%'));
0039 
0040     /**
0041      * Destructor.
0042      */
0043     virtual ~KMacroExpanderBase();
0044 
0045     /**
0046      * Perform safe macro expansion (substitution) on a string.
0047      *
0048      * @param str the string in which macros are expanded in-place
0049      */
0050     void expandMacros(QString &str);
0051 
0052     // TODO: This documentation is relevant for end-users. Where to put it?
0053     /**
0054      * Perform safe macro expansion (substitution) on a string for use
0055      * in shell commands.
0056      *
0057      * <h3>*NIX notes</h3>
0058      *
0059      * Explicitly supported shell constructs:
0060      *   \ '' "" $'' $"" {} () $(()) ${} $() ``
0061      *
0062      * Implicitly supported shell constructs:
0063      *   (())
0064      *
0065      * Unsupported shell constructs that will cause problems:
0066      *  Shortened &quot;<tt>case $v in pat)</tt>&quot; syntax. Use
0067      *   &quot;<tt>case $v in (pat)</tt>&quot; instead.
0068      *
0069      * The rest of the shell (incl. bash) syntax is simply ignored,
0070      * as it is not expected to cause problems.
0071      *
0072      * Note that bash contains a bug which makes macro expansion within
0073      * double quoted substitutions (<tt>"${VAR:-%macro}"</tt>) inherently
0074      * insecure.
0075      *
0076      * For security reasons, @em never put expandos in command line arguments
0077      * that are shell commands by themselves -
0078      * &quot;<tt>sh -c 'foo \%f'</tt>&quot; is taboo.
0079      * &quot;<tt>file=\%f sh -c 'foo "$file"'</tt>&quot; is OK.
0080      *
0081      * <h3>Windows notes</h3>
0082      *
0083      * All quoting syntax supported by KShell is supported here as well.
0084      * Additionally, command grouping via parentheses is recognized - note
0085      * however, that the parser is much stricter about unquoted parentheses
0086      * than cmd itself.
0087      * The rest of the cmd syntax is simply ignored, as it is not expected
0088      * to cause problems - do not use commands that embed other commands,
0089      * though - &quot;<tt>for /f ...</tt>&quot; is taboo.
0090      *
0091      * @param str the string in which macros are expanded in-place
0092      * @param pos the position inside the string at which parsing/substitution
0093      *  should start, and upon exit where processing stopped
0094      * @return false if the string could not be parsed and therefore no safe
0095      *  substitution was possible. Note that macros will have been processed
0096      *  up to the point where the error occurred. An unmatched closing paren
0097      *  or brace outside any shell construct is @em not an error (unlike in
0098      *  the function below), but still prematurely terminates processing.
0099      */
0100     bool expandMacrosShellQuote(QString &str, int &pos);
0101 
0102     /**
0103      * Same as above, but always starts at position 0, and unmatched closing
0104      * parens and braces are treated as errors.
0105      */
0106     bool expandMacrosShellQuote(QString &str);
0107 
0108     /**
0109      * Set the macro escape character.
0110      * @param c escape char indicating start of macros, or QChar::null if none
0111      */
0112     void setEscapeChar(QChar c);
0113 
0114     /**
0115      * Obtain the macro escape character.
0116      * @return escape char indicating start of macros, or QChar::null if none
0117      */
0118     QChar escapeChar() const;
0119 
0120 protected:
0121     /**
0122      * This function is called for every single char within the string if
0123      * the escape char is QChar::null. It should determine whether the
0124      * string starting at @p pos within @p str is a valid macro and return
0125      * the substitution value for it if so.
0126      * @param str the input string
0127      * @param pos the offset within @p str
0128      * @param ret return value: the string to substitute for the macro
0129      * @return If greater than zero, the number of chars at @p pos in @p str
0130      *  to substitute with @p ret (i.e., a valid macro was found). If less
0131      *  than zero, subtract this value from @p pos (to skip a macro, i.e.,
0132      *  substitute it with itself). If zero, no macro starts at @p pos.
0133      */
0134     virtual int expandPlainMacro(const QString &str, int pos, QStringList &ret);
0135 
0136     /**
0137      * This function is called every time the escape char is found if it is
0138      * not QChar::null. It should determine whether the
0139      * string starting at @p pos witin @p str is a valid macro and return
0140      * the substitution value for it if so.
0141      * @param str the input string
0142      * @param pos the offset within @p str. Note that this is the position of
0143      *  the occurrence of the escape char
0144      * @param ret return value: the string to substitute for the macro
0145      * @return If greater than zero, the number of chars at @p pos in @p str
0146      *  to substitute with @p ret (i.e., a valid macro was found). If less
0147      *  than zero, subtract this value from @p pos (to skip a macro, i.e.,
0148      *  substitute it with itself). If zero, scanning continues as if no
0149      *  escape char was encountered at all.
0150      */
0151     virtual int expandEscapedMacro(const QString &str, int pos, QStringList &ret);
0152 
0153 private:
0154     std::unique_ptr<KMacroExpanderBasePrivate> const d;
0155 };
0156 
0157 /**
0158  * \class KWordMacroExpander kmacroexpander.h <KMacroExpanderBase>
0159  *
0160  * Abstract base class for simple word macro substitutors. Use this instead of
0161  * the functions in the KMacroExpander namespace if speculatively pre-filling
0162  * the substitution map would be too expensive.
0163  *
0164  * A typical application:
0165  *
0166  * \code
0167  * class MyClass {
0168  * ...
0169  *   private:
0170  *     QString m_str;
0171  * ...
0172  *   friend class MyExpander;
0173  * };
0174  *
0175  * class MyExpander : public KWordMacroExpander {
0176  *   public:
0177  *     MyExpander( MyClass *_that ) : KWordMacroExpander(), that( _that ) {}
0178  *   protected:
0179  *     virtual bool expandMacro( const QString &str, QStringList &ret );
0180  *   private:
0181  *     MyClass *that;
0182  * };
0183  *
0184  * bool MyExpander::expandMacro( const QString &str, QStringList &ret )
0185  * {
0186  *   if (str == "macro") {
0187  *     ret += complexOperation( that->m_str );
0188  *     return true;
0189  *   }
0190  *   return false;
0191  * }
0192  *
0193  * ... MyClass::...(...)
0194  * {
0195  *   QString str;
0196  *   ...
0197  *   MyExpander mx( this );
0198  *   mx.expandMacrosShellQuote( str );
0199  *   ...
0200  * }
0201  * \endcode
0202  *
0203  * Alternatively MyClass could inherit from KWordMacroExpander directly.
0204  *
0205  * @author Oswald Buddenhagen <ossi@kde.org>
0206  */
0207 class KCOREADDONS_EXPORT KWordMacroExpander : public KMacroExpanderBase
0208 {
0209 public:
0210     /**
0211      * Constructor.
0212      * @param c escape char indicating start of macros, or QChar::null for none
0213      */
0214     explicit KWordMacroExpander(QChar c = QLatin1Char('%'))
0215         : KMacroExpanderBase(c)
0216     {
0217     }
0218 
0219 protected:
0220     /** \internal Not to be called or reimplemented. */
0221     int expandPlainMacro(const QString &str, int pos, QStringList &ret) override;
0222     /** \internal Not to be called or reimplemented. */
0223     int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override;
0224 
0225     /**
0226      * Return substitution list @p ret for string macro @p str.
0227      * @param str the macro to expand
0228      * @param ret return variable reference. It is guaranteed to be empty
0229      *  when expandMacro is entered.
0230      * @return @c true iff @p str was a recognized macro name
0231      */
0232     virtual bool expandMacro(const QString &str, QStringList &ret) = 0;
0233 };
0234 
0235 /**
0236  * \class KCharMacroExpander kmacroexpander.h <KMacroExpanderBase>
0237  *
0238  * Abstract base class for single char macro substitutors. Use this instead of
0239  * the functions in the KMacroExpander namespace if speculatively pre-filling
0240  * the substitution map would be too expensive.
0241  *
0242  * See KWordMacroExpander for a sample application.
0243  *
0244  * @author Oswald Buddenhagen <ossi@kde.org>
0245  */
0246 class KCOREADDONS_EXPORT KCharMacroExpander : public KMacroExpanderBase
0247 {
0248 public:
0249     /**
0250      * Constructor.
0251      * @param c escape char indicating start of macros, or QChar::null for none
0252      */
0253     explicit KCharMacroExpander(QChar c = QLatin1Char('%'))
0254         : KMacroExpanderBase(c)
0255     {
0256     }
0257 
0258 protected:
0259     /** \internal Not to be called or reimplemented. */
0260     int expandPlainMacro(const QString &str, int pos, QStringList &ret) override;
0261     /** \internal Not to be called or reimplemented. */
0262     int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override;
0263 
0264     /**
0265      * Return substitution list @p ret for single-character macro @p chr.
0266      * @param chr the macro to expand
0267      * @param ret return variable reference. It is guaranteed to be empty
0268      *  when expandMacro is entered.
0269      * @return @c true iff @p chr was a recognized macro name
0270      */
0271     virtual bool expandMacro(QChar chr, QStringList &ret) = 0;
0272 };
0273 
0274 /**
0275  * A group of functions providing macro expansion (substitution) in strings,
0276  * optionally with quoting appropriate for shell execution.
0277  */
0278 namespace KMacroExpander
0279 {
0280 /**
0281  * Perform safe macro expansion (substitution) on a string.
0282  * The escape char must be quoted with itself to obtain its literal
0283  * representation in the resulting string.
0284  *
0285  * @param str The string to expand
0286  * @param map map with substitutions
0287  * @param c escape char indicating start of macro, or QChar::null if none
0288  * @return the string with all valid macros expanded
0289  *
0290  * \code
0291  * // Code example
0292  * QHash<QChar,QString> map;
0293  * map.insert('u', "/tmp/myfile.txt");
0294  * map.insert('n', "My File");
0295  * QString s = "%% Title: %u:%n";
0296  * s = KMacroExpander::expandMacros(s, map);
0297  * // s is now "% Title: /tmp/myfile.txt:My File";
0298  * \endcode
0299  */
0300 KCOREADDONS_EXPORT QString expandMacros(const QString &str, const QHash<QChar, QString> &map, QChar c = QLatin1Char('%'));
0301 
0302 /**
0303  * Perform safe macro expansion (substitution) on a string for use
0304  * in shell commands.
0305  * The escape char must be quoted with itself to obtain its literal
0306  * representation in the resulting string.
0307  *
0308  * @param str The string to expand
0309  * @param map map with substitutions
0310  * @param c escape char indicating start of macro, or QChar::null if none
0311  * @return the string with all valid macros expanded, or a null string
0312  *  if a shell syntax error was detected in the command
0313  *
0314  * \code
0315  * // Code example
0316  * QHash<QChar,QString> map;
0317  * map.insert('u', "/tmp/myfile.txt");
0318  * map.insert('n', "My File");
0319  * QString s = "kwrite --qwindowtitle %n %u";
0320  * s = KMacroExpander::expandMacrosShellQuote(s, map);
0321  * // s is now "kwrite --qwindowtitle 'My File' '/tmp/myfile.txt'";
0322  * system(QFile::encodeName(s));
0323  * \endcode
0324  */
0325 KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash<QChar, QString> &map, QChar c = QLatin1Char('%'));
0326 
0327 /**
0328  * Perform safe macro expansion (substitution) on a string.
0329  * The escape char must be quoted with itself to obtain its literal
0330  * representation in the resulting string.
0331  * Macro names can consist of chars in the range [A-Za-z0-9_];
0332  * use braces to delimit macros from following words starting
0333  * with these chars, or to use other chars for macro names.
0334  *
0335  * @param str The string to expand
0336  * @param map map with substitutions
0337  * @param c escape char indicating start of macro, or QChar::null if none
0338  * @return the string with all valid macros expanded
0339  *
0340  * \code
0341  * // Code example
0342  * QHash<QString,QString> map;
0343  * map.insert("url", "/tmp/myfile.txt");
0344  * map.insert("name", "My File");
0345  * QString s = "Title: %{url}-%name";
0346  * s = KMacroExpander::expandMacros(s, map);
0347  * // s is now "Title: /tmp/myfile.txt-My File";
0348  * \endcode
0349  */
0350 KCOREADDONS_EXPORT QString expandMacros(const QString &str, const QHash<QString, QString> &map, QChar c = QLatin1Char('%'));
0351 
0352 /**
0353  * Perform safe macro expansion (substitution) on a string for use
0354  * in shell commands. See KMacroExpanderBase::expandMacrosShellQuote()
0355  * for the exact semantics.
0356  * The escape char must be quoted with itself to obtain its literal
0357  * representation in the resulting string.
0358  * Macro names can consist of chars in the range [A-Za-z0-9_];
0359  * use braces to delimit macros from following words starting
0360  * with these chars, or to use other chars for macro names.
0361  *
0362  * @param str The string to expand
0363  * @param map map with substitutions
0364  * @param c escape char indicating start of macro, or QChar::null if none
0365  * @return the string with all valid macros expanded, or a null string
0366  *  if a shell syntax error was detected in the command
0367  *
0368  * \code
0369  * // Code example
0370  * QHash<QString,QString> map;
0371  * map.insert("url", "/tmp/myfile.txt");
0372  * map.insert("name", "My File");
0373  * QString s = "kwrite --qwindowtitle %name %{url}";
0374  * s = KMacroExpander::expandMacrosShellQuote(s, map);
0375  * // s is now "kwrite --qwindowtitle 'My File' '/tmp/myfile.txt'";
0376  * system(QFile::encodeName(s));
0377  * \endcode
0378  */
0379 KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash<QString, QString> &map, QChar c = QLatin1Char('%'));
0380 
0381 /**
0382  * Same as above, except that the macros expand to string lists that
0383  * are simply join(" ")ed together.
0384  */
0385 KCOREADDONS_EXPORT QString expandMacros(const QString &str, const QHash<QChar, QStringList> &map, QChar c = QLatin1Char('%'));
0386 KCOREADDONS_EXPORT QString expandMacros(const QString &str, const QHash<QString, QStringList> &map, QChar c = QLatin1Char('%'));
0387 
0388 /**
0389  * Same as above, except that the macros expand to string lists.
0390  * If the macro appears inside a quoted string, the list is simply
0391  * join(" ")ed together; otherwise every element expands to a separate
0392  * quoted string.
0393  */
0394 KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash<QChar, QStringList> &map, QChar c = QLatin1Char('%'));
0395 KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash<QString, QStringList> &map, QChar c = QLatin1Char('%'));
0396 }
0397 
0398 #endif /* KMACROEXPANDER_H */