1 /*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2020 David Faure <faure@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 */
7
8 #include "openurljobtest.h"
9 #include "openurljob.h"
10 #include <KApplicationTrader>
11 #include <kprocessrunner_p.h>
12
13 #include "kiotesthelper.h" // createTestFile etc.
14 #include "mockcoredelegateextensions.h"
15 #include "mockguidelegateextensions.h"
16
17 #include <KConfigGroup>
18 #include <KDesktopFile>
19 #include <KJobUiDelegate>
20 #include <KService>
21
22 #ifdef Q_OS_UNIX
23 #include <signal.h> // kill
24 #endif
25
26 #include <KSharedConfig>
27 #include <QStandardPaths>
28 #include <QTemporaryFile>
29 #include <QTest>
30
31 QTEST_GUILESS_MAIN(OpenUrlJobTest)
32
33 extern KSERVICE_EXPORT int ksycoca_ms_between_checks;
34
35 static const char s_tempServiceName[] = "openurljobtest_service.desktop";
36
initTestCase()37 void OpenUrlJobTest::initTestCase()
38 {
39 QStandardPaths::setTestModeEnabled(true);
40
41 // Ensure no leftovers from other tests
42 QDir(QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation)).removeRecursively();
43 // (including a mimeapps.list file)
44 // Don't remove ConfigLocation completely, it's useful when enabling debug output with kdebugsettings --test-mode
45 const QString mimeApps = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QLatin1String("/mimeapps.list");
46 QFile::remove(mimeApps);
47
48 ksycoca_ms_between_checks = 0; // need it to check the ksycoca mtime
49 m_fakeService = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + QLatin1Char('/') + s_tempServiceName;
50 // not using %d because of remote urls
51 const QByteArray cmd = QByteArray("echo %u > " + QFile::encodeName(m_tempDir.path()) + "/dest");
52 writeApplicationDesktopFile(m_fakeService, cmd);
53 m_fakeService = QFileInfo(m_fakeService).canonicalFilePath();
54 m_filesToRemove.append(m_fakeService);
55
56 // Ensure our service is the preferred one
57 KConfig mimeAppsCfg(mimeApps);
58 KConfigGroup grp = mimeAppsCfg.group("Default Applications");
59 grp.writeEntry("text/plain", s_tempServiceName);
60 grp.writeEntry("text/html", s_tempServiceName);
61 grp.sync();
62
63 // "text/plain" encompasses all scripts (shell, python, perl)
64 KService::Ptr preferredTextEditor = KApplicationTrader::preferredService(QStringLiteral("text/plain"));
65 QVERIFY(preferredTextEditor);
66 QCOMPARE(preferredTextEditor->entryPath(), m_fakeService);
67
68 // As used for preferredService
69 QVERIFY(KService::serviceByDesktopName("openurljobtest_service"));
70
71 ksycoca_ms_between_checks = 5000; // all done, speed up again
72 }
73
cleanupTestCase()74 void OpenUrlJobTest::cleanupTestCase()
75 {
76 for (const QString &file : std::as_const(m_filesToRemove)) {
77 QFile::remove(file);
78 };
79 }
80
init()81 void OpenUrlJobTest::init()
82 {
83 QFile::remove(m_tempDir.path() + "/dest");
84 }
85
createSrcFile(const QString & path)86 static void createSrcFile(const QString &path)
87 {
88 QFile srcFile(path);
89 QVERIFY2(srcFile.open(QFile::WriteOnly), qPrintable(srcFile.errorString()));
90 srcFile.write("Hello world\n");
91 }
92
readFile(const QString & path)93 static QString readFile(const QString &path)
94 {
95 QFile file(path);
96 file.open(QIODevice::ReadOnly);
97 return QString::fromLocal8Bit(file.readAll()).trimmed();
98 }
99
startProcess_data()100 void OpenUrlJobTest::startProcess_data()
101 {
102 QTest::addColumn<QString>("mimeType");
103 QTest::addColumn<QString>("fileName");
104
105 // Known MIME type
106 QTest::newRow("text_file") << "text/plain"
107 << "srcfile.txt";
108 QTest::newRow("directory_file") << "application/x-desktop"
109 << ".directory";
110 QTest::newRow("desktop_file_link") << "application/x-desktop"
111 << "srcfile.txt";
112 QTest::newRow("desktop_file_link_preferred_service") << "application/x-desktop"
113 << "srcfile.html";
114 QTest::newRow("non_executable_script_running_not_allowed") << "application/x-shellscript"
115 << "srcfile.sh";
116 QTest::newRow("executable_script_running_not_allowed") << "application/x-shellscript"
117 << "srcfile.sh";
118
119 // Require MIME type determination
120 QTest::newRow("text_file_no_mimetype") << QString() << "srcfile.txt";
121 QTest::newRow("directory_file_no_mimetype") << QString() << ".directory";
122 }
123
startProcess()124 void OpenUrlJobTest::startProcess()
125 {
126 QFETCH(QString, mimeType);
127 QFETCH(QString, fileName);
128
129 // Given a file to open
130 QTemporaryDir tempDir;
131 const QString srcDir = tempDir.path();
132 const QString srcFile = srcDir + QLatin1Char('/') + fileName;
133 createSrcFile(srcFile);
134 QVERIFY(QFile::exists(srcFile));
135 const bool isLink = QByteArray(QTest::currentDataTag()).startsWith("desktop_file_link");
136 QUrl url = QUrl::fromLocalFile(srcFile);
137 if (isLink) {
138 const QString desktopFilePath = srcDir + QLatin1String("/link.desktop");
139 KDesktopFile linkDesktopFile(desktopFilePath);
140 linkDesktopFile.desktopGroup().writeEntry("Type", "Link");
141 linkDesktopFile.desktopGroup().writeEntry("URL", url);
142 const bool linkHasPreferredService = QByteArray(QTest::currentDataTag()) == "desktop_file_link_preferred_service";
143 if (linkHasPreferredService) {
144 linkDesktopFile.desktopGroup().writeEntry("X-KDE-LastOpenedWith", "openurljobtest_service");
145 }
146 url = QUrl::fromLocalFile(desktopFilePath);
147 }
148 if (QByteArray(QTest::currentDataTag()).startsWith("executable")) {
149 QFile file(srcFile);
150 QVERIFY(file.setPermissions(QFile::ExeUser | file.permissions()));
151 // Note however that running executables is not allowed by the OpenUrlJob below
152 // so this will end up opening it as a text file anyway.
153 }
154
155 // When running a OpenUrlJob
156 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(url, mimeType, this);
157 QVERIFY2(job->exec(), qPrintable(job->errorString()));
158
159 // Then m_fakeService should be executed, since it's associated with text/plain
160 // We can find out that it was executed because it writes to "dest".
161 const QString dest = m_tempDir.path() + "/dest";
162 QTRY_VERIFY2(QFile::exists(dest), qPrintable(dest));
163 QCOMPARE(readFile(dest), srcFile);
164 }
165
noServiceNoHandler()166 void OpenUrlJobTest::noServiceNoHandler()
167 {
168 QTemporaryFile tempFile;
169 QVERIFY(tempFile.open());
170 const QUrl url = QUrl::fromLocalFile(tempFile.fileName());
171 const QString mimeType = QStringLiteral("application/x-zerosize");
172 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(url, mimeType, this);
173 // This is going to try QDesktopServices::openUrl which will fail because we are no QGuiApplication, good.
174 QTest::ignoreMessage(QtWarningMsg, "QDesktopServices::openUrl: Application is not a GUI application");
175 QVERIFY(!job->exec());
176 QCOMPARE(job->error(), KJob::UserDefinedError);
177 QCOMPARE(job->errorString(), QStringLiteral("Failed to open the file."));
178 }
179
invalidUrl()180 void OpenUrlJobTest::invalidUrl()
181 {
182 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl(":/"), QStringLiteral("text/plain"), this);
183 QVERIFY(!job->exec());
184 QCOMPARE(job->error(), KIO::ERR_MALFORMED_URL);
185 QCOMPARE(job->errorString(), QStringLiteral("Malformed URL\nRelative URL's path component contains ':' before any '/'; source was \":/\"; path = \":/\""));
186
187 QUrl u;
188 u.setPath(QStringLiteral("/pathonly"));
189 KIO::OpenUrlJob *job2 = new KIO::OpenUrlJob(u, QStringLiteral("text/plain"), this);
190 QVERIFY(!job2->exec());
191 QCOMPARE(job2->error(), KIO::ERR_MALFORMED_URL);
192 QCOMPARE(job2->errorString(), QStringLiteral("Malformed URL\n/pathonly"));
193 }
194
refuseRunningLocalBinaries_data()195 void OpenUrlJobTest::refuseRunningLocalBinaries_data()
196 {
197 QTest::addColumn<QString>("mimeType");
198
199 // Executables under e.g. /usr/bin/ can be either of these two MIME types
200 // see https://gitlab.freedesktop.org/xdg/shared-mime-info/-/issues/11
201 QTest::newRow("x-sharedlib") << "application/x-sharedlib";
202 QTest::newRow("x-executable") << "application/x-executable";
203
204 QTest::newRow("msdos_executable") << "application/x-ms-dos-executable";
205 }
206
refuseRunningLocalBinaries()207 void OpenUrlJobTest::refuseRunningLocalBinaries()
208 {
209 QFETCH(QString, mimeType);
210
211 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl::fromLocalFile(QCoreApplication::applicationFilePath()), mimeType, this);
212 QVERIFY(!job->exec());
213 QCOMPARE(job->error(), KJob::UserDefinedError);
214 QVERIFY2(job->errorString().contains("For security reasons, launching executables is not allowed in this context."), qPrintable(job->errorString()));
215 }
216
refuseRunningRemoteNativeExecutables_data()217 void OpenUrlJobTest::refuseRunningRemoteNativeExecutables_data()
218 {
219 QTest::addColumn<QString>("mimeType");
220 QTest::newRow("x-sharedlib") << "application/x-sharedlib";
221 QTest::newRow("x-executable") << "application/x-executable";
222 }
223
refuseRunningRemoteNativeExecutables()224 void OpenUrlJobTest::refuseRunningRemoteNativeExecutables()
225 {
226 QFETCH(QString, mimeType);
227
228 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl("protocol://host/path/exe"), mimeType, this);
229 job->setRunExecutables(true); // even with this enabled, an error will occur
230 QVERIFY(!job->exec());
231 QCOMPARE(job->error(), KJob::UserDefinedError);
232 QVERIFY2(job->errorString().contains("is located on a remote filesystem. For safety reasons it will not be started"), qPrintable(job->errorString()));
233 }
234
235 KCONFIGCORE_EXPORT void loadUrlActionRestrictions(const KConfigGroup &cg);
236
notAuthorized()237 void OpenUrlJobTest::notAuthorized()
238 {
239 KConfigGroup cg(KSharedConfig::openConfig(), "KDE URL Restrictions");
240 cg.writeEntry("rule_count", 1);
241 cg.writeEntry("rule_1", QStringList{"open", {}, {}, {}, "file", "", "", "false"});
242 cg.sync();
243 loadUrlActionRestrictions(cg);
244
245 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl("file:///"), QStringLiteral("text/plain"), this);
246 QVERIFY(!job->exec());
247 QCOMPARE(job->error(), KIO::ERR_ACCESS_DENIED);
248 QCOMPARE(job->errorString(), QStringLiteral("Access denied to file:///."));
249
250 cg.deleteEntry("rule_1");
251 cg.deleteEntry("rule_count");
252 cg.sync();
253 loadUrlActionRestrictions(cg);
254 }
255
runScript_data()256 void OpenUrlJobTest::runScript_data()
257 {
258 QTest::addColumn<QString>("mimeType");
259
260 // All text-based scripts inherit text/plain and application/x-executable, no need to test
261 // all flavours (python, perl, lua, awk ...etc), this sample should be enough
262 QTest::newRow("shellscript") << "application/x-shellscript";
263 QTest::newRow("pythonscript") << "text/x-python";
264 QTest::newRow("javascript") << "application/javascript";
265 }
266
runScript()267 void OpenUrlJobTest::runScript()
268 {
269 #ifdef Q_OS_UNIX
270 QFETCH(QString, mimeType);
271
272 // Given an executable shell script that copies "src" to "dest"
273 QTemporaryDir tempDir;
274 const QString dir = tempDir.path();
275 createSrcFile(dir + QLatin1String("/src"));
276 const QString scriptFile = dir + QLatin1String("/script.sh");
277 QFile file(scriptFile);
278 QVERIFY(file.open(QIODevice::WriteOnly));
279 file.write("#!/bin/sh\ncp src dest");
280 file.close();
281 QVERIFY(file.setPermissions(QFile::ExeUser | file.permissions()));
282
283 // When using OpenUrlJob to run the script
284 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl::fromLocalFile(scriptFile), mimeType, this);
285 job->setRunExecutables(true); // startProcess and refuseRunningLocalBinaries test the case where this isn't set
286
287 // Then it works :-)
288 QVERIFY2(job->exec(), qPrintable(job->errorString()));
289 QTRY_VERIFY(QFileInfo::exists(dir + QLatin1String("/dest"))); // TRY because CommandLineLauncherJob finishes immediately
290 #endif
291 }
292
runNativeExecutable_data()293 void OpenUrlJobTest::runNativeExecutable_data()
294 {
295 QTest::addColumn<QString>("mimeType");
296 QTest::addColumn<bool>("withHandler");
297 QTest::addColumn<bool>("handlerRetVal");
298
299 QTest::newRow("no_handler_x-sharedlib") << "application/x-sharedlib" << false << false;
300 QTest::newRow("handler_false_x-sharedlib") << "application/x-sharedlib" << true << false;
301 QTest::newRow("handler_true_x-sharedlib") << "application/x-sharedlib" << true << true;
302
303 QTest::newRow("no_handler_x-executable") << "application/x-executable" << false << false;
304 QTest::newRow("handler_false_x-executable") << "application/x-executable" << true << false;
305 QTest::newRow("handler_true_x-executable") << "application/x-executable" << true << true;
306 }
307
runNativeExecutable()308 void OpenUrlJobTest::runNativeExecutable()
309 {
310 QFETCH(QString, mimeType);
311 QFETCH(bool, withHandler);
312 QFETCH(bool, handlerRetVal);
313
314 #ifdef Q_OS_UNIX
315 // Given an executable shell script that copies "src" to "dest" (we'll cheat with the MIME type to treat it like a native binary)
316 QTemporaryDir tempDir;
317 const QString dir = tempDir.path();
318 createSrcFile(dir + QLatin1String("/src"));
319 const QString scriptFile = dir + QLatin1String("/script.sh");
320 QFile file(scriptFile);
321 QVERIFY(file.open(QIODevice::WriteOnly));
322 file.write("#!/bin/sh\ncp src dest");
323 file.close();
324 // Note that it's missing executable permissions
325
326 // When using OpenUrlJob to run the executable
327 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl::fromLocalFile(scriptFile), mimeType, this);
328 job->setRunExecutables(true); // startProcess tests the case where this isn't set
329 job->setUiDelegate(new KJobUiDelegate);
330
331 // Then --- it depends on what the user says via the handler
332 if (!withHandler) {
333 QVERIFY(!job->exec());
334 QCOMPARE((int)job->error(), (int)KJob::UserDefinedError);
335 QCOMPARE(job->errorString(), QStringLiteral("The program \"%1\" needs to have executable permission before it can be launched.").arg(scriptFile));
336 } else {
337 auto *handler = new MockUntrustedProgramHandler(job->uiDelegate());
338 handler->setRetVal(handlerRetVal);
339
340 const bool success = job->exec();
341 if (handlerRetVal) {
342 QVERIFY(success);
343 QTRY_VERIFY(QFileInfo::exists(dir + QLatin1String("/dest"))); // TRY because CommandLineLauncherJob finishes immediately
344 } else {
345 QVERIFY(!success);
346 QCOMPARE((int)job->error(), (int)KIO::ERR_USER_CANCELED);
347 }
348 }
349 #endif
350 }
351
openOrExecuteScript_data()352 void OpenUrlJobTest::openOrExecuteScript_data()
353 {
354 QTest::addColumn<QString>("dialogResult");
355
356 QTest::newRow("execute_true") << "execute_true";
357 QTest::newRow("execute_false") << "execute_false";
358 QTest::newRow("canceled") << "canceled";
359 }
360
openOrExecuteScript()361 void OpenUrlJobTest::openOrExecuteScript()
362 {
363 #ifdef Q_OS_UNIX
364 QFETCH(QString, dialogResult);
365
366 // Given an executable shell script that copies "src" to "dest"
367 QTemporaryDir tempDir;
368 const QString dir = tempDir.path();
369 createSrcFile(dir + QLatin1String("/src"));
370 const QString scriptFile = dir + QLatin1String("/script.sh");
371 QFile file(scriptFile);
372 QVERIFY(file.open(QIODevice::WriteOnly));
373 file.write("#!/bin/sh\ncp src dest");
374 file.close();
375 // Set the executable bit, because OpenUrlJob will always open shell
376 // scripts that are not executable as text files
377 QVERIFY(file.setPermissions(QFile::ExeUser | file.permissions()));
378
379 // When using OpenUrlJob to open the script
380 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl::fromLocalFile(scriptFile), QStringLiteral("application/x-shellscript"), this);
381 job->setShowOpenOrExecuteDialog(true);
382 job->setUiDelegate(new KJobUiDelegate);
383 auto *openOrExecuteFileHandler = new MockOpenOrExecuteHandler(job->uiDelegate());
384
385 // Then --- it depends on what the user says via the handler
386 if (dialogResult == QLatin1String("execute_true")) {
387 job->setRunExecutables(false); // Overridden by the user's choice
388 openOrExecuteFileHandler->setExecuteFile(true);
389 QVERIFY(job->exec());
390 // TRY because CommandLineLauncherJob finishes immediately, and tempDir
391 // will go out of scope and get deleted before the copy operation actually finishes
392 QTRY_VERIFY(QFileInfo::exists(dir + QLatin1String("/dest")));
393 } else if (dialogResult == QLatin1String("execute_false")) {
394 job->setRunExecutables(true); // Overridden by the user's choice
395 openOrExecuteFileHandler->setExecuteFile(false);
396 QVERIFY(job->exec());
397 const QString testOpen = m_tempDir.path() + QLatin1String("/dest"); // see the .desktop file in writeApplicationDesktopFile
398 QTRY_VERIFY(QFileInfo::exists(testOpen));
399 } else if (dialogResult == QLatin1String("canceled")) {
400 openOrExecuteFileHandler->setCanceled();
401 QVERIFY(!job->exec());
402 QCOMPARE(job->error(), KIO::ERR_USER_CANCELED);
403 }
404 #endif
405 }
406
openOrExecuteDesktop_data()407 void OpenUrlJobTest::openOrExecuteDesktop_data()
408 {
409 QTest::addColumn<QString>("dialogResult");
410
411 QTest::newRow("execute_true") << "execute_true";
412 QTest::newRow("execute_false") << "execute_false";
413 QTest::newRow("canceled") << "canceled";
414 }
415
openOrExecuteDesktop()416 void OpenUrlJobTest::openOrExecuteDesktop()
417 {
418 #ifdef Q_OS_UNIX
419 QFETCH(QString, dialogResult);
420
421 // Given a .desktop file, with an Exec line that copies "src" to "dest"
422 QTemporaryDir tempDir;
423 const QString dir = tempDir.path();
424 const QString desktopFile = dir + QLatin1String("/testopenorexecute.desktop");
425 createSrcFile(dir + QLatin1String("/src"));
426 const QByteArray cmd("cp " + QFile::encodeName(dir) + "/src " + QFile::encodeName(dir) + "/dest-open-or-execute-desktop");
427 writeApplicationDesktopFile(desktopFile, cmd);
428 QFile file(desktopFile);
429 QVERIFY(file.setPermissions(QFile::ExeUser | file.permissions())); // otherwise we'll get the untrusted program warning
430
431 // When using OpenUrlJob to open the .desktop file
432 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl::fromLocalFile(desktopFile), QStringLiteral("application/x-desktop"), this);
433 job->setShowOpenOrExecuteDialog(true);
434 job->setUiDelegate(new KJobUiDelegate);
435 auto *openOrExecuteFileHandler = new MockOpenOrExecuteHandler(job->uiDelegate());
436
437 // Then --- it depends on what the user says via the handler
438 if (dialogResult == QLatin1String("execute_true")) {
439 job->setRunExecutables(false); // Overridden by the user's choice
440 openOrExecuteFileHandler->setExecuteFile(true);
441 QVERIFY2(job->exec(), qPrintable(job->errorString()));
442 // TRY because CommandLineLauncherJob finishes immediately, and tempDir
443 // will go out of scope and get deleted before the copy operation actually finishes
444 QTRY_VERIFY(QFileInfo::exists(dir + QLatin1String("/dest-open-or-execute-desktop")));
445 }
446 if (dialogResult == QLatin1String("execute_false")) {
447 job->setRunExecutables(true); // Overridden by the user's choice
448 openOrExecuteFileHandler->setExecuteFile(false);
449 QVERIFY2(job->exec(), qPrintable(job->errorString()));
450 const QString testOpen = m_tempDir.path() + QLatin1String("/dest"); // see the .desktop file in writeApplicationDesktopFile
451 QTRY_VERIFY(QFileInfo::exists(testOpen));
452 } else if (dialogResult == QLatin1String("canceled")) {
453 openOrExecuteFileHandler->setCanceled();
454 QVERIFY(!job->exec());
455 QCOMPARE(job->error(), KIO::ERR_USER_CANCELED);
456 }
457 #endif
458 }
459
launchExternalBrowser_data()460 void OpenUrlJobTest::launchExternalBrowser_data()
461 {
462 QTest::addColumn<bool>("useBrowserApp");
463 QTest::addColumn<bool>("useSchemeHandler");
464
465 QTest::newRow("browserapp") << true << false;
466 QTest::newRow("scheme_handler") << false << true;
467 }
468
launchExternalBrowser()469 void OpenUrlJobTest::launchExternalBrowser()
470 {
471 #ifdef Q_OS_UNIX
472 QFETCH(bool, useBrowserApp);
473 QFETCH(bool, useSchemeHandler);
474
475 QTemporaryDir tempDir;
476 const QString dir = tempDir.path();
477 createSrcFile(dir + QLatin1String("/src"));
478 const QString scriptFile = dir + QLatin1String("/browser.sh");
479 QFile file(scriptFile);
480 QVERIFY(file.open(QIODevice::WriteOnly));
481 file.write("#!/bin/sh\necho $1 > `dirname $0`/destbrowser");
482 file.close();
483 QVERIFY(file.setPermissions(QFile::ExeUser | file.permissions()));
484
485 QUrl remoteImage("http://example.org/image.jpg");
486 if (useBrowserApp) {
487 KConfigGroup(KSharedConfig::openConfig(), "General").writeEntry("BrowserApplication", QString(QLatin1Char('!') + scriptFile));
488 } else if (useSchemeHandler) {
489 remoteImage.setScheme("scheme");
490 }
491
492 // When using OpenUrlJob to run the script
493 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(remoteImage, this);
494
495 // Then it works :-)
496 QVERIFY2(job->exec(), qPrintable(job->errorString()));
497 QString dest;
498 if (useBrowserApp) {
499 dest = dir + QLatin1String("/destbrowser");
500 } else if (useSchemeHandler) {
501 dest = m_tempDir.path() + QLatin1String("/dest"); // see the .desktop file in writeApplicationDesktopFile
502 }
503 QTRY_VERIFY(QFileInfo::exists(dest)); // TRY because CommandLineLauncherJob finishes immediately
504 QCOMPARE(readFile(dest), remoteImage.toString());
505
506 // Restore settings
507 KConfigGroup(KSharedConfig::openConfig(), "General").deleteEntry("BrowserApplication");
508 #endif
509 }
510
nonExistingFile()511 void OpenUrlJobTest::nonExistingFile()
512 {
513 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl::fromLocalFile(QStringLiteral("/does/not/exist")), this);
514 QVERIFY(!job->exec());
515 QCOMPARE(job->error(), KIO::ERR_DOES_NOT_EXIST);
516 QCOMPARE(job->errorString(), "The file or folder /does/not/exist does not exist.");
517 }
518
httpUrlWithKIO()519 void OpenUrlJobTest::httpUrlWithKIO()
520 {
521 // This tests the scanFileWithGet() code path
522 const QUrl url(QStringLiteral("http://www.google.com/"));
523 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(url, this);
524 job->setEnableExternalBrowser(false);
525 job->setFollowRedirections(false);
526 QVERIFY2(job->exec(), qPrintable(job->errorString()));
527
528 // Then the service should be executed (which writes to "dest")
529 const QString dest = m_tempDir.path() + "/dest";
530 QTRY_VERIFY2(QFile::exists(dest), qPrintable(dest));
531 QCOMPARE(readFile(dest), url.toString());
532 }
533
ftpUrlWithKIO()534 void OpenUrlJobTest::ftpUrlWithKIO()
535 {
536 // This is just to test the statFile() code at least a bit
537 const QUrl url(QStringLiteral("ftp://localhost:2")); // unlikely that anything is running on that port
538 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(url, this);
539 QVERIFY(!job->exec());
540 QVERIFY(job->errorString() == QLatin1String("Could not connect to host localhost: Connection refused.")
541 || job->errorString() == QLatin1String("Could not connect to host localhost: Network unreachable."));
542 }
543
takeOverAfterMimeTypeFound()544 void OpenUrlJobTest::takeOverAfterMimeTypeFound()
545 {
546 // Given a local image file
547 QTemporaryDir tempDir;
548 const QString srcDir = tempDir.path();
549 const QString srcFile = srcDir + QLatin1String("/image.jpg");
550 createSrcFile(srcFile);
551
552 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl::fromLocalFile(srcFile), this);
553 QString foundMime = QStringLiteral("NONE");
554 connect(job, &KIO::OpenUrlJob::mimeTypeFound, this, [&](const QString &mimeType) {
555 foundMime = mimeType;
556 job->kill();
557 });
558 QVERIFY(!job->exec());
559 QCOMPARE(job->error(), KJob::KilledJobError);
560 QCOMPARE(foundMime, "image/jpeg");
561 }
562
runDesktopFileDirectly()563 void OpenUrlJobTest::runDesktopFileDirectly()
564 {
565 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl::fromLocalFile(m_fakeService), this);
566 job->setRunExecutables(true);
567 QVERIFY(job->exec());
568
569 const QString dest = m_tempDir.path() + "/dest";
570 QTRY_VERIFY2(QFile::exists(dest), qPrintable(dest));
571 QCOMPARE(readFile(dest), QString{});
572 }
573
writeApplicationDesktopFile(const QString & filePath,const QByteArray & command)574 void OpenUrlJobTest::writeApplicationDesktopFile(const QString &filePath, const QByteArray &command)
575 {
576 KDesktopFile file(filePath);
577 KConfigGroup group = file.desktopGroup();
578 group.writeEntry("Name", "KRunUnittestService");
579 group.writeEntry("MimeType", "text/plain;application/x-shellscript;x-scheme-handler/scheme");
580 group.writeEntry("Type", "Application");
581 group.writeEntry("Exec", command);
582 QVERIFY(file.sync());
583 }
584