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