add $$shell_quote() function
authorOswald Buddenhagen <oswald.buddenhagen@nokia.com>
Fri, 20 Apr 2012 13:40:11 +0000 (15:40 +0200)
committerOswald Buddenhagen <oswald.buddenhagen@nokia.com>
Tue, 19 Jun 2012 14:39:57 +0000 (16:39 +0200)
to be used in system() calls and when assembling EXTRA_COMPILER and
INSTALLS .commands by hand.

Change-Id: Id706cd56aa267a9fb4b14e3416692b4716fafa5b
Reviewed-by: Marius Storm-Olsen <marius.storm-olsen@nokia.com>
qmake/project.cpp
tests/auto/tools/qmake/testdata/functions/functions.pro

index b26796b..935400e 100644 (file)
@@ -82,7 +82,8 @@ enum ExpandFunc { E_MEMBER=1, E_FIRST, E_LAST, E_CAT, E_FROMFILE, E_EVAL, E_LIST
                   E_FIND, E_SYSTEM, E_UNIQUE, E_QUOTE, E_ESCAPE_EXPAND,
                   E_UPPER, E_LOWER, E_FILES, E_PROMPT, E_RE_ESCAPE, E_VAL_ESCAPE, E_REPLACE,
                   E_SIZE, E_SORT_DEPENDS, E_RESOLVE_DEPENDS, E_ENUMERATE_VARS,
-                  E_SHADOWED, E_ABSOLUTE_PATH, E_RELATIVE_PATH, E_CLEAN_PATH, E_NATIVE_PATH };
+                  E_SHADOWED, E_ABSOLUTE_PATH, E_RELATIVE_PATH, E_CLEAN_PATH, E_NATIVE_PATH,
+                  E_SHELL_QUOTE };
 QHash<QString, ExpandFunc> qmake_expandFunctions()
 {
     static QHash<QString, ExpandFunc> *qmake_expand_functions = 0;
@@ -124,6 +125,7 @@ QHash<QString, ExpandFunc> qmake_expandFunctions()
         qmake_expand_functions->insert("relative_path", E_RELATIVE_PATH);
         qmake_expand_functions->insert("clean_path", E_CLEAN_PATH);
         qmake_expand_functions->insert("native_path", E_NATIVE_PATH);
+        qmake_expand_functions->insert("shell_quote", E_SHELL_QUOTE);
     }
     return *qmake_expand_functions;
 }
@@ -1834,6 +1836,67 @@ subAll(QStringList *val, const QStringList &diffval)
         val->removeAll(dv);
 }
 
+inline static
+bool isSpecialChar(ushort c)
+{
+    // Chars that should be quoted (TM). This includes:
+#ifdef Q_OS_WIN
+    // - 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
+    };
+#else
+    static const uchar iqm[] = {
+        0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8,
+        0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78
+    }; // 0-32 \'"$`<>|;&(){}*?#!~[]
+#endif
+
+    return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
+}
+
+inline static
+bool hasSpecialChars(const QString &arg)
+{
+    for (int x = arg.length() - 1; x >= 0; --x)
+        if (isSpecialChar(arg.unicode()[x].unicode()))
+            return true;
+    return false;
+}
+
+static QString
+shellQuote(const QString &arg)
+{
+    if (!arg.length())
+        return QString::fromLatin1("\"\"");
+
+    QString ret(arg);
+    if (hasSpecialChars(ret)) {
+#ifdef Q_OS_WIN
+        // 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('"'));
+#else // Q_OS_WIN
+        ret.replace(QLatin1Char('\''), QLatin1String("'\\''"));
+        ret.prepend(QLatin1Char('\''));
+        ret.append(QLatin1Char('\''));
+#endif // Q_OS_WIN
+    }
+    return ret;
+}
+
 static QString
 quoteValue(const QString &val)
 {
@@ -2610,6 +2673,13 @@ QMakeProject::doProjectExpand(QString func, QList<QStringList> args_list,
         else
             ret += Option::fixPathToTargetOS(args.at(0), false);
         break;
+    case E_SHELL_QUOTE:
+        if (args.count() != 1)
+            fprintf(stderr, "%s:%d shell_quote(args) requires one argument.\n",
+                    parser.file.toLatin1().constData(), parser.line_no);
+        else
+            ret += shellQuote(args.at(0));
+        break;
     default: {
         fprintf(stderr, "%s:%d: Unknown replace function: %s\n",
                 parser.file.toLatin1().constData(), parser.line_no,
index 4268144..5fcfd8c 100644 (file)
@@ -136,3 +136,11 @@ testReplace($$absolute_path("crazy/trolls"), "$$PWD/crazy/trolls", "absolute_pat
 testReplace($$absolute_path("crazy/trolls", "/fake/path"), "/fake/path/crazy/trolls", "absolute_path with base")
 testReplace($$relative_path($$_PRO_FILE_PWD_), $$basename($$_PRO_FILE_), "relative_path")
 testReplace($$relative_path("/fake/trolls", "/fake/path"), "../trolls", "relative_path with base")
+
+#this test is very rudimentary. the backend function is thoroughly tested in qt creator
+in = "some nasty\" path\\"
+win32: \
+    out = "\"some nasty\"\\^\"\" path\"\\"
+else: \
+    out = "'some nasty\" path\\'"
+testReplace($$shell_quote($$in), $$out, "shell_quote")