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 
0010 #include "kmacroexpander_p.h"
0011 
0012 #include <QRegularExpression>
0013 #include <QStack>
0014 #include <QStringList>
0015 
0016 namespace KMacroExpander
0017 {
0018 enum Quoting {
0019     noquote,
0020     singlequote,
0021     doublequote,
0022     dollarquote,
0023     paren,
0024     subst,
0025     group,
0026     math,
0027 };
0028 typedef struct {
0029     Quoting current;
0030     bool dquote;
0031 } State;
0032 typedef struct {
0033     QString str;
0034     int pos;
0035 } Save;
0036 
0037 }
0038 
0039 using namespace KMacroExpander;
0040 
0041 // #pragma message("TODO: Import these methods into Qt")
0042 
0043 inline static bool isSpecial(QChar cUnicode)
0044 {
0045     static const uchar iqm[] = {0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78}; // 0-32 \'"$`<>|;&(){}*?#!~[]
0046 
0047     uint c = cUnicode.unicode();
0048     return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
0049 }
0050 
0051 static QString quoteArg(const QString &arg)
0052 {
0053     if (!arg.length()) {
0054         return QStringLiteral("''");
0055     }
0056     for (int i = 0; i < arg.length(); i++) {
0057         if (isSpecial(arg.unicode()[i])) {
0058             QChar q(QLatin1Char('\''));
0059             return q + QString(arg).replace(q, QLatin1String("'\\''")) + q;
0060         }
0061     }
0062     return arg;
0063 }
0064 
0065 static QString joinArgs(const QStringList &args)
0066 {
0067     QString ret;
0068     for (QStringList::ConstIterator it = args.begin(); it != args.end(); ++it) {
0069         if (!ret.isEmpty()) {
0070             ret.append(QLatin1Char(' '));
0071         }
0072         ret.append(quoteArg(*it));
0073     }
0074     return ret;
0075 }
0076 
0077 bool KMacroExpanderBase::expandMacrosShellQuote(QString &str, int &pos)
0078 {
0079     int len;
0080     int pos2;
0081     ushort ec = d->escapechar.unicode();
0082     State state = {noquote, false};
0083     QStack<State> sstack;
0084     QStack<Save> ostack;
0085     QStringList rst;
0086     QString rsts;
0087 
0088     while (pos < str.length()) {
0089         ushort cc = str.unicode()[pos].unicode();
0090         if (ec != 0) {
0091             if (cc != ec) {
0092                 goto nohit;
0093             }
0094             if (!(len = expandEscapedMacro(str, pos, rst))) {
0095                 goto nohit;
0096             }
0097         } else {
0098             if (!(len = expandPlainMacro(str, pos, rst))) {
0099                 goto nohit;
0100             }
0101         }
0102         if (len < 0) {
0103             pos -= len;
0104             continue;
0105         }
0106         if (state.dquote) {
0107             rsts = rst.join(QLatin1Char(' '));
0108             const static QRegularExpression regex(QStringLiteral("([$`\"\\\\])"));
0109             rsts.replace(regex, QStringLiteral("\\\\1"));
0110         } else if (state.current == dollarquote) {
0111             rsts = rst.join(QLatin1Char(' '));
0112             const static QRegularExpression regex(QStringLiteral("(['\\\\])"));
0113             rsts.replace(regex, QStringLiteral("\\\\1"));
0114         } else if (state.current == singlequote) {
0115             rsts = rst.join(QLatin1Char(' '));
0116             rsts.replace(QLatin1Char('\''), QLatin1String("'\\''"));
0117         } else {
0118             if (rst.isEmpty()) {
0119                 str.remove(pos, len);
0120                 continue;
0121             } else {
0122                 rsts = joinArgs(rst);
0123             }
0124         }
0125         rst.clear();
0126         str.replace(pos, len, rsts);
0127         pos += rsts.length();
0128         continue;
0129     nohit:
0130         if (state.current == singlequote) {
0131             if (cc == '\'') {
0132                 state = sstack.pop();
0133             }
0134         } else if (cc == '\\') {
0135             // always swallow the char -> prevent anomalies due to expansion
0136             pos += 2;
0137             continue;
0138         } else if (state.current == dollarquote) {
0139             if (cc == '\'') {
0140                 state = sstack.pop();
0141             }
0142         } else if (cc == '$') {
0143             cc = str.unicode()[++pos].unicode();
0144             if (cc == '(') {
0145                 sstack.push(state);
0146                 if (str.unicode()[pos + 1].unicode() == '(') {
0147                     Save sav = {str, pos + 2};
0148                     ostack.push(sav);
0149                     state.current = math;
0150                     pos += 2;
0151                     continue;
0152                 } else {
0153                     state.current = paren;
0154                     state.dquote = false;
0155                 }
0156             } else if (cc == '{') {
0157                 sstack.push(state);
0158                 state.current = subst;
0159             } else if (!state.dquote) {
0160                 if (cc == '\'') {
0161                     sstack.push(state);
0162                     state.current = dollarquote;
0163                 } else if (cc == '"') {
0164                     sstack.push(state);
0165                     state.current = doublequote;
0166                     state.dquote = true;
0167                 }
0168             }
0169             // always swallow the char -> prevent anomalies due to expansion
0170         } else if (cc == '`') {
0171             str.replace(pos, 1, QStringLiteral("$( ")); // add space -> avoid creating $((
0172             pos2 = pos += 3;
0173             for (;;) {
0174                 if (pos2 >= str.length()) {
0175                     pos = pos2;
0176                     return false;
0177                 }
0178                 cc = str.unicode()[pos2].unicode();
0179                 if (cc == '`') {
0180                     break;
0181                 }
0182                 if (cc == '\\') {
0183                     cc = str.unicode()[++pos2].unicode();
0184                     if (cc == '$' || cc == '`' || cc == '\\' || (cc == '"' && state.dquote)) {
0185                         str.remove(pos2 - 1, 1);
0186                         continue;
0187                     }
0188                 }
0189                 pos2++;
0190             }
0191             str[pos2] = QLatin1Char(')');
0192             sstack.push(state);
0193             state.current = paren;
0194             state.dquote = false;
0195             continue;
0196         } else if (state.current == doublequote) {
0197             if (cc == '"') {
0198                 state = sstack.pop();
0199             }
0200         } else if (cc == '\'') {
0201             if (!state.dquote) {
0202                 sstack.push(state);
0203                 state.current = singlequote;
0204             }
0205         } else if (cc == '"') {
0206             if (!state.dquote) {
0207                 sstack.push(state);
0208                 state.current = doublequote;
0209                 state.dquote = true;
0210             }
0211         } else if (state.current == subst) {
0212             if (cc == '}') {
0213                 state = sstack.pop();
0214             }
0215         } else if (cc == ')') {
0216             if (state.current == math) {
0217                 if (str.unicode()[pos + 1].unicode() == ')') {
0218                     state = sstack.pop();
0219                     pos += 2;
0220                 } else {
0221                     // false hit: the $(( was a $( ( in fact
0222                     // ash does not care, but bash does
0223                     pos = ostack.top().pos;
0224                     str = ostack.top().str;
0225                     ostack.pop();
0226                     state.current = paren;
0227                     state.dquote = false;
0228                     sstack.push(state);
0229                 }
0230                 continue;
0231             } else if (state.current == paren) {
0232                 state = sstack.pop();
0233             } else {
0234                 break;
0235             }
0236         } else if (cc == '}') {
0237             if (state.current == KMacroExpander::group) {
0238                 state = sstack.pop();
0239             } else {
0240                 break;
0241             }
0242         } else if (cc == '(') {
0243             sstack.push(state);
0244             state.current = paren;
0245         } else if (cc == '{') {
0246             sstack.push(state);
0247             state.current = KMacroExpander::group;
0248         }
0249         pos++;
0250     }
0251     return sstack.empty();
0252 }