diff --git a/autotests/kurifiltertest.cpp b/autotests/kurifiltertest.cpp index 75422cca..57b7dc5e 100644 --- a/autotests/kurifiltertest.cpp +++ b/autotests/kurifiltertest.cpp @@ -1,474 +1,478 @@ /* * 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("~bin", nullptr, KUriFilterData::LocalDir, QStringList(QStringLiteral("kshorturifilter"))); addRow("~does_not_exist", nullptr, KUriFilterData::Error, 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("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("$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("$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/ikws/CMakeLists.txt b/src/urifilters/ikws/CMakeLists.txt index a0e060b0..ebe5828e 100644 --- a/src/urifilters/ikws/CMakeLists.txt +++ b/src/urifilters/ikws/CMakeLists.txt @@ -1,40 +1,47 @@ add_subdirectory(searchproviders) #### -set(kuriikwsfilter_SRCS kuriikwsfiltereng.cpp kuriikwsfilter.cpp searchprovider.cpp) +set(kuriikwsfilter_SRCS + kuriikwsfiltereng.cpp + kuriikwsfilter.cpp + searchprovider.cpp + searchproviderregistry.cpp +) ki18n_wrap_ui(kuriikwsfilter_SRCS ikwsopts_ui.ui searchproviderdlg_ui.ui) kcoreaddons_add_plugin(kuriikwsfilter INSTALL_NAMESPACE "kf5/urifilters" JSON kuriikwsfilter.json SOURCES ${kuriikwsfilter_SRCS}) target_link_libraries(kuriikwsfilter KF5::ConfigWidgets # KCModule KF5::KIOWidgets # KUriFilter KF5::I18n Qt5::DBus ) ##### set(kurisearchfilter_SRCS kurisearchfilter.cpp kuriikwsfiltereng.cpp ikwsopts.cpp searchproviderdlg.cpp - searchprovider.cpp ) + searchprovider.cpp + searchproviderregistry.cpp +) ki18n_wrap_ui(kurisearchfilter_SRCS ikwsopts_ui.ui searchproviderdlg_ui.ui) kcoreaddons_add_plugin(kurisearchfilter INSTALL_NAMESPACE "kf5/urifilters" JSON kurisearchfilter.json SOURCES ${kurisearchfilter_SRCS}) target_link_libraries(kurisearchfilter KF5::ConfigWidgets # KCModule KF5::KIOWidgets # KUriFilter KF5::I18n Qt5::DBus ) ########### install files ############### install( FILES searchprovider.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR} ) diff --git a/src/urifilters/ikws/ikwsopts.cpp b/src/urifilters/ikws/ikwsopts.cpp index 1c89bd7d..59c29c8d 100644 --- a/src/urifilters/ikws/ikwsopts.cpp +++ b/src/urifilters/ikws/ikwsopts.cpp @@ -1,456 +1,452 @@ /* * Copyright (c) 2000 Yves Arrouye * Copyright (c) 2001, 2002 Dawit Alemayehu * Copyright (c) 2009 Nick Shaforostoff * * 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 "ikwsopts.h" #include "ikwsopts_p.h" #include "kuriikwsfiltereng.h" #include "searchprovider.h" #include "searchproviderdlg.h" -#include #include #include #include #include #include #include #include //BEGIN ProvidersModel ProvidersModel::~ProvidersModel() { qDeleteAll(m_providers); } QVariant ProvidersModel::headerData(int section, Qt::Orientation orientation, int role ) const { Q_UNUSED(orientation); if (role == Qt::DisplayRole) { switch (section) { case Name: return i18nc("@title:column Name label from web shortcuts column", "Name"); case Shortcuts: return i18nc("@title:column", "Shortcuts"); case Preferred: return i18nc("@title:column", "Preferred"); default: break; } } return QVariant(); } Qt::ItemFlags ProvidersModel::flags(const QModelIndex& index) const { if (!index.isValid()) return Qt::ItemIsEnabled; if (index.column()==Preferred) return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } bool ProvidersModel::setData (const QModelIndex& index, const QVariant& value, int role) { if (role==Qt::CheckStateRole) { if (value.toInt() == Qt::Checked) m_favoriteEngines.insert(m_providers.at(index.row())->desktopEntryName()); else m_favoriteEngines.remove(m_providers.at(index.row())->desktopEntryName()); emit dataModified(); return true; } return false; } QVariant ProvidersModel::data(const QModelIndex& index, int role) const { if (index.isValid()) { if (role == Qt::CheckStateRole && index.column()==Preferred) return (m_favoriteEngines.contains(m_providers.at(index.row())->desktopEntryName()) ? Qt::Checked : Qt::Unchecked); if (role == Qt::DisplayRole) { if (index.column()==Name) return m_providers.at(index.row())->name(); if (index.column()==Shortcuts) return m_providers.at(index.row())->keys().join(QStringLiteral(",")); } if (role == Qt::ToolTipRole || role == Qt::WhatsThisRole) { if (index.column() == Preferred) return xi18nc("@info:tooltip", "Check this box to select the highlighted web shortcut " "as preferred.Preferred web shortcuts are used in " "places where only a few select shortcuts can be shown " "at one time."); } if (role == Qt::UserRole) return index.row();//a nice way to bypass proxymodel } return QVariant(); } void ProvidersModel::setProviders(const QList& providers, const QStringList& favoriteEngines) { m_providers = providers; setFavoriteProviders(favoriteEngines); } void ProvidersModel::setFavoriteProviders(const QStringList& favoriteEngines) { beginResetModel(); m_favoriteEngines = QSet::fromList(favoriteEngines); endResetModel(); } int ProvidersModel::rowCount(const QModelIndex & parent) const { if (parent.isValid()) return 0; return m_providers.size(); } QAbstractListModel* ProvidersModel::createListModel() { ProvidersListModel* pListModel = new ProvidersListModel(m_providers, this); connect(this, SIGNAL(modelAboutToBeReset()), pListModel, SIGNAL(modelAboutToBeReset())); connect(this, SIGNAL(modelReset()), pListModel, SIGNAL(modelReset())); connect(this, SIGNAL(layoutAboutToBeChanged()), pListModel, SIGNAL(modelReset())); connect(this, SIGNAL(layoutChanged()), pListModel, SIGNAL(modelReset())); connect(this, SIGNAL(dataChanged(QModelIndex,QModelIndex)), pListModel, SLOT(emitDataChanged(QModelIndex,QModelIndex))); connect(this, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), pListModel, SLOT(emitRowsAboutToBeInserted(QModelIndex,int,int))); connect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), pListModel, SLOT(emitRowsAboutToBeRemoved(QModelIndex,int,int))); connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), pListModel, SLOT(emitRowsInserted(QModelIndex,int,int))); connect(this, SIGNAL(rowsRemoved(QModelIndex,int,int)), pListModel, SLOT(emitRowsRemoved(QModelIndex,int,int))); return pListModel; } void ProvidersModel::deleteProvider(SearchProvider* p) { const int row = m_providers.indexOf(p); beginRemoveRows(QModelIndex(), row, row); m_favoriteEngines.remove(m_providers.takeAt(row)->desktopEntryName()); endRemoveRows(); delete p; emit dataModified(); } void ProvidersModel::addProvider(SearchProvider* p) { beginInsertRows(QModelIndex(), m_providers.size(), m_providers.size()); m_providers.append(p); endInsertRows(); emit dataModified(); } void ProvidersModel::changeProvider(SearchProvider* p) { const int row = m_providers.indexOf(p); emit dataChanged(index(row,0),index(row,ColumnCount-1)); emit dataModified(); } QStringList ProvidersModel::favoriteEngines() const { return m_favoriteEngines.toList(); } //END ProvidersModel //BEGIN ProvidersListModel ProvidersListModel::ProvidersListModel(QList& providers, QObject* parent) : QAbstractListModel(parent) , m_providers(providers) {} QVariant ProvidersListModel::data(const QModelIndex& index, int role) const { if (index.isValid()) { if (role==Qt::DisplayRole) { if (index.row() == m_providers.size()) return i18nc("@item:inlistbox No default web shortcut", "None"); return m_providers.at(index.row())->name(); } if (role==ShortNameRole) { if (index.row() == m_providers.size()) return QString(); return m_providers.at(index.row())->desktopEntryName(); } } return QVariant(); } int ProvidersListModel::rowCount (const QModelIndex& parent) const { if (parent.isValid()) return 0; return m_providers.size() + 1; } //END ProvidersListModel static QSortFilterProxyModel* wrapInProxyModel(QAbstractItemModel* model) { QSortFilterProxyModel* proxyModel = new QSortFilterProxyModel(model); proxyModel->setSourceModel(model); proxyModel->setDynamicSortFilter(true); proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); proxyModel->setFilterKeyColumn(-1); return proxyModel; } FilterOptions::FilterOptions(const KAboutData* about, QWidget *parent) : KCModule(about, parent), m_providersModel(new ProvidersModel(this)) { m_dlg.setupUi(this); QSortFilterProxyModel* searchProviderModel = wrapInProxyModel(m_providersModel); m_dlg.lvSearchProviders->setModel(searchProviderModel); m_dlg.cmbDefaultEngine->setModel(wrapInProxyModel(m_providersModel->createListModel())); // Connect all the signals/slots... connect(m_dlg.cbEnableShortcuts, SIGNAL(toggled(bool)), SLOT(changed())); connect(m_dlg.cbEnableShortcuts, SIGNAL(toggled(bool)), SLOT(updateSearchProviderEditingButons())); connect(m_dlg.cbUseSelectedShortcutsOnly, SIGNAL(toggled(bool)), SLOT(changed())); connect(m_providersModel, SIGNAL(dataModified()), SLOT(changed())); connect(m_dlg.cmbDefaultEngine, SIGNAL(currentIndexChanged(int)), SLOT(changed())); connect(m_dlg.cmbDelimiter, SIGNAL(currentIndexChanged(int)), SLOT(changed())); connect(m_dlg.pbNew, SIGNAL(clicked()), SLOT(addSearchProvider())); connect(m_dlg.pbDelete, SIGNAL(clicked()), SLOT(deleteSearchProvider())); connect(m_dlg.pbChange, SIGNAL(clicked()), SLOT(changeSearchProvider())); connect(m_dlg.lvSearchProviders->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), SLOT(updateSearchProviderEditingButons())); connect(m_dlg.lvSearchProviders, SIGNAL(doubleClicked(QModelIndex)),SLOT(changeSearchProvider())); connect(m_dlg.searchLineEdit, SIGNAL(textEdited(QString)), searchProviderModel, SLOT(setFilterFixedString(QString))); } QString FilterOptions::quickHelp() const { return xi18nc("@info:whatsthis", "In this module you can configure the web shortcuts feature. " "Web shortcuts allow you to quickly search or lookup words on " "the Internet. For example, to search for information about the " "KDE project using the Google engine, you simply type gg:KDE " "or google:KDE." "If you select a default search engine, then you can search for " "normal words or phrases by simply typing them into the input widget " "of applications that have built-in support for such a feature, e.g " "Konqueror."); } void FilterOptions::setDefaultEngine(int index) { QSortFilterProxyModel* proxy = qobject_cast(m_dlg.cmbDefaultEngine->model()); if (index == -1) index = proxy->rowCount()-1;//"None" is the last const QModelIndex modelIndex = proxy->mapFromSource(proxy->sourceModel()->index(index,0)); m_dlg.cmbDefaultEngine->setCurrentIndex(modelIndex.row()); m_dlg.cmbDefaultEngine->view()->setCurrentIndex(modelIndex); //TODO: remove this when Qt bug is fixed } void FilterOptions::load() { KConfig config(KURISearchFilterEngine::self()->name() + QStringLiteral("rc"), KConfig::NoGlobals); KConfigGroup group = config.group("General"); const QString defaultSearchEngine = group.readEntry("DefaultWebShortcut"); const QStringList favoriteEngines = group.readEntry("PreferredWebShortcuts", DEFAULT_PREFERRED_SEARCH_PROVIDERS); - QList providers; - const KService::List services = KServiceTypeTrader::self()->query(QStringLiteral("SearchProvider")); - int defaultProviderIndex = services.size(); //default is "None", it is last in the list + const QList providers = m_registry.findAll(); + int defaultProviderIndex = providers.size(); //default is "None", it is last in the list - Q_FOREACH(const KService::Ptr &service, services) + for (SearchProvider *provider : providers) { - SearchProvider* provider = new SearchProvider(service); if (defaultSearchEngine == provider->desktopEntryName()) defaultProviderIndex = providers.size(); - providers.append(provider); } m_providersModel->setProviders(providers, favoriteEngines); m_dlg.lvSearchProviders->setColumnWidth(0,200); m_dlg.lvSearchProviders->resizeColumnToContents(1); m_dlg.lvSearchProviders->sortByColumn(0,Qt::AscendingOrder); m_dlg.cmbDefaultEngine->model()->sort(0,Qt::AscendingOrder); setDefaultEngine(defaultProviderIndex); m_dlg.cbEnableShortcuts->setChecked(group.readEntry("EnableWebShortcuts", true)); m_dlg.cbUseSelectedShortcutsOnly->setChecked(group.readEntry("UsePreferredWebShortcutsOnly", false)); const QString delimiter = group.readEntry ("KeywordDelimiter", ":"); setDelimiter(delimiter.at(0).toLatin1()); } char FilterOptions::delimiter() { const char delimiters[]={':',' '}; return delimiters[m_dlg.cmbDelimiter->currentIndex()]; } void FilterOptions::setDelimiter (char sep) { m_dlg.cmbDelimiter->setCurrentIndex(sep==' '); } void FilterOptions::save() { KConfig config(KURISearchFilterEngine::self()->name() + QStringLiteral("rc"), KConfig::NoGlobals ); KConfigGroup group = config.group("General"); group.writeEntry("EnableWebShortcuts", m_dlg.cbEnableShortcuts->isChecked()); group.writeEntry("KeywordDelimiter", QString(QLatin1Char(delimiter()))); group.writeEntry("DefaultWebShortcut", m_dlg.cmbDefaultEngine->view()->currentIndex().data(ProvidersListModel::ShortNameRole)); group.writeEntry("PreferredWebShortcuts", m_providersModel->favoriteEngines()); group.writeEntry("UsePreferredWebShortcutsOnly", m_dlg.cbUseSelectedShortcutsOnly->isChecked()); int changedProviderCount = 0; QList providers = m_providersModel->providers(); const QString path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/kservices5/searchproviders/"; Q_FOREACH(SearchProvider* provider, providers) { if (!provider->isDirty()) continue; changedProviderCount++; KConfig _service(path + provider->desktopEntryName() + ".desktop", KConfig::SimpleConfig ); KConfigGroup service(&_service, "Desktop Entry"); service.writeEntry("Type", "Service"); service.writeEntry("ServiceTypes", "SearchProvider"); service.writeEntry("Name", provider->name()); service.writeEntry("Query", provider->query()); service.writeEntry("Keys", provider->keys()); service.writeEntry("Charset", provider->charset()); service.writeEntry("Hidden", false); // we might be overwriting a hidden entry } const QStringList servicesDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kservices5/searchproviders/"), QStandardPaths::LocateDirectory); Q_FOREACH(const QString& providerName, m_deletedProviders) { QStringList matches; foreach(const QString& dir, servicesDirs) { QString current = dir + '/' + providerName + ".desktop"; if(QFile::exists(current)) matches += current; } // Shouldn't happen if (!matches.size()) continue; changedProviderCount++; if (matches.size() == 1 && matches.first().startsWith(path)) { // If only the local copy existed, unlink it // TODO: error handling QFile::remove(matches.first()); continue; } KConfig _service(path + providerName + ".desktop", KConfig::SimpleConfig ); KConfigGroup service(&_service, "Desktop Entry"); service.writeEntry("Type", "Service"); service.writeEntry("ServiceTypes", "SearchProvider"); service.writeEntry("Hidden", true); } config.sync(); emit changed(false); // Update filters in running applications... QDBusMessage msg = QDBusMessage::createSignal(QStringLiteral("/"), QStringLiteral("org.kde.KUriFilterPlugin"), QStringLiteral("configure")); QDBusConnection::sessionBus().send(msg); // If the providers changed, tell sycoca to rebuild its database... if (changedProviderCount) KBuildSycocaProgressDialog::rebuildKSycoca(this); } void FilterOptions::defaults() { m_dlg.cbEnableShortcuts->setChecked(true); m_dlg.cbUseSelectedShortcutsOnly->setChecked(false); m_providersModel->setFavoriteProviders(DEFAULT_PREFERRED_SEARCH_PROVIDERS); setDelimiter(':'); setDefaultEngine(-1); } void FilterOptions::addSearchProvider() { QList providers = m_providersModel->providers(); QPointer dlg = new SearchProviderDialog(nullptr, providers, this); if (dlg->exec()) { m_providersModel->addProvider(dlg->provider()); m_providersModel->changeProvider(dlg->provider()); } delete dlg; } void FilterOptions::changeSearchProvider() { QList providers = m_providersModel->providers(); SearchProvider* provider = providers.at(m_dlg.lvSearchProviders->currentIndex().data(Qt::UserRole).toInt()); QPointer dlg = new SearchProviderDialog(provider, providers, this); if (dlg->exec()) m_providersModel->changeProvider(dlg->provider()); delete dlg; } void FilterOptions::deleteSearchProvider() { SearchProvider* provider = m_providersModel->providers().at(m_dlg.lvSearchProviders->currentIndex().data(Qt::UserRole).toInt()); m_deletedProviders.append(provider->desktopEntryName()); m_providersModel->deleteProvider(provider); } void FilterOptions::updateSearchProviderEditingButons() { const bool enable = (m_dlg.cbEnableShortcuts->isChecked() && m_dlg.lvSearchProviders->currentIndex().isValid()); m_dlg.pbChange->setEnabled(enable); m_dlg.pbDelete->setEnabled(enable); } // kate: replace-tabs 1; indent-width 2; diff --git a/src/urifilters/ikws/ikwsopts.h b/src/urifilters/ikws/ikwsopts.h index 1cf560e2..f78e6868 100644 --- a/src/urifilters/ikws/ikwsopts.h +++ b/src/urifilters/ikws/ikwsopts.h @@ -1,68 +1,69 @@ /* * Copyright (c) 2000 Yves Arrouye * Copyright (c) 2002, 2003 Dawit Alemayehu * Copyright (c) 2009 Nick Shaforostoff * * 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. */ #ifndef IKWSOPTS_H #define IKWSOPTS_H #include #include #include -#include #include "ui_ikwsopts_ui.h" +#include "searchproviderregistry.h" class SearchProvider; class ProvidersModel; class FilterOptions : public KCModule { Q_OBJECT public: explicit FilterOptions(const KAboutData* about, QWidget *parent = nullptr); void load() Q_DECL_OVERRIDE; void save() Q_DECL_OVERRIDE; void defaults() Q_DECL_OVERRIDE; QString quickHelp() const Q_DECL_OVERRIDE; private Q_SLOTS: void updateSearchProviderEditingButons(); void addSearchProvider(); void changeSearchProvider(); void deleteSearchProvider(); private: void setDelimiter(char); char delimiter(); void setDefaultEngine(int); // The names of the providers that the user deleted, // these are marked as deleted in the user's homedirectory // on save if a global service file exists for it. QStringList m_deletedProviders; ProvidersModel* m_providersModel; + SearchProviderRegistry m_registry; Ui::FilterOptionsUI m_dlg; }; #endif diff --git a/src/urifilters/ikws/kuriikwsfilter.cpp b/src/urifilters/ikws/kuriikwsfilter.cpp index 995b9aac..8cfa3d70 100644 --- a/src/urifilters/ikws/kuriikwsfilter.cpp +++ b/src/urifilters/ikws/kuriikwsfilter.cpp @@ -1,173 +1,172 @@ /* This file is part of the KDE project Copyright (C) 1999 Simon Hausmann Copyright (C) 2000 Yves Arrouye Copyright (C) 2002, 2003 Dawit Alemayehu 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 "kuriikwsfilter.h" #include "kuriikwsfiltereng.h" #include "searchprovider.h" #include "ikwsopts.h" #include #include #include #define QL1S(x) QLatin1String(x) #define QL1C(x) QLatin1Char(x) namespace { QLoggingCategory category("org.kde.kurifilter-ikws", QtWarningMsg); } /** * IMPORTANT: If you change anything here, make sure you run the kurifiltertest * regression test (this should be included as part of "make test"). */ K_PLUGIN_FACTORY_WITH_JSON(KAutoWebSearchFactory, "kuriikwsfilter.json", registerPlugin();) KAutoWebSearch::KAutoWebSearch(QObject *parent, const QVariantList&) :KUriFilterPlugin( QStringLiteral("kuriikwsfilter"), parent ) { KLocalizedString::insertQtDomain("kurifilter"); QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/"), QStringLiteral("org.kde.KUriFilterPlugin"), QStringLiteral("configure"), this, SLOT(configure())); } KAutoWebSearch::~KAutoWebSearch() { } void KAutoWebSearch::configure() { qCDebug(category) << "Config reload requested..."; KURISearchFilterEngine::self()->loadConfig(); } void KAutoWebSearch::populateProvidersList(QList& searchProviders, const KUriFilterData& data, bool allproviders) const { QList providers; KURISearchFilterEngine *filter = KURISearchFilterEngine::self(); const QString searchTerm = filter->keywordDelimiter() + data.typedString(); if (allproviders) - providers = SearchProvider::findAll(); + providers = filter->registry()->findAll(); else { // Start with the search engines marked as preferred... QStringList favEngines = filter->favoriteEngineList(); if (favEngines.isEmpty()) favEngines = data.alternateSearchProviders(); // Get rid of duplicates... favEngines.removeDuplicates(); // Sort the items... qStableSort(favEngines); // Add the search engine set as the default provider... const QString defaultEngine = filter->defaultSearchEngine(); if (!defaultEngine.isEmpty()) { favEngines.removeAll(defaultEngine); favEngines.insert(0, defaultEngine); } QStringListIterator it (favEngines); while (it.hasNext()) { - SearchProvider *favProvider = SearchProvider::findByDesktopName(it.next()); + SearchProvider *favProvider = filter->registry()->findByDesktopName(it.next()); if (favProvider) providers << favProvider; } } for (int i = 0, count = providers.count(); i < count; ++i) { searchProviders << providers[i]; } } bool KAutoWebSearch::filterUri( KUriFilterData &data ) const { qCDebug(category) << data.typedString(); KUriFilterData::SearchFilterOptions option = data.searchFilteringOptions(); // Handle the flag to retrieve only preferred providers, no filtering... if (option & KUriFilterData::RetrievePreferredSearchProvidersOnly) { QList searchProviders; populateProvidersList(searchProviders, data); if (searchProviders.isEmpty()) { if (!(option & KUriFilterData::RetrieveSearchProvidersOnly)) { setUriType(data, KUriFilterData::Error); setErrorMsg(data, i18n("No preferred search providers were found.")); return false; } } else { setSearchProvider(data, QString(), data.typedString(), QL1C(KURISearchFilterEngine::self()->keywordDelimiter())); setSearchProviders(data, searchProviders); return true; } } if (option & KUriFilterData::RetrieveSearchProvidersOnly) { QList searchProviders; populateProvidersList(searchProviders, data, true); if (searchProviders.isEmpty()) { setUriType(data, KUriFilterData::Error); setErrorMsg(data, i18n("No search providers were found.")); return false; } setSearchProvider(data, QString(), data.typedString(), QL1C(KURISearchFilterEngine::self()->keywordDelimiter())); setSearchProviders(data, searchProviders); return true; } if ( data.uriType() == KUriFilterData::Unknown && data.uri().password().isEmpty() ) { KURISearchFilterEngine *filter = KURISearchFilterEngine::self(); SearchProvider *provider = filter->autoWebSearchQuery( data.typedString(), data.alternateDefaultSearchProvider() ); if( provider ) { const QUrl result = filter->formatResult(provider->query(), provider->charset(), QString(), data.typedString(), true); setFilteredUri(data, result); setUriType( data, KUriFilterData::NetProtocol ); setSearchProvider(data, provider->name(), data.typedString(), QL1C(filter->keywordDelimiter())); QList searchProviders; populateProvidersList(searchProviders, data); setSearchProviders(data, searchProviders); - delete provider; return true; } } return false; } #include "kuriikwsfilter.moc" diff --git a/src/urifilters/ikws/kuriikwsfiltereng.cpp b/src/urifilters/ikws/kuriikwsfiltereng.cpp index 214e6fa2..46210a3e 100644 --- a/src/urifilters/ikws/kuriikwsfiltereng.cpp +++ b/src/urifilters/ikws/kuriikwsfiltereng.cpp @@ -1,444 +1,449 @@ /* This file is part of the KDE project Copyright (C) 2002, 2003 Dawit Alemayehu Copyright (C) 2000 Yves Arrouye Copyright (C) 1999 Simon Hausmann Advanced web shortcuts: Copyright (C) 2001 Andreas Hochsteger 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 "kuriikwsfiltereng.h" #include "searchprovider.h" #include #include #include #include #include namespace { QLoggingCategory category("org.kde.kurifilter-ikws", QtWarningMsg); } #define PDVAR(n,v) qCDebug(category) << n << " = '" << v << "'" /** * IMPORTANT: If you change anything here, make sure you run the kurifiltertest * regression test (this should be included as part of "make test"). */ KURISearchFilterEngine::KURISearchFilterEngine() { loadConfig(); } KURISearchFilterEngine::~KURISearchFilterEngine() { } SearchProvider* KURISearchFilterEngine::webShortcutQuery(const QString& typedString, QString &searchTerm) const { SearchProvider *provider = nullptr; if (m_bWebShortcutsEnabled) { const int pos = typedString.indexOf(m_cKeywordDelimiter); QString key; if ( pos > -1 ) key = typedString.left(pos).toLower(); // #169801 else if ( !typedString.isEmpty() && m_cKeywordDelimiter == ' ') key = typedString; qCDebug(category) << "m_cKeywordDelimiter=" << QString(QChar(m_cKeywordDelimiter)) << "pos=" << pos << "key=" << key; if (!key.isEmpty() && !KProtocolInfo::isKnownProtocol(key)) { - provider = SearchProvider::findByKey(key); + provider = m_registry.findByKey(key); if (provider) { if (!m_bUseOnlyPreferredWebShortcuts || m_preferredWebShortcuts.contains(provider->desktopEntryName())) { searchTerm = typedString.mid(pos+1); qCDebug(category) << "found provider" << provider->desktopEntryName() << "searchTerm=" << searchTerm; } else { - delete provider; provider = nullptr; } } } } return provider; } SearchProvider* KURISearchFilterEngine::autoWebSearchQuery(const QString& typedString, const QString &defaultShortcut) const { SearchProvider *provider = nullptr; const QString defaultSearchProvider = (m_defaultWebShortcut.isEmpty() ? defaultShortcut : m_defaultWebShortcut); if (m_bWebShortcutsEnabled && !defaultSearchProvider.isEmpty()) { // Make sure we ignore supported protocols, e.g. "smb:", "http:" const int pos = typedString.indexOf(':'); - if (pos == -1 || !KProtocolInfo::isKnownProtocol(typedString.left(pos))) - provider = SearchProvider::findByDesktopName(defaultSearchProvider); + if (pos == -1 || !KProtocolInfo::isKnownProtocol(typedString.left(pos))) { + provider = m_registry.findByDesktopName(defaultSearchProvider); + } } return provider; } QByteArray KURISearchFilterEngine::name() const { return "kuriikwsfilter"; } char KURISearchFilterEngine::keywordDelimiter() const { return m_cKeywordDelimiter; } QString KURISearchFilterEngine::defaultSearchEngine() const { return m_defaultWebShortcut; } QStringList KURISearchFilterEngine::favoriteEngineList() const { return m_preferredWebShortcuts; } Q_GLOBAL_STATIC(KURISearchFilterEngine, sSelfPtr) KURISearchFilterEngine* KURISearchFilterEngine::self() { return sSelfPtr; } QStringList KURISearchFilterEngine::modifySubstitutionMap(SubstMap& map, const QString& query) const { // Returns the number of query words QString userquery = query; // Do some pre-encoding, before we can start the work: { int start = 0; int pos = 0; QRegExp qsexpr(QStringLiteral("\\\"[^\\\"]*\\\"")); // Temporary substitute spaces in quoted strings (" " -> "%20") // Needed to split user query into StringList correctly. while ((pos = qsexpr.indexIn(userquery, start)) >= 0) { QString s = userquery.mid (pos, qsexpr.matchedLength()); s.replace (' ', QLatin1String("%20")); start = pos + s.length(); // Move after last quote userquery = userquery.replace (pos, qsexpr.matchedLength(), s); } } // Split user query between spaces: QStringList l = userquery.simplified().split(' ', QString::SkipEmptyParts); // Back-substitute quoted strings (%20 -> " "): userquery.replace (QLatin1String("%20"), QLatin1String(" ")); l.replaceInStrings(QStringLiteral("%20"), QStringLiteral(" ")); qCDebug(category) << "Generating substitution map:\n"; // Generate substitution map from user query: for (int i=0; i<=l.count(); i++) { int pos = 0; QString v; QString nr = QString::number(i); // Add whole user query (\{0}) to substitution map: if (i==0) v = userquery; // Add partial user query items to substitution map: else v = l[i-1]; // Insert partial queries (referenced by \1 ... \n) to map: map.insert(QString::number(i), v); PDVAR (" map['" + nr + "']", map[nr]); // Insert named references (referenced by \name) to map: if ((i>0) && (pos = v.indexOf('=')) > 0) { QString s = v.mid(pos + 1); QString k = v.left(pos); // Back-substitute references contained in references (e.g. '\refname' substitutes to 'thisquery=\0') s.replace(QLatin1String("%5C"), QLatin1String("\\")); map.insert(k, s); PDVAR (" map['" + k + "']", map[k]); } } return l; } static QString encodeString(const QString& s, QTextCodec *codec) { // don't encode the space character, we replace it with + after the encoding QByteArray encoded = codec->fromUnicode(s).toPercentEncoding(QByteArrayLiteral(" ")); encoded.replace(' ', '+'); return QString::fromUtf8(encoded); } QString KURISearchFilterEngine::substituteQuery(const QString& url, SubstMap &map, const QString& userquery, QTextCodec *codec) const { QString newurl = url; QStringList ql = modifySubstitutionMap (map, userquery); int count = ql.count(); // Check, if old style '\1' is found and replace it with \{@} (compatibility mode): { int pos = -1; if ((pos = newurl.indexOf(QStringLiteral("\\1"))) >= 0) { qCWarning(category) << "WARNING: Using compatibility mode for newurl='" << newurl << "'. Please replace old style '\\1' with new style '\\{0}' " "in the query definition.\n"; newurl = newurl.replace(pos, 2, QStringLiteral("\\{@}")); } } qCDebug(category) << "Substitute references:\n"; // Substitute references (\{ref1,ref2,...}) with values from user query: { int pos = 0; QRegExp reflist(QStringLiteral("\\\\\\{[^\\}]+\\}")); // Substitute reflists (\{ref1,ref2,...}): while ((pos = reflist.indexIn(newurl)) >= 0) { bool found = false; //bool rest = false; QString v = QLatin1String(""); QString rlstring = newurl.mid(pos + 2, reflist.matchedLength() - 3); PDVAR (" reference list", rlstring); // \{@} gets a special treatment later if (rlstring == QLatin1String("@")) { v = QStringLiteral("\\@"); found = true; } // TODO: strip whitespaces around commas QStringList rl = rlstring.split(',', QString::SkipEmptyParts); int i = 0; while ((i= 0) { int pos = rlitem.indexOf(QStringLiteral("-")); int first = rlitem.leftRef(pos).toInt(); int last = rlitem.rightRef(rlitem.length()-pos-1).toInt(); if (first == 0) first = 1; if (last == 0) last = count; for (int i=first; i<=last; i++) { v += map[QString::number(i)] + ' '; // Remove used value from ql (needed for \{@}): ql[i-1] = QLatin1String(""); } v = v.trimmed(); if (!v.isEmpty()) found = true; PDVAR (" range", QString::number(first) + '-' + QString::number(last) + " => '" + v + '\''); v = encodeString(v, codec); } else if ( rlitem.startsWith('\"') && rlitem.endsWith('\"') ) { // Use default string from query definition: found = true; QString s = rlitem.mid(1, rlitem.length() - 2); v = encodeString(s, codec); PDVAR (" default", s); } else if (map.contains(rlitem)) { // Use value from substitution map: found = true; PDVAR (" map['" + rlitem + "']", map[rlitem]); v = encodeString(map[rlitem], codec); // Remove used value from ql (needed for \{@}): QString c = rlitem.left(1); if (c==QLatin1String("0")) { // It's a numeric reference to '0' for (QStringList::Iterator it = ql.begin(); it!=ql.end(); ++it) (*it) = QLatin1String(""); } else if ((c>=QLatin1String("0")) && (c<=QLatin1String("9"))) // krazy:excludeall=doublequote_chars { // It's a numeric reference > '0' int n = rlitem.toInt(); ql[n-1] = QLatin1String(""); } else { // It's a alphanumeric reference QStringList::Iterator it = ql.begin(); while ((it != ql.end()) && !it->startsWith(rlitem + '=')) ++it; if (it != ql.end()) it->clear(); } // Encode '+', otherwise it would be interpreted as space in the resulting url: v.replace('+', QLatin1String("%2B")); } else if (rlitem == QLatin1String("@")) { v = QStringLiteral("\\@"); PDVAR (" v", v); } i++; } newurl.replace(pos, reflist.matchedLength(), v); } // Special handling for \{@}; { PDVAR (" newurl", newurl); // Generate list of unmatched strings: QString v = ql.join(QStringLiteral(" ")).simplified(); PDVAR (" rest", v); v = encodeString(v, codec); // Substitute \{@} with list of unmatched query strings newurl.replace(QLatin1String("\\@"), v); } } return newurl; } QUrl KURISearchFilterEngine::formatResult( const QString& url, const QString& cset1, const QString& cset2, const QString& query, bool isMalformed ) const { SubstMap map; return formatResult (url, cset1, cset2, query, isMalformed, map); } QUrl KURISearchFilterEngine::formatResult( const QString& url, const QString& cset1, const QString& cset2, const QString& userquery, bool /* isMalformed */, SubstMap& map ) const { // Return nothing if userquery is empty and it contains // substitution strings... if (userquery.isEmpty() && url.indexOf(QStringLiteral("\\{")) > 0) return QUrl(); // Debug info of map: if (!map.isEmpty()) { qCDebug(category) << "Got non-empty substitution map:\n"; for(SubstMap::Iterator it = map.begin(); it != map.end(); ++it) PDVAR (" map['" + it.key() + "']", it.value()); } // Create a codec for the desired encoding so that we can transcode the user's "url". QString cseta = cset1; if (cseta.isEmpty()) cseta = QStringLiteral("UTF-8"); QTextCodec *csetacodec = QTextCodec::codecForName(cseta.toLatin1()); if (!csetacodec) { cseta = QStringLiteral("UTF-8"); csetacodec = QTextCodec::codecForName(cseta.toLatin1()); } PDVAR ("user query", userquery); PDVAR ("query definition", url); // Add charset indicator for the query to substitution map: map.insert(QStringLiteral("ikw_charset"), cseta); // Add charset indicator for the fallback query to substitution map: QString csetb = cset2; if (csetb.isEmpty()) csetb = QStringLiteral("UTF-8"); map.insert(QStringLiteral("wsc_charset"), csetb); QString newurl = substituteQuery (url, map, userquery, csetacodec); PDVAR ("substituted query", newurl); return QUrl(newurl, QUrl::StrictMode); } void KURISearchFilterEngine::loadConfig() { qCDebug(category) << "Keywords Engine: Loading config..."; // Load the config. KConfig config( name() + QStringLiteral("rc"), KConfig::NoGlobals ); KConfigGroup group = config.group( "General" ); m_cKeywordDelimiter = QString(group.readEntry("KeywordDelimiter", ":")).at(0).toLatin1(); m_bWebShortcutsEnabled = group.readEntry("EnableWebShortcuts", true); m_defaultWebShortcut = group.readEntry("DefaultWebShortcut"); m_bUseOnlyPreferredWebShortcuts = group.readEntry("UsePreferredWebShortcutsOnly", false); QStringList defaultPreferredShortcuts; if (!group.hasKey("PreferredWebShortcuts")) defaultPreferredShortcuts = DEFAULT_PREFERRED_SEARCH_PROVIDERS; m_preferredWebShortcuts = group.readEntry("PreferredWebShortcuts", defaultPreferredShortcuts); // Use either a white space or a : as the keyword delimiter... if (strchr (" :", m_cKeywordDelimiter) == nullptr) m_cKeywordDelimiter = ':'; qCDebug(category) << "Web Shortcuts Enabled: " << m_bWebShortcutsEnabled; qCDebug(category) << "Default Shortcut: " << m_defaultWebShortcut; qCDebug(category) << "Keyword Delimiter: " << m_cKeywordDelimiter; } + +SearchProviderRegistry * KURISearchFilterEngine::registry() +{ + return &m_registry; +} diff --git a/src/urifilters/ikws/kuriikwsfiltereng.h b/src/urifilters/ikws/kuriikwsfiltereng.h index 52cf9d05..eb086127 100644 --- a/src/urifilters/ikws/kuriikwsfiltereng.h +++ b/src/urifilters/ikws/kuriikwsfiltereng.h @@ -1,76 +1,80 @@ /* This file is part of the KDE project Copyright (C) 2002,2003 Dawit Alemayehu Copyright (C) 1999 Simon Hausmann Copyright (C) 1999 Yves Arrouye Advanced web shortcuts Copyright (C) 2001 Andreas Hochsteger 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. */ #ifndef KURIIKWSFILTERENG_H #define KURIIKWSFILTERENG_H #include #include #include +#include "searchproviderregistry.h" #define DEFAULT_PREFERRED_SEARCH_PROVIDERS \ QStringList() << QStringLiteral("google") << QStringLiteral("youtube") << QStringLiteral("yahoo") << QStringLiteral("wikipedia") << QStringLiteral("wikit") class SearchProvider; class KURISearchFilterEngine { public: typedef QMap SubstMap; KURISearchFilterEngine(); ~KURISearchFilterEngine(); QByteArray name() const; char keywordDelimiter() const; QString defaultSearchEngine() const; QStringList favoriteEngineList() const; SearchProvider* webShortcutQuery (const QString& typedString, QString& searchTerm) const; SearchProvider* autoWebSearchQuery (const QString& typedString, const QString& defaultShortcut = QString()) const; QUrl formatResult (const QString& url, const QString& cset1, const QString& cset2, const QString& query, bool isMalformed) const; + SearchProviderRegistry *registry(); + static KURISearchFilterEngine *self(); void loadConfig(); protected: QUrl formatResult (const QString& url, const QString& cset1, const QString& cset2, const QString& query, bool isMalformed, SubstMap& map) const; private: - KURISearchFilterEngine(const KURISearchFilterEngine&); - KURISearchFilterEngine& operator= (const KURISearchFilterEngine&); + KURISearchFilterEngine(const KURISearchFilterEngine&) = delete; + KURISearchFilterEngine& operator= (const KURISearchFilterEngine&) = delete; QStringList modifySubstitutionMap (SubstMap& map, const QString& query) const; QString substituteQuery (const QString& url, SubstMap &map, const QString& userquery, QTextCodec *codec) const; + SearchProviderRegistry m_registry; QString m_defaultWebShortcut; QStringList m_preferredWebShortcuts; bool m_bWebShortcutsEnabled; bool m_bUseOnlyPreferredWebShortcuts; char m_cKeywordDelimiter; }; #endif // KURIIKWSFILTERENG_H diff --git a/src/urifilters/ikws/kurisearchfilter.cpp b/src/urifilters/ikws/kurisearchfilter.cpp index 63654add..3539c813 100644 --- a/src/urifilters/ikws/kurisearchfilter.cpp +++ b/src/urifilters/ikws/kurisearchfilter.cpp @@ -1,95 +1,96 @@ /* This file is part of the KDE project Copyright (C) 1999 Simon Hausmann Copyright (C) 2000 Yves Arrouye Copyright (C) 2002, 2003 Dawit Alemayehu 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 "kurisearchfilter.h" #include "kuriikwsfiltereng.h" #include "searchprovider.h" #include "ikwsopts.h" #include +#include #include #include /** * IMPORTANT: If you change anything here, make sure you run the kurifiltertest * regression test (this should be included as part of "make test"). */ K_PLUGIN_FACTORY_WITH_JSON(KUriSearchFilterFactory, "kurisearchfilter.json", registerPlugin();) namespace { QLoggingCategory category("org.kde.kurifilter-ikws", QtWarningMsg); } KUriSearchFilter::KUriSearchFilter(QObject *parent, const QVariantList &) :KUriFilterPlugin( QStringLiteral("kurisearchfilter"), parent ) { KLocalizedString::insertQtDomain("kurifilter"); QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/"), QStringLiteral("org.kde.KUriFilterPlugin"), QStringLiteral("configure"), this, SLOT(configure())); } KUriSearchFilter::~KUriSearchFilter() { } void KUriSearchFilter::configure() { qCDebug(category) << "Config reload requested..."; KURISearchFilterEngine::self()->loadConfig(); } bool KUriSearchFilter::filterUri( KUriFilterData &data ) const { qCDebug(category) << data.typedString() << ":" << data.uri() << ", type =" << data.uriType(); // some URLs like gg:www.kde.org are not accepted by QUrl, but we still want them // This means we also have to allow KUriFilterData::Error if (data.uriType() != KUriFilterData::Unknown && data.uriType() != KUriFilterData::Error) { return false; } QString searchTerm; KURISearchFilterEngine *filter = KURISearchFilterEngine::self(); - QScopedPointer provider(filter->webShortcutQuery(data.typedString(), searchTerm)); + SearchProvider* provider(filter->webShortcutQuery(data.typedString(), searchTerm)); if (!provider) { return false; } const QUrl result = filter->formatResult(provider->query(), provider->charset(), QString(), searchTerm, true ); setFilteredUri(data, result); setUriType( data, KUriFilterData::NetProtocol ); setSearchProvider( data, provider->name(), searchTerm, QLatin1Char(filter->keywordDelimiter())); return true; } KCModule *KUriSearchFilter::configModule(QWidget *parent, const char *) const { return new FilterOptions( KAboutData::pluginData(QStringLiteral("kcmkurifilt")), parent); } QString KUriSearchFilter::configName() const { return i18n("Search F&ilters"); } #include "kurisearchfilter.moc" diff --git a/src/urifilters/ikws/searchprovider.cpp b/src/urifilters/ikws/searchprovider.cpp index 3b4959df..600057fa 100644 --- a/src/urifilters/ikws/searchprovider.cpp +++ b/src/urifilters/ikws/searchprovider.cpp @@ -1,141 +1,128 @@ /* * 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 "searchprovider.h" #include #include #include #include // KIO::iconNameForUrl +#include +#include +#include +#include -SearchProvider::SearchProvider(const KService::Ptr service) +SearchProvider::SearchProvider(const QString &servicePath) : m_dirty(false) { - setDesktopEntryName(service->desktopEntryName()); - setName(service->name()); - setKeys(service->property(QStringLiteral("Keys")).toStringList()); + setDesktopEntryName(QFileInfo(servicePath).baseName()); + KDesktopFile parser(servicePath); + setName(parser.name()); + KConfigGroup group(parser.desktopGroup()); + setKeys(group.readEntry(QStringLiteral("Keys"), QStringList())); + + m_query = group.readEntry(QStringLiteral("Query")); + m_charset = group.readEntry(QStringLiteral("Charset")); +} - m_query = service->property(QStringLiteral("Query")).toString(); - m_charset = service->property(QStringLiteral("Charset")).toString(); +SearchProvider::~SearchProvider() +{ } void SearchProvider::setName(const QString &name) { if (KUriFilterSearchProvider::name() == name) return; KUriFilterSearchProvider::setName(name); } void SearchProvider::setQuery(const QString &query) { if (m_query == query) return; m_query = query; } void SearchProvider::setKeys(const QStringList &keys) { if (KUriFilterSearchProvider::keys() == keys) return; KUriFilterSearchProvider::setKeys(keys); QString name = desktopEntryName(); if (!name.isEmpty()) return; // New provider. Set the desktopEntryName. // Take the longest search shortcut as filename, // if such a file already exists, append a number and increase it // until the name is unique Q_FOREACH(const QString& key, keys) { if (key.length() > name.length()) name = key.toLower(); } const QString path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/kservices5/searchproviders/"; bool firstRun = true; while (true) { QString check(name); if (!firstRun) check += KRandom::randomString(4); const QString located = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kservices5/searchproviders/") + check + ".desktop"); if (located.isEmpty()) { name = check; break; } else if (located.startsWith(path)) { // If it's a deleted (hidden) entry, overwrite it if (KService(located).isDeleted()) break; } firstRun = false; } setDesktopEntryName(name); } void SearchProvider::setCharset(const QString &charset) { if (m_charset == charset) return; m_charset = charset; } QString SearchProvider::iconName() const { return KIO::iconNameForUrl(QUrl(m_query)); } void SearchProvider::setDirty(bool dirty) { m_dirty = dirty; } - -SearchProvider *SearchProvider::findByDesktopName(const QString &name) -{ - KService::Ptr service = - KService::serviceByDesktopPath(QStringLiteral("searchproviders/%1.desktop").arg(name)); - return service ? new SearchProvider(service) : nullptr; -} - -SearchProvider *SearchProvider::findByKey(const QString &key) -{ - KService::List providers = - KServiceTypeTrader::self()->query(QStringLiteral("SearchProvider"), QStringLiteral("'%1' in Keys").arg(key)); - return providers.count() ? new SearchProvider(providers[0]) : nullptr; -} - -QList SearchProvider::findAll() -{ - QList ret; - Q_FOREACH (const KService::Ptr &provider, KServiceTypeTrader::self()->query(QLatin1String("SearchProvider"))) { - ret.append(new SearchProvider(provider)); - } - return ret; -} diff --git a/src/urifilters/ikws/searchprovider.h b/src/urifilters/ikws/searchprovider.h index 34d2feca..471e9ff8 100644 --- a/src/urifilters/ikws/searchprovider.h +++ b/src/urifilters/ikws/searchprovider.h @@ -1,53 +1,50 @@ /* * 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. */ #ifndef SEARCHPROVIDER_H #define SEARCHPROVIDER_H -#include #include class SearchProvider : public KUriFilterSearchProvider { public: SearchProvider() : m_dirty(false) {} - explicit SearchProvider(const KService::Ptr service); + explicit SearchProvider(const QString &servicePath); + ~SearchProvider(); const QString& charset() const { return m_charset; } const QString& query() const { return m_query; } bool isDirty() const { return m_dirty; } void setName(const QString&); void setQuery(const QString&); void setKeys(const QStringList&); void setCharset(const QString&); void setDirty(bool dirty); QString iconName() const Q_DECL_OVERRIDE; - static SearchProvider *findByDesktopName(const QString &); - static SearchProvider *findByKey(const QString &); - static QList findAll(); private: QString m_query; QString m_charset; bool m_dirty; }; #endif diff --git a/src/urifilters/ikws/searchproviderregistry.cpp b/src/urifilters/ikws/searchproviderregistry.cpp new file mode 100644 index 00000000..7fedf13c --- /dev/null +++ b/src/urifilters/ikws/searchproviderregistry.cpp @@ -0,0 +1,84 @@ +/* + * This file is part of the KDE project + * Copyright 2017 David Faure + * + * 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) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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, see . + */ + +#include "searchproviderregistry.h" +#include "searchprovider.h" + +#include +#include +#include + +SearchProviderRegistry::SearchProviderRegistry() +{ + reload(); +} + +SearchProviderRegistry::~SearchProviderRegistry() +{ + qDeleteAll(m_searchProviders); +} + +QStringList SearchProviderRegistry::directories() const +{ + const QString testDir = QFile::decodeName(qgetenv("KIO_SEARCHPROVIDERS_DIR")); // for unittests + if (!testDir.isEmpty()) + return { testDir }; + return QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kservices5/searchproviders/"), QStandardPaths::LocateDirectory); +} + +void SearchProviderRegistry::reload() +{ + m_searchProvidersByKey.clear(); + m_searchProvidersByDesktopName.clear(); + qDeleteAll(m_searchProviders); + m_searchProviders.clear(); + + const QStringList servicesDirs = directories(); + for (const QString &dirPath : servicesDirs) { + QDir dir(dirPath); + for (const QString &file : dir.entryList({QStringLiteral("*.desktop")}, QDir::Files)) { + if (!m_searchProvidersByDesktopName.contains(file)) { + const QString filePath = dir.path() + QLatin1Char('/') + file; + auto *provider = new SearchProvider(filePath); + m_searchProvidersByDesktopName.insert(file, provider); + m_searchProviders.append(provider); + for (const QString &key : provider->keys()) { + m_searchProvidersByKey.insert(key, provider); + } + } + } + } +} + +QList SearchProviderRegistry::findAll() +{ + return m_searchProviders; +} + +SearchProvider* SearchProviderRegistry::findByKey(const QString& key) const +{ + return m_searchProvidersByKey.value(key); +} + +SearchProvider* SearchProviderRegistry::findByDesktopName(const QString &name) const +{ + return m_searchProvidersByDesktopName.value(name + ".desktop"); +} diff --git a/src/urifilters/ikws/searchproviderregistry.h b/src/urifilters/ikws/searchproviderregistry.h new file mode 100644 index 00000000..fd8262b3 --- /dev/null +++ b/src/urifilters/ikws/searchproviderregistry.h @@ -0,0 +1,61 @@ +/* + * This file is part of the KDE project + * Copyright 2017 David Faure + * + * 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) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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, see . + */ + +#ifndef SEARCHPROVIDERREGISTRY_H +#define SEARCHPROVIDERREGISTRY_H + +#include +#include + +class SearchProvider; + +/** + * Memory cache for search provider desktop files + */ +class SearchProviderRegistry +{ +public: + /** + * Default constructor + */ + SearchProviderRegistry(); + + /** + * Destructor + */ + ~SearchProviderRegistry(); + + QList findAll(); + + SearchProvider *findByKey(const QString &key) const; + + SearchProvider *findByDesktopName(const QString &desktopName) const; + +private: + void reload(); + QStringList directories() const; + + QList m_searchProviders; + QMap m_searchProvidersByKey; + QMap m_searchProvidersByDesktopName; +}; + +#endif // SEARCHPROVIDERREGISTRY_H diff --git a/src/widgets/kurifilter.cpp b/src/widgets/kurifilter.cpp index 5db6aae0..2f4caf0e 100644 --- a/src/widgets/kurifilter.cpp +++ b/src/widgets/kurifilter.cpp @@ -1,695 +1,694 @@ /* This file is part of the KDE libraries * Copyright (C) 2000 Yves Arrouye * Copyright (C) 2000,2010 Dawit Alemayehu * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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 "kurifilter.h" #include "hostinfo.h" #include #include #include #include #include #include #include #include typedef QList KUriFilterPluginList; typedef QMap SearchProviderMap; static QString lookupIconNameFor(const QUrl &url, KUriFilterData::UriTypes type) { QString iconName; switch (type) { case KUriFilterData::NetProtocol: iconName = KIO::iconNameForUrl(url); break; case KUriFilterData::Executable: { QString exeName = url.path(); exeName = exeName.mid(exeName.lastIndexOf('/') + 1); // strip path if given KService::Ptr service = KService::serviceByDesktopName(exeName); if (service && service->icon() != QLatin1String("unknown")) { iconName = service->icon(); } // Try to find an icon with the same name as the binary (useful for non-kde apps) // Use iconPath rather than loadIcon() as the latter uses QPixmap (not threadsafe) else if (!KIconLoader::global()->iconPath(exeName, KIconLoader::NoGroup, true).isNull()) { iconName = exeName; } else // not found, use default { iconName = QStringLiteral("system-run"); } break; } case KUriFilterData::Help: { iconName = QStringLiteral("khelpcenter"); break; } case KUriFilterData::Shell: { iconName = QStringLiteral("konsole"); break; } case KUriFilterData::Error: case KUriFilterData::Blocked: { iconName = QStringLiteral("error"); break; } default: break; } return iconName; } class Q_DECL_HIDDEN KUriFilterSearchProvider::KUriFilterSearchProviderPrivate { public: KUriFilterSearchProviderPrivate() {} KUriFilterSearchProviderPrivate(const KUriFilterSearchProviderPrivate &other) : desktopEntryName(other.desktopEntryName), iconName(other.iconName), name(other.name), keys(other.keys) {} QString desktopEntryName; QString iconName; QString name; QStringList keys; }; KUriFilterSearchProvider::KUriFilterSearchProvider() : d(new KUriFilterSearchProvider::KUriFilterSearchProviderPrivate) { } KUriFilterSearchProvider::KUriFilterSearchProvider(const KUriFilterSearchProvider &other) : d(new KUriFilterSearchProvider::KUriFilterSearchProviderPrivate(*(other.d))) { } KUriFilterSearchProvider::~KUriFilterSearchProvider() { delete d; } QString KUriFilterSearchProvider::desktopEntryName() const { return d->desktopEntryName; } QString KUriFilterSearchProvider::iconName() const { return d->iconName; } QString KUriFilterSearchProvider::name() const { return d->name; } QStringList KUriFilterSearchProvider::keys() const { return d->keys; } QString KUriFilterSearchProvider::defaultKey() const { if (d->keys.isEmpty()) { return QString(); } return d->keys.first(); } KUriFilterSearchProvider &KUriFilterSearchProvider::operator=(const KUriFilterSearchProvider &other) { d->desktopEntryName = other.d->desktopEntryName; d->iconName = other.d->iconName; d->keys = other.d->keys; d->name = other.d->name; return *this; } void KUriFilterSearchProvider::setDesktopEntryName(const QString &desktopEntryName) { d->desktopEntryName = desktopEntryName; } void KUriFilterSearchProvider::setIconName(const QString &iconName) { d->iconName = iconName; } void KUriFilterSearchProvider::setName(const QString &name) { d->name = name; } void KUriFilterSearchProvider::setKeys(const QStringList &keys) { d->keys = keys; } class KUriFilterDataPrivate { public: explicit KUriFilterDataPrivate(const QUrl &u, const QString &typedUrl) : checkForExecs(true), wasModified(true), uriType(KUriFilterData::Unknown), searchFilterOptions(KUriFilterData::SearchFilterOptionNone), url(u), typedString(typedUrl) { } ~KUriFilterDataPrivate() { - qDeleteAll(searchProviderMap.begin(), searchProviderMap.end()); } void setData(const QUrl &u, const QString &typedUrl) { checkForExecs = true; wasModified = true; uriType = KUriFilterData::Unknown; searchFilterOptions = KUriFilterData::SearchFilterOptionNone; url = u; typedString = typedUrl; errMsg.clear(); iconName.clear(); absPath.clear(); args.clear(); searchTerm.clear(); searchProvider.clear(); searchTermSeparator = QChar(); alternateDefaultSearchProvider.clear(); alternateSearchProviders.clear(); searchProviderMap.clear(); defaultUrlScheme.clear(); } KUriFilterDataPrivate(KUriFilterDataPrivate *data) { wasModified = data->wasModified; checkForExecs = data->checkForExecs; uriType = data->uriType; searchFilterOptions = data->searchFilterOptions; url = data->url; typedString = data->typedString; errMsg = data->errMsg; iconName = data->iconName; absPath = data->absPath; args = data->args; searchTerm = data->searchTerm; searchTermSeparator = data->searchTermSeparator; searchProvider = data->searchProvider; alternateDefaultSearchProvider = data->alternateDefaultSearchProvider; alternateSearchProviders = data->alternateSearchProviders; searchProviderMap = data->searchProviderMap; defaultUrlScheme = data->defaultUrlScheme; } bool checkForExecs; bool wasModified; KUriFilterData::UriTypes uriType; KUriFilterData::SearchFilterOptions searchFilterOptions; QUrl url; QString typedString; QString errMsg; QString iconName; QString absPath; QString args; QString searchTerm; QString searchProvider; QString alternateDefaultSearchProvider; QString defaultUrlScheme; QChar searchTermSeparator; QStringList alternateSearchProviders; QStringList searchProviderList; SearchProviderMap searchProviderMap; }; KUriFilterData::KUriFilterData() : d(new KUriFilterDataPrivate(QUrl(), QString())) { } KUriFilterData::KUriFilterData(const QUrl &url) : d(new KUriFilterDataPrivate(url, url.toString())) { } KUriFilterData::KUriFilterData(const QString &url) : d(new KUriFilterDataPrivate(QUrl::fromUserInput(url), url)) { } KUriFilterData::KUriFilterData(const KUriFilterData &other) : d(new KUriFilterDataPrivate(other.d)) { } KUriFilterData::~KUriFilterData() { delete d; } QUrl KUriFilterData::uri() const { return d->url; } QString KUriFilterData::errorMsg() const { return d->errMsg; } KUriFilterData::UriTypes KUriFilterData::uriType() const { return d->uriType; } QString KUriFilterData::absolutePath() const { return d->absPath; } bool KUriFilterData::hasAbsolutePath() const { return !d->absPath.isEmpty(); } QString KUriFilterData::argsAndOptions() const { return d->args; } bool KUriFilterData::hasArgsAndOptions() const { return !d->args.isEmpty(); } bool KUriFilterData::checkForExecutables() const { return d->checkForExecs; } QString KUriFilterData::typedString() const { return d->typedString; } QString KUriFilterData::searchTerm() const { return d->searchTerm; } QChar KUriFilterData::searchTermSeparator() const { return d->searchTermSeparator; } QString KUriFilterData::searchProvider() const { return d->searchProvider; } QStringList KUriFilterData::preferredSearchProviders() const { return d->searchProviderList; } KUriFilterSearchProvider KUriFilterData::queryForSearchProvider(const QString &provider) const { const KUriFilterSearchProvider *searchProvider = d->searchProviderMap.value(provider); if (searchProvider) { return *(searchProvider); } return KUriFilterSearchProvider(); } QString KUriFilterData::queryForPreferredSearchProvider(const QString &provider) const { const KUriFilterSearchProvider *searchProvider = d->searchProviderMap.value(provider); if (searchProvider) { return (searchProvider->defaultKey() % searchTermSeparator() % searchTerm()); } return QString(); } QStringList KUriFilterData::allQueriesForSearchProvider(const QString &provider) const { const KUriFilterSearchProvider *searchProvider = d->searchProviderMap.value(provider); if (searchProvider) { return searchProvider->keys(); } return QStringList(); } QString KUriFilterData::iconNameForPreferredSearchProvider(const QString &provider) const { const KUriFilterSearchProvider *searchProvider = d->searchProviderMap.value(provider); if (searchProvider) { return searchProvider->iconName(); } return QString(); } QStringList KUriFilterData::alternateSearchProviders() const { return d->alternateSearchProviders; } QString KUriFilterData::alternateDefaultSearchProvider() const { return d->alternateDefaultSearchProvider; } QString KUriFilterData::defaultUrlScheme() const { return d->defaultUrlScheme; } KUriFilterData::SearchFilterOptions KUriFilterData::searchFilteringOptions() const { return d->searchFilterOptions; } QString KUriFilterData::iconName() { if (d->wasModified) { d->iconName = lookupIconNameFor(d->url, d->uriType); d->wasModified = false; } return d->iconName; } void KUriFilterData::setData(const QUrl &url) { d->setData(url, url.toString()); } void KUriFilterData::setData(const QString &url) { d->setData(QUrl(url), url); } bool KUriFilterData::setAbsolutePath(const QString &absPath) { // Since a malformed URL could possibly be a relative // URL we tag it as a possible local resource... if ((d->url.scheme().isEmpty() || d->url.isLocalFile())) { d->absPath = absPath; return true; } return false; } void KUriFilterData::setCheckForExecutables(bool check) { d->checkForExecs = check; } void KUriFilterData::setAlternateSearchProviders(const QStringList &providers) { d->alternateSearchProviders = providers; } void KUriFilterData::setAlternateDefaultSearchProvider(const QString &provider) { d->alternateDefaultSearchProvider = provider; } void KUriFilterData::setDefaultUrlScheme(const QString &scheme) { d->defaultUrlScheme = scheme; } void KUriFilterData::setSearchFilteringOptions(SearchFilterOptions options) { d->searchFilterOptions = options; } KUriFilterData &KUriFilterData::operator=(const QUrl &url) { d->setData(url, url.toString()); return *this; } KUriFilterData &KUriFilterData::operator=(const QString &url) { d->setData(QUrl(url), url); return *this; } /************************* KUriFilterPlugin ******************************/ KUriFilterPlugin::KUriFilterPlugin(const QString &name, QObject *parent) : QObject(parent), d(nullptr) { setObjectName(name); } // KF6 TODO //KUriFilterPlugin::~KUriFilterPlugin() //{ //} KCModule *KUriFilterPlugin::configModule(QWidget *, const char *) const { return nullptr; } QString KUriFilterPlugin::configName() const { return objectName(); } void KUriFilterPlugin::setFilteredUri(KUriFilterData &data, const QUrl &uri) const { data.d->url = uri; data.d->wasModified = true; //qDebug() << "Got filtered to:" << uri; } void KUriFilterPlugin::setErrorMsg(KUriFilterData &data, const QString &errmsg) const { data.d->errMsg = errmsg; } void KUriFilterPlugin::setUriType(KUriFilterData &data, KUriFilterData::UriTypes type) const { data.d->uriType = type; data.d->wasModified = true; } void KUriFilterPlugin::setArguments(KUriFilterData &data, const QString &args) const { data.d->args = args; } void KUriFilterPlugin::setSearchProvider(KUriFilterData &data, const QString &provider, const QString &term, const QChar &separator) const { data.d->searchProvider = provider; data.d->searchTerm = term; data.d->searchTermSeparator = separator; } void KUriFilterPlugin::setSearchProviders(KUriFilterData &data, const QList &providers) const { Q_FOREACH (KUriFilterSearchProvider *searchProvider, providers) { data.d->searchProviderList << searchProvider->name(); data.d->searchProviderMap.insert(searchProvider->name(), searchProvider); } } QString KUriFilterPlugin::iconNameFor(const QUrl &url, KUriFilterData::UriTypes type) const { return lookupIconNameFor(url, type); } QHostInfo KUriFilterPlugin::resolveName(const QString &hostname, unsigned long timeout) const { return KIO::HostInfo::lookupHost(hostname, timeout); } /******************************* KUriFilter ******************************/ class KUriFilterPrivate { public: KUriFilterPrivate() {} ~KUriFilterPrivate() { qDeleteAll(pluginList); pluginList.clear(); } QVector pluginList; }; class KUriFilterSingleton { public: KUriFilter instance; }; Q_GLOBAL_STATIC(KUriFilterSingleton, m_self) KUriFilter *KUriFilter::self() { return &m_self()->instance; } KUriFilter::KUriFilter() : d(new KUriFilterPrivate()) { loadPlugins(); } KUriFilter::~KUriFilter() { delete d; } bool KUriFilter::filterUri(KUriFilterData &data, const QStringList &filters) { bool filtered = false; for (KUriFilterPlugin *plugin : d->pluginList) { // If no specific filters were requested, iterate through all the plugins. // Otherwise, only use available filters. if (filters.isEmpty() || filters.contains(plugin->objectName())) { if (plugin->filterUri(data)) { filtered = true; } } } return filtered; } bool KUriFilter::filterUri(QUrl &uri, const QStringList &filters) { KUriFilterData data(uri); bool filtered = filterUri(data, filters); if (filtered) { uri = data.uri(); } return filtered; } bool KUriFilter::filterUri(QString &uri, const QStringList &filters) { KUriFilterData data(uri); bool filtered = filterUri(data, filters); if (filtered) { uri = data.uri().toString(); } return filtered; } QUrl KUriFilter::filteredUri(const QUrl &uri, const QStringList &filters) { KUriFilterData data(uri); filterUri(data, filters); return data.uri(); } QString KUriFilter::filteredUri(const QString &uri, const QStringList &filters) { KUriFilterData data(uri); filterUri(data, filters); return data.uri().toString(); } #ifndef KIOWIDGETS_NO_DEPRECATED bool KUriFilter::filterSearchUri(KUriFilterData &data) { return filterSearchUri(data, (NormalTextFilter | WebShortcutFilter)); } #endif bool KUriFilter::filterSearchUri(KUriFilterData &data, SearchFilterTypes types) { QStringList filters; if (types & WebShortcutFilter) { filters << QStringLiteral("kurisearchfilter"); } if (types & NormalTextFilter) { filters << QStringLiteral("kuriikwsfilter"); } return filterUri(data, filters); } QStringList KUriFilter::pluginNames() const { QStringList res; res.reserve(d->pluginList.size()); std::transform(d->pluginList.constBegin(), d->pluginList.constEnd(), std::back_inserter(res), [](const KUriFilterPlugin *plugin) { return plugin->objectName(); }); return res; } void KUriFilter::loadPlugins() { QVector plugins = KPluginLoader::findPlugins("kf5/urifilters"); // Sort the plugins by order of priority std::sort(plugins.begin(), plugins.end(), [](const KPluginMetaData &a, const KPluginMetaData &b) { return a.rawData().value("X-KDE-InitialPreference").toInt() > b.rawData().value("X-KDE-InitialPreference").toInt(); }); QStringList pluginNames; pluginNames.reserve(plugins.count()); for (const KPluginMetaData &pluginMetaData : plugins) { const QString fileName = pluginMetaData.fileName().section(QLatin1Char('/'), -1); if (!pluginNames.contains(fileName)) { pluginNames << fileName; KPluginFactory *factory = qobject_cast(pluginMetaData.instantiate()); if (factory) { KUriFilterPlugin *plugin = factory->create(nullptr); if (plugin) { d->pluginList << plugin; } } } } }