File indexing completed on 2024-05-12 11:48:03
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 rsts.replace(QRegularExpression(QStringLiteral("([$`\"\\\\])")), QStringLiteral("\\\\1")); 0109 } else if (state.current == dollarquote) { 0110 rsts = rst.join(QLatin1Char(' ')); 0111 rsts.replace(QRegularExpression(QStringLiteral("(['\\\\])")), QStringLiteral("\\\\1")); 0112 } else if (state.current == singlequote) { 0113 rsts = rst.join(QLatin1Char(' ')); 0114 rsts.replace(QLatin1Char('\''), QLatin1String("'\\''")); 0115 } else { 0116 if (rst.isEmpty()) { 0117 str.remove(pos, len); 0118 continue; 0119 } else { 0120 rsts = joinArgs(rst); 0121 } 0122 } 0123 rst.clear(); 0124 str.replace(pos, len, rsts); 0125 pos += rsts.length(); 0126 continue; 0127 nohit: 0128 if (state.current == singlequote) { 0129 if (cc == '\'') { 0130 state = sstack.pop(); 0131 } 0132 } else if (cc == '\\') { 0133 // always swallow the char -> prevent anomalies due to expansion 0134 pos += 2; 0135 continue; 0136 } else if (state.current == dollarquote) { 0137 if (cc == '\'') { 0138 state = sstack.pop(); 0139 } 0140 } else if (cc == '$') { 0141 cc = str.unicode()[++pos].unicode(); 0142 if (cc == '(') { 0143 sstack.push(state); 0144 if (str.unicode()[pos + 1].unicode() == '(') { 0145 Save sav = {str, pos + 2}; 0146 ostack.push(sav); 0147 state.current = math; 0148 pos += 2; 0149 continue; 0150 } else { 0151 state.current = paren; 0152 state.dquote = false; 0153 } 0154 } else if (cc == '{') { 0155 sstack.push(state); 0156 state.current = subst; 0157 } else if (!state.dquote) { 0158 if (cc == '\'') { 0159 sstack.push(state); 0160 state.current = dollarquote; 0161 } else if (cc == '"') { 0162 sstack.push(state); 0163 state.current = doublequote; 0164 state.dquote = true; 0165 } 0166 } 0167 // always swallow the char -> prevent anomalies due to expansion 0168 } else if (cc == '`') { 0169 str.replace(pos, 1, QStringLiteral("$( ")); // add space -> avoid creating $(( 0170 pos2 = pos += 3; 0171 for (;;) { 0172 if (pos2 >= str.length()) { 0173 pos = pos2; 0174 return false; 0175 } 0176 cc = str.unicode()[pos2].unicode(); 0177 if (cc == '`') { 0178 break; 0179 } 0180 if (cc == '\\') { 0181 cc = str.unicode()[++pos2].unicode(); 0182 if (cc == '$' || cc == '`' || cc == '\\' || (cc == '"' && state.dquote)) { 0183 str.remove(pos2 - 1, 1); 0184 continue; 0185 } 0186 } 0187 pos2++; 0188 } 0189 str[pos2] = QLatin1Char(')'); 0190 sstack.push(state); 0191 state.current = paren; 0192 state.dquote = false; 0193 continue; 0194 } else if (state.current == doublequote) { 0195 if (cc == '"') { 0196 state = sstack.pop(); 0197 } 0198 } else if (cc == '\'') { 0199 if (!state.dquote) { 0200 sstack.push(state); 0201 state.current = singlequote; 0202 } 0203 } else if (cc == '"') { 0204 if (!state.dquote) { 0205 sstack.push(state); 0206 state.current = doublequote; 0207 state.dquote = true; 0208 } 0209 } else if (state.current == subst) { 0210 if (cc == '}') { 0211 state = sstack.pop(); 0212 } 0213 } else if (cc == ')') { 0214 if (state.current == math) { 0215 if (str.unicode()[pos + 1].unicode() == ')') { 0216 state = sstack.pop(); 0217 pos += 2; 0218 } else { 0219 // false hit: the $(( was a $( ( in fact 0220 // ash does not care, but bash does 0221 pos = ostack.top().pos; 0222 str = ostack.top().str; 0223 ostack.pop(); 0224 state.current = paren; 0225 state.dquote = false; 0226 sstack.push(state); 0227 } 0228 continue; 0229 } else if (state.current == paren) { 0230 state = sstack.pop(); 0231 } else { 0232 break; 0233 } 0234 } else if (cc == '}') { 0235 if (state.current == KMacroExpander::group) { 0236 state = sstack.pop(); 0237 } else { 0238 break; 0239 } 0240 } else if (cc == '(') { 0241 sstack.push(state); 0242 state.current = paren; 0243 } else if (cc == '{') { 0244 sstack.push(state); 0245 state.current = KMacroExpander::group; 0246 } 0247 pos++; 0248 } 0249 return sstack.empty(); 0250 }