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 }