From ba145f6090f481a41e7a476355c83039ed8ff521 Mon Sep 17 00:00:00 2001 From: Karsten Heimrich Date: Wed, 30 Mar 2016 12:53:31 +0200 Subject: Make communication via installer.execute() Unicode safe It was impossible to pass Unicode data safely to a process started via installer.execute(), or to read Unicode data printed by that process safely back in. The reason for this is that the code hardcoded the latin1 codec for converting between strings used in the script interpreter and bytes used by the QProcessWrapper API. Fix this by adding two new optional arguments to installer.execute() which can be used to define the codec to be used for writing to stdin resp. reading from stdout. This defaults to latin1 for backwards compatibility. Change-Id: I290d8d9617b286ef90b2f0a05c6e7a47f6df317f Reviewed-by: Karsten Heimrich Reviewed-by: Frerich Raabe --- doc/scripting-api/packagemanagercore.qdoc | 9 +++- src/libs/installer/packagemanagercore.cpp | 24 ++++++++-- src/libs/installer/packagemanagercore.h | 4 +- tests/auto/installer/installer.pro | 2 + .../installer/scriptengine/tst_scriptengine.cpp | 52 +++++++++++++++++++++ tests/auto/installer/unicodeexecutable/main.c | 54 ++++++++++++++++++++++ .../auto/installer/unicodeexecutable/stringdata.h | 41 ++++++++++++++++ .../unicodeexecutable/unicodeexecutable.pro | 8 ++++ 8 files changed, 189 insertions(+), 5 deletions(-) create mode 100644 tests/auto/installer/unicodeexecutable/main.c create mode 100644 tests/auto/installer/unicodeexecutable/stringdata.h create mode 100644 tests/auto/installer/unicodeexecutable/unicodeexecutable.pro diff --git a/doc/scripting-api/packagemanagercore.qdoc b/doc/scripting-api/packagemanagercore.qdoc index 418dbdf14..66441fce5 100644 --- a/doc/scripting-api/packagemanagercore.qdoc +++ b/doc/scripting-api/packagemanagercore.qdoc @@ -505,13 +505,20 @@ /*! \qmlmethod array installer::execute(string program, stringlist arguments = undefined, - string stdin = "") + string stdin = "", string stdinCodec = "latin1", + string stdoutCodec = "latin1") Starts the program \a program with the arguments \a arguments in a new process and waits for it to finish. \a stdin is sent as standard input to the application. + \a stdInCodec is the name of the codec to use for converting the input string + into bytes to write to the standard input of the application. + + \a stdOutCodec is the name of the codec to use for converting data written by the + application to standard output into a string. + Returns an empty array if the program could not be executed, otherwise the output of command as the first item, and the return code as the second. diff --git a/src/libs/installer/packagemanagercore.cpp b/src/libs/installer/packagemanagercore.cpp index b0a773671..af517da5b 100644 --- a/src/libs/installer/packagemanagercore.cpp +++ b/src/libs/installer/packagemanagercore.cpp @@ -62,6 +62,8 @@ #include #include #include +#include +#include #include #include @@ -1842,6 +1844,12 @@ bool PackageManagerCore::localInstallerBinaryUsed() \a stdIn is sent as standard input to the application. + \a stdInCodec is the name of the codec to use for converting the input string + into bytes to write to the standard input of the application. + + \a stdOutCodec is the name of the codec to use for converting data written by the + application to standard output into a string. + Returns an empty array if the program could not be executed, otherwise the output of command as the first item, and the return code as the second. @@ -1851,7 +1859,7 @@ bool PackageManagerCore::localInstallerBinaryUsed() \sa executeDetached() */ QList PackageManagerCore::execute(const QString &program, const QStringList &arguments, - const QString &stdIn) const + const QString &stdIn, const QString &stdInCodec, const QString &stdOutCodec) const { QProcessWrapper process; @@ -1868,13 +1876,23 @@ QList PackageManagerCore::execute(const QString &program, const QStrin return QList< QVariant >(); if (!adjustedStdIn.isNull()) { - process.write(adjustedStdIn.toLatin1()); + QTextCodec *codec = QTextCodec::codecForName(qPrintable(stdInCodec)); + if (!codec) + return QList(); + + QTextEncoder encoder(codec); + process.write(encoder.fromUnicode(adjustedStdIn)); process.closeWriteChannel(); } process.waitForFinished(-1); - return QList() << QString::fromLatin1(process.readAllStandardOutput()) << process.exitCode(); + QTextCodec *codec = QTextCodec::codecForName(qPrintable(stdOutCodec)); + if (!codec) + return QList(); + return QList() + << QTextDecoder(codec).toUnicode(process.readAllStandardOutput()) + << process.exitCode(); } /*! diff --git a/src/libs/installer/packagemanagercore.h b/src/libs/installer/packagemanagercore.h index f00f33e72..eebc33c4b 100644 --- a/src/libs/installer/packagemanagercore.h +++ b/src/libs/installer/packagemanagercore.h @@ -138,7 +138,9 @@ public: Q_INVOKABLE bool localInstallerBinaryUsed(); Q_INVOKABLE QList execute(const QString &program, - const QStringList &arguments = QStringList(), const QString &stdIn = QString()) const; + const QStringList &arguments = QStringList(), const QString &stdIn = QString(), + const QString &stdInCodec = QLatin1String("latin1"), + const QString &stdOutCodec = QLatin1String("latin1")) const; Q_INVOKABLE bool executeDetached(const QString &program, const QStringList &arguments = QStringList(), const QString &workingDirectory = QString()) const; diff --git a/tests/auto/installer/installer.pro b/tests/auto/installer/installer.pro index 7f18fa4d1..ba2dd0244 100644 --- a/tests/auto/installer/installer.pro +++ b/tests/auto/installer/installer.pro @@ -8,6 +8,7 @@ SUBDIRS += \ messageboxhandler \ extractarchiveoperationtest \ lib7zfacade \ + unicodeexecutable \ scriptengine \ consumeoutputoperationtest \ mkdiroperationtest \ @@ -23,3 +24,4 @@ SUBDIRS += \ win32 { SUBDIRS += registerfiletypeoperation } +scriptengine.depends += unicodeexecutable diff --git a/tests/auto/installer/scriptengine/tst_scriptengine.cpp b/tests/auto/installer/scriptengine/tst_scriptengine.cpp index f02946148..249d42f56 100644 --- a/tests/auto/installer/scriptengine/tst_scriptengine.cpp +++ b/tests/auto/installer/scriptengine/tst_scriptengine.cpp @@ -39,6 +39,8 @@ #include #include +#include <../unicodeexecutable/stringdata.h> + #include #include #include @@ -472,6 +474,56 @@ private slots: QCOMPARE(gui.widget()->property("complete").toString(), QString("true")); } + void testInstallerExecuteEncodings_data() + { + QTest::addColumn("argumentsToInstallerExecute"); + QTest::addColumn("expectedOutput"); + QTest::addColumn("expectedExitCode"); + + QTest::newRow("default_encoding_ascii_output_exit_code_0") + << QString::fromLatin1("['ascii', '0']") << QString::fromLatin1(asciiText) << 0; + QTest::newRow("default_encoding_ascii_output_exit_code_52") + << QString::fromLatin1("['ascii', '52']") << QString::fromLatin1(asciiText) << 52; + + QTest::newRow("latin1_encoding_ascii_output") + << QString::fromLatin1("['ascii', '0'], '', 'latin1', 'latin1'") << QString::fromLatin1(asciiText) << 0; + QTest::newRow("latin1_encoding_utf8_output") + << QString::fromLatin1("['utf8', '0'], '', 'latin1', 'latin1'") << QString::fromLatin1(utf8Text) << 0; + + QTest::newRow("utf8_encoding_ascii_output") + << QString::fromLatin1("['ascii', '0'], '', 'utf8', 'utf8'") << QString::fromUtf8(asciiText) << 0; + QTest::newRow("utf8_encoding_utf8_output") + << QString::fromLatin1("['utf8', '0'], '', 'utf8', 'utf8'") << QString::fromUtf8(utf8Text) << 0; + } + + void testInstallerExecuteEncodings() + { + QString unicodeExecutableName = QLatin1String("../unicodeexecutable/unicodeexecutable"); +#if defined(Q_OS_WIN) + unicodeExecutableName += QLatin1String(".exe"); +#endif + + QFileInfo unicodeExecutable(unicodeExecutableName); + if (!unicodeExecutable.isExecutable()) { + QFAIL(qPrintable(QString::fromLatin1("ScriptEngine error: test program %1 is not executable") + .arg(unicodeExecutable.absoluteFilePath()))); + return; + } + + const QString testProgramPath = unicodeExecutable.absoluteFilePath(); + + QFETCH(QString, argumentsToInstallerExecute); + QFETCH(QString, expectedOutput); + QFETCH(int, expectedExitCode); + + QJSValue result = m_scriptEngine->evaluate(QString::fromLatin1("installer.execute('%1', %2);") + .arg(testProgramPath) + .arg(argumentsToInstallerExecute)); + QCOMPARE(result.isArray(), true); + QCOMPARE(result.property(0).toString(), expectedOutput); + QCOMPARE(result.property(1).toString(), QString::number(expectedExitCode)); + } + void checkEnteringCalledBeforePageCallback() { EnteringGui gui(&m_core); diff --git a/tests/auto/installer/unicodeexecutable/main.c b/tests/auto/installer/unicodeexecutable/main.c new file mode 100644 index 000000000..d82e0cb60 --- /dev/null +++ b/tests/auto/installer/unicodeexecutable/main.c @@ -0,0 +1,54 @@ +/************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#include "stringdata.h" + +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + assert(argc == 3 || !"incorrect number of arguments"); + + if (!strcmp(argv[1], "utf8")) { + printf("%s", utf8Text); + } else { + printf("%s", asciiText); + } + + return atoi(argv[2]); +} + diff --git a/tests/auto/installer/unicodeexecutable/stringdata.h b/tests/auto/installer/unicodeexecutable/stringdata.h new file mode 100644 index 000000000..901fac31a --- /dev/null +++ b/tests/auto/installer/unicodeexecutable/stringdata.h @@ -0,0 +1,41 @@ +/************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#ifndef STRINGDATA_H +#define STRINGDATA_H + +const char asciiText[] = "This is some ASCII text."; +const char utf8Text[] = "\x46\x6F\x6F\x20\xC2\xA9\x20\x62\x61\x72\x20\xF0\x9D\x8C\x86\x20\x62\x61\x7A\x20\xE2\x98\x83\x20\x71\x75\x78"; + +#endif // !defined(STRINGDATA_H) diff --git a/tests/auto/installer/unicodeexecutable/unicodeexecutable.pro b/tests/auto/installer/unicodeexecutable/unicodeexecutable.pro new file mode 100644 index 000000000..2da7be243 --- /dev/null +++ b/tests/auto/installer/unicodeexecutable/unicodeexecutable.pro @@ -0,0 +1,8 @@ +SOURCES = main.c + +CONFIG -= qt app_bundle +CONFIG += console + +DESTDIR = ./ + +QT = -- cgit v1.2.3