diff --git a/src/shared/proparser/ioutils.cpp b/src/shared/proparser/ioutils.cpp index e4a0e365696c166b6141afb64c2657bc2f1abf6c..d17ddff8d22d9d41009307c719114e9f98ffb92b 100644 --- a/src/shared/proparser/ioutils.cpp +++ b/src/shared/proparser/ioutils.cpp @@ -44,6 +44,7 @@ using namespace ProFileEvaluatorInternal; IoUtils::FileType IoUtils::fileType(const QString &fileName) { + Q_ASSERT(fileName.isEmpty() || isAbsolutePath(fileName)); #ifdef Q_OS_WIN DWORD attr = GetFileAttributesW((WCHAR*)fileName.constData()); if (attr == INVALID_FILE_ATTRIBUTES) @@ -86,3 +87,116 @@ QString IoUtils::resolvePath(const QString &baseDir, const QString &fileName) return QDir::cleanPath(fileName); return QDir::cleanPath(baseDir + QLatin1Char('/') + fileName); } + +#ifdef Q_OS_WIN + +// FIXME: Without this, quoting is not foolproof. But it needs support in the process setup, etc. +//#define PERCENT_ESCAPE QLatin1String("%PERCENT_SIGN%") + +static QString quoteArgInternal(const QString &arg) +{ + // Escape quotes, preceding backslashes are doubled. Surround with quotes. + // Note that cmd does not understand quote escapes in quoted strings, + // so the quoting needs to be "suspended". + const QLatin1Char bs('\\'), dq('"'); + QString ret; + bool inquote = false; + int bslashes = 0; + for (int p = 0; p < arg.length(); p++) { + if (arg[p] == bs) { + bslashes++; + } else if (arg[p] == dq) { + if (inquote) { + ret.append(dq); + inquote = false; + } + for (; bslashes; bslashes--) + ret.append(QLatin1String("\\\\")); + ret.append(QLatin1String("\\^\"")); + } else { + if (!inquote) { + ret.append(dq); + inquote = true; + } + for (; bslashes; bslashes--) + ret.append(bs); + ret.append(arg[p]); + } + } + //ret.replace(QLatin1Char('%'), PERCENT_ESCAPE); + if (bslashes) { + // Ensure that we don't have directly trailing backslashes, + // so concatenating with another string won't cause surprises. + if (!inquote) + ret.append(dq); + for (; bslashes; bslashes--) + ret.append(QLatin1String("\\\\")); + ret.append(dq); + } else if (inquote) { + ret.append(dq); + } + return ret; +} + +inline static bool isSpecialChar(ushort c) +{ + // Chars that should be quoted (TM). This includes: + // - control chars & space + // - the shell meta chars &()<>^| + // - the potential separators ,;= + static const uchar iqm[] = { + 0xff, 0xff, 0xff, 0xff, 0x41, 0x13, 0x00, 0x78, + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10 + }; + + return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))); +} + +QString IoUtils::shellQuote(const QString &arg) +{ + if (arg.isEmpty()) + return QString::fromLatin1("\"\""); + + // Ensure that we don't have directly trailing backslashes, + // so concatenating with another string won't cause surprises. + if (arg.endsWith(QLatin1Char('\\'))) + return quoteArgInternal(arg); + + for (int x = arg.length() - 1; x >= 0; --x) + if (isSpecialChar(arg[x].unicode())) + return quoteArgInternal(arg); + + // Escape quotes. Preceding backslashes are doubled. + // Note that the remaining string is not quoted. + QString ret(arg); + ret.replace(QRegExp(QLatin1String("(\\\\*)\"")), QLatin1String("\\1\\1\\^\"")); + //ret.replace('%', PERCENT_ESCAPE); + return ret; +} + +#else // Q_OS_WIN + +inline static bool isSpecial(const QChar &cUnicode) +{ + static const uchar iqm[] = { + 0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8, + 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78 + }; // 0-32 \'"$`<>|;&(){}*?#!~[] + + uint c = cUnicode.unicode(); + return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))); +} + +QString IoUtils::shellQuote(const QString &arg) +{ + if (!arg.length()) + return QString::fromLatin1("''"); + for (int i = 0; i < arg.length(); i++) + if (isSpecial(arg.unicode()[i])) { + const QLatin1Char q('\''); + return q + QString(arg).replace(q, QLatin1String("'\\''")) + q; + } + return arg; +} + +#endif // Q_OS_WIN diff --git a/src/shared/proparser/ioutils.h b/src/shared/proparser/ioutils.h index 4655649f4e7734bd8c03e4119edacef55d4e8724..402612601eac3f7986b921478108dce9e37c07dd 100644 --- a/src/shared/proparser/ioutils.h +++ b/src/shared/proparser/ioutils.h @@ -52,6 +52,7 @@ public: static bool isAbsolutePath(const QString &fileName) { return !isRelativePath(fileName); } static QStringRef fileName(const QString &fileName); // Requires normalized path static QString resolvePath(const QString &baseDir, const QString &fileName); + static QString shellQuote(const QString &arg); }; } diff --git a/src/shared/proparser/profileevaluator.cpp b/src/shared/proparser/profileevaluator.cpp index 71101e41b9239992bf1c933b3b79db7f61c8dfe7..92776f3d69cc76b53a4e2274166d6b619d91d4a3 100644 --- a/src/shared/proparser/profileevaluator.cpp +++ b/src/shared/proparser/profileevaluator.cpp @@ -282,7 +282,6 @@ public: bool m_hadCondition; // Nested calls set it on return, so no need for it to be in State int m_skipLevel; bool m_cumulative; - QStack<QString> m_oldPathStack; // To restore the current path to the path QStack<ProFile*> m_profileStack; // To handle 'include(a.pri), so we can track back to 'a.pro' when finished with 'a.pri' struct ProLoop { QString variable; @@ -1051,12 +1050,6 @@ ProItem::ProItemReturn ProFileEvaluator::Private::visitProFile(ProFile *pro) { m_lineNo = pro->lineNumber(); - m_oldPathStack.push(QDir::currentPath()); - if (!QDir::setCurrent(pro->directoryName())) { - m_oldPathStack.pop(); - return ProItem::ReturnFalse; - } - m_profileStack.push(pro); if (m_profileStack.count() == 1) { // Do this only for the initial profile we visit, since @@ -1114,7 +1107,8 @@ ProItem::ProItemReturn ProFileEvaluator::Private::visitProFile(ProFile *pro) } if (IoUtils::isRelativePath(qmakespec)) { - if (IoUtils::exists(qmakespec + QLatin1String("/qmake.conf"))) { + if (IoUtils::exists(currentDirectory() + QLatin1Char('/') + qmakespec + + QLatin1String("/qmake.conf"))) { qmakespec = currentDirectory() + QLatin1Char('/') + qmakespec; } else if (!m_outputDir.isEmpty() && IoUtils::exists(m_outputDir + QLatin1Char('/') + qmakespec @@ -1199,7 +1193,7 @@ ProItem::ProItemReturn ProFileEvaluator::Private::visitProFile(ProFile *pro) } m_profileStack.pop(); - return returnBool(QDir::setCurrent(m_oldPathStack.pop())); + return ProItem::ReturnTrue; } ProItem::ProItemReturn ProFileEvaluator::Private::visitProFunction(ProFunction *func) @@ -1927,7 +1921,7 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun if (args.count() > 1) singleLine = (!args[1].compare(QLatin1String("true"), Qt::CaseInsensitive)); - QFile qfile(file); + QFile qfile(resolvePath(file)); if (qfile.open(QIODevice::ReadOnly)) { QTextStream stream(&qfile); while (!stream.atEnd()) { @@ -1981,7 +1975,9 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun logMessage(format("system(execute) requires one or two arguments.")); } else { char buff[256]; - FILE *proc = QT_POPEN(args[0].toLatin1(), "r"); + FILE *proc = QT_POPEN((QLatin1String("cd ") + + IoUtils::shellQuote(currentDirectory()) + + QLatin1String(" && ") + args[0]).toLatin1(), "r"); bool singleLine = true; if (args.count() > 1) singleLine = (!args[1].compare(QLatin1String("true"), Qt::CaseInsensitive)); @@ -2068,20 +2064,20 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun if (args.count() == 2) recursive = (!args[1].compare(QLatin1String("true"), Qt::CaseInsensitive) || args[1].toInt()); QStringList dirs; - QString r = fixPathToLocalOS(args[0]); + QString r = fixPathToLocalOS(resolvePath(args[0])); int slash = r.lastIndexOf(QDir::separator()); if (slash != -1) { dirs.append(r.left(slash)); r = r.mid(slash+1); } else { - dirs.append(QString()); + dirs.append(fixPathToLocalOS(currentDirectory())); } const QRegExp regex(r, Qt::CaseSensitive, QRegExp::Wildcard); for (int d = 0; d < dirs.count(); d++) { QString dir = dirs[d]; - if (!dir.isEmpty() && !dir.endsWith(m_option->dir_sep)) - dir += QLatin1Char('/'); + if (!dir.endsWith(QDir::separator())) + dir += QDir::separator(); QDir qdir(dir); for (int i = 0; i < (int)qdir.count(); ++i) { @@ -2617,7 +2613,9 @@ ProItem::ProItemReturn ProFileEvaluator::Private::evaluateConditionalFunction( logMessage(format("system(exec) requires one argument.")); ProItem::ReturnFalse; } - return returnBool(system(args.first().toLatin1().constData()) == 0); + return returnBool(system((QLatin1String("cd ") + + IoUtils::shellQuote(currentDirectory()) + + QLatin1String(" && ") + args.first()).toLatin1().constData()) == 0); } #endif case T_ISEMPTY: { @@ -2641,7 +2639,7 @@ ProItem::ProItemReturn ProFileEvaluator::Private::evaluateConditionalFunction( return ProItem::ReturnFalse; } QString file = args.first(); - file = fixPathToLocalOS(file); + file = fixPathToLocalOS(resolvePath(file)); if (IoUtils::exists(file)) { return ProItem::ReturnTrue; @@ -2871,7 +2869,7 @@ bool ProFileEvaluator::Private::evaluateFeatureFile( if (!fn.endsWith(QLatin1String(".prf"))) fn += QLatin1String(".prf"); - if (!fileName.contains((ushort)'/') || !IoUtils::exists(fn)) { + if (!fileName.contains((ushort)'/') || !IoUtils::exists(resolvePath(fn))) { if (m_option->feature_roots.isEmpty()) m_option->feature_roots = qmakeFeaturePaths(); int start_root = 0;