File indexing completed on 2025-01-19 12:48:27
0001 /* 0002 SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org> 0003 SPDX-FileCopyrightText: 2007, 2009 David Faure <faure@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "desktopexecparsertest.h" 0009 0010 #include <QTest> 0011 0012 QTEST_GUILESS_MAIN(DesktopExecParserTest) 0013 0014 #include <QStandardPaths> 0015 0016 #include <desktopexecparser.h> 0017 0018 #include <KApplicationTrader> 0019 #include <KConfigGroup> 0020 #include <KProcess> 0021 #include <KService> 0022 #include <KSharedConfig> 0023 #include <KShell> 0024 0025 void DesktopExecParserTest::initTestCase() 0026 { 0027 QStandardPaths::setTestModeEnabled(true); 0028 0029 qputenv("PATH", QByteArray(qgetenv("PATH") + QFile::encodeName(QDir::listSeparator() + QCoreApplication::applicationDirPath()))); 0030 0031 // testProcessDesktopExec works only if your terminal application is set to "xterm" 0032 KConfigGroup cg(KSharedConfig::openConfig(), "General"); 0033 cg.writeEntry("TerminalApplication", "true --test"); 0034 0035 // We just want to test if the command is properly constructed 0036 m_pseudoTerminalProgram = QStandardPaths::findExecutable(QStringLiteral("true")); 0037 QVERIFY(!m_pseudoTerminalProgram.isEmpty()); 0038 0039 // Determine the full path of sh - this is needed to make testProcessDesktopExecNoFile() 0040 // pass on systems where QStandardPaths::findExecutable("sh") is not "/bin/sh". 0041 m_sh = QStandardPaths::findExecutable(QStringLiteral("sh")); 0042 if (m_sh.isEmpty()) { 0043 m_sh = QStringLiteral("/bin/sh"); 0044 } 0045 } 0046 0047 void DesktopExecParserTest::testExecutableName_data() 0048 { 0049 QTest::addColumn<QString>("execLine"); 0050 QTest::addColumn<QString>("expectedPath"); 0051 QTest::addColumn<QString>("expectedName"); 0052 0053 QTest::newRow("/usr/bin/ls") << "/usr/bin/ls" 0054 << "/usr/bin/ls" 0055 << "ls"; 0056 QTest::newRow("/path/to/wine \"long argument with path\"") << "/path/to/wine \"long argument with path\"" 0057 << "/path/to/wine" 0058 << "wine"; 0059 QTest::newRow("/path/with/a/sp\\ ace/exe arg1 arg2") << "/path/with/a/sp\\ ace/exe arg1 arg2" 0060 << "/path/with/a/sp ace/exe" 0061 << "exe"; 0062 QTest::newRow("\"progname\" \"arg1\"") << "\"progname\" \"arg1\"" 0063 << "progname" 0064 << "progname"; 0065 QTest::newRow("'quoted' \"arg1\"") << "'quoted' \"arg1\"" 0066 << "quoted" 0067 << "quoted"; 0068 QTest::newRow(" 'leading space' arg1") << " 'leading space' arg1" 0069 << "leading space" 0070 << "leading space"; 0071 QTest::newRow("if_command") << "if test -e /tmp/foo; then kwrite ; else konsole ; fi" 0072 << "" 0073 << ""; // "if" isn't a known executable, so this is good... 0074 } 0075 0076 void DesktopExecParserTest::testExecutableName() 0077 { 0078 QFETCH(QString, execLine); 0079 QFETCH(QString, expectedPath); 0080 QFETCH(QString, expectedName); 0081 QCOMPARE(KIO::DesktopExecParser::executableName(execLine), expectedName); 0082 QCOMPARE(KIO::DesktopExecParser::executablePath(execLine), expectedPath); 0083 } 0084 0085 // static const char *bt(bool tr) { return tr?"true":"false"; } 0086 static void checkDesktopExecParser(const char *exec, const char *term, const char *sus, const QList<QUrl> &urls, bool tf, const QString &b) 0087 { 0088 QFile out(QStringLiteral("kruntest.desktop")); 0089 if (!out.open(QIODevice::WriteOnly)) { 0090 abort(); 0091 } 0092 QByteArray str( 0093 "[Desktop Entry]\n" 0094 "Type=Application\n" 0095 "Name=just_a_test\n" 0096 "Icon=~/icon.png\n"); 0097 str += QByteArray(exec) + '\n'; 0098 str += QByteArray(term) + '\n'; 0099 str += QByteArray(sus) + '\n'; 0100 out.write(str); 0101 out.close(); 0102 0103 KService service(QDir::currentPath() + "/kruntest.desktop"); 0104 /*qDebug() << QString().sprintf( 0105 "processDesktopExec( " 0106 "service = {\nexec = %s\nterminal = %s, terminalOptions = %s\nsubstituteUid = %s, user = %s }," 0107 "\nURLs = { %s },\ntemp_files = %s )", 0108 service.exec().toLatin1().constData(), bt(service.terminal()), service.terminalOptions().toLatin1().constData(), bt(service.substituteUid()), 0109 service.username().toLatin1().constData(), KShell::joinArgs(urls.toStringList()).toLatin1().constData(), bt(tf)); 0110 */ 0111 KIO::DesktopExecParser parser(service, urls); 0112 parser.setUrlsAreTempFiles(tf); 0113 QCOMPARE(KShell::joinArgs(parser.resultingArguments()), b); 0114 0115 QFile::remove(QStringLiteral("kruntest.desktop")); 0116 } 0117 0118 void DesktopExecParserTest::testProcessDesktopExec() 0119 { 0120 QList<QUrl> l0; 0121 static const char *const execs[] = {"Exec=date -u", "Exec=echo $PWD"}; 0122 static const char *const terms[] = {"Terminal=false", "Terminal=true\nTerminalOptions=-T \"%f - %c\""}; 0123 static const char *const sus[] = {"X-KDE-SubstituteUID=false", "X-KDE-SubstituteUID=true\nX-KDE-Username=sprallo"}; 0124 static const char *const results[] = { 0125 "/bin/date -u", // 0 0126 "/bin/sh -c 'echo $PWD '", // 1 0127 "/bin/true --test -T ' - just_a_test' -e /bin/date -u", // 2 0128 "/bin/true --test -T ' - just_a_test' -e /bin/sh -c 'echo $PWD '", // 3 0129 /* kdesu */ " -u sprallo -c '/bin/date -u'", // 4 0130 /* kdesu */ " -u sprallo -c '/bin/sh -c '\\''echo $PWD '\\'''", // 5 0131 "/bin/true --test -T ' - just_a_test' -e su sprallo -c '/bin/date -u'", // 6 0132 "/bin/true --test -T ' - just_a_test' -e su sprallo -c '/bin/sh -c '\\''echo $PWD '\\'''", // 7 0133 }; 0134 0135 // Find out the full path of the shell which will be used to execute shell commands 0136 KProcess process; 0137 process.setShellCommand(QLatin1String("")); 0138 const QString shellPath = process.program().at(0); 0139 0140 // Arch moved /bin/date to /usr/bin/date... 0141 const QString datePath = QStandardPaths::findExecutable(QStringLiteral("date")); 0142 0143 for (int su = 0; su < 2; su++) { 0144 for (int te = 0; te < 2; te++) { 0145 for (int ex = 0; ex < 2; ex++) { 0146 int pt = ex + te * 2 + su * 4; 0147 QString exe; 0148 if (pt == 4 || pt == 5) { 0149 exe = QFile::decodeName(KDE_INSTALL_FULL_LIBEXECDIR_KF "/kdesu"); 0150 if (!QFile::exists(exe)) { 0151 qWarning() << "kdesu not found, skipping test"; 0152 continue; 0153 } 0154 } 0155 const QString result = QString::fromLatin1(results[pt]) 0156 .replace(QLatin1String("/bin/true"), m_pseudoTerminalProgram) 0157 .replace(QLatin1String("/bin/sh"), shellPath) 0158 .replace(QLatin1String("/bin/date"), datePath); 0159 checkDesktopExecParser(execs[ex], terms[te], sus[su], l0, false, exe + result); 0160 } 0161 } 0162 } 0163 } 0164 0165 void DesktopExecParserTest::testProcessDesktopExecNoFile_data() 0166 { 0167 /* clang-format off */ 0168 QTest::addColumn<QString>("execLine"); 0169 QTest::addColumn<QList<QUrl>>("urls"); 0170 QTest::addColumn<bool>("tempfiles"); 0171 QTest::addColumn<QString>("expected"); 0172 0173 QList<QUrl> l0; 0174 QList<QUrl> l1; 0175 l1 << QUrl(QStringLiteral("file:/tmp")); 0176 QList<QUrl> l2; 0177 l2 << QUrl(QStringLiteral("http://localhost/foo")); 0178 QList<QUrl> l3; 0179 l3 << QUrl(QStringLiteral("file:/local/some file")) << QUrl(QStringLiteral("http://remotehost.org/bar")); 0180 QList<QUrl> l4; 0181 l4 << QUrl(QStringLiteral("http://login:password@www.kde.org")); 0182 0183 // A real-world use case would be kate. 0184 // But I picked ktrash5 since it's installed by kio 0185 QString ktrash = QStandardPaths::findExecutable(QStringLiteral("ktrash5")); 0186 QVERIFY(!ktrash.isEmpty()); 0187 QString ktrashQuoted = KShell::quoteArg(ktrash); 0188 0189 QString kioexec = QCoreApplication::applicationDirPath() + "/kioexec"; 0190 if (!QFileInfo::exists(kioexec)) { 0191 kioexec = KDE_INSTALL_FULL_LIBEXECDIR_KF "/kioexec"; 0192 } 0193 QVERIFY(QFileInfo::exists(kioexec)); 0194 QString kioexecQuoted = KShell::quoteArg(kioexec); 0195 0196 QTest::newRow("%U l0") << "ktrash5 %U" << l0 << false << ktrashQuoted; 0197 QTest::newRow("%U l1") << "ktrash5 %U" << l1 << false << ktrashQuoted + " /tmp"; 0198 QTest::newRow("%U l2") << "ktrash5 %U" << l2 << false << ktrashQuoted + " http://localhost/foo"; 0199 QTest::newRow("%U l3") << "ktrash5 %U" << l3 << false << ktrashQuoted + " '/local/some file' http://remotehost.org/bar"; 0200 0201 // QTest::newRow("%u l0") << "ktrash5 %u" << l0 << false << ktrashQuoted; // gives runtime warning 0202 QTest::newRow("%u l1") << "ktrash5 %u" << l1 << false << ktrashQuoted + " /tmp"; 0203 QTest::newRow("%u l2") << "ktrash5 %u" << l2 << false << ktrashQuoted + " http://localhost/foo"; 0204 // QTest::newRow("%u l3") << "ktrash5 %u" << l3 << false << ktrashQuoted; // gives runtime warning 0205 0206 QTest::newRow("%F l0") << "ktrash5 %F" << l0 << false << ktrashQuoted; 0207 QTest::newRow("%F l1") << "ktrash5 %F" << l1 << false << ktrashQuoted + " /tmp"; 0208 QTest::newRow("%F l2") << "ktrash5 %F" << l2 << false << kioexecQuoted + " 'ktrash5 %F' http://localhost/foo"; 0209 QTest::newRow("%F l3") << "ktrash5 %F" << l3 << false << kioexecQuoted + " 'ktrash5 %F' 'file:///local/some file' http://remotehost.org/bar"; 0210 0211 QTest::newRow("%F l1 tempfile") << "ktrash5 %F" << l1 << true << kioexecQuoted + " --tempfiles 'ktrash5 %F' file:///tmp"; 0212 QTest::newRow("%f l1 tempfile") << "ktrash5 %f" << l1 << true << kioexecQuoted + " --tempfiles 'ktrash5 %f' file:///tmp"; 0213 0214 QTest::newRow("sh -c ktrash5 %F") 0215 << R"(sh -c "ktrash5 "'\"'"%F"'\"')" 0216 << l1 0217 << false 0218 << m_sh + R"( -c 'ktrash5 \"/tmp\"')"; 0219 0220 // This was originally with kmailservice5, but that relies on it being installed 0221 QTest::newRow("ktrash5 %u l1") << "ktrash5 %u" << l1 << false << ktrashQuoted + " /tmp"; 0222 QTest::newRow("ktrash5 %u l4") << "ktrash5 %u" << l4 << false << ktrashQuoted + " http://login:password@www.kde.org"; 0223 0224 /* clang-format on */ 0225 } 0226 0227 void DesktopExecParserTest::testProcessDesktopExecNoFile() 0228 { 0229 QFETCH(QString, execLine); 0230 KService service(QStringLiteral("dummy"), execLine, QStringLiteral("app")); 0231 QFETCH(QList<QUrl>, urls); 0232 QFETCH(bool, tempfiles); 0233 QFETCH(QString, expected); 0234 KIO::DesktopExecParser parser(service, urls); 0235 parser.setUrlsAreTempFiles(tempfiles); 0236 const QStringList args = parser.resultingArguments(); 0237 QVERIFY2(!args.isEmpty(), qPrintable(parser.errorMessage())); 0238 QCOMPARE(KShell::joinArgs(args), expected); 0239 } 0240 0241 extern KSERVICE_EXPORT int ksycoca_ms_between_checks; 0242 0243 void DesktopExecParserTest::testKtelnetservice() 0244 { 0245 const QString ktelnetDesk = QFINDTESTDATA(QStringLiteral("../src/schemehandlers/telnet/ktelnetservice5.desktop")); 0246 QVERIFY(!ktelnetDesk.isEmpty()); 0247 0248 // KApplicationTrader in KIO::DesktopExecParser::hasSchemeHandler() needs the .desktop file to be installed 0249 const QString destDir = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation); 0250 QVERIFY(QDir().mkpath(destDir)); 0251 QFile::remove(destDir + QLatin1String("/ktelnetservice5.desktop")); 0252 QVERIFY(QFile::copy(ktelnetDesk, destDir + QLatin1String("/ktelnetservice5.desktop"))); 0253 0254 ksycoca_ms_between_checks = 0; // need it to check the ksycoca mtime 0255 0256 KService::Ptr service = KService::serviceByStorageId(QStringLiteral("ktelnetservice5.desktop")); 0257 QVERIFY(service); 0258 0259 QString ktelnetExec = QStandardPaths::findExecutable(QStringLiteral("ktelnetservice5")); 0260 // if KIO is installed we'll find <bindir>/ktelnetservice5, otherwise KIO::DesktopExecParser will 0261 // use the executable from Exec= line 0262 if (ktelnetExec.isEmpty()) { 0263 ktelnetExec = service->exec().remove(QLatin1String(" %u")); 0264 } 0265 QVERIFY(!ktelnetExec.isEmpty()); 0266 0267 const QString expected = KShell::quoteArg(ktelnetExec) + QLatin1String(" %1://root@10.1.1.1"); 0268 const QStringList protocols({QStringLiteral("ssh"), QStringLiteral("telnet"), QStringLiteral("rlogin")}); 0269 for (const QString &protocol : protocols) { 0270 const QList<QUrl> urls({QUrl(QStringLiteral("%1://root@10.1.1.1").arg(protocol))}); 0271 KIO::DesktopExecParser parser(*service, urls); 0272 QCOMPARE(KShell::joinArgs(parser.resultingArguments()), expected.arg(protocol)); 0273 } 0274 } 0275 0276 #include "moc_desktopexecparsertest.cpp"