diff --git a/autotests/kurifiltertest.cpp b/autotests/kurifiltertest.cpp index 57b7dc5e..d030742e 100644 --- a/autotests/kurifiltertest.cpp +++ b/autotests/kurifiltertest.cpp @@ -1,478 +1,479 @@ /* * Copyright (C) 2002, 2003 David Faure * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation; * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kurifiltertest.h" #include #include #include #include #include #include #include #include #include QTEST_MAIN(KUriFilterTest) static const char *const s_uritypes[] = { "NetProtocol", "LOCAL_FILE", "LOCAL_DIR", "EXECUTABLE", "HELP", "SHELL", "BLOCKED", "ERROR", "UNKNOWN" }; #define NO_FILTERING -2 static void setupColumns() { QTest::addColumn("input"); QTest::addColumn("expectedResult"); QTest::addColumn("expectedUriType"); QTest::addColumn("list"); QTest::addColumn("absPath"); QTest::addColumn("checkForExecutables"); } static void addRow(const char *input, const QString &expectedResult = QString(), int expectedUriType = -1, const QStringList &list = QStringList(), const QString &absPath = QString(), bool checkForExecutables = true) { QTest::newRow(input) << input << expectedResult << expectedUriType << list << absPath << checkForExecutables; } static void runFilterTest(const QString &a, const QString &expectedResult = nullptr, int expectedUriType = -1, const QStringList &list = QStringList(), const QString &absPath = nullptr, bool checkForExecutables = true) { KUriFilterData *filterData = new KUriFilterData; filterData->setData(a); filterData->setCheckForExecutables(checkForExecutables); if (!absPath.isEmpty()) { filterData->setAbsolutePath(absPath); qDebug() << "Filtering: " << a << " with absPath=" << absPath; } else { qDebug() << "Filtering: " << a; } if (KUriFilter::self()->filterUri(*filterData, list)) { if (expectedUriType == NO_FILTERING) { qCritical() << a << "Did not expect filtering. Got" << filterData->uri(); QVERIFY(expectedUriType != NO_FILTERING); // fail the test } // Copied from minicli... QString cmd; QUrl uri = filterData->uri(); if (uri.isLocalFile() && !uri.hasFragment() && !uri.hasQuery() && (filterData->uriType() != KUriFilterData::NetProtocol)) { cmd = uri.toLocalFile(); } else { cmd = uri.url(QUrl::FullyEncoded); } switch (filterData->uriType()) { case KUriFilterData::LocalFile: case KUriFilterData::LocalDir: qDebug() << "*** Result: Local Resource => '" << filterData->uri().toLocalFile() << "'" << endl; break; case KUriFilterData::Help: qDebug() << "*** Result: Local Resource => '" << filterData->uri().url() << "'" << endl; break; case KUriFilterData::NetProtocol: qDebug() << "*** Result: Network Resource => '" << filterData->uri().url() << "'" << endl; break; case KUriFilterData::Shell: case KUriFilterData::Executable: if (filterData->hasArgsAndOptions()) { cmd += filterData->argsAndOptions(); } qDebug() << "*** Result: Executable/Shell => '" << cmd << "'"; break; case KUriFilterData::Error: qDebug() << "*** Result: Encountered error => '" << cmd << "'"; qDebug() << "Reason:" << filterData->errorMsg(); break; default: qDebug() << "*** Result: Unknown or invalid resource."; } if (!expectedResult.isEmpty()) { // Hack for other locales than english, normalize google hosts to google.com cmd = cmd.replace(QRegExp(QStringLiteral("www\\.google\\.[^/]*/")), QStringLiteral("www.google.com/")); if (cmd != expectedResult) { qWarning() << a; QCOMPARE(cmd, expectedResult); } } if (expectedUriType != -1 && expectedUriType != filterData->uriType()) { qWarning() << a << "Got URI type" << s_uritypes[filterData->uriType()] << "expected" << s_uritypes[expectedUriType]; QCOMPARE(s_uritypes[filterData->uriType()], s_uritypes[expectedUriType]); } } else { if (expectedUriType == NO_FILTERING) { qDebug() << "*** No filtering required."; } else { qDebug() << "*** Could not be filtered."; if (expectedUriType != filterData->uriType()) { QCOMPARE(s_uritypes[filterData->uriType()], s_uritypes[expectedUriType]); } } } delete filterData; qDebug() << "-----"; } static void runFilterTest() { QFETCH(QString, input); QFETCH(QString, expectedResult); QFETCH(int, expectedUriType); QFETCH(QStringList, list); QFETCH(QString, absPath); QFETCH(bool, checkForExecutables); runFilterTest(input, expectedResult, expectedUriType, list, absPath, checkForExecutables); } static void testLocalFile(const QString &filename) { QFile tmpFile(filename); // Yeah, I know, security risk blah blah. This is a test prog! if (tmpFile.open(QIODevice::ReadWrite)) { QByteArray fname = QFile::encodeName(tmpFile.fileName()); runFilterTest(fname, fname, KUriFilterData::LocalFile); tmpFile.close(); tmpFile.remove(); } else { qDebug() << "Couldn't create " << tmpFile.fileName() << ", skipping test"; } } static char s_delimiter = ':'; // the alternative is ' ' void KUriFilterTest::initTestCase() { QStandardPaths::setTestModeEnabled(true); minicliFilters << QStringLiteral("kshorturifilter") << QStringLiteral("kurisearchfilter") << QStringLiteral("localdomainurifilter"); qtdir = qgetenv("QTDIR"); home = qgetenv("HOME"); qputenv("DATAHOME", QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation))); datahome = qgetenv("DATAHOME"); qDebug() << "libpaths" << QCoreApplication::libraryPaths(); qputenv("KDE_FORK_SLAVES", "yes"); // simpler, for the final cleanup QLoggingCategory::setFilterRules(QStringLiteral("org.kde.kurifilter-*=true")); QString searchProvidersDir = QFINDTESTDATA("../src/urifilters/ikws/searchproviders/google.desktop").section('/', 0, -2); QVERIFY(!searchProvidersDir.isEmpty()); qputenv("KIO_SEARCHPROVIDERS_DIR", QFile::encodeName(searchProvidersDir)); // Allow testing of the search engine using both delimiters... const char *envDelimiter = ::getenv("KURIFILTERTEST_DELIMITER"); if (envDelimiter) { s_delimiter = envDelimiter[0]; } // Many tests check the "default search engine" feature. // There is no default search engine by default (since it was annoying when making typos), // so the user has to set it up, which we do here. { KConfigGroup cfg(KSharedConfig::openConfig(QStringLiteral("kuriikwsfilterrc"), KConfig::SimpleConfig), "General"); cfg.writeEntry("DefaultWebShortcut", "google"); cfg.writeEntry("KeywordDelimiter", QString(s_delimiter)); cfg.sync(); } // Copy kshorturifilterrc from the src dir so we don't depend on make install / env vars. { const QString rcFile = QFINDTESTDATA("../src/urifilters/shorturi/kshorturifilterrc"); QVERIFY(!rcFile.isEmpty()); const QString localFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/kshorturifilterrc"; QFile::remove(localFile); QVERIFY(QFile(rcFile).copy(localFile)); } QDir().mkpath(datahome + QStringLiteral("/urifilter")); } void KUriFilterTest::pluginNames() { const QStringList plugins = KUriFilter::self()->pluginNames(); qDebug() << plugins; const QByteArray debugString = plugins.join(',').toLatin1(); // To make it possible to have external plugins (if there's any...) // we don't just have an expected result list, we just probe it for specific entries. QVERIFY2(plugins.contains("kshorturifilter"), debugString.constData()); QVERIFY2(plugins.contains("kurisearchfilter"), debugString.constData()); QVERIFY2(plugins.contains("localdomainurifilter"), debugString.constData()); QVERIFY2(plugins.contains("fixhosturifilter"), debugString.constData()); QVERIFY2(plugins.contains("kuriikwsfilter"), debugString.constData()); // No duplicates QCOMPARE(plugins.count("kshorturifilter"), 1); } void KUriFilterTest::noFiltering_data() { setupColumns(); // URI that should require no filtering addRow("http://www.kde.org", QStringLiteral("http://www.kde.org"), KUriFilterData::NetProtocol); addRow("http://www.kde.org/developer//index.html", QStringLiteral("http://www.kde.org/developer//index.html"), KUriFilterData::NetProtocol); addRow("file:///", QStringLiteral("/"), KUriFilterData::LocalDir); addRow("file:///etc", QStringLiteral("/etc"), KUriFilterData::LocalDir); addRow("file:///etc/passwd", QStringLiteral("/etc/passwd"), KUriFilterData::LocalFile); } void KUriFilterTest::noFiltering() { runFilterTest(); } void KUriFilterTest::localFiles_data() { setupColumns(); addRow("/", QStringLiteral("/"), KUriFilterData::LocalDir); addRow("/", QStringLiteral("/"), KUriFilterData::LocalDir, QStringList(QStringLiteral("kshorturifilter"))); addRow("//", QStringLiteral("/"), KUriFilterData::LocalDir); addRow("///", QStringLiteral("/"), KUriFilterData::LocalDir); addRow("////", QStringLiteral("/"), KUriFilterData::LocalDir); addRow("///tmp", QStringLiteral("/tmp"), KUriFilterData::LocalDir); if (QFile::exists(QDir::homePath() + QLatin1String("/.bashrc"))) { addRow("~/.bashrc", QDir::homePath() + QStringLiteral("/.bashrc"), KUriFilterData::LocalFile, QStringList(QStringLiteral("kshorturifilter"))); } - addRow("~", QDir::homePath().toLocal8Bit(), KUriFilterData::LocalDir, QStringList(QStringLiteral("kshorturifilter")), QStringLiteral("/tmp")); + addRow("~", QDir::homePath(), KUriFilterData::LocalDir, QStringList(QStringLiteral("kshorturifilter")), QStringLiteral("/tmp")); addRow("~bin", nullptr, KUriFilterData::LocalDir, QStringList(QStringLiteral("kshorturifilter"))); addRow("~does_not_exist", nullptr, KUriFilterData::Error, QStringList(QStringLiteral("kshorturifilter"))); + addRow("~/does_not_exist", QDir::homePath() + "/does_not_exist", KUriFilterData::LocalFile, QStringList(QStringLiteral("kshorturifilter"))); // Absolute Path tests for kshorturifilter const QStringList kshorturifilter(QStringLiteral("kshorturifilter")); addRow("./", datahome, KUriFilterData::LocalDir, kshorturifilter, datahome + QStringLiteral("/")); // cleanPath removes the trailing slash const QString parentDir = QDir().cleanPath(datahome + QStringLiteral("/..")); addRow("../", QFile::encodeName(parentDir), KUriFilterData::LocalDir, kshorturifilter, datahome); addRow("share", datahome, KUriFilterData::LocalDir, kshorturifilter, QFile::encodeName(parentDir)); // Invalid URLs addRow("http://a[b]", QStringLiteral("http://a[b]"), KUriFilterData::Unknown, kshorturifilter, QStringLiteral("/")); } void KUriFilterTest::localFiles() { runFilterTest(); } void KUriFilterTest::refOrQuery_data() { setupColumns(); // URL with reference addRow("http://www.kde.org/index.html#q8", QStringLiteral("http://www.kde.org/index.html#q8"), KUriFilterData::NetProtocol); // local file with reference addRow("file:/etc/passwd#q8", QStringLiteral("file:///etc/passwd#q8"), KUriFilterData::LocalFile); addRow("file:///etc/passwd#q8", QStringLiteral("file:///etc/passwd#q8"), KUriFilterData::LocalFile); addRow("/etc/passwd#q8", QStringLiteral("file:///etc/passwd#q8"), KUriFilterData::LocalFile); // local file with query (can be used by javascript) addRow("file:/etc/passwd?foo=bar", QStringLiteral("file:///etc/passwd?foo=bar"), KUriFilterData::LocalFile); testLocalFile(QStringLiteral("/tmp/kurifiltertest?foo")); // local file with ? in the name (#58990) testLocalFile(QStringLiteral("/tmp/kurlfiltertest#foo")); // local file with '#' in the name testLocalFile(QStringLiteral("/tmp/kurlfiltertest#foo?bar")); // local file with both testLocalFile(QStringLiteral("/tmp/kurlfiltertest?foo#bar")); // local file with both, the other way round } void KUriFilterTest::refOrQuery() { runFilterTest(); } void KUriFilterTest::shortUris_data() { setupColumns(); // hostnames are lowercased by QUrl addRow("http://www.myDomain.commyPort/ViewObjectRes//Default:name=hello", QStringLiteral("http://www.mydomain.commyport/ViewObjectRes//Default:name=hello"), KUriFilterData::NetProtocol); addRow("ftp://ftp.kde.org", QStringLiteral("ftp://ftp.kde.org"), KUriFilterData::NetProtocol); addRow("ftp://username@ftp.kde.org:500", QStringLiteral("ftp://username@ftp.kde.org:500"), KUriFilterData::NetProtocol); // ShortURI/LocalDomain filter tests. addRow("linuxtoday.com", QStringLiteral("http://linuxtoday.com"), KUriFilterData::NetProtocol); addRow("LINUXTODAY.COM", QStringLiteral("http://linuxtoday.com"), KUriFilterData::NetProtocol); addRow("kde.org", QStringLiteral("http://kde.org"), KUriFilterData::NetProtocol); addRow("ftp.kde.org", QStringLiteral("ftp://ftp.kde.org"), KUriFilterData::NetProtocol); addRow("ftp.kde.org:21", QStringLiteral("ftp://ftp.kde.org:21"), KUriFilterData::NetProtocol); addRow("cr.yp.to", QStringLiteral("http://cr.yp.to"), KUriFilterData::NetProtocol); addRow("www.kde.org:21", QStringLiteral("http://www.kde.org:21"), KUriFilterData::NetProtocol); // This one passes but the DNS lookup takes 5 seconds to fail //addRow("foobar.local:8000", QStringLiteral("http://foobar.local:8000"), KUriFilterData::NetProtocol); addRow("foo@bar.com", QStringLiteral("mailto:foo@bar.com"), KUriFilterData::NetProtocol); addRow("firstname.lastname@x.foo.bar", QStringLiteral("mailto:firstname.lastname@x.foo.bar"), KUriFilterData::NetProtocol); addRow("mailto:foo@bar.com", QStringLiteral("mailto:foo@bar.com"), KUriFilterData::NetProtocol); addRow("www.123.foo", QStringLiteral("http://www.123.foo"), KUriFilterData::NetProtocol); addRow("user@www.123.foo:3128", QStringLiteral("http://user@www.123.foo:3128"), KUriFilterData::NetProtocol); addRow("ftp://user@user@www.123.foo:3128", QStringLiteral("ftp://user%40user@www.123.foo:3128"), KUriFilterData::NetProtocol); addRow("user@user@www.123.foo:3128", QStringLiteral("http://user%40user@www.123.foo:3128"), KUriFilterData::NetProtocol); // IPv4 address formats... addRow("user@192.168.1.0:3128", QStringLiteral("http://user@192.168.1.0:3128"), KUriFilterData::NetProtocol); addRow("127.0.0.1", QStringLiteral("http://127.0.0.1"), KUriFilterData::NetProtocol); addRow("127.0.0.1:3128", QStringLiteral("http://127.0.0.1:3128"), KUriFilterData::NetProtocol); addRow("127.1", QStringLiteral("http://127.0.0.1"), KUriFilterData::NetProtocol); // Qt5: QUrl resolves to 127.0.0.1 addRow("127.0.1", QStringLiteral("http://127.0.0.1"), KUriFilterData::NetProtocol); // Qt5: QUrl resolves to 127.0.0.1 // IPv6 address formats (taken from RFC 2732)... addRow("[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html", QStringLiteral("http://[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:80/index.html"), KUriFilterData::NetProtocol); addRow("[1080:0:0:0:8:800:200C:417A]/index.html", QStringLiteral("http://[1080::8:800:200c:417a]/index.html"), KUriFilterData::NetProtocol); // Qt5 QUrl change addRow("[3ffe:2a00:100:7031::1]", QStringLiteral("http://[3ffe:2a00:100:7031::1]"), KUriFilterData::NetProtocol); addRow("[1080::8:800:200C:417A]/foo", QStringLiteral("http://[1080::8:800:200c:417a]/foo"), KUriFilterData::NetProtocol); addRow("[::192.9.5.5]/ipng", QStringLiteral("http://[::192.9.5.5]/ipng"), KUriFilterData::NetProtocol); addRow("[::FFFF:129.144.52.38]:80/index.html", QStringLiteral("http://[::ffff:129.144.52.38]:80/index.html"), KUriFilterData::NetProtocol); addRow("[2010:836B:4179::836B:4179]", QStringLiteral("http://[2010:836b:4179::836b:4179]"), KUriFilterData::NetProtocol); // Local domain filter - If you uncomment these test, make sure you // you adjust it based on the localhost entry in your /etc/hosts file. // addRow( "localhost:3128", "http://localhost.localdomain:3128", KUriFilterData::NetProtocol ); // addRow( "localhost", "http://localhost.localdomain", KUriFilterData::NetProtocol ); // addRow( "localhost/~blah", "http://localhost.localdomain/~blah", KUriFilterData::NetProtocol ); addRow("user@host.domain", QStringLiteral("mailto:user@host.domain"), KUriFilterData::NetProtocol); // new in KDE-3.2 // Windows style SMB (UNC) URL. Should be converted into the valid smb format... addRow("\\\\mainserver\\share\\file", QStringLiteral("smb://mainserver/share/file"), KUriFilterData::NetProtocol); // KDE3: was not be filtered at all. All valid protocols of this form were be ignored. // KDE4: parsed as "network protocol", seems fine to me (DF) addRow("ftp:", QStringLiteral("ftp:"), KUriFilterData::NetProtocol); addRow("http:", QStringLiteral("http:"), KUriFilterData::NetProtocol); // The default search engine is set to 'Google' //this may fail if your DNS knows domains KDE or FTP addRow("gg:", QLatin1String(""), KUriFilterData::NetProtocol); // see bug 56218 addRow("KDE", QStringLiteral("https://www.google.com/search?q=KDE&ie=UTF-8"), KUriFilterData::NetProtocol); addRow("HTTP", QStringLiteral("https://www.google.com/search?q=HTTP&ie=UTF-8"), KUriFilterData::NetProtocol); } void KUriFilterTest::shortUris() { runFilterTest(); } void KUriFilterTest::executables_data() { setupColumns(); // Executable tests - No IKWS in minicli addRow("cp", QStringLiteral("cp"), KUriFilterData::Executable, minicliFilters); addRow("kbuildsycoca5", QStringLiteral("kbuildsycoca5"), KUriFilterData::Executable, minicliFilters); addRow("KDE", QStringLiteral("KDE"), NO_FILTERING, minicliFilters); - addRow("I/dont/exist", QStringLiteral("I/dont/exist"), NO_FILTERING, minicliFilters); //krazy:exclude=spelling - addRow("/I/dont/exist", nullptr, KUriFilterData::Error, minicliFilters); //krazy:exclude=spelling - addRow("/I/dont/exist#a", nullptr, KUriFilterData::Error, minicliFilters); //krazy:exclude=spelling + addRow("does/not/exist", QStringLiteral("does/not/exist"), NO_FILTERING, minicliFilters); + addRow("/does/not/exist", QStringLiteral("/does/not/exist"), KUriFilterData::LocalFile, minicliFilters); + addRow("/does/not/exist#a", QStringLiteral("/does/not/exist#a"), KUriFilterData::LocalFile, minicliFilters); addRow("kbuildsycoca5 --help", QStringLiteral("kbuildsycoca5 --help"), KUriFilterData::Executable, minicliFilters); // the args are in argsAndOptions() addRow("/bin/sh", QStringLiteral("/bin/sh"), KUriFilterData::Executable, minicliFilters); addRow("/bin/sh -q -option arg1", QStringLiteral("/bin/sh -q -option arg1"), KUriFilterData::Executable, minicliFilters); // the args are in argsAndOptions() // Typing 'cp' or any other valid unix command in konq's location bar should result in // a search using the default search engine // 'ls' is a bit of a special case though, due to the toplevel domain called 'ls' addRow("cp", QStringLiteral("https://www.google.com/search?q=cp&ie=UTF-8"), KUriFilterData::NetProtocol, QStringList(), nullptr, false /* don't check for executables, see konq_misc.cc */); } void KUriFilterTest::executables() { runFilterTest(); } void KUriFilterTest::environmentVariables_data() { setupColumns(); // ENVIRONMENT variable qputenv("SOMEVAR", "/somevar"); qputenv("ETC", "/etc"); - addRow("$SOMEVAR/kdelibs/kio", nullptr, KUriFilterData::Error); // note: this dir doesn't exist... + addRow("$SOMEVAR/kdelibs/kio", "/somevar/kdelibs/kio", KUriFilterData::LocalFile); // note: this dir doesn't exist... addRow("$ETC/passwd", QStringLiteral("/etc/passwd"), KUriFilterData::LocalFile); QString qtdocPath = qtdir + QStringLiteral("/doc/html/functions.html"); if (QFile::exists(qtdocPath)) { QString expectedUrl = QUrl::fromLocalFile(qtdocPath).toString() + "#s"; addRow("$QTDIR/doc/html/functions.html#s", expectedUrl.toUtf8(), KUriFilterData::LocalFile); } addRow("http://www.kde.org/$USER", QStringLiteral("http://www.kde.org/$USER"), KUriFilterData::NetProtocol); // no expansion addRow("$DATAHOME", datahome, KUriFilterData::LocalDir); QDir().mkpath(datahome + QStringLiteral("/urifilter/a+plus")); addRow("$DATAHOME/urifilter/a+plus", datahome + QStringLiteral("/urifilter/a+plus"), KUriFilterData::LocalDir); // BR 27788 QDir().mkpath(datahome + QStringLiteral("/Dir With Space")); addRow("$DATAHOME/Dir With Space", datahome + QStringLiteral("/Dir With Space"), KUriFilterData::LocalDir); // support for name filters (BR 93825) addRow("$DATAHOME/*.txt", datahome + QStringLiteral("/*.txt"), KUriFilterData::LocalDir); addRow("$DATAHOME/[a-b]*.txt", datahome + QStringLiteral("/[a-b]*.txt"), KUriFilterData::LocalDir); addRow("$DATAHOME/a?c.txt", datahome + QStringLiteral("/a?c.txt"), KUriFilterData::LocalDir); addRow("$DATAHOME/?c.txt", datahome + QStringLiteral("/?c.txt"), KUriFilterData::LocalDir); // but let's check that a directory with * in the name still works QDir().mkpath(datahome + QStringLiteral("/share/Dir*With*Stars")); addRow("$DATAHOME/Dir*With*Stars", datahome + QStringLiteral("/Dir*With*Stars"), KUriFilterData::LocalDir); QDir().mkpath(datahome + QStringLiteral("/Dir?QuestionMark")); addRow("$DATAHOME/Dir?QuestionMark", datahome + QStringLiteral("/Dir?QuestionMark"), KUriFilterData::LocalDir); QDir().mkpath(datahome + QStringLiteral("/Dir[Bracket")); addRow("$DATAHOME/Dir[Bracket", datahome + QStringLiteral("/Dir[Bracket"), KUriFilterData::LocalDir); - addRow("$HOME/$KDEDIR/kdebase/kcontrol/ebrowsing", nullptr, KUriFilterData::Error); + addRow("$HOME/$KDEDIR/kdebase/kcontrol/ebrowsing", "", KUriFilterData::LocalFile); addRow("$1/$2/$3", QStringLiteral("https://www.google.com/search?q=%241%2F%242%2F%243&ie=UTF-8"), KUriFilterData::NetProtocol); // can be used as bogus or valid test. Currently triggers default search, i.e. google addRow("$$$$", QStringLiteral("https://www.google.com/search?q=%24%24%24%24&ie=UTF-8"), KUriFilterData::NetProtocol); // worst case scenarios. if (!qtdir.isEmpty()) { addRow("$QTDIR", qtdir, KUriFilterData::LocalDir, QStringList(QStringLiteral("kshorturifilter"))); //use specific filter. } addRow("$HOME", home, KUriFilterData::LocalDir, QStringList(QStringLiteral("kshorturifilter"))); //use specific filter. } void KUriFilterTest::environmentVariables() { runFilterTest(); } void KUriFilterTest::internetKeywords_data() { setupColumns(); QString sc; addRow(sc.sprintf("gg%cfoo bar", s_delimiter).toUtf8(), QStringLiteral("https://www.google.com/search?q=foo+bar&ie=UTF-8"), KUriFilterData::NetProtocol); addRow(sc.sprintf("bug%c55798", s_delimiter).toUtf8(), QStringLiteral("https://bugs.kde.org/show_bug.cgi?id=55798"), KUriFilterData::NetProtocol); addRow(sc.sprintf("gg%cC++", s_delimiter).toUtf8(), QStringLiteral("https://www.google.com/search?q=C%2B%2B&ie=UTF-8"), KUriFilterData::NetProtocol); addRow(sc.sprintf("gg%cC#", s_delimiter).toUtf8(), QStringLiteral("https://www.google.com/search?q=C%23&ie=UTF-8"), KUriFilterData::NetProtocol); addRow(sc.sprintf("ya%cfoo bar was here", s_delimiter).toUtf8(), nullptr, -1); // this triggers default search, i.e. google addRow(sc.sprintf("gg%cwww.kde.org", s_delimiter).toUtf8(), QStringLiteral("https://www.google.com/search?q=www.kde.org&ie=UTF-8"), KUriFilterData::NetProtocol); addRow(QStringLiteral("gg%1é").arg(s_delimiter).toUtf8() /*eaccent in utf8*/, QStringLiteral("https://www.google.com/search?q=%C3%A9&ie=UTF-8"), KUriFilterData::NetProtocol); addRow(QStringLiteral("gg%1прйвет").arg(s_delimiter).toUtf8() /* greetings in russian utf-8*/, QStringLiteral("https://www.google.com/search?q=%D0%BF%D1%80%D0%B9%D0%B2%D0%B5%D1%82&ie=UTF-8"), KUriFilterData::NetProtocol); } void KUriFilterTest::internetKeywords() { runFilterTest(); } void KUriFilterTest::localdomain() { const QString host = QHostInfo::localHostName(); if (host.isEmpty()) { const QString expected = QLatin1String("http://") + host; runFilterTest(host, expected, KUriFilterData::NetProtocol, QStringList() << QStringLiteral("localdomainurifilter"), nullptr, false); } } diff --git a/src/urifilters/shorturi/kshorturifilter.cpp b/src/urifilters/shorturi/kshorturifilter.cpp index 2ec3fc51..4b28c660 100644 --- a/src/urifilters/shorturi/kshorturifilter.cpp +++ b/src/urifilters/shorturi/kshorturifilter.cpp @@ -1,574 +1,567 @@ /* -*- c-basic-offset: 2 -*- kshorturifilter.h This file is part of the KDE project Copyright (C) 2000 Dawit Alemayehu Copyright (C) 2000 Malte Starostik This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kshorturifilter.h" #include "../../pathhelpers_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { QLoggingCategory category("org.kde.kurifilter-shorturi", QtWarningMsg); } /** * IMPORTANT: If you change anything here, make sure you run the kurifiltertest * regression test (this should be included as part of "make test"). * * If you add anything, make sure to extend kurifiltertest to make sure it is * covered. */ typedef QMap EntryMap; static QRegularExpression sEnvVarExp (QStringLiteral("\\$[a-zA-Z_][a-zA-Z0-9_]*")); static bool isPotentialShortURL(const QString& cmd) { // Host names and IPv4 address... if (cmd.contains(QLatin1Char('.'))) { return true; } // IPv6 Address... if (cmd.startsWith(QLatin1Char('[')) && cmd.contains(QLatin1Char(':'))) { return true; } return false; } static QString removeArgs( const QString& _cmd ) { QString cmd( _cmd ); if( cmd[0] != '\'' && cmd[0] != '"' ) { // Remove command-line options (look for first non-escaped space) int spacePos = 0; do { spacePos = cmd.indexOf( ' ', spacePos+1 ); } while ( spacePos > 1 && cmd[spacePos - 1] == '\\' ); if( spacePos > 0 ) { cmd = cmd.left( spacePos ); qCDebug(category) << "spacePos=" << spacePos << " returning " << cmd; } } return cmd; } static bool isKnownProtocol(const QString &protocol) { if (KProtocolInfo::isKnownProtocol(protocol) || protocol == QLatin1String("mailto")) { return true; } const KService::Ptr service = KMimeTypeTrader::self()->preferredService(QLatin1String("x-scheme-handler/") + protocol); return service; } KShortUriFilter::KShortUriFilter( QObject *parent, const QVariantList & /*args*/ ) :KUriFilterPlugin( QStringLiteral("kshorturifilter"), parent ) { QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/"), QStringLiteral("org.kde.KUriFilterPlugin"), QStringLiteral("configure"), this, SLOT(configure())); configure(); } bool KShortUriFilter::filterUri( KUriFilterData& data ) const { /* * Here is a description of how the shortURI deals with the supplied * data. First it expands any environment variable settings and then * deals with special shortURI cases. These special cases are the "smb:" * URL scheme which is very specific to KDE, "#" and "##" which are * shortcuts for man:/ and info:/ protocols respectively. It then handles * local files. Then it checks to see if the URL is valid and one that is * supported by KDE's IO system. If all the above checks fails, it simply * lookups the URL in the user-defined list and returns without filtering * if it is not found. TODO: the user-defined table is currently only manually * hackable and is missing a config dialog. */ //QUrl url = data.uri(); QString cmd = data.typedString(); int firstNonSlash = 0; while (firstNonSlash < cmd.length() && (cmd.at(firstNonSlash) == '/')) { firstNonSlash++; } if (firstNonSlash > 1) { cmd = cmd.mid(firstNonSlash - 1); } // Replicate what KUrl(cmd) did in KDE4. This could later be folded into the checks further down... QUrl url; if (QDir::isAbsolutePath(cmd)) { url = QUrl::fromLocalFile(cmd); } else { url.setUrl(cmd); } // WORKAROUND: Allow the use of '@' in the username component of a URL since // other browsers such as firefox in their infinite wisdom allow such blatant // violations of RFC 3986. BR# 69326/118413. if (cmd.count(QLatin1Char('@')) > 1) { const int lastIndex = cmd.lastIndexOf(QLatin1Char('@')); // Percent encode all but the last '@'. QString encodedCmd = QUrl::toPercentEncoding(cmd.left(lastIndex), ":/"); encodedCmd += cmd.midRef(lastIndex); cmd = encodedCmd; url.setUrl(encodedCmd); } const bool isMalformed = !url.isValid(); QString protocol = url.scheme(); qCDebug(category) << cmd; // Fix misparsing of "foo:80", QUrl thinks "foo" is the protocol and "80" is the path. // However, be careful not to do that for valid hostless URLs, e.g. file:///foo! if (!protocol.isEmpty() && url.host().isEmpty() && !url.path().isEmpty() && cmd.contains(':') && !isKnownProtocol(protocol)) { protocol.clear(); } qCDebug(category) << "url=" << url << "cmd=" << cmd << "isMalformed=" << isMalformed; // TODO: Make this a bit more intelligent for Minicli! There // is no need to make comparisons if the supplied data is a local // executable and only the argument part, if any, changed! (Dawit) // You mean caching the last filtering, to try and reuse it, to save stat()s? (David) const QString starthere_proto = QStringLiteral("start-here:"); if (cmd.indexOf(starthere_proto) == 0 ) { setFilteredUri( data, QUrl(QStringLiteral("system:/")) ); setUriType( data, KUriFilterData::LocalDir ); return true; } // Handle MAN & INFO pages shortcuts... const QString man_proto = QStringLiteral("man:"); const QString info_proto = QStringLiteral("info:"); if( cmd[0] == '#' || cmd.indexOf( man_proto ) == 0 || cmd.indexOf( info_proto ) == 0 ) { if( cmd.leftRef(2) == QLatin1String("##") ) cmd = QStringLiteral("info:/") + cmd.mid(2); else if ( cmd[0] == '#' ) cmd = QStringLiteral("man:/") + cmd.mid(1); else if ((cmd==info_proto) || (cmd==man_proto)) cmd+='/'; setFilteredUri( data, QUrl( cmd )); setUriType( data, KUriFilterData::Help ); return true; } // Detect UNC style (aka windows SMB) URLs if ( cmd.startsWith( QLatin1String( "\\\\") ) ) { // make sure path is unix style cmd.replace('\\', '/'); cmd.prepend( QLatin1String( "smb:" ) ); setFilteredUri( data, QUrl( cmd )); setUriType( data, KUriFilterData::NetProtocol ); return true; } bool expanded = false; // Expanding shortcut to HOME URL... QString path; QString ref; QString query; QString nameFilter; if (QDir::isRelativePath(cmd) && QUrl(cmd).isRelative()) { path = cmd; qCDebug(category) << "path=cmd=" << path; } else { if (url.isLocalFile()) { qCDebug(category) << "hasRef=" << url.hasFragment(); // Split path from ref/query // but not for "/tmp/a#b", if "a#b" is an existing file, // or for "/tmp/a?b" (#58990) if( ( url.hasFragment() || !url.query().isEmpty() ) && !url.path().endsWith(QLatin1Char('/')) ) // /tmp/?foo is a namefilter, not a query { path = url.path(); ref = url.fragment(); qCDebug(category) << "isLocalFile set path to" << path << "and ref to" << ref; query = url.query(); if (path.isEmpty() && !url.host().isEmpty()) path = '/'; } else { if (cmd.startsWith(QLatin1String("file://"))) { path = cmd.mid(strlen("file://")); } else { path = cmd; } qCDebug(category) << "(2) path=cmd=" << path; } } } if( path[0] == '~' ) { int slashPos = path.indexOf('/'); if( slashPos == -1 ) slashPos = path.length(); if( slashPos == 1 ) // ~/ { path.replace ( 0, 1, QDir::homePath() ); } else // ~username/ { const QString userName (path.mid( 1, slashPos-1 )); KUser user (userName); if( user.isValid() && !user.homeDir().isEmpty()) { path.replace (0, slashPos, user.homeDir()); } else { if (user.isValid()) { setErrorMsg(data, i18n("%1 does not have a home folder.", userName)); } else { setErrorMsg(data, i18n("There is no user called %1.", userName)); } setUriType( data, KUriFilterData::Error ); // Always return true for error conditions so // that other filters will not be invoked !! return true; } } expanded = true; } else if ( path[0] == '$' ) { // Environment variable expansion. auto match = sEnvVarExp.match(path); if ( match.hasMatch() ) { QByteArray exp = qgetenv( path.mid( 1, match.capturedLength() - 1 ).toLocal8Bit().data() ); if (!exp.isEmpty()) { path.replace( 0, match.capturedLength(), QFile::decodeName(exp) ); expanded = true; } } } if ( expanded || cmd.startsWith( '/' ) ) { // Look for #ref again, after $ and ~ expansion (testcase: $QTDIR/doc/html/functions.html#s) // Can't use QUrl here, setPath would escape it... const int pos = path.indexOf('#'); if ( pos > -1 ) { const QString newPath = path.left( pos ); if ( QFile::exists( newPath ) ) { ref = path.mid( pos + 1 ); path = newPath; qCDebug(category) << "Extracted ref: path=" << path << " ref=" << ref; } } } bool isLocalFullPath = QDir::isAbsolutePath(path); // Checking for local resource match... // Determine if "uri" is an absolute path to a local resource OR // A local resource with a supplied absolute path in KUriFilterData const QString abs_path = data.absolutePath(); const bool canBeAbsolute = (protocol.isEmpty() && !abs_path.isEmpty()); const bool canBeLocalAbsolute = (canBeAbsolute && abs_path[0] =='/' && !isMalformed); bool exists = false; /*qCDebug(category) << "abs_path=" << abs_path << "protocol=" << protocol << "canBeAbsolute=" << canBeAbsolute << "canBeLocalAbsolute=" << canBeLocalAbsolute << "isLocalFullPath=" << isLocalFullPath;*/ QT_STATBUF buff; if ( canBeLocalAbsolute ) { QString abs = QDir::cleanPath( abs_path ); // combine absolute path (abs_path) and relative path (cmd) into abs_path int len = path.length(); if( (len==1 && path[0]=='.') || (len==2 && path[0]=='.' && path[1]=='.') ) path += '/'; qCDebug(category) << "adding " << abs << " and " << path; abs = QDir::cleanPath(abs + '/' + path); qCDebug(category) << "checking whether " << abs << " exists."; // Check if it exists if(QT_STAT(QFile::encodeName(abs), &buff) == 0) { path = abs; // yes -> store as the new cmd exists = true; isLocalFullPath = true; } } if (isLocalFullPath && !exists && !isMalformed) { exists = QT_STAT(QFile::encodeName(path), &buff) == 0; if ( !exists ) { // Support for name filter (/foo/*.txt), see also KonqMainWindow::detectNameFilter // If the app using this filter doesn't support it, well, it'll simply error out itself int lastSlash = path.lastIndexOf( '/' ); if ( lastSlash > -1 && path.indexOf( ' ', lastSlash ) == -1 ) // no space after last slash, otherwise it's more likely command-line arguments { QString fileName = path.mid( lastSlash + 1 ); QString testPath = path.left(lastSlash); if ((fileName.indexOf('*') != -1 || fileName.indexOf('[') != -1 || fileName.indexOf( '?' ) != -1) && QT_STAT(QFile::encodeName(testPath), &buff) == 0) { nameFilter = fileName; qCDebug(category) << "Setting nameFilter to" << nameFilter << "and path to" << testPath; path = testPath; exists = true; } } } } qCDebug(category) << "path =" << path << " isLocalFullPath=" << isLocalFullPath << " exists=" << exists << " url=" << url; if( exists ) { QUrl u = QUrl::fromLocalFile(path); qCDebug(category) << "ref=" << ref << "query=" << query; u.setFragment(ref); u.setQuery(query); if (!KUrlAuthorized::authorizeUrlAction( QStringLiteral("open"), QUrl(), u)) { // No authorization, we pretend it's a file will get // an access denied error later on. setFilteredUri( data, u ); setUriType( data, KUriFilterData::LocalFile ); return true; } // Can be abs path to file or directory, or to executable with args bool isDir = ((buff.st_mode & QT_STAT_MASK) == QT_STAT_DIR); if( !isDir && access ( QFile::encodeName(path).data(), X_OK) == 0 ) { qCDebug(category) << "Abs path to EXECUTABLE"; setFilteredUri( data, u ); setUriType( data, KUriFilterData::Executable ); return true; } // Open "uri" as file:/xxx if it is a non-executable local resource. if( isDir || (( buff.st_mode & QT_STAT_MASK ) == QT_STAT_REG) ) { qCDebug(category) << "Abs path as local file or directory"; if ( !nameFilter.isEmpty() ) u.setPath(concatPaths(u.path(), nameFilter)); setFilteredUri( data, u ); setUriType( data, ( isDir ) ? KUriFilterData::LocalDir : KUriFilterData::LocalFile ); return true; } // Should we return LOCAL_FILE for non-regular files too? qCDebug(category) << "File found, but not a regular file nor dir... socket?"; } if( data.checkForExecutables()) { // Let us deal with possible relative URLs to see // if it is executable under the user's $PATH variable. // We try hard to avoid parsing any possible command // line arguments or options that might have been supplied. QString exe = removeArgs( cmd ); qCDebug(category) << "findExe with" << exe; if (!QStandardPaths::findExecutable( exe ).isNull() ) { qCDebug(category) << "EXECUTABLE exe=" << exe; setFilteredUri( data, QUrl::fromLocalFile( exe )); // check if we have command line arguments if( exe != cmd ) setArguments(data, cmd.right(cmd.length() - exe.length())); setUriType( data, KUriFilterData::Executable ); return true; } } // Process URLs of known and supported protocols so we don't have // to resort to the pattern matching scheme below which can possibly // slow things down... if ( !isMalformed && !isLocalFullPath && !protocol.isEmpty() ) { qCDebug(category) << "looking for protocol" << protocol; if (isKnownProtocol(protocol)) { setFilteredUri( data, url ); if ( protocol == QLatin1String("man") || protocol == QLatin1String("help") ) setUriType( data, KUriFilterData::Help ); else setUriType( data, KUriFilterData::NetProtocol ); return true; } } // Short url matches if ( !cmd.contains( ' ' ) ) { // Okay this is the code that allows users to supply custom matches for // specific URLs using Qt's regexp class. This is hard-coded for now. // TODO: Make configurable at some point... Q_FOREACH(const URLHint& hint, m_urlHints) { qCDebug(category) << "testing regexp for" << hint.prepend; if (hint.regexp.indexIn(cmd) == 0) { const QString cmdStr = hint.prepend + cmd; QUrl url(cmdStr); qCDebug(category) << "match - prepending" << hint.prepend << "->" << cmdStr << "->" << url; setFilteredUri( data, url ); setUriType( data, hint.type ); return true; } } // No protocol and not malformed means a valid short URL such as kde.org or // user@192.168.0.1. However, it might also be valid only because it lacks // the scheme component, e.g. www.kde,org (illegal ',' before 'org'). The // check below properly deciphers the difference between the two and sends // back the proper result. if (protocol.isEmpty() && isPotentialShortURL(cmd)) { QString urlStr = data.defaultUrlScheme(); if (urlStr.isEmpty()) urlStr = m_strDefaultUrlScheme; const int index = urlStr.indexOf(QLatin1Char(':')); if (index == -1 || !isKnownProtocol(urlStr.left(index))) urlStr += QStringLiteral("://"); urlStr += cmd; QUrl url (urlStr); if (url.isValid()) { setFilteredUri(data, url); setUriType(data, KUriFilterData::NetProtocol); } else if (isKnownProtocol(url.scheme())) { setFilteredUri(data, data.uri()); setUriType(data, KUriFilterData::Error); } return true; } } // If we previously determined that the URL might be a file, - // and if it doesn't exist, then error + // and if it doesn't exist... we'll pretend it exists. + // This allows to use it for completion purposes. + // (If you change this logic again, look at the commit that was testing + // for KUrlAuthorized::authorizeUrlAction("open")) if( isLocalFullPath && !exists ) { QUrl u = QUrl::fromLocalFile(path); u.setFragment(ref); - - if (!KUrlAuthorized::authorizeUrlAction( QStringLiteral("open"), QUrl(), u)) - { - // No authorization, we pretend it exists and will get - // an access denied error later on. - setFilteredUri( data, u ); - setUriType( data, KUriFilterData::LocalFile ); - return true; - } - qCDebug(category) << "fileNotFound -> ERROR"; - setErrorMsg( data, i18n( "The file or folder %1 does not exist.", data.uri().toDisplayString() ) ); - setUriType( data, KUriFilterData::Error ); + setFilteredUri(data, u); + setUriType(data, KUriFilterData::LocalFile); return true; } // If we reach this point, we cannot filter this thing so simply return false // so that other filters, if present, can take a crack at it. return false; } KCModule* KShortUriFilter::configModule( QWidget*, const char* ) const { return nullptr; //new KShortUriOptions( parent, name ); } QString KShortUriFilter::configName() const { // return i18n("&ShortURLs"); we don't have a configModule so no need for a configName that confuses translators return KUriFilterPlugin::configName(); } void KShortUriFilter::configure() { KConfig config( objectName() + QStringLiteral( "rc"), KConfig::NoGlobals ); KConfigGroup cg( config.group("") ); m_strDefaultUrlScheme = cg.readEntry( "DefaultProtocol", QStringLiteral("http://") ); const EntryMap patterns = config.entryMap( QStringLiteral("Pattern") ); const EntryMap protocols = config.entryMap( QStringLiteral("Protocol") ); KConfigGroup typeGroup(&config, "Type"); for( EntryMap::ConstIterator it = patterns.begin(); it != patterns.end(); ++it ) { QString protocol = protocols[it.key()]; if (!protocol.isEmpty()) { int type = typeGroup.readEntry(it.key(), -1); if (type > -1 && type <= KUriFilterData::Unknown) m_urlHints.append( URLHint(it.value(), protocol, static_cast(type) ) ); else m_urlHints.append( URLHint(it.value(), protocol) ); } } } K_PLUGIN_FACTORY_WITH_JSON(KShortUriFilterFactory, "kshorturifilter.json", registerPlugin();) #include "kshorturifilter.moc"