From a03dc073c155b9ac82e17a49e68ada61a4d4605b Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Fri, 11 Apr 2014 11:48:15 +0200 Subject: [PATCH] androiddeployqt: Escape arguments to shell tools Code is borrowed from qmake to escape and quote arguments passed to shell tools as needed. Task-number: QTBUG-38249 Change-Id: I4932df3963b0c1706374b4ba78c5e23c8e3304d2 Reviewed-by: Christian Stromme --- src/androiddeployqt/main.cpp | 146 ++++++++++++++++++++++++++++++++----------- 1 file changed, 109 insertions(+), 37 deletions(-) diff --git a/src/androiddeployqt/main.cpp b/src/androiddeployqt/main.cpp index de8cf13..02eb063 100644 --- a/src/androiddeployqt/main.cpp +++ b/src/androiddeployqt/main.cpp @@ -175,6 +175,78 @@ struct Options QStringList features; }; +// Copy-pasted from qmake/library/ioutil.cpp +inline static bool hasSpecialChars(const QString &arg, const uchar (&iqm)[16]) +{ + for (int x = arg.length() - 1; x >= 0; --x) { + ushort c = arg.unicode()[x].unicode(); + if ((c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)))) + return true; + } + return false; +} + +static QString shellQuoteUnix(const QString &arg) +{ + // Chars that should be quoted (TM). This includes: + static const uchar iqm[] = { + 0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8, + 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78 + }; // 0-32 \'"$`<>|;&(){}*?#!~[] + + if (!arg.length()) + return QString::fromLatin1("\"\""); + + QString ret(arg); + if (hasSpecialChars(ret, iqm)) { + ret.replace(QLatin1Char('\''), QLatin1String("'\\''")); + ret.prepend(QLatin1Char('\'')); + ret.append(QLatin1Char('\'')); + } + return ret; +} + +static QString shellQuoteWin(const QString &arg) +{ + // 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, 0x45, 0x13, 0x00, 0x78, + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10 + }; + + if (!arg.length()) + return QString::fromLatin1("\"\""); + + QString ret(arg); + if (hasSpecialChars(ret, iqm)) { + // Quotes are escaped and their preceding backslashes are doubled. + // It's impossible to escape anything inside a quoted string on cmd + // level, so the outer quoting must be "suspended". + ret.replace(QRegExp(QLatin1String("(\\\\*)\"")), QLatin1String("\"\\1\\1\\^\"\"")); + // The argument must not end with a \ since this would be interpreted + // as escaping the quote -- rather put the \ behind the quote: e.g. + // rather use "foo"\ than "foo\" + int i = ret.length(); + while (i > 0 && ret.at(i - 1) == QLatin1Char('\\')) + --i; + ret.insert(i, QLatin1Char('"')); + ret.prepend(QLatin1Char('"')); + } + return ret; +} + +static QString shellQuote(const QString &arg) +{ + if (QDir::separator() == QLatin1Char('\\')) + return shellQuoteWin(arg); + else + return shellQuoteUnix(arg); +} + + Options parseOptions() { Options options; @@ -1185,7 +1257,7 @@ QStringList getQtLibsFromElf(const Options &options, const QString &fileName) return QStringList(); } - readElf = QString::fromLatin1("\"%1\" -d -W %2").arg(readElf).arg(fileName); + readElf = QString::fromLatin1("%1 -d -W %2").arg(shellQuote(readElf)).arg(shellQuote(fileName)); FILE *readElfCommand = popen(readElf.toLocal8Bit().constData(), "r"); if (readElfCommand == 0) { @@ -1329,7 +1401,7 @@ bool stripFile(const Options &options, const QString &fileName) return false; } - strip = QString::fromLatin1("\"%1\" %2").arg(strip).arg(fileName); + strip = QString::fromLatin1("%1 %2").arg(shellQuote(strip)).arg(shellQuote(fileName)); FILE *stripCommand = popen(strip.toLocal8Bit().constData(), "r"); if (stripCommand == 0) { @@ -1405,9 +1477,9 @@ FILE *runAdb(const Options &options, const QString &arguments) } QString installOption; if (!options.installLocation.isEmpty()) - installOption = QLatin1String(" -s ") + options.installLocation; + installOption = QLatin1String(" -s ") + shellQuote(options.installLocation); - adb = QString::fromLatin1("\"%1\"%2 %3").arg(adb).arg(installOption).arg(arguments); + adb = QString::fromLatin1("%1%2 %3").arg(shellQuote(adb)).arg(installOption).arg(arguments); if (options.verbose) fprintf(stdout, "Running command \"%s\"\n", adb.toLocal8Bit().constData()); @@ -1425,7 +1497,7 @@ bool fetchRemoteModifications(Options *options, const QString &directory) { options->fetchedRemoteModificationDates = true; - FILE *adbCommand = runAdb(*options, QLatin1String(" shell cat ") + directory + QLatin1String("/modification.txt")); + FILE *adbCommand = runAdb(*options, QLatin1String(" shell cat ") + shellQuote(directory + QLatin1String("/modification.txt"))); if (adbCommand == 0) return false; @@ -1437,7 +1509,7 @@ bool fetchRemoteModifications(Options *options, const QString &directory) pclose(adbCommand); if (options->qtInstallDirectory != qtPath) { - adbCommand = runAdb(*options, QLatin1String(" shell rm -r ") + directory); + adbCommand = runAdb(*options, QLatin1String(" shell rm -r ") + shellQuote(directory)); if (options->verbose) { fprintf(stdout, " -- Removing old Qt libs.\n"); while (fgets(buffer, sizeof(buffer), adbCommand) != 0) @@ -1446,7 +1518,7 @@ bool fetchRemoteModifications(Options *options, const QString &directory) pclose(adbCommand); } - adbCommand = runAdb(*options, QLatin1String(" ls ") + directory); + adbCommand = runAdb(*options, QLatin1String(" ls ") + shellQuote(directory)); if (adbCommand == 0) return false; @@ -1639,10 +1711,10 @@ bool createAndroidProject(const Options &options) return false; } - androidTool = QString::fromLatin1("\"%1\" update project --path %2 --target %3 --name QtApp") - .arg(androidTool) - .arg(options.outputDirectory) - .arg(options.androidPlatform); + androidTool = QString::fromLatin1("%1 update project --path %2 --target %3 --name QtApp") + .arg(shellQuote(androidTool)) + .arg(shellQuote(options.outputDirectory)) + .arg(shellQuote(options.androidPlatform)); if (options.verbose) fprintf(stdout, " -- Command: %s\n", qPrintable(androidTool)); @@ -1705,7 +1777,7 @@ bool buildAndroidProject(const Options &options) return false; } - QString ant = QString::fromLatin1("\"%1\" %2").arg(antTool).arg(options.releasePackage ? QLatin1String(" release") : QLatin1String(" debug")); + QString ant = QString::fromLatin1("%1 %2").arg(shellQuote(antTool)).arg(options.releasePackage ? QLatin1String(" release") : QLatin1String(" debug")); FILE *antCommand = popen(ant.toLocal8Bit().constData(), "r"); if (antCommand == 0) { @@ -1739,7 +1811,7 @@ bool uninstallApk(const Options &options) fprintf(stdout, "Uninstalling old Android package %s if present.\n", qPrintable(options.packageName)); - FILE *adbCommand = runAdb(options, QLatin1String(" uninstall ") + options.packageName); + FILE *adbCommand = runAdb(options, QLatin1String(" uninstall ") + shellQuote(options.packageName)); if (adbCommand == 0) return false; @@ -1781,10 +1853,10 @@ bool installApk(const Options &options) FILE *adbCommand = runAdb(options, QLatin1String(" install -r ") - + options.outputDirectory - + QLatin1String("/bin/") - + apkName(options) - + QLatin1String(".apk")); + + shellQuote(options.outputDirectory) + + shellQuote(QLatin1String("/bin/") + + apkName(options) + + QLatin1String(".apk"))); if (adbCommand == 0) return false; @@ -1864,29 +1936,29 @@ bool signPackage(const Options &options) return false; } - jarSignerTool = QString::fromLatin1("\"%1\" -sigalg \"%2\" -digestalg \"%3\" -keystore \"%4\"") - .arg(jarSignerTool).arg(options.sigAlg).arg(options.digestAlg).arg(options.keyStore); + jarSignerTool = QString::fromLatin1("%1 -sigalg %2 -digestalg %3 -keystore %4") + .arg(shellQuote(jarSignerTool)).arg(shellQuote(options.sigAlg)).arg(shellQuote(options.digestAlg)).arg(shellQuote(options.keyStore)); if (!options.keyStorePassword.isEmpty()) - jarSignerTool += QString::fromLatin1(" -storepass \"%1\"").arg(options.keyStorePassword); + jarSignerTool += QString::fromLatin1(" -storepass %1").arg(shellQuote(options.keyStorePassword)); if (!options.storeType.isEmpty()) - jarSignerTool += QString::fromLatin1(" -storetype \"%1\"").arg(options.storeType); + jarSignerTool += QString::fromLatin1(" -storetype %1").arg(shellQuote(options.storeType)); if (!options.keyPass.isEmpty()) - jarSignerTool += QString::fromLatin1(" -keypass \"%1\"").arg(options.keyPass); + jarSignerTool += QString::fromLatin1(" -keypass %1").arg(shellQuote(options.keyPass)); if (!options.sigFile.isEmpty()) - jarSignerTool += QString::fromLatin1(" -sigfile \"%1\"").arg(options.sigFile); + jarSignerTool += QString::fromLatin1(" -sigfile %1").arg(shellQuote(options.sigFile)); if (!options.signedJar.isEmpty()) - jarSignerTool += QString::fromLatin1(" -signedjar \"%1\"").arg(options.signedJar); + jarSignerTool += QString::fromLatin1(" -signedjar %1").arg(shellQuote(options.signedJar)); if (!options.tsaUrl.isEmpty()) - jarSignerTool += QString::fromLatin1(" -tsa \"%1\"").arg(options.tsaUrl); + jarSignerTool += QString::fromLatin1(" -tsa %1").arg(shellQuote(options.tsaUrl)); if (!options.tsaCert.isEmpty()) - jarSignerTool += QString::fromLatin1(" -tsacert \"%1\"").arg(options.tsaCert); + jarSignerTool += QString::fromLatin1(" -tsacert %1").arg(shellQuote(options.tsaCert)); if (options.internalSf) jarSignerTool += QLatin1String(" -internalsf"); @@ -1897,12 +1969,12 @@ bool signPackage(const Options &options) if (options.protectedAuthenticationPath) jarSignerTool += QLatin1String(" -protected"); - jarSignerTool += QString::fromLatin1(" %1 \"%2\"") - .arg(options.outputDirectory + jarSignerTool += QString::fromLatin1(" %1 %2") + .arg(shellQuote(options.outputDirectory + QLatin1String("/bin/") + apkName(options) - + QLatin1String("-unsigned.apk")) - .arg(options.keyStoreAlias); + + QLatin1String("-unsigned.apk"))) + .arg(shellQuote(options.keyStoreAlias)); FILE *jarSignerCommand = popen(jarSignerTool.toLocal8Bit().constData(), "r"); if (jarSignerCommand == 0) { @@ -1934,17 +2006,17 @@ bool signPackage(const Options &options) return false; } - zipAlignTool = QString::fromLatin1("\"%1\"%2 -f 4 %3 %4") - .arg(zipAlignTool) + zipAlignTool = QString::fromLatin1("%1%2 -f 4 %3 %4") + .arg(shellQuote(zipAlignTool)) .arg(options.verbose ? QString::fromLatin1(" -v") : QString()) - .arg(options.outputDirectory + .arg(shellQuote(options.outputDirectory + QLatin1String("/bin/") + apkName(options) - + QLatin1String("-unsigned.apk ")) - .arg(options.outputDirectory + + QLatin1String("-unsigned.apk"))) + .arg(shellQuote(options.outputDirectory + QLatin1String("/bin/") + apkName(options) - + QLatin1String(".apk")); + + QLatin1String(".apk"))); FILE *zipAlignCommand = popen(zipAlignTool.toLocal8Bit(), "r"); if (zipAlignCommand == 0) { @@ -2003,7 +2075,7 @@ bool deployAllToLocalTmp(const Options &options) { FILE *adbCommand = runAdb(options, QString::fromLatin1(" push %1 /data/local/tmp/qt/") - .arg(options.temporaryDirectoryName)); + .arg(shellQuote(options.temporaryDirectoryName))); if (adbCommand == 0) return false; -- 2.7.4