diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 24459dd5..399eea62 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,138 +1,138 @@ if(POLICY CMP0028) cmake_policy(SET CMP0028 OLD) endif() remove_definitions(-DQT_NO_CAST_FROM_ASCII) include(ECMAddTests) add_subdirectory(http) add_subdirectory(kcookiejar) find_package(Qt5Widgets REQUIRED) ########### unittests ############### find_package(Qt5Concurrent ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE) ecm_add_tests( kacltest.cpp listdirtest.cpp kmountpointtest.cpp upurltest.cpp dataprotocoltest.cpp jobtest.cpp jobremotetest.cpp kfileitemtest.cpp kprotocolinfotest.cpp ktcpsockettest.cpp globaltest.cpp mkpathjobtest.cpp threadtest.cpp udsentrytest.cpp udsentry_benchmark.cpp deletejobtest.cpp urlutiltest.cpp batchrenamejobtest.cpp NAME_PREFIX "kiocore-" LINK_LIBRARIES KF5::KIOCore KF5::I18n Qt5::Test Qt5::Network ) target_link_libraries(threadtest Qt5::Concurrent) ecm_add_test( http_jobtest.cpp httpserver_p.cpp TEST_NAME http_jobtest NAME_PREFIX "kiocore-" LINK_LIBRARIES KF5::KIOCore KF5::I18n Qt5::Test Qt5::Network ) if(UNIX) ecm_add_tests( klocalsockettest.cpp klocalsocketservertest.cpp privilegejobtest.cpp NAME_PREFIX "kiocore-" LINK_LIBRARIES KF5::KIOCore KF5::I18n Qt5::Test Qt5::Network ) endif() if (TARGET KF5::KIOGui) ecm_add_tests( favicontest.cpp NAME_PREFIX "kiogui-" LINK_LIBRARIES KF5::KIOCore KF5::KIOGui Qt5::Test ) target_link_libraries(favicontest Qt5::Concurrent) endif() if (TARGET KF5::KIOWidgets) ecm_add_tests( clipboardupdatertest.cpp dropjobtest.cpp kdynamicjobtrackernowidgetstest.cpp krununittest.cpp kdirlistertest.cpp kdirmodeltest.cpp kfileitemactionstest.cpp fileundomanagertest.cpp kurifiltertest.cpp kurlcompletiontest.cpp jobguitest.cpp pastetest.cpp accessmanagertest.cpp kurifiltersearchprovideractionstest.cpp NAME_PREFIX "kiowidgets-" - LINK_LIBRARIES KF5::KIOCore KF5::KIOWidgets Qt5::Test + LINK_LIBRARIES KF5::KIOCore KF5::KIOWidgets Qt5::Test Qt5::DBus ) set_target_properties(krununittest PROPERTIES COMPILE_FLAGS "-DCMAKE_INSTALL_FULL_LIBEXECDIR_KF5=\"\\\"${CMAKE_INSTALL_FULL_LIBEXECDIR_KF5}\\\"\"") # Same as accessmanagertest, but using QNetworkAccessManager, to make sure we # behave the same ecm_add_test( accessmanagertest.cpp TEST_NAME accessmanagertest-qnam NAME_PREFIX "kiowidgets-" LINK_LIBRARIES KF5::KIOCore KF5::KIOWidgets Qt5::Test ) set_target_properties(accessmanagertest-qnam PROPERTIES COMPILE_FLAGS "-DUSE_QNAM") # Same as kurlcompletiontest, but with immediate return, and results posted by thread later ecm_add_test( kurlcompletiontest.cpp TEST_NAME kurlcompletiontest-nowait NAME_PREFIX "kiowidgets-" LINK_LIBRARIES KF5::KIOCore KF5::KIOWidgets Qt5::Test ) set_target_properties(kurlcompletiontest-nowait PROPERTIES COMPILE_FLAGS "-DNO_WAIT") endif() if (TARGET KF5::KIOFileWidgets) find_package(KF5XmlGui ${KF5_DEP_VERSION} REQUIRED) include_directories(${CMAKE_SOURCE_DIR}/src/filewidgets ${CMAKE_BINARY_DIR}/src/filewidgets) ecm_add_tests( kurlnavigatortest.cpp kurlcomboboxtest.cpp kdiroperatortest.cpp kfilewidgettest.cpp kfilecustomdialogtest.cpp knewfilemenutest.cpp kfilecopytomenutest.cpp kfileplacesmodeltest.cpp kfileplacesviewtest.cpp kurlrequestertest.cpp NAME_PREFIX "kiofilewidgets-" LINK_LIBRARIES KF5::KIOFileWidgets KF5::KIOWidgets KF5::XmlGui KF5::Bookmarks Qt5::Test KF5::I18n ) set_tests_properties(kiofilewidgets-kfileplacesmodeltest PROPERTIES RUN_SERIAL TRUE) set_tests_properties(kiofilewidgets-kfileplacesviewtest PROPERTIES RUN_SERIAL TRUE) endif() # this should be done by cmake, see bug 371721 if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND Qt5Core_VERSION VERSION_GREATER 5.8.0) set_property(TARGET jobtest APPEND PROPERTY AUTOMOC_MOC_OPTIONS --include ${CMAKE_BINARY_DIR}/src/core/moc_predefs.h) endif() diff --git a/autotests/accessmanagertest.cpp b/autotests/accessmanagertest.cpp index 16fe38d8..2d9311d0 100644 --- a/autotests/accessmanagertest.cpp +++ b/autotests/accessmanagertest.cpp @@ -1,130 +1,130 @@ /* This file is part of the KDE libraries Copyright (c) 2015 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 Lesser 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 +#include #include #include /** * Unit test for AccessManager */ class AccessManagerTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase() { qputenv("KDE_FORK_SLAVES", "yes"); // To avoid a runtime dependency on klauncher qputenv("KIOSLAVE_ENABLE_TESTMODE", "1"); // ensure the ioslaves call QStandardPaths::setTestModeEnabled too QStandardPaths::setTestModeEnabled(true); } void testGet() { const QString aFile = QFINDTESTDATA("accessmanagertest.cpp"); QNetworkReply *reply = manager()->get(QNetworkRequest(QUrl::fromLocalFile(aFile))); QSignalSpy spy(reply, SIGNAL(finished())); QVERIFY(spy.wait()); QFile f(aFile); QVERIFY(f.open(QIODevice::ReadOnly)); QCOMPARE(f.readAll(), reply->readAll()); } void testPut() { #if defined(USE_QNAM) && QT_VERSION <= QT_VERSION_CHECK(5, 5, 0) QSKIP("This test is broken with Qt < 5.5, the fix is 9286a8e5dd97c5d4d7e0ed07a73d4ce7240fdc1d in qtbase"); #endif const QString aDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); QVERIFY(QDir::temp().mkpath(aDir)); const QString aFile = aDir + QStringLiteral("/accessmanagertest-data"); const QByteArray content = "We love free software!"; QBuffer buffer; buffer.setData(content); QVERIFY(buffer.open(QIODevice::ReadOnly)); QFile::remove(aFile); QNetworkReply *reply = manager()->put(QNetworkRequest(QUrl::fromLocalFile(aFile)), &buffer); QSignalSpy spy(reply, SIGNAL(finished())); QVERIFY(reply->isRunning()); QVERIFY(spy.wait()); QVERIFY(QFile::exists(aFile)); QFile f(aFile); QVERIFY(f.open(QIODevice::ReadOnly)); QCOMPARE(f.readAll(), content); QFile::remove(aFile); } void testPutSequential() { #if defined(USE_QNAM) && QT_VERSION <= QT_VERSION_CHECK(5, 5, 0) QSKIP("This test is broken with Qt < 5.5, the fix is 9286a8e5dd97c5d4d7e0ed07a73d4ce7240fdc1d in qtbase"); #endif const QString aDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); QVERIFY(QDir::temp().mkpath(aDir)); const QString aFile = aDir + QStringLiteral("/accessmanagertest-data2"); const QString putDataContents = "We love free software! " + QString(24000, 'c'); QProcess process; process.start(QStringLiteral("echo"), QStringList() << putDataContents); QFile::remove(aFile); QNetworkReply *reply = manager()->put(QNetworkRequest(QUrl::fromLocalFile(aFile)), &process); QSignalSpy spy(reply, SIGNAL(finished())); QVERIFY(spy.wait()); QVERIFY(QFile::exists(aFile)); QFile f(aFile); QVERIFY(f.open(QIODevice::ReadOnly)); QByteArray cts = f.readAll(); cts.chop(1); //we remove the eof QCOMPARE(QString::fromUtf8(cts).size(), putDataContents.size()); QCOMPARE(QString::fromUtf8(cts), putDataContents); QFile::remove(aFile); } private: /** * we want to run the tests both on QNAM and KIO::AccessManager * to make sure they behave the same way. */ QNetworkAccessManager *manager() { static QNetworkAccessManager *ret = nullptr; if (!ret) { #ifdef USE_QNAM ret = new QNetworkAccessManager(this); #else ret = new KIO::AccessManager(this); #endif } return ret; } }; QTEST_MAIN(AccessManagerTest) #include "accessmanagertest.moc" diff --git a/autotests/dataprotocoltest.h b/autotests/dataprotocoltest.h index bddb78da..17d71915 100644 --- a/autotests/dataprotocoltest.h +++ b/autotests/dataprotocoltest.h @@ -1,21 +1,21 @@ /* * Copyright (C) 2012 Rolf Eike Beer */ #ifndef DATAPROTOCOLTEST_H #define DATAPROTOCOLTEST_H #include -#include -#include +#include +#include class DataProtocolTest : public QObject { Q_OBJECT private Q_SLOTS: void runAllTests(); void runAllTests_data(); }; #endif /* DATAPROTOCOLTEST_H */ diff --git a/autotests/deletejobtest.cpp b/autotests/deletejobtest.cpp index e440d449..f43b626e 100644 --- a/autotests/deletejobtest.cpp +++ b/autotests/deletejobtest.cpp @@ -1,113 +1,113 @@ /* * Copyright (C) 2015 Martin Blumenstingl * * 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 "deletejobtest.h" -#include +#include #include #include #include #include #include #include QTEST_MAIN(DeleteJobTest) #define KJOB_NO_ERROR static_cast(KJob::NoError) void DeleteJobTest::initTestCase() { // To avoid a runtime dependency on klauncher qputenv("KDE_FORK_SLAVES", "yes"); } void DeleteJobTest::deleteFileTestCase_data() const { QTest::addColumn("fileName"); QTest::newRow("latin characters") << "testfile"; QTest::newRow("german umlauts") << "testger\u00E4t"; QTest::newRow("chinese characters") << "\u8A66"; } void DeleteJobTest::deleteFileTestCase() { QFETCH(QString, fileName); QTemporaryFile tempFile; tempFile.setFileTemplate(fileName + QStringLiteral("XXXXXX")); QVERIFY(tempFile.open()); const QString path = tempFile.fileName(); tempFile.close(); QVERIFY(QFile::exists(path)); /*QBENCHMARK*/ { KIO::DeleteJob *job = KIO::del(QUrl::fromLocalFile(path), KIO::HideProgressInfo); job->setUiDelegate(nullptr); QSignalSpy spy(job, SIGNAL(result(KJob*))); QVERIFY(spy.isValid()); QVERIFY(spy.wait(100000)); QCOMPARE(job->error(), KJOB_NO_ERROR); QVERIFY(!tempFile.exists()); } } void DeleteJobTest::deleteDirectoryTestCase_data() const { QTest::addColumn("fileNames"); QStringList filesInNonEmptyDirectory = QStringList() << QStringLiteral("1.txt"); QTest::newRow("non-empty directory") << filesInNonEmptyDirectory; QTest::newRow("empty directory") << QStringList(); } void DeleteJobTest::deleteDirectoryTestCase() { QTemporaryDir tempDir; QVERIFY(tempDir.isValid()); QFETCH(QStringList, fileNames); createEmptyTestFiles(fileNames, tempDir.path()); /*QBENCHMARK*/ { KIO::DeleteJob *job = KIO::del(QUrl::fromLocalFile(tempDir.path()), KIO::HideProgressInfo); job->setUiDelegate(nullptr); QSignalSpy spy(job, SIGNAL(result(KJob*))); QVERIFY(spy.isValid()); QVERIFY(spy.wait(100000)); QCOMPARE(job->error(), KJOB_NO_ERROR); QVERIFY(!QDir(tempDir.path()).exists()); } } void DeleteJobTest::createEmptyTestFiles(const QStringList &fileNames, const QString &path) const { QStringListIterator iterator(fileNames); while (iterator.hasNext()) { const QString filename = path + QDir::separator() + iterator.next(); QFile file(filename); QVERIFY(file.open(QIODevice::WriteOnly)); } QCOMPARE(QDir(path).entryList(QDir::Files).count(), fileNames.size()); } diff --git a/autotests/globaltest.h b/autotests/globaltest.h index 246fb245..e954b0a1 100644 --- a/autotests/globaltest.h +++ b/autotests/globaltest.h @@ -1,40 +1,40 @@ /* This file is part of the KDE project Copyright (C) 2013 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. */ #ifndef KIO_GLOBALTEST_H #define KIO_GLOBALTEST_H -#include +#include class GlobalTest : public QObject { Q_OBJECT public: GlobalTest() {} private Q_SLOTS: void testUserPermissionConversion(); void testGroupPermissionConversion(); void testOtherPermissionConversion(); void testSuggestName_data(); void testSuggestName(); }; #endif diff --git a/autotests/http/httpauthenticationtest.cpp b/autotests/http/httpauthenticationtest.cpp index 55c39395..982ff72a 100644 --- a/autotests/http/httpauthenticationtest.cpp +++ b/autotests/http/httpauthenticationtest.cpp @@ -1,344 +1,344 @@ /* This file is part of the KDE libraries Copyright (C) 2011 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 "httpauthenticationtest.h" #include -#include -#include -#include -#include +#include +#include +#include +#include #include #define ENABLE_HTTP_AUTH_NONCE_SETTER #include "httpauthentication.cpp" // QT5 TODO QTEST_GUILESS_MAIN(HTTPAuthenticationTest) QTEST_MAIN(HTTPAuthenticationTest) static void parseAuthHeader(const QByteArray &header, QByteArray *bestOffer, QByteArray *scheme, QList *result) { const QList authHeaders = KAbstractHttpAuthentication::splitOffers(QList() << header); QByteArray chosenHeader = KAbstractHttpAuthentication::bestOffer(authHeaders); if (bestOffer) { *bestOffer = chosenHeader; } if (!scheme && !result) { return; } QByteArray authScheme; const QList parseResult = parseChallenge(chosenHeader, &authScheme); if (scheme) { *scheme = authScheme; } if (result) { *result = parseResult; } } static QByteArray hmacMD5(const QByteArray &data, const QByteArray &key) { QByteArray ret; QByteArray ipad(64, 0x36); QByteArray opad(64, 0x5c); Q_ASSERT(key.size() <= 64); for (int i = qMin(key.size(), 64) - 1; i >= 0; i--) { ipad.data()[i] ^= key[i]; opad.data()[i] ^= key[i]; } QByteArray content(ipad + data); QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(content); content = opad + md5.result(); md5.reset(); md5.addData(content); return md5.result(); } static QByteArray QString2UnicodeLE(const QString &target) { QByteArray unicode(target.length() * 2, 0); for (int i = 0; i < target.length(); i++) { ((quint16 *) unicode.data()) [ i ] = qToLittleEndian(target[i].unicode()); } return unicode; } void HTTPAuthenticationTest::testHeaderParsing_data() { QTest::addColumn("header"); QTest::addColumn("resultScheme"); QTest::addColumn("resultValues"); // Tests cases from http://greenbytes.de/tech/tc/httpauth/ QTest::newRow("greenbytes-simplebasic") << QByteArray("Basic realm=\"foo\"") << QByteArray("Basic") << QByteArray("realm,foo"); QTest::newRow("greenbytes-simplebasictok") << QByteArray("Basic realm=foo") << QByteArray("Basic") << QByteArray("realm,foo"); QTest::newRow("greenbytes-simplebasiccomma") << QByteArray("Basic , realm=\"foo\"") << QByteArray("Basic") << QByteArray("realm,foo"); // there must be a space after the scheme QTest::newRow("greenbytes-simplebasiccomma2") << QByteArray("Basic, realm=\"foo\"") << QByteArray() << QByteArray(); // we accept scheme without any parameters to maintain compatibility with too simple minded servers out there QTest::newRow("greenbytes-simplebasicnorealm") << QByteArray("Basic") << QByteArray("Basic") << QByteArray(); QTest::newRow("greenbytes-simplebasicwsrealm") << QByteArray("Basic realm = \"foo\"") << QByteArray("Basic") << QByteArray("realm,foo"); QTest::newRow("greenbytes-simplebasicrealmsqc") << QByteArray("Basic realm=\"\\f\\o\\o\"") << QByteArray("Basic") << QByteArray("realm,foo"); QTest::newRow("greenbytes-simplebasicrealmsqc2") << QByteArray("Basic realm=\"\\\"foo\\\"\"") << QByteArray("Basic") << QByteArray("realm,\"foo\""); QTest::newRow("greenbytes-simplebasicnewparam1") << QByteArray("Basic realm=\"foo\", bar=\"xyz\"") << QByteArray("Basic") << QByteArray("realm,foo,bar,xyz"); QTest::newRow("greenbytes-simplebasicnewparam2") << QByteArray("Basic bar=\"xyz\", realm=\"foo\"") << QByteArray("Basic") << QByteArray("bar,xyz,realm,foo"); // a Basic challenge following an empty one QTest::newRow("greenbytes-multibasicempty") << QByteArray(",Basic realm=\"foo\"") << QByteArray("Basic") << QByteArray("realm,foo"); QTest::newRow("greenbytes-multibasicunknown") << QByteArray("Basic realm=\"basic\", Newauth realm=\"newauth\"") << QByteArray("Basic") << QByteArray("realm,basic"); QTest::newRow("greenbytes-multibasicunknown2") << QByteArray("Newauth realm=\"newauth\", Basic realm=\"basic\"") << QByteArray("Basic") << QByteArray("realm,basic"); QTest::newRow("greenbytes-unknown") << QByteArray("Newauth realm=\"newauth\"") << QByteArray() << QByteArray(); // Misc. test cases QTest::newRow("ntlm") << QByteArray("NTLM ") << QByteArray("NTLM") << QByteArray(); QTest::newRow("unterminated-quoted-value") << QByteArray("Basic realm=\"") << QByteArray("Basic") << QByteArray(); QTest::newRow("spacing-and-tabs") << QByteArray("bAsic bar\t =\t\"baz\", realm =\t\"foo\"") << QByteArray("bAsic") << QByteArray("bar,baz,realm,foo"); QTest::newRow("empty-fields") << QByteArray("Basic realm=foo , , , ,, bar=\"baz\"\t,") << QByteArray("Basic") << QByteArray("realm,foo,bar,baz"); QTest::newRow("spacing") << QByteArray("Basic realm=foo, bar = baz") << QByteArray("Basic") << QByteArray("realm,foo,bar,baz"); QTest::newRow("missing-comma-between-fields") << QByteArray("Basic realm=foo bar = baz") << QByteArray("Basic") << QByteArray("realm,foo"); // quotes around text, every character needlessly quoted QTest::newRow("quote-excess") << QByteArray("Basic realm=\"\\\"\\f\\o\\o\\\"\"") << QByteArray("Basic") << QByteArray("realm,\"foo\""); // quotes around text, quoted backslashes QTest::newRow("quoted-backslash") << QByteArray("Basic realm=\"\\\"foo\\\\\\\\\"") << QByteArray("Basic") << QByteArray("realm,\"foo\\\\"); // quotes around text, quoted backslashes, quote hidden behind them QTest::newRow("quoted-backslash-and-quote") << QByteArray("Basic realm=\"\\\"foo\\\\\\\"\"") << QByteArray("Basic") << QByteArray("realm,\"foo\\\""); // invalid quoted text QTest::newRow("invalid-quoted") << QByteArray("Basic realm=\"\\\"foo\\\\\\\"") << QByteArray("Basic") << QByteArray(); // ends in backslash without quoted value QTest::newRow("invalid-quote") << QByteArray("Basic realm=\"\\\"foo\\\\\\") << QByteArray("Basic") << QByteArray(); } QByteArray joinQByteArray(const QList &list) { QByteArray data; const int count = list.count(); for (int i = 0; i < count; ++i) { if (i > 0) { data += ','; } data += list.at(i); } return data; } void HTTPAuthenticationTest::testHeaderParsing() { QFETCH(QByteArray, header); QFETCH(QByteArray, resultScheme); QFETCH(QByteArray, resultValues); QByteArray chosenHeader, chosenScheme; QList parsingResult, expectedResult; parseAuthHeader(header, &chosenHeader, &chosenScheme, &parsingResult); QCOMPARE(chosenScheme, resultScheme); QCOMPARE(joinQByteArray(parsingResult), resultValues); } void HTTPAuthenticationTest::testAuthenticationSelection_data() { QTest::addColumn("input"); QTest::addColumn("expectedScheme"); QTest::addColumn("expectedOffer"); #if HAVE_LIBGSSAPI QTest::newRow("all-with-negotiate") << QByteArray("Negotiate , Digest , NTLM , Basic") << QByteArray("Negotiate") << QByteArray("Negotiate"); #endif QTest::newRow("all-without-negotiate") << QByteArray("Digest , NTLM , Basic , NewAuth") << QByteArray("Digest") << QByteArray("Digest"); QTest::newRow("ntlm-basic-unknown") << QByteArray("NTLM , Basic , NewAuth") << QByteArray("NTLM") << QByteArray("NTLM"); QTest::newRow("basic-unknown") << QByteArray("Basic , NewAuth") << QByteArray("Basic") << QByteArray("Basic"); QTest::newRow("ntlm-basic+param-ntlm") << QByteArray("NTLM , Basic realm=foo, bar = baz, NTLM") << QByteArray("NTLM") << QByteArray("NTLM"); QTest::newRow("ntlm-with-type{2|3}") << QByteArray("NTLM VFlQRV8yX09SXzNfTUVTU0FHRQo=") << QByteArray("NTLM") << QByteArray("NTLM VFlQRV8yX09SXzNfTUVTU0FHRQo="); // Unknown schemes always return blank, i.e. auth request should be ignored QTest::newRow("unknown-param") << QByteArray("Newauth realm=\"newauth\"") << QByteArray() << QByteArray(); QTest::newRow("unknown-unknown") << QByteArray("NewAuth , NewAuth2") << QByteArray() << QByteArray(); } void HTTPAuthenticationTest::testAuthenticationSelection() { QFETCH(QByteArray, input); QFETCH(QByteArray, expectedScheme); QFETCH(QByteArray, expectedOffer); QByteArray scheme, offer; parseAuthHeader(input, &offer, &scheme, nullptr); QCOMPARE(scheme, expectedScheme); QCOMPARE(offer, expectedOffer); } void HTTPAuthenticationTest::testAuthentication_data() { QTest::addColumn("input"); QTest::addColumn("expectedResponse"); QTest::addColumn("user"); QTest::addColumn("pass"); QTest::addColumn("url"); QTest::addColumn("cnonce"); // Test cases from RFC 2617... QTest::newRow("rfc-2617-basic-example") << QByteArray("Basic realm=\"WallyWorld\"") << QByteArray("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==") << QByteArray("Aladdin") << QByteArray("open sesame") << QByteArray() << QByteArray(); QTest::newRow("rfc-2617-digest-example") << QByteArray("Digest realm=\"testrealm@host.com\", qop=\"auth,auth-int\", nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"") << QByteArray("Digest username=\"Mufasa\", realm=\"testrealm@host.com\", nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", uri=\"/dir/index.html\", algorithm=MD5, qop=auth, cnonce=\"0a4f113b\", nc=00000001, response=\"6629fae49393a05397450978507c4ef1\", opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"") << QByteArray("Mufasa") << QByteArray("Circle Of Life") << QByteArray("http://www.nowhere.org/dir/index.html") << QByteArray("0a4f113b"); QTest::newRow("ntlm-negotiate-type1") << QByteArray("NTLM") << QByteArray("NTLM TlRMTVNTUAABAAAABQIAAAAAAAAAAAAAAAAAAAAAAAA=") << QByteArray() << QByteArray() << QByteArray() << QByteArray(); QTest::newRow("ntlm-challenge-type2") << QByteArray("NTLM TlRMTVNTUAACAAAAFAAUACgAAAABggAAU3J2Tm9uY2UAAAAAAAAAAFUAcgBzAGEALQBNAGEAagBvAHIA") << QByteArray("NTLM TlRMTVNTUAADAAAAGAAYAFgAAAAYABgAQAAAABQAFABwAAAADAAMAIQAAAAWABYAkAAAAAAAAAAAAAAAAYIAAODgDeMQShvyBT8Hx92oLTxImumJ4bAA062Hym3v40aFucQ8R3qMQtYAZn1okufol1UAcgBzAGEALQBNAGkAbgBvAHIAWgBhAHAAaABvAGQAVwBPAFIASwBTAFQAQQBUAEkATwBOAA==") << QByteArray("Ursa-Minor\\Zaphod") << QByteArray("Beeblebrox") << QByteArray() << QByteArray(); QTest::newRow("ntlm-challenge-type2-no-domain") << QByteArray("NTLM TlRMTVNTUAACAAAAFAAUACgAAAABggAAU3J2Tm9uY2UAAAAAAAAAAFUAcgBzAGEALQBNAGEAagBvAHIA") << QByteArray("NTLM TlRMTVNTUAADAAAAGAAYAFgAAAAYABgAQAAAABQAFABwAAAADAAMAIQAAAAWABYAkAAAAAAAAAAAAAAAAYIAAODgDeMQShvyBT8Hx92oLTxImumJ4bAA062Hym3v40aFucQ8R3qMQtYAZn1okufol1UAcgBzAGEALQBNAGEAagBvAHIAWgBhAHAAaABvAGQAVwBPAFIASwBTAFQAQQBUAEkATwBOAA==") << QByteArray("Zaphod") << QByteArray("Beeblebrox") << QByteArray() << QByteArray(); QTest::newRow("ntlm-challenge-type2-empty-domain") << QByteArray("NTLM TlRMTVNTUAACAAAAFAAUACgAAAABggAAU3J2Tm9uY2UAAAAAAAAAAFUAcgBzAGEALQBNAGEAagBvAHIA") << QByteArray("NTLM TlRMTVNTUAADAAAAGAAYAFgAAAAYABgAQAAAAAAAAAAAAAAADAAMAHAAAAAWABYAfAAAAAAAAAAAAAAAAYIAAODgDeMQShvyBT8Hx92oLTxImumJ4bAA062Hym3v40aFucQ8R3qMQtYAZn1okufol1oAYQBwAGgAbwBkAFcATwBSAEsAUwBUAEEAVABJAE8ATgA=") << QByteArray("\\Zaphod") << QByteArray("Beeblebrox") << QByteArray() << QByteArray(); } void HTTPAuthenticationTest::testAuthentication() { QFETCH(QByteArray, input); QFETCH(QByteArray, expectedResponse); QFETCH(QByteArray, user); QFETCH(QByteArray, pass); QFETCH(QByteArray, url); QFETCH(QByteArray, cnonce); QByteArray bestOffer; parseAuthHeader(input, &bestOffer, nullptr, nullptr); KAbstractHttpAuthentication *authObj = KAbstractHttpAuthentication::newAuth(bestOffer); QVERIFY(authObj); if (!cnonce.isEmpty()) { authObj->setDigestNonceValue(cnonce); } authObj->setChallenge(bestOffer, QUrl(url), "GET"); authObj->generateResponse(QString(user), QString(pass)); QCOMPARE(authObj->headerFragment().trimmed().constData(), expectedResponse.constData()); delete authObj; } void HTTPAuthenticationTest::testAuthenticationNTLMv2() { QByteArray input("NTLM TlRMTVNTUAACAAAABgAGADgAAAAFAokCT0wyUnb4OSQAAAAAAAAAAMYAxgA+AAAABgGxHQAAAA9UAFMAVAACAAYAVABTAFQAAQASAEQAVgBHAFIASwBWAFEAUABEAAQAKgB0AHMAdAAuAGQAagBrAGgAcQBjAGkAaABtAGMAbwBmAGoALgBvAHIAZwADAD4ARABWAEcAUgBLAFYAUQBQAEQALgB0AHMAdAAuAGQAagBrAGgAcQBjAGkAaABtAGMAbwBmAGoALgBvAHIAZwAFACIAZABqAGsAaABxAGMAaQBoAG0AYwBvAGYAagAuAG8AcgBnAAcACABvb9jXZl7RAQAAAAA="); QByteArray expectedResponse("TlRMTVNTUAADAAAAGAAYADYBAAD2APYAQAAAAAYABgBOAQAABgAGAFQBAAAWABYAWgEAAAAAAAAAAAAABQKJArXyhsxZPveKcfcV21viIsUBAQAAAAAAAAC8GQxfX9EBTHOi1kJbHbQAAAAAAgAGAFQAUwBUAAEAEgBEAFYARwBSAEsAVgBRAFAARAAEACoAdABzAHQALgBkAGoAawBoAHEAYwBpAGgAbQBjAG8AZgBqAC4AbwByAGcAAwA+AEQAVgBHAFIASwBWAFEAUABEAC4AdABzAHQALgBkAGoAawBoAHEAYwBpAGgAbQBjAG8AZgBqAC4AbwByAGcABQAiAGQAagBrAGgAcQBjAGkAaABtAGMAbwBmAGoALgBvAHIAZwAHAAgAb2/Y12Ze0QEAAAAAAAAAAOInN0N/15GHBtz3WXvvV159KG/2MbYk0FQAUwBUAGIAbwBiAFcATwBSAEsAUwBUAEEAVABJAE8ATgA="); QString user("TST\\bob"); QString pass("cacamas"); QString target("TST"); QByteArray bestOffer; parseAuthHeader(input, &bestOffer, nullptr, nullptr); KConfig conf; KConfigGroup confGroup = conf.group("test"); confGroup.writeEntry("EnableNTLMv2Auth", true); KAbstractHttpAuthentication *authObj = KAbstractHttpAuthentication::newAuth(bestOffer, &confGroup); QVERIFY(authObj); authObj->setChallenge(bestOffer, QUrl(), "GET"); authObj->generateResponse(QString(user), QString(pass)); QByteArray resp(QByteArray::fromBase64(authObj->headerFragment().trimmed().mid(5))); QByteArray expResp(QByteArray::fromBase64(expectedResponse)); /* Prepare responses stripped from any data that is variable. */ QByteArray strippedResp(resp); memset(strippedResp.data() + 0x40, 0, 0x10); // NTLMv2 MAC memset(strippedResp.data() + 0x58, 0, 0x10); // timestamp + client nonce memset(strippedResp.data() + 0x136, 0, 0x18); // LMv2 MAC QByteArray strippedExpResp(expResp); memset(strippedExpResp.data() + 0x40, 0, 0x10); // NTLMv2 MAC memset(strippedExpResp.data() + 0x58, 0, 0x10); // timestamp + client nonce memset(strippedExpResp.data() + 0x136, 0, 0x18); // LMv2 MAC /* Compare the stripped responses. */ QCOMPARE(strippedResp.toBase64(), strippedExpResp.toBase64()); /* Verify the NTLMv2 response MAC. */ QByteArray challenge(QByteArray::fromBase64(input.mid(5))); QByteArray serverNonce(challenge.mid(0x18, 8)); QByteArray uniPass(QString2UnicodeLE(pass)); QByteArray ntlmHash(QCryptographicHash::hash(uniPass, QCryptographicHash::Md4)); int i = user.indexOf('\\'); QString username; if (i >= 0) { username = user.mid(i + 1); } else { username = user; } QByteArray userTarget(QString2UnicodeLE(username.toUpper() + target)); QByteArray ntlm2Hash(hmacMD5(userTarget, ntlmHash)); QByteArray hashData(serverNonce + resp.mid(0x50, 230)); QByteArray mac(hmacMD5(hashData, ntlm2Hash)); QCOMPARE(mac.toHex(), resp.mid(0x40, 16).toHex()); /* Verify the LMv2 response MAC. */ QByteArray lmHashData(serverNonce + resp.mid(0x146, 8)); QByteArray lmHash(hmacMD5(lmHashData, ntlm2Hash)); QCOMPARE(lmHash.toHex(), resp.mid(0x136, 16).toHex()); delete authObj; } diff --git a/autotests/http/httpauthenticationtest.h b/autotests/http/httpauthenticationtest.h index b6900b29..4a4c7c50 100644 --- a/autotests/http/httpauthenticationtest.h +++ b/autotests/http/httpauthenticationtest.h @@ -1,33 +1,33 @@ /* This file is part of the KDE libraries Copyright (c) 2011 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 +#include class HTTPAuthenticationTest : public QObject { Q_OBJECT private Q_SLOTS: void testHeaderParsing(); void testHeaderParsing_data(); void testAuthenticationSelection(); void testAuthenticationSelection_data(); void testAuthentication(); void testAuthentication_data(); void testAuthenticationNTLMv2(); }; diff --git a/autotests/http/httpfiltertest.cpp b/autotests/http/httpfiltertest.cpp index b97a4a89..f927bd75 100644 --- a/autotests/http/httpfiltertest.cpp +++ b/autotests/http/httpfiltertest.cpp @@ -1,153 +1,153 @@ /* * Copyright (C) 2002-2005 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 +#include #include "kfilterdev.h" #include "kfilterbase.h" -#include -#include -#include +#include +#include +#include #include #include "httpfilter.h" class HTTPFilterTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void test_deflateWithZlibHeader(); void test_httpFilterGzip(); private: void test_block_write(const QString &fileName, const QByteArray &data); void test_block_read(const QString &fileName); void test_getch(const QString &fileName); void test_textstream(const QString &fileName); void test_readall(const QString &fileName, const QString &mimeType, const QByteArray &expectedData); protected Q_SLOTS: void slotFilterOutput(const QByteArray &data); private: QString pathgz; QByteArray testData; QByteArray m_filterOutput; }; QTEST_MAIN(HTTPFilterTest) void HTTPFilterTest::initTestCase() { qRegisterMetaType(); const QString currentdir = QDir::currentPath(); pathgz = currentdir + "/test.gz"; testData = "hello world\n"; // Create the gz file KFilterDev dev(pathgz); QVERIFY(dev.open(QIODevice::WriteOnly)); const int ret = dev.write(testData); QCOMPARE(ret, testData.size()); dev.close(); } static void getCompressedData(QByteArray &data, QByteArray &compressedData) { data = "Hello world, this is a test for deflate, from bug 114830 / 117683"; compressedData.resize(long(data.size() * 1.1f) + 12L); // requirements of zlib::compress2 unsigned long out_bufferlen = compressedData.size(); const int ret = compress2((Bytef *)compressedData.data(), &out_bufferlen, (const Bytef *)data.constData(), data.size(), 1); QCOMPARE(ret, Z_OK); compressedData.resize(out_bufferlen); } void HTTPFilterTest::test_deflateWithZlibHeader() { QByteArray data, deflatedData; getCompressedData(data, deflatedData); { HTTPFilterDeflate filter; QSignalSpy spyOutput(&filter, SIGNAL(output(QByteArray))); QSignalSpy spyError(&filter, SIGNAL(error(QString))); filter.slotInput(deflatedData); QCOMPARE(spyOutput.count(), 2); QCOMPARE(spyOutput[0][0].toByteArray(), data); QCOMPARE(spyOutput[1][0].toByteArray(), QByteArray()); QCOMPARE(spyError.count(), 0); } { // Now a test for giving raw deflate data to HTTPFilter HTTPFilterDeflate filter; QSignalSpy spyOutput(&filter, SIGNAL(output(QByteArray))); QSignalSpy spyError(&filter, SIGNAL(error(QString))); QByteArray rawDeflate = deflatedData.mid(2); // remove CMF+FLG rawDeflate.truncate(rawDeflate.size() - 4); // remove trailing Adler32. filter.slotInput(rawDeflate); QCOMPARE(spyOutput.count(), 2); QCOMPARE(spyOutput[0][0].toByteArray(), data); QCOMPARE(spyOutput[1][0].toByteArray(), QByteArray()); QCOMPARE(spyError.count(), 0); } } void HTTPFilterTest::test_httpFilterGzip() { QFile file(pathgz); QVERIFY(file.open(QIODevice::ReadOnly)); const QByteArray compressed = file.readAll(); // Test sending the whole data in one go { HTTPFilterGZip filter; QSignalSpy spyOutput(&filter, SIGNAL(output(QByteArray))); QSignalSpy spyError(&filter, SIGNAL(error(QString))); filter.slotInput(compressed); QCOMPARE(spyOutput.count(), 2); QCOMPARE(spyOutput[0][0].toByteArray(), testData); QCOMPARE(spyOutput[1][0].toByteArray(), QByteArray()); QCOMPARE(spyError.count(), 0); } // Test sending the data byte by byte { m_filterOutput.clear(); HTTPFilterGZip filter; QSignalSpy spyOutput(&filter, SIGNAL(output(QByteArray))); connect(&filter, SIGNAL(output(QByteArray)), this, SLOT(slotFilterOutput(QByteArray))); QSignalSpy spyError(&filter, SIGNAL(error(QString))); for (int i = 0; i < compressed.size(); ++i) { //qDebug() << "sending byte number" << i << ":" << (uchar)compressed[i]; filter.slotInput(QByteArray(compressed.constData() + i, 1)); QCOMPARE(spyError.count(), 0); } QCOMPARE(m_filterOutput, testData); QCOMPARE(spyOutput[spyOutput.count() - 1][0].toByteArray(), QByteArray()); // last one was empty } } void HTTPFilterTest::slotFilterOutput(const QByteArray &data) { m_filterOutput += data; } #include "httpfiltertest.moc" diff --git a/autotests/http/httpheaderdispositiontest.cpp b/autotests/http/httpheaderdispositiontest.cpp index ac416562..b2f6692b 100644 --- a/autotests/http/httpheaderdispositiontest.cpp +++ b/autotests/http/httpheaderdispositiontest.cpp @@ -1,382 +1,382 @@ /* This file is part of the KDE libraries Copyright (C) 2010,2011 Rolf Eike Beer 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 "httpheaderdispositiontest.h" #include -#include +#include #include #include #include // QT5 TODO QTEST_GUILESS_MAIN(HeaderDispositionTest) QTEST_MAIN(HeaderDispositionTest) static void runTest(const QString &header, const QByteArray &result) { QMap parameters = contentDispositionParser(header); QList results = result.split('\n'); if (result.isEmpty()) { results.clear(); } foreach (const QByteArray &ba, results) { QList values = ba.split('\t'); const QString key(QString::fromLatin1(values.takeFirst())); QVERIFY(parameters.contains(key)); const QByteArray val = values.takeFirst(); QVERIFY(values.isEmpty()); QCOMPARE(parameters[key], QString::fromUtf8(val.constData(), val.length())); } QCOMPARE(parameters.count(), results.count()); } void HeaderDispositionTest::runAllTests_data() { QTest::addColumn("header"); QTest::addColumn("result"); // http://greenbytes.de/tech/tc2231/ QTest::newRow("greenbytes-inlonly") << "inline" << QByteArray("type\tinline"); QTest::newRow("greenbytes-inlonlyquoted") << "\"inline\"" << QByteArray(); QTest::newRow("greenbytes-inlwithasciifilename") << "inline; filename=\"foo.html\"" << QByteArray("type\tinline\n" "filename\tfoo.html"); QTest::newRow("greenbytes-inlwithfnattach") << "inline; filename=\"Not an attachment!\"" << QByteArray("type\tinline\n" "filename\tNot an attachment!"); QTest::newRow("greenbytes-inlwithasciifilenamepdf") << "inline; filename=\"foo.pdf\"" << QByteArray("type\tinline\n" "filename\tfoo.pdf"); QTest::newRow("greenbytes-attonly") << "attachment" << QByteArray("type\tattachment"); QTest::newRow("greenbytes-attonlyquoted") << "\"attachment\"" << QByteArray(); QTest::newRow("greenbytes-attonlyucase") << "ATTACHMENT" << QByteArray("type\tattachment"); QTest::newRow("greenbytes-attwithasciifilename") << "attachment; filename=\"foo.html\"" << QByteArray("type\tattachment\n" "filename\tfoo.html"); QTest::newRow("greenbytes-attwithasciifnescapedchar") << "attachment; filename=\"f\\oo.html\"" << QByteArray("type\tattachment\n" "filename\tfoo.html"); QTest::newRow("greenbytes-attwithasciifnescapedquote") << "attachment; filename=\"\\\"quoting\\\" tested.html\"" << QByteArray("type\tattachment\n" "filename\t\"quoting\" tested.html"); QTest::newRow("greenbytes-attwithquotedsemicolon") << "attachment; filename=\"Here's a semicolon;.html\"" << QByteArray("type\tattachment\n" "filename\tHere's a semicolon;.html"); QTest::newRow("greenbytes-attwithfilenameandextparam") << "attachment; foo=\"bar\"; filename=\"foo.html\"" << QByteArray("type\tattachment\n" "foo\tbar\n" "filename\tfoo.html"); QTest::newRow("greenbytes-attwithfilenameandextparamescaped") << "attachment; foo=\"\\\"\\\\\";filename=\"foo.html\"" << QByteArray("type\tattachment\n" "foo\t\"\\\n" "filename\tfoo.html"); QTest::newRow("greenbytes-attwithasciifilenameucase") << "attachment; FILENAME=\"foo.html\"" << QByteArray("type\tattachment\n" "filename\tfoo.html"); // specification bug in RfC 2616, legal through RfC 2183 and 6266 QTest::newRow("greenbytes-attwithasciifilenamenq") << "attachment; filename=foo.html" << QByteArray("type\tattachment\n" "filename\tfoo.html"); QTest::newRow("greenbytes-attwithasciifilenamenqws") << "attachment; filename=foo bar.html" << QByteArray("type\tattachment"); QTest::newRow("greenbytes-attwithfntokensq") << "attachment; filename='foo.bar'" << QByteArray("type\tattachment\n" "filename\t'foo.bar'"); QTest::newRow("greenbytes-attwithisofnplain-x") << QStringLiteral("attachment; filename=\"foo-\xe4.html\"") << QByteArray("type\tattachment\n" "filename\tfoo-ä.html"); QTest::newRow("greenbytes-attwithisofnplain") << QString::fromLatin1("attachment; filename=\"foo-ä.html\"") << QByteArray("type\tattachment\n" "filename\tfoo-ä.html"); QTest::newRow("greenbytes-attwithfnrawpctenca") << "attachment; filename=\"foo-%41.html\"" << QByteArray("type\tattachment\n" "filename\tfoo-%41.html"); QTest::newRow("greenbytes-attwithfnusingpct") << "attachment; filename=\"50%.html\"" << QByteArray("type\tattachment\n" "filename\t50%.html"); QTest::newRow("greenbytes-attwithfnrawpctencaq") << "attachment; filename=\"foo-%\\41.html\"" << QByteArray("type\tattachment\n" "filename\tfoo-%41.html"); QTest::newRow("greenbytes-attwithnamepct") << "attachment; name=\"foo-%41.html\"" << QByteArray("type\tattachment\n" "name\tfoo-%41.html"); QTest::newRow("greenbytes-attwithfilenamepctandiso") << QStringLiteral("attachment; filename=\"\xe4-%41.html\"") << QByteArray("type\tattachment\n" "filename\tä-%41.html"); QTest::newRow("greenbytes-attwithfnrawpctenclong") << "attachment; filename=\"foo-%c3%a4-%e2%82%ac.html\"" << QByteArray("type\tattachment\n" "filename\tfoo-%c3%a4-%e2%82%ac.html"); QTest::newRow("greenbytes-attwithasciifilenamews1") << "attachment; filename =\"foo.html\"" << QByteArray("type\tattachment\n" "filename\tfoo.html"); QTest::newRow("greenbytes-attwith2filenames") << "attachment; filename=\"foo.html\"; filename=\"bar.html\"" << QByteArray("type\tattachment"); QTest::newRow("greenbytes-attfnbrokentoken") << "attachment; filename=foo[1](2).html" << QByteArray("type\tattachment"); QTest::newRow("greenbytes-attmissingdisposition") << "filename=foo.html" << QByteArray(); QTest::newRow("greenbytes-attmissingdisposition2") << "x=y; filename=foo.html" << QByteArray(); QTest::newRow("greenbytes-attmissingdisposition3") << "\"foo; filename=bar;baz\"; filename=qux" << QByteArray(); QTest::newRow("greenbytes-attmissingdisposition4") << "filename=foo.html, filename=bar.html" << QByteArray(); QTest::newRow("greenbytes-emptydisposition") << "; filename=foo.html" << QByteArray(); QTest::newRow("greenbytes-attbrokenquotedfn") << "attachment; filename=\"foo.html\".txt" << QByteArray("type\tattachment"); QTest::newRow("greenbytes-attbrokenquotedfn2") << "attachment; filename=\"bar" << QByteArray("type\tattachment"); QTest::newRow("greenbytes-attbrokenquotedfn3") << "attachment; filename=foo\"bar;baz\"qux" << QByteArray("type\tattachment"); QTest::newRow("greenbytes-attreversed") << "filename=foo.html; attachment" << QByteArray(); QTest::newRow("greenbytes-attconfusedparam") << "attachment; xfilename=foo.html" << QByteArray("type\tattachment\n" "xfilename\tfoo.html"); QTest::newRow("greenbytes-attabspath") << "attachment; filename=\"/foo.html\"" << QByteArray("type\tattachment\n" "filename\tfoo.html"); #ifdef Q_OS_WIN QTest::newRow("greenbytes-attabspath") << "attachment; filename=\"\\\\foo.html\"" << QByteArray("type\tattachment\n" "filename\tfoo.html"); #else // Q_OS_WIN QTest::newRow("greenbytes-attabspath") << "attachment; filename=\"\\\\foo.html\"" << QByteArray("type\tattachment\n" "filename\t\\foo.html"); #endif // Q_OS_WIN QTest::newRow("greenbytes-") << "attachment; creation-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"" << QByteArray("type\tattachment\n" "creation-date\tWed, 12 Feb 1997 16:29:51 -0500"); QTest::newRow("greenbytes-") << "attachment; modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"" << QByteArray("type\tattachment\n" "modification-date\tWed, 12 Feb 1997 16:29:51 -0500"); QTest::newRow("greenbytes-dispext") << "foobar" << QByteArray("type\tfoobar"); QTest::newRow("greenbytes-dispextbadfn") << "attachment; example=\"filename=example.txt\"" << QByteArray("type\tattachment\n" "example\tfilename=example.txt"); QTest::newRow("greenbytes-attwithisofn2231iso") << "attachment; filename*=iso-8859-1''foo-%E4.html" << QByteArray("type\tattachment\n" "filename\tfoo-ä.html"); QTest::newRow("greenbytes-attwithfn2231utf8") << "attachment; filename*=UTF-8''foo-%c3%a4-%e2%82%ac.html" << QByteArray("type\tattachment\n" "filename\tfoo-ä-€.html"); QTest::newRow("greenbytes-attwithfn2231noc") << "attachment; filename*=''foo-%c3%a4-%e2%82%ac.html" << QByteArray("type\tattachment"); // it's not filename, but "filename " QTest::newRow("greenbytes-attwithfn2231ws1") << "attachment; filename *=UTF-8''foo-%c3%a4.html" << QByteArray("type\tattachment"); QTest::newRow("greenbytes-attwithfn2231ws2") << "attachment; filename*= UTF-8''foo-%c3%a4.html" << QByteArray("type\tattachment\n" "filename\tfoo-ä.html"); QTest::newRow("greenbytes-attwithfn2231ws3") << "attachment; filename* =UTF-8''foo-%c3%a4.html" << QByteArray("type\tattachment\n" "filename\tfoo-ä.html"); // argument must not be enclosed in double quotes QTest::newRow("greenbytes-attwithfn2231quot") << "attachment; filename*=\"UTF-8''foo-%c3%a4.html\"" << QByteArray("type\tattachment"); QTest::newRow("greenbytes-attwithfn2231dpct") << "attachment; filename*=UTF-8''A-%2541.html" << QByteArray("type\tattachment\n" "filename\tA-%41.html"); #ifdef Q_OS_WIN QTest::newRow("greenbytes-attwithfn2231abspathdisguised") << "attachment; filename*=UTF-8''%5cfoo.html" << QByteArray("type\tattachment\n" "filename\tfoo.html"); #else // Q_OS_WIN QTest::newRow("greenbytes-attwithfn2231abspathdisguised") << "attachment; filename*=UTF-8''%5cfoo.html" << QByteArray("type\tattachment\n" "filename\t\\foo.html"); #endif // Q_OS_WIN QTest::newRow("greenbytes-attfncont") << "attachment; filename*0=\"foo.\"; filename*1=\"html\"" << QByteArray("type\tattachment\n" "filename\tfoo.html"); QTest::newRow("greenbytes-attfncontenc") << "attachment; filename*0*=UTF-8''foo-%c3%a4; filename*1=\".html\"" << QByteArray("type\tattachment\n" "filename\tfoo-ä.html"); // no leading zeros QTest::newRow("greenbytes-attfncontlz") << "attachment; filename*0=\"foo\"; filename*01=\"bar\"" << QByteArray("type\tattachment\n" "filename\tfoo"); QTest::newRow("greenbytes-attfncontnc") << "attachment; filename*0=\"foo\"; filename*2=\"bar\"" << QByteArray("type\tattachment\n" "filename\tfoo"); // first element must have number 0 QTest::newRow("greenbytes-attfnconts1") << "attachment; filename*1=\"foo.\"; filename*2=\"html\"" << QByteArray("type\tattachment"); // we must not rely on element ordering QTest::newRow("greenbytes-attfncontord") << "attachment; filename*1=\"bar\"; filename*0=\"foo\"" << QByteArray("type\tattachment\n" "filename\tfoobar"); // specifying both param and param* is allowed, param* should be taken QTest::newRow("greenbytes-attfnboth") << "attachment; filename=\"foo-ae.html\"; filename*=UTF-8''foo-%c3%a4.html" << QByteArray("type\tattachment\n" "filename\tfoo-ä.html"); // specifying both param and param* is allowed, param* should be taken QTest::newRow("greenbytes-attfnboth2") << "attachment; filename*=UTF-8''foo-%c3%a4.html; filename=\"foo-ae.html\"" << QByteArray("type\tattachment\n" "filename\tfoo-ä.html"); QTest::newRow("greenbytes-attnewandfn") << "attachment; foobar=x; filename=\"foo.html\"" << QByteArray("type\tattachment\n" "filename\tfoo.html\n" "foobar\tx"); // invalid argument, should be ignored QTest::newRow("greenbytes-attrfc2047token") << "attachment; filename==?ISO-8859-1?Q?foo-=E4.html?=" << QByteArray("type\tattachment"); QTest::newRow("space_before_value") << "attachment; filename= \"foo.html\"" << QByteArray("type\tattachment\n" "filename\tfoo.html"); // no character set given but 8 bit characters QTest::newRow("8bit_in_ascii") << "attachment; filename*=''foo-%c3%a4.html" << QByteArray("type\tattachment"); // there may not be gaps in numbering QTest::newRow("continuation013") << "attachment; filename*0=\"foo.\"; filename*1=\"html\"; filename*3=\"bar\"" << QByteArray("type\tattachment\n" "filename\tfoo.html"); // "wrong" element ordering and encoding QTest::newRow("reversed_continuation+encoding") << "attachment; filename*1=\"html\"; filename*0*=us-ascii''foo." << QByteArray("type\tattachment\n" "filename\tfoo.html"); // unknown charset QTest::newRow("unknown_charset") << "attachment; filename*=unknown''foo" << QByteArray("type\tattachment"); // no apostrophs QTest::newRow("encoding-no-apostrophs") << "attachment; filename*=foo" << QByteArray("type\tattachment"); // only one apostroph QTest::newRow("encoding-one-apostroph") << "attachment; filename*=us-ascii'foo" << QByteArray("type\tattachment"); // duplicate filename, both should be ignored and parsing should stop QTest::newRow("duplicate-filename") << "attachment; filename=foo; filename=bar; foo=bar" << QByteArray("type\tattachment"); // garbage after closing quote, parsing should stop there QTest::newRow("garbage_after_closing_quote") << "attachment; filename*=''foo; bar=\"f\"oo; baz=foo" << QByteArray("type\tattachment\n" "filename\tfoo"); // trailing whitespace should be ignored QTest::newRow("whitespace_after_value") << "attachment; filename=\"foo\" ; bar=baz" << QByteArray("type\tattachment\n" "filename\tfoo\n" "bar\tbaz"); // invalid syntax for type QTest::newRow("invalid_type1") << "filename=foo.html" << QByteArray(); // invalid syntax for type QTest::newRow("invalid_type2") << "inline{; filename=\"foo\"" << QByteArray(); QTest::newRow("invalid_type3") << "foo bar; filename=\"foo\"" << QByteArray(); QTest::newRow("invalid_type4") << "foo\tbar; filename=\"foo\"" << QByteArray(); // missing closing quote, so parameter is broken QTest::newRow("no_closing_quote") << "attachment; filename=\"bar" << QByteArray("type\tattachment"); // we ignore any path given in the header and use only the filename QTest::newRow("full_path_given") << "attachment; filename=\"/etc/shadow\"" << QByteArray("type\tattachment\n" "filename\tshadow"); // we ignore any path given in the header and use only the filename even if there is an error later QTest::newRow("full_path_and_parse_error") << "attachment; filename=\"/etc/shadow\"; foo=\"baz\"; foo=\"bar\"" << QByteArray("type\tattachment\n" "filename\tshadow"); // control characters are forbidden in the quoted string QTest::newRow("control_character_in_value") << "attachment; filename=\"foo\003\"" << QByteArray("type\tattachment"); // duplicate keys must be ignored QTest::newRow("duplicate_with_continuation") << "attachment; filename=\"bar\"; filename*0=\"foo.\"; filename*1=\"html\"" << QByteArray("type\tattachment"); // percent encoding, invalid first character QTest::newRow("percent-first-char-invalid") << "attachment; filename*=UTF-8''foo-%o5.html" << QByteArray("type\tattachment"); // percent encoding, invalid second character QTest::newRow("percent-second-char-invalid") << "attachment; filename*=UTF-8''foo-%5o.html" << QByteArray("type\tattachment"); // percent encoding, both characters invalid QTest::newRow("greenbytes-attwithfn2231nbadpct2") << "attachment; filename*=UTF-8''foo-%oo.html" << QByteArray("type\tattachment"); // percent encoding, invalid second character QTest::newRow("percent-second-char-missing") << "attachment; filename*=UTF-8''foo-%f.html" << QByteArray("type\tattachment"); // percent encoding, too short value QTest::newRow("percent-short-encoding-at-end") << "attachment; filename*=UTF-8''foo-%f" << QByteArray("type\tattachment"); } #if 0 // currently unclear if our behaviour is only accidentially correct // invalid syntax { "inline; attachment; filename=foo.html", "type\tinline" }, // invalid syntax { "attachment; inline; filename=foo.html", "type\tattachment" }, // deactivated for now: failing due to missing implementation { "attachment; filename=\"foo-ä.html\"", "type\tattachment\n" "filename\tfoo-ä.html" }, // deactivated for now: not the same utf, no idea if the expected value is actually correct { "attachment; filename*=UTF-8''foo-a%cc%88.html", "type\tattachment\n" "filename\tfoo-ä.html" } // deactivated for now: only working to missing implementation // string is not valid iso-8859-1 so filename should be ignored //"attachment; filename*=iso-8859-1''foo-%c3%a4-%e2%82%ac.html", //"type\tattachment", // deactivated for now: only working to missing implementation // should not be decoded //"attachment; filename=\"=?ISO-8859-1?Q?foo-=E4.html?=\"", //"type\tattachment\n" //"filename\t=?ISO-8859-1?Q?foo-=E4.html?=", }; #endif void HeaderDispositionTest::runAllTests() { QFETCH(QString, header); QFETCH(QByteArray, result); runTest(header, result); } diff --git a/autotests/http/httpheaderdispositiontest.h b/autotests/http/httpheaderdispositiontest.h index 383ccf0f..4c7b3d68 100644 --- a/autotests/http/httpheaderdispositiontest.h +++ b/autotests/http/httpheaderdispositiontest.h @@ -1,33 +1,33 @@ /* This file is part of the KDE libraries Copyright (c) 2010 Rolf Eike Beer 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. */ #ifndef HTTPHEADERDISPOSITIONTEST_H #define HTTPHEADERDISPOSITIONTEST_H -#include +#include class HeaderDispositionTest : public QObject { Q_OBJECT private Q_SLOTS: void runAllTests(); void runAllTests_data(); }; #endif //HTTPHEADERDISPOSITIONTEST_H diff --git a/autotests/http/httpheadertokenizetest.cpp b/autotests/http/httpheadertokenizetest.cpp index ba019166..e9f3831d 100644 --- a/autotests/http/httpheadertokenizetest.cpp +++ b/autotests/http/httpheadertokenizetest.cpp @@ -1,196 +1,196 @@ /* This file is part of the KDE libraries Copyright (c) 2008 Andreas Hartmetz 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 "httpheadertokenizetest.h" #include -#include -#include +#include +#include #include #include #include // QT5 TODO QTEST_GUILESS_MAIN(HeaderTokenizeTest) QTEST_MAIN(HeaderTokenizeTest) /* some possible fields that can be used for test headers {"accept-ranges", false}, {"cache-control", true}, {"connection", true}, {"content-disposition", false}, //is multi-valued in a way, but with ";" separator! {"content-encoding", true}, {"content-language", true}, {"content-length", false}, {"content-location", false}, {"content-md5", false}, {"content-type", false}, {"date", false}, {"dav", true}, //RFC 2518 {"etag", false}, {"expires", false}, {"keep-alive", false}, //RFC 2068 {"last-modified", false}, {"link", false}, //RFC 2068, multi-valued with ";" separator {"location", false}, */ //no use testing many different headers, just a couple each of the multi-valued //and the single-valued group to make sure that corner cases work both if there //are already entries for the header and if there are no entries. static const char messyHeader[] = "\n" "accept-ranges:foo\r\n" "connection: one\r\n" " t_\r\n" " wo,\r\n" "\tthree\r\n" "accept-ranges:42\n" "accept-Ranges:\tmaybe \r" " or not\n" "CoNNectIoN:four, , ,, , \r\n" " :fi:ve\r\n" ":invalid stuff\r\n" "\tinvalid: connection:close\t\r" "connection: Six, seven ,, , eight\r" //one malformed newline... "\n\r "; //two malformed newlines; end of header. also observe the trailing space. //tab separates values, newline separates header lines. the first word is the key. static const char messyResult[] = "accept-ranges\tfoo\t42\tmaybe or not\n" "connection\tone t_ wo\tthree\tfour\t:fi:ve\tSix\tseven\teight"; static const char redirectHeader[] = //"HTTP/1.1 302 Moved Temporarily\r\n" "Location: http://www.hertz.de/rentacar/index.jsp?bsc=t&targetPage=reservationOnHomepage.jsp\r\n" "Connection:close\r\n" "Cache-Control: no-cache\r\n" "Pragma: no-cache\r\n" "\r\n"; static const char redirectResult[] = "cache-control\tno-cache\n" "connection\tclose\n" "location\thttp://www.hertz.de/rentacar/index.jsp?bsc=t&targetPage=reservationOnHomepage.jsp\n" "pragma\tno-cache"; static const int bufSize = 4096; char buffer[bufSize]; void HeaderTokenizeTest::testMessyHeader() { //Copy the header into a writable buffer for (int i = 0; i < bufSize; i++) { buffer[i] = 0; } strcpy(buffer, messyHeader); HeaderTokenizer tokenizer(buffer); int tokenizeEnd = tokenizer.tokenize(0, strlen(messyHeader)); QCOMPARE(tokenizeEnd, (int)(strlen(messyHeader) - 1)); // If the output of the tokenizer contains all the terms that should be there and // exactly the number of terms that should be there then it's exactly correct. // We are lax wrt trailing whitespace, by the way. It does neither explicitly matter // nor not matter according to the standard. Internal whitespace similarly should not // matter but we have to be exact because the tokenizer does not move strings around, // it only overwrites \r and \n in case of line continuations. typedef QPair intPair; //foreach is a macro and does not like commas int nValues = 0; foreach (const QByteArray &ba, QByteArray(messyResult).split('\n')) { QList values = ba.split('\t'); QByteArray key = values.takeFirst(); nValues += values.count(); QList comparisonValues; foreach (intPair be, tokenizer.value(key).beginEnd) { comparisonValues.append(QByteArray(buffer + be.first, be.second - be.first)); } QCOMPARE(comparisonValues.count(), values.count()); for (int i = 0; i < values.count(); i++) { QVERIFY(comparisonValues[i].startsWith(values[i])); } } int nValues2 = 0; HeaderTokenizer::ConstIterator it = tokenizer.constBegin(); for (; it != tokenizer.constEnd(); ++it) { nValues2 += it.value().beginEnd.count(); } QCOMPARE(nValues2, nValues); return; //comment out for parsed header dump to stdout it = tokenizer.constBegin(); for (; it != tokenizer.constEnd(); ++it) { if (!it.value().beginEnd.isEmpty()) { qDebug() << it.key() << ":"; } foreach (intPair be, it.value().beginEnd) { qDebug() << " " << QByteArray(buffer + be.first, be.second - be.first); } } } void HeaderTokenizeTest::testRedirectHeader() { //Copy the header into a writable buffer for (int i = 0; i < bufSize; i++) { buffer[i] = 0; } strcpy(buffer, redirectHeader); HeaderTokenizer tokenizer(buffer); int tokenizeEnd = tokenizer.tokenize(0, strlen(redirectHeader)); QCOMPARE(tokenizeEnd, (int)strlen(redirectHeader)); typedef QPair intPair; int nValues = 0; foreach (const QByteArray &ba, QByteArray(redirectResult).split('\n')) { QList values = ba.split('\t'); QByteArray key = values.takeFirst(); nValues += values.count(); QList comparisonValues; foreach (intPair be, tokenizer.value(key).beginEnd) { comparisonValues.append(QByteArray(buffer + be.first, be.second - be.first)); } QCOMPARE(comparisonValues.count(), values.count()); for (int i = 0; i < values.count(); i++) { QVERIFY(comparisonValues[i].startsWith(values[i])); } } int nValues2 = 0; HeaderTokenizer::ConstIterator it = tokenizer.constBegin(); for (; it != tokenizer.constEnd(); ++it) { nValues2 += it.value().beginEnd.count(); } QCOMPARE(nValues2, nValues); // Fix compiler warning (void)contentDispositionParser; } diff --git a/autotests/http/httpheadertokenizetest.h b/autotests/http/httpheadertokenizetest.h index e8690daa..cd06d3ed 100644 --- a/autotests/http/httpheadertokenizetest.h +++ b/autotests/http/httpheadertokenizetest.h @@ -1,33 +1,33 @@ /* This file is part of the KDE libraries Copyright (c) 2008 Andreas Hartmetz 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. */ #ifndef HTTPHEADERTOKENIZETEST_H #define HTTPHEADERTOKENIZETEST_H -#include +#include class HeaderTokenizeTest : public QObject { Q_OBJECT private Q_SLOTS: void testMessyHeader(); void testRedirectHeader(); }; #endif //HTTPHEADERTOKENIZETEST_H diff --git a/autotests/http/httpobjecttest.cpp b/autotests/http/httpobjecttest.cpp index 466ae63f..c56e95c6 100644 --- a/autotests/http/httpobjecttest.cpp +++ b/autotests/http/httpobjecttest.cpp @@ -1,52 +1,52 @@ /* This file is part of the KDE libraries Copyright (C) 2012 Rolf Eike Beer 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 "httpobjecttest.h" -#include +#include -#include +#include QTEST_MAIN(HeaderObjectTest) static void runTest() { TestHTTPProtocol protocol("http", QByteArray(), "local://"); protocol.testParseContentDisposition(QStringLiteral("inline; filename=\"foo.pdf\"")); } void HeaderObjectTest::runAllTests() { runTest(); } TestHTTPProtocol::TestHTTPProtocol(const QByteArray &protocol, const QByteArray &pool, const QByteArray &app) : HTTPProtocol(protocol, pool, app) { } TestHTTPProtocol::~TestHTTPProtocol() { } void TestHTTPProtocol::testParseContentDisposition(const QString &disposition) { parseContentDisposition(disposition); } diff --git a/autotests/http/httpobjecttest.h b/autotests/http/httpobjecttest.h index 8814ab1d..1184f5b6 100644 --- a/autotests/http/httpobjecttest.h +++ b/autotests/http/httpobjecttest.h @@ -1,44 +1,44 @@ /* This file is part of the KDE libraries Copyright (c) 2012 Rolf Eike Beer 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. */ #ifndef HTTPOBJECTTEST_H #define HTTPOBJECTTEST_H -#include +#include #include class HeaderObjectTest : public QObject { Q_OBJECT private Q_SLOTS: void runAllTests(); }; class TestHTTPProtocol : public HTTPProtocol { Q_OBJECT public: TestHTTPProtocol(const QByteArray &protocol, const QByteArray &pool, const QByteArray &app); virtual ~TestHTTPProtocol(); void testParseContentDisposition(const QString &disposition); }; #endif //HTTPOBJECTTEST_H diff --git a/autotests/jobremotetest.cpp b/autotests/jobremotetest.cpp index a5981d98..881bb276 100644 --- a/autotests/jobremotetest.cpp +++ b/autotests/jobremotetest.cpp @@ -1,389 +1,389 @@ /* This file is part of the KDE project Copyright (C) 2004-2006 David Faure Copyright (C) 2008 Norbert Frese 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 #include "jobremotetest.h" #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include #include #include #include #include #include //#include "kiotesthelper.h" // createTestFile etc. QTEST_MAIN(JobRemoteTest) QDateTime s_referenceTimeStamp; // The code comes partly from jobtest.cpp static QUrl remoteTmpUrl() { QString customDir(qgetenv("KIO_JOBREMOTETEST_REMOTETMP")); if (customDir.isEmpty()) { return QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + '/'); } else { // Could be a path or a URL return QUrl::fromUserInput(customDir + '/'); } } static QString localTmpDir() { #ifdef Q_OS_WIN return QDir::tempPath() + "/jobremotetest/"; #else // This one needs to be on another partition return QStringLiteral("/tmp/jobremotetest/"); #endif } static bool myExists(const QUrl &url) { KIO::Job *job = KIO::stat(url, KIO::StatJob::DestinationSide, 0, KIO::HideProgressInfo); job->setUiDelegate(nullptr); return job->exec(); } static bool myMkdir(const QUrl &url) { KIO::Job *job = KIO::mkdir(url, -1); job->setUiDelegate(nullptr); return job->exec(); } void JobRemoteTest::initTestCase() { QStandardPaths::enableTestMode(true); // To avoid a runtime dependency on klauncher qputenv("KDE_FORK_SLAVES", "yes"); s_referenceTimeStamp = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago // Start with a clean base dir cleanupTestCase(); QUrl url = remoteTmpUrl(); if (!myExists(url)) { const bool ok = url.isLocalFile() ? QDir().mkpath(url.toLocalFile()) : myMkdir(url); if (!ok) { qFatal("couldn't create %s", qPrintable(url.toString())); } } const bool ok = QDir().mkpath(localTmpDir()); if (!ok) { qFatal("couldn't create %s", qPrintable(localTmpDir())); } } static void delDir(const QUrl &pathOrUrl) { KIO::Job *job = KIO::del(pathOrUrl, KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->exec(); } void JobRemoteTest::cleanupTestCase() { delDir(remoteTmpUrl()); delDir(QUrl::fromLocalFile(localTmpDir())); } void JobRemoteTest::enterLoop() { QEventLoop eventLoop; connect(this, SIGNAL(exitLoop()), &eventLoop, SLOT(quit())); eventLoop.exec(QEventLoop::ExcludeUserInputEvents); } ///// void JobRemoteTest::putAndGet() { QUrl u(remoteTmpUrl()); u.setPath(u.path() + "putAndGetFile"); KIO::TransferJob *job = KIO::put(u, 0600, KIO::Overwrite | KIO::HideProgressInfo); QDateTime mtime = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago mtime.setTime_t(mtime.toTime_t()); // hack for losing the milliseconds job->setModificationTime(mtime); job->setUiDelegate(nullptr); connect(job, SIGNAL(result(KJob*)), this, SLOT(slotResult(KJob*))); connect(job, SIGNAL(dataReq(KIO::Job*,QByteArray&)), this, SLOT(slotDataReq(KIO::Job*,QByteArray&))); m_result = -1; m_dataReqCount = 0; enterLoop(); QVERIFY(m_result == 0); // no error m_result = -1; KIO::StoredTransferJob *getJob = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo); getJob->setUiDelegate(nullptr); connect(getJob, SIGNAL(result(KJob*)), this, SLOT(slotGetResult(KJob*))); enterLoop(); QCOMPARE(m_result, 0); // no error QCOMPARE(m_data, QByteArray("This is a test for KIO::put()\n")); //QCOMPARE( m_data.size(), 11 ); } void JobRemoteTest::slotGetResult(KJob *job) { m_result = job->error(); m_data = static_cast(job)->data(); emit exitLoop(); } void JobRemoteTest::slotDataReq(KIO::Job *, QByteArray &data) { // Really not the way you'd write a slotDataReq usually :) switch (m_dataReqCount++) { case 0: data = "This is a test for "; break; case 1: data = "KIO::put()\n"; break; case 2: data = QByteArray(); break; } } void JobRemoteTest::slotResult(KJob *job) { m_result = job->error(); emit exitLoop(); } //// void JobRemoteTest::openFileWriting() { m_rwCount = 0; QUrl u(remoteTmpUrl()); u.setPath(u.path() + "openFileWriting"); fileJob = KIO::open(u, QIODevice::WriteOnly); fileJob->setUiDelegate(nullptr); connect(fileJob, SIGNAL(result(KJob*)), this, SLOT(slotResult(KJob*))); connect(fileJob, SIGNAL(data(KIO::Job*,QByteArray)), this, SLOT(slotFileJobData(KIO::Job*,QByteArray))); connect(fileJob, SIGNAL(open(KIO::Job*)), this, SLOT(slotFileJobOpen(KIO::Job*))); connect(fileJob, SIGNAL(written(KIO::Job*,KIO::filesize_t)), this, SLOT(slotFileJobWritten(KIO::Job*,KIO::filesize_t))); connect(fileJob, SIGNAL(position(KIO::Job*,KIO::filesize_t)), this, SLOT(slotFileJobPosition(KIO::Job*,KIO::filesize_t))); connect(fileJob, SIGNAL(close(KIO::Job*)), this, SLOT(slotFileJobClose(KIO::Job*))); m_result = -1; enterLoop(); QEXPECT_FAIL("", "Needs fixing in kio_file", Abort); QVERIFY(m_result == 0); // no error KIO::StoredTransferJob *getJob = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo); getJob->setUiDelegate(nullptr); connect(getJob, SIGNAL(result(KJob*)), this, SLOT(slotGetResult(KJob*))); enterLoop(); QCOMPARE(m_result, 0); // no error qDebug() << "m_data: " << m_data; QCOMPARE(m_data, QByteArray("test....test....test....test....test....test....end")); } void JobRemoteTest::slotFileJobData(KIO::Job *job, const QByteArray &data) { Q_UNUSED(job); Q_UNUSED(data); } void JobRemoteTest::slotFileJobRedirection(KIO::Job *job, const QUrl &url) { Q_UNUSED(job); Q_UNUSED(url); } void JobRemoteTest::slotFileJobMimetype(KIO::Job *job, const QString &type) { Q_UNUSED(job); Q_UNUSED(type); } void JobRemoteTest::slotFileJobOpen(KIO::Job *job) { Q_UNUSED(job); fileJob->seek(0); } void JobRemoteTest::slotFileJobWritten(KIO::Job *job, KIO::filesize_t written) { Q_UNUSED(job); Q_UNUSED(written); if (m_rwCount > 5) { fileJob->close(); } else { fileJob->seek(m_rwCount * 8); m_rwCount++; } } void JobRemoteTest::slotFileJobPosition(KIO::Job *job, KIO::filesize_t offset) { Q_UNUSED(job); Q_UNUSED(offset); const QByteArray data("test....end"); fileJob->write(data); } void JobRemoteTest::slotFileJobClose(KIO::Job *job) { Q_UNUSED(job); qDebug() << "+++++++++ closed"; } //// void JobRemoteTest::openFileReading() { QUrl u(remoteTmpUrl()); u.setPath(u.path() + "openFileReading"); const QByteArray putData("test1test2test3test4test5"); KIO::StoredTransferJob *putJob = KIO::storedPut(putData, u, 0600, KIO::Overwrite | KIO::HideProgressInfo ); QDateTime mtime = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago mtime.setTime_t(mtime.toTime_t()); // hack for losing the milliseconds putJob->setModificationTime(mtime); putJob->setUiDelegate(nullptr); connect(putJob, SIGNAL(result(KJob*)), this, SLOT(slotResult(KJob*))); m_result = -1; enterLoop(); QVERIFY(m_result == 0); // no error m_rwCount = 4; m_data = QByteArray(); fileJob = KIO::open(u, QIODevice::ReadOnly); fileJob->setUiDelegate(nullptr); connect(fileJob, SIGNAL(result(KJob*)), this, SLOT(slotResult(KJob*))); connect(fileJob, SIGNAL(data(KIO::Job*,QByteArray)), this, SLOT(slotFileJob2Data(KIO::Job*,QByteArray))); connect(fileJob, SIGNAL(open(KIO::Job*)), this, SLOT(slotFileJob2Open(KIO::Job*))); connect(fileJob, SIGNAL(written(KIO::Job*,KIO::filesize_t)), this, SLOT(slotFileJob2Written(KIO::Job*,KIO::filesize_t))); connect(fileJob, SIGNAL(position(KIO::Job*,KIO::filesize_t)), this, SLOT(slotFileJob2Position(KIO::Job*,KIO::filesize_t))); connect(fileJob, SIGNAL(close(KIO::Job*)), this, SLOT(slotFileJob2Close(KIO::Job*))); m_result = -1; enterLoop(); QVERIFY(m_result == 0); // no error qDebug() << "resulting m_data: " << QString(m_data); QCOMPARE(m_data, QByteArray("test5test4test3test2test1")); } void JobRemoteTest::slotFileJob2Data(KIO::Job *job, const QByteArray &data) { Q_UNUSED(job); qDebug() << "m_rwCount = " << m_rwCount << " data: " << data; m_data.append(data); if (m_rwCount < 0) { fileJob->close(); } else { fileJob->seek(m_rwCount-- * 5); } } void JobRemoteTest::slotFileJob2Redirection(KIO::Job *job, const QUrl &url) { Q_UNUSED(job); Q_UNUSED(url); } void JobRemoteTest::slotFileJob2Mimetype(KIO::Job *job, const QString &type) { Q_UNUSED(job); qDebug() << "mimetype: " << type; } void JobRemoteTest::slotFileJob2Open(KIO::Job *job) { Q_UNUSED(job); fileJob->seek(m_rwCount-- * 5); } void JobRemoteTest::slotFileJob2Written(KIO::Job *job, KIO::filesize_t written) { Q_UNUSED(job); Q_UNUSED(written); } void JobRemoteTest::slotFileJob2Position(KIO::Job *job, KIO::filesize_t offset) { Q_UNUSED(job); qDebug() << "position : " << offset << " -> read (5)"; fileJob->read(5); } void JobRemoteTest::slotFileJob2Close(KIO::Job *job) { Q_UNUSED(job); qDebug() << "+++++++++ job2 closed"; } //// void JobRemoteTest::slotMimetype(KIO::Job *job, const QString &type) { QVERIFY(job != nullptr); m_mimetype = type; } diff --git a/autotests/jobremotetest.h b/autotests/jobremotetest.h index e3374dd4..5e99a100 100644 --- a/autotests/jobremotetest.h +++ b/autotests/jobremotetest.h @@ -1,92 +1,92 @@ /* This file is part of the KDE project Copyright (C) 2004 David Faure Copyright (C) 2008 Norbert Frese 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. */ /* Please set KIO_JOBREMOTETEST_REMOTETMP to test other protocols than kio_file. Don't forget the trailing slash! */ #ifndef JOBTEST_H #define JOBTEST_H -#include -#include +#include +#include #include class JobRemoteTest : public QObject { Q_OBJECT public: JobRemoteTest() {} private Q_SLOTS: void initTestCase(); void cleanupTestCase(); // Remote tests void putAndGet(); void openFileWriting(); void openFileReading(); //void calculateRemainingSeconds(); Q_SIGNALS: void exitLoop(); protected Q_SLOTS: //void slotEntries( KIO::Job*, const KIO::UDSEntryList& lst ); void slotGetResult(KJob *); void slotDataReq(KIO::Job *, QByteArray &); void slotResult(KJob *); void slotMimetype(KIO::Job *, const QString &); void slotFileJobData(KIO::Job *job, const QByteArray &data); void slotFileJobRedirection(KIO::Job *job, const QUrl &url); void slotFileJobMimetype(KIO::Job *job, const QString &type); void slotFileJobOpen(KIO::Job *job); void slotFileJobWritten(KIO::Job *job, KIO::filesize_t written); void slotFileJobPosition(KIO::Job *job, KIO::filesize_t offset); void slotFileJobClose(KIO::Job *job); void slotFileJob2Data(KIO::Job *job, const QByteArray &data); void slotFileJob2Redirection(KIO::Job *job, const QUrl &url); void slotFileJob2Mimetype(KIO::Job *job, const QString &type); void slotFileJob2Open(KIO::Job *job); void slotFileJob2Written(KIO::Job *job, KIO::filesize_t written); void slotFileJob2Position(KIO::Job *job, KIO::filesize_t offset); void slotFileJob2Close(KIO::Job *job); private: void enterLoop(); enum { AlreadyExists = 1 }; int m_result; QByteArray m_data; QStringList m_names; int m_dataReqCount; QString m_mimetype; // openReadWrite test KIO::FileJob *fileJob; int m_rwCount; }; #endif diff --git a/autotests/jobtest.cpp b/autotests/jobtest.cpp index 445e6470..17a07a9e 100644 --- a/autotests/jobtest.cpp +++ b/autotests/jobtest.cpp @@ -1,1975 +1,1975 @@ /* This file is part of the KDE project Copyright (C) 2004-2006 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 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 #include "jobtest.h" #include #include #include #include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include #include #include #include #include #include #include #include "kiotesthelper.h" // createTestFile etc. #ifndef Q_OS_WIN #include // for readlink #endif QTEST_MAIN(JobTest) // The code comes partly from kdebase/kioslave/trash/testtrash.cpp static QString otherTmpDir() { #ifdef Q_OS_WIN return QDir::tempPath() + "/jobtest/"; #else // This one needs to be on another partition return QStringLiteral("/tmp/jobtest/"); #endif } #if 0 static QUrl systemTmpDir() { #ifdef Q_OS_WIN return QUrl("system:" + QDir::homePath() + "/.kde-unit-test/jobtest-system/"); #else return QUrl("system:/home/.kde-unit-test/jobtest-system/"); #endif } static QString realSystemPath() { return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/jobtest-system/"; } #endif void JobTest::initTestCase() { QStandardPaths::enableTestMode(true); QCoreApplication::instance()->setApplicationName("kio/jobtest"); // testing for #357499 // To avoid a runtime dependency on klauncher qputenv("KDE_FORK_SLAVES", "yes"); s_referenceTimeStamp = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago // Start with a clean base dir cleanupTestCase(); homeTmpDir(); // create it if (!QFile::exists(otherTmpDir())) { bool ok = QDir().mkdir(otherTmpDir()); if (!ok) { qFatal("couldn't create %s", qPrintable(otherTmpDir())); } } #if 0 if (KProtocolInfo::isKnownProtocol("system")) { if (!QFile::exists(realSystemPath())) { bool ok = dir.mkdir(realSystemPath()); if (!ok) { qFatal("couldn't create %s", qPrintable(realSystemPath())); } } } #endif qRegisterMetaType("KJob*"); qRegisterMetaType("KIO::Job*"); qRegisterMetaType("QDateTime"); } void JobTest::cleanupTestCase() { QDir(homeTmpDir()).removeRecursively(); QDir(otherTmpDir()).removeRecursively(); #if 0 if (KProtocolInfo::isKnownProtocol("system")) { delDir(systemTmpDir()); } #endif } void JobTest::enterLoop() { QEventLoop eventLoop; connect(this, SIGNAL(exitLoop()), &eventLoop, SLOT(quit())); eventLoop.exec(QEventLoop::ExcludeUserInputEvents); } void JobTest::storedGet() { qDebug(); const QString filePath = homeTmpDir() + "fileFromHome"; createTestFile(filePath); QUrl u = QUrl::fromLocalFile(filePath); m_result = -1; KIO::StoredTransferJob *job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo); QSignalSpy spyPercent(job, SIGNAL(percent(KJob*,ulong))); QVERIFY(spyPercent.isValid()); job->setUiDelegate(nullptr); connect(job, SIGNAL(result(KJob*)), this, SLOT(slotGetResult(KJob*))); enterLoop(); QCOMPARE(m_result, 0); // no error QCOMPARE(m_data, QByteArray("Hello\0world", 11)); QCOMPARE(m_data.size(), 11); QVERIFY(!spyPercent.isEmpty()); } void JobTest::slotGetResult(KJob *job) { m_result = job->error(); m_data = static_cast(job)->data(); emit exitLoop(); } void JobTest::put() { const QString filePath = homeTmpDir() + "fileFromHome"; QUrl u = QUrl::fromLocalFile(filePath); KIO::TransferJob *job = KIO::put(u, 0600, KIO::Overwrite | KIO::HideProgressInfo); QDateTime mtime = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago mtime.setTime_t(mtime.toTime_t()); // hack for losing the milliseconds job->setModificationTime(mtime); job->setUiDelegate(nullptr); connect(job, SIGNAL(result(KJob*)), this, SLOT(slotResult(KJob*))); connect(job, SIGNAL(dataReq(KIO::Job*,QByteArray&)), this, SLOT(slotDataReq(KIO::Job*,QByteArray&))); m_result = -1; m_dataReqCount = 0; enterLoop(); QVERIFY(m_result == 0); // no error QFileInfo fileInfo(filePath); QVERIFY(fileInfo.exists()); QCOMPARE(fileInfo.size(), 30LL); // "This is a test for KIO::put()\n" QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser)); QCOMPARE(fileInfo.lastModified(), mtime); } void JobTest::slotDataReq(KIO::Job *, QByteArray &data) { // Really not the way you'd write a slotDataReq usually :) switch (m_dataReqCount++) { case 0: data = "This is a test for "; break; case 1: data = "KIO::put()\n"; break; case 2: data = QByteArray(); break; } } void JobTest::slotResult(KJob *job) { m_result = job->error(); emit exitLoop(); } void JobTest::storedPut() { const QString filePath = homeTmpDir() + "fileFromHome"; QUrl u = QUrl::fromLocalFile(filePath); QByteArray putData = "This is the put data"; KIO::TransferJob *job = KIO::storedPut(putData, u, 0600, KIO::Overwrite | KIO::HideProgressInfo); QSignalSpy spyPercent(job, SIGNAL(percent(KJob*,ulong))); QVERIFY(spyPercent.isValid()); QDateTime mtime = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago mtime.setTime_t(mtime.toTime_t()); // hack for losing the milliseconds job->setModificationTime(mtime); job->setUiDelegate(nullptr); QVERIFY(job->exec()); QFileInfo fileInfo(filePath); QVERIFY(fileInfo.exists()); QCOMPARE(fileInfo.size(), (long long)putData.size()); QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser)); QCOMPARE(fileInfo.lastModified(), mtime); QVERIFY(!spyPercent.isEmpty()); } void JobTest::storedPutIODevice() { const QString filePath = homeTmpDir() + "fileFromHome"; QBuffer putData; putData.setData("This is the put data"); QVERIFY(putData.open(QIODevice::ReadOnly)); KIO::TransferJob *job = KIO::storedPut(&putData, QUrl::fromLocalFile(filePath), 0600, KIO::Overwrite | KIO::HideProgressInfo); QSignalSpy spyPercent(job, SIGNAL(percent(KJob*,ulong))); QVERIFY(spyPercent.isValid()); QDateTime mtime = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago mtime.setTime_t(mtime.toTime_t()); // hack for losing the milliseconds job->setModificationTime(mtime); job->setUiDelegate(nullptr); QVERIFY(job->exec()); QFileInfo fileInfo(filePath); QVERIFY(fileInfo.exists()); QCOMPARE(fileInfo.size(), (long long)putData.size()); QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser)); QCOMPARE(fileInfo.lastModified(), mtime); QVERIFY(!spyPercent.isEmpty()); } void JobTest::storedPutIODeviceFile() { // Given a source file and a destination file const QString src = homeTmpDir() + "fileFromHome"; createTestFile(src); QVERIFY(QFile::exists(src)); QFile srcFile(src); QVERIFY(srcFile.open(QIODevice::ReadOnly)); const QString dest = homeTmpDir() + "fileFromHome_copied"; QFile::remove(dest); const QUrl destUrl = QUrl::fromLocalFile(dest); // When using storedPut with the QFile as argument KIO::StoredTransferJob *job = KIO::storedPut(&srcFile, destUrl, 0600, KIO::Overwrite | KIO::HideProgressInfo); // Then the copy should succeed and the dest file exist QVERIFY2(job->exec(), qPrintable(job->errorString())); QVERIFY(QFile::exists(dest)); QCOMPARE(QFileInfo(src).size(), QFileInfo(dest).size()); QFile::remove(dest); } void JobTest::storedPutIODeviceTempFile() { // Create a temp file in the current dir. QTemporaryFile tempFile(QStringLiteral("jobtest-tmp")); QVERIFY(tempFile.open()); // Write something into the file. QTextStream stream(&tempFile); stream << QStringLiteral("This is the put data"); stream.flush(); QVERIFY(QFileInfo(tempFile).size() > 0); const QString dest = homeTmpDir() + QLatin1String("tmpfile-dest"); const QUrl destUrl = QUrl::fromLocalFile(dest); // QTemporaryFiles are open in ReadWrite mode, // so we don't need to close and reopen, // but we need to rewind to the beginning. tempFile.seek(0); auto job = KIO::storedPut(&tempFile, destUrl, -1); QVERIFY2(job->exec(), qPrintable(job->errorString())); QVERIFY(QFileInfo::exists(dest)); QCOMPARE(QFileInfo(dest).size(), QFileInfo(tempFile).size()); QVERIFY(QFile::remove(dest)); } void JobTest::storedPutIODeviceFastDevice() { const QString filePath = homeTmpDir() + "fileFromHome"; const QUrl u = QUrl::fromLocalFile(filePath); const QByteArray putDataContents = "This is the put data"; QBuffer putDataBuffer; QVERIFY(putDataBuffer.open(QIODevice::ReadWrite)); KIO::StoredTransferJob *job = KIO::storedPut(&putDataBuffer, u, 0600, KIO::Overwrite | KIO::HideProgressInfo); QSignalSpy spyPercent(job, SIGNAL(percent(KJob*,ulong))); QVERIFY(spyPercent.isValid()); QDateTime mtime = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago mtime.setTime_t(mtime.toTime_t()); // hack for losing the milliseconds job->setModificationTime(mtime); job->setTotalSize(putDataContents.size()); job->setUiDelegate(nullptr); job->setAsyncDataEnabled(true); // Emit the readChannelFinished even before the job has had time to start const auto pos = putDataBuffer.pos(); int size = putDataBuffer.write(putDataContents); putDataBuffer.seek(pos); putDataBuffer.readChannelFinished(); QVERIFY(job->exec()); QCOMPARE(size, putDataContents.size()); QCOMPARE(putDataBuffer.bytesAvailable(), 0); QFileInfo fileInfo(filePath); QVERIFY(fileInfo.exists()); QCOMPARE(fileInfo.size(), (long long)putDataContents.size()); QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser)); QCOMPARE(fileInfo.lastModified(), mtime); QVERIFY(!spyPercent.isEmpty()); } void JobTest::storedPutIODeviceSlowDevice() { const QString filePath = homeTmpDir() + "fileFromHome"; const QUrl u = QUrl::fromLocalFile(filePath); const QByteArray putDataContents = "This is the put data"; QBuffer putDataBuffer; QVERIFY(putDataBuffer.open(QIODevice::ReadWrite)); KIO::StoredTransferJob *job = KIO::storedPut(&putDataBuffer, u, 0600, KIO::Overwrite | KIO::HideProgressInfo); QSignalSpy spyPercent(job, SIGNAL(percent(KJob*,ulong))); QVERIFY(spyPercent.isValid()); QDateTime mtime = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago mtime.setTime_t(mtime.toTime_t()); // hack for losing the milliseconds job->setModificationTime(mtime); job->setTotalSize(putDataContents.size()); job->setUiDelegate(nullptr); job->setAsyncDataEnabled(true); int size = 0; const auto writeOnce = [&putDataBuffer, &size, putDataContents]() { const auto pos = putDataBuffer.pos(); size += putDataBuffer.write(putDataContents); putDataBuffer.seek(pos); // qDebug() << "written" << size; }; QTimer::singleShot(200, this, writeOnce); QTimer::singleShot(400, this, writeOnce); // Simulate the transfer is done QTimer::singleShot(450, this, [&putDataBuffer](){ putDataBuffer.readChannelFinished(); }); QVERIFY(job->exec()); QCOMPARE(size, putDataContents.size() * 2); QCOMPARE(putDataBuffer.bytesAvailable(), 0); QFileInfo fileInfo(filePath); QVERIFY(fileInfo.exists()); QCOMPARE(fileInfo.size(), (long long)putDataContents.size() * 2); QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser)); QCOMPARE(fileInfo.lastModified(), mtime); QVERIFY(!spyPercent.isEmpty()); } void JobTest::storedPutIODeviceSlowDeviceBigChunk() { const QString filePath = homeTmpDir() + "fileFromHome"; const QUrl u = QUrl::fromLocalFile(filePath); const QByteArray putDataContents(300000, 'K'); // Make sure the 300000 is bigger than MAX_READ_BUF_SIZE QBuffer putDataBuffer; QVERIFY(putDataBuffer.open(QIODevice::ReadWrite)); KIO::StoredTransferJob *job = KIO::storedPut(&putDataBuffer, u, 0600, KIO::Overwrite | KIO::HideProgressInfo); QSignalSpy spyPercent(job, SIGNAL(percent(KJob*,ulong))); QVERIFY(spyPercent.isValid()); QDateTime mtime = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago mtime.setTime_t(mtime.toTime_t()); // hack for losing the milliseconds job->setModificationTime(mtime); job->setTotalSize(putDataContents.size()); job->setUiDelegate(nullptr); job->setAsyncDataEnabled(true); int size = 0; const auto writeOnce = [&putDataBuffer, &size, putDataContents]() { const auto pos = putDataBuffer.pos(); size += putDataBuffer.write(putDataContents); putDataBuffer.seek(pos); // qDebug() << "written" << size; }; QTimer::singleShot(200, this, writeOnce); // Simulate the transfer is done QTimer::singleShot(450, this, [&putDataBuffer](){ putDataBuffer.readChannelFinished(); }); QVERIFY(job->exec()); QCOMPARE(size, putDataContents.size()); QCOMPARE(putDataBuffer.bytesAvailable(), 0); QFileInfo fileInfo(filePath); QVERIFY(fileInfo.exists()); QCOMPARE(fileInfo.size(), (long long)putDataContents.size()); QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser)); QCOMPARE(fileInfo.lastModified(), mtime); QVERIFY(!spyPercent.isEmpty()); } //// void JobTest::copyLocalFile(const QString &src, const QString &dest) { const QUrl u = QUrl::fromLocalFile(src); const QUrl d = QUrl::fromLocalFile(dest); const int perms = 0666; // copy the file with file_copy KIO::Job *job = KIO::file_copy(u, d, perms, KIO::HideProgressInfo); job->setUiDelegate(nullptr); bool ok = job->exec(); QVERIFY(ok); QVERIFY(QFile::exists(dest)); QVERIFY(QFile::exists(src)); // still there QCOMPARE(int(QFileInfo(dest).permissions()), int(0x6666)); { // check that the timestamp is the same (#24443) // Note: this only works because of copy() in kio_file. // The datapump solution ignores mtime, the app has to call FileCopyJob::setModificationTime() QFileInfo srcInfo(src); QFileInfo destInfo(dest); #ifdef Q_OS_WIN // win32 time may differs in msec part QCOMPARE(srcInfo.lastModified().toString("dd.MM.yyyy hh:mm"), destInfo.lastModified().toString("dd.MM.yyyy hh:mm")); #else QCOMPARE(srcInfo.lastModified(), destInfo.lastModified()); #endif } // cleanup and retry with KIO::copy() QFile::remove(dest); job = KIO::copy(u, d, KIO::HideProgressInfo); QSignalSpy spyCopyingDone(job, SIGNAL(copyingDone(KIO::Job*,QUrl,QUrl,QDateTime,bool,bool))); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); ok = job->exec(); QVERIFY(ok); QVERIFY(QFile::exists(dest)); QVERIFY(QFile::exists(src)); // still there { // check that the timestamp is the same (#24443) QFileInfo srcInfo(src); QFileInfo destInfo(dest); #ifdef Q_OS_WIN // win32 time may differs in msec part QCOMPARE(srcInfo.lastModified().toString("dd.MM.yyyy hh:mm"), destInfo.lastModified().toString("dd.MM.yyyy hh:mm")); #else QCOMPARE(srcInfo.lastModified(), destInfo.lastModified()); #endif } QCOMPARE(spyCopyingDone.count(), 1); // cleanup and retry with KIO::copyAs() QFile::remove(dest); job = KIO::copyAs(u, d, KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); QVERIFY(job->exec()); QVERIFY(QFile::exists(dest)); QVERIFY(QFile::exists(src)); // still there // Do it again, with Overwrite. job = KIO::copyAs(u, d, KIO::Overwrite | KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); QVERIFY(job->exec()); QVERIFY(QFile::exists(dest)); QVERIFY(QFile::exists(src)); // still there // Do it again, without Overwrite (should fail). job = KIO::copyAs(u, d, KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); QVERIFY(!job->exec()); // Clean up QFile::remove(dest); } void JobTest::copyLocalDirectory(const QString &src, const QString &_dest, int flags) { QVERIFY(QFileInfo(src).isDir()); QVERIFY(QFileInfo(src + "/testfile").isFile()); QUrl u = QUrl::fromLocalFile(src); QString dest(_dest); QUrl d = QUrl::fromLocalFile(dest); if (flags & AlreadyExists) { QVERIFY(QFile::exists(dest)); } else { QVERIFY(!QFile::exists(dest)); } KIO::Job *job = KIO::copy(u, d, KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); bool ok = job->exec(); QVERIFY(ok); QVERIFY(QFile::exists(dest)); QVERIFY(QFileInfo(dest).isDir()); QVERIFY(QFileInfo(dest + "/testfile").isFile()); QVERIFY(QFile::exists(src)); // still there if (flags & AlreadyExists) { dest += '/' + u.fileName(); //qDebug() << "Expecting dest=" << dest; } // CopyJob::setNextDirAttribute isn't implemented for Windows currently. #ifndef Q_OS_WIN { // Check that the timestamp is the same (#24443) QFileInfo srcInfo(src); QFileInfo destInfo(dest); QCOMPARE(srcInfo.lastModified(), destInfo.lastModified()); } #endif // Do it again, with Overwrite. // Use copyAs, we don't want a subdir inside d. job = KIO::copyAs(u, d, KIO::HideProgressInfo | KIO::Overwrite); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); ok = job->exec(); QVERIFY(ok); // Do it again, without Overwrite (should fail). job = KIO::copyAs(u, d, KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); ok = job->exec(); QVERIFY(!ok); } #ifndef Q_OS_WIN static QString linkTarget(const QString &path) { // Use readlink on Unix because symLinkTarget turns relative targets into absolute (#352927) char linkTargetBuffer[4096]; const int n = readlink(QFile::encodeName(path).constData(), linkTargetBuffer, sizeof(linkTargetBuffer) - 1); if (n != -1) { linkTargetBuffer[n] = 0; } return QFile::decodeName(linkTargetBuffer); } static void copyLocalSymlink(const QString &src, const QString &dest, const QString &expectedLinkTarget) { QT_STATBUF buf; QVERIFY(QT_LSTAT(QFile::encodeName(src).constData(), &buf) == 0); QUrl u = QUrl::fromLocalFile(src); QUrl d = QUrl::fromLocalFile(dest); // copy the symlink KIO::Job *job = KIO::copy(u, d, KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); QVERIFY2(job->exec(), qPrintable(job->error())); QVERIFY(QT_LSTAT(QFile::encodeName(dest).constData(), &buf) == 0); // dest exists QCOMPARE(linkTarget(dest), expectedLinkTarget); // cleanup QFile::remove(dest); } #endif void JobTest::copyFileToSamePartition() { const QString filePath = homeTmpDir() + "fileFromHome"; const QString dest = homeTmpDir() + "fileFromHome_copied"; createTestFile(filePath); copyLocalFile(filePath, dest); } void JobTest::copyDirectoryToSamePartition() { qDebug(); const QString src = homeTmpDir() + "dirFromHome"; const QString dest = homeTmpDir() + "dirFromHome_copied"; createTestDirectory(src); copyLocalDirectory(src, dest); } void JobTest::copyDirectoryToExistingDirectory() { qDebug(); // just the same as copyDirectoryToSamePartition, but this time dest exists. // So we get a subdir, "dirFromHome_copy/dirFromHome" const QString src = homeTmpDir() + "dirFromHome"; const QString dest = homeTmpDir() + "dirFromHome_copied"; createTestDirectory(src); createTestDirectory(dest); copyLocalDirectory(src, dest, AlreadyExists); } void JobTest::copyFileToOtherPartition() { qDebug(); const QString filePath = homeTmpDir() + "fileFromHome"; const QString dest = otherTmpDir() + "fileFromHome_copied"; createTestFile(filePath); copyLocalFile(filePath, dest); } void JobTest::copyDirectoryToOtherPartition() { qDebug(); const QString src = homeTmpDir() + "dirFromHome"; const QString dest = otherTmpDir() + "dirFromHome_copied"; createTestDirectory(src); copyLocalDirectory(src, dest); } void JobTest::copyRelativeSymlinkToSamePartition() // #352927 { #ifdef Q_OS_WIN QSKIP("Skipping symlink test on Windows"); #else const QString filePath = homeTmpDir() + "testlink"; const QString dest = homeTmpDir() + "testlink_copied"; createTestSymlink(filePath, "relative"); copyLocalSymlink(filePath, dest, QStringLiteral("relative")); QFile::remove(filePath); #endif } void JobTest::copyAbsoluteSymlinkToOtherPartition() { #ifdef Q_OS_WIN QSKIP("Skipping symlink test on Windows"); #else const QString filePath = homeTmpDir() + "testlink"; const QString dest = otherTmpDir() + "testlink_copied"; createTestSymlink(filePath, QFile::encodeName(homeTmpDir())); copyLocalSymlink(filePath, dest, homeTmpDir()); QFile::remove(filePath); #endif } void JobTest::copyFolderWithUnaccessibleSubfolder() { #ifdef Q_OS_WIN QSKIP("Skipping unaccessible folder test on Windows, cannot remove all permissions from a folder"); #endif const QString src_dir = homeTmpDir() + "srcHome"; const QString dst_dir = homeTmpDir() + "dstHome"; QDir().remove(src_dir); QDir().remove(dst_dir); createTestDirectory(src_dir); createTestDirectory(src_dir + "/folder1"); QString inaccessible = src_dir + "/folder1/inaccessible"; createTestDirectory(inaccessible); QFile(inaccessible).setPermissions(QFile::Permissions()); // Make it inaccessible //Copying should throw some warnings, as it cannot access some folders KIO::CopyJob *job = KIO::copy(QUrl::fromLocalFile(src_dir), QUrl::fromLocalFile(dst_dir), KIO::HideProgressInfo); QSignalSpy spy(job, SIGNAL(warning(KJob*,QString,QString))); job->setUiDelegate(nullptr); // no skip dialog, thanks QVERIFY(job->exec()); QFile(inaccessible).setPermissions(QFile::Permissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)); KIO::DeleteJob *deljob1 = KIO::del(QUrl::fromLocalFile(src_dir), KIO::HideProgressInfo); deljob1->setUiDelegate(nullptr); // no skip dialog, thanks QVERIFY(deljob1->exec()); KIO::DeleteJob *deljob2 = KIO::del(QUrl::fromLocalFile(dst_dir), KIO::HideProgressInfo); deljob2->setUiDelegate(nullptr); // no skip dialog, thanks QVERIFY(deljob2->exec()); QCOMPARE(spy.count(), 1); // one warning should be emitted by the copy job } void JobTest::copyDataUrl() { // GIVEN const QString dst_dir = homeTmpDir(); QVERIFY(!QFileInfo::exists(dst_dir + "/data")); // WHEN KIO::CopyJob *job = KIO::copy(QUrl("data:,Hello%2C%20World!"), QUrl::fromLocalFile(dst_dir), KIO::HideProgressInfo); QVERIFY(job->exec()); // THEN QVERIFY(QFileInfo(dst_dir + "/data").isFile()); QFile::remove(dst_dir + "/data"); } void JobTest::suspendFileCopy() { const QString filePath = homeTmpDir() + "fileFromHome"; const QString dest = homeTmpDir() + "fileFromHome_copied"; createTestFile(filePath); const QUrl u = QUrl::fromLocalFile(filePath); const QUrl d = QUrl::fromLocalFile(dest); KIO::Job *job = KIO::file_copy(u, d, KIO::HideProgressInfo); QSignalSpy spyResult(job, SIGNAL(result(KJob*))); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); QVERIFY(job->suspend()); QVERIFY(!spyResult.wait(300)); QVERIFY(job->resume()); QVERIFY(job->exec()); QVERIFY(QFile::exists(dest)); QFile::remove(dest); } void JobTest::suspendCopy() { const QString filePath = homeTmpDir() + "fileFromHome"; const QString dest = homeTmpDir() + "fileFromHome_copied"; createTestFile(filePath); const QUrl u = QUrl::fromLocalFile(filePath); const QUrl d = QUrl::fromLocalFile(dest); KIO::Job *job = KIO::copy(u, d, KIO::HideProgressInfo); QSignalSpy spyResult(job, SIGNAL(result(KJob*))); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); QVERIFY(job->suspend()); QVERIFY(!spyResult.wait(300)); QVERIFY(job->resume()); QVERIFY(job->exec()); QVERIFY(QFile::exists(dest)); QFile::remove(dest); } void JobTest::moveLocalFile(const QString &src, const QString &dest) { QVERIFY(QFile::exists(src)); QUrl u = QUrl::fromLocalFile(src); QUrl d = QUrl::fromLocalFile(dest); // move the file with file_move KIO::Job *job = KIO::file_move(u, d, 0666, KIO::HideProgressInfo); job->setUiDelegate(nullptr); bool ok = job->exec(); QVERIFY(ok); QVERIFY(QFile::exists(dest)); QVERIFY(!QFile::exists(src)); // not there anymore QCOMPARE(int(QFileInfo(dest).permissions()), int(0x6666)); // move it back with KIO::move() job = KIO::move(d, u, KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); ok = job->exec(); QVERIFY(ok); QVERIFY(!QFile::exists(dest)); QVERIFY(QFile::exists(src)); // it's back } static void moveLocalSymlink(const QString &src, const QString &dest) { QT_STATBUF buf; QVERIFY(QT_LSTAT(QFile::encodeName(src).constData(), &buf) == 0); QUrl u = QUrl::fromLocalFile(src); QUrl d = QUrl::fromLocalFile(dest); // move the symlink with move, NOT with file_move KIO::Job *job = KIO::move(u, d, KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); bool ok = job->exec(); if (!ok) { qWarning() << job->error(); } QVERIFY(ok); QVERIFY(QT_LSTAT(QFile::encodeName(dest).constData(), &buf) == 0); QVERIFY(!QFile::exists(src)); // not there anymore // move it back with KIO::move() job = KIO::move(d, u, KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); ok = job->exec(); QVERIFY(ok); QVERIFY(QT_LSTAT(QFile::encodeName(dest).constData(), &buf) != 0); // doesn't exist anymore QVERIFY(QT_LSTAT(QFile::encodeName(src).constData(), &buf) == 0); // it's back } void JobTest::moveLocalDirectory(const QString &src, const QString &dest) { qDebug() << src << " " << dest; QVERIFY(QFile::exists(src)); QVERIFY(QFileInfo(src).isDir()); QVERIFY(QFileInfo(src + "/testfile").isFile()); #ifndef Q_OS_WIN QVERIFY(QFileInfo(src + "/testlink").isSymLink()); #endif QUrl u = QUrl::fromLocalFile(src); QUrl d = QUrl::fromLocalFile(dest); KIO::Job *job = KIO::move(u, d, KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); bool ok = job->exec(); QVERIFY2(ok, qPrintable(job->errorString())); QVERIFY(QFile::exists(dest)); QVERIFY(QFileInfo(dest).isDir()); QVERIFY(QFileInfo(dest + "/testfile").isFile()); QVERIFY(!QFile::exists(src)); // not there anymore #ifndef Q_OS_WIN QVERIFY(QFileInfo(dest + "/testlink").isSymLink()); #endif } void JobTest::moveFileToSamePartition() { qDebug(); const QString filePath = homeTmpDir() + "fileFromHome"; const QString dest = homeTmpDir() + "fileFromHome_moved"; createTestFile(filePath); moveLocalFile(filePath, dest); } void JobTest::moveDirectoryToSamePartition() { qDebug(); const QString src = homeTmpDir() + "dirFromHome"; const QString dest = homeTmpDir() + "dirFromHome_moved"; createTestDirectory(src); moveLocalDirectory(src, dest); } void JobTest::moveDirectoryIntoItself() { qDebug(); const QString src = homeTmpDir() + "dirFromHome"; const QString dest = src + "/foo"; createTestDirectory(src); QVERIFY(QFile::exists(src)); QUrl u = QUrl::fromLocalFile(src); QUrl d = QUrl::fromLocalFile(dest); KIO::CopyJob *job = KIO::move(u, d); QVERIFY(!job->exec()); QCOMPARE(job->error(), (int)KIO::ERR_CANNOT_MOVE_INTO_ITSELF); QCOMPARE(job->errorString(), i18n("A folder cannot be moved into itself")); QDir(dest).removeRecursively(); } void JobTest::moveFileToOtherPartition() { qDebug(); const QString filePath = homeTmpDir() + "fileFromHome"; const QString dest = otherTmpDir() + "fileFromHome_moved"; createTestFile(filePath); moveLocalFile(filePath, dest); } void JobTest::moveSymlinkToOtherPartition() { #ifndef Q_OS_WIN qDebug(); const QString filePath = homeTmpDir() + "testlink"; const QString dest = otherTmpDir() + "testlink_moved"; createTestSymlink(filePath); moveLocalSymlink(filePath, dest); #endif } void JobTest::moveDirectoryToOtherPartition() { qDebug(); #ifndef Q_OS_WIN const QString src = homeTmpDir() + "dirFromHome"; const QString dest = otherTmpDir() + "dirFromHome_moved"; createTestDirectory(src); moveLocalDirectory(src, dest); #endif } void JobTest::moveFileNoPermissions() { #ifdef Q_OS_WIN QSKIP("Skipping unaccessible folder test on Windows, cannot remove all permissions from a folder"); #endif // Given a file that cannot be moved (subdir has no permissions) const QString subdir = homeTmpDir() + "subdir"; QVERIFY(QDir().mkpath(subdir)); const QString src = subdir + "/thefile"; createTestFile(src); QVERIFY(QFile(subdir).setPermissions(QFile::Permissions())); // Make it inaccessible // When trying to move it const QString dest = homeTmpDir() + "dest"; KIO::CopyJob *job = KIO::move(QUrl::fromLocalFile(src), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); // no skip dialog, thanks // The job should fail with "access denied" QVERIFY(!job->exec()); QCOMPARE(job->error(), (int)KIO::ERR_ACCESS_DENIED); // Note that, just like mv(1), KIO's behavior depends on whether // a direct rename(2) was used, or a full copy+del. In the first case // there is no destination file created, but in the second case the // destination file remains. // In this test it's the same partition, so no dest created. QVERIFY(!QFile::exists(dest)); // Cleanup QVERIFY(QFile(subdir).setPermissions(QFile::Permissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner))); QVERIFY(QFile::exists(src)); QVERIFY(QDir(subdir).removeRecursively()); } void JobTest::moveDirectoryNoPermissions() { #ifdef Q_OS_WIN QSKIP("Skipping unaccessible folder test on Windows, cannot remove all permissions from a folder"); #endif // Given a dir that cannot be moved (parent dir has no permissions) const QString subdir = homeTmpDir() + "subdir"; const QString src = subdir + "/thedir"; QVERIFY(QDir().mkpath(src)); QVERIFY(QFileInfo(src).isDir()); QVERIFY(QFile(subdir).setPermissions(QFile::Permissions())); // Make it inaccessible // When trying to move it const QString dest = homeTmpDir() + "mdnp"; KIO::CopyJob *job = KIO::move(QUrl::fromLocalFile(src), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); // no skip dialog, thanks // The job should fail with "access denied" QVERIFY(!job->exec()); QCOMPARE(job->error(), (int)KIO::ERR_ACCESS_DENIED); QVERIFY(!QFile::exists(dest)); // Cleanup QVERIFY(QFile(subdir).setPermissions(QFile::Permissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner))); QVERIFY(QFile::exists(src)); QVERIFY(QDir(subdir).removeRecursively()); } void JobTest::listRecursive() { // Note: many other tests must have been run before since we rely on the files they created const QString src = homeTmpDir(); #ifndef Q_OS_WIN // Add a symlink to a dir, to make sure we don't recurse into those bool symlinkOk = symlink("dirFromHome", QFile::encodeName(src + "/dirFromHome_link").constData()) == 0; QVERIFY(symlinkOk); #endif KIO::ListJob *job = KIO::listRecursive(QUrl::fromLocalFile(src), KIO::HideProgressInfo); job->setUiDelegate(nullptr); connect(job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList))); bool ok = job->exec(); QVERIFY(ok); m_names.sort(); QByteArray ref_names = QByteArray(".,..," "dirFromHome,dirFromHome/testfile," "dirFromHome/testlink," // exists on Windows too, see createTestDirectory "dirFromHome_copied," "dirFromHome_copied/dirFromHome,dirFromHome_copied/dirFromHome/testfile," "dirFromHome_copied/dirFromHome/testlink," "dirFromHome_copied/testfile," "dirFromHome_copied/testlink," #ifndef Q_OS_WIN "dirFromHome_link," #endif "fileFromHome"); const QString joinedNames = m_names.join(QStringLiteral(",")); if (joinedNames.toLatin1() != ref_names) { qDebug("%s", qPrintable(joinedNames)); qDebug("%s", ref_names.data()); } QCOMPARE(joinedNames.toLatin1(), ref_names); } void JobTest::listFile() { const QString filePath = homeTmpDir() + "fileFromHome"; createTestFile(filePath); KIO::ListJob *job = KIO::listDir(QUrl::fromLocalFile(filePath), KIO::HideProgressInfo); job->setUiDelegate(nullptr); QVERIFY(!job->exec()); QCOMPARE(job->error(), static_cast(KIO::ERR_IS_FILE)); // And list something that doesn't exist const QString path = homeTmpDir() + "fileFromHomeDoesNotExist"; job = KIO::listDir(QUrl::fromLocalFile(path), KIO::HideProgressInfo); job->setUiDelegate(nullptr); QVERIFY(!job->exec()); QCOMPARE(job->error(), static_cast(KIO::ERR_DOES_NOT_EXIST)); } void JobTest::killJob() { const QString src = homeTmpDir(); KIO::ListJob *job = KIO::listDir(QUrl::fromLocalFile(src), KIO::HideProgressInfo); QVERIFY(job->isAutoDelete()); QPointer ptr(job); job->setUiDelegate(nullptr); qApp->processEvents(); // let the job start, it's no fun otherwise job->kill(); qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete); // process the deferred delete of the job QVERIFY(ptr.isNull()); } void JobTest::killJobBeforeStart() { const QString src = homeTmpDir(); KIO::Job *job = KIO::stat(QUrl::fromLocalFile(src), KIO::HideProgressInfo); QVERIFY(job->isAutoDelete()); QPointer ptr(job); job->setUiDelegate(nullptr); job->kill(); qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete); // process the deferred delete of the job QVERIFY(ptr.isNull()); qApp->processEvents(); // does KIO scheduler crash here? nope. } void JobTest::deleteJobBeforeStart() // #163171 { const QString src = homeTmpDir(); KIO::Job *job = KIO::stat(QUrl::fromLocalFile(src), KIO::HideProgressInfo); QVERIFY(job->isAutoDelete()); job->setUiDelegate(nullptr); delete job; qApp->processEvents(); // does KIO scheduler crash here? } void JobTest::directorySize() { // Note: many other tests must have been run before since we rely on the files they created const QString src = homeTmpDir(); KIO::DirectorySizeJob *job = KIO::directorySize(QUrl::fromLocalFile(src)); job->setUiDelegate(nullptr); bool ok = job->exec(); QVERIFY(ok); qDebug() << "totalSize: " << job->totalSize(); qDebug() << "totalFiles: " << job->totalFiles(); qDebug() << "totalSubdirs: " << job->totalSubdirs(); #ifdef Q_OS_WIN QCOMPARE(job->totalFiles(), 5ULL); // see expected result in listRecursive() above QCOMPARE(job->totalSubdirs(), 3ULL); // see expected result in listRecursive() above QVERIFY(job->totalSize() > 54); #else QCOMPARE(job->totalFiles(), 7ULL); // see expected result in listRecursive() above QCOMPARE(job->totalSubdirs(), 4ULL); // see expected result in listRecursive() above QVERIFY(job->totalSize() >= 260); // size of subdir entries is filesystem dependent. E.g. this is 16428 with ext4 but only 272 with xfs. #endif qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete); } void JobTest::directorySizeError() { KIO::DirectorySizeJob *job = KIO::directorySize(QUrl::fromLocalFile(QStringLiteral("/I/Dont/Exist"))); job->setUiDelegate(nullptr); bool ok = job->exec(); QVERIFY(!ok); qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete); } void JobTest::slotEntries(KIO::Job *, const KIO::UDSEntryList &lst) { for (KIO::UDSEntryList::ConstIterator it = lst.begin(); it != lst.end(); ++it) { QString displayName = (*it).stringValue(KIO::UDSEntry::UDS_NAME); //QUrl url = (*it).stringValue( KIO::UDSEntry::UDS_URL ); m_names.append(displayName); } } void JobTest::calculateRemainingSeconds() { unsigned int seconds = KIO::calculateRemainingSeconds(2 * 86400 - 60, 0, 1); QCOMPARE(seconds, static_cast(2 * 86400 - 60)); QString text = KIO::convertSeconds(seconds); QCOMPARE(text, i18n("1 day 23:59:00")); seconds = KIO::calculateRemainingSeconds(520, 20, 10); QCOMPARE(seconds, static_cast(50)); text = KIO::convertSeconds(seconds); QCOMPARE(text, i18n("00:00:50")); } #if 0 void JobTest::copyFileToSystem() { if (!KProtocolInfo::isKnownProtocol("system")) { qDebug() << "no kio_system, skipping test"; return; } // First test with support for UDS_LOCAL_PATH copyFileToSystem(true); QString dest = realSystemPath() + "fileFromHome_copied"; QFile::remove(dest); // Then disable support for UDS_LOCAL_PATH, i.e. test what would // happen for ftp, smb, http etc. copyFileToSystem(false); } void JobTest::copyFileToSystem(bool resolve_local_urls) { qDebug() << resolve_local_urls; extern KIOCORE_EXPORT bool kio_resolve_local_urls; kio_resolve_local_urls = resolve_local_urls; const QString src = homeTmpDir() + "fileFromHome"; createTestFile(src); QUrl u = QUrl::fromLocalFile(src); QUrl d = QUrl::fromLocalFile(systemTmpDir()); d.addPath("fileFromHome_copied"); qDebug() << "copying " << u << " to " << d; // copy the file with file_copy m_mimetype.clear(); KIO::FileCopyJob *job = KIO::file_copy(u, d, -1, KIO::HideProgressInfo); job->setUiDelegate(0); connect(job, SIGNAL(mimetype(KIO::Job*,QString)), this, SLOT(slotMimetype(KIO::Job*,QString))); bool ok = job->exec(); QVERIFY(ok); QString dest = realSystemPath() + "fileFromHome_copied"; QVERIFY(QFile::exists(dest)); QVERIFY(QFile::exists(src)); // still there { // do NOT check that the timestamp is the same. // It can't work with file_copy when it uses the datapump, // unless we use setModificationTime in the app code. } // Check mimetype QCOMPARE(m_mimetype, QString("text/plain")); // cleanup and retry with KIO::copy() QFile::remove(dest); job = KIO::copy(u, d, KIO::HideProgressInfo); job->setUiDelegate(0); ok = job->exec(); QVERIFY(ok); QVERIFY(QFile::exists(dest)); QVERIFY(QFile::exists(src)); // still there { // check that the timestamp is the same (#79937) QFileInfo srcInfo(src); QFileInfo destInfo(dest); QCOMPARE(srcInfo.lastModified(), destInfo.lastModified()); } // restore normal behavior kio_resolve_local_urls = true; } #endif void JobTest::getInvalidUrl() { QUrl url(QStringLiteral("http://strange/")); QVERIFY(!url.isValid()); KIO::SimpleJob *job = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo); QVERIFY(job != nullptr); job->setUiDelegate(nullptr); KIO::Scheduler::setJobPriority(job, 1); // shouldn't crash (#135456) bool ok = job->exec(); QVERIFY(!ok); // it should fail :) } void JobTest::slotMimetype(KIO::Job *job, const QString &type) { QVERIFY(job != nullptr); m_mimetype = type; } void JobTest::deleteFile() { const QString dest = otherTmpDir() + "fileFromHome_copied"; createTestFile(dest); KIO::Job *job = KIO::del(QUrl::fromLocalFile(dest), KIO::HideProgressInfo); job->setUiDelegate(nullptr); bool ok = job->exec(); QVERIFY(ok); QVERIFY(!QFile::exists(dest)); } void JobTest::deleteDirectory() { const QString dest = otherTmpDir() + "dirFromHome_copied"; if (!QFile::exists(dest)) { createTestDirectory(dest); } // Let's put a few things in there to see if the recursive deletion works correctly // A hidden file: createTestFile(dest + "/.hidden"); #ifndef Q_OS_WIN // A broken symlink: createTestSymlink(dest + "/broken_symlink"); // A symlink to a dir: bool symlink_ok = symlink(QFile::encodeName(QFileInfo(QFINDTESTDATA("jobtest.cpp")).absolutePath()).constData(), QFile::encodeName(dest + "/symlink_to_dir").constData()) == 0; if (!symlink_ok) { qFatal("couldn't create symlink: %s", strerror(errno)); } #endif KIO::Job *job = KIO::del(QUrl::fromLocalFile(dest), KIO::HideProgressInfo); job->setUiDelegate(nullptr); bool ok = job->exec(); QVERIFY(ok); QVERIFY(!QFile::exists(dest)); } void JobTest::deleteSymlink(bool using_fast_path) { extern KIOCORE_EXPORT bool kio_resolve_local_urls; kio_resolve_local_urls = !using_fast_path; #ifndef Q_OS_WIN const QString src = homeTmpDir() + "dirFromHome"; createTestDirectory(src); QVERIFY(QFile::exists(src)); const QString dest = homeTmpDir() + "/dirFromHome_link"; if (!QFile::exists(dest)) { // Add a symlink to a dir, to make sure we don't recurse into those bool symlinkOk = symlink(QFile::encodeName(src).constData(), QFile::encodeName(dest).constData()) == 0; QVERIFY(symlinkOk); QVERIFY(QFile::exists(dest)); } KIO::Job *job = KIO::del(QUrl::fromLocalFile(dest), KIO::HideProgressInfo); job->setUiDelegate(nullptr); bool ok = job->exec(); QVERIFY(ok); QVERIFY(!QFile::exists(dest)); QVERIFY(QFile::exists(src)); #endif kio_resolve_local_urls = true; } void JobTest::deleteSymlink() { #ifndef Q_OS_WIN deleteSymlink(true); deleteSymlink(false); #endif } void JobTest::deleteManyDirs(bool using_fast_path) { extern KIOCORE_EXPORT bool kio_resolve_local_urls; kio_resolve_local_urls = !using_fast_path; const int numDirs = 50; QList dirs; for (int i = 0; i < numDirs; ++i) { const QString dir = homeTmpDir() + "dir" + QString::number(i); createTestDirectory(dir); dirs << QUrl::fromLocalFile(dir); } QTime dt; dt.start(); KIO::Job *job = KIO::del(dirs, KIO::HideProgressInfo); job->setUiDelegate(nullptr); bool ok = job->exec(); QVERIFY(ok); Q_FOREACH (const QUrl &dir, dirs) { QVERIFY(!QFile::exists(dir.toLocalFile())); } qDebug() << "Deleted" << numDirs << "dirs in" << dt.elapsed() << "milliseconds"; kio_resolve_local_urls = true; } void JobTest::deleteManyDirs() { deleteManyDirs(true); deleteManyDirs(false); } static QList createManyFiles(const QString &baseDir, int numFiles) { QList ret; ret.reserve(numFiles); for (int i = 0; i < numFiles; ++i) { // create empty file const QString file = baseDir + QString::number(i); QFile f(file); bool ok = f.open(QIODevice::WriteOnly); if (ok) { f.write("Hello"); ret.append(QUrl::fromLocalFile(file)); } } return ret; } void JobTest::deleteManyFilesIndependently() { QTime dt; dt.start(); const int numFiles = 100; // Use 1000 for performance testing const QString baseDir = homeTmpDir(); const QList urls = createManyFiles(baseDir, numFiles); QCOMPARE(urls.count(), numFiles); for (int i = 0; i < numFiles; ++i) { // delete each file independently. lots of jobs. this stress-tests kio scheduling. const QUrl url = urls.at(i); const QString file = url.toLocalFile(); QVERIFY(QFile::exists(file)); //qDebug() << file; KIO::Job *job = KIO::del(url, KIO::HideProgressInfo); job->setUiDelegate(nullptr); bool ok = job->exec(); QVERIFY(ok); QVERIFY(!QFile::exists(file)); } qDebug() << "Deleted" << numFiles << "files in" << dt.elapsed() << "milliseconds"; } void JobTest::deleteManyFilesTogether(bool using_fast_path) { extern KIOCORE_EXPORT bool kio_resolve_local_urls; kio_resolve_local_urls = !using_fast_path; QTime dt; dt.start(); const int numFiles = 100; // Use 1000 for performance testing const QString baseDir = homeTmpDir(); const QList urls = createManyFiles(baseDir, numFiles); QCOMPARE(urls.count(), numFiles); //qDebug() << file; KIO::Job *job = KIO::del(urls, KIO::HideProgressInfo); job->setUiDelegate(nullptr); bool ok = job->exec(); QVERIFY(ok); qDebug() << "Deleted" << numFiles << "files in" << dt.elapsed() << "milliseconds"; kio_resolve_local_urls = true; } void JobTest::deleteManyFilesTogether() { deleteManyFilesTogether(true); deleteManyFilesTogether(false); } void JobTest::rmdirEmpty() { const QString dir = homeTmpDir() + "dir"; QDir().mkdir(dir); QVERIFY(QFile::exists(dir)); KIO::Job *job = KIO::rmdir(QUrl::fromLocalFile(dir)); QVERIFY(job->exec()); QVERIFY(!QFile::exists(dir)); } void JobTest::rmdirNotEmpty() { const QString dir = homeTmpDir() + "dir"; createTestDirectory(dir); createTestDirectory(dir + "/subdir"); KIO::Job *job = KIO::rmdir(QUrl::fromLocalFile(dir)); QVERIFY(!job->exec()); QVERIFY(QFile::exists(dir)); } void JobTest::stat() { #if 1 const QString filePath = homeTmpDir() + "fileFromHome"; createTestFile(filePath); const QUrl url(QUrl::fromLocalFile(filePath)); KIO::StatJob *job = KIO::stat(url, KIO::HideProgressInfo); QVERIFY(job); bool ok = job->exec(); QVERIFY(ok); // TODO set setSide, setDetails const KIO::UDSEntry &entry = job->statResult(); QVERIFY(!entry.isDir()); QVERIFY(!entry.isLink()); QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QStringLiteral("fileFromHome")); // Compare what we get via kio_file and what we get when KFileItem stat()s directly const KFileItem kioItem(entry, url); const KFileItem fileItem(url); QCOMPARE(kioItem.name(), fileItem.name()); QCOMPARE(kioItem.url(), fileItem.url()); QCOMPARE(kioItem.size(), fileItem.size()); QCOMPARE(kioItem.user(), fileItem.user()); QCOMPARE(kioItem.group(), fileItem.group()); QCOMPARE(kioItem.mimetype(), fileItem.mimetype()); QCOMPARE(kioItem.permissions(), fileItem.permissions()); QCOMPARE(kioItem.time(KFileItem::ModificationTime), fileItem.time(KFileItem::ModificationTime)); QCOMPARE(kioItem.time(KFileItem::AccessTime), fileItem.time(KFileItem::AccessTime)); #else // Testing stat over HTTP KIO::StatJob *job = KIO::stat(QUrl("http://www.kde.org"), KIO::HideProgressInfo); QVERIFY(job); bool ok = job->exec(); QVERIFY(ok); // TODO set setSide, setDetails const KIO::UDSEntry &entry = job->statResult(); QVERIFY(!entry.isDir()); QVERIFY(!entry.isLink()); QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QString()); #endif } #ifndef Q_OS_WIN void JobTest::statSymlink() { const QString filePath = homeTmpDir() + "fileFromHome"; createTestFile(filePath); const QString symlink = otherTmpDir() + "link"; QVERIFY(QFile(filePath).link(symlink)); QVERIFY(QFile::exists(symlink)); setTimeStamp(symlink, QDateTime::currentDateTime().addSecs(-20)); // differenciate link time and source file time const QUrl url(QUrl::fromLocalFile(symlink)); KIO::StatJob *job = KIO::stat(url, KIO::HideProgressInfo); QVERIFY(job); bool ok = job->exec(); QVERIFY(ok); // TODO set setSide, setDetails const KIO::UDSEntry &entry = job->statResult(); QVERIFY(!entry.isDir()); QVERIFY(entry.isLink()); QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QStringLiteral("link")); // Compare what we get via kio_file and what we get when KFileItem stat()s directly const KFileItem kioItem(entry, url); const KFileItem fileItem(url); QCOMPARE(kioItem.name(), fileItem.name()); QCOMPARE(kioItem.url(), fileItem.url()); QVERIFY(kioItem.isLink()); QVERIFY(fileItem.isLink()); QCOMPARE(kioItem.linkDest(), fileItem.linkDest()); QCOMPARE(kioItem.size(), fileItem.size()); QCOMPARE(kioItem.user(), fileItem.user()); QCOMPARE(kioItem.group(), fileItem.group()); QCOMPARE(kioItem.mimetype(), fileItem.mimetype()); QCOMPARE(kioItem.permissions(), fileItem.permissions()); QCOMPARE(kioItem.time(KFileItem::ModificationTime), fileItem.time(KFileItem::ModificationTime)); QCOMPARE(kioItem.time(KFileItem::AccessTime), fileItem.time(KFileItem::AccessTime)); } #endif void JobTest::mostLocalUrl() { const QString filePath = homeTmpDir() + "fileFromHome"; createTestFile(filePath); KIO::StatJob *job = KIO::mostLocalUrl(QUrl::fromLocalFile(filePath), KIO::HideProgressInfo); QVERIFY(job); bool ok = job->exec(); QVERIFY(ok); QCOMPARE(job->mostLocalUrl().toLocalFile(), filePath); } void JobTest::chmodFile() { const QString filePath = homeTmpDir() + "fileForChmod"; createTestFile(filePath); KFileItem item(QUrl::fromLocalFile(filePath)); const mode_t origPerm = item.permissions(); mode_t newPerm = origPerm ^ S_IWGRP; QVERIFY(newPerm != origPerm); KFileItemList items; items << item; KIO::Job *job = KIO::chmod(items, newPerm, S_IWGRP /*TODO: QFile::WriteGroup*/, QString(), QString(), false, KIO::HideProgressInfo); job->setUiDelegate(nullptr); QVERIFY(job->exec()); KFileItem newItem(QUrl::fromLocalFile(filePath)); QCOMPARE(QString::number(newItem.permissions(), 8), QString::number(newPerm, 8)); QFile::remove(filePath); } #ifdef Q_OS_UNIX void JobTest::chmodSticky() { const QString dirPath = homeTmpDir() + "dirForChmodSticky"; QDir().mkpath(dirPath); KFileItem item(QUrl::fromLocalFile(dirPath)); const mode_t origPerm = item.permissions(); mode_t newPerm = origPerm ^ S_ISVTX; QVERIFY(newPerm != origPerm); KFileItemList items({item}); KIO::Job *job = KIO::chmod(items, newPerm, S_ISVTX, QString(), QString(), false, KIO::HideProgressInfo); job->setUiDelegate(nullptr); QVERIFY(job->exec()); KFileItem newItem(QUrl::fromLocalFile(dirPath)); QCOMPARE(QString::number(newItem.permissions(), 8), QString::number(newPerm, 8)); QVERIFY(QDir().rmdir(dirPath)); } #endif void JobTest::chmodFileError() { // chown(root) should fail const QString filePath = homeTmpDir() + "fileForChmod"; createTestFile(filePath); KFileItem item(QUrl::fromLocalFile(filePath)); const mode_t origPerm = item.permissions(); mode_t newPerm = origPerm ^ S_IWGRP; QVERIFY(newPerm != origPerm); KFileItemList items; items << item; KIO::Job *job = KIO::chmod(items, newPerm, S_IWGRP /*TODO: QFile::WriteGroup*/, QStringLiteral("root"), QString(), false, KIO::HideProgressInfo); // Simulate the user pressing "Skip" in the dialog. PredefinedAnswerJobUiDelegate extension; extension.m_skipResult = KIO::S_SKIP; job->setUiDelegateExtension(&extension); QVERIFY(job->exec()); QCOMPARE(extension.m_askSkipCalled, 1); KFileItem newItem(QUrl::fromLocalFile(filePath)); // We skipped, so the chmod didn't happen. QCOMPARE(QString::number(newItem.permissions(), 8), QString::number(origPerm, 8)); QFile::remove(filePath); } void JobTest::mimeType() { #if 1 const QString filePath = homeTmpDir() + "fileFromHome"; createTestFile(filePath); KIO::MimetypeJob *job = KIO::mimetype(QUrl::fromLocalFile(filePath), KIO::HideProgressInfo); QVERIFY(job); QSignalSpy spyMimeType(job, SIGNAL(mimetype(KIO::Job*,QString))); bool ok = job->exec(); QVERIFY(ok); QCOMPARE(spyMimeType.count(), 1); QCOMPARE(spyMimeType[0][0], QVariant::fromValue(static_cast(job))); QCOMPARE(spyMimeType[0][1].toString(), QStringLiteral("application/octet-stream")); #else // Testing mimetype over HTTP KIO::MimetypeJob *job = KIO::mimetype(QUrl("http://www.kde.org"), KIO::HideProgressInfo); QVERIFY(job); QSignalSpy spyMimeType(job, SIGNAL(mimetype(KIO::Job*,QString))); bool ok = job->exec(); QVERIFY(ok); QCOMPARE(spyMimeType.count(), 1); QCOMPARE(spyMimeType[0][0], QVariant::fromValue(static_cast(job))); QCOMPARE(spyMimeType[0][1].toString(), QString("text/html")); #endif } void JobTest::mimeTypeError() { // KIO::mimetype() on a file that doesn't exist const QString filePath = homeTmpDir() + "doesNotExist"; KIO::MimetypeJob *job = KIO::mimetype(QUrl::fromLocalFile(filePath), KIO::HideProgressInfo); QVERIFY(job); QSignalSpy spyMimeType(job, SIGNAL(mimetype(KIO::Job*,QString))); QSignalSpy spyResult(job, SIGNAL(result(KJob*))); bool ok = job->exec(); QVERIFY(!ok); QCOMPARE(spyMimeType.count(), 0); QCOMPARE(spyResult.count(), 1); } void JobTest::moveFileDestAlreadyExists() // #157601 { const QString file1 = homeTmpDir() + "fileFromHome"; createTestFile(file1); const QString file2 = homeTmpDir() + "anotherFile"; createTestFile(file2); const QString existingDest = otherTmpDir() + "fileFromHome"; createTestFile(existingDest); QList urls; urls << QUrl::fromLocalFile(file1) << QUrl::fromLocalFile(file2); KIO::CopyJob *job = KIO::move(urls, QUrl::fromLocalFile(otherTmpDir()), KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); job->setAutoSkip(true); bool ok = job->exec(); QVERIFY(ok); QVERIFY(QFile::exists(file1)); // it was skipped QVERIFY(!QFile::exists(file2)); // it was moved } void JobTest::moveDestAlreadyExistsAutoRename_data() { QTest::addColumn("samePartition"); QTest::addColumn("moveDirs"); QTest::newRow("files same partition") << true << false; QTest::newRow("files other partition") << false << false; QTest::newRow("dirs same partition") << true << true; QTest::newRow("dirs other partition") << false << true; } void JobTest::moveDestAlreadyExistsAutoRename() { QFETCH(bool, samePartition); QFETCH(bool, moveDirs); QString dir; if (samePartition) { dir = homeTmpDir() + "dir/"; QVERIFY(QDir(dir).exists() || QDir().mkdir(dir)); } else { dir = otherTmpDir(); } moveDestAlreadyExistsAutoRename(dir, moveDirs); if (samePartition) { // cleanup KIO::Job *job = KIO::del(QUrl::fromLocalFile(dir), KIO::HideProgressInfo); QVERIFY(job->exec()); QVERIFY(!QFile::exists(dir)); } } void JobTest::moveDestAlreadyExistsAutoRename(const QString &destDir, bool moveDirs) // #256650 { const QString prefix = moveDirs ? QStringLiteral("dir ") : QStringLiteral("file "); QStringList sources; const QString file1 = homeTmpDir() + prefix + "(1)"; const QString file2 = homeTmpDir() + prefix + "(2)"; const QString existingDest1 = destDir + prefix + "(1)"; const QString existingDest2 = destDir + prefix + "(2)"; sources << file1 << file2 << existingDest1 << existingDest2; Q_FOREACH (const QString &source, sources) { if (moveDirs) { QVERIFY(QDir().mkdir(source)); } else { createTestFile(source); } } QList urls; urls << QUrl::fromLocalFile(file1) << QUrl::fromLocalFile(file2); KIO::CopyJob *job = KIO::move(urls, QUrl::fromLocalFile(destDir), KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); job->setAutoRename(true); //qDebug() << QDir(destDir).entryList(); bool ok = job->exec(); qDebug() << QDir(destDir).entryList(); QVERIFY(ok); QVERIFY(!QFile::exists(file1)); // it was moved QVERIFY(!QFile::exists(file2)); // it was moved QVERIFY(QFile::exists(existingDest1)); QVERIFY(QFile::exists(existingDest2)); const QString file3 = destDir + prefix + "(3)"; const QString file4 = destDir + prefix + "(4)"; QVERIFY(QFile::exists(file3)); QVERIFY(QFile::exists(file4)); if (moveDirs) { QDir().rmdir(file1); QDir().rmdir(file2); QDir().rmdir(file3); QDir().rmdir(file4); } else { QFile::remove(file1); QFile::remove(file2); QFile::remove(file3); QFile::remove(file4); } } void JobTest::moveAndOverwrite() { const QString sourceFile = homeTmpDir() + "fileFromHome"; createTestFile(sourceFile); QString existingDest = otherTmpDir() + "fileFromHome"; createTestFile(existingDest); KIO::FileCopyJob *job = KIO::file_move(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite); job->setUiDelegate(nullptr); bool ok = job->exec(); QVERIFY(ok); QVERIFY(!QFile::exists(sourceFile)); // it was moved #ifndef Q_OS_WIN // Now same thing when the target is a symlink to the source createTestFile(sourceFile); createTestSymlink(existingDest, QFile::encodeName(sourceFile)); QVERIFY(QFile::exists(existingDest)); job = KIO::file_move(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite); job->setUiDelegate(nullptr); ok = job->exec(); QVERIFY(ok); QVERIFY(!QFile::exists(sourceFile)); // it was moved // Now same thing when the target is a symlink to another file createTestFile(sourceFile); createTestFile(sourceFile + "2"); createTestSymlink(existingDest, QFile::encodeName(sourceFile + "2")); QVERIFY(QFile::exists(existingDest)); job = KIO::file_move(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite); job->setUiDelegate(nullptr); ok = job->exec(); QVERIFY(ok); QVERIFY(!QFile::exists(sourceFile)); // it was moved // Now same thing when the target is a _broken_ symlink createTestFile(sourceFile); createTestSymlink(existingDest); QVERIFY(!QFile::exists(existingDest)); // it exists, but it's broken... job = KIO::file_move(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite); job->setUiDelegate(nullptr); ok = job->exec(); QVERIFY(ok); QVERIFY(!QFile::exists(sourceFile)); // it was moved #endif } void JobTest::moveOverSymlinkToSelf() // #169547 { #ifndef Q_OS_WIN const QString sourceFile = homeTmpDir() + "fileFromHome"; createTestFile(sourceFile); const QString existingDest = homeTmpDir() + "testlink"; createTestSymlink(existingDest, QFile::encodeName(sourceFile)); QVERIFY(QFile::exists(existingDest)); KIO::CopyJob *job = KIO::move(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(existingDest), KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); bool ok = job->exec(); QVERIFY(!ok); QCOMPARE(job->error(), (int)KIO::ERR_FILE_ALREADY_EXIST); // and not ERR_IDENTICAL_FILES! QVERIFY(QFile::exists(sourceFile)); // it not moved #endif } void JobTest::createSymlink() { #ifdef Q_OS_WIN QSKIP("Test skipped on Windows"); #endif const QString sourceFile = homeTmpDir() + "fileFromHome"; createTestFile(sourceFile); const QString destDir = homeTmpDir() + "dest"; QVERIFY(QDir().mkpath(destDir)); // With KIO::link (high-level) KIO::CopyJob *job = KIO::link(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(destDir), KIO::HideProgressInfo); QVERIFY(job->exec()); QVERIFY(QFileInfo::exists(sourceFile)); const QString dest = destDir + "/fileFromHome"; QVERIFY(QFileInfo(dest).isSymLink()); QCOMPARE(QFileInfo(dest).symLinkTarget(), sourceFile); QFile::remove(dest); // With KIO::symlink (low-level) const QString linkPath = destDir + "/link"; KIO::Job *symlinkJob = KIO::symlink(sourceFile, QUrl::fromLocalFile(linkPath), KIO::HideProgressInfo); QVERIFY(symlinkJob->exec()); QVERIFY(QFileInfo::exists(sourceFile)); QVERIFY(QFileInfo(linkPath).isSymLink()); QCOMPARE(QFileInfo(linkPath).symLinkTarget(), sourceFile); // Cleanup QVERIFY(QDir(destDir).removeRecursively()); } void JobTest::createSymlinkTargetDirDoesntExist() { #ifdef Q_OS_WIN QSKIP("Test skipped on Windows"); #endif const QString sourceFile = homeTmpDir() + "fileFromHome"; createTestFile(sourceFile); const QString destDir = homeTmpDir() + "dest/does/not/exist"; KIO::CopyJob *job = KIO::link(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(destDir), KIO::HideProgressInfo); QVERIFY(!job->exec()); QCOMPARE(job->error(), static_cast(KIO::ERR_CANNOT_SYMLINK)); } void JobTest::createSymlinkAsShouldSucceed() { #ifdef Q_OS_WIN QSKIP("Test skipped on Windows"); #endif const QString sourceFile = homeTmpDir() + "fileFromHome"; createTestFile(sourceFile); const QString dest = homeTmpDir() + "testlink"; QFile::remove(dest); // just in case KIO::CopyJob *job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); QVERIFY(job->exec()); QVERIFY(QFileInfo::exists(sourceFile)); QVERIFY(QFileInfo(dest).isSymLink()); QVERIFY(QFile::remove(dest)); } void JobTest::createSymlinkAsShouldFailDirectoryExists() { #ifdef Q_OS_WIN QSKIP("Test skipped on Windows"); #endif const QString sourceFile = homeTmpDir() + "fileFromHome"; createTestFile(sourceFile); const QString dest = homeTmpDir() + "dest"; QVERIFY(QDir().mkpath(dest)); // dest exists as a directory // With KIO::link (high-level) KIO::CopyJob *job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); QVERIFY(!job->exec()); QCOMPARE(job->error(), (int)KIO::ERR_DIR_ALREADY_EXIST); QVERIFY(QFileInfo::exists(sourceFile)); QVERIFY(!QFileInfo::exists(dest + "/fileFromHome")); // With KIO::symlink (low-level) KIO::Job *symlinkJob = KIO::symlink(sourceFile, QUrl::fromLocalFile(dest), KIO::HideProgressInfo); QVERIFY(!symlinkJob->exec()); QCOMPARE(symlinkJob->error(), (int)KIO::ERR_DIR_ALREADY_EXIST); QVERIFY(QFileInfo::exists(sourceFile)); // Cleanup QVERIFY(QDir().rmdir(dest)); } void JobTest::createSymlinkAsShouldFailFileExists() { #ifdef Q_OS_WIN QSKIP("Test skipped on Windows"); #endif const QString sourceFile = homeTmpDir() + "fileFromHome"; createTestFile(sourceFile); const QString dest = homeTmpDir() + "testlink"; QFile::remove(dest); // just in case // First time works KIO::CopyJob *job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); QVERIFY(job->exec()); QVERIFY(QFileInfo(dest).isSymLink()); // Second time fails (already exists) job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); QVERIFY(!job->exec()); QCOMPARE(job->error(), (int)KIO::ERR_FILE_ALREADY_EXIST); // KIO::symlink fails too KIO::Job *symlinkJob = KIO::symlink(sourceFile, QUrl::fromLocalFile(dest), KIO::HideProgressInfo); QVERIFY(!symlinkJob->exec()); QCOMPARE(symlinkJob->error(), (int)KIO::ERR_FILE_ALREADY_EXIST); // Cleanup QVERIFY(QFile::remove(sourceFile)); QVERIFY(QFile::remove(dest)); } void JobTest::createSymlinkWithOverwriteShouldWork() { #ifdef Q_OS_WIN QSKIP("Test skipped on Windows"); #endif const QString sourceFile = homeTmpDir() + "fileFromHome"; createTestFile(sourceFile); const QString dest = homeTmpDir() + "testlink"; QFile::remove(dest); // just in case // First time works KIO::CopyJob *job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); QVERIFY(job->exec()); QVERIFY(QFileInfo(dest).isSymLink()); // Changing the link target, with overwrite, works job = KIO::linkAs(QUrl::fromLocalFile(sourceFile + "2"), QUrl::fromLocalFile(dest), KIO::Overwrite | KIO::HideProgressInfo); QVERIFY(job->exec()); QVERIFY(QFileInfo(dest).isSymLink()); QCOMPARE(QFileInfo(dest).symLinkTarget(), QString(sourceFile + "2")); // Changing the link target using KIO::symlink, with overwrite, works KIO::Job *symlinkJob = KIO::symlink(sourceFile + "3", QUrl::fromLocalFile(dest), KIO::Overwrite | KIO::HideProgressInfo); QVERIFY(symlinkJob->exec()); QVERIFY(QFileInfo(dest).isSymLink()); QCOMPARE(QFileInfo(dest).symLinkTarget(), QString(sourceFile + "3")); // Cleanup QVERIFY(QFile::remove(dest)); QVERIFY(QFile::remove(sourceFile)); } void JobTest::createBrokenSymlink() { #ifdef Q_OS_WIN QSKIP("Test skipped on Windows"); #endif const QString sourceFile = "/does/not/exist"; const QString dest = homeTmpDir() + "testlink"; QFile::remove(dest); // just in case KIO::CopyJob *job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); QVERIFY(job->exec()); QVERIFY(QFileInfo(dest).isSymLink()); // Second time fails (already exists) job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); QVERIFY(!job->exec()); QCOMPARE(job->error(), (int)KIO::ERR_FILE_ALREADY_EXIST); QVERIFY(QFile::remove(dest)); } void JobTest::multiGet() { const int numFiles = 10; const QString baseDir = homeTmpDir(); const QList urls = createManyFiles(baseDir, numFiles); QCOMPARE(urls.count(), numFiles); //qDebug() << file; KIO::MultiGetJob *job = KIO::multi_get(0, urls.at(0), KIO::MetaData()); // TODO: missing KIO::HideProgressInfo QSignalSpy spyData(job, SIGNAL(data(long,QByteArray))); QSignalSpy spyMimeType(job, SIGNAL(mimetype(long,QString))); QSignalSpy spyResultId(job, SIGNAL(result(long))); QSignalSpy spyResult(job, SIGNAL(result(KJob*))); job->setUiDelegate(nullptr); for (int i = 1; i < numFiles; ++i) { const QUrl url = urls.at(i); job->get(i, url, KIO::MetaData()); } //connect(job, &KIO::MultiGetJob::result, [=] (long id) { qDebug() << "ID I got" << id;}); //connect(job, &KJob::result, [this](KJob* ) {qDebug() << "END";}); bool ok = job->exec(); QVERIFY(ok); QCOMPARE(spyResult.count(), 1); QCOMPARE(spyResultId.count(), numFiles); QCOMPARE(spyMimeType.count(), numFiles); QCOMPARE(spyData.count(), numFiles * 2); for (int i = 0; i < numFiles; ++i) { QCOMPARE(spyResultId.at(i).at(0).toInt(), i); QCOMPARE(spyMimeType.at(i).at(0).toInt(), i); QCOMPARE(spyMimeType.at(i).at(1).toString(), QStringLiteral("text/plain")); QCOMPARE(spyData.at(i * 2).at(0).toInt(), i); QCOMPARE(QString(spyData.at(i * 2).at(1).toByteArray()), QStringLiteral("Hello")); QCOMPARE(spyData.at(i * 2 + 1).at(0).toInt(), i); QCOMPARE(QString(spyData.at(i * 2 + 1).at(1).toByteArray()), QLatin1String("")); } } diff --git a/autotests/jobtest.h b/autotests/jobtest.h index 20806680..9d9e2d82 100644 --- a/autotests/jobtest.h +++ b/autotests/jobtest.h @@ -1,145 +1,145 @@ /* This file is part of the KDE project Copyright (C) 2004 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 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. */ #ifndef JOBTEST_H #define JOBTEST_H -#include -#include +#include +#include #include class JobTest : public QObject { Q_OBJECT public: JobTest() {} private Q_SLOTS: void initTestCase(); void cleanupTestCase(); // Local tests (kio_file only) void storedGet(); void put(); void storedPut(); void storedPutIODevice(); void storedPutIODeviceFile(); void storedPutIODeviceTempFile(); void storedPutIODeviceFastDevice(); void storedPutIODeviceSlowDevice(); void storedPutIODeviceSlowDeviceBigChunk(); void copyFileToSamePartition(); void copyDirectoryToSamePartition(); void copyDirectoryToExistingDirectory(); void copyFileToOtherPartition(); void copyDirectoryToOtherPartition(); void copyRelativeSymlinkToSamePartition(); void copyAbsoluteSymlinkToOtherPartition(); void copyFolderWithUnaccessibleSubfolder(); void copyDataUrl(); void suspendFileCopy(); void suspendCopy(); void listRecursive(); void listFile(); void killJob(); void killJobBeforeStart(); void deleteJobBeforeStart(); void directorySize(); void directorySizeError(); void moveFileToSamePartition(); void moveDirectoryToSamePartition(); void moveDirectoryIntoItself(); void moveFileToOtherPartition(); void moveSymlinkToOtherPartition(); void moveDirectoryToOtherPartition(); void moveFileNoPermissions(); void moveDirectoryNoPermissions(); void deleteFile(); void deleteDirectory(); void deleteSymlink(); void deleteManyDirs(); void deleteManyFilesIndependently(); void deleteManyFilesTogether(); void rmdirEmpty(); void rmdirNotEmpty(); void stat(); #ifndef Q_OS_WIN void statSymlink(); #endif void mostLocalUrl(); void chmodFile(); #ifdef Q_OS_UNIX void chmodSticky(); #endif void chmodFileError(); void mimeType(); void mimeTypeError(); void calculateRemainingSeconds(); void moveFileDestAlreadyExists(); void moveDestAlreadyExistsAutoRename_data(); void moveDestAlreadyExistsAutoRename(); void moveAndOverwrite(); void moveOverSymlinkToSelf(); void createSymlink(); void createSymlinkTargetDirDoesntExist(); void createSymlinkAsShouldSucceed(); void createSymlinkAsShouldFailDirectoryExists(); void createSymlinkAsShouldFailFileExists(); void createSymlinkWithOverwriteShouldWork(); void createBrokenSymlink(); // Remote tests //void copyFileToSystem(); void getInvalidUrl(); void multiGet(); Q_SIGNALS: void exitLoop(); protected Q_SLOTS: void slotEntries(KIO::Job *, const KIO::UDSEntryList &lst); void slotGetResult(KJob *); void slotDataReq(KIO::Job *, QByteArray &); void slotResult(KJob *); void slotMimetype(KIO::Job *, const QString &); private: void enterLoop(); enum { AlreadyExists = 1 }; void copyLocalFile(const QString &src, const QString &dest); void copyLocalDirectory(const QString &src, const QString &dest, int flags = 0); void moveLocalFile(const QString &src, const QString &dest); void moveLocalDirectory(const QString &src, const QString &dest); //void copyFileToSystem( bool resolve_local_urls ); void deleteSymlink(bool using_fast_path); void deleteManyDirs(bool using_fast_path); void deleteManyFilesTogether(bool using_fast_path); void moveDestAlreadyExistsAutoRename(const QString &destDir, bool moveDirs); int m_result; QByteArray m_data; QStringList m_names; int m_dataReqCount; QString m_mimetype; }; #endif diff --git a/autotests/kacltest.cpp b/autotests/kacltest.cpp index 0d5e223c..4fab4724 100644 --- a/autotests/kacltest.cpp +++ b/autotests/kacltest.cpp @@ -1,221 +1,221 @@ /* This file is part of the KDE project Copyright (C) 2005 Till Adam 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 "kacltest.h" -#include +#include #include #include #include #include // The code comes partly from kdebase/kioslave/trash/testtrash.cpp QTEST_MAIN(KACLTest) static const QString s_testACL(QStringLiteral("user::rw-\nuser:bin:rwx\ngroup::rw-\nmask::rwx\nother::r--\n")); static const QString s_testACL2(QStringLiteral("user::rwx\nuser:bin:rwx\ngroup::rw-\ngroup:users:r--\ngroup:audio:--x\nmask::r-x\nother::r--\n")); static const QString s_testACLEffective(QStringLiteral("user::rwx\nuser:bin:rwx #effective:r-x\ngroup::rw- #effective:r--\ngroup:audio:--x\ngroup:users:r--\nmask::r-x\nother::r--\n")); KACLTest::KACLTest() : m_acl(s_testACL) { } void KACLTest::initTestCase() { #if !HAVE_POSIX_ACL QSKIP("ACL support not compiled"); #endif m_acl2.setACL(s_testACL2); } void KACLTest::testAsString() { QCOMPARE(m_acl.asString(), s_testACL); } void KACLTest::testSetACL() { QCOMPARE(m_acl2.asString().simplified(), s_testACLEffective.simplified()); } void KACLTest::testGetOwnerPermissions() { QCOMPARE(int(m_acl.ownerPermissions()), 6); } void KACLTest::testGetOwningGroupPermissions() { QCOMPARE(int(m_acl.owningGroupPermissions()), 6); } void KACLTest::testGetOthersPermissions() { QCOMPARE(int(m_acl.othersPermissions()), 4); } void KACLTest::testGetMaskPermissions() { bool exists = false; int mask = m_acl.maskPermissions(exists); QVERIFY(exists); QCOMPARE(mask, 7); } void KACLTest::testGetAllUserPermissions() { ACLUserPermissionsList list = m_acl.allUserPermissions(); ACLUserPermissionsConstIterator it = list.constBegin(); QString name; int permissions = 0; int count = 0; while (it != list.constEnd()) { name = (*it).first; permissions = (*it).second; ++it; ++count; } QCOMPARE(count, 1); QCOMPARE(name, QStringLiteral("bin")); QCOMPARE(permissions, 7); } void KACLTest::testGetAllGroupsPermissions() { ACLGroupPermissionsList list = m_acl2.allGroupPermissions(); ACLGroupPermissionsConstIterator it = list.constBegin(); QString name; int permissions; int count = 0; while (it != list.constEnd()) { name = (*it).first; permissions = (*it).second; // setACL sorts them alphabetically ... if (count == 0) { QCOMPARE(name, QStringLiteral("audio")); QCOMPARE(permissions, 1); } else if (count == 1) { QCOMPARE(name, QStringLiteral("users")); QCOMPARE(permissions, 4); } ++it; ++count; } QCOMPARE(count, 2); } void KACLTest::testIsExtended() { KACL dukeOfMonmoth(s_testACL); QVERIFY(dukeOfMonmoth.isExtended()); KACL earlOfUpnor(QStringLiteral("user::r--\ngroup::r--\nother::r--\n")); QVERIFY(!earlOfUpnor.isExtended()); } void KACLTest::testOperators() { KACL dukeOfMonmoth(s_testACL); KACL JamesScott(s_testACL); KACL earlOfUpnor(s_testACL2); QVERIFY(!(dukeOfMonmoth == earlOfUpnor)); QVERIFY(dukeOfMonmoth != earlOfUpnor); QVERIFY(dukeOfMonmoth != earlOfUpnor); QVERIFY(!(dukeOfMonmoth != JamesScott)); } void KACLTest::testSettingBasic() { KACL CharlesII(s_testACL); CharlesII.setOwnerPermissions(7); // clearly CharlesII.setOwningGroupPermissions(0); CharlesII.setOthersPermissions(0); QCOMPARE(int(CharlesII.ownerPermissions()), 7); QCOMPARE(int(CharlesII.owningGroupPermissions()), 0); QCOMPARE(int(CharlesII.othersPermissions()), 0); } void KACLTest::testSettingExtended() { KACL CharlesII(s_testACL); CharlesII.setMaskPermissions(7); // clearly bool dummy = false; QCOMPARE(int(CharlesII.maskPermissions(dummy)), 7); const QString expected(QStringLiteral("user::rw-\nuser:root:rwx\nuser:bin:r--\ngroup::rw-\nmask::rwx\nother::r--\n")); ACLUserPermissionsList users; ACLUserPermissions user = qMakePair(QStringLiteral("root"), (unsigned short)7); users.append(user); user = qMakePair(QStringLiteral("bin"), (unsigned short)4); users.append(user); CharlesII.setAllUserPermissions(users); QCOMPARE(CharlesII.asString(), expected); CharlesII.setACL(s_testACL); // reset // it already has an entry for bin, let's change it CharlesII.setNamedUserPermissions(QStringLiteral("bin"), 4); CharlesII.setNamedUserPermissions(QStringLiteral("root"), 7); QCOMPARE(CharlesII.asString(), expected); // groups, all and named const QString expected2(QStringLiteral("user::rw-\nuser:bin:rwx\ngroup::rw-\ngroup:audio:-wx\ngroup:users:r--\nmask::rwx\nother::r--\n")); CharlesII.setACL(s_testACL); // reset ACLGroupPermissionsList groups; ACLGroupPermissions group = qMakePair(QStringLiteral("audio"), (unsigned short)3); groups.append(group); group = qMakePair(QStringLiteral("users"), (unsigned short)4); groups.append(group); CharlesII.setAllGroupPermissions(groups); QCOMPARE(CharlesII.asString(), expected2); CharlesII.setACL(s_testACL); // reset CharlesII.setNamedGroupPermissions(QStringLiteral("audio"), 3); CharlesII.setNamedGroupPermissions(QStringLiteral("users"), 4); QCOMPARE(CharlesII.asString(), expected2); } void KACLTest::testSettingErrorHandling() { KACL foo(s_testACL); bool v = foo.setNamedGroupPermissions(QStringLiteral("audio"), 7); // existing group QVERIFY(v); v = foo.setNamedGroupPermissions(QStringLiteral("jongel"), 7); // non-existing group QVERIFY(!v); v = foo.setNamedUserPermissions(QStringLiteral("bin"), 7); // existing user QVERIFY(v); v = foo.setNamedUserPermissions(QStringLiteral("jongel"), 7); // non-existing user QVERIFY(!v); } void KACLTest::testNewMask() { KACL CharlesII(QStringLiteral("user::rw-\ngroup::rw-\nother::rw\n")); bool dummy = false; CharlesII.maskPermissions(dummy); QVERIFY(!dummy); CharlesII.setMaskPermissions(6); QCOMPARE(int(CharlesII.maskPermissions(dummy)), 6); QVERIFY(dummy); // mask exists now } diff --git a/autotests/kacltest.h b/autotests/kacltest.h index 9f4451ad..f2e8ea18 100644 --- a/autotests/kacltest.h +++ b/autotests/kacltest.h @@ -1,54 +1,54 @@ /* This file is part of the KDE project Copyright (C) 2005 Till Adam 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. */ #ifndef KACLTEST_H #define KACLTEST_H -#include +#include #include class KACLTest : public QObject { Q_OBJECT public: KACLTest(); private Q_SLOTS: void initTestCase(); void testAsString(); void testSetACL(); void testGetOwnerPermissions(); void testGetOwningGroupPermissions(); void testGetOthersPermissions(); void testGetMaskPermissions(); void testGetAllUserPermissions(); void testGetAllGroupsPermissions(); void testIsExtended(); void testOperators(); void testSettingBasic(); void testSettingExtended(); void testSettingErrorHandling(); void testNewMask(); private: KACL m_acl; KACL m_acl2; }; #endif diff --git a/autotests/kcookiejar/kcookiejartest.cpp b/autotests/kcookiejar/kcookiejartest.cpp index 14d12a16..95d4bfaa 100644 --- a/autotests/kcookiejar/kcookiejartest.cpp +++ b/autotests/kcookiejar/kcookiejartest.cpp @@ -1,320 +1,320 @@ /* This file is part of KDE Copyright (C) 2004 Waldo Bastian (bastian@kde.org) Copyright 2008 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 as published by the Free Software Foundation; either version 2, or (at your option) version 3. This software 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 library; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include -#include +#include +#include -#include +#include #include #include #include "../../src/ioslaves/http/kcookiejar/kcookiejar.cpp" static KCookieJar *jar; static QString *lastYear; static QString *nextYear; static KConfig *config = nullptr; static int windowId = 1234; // random number to be used as windowId for test cookies static void FAIL(const QString &msg) { qWarning("%s", msg.toLocal8Bit().data()); exit(1); } static void popArg(QString &command, QString &line) { int i = line.indexOf(' '); if (i != -1) { command = line.left(i); line = line.mid(i + 1); } else { command = line; line.clear(); } } static void clearConfig() { delete config; QString file = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + "kcookiejar-testconfig"; QFile::remove(file); config = new KConfig(file); KConfigGroup cg(config, "Cookie Policy"); cg.writeEntry("RejectCrossDomainCookies", false); cg.writeEntry("AcceptSessionCookies", false); cg.writeEntry("CookieGlobalAdvice", "Ask"); jar->loadConfig(config, false); } static void clearCookies(bool sessionOnly = false) { if (sessionOnly) { jar->eatSessionCookies(windowId); } else { jar->eatAllCookies(); } } static void saveCookies() { QString file = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + "kcookiejar-testcookies"; QFile::remove(file); jar->saveCookies(file); // Add an empty domain to the cookies file, just for testing robustness QFile f(file); f.open(QIODevice::Append); f.write("[]\n \"\" \"/\" 1584320400 0 h 4 x\n"); f.close(); delete jar; jar = new KCookieJar(); clearConfig(); jar->loadCookies(file); } static void endSession() { jar->eatSessionCookies(windowId); } static void processCookie(QString &line) { QString policy; popArg(policy, line); KCookieAdvice expectedAdvice = KCookieJar::strToAdvice(policy); if (expectedAdvice == KCookieDunno) { FAIL(QStringLiteral("Unknown accept policy '%1'").arg(policy)); } QString urlStr; popArg(urlStr, line); QUrl url(urlStr); if (!url.isValid()) { FAIL(QStringLiteral("Invalid URL '%1'").arg(urlStr)); } if (url.isEmpty()) { FAIL(QStringLiteral("Missing URL")); } line.replace(QLatin1String("%LASTYEAR%"), *lastYear); line.replace(QLatin1String("%NEXTYEAR%"), *nextYear); KHttpCookieList list = jar->makeCookies(urlStr, line.toUtf8(), windowId); if (list.isEmpty()) { FAIL(QStringLiteral("Failed to make cookies from: '%1'").arg(line)); } for (KHttpCookieList::iterator cookieIterator = list.begin(); cookieIterator != list.end(); ++cookieIterator) { KHttpCookie &cookie = *cookieIterator; const KCookieAdvice cookieAdvice = jar->cookieAdvice(cookie); if (cookieAdvice != expectedAdvice) FAIL(urlStr + QStringLiteral("\n'%2'\nGot advice '%3' expected '%4'") .arg(line, KCookieJar::adviceToStr(cookieAdvice), KCookieJar::adviceToStr(expectedAdvice)) ); jar->addCookie(cookie); } } static void processCheck(QString &line) { QString urlStr; popArg(urlStr, line); QUrl url(urlStr); if (!url.isValid()) { FAIL(QStringLiteral("Invalid URL '%1'").arg(urlStr)); } if (url.isEmpty()) { FAIL(QStringLiteral("Missing URL")); } QString expectedCookies = line; QString cookies = jar->findCookies(urlStr, false, windowId, nullptr).trimmed(); if (cookies != expectedCookies) FAIL(urlStr + QStringLiteral("\nGot '%1' expected '%2'") .arg(cookies, expectedCookies)); } static void processClear(QString &line) { if (line == QLatin1String("CONFIG")) { clearConfig(); } else if (line == QLatin1String("COOKIES")) { clearCookies(); } else if (line == QLatin1String("SESSIONCOOKIES")) { clearCookies(true); } else { FAIL(QStringLiteral("Unknown command 'CLEAR %1'").arg(line)); } } static void processConfig(QString &line) { QString key; popArg(key, line); if (key.isEmpty()) { FAIL(QStringLiteral("Missing Key")); } KConfigGroup cg(config, "Cookie Policy"); cg.writeEntry(key, line); jar->loadConfig(config, false); } static void processLine(QString line) { if (line.isEmpty()) { return; } if (line[0] == '#') { if (line[1] == '#') { qDebug("%s", line.toLatin1().constData()); } return; } QString command; popArg(command, line); if (command.isEmpty()) { return; } if (command == QLatin1String("COOKIE")) { processCookie(line); } else if (command == QLatin1String("CHECK")) { processCheck(line); } else if (command == QLatin1String("CLEAR")) { processClear(line); } else if (command == QLatin1String("CONFIG")) { processConfig(line); } else if (command == QLatin1String("SAVE")) { saveCookies(); } else if (command == QLatin1String("ENDSESSION")) { endSession(); } else { FAIL(QStringLiteral("Unknown command '%1'").arg(command)); } } static void runRegression(const QString &filename) { FILE *file = QT_FOPEN(QFile::encodeName(filename).constData(), "r"); if (!file) { FAIL(QStringLiteral("Can't open '%1'").arg(filename)); } char buf[4096]; while (fgets(buf, sizeof(buf), file)) { int l = strlen(buf); if (l) { l--; buf[l] = 0; } processLine(QString::fromUtf8(buf)); } fclose(file); qDebug("%s OK", filename.toLocal8Bit().data()); } class KCookieJarTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase() { QStandardPaths::enableTestMode(true); jar = new KCookieJar; QDateTime dt = QDateTime::currentDateTime(); lastYear = new QString(dt.addYears(-1).toString(Qt::RFC2822Date)); nextYear = new QString(dt.addYears(1).toString(Qt::RFC2822Date)); } void testCookieFile_data() { QTest::addColumn("fileName"); QTest::newRow("cookie.test") << QFINDTESTDATA("cookie.test"); QTest::newRow("cookie_rfc.test") << QFINDTESTDATA("cookie_rfc.test"); QTest::newRow("cookie_saving.test") << QFINDTESTDATA("cookie_saving.test"); QTest::newRow("cookie_settings.test") << QFINDTESTDATA("cookie_settings.test"); QTest::newRow("cookie_session.test") << QFINDTESTDATA("cookie_session.test"); } void testCookieFile() { QFETCH(QString, fileName); clearConfig(); runRegression(fileName); } void testParseUrl_data() { QTest::addColumn("url"); QTest::addColumn("expectedResult"); QTest::addColumn("expectedFqdn"); QTest::addColumn("expectedPath"); QTest::newRow("empty") << "" << false << "" << ""; QTest::newRow("url with no path") << "http://bugs.kde.org" << true << "bugs.kde.org" << "/"; QTest::newRow("url with path") << "http://bugs.kde.org/foo" << true << "bugs.kde.org" << "/foo"; QTest::newRow("just a host") << "bugs.kde.org" << false << "" << ""; } void testParseUrl() { QFETCH(QString, url); QFETCH(bool, expectedResult); QFETCH(QString, expectedFqdn); QFETCH(QString, expectedPath); QString fqdn; QString path; bool result = KCookieJar::parseUrl(url, fqdn, path); QCOMPARE(result, expectedResult); QCOMPARE(fqdn, expectedFqdn); QCOMPARE(path, expectedPath); } void testExtractDomains_data() { QTest::addColumn("fqdn"); QTest::addColumn("expectedDomains"); QTest::newRow("empty") << "" << (QStringList() << QStringLiteral("localhost")); QTest::newRow("ipv4") << "1.2.3.4" << (QStringList() << QStringLiteral("1.2.3.4")); QTest::newRow("ipv6") << "[fe80::213:d3ff:fef4:8c92]" << (QStringList() << QStringLiteral("[fe80::213:d3ff:fef4:8c92]")); QTest::newRow("bugs.kde.org") << "bugs.kde.org" << (QStringList() << QStringLiteral("bugs.kde.org") << QStringLiteral(".bugs.kde.org") << QStringLiteral("kde.org") << QStringLiteral(".kde.org")); } void testExtractDomains() { QFETCH(QString, fqdn); QFETCH(QStringList, expectedDomains); KCookieJar jar; QStringList lst; jar.extractDomains(fqdn, lst); QCOMPARE(lst, expectedDomains); } }; QTEST_MAIN(KCookieJarTest) #include "kcookiejartest.moc" diff --git a/autotests/kdirlistertest.h b/autotests/kdirlistertest.h index 45889244..7f5d398c 100644 --- a/autotests/kdirlistertest.h +++ b/autotests/kdirlistertest.h @@ -1,151 +1,151 @@ /* This file is part of the KDE project Copyright (C) 2007 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 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. */ #ifndef KDIRLISTERTEST_H #define KDIRLISTERTEST_H #include -#include +#include #include -#include +#include #include -#include +#include Q_DECLARE_METATYPE(KFileItemList) class GlobalInits { public: GlobalInits() { // Must be done before the QSignalSpys connect qRegisterMetaType(); qRegisterMetaType(); } }; class MyDirLister : public KDirLister, GlobalInits { public: MyDirLister() : spyStarted(this, SIGNAL(started(QUrl))), spyClear(this, SIGNAL(clear())), spyClearQUrl(this, SIGNAL(clear(QUrl))), spyCompleted(this, SIGNAL(completed())), spyCompletedQUrl(this, SIGNAL(completed(QUrl))), spyCanceled(this, SIGNAL(canceled())), spyCanceledQUrl(this, SIGNAL(canceled(QUrl))), spyRedirection(this, SIGNAL(redirection(QUrl))), spyItemsDeleted(this, SIGNAL(itemsDeleted(KFileItemList))) {} void clearSpies() { spyStarted.clear(); spyClear.clear(); spyClearQUrl.clear(); spyCompleted.clear(); spyCompletedQUrl.clear(); spyCanceled.clear(); spyCanceledQUrl.clear(); spyRedirection.clear(); spyItemsDeleted.clear(); } QSignalSpy spyStarted; QSignalSpy spyClear; QSignalSpy spyClearQUrl; QSignalSpy spyCompleted; QSignalSpy spyCompletedQUrl; QSignalSpy spyCanceled; QSignalSpy spyCanceledQUrl; QSignalSpy spyRedirection; QSignalSpy spyItemsDeleted; protected: void handleError(KIO::Job *job) Q_DECL_OVERRIDE; }; class KDirListerTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanup(); void testOpenUrl(); void testOpenUrlFromCache(); void testNewItems(); void testNewItemByCopy(); void testNewItemsInSymlink(); void testRefreshItems(); void testRefreshRootItem(); void testDeleteItem(); void testRenameItem(); void testRenameAndOverwrite(); void testConcurrentListing(); void testConcurrentHoldingListing(); void testConcurrentListingAndStop(); void testDeleteListerEarly(); void testOpenUrlTwice(); void testOpenUrlTwiceWithKeep(); void testOpenAndStop(); void testBug211472(); void testRenameCurrentDir(); void testRenameCurrentDirOpenUrl(); void testRedirection(); void testListEmptyDirFromCache(); void testWatchingAfterCopyJob(); void testRemoveWatchedDirectory(); void testDirPermissionChange(); void testCopyAfterListingAndMove(); // #353195 void testDeleteCurrentDir(); // must be last! protected Q_SLOTS: // 'more private than private slots' - i.e. not seen by qtestlib void exitLoop(); void slotNewItems(const KFileItemList &); void slotNewItems2(const KFileItemList &); void slotRefreshItems(const QList > &); void slotRefreshItems2(const QList > &); void slotOpenUrlOnRename(const QUrl &); Q_SIGNALS: void refreshItemsReceived(); private: void enterLoop(int exitCount = 1); int fileCount() const; QString path() const { return m_tempDir.path() + '/'; } bool waitForRefreshedItems(); void createSimpleFile(const QString &fileName); void fillDirLister2(MyDirLister &lister, const QString &path); void waitUntilMTimeChange(const QString &path); void waitUntilAfter(const QDateTime &ctime); private: int m_exitCount; QEventLoop m_eventLoop; QTemporaryDir m_tempDir; MyDirLister m_dirLister; KFileItemList m_items; KFileItemList m_items2; QList > m_refreshedItems, m_refreshedItems2; }; #endif diff --git a/autotests/kdirmodeltest.h b/autotests/kdirmodeltest.h index 3dfa3935..bbc8c534 100644 --- a/autotests/kdirmodeltest.h +++ b/autotests/kdirmodeltest.h @@ -1,117 +1,117 @@ /* This file is part of the KDE project Copyright (C) 2006 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 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. */ #ifndef KDIRMODELTEST_H #define KDIRMODELTEST_H -#include +#include #include -#include +#include #include -#include -#include +#include +#include // If you disable this, you need to change all exitLoop into quit in connect() statements... #define USE_QTESTEVENTLOOP class KDirModelTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void cleanup(); void testRowCount(); void testIndex(); void testNames(); void testItemForIndex(); void testIndexForItem(); void testData(); void testReload(); void testModifyFile(); void testRenameFile(); void testMoveDirectory(); void testRenameDirectory(); void testRenameDirectoryInCache(); void testChmodDirectory(); void testExpandToUrl_data(); void testExpandToUrl(); void testFilter(); void testMimeFilter(); void testShowHiddenFiles(); void testMultipleSlashes(); void testUrlWithRef(); void testRemoteUrlWithHost(); void testZipFile(); void testSmb(); void testBug196695(); void testMimeData(); void testDotHiddenFile_data(); void testDotHiddenFile(); // These tests must be done last void testDeleteFile(); void testDeleteFileWhileListing(); void testOverwriteFileWithDir(); void testDeleteFiles(); void testRenameFileToHidden(); void testDeleteDirectory(); void testDeleteCurrentDirectory(); // Somewhat unrelated void testQUrlHash(); protected Q_SLOTS: // 'more private than private slots' - i.e. not seen by qtestlib void slotListingCompleted(); void slotExpand(const QModelIndex &index); void slotRowsInserted(const QModelIndex &index, int, int); private: void recreateTestData(); void enterLoop(); void fillModel(bool reload, bool expectAllIndexes = true); void collectKnownIndexes(); void testMoveDirectory(const QString &srcdir); void testUpdateParentAfterExpand(); private: #ifdef USE_QTESTEVENTLOOP QTestEventLoop m_eventLoop; #else QEventLoop m_eventLoop; #endif QTemporaryDir *m_tempDir; KDirModel *m_dirModel; QModelIndex m_fileIndex; QModelIndex m_specialFileIndex; QModelIndex m_secondFileIndex; QModelIndex m_dirIndex; QModelIndex m_fileInDirIndex; QModelIndex m_fileInSubdirIndex; QStringList m_topLevelFileNames; // files only // for slotExpand QStringList m_expectedExpandSignals; int m_nextExpectedExpandSignals; // index into m_expectedExpandSignals KDirModel *m_dirModelForExpand; QUrl m_urlToExpandTo; bool m_rowsInsertedEmitted; bool m_expectRowsInserted; }; #endif diff --git a/autotests/kdiroperatortest.cpp b/autotests/kdiroperatortest.cpp index 49862c7e..0178eef0 100644 --- a/autotests/kdiroperatortest.cpp +++ b/autotests/kdiroperatortest.cpp @@ -1,139 +1,139 @@ /* This file is part of the KDE libraries Copyright (c) 2009 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 Lesser 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 +#include #include #include #include #include #include #include /** * Unit test for KDirOperator */ class KDirOperatorTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase() { // To avoid a runtime dependency on klauncher qputenv("KDE_FORK_SLAVES", "yes"); } void cleanupTestCase() { } void testNoViewConfig() { KDirOperator dirOp; // setIconsZoom tries to write config. // Make sure it won't crash if setViewConfig() isn't called dirOp.setIconsZoom(5); QCOMPARE(dirOp.iconsZoom(), 5); } void testReadConfig() { // Test: Make sure readConfig() and then setView() restores // the correct kind of view. KDirOperator *dirOp = new KDirOperator; dirOp->setView(KFile::DetailTree); dirOp->setShowHiddenFiles(true); KConfigGroup cg(KSharedConfig::openConfig(), "diroperator"); dirOp->writeConfig(cg); delete dirOp; dirOp = new KDirOperator; dirOp->readConfig(cg); dirOp->setView(KFile::Default); QVERIFY(dirOp->showHiddenFiles()); // KDirOperatorDetail inherits QTreeView, so this test should work QVERIFY(qobject_cast(dirOp->view())); delete dirOp; } /** * testBug187066 does the following: * * 1. Open a KDirOperator in kdelibs/kfile * 2. Set the current item to "file:///" * 3. Set the current item to "file:///.../kdelibs/kfile/tests/kdiroperatortest.cpp" * * This may result in a crash, see https://bugs.kde.org/show_bug.cgi?id=187066 */ void testBug187066() { const QString dir = QFileInfo(QFINDTESTDATA("kdiroperatortest.cpp")).absolutePath(); const QUrl kFileDirUrl(QUrl::fromLocalFile(dir).adjusted(QUrl::RemoveFilename)); KDirOperator dirOp(kFileDirUrl); QSignalSpy completedSpy(dirOp.dirLister(), SIGNAL(completed())); dirOp.setView(KFile::DetailTree); completedSpy.wait(1000); dirOp.setCurrentItem(QUrl(QStringLiteral("file:///"))); dirOp.setCurrentItem(QUrl::fromLocalFile(QFINDTESTDATA("kdiroperatortest.cpp"))); //completedSpy.wait(1000); QTest::qWait(1000); } void testSetUrlPathAdjustment_data() { QTest::addColumn("url"); QTest::addColumn("expectedUrl"); QTest::newRow("with_host") << QUrl(QStringLiteral("ftp://foo.com/folder")) << QUrl(QStringLiteral("ftp://foo.com/folder/")); QTest::newRow("with_no_host") << QUrl(QStringLiteral("smb://")) << QUrl(QStringLiteral("smb://")); QTest::newRow("with_host_without_path") << QUrl(QStringLiteral("ftp://user@example.com")) << QUrl(QStringLiteral("ftp://user@example.com")); } void testSetUrlPathAdjustment() { QFETCH(QUrl, url); QFETCH(QUrl, expectedUrl); KDirOperator dirOp; QSignalSpy spy(&dirOp, SIGNAL(urlEntered(QUrl))); dirOp.setUrl(url, true); QCOMPARE(spy.takeFirst().at(0).toUrl(), expectedUrl); } void testSupportedSchemes() { KDirOperator dirOp; QSignalSpy spy(&dirOp, &KDirOperator::urlEntered); QCOMPARE(dirOp.supportedSchemes(), QStringList()); dirOp.setSupportedSchemes({"file"}); QCOMPARE(dirOp.supportedSchemes(), QStringList("file")); dirOp.setUrl(QUrl("smb://foo/bar"), true); QCOMPARE(spy.count(), 0); const auto fileUrl = QUrl::fromLocalFile(QDir::homePath() + QLatin1Char('/')); dirOp.setUrl(fileUrl, true); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().at(0).toUrl(), fileUrl); } }; QTEST_MAIN(KDirOperatorTest) #include "kdiroperatortest.moc" diff --git a/autotests/kfileitemtest.h b/autotests/kfileitemtest.h index cdb6fdce..c47d673a 100644 --- a/autotests/kfileitemtest.h +++ b/autotests/kfileitemtest.h @@ -1,68 +1,68 @@ /* This file is part of the KDE project Copyright (C) 2006 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 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. */ #ifndef KFILEITEMTEST_H #define KFILEITEMTEST_H -#include +#include class KFileItemTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testPermissionsString(); void testNull(); void testDoesNotExist(); void testDetach(); void testMove(); void testBasic(); void testRootDirectory(); void testHiddenFile(); void testMimeTypeOnDemand(); void testCmp(); void testRename(); void testRefresh(); void testDotDirectory(); void testMimetypeForRemoteFolder(); void testMimetypeForRemoteFolderWithFileType(); void testCurrentMimetypeForRemoteFolder(); void testCurrentMimetypeForRemoteFolderWithFileType(); void testIconNameForCustomFolderIcons(); void testIconNameForStandardPath(); #ifndef Q_OS_WIN void testIsReadable_data(); void testIsReadable(); #endif void testDecodeFileName_data(); void testDecodeFileName(); void testEncodeFileName_data(); void testEncodeFileName(); // KFileItemListProperties tests void testListProperties_data(); void testListProperties(); // KIO global tests void testIconNameForUrl_data(); void testIconNameForUrl(); }; #endif diff --git a/autotests/kfileplacesmodeltest.cpp b/autotests/kfileplacesmodeltest.cpp index b2de2892..a1be3d1f 100644 --- a/autotests/kfileplacesmodeltest.cpp +++ b/autotests/kfileplacesmodeltest.cpp @@ -1,1308 +1,1308 @@ /* This file is part of the KDE project Copyright (C) 2007 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. 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 -#include +#include +#include #include #include #include #include #include #include #include -#include +#include #include #include Q_DECLARE_METATYPE(KFilePlacesModel::GroupType) #ifdef Q_OS_WIN //c:\ as root for windows #define KDE_ROOT_PATH "C:\\" #else #define KDE_ROOT_PATH "/" #endif // Avoid QHash randomization so that the order of the devices is stable static void seedInit() { qputenv("QT_HASH_SEED", "0"); // This env var has no effect because this comes too late. qCpuFeatures() was already called by // a Q_CONSTRUCTOR_FUNCTION inside QtGui (see image/qimage_conversions.cpp). Argh. QTBUG-47566. qputenv("QT_NO_CPU_FEATURE", "sse4.2"); } Q_CONSTRUCTOR_FUNCTION(seedInit) class KFilePlacesModelTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void testInitialState(); void testInitialList(); void testReparse(); void testInternalBookmarksHaveIds(); void testHiding(); void testMove(); void testPlacesLifecycle(); void testDevicePlugging(); void testDragAndDrop(); void testDeviceSetupTeardown(); void testEnableBaloo(); void testRemoteUrls_data(); void testRemoteUrls(); void testRefresh(); void testConvertedUrl_data(); void testConvertedUrl(); void testBookmarkObject(); void testDataChangedSignal(); void testIconRole_data(); void testIconRole(); void testMoveFunction(); void testPlaceGroupHidden(); void testPlaceGroupHiddenVsPlaceChildShown(); void testPlaceGroupHiddenAndShownWithHiddenChild(); void testPlaceGroupHiddenGroupIndexesIntegrity(); void testPlaceGroupHiddenSignal(); void testPlaceGroupHiddenRole(); void testFilterWithAlternativeApplicationName(); void testSupportedSchemes(); private: QStringList placesUrls(KFilePlacesModel *model = nullptr) const; QDBusInterface *fakeManager(); QDBusInterface *fakeDevice(const QString &udi); KFilePlacesModel *m_places; KFilePlacesModel *m_places2; // To check that they always stay in sync // actually supposed to work across processes, // but much harder to test QMap m_interfacesMap; }; static QString bookmarksFile() { return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/user-places.xbel"); } void KFilePlacesModelTest::initTestCase() { qputenv("KDE_FORK_SLAVES", "yes"); // to avoid a runtime dependency on klauncher QStandardPaths::setTestModeEnabled(true); // Ensure we'll have a clean bookmark file to start QFile::remove(bookmarksFile()); // disable baloo by default KConfig config(QStringLiteral("baloofilerc")); KConfigGroup basicSettings = config.group("Basic Settings"); basicSettings.writeEntry("Indexing-Enabled", false); config.sync(); qRegisterMetaType(); qRegisterMetaType(); const QString fakeHw = QFINDTESTDATA("fakecomputer.xml"); QVERIFY(!fakeHw.isEmpty()); qputenv("SOLID_FAKEHW", QFile::encodeName(fakeHw)); m_places = new KFilePlacesModel(); m_places2 = new KFilePlacesModel(); } void KFilePlacesModelTest::cleanupTestCase() { delete m_places; delete m_places2; qDeleteAll(m_interfacesMap); QFile::remove(bookmarksFile()); } QStringList KFilePlacesModelTest::placesUrls(KFilePlacesModel *model) const { KFilePlacesModel *currentModel = model; if (!currentModel) { currentModel = m_places; } QStringList urls; for (int row = 0; row < currentModel->rowCount(); ++row) { QModelIndex index = currentModel->index(row, 0); urls << currentModel->url(index).toDisplayString(QUrl::PreferLocalFile); } return urls; } #define CHECK_PLACES_URLS(urls) \ if (placesUrls() != urls) { \ qDebug() << "Expected:" << urls; \ qDebug() << "Got:" << placesUrls(); \ QCOMPARE(placesUrls(), urls); \ } \ for (int row = 0; row < urls.size(); ++row) { \ QModelIndex index = m_places->index(row, 0); \ \ QCOMPARE(m_places->url(index).toString(), QUrl::fromUserInput(urls[row]).toString()); \ QCOMPARE(m_places->data(index, KFilePlacesModel::UrlRole).toUrl(), \ QUrl(m_places->url(index))); \ \ index = m_places2->index(row, 0); \ \ QCOMPARE(m_places2->url(index).toString(), QUrl::fromUserInput(urls[row]).toString()); \ QCOMPARE(m_places2->data(index, KFilePlacesModel::UrlRole).toUrl(), \ QUrl(m_places2->url(index))); \ } \ \ QCOMPARE(urls.size(), m_places->rowCount()); \ QCOMPARE(urls.size(), m_places2->rowCount()); QDBusInterface *KFilePlacesModelTest::fakeManager() { return fakeDevice(QStringLiteral("/org/kde/solid/fakehw")); } QDBusInterface *KFilePlacesModelTest::fakeDevice(const QString &udi) { QDBusInterface *interface = m_interfacesMap[udi]; if (interface) { return interface; } QDBusInterface *iface = new QDBusInterface(QDBusConnection::sessionBus().baseService(), udi); m_interfacesMap[udi] = iface; return iface; } void KFilePlacesModelTest::testInitialState() { QCOMPARE(m_places->rowCount(), 4); // when the xbel file is empty, KFilePlacesModel fills it with 4 default items QCoreApplication::processEvents(); // Devices have a delayed loading QCOMPARE(m_places->rowCount(), 9); } static const QStringList initialListOfPlaces() { return QStringList() << QDir::homePath() << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/"); } static const QStringList initialListOfShared() { return QStringList() << QStringLiteral("remote:/"); } static const QStringList initialListOfDevices() { return QStringList() << QStringLiteral("/media/nfs") << QStringLiteral("/foreign"); } static const QStringList initialListOfRemovableDevices() { return QStringList() << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom"); } static const QStringList initialListOfUrls() { return QStringList() << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); } void KFilePlacesModelTest::testInitialList() { const QStringList urls = initialListOfUrls(); CHECK_PLACES_URLS(urls); } void KFilePlacesModelTest::testReparse() { QStringList urls; // add item m_places->addPlace(QStringLiteral("foo"), QUrl::fromLocalFile(QStringLiteral("/foo")), QString(), QString()); urls = initialListOfUrls(); // it will be added at the end of places section urls.insert(3, QStringLiteral("/foo")); CHECK_PLACES_URLS(urls); // reparse the bookmark file KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); bookmarkManager->notifyCompleteChange(QString()); // check if they are the same CHECK_PLACES_URLS(urls); // try to remove item m_places->removePlace(m_places->index(3, 0)); urls = initialListOfUrls(); CHECK_PLACES_URLS(urls); } void KFilePlacesModelTest::testInternalBookmarksHaveIds() { KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); KBookmarkGroup root = bookmarkManager->root(); // Verify every entry has an id or an udi KBookmark bookmark = root.first(); while (!bookmark.isNull()) { QVERIFY(!bookmark.metaDataItem(QStringLiteral("ID")).isEmpty() || !bookmark.metaDataItem(QStringLiteral("UDI")).isEmpty()); // It's mutualy exclusive though QVERIFY(bookmark.metaDataItem(QStringLiteral("ID")).isEmpty() || bookmark.metaDataItem(QStringLiteral("UDI")).isEmpty()); bookmark = root.next(bookmark); } // Verify that adding a bookmark behind its back the model gives it an id // (in real life it requires the user to modify the file by hand, // unlikely but better safe than sorry). // It induces a small race condition which means several ids will be // successively set on the same bookmark but no big deal since it won't // break the system KBookmark foo = root.addBookmark(QStringLiteral("Foo"), QUrl(QStringLiteral("file:/foo")), QStringLiteral("red-folder")); QCOMPARE(foo.text(), QStringLiteral("Foo")); QVERIFY(foo.metaDataItem(QStringLiteral("ID")).isEmpty()); bookmarkManager->emitChanged(root); QCOMPARE(foo.text(), QStringLiteral("Foo")); QVERIFY(!foo.metaDataItem(QStringLiteral("ID")).isEmpty()); // Verify that all the ids are different bookmark = root.first(); QSet ids; while (!bookmark.isNull()) { QString id; if (!bookmark.metaDataItem(QStringLiteral("UDI")).isEmpty()) { id = bookmark.metaDataItem(QStringLiteral("UDI")); } else { id = bookmark.metaDataItem(QStringLiteral("ID")); } QVERIFY2(!ids.contains(id), "Duplicated ID found!"); ids << id; bookmark = root.next(bookmark); } // Cleanup foo root.deleteBookmark(foo); bookmarkManager->emitChanged(root); } void KFilePlacesModelTest::testHiding() { // Verify that nothing is hidden for (int row = 0; row < m_places->rowCount(); ++row) { QModelIndex index = m_places->index(row, 0); QVERIFY(!m_places->isHidden(index)); } QModelIndex a = m_places->index(2, 0); QModelIndex b = m_places->index(6, 0); QList args; QSignalSpy spy(m_places, SIGNAL(dataChanged(QModelIndex,QModelIndex))); // Verify that hidden is taken into account and is not global m_places->setPlaceHidden(a, true); QVERIFY(m_places->isHidden(a)); QVERIFY(m_places->data(a, KFilePlacesModel::HiddenRole).toBool()); QVERIFY(!m_places->isHidden(b)); QVERIFY(!m_places->data(b, KFilePlacesModel::HiddenRole).toBool()); QCOMPARE(spy.count(), 1); args = spy.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), a); QCOMPARE(args.at(1).toModelIndex(), a); m_places->setPlaceHidden(b, true); QVERIFY(m_places->isHidden(a)); QVERIFY(m_places->data(a, KFilePlacesModel::HiddenRole).toBool()); QVERIFY(m_places->isHidden(b)); QVERIFY(m_places->data(b, KFilePlacesModel::HiddenRole).toBool()); QCOMPARE(spy.count(), 1); args = spy.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), b); QCOMPARE(args.at(1).toModelIndex(), b); m_places->setPlaceHidden(a, false); m_places->setPlaceHidden(b, false); QVERIFY(!m_places->isHidden(a)); QVERIFY(!m_places->data(a, KFilePlacesModel::HiddenRole).toBool()); QVERIFY(!m_places->isHidden(b)); QVERIFY(!m_places->data(b, KFilePlacesModel::HiddenRole).toBool()); QCOMPARE(spy.count(), 2); args = spy.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), a); QCOMPARE(args.at(1).toModelIndex(), a); args = spy.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), b); QCOMPARE(args.at(1).toModelIndex(), b); } void KFilePlacesModelTest::testMove() { QList args; QSignalSpy spy_inserted(m_places, SIGNAL(rowsInserted(QModelIndex,int,int))); QSignalSpy spy_removed(m_places, SIGNAL(rowsRemoved(QModelIndex,int,int))); KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); KBookmarkGroup root = bookmarkManager->root(); KBookmark system_root = m_places->bookmarkForIndex(m_places->index(1, 0)); KBookmark before_system_root = m_places->bookmarkForIndex(m_places->index(0, 0)); // Trying move the root at the end of the list, should move it to the end of places section instead // to keep it grouped KBookmark last = root.first(); while (!root.next(last).isNull()) { last = root.next(last); } root.moveBookmark(system_root, last); bookmarkManager->emitChanged(root); QStringList urls; urls << QDir::homePath() << QStringLiteral("trash:/") << QStringLiteral(KDE_ROOT_PATH) << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 2); QCOMPARE(args.at(2).toInt(), 2); QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 1); QCOMPARE(args.at(2).toInt(), 1); // Move the root at the beginning of the list root.moveBookmark(system_root, KBookmark()); bookmarkManager->emitChanged(root); urls.clear(); urls << QStringLiteral(KDE_ROOT_PATH) << QDir::homePath() << QStringLiteral("trash:/") << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 0); QCOMPARE(args.at(2).toInt(), 0); QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 3); QCOMPARE(args.at(2).toInt(), 3); // Move the root in the list (at its original place) root.moveBookmark(system_root, before_system_root); bookmarkManager->emitChanged(root); urls.clear(); urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 1); QCOMPARE(args.at(2).toInt(), 1); QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 0); QCOMPARE(args.at(2).toInt(), 0); } void KFilePlacesModelTest::testDragAndDrop() { QList args; QSignalSpy spy_moved(m_places, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int))); // Monitor rowsInserted() and rowsRemoved() to ensure they are never emitted: // Moving with drag and drop is expected to emit rowsMoved() QSignalSpy spy_inserted(m_places, SIGNAL(rowsInserted(QModelIndex,int,int))); QSignalSpy spy_removed(m_places, SIGNAL(rowsRemoved(QModelIndex,int,int))); // Move the KDE_ROOT_PATH at the end of the places list QModelIndexList indexes; indexes << m_places->index(1, 0); QMimeData *mimeData = m_places->mimeData(indexes); QVERIFY(m_places->dropMimeData(mimeData, Qt::MoveAction, 3, 0, QModelIndex())); QStringList urls; urls << QDir::homePath() << QStringLiteral("trash:/") << QStringLiteral(KDE_ROOT_PATH) << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 0); QCOMPARE(spy_removed.count(), 0); QCOMPARE(spy_moved.count(), 1); args = spy_moved.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 1); QCOMPARE(args.at(2).toInt(), 1); QCOMPARE(args.at(3).toModelIndex(), QModelIndex()); QCOMPARE(args.at(4).toInt(), 3); // Move the KDE_ROOT_PATH at the beginning of the list indexes.clear(); indexes << m_places->index(2, 0); mimeData = m_places->mimeData(indexes); QVERIFY(m_places->dropMimeData(mimeData, Qt::MoveAction, 0, 0, QModelIndex())); urls.clear(); urls << QStringLiteral(KDE_ROOT_PATH) << QDir::homePath() << QStringLiteral("trash:/") << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 0); QCOMPARE(spy_removed.count(), 0); QCOMPARE(spy_moved.count(), 1); args = spy_moved.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 2); QCOMPARE(args.at(2).toInt(), 2); QCOMPARE(args.at(3).toModelIndex(), QModelIndex()); QCOMPARE(args.at(4).toInt(), 0); // Move the KDE_ROOT_PATH in the list (at its original place) indexes.clear(); indexes << m_places->index(0, 0); mimeData = m_places->mimeData(indexes); QVERIFY(m_places->dropMimeData(mimeData, Qt::MoveAction, 2, 0, QModelIndex())); urls.clear(); urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 0); QCOMPARE(spy_removed.count(), 0); QCOMPARE(spy_moved.count(), 1); args = spy_moved.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 0); QCOMPARE(args.at(2).toInt(), 0); QCOMPARE(args.at(3).toModelIndex(), QModelIndex()); QCOMPARE(args.at(4).toInt(), 2); // Dropping on an item is not allowed indexes.clear(); indexes << m_places->index(4, 0); mimeData = m_places->mimeData(indexes); QVERIFY(!m_places->dropMimeData(mimeData, Qt::MoveAction, -1, 0, m_places->index(2, 0))); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 0); QCOMPARE(spy_removed.count(), 0); QCOMPARE(spy_moved.count(), 0); } void KFilePlacesModelTest::testPlacesLifecycle() { QList args; QSignalSpy spy_inserted(m_places, SIGNAL(rowsInserted(QModelIndex,int,int))); QSignalSpy spy_removed(m_places, SIGNAL(rowsRemoved(QModelIndex,int,int))); QSignalSpy spy_changed(m_places, SIGNAL(dataChanged(QModelIndex,QModelIndex))); m_places->addPlace(QStringLiteral("Foo"), QUrl::fromLocalFile(QStringLiteral("/home/foo"))); QStringList urls; urls << initialListOfPlaces() << QStringLiteral("/home/foo") << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 3); QCOMPARE(args.at(2).toInt(), 3); QCOMPARE(spy_removed.count(), 0); KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); KBookmarkGroup root = bookmarkManager->root(); KBookmark before_trash = m_places->bookmarkForIndex(m_places->index(1, 0)); KBookmark foo = m_places->bookmarkForIndex(m_places->index(3, 0)); root.moveBookmark(foo, before_trash); bookmarkManager->emitChanged(root); urls.clear(); urls << QDir::homePath() << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("/home/foo") << QStringLiteral("trash:/") << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 3); QCOMPARE(args.at(2).toInt(), 3); QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 2); QCOMPARE(args.at(2).toInt(), 2); m_places->editPlace(m_places->index(2, 0), QStringLiteral("Foo"), QUrl::fromLocalFile(QStringLiteral("/mnt/foo"))); urls.clear(); urls << QDir::homePath() << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("/mnt/foo") << QStringLiteral("trash:/") << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 0); QCOMPARE(spy_removed.count(), 0); QCOMPARE(spy_changed.count(), 1); args = spy_changed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), m_places->index(2, 0)); QCOMPARE(args.at(1).toModelIndex(), m_places->index(2, 0)); foo = m_places->bookmarkForIndex(m_places->index(2, 0)); foo.setFullText(QStringLiteral("Bar")); bookmarkManager->notifyCompleteChange(QString()); urls.clear(); urls << QDir::homePath() << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("/mnt/foo") << QStringLiteral("trash:/") << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 0); QCOMPARE(spy_removed.count(), 0); QCOMPARE(spy_changed.count(), 10); args = spy_changed[2]; QCOMPARE(args.at(0).toModelIndex(), m_places->index(2, 0)); QCOMPARE(args.at(1).toModelIndex(), m_places->index(2, 0)); spy_changed.clear(); m_places->removePlace(m_places->index(2, 0)); urls.clear(); urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 0); QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 2); QCOMPARE(args.at(2).toInt(), 2); m_places->addPlace(QStringLiteral("Foo"), QUrl::fromLocalFile(QStringLiteral("/home/foo")), QString(), QString(), m_places->index(0, 0)); urls.clear(); urls << QDir::homePath() << QStringLiteral("/home/foo") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 1); QCOMPARE(args.at(2).toInt(), 1); QCOMPARE(spy_removed.count(), 0); m_places->removePlace(m_places->index(1, 0)); } void KFilePlacesModelTest::testDevicePlugging() { QList args; QSignalSpy spy_inserted(m_places, SIGNAL(rowsInserted(QModelIndex,int,int))); QSignalSpy spy_removed(m_places, SIGNAL(rowsRemoved(QModelIndex,int,int))); fakeManager()->call(QStringLiteral("unplug"), "/org/kde/solid/fakehw/volume_part1_size_993284096"); QStringList urls; urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << QStringLiteral("/media/floppy0") << QStringLiteral("/media/cdrom"); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 0); QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 7); QCOMPARE(args.at(2).toInt(), 7); fakeManager()->call(QStringLiteral("plug"), "/org/kde/solid/fakehw/volume_part1_size_993284096"); urls.clear(); urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 7); QCOMPARE(args.at(2).toInt(), 7); QCOMPARE(spy_removed.count(), 0); // Move the device in the list, and check that it memorizes the position across plug/unplug KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); KBookmarkGroup root = bookmarkManager->root(); KBookmark before_floppy; KBookmark device = root.first(); // The device we'll move is the 7th bookmark for (int i = 0; i < 6; i++) { if (i == 3) { // store item before to be able to move it back to original position device = before_floppy = root.next(device); } else { device = root.next(device); } } root.moveBookmark(device, before_floppy); bookmarkManager->emitChanged(root); urls.clear(); urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/cdrom"); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 7); QCOMPARE(args.at(2).toInt(), 7); QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 6); QCOMPARE(args.at(2).toInt(), 6); fakeManager()->call(QStringLiteral("unplug"), "/org/kde/solid/fakehw/volume_part1_size_993284096"); urls.clear(); urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << QStringLiteral("/media/floppy0") << QStringLiteral("/media/cdrom"); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 0); QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 6); QCOMPARE(args.at(2).toInt(), 6); fakeManager()->call(QStringLiteral("plug"), "/org/kde/solid/fakehw/volume_part1_size_993284096"); urls.clear(); urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/cdrom"); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 6); QCOMPARE(args.at(2).toInt(), 6); QCOMPARE(spy_removed.count(), 0); KBookmark seventh = root.first(); for (int i = 0; i < 6; i++) { seventh = root.next(seventh); } root.moveBookmark(device, seventh); bookmarkManager->emitChanged(root); urls.clear(); urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 7); QCOMPARE(args.at(2).toInt(), 7); QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), 6); QCOMPARE(args.at(2).toInt(), 6); } void KFilePlacesModelTest::testDeviceSetupTeardown() { QList args; QSignalSpy spy_changed(m_places, SIGNAL(dataChanged(QModelIndex,QModelIndex))); fakeDevice(QStringLiteral("/org/kde/solid/fakehw/volume_part1_size_993284096/StorageAccess"))->call(QStringLiteral("teardown")); QCOMPARE(spy_changed.count(), 1); args = spy_changed.takeFirst(); QCOMPARE(args.at(0).toModelIndex().row(), 7); QCOMPARE(args.at(1).toModelIndex().row(), 7); fakeDevice(QStringLiteral("/org/kde/solid/fakehw/volume_part1_size_993284096/StorageAccess"))->call(QStringLiteral("setup")); QCOMPARE(spy_changed.count(), 1); args = spy_changed.takeFirst(); QCOMPARE(args.at(0).toModelIndex().row(), 7); QCOMPARE(args.at(1).toModelIndex().row(), 7); } void KFilePlacesModelTest::testEnableBaloo() { KConfig config(QStringLiteral("baloofilerc")); KConfigGroup basicSettings = config.group("Basic Settings"); basicSettings.writeEntry("Indexing-Enabled", true); config.sync(); KFilePlacesModel places_with_baloo; QStringList urls; for (int row = 0; row < places_with_baloo.rowCount(); ++row) { QModelIndex index = places_with_baloo.index(row, 0); urls << places_with_baloo.url(index).toDisplayString(QUrl::PreferLocalFile); } QVERIFY(urls.contains("timeline:/today")); QVERIFY(urls.contains("timeline:/yesterday")); QVERIFY(urls.contains("timeline:/thismonth")); QVERIFY(urls.contains("timeline:/lastmonth")); QVERIFY(urls.contains("search:/documents")); QVERIFY(urls.contains("search:/images")); QVERIFY(urls.contains("search:/audio")); QVERIFY(urls.contains("search:/videos")); } void KFilePlacesModelTest::testRemoteUrls_data() { QTest::addColumn("url"); QTest::addColumn("expectedRow"); QTest::addColumn("expectedGroup"); QTest::newRow("Ftp") << QUrl(QStringLiteral("ftp://192.168.1.1/ftp")) << 4 << QStringLiteral("Remote"); QTest::newRow("Samba") << QUrl(QStringLiteral("smb://192.168.1.1/share")) << 4 << QStringLiteral("Remote"); QTest::newRow("Sftp") << QUrl(QStringLiteral("sftp://192.168.1.1/share")) << 4 << QStringLiteral("Remote"); QTest::newRow("Fish") << QUrl(QStringLiteral("fish://192.168.1.1/share")) << 4 << QStringLiteral("Remote"); QTest::newRow("Webdav") << QUrl(QStringLiteral("webdav://192.168.1.1/share")) << 4 << QStringLiteral("Remote"); } void KFilePlacesModelTest::testRemoteUrls() { QFETCH(QUrl, url); QFETCH(int, expectedRow); QFETCH(QString, expectedGroup); QList args; QSignalSpy spy_inserted(m_places, SIGNAL(rowsInserted(QModelIndex,int,int))); // insert a new network url m_places->addPlace(QStringLiteral("My Shared"), url, QString(), QString(), QModelIndex()); // check if url list is correct after insertion QStringList urls; urls << QDir::homePath() << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") // places << QStringLiteral("remote:/") << url.toString() << QStringLiteral("/media/nfs") << QStringLiteral("/foreign") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom"); CHECK_PLACES_URLS(urls); // check if the new url was inserted in the right position (end of "Remote" section) QTRY_COMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); QCOMPARE(args.at(1).toInt(), expectedRow); QCOMPARE(args.at(2).toInt(), expectedRow); // check if the new url has the right group "Remote" const QModelIndex index = m_places->index(expectedRow, 0); QCOMPARE(index.data(KFilePlacesModel::GroupRole).toString(), expectedGroup); m_places->removePlace(index); } void KFilePlacesModelTest::testRefresh() { KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); KBookmarkGroup root = bookmarkManager->root(); KBookmark homePlace = root.first(); const QModelIndex homePlaceIndex = m_places->index(0, 0); QCOMPARE(m_places->text(homePlaceIndex), homePlace.fullText()); // modify bookmark homePlace.setFullText("Test change the text"); QVERIFY(m_places->text(homePlaceIndex) != homePlace.fullText()); // reload bookmark data m_places->refresh(); QCOMPARE(m_places->text(homePlaceIndex), homePlace.fullText()); } void KFilePlacesModelTest::testConvertedUrl_data() { QTest::addColumn("url"); QTest::addColumn("expectedUrl"); // places QTest::newRow("Places - Home") << QUrl::fromLocalFile(QDir::homePath()) << QUrl::fromLocalFile(QDir::homePath()); // baloo -search const QString jsonQuery(QStringLiteral("{\"dayFilter\": 0,\ \"monthFilter\": 0, \ \"yearFilter\": 0, \ \"type\": [ \"Document\"]}")); QUrl url; url.setScheme(QStringLiteral("baloosearch")); QUrlQuery urlQuery; urlQuery.addQueryItem(QStringLiteral("json"), jsonQuery.simplified()); url.setQuery(urlQuery); QTest::newRow("Baloo - Documents") << QUrl("search:/documents") << url; // baloo - timeline const QDate lastMonthDate = QDate::currentDate().addMonths(-1); QTest::newRow("Baloo - Last Month") << QUrl("timeline:/lastmonth") << QUrl(QString("timeline:/%1-%2").arg(lastMonthDate.year()).arg(lastMonthDate.month(), 2, 10, QLatin1Char('0'))); // devices QTest::newRow("Devices - Floppy") << QUrl("file:///media/floppy0") << QUrl("file:///media/floppy0"); } void KFilePlacesModelTest::testConvertedUrl() { QFETCH(QUrl, url); QFETCH(QUrl, expectedUrl); const QUrl convertedUrl = KFilePlacesModel::convertedUrl(url); QCOMPARE(convertedUrl.scheme(), expectedUrl.scheme()); QCOMPARE(convertedUrl.path(), expectedUrl.path()); QCOMPARE(convertedUrl, expectedUrl); } void KFilePlacesModelTest::testBookmarkObject() { //make sure that all items return a valid bookmark for (int row = 0; row < m_places->rowCount(); row++) { const QModelIndex index = m_places->index(row, 0); const KBookmark bookmark = m_places->bookmarkForIndex(index); QVERIFY(!bookmark.isNull()); } } void KFilePlacesModelTest::testDataChangedSignal() { QSignalSpy dataChangedSpy(m_places, &KFilePlacesModel::dataChanged); const QModelIndex index = m_places->index(1, 0); const KBookmark bookmark = m_places->bookmarkForIndex(index); // call function with the same data m_places->editPlace(index, bookmark.fullText(), bookmark.url(), bookmark.icon(), bookmark.metaDataItem(QStringLiteral("OnlyInApp"))); QCOMPARE(dataChangedSpy.count(), 0); // call function with different data const QString originalText = bookmark.fullText(); m_places->editPlace(index, QStringLiteral("My text"), bookmark.url(), bookmark.icon(), bookmark.metaDataItem(QStringLiteral("OnlyInApp"))); QCOMPARE(dataChangedSpy.count(), 1); QList args = dataChangedSpy.takeFirst(); QCOMPARE(args.at(0).toModelIndex().row(), 1); QCOMPARE(args.at(0).toModelIndex().column(), 0); QCOMPARE(args.at(1).toModelIndex().row(), 1); QCOMPARE(args.at(1).toModelIndex().column(), 0); QCOMPARE(m_places->text(index), QStringLiteral("My text")); // restore original value dataChangedSpy.clear(); m_places->editPlace(index, originalText, bookmark.url(), bookmark.icon(), bookmark.metaDataItem(QStringLiteral("OnlyInApp"))); QCOMPARE(dataChangedSpy.count(), 1); } void KFilePlacesModelTest::testIconRole_data() { QTest::addColumn("index"); QTest::addColumn("expectedIconName"); // places QTest::newRow("Places - Home") << m_places->index(0, 0) << QStringLiteral("user-home"); QTest::newRow("Places - Root") << m_places->index(1, 0) << QStringLiteral("folder-red"); QTest::newRow("Places - Trash") << m_places->index(2, 0) << QStringLiteral("user-trash-full"); QTest::newRow("Remote - Network") << m_places->index(3, 0) << QStringLiteral("network-workgroup"); QTest::newRow("Devices - Nfs") << m_places->index(4, 0) << QStringLiteral("hwinfo"); QTest::newRow("Devices - foreign") << m_places->index(5, 0) << QStringLiteral("blockdevice"); QTest::newRow("Devices - Floppy") << m_places->index(6, 0) << QStringLiteral("blockdevice"); QTest::newRow("Devices - cdrom") << m_places->index(7, 0) << QStringLiteral("blockdevice"); } void KFilePlacesModelTest::testIconRole() { QFETCH(QModelIndex, index); QFETCH(QString, expectedIconName); QCOMPARE(index.data(KFilePlacesModel::IconNameRole).toString(), expectedIconName); } void KFilePlacesModelTest::testMoveFunction() { QList args; QStringList urls = initialListOfUrls(); QSignalSpy rowsMoved(m_places, &KFilePlacesModel::rowsMoved); // move item 0 to pos 2 QVERIFY(m_places->movePlace(0, 3)); urls.move(0, 2); QTRY_COMPARE(rowsMoved.count(), 1); args = rowsMoved.takeFirst(); QCOMPARE(args.at(1).toInt(), 0); // start QCOMPARE(args.at(2).toInt(), 0); // end QCOMPARE(args.at(4).toInt(), 3); // row (destination) QCOMPARE(placesUrls(), urls); rowsMoved.clear(); // move it back QVERIFY(m_places->movePlace(2, 0)); urls.move(2, 0); QTRY_COMPARE(rowsMoved.count(), 1); args = rowsMoved.takeFirst(); QCOMPARE(args.at(1).toInt(), 2); // start QCOMPARE(args.at(2).toInt(), 2); // end QCOMPARE(args.at(4).toInt(), 0); // row (destination) QCOMPARE(placesUrls(), urls); rowsMoved.clear(); // target position is greater than model rows // will move to the end of the first group QVERIFY(m_places->movePlace(0, 20)); urls.move(0, 2); QTRY_COMPARE(rowsMoved.count(), 1); args = rowsMoved.takeFirst(); QCOMPARE(args.at(1).toInt(), 0); // start QCOMPARE(args.at(2).toInt(), 0); // end QCOMPARE(args.at(4).toInt(), 3); // row (destination) QCOMPARE(placesUrls(), urls); rowsMoved.clear(); // move it back QVERIFY(m_places->movePlace(2, 0)); urls.move(2, 0); QTRY_COMPARE(rowsMoved.count(), 1); args = rowsMoved.takeFirst(); QCOMPARE(args.at(1).toInt(), 2); // start QCOMPARE(args.at(2).toInt(), 2); // end QCOMPARE(args.at(4).toInt(), 0); // row (destination) QCOMPARE(placesUrls(), urls); rowsMoved.clear(); QVERIFY(m_places->movePlace(8, 6)); urls.move(8, 6); QTRY_COMPARE(rowsMoved.count(), 1); args = rowsMoved.takeFirst(); QCOMPARE(args.at(1).toInt(), 8); // start QCOMPARE(args.at(2).toInt(), 8); // end QCOMPARE(args.at(4).toInt(), 6); // row (destination) QCOMPARE(placesUrls(), urls); rowsMoved.clear(); // move it back QVERIFY(m_places->movePlace(6, 9)); urls.move(6, 8); QTRY_COMPARE(rowsMoved.count(), 1); args = rowsMoved.takeFirst(); QCOMPARE(args.at(1).toInt(), 6); // start QCOMPARE(args.at(2).toInt(), 6); // end QCOMPARE(args.at(4).toInt(), 9); // row (destination) QCOMPARE(placesUrls(), urls); rowsMoved.clear(); //use a invalid start position QVERIFY(!m_places->movePlace(100, 20)); QCOMPARE(rowsMoved.count(), 0); //use same start and target position QVERIFY(!m_places->movePlace(1, 1)); QCOMPARE(rowsMoved.count(), 0); } void KFilePlacesModelTest::testPlaceGroupHidden() { // GIVEN QCOMPARE(m_places->hiddenCount(), 0); QStringList urls; urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QVector indexesHidden; // WHEN m_places->setGroupHidden(KFilePlacesModel::PlacesType, true); // THEN for (int row = 0; row < m_places->rowCount(); ++row) { QModelIndex index = m_places->index(row, 0); if (m_places->groupType(index) == KFilePlacesModel::PlacesType) { QVERIFY(m_places->isHidden(index)); indexesHidden << index; } } QCOMPARE(indexesHidden.count(), initialListOfPlaces().size()); QCOMPARE(m_places->hiddenCount(), indexesHidden.size()); // and GIVEN QVector indexesShown; // WHEN m_places->setGroupHidden(KFilePlacesModel::PlacesType, false); // THEN for (int row = 0; row < m_places->rowCount(); ++row) { QModelIndex index = m_places->index(row, 0); if (m_places->groupType(index) == KFilePlacesModel::PlacesType) { QVERIFY(!m_places->isHidden(index)); indexesShown << index; } } QCOMPARE(m_places->hiddenCount(), 0); QCOMPARE(indexesShown.count(), initialListOfPlaces().size()); } void KFilePlacesModelTest::testPlaceGroupHiddenVsPlaceChildShown() { // GIVEN for (int row = 0; row < m_places->rowCount(); ++row) { QModelIndex index = m_places->index(row, 0); QVERIFY(!m_places->isHidden(index)); } m_places->setGroupHidden(KFilePlacesModel::PlacesType, true); QModelIndex firstIndex = m_places->index(0,0); const int amountOfPlaces = initialListOfPlaces().size(); for (int row = 0; row < amountOfPlaces; ++row) { QModelIndex index = m_places->index(row, 0); QVERIFY(m_places->isHidden(index)); } // WHEN m_places->setPlaceHidden(firstIndex, false); // THEN QVERIFY(m_places->isHidden(firstIndex)); // a child cannot show against its parent state // leaving in a clean state m_places->setGroupHidden(KFilePlacesModel::PlacesType, false); } void KFilePlacesModelTest::testPlaceGroupHiddenAndShownWithHiddenChild() { // GIVEN QCOMPARE(m_places->hiddenCount(), 0); QStringList urls; urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); CHECK_PLACES_URLS(urls); QModelIndex firstIndexHidden = m_places->index(0,0); m_places->setPlaceHidden(firstIndexHidden, true); // first place index is hidden within an hidden parent m_places->setGroupHidden(KFilePlacesModel::PlacesType, true); QCOMPARE(m_places->hiddenCount(), initialListOfPlaces().size()); // WHEN m_places->setGroupHidden(KFilePlacesModel::PlacesType, false); // THEN QVector indexesShown; for (int row = 0; row < m_places->rowCount(); ++row) { QModelIndex index = m_places->index(row, 0); if (index == firstIndexHidden) { QVERIFY(m_places->isHidden(firstIndexHidden)); continue; } if (m_places->groupType(index) == KFilePlacesModel::PlacesType) { QVERIFY(!m_places->isHidden(index)); indexesShown << index; } } QCOMPARE(m_places->hiddenCount(), 1); QCOMPARE(indexesShown.count(), initialListOfPlaces().size() - 1 /*first child remains hidden*/); // leaving in a clean state m_places->setPlaceHidden(firstIndexHidden, false); } void KFilePlacesModelTest::testPlaceGroupHiddenGroupIndexesIntegrity() { // GIVEN m_places->setGroupHidden(KFilePlacesModel::PlacesType, true); QVERIFY(m_places->groupIndexes(KFilePlacesModel::UnknownType).isEmpty()); QVERIFY(m_places->isGroupHidden(KFilePlacesModel::PlacesType)); QCOMPARE(m_places->groupIndexes(KFilePlacesModel::PlacesType).count(), initialListOfPlaces().count()); QCOMPARE(m_places->groupIndexes(KFilePlacesModel::RecentlySavedType).count(), 0); QCOMPARE(m_places->groupIndexes(KFilePlacesModel::SearchForType).count(), 0); QCOMPARE(m_places->groupIndexes(KFilePlacesModel::DevicesType).count(), initialListOfDevices().count()); QCOMPARE(m_places->groupIndexes(KFilePlacesModel::RemovableDevicesType).count(), initialListOfRemovableDevices().count()); //WHEN m_places->setGroupHidden(KFilePlacesModel::PlacesType, false); // THEN // Make sure that hidden place group doesn't change model QVERIFY(!m_places->isGroupHidden(KFilePlacesModel::PlacesType)); QCOMPARE(m_places->groupIndexes(KFilePlacesModel::PlacesType).count(), initialListOfPlaces().count()); QCOMPARE(m_places->groupIndexes(KFilePlacesModel::RecentlySavedType).count(), 0); QCOMPARE(m_places->groupIndexes(KFilePlacesModel::SearchForType).count(), 0); QCOMPARE(m_places->groupIndexes(KFilePlacesModel::DevicesType).count(), initialListOfDevices().count()); QCOMPARE(m_places->groupIndexes(KFilePlacesModel::RemovableDevicesType).count(), initialListOfRemovableDevices().count()); } void KFilePlacesModelTest::testPlaceGroupHiddenSignal() { QSignalSpy groupHiddenSignal(m_places, &KFilePlacesModel::groupHiddenChanged); m_places->setGroupHidden(KFilePlacesModel::SearchForType, true); // hide SearchForType group QTRY_COMPARE(groupHiddenSignal.count(), 1); QList args = groupHiddenSignal.takeFirst(); QCOMPARE(args.at(0).toInt(), static_cast(KFilePlacesModel::SearchForType)); QCOMPARE(args.at(1).toBool(), true); groupHiddenSignal.clear(); // try hide SearchForType which is already hidden m_places->setGroupHidden(KFilePlacesModel::SearchForType, true); QCOMPARE(groupHiddenSignal.count(), 0); // show SearchForType group m_places->setGroupHidden(KFilePlacesModel::SearchForType, false); QTRY_COMPARE(groupHiddenSignal.count(), 1); args = groupHiddenSignal.takeFirst(); QCOMPARE(args.at(0).toInt(), static_cast(KFilePlacesModel::SearchForType)); QCOMPARE(args.at(1).toBool(), false); } void KFilePlacesModelTest::testPlaceGroupHiddenRole() { // on startup all groups are visible for (int r = 0, rMax = m_places->rowCount(); r < rMax; r++) { const QModelIndex index = m_places->index(r, 0); QCOMPARE(index.data(KFilePlacesModel::GroupHiddenRole).toBool(), false); } // set SearchFor group hidden m_places->setGroupHidden(KFilePlacesModel::SearchForType, true); for (auto groupType : {KFilePlacesModel::PlacesType, KFilePlacesModel::RemoteType, KFilePlacesModel::RecentlySavedType, KFilePlacesModel::SearchForType, KFilePlacesModel::DevicesType, KFilePlacesModel::RemovableDevicesType}) { const bool groupShouldBeHidden = (groupType == KFilePlacesModel::SearchForType); for (auto index : m_places->groupIndexes(groupType)) { QCOMPARE(index.data(KFilePlacesModel::GroupHiddenRole).toBool(), groupShouldBeHidden); } } // set SearchFor group visible again m_places->setGroupHidden(KFilePlacesModel::SearchForType, false); for (int r = 0, rMax = m_places->rowCount(); r < rMax; r++) { const QModelIndex index = m_places->index(r, 0); QCOMPARE(index.data(KFilePlacesModel::GroupHiddenRole).toBool(), false); } } void KFilePlacesModelTest::testFilterWithAlternativeApplicationName() { const QStringList urls = initialListOfUrls(); const QString alternativeApplicationName = QStringLiteral("kfile_places_model_test"); KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); KBookmarkGroup root = bookmarkManager->root(); // create a new entry with alternative application name KBookmark bookmark = root.addBookmark(QStringLiteral("Extra entry"), QUrl(QStringLiteral("search:/videos-alternative")), {}); const QString id = QUuid::createUuid().toString(); bookmark.setMetaDataItem(QStringLiteral("ID"), id); bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), alternativeApplicationName); bookmarkManager->emitChanged(bookmarkManager->root()); // make sure that the entry is not visible on the original model CHECK_PLACES_URLS(urls); // create a new model with alternativeApplicationName KFilePlacesModel *newModel = new KFilePlacesModel(alternativeApplicationName); QTRY_COMPARE(placesUrls(newModel).count(QStringLiteral("search:/videos-alternative")), 1); delete newModel; } void KFilePlacesModelTest::testSupportedSchemes() { QCoreApplication::processEvents(); // support running this test on its own QCOMPARE(m_places->supportedSchemes(), QStringList()); QCOMPARE(placesUrls(), initialListOfUrls()); m_places->setSupportedSchemes({"trash"}); QCOMPARE(m_places->supportedSchemes(), QStringList("trash")); QCOMPARE(placesUrls(), QStringList("trash:/")); m_places->setSupportedSchemes({}); QCOMPARE(m_places->supportedSchemes(), QStringList()); QCOMPARE(placesUrls(), initialListOfUrls()); } QTEST_MAIN(KFilePlacesModelTest) #include "kfileplacesmodeltest.moc" diff --git a/autotests/kiotesthelper.h b/autotests/kiotesthelper.h index aa79b15c..470e0fa2 100644 --- a/autotests/kiotesthelper.h +++ b/autotests/kiotesthelper.h @@ -1,205 +1,205 @@ /* This file is part of the KDE project Copyright (C) 2006 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 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. */ // This file can only be included once in a given binary #include -#include +#include #include #include #include #ifdef Q_OS_UNIX #include #else #include #endif #include #include "kioglobal_p.h" QString homeTmpDir() { const QString dir(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/kiotests/"); if (!QFile::exists(dir)) { const bool ok = QDir().mkpath(dir); if (!ok) { qFatal("Couldn't create %s", qPrintable(dir)); } } return dir; } static QDateTime s_referenceTimeStamp; static void setTimeStamp(const QString &path, const QDateTime &mtime) { #ifdef Q_OS_UNIX // Put timestamp in the past so that we can check that the listing is correct struct utimbuf utbuf; utbuf.actime = mtime.toTime_t(); utbuf.modtime = utbuf.actime; utime(QFile::encodeName(path), &utbuf); //qDebug( "Time changed for %s", qPrintable( path ) ); #elif defined(Q_OS_WIN) struct _utimbuf utbuf; utbuf.actime = mtime.toTime_t(); utbuf.modtime = utbuf.actime; _wutime(reinterpret_cast(path.utf16()), &utbuf); #endif } static void createTestFile(const QString &path, bool plainText = false) { QDir().mkpath(QFileInfo(path).absolutePath()); QFile f(path); if (!f.open(QIODevice::WriteOnly)) { qFatal("Couldn't create %s", qPrintable(path)); } QByteArray data(plainText ? "Hello world" : "Hello\0world", 11); QCOMPARE(data.size(), 11); f.write(data); f.close(); setTimeStamp(path, s_referenceTimeStamp); } static void createTestSymlink(const QString &path, const QByteArray &target = "/IDontExist") { QFile::remove(path); bool ok = KIOPrivate::createSymlink(target, path); // broken symlink if (!ok) { qFatal("couldn't create symlink: %s", strerror(errno)); } QT_STATBUF buf; QVERIFY(QT_LSTAT(QFile::encodeName(path), &buf) == 0); QVERIFY((buf.st_mode & QT_STAT_MASK) == QT_STAT_LNK); //qDebug( "symlink %s created", qPrintable( path ) ); QVERIFY(QFileInfo(path).isSymLink()); } enum CreateTestDirectoryOptions { DefaultOptions = 0, NoSymlink = 1 }; static inline void createTestDirectory(const QString &path, CreateTestDirectoryOptions opt = DefaultOptions) { QDir dir; bool ok = dir.mkdir(path); if (!ok && !dir.exists()) { qFatal("Couldn't create %s", qPrintable(path)); } createTestFile(path + "/testfile"); if ((opt & NoSymlink) == 0) { #ifndef Q_OS_WIN createTestSymlink(path + "/testlink"); QVERIFY(QFileInfo(path + "/testlink").isSymLink()); #else // to not change the filecount everywhere in the tests createTestFile(path + "/testlink"); #endif } setTimeStamp(path, s_referenceTimeStamp); } #include class PredefinedAnswerJobUiDelegate : public KIO::JobUiDelegateExtension { public: PredefinedAnswerJobUiDelegate() : JobUiDelegateExtension(), m_askFileRenameCalled(0), m_askSkipCalled(0), m_askDeleteCalled(0), m_messageBoxCalled(0), m_renameResult(KIO::R_SKIP), m_skipResult(KIO::S_SKIP), m_deleteResult(false), m_messageBoxResult(0) { } KIO::RenameDialog_Result askFileRename(KJob *job, const QString &caption, const QUrl &src, const QUrl &dest, KIO::RenameDialog_Options options, QString &newDest, KIO::filesize_t = (KIO::filesize_t) - 1, KIO::filesize_t = (KIO::filesize_t) - 1, const QDateTime & = QDateTime(), const QDateTime & = QDateTime(), const QDateTime & = QDateTime(), const QDateTime & = QDateTime()) Q_DECL_OVERRIDE { Q_UNUSED(job) Q_UNUSED(caption) Q_UNUSED(src) Q_UNUSED(dest) Q_UNUSED(options) Q_UNUSED(newDest) ++m_askFileRenameCalled; return m_renameResult; } KIO::SkipDialog_Result askSkip(KJob *job, KIO::SkipDialog_Options options, const QString &error_text) Q_DECL_OVERRIDE { Q_UNUSED(job) Q_UNUSED(options) Q_UNUSED(error_text) ++m_askSkipCalled; return m_skipResult; } bool askDeleteConfirmation(const QList &urls, DeletionType deletionType, ConfirmationType confirmationType) Q_DECL_OVERRIDE { Q_UNUSED(urls); Q_UNUSED(deletionType); Q_UNUSED(confirmationType); ++m_askDeleteCalled; return m_deleteResult; } int requestMessageBox(MessageBoxType type, const QString &text, const QString &caption, const QString &buttonYes, const QString &buttonNo, const QString &iconYes = QString(), const QString &iconNo = QString(), const QString &dontAskAgainName = QString(), const KIO::MetaData &sslMetaData = KIO::MetaData()) Q_DECL_OVERRIDE { Q_UNUSED(type); Q_UNUSED(text); Q_UNUSED(caption); Q_UNUSED(buttonYes); Q_UNUSED(buttonNo); Q_UNUSED(iconYes); Q_UNUSED(iconNo); Q_UNUSED(dontAskAgainName); Q_UNUSED(sslMetaData); ++m_messageBoxCalled; return m_messageBoxResult; } // yeah, public, for get and reset. int m_askFileRenameCalled; int m_askSkipCalled; int m_askDeleteCalled; int m_messageBoxCalled; KIO::RenameDialog_Result m_renameResult; KIO::SkipDialog_Result m_skipResult; bool m_deleteResult; int m_messageBoxResult; }; diff --git a/autotests/klocalsocketservertest.cpp b/autotests/klocalsocketservertest.cpp index 4ce05b71..b57afc19 100644 --- a/autotests/klocalsocketservertest.cpp +++ b/autotests/klocalsocketservertest.cpp @@ -1,311 +1,311 @@ /* * This file is part of the KDE libraries * Copyright (C) 2007 Thiago Macieira * * 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 "klocalsocketservertest.h" #include #include #include -#include +#include #include "klocalsocket.h" static const char afile[] = "/tmp/afile"; static const char asocket[] = "/tmp/asocket"; tst_KLocalSocketServer::tst_KLocalSocketServer() { QFile f(QFile::encodeName(afile)); f.open(QIODevice::ReadWrite | QIODevice::Truncate); } tst_KLocalSocketServer::~tst_KLocalSocketServer() { QFile::remove(afile); } class TimedConnection: public QThread { Q_OBJECT public: ~TimedConnection() { wait(); } protected: void run() Q_DECL_OVERRIDE { KLocalSocket socket; QThread::usleep(200); socket.connectToPath(asocket); socket.waitForConnected(); } }; void tst_KLocalSocketServer::cleanup() { QFile::remove(asocket); } void tst_KLocalSocketServer::listen_data() { QTest::addColumn("path"); QTest::addColumn("success"); QTest::newRow("null") << QString() << false; QTest::newRow("empty") << "" << false; QTest::newRow("a-dir") << "/tmp/" << false; QTest::newRow("not-a-dir") << QString(afile + QLatin1String("/foo")) << false; QTest::newRow("not-permitted") << "/root/foo" << false; QTest::newRow("valid") << asocket << true; } void tst_KLocalSocketServer::listen() { QFETCH(QString, path); KLocalSocketServer server; QTEST(server.listen(path), "success"); } void tst_KLocalSocketServer::waitForConnection() { KLocalSocketServer server; QVERIFY(server.listen(asocket)); QVERIFY(!server.hasPendingConnections()); { KLocalSocket socket; socket.connectToPath(asocket); QVERIFY(socket.waitForConnected()); // make sure we can accept that connection QVERIFY(server.waitForNewConnection()); QVERIFY(server.hasPendingConnections()); delete server.nextPendingConnection(); } // test a timeout now QVERIFY(!server.hasPendingConnections()); QVERIFY(!server.waitForNewConnection(0)); QVERIFY(!server.waitForNewConnection(200)); { // now try a timed connection TimedConnection conn; conn.start(); QVERIFY(server.waitForNewConnection(500)); QVERIFY(server.hasPendingConnections()); delete server.nextPendingConnection(); } } void tst_KLocalSocketServer::newConnection() { KLocalSocketServer server; QVERIFY(server.listen(asocket)); QVERIFY(!server.hasPendingConnections()); // catch the signal QSignalSpy spy(&server, SIGNAL(newConnection())); KLocalSocket socket; socket.connectToPath(asocket); QVERIFY(socket.waitForConnected()); // let the events be processed QTest::qWait(100); QVERIFY(spy.count() == 1); } void tst_KLocalSocketServer::accept() { KLocalSocketServer server; QVERIFY(server.listen(asocket)); QVERIFY(!server.hasPendingConnections()); KLocalSocket socket; socket.connectToPath(asocket); QVERIFY(socket.waitForConnected()); QVERIFY(server.waitForNewConnection()); QVERIFY(server.hasPendingConnections()); KLocalSocket *socket2 = server.nextPendingConnection(); QVERIFY(!server.hasPendingConnections()); QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); QCOMPARE(socket2->state(), QAbstractSocket::ConnectedState); delete socket2; } void tst_KLocalSocketServer::state() { KLocalSocketServer server; // sanity check of the initial state: QVERIFY(!server.isListening()); QVERIFY(server.localPath().isEmpty()); QCOMPARE(int(server.localSocketType()), int(KLocalSocket::UnknownLocalSocketType)); QVERIFY(!server.hasPendingConnections()); QVERIFY(!server.nextPendingConnection()); // it's not connected, so it shouldn't change timedOut bool timedOut = true; QVERIFY(!server.waitForNewConnection(0, &timedOut)); QVERIFY(timedOut); timedOut = false; QVERIFY(!server.waitForNewConnection(0, &timedOut)); QVERIFY(!timedOut); // start listening: QVERIFY(server.listen(asocket)); QVERIFY(server.isListening()); QCOMPARE(server.localPath(), QString(asocket)); QCOMPARE(int(server.localSocketType()), int(KLocalSocket::UnixSocket)); QVERIFY(!server.hasPendingConnections()); QVERIFY(!server.nextPendingConnection()); // it must timeout now: timedOut = false; QVERIFY(!server.waitForNewConnection(0, &timedOut)); QVERIFY(timedOut); // make a connection: KLocalSocket socket; socket.connectToPath(asocket); QVERIFY(socket.waitForConnected()); // it mustn't time out now: timedOut = true; QVERIFY(server.waitForNewConnection(0, &timedOut)); QVERIFY(!timedOut); QVERIFY(server.hasPendingConnections()); KLocalSocket *socket2 = server.nextPendingConnection(); QVERIFY(socket2); delete socket2; // close: server.close(); // verify state: QVERIFY(!server.isListening()); QVERIFY(server.localPath().isEmpty()); QCOMPARE(int(server.localSocketType()), int(KLocalSocket::UnknownLocalSocketType)); QVERIFY(!server.hasPendingConnections()); QVERIFY(!server.nextPendingConnection()); } void tst_KLocalSocketServer::setMaxPendingConnections() { KLocalSocketServer server; QVERIFY(server.listen(asocket)); QVERIFY(!server.hasPendingConnections()); server.setMaxPendingConnections(0); // we don't want to receive // check if the event loop won't cause a connection to accepted KLocalSocket socket; socket.connectToPath(asocket); QTest::qWait(100); // 100 ms doing absolutely nothing QVERIFY(!server.hasPendingConnections()); // now check if we get that conenction server.setMaxPendingConnections(1); QTest::qWait(100); QVERIFY(server.hasPendingConnections()); delete server.nextPendingConnection(); QVERIFY(socket.waitForDisconnected()); // check if we receive only one of the two pending connections KLocalSocket socket2; socket.connectToPath(asocket); socket2.connectToPath(asocket); QTest::qWait(100); QVERIFY(server.hasPendingConnections()); delete server.nextPendingConnection(); QVERIFY(!server.hasPendingConnections()); QVERIFY(!server.nextPendingConnection()); } void tst_KLocalSocketServer::abstractUnixSocket_data() { #ifndef Q_OS_LINUX QSKIP("Abstract UNIX sockets are specific for Linux"); #endif QTest::addColumn("path"); QTest::addColumn("success"); QTest::newRow("null") << QString() << false; QTest::newRow("empty") << "" << false; #if 0 // apparently, we are allowed to put sockets there, even if we don't have permission to QTest::newRow("a-dir") << "/tmp/" << false; QTest::newRow("not-a-dir") << afile + QLatin1String("/foo") << false; QTest::newRow("not-permitted") << "/root/foo" << false; #endif QTest::newRow("valid") << asocket << true; } void tst_KLocalSocketServer::abstractUnixSocket() { QFETCH(QString, path); QFETCH(bool, success); if (success) { QVERIFY(!QFile::exists(path)); } KLocalSocketServer server; QCOMPARE(server.listen(path, KLocalSocket::AbstractUnixSocket), success); if (success) { // the socket must not exist in the filesystem QVERIFY(!QFile::exists(path)); // now try to connect to it KLocalSocket socket; socket.connectToPath(path, KLocalSocket::AbstractUnixSocket); QVERIFY(socket.waitForConnected(100)); QVERIFY(server.waitForNewConnection(100)); QVERIFY(server.hasPendingConnections()); // the socket must still not exist in the filesystem QVERIFY(!QFile::exists(path)); // verify that they can exchange data too: KLocalSocket *socket2 = server.nextPendingConnection(); QByteArray data("Hello"); socket2->write(data); QVERIFY(socket2->bytesToWrite() == 0 || socket2->waitForBytesWritten(100)); QVERIFY(socket.waitForReadyRead(100)); QCOMPARE(socket.read(data.length()), data); socket.write(data); QVERIFY(socket.bytesToWrite() == 0 || socket.waitForBytesWritten(100)); QVERIFY(socket2->waitForReadyRead(100)); QCOMPARE(socket2->read(data.length()), data); delete socket2; QVERIFY(socket.waitForDisconnected(100)); } } QTEST_MAIN(tst_KLocalSocketServer) #include "klocalsocketservertest.moc" diff --git a/autotests/klocalsocketservertest.h b/autotests/klocalsocketservertest.h index 748fa657..285812a4 100644 --- a/autotests/klocalsocketservertest.h +++ b/autotests/klocalsocketservertest.h @@ -1,51 +1,51 @@ /* * This file is part of the KDE libraries * Copyright (C) 2007 Thiago Macieira * * 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. */ #ifndef KLOCALSOCKETSERVERTEST_H #define KLOCALSOCKETSERVERTEST_H -#include +#include class tst_KLocalSocketServer : public QObject { Q_OBJECT public: tst_KLocalSocketServer(); ~tst_KLocalSocketServer(); private Q_SLOTS: void cleanup(); void listen_data(); void listen(); void waitForConnection(); void newConnection(); void accept(); void state(); void setMaxPendingConnections(); void abstractUnixSocket_data(); void abstractUnixSocket(); }; #endif diff --git a/autotests/klocalsockettest.cpp b/autotests/klocalsockettest.cpp index 4d6c7dda..95e161c0 100644 --- a/autotests/klocalsockettest.cpp +++ b/autotests/klocalsockettest.cpp @@ -1,249 +1,249 @@ /* * This file is part of the KDE libraries * Copyright (C) 2007 Thiago Macieira * * 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 "klocalsockettest.h" -#include +#include -#include -#include -#include +#include +#include +#include #include "klocalsocket.h" static const char socketpath[] = "/tmp/testsocket"; tst_KLocalSocket::tst_KLocalSocket() { server = nullptr; QFile::remove(QFile::decodeName(socketpath)); } tst_KLocalSocket::~tst_KLocalSocket() { delete server; QFile::remove(QFile::decodeName(socketpath)); } #include class TimedTest: public QThread { Q_OBJECT public: KLocalSocket *socket; TimedTest(KLocalSocket *s) : socket(s) { } ~TimedTest() { wait(1000); } void run() Q_DECL_OVERRIDE { QThread::usleep(100000); socket->write("Hello, World!", 13); socket->waitForBytesWritten(); QThread::usleep(100000); socket->close(); delete socket; } }; void tst_KLocalSocket::initTestCase() { server = new KLocalSocketServer(this); QVERIFY(server->listen(socketpath)); } void tst_KLocalSocket::connection_data() { QTest::addColumn("path"); QTest::newRow("null-path") << QString(); QTest::newRow("empty-path") << ""; QTest::newRow("directory") << "/tmp"; QTest::newRow("directory2") << "/tmp/"; QTest::newRow("non-existing") << "/tmp/nonexistingsocket"; QTest::newRow("real") << socketpath; } void tst_KLocalSocket::connection() { QFETCH(QString, path); KLocalSocket socket; socket.connectToPath(path); bool shouldSucceed = path == socketpath; QCOMPARE(socket.waitForConnected(1000), shouldSucceed); if (shouldSucceed) { QVERIFY(server->waitForNewConnection()); delete server->nextPendingConnection(); } else { qDebug() << socket.errorString(); } } void tst_KLocalSocket::waitFor() { KLocalSocket socket; socket.connectToPath(socketpath); QVERIFY(socket.waitForConnected(1000)); QVERIFY(server->waitForNewConnection()); // now accept: KLocalSocket *socket2 = server->nextPendingConnection(); // start thread: TimedTest thr(socket2); socket2->setParent(nullptr); socket2->moveToThread(&thr); thr.start(); QVERIFY(socket.waitForReadyRead(500)); QByteArray data = socket.read(512); QVERIFY(socket.waitForDisconnected(500)); } void tst_KLocalSocket::reading() { static const char data1[] = "Hello ", data2[] = "World"; KLocalSocket socket; socket.connectToPath(socketpath); QVERIFY(socket.waitForConnected(1000)); QVERIFY(server->waitForNewConnection()); // now accept and write something: KLocalSocket *socket2 = server->nextPendingConnection(); socket2->write(data1, sizeof data1 - 1); QVERIFY(socket2->bytesToWrite() == 0 || socket2->waitForBytesWritten(200)); QVERIFY(socket.waitForReadyRead(200)); QByteArray read = socket.read(sizeof data1 - 1); QCOMPARE(read.length(), int(sizeof data1) - 1); QCOMPARE(read.constData(), data1); // write data2 socket2->write(data2, sizeof data2 - 1); QVERIFY(socket2->bytesToWrite() == 0 || socket2->waitForBytesWritten(200)); QVERIFY(socket.waitForReadyRead(200)); read = socket.read(sizeof data2 - 1); QCOMPARE(read.length(), int(sizeof data2) - 1); QCOMPARE(read.constData(), data2); delete socket2; } void tst_KLocalSocket::writing() { static const char data1[] = "Hello ", data2[] = "World"; KLocalSocket socket; socket.connectToPath(socketpath); QVERIFY(socket.waitForConnected(1000)); QVERIFY(server->waitForNewConnection()); // now accept and write something: KLocalSocket *socket2 = server->nextPendingConnection(); QCOMPARE(socket.write(data1, sizeof data1 - 1), Q_INT64_C(sizeof data1 - 1)); QVERIFY(socket.bytesToWrite() == 0 || socket.waitForBytesWritten(100)); QVERIFY(socket2->waitForReadyRead()); QByteArray read = socket2->read(sizeof data1 - 1); QCOMPARE(read.length(), int(sizeof data1) - 1); QCOMPARE(read.constData(), data1); // write data2 QCOMPARE(socket.write(data2, sizeof data2 - 1), Q_INT64_C(sizeof data2 - 1)); QVERIFY(socket.bytesToWrite() == 0 || socket.waitForBytesWritten(100)); QVERIFY(socket2->waitForReadyRead()); read = socket2->read(sizeof data2 - 1); QCOMPARE(read.length(), int(sizeof data2) - 1); QCOMPARE(read.constData(), data2); delete socket2; } void tst_KLocalSocket::state() { KLocalSocket socket; // sanity check: QCOMPARE(int(socket.localSocketType()), int(KLocalSocket::UnknownLocalSocketType)); QVERIFY(socket.localPath().isEmpty()); QVERIFY(socket.peerPath().isEmpty()); QCOMPARE(int(socket.state()), int(QAbstractSocket::UnconnectedState)); // now connect and accept socket.connectToPath(socketpath); QVERIFY(socket.waitForConnected(1000)); QVERIFY(server->waitForNewConnection()); KLocalSocket *socket2 = server->nextPendingConnection(); QCOMPARE(socket.peerPath(), QString(socketpath)); QCOMPARE(socket2->localPath(), QString(socketpath)); QCOMPARE(int(socket.state()), int(QAbstractSocket::ConnectedState)); QCOMPARE(int(socket2->state()), int(QAbstractSocket::ConnectedState)); QCOMPARE(int(socket.localSocketType()), int(KLocalSocket::UnixSocket)); QCOMPARE(int(socket2->localSocketType()), int(KLocalSocket::UnixSocket)); // now close one of the sockets: socket.close(); // it must have reset its state: QCOMPARE(int(socket.localSocketType()), int(KLocalSocket::UnknownLocalSocketType)); QVERIFY(socket.peerPath().isEmpty()); QCOMPARE(int(socket.state()), int(QAbstractSocket::UnconnectedState)); // but the other one mustn't have yet: QCOMPARE(int(socket2->state()), int(QAbstractSocket::ConnectedState)); QVERIFY(!socket2->localPath().isEmpty()); QCOMPARE(int(socket2->localSocketType()), int(KLocalSocket::UnixSocket)); // wait for disconnected: QVERIFY(socket2->waitForDisconnected()); // now it must have: QCOMPARE(int(socket2->state()), int(QAbstractSocket::UnconnectedState)); QVERIFY(socket2->localPath().isEmpty()); QCOMPARE(int(socket2->localSocketType()), int(KLocalSocket::UnknownLocalSocketType)); delete socket2; } void tst_KLocalSocket::connected() { KLocalSocket socket; socket.connectToPath(socketpath); QEXPECT_FAIL("", "Will fix later", Continue); QVERIFY(!socket.isOpen()); QSignalSpy spy(&socket, SIGNAL(connected())); QTest::qWait(100); QEXPECT_FAIL("", "Will fix later", Continue); QCOMPARE(spy.count(), 1); } QTEST_MAIN(tst_KLocalSocket) #include "klocalsockettest.moc" \ No newline at end of file diff --git a/autotests/klocalsockettest.h b/autotests/klocalsockettest.h index a76bf9ea..00668d6d 100644 --- a/autotests/klocalsockettest.h +++ b/autotests/klocalsockettest.h @@ -1,48 +1,48 @@ /* * This file is part of the KDE libraries * Copyright (C) 2007 Thiago Macieira * * 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. */ #ifndef KLOCALSOCKETTEST_H #define KLOCALSOCKETTEST_H -#include +#include class KLocalSocketServer; class tst_KLocalSocket : public QObject { Q_OBJECT public: KLocalSocketServer *server; tst_KLocalSocket(); ~tst_KLocalSocket(); private Q_SLOTS: void initTestCase(); void connection_data(); void connection(); void waitFor(); void reading(); void writing(); void state(); void connected(); }; #endif diff --git a/autotests/kmountpointtest.cpp b/autotests/kmountpointtest.cpp index 35fc2a37..041be159 100644 --- a/autotests/kmountpointtest.cpp +++ b/autotests/kmountpointtest.cpp @@ -1,134 +1,134 @@ /* * Copyright (C) 2006 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 "kmountpointtest.h" -#include +#include #include "kmountpoint.h" #include #include QTEST_MAIN(KMountPointTest) void KMountPointTest::initTestCase() { } void KMountPointTest::testCurrentMountPoints() { const KMountPoint::List mountPoints = KMountPoint::currentMountPoints(KMountPoint::NeedRealDeviceName); if (mountPoints.isEmpty()) { // can happen in chroot jails QSKIP("mtab is empty"); return; } KMountPoint::Ptr mountWithDevice; foreach (KMountPoint::Ptr mountPoint, mountPoints) { qDebug() << "Mount: " << mountPoint->mountedFrom() << " (" << mountPoint->realDeviceName() << ") " << mountPoint->mountPoint() << " " << mountPoint->mountType() << endl; QVERIFY(!mountPoint->mountedFrom().isEmpty()); QVERIFY(!mountPoint->mountPoint().isEmpty()); QVERIFY(!mountPoint->mountType().isEmpty()); // old bug, happened because KMountPoint called KStandardDirs::realPath instead of realFilePath if (mountPoint->realDeviceName().startsWith(QLatin1String("/dev"))) { // skip this check for cifs mounts for instance QVERIFY(!mountPoint->realDeviceName().endsWith('/')); } // keep one (any) mountpoint with a device name for the test below if (!mountPoint->realDeviceName().isEmpty() && !mountWithDevice) { mountWithDevice = mountPoint; } } if (!mountWithDevice) { // This happens on build.kde.org (LXC virtualization, mtab points to non-existing device paths) qWarning() << "Couldn't find any mountpoint with a valid device?"; } else { // Check findByDevice KMountPoint::Ptr found = mountPoints.findByDevice(mountWithDevice->mountedFrom()); QVERIFY(found); QCOMPARE(found->mountPoint(), mountWithDevice->mountPoint()); found = mountPoints.findByDevice(QStringLiteral("/I/Dont/Exist")); // krazy:exclude=spelling QVERIFY(!found); } // Check findByPath #ifdef Q_OS_UNIX const KMountPoint::Ptr rootMountPoint = mountPoints.findByPath(QStringLiteral("/")); QVERIFY(rootMountPoint); QCOMPARE(rootMountPoint->mountPoint(), QStringLiteral("/")); QVERIFY(!rootMountPoint->probablySlow()); QT_STATBUF rootStatBuff; QCOMPARE(QT_STAT("/", &rootStatBuff), 0); QT_STATBUF homeStatBuff; if (QT_STAT("/home", &homeStatBuff) == 0) { bool sameDevice = rootStatBuff.st_dev == homeStatBuff.st_dev; const KMountPoint::Ptr homeMountPoint = mountPoints.findByPath(QStringLiteral("/home")); QVERIFY(homeMountPoint); //qDebug() << "Checking the home mount point, sameDevice=" << sameDevice; if (sameDevice) { QCOMPARE(homeMountPoint->mountPoint(), QStringLiteral("/")); } else { QCOMPARE(homeMountPoint->mountPoint(), QStringLiteral("/home")); } } else { qDebug() << "/home doesn't seem to exist, skipping test"; } #endif } void KMountPointTest::testPossibleMountPoints() { const KMountPoint::List mountPoints = KMountPoint::possibleMountPoints(KMountPoint::NeedRealDeviceName | KMountPoint::NeedMountOptions); if (mountPoints.isEmpty()) { // can happen in chroot jails QSKIP("fstab is empty"); return; } KMountPoint::Ptr mountWithDevice; foreach (KMountPoint::Ptr mountPoint, mountPoints) { qDebug() << "Possible mount: " << mountPoint->mountedFrom() << " (" << mountPoint->realDeviceName() << ") " << mountPoint->mountPoint() << " " << mountPoint->mountType() << " options:" << mountPoint->mountOptions() << endl; QVERIFY(!mountPoint->mountedFrom().isEmpty()); QVERIFY(!mountPoint->mountPoint().isEmpty()); QVERIFY(!mountPoint->mountType().isEmpty()); QVERIFY(!mountPoint->mountOptions().isEmpty()); // old bug, happened because KMountPoint called KStandardDirs::realPath instead of realFilePath QVERIFY(!mountPoint->realDeviceName().endsWith('/')); // keep one (any) mountpoint with a device name for the test below if (!mountPoint->realDeviceName().isEmpty()) { mountWithDevice = mountPoint; } } QVERIFY(mountWithDevice); #ifdef Q_OS_UNIX const KMountPoint::Ptr rootMountPoint = mountPoints.findByPath(QStringLiteral("/")); QVERIFY(rootMountPoint); QCOMPARE(rootMountPoint->mountPoint(), QStringLiteral("/")); QVERIFY(rootMountPoint->realDeviceName().startsWith(QLatin1String("/"))); // Usually /dev, but can be /host/ubuntu/disks/root.disk... QVERIFY(!rootMountPoint->mountOptions().contains(QStringLiteral("noauto"))); // how would this work? QVERIFY(!rootMountPoint->probablySlow()); #endif } diff --git a/autotests/kmountpointtest.h b/autotests/kmountpointtest.h index 73fb0156..419ee554 100644 --- a/autotests/kmountpointtest.h +++ b/autotests/kmountpointtest.h @@ -1,35 +1,35 @@ /* * Copyright (C) 2006 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. */ #ifndef KMOUNTPOINTTEST_H #define KMOUNTPOINTTEST_H -#include +#include class KMountPointTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testCurrentMountPoints(); void testPossibleMountPoints(); private: }; #endif diff --git a/autotests/knewfilemenutest.cpp b/autotests/knewfilemenutest.cpp index 67bac52e..156e5aa6 100644 --- a/autotests/knewfilemenutest.cpp +++ b/autotests/knewfilemenutest.cpp @@ -1,194 +1,194 @@ /* This file is part of the KDE libraries Copyright (c) 2012 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 Lesser 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 +#include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_UNIX #include #include #endif class KNewFileMenuTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase() { qputenv("KDE_FORK_SLAVES", "yes"); // to avoid a runtime dependency on klauncher #ifdef Q_OS_UNIX m_umask = ::umask(0); ::umask(m_umask); #endif QVERIFY(m_tmpDir.isValid()); } void cleanupTestCase() { } // Ensure that we can use storedPut() with a qrc file as input // similar to JobTest::storedPutIODeviceFile, but with a qrc file as input // (and here because jobtest doesn't link to KIO::FileWidgets, which has the qrc) void storedPutIODeviceQrcFile() { // Given a source (in a Qt resource file) and a destination file const QString src = ":/kio5/newfile-templates/.source/HTMLFile.html"; QVERIFY(QFile::exists(src)); QFile srcFile(src); QVERIFY(srcFile.open(QIODevice::ReadOnly)); const QString dest = m_tmpDir.path() + "/dest"; QFile::remove(dest); const QUrl destUrl = QUrl::fromLocalFile(dest); // When using storedPut with the QFile as argument KIO::StoredTransferJob *job = KIO::storedPut(&srcFile, destUrl, -1, KIO::Overwrite | KIO::HideProgressInfo); // Then the copy should succeed and the dest file exist QVERIFY2(job->exec(), qPrintable(job->errorString())); QVERIFY(QFile::exists(dest)); QCOMPARE(QFileInfo(src).size(), QFileInfo(dest).size()); // And the permissions should respect the umask (#359581) #ifdef Q_OS_UNIX if (m_umask & S_IWOTH) { QVERIFY2(!(QFileInfo(dest).permissions() & QFileDevice::WriteOther), qPrintable(dest)); } if (m_umask & S_IWGRP) { QVERIFY(!(QFileInfo(dest).permissions() & QFileDevice::WriteGroup)); } #endif QFile::remove(dest); } void test_data() { QTest::addColumn("actionText"); // the action we're clicking on QTest::addColumn("expectedDefaultFilename"); // the initial filename in the dialog QTest::addColumn("typedFilename"); // what the user is typing QTest::addColumn("expectedFilename"); // the final file name QTest::newRow("text file") << "Text File" << "Text File" << "tmp_knewfilemenutest.txt" << "tmp_knewfilemenutest.txt"; QTest::newRow("text file with jpeg extension") << "Text File" << "Text File" << "foo.jpg" << "foo.jpg.txt"; QTest::newRow("html file") << "HTML File" << "HTML File" << "foo.html" << "foo.html"; QTest::newRow("url desktop file") << "Link to Location " << "" << "tmp_link.desktop" << "tmp_link.desktop"; QTest::newRow("url desktop file no extension") << "Link to Location " << "" << "tmp_link" << "tmp_link"; QTest::newRow("url desktop file .pl extension") << "Link to Location " << "" << "tmp_link.pl" << "tmp_link.pl.desktop"; QTest::newRow("symlink") << "Basic link" << "" << "thelink" << "thelink"; QTest::newRow("folder") << "Folder..." << "New Folder" << "folder1" << "folder1"; QTest::newRow("folder_default_name") << "Folder..." << "New Folder" << "New Folder" << "New Folder"; QTest::newRow("folder_with_suggested_name") << "Folder..." << "New Folder (1)" << "New Folder" << "New Folder"; QTest::newRow("application") << "Link to Application..." << "Link to Application" << "app1" << "app1.desktop"; } void test() { QFETCH(QString, actionText); QFETCH(QString, expectedDefaultFilename); QFETCH(QString, typedFilename); QFETCH(QString, expectedFilename); QWidget parentWidget; KActionCollection coll(this, QStringLiteral("foo")); KNewFileMenu menu(&coll, QStringLiteral("the_action"), this); menu.setModal(false); menu.setParentWidget(&parentWidget); QList lst; lst << QUrl::fromLocalFile(m_tmpDir.path()); menu.setPopupFiles(lst); menu.checkUpToDate(); QAction *action = coll.action(QStringLiteral("the_action")); QVERIFY(action); QAction *textAct = nullptr; Q_FOREACH (QAction *act, action->menu()->actions()) { if (act->text().contains(actionText)) { textAct = act; } } if (!textAct) { Q_FOREACH (QAction *act, action->menu()->actions()) { qDebug() << act << act->text() << act->data(); } const QString err = "action with text \"" + actionText + "\" not found."; QVERIFY2(textAct, qPrintable(err)); } textAct->trigger(); QDialog *dialog = parentWidget.findChild(); QVERIFY(dialog); if (KNameAndUrlInputDialog *nauiDialog = qobject_cast(dialog)) { QCOMPARE(nauiDialog->name(), expectedDefaultFilename); nauiDialog->setSuggestedName(typedFilename); nauiDialog->setSuggestedUrl(QUrl(QStringLiteral("file:///etc"))); } else if (KPropertiesDialog *propsDialog = qobject_cast(dialog)) { QLineEdit *lineEdit = propsDialog->findChild("KFilePropsPlugin::nameLineEdit"); QVERIFY(lineEdit); QCOMPARE(lineEdit->text(), expectedDefaultFilename); lineEdit->setText(typedFilename); } else { QLineEdit *lineEdit = dialog->findChild(); QVERIFY(lineEdit); QCOMPARE(lineEdit->text(), expectedDefaultFilename); lineEdit->setText(typedFilename); } QUrl emittedUrl; QSignalSpy spy(&menu, SIGNAL(fileCreated(QUrl))); QSignalSpy folderSpy(&menu, SIGNAL(directoryCreated(QUrl))); dialog->accept(); const QString path = m_tmpDir.path() + '/' + expectedFilename; if (actionText == QLatin1String("Folder...")) { QVERIFY(folderSpy.wait(1000)); emittedUrl = folderSpy.at(0).at(0).toUrl(); QVERIFY(QFileInfo(path).isDir()); } else { if (spy.isEmpty()) { QVERIFY(spy.wait(1000)); } emittedUrl = spy.at(0).at(0).toUrl(); QVERIFY(QFile::exists(path)); if (actionText != QLatin1String("Basic link")) { QFile file(path); QVERIFY(file.open(QIODevice::ReadOnly)); const QByteArray contents = file.readAll(); if (actionText.startsWith("HTML")) { QCOMPARE(QString::fromLatin1(contents.left(6)), QStringLiteral("")); } } } QCOMPARE(emittedUrl.toLocalFile(), path); } private: QTemporaryDir m_tmpDir; #ifdef Q_OS_UNIX mode_t m_umask; #endif }; QTEST_MAIN(KNewFileMenuTest) #include "knewfilemenutest.moc" diff --git a/autotests/krununittest.cpp b/autotests/krununittest.cpp index 2c150a69..2db32eb7 100644 --- a/autotests/krununittest.cpp +++ b/autotests/krununittest.cpp @@ -1,399 +1,399 @@ /* * Copyright (C) 2003 Waldo Bastian * Copyright (C) 2007, 2009 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 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. */ #undef QT_USE_FAST_OPERATOR_PLUS #undef QT_USE_FAST_CONCATENATION #include "krununittest.h" -#include +#include QTEST_GUILESS_MAIN(KRunUnitTest) #include #include "krun.h" #include #include #include #include #include #include #include #include "kiotesthelper.h" // createTestFile etc. #ifdef Q_OS_UNIX #include // kill #endif void KRunUnitTest::initTestCase() { QStandardPaths::enableTestMode(true); // testProcessDesktopExec works only if your terminal application is set to "x-term" KConfigGroup cg(KSharedConfig::openConfig(), "General"); cg.writeEntry("TerminalApplication", "x-term"); // Determine the full path of sh - this is needed to make testProcessDesktopExecNoFile() // pass on systems where QStandardPaths::findExecutable("sh") is not "/bin/sh". m_sh = QStandardPaths::findExecutable(QStringLiteral("sh")); if (m_sh.isEmpty()) { m_sh = QStringLiteral("/bin/sh"); } } void KRunUnitTest::cleanupTestCase() { std::for_each(m_filesToRemove.begin(), m_filesToRemove.end(), [](const QString & f) { QFile::remove(f); }); } void KRunUnitTest::testExecutableName_data() { QTest::addColumn("execLine"); QTest::addColumn("expectedPath"); QTest::addColumn("expectedName"); QTest::newRow("/usr/bin/ls") << "/usr/bin/ls" << "/usr/bin/ls" << "ls"; QTest::newRow("/path/to/wine \"long argument with path\"") << "/path/to/wine \"long argument with path\"" << "/path/to/wine" << "wine"; QTest::newRow("/path/with/a/sp\\ ace/exe arg1 arg2") << "/path/with/a/sp\\ ace/exe arg1 arg2" << "/path/with/a/sp ace/exe" << "exe"; QTest::newRow("\"progname\" \"arg1\"") << "\"progname\" \"arg1\"" << "progname" << "progname"; QTest::newRow("'quoted' \"arg1\"") << "'quoted' \"arg1\"" << "quoted" << "quoted"; QTest::newRow(" 'leading space' arg1") << " 'leading space' arg1" << "leading space" << "leading space"; QTest::newRow("if_command") << "if test -e /tmp/foo; then kwrite ; else konsole ; fi" << "if" << "if"; } void KRunUnitTest::testExecutableName() { QFETCH(QString, execLine); QFETCH(QString, expectedPath); QFETCH(QString, expectedName); QCOMPARE(KIO::DesktopExecParser::executableName(execLine), expectedName); QCOMPARE(KIO::DesktopExecParser::executablePath(execLine), expectedPath); } //static const char *bt(bool tr) { return tr?"true":"false"; } static void checkDesktopExecParser(const char *exec, const char *term, const char *sus, const QList &urls, bool tf, const QString &b) { QFile out(QStringLiteral("kruntest.desktop")); if (!out.open(QIODevice::WriteOnly)) { abort(); } QByteArray str("[Desktop Entry]\n" "Type=Application\n" "Name=just_a_test\n" "Icon=~/icon.png\n"); str += QByteArray(exec) + '\n'; str += QByteArray(term) + '\n'; str += QByteArray(sus) + '\n'; out.write(str); out.close(); KService service(QDir::currentPath() + "/kruntest.desktop"); /*qDebug() << QString().sprintf( "processDesktopExec( " "service = {\nexec = %s\nterminal = %s, terminalOptions = %s\nsubstituteUid = %s, user = %s }," "\nURLs = { %s },\ntemp_files = %s )", service.exec().toLatin1().constData(), bt(service.terminal()), service.terminalOptions().toLatin1().constData(), bt(service.substituteUid()), service.username().toLatin1().constData(), KShell::joinArgs(urls.toStringList()).toLatin1().constData(), bt(tf)); */ KIO::DesktopExecParser parser(service, urls); parser.setUrlsAreTempFiles(tf); QCOMPARE(KShell::joinArgs(parser.resultingArguments()), b); QFile::remove(QStringLiteral("kruntest.desktop")); } void KRunUnitTest::testProcessDesktopExec() { QList l0; static const char *const execs[] = { "Exec=date -u", "Exec=echo $PWD" }; static const char *const terms[] = { "Terminal=false", "Terminal=true\nTerminalOptions=-T \"%f - %c\"" }; static const char *const sus[] = { "X-KDE-SubstituteUID=false", "X-KDE-SubstituteUID=true\nX-KDE-Username=sprallo" }; static const char *const results[] = { "/bin/date -u", // 0 "/bin/sh -c 'echo $PWD '", // 1 "x-term -T ' - just_a_test' -e /bin/date -u", // 2 "x-term -T ' - just_a_test' -e /bin/sh -c 'echo $PWD '", // 3 /* kdesu */ " -u sprallo -c '/bin/date -u'", // 4 /* kdesu */ " -u sprallo -c '/bin/sh -c '\\''echo $PWD '\\'''", // 5 "x-term -T ' - just_a_test' -e su sprallo -c '/bin/date -u'", // 6 "x-term -T ' - just_a_test' -e su sprallo -c '/bin/sh -c '\\''echo $PWD '\\'''", // 7 }; // Find out the full path of the shell which will be used to execute shell commands KProcess process; process.setShellCommand(QLatin1String("")); const QString shellPath = process.program().at(0); // Arch moved /bin/date to /usr/bin/date... const QString datePath = QStandardPaths::findExecutable(QStringLiteral("date")); for (int su = 0; su < 2; su++) for (int te = 0; te < 2; te++) for (int ex = 0; ex < 2; ex++) { int pt = ex + te * 2 + su * 4; QString exe; if (pt == 4 || pt == 5) { exe = QFile::decodeName(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/kdesu"); if (!QFile::exists(exe)) { qWarning() << "kdesu not found, skipping test"; continue; } } const QString result = QString::fromLatin1(results[pt]) .replace(QLatin1String("/bin/sh"), shellPath) .replace(QLatin1String("/bin/date"), datePath); checkDesktopExecParser(execs[ex], terms[te], sus[su], l0, false, exe + result); } } void KRunUnitTest::testProcessDesktopExecNoFile_data() { QTest::addColumn("execLine"); QTest::addColumn >("urls"); QTest::addColumn("tempfiles"); QTest::addColumn("expected"); QList l0; QList l1; l1 << QUrl(QStringLiteral("file:/tmp")); QList l2; l2 << QUrl(QStringLiteral("http://localhost/foo")); QList l3; l3 << QUrl(QStringLiteral("file:/local/some file")) << QUrl(QStringLiteral("http://remotehost.org/bar")); QList l4; l4 << QUrl(QStringLiteral("http://login:password@www.kde.org")); // A real-world use case would be kate. // But I picked kdeinit5 since it's installed by kdelibs QString kdeinit = QStandardPaths::findExecutable(QStringLiteral("kdeinit5")); if (kdeinit.isEmpty()) { kdeinit = QStringLiteral("kdeinit5"); } QString kioexec = QCoreApplication::applicationDirPath() + "/kioexec"; if (!QFileInfo::exists(kioexec)) { kioexec = CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/kioexec"; } QVERIFY(QFileInfo::exists(kioexec)); QString kioexecQuoted = KShell::quoteArg(kioexec); QString kmailservice = QStandardPaths::findExecutable(QStringLiteral("kmailservice5")); if (!QFile::exists(kmailservice)) { kmailservice = QStringLiteral("kmailservice5"); } QTest::newRow("%U l0") << "kdeinit5 %U" << l0 << false << kdeinit; QTest::newRow("%U l1") << "kdeinit5 %U" << l1 << false << kdeinit + " /tmp"; QTest::newRow("%U l2") << "kdeinit5 %U" << l2 << false << kdeinit + " http://localhost/foo"; QTest::newRow("%U l3") << "kdeinit5 %U" << l3 << false << kdeinit + " '/local/some file' http://remotehost.org/bar"; //QTest::newRow("%u l0") << "kdeinit5 %u" << l0 << false << kdeinit; // gives runtime warning QTest::newRow("%u l1") << "kdeinit5 %u" << l1 << false << kdeinit + " /tmp"; QTest::newRow("%u l2") << "kdeinit5 %u" << l2 << false << kdeinit + " http://localhost/foo"; //QTest::newRow("%u l3") << "kdeinit5 %u" << l3 << false << kdeinit; // gives runtime warning QTest::newRow("%F l0") << "kdeinit5 %F" << l0 << false << kdeinit; QTest::newRow("%F l1") << "kdeinit5 %F" << l1 << false << kdeinit + " /tmp"; QTest::newRow("%F l2") << "kdeinit5 %F" << l2 << false << kioexecQuoted + " 'kdeinit5 %F' http://localhost/foo"; QTest::newRow("%F l3") << "kdeinit5 %F" << l3 << false << kioexecQuoted + " 'kdeinit5 %F' 'file:///local/some file' http://remotehost.org/bar"; QTest::newRow("%F l1 tempfile") << "kdeinit5 %F" << l1 << true << kioexecQuoted + " --tempfiles 'kdeinit5 %F' file:///tmp"; QTest::newRow("%f l1 tempfile") << "kdeinit5 %f" << l1 << true << kioexecQuoted + " --tempfiles 'kdeinit5 %f' file:///tmp"; QTest::newRow("sh -c kdeinit5 %F") << "sh -c \"kdeinit5 \"'\\\"'\"%F\"'\\\"'" << l1 << false << m_sh + " -c 'kdeinit5 \\\"/tmp\\\"'"; QTest::newRow("kmailservice5 %u l1") << "kmailservice5 %u" << l1 << false << kmailservice + " /tmp"; QTest::newRow("kmailservice5 %u l4") << "kmailservice5 %u" << l4 << false << kmailservice + " http://login:password@www.kde.org"; } void KRunUnitTest::testProcessDesktopExecNoFile() { QFETCH(QString, execLine); KService service(QStringLiteral("dummy"), execLine, QStringLiteral("app")); QFETCH(QList, urls); QFETCH(bool, tempfiles); QFETCH(QString, expected); KIO::DesktopExecParser parser(service, urls); parser.setUrlsAreTempFiles(tempfiles); QCOMPARE(KShell::joinArgs(parser.resultingArguments()), expected); } class KRunImpl : public KRun { public: KRunImpl(const QUrl &url) : KRun(url, nullptr, false), m_errCode(-1) {} void foundMimeType(const QString &type) Q_DECL_OVERRIDE { m_mimeType = type; // don't call KRun::foundMimeType, we don't want to start an app ;-) setFinished(true); } void handleInitError(int kioErrorCode, const QString &err) Q_DECL_OVERRIDE { m_errCode = kioErrorCode; m_errText = err; } QString mimeTypeFound() const { return m_mimeType; } int errorCode() const { return m_errCode; } QString errorText() const { return m_errText; } private: int m_errCode; QString m_errText; QString m_mimeType; }; void KRunUnitTest::testMimeTypeFile() { const QString filePath = homeTmpDir() + "file"; createTestFile(filePath, true); KRunImpl *krun = new KRunImpl(QUrl::fromLocalFile(filePath)); krun->setAutoDelete(false); QSignalSpy spyFinished(krun, SIGNAL(finished())); QVERIFY(spyFinished.wait(1000)); QCOMPARE(krun->mimeTypeFound(), QString::fromLatin1("text/plain")); delete krun; } void KRunUnitTest::testMimeTypeDirectory() { const QString dir = homeTmpDir() + "dir"; createTestDirectory(dir); KRunImpl *krun = new KRunImpl(QUrl::fromLocalFile(dir)); QSignalSpy spyFinished(krun, SIGNAL(finished())); QVERIFY(spyFinished.wait(1000)); QCOMPARE(krun->mimeTypeFound(), QString::fromLatin1("inode/directory")); } void KRunUnitTest::testMimeTypeBrokenLink() { const QString dir = homeTmpDir() + "dir"; createTestDirectory(dir); KRunImpl *krun = new KRunImpl(QUrl::fromLocalFile(dir + "/testlink")); QSignalSpy spyError(krun, SIGNAL(error())); QSignalSpy spyFinished(krun, SIGNAL(finished())); QVERIFY(spyFinished.wait(1000)); QVERIFY(krun->mimeTypeFound().isEmpty()); QCOMPARE(spyError.count(), 1); QCOMPARE(krun->errorCode(), int(KIO::ERR_DOES_NOT_EXIST)); QVERIFY(krun->errorText().contains("does not exist")); QTest::qWait(100); // let auto-deletion proceed. } void KRunUnitTest::testMimeTypeDoesNotExist() { KRunImpl *krun = new KRunImpl(QUrl::fromLocalFile(QStringLiteral("/does/not/exist"))); QSignalSpy spyError(krun, SIGNAL(error())); QSignalSpy spyFinished(krun, SIGNAL(finished())); QVERIFY(spyFinished.wait(1000)); QVERIFY(krun->mimeTypeFound().isEmpty()); QCOMPARE(spyError.count(), 1); QTest::qWait(100); // let auto-deletion proceed. } static const char s_tempServiceName[] = "krununittest_service.desktop"; static void createSrcFile(const QString path) { QFile srcFile(path); QVERIFY2(srcFile.open(QFile::WriteOnly), qPrintable(srcFile.errorString())); srcFile.write("Hello world\n"); } void KRunUnitTest::KRunRunService_data() { QTest::addColumn("tempFile"); QTest::addColumn("useRunApplication"); QTest::newRow("standard") << false << false; QTest::newRow("tempfile") << true << false; QTest::newRow("runApp") << false << true; QTest::newRow("runApp_tempfile") << true << true; } void KRunUnitTest::KRunRunService() { QFETCH(bool, tempFile); QFETCH(bool, useRunApplication); // Given a service desktop file and a source file const QString path = createTempService(); //KService::Ptr service = KService::serviceByDesktopPath(s_tempServiceName); //QVERIFY(service); KService service(path); QTemporaryDir tempDir; const QString srcDir = tempDir.path(); const QString srcFile = srcDir + "/srcfile"; createSrcFile(srcFile); QVERIFY(QFile::exists(srcFile)); QList urls; urls.append(QUrl::fromLocalFile(srcFile)); // When calling KRun::runService or KRun::runApplication qint64 pid = useRunApplication ? KRun::runApplication(service, urls, nullptr, tempFile ? KRun::RunFlags(KRun::DeleteTemporaryFiles) : KRun::RunFlags()) : KRun::runService(service, urls, nullptr, tempFile); // Then the service should be executed (which copies the source file to "dest") QVERIFY(pid != 0); const QString dest = srcDir + "/dest"; QTRY_VERIFY(QFile::exists(dest)); QVERIFY(QFile::exists(srcFile)); // if tempfile is true, kioexec will delete it... in 3 minutes. // All done, clean up. QVERIFY(QFile::remove(dest)); #ifdef Q_OS_UNIX ::kill(pid, SIGTERM); #endif } QString KRunUnitTest::createTempService() { // fakeservice: deleted and recreated by testKSycocaUpdate, don't use in other tests const QString fileName = s_tempServiceName; //bool mustUpdateKSycoca = !KService::serviceByDesktopPath(fileName); const QString fakeService = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/") + fileName; if (!QFile::exists(fakeService)) { //mustUpdateKSycoca = true; KDesktopFile file(fakeService); KConfigGroup group = file.desktopGroup(); group.writeEntry("Name", "KRunUnittestService"); group.writeEntry("Type", "Service"); #ifdef Q_OS_WIN group.writeEntry("Exec", "copy.exe %f %d/dest"); #else group.writeEntry("Exec", "cp %f %d/dest"); #endif file.sync(); QFile f(fakeService); f.setPermissions(f.permissions() | QFile::ExeOwner | QFile::ExeUser); } m_filesToRemove.append(fakeService); return fakeService; } diff --git a/autotests/ktcpsockettest.cpp b/autotests/ktcpsockettest.cpp index 34b20d65..50043a51 100644 --- a/autotests/ktcpsockettest.cpp +++ b/autotests/ktcpsockettest.cpp @@ -1,384 +1,384 @@ /* * This file is part of the KDE libraries * Copyright (C) 2007 Andreas Hartmetz * * 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 "ktcpsockettest.h" -#include -#include -#include +#include +#include +#include #include "ktcpsocket.h" /* TODO items: - test errors including error strings - test overriding errors - test the most important SSL operations (full coverage is very hard) - test readLine() - test nonblocking, signal based usage - test that waitForDisconnected() writes out all buffered data - (test local and peer address and port getters) - test isValid(). Its documentation is less than clear :( */ static const quint16 testPort = 22342; KTcpSocketTest::KTcpSocketTest() { server = nullptr; } KTcpSocketTest::~KTcpSocketTest() { } void KTcpSocketTest::invokeOnServer(const char *method) { QMetaObject::invokeMethod(server, method, Qt::QueuedConnection); QTest::qWait(1); //Enter the event loop } Server::Server(quint16 _port) : listener(new QTcpServer(this)), socket(nullptr), port(_port) { listener->listen(QHostAddress(QStringLiteral("127.0.0.1")), testPort); } Server::~Server() { } void Server::cleanupSocket() { Q_ASSERT(socket); socket->close(); socket->deleteLater(); socket = nullptr; } void KTcpSocketTest::initTestCase() { m_thread = new QThread(); server = new Server(testPort); server->moveToThread(m_thread); connect(m_thread, &QThread::finished, server, &QObject::deleteLater); m_thread->start(); } void KTcpSocketTest::cleanupTestCase() { m_thread->quit(); m_thread->wait(); delete m_thread; } void KTcpSocketTest::connectDisconnect() { invokeOnServer("connectDisconnect"); KTcpSocket *s = new KTcpSocket(this); QCOMPARE(s->openMode(), QIODevice::NotOpen); QCOMPARE(s->error(), KTcpSocket::UnknownError); s->connectToHost(QStringLiteral("127.0.0.1"), testPort); QCOMPARE(s->state(), KTcpSocket::ConnectingState); QVERIFY(s->openMode() & QIODevice::ReadWrite); const bool connected = s->waitForConnected(150); QVERIFY(connected); QCOMPARE(s->state(), KTcpSocket::ConnectedState); s->waitForDisconnected(150); //ClosingState occurs only when there is buffered data QCOMPARE(s->state(), KTcpSocket::UnconnectedState); s->deleteLater(); } void Server::connectDisconnect() { listener->waitForNewConnection(10000, nullptr); socket = listener->nextPendingConnection(); cleanupSocket(); } #define TESTDATA QByteArray("things and stuff and a bag of chips") void KTcpSocketTest::read() { invokeOnServer("read"); KTcpSocket *s = new KTcpSocket(this); s->connectToHost(QStringLiteral("127.0.0.1"), testPort); s->waitForConnected(40); s->waitForReadyRead(40); QCOMPARE((int)s->bytesAvailable(), TESTDATA.size()); QCOMPARE(s->readAll(), TESTDATA); s->deleteLater(); } void Server::read() { listener->waitForNewConnection(10000, nullptr); socket = listener->nextPendingConnection(); socket->write(TESTDATA); socket->waitForBytesWritten(150); cleanupSocket(); } void KTcpSocketTest::write() { invokeOnServer("write"); KTcpSocket *s = new KTcpSocket(this); s->connectToHost(QStringLiteral("127.0.0.1"), testPort); s->waitForConnected(40); s->write(TESTDATA); QCOMPARE((int)s->bytesToWrite(), TESTDATA.size()); s->waitForReadyRead(150); QCOMPARE((int)s->bytesAvailable(), TESTDATA.size()); QCOMPARE(s->readAll(), TESTDATA); s->write(TESTDATA); QCOMPARE((int)s->bytesToWrite(), TESTDATA.size()); s->disconnectFromHost(); //Test closing with pending data to transmit (pending rx data comes later) QCOMPARE(s->state(), KTcpSocket::ClosingState); s->waitForDisconnected(150); QCOMPARE(s->state(), KTcpSocket::UnconnectedState); s->deleteLater(); } void Server::write() { listener->waitForNewConnection(10000, nullptr); socket = listener->nextPendingConnection(); socket->waitForReadyRead(40); socket->write(socket->readAll()); //echo socket->waitForBytesWritten(150); socket->waitForReadyRead(40); socket->write(socket->readAll()); cleanupSocket(); } static QString stateToString(KTcpSocket::State state) { switch (state) { case KTcpSocket::UnconnectedState: return QStringLiteral("UnconnectedState"); case KTcpSocket::HostLookupState: return QStringLiteral("HostLookupState"); case KTcpSocket::ConnectingState: return QStringLiteral("ConnectingState"); case KTcpSocket::ConnectedState: return QStringLiteral("ConnectedState"); case KTcpSocket::BoundState: return QStringLiteral("BoundState"); case KTcpSocket::ListeningState: return QStringLiteral("ListeningState"); case KTcpSocket::ClosingState: return QStringLiteral("ClosingState"); } return QStringLiteral("ERROR"); } #define HTTPREQUEST QByteArray("GET / HTTP/1.1\nHost: www.example.com\n\n") // I assume that example.com, hosted by the IANA, will exist indefinitely. // It is a nice test site because it serves a very small HTML page that should // fit into a TCP packet or two. void KTcpSocketTest::statesIana() { QSKIP("Too unreliable"); //A connection to a real internet host KTcpSocket *s = new KTcpSocket(this); connect(s, SIGNAL(hostFound()), this, SLOT(states_hostFound())); QCOMPARE(s->state(), KTcpSocket::UnconnectedState); s->connectToHost(QStringLiteral("www.iana.org"), 80); QCOMPARE(s->state(), KTcpSocket::HostLookupState); s->write(HTTPREQUEST); QCOMPARE(s->state(), KTcpSocket::HostLookupState); s->waitForBytesWritten(2500); QCOMPARE(s->state(), KTcpSocket::ConnectedState); // Try to ensure that inbound data in the next part of the test is really from the second request; // it is not *guaranteed* that this reads all data, e.g. if the connection is very slow (so too many // of the waitForReadyRead() time out), or if the reply packets are extremely fragmented (so 50 reads // are not enough to receive all of them). I don't know the details of fragmentation so the latter // problem could be nonexistent. QByteArray received; for (int i = 0; i < 50; i++) { s->waitForReadyRead(50); received.append(s->readAll()); } QVERIFY(received.size() > 200); // Here, the connection should neither have data in its write buffer nor inbound packets in flight // Now reuse the connection for another request / reply pair s->write(HTTPREQUEST); s->waitForReadyRead(); // After waitForReadyRead(), the write buffer should be empty because the server has to wait for the // end of the request before sending a reply. // The socket can then shut down without having to wait for draining the write buffer. // Incoming data cannot delay the transition to UnconnectedState, as documented in // QAbstractSocket::disconnectFromHost(). close() just wraps disconnectFromHost(). s->close(); QCOMPARE((int)s->state(), (int)KTcpSocket::UnconnectedState); delete s; } void KTcpSocketTest::statesLocalHost() { //Now again an internal connection invokeOnServer("states"); KTcpSocket *s = new KTcpSocket(this); connect(s, SIGNAL(hostFound()), this, SLOT(states_hostFound())); s->connectToHost(QStringLiteral("127.0.0.1"), testPort); QCOMPARE(s->state(), KTcpSocket::ConnectingState); s->waitForConnected(40); QCOMPARE(s->state(), KTcpSocket::ConnectedState); s->write(HTTPREQUEST); s->waitForReadyRead(); QCOMPARE((int)s->bytesAvailable(), HTTPREQUEST.size()); //for good measure... QCOMPARE(s->state(), KTcpSocket::ConnectedState); s->waitForDisconnected(40); QCOMPARE(s->state(), KTcpSocket::UnconnectedState); disconnect(s, SIGNAL(hostFound())); delete s; } void KTcpSocketTest::statesManyHosts() { KTcpSocket *s = new KTcpSocket(this); QByteArray requestProlog("GET / HTTP/1.1\r\n" //exact copy of a real HTTP query "Connection: Keep-Alive\r\n" //not really... "User-Agent: Mozilla/5.0 (compatible; Konqueror/3.96; Linux) " "KHTML/3.96.0 (like Gecko)\r\n" "Pragma: no-cache\r\n" "Cache-control: no-cache\r\n" "Accept: text/html, image/jpeg, image/png, text/*, image/*, */*\r\n" "Accept-Encoding: x-gzip, x-deflate, gzip, deflate\r\n" "Accept-Charset: utf-8, utf-8;q=0.5, *;q=0.5\r\n" "Accept-Language: en-US, en\r\n" "Host: "); QByteArray requestEpilog("\r\n\r\n"); //Test rapid connection and disconnection to different hosts static const char *hosts[] = {"www.google.de", "www.spiegel.de", "www.stern.de", "www.laut.de"}; static const int numHosts = 4; for (int i = 0; i < numHosts * 5; i++) { qDebug("\nNow trying %s...", hosts[i % numHosts]); QCOMPARE(s->state(), KTcpSocket::UnconnectedState); s->connectToHost(hosts[i % numHosts], 80); bool skip = false; KTcpSocket::State expectedState = KTcpSocket::ConnectingState; if (i < numHosts) { expectedState = KTcpSocket::HostLookupState; } else { expectedState = KTcpSocket::ConnectingState; } if (!skip) { QCOMPARE(stateToString(s->state()), stateToString(expectedState)); } else { // let's make sure it's at least one of the two expected states QVERIFY(stateToString(s->state()) == stateToString(KTcpSocket::HostLookupState) || stateToString(s->state()) == stateToString(KTcpSocket::ConnectingState)); } //weave the host address into the HTTP request QByteArray request(requestProlog); request.append(hosts[i % numHosts]); request.append(requestEpilog); s->write(request); if (!skip) { QCOMPARE(stateToString(s->state()), stateToString(expectedState)); } s->waitForBytesWritten(-1); QCOMPARE(s->state(), KTcpSocket::ConnectedState); int tries = 0; while (s->bytesAvailable() <= 100 && ++tries < 10) { s->waitForReadyRead(-1); } QVERIFY(s->bytesAvailable() > 100); if (i % (numHosts + 1)) { s->readAll(); QVERIFY(s->bytesAvailable() == 0); } else { char dummy[4]; s->read(dummy, 1); QVERIFY(s->bytesAvailable() > 100 - 1); } s->disconnectFromHost(); if (s->state() != KTcpSocket::UnconnectedState) { s->waitForDisconnected(-1); } if (i % 2) { s->close(); //close() is not very well defined for sockets so just check that it //does no harm } } s->deleteLater(); } void KTcpSocketTest::states_hostFound() { QCOMPARE(static_cast(sender())->state(), KTcpSocket::ConnectingState); } void Server::states() { listener->waitForNewConnection(10000, nullptr); socket = listener->nextPendingConnection(); socket->waitForReadyRead(40); socket->write(socket->readAll()); //echo socket->waitForBytesWritten(150); cleanupSocket(); } void KTcpSocketTest::errors() { //invokeOnServer("errors"); } void Server::errors() { listener->waitForNewConnection(10000, nullptr); socket = listener->nextPendingConnection(); cleanupSocket(); } QTEST_MAIN(KTcpSocketTest) diff --git a/autotests/kurlcompletiontest.cpp b/autotests/kurlcompletiontest.cpp index 9f894d3a..16e89f56 100644 --- a/autotests/kurlcompletiontest.cpp +++ b/autotests/kurlcompletiontest.cpp @@ -1,446 +1,446 @@ /* * Copyright (C) 2004 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 +#include #include #include #include #include -#include -#include +#include +#include #include #include #include class KUrlCompletionTest : public QObject { Q_OBJECT private Q_SLOTS: void test(); public: KUrlCompletionTest() { #ifdef NO_WAIT // kurlcompletiontest-nowait sets this, to test what happens on slower systems (or systems with many dirs or users) qputenv("KURLCOMPLETION_WAIT", "1"); // 1ms, too short for a full listing of /usr/bin, but at least give a chance for a few items in the result #endif } ~KUrlCompletionTest() { teardown(); } void runAllTests(); void setup(); void teardown(); void testLocalRelativePath(); void testLocalAbsolutePath(); void testLocalURL(); void testEmptyCwd(); void testBug346920(); void testInvalidProtocol(); void testUser(); void testCancel(); // remember to register new test methods in runAllTests private: void waitForCompletion(KUrlCompletion *completion); KUrlCompletion *m_completion; KUrlCompletion *m_completionWithMimeFilter; QTemporaryDir *m_tempDir; QUrl m_dirURL; QString m_dir; KUrlCompletion *m_completionEmptyCwd; }; void KUrlCompletionTest::setup() { qDebug(); m_completion = new KUrlCompletion; m_completionWithMimeFilter = new KUrlCompletion; m_completionWithMimeFilter->setMimeTypeFilters({QStringLiteral("text/x-c++src")}); m_tempDir = new QTemporaryDir; m_dir = m_tempDir->path(); m_dir += QLatin1String("/Dir With#Spaces/"); QDir().mkdir(m_dir); qDebug() << "m_dir=" << m_dir; m_completion->setDir(QUrl::fromLocalFile(m_dir)); m_completionWithMimeFilter->setDir(m_completion->dir()); m_dirURL = QUrl::fromLocalFile(m_dir); QFile f1(m_dir + "/file1"); bool ok = f1.open(QIODevice::WriteOnly); QVERIFY(ok); f1.close(); QFile f2(m_dir + "/file#a"); ok = f2.open(QIODevice::WriteOnly); QVERIFY(ok); f2.close(); QFile f3(m_dir + "/file."); ok = f3.open(QIODevice::WriteOnly); QVERIFY(ok); f3.close(); QFile f4(m_dir + "/source.cpp"); ok = f4.open(QIODevice::WriteOnly); QVERIFY(ok); f4.close(); QFile f5(m_dir + "/source.php"); ok = f5.open(QIODevice::WriteOnly); QVERIFY(ok); f5.close(); QDir().mkdir(m_dir + "/file_subdir"); QDir().mkdir(m_dir + "/.1_hidden_file_subdir"); QDir().mkdir(m_dir + "/.2_hidden_file_subdir"); m_completionEmptyCwd = new KUrlCompletion; m_completionEmptyCwd->setDir(QUrl()); } void KUrlCompletionTest::teardown() { delete m_completion; m_completion = nullptr; delete m_completionWithMimeFilter; m_completionWithMimeFilter = nullptr; delete m_tempDir; m_tempDir = nullptr; delete m_completionEmptyCwd; m_completionEmptyCwd = nullptr; } void KUrlCompletionTest::waitForCompletion(KUrlCompletion *completion) { while (completion->isRunning()) { qDebug() << "waiting for thread..."; QTest::qWait(5); } // The thread emitted a signal, process it. qApp->sendPostedEvents(nullptr, QEvent::MetaCall); } void KUrlCompletionTest::testLocalRelativePath() { qDebug(); // Completion from relative path, with two matches m_completion->makeCompletion(QStringLiteral("f")); waitForCompletion(m_completion); QStringList comp1all = m_completion->allMatches(); qDebug() << comp1all; QCOMPARE(comp1all.count(), 4); QVERIFY(comp1all.contains("file1")); QVERIFY(comp1all.contains("file#a")); QVERIFY(comp1all.contains("file.")); QVERIFY(comp1all.contains("file_subdir/")); QString comp1 = m_completion->replacedPath(QStringLiteral("file1")); // like KUrlRequester does QCOMPARE(comp1, QString("file1")); // Completion from relative path qDebug() << endl << "now completing on 'file#'"; m_completion->makeCompletion(QStringLiteral("file#")); QVERIFY(!m_completion->isRunning()); // last listing reused QStringList compall = m_completion->allMatches(); qDebug() << compall; QCOMPARE(compall.count(), 1); QCOMPARE(compall.first(), QString("file#a")); QString comp2 = m_completion->replacedPath(compall.first()); // like KUrlRequester does QCOMPARE(comp2, QString("file#a")); // Completion with empty string qDebug() << endl << "now completing on ''"; m_completion->makeCompletion(QLatin1String("")); waitForCompletion(m_completion); QStringList compEmpty = m_completion->allMatches(); QCOMPARE(compEmpty.count(), 0); m_completion->makeCompletion("."); waitForCompletion(m_completion); const auto compAllHidden = m_completion->allMatches(); QCOMPARE(compAllHidden.count(), 2); QVERIFY(compAllHidden.contains(".1_hidden_file_subdir/")); QVERIFY(compAllHidden.contains(".2_hidden_file_subdir/")); // Completion with '.2', should find only hidden folders starting with '2' m_completion->makeCompletion(".2"); waitForCompletion(m_completion); const auto compHiddenStartingWith2 = m_completion->allMatches(); QCOMPARE(compHiddenStartingWith2.count(), 1); QVERIFY(compHiddenStartingWith2.contains(".2_hidden_file_subdir/")); // Completion with 'file.', should only find one file m_completion->makeCompletion("file."); waitForCompletion(m_completion); const auto compFileEndingWithDot = m_completion->allMatches(); QCOMPARE(compFileEndingWithDot.count(), 1); QVERIFY(compFileEndingWithDot.contains("file.")); // Completion with 'source' should only find the C++ file m_completionWithMimeFilter->makeCompletion("source"); waitForCompletion(m_completionWithMimeFilter); const auto compSourceFile = m_completionWithMimeFilter->allMatches(); QCOMPARE(compSourceFile.count(), 1); QVERIFY(compSourceFile.contains("source.cpp")); // But it should also be able to find folders m_completionWithMimeFilter->makeCompletion("file_subdir"); waitForCompletion(m_completionWithMimeFilter); const auto compMimeFolder = m_completionWithMimeFilter->allMatches(); QCOMPARE(compMimeFolder.count(), 1); QVERIFY(compMimeFolder.contains("file_subdir/")); } void KUrlCompletionTest::testLocalAbsolutePath() { // Completion from absolute path qDebug() << m_dir + "file#"; m_completion->makeCompletion(m_dir + "file#"); waitForCompletion(m_completion); QStringList compall = m_completion->allMatches(); qDebug() << compall; QCOMPARE(compall.count(), 1); QString comp = compall.first(); QCOMPARE(comp, QString(m_dir + "file#a")); comp = m_completion->replacedPath(comp); // like KUrlRequester does QCOMPARE(comp, QString(m_dir + "file#a")); // Completion with '.', should find all hidden folders m_completion->makeCompletion(m_dir + "."); waitForCompletion(m_completion); const auto compAllHidden = m_completion->allMatches(); QCOMPARE(compAllHidden.count(), 2); QVERIFY(compAllHidden.contains(m_dir + ".1_hidden_file_subdir/")); QVERIFY(compAllHidden.contains(m_dir + ".2_hidden_file_subdir/")); // Completion with '.2', should find only hidden folders starting with '2' m_completion->makeCompletion(m_dir + ".2"); waitForCompletion(m_completion); const auto compHiddenStartingWith2 = m_completion->allMatches(); QCOMPARE(compHiddenStartingWith2.count(), 1); QVERIFY(compHiddenStartingWith2.contains(m_dir + ".2_hidden_file_subdir/")); // Completion with 'file.', should only find one file m_completion->makeCompletion(m_dir + "file."); waitForCompletion(m_completion); const auto compFileEndingWithDot = m_completion->allMatches(); QCOMPARE(compFileEndingWithDot.count(), 1); QVERIFY(compFileEndingWithDot.contains(m_dir + "file.")); // Completion with 'source' should only find the C++ file m_completionWithMimeFilter->makeCompletion(m_dir + "source"); waitForCompletion(m_completionWithMimeFilter); const auto compSourceFile = m_completionWithMimeFilter->allMatches(); QCOMPARE(compSourceFile.count(), 1); QVERIFY(compSourceFile.contains(m_dir + "source.cpp")); // But it should also be able to find folders m_completionWithMimeFilter->makeCompletion(m_dir + "file_subdir"); waitForCompletion(m_completionWithMimeFilter); const auto compMimeFolder = m_completionWithMimeFilter->allMatches(); QCOMPARE(compMimeFolder.count(), 1); QVERIFY(compMimeFolder.contains(m_dir + "file_subdir/")); } void KUrlCompletionTest::testLocalURL() { // Completion from URL qDebug(); QUrl url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + "file"); m_completion->makeCompletion(url.toString()); waitForCompletion(m_completion); QStringList comp1all = m_completion->allMatches(); qDebug() << comp1all; QCOMPARE(comp1all.count(), 4); qDebug() << "Looking for" << m_dirURL.toString() + "file1"; QVERIFY(comp1all.contains(m_dirURL.toString() + "file1")); qDebug() << "Looking for" << m_dirURL.toString() + "file."; QVERIFY(comp1all.contains(m_dirURL.toString() + "file.")); QVERIFY(comp1all.contains(m_dirURL.toString() + "file_subdir/")); QString filehash = m_dirURL.toString() + "file%23a"; qDebug() << "Looking for" << filehash; QVERIFY(comp1all.contains(filehash)); QString filehashPath = m_completion->replacedPath(filehash); // note that it returns a path!! qDebug() << filehashPath; QCOMPARE(filehashPath, QString(m_dirURL.toLocalFile() + "file#a")); // Completion from URL with no match url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + "foobar"); qDebug() << "makeCompletion(" << url << ")"; QString comp2 = m_completion->makeCompletion(url.toString()); QVERIFY(comp2.isEmpty()); waitForCompletion(m_completion); QVERIFY(m_completion->allMatches().isEmpty()); // Completion from URL with a ref -> no match url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + 'f'); url.setFragment(QStringLiteral("ref")); qDebug() << "makeCompletion(" << url << ")"; m_completion->makeCompletion(url.toString()); waitForCompletion(m_completion); QVERIFY(m_completion->allMatches().isEmpty()); // Completion with '.', should find all hidden folders qDebug() << "makeCompletion(" << (m_dirURL.toString() + ".") << ")"; m_completion->makeCompletion(m_dirURL.toString() + "."); waitForCompletion(m_completion); const auto compAllHidden = m_completion->allMatches(); QCOMPARE(compAllHidden.count(), 2); QVERIFY(compAllHidden.contains(m_dirURL.toString() + ".1_hidden_file_subdir/")); QVERIFY(compAllHidden.contains(m_dirURL.toString() + ".2_hidden_file_subdir/")); // Completion with '.2', should find only hidden folders starting with '2' url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + ".2"); qDebug() << "makeCompletion(" << url << ")"; m_completion->makeCompletion(url.toString()); waitForCompletion(m_completion); const auto compHiddenStartingWith2 = m_completion->allMatches(); QCOMPARE(compHiddenStartingWith2.count(), 1); QVERIFY(compHiddenStartingWith2.contains(m_dirURL.toString() + ".2_hidden_file_subdir/")); // Completion with 'file.', should only find one file url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + "file."); qDebug() << "makeCompletion(" << url << ")"; m_completion->makeCompletion(url.toString()); waitForCompletion(m_completion); const auto compFileEndingWithDot = m_completion->allMatches(); QCOMPARE(compFileEndingWithDot.count(), 1); QVERIFY(compFileEndingWithDot.contains(m_dirURL.toString() + "file.")); // Completion with 'source' should only find the C++ file m_completionWithMimeFilter->makeCompletion(m_dirURL.toString() + "source"); waitForCompletion(m_completionWithMimeFilter); const auto compSourceFile = m_completionWithMimeFilter->allMatches(); QCOMPARE(compSourceFile.count(), 1); QVERIFY(compSourceFile.contains(m_dirURL.toString() + "source.cpp")); // But it should also be able to find folders m_completionWithMimeFilter->makeCompletion(m_dirURL.toString() + "file_subdir"); waitForCompletion(m_completionWithMimeFilter); const auto compMimeFolder = m_completionWithMimeFilter->allMatches(); QCOMPARE(compMimeFolder.count(), 1); QVERIFY(compMimeFolder.contains(m_dirURL.toString() + "file_subdir/")); } void KUrlCompletionTest::testEmptyCwd() { // Completion with empty string (with a KUrlCompletion whose cwd is "") qDebug() << endl << "now completing on '' with empty cwd"; m_completionEmptyCwd->makeCompletion(QLatin1String("")); waitForCompletion(m_completionEmptyCwd); QStringList compEmpty = m_completionEmptyCwd->allMatches(); QCOMPARE(compEmpty.count(), 0); } void KUrlCompletionTest::testBug346920() { m_completionEmptyCwd->makeCompletion(QStringLiteral("~/.")); waitForCompletion(m_completionEmptyCwd); m_completionEmptyCwd->allMatches(); // just don't crash } void KUrlCompletionTest::testInvalidProtocol() { m_completion->makeCompletion(QStringLiteral(":/")); waitForCompletion(m_completion); m_completion->allMatches(); // just don't crash } void KUrlCompletionTest::testUser() { m_completionEmptyCwd->makeCompletion(QStringLiteral("~")); waitForCompletion(m_completionEmptyCwd); const auto matches = m_completionEmptyCwd->allMatches(); if (!KUser::allUserNames().isEmpty()) { Q_ASSERT(!matches.isEmpty()); } foreach (const auto &user, KUser::allUserNames()) { QVERIFY2(matches.contains(QLatin1Char('~') + user), qPrintable(matches.join(' '))); } // Check that the same query doesn't re-list m_completionEmptyCwd->makeCompletion(QStringLiteral("~")); QVERIFY(!m_completionEmptyCwd->isRunning()); QCOMPARE(m_completionEmptyCwd->allMatches(), matches); } // Test cancelling a running thread // In a normal run (./kurlcompletiontest) and a reasonable amount of files, we have few chances of making this happen // But in a "nowait" run (./kurlcompletiontest-nowait), this will cancel the thread before it even starts listing the dir. void KUrlCompletionTest::testCancel() { KUrlCompletion comp; comp.setDir(QUrl::fromLocalFile("/usr/bin")); comp.makeCompletion(QStringLiteral("g")); const QStringList matchesG = comp.allMatches(); // We get many matches in a normal run, and usually 0 matches when testing "no wait" (thread is sleeping) -> this is where this method can test cancelling //qDebug() << "got" << matchesG.count() << "matches"; bool done = !comp.isRunning(); // Doing the same search again, should hopefully not restart everything from scratch comp.makeCompletion(QStringLiteral("g")); const QStringList matchesG2 = comp.allMatches(); QVERIFY(matchesG2.count() >= matchesG.count()); if (done) { QVERIFY(!comp.isRunning()); // it had no reason to restart } done = !comp.isRunning(); // Search for something else, should reuse dir listing but not mix up results comp.makeCompletion(QStringLiteral("a")); if (done) { QVERIFY(!comp.isRunning()); // it had no reason to restart } const QStringList matchesA = comp.allMatches(); //qDebug() << "got" << matchesA.count() << "matches"; foreach (const QString &match, matchesA) { QVERIFY2(!match.startsWith('g'), qPrintable(match)); } waitForCompletion(&comp); foreach (const QString &match, comp.allMatches()) { QVERIFY2(!match.startsWith('g'), qPrintable(match)); } } void KUrlCompletionTest::test() { runAllTests(); // Try again, with another QTemporaryDir (to check that the caching doesn't give us wrong results) runAllTests(); } void KUrlCompletionTest::runAllTests() { setup(); testLocalRelativePath(); testLocalAbsolutePath(); testLocalURL(); testEmptyCwd(); testBug346920(); testInvalidProtocol(); testUser(); testCancel(); teardown(); } QTEST_MAIN(KUrlCompletionTest) #include "kurlcompletiontest.moc" diff --git a/autotests/listdirtest.cpp b/autotests/listdirtest.cpp index 22893d06..78da370d 100644 --- a/autotests/listdirtest.cpp +++ b/autotests/listdirtest.cpp @@ -1,80 +1,80 @@ /* * Copyright (C) 2013 Mark Gaiser * * 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 "listdirtest.h" -#include +#include #include #include #include #include #include QTEST_MAIN(ListDirTest) void ListDirTest::initTestCase() { // To avoid a runtime dependency on klauncher qputenv("KDE_FORK_SLAVES", "yes"); } void ListDirTest::numFilesTestCase_data() { QTest::addColumn("numOfFiles"); QTest::newRow("10 files") << 10; QTest::newRow("100 files") << 100; QTest::newRow("1000 files") << 1000; } void ListDirTest::numFilesTestCase() { QFETCH(int, numOfFiles); QTemporaryDir tempDir; QVERIFY(tempDir.isValid()); createEmptyTestFiles(numOfFiles, tempDir.path()); /*QBENCHMARK*/ { m_receivedEntryCount = -2; // We start at -2 for . and .. slotResult will just increment this value KIO::ListJob *job = KIO::listDir(QUrl::fromLocalFile(tempDir.path()), KIO::HideProgressInfo); job->setUiDelegate(nullptr); connect(job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), this, SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList))); QSignalSpy spy(job, SIGNAL(result(KJob*))); QVERIFY(spy.wait(100000)); QCOMPARE(job->error(), 0); // no error } QCOMPARE(m_receivedEntryCount, numOfFiles); } void ListDirTest::slotEntries(KIO::Job *, const KIO::UDSEntryList &entries) { m_receivedEntryCount += entries.count(); } void ListDirTest::createEmptyTestFiles(int numOfFilesToCreate, const QString &path) { for (int i = 0; i < numOfFilesToCreate; i++) { const QString filename = path + QDir::separator() + QString::number(i) + ".txt"; QFile file(filename); QVERIFY(file.open(QIODevice::WriteOnly)); } QCOMPARE(QDir(path).entryList(QDir::Files).count(), numOfFilesToCreate); } diff --git a/autotests/privilegejobtest.cpp b/autotests/privilegejobtest.cpp index 06ac32a8..3acc0085 100644 --- a/autotests/privilegejobtest.cpp +++ b/autotests/privilegejobtest.cpp @@ -1,134 +1,134 @@ /*** Copyright (C) 2017 by Chinmoy Ranjan Pradhan This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . ***/ #include "privilegejobtest.h" -#include +#include #include #include #include #include #include #include #include "kiotesthelper.h" QTEST_MAIN(PrivilegeJobTest) void PrivilegeJobTest::initTestCase() { // To avoid a runtime dependency on klauncher qputenv("KDE_FORK_SLAVES", "yes"); cleanupTestCase(); homeTmpDir(); m_testFilePath = homeTmpDir() + "testfile"; createTestFile(m_testFilePath); QVERIFY(QFile::exists(m_testFilePath)); QVERIFY(QFile::setPermissions(homeTmpDir(), QFileDevice::ReadOwner | QFileDevice::ExeOwner)); } void PrivilegeJobTest::cleanupTestCase() { QFile::setPermissions(homeTmpDir(), QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner); QDir(homeTmpDir()).removeRecursively(); } void PrivilegeJobTest::privilegeChmod() { KFileItem item(QUrl::fromLocalFile(m_testFilePath)); const mode_t origPerm = item.permissions(); mode_t newPerm = origPerm ^ S_IWGRP; QVERIFY(newPerm != origPerm); // Remove search permission QVERIFY(QFile::setPermissions(homeTmpDir(), QFileDevice::ReadOwner)); KFileItemList items; items << item; KIO::Job *job = KIO::chmod(items, newPerm, S_IWGRP, QString(), QString(), false, KIO::HideProgressInfo); job->addMetaData("UnitTesting", "true"); job->setUiDelegate(nullptr); QVERIFY(job->exec()); QCOMPARE(job->queryMetaData("TestData"), QLatin1String("PrivilegeOperationAllowed")); // Bring it back QVERIFY(QFile::setPermissions(homeTmpDir(), QFileDevice::ReadOwner | QFileDevice::ExeOwner)); } void PrivilegeJobTest::privilegeCopy() { const QUrl src = QUrl::fromLocalFile(m_testFilePath); const QUrl dest = QUrl::fromLocalFile(homeTmpDir() + "newtestfile"); KIO::CopyJob *job = KIO::copy(src, dest, KIO::HideProgressInfo); job->addMetaData("UnitTesting", "true"); job->setUiDelegate(nullptr); QVERIFY(job->exec()); QCOMPARE(job->queryMetaData("TestData"), QStringLiteral("PrivilegeOperationAllowed")); } void PrivilegeJobTest::privilegeDelete() { const QUrl url = QUrl::fromLocalFile(m_testFilePath); KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); job->addMetaData("UnitTesting", "true"); job->setUiDelegate(nullptr); QVERIFY(job->exec()); QCOMPARE(job->queryMetaData("TestData"), QStringLiteral("PrivilegeOperationAllowed")); } void PrivilegeJobTest::privilegeMkpath() { const QUrl dirUrl = QUrl::fromLocalFile(homeTmpDir() + "testdir"); KIO::MkpathJob *job = KIO::mkpath(dirUrl, QUrl(), KIO::HideProgressInfo); job->addMetaData("UnitTesting", "true"); job->setUiDelegate(nullptr); QVERIFY(job->exec()); QCOMPARE(job->queryMetaData("TestData"), QStringLiteral("PrivilegeOperationAllowed")); } void PrivilegeJobTest::privilegePut() { const QUrl url = QUrl::fromLocalFile(homeTmpDir() + "putfile"); KIO::TransferJob *job = KIO::put(url, -1, KIO::HideProgressInfo); job->addMetaData("UnitTesting", "true"); job->setUiDelegate(nullptr); QVERIFY(job->exec()); QCOMPARE(job->queryMetaData("TestData"), QStringLiteral("PrivilegeOperationAllowed")); } void PrivilegeJobTest::privilegeRename() { const QUrl src = QUrl::fromLocalFile(homeTmpDir() + "testfile"); const QUrl dest = QUrl::fromLocalFile(homeTmpDir() + "newtestfile"); KIO::SimpleJob *job = KIO::rename(src, dest, KIO::HideProgressInfo); job->addMetaData("UnitTesting", "true"); job->setUiDelegate(nullptr); QVERIFY(job->exec()); QCOMPARE(job->queryMetaData("TestData"), QStringLiteral("PrivilegeOperationAllowed")); } void PrivilegeJobTest::privileSymlink() { const QString target = homeTmpDir() + "testfile"; const QUrl dest = QUrl::fromLocalFile(homeTmpDir() + "symlink"); KIO::SimpleJob *job = KIO::symlink(target, dest, KIO::HideProgressInfo); job->addMetaData("UnitTesting", "true"); job->setUiDelegate(nullptr); QVERIFY(job->exec()); QCOMPARE(job->queryMetaData("TestData"), QStringLiteral("PrivilegeOperationAllowed")); } diff --git a/src/core/authinfo.cpp b/src/core/authinfo.cpp index b76e6c85..862499ae 100644 --- a/src/core/authinfo.cpp +++ b/src/core/authinfo.cpp @@ -1,481 +1,481 @@ /* * This file is part of the KDE libraries * Copyright (C) 2000-2001 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 "authinfo.h" -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include using namespace KIO; ////// class ExtraField { public: ExtraField() : flags(AuthInfo::ExtraFieldNoFlags) { } ExtraField(const ExtraField &other) : customTitle(other.customTitle), flags(other.flags), value(other.value) { } ExtraField &operator=(const ExtraField &other) { customTitle = other.customTitle; flags = other.flags; value = other.value; return *this; } QString customTitle; // reserved for future use AuthInfo::FieldFlags flags; QVariant value; }; Q_DECLARE_METATYPE(ExtraField) static QDataStream &operator<< (QDataStream &s, const ExtraField &extraField) { s << extraField.customTitle; s << static_cast(extraField.flags); s << extraField.value; return s; } static QDataStream &operator>> (QDataStream &s, ExtraField &extraField) { s >> extraField.customTitle; int i; s >> i; extraField.flags = AuthInfo::FieldFlags(i); s >> extraField.value; return s; } static QDBusArgument &operator<<(QDBusArgument &argument, const ExtraField &extraField) { argument.beginStructure(); argument << extraField.customTitle << static_cast(extraField.flags) << QDBusVariant(extraField.value); argument.endStructure(); return argument; } static const QDBusArgument &operator>>(const QDBusArgument &argument, ExtraField &extraField) { QDBusVariant value; int flag; argument.beginStructure(); argument >> extraField.customTitle >> flag >> value; argument.endStructure(); extraField.value = value.variant(); extraField.flags = KIO::AuthInfo::FieldFlags(flag); return argument; } class KIO::AuthInfoPrivate { public: QMap extraFields; }; ////// AuthInfo::AuthInfo() : d(new AuthInfoPrivate()) { modified = false; readOnly = false; verifyPath = false; keepPassword = false; AuthInfo::registerMetaTypes(); } AuthInfo::AuthInfo(const AuthInfo &info) : d(new AuthInfoPrivate()) { (*this) = info; AuthInfo::registerMetaTypes(); } AuthInfo::~AuthInfo() { delete d; } AuthInfo &AuthInfo::operator= (const AuthInfo &info) { url = info.url; username = info.username; password = info.password; prompt = info.prompt; caption = info.caption; comment = info.comment; commentLabel = info.commentLabel; realmValue = info.realmValue; digestInfo = info.digestInfo; verifyPath = info.verifyPath; readOnly = info.readOnly; keepPassword = info.keepPassword; modified = info.modified; d->extraFields = info.d->extraFields; return *this; } bool AuthInfo::isModified() const { return modified; } void AuthInfo::setModified(bool flag) { modified = flag; } ///// void AuthInfo::setExtraField(const QString &fieldName, const QVariant &value) { d->extraFields[fieldName].value = value; } void AuthInfo::setExtraFieldFlags(const QString &fieldName, const FieldFlags flags) { d->extraFields[fieldName].flags = flags; } QVariant AuthInfo::getExtraField(const QString &fieldName) const { if (!d->extraFields.contains(fieldName)) { return QVariant(); } return d->extraFields[fieldName].value; } AuthInfo::FieldFlags AuthInfo::getExtraFieldFlags(const QString &fieldName) const { if (!d->extraFields.contains(fieldName)) { return AuthInfo::ExtraFieldNoFlags; } return d->extraFields[fieldName].flags; } void AuthInfo::registerMetaTypes() { qRegisterMetaType(); qRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); } ///// QDataStream &KIO::operator<< (QDataStream &s, const AuthInfo &a) { s << quint8(1) << a.url << a.username << a.password << a.prompt << a.caption << a.comment << a.commentLabel << a.realmValue << a.digestInfo << a.verifyPath << a.readOnly << a.keepPassword << a.modified << a.d->extraFields; return s; } QDataStream &KIO::operator>> (QDataStream &s, AuthInfo &a) { quint8 version; s >> version >> a.url >> a.username >> a.password >> a.prompt >> a.caption >> a.comment >> a.commentLabel >> a.realmValue >> a.digestInfo >> a.verifyPath >> a.readOnly >> a.keepPassword >> a.modified >> a.d->extraFields; return s; } QDBusArgument &KIO::operator<<(QDBusArgument &argument, const AuthInfo &a) { argument.beginStructure(); argument << quint8(1) << a.url.toString() << a.username << a.password << a.prompt << a.caption << a.comment << a.commentLabel << a.realmValue << a.digestInfo << a.verifyPath << a.readOnly << a.keepPassword << a.modified << a.d->extraFields; argument.endStructure(); return argument; } const QDBusArgument &KIO::operator>>(const QDBusArgument &argument, AuthInfo &a) { QString url; quint8 version; argument.beginStructure(); argument >> version >> url >> a.username >> a.password >> a.prompt >> a.caption >> a.comment >> a.commentLabel >> a.realmValue >> a.digestInfo >> a.verifyPath >> a.readOnly >> a.keepPassword >> a.modified >> a.d->extraFields; argument.endStructure(); a.url = QUrl(url); return argument; } typedef QList LoginList; typedef QMap LoginMap; class Q_DECL_HIDDEN NetRC::NetRCPrivate { public: NetRCPrivate() : isDirty(false), index(-1) {} QString extract(const QString &buf, const QString &key); void getMachinePart(const QString &line); void getMacdefPart(const QString &line); bool isDirty; LoginMap loginMap; QTextStream fstream; QString type; int index; }; NetRC *NetRC::instance = nullptr; NetRC::NetRC() : d(new NetRCPrivate) { } NetRC::~NetRC() { delete instance; instance = nullptr; delete d; } NetRC *NetRC::self() { if (!instance) { instance = new NetRC; } return instance; } bool NetRC::lookup(const QUrl &url, AutoLogin &login, bool userealnetrc, const QString &_type, LookUpMode mode) { //qDebug() << "AutoLogin lookup for: " << url.host(); if (!url.isValid()) { return false; } QString type = _type; if (type.isEmpty()) { type = url.scheme(); } if (d->loginMap.isEmpty() || d->isDirty) { d->loginMap.clear(); QString filename = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + QLatin1String("kionetrc"); bool kionetrcStatus = parse(filename); bool netrcStatus = false; if (userealnetrc) { filename = QDir::homePath() + QLatin1String("/.netrc"); netrcStatus = parse(filename); } if (!(kionetrcStatus || netrcStatus)) { return false; } } if (!d->loginMap.contains(type)) { return false; } const LoginList &l = d->loginMap[type]; if (l.isEmpty()) { return false; } for (LoginList::ConstIterator it = l.begin(); it != l.end(); ++it) { const AutoLogin &log = *it; if ((mode & defaultOnly) == defaultOnly && log.machine == QLatin1String("default") && (login.login.isEmpty() || login.login == log.login)) { login.type = log.type; login.machine = log.machine; login.login = log.login; login.password = log.password; login.macdef = log.macdef; } if ((mode & presetOnly) == presetOnly && log.machine == QLatin1String("preset") && (login.login.isEmpty() || login.login == log.login)) { login.type = log.type; login.machine = log.machine; login.login = log.login; login.password = log.password; login.macdef = log.macdef; } if ((mode & exactOnly) == exactOnly && log.machine == url.host() && (login.login.isEmpty() || login.login == log.login)) { login.type = log.type; login.machine = log.machine; login.login = log.login; login.password = log.password; login.macdef = log.macdef; break; } } return true; } void NetRC::reload() { d->isDirty = true; } bool NetRC::parse(const QString &fileName) { QFile file(fileName); if (file.permissions() != (QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser)) { return false; } if (!file.open(QIODevice::ReadOnly)) { return false; } d->fstream.setDevice(&file); QString line; while (!d->fstream.atEnd()) { line = d->fstream.readLine().simplified(); // If line is a comment or is empty, read next line if ((line.startsWith(QLatin1String("#")) || line.isEmpty())) { continue; } // If line refers to a machine, maybe it is spread in more lines. // getMachinePart() will take care of getting all the info and putting it into loginMap. if ((line.startsWith(QLatin1String("machine")) || line.startsWith(QLatin1String("default")) || line.startsWith(QLatin1String("preset")))) { d->getMachinePart(line); continue; } // If line refers to a macdef, it will be more than one line. // getMacdefPart() will take care of getting all the lines of the macro // and putting them into loginMap if (line.startsWith(QLatin1String("macdef"))) { d->getMacdefPart(line); continue; } } return true; } QString NetRC::NetRCPrivate::extract(const QString &buf, const QString &key) { QStringList stringList = buf.split(QLatin1Char(' '), QString::SkipEmptyParts); int i = stringList.indexOf(key); if ((i != -1) && (i + 1 < stringList.size())) { return stringList.at(i + 1); } else { return QString(); } } void NetRC::NetRCPrivate::getMachinePart(const QString &line) { QString buf = line; while (!(buf.contains(QStringLiteral("login")) && (buf.contains(QStringLiteral("password")) || buf.contains(QStringLiteral("account")) || buf.contains(QStringLiteral("type"))))) { buf += QStringLiteral(" "); buf += fstream.readLine().simplified(); } // Once we've got all the info, process it. AutoLogin l; l.machine = extract(buf, QStringLiteral("machine")); if (l.machine.isEmpty()) { if (buf.contains(QStringLiteral("default"))) { l.machine = QStringLiteral("default"); } else if (buf.contains(QStringLiteral("preset"))) { l.machine = QStringLiteral("preset"); } } l.login = extract(buf, QStringLiteral("login")); l.password = extract(buf, QStringLiteral("password")); if (l.password.isEmpty()) { l.password = extract(buf, QStringLiteral("account")); } type = l.type = extract(buf, QStringLiteral("type")); if (l.type.isEmpty() && !l.machine.isEmpty()) { type = l.type = QStringLiteral("ftp"); } loginMap[l.type].append(l); index = loginMap[l.type].count() - 1; } void NetRC::NetRCPrivate::getMacdefPart(const QString &line) { QString buf = line; QString macro = extract(buf, QStringLiteral("macdef")); QString newLine; while (!fstream.atEnd()) { newLine = fstream.readLine().simplified(); if (!newLine.isEmpty()) { buf += QStringLiteral("\n"); buf += newLine; } else { break; } } loginMap[type][index].macdef[macro].append(buf); } diff --git a/src/core/authinfo.h b/src/core/authinfo.h index 98d36498..656fcb2c 100644 --- a/src/core/authinfo.h +++ b/src/core/authinfo.h @@ -1,384 +1,384 @@ /* * This file is part of the KDE libraries * Copyright (C) 2000-2001 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. */ #ifndef KIO_AUTHINFO_H #define KIO_AUTHINFO_H #include "kiocore_export.h" -#include -#include -#include -#include -#include // Q_DECLARE_METATYPE +#include +#include +#include +#include +#include // Q_DECLARE_METATYPE class QDBusArgument; namespace KIO { class AuthInfoPrivate; /** * @class KIO::AuthInfo authinfo.h * * This class is intended to make it easier to prompt for, cache * and retrieve authorization information. * * When using this class to cache, retrieve or prompt authentication * information, you only need to set the necessary attributes. For * example, to check whether a password is already cached, the only * required information is the URL of the resource and optionally * whether or not a path match should be performed. Similarly, to * prompt for password you only need to optionally set the prompt, * username (if already supplied), comment and commentLabel fields. * * SPECIAL NOTE: If you extend this class to add additional * parameters do not forget to overload the stream insertion and * extraction operators ("<<" and ">>") so that the added data can * be correctly serialzed. * * @short A two way messaging class for passing authentication information. * @author Dawit Alemayehu */ class KIOCORE_EXPORT AuthInfo { KIOCORE_EXPORT friend QDataStream &operator<< (QDataStream &s, const AuthInfo &a); KIOCORE_EXPORT friend QDataStream &operator>> (QDataStream &s, AuthInfo &a); KIOCORE_EXPORT friend QDBusArgument &operator<<(QDBusArgument &argument, const AuthInfo &a); KIOCORE_EXPORT friend const QDBusArgument &operator>>(const QDBusArgument &argument, AuthInfo &a); public: /** * Default constructor. */ AuthInfo(); /** * Copy constructor. */ AuthInfo(const AuthInfo &info); /** * Destructor * @since 4.1 */ ~AuthInfo(); /** * Custom assignment operator. */ AuthInfo &operator=(const AuthInfo &info); /** * Use this method to check if the object was modified. * @return true if the object has been modified */ bool isModified() const; /** * Use this method to indicate that this object has been modified. * @param flag true to mark the object as modified, false to clear */ void setModified(bool flag); /** * The URL for which authentication is to be stored. * * This field is required when attempting to cache authorization * and retrieve it. However, it is not needed when prompting * the user for authorization info. * * This setting is @em required except when prompting the * user for password. */ QUrl url; /** * This is @em required for caching. */ QString username; /** * This is @em required for caching. */ QString password; /** * Information to be displayed when prompting * the user for authentication information. * * @note If this field is not set, the authentication * dialog simply displays the preset default prompt. * * This setting is @em optional and empty by default. */ QString prompt; /** * The text to displayed in the title bar of * the password prompting dialog. * * @note If this field is not set, the authentication * dialog simply displays the preset default caption. * * This setting is @em optional and empty by default. */ QString caption; /** * Additional comment to be displayed when prompting * the user for authentication information. * * This field allows you to display a short (no more than * 80 characters) extra description in the password prompt * dialog. For example, this field along with the * commentLabel can be used to describe the server that * requested the authentication: * * \code * Server: Squid Proxy @ foo.com * \endcode * * where "Server:" is the commentLabel and the rest is the * actual comment. Note that it is always better to use * the @p commentLabel field as it will be placed properly * in the dialog rather than to include it within the actual * comment. * * This setting is @em optional and empty by default. */ QString comment; /** * Descriptive label to be displayed in front of the * comment when prompting the user for password. * * This setting is @em optional and only applicable when * the comment field is also set. */ QString commentLabel; /** * A unique identifier that allows caching of multiple * passwords for different resources in the same server. * * Mostly this setting is applicable to the HTTP protocol * whose authentication scheme explicitly defines the use * of such a unique key. However, any protocol that can * generate or supply a unique id can effectively use it * to distinguish passwords. * * This setting is @em optional and not set by default. */ QString realmValue; /** * Field to store any extra authentication information for * protocols that need it. * * This setting is @em optional and mostly applicable for HTTP * protocol. However, any protocol can make use of it to * store extra info. */ QString digestInfo; /** * Flag that, if set, indicates whether a path match should be * performed when requesting for cached authorization. * * A path is deemed to be a match if it is equal to or is a subset * of the cached path. For example, if stored path is "/foo/bar" * and the request's path set to "/foo/bar/acme", then it is a match * whereas it would not if the request's path was set to "/foo". * * This setting is @em optional and false by default. */ bool verifyPath; /** * Flag which if set forces the username field to be read-only. * * This setting is @em optional and false by default. */ bool readOnly; /** * Flag to indicate the persistence of the given password. * * This is a two-way flag, when set before calling openPasswordDialog * it makes the "keep Password" check box visible to the user. * In return the flag will indicate the state of the check box. * By default if the flag is checked the password will be cached * for the entire life of the current KDE session otherwise the * cached password is deleted right after the application using * it has been closed. */ bool keepPassword; /** * Flags for extra fields * @since 4.1 */ enum FieldFlags { ExtraFieldNoFlags = 0, ExtraFieldReadOnly = 1 << 1, ExtraFieldMandatory = 1 << 2 }; /** * Set Extra Field Value. * Currently supported extra-fields: * "domain" (QString), * "anonymous" (bool) * Setting it to an invalid QVariant() will disable the field. * Extra Fields are disabled by default. * @since 4.1 */ void setExtraField(const QString &fieldName, const QVariant &value); /** * Set Extra Field Flags * @since 4.1 */ void setExtraFieldFlags(const QString &fieldName, const FieldFlags flags); /** * Get Extra Field Value * Check QVariant::isValid() to find out if the field exists. * @since 4.1 */ QVariant getExtraField(const QString &fieldName) const; /** * Get Extra Field Flags * @since 4.1 */ AuthInfo::FieldFlags getExtraFieldFlags(const QString &fieldName) const; /** * Register the meta-types for AuthInfo. This is called from * AuthInfo's constructor but needed by daemons on the D-Bus such * as kpasswdserver. * @since 4.3 */ static void registerMetaTypes(); protected: bool modified; private: friend class ::KIO::AuthInfoPrivate; AuthInfoPrivate *const d; }; KIOCORE_EXPORT QDataStream &operator<< (QDataStream &s, const AuthInfo &a); KIOCORE_EXPORT QDataStream &operator>> (QDataStream &s, AuthInfo &a); KIOCORE_EXPORT QDBusArgument &operator<<(QDBusArgument &argument, const AuthInfo &a); KIOCORE_EXPORT const QDBusArgument &operator>>(const QDBusArgument &argument, AuthInfo &a); /** * A Singleton class that provides access to passwords * stored in .netrc files for automatic login purposes. * This is only meant to address backward compatibility * with old automated ftp client style logins... * * @short An interface to the ftp .netrc files * @author Dawit Alemayehu */ class KIOCORE_EXPORT NetRC { public: /** * Specifies the mode to be used when searching for a * matching automatic login info for a given site : * * @li exactOnly search entries with exact host name matches. * @li defaultOnly search entries that are specified as "default". * @li presetOnly search entries that are specified as "preset". * * @see lookup */ enum LookUpModeFlag { exactOnly = 0x0002, defaultOnly = 0x0004, presetOnly = 0x0008 }; Q_DECLARE_FLAGS(LookUpMode, LookUpModeFlag) /** * Contains auto login information. * @see lookup() */ struct AutoLogin { QString type; QString machine; QString login; QString password; QMap macdef; }; /** * A reference to the instance of the class. * @return the class */ static NetRC *self(); /** * Looks up the @p login information for the given @p url. * * @param url the url whose login information will be checked * @param login the login information will be writte here * @param userealnetrc if true, use $HOME/.netrc file * @param type the type of the login. If null, the @p url's protocol * will be taken * @param mode the LookUpMode flags (ORed) for the query */ bool lookup(const QUrl &url, AutoLogin &login, bool userealnetrc = false, const QString &type = QString(), LookUpMode mode = LookUpMode(exactOnly) | defaultOnly); /** * Reloads the auto login information. */ void reload(); protected: bool parse(const QString &fileName); private: NetRC(); ~NetRC(); private: static NetRC *instance; class NetRCPrivate; NetRCPrivate *const d; }; } Q_DECLARE_OPERATORS_FOR_FLAGS(KIO::NetRC::LookUpMode) Q_DECLARE_METATYPE(KIO::AuthInfo) #endif diff --git a/src/core/chmodjob.cpp b/src/core/chmodjob.cpp index 886a059b..68f7623e 100644 --- a/src/core/chmodjob.cpp +++ b/src/core/chmodjob.cpp @@ -1,294 +1,294 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow David Faure Waldo Bastian 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 "chmodjob.h" #include "../pathhelpers_p.h" #include #include -#include -#include +#include +#include #include #include "listjob.h" #include "job_p.h" #include "jobuidelegatefactory.h" #include "kioglobal_p.h" namespace KIO { struct ChmodInfo { QUrl url; int permissions; }; enum ChmodJobState { CHMODJOB_STATE_LISTING, CHMODJOB_STATE_CHMODING }; class ChmodJobPrivate: public KIO::JobPrivate { public: ChmodJobPrivate(const KFileItemList &lstItems, int permissions, int mask, KUserId newOwner, KGroupId newGroup, bool recursive) : state(CHMODJOB_STATE_LISTING) , m_permissions(permissions) , m_mask(mask) , m_newOwner(newOwner) , m_newGroup(newGroup) , m_recursive(recursive) , m_bAutoSkipFiles(false) , m_lstItems(lstItems) { } ChmodJobState state; int m_permissions; int m_mask; KUserId m_newOwner; KGroupId m_newGroup; bool m_recursive; bool m_bAutoSkipFiles; KFileItemList m_lstItems; QLinkedList m_infos; // linkedlist since we keep removing the first item void _k_chmodNextFile(); void _k_slotEntries(KIO::Job *, const KIO::UDSEntryList &); void _k_processList(); Q_DECLARE_PUBLIC(ChmodJob) static inline ChmodJob *newJob(const KFileItemList &lstItems, int permissions, int mask, KUserId newOwner, KGroupId newGroup, bool recursive, JobFlags flags) { ChmodJob *job = new ChmodJob(*new ChmodJobPrivate(lstItems, permissions, mask, newOwner, newGroup, recursive)); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); if (!(flags & HideProgressInfo)) { KIO::getJobTracker()->registerJob(job); } if (!(flags & NoPrivilegeExecution)) { job->d_func()->m_privilegeExecutionEnabled = true; job->d_func()->m_operationType = ChangeAttr; } return job; } }; } // namespace KIO using namespace KIO; ChmodJob::ChmodJob(ChmodJobPrivate &dd) : KIO::Job(dd) { QMetaObject::invokeMethod(this, "_k_processList", Qt::QueuedConnection); } ChmodJob::~ChmodJob() { } void ChmodJobPrivate::_k_processList() { Q_Q(ChmodJob); while (!m_lstItems.isEmpty()) { const KFileItem item = m_lstItems.first(); if (!item.isLink()) { // don't do anything with symlinks // File or directory -> remember to chmod ChmodInfo info; info.url = item.url(); // This is a toplevel file, we apply changes directly (no +X emulation here) const mode_t permissions = item.permissions() & 0777; // get rid of "set gid" and other special flags info.permissions = (m_permissions & m_mask) | (permissions & ~m_mask); /*//qDebug() << "toplevel url:" << info.url << "\n current permissions=" << QString::number(permissions,8) << "\n wanted permission=" << QString::number(m_permissions,8) << "\n with mask=" << QString::number(m_mask,8) << "\n with ~mask (mask bits we keep) =" << QString::number((uint)~m_mask,8) << "\n bits we keep =" << QString::number(permissions & ~m_mask,8) << "\n new permissions = " << QString::number(info.permissions,8);*/ m_infos.prepend(info); //qDebug() << "processList : Adding info for " << info.url; // Directory and recursive -> list if (item.isDir() && m_recursive) { //qDebug() << "ChmodJob::processList dir -> listing"; KIO::ListJob *listJob = KIO::listRecursive(item.url(), KIO::HideProgressInfo); q->connect(listJob, SIGNAL(entries(KIO::Job *, const KIO::UDSEntryList &)), SLOT(_k_slotEntries(KIO::Job*,KIO::UDSEntryList))); q->addSubjob(listJob); return; // we'll come back later, when this one's finished } } m_lstItems.removeFirst(); } //qDebug() << "ChmodJob::processList -> going to STATE_CHMODING"; // We have finished, move on state = CHMODJOB_STATE_CHMODING; _k_chmodNextFile(); } void ChmodJobPrivate::_k_slotEntries(KIO::Job *, const KIO::UDSEntryList &list) { KIO::UDSEntryList::ConstIterator it = list.begin(); KIO::UDSEntryList::ConstIterator end = list.end(); for (; it != end; ++it) { const KIO::UDSEntry &entry = *it; const bool isLink = !entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST).isEmpty(); const QString relativePath = entry.stringValue(KIO::UDSEntry::UDS_NAME); if (!isLink && relativePath != QLatin1String("..")) { const mode_t permissions = entry.numberValue(KIO::UDSEntry::UDS_ACCESS) & 0777; // get rid of "set gid" and other special flags ChmodInfo info; info.url = m_lstItems.first().url(); // base directory info.url.setPath(concatPaths(info.url.path(), relativePath)); int mask = m_mask; // Emulate -X: only give +x to files that had a +x bit already // So the check is the opposite : if the file had no x bit, don't touch x bits // For dirs this doesn't apply if (!entry.isDir()) { int newPerms = m_permissions & mask; if ((newPerms & 0111) && !(permissions & 0111)) { // don't interfere with mandatory file locking if (newPerms & 02000) { mask = mask & ~0101; } else { mask = mask & ~0111; } } } info.permissions = (m_permissions & mask) | (permissions & ~mask); /*//qDebug() << info.url << "\n current permissions=" << QString::number(permissions,8) << "\n wanted permission=" << QString::number(m_permissions,8) << "\n with mask=" << QString::number(mask,8) << "\n with ~mask (mask bits we keep) =" << QString::number((uint)~mask,8) << "\n bits we keep =" << QString::number(permissions & ~mask,8) << "\n new permissions = " << QString::number(info.permissions,8);*/ // Prepend this info in our todo list. // This way, the toplevel dirs are done last. m_infos.prepend(info); } } } void ChmodJobPrivate::_k_chmodNextFile() { Q_Q(ChmodJob); if (!m_infos.isEmpty()) { ChmodInfo info = m_infos.takeFirst(); // First update group / owner (if local file) // (permissions have to set after, in case of suid and sgid) if (info.url.isLocalFile() && (m_newOwner.isValid() || m_newGroup.isValid())) { QString path = info.url.toLocalFile(); if (!KIOPrivate::changeOwnership(path, m_newOwner, m_newGroup)) { if (!m_uiDelegateExtension) { emit q->warning(q, i18n("Could not modify the ownership of file %1", path)); } else if (!m_bAutoSkipFiles) { const QString errMsg = i18n("Could not modify the ownership of file %1. You have insufficient access to the file to perform the change.", path); SkipDialog_Options options; if (m_infos.count() > 1) { options |= SkipDialog_MultipleItems; } const SkipDialog_Result skipResult = m_uiDelegateExtension->askSkip(q, options, errMsg); switch (skipResult) { case Result_AutoSkip: m_bAutoSkipFiles = true; // fall through case Result_Skip: QMetaObject::invokeMethod(q, "_k_chmodNextFile", Qt::QueuedConnection); return; case Result_Retry: m_infos.prepend(info); QMetaObject::invokeMethod(q, "_k_chmodNextFile", Qt::QueuedConnection); return; case Result_Cancel: default: q->setError(ERR_USER_CANCELED); q->emitResult(); return; } } } } /*qDebug() << "chmod'ing" << info.url << "to" << QString::number(info.permissions,8);*/ KIO::SimpleJob *job = KIO::chmod(info.url, info.permissions); job->setParentJob(q); // copy the metadata for acl and default acl const QString aclString = q->queryMetaData(QStringLiteral("ACL_STRING")); const QString defaultAclString = q->queryMetaData(QStringLiteral("DEFAULT_ACL_STRING")); if (!aclString.isEmpty()) { job->addMetaData(QStringLiteral("ACL_STRING"), aclString); } if (!defaultAclString.isEmpty()) { job->addMetaData(QStringLiteral("DEFAULT_ACL_STRING"), defaultAclString); } q->addSubjob(job); } else // We have finished { q->emitResult(); } } void ChmodJob::slotResult(KJob *job) { Q_D(ChmodJob); removeSubjob(job); if (job->error()) { setError(job->error()); setErrorText(job->errorText()); emitResult(); return; } //qDebug() << "d->m_lstItems:" << d->m_lstItems.count(); switch (d->state) { case CHMODJOB_STATE_LISTING: d->m_lstItems.removeFirst(); //qDebug() << "-> processList"; d->_k_processList(); return; case CHMODJOB_STATE_CHMODING: //qDebug() << "-> chmodNextFile"; d->_k_chmodNextFile(); return; default: Q_ASSERT(false); return; } } ChmodJob *KIO::chmod(const KFileItemList &lstItems, int permissions, int mask, const QString &owner, const QString &group, bool recursive, JobFlags flags) { KUserId uid = KUserId::fromName(owner); KGroupId gid = KGroupId::fromName(group); return ChmodJobPrivate::newJob(lstItems, permissions, mask, uid, gid, recursive, flags); } #include "moc_chmodjob.cpp" diff --git a/src/core/copyjob.cpp b/src/core/copyjob.cpp index 31d69ec0..b9424d7b 100644 --- a/src/core/copyjob.cpp +++ b/src/core/copyjob.cpp @@ -1,2270 +1,2270 @@ /* This file is part of the KDE libraries Copyright 2000 Stephan Kulow Copyright 2000-2006 David Faure Copyright 2000 Waldo Bastian 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 "copyjob.h" #include "kiocoredebug.h" #include #include "kcoredirlister.h" #include "kfileitem.h" #include "job.h" // buildErrorString #include "mkdirjob.h" #include "listjob.h" #include "statjob.h" #include "deletejob.h" #include "filecopyjob.h" #include "../pathhelpers_p.h" #include #include #include #include "slave.h" #include "scheduler.h" #include "kdirwatch.h" #include "kprotocolmanager.h" #include #include #include #ifdef Q_OS_UNIX #include #endif #include #include -#include -#include -#include +#include +#include +#include #include #include // mode_t #include #include "job_p.h" #include #include #include Q_DECLARE_LOGGING_CATEGORY(KIO_COPYJOB_DEBUG) Q_LOGGING_CATEGORY(KIO_COPYJOB_DEBUG, "kf5.kio.core.copyjob", QtWarningMsg) using namespace KIO; //this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX #define REPORT_TIMEOUT 200 enum DestinationState { DEST_NOT_STATED, DEST_IS_DIR, DEST_IS_FILE, DEST_DOESNT_EXIST }; /** * States: * STATE_INITIAL the constructor was called * STATE_STATING for the dest * statCurrentSrc then does, for each src url: * STATE_RENAMING if direct rename looks possible * (on already exists, and user chooses rename, TODO: go to STATE_RENAMING again) * STATE_STATING * and then, if dir -> STATE_LISTING (filling 'd->dirs' and 'd->files') * STATE_CREATING_DIRS (createNextDir, iterating over 'd->dirs') * if conflict: STATE_CONFLICT_CREATING_DIRS * STATE_COPYING_FILES (copyNextFile, iterating over 'd->files') * if conflict: STATE_CONFLICT_COPYING_FILES * STATE_DELETING_DIRS (deleteNextDir) (if moving) * STATE_SETTING_DIR_ATTRIBUTES (setNextDirAttribute, iterating over d->m_directoriesCopied) * done. */ enum CopyJobState { STATE_INITIAL, STATE_STATING, STATE_RENAMING, STATE_LISTING, STATE_CREATING_DIRS, STATE_CONFLICT_CREATING_DIRS, STATE_COPYING_FILES, STATE_CONFLICT_COPYING_FILES, STATE_DELETING_DIRS, STATE_SETTING_DIR_ATTRIBUTES }; static QUrl addPathToUrl(const QUrl &url, const QString &relPath) { QUrl u(url); u.setPath(concatPaths(url.path(), relPath)); return u; } /** @internal */ class KIO::CopyJobPrivate: public KIO::JobPrivate { public: CopyJobPrivate(const QList &src, const QUrl &dest, CopyJob::CopyMode mode, bool asMethod) : m_globalDest(dest) , m_globalDestinationState(DEST_NOT_STATED) , m_defaultPermissions(false) , m_bURLDirty(false) , m_mode(mode) , m_asMethod(asMethod) , destinationState(DEST_NOT_STATED) , state(STATE_INITIAL) , m_freeSpace(-1) , m_totalSize(0) , m_processedSize(0) , m_fileProcessedSize(0) , m_processedFiles(0) , m_processedDirs(0) , m_srcList(src) , m_currentStatSrc(m_srcList.constBegin()) , m_bCurrentOperationIsLink(false) , m_bSingleFileCopy(false) , m_bOnlyRenames(mode == CopyJob::Move) , m_dest(dest) , m_bAutoRenameFiles(false) , m_bAutoRenameDirs(false) , m_bAutoSkipFiles(false) , m_bAutoSkipDirs(false) , m_bOverwriteAllFiles(false) , m_bOverwriteAllDirs(false) , m_conflictError(0) , m_reportTimer(nullptr) { } // This is the dest URL that was initially given to CopyJob // It is copied into m_dest, which can be changed for a given src URL // (when using the RENAME dialog in slotResult), // and which will be reset for the next src URL. QUrl m_globalDest; // The state info about that global dest DestinationState m_globalDestinationState; // See setDefaultPermissions bool m_defaultPermissions; // Whether URLs changed (and need to be emitted by the next slotReport call) bool m_bURLDirty; // Used after copying all the files into the dirs, to set mtime (TODO: and permissions?) // after the copy is done QLinkedList m_directoriesCopied; QLinkedList::const_iterator m_directoriesCopiedIterator; CopyJob::CopyMode m_mode; bool m_asMethod; DestinationState destinationState; CopyJobState state; KIO::filesize_t m_freeSpace; KIO::filesize_t m_totalSize; KIO::filesize_t m_processedSize; KIO::filesize_t m_fileProcessedSize; int m_processedFiles; int m_processedDirs; QList files; QList dirs; QList dirsToRemove; QList m_srcList; QList m_successSrcList; // Entries in m_srcList that have successfully been moved QList::const_iterator m_currentStatSrc; bool m_bCurrentSrcIsDir; bool m_bCurrentOperationIsLink; bool m_bSingleFileCopy; bool m_bOnlyRenames; QUrl m_dest; QUrl m_currentDest; // set during listing, used by slotEntries // QStringList m_skipList; QSet m_overwriteList; bool m_bAutoRenameFiles; bool m_bAutoRenameDirs; bool m_bAutoSkipFiles; bool m_bAutoSkipDirs; bool m_bOverwriteAllFiles; bool m_bOverwriteAllDirs; int m_conflictError; QTimer *m_reportTimer; // The current src url being stat'ed or copied // During the stat phase, this is initially equal to *m_currentStatSrc but it can be resolved to a local file equivalent (#188903). QUrl m_currentSrcURL; QUrl m_currentDestURL; QSet m_parentDirs; void statCurrentSrc(); void statNextSrc(); // Those aren't slots but submethods for slotResult. void slotResultStating(KJob *job); void startListing(const QUrl &src); void slotResultCreatingDirs(KJob *job); void slotResultConflictCreatingDirs(KJob *job); void createNextDir(); void slotResultCopyingFiles(KJob *job); void slotResultErrorCopyingFiles(KJob *job); // KIO::Job* linkNextFile( const QUrl& uSource, const QUrl& uDest, bool overwrite ); KIO::Job *linkNextFile(const QUrl &uSource, const QUrl &uDest, JobFlags flags); void copyNextFile(); void slotResultDeletingDirs(KJob *job); void deleteNextDir(); void sourceStated(const UDSEntry &entry, const QUrl &sourceUrl); void skip(const QUrl &sourceURL, bool isDir); void slotResultRenaming(KJob *job); void slotResultSettingDirAttributes(KJob *job); void setNextDirAttribute(); void startRenameJob(const QUrl &slave_url); bool shouldOverwriteDir(const QString &path) const; bool shouldOverwriteFile(const QString &path) const; bool shouldSkip(const QString &path) const; void skipSrc(bool isDir); void renameDirectory(QList::iterator it, const QUrl &newUrl); QUrl finalDestUrl(const QUrl &src, const QUrl &dest) const; void slotStart(); void slotEntries(KIO::Job *, const KIO::UDSEntryList &list); void slotSubError(KIO::ListJob *job, KIO::ListJob *subJob); void addCopyInfoFromUDSEntry(const UDSEntry &entry, const QUrl &srcUrl, bool srcIsDir, const QUrl ¤tDest); /** * Forward signal from subjob */ void slotProcessedSize(KJob *, qulonglong data_size); /** * Forward signal from subjob * @param size the total size */ void slotTotalSize(KJob *, qulonglong size); void slotReport(); Q_DECLARE_PUBLIC(CopyJob) static inline CopyJob *newJob(const QList &src, const QUrl &dest, CopyJob::CopyMode mode, bool asMethod, JobFlags flags) { CopyJob *job = new CopyJob(*new CopyJobPrivate(src, dest, mode, asMethod)); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); if (!(flags & HideProgressInfo)) { KIO::getJobTracker()->registerJob(job); } if (flags & KIO::Overwrite) { job->d_func()->m_bOverwriteAllDirs = true; job->d_func()->m_bOverwriteAllFiles = true; } if (!(flags & KIO::NoPrivilegeExecution)) { job->d_func()->m_privilegeExecutionEnabled = true; FileOperationType copyType; switch (mode) { case CopyJob::Copy: copyType = Copy; break; case CopyJob::Move: copyType = Move; break; case CopyJob::Link: copyType = Symlink; break; } job->d_func()->m_operationType = copyType; } return job; } }; CopyJob::CopyJob(CopyJobPrivate &dd) : Job(dd) { setProperty("destUrl", d_func()->m_dest.toString()); QTimer::singleShot(0, this, SLOT(slotStart())); qRegisterMetaType(); } CopyJob::~CopyJob() { } QList CopyJob::srcUrls() const { return d_func()->m_srcList; } QUrl CopyJob::destUrl() const { return d_func()->m_dest; } void CopyJobPrivate::slotStart() { Q_Q(CopyJob); if (q->isSuspended()) { return; } if (m_mode == CopyJob::CopyMode::Move) { Q_FOREACH (const QUrl &url, m_srcList) { if (m_dest.scheme() == url.scheme() && m_dest.host() == url.host()) { QString srcPath = url.path(); if (!srcPath.endsWith(QLatin1Char('/'))) srcPath += QLatin1Char('/'); if (m_dest.path().startsWith(srcPath)) { q->setError(KIO::ERR_CANNOT_MOVE_INTO_ITSELF); q->emitResult(); return; } } } } /** We call the functions directly instead of using signals. Calling a function via a signal takes approx. 65 times the time compared to calling it directly (at least on my machine). aleXXX */ m_reportTimer = new QTimer(q); q->connect(m_reportTimer, SIGNAL(timeout()), q, SLOT(slotReport())); m_reportTimer->start(REPORT_TIMEOUT); // Stat the dest state = STATE_STATING; const QUrl dest = m_asMethod ? m_dest.adjusted(QUrl::RemoveFilename) : m_dest; KIO::Job *job = KIO::stat(dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo); qCDebug(KIO_COPYJOB_DEBUG) << "CopyJob: stating the dest" << m_dest; q->addSubjob(job); } // For unit test purposes KIOCORE_EXPORT bool kio_resolve_local_urls = true; void CopyJobPrivate::slotResultStating(KJob *job) { Q_Q(CopyJob); qCDebug(KIO_COPYJOB_DEBUG); // Was there an error while stating the src ? if (job->error() && destinationState != DEST_NOT_STATED) { const QUrl srcurl = static_cast(job)->url(); if (!srcurl.isLocalFile()) { // Probably : src doesn't exist. Well, over some protocols (e.g. FTP) // this info isn't really reliable (thanks to MS FTP servers). // We'll assume a file, and try to download anyway. qCDebug(KIO_COPYJOB_DEBUG) << "Error while stating source. Activating hack"; q->removeSubjob(job); assert(!q->hasSubjobs()); // We should have only one job at a time ... struct CopyInfo info; info.permissions = (mode_t) - 1; info.size = (KIO::filesize_t) - 1; info.uSource = srcurl; info.uDest = m_dest; // Append filename or dirname to destination URL, if allowed if (destinationState == DEST_IS_DIR && !m_asMethod) { const QString fileName = srcurl.scheme() == "data" ? "data" : srcurl.fileName(); // #379093 info.uDest = addPathToUrl(info.uDest, fileName); } files.append(info); statNextSrc(); return; } // Local file. If stat fails, the file definitely doesn't exist. // yes, q->Job::, because we don't want to call our override q->Job::slotResult(job); // will set the error and emit result(this) return; } // Keep copy of the stat result const UDSEntry entry = static_cast(job)->statResult(); if (destinationState == DEST_NOT_STATED) { if (m_dest.isLocalFile()) { //works for dirs as well QString path(m_dest.toLocalFile()); QFileInfo fileInfo(path); if (m_asMethod || !fileInfo.exists()) { // In copy-as mode, we want to check the directory to which we're // copying. The target file or directory does not exist yet, which // might confuse KDiskFreeSpaceInfo. path = fileInfo.absolutePath(); } KDiskFreeSpaceInfo freeSpaceInfo = KDiskFreeSpaceInfo::freeSpaceInfo(path); if (freeSpaceInfo.isValid()) { m_freeSpace = freeSpaceInfo.available(); } else { qCDebug(KIO_COPYJOB_DEBUG) << "Couldn't determine free space information for" << path; } //TODO actually preliminary check is even more valuable for slow NFS/SMB mounts, //but we need to find a way to report connection errors to user } const bool isGlobalDest = m_dest == m_globalDest; const bool isDir = entry.isDir(); // we were stating the dest if (job->error()) { destinationState = DEST_DOESNT_EXIST; qCDebug(KIO_COPYJOB_DEBUG) << "dest does not exist"; } else { // Treat symlinks to dirs as dirs here, so no test on isLink destinationState = isDir ? DEST_IS_DIR : DEST_IS_FILE; qCDebug(KIO_COPYJOB_DEBUG) << "dest is dir:" << isDir; const QString sLocalPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); if (!sLocalPath.isEmpty() && kio_resolve_local_urls && destinationState != DEST_DOESNT_EXIST) { const QString fileName = m_dest.fileName(); m_dest = QUrl::fromLocalFile(sLocalPath); if (m_asMethod) { m_dest = addPathToUrl(m_dest, fileName); } qCDebug(KIO_COPYJOB_DEBUG) << "Setting m_dest to the local path:" << sLocalPath; if (isGlobalDest) { m_globalDest = m_dest; } } } if (isGlobalDest) { m_globalDestinationState = destinationState; } q->removeSubjob(job); assert(!q->hasSubjobs()); // After knowing what the dest is, we can start stat'ing the first src. statCurrentSrc(); } else { sourceStated(entry, static_cast(job)->url()); q->removeSubjob(job); } } void CopyJobPrivate::sourceStated(const UDSEntry &entry, const QUrl &sourceUrl) { const QString sLocalPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); const bool isDir = entry.isDir(); // We were stating the current source URL // Is it a file or a dir ? // There 6 cases, and all end up calling addCopyInfoFromUDSEntry first : // 1 - src is a dir, destination is a directory, // slotEntries will append the source-dir-name to the destination // 2 - src is a dir, destination is a file -- will offer to overwrite, later on. // 3 - src is a dir, destination doesn't exist, then it's the destination dirname, // so slotEntries will use it as destination. // 4 - src is a file, destination is a directory, // slotEntries will append the filename to the destination. // 5 - src is a file, destination is a file, m_dest is the exact destination name // 6 - src is a file, destination doesn't exist, m_dest is the exact destination name QUrl srcurl; if (!sLocalPath.isEmpty() && destinationState != DEST_DOESNT_EXIST) { qCDebug(KIO_COPYJOB_DEBUG) << "Using sLocalPath. destinationState=" << destinationState; // Prefer the local path -- but only if we were able to stat() the dest. // Otherwise, renaming a desktop:/ url would copy from src=file to dest=desktop (#218719) srcurl = QUrl::fromLocalFile(sLocalPath); } else { srcurl = sourceUrl; } addCopyInfoFromUDSEntry(entry, srcurl, false, m_dest); m_currentDest = m_dest; m_bCurrentSrcIsDir = false; if (isDir // treat symlinks as files (no recursion) && !entry.isLink() && m_mode != CopyJob::Link) { // No recursion in Link mode either. qCDebug(KIO_COPYJOB_DEBUG) << "Source is a directory"; if (srcurl.isLocalFile()) { const QString parentDir = srcurl.adjusted(QUrl::StripTrailingSlash).toLocalFile(); m_parentDirs.insert(parentDir); } m_bCurrentSrcIsDir = true; // used by slotEntries if (destinationState == DEST_IS_DIR) { // (case 1) if (!m_asMethod) { // Use / as destination, from now on QString directory = srcurl.fileName(); const QString sName = entry.stringValue(KIO::UDSEntry::UDS_NAME); KProtocolInfo::FileNameUsedForCopying fnu = KProtocolManager::fileNameUsedForCopying(srcurl); if (fnu == KProtocolInfo::Name) { if (!sName.isEmpty()) { directory = sName; } } else if (fnu == KProtocolInfo::DisplayName) { const QString dispName = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME); if (!dispName.isEmpty()) { directory = dispName; } else if (!sName.isEmpty()) { directory = sName; } } m_currentDest = addPathToUrl(m_currentDest, directory); } } else { // (case 3) // otherwise dest is new name for toplevel dir // so the destination exists, in fact, from now on. // (This even works with other src urls in the list, since the // dir has effectively been created) destinationState = DEST_IS_DIR; if (m_dest == m_globalDest) { m_globalDestinationState = destinationState; } } startListing(srcurl); } else { qCDebug(KIO_COPYJOB_DEBUG) << "Source is a file (or a symlink), or we are linking -> no recursive listing"; if (srcurl.isLocalFile()) { const QString parentDir = srcurl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path(); m_parentDirs.insert(parentDir); } statNextSrc(); } } bool CopyJob::doSuspend() { Q_D(CopyJob); d->slotReport(); return Job::doSuspend(); } bool CopyJob::doResume() { Q_D(CopyJob); switch (d->state) { case STATE_INITIAL: QTimer::singleShot(0, this, SLOT(slotStart())); break; default: // not implemented break; } return Job::doResume(); } void CopyJobPrivate::slotReport() { Q_Q(CopyJob); if (q->isSuspended()) { return; } // If showProgressInfo was set, progressId() is > 0. switch (state) { case STATE_RENAMING: q->setTotalAmount(KJob::Files, m_srcList.count()); // fall-through intended #if QT_VERSION >= QT_VERSION_CHECK(5,8,0) Q_FALLTHROUGH(); #endif case STATE_COPYING_FILES: q->setProcessedAmount(KJob::Files, m_processedFiles); if (m_bURLDirty) { // Only emit urls when they changed. This saves time, and fixes #66281 m_bURLDirty = false; if (m_mode == CopyJob::Move) { emitMoving(q, m_currentSrcURL, m_currentDestURL); emit q->moving(q, m_currentSrcURL, m_currentDestURL); } else if (m_mode == CopyJob::Link) { emitCopying(q, m_currentSrcURL, m_currentDestURL); // we don't have a delegate->linking emit q->linking(q, m_currentSrcURL.path(), m_currentDestURL); } else { emitCopying(q, m_currentSrcURL, m_currentDestURL); emit q->copying(q, m_currentSrcURL, m_currentDestURL); } } break; case STATE_CREATING_DIRS: q->setProcessedAmount(KJob::Directories, m_processedDirs); if (m_bURLDirty) { m_bURLDirty = false; emit q->creatingDir(q, m_currentDestURL); emitCreatingDir(q, m_currentDestURL); } break; case STATE_STATING: case STATE_LISTING: if (m_bURLDirty) { m_bURLDirty = false; if (m_mode == CopyJob::Move) { emitMoving(q, m_currentSrcURL, m_currentDestURL); } else { emitCopying(q, m_currentSrcURL, m_currentDestURL); } } q->setTotalAmount(KJob::Bytes, m_totalSize); q->setTotalAmount(KJob::Files, files.count()); q->setTotalAmount(KJob::Directories, dirs.count()); break; default: break; } } void CopyJobPrivate::slotEntries(KIO::Job *job, const UDSEntryList &list) { //Q_Q(CopyJob); UDSEntryList::ConstIterator it = list.constBegin(); UDSEntryList::ConstIterator end = list.constEnd(); for (; it != end; ++it) { const UDSEntry &entry = *it; addCopyInfoFromUDSEntry(entry, static_cast(job)->url(), m_bCurrentSrcIsDir, m_currentDest); } } void CopyJobPrivate::slotSubError(ListJob *job, ListJob *subJob) { const QUrl url = subJob->url(); qCWarning(KIO_CORE) << url << subJob->errorString(); Q_Q(CopyJob); emit q->warning(job, subJob->errorString(), QString()); skip(url, true); } void CopyJobPrivate::addCopyInfoFromUDSEntry(const UDSEntry &entry, const QUrl &srcUrl, bool srcIsDir, const QUrl ¤tDest) { struct CopyInfo info; info.permissions = entry.numberValue(KIO::UDSEntry::UDS_ACCESS, -1); info.mtime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1)); info.ctime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1)); info.size = (KIO::filesize_t) entry.numberValue(KIO::UDSEntry::UDS_SIZE, -1); if (info.size != (KIO::filesize_t) - 1) { m_totalSize += info.size; } // recursive listing, displayName can be a/b/c/d const QString fileName = entry.stringValue(KIO::UDSEntry::UDS_NAME); const QString urlStr = entry.stringValue(KIO::UDSEntry::UDS_URL); QUrl url; if (!urlStr.isEmpty()) { url = QUrl(urlStr); } QString localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); const bool isDir = entry.isDir(); info.linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST); if (fileName != QLatin1String("..") && fileName != QLatin1String(".")) { const bool hasCustomURL = !url.isEmpty() || !localPath.isEmpty(); if (!hasCustomURL) { // Make URL from displayName url = srcUrl; if (srcIsDir) { // Only if src is a directory. Otherwise uSource is fine as is qCDebug(KIO_COPYJOB_DEBUG) << "adding path" << fileName; url = addPathToUrl(url, fileName); } } qCDebug(KIO_COPYJOB_DEBUG) << "fileName=" << fileName << "url=" << url; if (!localPath.isEmpty() && kio_resolve_local_urls && destinationState != DEST_DOESNT_EXIST) { url = QUrl::fromLocalFile(localPath); } info.uSource = url; info.uDest = currentDest; qCDebug(KIO_COPYJOB_DEBUG) << "uSource=" << info.uSource << "uDest(1)=" << info.uDest; // Append filename or dirname to destination URL, if allowed if (destinationState == DEST_IS_DIR && // "copy/move as " means 'foo' is the dest for the base srcurl // (passed here during stating) but not its children (during listing) (!(m_asMethod && state == STATE_STATING))) { QString destFileName; KProtocolInfo::FileNameUsedForCopying fnu = KProtocolManager::fileNameUsedForCopying(url); if (hasCustomURL && fnu == KProtocolInfo::FromUrl) { //destFileName = url.fileName(); // Doesn't work for recursive listing // Count the number of prefixes used by the recursive listjob int numberOfSlashes = fileName.count('/'); // don't make this a find()! QString path = url.path(); int pos = 0; for (int n = 0; n < numberOfSlashes + 1; ++n) { pos = path.lastIndexOf('/', pos - 1); if (pos == -1) { // error qCWarning(KIO_CORE) << "kioslave bug: not enough slashes in UDS_URL" << path << "- looking for" << numberOfSlashes << "slashes"; break; } } if (pos >= 0) { destFileName = path.mid(pos + 1); } } else if (fnu == KProtocolInfo::Name) { // destination filename taken from UDS_NAME destFileName = fileName; } else { // from display name (with fallback to name) const QString displayName = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME); destFileName = displayName.isEmpty() ? fileName : displayName; } // Here we _really_ have to add some filename to the dest. // Otherwise, we end up with e.g. dest=..../Desktop/ itself. // (This can happen when dropping a link to a webpage with no path) if (destFileName.isEmpty()) { destFileName = KIO::encodeFileName(info.uSource.toDisplayString()); } qCDebug(KIO_COPYJOB_DEBUG) << " adding destFileName=" << destFileName; info.uDest = addPathToUrl(info.uDest, destFileName); } qCDebug(KIO_COPYJOB_DEBUG) << " uDest(2)=" << info.uDest; qCDebug(KIO_COPYJOB_DEBUG) << " " << info.uSource << "->" << info.uDest; if (info.linkDest.isEmpty() && isDir && m_mode != CopyJob::Link) { // Dir dirs.append(info); // Directories if (m_mode == CopyJob::Move) { dirsToRemove.append(info.uSource); } } else { files.append(info); // Files and any symlinks } } } // Adjust for kio_trash choosing its own dest url... QUrl CopyJobPrivate::finalDestUrl(const QUrl& src, const QUrl &dest) const { Q_Q(const CopyJob); if (dest.scheme() == QLatin1String("trash")) { const QMap& metaData = q->metaData(); QMap::ConstIterator it = metaData.find("trashURL-" + src.path()); if (it != metaData.constEnd()) { qCDebug(KIO_COPYJOB_DEBUG) << "finalDestUrl=" << it.value(); return QUrl(it.value()); } } return dest; } void CopyJobPrivate::skipSrc(bool isDir) { m_dest = m_globalDest; destinationState = m_globalDestinationState; skip(*m_currentStatSrc, isDir); ++m_currentStatSrc; statCurrentSrc(); } void CopyJobPrivate::statNextSrc() { /* Revert to the global destination, the one that applies to all source urls. * Imagine you copy the items a b and c into /d, but /d/b exists so the user uses "Rename" to put it in /foo/b instead. * d->m_dest is /foo/b for b, but we have to revert to /d for item c and following. */ m_dest = m_globalDest; qCDebug(KIO_COPYJOB_DEBUG) << "Setting m_dest to" << m_dest; destinationState = m_globalDestinationState; ++m_currentStatSrc; statCurrentSrc(); } void CopyJobPrivate::statCurrentSrc() { Q_Q(CopyJob); if (m_currentStatSrc != m_srcList.constEnd()) { m_currentSrcURL = (*m_currentStatSrc); m_bURLDirty = true; if (m_mode == CopyJob::Link) { // Skip the "stating the source" stage, we don't need it for linking m_currentDest = m_dest; struct CopyInfo info; info.permissions = -1; info.size = (KIO::filesize_t) - 1; info.uSource = m_currentSrcURL; info.uDest = m_currentDest; // Append filename or dirname to destination URL, if allowed if (destinationState == DEST_IS_DIR && !m_asMethod) { if ( (m_currentSrcURL.scheme() == info.uDest.scheme()) && (m_currentSrcURL.host() == info.uDest.host()) && (m_currentSrcURL.port() == info.uDest.port()) && (m_currentSrcURL.userName() == info.uDest.userName()) && (m_currentSrcURL.password() == info.uDest.password())) { // This is the case of creating a real symlink info.uDest = addPathToUrl(info.uDest, m_currentSrcURL.fileName()); } else { // Different protocols, we'll create a .desktop file // We have to change the extension anyway, so while we're at it, // name the file like the URL info.uDest = addPathToUrl(info.uDest, KIO::encodeFileName(m_currentSrcURL.toDisplayString()) + ".desktop"); } } files.append(info); // Files and any symlinks statNextSrc(); // we could use a loop instead of a recursive call :) return; } // Let's see if we can skip stat'ing, for the case where a directory view has the info already KIO::UDSEntry entry; const KFileItem cachedItem = KCoreDirLister::cachedItemForUrl(m_currentSrcURL); if (!cachedItem.isNull()) { entry = cachedItem.entry(); if (destinationState != DEST_DOESNT_EXIST) { // only resolve src if we could resolve dest (#218719) bool dummyIsLocal; m_currentSrcURL = cachedItem.mostLocalUrl(dummyIsLocal); // #183585 } } if (m_mode == CopyJob::Move && ( // Don't go renaming right away if we need a stat() to find out the destination filename KProtocolManager::fileNameUsedForCopying(m_currentSrcURL) == KProtocolInfo::FromUrl || destinationState != DEST_IS_DIR || m_asMethod) ) { // If moving, before going for the full stat+[list+]copy+del thing, try to rename // The logic is pretty similar to FileCopyJobPrivate::slotStart() if ((m_currentSrcURL.scheme() == m_dest.scheme()) && (m_currentSrcURL.host() == m_dest.host()) && (m_currentSrcURL.port() == m_dest.port()) && (m_currentSrcURL.userName() == m_dest.userName()) && (m_currentSrcURL.password() == m_dest.password())) { startRenameJob(m_currentSrcURL); return; } else if (m_currentSrcURL.isLocalFile() && KProtocolManager::canRenameFromFile(m_dest)) { startRenameJob(m_dest); return; } else if (m_dest.isLocalFile() && KProtocolManager::canRenameToFile(m_currentSrcURL)) { startRenameJob(m_currentSrcURL); return; } } // if the file system doesn't support deleting, we do not even stat if (m_mode == CopyJob::Move && !KProtocolManager::supportsDeleting(m_currentSrcURL)) { QPointer that = q; emit q->warning(q, buildErrorString(ERR_CANNOT_DELETE, m_currentSrcURL.toDisplayString())); if (that) { statNextSrc(); // we could use a loop instead of a recursive call :) } return; } m_bOnlyRenames = false; // Testing for entry.count()>0 here is not good enough; KFileItem inserts // entries for UDS_USER and UDS_GROUP even on initially empty UDSEntries (#192185) if (entry.contains(KIO::UDSEntry::UDS_NAME)) { qCDebug(KIO_COPYJOB_DEBUG) << "fast path! found info about" << m_currentSrcURL << "in KCoreDirLister"; // sourceStated(entry, m_currentSrcURL); // don't recurse, see #319747, use queued invokeMethod instead QMetaObject::invokeMethod(q, "sourceStated", Qt::QueuedConnection, Q_ARG(KIO::UDSEntry, entry), Q_ARG(QUrl, m_currentSrcURL)); return; } // Stat the next src url Job *job = KIO::stat(m_currentSrcURL, StatJob::SourceSide, 2, KIO::HideProgressInfo); qCDebug(KIO_COPYJOB_DEBUG) << "KIO::stat on" << m_currentSrcURL; state = STATE_STATING; q->addSubjob(job); m_currentDestURL = m_dest; m_bURLDirty = true; } else { // Finished the stat'ing phase // First make sure that the totals were correctly emitted state = STATE_STATING; m_bURLDirty = true; slotReport(); qCDebug(KIO_COPYJOB_DEBUG)<<"Stating finished. To copy:"<aboutToCreate(q, dirs); } if (!files.isEmpty()) { emit q->aboutToCreate(q, files); } // Check if we are copying a single file m_bSingleFileCopy = (files.count() == 1 && dirs.isEmpty()); // Then start copying things state = STATE_CREATING_DIRS; createNextDir(); } } void CopyJobPrivate::startRenameJob(const QUrl &slave_url) { Q_Q(CopyJob); // Silence KDirWatch notifications, otherwise performance is horrible if (m_currentSrcURL.isLocalFile()) { const QString parentDir = m_currentSrcURL.adjusted(QUrl::RemoveFilename).path(); if (!m_parentDirs.contains(parentDir)) { KDirWatch::self()->stopDirScan(parentDir); m_parentDirs.insert(parentDir); } } QUrl dest = m_dest; // Append filename or dirname to destination URL, if allowed if (destinationState == DEST_IS_DIR && !m_asMethod) { dest = addPathToUrl(dest, m_currentSrcURL.fileName()); } m_currentDestURL = dest; qCDebug(KIO_COPYJOB_DEBUG) << m_currentSrcURL << "->" << dest << "trying direct rename first"; state = STATE_RENAMING; struct CopyInfo info; info.permissions = -1; info.size = (KIO::filesize_t) - 1; info.uSource = m_currentSrcURL; info.uDest = dest; QList files; files.append(info); emit q->aboutToCreate(q, files); KIO_ARGS << m_currentSrcURL << dest << (qint8) false /*no overwrite*/; SimpleJob *newJob = SimpleJobPrivate::newJobNoUi(slave_url, CMD_RENAME, packedArgs); newJob->setParentJob(q); Scheduler::setJobPriority(newJob, 1); q->addSubjob(newJob); if (m_currentSrcURL.adjusted(QUrl::RemoveFilename) != dest.adjusted(QUrl::RemoveFilename)) { // For the user, moving isn't renaming. Only renaming is. m_bOnlyRenames = false; } } void CopyJobPrivate::startListing(const QUrl &src) { Q_Q(CopyJob); state = STATE_LISTING; m_bURLDirty = true; ListJob *newjob = listRecursive(src, KIO::HideProgressInfo); newjob->setUnrestricted(true); q->connect(newjob, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList))); q->connect(newjob, SIGNAL(subError(KIO::ListJob*,KIO::ListJob*)), SLOT(slotSubError(KIO::ListJob*,KIO::ListJob*))); q->addSubjob(newjob); } void CopyJobPrivate::skip(const QUrl &sourceUrl, bool isDir) { QUrl dir(sourceUrl); if (!isDir) { // Skipping a file: make sure not to delete the parent dir (#208418) dir = dir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); } while (dirsToRemove.removeAll(dir) > 0) { // Do not rely on rmdir() on the parent directories aborting. // Exclude the parent dirs explicitly. dir = dir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); } } bool CopyJobPrivate::shouldOverwriteDir(const QString &path) const { if (m_bOverwriteAllDirs) { return true; } return m_overwriteList.contains(path); } bool CopyJobPrivate::shouldOverwriteFile(const QString &path) const { if (m_bOverwriteAllFiles) { return true; } return m_overwriteList.contains(path); } bool CopyJobPrivate::shouldSkip(const QString &path) const { Q_FOREACH (const QString &skipPath, m_skipList) { if (path.startsWith(skipPath)) { return true; } } return false; } void CopyJobPrivate::renameDirectory(QList::iterator it, const QUrl &newUrl) { Q_Q(CopyJob); emit q->renamed(q, (*it).uDest, newUrl); // for e.g. KPropertiesDialog QString oldPath = (*it).uDest.path(); if (!oldPath.endsWith('/')) { oldPath += '/'; } // Change the current one and strip the trailing '/' (*it).uDest = newUrl.adjusted(QUrl::StripTrailingSlash); QString newPath = newUrl.path(); // With trailing slash if (!newPath.endsWith('/')) { newPath += '/'; } QList::Iterator renamedirit = it; ++renamedirit; // Change the name of subdirectories inside the directory for (; renamedirit != dirs.end(); ++renamedirit) { QString path = (*renamedirit).uDest.path(); if (path.startsWith(oldPath)) { QString n = path; n.replace(0, oldPath.length(), newPath); /*qDebug() << "dirs list:" << (*renamedirit).uSource.path() << "was going to be" << path << ", changed into" << n;*/ (*renamedirit).uDest.setPath(n, QUrl::DecodedMode); } } // Change filenames inside the directory QList::Iterator renamefileit = files.begin(); for (; renamefileit != files.end(); ++renamefileit) { QString path = (*renamefileit).uDest.path(QUrl::FullyDecoded); if (path.startsWith(oldPath)) { QString n = path; n.replace(0, oldPath.length(), newPath); /*qDebug() << "files list:" << (*renamefileit).uSource.path() << "was going to be" << path << ", changed into" << n;*/ (*renamefileit).uDest.setPath(n, QUrl::DecodedMode); } } if (!dirs.isEmpty()) { emit q->aboutToCreate(q, dirs); } if (!files.isEmpty()) { emit q->aboutToCreate(q, files); } } void CopyJobPrivate::slotResultCreatingDirs(KJob *job) { Q_Q(CopyJob); // The dir we are trying to create: QList::Iterator it = dirs.begin(); // Was there an error creating a dir ? if (job->error()) { m_conflictError = job->error(); if ((m_conflictError == ERR_DIR_ALREADY_EXIST) || (m_conflictError == ERR_FILE_ALREADY_EXIST)) { // can't happen? QUrl oldURL = ((SimpleJob *)job)->url(); // Should we skip automatically ? if (m_bAutoSkipDirs) { // We don't want to copy files in this directory, so we put it on the skip list QString path = oldURL.path(); if (!path.endsWith('/')) { path += '/'; } m_skipList.append(path); skip(oldURL, true); dirs.erase(it); // Move on to next dir } else { // Did the user choose to overwrite already? const QString destDir = (*it).uDest.path(); if (shouldOverwriteDir(destDir)) { // overwrite => just skip emit q->copyingDone(q, (*it).uSource, finalDestUrl((*it).uSource, (*it).uDest), (*it).mtime, true /* directory */, false /* renamed */); dirs.erase(it); // Move on to next dir } else { if (m_bAutoRenameDirs) { const QUrl destDirectory = (*it).uDest.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); const QString newName = KIO::suggestName(destDirectory, (*it).uDest.fileName()); QUrl newUrl(destDirectory); newUrl.setPath(concatPaths(newUrl.path(), newName)); renameDirectory(it, newUrl); } else { if (!q->uiDelegateExtension()) { q->Job::slotResult(job); // will set the error and emit result(this) return; } Q_ASSERT(((SimpleJob *)job)->url() == (*it).uDest); q->removeSubjob(job); assert(!q->hasSubjobs()); // We should have only one job at a time ... // We need to stat the existing dir, to get its last-modification time QUrl existingDest((*it).uDest); SimpleJob *newJob = KIO::stat(existingDest, StatJob::DestinationSide, 2, KIO::HideProgressInfo); Scheduler::setJobPriority(newJob, 1); qCDebug(KIO_COPYJOB_DEBUG) << "KIO::stat for resolving conflict on" << existingDest; state = STATE_CONFLICT_CREATING_DIRS; q->addSubjob(newJob); return; // Don't move to next dir yet ! } } } } else { // Severe error, abort q->Job::slotResult(job); // will set the error and emit result(this) return; } } else { // no error : remove from list, to move on to next dir //this is required for the undo feature emit q->copyingDone(q, (*it).uSource, finalDestUrl((*it).uSource, (*it).uDest), (*it).mtime, true, false); m_directoriesCopied.append(*it); dirs.erase(it); } m_processedDirs++; //emit processedAmount( this, KJob::Directories, m_processedDirs ); q->removeSubjob(job); assert(!q->hasSubjobs()); // We should have only one job at a time ... createNextDir(); } void CopyJobPrivate::slotResultConflictCreatingDirs(KJob *job) { Q_Q(CopyJob); // We come here after a conflict has been detected and we've stated the existing dir // The dir we were trying to create: QList::Iterator it = dirs.begin(); const UDSEntry entry = ((KIO::StatJob *)job)->statResult(); // Its modification time: const QDateTime destmtime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1)); const QDateTime destctime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1)); const KIO::filesize_t destsize = entry.numberValue(KIO::UDSEntry::UDS_SIZE); const QString linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST); q->removeSubjob(job); assert(!q->hasSubjobs()); // We should have only one job at a time ... // Always multi and skip (since there are files after that) RenameDialog_Options options(RenameDialog_MultipleItems | RenameDialog_Skip | RenameDialog_IsDirectory); // Overwrite only if the existing thing is a dir (no chance with a file) if (m_conflictError == ERR_DIR_ALREADY_EXIST) { if ((*it).uSource == (*it).uDest || ((*it).uSource.scheme() == (*it).uDest.scheme() && (*it).uSource.adjusted(QUrl::StripTrailingSlash).path() == linkDest)) { options |= RenameDialog_OverwriteItself; } else { options |= RenameDialog_Overwrite; } } const QString existingDest = (*it).uDest.path(); QString newPath; if (m_reportTimer) { m_reportTimer->stop(); } RenameDialog_Result r = q->uiDelegateExtension()->askFileRename(q, i18n("Folder Already Exists"), (*it).uSource, (*it).uDest, options, newPath, (*it).size, destsize, (*it).ctime, destctime, (*it).mtime, destmtime); if (m_reportTimer) { m_reportTimer->start(REPORT_TIMEOUT); } switch (r) { case Result_Cancel: q->setError(ERR_USER_CANCELED); q->emitResult(); return; case Result_AutoRename: m_bAutoRenameDirs = true; // fall through case Result_Rename: { QUrl newUrl((*it).uDest); newUrl.setPath(newPath, QUrl::DecodedMode); renameDirectory(it, newUrl); } break; case Result_AutoSkip: m_bAutoSkipDirs = true; // fall through case Result_Skip: m_skipList.append(existingDest); skip((*it).uSource, true); // Move on to next dir dirs.erase(it); m_processedDirs++; break; case Result_Overwrite: m_overwriteList.insert(existingDest); emit q->copyingDone(q, (*it).uSource, finalDestUrl((*it).uSource, (*it).uDest), (*it).mtime, true /* directory */, false /* renamed */); // Move on to next dir dirs.erase(it); m_processedDirs++; break; case Result_OverwriteAll: m_bOverwriteAllDirs = true; emit q->copyingDone(q, (*it).uSource, finalDestUrl((*it).uSource, (*it).uDest), (*it).mtime, true /* directory */, false /* renamed */); // Move on to next dir dirs.erase(it); m_processedDirs++; break; default: assert(0); } state = STATE_CREATING_DIRS; //emit processedAmount( this, KJob::Directories, m_processedDirs ); createNextDir(); } void CopyJobPrivate::createNextDir() { Q_Q(CopyJob); QUrl udir; if (!dirs.isEmpty()) { // Take first dir to create out of list QList::Iterator it = dirs.begin(); // Is this URL on the skip list or the overwrite list ? while (it != dirs.end() && udir.isEmpty()) { const QString dir = (*it).uDest.path(); if (shouldSkip(dir)) { it = dirs.erase(it); } else { udir = (*it).uDest; } } } if (!udir.isEmpty()) { // any dir to create, finally ? // Create the directory - with default permissions so that we can put files into it // TODO : change permissions once all is finished; but for stuff coming from CDROM it sucks... KIO::SimpleJob *newjob = KIO::mkdir(udir, -1); newjob->setParentJob(q); Scheduler::setJobPriority(newjob, 1); if (shouldOverwriteFile(udir.path())) { // if we are overwriting an existing file or symlink newjob->addMetaData(QStringLiteral("overwrite"), QStringLiteral("true")); } m_currentDestURL = udir; m_bURLDirty = true; q->addSubjob(newjob); return; } else { // we have finished creating dirs q->setProcessedAmount(KJob::Directories, m_processedDirs); // make sure final number appears if (m_mode == CopyJob::Move) { // Now we know which dirs hold the files we're going to delete. // To speed things up and prevent double-notification, we disable KDirWatch // on those dirs temporarily (using KDirWatch::self, that's the instanced // used by e.g. kdirlister). for (QSet::const_iterator it = m_parentDirs.constBegin(); it != m_parentDirs.constEnd(); ++it) { KDirWatch::self()->stopDirScan(*it); } } state = STATE_COPYING_FILES; m_processedFiles++; // Ralf wants it to start at 1, not 0 copyNextFile(); } } void CopyJobPrivate::slotResultCopyingFiles(KJob *job) { Q_Q(CopyJob); // The file we were trying to copy: QList::Iterator it = files.begin(); if (job->error()) { // Should we skip automatically ? if (m_bAutoSkipFiles) { skip((*it).uSource, false); m_fileProcessedSize = (*it).size; files.erase(it); // Move on to next file } else { m_conflictError = job->error(); // save for later // Existing dest ? if ((m_conflictError == ERR_FILE_ALREADY_EXIST) || (m_conflictError == ERR_DIR_ALREADY_EXIST) || (m_conflictError == ERR_IDENTICAL_FILES)) { if (m_bAutoRenameFiles) { QUrl destDirectory = (*it).uDest.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); const QString newName = KIO::suggestName(destDirectory, (*it).uDest.fileName()); QUrl newDest(destDirectory); newDest.setPath(concatPaths(newDest.path(), newName)); emit q->renamed(q, (*it).uDest, newDest); // for e.g. kpropsdlg (*it).uDest = newDest; QList files; files.append(*it); emit q->aboutToCreate(q, files); } else { if (!q->uiDelegateExtension()) { q->Job::slotResult(job); // will set the error and emit result(this) return; } q->removeSubjob(job); assert(!q->hasSubjobs()); // We need to stat the existing file, to get its last-modification time QUrl existingFile((*it).uDest); SimpleJob *newJob = KIO::stat(existingFile, StatJob::DestinationSide, 2, KIO::HideProgressInfo); Scheduler::setJobPriority(newJob, 1); qCDebug(KIO_COPYJOB_DEBUG) << "KIO::stat for resolving conflict on" << existingFile; state = STATE_CONFLICT_COPYING_FILES; q->addSubjob(newJob); return; // Don't move to next file yet ! } } else { if (m_bCurrentOperationIsLink && qobject_cast(job)) { // Very special case, see a few lines below // We are deleting the source of a symlink we successfully moved... ignore error m_fileProcessedSize = (*it).size; files.erase(it); } else { if (!q->uiDelegateExtension()) { q->Job::slotResult(job); // will set the error and emit result(this) return; } // Go directly to the conflict resolution, there is nothing to stat slotResultErrorCopyingFiles(job); return; } } } } else { // no error // Special case for moving links. That operation needs two jobs, unlike others. if (m_bCurrentOperationIsLink && m_mode == CopyJob::Move && !qobject_cast(job) // Deleting source not already done ) { q->removeSubjob(job); assert(!q->hasSubjobs()); // The only problem with this trick is that the error handling for this del operation // is not going to be right... see 'Very special case' above. KIO::Job *newjob = KIO::del((*it).uSource, HideProgressInfo); newjob->setParentJob(q); q->addSubjob(newjob); return; // Don't move to next file yet ! } const QUrl finalUrl = finalDestUrl((*it).uSource, (*it).uDest); if (m_bCurrentOperationIsLink) { QString target = (m_mode == CopyJob::Link ? (*it).uSource.path() : (*it).linkDest); //required for the undo feature emit q->copyingLinkDone(q, (*it).uSource, target, finalUrl); } else { //required for the undo feature emit q->copyingDone(q, (*it).uSource, finalUrl, (*it).mtime, false, false); if (m_mode == CopyJob::Move) { org::kde::KDirNotify::emitFileMoved((*it).uSource, finalUrl); } m_successSrcList.append((*it).uSource); if (m_freeSpace != (KIO::filesize_t) - 1 && (*it).size != (KIO::filesize_t) - 1) { m_freeSpace -= (*it).size; } } // remove from list, to move on to next file files.erase(it); } m_processedFiles++; // clear processed size for last file and add it to overall processed size m_processedSize += m_fileProcessedSize; m_fileProcessedSize = 0; qCDebug(KIO_COPYJOB_DEBUG) << files.count() << "files remaining"; // Merge metadata from subjob KIO::Job *kiojob = dynamic_cast(job); Q_ASSERT(kiojob); m_incomingMetaData += kiojob->metaData(); q->removeSubjob(job); assert(!q->hasSubjobs()); // We should have only one job at a time ... copyNextFile(); } void CopyJobPrivate::slotResultErrorCopyingFiles(KJob *job) { Q_Q(CopyJob); // We come here after a conflict has been detected and we've stated the existing file // The file we were trying to create: QList::Iterator it = files.begin(); RenameDialog_Result res; QString newPath; if (m_reportTimer) { m_reportTimer->stop(); } if ((m_conflictError == ERR_FILE_ALREADY_EXIST) || (m_conflictError == ERR_DIR_ALREADY_EXIST) || (m_conflictError == ERR_IDENTICAL_FILES)) { // Its modification time: const UDSEntry entry = static_cast(job)->statResult(); const QDateTime destmtime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1)); const QDateTime destctime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1)); const KIO::filesize_t destsize = entry.numberValue(KIO::UDSEntry::UDS_SIZE); const QString linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST); // Offer overwrite only if the existing thing is a file // If src==dest, use "overwrite-itself" RenameDialog_Options options; bool isDir = true; if (m_conflictError == ERR_DIR_ALREADY_EXIST) { options = RenameDialog_IsDirectory; } else { if ((*it).uSource == (*it).uDest || ((*it).uSource.scheme() == (*it).uDest.scheme() && (*it).uSource.adjusted(QUrl::StripTrailingSlash).path() == linkDest)) { options = RenameDialog_OverwriteItself; } else { options = RenameDialog_Overwrite; } isDir = false; } if (!m_bSingleFileCopy) { options = RenameDialog_Options(options | RenameDialog_MultipleItems | RenameDialog_Skip); } res = q->uiDelegateExtension()->askFileRename(q, !isDir ? i18n("File Already Exists") : i18n("Already Exists as Folder"), (*it).uSource, (*it).uDest, options, newPath, (*it).size, destsize, (*it).ctime, destctime, (*it).mtime, destmtime); } else { if (job->error() == ERR_USER_CANCELED) { res = Result_Cancel; } else if (!q->uiDelegateExtension()) { q->Job::slotResult(job); // will set the error and emit result(this) return; } else { SkipDialog_Options options; if (files.count() > 1) { options |= SkipDialog_MultipleItems; } res = q->uiDelegateExtension()->askSkip(q, options, job->errorString()); } } if (m_reportTimer) { m_reportTimer->start(REPORT_TIMEOUT); } q->removeSubjob(job); assert(!q->hasSubjobs()); switch (res) { case Result_Cancel: q->setError(ERR_USER_CANCELED); q->emitResult(); return; case Result_AutoRename: m_bAutoRenameFiles = true; // fall through case Result_Rename: { QUrl newUrl((*it).uDest); newUrl.setPath(newPath); emit q->renamed(q, (*it).uDest, newUrl); // for e.g. kpropsdlg (*it).uDest = newUrl; QList files; files.append(*it); emit q->aboutToCreate(q, files); } break; case Result_AutoSkip: m_bAutoSkipFiles = true; // fall through case Result_Skip: // Move on to next file skip((*it).uSource, false); m_processedSize += (*it).size; files.erase(it); m_processedFiles++; break; case Result_OverwriteAll: m_bOverwriteAllFiles = true; break; case Result_Overwrite: // Add to overwrite list, so that copyNextFile knows to overwrite m_overwriteList.insert((*it).uDest.path()); break; case Result_Retry: // Do nothing, copy file again break; default: assert(0); } state = STATE_COPYING_FILES; copyNextFile(); } KIO::Job *CopyJobPrivate::linkNextFile(const QUrl &uSource, const QUrl &uDest, JobFlags flags) { qCDebug(KIO_COPYJOB_DEBUG) << "Linking"; if ( (uSource.scheme() == uDest.scheme()) && (uSource.host() == uDest.host()) && (uSource.port() == uDest.port()) && (uSource.userName() == uDest.userName()) && (uSource.password() == uDest.password())) { // This is the case of creating a real symlink KIO::SimpleJob *newJob = KIO::symlink(uSource.path(), uDest, flags | HideProgressInfo /*no GUI*/); newJob->setParentJob(q_func()); Scheduler::setJobPriority(newJob, 1); qCDebug(KIO_COPYJOB_DEBUG) << "Linking target=" << uSource.path() << "link=" << uDest; //emit linking( this, uSource.path(), uDest ); m_bCurrentOperationIsLink = true; m_currentSrcURL = uSource; m_currentDestURL = uDest; m_bURLDirty = true; //Observer::self()->slotCopying( this, uSource, uDest ); // should be slotLinking perhaps return newJob; } else { Q_Q(CopyJob); qCDebug(KIO_COPYJOB_DEBUG) << "Linking URL=" << uSource << "link=" << uDest; if (uDest.isLocalFile()) { // if the source is a devices url, handle it a littlebit special QString path = uDest.toLocalFile(); qCDebug(KIO_COPYJOB_DEBUG) << "path=" << path; QFile f(path); if (f.open(QIODevice::ReadWrite)) { f.close(); KDesktopFile desktopFile(path); KConfigGroup config = desktopFile.desktopGroup(); QUrl url = uSource; url.setPassword(QString()); config.writePathEntry("URL", url.toString()); config.writeEntry("Name", url.toString()); config.writeEntry("Type", QStringLiteral("Link")); QString protocol = uSource.scheme(); if (protocol == QLatin1String("ftp")) { config.writeEntry("Icon", QStringLiteral("folder-remote")); } else if (protocol == QLatin1String("http")) { config.writeEntry("Icon", QStringLiteral("text-html")); } else if (protocol == QLatin1String("info")) { config.writeEntry("Icon", QStringLiteral("text-x-texinfo")); } else if (protocol == QLatin1String("mailto")) { // sven: config.writeEntry("Icon", QStringLiteral("internet-mail")); // added mailto: support } else if (protocol == QLatin1String("trash") && url.path().length() <= 1) { // trash:/ link config.writeEntry("Name", i18n("Trash")); config.writeEntry("Icon", QStringLiteral("user-trash-full")); config.writeEntry("EmptyIcon", QStringLiteral("user-trash")); } else { config.writeEntry("Icon", QStringLiteral("unknown")); } config.sync(); files.erase(files.begin()); // done with this one, move on m_processedFiles++; //emit processedAmount( this, KJob::Files, m_processedFiles ); copyNextFile(); return nullptr; } else { qCDebug(KIO_COPYJOB_DEBUG) << "ERR_CANNOT_OPEN_FOR_WRITING"; q->setError(ERR_CANNOT_OPEN_FOR_WRITING); q->setErrorText(uDest.toLocalFile()); q->emitResult(); return nullptr; } } else { // Todo: not show "link" on remote dirs if the src urls are not from the same protocol+host+... q->setError(ERR_CANNOT_SYMLINK); q->setErrorText(uDest.toDisplayString()); q->emitResult(); return nullptr; } } } void CopyJobPrivate::copyNextFile() { Q_Q(CopyJob); bool bCopyFile = false; qCDebug(KIO_COPYJOB_DEBUG); // Take the first file in the list QList::Iterator it = files.begin(); // Is this URL on the skip list ? while (it != files.end() && !bCopyFile) { const QString destFile = (*it).uDest.path(); bCopyFile = !shouldSkip(destFile); if (!bCopyFile) { it = files.erase(it); } } if (bCopyFile) { // any file to create, finally ? qCDebug(KIO_COPYJOB_DEBUG)<<"preparing to copy"<<(*it).uSource<<(*it).size<setError(ERR_DISK_FULL); q->emitResult(); return; } //TODO check if dst mount is msdos and (*it).size exceeds it's limits } const QUrl &uSource = (*it).uSource; const QUrl &uDest = (*it).uDest; // Do we set overwrite ? bool bOverwrite; const QString destFile = uDest.path(); qCDebug(KIO_COPYJOB_DEBUG) << "copying" << destFile; if (uDest == uSource) { bOverwrite = false; } else { bOverwrite = shouldOverwriteFile(destFile); } // If source isn't local and target is local, we ignore the original permissions // Otherwise, files downloaded from HTTP end up with -r--r--r-- const bool remoteSource = !KProtocolManager::supportsListing(uSource) || uSource.scheme() == QLatin1String("trash"); int permissions = (*it).permissions; if (m_defaultPermissions || (remoteSource && uDest.isLocalFile())) { permissions = -1; } const JobFlags flags = bOverwrite ? Overwrite : DefaultFlags; m_bCurrentOperationIsLink = false; KIO::Job *newjob = nullptr; if (m_mode == CopyJob::Link) { // User requested that a symlink be made newjob = linkNextFile(uSource, uDest, flags); if (!newjob) { return; } } else if (!(*it).linkDest.isEmpty() && (uSource.scheme() == uDest.scheme()) && (uSource.host() == uDest.host()) && (uSource.port() == uDest.port()) && (uSource.userName() == uDest.userName()) && (uSource.password() == uDest.password())) // Copying a symlink - only on the same protocol/host/etc. (#5601, downloading an FTP file through its link), { KIO::SimpleJob *newJob = KIO::symlink((*it).linkDest, uDest, flags | HideProgressInfo /*no GUI*/); newJob->setParentJob(q); Scheduler::setJobPriority(newJob, 1); newjob = newJob; qCDebug(KIO_COPYJOB_DEBUG) << "Linking target=" << (*it).linkDest << "link=" << uDest; m_currentSrcURL = QUrl::fromUserInput((*it).linkDest); m_currentDestURL = uDest; m_bURLDirty = true; //emit linking( this, (*it).linkDest, uDest ); //Observer::self()->slotCopying( this, m_currentSrcURL, uDest ); // should be slotLinking perhaps m_bCurrentOperationIsLink = true; // NOTE: if we are moving stuff, the deletion of the source will be done in slotResultCopyingFiles } else if (m_mode == CopyJob::Move) { // Moving a file KIO::FileCopyJob *moveJob = KIO::file_move(uSource, uDest, permissions, flags | HideProgressInfo/*no GUI*/); moveJob->setParentJob(q); moveJob->setSourceSize((*it).size); moveJob->setModificationTime((*it).mtime); // #55804 newjob = moveJob; qCDebug(KIO_COPYJOB_DEBUG) << "Moving" << uSource << "to" << uDest; //emit moving( this, uSource, uDest ); m_currentSrcURL = uSource; m_currentDestURL = uDest; m_bURLDirty = true; //Observer::self()->slotMoving( this, uSource, uDest ); } else { // Copying a file KIO::FileCopyJob *copyJob = KIO::file_copy(uSource, uDest, permissions, flags | HideProgressInfo/*no GUI*/); copyJob->setParentJob(q); // in case of rename dialog copyJob->setSourceSize((*it).size); copyJob->setModificationTime((*it).mtime); newjob = copyJob; qCDebug(KIO_COPYJOB_DEBUG) << "Copying" << uSource << "to" << uDest; m_currentSrcURL = uSource; m_currentDestURL = uDest; m_bURLDirty = true; } q->addSubjob(newjob); q->connect(newjob, SIGNAL(processedSize(KJob*,qulonglong)), SLOT(slotProcessedSize(KJob*,qulonglong))); q->connect(newjob, SIGNAL(totalSize(KJob*,qulonglong)), SLOT(slotTotalSize(KJob*,qulonglong))); } else { // We're done qCDebug(KIO_COPYJOB_DEBUG) << "copyNextFile finished"; deleteNextDir(); } } void CopyJobPrivate::deleteNextDir() { Q_Q(CopyJob); if (m_mode == CopyJob::Move && !dirsToRemove.isEmpty()) { // some dirs to delete ? state = STATE_DELETING_DIRS; m_bURLDirty = true; // Take first dir to delete out of list - last ones first ! QList::Iterator it = --dirsToRemove.end(); SimpleJob *job = KIO::rmdir(*it); job->setParentJob(q); Scheduler::setJobPriority(job, 1); dirsToRemove.erase(it); q->addSubjob(job); } else { // This step is done, move on state = STATE_SETTING_DIR_ATTRIBUTES; m_directoriesCopiedIterator = m_directoriesCopied.constBegin(); setNextDirAttribute(); } } void CopyJobPrivate::setNextDirAttribute() { Q_Q(CopyJob); while (m_directoriesCopiedIterator != m_directoriesCopied.constEnd() && !(*m_directoriesCopiedIterator).mtime.isValid()) { ++m_directoriesCopiedIterator; } if (m_directoriesCopiedIterator != m_directoriesCopied.constEnd()) { const QUrl url = (*m_directoriesCopiedIterator).uDest; const QDateTime dt = (*m_directoriesCopiedIterator).mtime; ++m_directoriesCopiedIterator; KIO::SimpleJob *job = KIO::setModificationTime(url, dt); job->setParentJob(q); Scheduler::setJobPriority(job, 1); q->addSubjob(job); #if 0 // ifdef Q_OS_UNIX // TODO: can be removed now. Or reintroduced as a fast path for local files // if launching even more jobs as done above is a performance problem. // QLinkedList::const_iterator it = m_directoriesCopied.constBegin(); for (; it != m_directoriesCopied.constEnd(); ++it) { const QUrl &url = (*it).uDest; if (url.isLocalFile() && (*it).mtime != (time_t) - 1) { QT_STATBUF statbuf; if (QT_LSTAT(url.path(), &statbuf) == 0) { struct utimbuf utbuf; utbuf.actime = statbuf.st_atime; // access time, unchanged utbuf.modtime = (*it).mtime; // modification time utime(path, &utbuf); } } } m_directoriesCopied.clear(); // but then we need to jump to the else part below. Maybe with a recursive call? #endif } else { if (m_reportTimer) { m_reportTimer->stop(); } --m_processedFiles; // undo the "start at 1" hack slotReport(); // display final numbers, important if progress dialog stays up q->emitResult(); } } void CopyJob::emitResult() { Q_D(CopyJob); // Before we go, tell the world about the changes that were made. // Even if some error made us abort midway, we might still have done // part of the job so we better update the views! (#118583) if (!d->m_bOnlyRenames) { // If only renaming happened, KDirNotify::FileRenamed was emitted by the rename jobs QUrl url(d->m_globalDest); if (d->m_globalDestinationState != DEST_IS_DIR || d->m_asMethod) { url = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); } qCDebug(KIO_COPYJOB_DEBUG) << "KDirNotify'ing FilesAdded" << url; org::kde::KDirNotify::emitFilesAdded(url); if (d->m_mode == CopyJob::Move && !d->m_successSrcList.isEmpty()) { qCDebug(KIO_COPYJOB_DEBUG) << "KDirNotify'ing FilesRemoved" << d->m_successSrcList; org::kde::KDirNotify::emitFilesRemoved(d->m_successSrcList); } } // Re-enable watching on the dirs that held the deleted/moved files if (d->m_mode == CopyJob::Move) { for (QSet::const_iterator it = d->m_parentDirs.constBegin(); it != d->m_parentDirs.constEnd(); ++it) { KDirWatch::self()->restartDirScan(*it); } } Job::emitResult(); } void CopyJobPrivate::slotProcessedSize(KJob *, qulonglong data_size) { Q_Q(CopyJob); qCDebug(KIO_COPYJOB_DEBUG) << data_size; m_fileProcessedSize = data_size; q->setProcessedAmount(KJob::Bytes, m_processedSize + m_fileProcessedSize); if (m_processedSize + m_fileProcessedSize > m_totalSize) { // Example: download any attachment from bugs.kde.org m_totalSize = m_processedSize + m_fileProcessedSize; qCDebug(KIO_COPYJOB_DEBUG) << "Adjusting m_totalSize to" << m_totalSize; q->setTotalAmount(KJob::Bytes, m_totalSize); // safety } qCDebug(KIO_COPYJOB_DEBUG) << "emit processedSize" << (unsigned long) (m_processedSize + m_fileProcessedSize); q->setProcessedAmount(KJob::Bytes, m_processedSize + m_fileProcessedSize); } void CopyJobPrivate::slotTotalSize(KJob *, qulonglong size) { Q_Q(CopyJob); qCDebug(KIO_COPYJOB_DEBUG) << size; // Special case for copying a single file // This is because some protocols don't implement stat properly // (e.g. HTTP), and don't give us a size in some cases (redirection) // so we'd rather rely on the size given for the transfer if (m_bSingleFileCopy && size != m_totalSize) { qCDebug(KIO_COPYJOB_DEBUG) << "slotTotalSize: updating totalsize to" << size; m_totalSize = size; q->setTotalAmount(KJob::Bytes, size); } } void CopyJobPrivate::slotResultDeletingDirs(KJob *job) { Q_Q(CopyJob); if (job->error()) { // Couldn't remove directory. Well, perhaps it's not empty // because the user pressed Skip for a given file in it. // Let's not display "Could not remove dir ..." for each of those dir ! } else { m_successSrcList.append(static_cast(job)->url()); } q->removeSubjob(job); assert(!q->hasSubjobs()); deleteNextDir(); } void CopyJobPrivate::slotResultSettingDirAttributes(KJob *job) { Q_Q(CopyJob); if (job->error()) { // Couldn't set directory attributes. Ignore the error, it can happen // with inferior file systems like VFAT. // Let's not display warnings for each dir like "cp -a" does. } q->removeSubjob(job); assert(!q->hasSubjobs()); setNextDirAttribute(); } // We were trying to do a direct renaming, before even stat'ing void CopyJobPrivate::slotResultRenaming(KJob *job) { Q_Q(CopyJob); int err = job->error(); const QString errText = job->errorText(); // Merge metadata from subjob KIO::Job *kiojob = dynamic_cast(job); Q_ASSERT(kiojob); m_incomingMetaData += kiojob->metaData(); q->removeSubjob(job); assert(!q->hasSubjobs()); // Determine dest again QUrl dest = m_dest; if (destinationState == DEST_IS_DIR && !m_asMethod) { dest = addPathToUrl(dest, m_currentSrcURL.fileName()); } if (err) { // Direct renaming didn't work. Try renaming to a temp name, // this can help e.g. when renaming 'a' to 'A' on a VFAT partition. // In that case it's the _same_ dir, we don't want to copy+del (data loss!) // TODO: replace all this code with QFile::rename once // https://codereview.qt-project.org/44823 is in if ((err == ERR_FILE_ALREADY_EXIST || err == ERR_DIR_ALREADY_EXIST || err == ERR_IDENTICAL_FILES) && m_currentSrcURL.isLocalFile() && dest.isLocalFile()) { const QString _src(m_currentSrcURL.adjusted(QUrl::StripTrailingSlash).toLocalFile()); const QString _dest(dest.adjusted(QUrl::StripTrailingSlash).toLocalFile()); if (_src != _dest && QString::compare(_src, _dest, Qt::CaseInsensitive) == 0) { qCDebug(KIO_COPYJOB_DEBUG) << "Couldn't rename directly, dest already exists. Detected special case of lower/uppercase renaming in same dir, try with 2 rename calls"; const QString srcDir = QFileInfo(_src).absolutePath(); QTemporaryFile tmpFile(srcDir + "kio_XXXXXX"); const bool openOk = tmpFile.open(); if (!openOk) { qCWarning(KIO_CORE) << "Couldn't open temp file in" << srcDir; } else { const QString _tmp(tmpFile.fileName()); tmpFile.close(); tmpFile.remove(); qCDebug(KIO_COPYJOB_DEBUG) << "QTemporaryFile using" << _tmp << "as intermediary"; if (QFile::rename(_src, _tmp)) { qCDebug(KIO_COPYJOB_DEBUG) << "Renaming" << _src << "to" << _tmp << "succeeded"; if (!QFile::exists(_dest) && QFile::rename(_tmp, _dest)) { err = 0; org::kde::KDirNotify::emitFileRenamed(m_currentSrcURL, dest); } else { qCDebug(KIO_COPYJOB_DEBUG) << "Didn't manage to rename" << _tmp << "to" << _dest << ", reverting"; // Revert back to original name! if (!QFile::rename(_tmp, _src)) { qCWarning(KIO_CORE) << "Couldn't rename" << _tmp << "back to" << _src << '!'; // Severe error, abort q->Job::slotResult(job); // will set the error and emit result(this) return; } } } else { qCDebug(KIO_COPYJOB_DEBUG) << "mv" << _src << _tmp << "failed:" << strerror(errno); } } } } } if (err) { // This code is similar to CopyJobPrivate::slotResultErrorCopyingFiles // but here it's about the base src url being moved/renamed // (m_currentSrcURL) and its dest (m_dest), not about a single file. // It also means we already stated the dest, here. // On the other hand we haven't stated the src yet (we skipped doing it // to save time, since it's not necessary to rename directly!)... // Existing dest? if (err == ERR_DIR_ALREADY_EXIST || err == ERR_FILE_ALREADY_EXIST || err == ERR_IDENTICAL_FILES) { // Should we skip automatically ? bool isDir = (err == ERR_DIR_ALREADY_EXIST); // ## technically, isDir means "source is dir", not "dest is dir" ####### if ((isDir && m_bAutoSkipDirs) || (!isDir && m_bAutoSkipFiles)) { // Move on to next source url skipSrc(isDir); return; } else if ((isDir && m_bOverwriteAllDirs) || (!isDir && m_bOverwriteAllFiles)) { ; // nothing to do, stat+copy+del will overwrite } else if ((isDir && m_bAutoRenameDirs) || (!isDir && m_bAutoRenameFiles)) { QUrl destDirectory = m_currentDestURL.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); // m_currendDestURL includes filename const QString newName = KIO::suggestName(destDirectory, m_currentDestURL.fileName()); m_dest = destDirectory; m_dest.setPath(concatPaths(m_dest.path(), newName)); KIO::Job *job = KIO::stat(m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo); state = STATE_STATING; destinationState = DEST_NOT_STATED; q->addSubjob(job); return; } else if (q->uiDelegateExtension()) { QString newPath; // we lack mtime info for both the src (not stated) // and the dest (stated but this info wasn't stored) // Let's do it for local files, at least KIO::filesize_t sizeSrc = (KIO::filesize_t) - 1; KIO::filesize_t sizeDest = (KIO::filesize_t) - 1; QDateTime ctimeSrc; QDateTime ctimeDest; QDateTime mtimeSrc; QDateTime mtimeDest; bool destIsDir = err == ERR_DIR_ALREADY_EXIST; // ## TODO we need to stat the source using KIO::stat // so that this code is properly network-transparent. if (m_currentSrcURL.isLocalFile()) { QFileInfo info(m_currentSrcURL.toLocalFile()); if (info.exists()) { sizeSrc = info.size(); ctimeSrc = info.created(); mtimeSrc = info.lastModified(); isDir = info.isDir(); } } if (dest.isLocalFile()) { QFileInfo destInfo(dest.toLocalFile()); if (destInfo.exists()) { sizeDest = destInfo.size(); ctimeDest = destInfo.created(); mtimeDest = destInfo.lastModified(); destIsDir = destInfo.isDir(); } } // If src==dest, use "overwrite-itself" RenameDialog_Options options = (m_currentSrcURL == dest) ? RenameDialog_OverwriteItself : RenameDialog_Overwrite; if (!isDir && destIsDir) { // We can't overwrite a dir with a file. options = RenameDialog_Options(); } if (m_srcList.count() > 1) { options |= RenameDialog_Options(RenameDialog_MultipleItems | RenameDialog_Skip); } if (destIsDir) { options |= RenameDialog_IsDirectory; } if (m_reportTimer) { m_reportTimer->stop(); } RenameDialog_Result r = q->uiDelegateExtension()->askFileRename( q, err != ERR_DIR_ALREADY_EXIST ? i18n("File Already Exists") : i18n("Already Exists as Folder"), m_currentSrcURL, dest, options, newPath, sizeSrc, sizeDest, ctimeSrc, ctimeDest, mtimeSrc, mtimeDest); if (m_reportTimer) { m_reportTimer->start(REPORT_TIMEOUT); } switch (r) { case Result_Cancel: { q->setError(ERR_USER_CANCELED); q->emitResult(); return; } case Result_AutoRename: if (isDir) { m_bAutoRenameDirs = true; } else { m_bAutoRenameFiles = true; } // fall through case Result_Rename: { // Set m_dest to the chosen destination // This is only for this src url; the next one will revert to m_globalDest m_dest.setPath(newPath); KIO::Job *job = KIO::stat(m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo); state = STATE_STATING; destinationState = DEST_NOT_STATED; q->addSubjob(job); return; } case Result_AutoSkip: if (isDir) { m_bAutoSkipDirs = true; } else { m_bAutoSkipFiles = true; } // fall through case Result_Skip: // Move on to next url skipSrc(isDir); return; case Result_OverwriteAll: if (destIsDir) { m_bOverwriteAllDirs = true; } else { m_bOverwriteAllFiles = true; } break; case Result_Overwrite: // Add to overwrite list // Note that we add dest, not m_dest. // This ensures that when moving several urls into a dir (m_dest), // we only overwrite for the current one, not for all. // When renaming a single file (m_asMethod), it makes no difference. qCDebug(KIO_COPYJOB_DEBUG) << "adding to overwrite list: " << dest.path(); m_overwriteList.insert(dest.path()); break; default: //assert( 0 ); break; } } else if (err != KIO::ERR_UNSUPPORTED_ACTION) { // Dest already exists, and job is not interactive -> abort with error q->setError(err); q->setErrorText(errText); q->emitResult(); return; } } else if (err != KIO::ERR_UNSUPPORTED_ACTION) { qCDebug(KIO_COPYJOB_DEBUG) << "Couldn't rename" << m_currentSrcURL << "to" << dest << ", aborting"; q->setError(err); q->setErrorText(errText); q->emitResult(); return; } qCDebug(KIO_COPYJOB_DEBUG) << "Couldn't rename" << m_currentSrcURL << "to" << dest << ", reverting to normal way, starting with stat"; qCDebug(KIO_COPYJOB_DEBUG) << "KIO::stat on" << m_currentSrcURL; KIO::Job *job = KIO::stat(m_currentSrcURL, StatJob::SourceSide, 2, KIO::HideProgressInfo); state = STATE_STATING; q->addSubjob(job); m_bOnlyRenames = false; } else { qCDebug(KIO_COPYJOB_DEBUG) << "Renaming succeeded, move on"; ++m_processedFiles; emit q->copyingDone(q, *m_currentStatSrc, finalDestUrl(*m_currentStatSrc, dest), QDateTime() /*mtime unknown, and not needed*/, true, true); m_successSrcList.append(*m_currentStatSrc); statNextSrc(); } } void CopyJob::slotResult(KJob *job) { Q_D(CopyJob); qCDebug(KIO_COPYJOB_DEBUG) << "d->state=" << (int) d->state; // In each case, what we have to do is : // 1 - check for errors and treat them // 2 - removeSubjob(job); // 3 - decide what to do next switch (d->state) { case STATE_STATING: // We were trying to stat a src url or the dest d->slotResultStating(job); break; case STATE_RENAMING: { // We were trying to do a direct renaming, before even stat'ing d->slotResultRenaming(job); break; } case STATE_LISTING: // recursive listing finished qCDebug(KIO_COPYJOB_DEBUG) << "totalSize:" << (unsigned int) d->m_totalSize << "files:" << d->files.count() << "d->dirs:" << d->dirs.count(); // Was there an error ? if (job->error()) { Job::slotResult(job); // will set the error and emit result(this) return; } removeSubjob(job); assert(!hasSubjobs()); d->statNextSrc(); break; case STATE_CREATING_DIRS: d->slotResultCreatingDirs(job); break; case STATE_CONFLICT_CREATING_DIRS: d->slotResultConflictCreatingDirs(job); break; case STATE_COPYING_FILES: d->slotResultCopyingFiles(job); break; case STATE_CONFLICT_COPYING_FILES: d->slotResultErrorCopyingFiles(job); break; case STATE_DELETING_DIRS: d->slotResultDeletingDirs(job); break; case STATE_SETTING_DIR_ATTRIBUTES: d->slotResultSettingDirAttributes(job); break; default: assert(0); } } void KIO::CopyJob::setDefaultPermissions(bool b) { d_func()->m_defaultPermissions = b; } KIO::CopyJob::CopyMode KIO::CopyJob::operationMode() const { return d_func()->m_mode; } void KIO::CopyJob::setAutoSkip(bool autoSkip) { d_func()->m_bAutoSkipFiles = autoSkip; d_func()->m_bAutoSkipDirs = autoSkip; } void KIO::CopyJob::setAutoRename(bool autoRename) { d_func()->m_bAutoRenameFiles = autoRename; d_func()->m_bAutoRenameDirs = autoRename; } void KIO::CopyJob::setWriteIntoExistingDirectories(bool overwriteAll) // #65926 { d_func()->m_bOverwriteAllDirs = overwriteAll; } CopyJob *KIO::copy(const QUrl &src, const QUrl &dest, JobFlags flags) { qCDebug(KIO_COPYJOB_DEBUG) << "src=" << src << "dest=" << dest; QList srcList; srcList.append(src); return CopyJobPrivate::newJob(srcList, dest, CopyJob::Copy, false, flags); } CopyJob *KIO::copyAs(const QUrl &src, const QUrl &dest, JobFlags flags) { qCDebug(KIO_COPYJOB_DEBUG) << "src=" << src << "dest=" << dest; QList srcList; srcList.append(src); return CopyJobPrivate::newJob(srcList, dest, CopyJob::Copy, true, flags); } CopyJob *KIO::copy(const QList &src, const QUrl &dest, JobFlags flags) { qCDebug(KIO_COPYJOB_DEBUG) << src << dest; return CopyJobPrivate::newJob(src, dest, CopyJob::Copy, false, flags); } CopyJob *KIO::move(const QUrl &src, const QUrl &dest, JobFlags flags) { qCDebug(KIO_COPYJOB_DEBUG) << src << dest; QList srcList; srcList.append(src); CopyJob *job = CopyJobPrivate::newJob(srcList, dest, CopyJob::Move, false, flags); if (job->uiDelegateExtension()) { job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::UpdateContent); } return job; } CopyJob *KIO::moveAs(const QUrl &src, const QUrl &dest, JobFlags flags) { qCDebug(KIO_COPYJOB_DEBUG) << src << dest; QList srcList; srcList.append(src); CopyJob *job = CopyJobPrivate::newJob(srcList, dest, CopyJob::Move, true, flags); if (job->uiDelegateExtension()) { job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::UpdateContent); } return job; } CopyJob *KIO::move(const QList &src, const QUrl &dest, JobFlags flags) { qCDebug(KIO_COPYJOB_DEBUG) << src << dest; CopyJob *job = CopyJobPrivate::newJob(src, dest, CopyJob::Move, false, flags); if (job->uiDelegateExtension()) { job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::UpdateContent); } return job; } CopyJob *KIO::link(const QUrl &src, const QUrl &destDir, JobFlags flags) { QList srcList; srcList.append(src); return CopyJobPrivate::newJob(srcList, destDir, CopyJob::Link, false, flags); } CopyJob *KIO::link(const QList &srcList, const QUrl &destDir, JobFlags flags) { return CopyJobPrivate::newJob(srcList, destDir, CopyJob::Link, false, flags); } CopyJob *KIO::linkAs(const QUrl &src, const QUrl &destDir, JobFlags flags) { QList srcList; srcList.append(src); return CopyJobPrivate::newJob(srcList, destDir, CopyJob::Link, true, flags); } CopyJob *KIO::trash(const QUrl &src, JobFlags flags) { QList srcList; srcList.append(src); return CopyJobPrivate::newJob(srcList, QUrl(QStringLiteral("trash:/")), CopyJob::Move, false, flags); } CopyJob *KIO::trash(const QList &srcList, JobFlags flags) { return CopyJobPrivate::newJob(srcList, QUrl(QStringLiteral("trash:/")), CopyJob::Move, false, flags); } #include "moc_copyjob.cpp" diff --git a/src/core/copyjob.h b/src/core/copyjob.h index b683cdd9..0f2fff55 100644 --- a/src/core/copyjob.h +++ b/src/core/copyjob.h @@ -1,436 +1,436 @@ // -*- c++ -*- /* This file is part of the KDE libraries Copyright 2000 Stephan Kulow Copyright 2000-2006 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 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. */ #ifndef KIO_COPYJOB_H #define KIO_COPYJOB_H -#include -#include +#include +#include #include #include #include "kiocore_export.h" #include // filesize_t #include "job_base.h" class QTimer; namespace KIO { /// @internal /// KF6 TODO: move to .cpp and remove aboutToCreate signal struct CopyInfo { QUrl uSource; QUrl uDest; QString linkDest; // for symlinks only int permissions; QDateTime ctime; QDateTime mtime; KIO::filesize_t size; // 0 for dirs }; class CopyJobPrivate; /** * @class KIO::CopyJob copyjob.h * * CopyJob is used to move, copy or symlink files and directories. * Don't create the job directly, but use KIO::copy(), * KIO::move(), KIO::link() and friends. * * @see KIO::copy() * @see KIO::copyAs() * @see KIO::move() * @see KIO::moveAs() * @see KIO::link() * @see KIO::linkAs() */ class KIOCORE_EXPORT CopyJob : public Job { Q_OBJECT public: /** * Defines the mode of the operation */ enum CopyMode { Copy, Move, Link }; virtual ~CopyJob(); /** * Returns the mode of the operation (copy, move, or link), * depending on whether KIO::copy(), KIO::move() or KIO::link() was called. */ CopyMode operationMode() const; /** * Returns the list of source URLs. * @return the list of source URLs. */ QList srcUrls() const; /** * Returns the destination URL. * @return the destination URL */ QUrl destUrl() const; /** * By default the permissions of the copied files will be those of the source files. * * But when copying "template" files to "new" files, people prefer the umask * to apply, rather than the template's permissions. * For that case, call setDefaultPermissions(true) */ void setDefaultPermissions(bool b); /** * Skip copying or moving any file when the destination already exists, * instead of the default behavior (interactive mode: showing a dialog to the user, * non-interactive mode: aborting with an error). * Initially added for a unit test. * \since 4.2 */ void setAutoSkip(bool autoSkip); /** * Rename files automatically when the destination already exists, * instead of the default behavior (interactive mode: showing a dialog to the user, * non-interactive mode: aborting with an error). * Initially added for a unit test. * \since 4.7 */ void setAutoRename(bool autoRename); /** * Reuse any directory that already exists, instead of the default behavior * (interactive mode: showing a dialog to the user, * non-interactive mode: aborting with an error). * \since 4.2 */ void setWriteIntoExistingDirectories(bool overwriteAllDirs); /** * Reimplemented for internal reasons */ bool doSuspend() Q_DECL_OVERRIDE; /** * Reimplemented for internal reasons */ bool doResume() Q_DECL_OVERRIDE; Q_SIGNALS: /** * Emitted when the total number of files is known. * @param job the job that emitted this signal * @param files the total number of files */ void totalFiles(KJob *job, unsigned long files); /** * Emitted when the toal number of direcotries is known. * @param job the job that emitted this signal * @param dirs the total number of directories */ void totalDirs(KJob *job, unsigned long dirs); /** * Emitted when it is known which files / directories are going * to be created. Note that this may still change e.g. when * existing files with the same name are discovered. * @param job the job that emitted this signal * @param files a list of items that are about to be created. * @deprecated since 5.2 -- this signal is unused since kde 3... */ QT_MOC_COMPAT void aboutToCreate(KIO::Job *job, const QList &files); /** * Sends the number of processed files. * @param job the job that emitted this signal * @param files the number of processed files */ void processedFiles(KIO::Job *job, unsigned long files); /** * Sends the number of processed directories. * @param job the job that emitted this signal * @param dirs the number of processed dirs */ void processedDirs(KIO::Job *job, unsigned long dirs); /** * The job is copying a file or directory. * * Note: This signal is used for progress dialogs, it's not emitted for * every file or directory (this would be too slow), but every 200ms. * * @param job the job that emitted this signal * @param src the URL of the file or directory that is currently * being copied * @param dest the destination of the current operation */ void copying(KIO::Job *job, const QUrl &src, const QUrl &dest); /** * The job is creating a symbolic link. * * Note: This signal is used for progress dialogs, it's not emitted for * every file or directory (this would be too slow), but every 200ms. * * @param job the job that emitted this signal * @param target the URL of the file or directory that is currently * being linked * @param to the destination of the current operation */ void linking(KIO::Job *job, const QString &target, const QUrl &to); /** * The job is moving a file or directory. * * Note: This signal is used for progress dialogs, it's not emitted for * every file or directory (this would be too slow), but every 200ms. * * @param job the job that emitted this signal * @param from the URL of the file or directory that is currently * being moved * @param to the destination of the current operation */ void moving(KIO::Job *job, const QUrl &from, const QUrl &to); /** * The job is creating the directory @p dir. * * This signal is emitted for every directory being created. * * @param job the job that emitted this signal * @param dir the directory that is currently being created */ void creatingDir(KIO::Job *job, const QUrl &dir); /** * The user chose to rename @p from to @p to. * * @param job the job that emitted this signal * @param from the original name * @param to the new name */ void renamed(KIO::Job *job, const QUrl &from, const QUrl &to); /** * The job emits this signal when copying or moving a file or directory successfully finished. * This signal is mainly for the Undo feature. * If you simply want to know when a copy job is done, use result(). * * @param job the job that emitted this signal * @param from the source URL * @param to the destination URL * @param mtime the modification time of the source file, hopefully set on the destination file * too (when the kioslave supports it). * @param directory indicates whether a file or directory was successfully copied/moved. * true for a directory, false for file * @param renamed indicates that the destination URL was created using a * rename operation (i.e. fast directory moving). true if is has been renamed */ void copyingDone(KIO::Job *job, const QUrl &from, const QUrl &to, const QDateTime &mtime, bool directory, bool renamed); /** * The job is copying or moving a symbolic link, that points to target. * The new link is created in @p to. The existing one is/was in @p from. * This signal is mainly for the Undo feature. * @param job the job that emitted this signal * @param from the source URL * @param target the target * @param to the destination URL */ void copyingLinkDone(KIO::Job *job, const QUrl &from, const QString &target, const QUrl &to); protected Q_SLOTS: void slotResult(KJob *job) Q_DECL_OVERRIDE; protected: CopyJob(CopyJobPrivate &dd); void emitResult(); private: Q_PRIVATE_SLOT(d_func(), void slotStart()) Q_PRIVATE_SLOT(d_func(), void slotEntries(KIO::Job *, const KIO::UDSEntryList &list)) Q_PRIVATE_SLOT(d_func(), void slotSubError(KIO::ListJob *, KIO::ListJob *)) Q_PRIVATE_SLOT(d_func(), void slotProcessedSize(KJob *, qulonglong data_size)) Q_PRIVATE_SLOT(d_func(), void slotTotalSize(KJob *, qulonglong size)) Q_PRIVATE_SLOT(d_func(), void slotReport()) Q_PRIVATE_SLOT(d_func(), void sourceStated(const KIO::UDSEntry &entry, const QUrl &sourceUrl)) Q_DECLARE_PRIVATE(CopyJob) }; /** * Copy a file or directory @p src into the destination @p dest, * which can be a file (including the final filename) or a directory * (into which @p src will be copied). * * This emulates the cp command completely. * * @param src the file or directory to copy * @param dest the destination * @param flags copy() supports HideProgressInfo and Overwrite. * Note: Overwrite has the meaning of both "write into existing directories" and * "overwrite existing files". However if "dest" exists, then src is copied * into a subdir of dest, just like "cp" does. Use copyAs if you don't want that. * * @return the job handling the operation * @see copyAs() */ KIOCORE_EXPORT CopyJob *copy(const QUrl &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Copy a file or directory @p src into the destination @p dest, * which is the destination name in any case, even for a directory. * * As opposed to copy(), this doesn't emulate cp, but is the only * way to copy a directory, giving it a new name and getting an error * box if a directory already exists with the same name (or writing the * contents of @p src into @p dest, when using Overwrite). * * @param src the file or directory to copy * @param dest the destination * @param flags copyAs() supports HideProgressInfo and Overwrite. * Note: Overwrite has the meaning of both "write into existing directories" and * "overwrite existing files". * * * @return the job handling the operation */ KIOCORE_EXPORT CopyJob *copyAs(const QUrl &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Copy a list of file/dirs @p src into a destination directory @p dest. * * @param src the list of files and/or directories * @param dest the destination * @param flags copy() supports HideProgressInfo and Overwrite. * Note: Overwrite has the meaning of both "write into existing directories" and * "overwrite existing files". However if "dest" exists, then src is copied * into a subdir of dest, just like "cp" does. * @return the job handling the operation */ KIOCORE_EXPORT CopyJob *copy(const QList &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Moves a file or directory @p src to the given destination @p dest. * * @param src the file or directory to copy * @param dest the destination * @param flags move() supports HideProgressInfo and Overwrite. * Note: Overwrite has the meaning of both "write into existing directories" and * "overwrite existing files". However if "dest" exists, then src is copied * into a subdir of dest, just like "cp" does. * @return the job handling the operation * @see copy() * @see moveAs() */ KIOCORE_EXPORT CopyJob *move(const QUrl &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Moves a file or directory @p src to the given destination @p dest. Unlike move() * this operation will not move @p src into @p dest when @p dest exists: it will * either fail, or move the contents of @p src into it if Overwrite is set. * * @param src the file or directory to copy * @param dest the destination * @param flags moveAs() supports HideProgressInfo and Overwrite. * Note: Overwrite has the meaning of both "write into existing directories" and * "overwrite existing files". * @return the job handling the operation * @see copyAs() */ KIOCORE_EXPORT CopyJob *moveAs(const QUrl &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Moves a list of files or directories @p src to the given destination @p dest. * * @param src the list of files or directories to copy * @param dest the destination * @param flags move() supports HideProgressInfo and Overwrite. * Note: Overwrite has the meaning of both "write into existing directories" and * "overwrite existing files". However if "dest" exists, then src is copied * into a subdir of dest, just like "cp" does. * @return the job handling the operation * @see copy() */ KIOCORE_EXPORT CopyJob *move(const QList &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Create a link. * If the protocols and hosts are the same, a Unix symlink will be created. * Otherwise, a .desktop file of Type Link and pointing to the src URL will be created. * * @param src The existing file or directory, 'target' of the link. * @param destDir Destination directory where the link will be created. * @param flags link() supports HideProgressInfo only * @return the job handling the operation */ KIOCORE_EXPORT CopyJob *link(const QUrl &src, const QUrl &destDir, JobFlags flags = DefaultFlags); /** * Create several links * If the protocols and hosts are the same, a Unix symlink will be created. * Otherwise, a .desktop file of Type Link and pointing to the src URL will be created. * * @param src The existing files or directories, 'targets' of the link. * @param destDir Destination directory where the links will be created. * @param flags link() supports HideProgressInfo only * @return the job handling the operation * @see link() */ KIOCORE_EXPORT CopyJob *link(const QList &src, const QUrl &destDir, JobFlags flags = DefaultFlags); /** * Create a link. Unlike link() this operation will fail when @p dest is an existing * directory rather than the final name for the link. * If the protocols and hosts are the same, a Unix symlink will be created. * Otherwise, a .desktop file of Type Link and pointing to the src URL will be created. * * @param src The existing file or directory, 'target' of the link. * @param dest Destination (i.e. the final symlink) * @param flags linkAs() supports HideProgressInfo only * @return the job handling the operation * @see link () * @see copyAs() */ KIOCORE_EXPORT CopyJob *linkAs(const QUrl &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Trash a file or directory. * This is currently only supported for local files and directories. * Use QUrl::fromLocalFile to create a URL from a local file path. * * @param src file to delete * @param flags trash() supports HideProgressInfo only * @return the job handling the operation */ KIOCORE_EXPORT CopyJob *trash(const QUrl &src, JobFlags flags = DefaultFlags); /** * Trash a list of files or directories. * This is currently only supported for local files and directories. * * @param src the files to delete * @param flags trash() supports HideProgressInfo only * @return the job handling the operation */ KIOCORE_EXPORT CopyJob *trash(const QList &src, JobFlags flags = DefaultFlags); } #endif diff --git a/src/core/dataprotocol.cpp b/src/core/dataprotocol.cpp index 7a95fc46..f4e1f926 100644 --- a/src/core/dataprotocol.cpp +++ b/src/core/dataprotocol.cpp @@ -1,326 +1,326 @@ // dataprotocol.cpp // ================== // // Implementation of the data protocol (rfc 2397) // // Author: Leo Savernik // Email: l.savernik@aon.at // Copyright (C) 2002, 2003 by Leo Savernik // Created: Sam Dez 28 14:11:18 CET 2002 /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License as * * published by the Free Software Foundation; version 2. * * * ***************************************************************************/ #include "dataprotocol_p.h" #include "global.h" -#include -#include -#include -#include +#include +#include +#include +#include #ifdef DATAKIOSLAVE # include # include #endif #if !defined(DATAKIOSLAVE) # define DISPATCH(f) dispatch_##f #else # define DISPATCH(f) f #endif using namespace KIO; #ifdef DATAKIOSLAVE extern "C" { int kdemain(int argc, char **argv) { //qDebug() << "*** Starting kio_data "; if (argc != 4) { //qDebug() << "Usage: kio_data protocol domain-socket1 domain-socket2"; exit(-1); } DataProtocol slave(argv[2], argv[3]); slave.dispatchLoop(); //qDebug() << "*** kio_data Done"; return 0; } } #endif /** structure containing header information */ struct DataHeader { QString mime_type; // mime type of content (lowercase) MetaData attributes; // attribute/value pairs (attribute lowercase, // value unchanged) bool is_base64; // true if data is base64 encoded QByteArray url; // reference to decoded url int data_offset; // zero-indexed position within url // where the real data begins. May point beyond // the end to indicate that there is no data }; /** returns the position of the first occurrence of any of the given * characters @p c1 or comma (',') or semicolon (';') or buf.length() * if none is contained. * * @param buf buffer where to look for c * @param begin zero-indexed starting position * @param c1 character to find or '\0' to ignore */ static int find(const QByteArray &buf, int begin, const char c1) { static const char comma = ','; static const char semicolon = ';'; int pos = begin; int size = buf.length(); while (pos < size) { const char ch = buf[pos]; if (ch == comma || ch == semicolon || (c1 != '\0' && ch == c1)) { break; } pos++; }/*wend*/ return pos; } /** extracts the string between the current position @p pos and the first * occurrence of either @p c1 or comma (',') or semicolon (';') exclusively * and updates @p pos to point at the found delimiter or at the end of the * buffer if neither character occurred. * @param buf buffer where to look for * @param pos zero-indexed position within buffer * @param c1 character to find or '\0' to ignore */ static inline QString extract(const QByteArray &buf, int &pos, const char c1 = '\0') { int oldpos = pos; pos = find(buf, oldpos, c1); return buf.mid(oldpos, pos - oldpos); } /** ignores all whitespaces * @param buf buffer to operate on * @param pos position to shift to first non-whitespace character * Upon return @p pos will either point to the first non-whitespace * character or to the end of the buffer. */ static inline void ignoreWS(const QString &buf, int &pos) { int size = buf.length(); while (pos < size && buf[pos].isSpace()) { ++pos; } } /** parses a quoted string as per rfc 822. * * If trailing quote is missing, the whole rest of the buffer is returned. * @param buf buffer to operate on * @param pos position pointing to the leading quote * @return the extracted string. @p pos will be updated to point to the * character following the trailing quote. */ static QString parseQuotedString(const QString &buf, int &pos) { int size = buf.length(); QString res; res.reserve(size); // can't be larger than buf pos++; // jump over leading quote bool escaped = false; // if true means next character is literal bool parsing = true; // true as long as end quote not found while (parsing && pos < size) { const QChar ch = buf[pos++]; if (escaped) { res += ch; escaped = false; } else { switch (ch.unicode()) { case '"': parsing = false; break; case '\\': escaped = true; break; default: res += ch; break; }/*end switch*/ }/*end if*/ }/*wend*/ res.squeeze(); return res; } /** parses the header of a data url * @param url the data url * @param mimeOnly if the only interesting information is the mime type * @return DataHeader structure with the header information */ static DataHeader parseDataHeader(const QUrl &url, const bool mimeOnly) { DataHeader header_info; // initialize header info members header_info.mime_type = QStringLiteral("text/plain"); header_info.attributes.insert(QStringLiteral("charset"), QStringLiteral("us-ascii")); header_info.is_base64 = false; // decode url and save it const QByteArray &raw_url = header_info.url = QByteArray::fromPercentEncoding(url.path(QUrl::FullyEncoded).toLatin1()); const int raw_url_len = raw_url.length(); header_info.data_offset = 0; // read mime type if (raw_url_len == 0) { return header_info; } const QString mime_type = extract(raw_url, header_info.data_offset).trimmed(); if (!mime_type.isEmpty()) { header_info.mime_type = mime_type; } if (mimeOnly) { return header_info; } if (header_info.data_offset >= raw_url_len) { return header_info; } // jump over delimiter token and return if data reached if (raw_url[header_info.data_offset++] == QLatin1Char(',')) { return header_info; } // read all attributes and store them bool data_begin_reached = false; while (!data_begin_reached && header_info.data_offset < raw_url_len) { // read attribute const QString attribute = extract(raw_url, header_info.data_offset, '=').trimmed(); if (header_info.data_offset >= raw_url_len || raw_url[header_info.data_offset] != QLatin1Char('=')) { // no assigment, must be base64 option if (attribute == QLatin1String("base64")) { header_info.is_base64 = true; } } else { header_info.data_offset++; // jump over '=' token // read value ignoreWS(raw_url, header_info.data_offset); if (header_info.data_offset >= raw_url_len) { return header_info; } QString value; if (raw_url[header_info.data_offset] == QLatin1Char('"')) { value = parseQuotedString(raw_url, header_info.data_offset); ignoreWS(raw_url, header_info.data_offset); } else { value = extract(raw_url, header_info.data_offset).trimmed(); } // add attribute to map header_info.attributes[attribute.toLower()] = value; }/*end if*/ if (header_info.data_offset < raw_url_len && raw_url[header_info.data_offset] == QLatin1Char(',')) { data_begin_reached = true; } header_info.data_offset++; // jump over separator token }/*wend*/ return header_info; } #ifdef DATAKIOSLAVE DataProtocol::DataProtocol(const QByteArray &pool_socket, const QByteArray &app_socket) : SlaveBase("kio_data", pool_socket, app_socket) { #else DataProtocol::DataProtocol() { #endif //qDebug(); } /* --------------------------------------------------------------------- */ DataProtocol::~DataProtocol() { //qDebug(); } /* --------------------------------------------------------------------- */ void DataProtocol::get(const QUrl &url) { ref(); //qDebug() << this; const DataHeader hdr = parseDataHeader(url, false); const int size = hdr.url.length(); const int data_ofs = qMin(hdr.data_offset, size); // FIXME: string is copied, would be nice if we could have a reference only const QByteArray url_data = hdr.url.mid(data_ofs); QByteArray outData; if (hdr.is_base64) { // base64 stuff is expected to contain the correct charset, so we just // decode it and pass it to the receiver outData = QByteArray::fromBase64(url_data); } else { QTextCodec *codec = QTextCodec::codecForName(hdr.attributes[QStringLiteral("charset")].toLatin1()); if (codec != nullptr) { outData = codec->toUnicode(url_data).toUtf8(); } else { outData = url_data; }/*end if*/ }/*end if*/ //qDebug() << "emit mimeType@"< * Derived from slave.cpp * * 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 "dataslave_p.h" #include "dataprotocol_p.h" #include "commands_p.h" #include "slavebase.h" #include -#include -#include +#include +#include using namespace KIO; #define KIO_DATA_POLL_INTERVAL 0 // don't forget to sync DISPATCH_DECL in dataslave_p.h #define DISPATCH_IMPL(type) \ void DataSlave::dispatch_##type() { \ if (_suspended) { \ QueueStruct q(Queue_##type); \ q.size = -1; \ dispatchQueue.push_back(q); \ if (!timer->isActive()) timer->start(KIO_DATA_POLL_INTERVAL); \ } else \ type(); \ } // don't forget to sync DISPATCH_DECL1 in dataslave_p.h #define DISPATCH_IMPL1(type, paramtype, paramname) \ void DataSlave::dispatch_##type(paramtype paramname) { \ if (_suspended) { \ QueueStruct q(Queue_##type); \ q.paramname = paramname; \ dispatchQueue.push_back(q); \ if (!timer->isActive()) timer->start(KIO_DATA_POLL_INTERVAL); \ } else \ type(paramname); \ } DataSlave::DataSlave() : Slave(QStringLiteral("data")) { //qDebug() << this; _suspended = false; timer = new QTimer(this); connect(timer, SIGNAL(timeout()), SLOT(dispatchNext())); } DataSlave::~DataSlave() { //qDebug() << this; } void DataSlave::hold(const QUrl &/*url*/) { // ignored } void DataSlave::suspend() { _suspended = true; //qDebug() << this; timer->stop(); } void DataSlave::resume() { _suspended = false; //qDebug() << this; // aarrrgh! This makes the once hyper fast and efficient data protocol // implementation slow as molasses. But it wouldn't work otherwise, // and I don't want to start messing around with threads timer->start(KIO_DATA_POLL_INTERVAL); } // finished is a special case. If we emit it right away, then // TransferJob::start can delete the job even before the end of the method void DataSlave::dispatch_finished() { QueueStruct q(Queue_finished); q.size = -1; dispatchQueue.push_back(q); if (!timer->isActive()) { timer->start(KIO_DATA_POLL_INTERVAL); } } void DataSlave::dispatchNext() { if (dispatchQueue.empty()) { timer->stop(); return; } const QueueStruct &q = dispatchQueue.front(); //qDebug() << this << "dispatching" << q.type << dispatchQueue.size() << "left"; switch (q.type) { case Queue_mimeType: mimeType(q.s); break; case Queue_totalSize: totalSize(q.size); break; case Queue_sendMetaData: sendMetaData(); break; case Queue_data: data(q.ba); break; case Queue_finished: finished(); break; }/*end switch*/ dispatchQueue.pop_front(); } void DataSlave::send(int cmd, const QByteArray &arr) { QDataStream stream(arr); QUrl url; switch (cmd) { case CMD_GET: { stream >> url; get(url); break; } case CMD_MIMETYPE: { stream >> url; mimetype(url); break; } // ignore these (must not emit error, otherwise SIGSEGV occurs) case CMD_REPARSECONFIGURATION: case CMD_META_DATA: case CMD_SUBURL: break; default: error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(QStringLiteral("data"), cmd)); }/*end switch*/ } bool DataSlave::suspended() { return _suspended; } void DataSlave::setHost(const QString &/*host*/, quint16 /*port*/, const QString &/*user*/, const QString &/*passwd*/) { // irrelevant -> will be ignored } void DataSlave::setConfig(const MetaData &/*config*/) { // FIXME: decide to handle this directly or not at all #if 0 QByteArray data; QDataStream stream(data, QIODevice::WriteOnly); stream << config; slaveconn.send(CMD_CONFIG, data); #endif } void DataSlave::setAllMetaData(const MetaData &md) { meta_data = md; } void DataSlave::sendMetaData() { emit metaData(meta_data); } DISPATCH_IMPL1(mimeType, const QString &, s) DISPATCH_IMPL1(totalSize, KIO::filesize_t, size) DISPATCH_IMPL(sendMetaData) DISPATCH_IMPL1(data, const QByteArray &, ba) #undef DISPATCH_IMPL #undef DISPATCH_IMPL1 diff --git a/src/core/davjob.cpp b/src/core/davjob.cpp index 6389cb0f..c4cee0b0 100644 --- a/src/core/davjob.cpp +++ b/src/core/davjob.cpp @@ -1,164 +1,164 @@ // -*- c++ -*- /* This file is part of the KDE libraries Copyright (C) 2002 Jan-Pascal van Best 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 "davjob.h" -#include -#include +#include +#include #include "httpmethod_p.h" #include "jobclasses.h" #include "global.h" #include "job.h" #include "job_p.h" using namespace KIO; /** @internal */ class KIO::DavJobPrivate: public KIO::TransferJobPrivate { public: DavJobPrivate(const QUrl &url) : TransferJobPrivate(url, KIO::CMD_SPECIAL, QByteArray(), QByteArray()) {} QByteArray savedStaticData; QByteArray str_response; QDomDocument m_response; //TransferJob *m_subJob; //bool m_suspended; Q_DECLARE_PUBLIC(DavJob) static inline DavJob *newJob(const QUrl &url, int method, const QString &request, JobFlags flags) { DavJob *job = new DavJob(*new DavJobPrivate(url), method, request); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); if (!(flags & HideProgressInfo)) { KIO::getJobTracker()->registerJob(job); } return job; } }; DavJob::DavJob(DavJobPrivate &dd, int method, const QString &request) : TransferJob(dd) { // We couldn't set the args when calling the parent constructor, // so do it now. Q_D(DavJob); QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly); stream << (int) 7 << d->m_url << method; // Same for static data if (! request.isEmpty()) { d->staticData = "\r\n" + request.toUtf8(); d->staticData.truncate(d->staticData.size() - 1); d->savedStaticData = d->staticData; stream << static_cast(d->staticData.size()); } else { stream << static_cast(-1); } } QDomDocument &DavJob::response() { return d_func()->m_response; } void DavJob::slotData(const QByteArray &data) { Q_D(DavJob); if (d->m_redirectionURL.isEmpty() || !d->m_redirectionURL.isValid() || error()) { unsigned int oldSize = d->str_response.size(); d->str_response.resize(oldSize + data.size()); memcpy(d->str_response.data() + oldSize, data.data(), data.size()); } } void DavJob::slotFinished() { Q_D(DavJob); //qDebug() << d->str_response; if (!d->m_redirectionURL.isEmpty() && d->m_redirectionURL.isValid() && (d->m_command == CMD_SPECIAL)) { QDataStream istream(d->m_packedArgs); int s_cmd, s_method; qint64 s_size; QUrl s_url; istream >> s_cmd; istream >> s_url; istream >> s_method; istream >> s_size; // PROPFIND if ((s_cmd == 7) && (s_method == (int)KIO::DAV_PROPFIND)) { d->m_packedArgs.truncate(0); QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly); stream << (int)7 << d->m_redirectionURL << (int)KIO::DAV_PROPFIND << s_size; } } else if (! d->m_response.setContent(d->str_response, true)) { // An error occurred parsing the XML response QDomElement root = d->m_response.createElementNS(QStringLiteral("DAV:"), QStringLiteral("error-report")); d->m_response.appendChild(root); QDomElement el = d->m_response.createElementNS(QStringLiteral("DAV:"), QStringLiteral("offending-response")); QDomText textnode = d->m_response.createTextNode(d->str_response); el.appendChild(textnode); root.appendChild(el); } //qDebug() << d->m_response.toString(); TransferJob::slotFinished(); d->staticData = d->savedStaticData; // Need to send DAV request to this host too } /* Convenience methods */ DavJob *KIO::davPropFind(const QUrl &url, const QDomDocument &properties, const QString &depth, JobFlags flags) { DavJob *job = DavJobPrivate::newJob(url, (int) KIO::DAV_PROPFIND, properties.toString(), flags); job->addMetaData(QStringLiteral("davDepth"), depth); return job; } DavJob *KIO::davPropPatch(const QUrl &url, const QDomDocument &properties, JobFlags flags) { return DavJobPrivate::newJob(url, (int) KIO::DAV_PROPPATCH, properties.toString(), flags); } DavJob *KIO::davSearch(const QUrl &url, const QString &nsURI, const QString &qName, const QString &query, JobFlags flags) { QDomDocument doc; QDomElement searchrequest = doc.createElementNS(QStringLiteral("DAV:"), QStringLiteral("searchrequest")); QDomElement searchelement = doc.createElementNS(nsURI, qName); QDomText text = doc.createTextNode(query); searchelement.appendChild(text); searchrequest.appendChild(searchelement); doc.appendChild(searchrequest); return DavJobPrivate::newJob(url, KIO::DAV_SEARCH, doc.toString(), flags); } DavJob *KIO::davReport(const QUrl &url, const QString &report, const QString &depth, JobFlags flags) { DavJob *job = DavJobPrivate::newJob(url, (int) KIO::DAV_REPORT, report, flags); job->addMetaData(QStringLiteral("davDepth"), depth); return job; } diff --git a/src/core/davjob.h b/src/core/davjob.h index c2f07497..1b595b92 100644 --- a/src/core/davjob.h +++ b/src/core/davjob.h @@ -1,126 +1,126 @@ // -*- c++ -*- /* This file is part of the KDE libraries Copyright (C) 2002 Jan-Pascal van Best 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. */ #ifndef KIO_DAVJOB_H #define KIO_DAVJOB_H -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include "kiocore_export.h" #include "transferjob.h" #include "global.h" namespace KIO { class Slave; class DavJobPrivate; /** * @class KIO::DavJob davjob.h * * The transfer job pumps data into and/or out of a Slave. * Data is sent to the slave on request of the slave ( dataReq). * If data coming from the slave can not be handled, the * reading of data from the slave should be suspended. * @see KIO::davPropFind() * @see KIO::davPropPatch() * @see KIO::davSearch() */ class KIOCORE_EXPORT DavJob : public TransferJob { Q_OBJECT public: /** * Returns the response as a QDomDocument. * @return the response document */ QDomDocument &response(); protected Q_SLOTS: void slotFinished() Q_DECL_OVERRIDE; void slotData(const QByteArray &data) Q_DECL_OVERRIDE; protected: DavJob(DavJobPrivate &dd, int, const QString &); private: Q_DECLARE_PRIVATE(DavJob) }; /** * Creates a new DavJob that issues a PROPFIND command. PROPFIND retrieves * the properties of the resource identified by the given @p url. * * @param url the URL of the resource * @param properties a propfind document that describes the properties that * should be retrieved * @param depth the depth of the request. Can be "0", "1" or "infinity" * @param flags We support HideProgressInfo here * @return the new DavJob */ KIOCORE_EXPORT DavJob *davPropFind(const QUrl &url, const QDomDocument &properties, const QString &depth, JobFlags flags = DefaultFlags); /** * Creates a new DavJob that issues a PROPPATCH command. PROPPATCH sets * the properties of the resource identified by the given @p url. * * @param url the URL of the resource * @param properties a PROPPACTCH document that describes the properties that * should be modified and its new values * @param flags We support HideProgressInfo here * @return the new DavJob */ KIOCORE_EXPORT DavJob *davPropPatch(const QUrl &url, const QDomDocument &properties, JobFlags flags = DefaultFlags); /** * Creates a new DavJob that issues a SEARCH command. * * @param url the URL of the resource * @param nsURI the URI of the search method's qualified name * @param qName the local part of the search method's qualified name * @param query the search string * @param flags We support HideProgressInfo here * @return the new DavJob */ KIOCORE_EXPORT DavJob *davSearch(const QUrl &url, const QString &nsURI, const QString &qName, const QString &query, JobFlags flags = DefaultFlags); /** * Creates a new DavJob that issues a REPORT command. * * @param url the URL of the resource * @param report a REPORT document that describes the request to make * @param depth the depth of the request. Can be "0", "1" or "infinity" * @param flags We support HideProgressInfo here * @return the new DavJob * @since 4.4 */ KIOCORE_EXPORT DavJob *davReport(const QUrl &url, const QString &report, const QString &depth, JobFlags flags = DefaultFlags); } #endif diff --git a/src/core/deletejob.cpp b/src/core/deletejob.cpp index cabf40ae..229a1b07 100644 --- a/src/core/deletejob.cpp +++ b/src/core/deletejob.cpp @@ -1,514 +1,514 @@ /* This file is part of the KDE libraries Copyright 2000 Stephan Kulow Copyright 2000-2009 David Faure Copyright 2000 Waldo Bastian 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 "deletejob.h" #include "job.h" // buildErrorString #include "statjob.h" #include "listjob.h" #include "kcoredirlister.h" #include "scheduler.h" #include "kdirwatch.h" #include "kprotocolmanager.h" #include #include "../pathhelpers_p.h" #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include #include "job_p.h" extern bool kio_resolve_local_urls; // from copyjob.cpp, abused here to save a symbol. static bool isHttpProtocol(const QString &protocol) { return (protocol.startsWith(QLatin1String("webdav"), Qt::CaseInsensitive) || protocol.startsWith(QLatin1String("http"), Qt::CaseInsensitive)); } namespace KIO { enum DeleteJobState { DELETEJOB_STATE_STATING, DELETEJOB_STATE_DELETING_FILES, DELETEJOB_STATE_DELETING_DIRS }; /* static const char* const s_states[] = { "DELETEJOB_STATE_STATING", "DELETEJOB_STATE_DELETING_FILES", "DELETEJOB_STATE_DELETING_DIRS" }; */ class DeleteJobPrivate: public KIO::JobPrivate { public: DeleteJobPrivate(const QList &src) : state(DELETEJOB_STATE_STATING) , m_processedFiles(0) , m_processedDirs(0) , m_totalFilesDirs(0) , m_srcList(src) , m_currentStat(m_srcList.begin()) , m_reportTimer(nullptr) { } DeleteJobState state; int m_processedFiles; int m_processedDirs; int m_totalFilesDirs; QUrl m_currentURL; QList files; QList symlinks; QList dirs; QList m_srcList; QList::iterator m_currentStat; QSet m_parentDirs; QTimer *m_reportTimer; void statNextSrc(); void currentSourceStated(bool isDir, bool isLink); void finishedStatPhase(); void deleteNextFile(); void deleteNextDir(); void restoreDirWatch() const; void slotReport(); void slotStart(); void slotEntries(KIO::Job *, const KIO::UDSEntryList &list); Q_DECLARE_PUBLIC(DeleteJob) static inline DeleteJob *newJob(const QList &src, JobFlags flags) { DeleteJob *job = new DeleteJob(*new DeleteJobPrivate(src)); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); if (!(flags & HideProgressInfo)) { KIO::getJobTracker()->registerJob(job); } if (!(flags & NoPrivilegeExecution)) { job->d_func()->m_privilegeExecutionEnabled = true; job->d_func()->m_operationType = Delete; } return job; } }; } // namespace KIO using namespace KIO; DeleteJob::DeleteJob(DeleteJobPrivate &dd) : Job(dd) { d_func()->m_reportTimer = new QTimer(this); connect(d_func()->m_reportTimer, SIGNAL(timeout()), this, SLOT(slotReport())); //this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX d_func()->m_reportTimer->start(200); QTimer::singleShot(0, this, SLOT(slotStart())); } DeleteJob::~DeleteJob() { } QList DeleteJob::urls() const { return d_func()->m_srcList; } void DeleteJobPrivate::slotStart() { statNextSrc(); } void DeleteJobPrivate::slotReport() { Q_Q(DeleteJob); emit q->deleting(q, m_currentURL); // TODO: maybe we could skip everything else when (flags & HideProgressInfo) ? JobPrivate::emitDeleting(q, m_currentURL); switch (state) { case DELETEJOB_STATE_STATING: q->setTotalAmount(KJob::Files, files.count()); q->setTotalAmount(KJob::Directories, dirs.count()); break; case DELETEJOB_STATE_DELETING_DIRS: q->setProcessedAmount(KJob::Directories, m_processedDirs); q->emitPercent(m_processedFiles + m_processedDirs, m_totalFilesDirs); break; case DELETEJOB_STATE_DELETING_FILES: q->setProcessedAmount(KJob::Files, m_processedFiles); q->emitPercent(m_processedFiles, m_totalFilesDirs); break; } } void DeleteJobPrivate::slotEntries(KIO::Job *job, const UDSEntryList &list) { UDSEntryList::ConstIterator it = list.begin(); const UDSEntryList::ConstIterator end = list.end(); for (; it != end; ++it) { const UDSEntry &entry = *it; const QString displayName = entry.stringValue(KIO::UDSEntry::UDS_NAME); Q_ASSERT(!displayName.isEmpty()); if (displayName != QLatin1String("..") && displayName != QLatin1String(".")) { QUrl url; const QString urlStr = entry.stringValue(KIO::UDSEntry::UDS_URL); if (!urlStr.isEmpty()) { url = QUrl(urlStr); } else { url = static_cast(job)->url(); // assumed to be a dir url.setPath(concatPaths(url.path(), displayName)); } //qDebug() << displayName << "(" << url << ")"; if (entry.isLink()) { symlinks.append(url); } else if (entry.isDir()) { dirs.append(url); } else { files.append(url); } } } } void DeleteJobPrivate::statNextSrc() { Q_Q(DeleteJob); //qDebug(); if (m_currentStat != m_srcList.end()) { m_currentURL = (*m_currentStat); // if the file system doesn't support deleting, we do not even stat if (!KProtocolManager::supportsDeleting(m_currentURL)) { QPointer that = q; ++m_currentStat; emit q->warning(q, buildErrorString(ERR_CANNOT_DELETE, m_currentURL.toDisplayString())); if (that) { statNextSrc(); } return; } // Stat it state = DELETEJOB_STATE_STATING; // Fast path for KFileItems in directory views while (m_currentStat != m_srcList.end()) { m_currentURL = (*m_currentStat); const KFileItem cachedItem = KCoreDirLister::cachedItemForUrl(m_currentURL); if (cachedItem.isNull()) { break; } //qDebug() << "Found cached info about" << m_currentURL << "isDir=" << cachedItem.isDir() << "isLink=" << cachedItem.isLink(); currentSourceStated(cachedItem.isDir(), cachedItem.isLink()); ++m_currentStat; } // Hook for unit test to disable the fast path. if (!kio_resolve_local_urls) { // Fast path for local files // (using a loop, instead of a huge recursion) while (m_currentStat != m_srcList.end() && (*m_currentStat).isLocalFile()) { m_currentURL = (*m_currentStat); QFileInfo fileInfo(m_currentURL.toLocalFile()); currentSourceStated(fileInfo.isDir(), fileInfo.isSymLink()); ++m_currentStat; } } if (m_currentStat == m_srcList.end()) { // Done, jump to the last else of this method statNextSrc(); } else { KIO::SimpleJob *job = KIO::stat(m_currentURL, StatJob::SourceSide, 0, KIO::HideProgressInfo); Scheduler::setJobPriority(job, 1); //qDebug() << "stat'ing" << m_currentURL; q->addSubjob(job); } } else { if (!q->hasSubjobs()) { // don't go there yet if we're still listing some subdirs finishedStatPhase(); } } } void DeleteJobPrivate::finishedStatPhase() { m_totalFilesDirs = files.count() + symlinks.count() + dirs.count(); slotReport(); // Now we know which dirs hold the files we're going to delete. // To speed things up and prevent double-notification, we disable KDirWatch // on those dirs temporarily (using KDirWatch::self, that's the instance // used by e.g. kdirlister). const QSet::const_iterator itEnd = m_parentDirs.constEnd(); for (QSet::const_iterator it = m_parentDirs.constBegin(); it != itEnd; ++it) { KDirWatch::self()->stopDirScan(*it); } state = DELETEJOB_STATE_DELETING_FILES; deleteNextFile(); } void DeleteJobPrivate::deleteNextFile() { Q_Q(DeleteJob); //qDebug(); if (!files.isEmpty() || !symlinks.isEmpty()) { SimpleJob *job; do { // Take first file to delete out of list QList::iterator it = files.begin(); bool isLink = false; if (it == files.end()) { // No more files it = symlinks.begin(); // Pick up a symlink to delete isLink = true; } // Normal deletion // If local file, try do it directly if ((*it).isLocalFile() && QFile::remove((*it).toLocalFile())) { //kdDebug(7007) << "DeleteJob deleted" << (*it).toLocalFile(); job = nullptr; m_processedFiles++; if (m_processedFiles % 300 == 1 || m_totalFilesDirs < 300) { // update progress info every 300 files m_currentURL = *it; slotReport(); } } else { // if remote - or if unlink() failed (we'll use the job's error handling in that case) //qDebug() << "calling file_delete on" << *it; if (isHttpProtocol(it->scheme())) { job = KIO::http_delete(*it, KIO::HideProgressInfo); } else { job = KIO::file_delete(*it, KIO::HideProgressInfo); job->setParentJob(q); } Scheduler::setJobPriority(job, 1); m_currentURL = (*it); } if (isLink) { symlinks.erase(it); } else { files.erase(it); } if (job) { q->addSubjob(job); return; } // loop only if direct deletion worked (job=0) and there is something else to delete } while (!job && (!files.isEmpty() || !symlinks.isEmpty())); } state = DELETEJOB_STATE_DELETING_DIRS; deleteNextDir(); } void DeleteJobPrivate::deleteNextDir() { Q_Q(DeleteJob); if (!dirs.isEmpty()) { // some dirs to delete ? do { // Take first dir to delete out of list - last ones first ! QList::iterator it = --dirs.end(); // If local dir, try to rmdir it directly if ((*it).isLocalFile() && QDir().rmdir((*it).toLocalFile())) { m_processedDirs++; if (m_processedDirs % 100 == 1) { // update progress info every 100 dirs m_currentURL = *it; slotReport(); } } else { // Call rmdir - works for kioslaves with canDeleteRecursive too, // CMD_DEL will trigger the recursive deletion in the slave. SimpleJob *job = KIO::rmdir(*it); job->setParentJob(q); job->addMetaData(QStringLiteral("recurse"), QStringLiteral("true")); Scheduler::setJobPriority(job, 1); dirs.erase(it); q->addSubjob(job); return; } dirs.erase(it); } while (!dirs.isEmpty()); } // Re-enable watching on the dirs that held the deleted files restoreDirWatch(); // Finished - tell the world if (!m_srcList.isEmpty()) { //qDebug() << "KDirNotify'ing FilesRemoved" << m_srcList; org::kde::KDirNotify::emitFilesRemoved(m_srcList); } if (m_reportTimer != nullptr) { m_reportTimer->stop(); } q->emitResult(); } void DeleteJobPrivate::restoreDirWatch() const { const auto itEnd = m_parentDirs.constEnd(); for (auto it = m_parentDirs.constBegin(); it != itEnd; ++it) { KDirWatch::self()->restartDirScan(*it); } } void DeleteJobPrivate::currentSourceStated(bool isDir, bool isLink) { Q_Q(DeleteJob); const QUrl url = (*m_currentStat); if (isDir && !isLink) { // Add toplevel dir in list of dirs dirs.append(url); if (url.isLocalFile()) { // We are about to delete this dir, no need to watch it // Maybe we should ask kdirwatch to remove all watches recursively? // But then there would be no feedback (things disappearing progressively) during huge deletions KDirWatch::self()->stopDirScan(url.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } if (!KProtocolManager::canDeleteRecursive(url)) { //qDebug() << url << "is a directory, let's list it"; ListJob *newjob = KIO::listRecursive(url, KIO::HideProgressInfo); newjob->addMetaData(QStringLiteral("details"), QStringLiteral("0")); newjob->setUnrestricted(true); // No KIOSK restrictions Scheduler::setJobPriority(newjob, 1); QObject::connect(newjob, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), q, SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList))); q->addSubjob(newjob); // Note that this listing job will happen in parallel with other stat jobs. } } else { if (isLink) { //qDebug() << "Target is a symlink"; symlinks.append(url); } else { //qDebug() << "Target is a file"; files.append(url); } } if (url.isLocalFile()) { const QString parentDir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path(); m_parentDirs.insert(parentDir); } } void DeleteJob::slotResult(KJob *job) { Q_D(DeleteJob); switch (d->state) { case DELETEJOB_STATE_STATING: removeSubjob(job); // Was this a stat job or a list job? We do both in parallel. if (StatJob *statJob = qobject_cast(job)) { // Was there an error while stating ? if (job->error()) { // Probably : doesn't exist Job::slotResult(job); // will set the error and emit result(this) d->restoreDirWatch(); return; } const UDSEntry entry = statJob->statResult(); // Is it a file or a dir ? const bool isLink = entry.isLink(); const bool isDir = entry.isDir(); d->currentSourceStated(isDir, isLink); ++d->m_currentStat; d->statNextSrc(); } else { if (job->error()) { // Try deleting nonetheless, it may be empty (and non-listable) } if (!hasSubjobs()) { d->finishedStatPhase(); } } break; case DELETEJOB_STATE_DELETING_FILES: // Propagate the subjob's metadata (a SimpleJob) to the real DeleteJob // FIXME: setMetaData() in the KIO API only allows access to outgoing metadata, // but we need to alter the incoming one d->m_incomingMetaData = dynamic_cast(job)->metaData(); if (job->error()) { Job::slotResult(job); // will set the error and emit result(this) d->restoreDirWatch(); return; } removeSubjob(job); Q_ASSERT(!hasSubjobs()); d->m_processedFiles++; d->deleteNextFile(); break; case DELETEJOB_STATE_DELETING_DIRS: if (job->error()) { Job::slotResult(job); // will set the error and emit result(this) d->restoreDirWatch(); return; } removeSubjob(job); Q_ASSERT(!hasSubjobs()); d->m_processedDirs++; //emit processedAmount( this, KJob::Directories, d->m_processedDirs ); //emitPercent( d->m_processedFiles + d->m_processedDirs, d->m_totalFilesDirs ); d->deleteNextDir(); break; default: Q_ASSERT(0); } } DeleteJob *KIO::del(const QUrl &src, JobFlags flags) { QList srcList; srcList.append(src); DeleteJob *job = DeleteJobPrivate::newJob(srcList, flags); if (job->uiDelegateExtension()) { job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::RemoveContent); } return job; } DeleteJob *KIO::del(const QList &src, JobFlags flags) { DeleteJob *job = DeleteJobPrivate::newJob(src, flags); if (job->uiDelegateExtension()) { job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::RemoveContent); } return job; } #include "moc_deletejob.cpp" diff --git a/src/core/deletejob.h b/src/core/deletejob.h index 17f682e3..86a612f2 100644 --- a/src/core/deletejob.h +++ b/src/core/deletejob.h @@ -1,127 +1,127 @@ // -*- c++ -*- /* This file is part of the KDE libraries Copyright 2000 Stephan Kulow Copyright 2000-2006 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 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. */ #ifndef KIO_DELETEJOB_H #define KIO_DELETEJOB_H -#include +#include #include "kiocore_export.h" #include "global.h" #include "job_base.h" class QTimer; namespace KIO { class DeleteJobPrivate; /** * @class KIO::DeleteJob deletejob.h * * A more complex Job to delete files and directories. * Don't create the job directly, but use KIO::del() instead. * * @see KIO::del() */ class KIOCORE_EXPORT DeleteJob : public Job { Q_OBJECT public: virtual ~DeleteJob(); /** * Returns the list of URLs. * @return the list of URLs. */ QList urls() const; Q_SIGNALS: /** * Emitted when the total number of files is known. * @param job the job that emitted this signal * @param files the total number of files */ void totalFiles(KJob *job, unsigned long files); /** * Emitted when the toal number of direcotries is known. * @param job the job that emitted this signal * @param dirs the total number of directories */ void totalDirs(KJob *job, unsigned long dirs); /** * Sends the number of processed files. * @param job the job that emitted this signal * @param files the number of processed files */ void processedFiles(KIO::Job *job, unsigned long files); /** * Sends the number of processed directories. * @param job the job that emitted this signal * @param dirs the number of processed dirs */ void processedDirs(KIO::Job *job, unsigned long dirs); /** * Sends the URL of the file that is currently being deleted. * @param job the job that emitted this signal * @param file the URL of the file or directory that is being * deleted */ void deleting(KIO::Job *job, const QUrl &file); protected Q_SLOTS: void slotResult(KJob *job) Q_DECL_OVERRIDE; protected: DeleteJob(DeleteJobPrivate &dd); private: Q_PRIVATE_SLOT(d_func(), void slotStart()) Q_PRIVATE_SLOT(d_func(), void slotEntries(KIO::Job *, const KIO::UDSEntryList &list)) Q_PRIVATE_SLOT(d_func(), void slotReport()) Q_DECLARE_PRIVATE(DeleteJob) }; /** * Delete a file or directory. * * @param src file to delete * @param flags We support HideProgressInfo here * @return the job handling the operation */ KIOCORE_EXPORT DeleteJob *del(const QUrl &src, JobFlags flags = DefaultFlags); /** * Deletes a list of files or directories. * * @param src the files to delete * @param flags We support HideProgressInfo here * @return the job handling the operation */ KIOCORE_EXPORT DeleteJob *del(const QList &src, JobFlags flags = DefaultFlags); } #endif diff --git a/src/core/directorysizejob.cpp b/src/core/directorysizejob.cpp index 408ab692..97fcf8ab 100644 --- a/src/core/directorysizejob.cpp +++ b/src/core/directorysizejob.cpp @@ -1,206 +1,206 @@ /* This file is part of the KDE libraries Copyright (C) 2000, 2006 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 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 "directorysizejob.h" #include "listjob.h" #include #include -#include +#include #include "job_p.h" namespace KIO { class DirectorySizeJobPrivate: public KIO::JobPrivate { public: DirectorySizeJobPrivate() : m_totalSize(0L) , m_totalFiles(0L) , m_totalSubdirs(0L) , m_currentItem(0) { } DirectorySizeJobPrivate(const KFileItemList &lstItems) : m_totalSize(0L) , m_totalFiles(0L) , m_totalSubdirs(0L) , m_lstItems(lstItems) , m_currentItem(0) { } KIO::filesize_t m_totalSize; KIO::filesize_t m_totalFiles; KIO::filesize_t m_totalSubdirs; KFileItemList m_lstItems; int m_currentItem; QHash > m_visitedInodes; // device -> set of inodes void startNextJob(const QUrl &url); void slotEntries(KIO::Job *, const KIO::UDSEntryList &); void processNextItem(); Q_DECLARE_PUBLIC(DirectorySizeJob) static inline DirectorySizeJob *newJob(const QUrl &directory) { DirectorySizeJobPrivate *d = new DirectorySizeJobPrivate; DirectorySizeJob *job = new DirectorySizeJob(*d); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); d->startNextJob(directory); return job; } static inline DirectorySizeJob *newJob(const KFileItemList &lstItems) { DirectorySizeJobPrivate *d = new DirectorySizeJobPrivate(lstItems); DirectorySizeJob *job = new DirectorySizeJob(*d); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); QTimer::singleShot(0, job, SLOT(processNextItem())); return job; } }; } // namespace KIO using namespace KIO; DirectorySizeJob::DirectorySizeJob(DirectorySizeJobPrivate &dd) : KIO::Job(dd) { } DirectorySizeJob::~DirectorySizeJob() { } KIO::filesize_t DirectorySizeJob::totalSize() const { return d_func()->m_totalSize; } KIO::filesize_t DirectorySizeJob::totalFiles() const { return d_func()->m_totalFiles; } KIO::filesize_t DirectorySizeJob::totalSubdirs() const { return d_func()->m_totalSubdirs; } void DirectorySizeJobPrivate::processNextItem() { Q_Q(DirectorySizeJob); while (m_currentItem < m_lstItems.count()) { const KFileItem item = m_lstItems[m_currentItem++]; if (!item.isLink()) { if (item.isDir()) { //qDebug() << "dir -> listing"; startNextJob(item.url()); return; // we'll come back later, when this one's finished } else { m_totalSize += item.size(); m_totalFiles++; //qDebug() << "file -> " << m_totalSize; } } else { m_totalFiles++; } } //qDebug() << "finished"; q->emitResult(); } void DirectorySizeJobPrivate::startNextJob(const QUrl &url) { Q_Q(DirectorySizeJob); //qDebug() << url; KIO::ListJob *listJob = KIO::listRecursive(url, KIO::HideProgressInfo); listJob->addMetaData(QStringLiteral("details"), QStringLiteral("3")); q->connect(listJob, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList))); q->addSubjob(listJob); } void DirectorySizeJobPrivate::slotEntries(KIO::Job *, const KIO::UDSEntryList &list) { KIO::UDSEntryList::ConstIterator it = list.begin(); const KIO::UDSEntryList::ConstIterator end = list.end(); for (; it != end; ++it) { const KIO::UDSEntry &entry = *it; const long device = entry.numberValue(KIO::UDSEntry::UDS_DEVICE_ID, 0); if (device) { // Hard-link detection (#67939) const long inode = entry.numberValue(KIO::UDSEntry::UDS_INODE, 0); QSet &visitedInodes = m_visitedInodes[device]; // find or insert if (visitedInodes.contains(inode)) { continue; } visitedInodes.insert(inode); } const KIO::filesize_t size = entry.numberValue(KIO::UDSEntry::UDS_SIZE, 0); const QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME); if (name == QLatin1String(".")) { m_totalSize += size; //qDebug() << "'.': added" << size << "->" << m_totalSize; } else if (name != QLatin1String("..")) { if (!entry.isLink()) { m_totalSize += size; } if (!entry.isDir()) { m_totalFiles++; } else { m_totalSubdirs++; } //qDebug() << name << ":" << size << "->" << m_totalSize; } } } void DirectorySizeJob::slotResult(KJob *job) { Q_D(DirectorySizeJob); //qDebug() << d->m_totalSize; removeSubjob(job); if (d->m_currentItem < d->m_lstItems.count()) { d->processNextItem(); } else { if (job->error()) { setError(job->error()); setErrorText(job->errorText()); } emitResult(); } } //static DirectorySizeJob *KIO::directorySize(const QUrl &directory) { return DirectorySizeJobPrivate::newJob(directory); // useless - but consistent with other jobs } //static DirectorySizeJob *KIO::directorySize(const KFileItemList &lstItems) { return DirectorySizeJobPrivate::newJob(lstItems); } #include "moc_directorysizejob.cpp" diff --git a/src/core/forwardingslavebase.cpp b/src/core/forwardingslavebase.cpp index f2f9a2d6..dd2db7dc 100644 --- a/src/core/forwardingslavebase.cpp +++ b/src/core/forwardingslavebase.cpp @@ -1,508 +1,508 @@ /* This file is part of the KDE project Copyright (c) 2004 Kevin Ottens 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 "forwardingslavebase.h" #include "../pathhelpers_p.h" #include "deletejob.h" #include "mkdirjob.h" #include "job.h" #include -#include +#include namespace KIO { class ForwardingSlaveBasePrivate { public: ForwardingSlaveBasePrivate(QObject *eventLoopParent) : eventLoop(eventLoopParent) {} ForwardingSlaveBase *q; QUrl m_processedURL; QUrl m_requestedURL; QEventLoop eventLoop; bool internalRewriteUrl(const QUrl &url, QUrl &newURL); void connectJob(Job *job); void connectSimpleJob(SimpleJob *job); void connectListJob(ListJob *job); void connectTransferJob(TransferJob *job); void _k_slotResult(KJob *job); void _k_slotWarning(KJob *job, const QString &msg); void _k_slotInfoMessage(KJob *job, const QString &msg); void _k_slotTotalSize(KJob *job, qulonglong size); void _k_slotProcessedSize(KJob *job, qulonglong size); void _k_slotSpeed(KJob *job, unsigned long bytesPerSecond); // KIO::SimpleJob subclasses void _k_slotRedirection(KIO::Job *job, const QUrl &url); // KIO::ListJob void _k_slotEntries(KIO::Job *job, const KIO::UDSEntryList &entries); // KIO::TransferJob void _k_slotData(KIO::Job *job, const QByteArray &data); void _k_slotDataReq(KIO::Job *job, QByteArray &data); void _k_slotMimetype(KIO::Job *job, const QString &type); void _k_slotCanResume(KIO::Job *job, KIO::filesize_t offset); }; ForwardingSlaveBase::ForwardingSlaveBase(const QByteArray &protocol, const QByteArray &poolSocket, const QByteArray &appSocket) : QObject(), SlaveBase(protocol, poolSocket, appSocket), d(new ForwardingSlaveBasePrivate(this)) { d->q = this; } ForwardingSlaveBase::~ForwardingSlaveBase() { delete d; } bool ForwardingSlaveBasePrivate::internalRewriteUrl(const QUrl &url, QUrl &newURL) { bool result = true; if (url.scheme() == q->mProtocol) { result = q->rewriteUrl(url, newURL); } else { newURL = url; } m_processedURL = newURL; m_requestedURL = url; return result; } void ForwardingSlaveBase::prepareUDSEntry(KIO::UDSEntry &entry, bool listing) const { //qDebug() << "listing==" << listing; const QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME); QString mimetype = entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE); QUrl url; const QString urlStr = entry.stringValue(KIO::UDSEntry::UDS_URL); const bool url_found = !urlStr.isEmpty(); if (url_found) { url = QUrl(urlStr); QUrl new_url(d->m_requestedURL); if (listing) { new_url.setPath(concatPaths(new_url.path(), url.fileName())); } // ## Didn't find a way to use an iterator instead of re-doing a key lookup entry.insert(KIO::UDSEntry::UDS_URL, new_url.toString()); //qDebug() << "URL =" << url; //qDebug() << "New URL =" << new_url; } if (mimetype.isEmpty()) { QUrl new_url(d->m_processedURL); if (url_found && listing) { new_url.setPath(concatPaths(new_url.path(), url.fileName())); } else if (listing) { new_url.setPath(concatPaths(new_url.path(), name)); } QMimeDatabase db; mimetype = db.mimeTypeForUrl(new_url).name(); entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, mimetype); //qDebug() << "New Mimetype = " << mimetype; } if (d->m_processedURL.isLocalFile()) { QUrl new_url(d->m_processedURL); if (listing) { new_url.setPath(concatPaths(new_url.path(), name)); } entry.insert(KIO::UDSEntry::UDS_LOCAL_PATH, new_url.toLocalFile()); } } QUrl ForwardingSlaveBase::processedUrl() const { return d->m_processedURL; } QUrl ForwardingSlaveBase::requestedUrl() const { return d->m_requestedURL; } void ForwardingSlaveBase::get(const QUrl &url) { //qDebug() << url; QUrl new_url; if (d->internalRewriteUrl(url, new_url)) { KIO::TransferJob *job = KIO::get(new_url, NoReload, HideProgressInfo); d->connectTransferJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); } } void ForwardingSlaveBase::put(const QUrl &url, int permissions, JobFlags flags) { //qDebug() << url; QUrl new_url; if (d->internalRewriteUrl(url, new_url)) { KIO::TransferJob *job = KIO::put(new_url, permissions, flags | HideProgressInfo); d->connectTransferJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_MALFORMED_URL, url.toDisplayString()); } } void ForwardingSlaveBase::stat(const QUrl &url) { //qDebug() << url; QUrl new_url; if (d->internalRewriteUrl(url, new_url)) { KIO::SimpleJob *job = KIO::stat(new_url, KIO::HideProgressInfo); d->connectSimpleJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); } } void ForwardingSlaveBase::mimetype(const QUrl &url) { //qDebug() << url; QUrl new_url; if (d->internalRewriteUrl(url, new_url)) { KIO::TransferJob *job = KIO::mimetype(new_url, KIO::HideProgressInfo); d->connectTransferJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); } } void ForwardingSlaveBase::listDir(const QUrl &url) { //qDebug() << url; QUrl new_url; if (d->internalRewriteUrl(url, new_url)) { KIO::ListJob *job = KIO::listDir(new_url, KIO::HideProgressInfo); d->connectListJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); } } void ForwardingSlaveBase::mkdir(const QUrl &url, int permissions) { //qDebug() << url; QUrl new_url; if (d->internalRewriteUrl(url, new_url)) { KIO::SimpleJob *job = KIO::mkdir(new_url, permissions); d->connectSimpleJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_MALFORMED_URL, url.toDisplayString()); } } void ForwardingSlaveBase::rename(const QUrl &src, const QUrl &dest, JobFlags flags) { //qDebug() << src << "," << dest; QUrl new_src, new_dest; if (!d->internalRewriteUrl(src, new_src)) { error(KIO::ERR_DOES_NOT_EXIST, src.toDisplayString()); } else if (d->internalRewriteUrl(dest, new_dest)) { KIO::Job *job = KIO::rename(new_src, new_dest, flags); d->connectJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_MALFORMED_URL, dest.toDisplayString()); } } void ForwardingSlaveBase::symlink(const QString &target, const QUrl &dest, JobFlags flags) { //qDebug() << target << ", " << dest; QUrl new_dest; if (d->internalRewriteUrl(dest, new_dest)) { KIO::SimpleJob *job = KIO::symlink(target, new_dest, flags | HideProgressInfo); d->connectSimpleJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_MALFORMED_URL, dest.toDisplayString()); } } void ForwardingSlaveBase::chmod(const QUrl &url, int permissions) { //qDebug() << url; QUrl new_url; if (d->internalRewriteUrl(url, new_url)) { KIO::SimpleJob *job = KIO::chmod(new_url, permissions); d->connectSimpleJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); } } void ForwardingSlaveBase::setModificationTime(const QUrl &url, const QDateTime &mtime) { //qDebug() << url; QUrl new_url; if (d->internalRewriteUrl(url, new_url)) { KIO::SimpleJob *job = KIO::setModificationTime(new_url, mtime); d->connectSimpleJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); } } void ForwardingSlaveBase::copy(const QUrl &src, const QUrl &dest, int permissions, JobFlags flags) { //qDebug() << src << "," << dest; QUrl new_src, new_dest; if (!d->internalRewriteUrl(src, new_src)) { error(KIO::ERR_DOES_NOT_EXIST, src.toDisplayString()); } else if (d->internalRewriteUrl(dest, new_dest)) { KIO::Job *job = KIO::file_copy(new_src, new_dest, permissions, flags | HideProgressInfo); d->connectJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_MALFORMED_URL, dest.toDisplayString()); } } void ForwardingSlaveBase::del(const QUrl &url, bool isfile) { //qDebug() << url; QUrl new_url; if (d->internalRewriteUrl(url, new_url)) { if (isfile) { KIO::DeleteJob *job = KIO::del(new_url, HideProgressInfo); d->connectJob(job); } else { KIO::SimpleJob *job = KIO::rmdir(new_url); d->connectSimpleJob(job); } d->eventLoop.exec(); } else { error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); } } ////////////////////////////////////////////////////////////////////////////// void ForwardingSlaveBasePrivate::connectJob(KIO::Job *job) { // We will forward the warning message, no need to let the job // display it itself job->setUiDelegate(nullptr); // Forward metadata (e.g. modification time for put()) job->setMetaData(q->allMetaData()); #if 0 // debug code //qDebug() << "transferring metadata:"; const MetaData md = allMetaData(); for (MetaData::const_iterator it = md.begin(); it != md.end(); ++it) //qDebug() << it.key() << " = " << it.data(); #endif q->connect(job, SIGNAL(result(KJob*)), SLOT(_k_slotResult(KJob*))); q->connect(job, SIGNAL(warning(KJob*,QString,QString)), SLOT(_k_slotWarning(KJob*,QString))); q->connect(job, SIGNAL(infoMessage(KJob*,QString,QString)), SLOT(_k_slotInfoMessage(KJob*,QString))); q->connect(job, SIGNAL(totalSize(KJob*,qulonglong)), SLOT(_k_slotTotalSize(KJob*,qulonglong))); q->connect(job, SIGNAL(processedSize(KJob*,qulonglong)), SLOT(_k_slotProcessedSize(KJob*,qulonglong))); q->connect(job, SIGNAL(speed(KJob*,ulong)), SLOT(_k_slotSpeed(KJob*,ulong))); } void ForwardingSlaveBasePrivate::connectSimpleJob(KIO::SimpleJob *job) { connectJob(job); if (job->metaObject()->indexOfSignal("redirection(KIO::Job*,QUrl)") > -1) { q->connect(job, SIGNAL(redirection(KIO::Job*,QUrl)), SLOT(_k_slotRedirection(KIO::Job*,QUrl))); } } void ForwardingSlaveBasePrivate::connectListJob(KIO::ListJob *job) { connectSimpleJob(job); q->connect(job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), SLOT(_k_slotEntries(KIO::Job*,KIO::UDSEntryList))); } void ForwardingSlaveBasePrivate::connectTransferJob(KIO::TransferJob *job) { connectSimpleJob(job); q->connect(job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(_k_slotData(KIO::Job*,QByteArray))); q->connect(job, SIGNAL(dataReq(KIO::Job*,QByteArray&)), SLOT(_k_slotDataReq(KIO::Job*,QByteArray&))); q->connect(job, SIGNAL(mimetype(KIO::Job*,QString)), SLOT(_k_slotMimetype(KIO::Job*,QString))); q->connect(job, SIGNAL(canResume(KIO::Job*,KIO::filesize_t)), SLOT(_k_slotCanResume(KIO::Job*,KIO::filesize_t))); } ////////////////////////////////////////////////////////////////////////////// void ForwardingSlaveBasePrivate::_k_slotResult(KJob *job) { if (job->error() != 0) { q->error(job->error(), job->errorText()); } else { KIO::StatJob *stat_job = qobject_cast(job); if (stat_job != nullptr) { KIO::UDSEntry entry = stat_job->statResult(); q->prepareUDSEntry(entry); q->statEntry(entry); } q->finished(); } eventLoop.exit(); } void ForwardingSlaveBasePrivate::_k_slotWarning(KJob * /*job*/, const QString &msg) { q->warning(msg); } void ForwardingSlaveBasePrivate::_k_slotInfoMessage(KJob * /*job*/, const QString &msg) { q->infoMessage(msg); } void ForwardingSlaveBasePrivate::_k_slotTotalSize(KJob * /*job*/, qulonglong size) { q->totalSize(size); } void ForwardingSlaveBasePrivate::_k_slotProcessedSize(KJob * /*job*/, qulonglong size) { q->processedSize(size); } void ForwardingSlaveBasePrivate::_k_slotSpeed(KJob * /*job*/, unsigned long bytesPerSecond) { q->speed(bytesPerSecond); } void ForwardingSlaveBasePrivate::_k_slotRedirection(KIO::Job *job, const QUrl &url) { q->redirection(url); // We've been redirected stop everything. job->kill(KJob::Quietly); q->finished(); eventLoop.exit(); } void ForwardingSlaveBasePrivate::_k_slotEntries(KIO::Job * /*job*/, const KIO::UDSEntryList &entries) { KIO::UDSEntryList final_entries = entries; KIO::UDSEntryList::iterator it = final_entries.begin(); const KIO::UDSEntryList::iterator end = final_entries.end(); for (; it != end; ++it) { q->prepareUDSEntry(*it, true); } q->listEntries(final_entries); } void ForwardingSlaveBasePrivate::_k_slotData(KIO::Job * /*job*/, const QByteArray &_data) { q->data(_data); } void ForwardingSlaveBasePrivate::_k_slotDataReq(KIO::Job * /*job*/, QByteArray &data) { q->dataReq(); q->readData(data); } void ForwardingSlaveBasePrivate::_k_slotMimetype(KIO::Job * /*job*/, const QString &type) { q->mimeType(type); } void ForwardingSlaveBasePrivate::_k_slotCanResume(KIO::Job * /*job*/, KIO::filesize_t offset) { q->canResume(offset); } } #include "moc_forwardingslavebase.cpp" diff --git a/src/core/forwardingslavebase.h b/src/core/forwardingslavebase.h index 583fa6cb..30b54347 100644 --- a/src/core/forwardingslavebase.h +++ b/src/core/forwardingslavebase.h @@ -1,194 +1,194 @@ /* This file is part of the KDE project Copyright (c) 2004 Kevin Ottens 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. */ #ifndef _FORWARDING_SLAVE_BASE_H_ #define _FORWARDING_SLAVE_BASE_H_ #include "kiocore_export.h" #include #include "job_base.h" // JobFlags -#include -#include +#include +#include namespace KIO { class ForwardingSlaveBasePrivate; /** * @class KIO::ForwardingSlaveBase forwardingslavebase.h * * This class should be used as a base for ioslaves acting as a * forwarder to other ioslaves. It has been designed to support only * local filesystem like ioslaves. * * If the resulting ioslave should be a simple proxy, you only need * to implement the ForwardingSlaveBase::rewriteUrl() method. * * For more advanced behavior, the classic ioslave methods should * be reimplemented, because their default behavior in this class * is to forward using the ForwardingSlaveBase::rewriteUrl() method. * * A possible code snippet for an advanced stat() behavior would look * like this in the child class: * * \code * void ChildProtocol::stat(const QUrl &url) * { * bool is_special = false; * * // Process the URL to see if it should have * // a special treatment * * if ( is_special ) * { * // Handle the URL ourselves * KIO::UDSEntry entry; * // Fill entry with UDSAtom instances * statEntry(entry); * finished(); * } * else * { * // Setup the ioslave internal state if * // required by ChildProtocol::rewriteUrl() * ForwardingSlaveBase::stat(url); * } * } * \endcode * * Of course in this case, you surely need to reimplement listDir() * and get() accordingly. * * If you want view on directories to be correctly refreshed when * something changes on a forwarded URL, you'll need a companion kded * module to emit the KDirNotify Files*() D-Bus signals. * * This class was initially used for media:/ ioslave. This ioslave code * and the MediaDirNotify class of its companion kded module can be a * good source of inspiration. * * @see ForwardingSlaveBase::rewriteUrl() * @author Kevin Ottens */ class KIOCORE_EXPORT ForwardingSlaveBase : public QObject, public SlaveBase { Q_OBJECT public: ForwardingSlaveBase(const QByteArray &protocol, const QByteArray &poolSocket, const QByteArray &appSocket); virtual ~ForwardingSlaveBase(); void get(const QUrl &url) Q_DECL_OVERRIDE; void put(const QUrl &url, int permissions, JobFlags flags) Q_DECL_OVERRIDE; void stat(const QUrl &url) Q_DECL_OVERRIDE; void mimetype(const QUrl &url) Q_DECL_OVERRIDE; void listDir(const QUrl &url) Q_DECL_OVERRIDE; void mkdir(const QUrl &url, int permissions) Q_DECL_OVERRIDE; void rename(const QUrl &src, const QUrl &dest, JobFlags flags) Q_DECL_OVERRIDE; void symlink(const QString &target, const QUrl &dest, JobFlags flags) Q_DECL_OVERRIDE; void chmod(const QUrl &url, int permissions) Q_DECL_OVERRIDE; void setModificationTime(const QUrl &url, const QDateTime &mtime) Q_DECL_OVERRIDE; void copy(const QUrl &src, const QUrl &dest, int permissions, JobFlags flags) Q_DECL_OVERRIDE; void del(const QUrl &url, bool isfile) Q_DECL_OVERRIDE; protected: /** * Rewrite an url to its forwarded counterpart. It should return * true if everything was ok, and false otherwise. * * If a problem is detected it's up to this method to trigger error() * before returning. Returning false silently cancels the current * slave operation. * * @param url The URL as given during the slave call * @param newURL The new URL to forward the slave call to * @return true if the given url could be correctly rewritten */ virtual bool rewriteUrl(const QUrl &url, QUrl &newURL) = 0; /** * Allow to modify a UDSEntry before it's sent to the ioslave endpoint. * This is the default implementation working in most cases, but sometimes * you could make use of more forwarding black magic (for example * dynamically transform any desktop file into a fake directory...) * * @param entry the UDSEntry to post-process * @param listing indicate if this entry it created during a listDir * operation */ virtual void prepareUDSEntry(KIO::UDSEntry &entry, bool listing = false) const; /** * Return the URL being processed by the ioslave * Only access it inside prepareUDSEntry() */ QUrl processedUrl() const; /** * Return the URL asked to the ioslave * Only access it inside prepareUDSEntry() */ QUrl requestedUrl() const; private: // KIO::Job Q_PRIVATE_SLOT(d, void _k_slotResult(KJob *job)) Q_PRIVATE_SLOT(d, void _k_slotWarning(KJob *job, const QString &msg)) Q_PRIVATE_SLOT(d, void _k_slotInfoMessage(KJob *job, const QString &msg)) Q_PRIVATE_SLOT(d, void _k_slotTotalSize(KJob *job, qulonglong size)) Q_PRIVATE_SLOT(d, void _k_slotProcessedSize(KJob *job, qulonglong size)) Q_PRIVATE_SLOT(d, void _k_slotSpeed(KJob *job, unsigned long bytesPerSecond)) // KIO::SimpleJob subclasses Q_PRIVATE_SLOT(d, void _k_slotRedirection(KIO::Job *job, const QUrl &url)) // KIO::ListJob Q_PRIVATE_SLOT(d, void _k_slotEntries(KIO::Job *job, const KIO::UDSEntryList &entries)) // KIO::TransferJob Q_PRIVATE_SLOT(d, void _k_slotData(KIO::Job *job, const QByteArray &data)) Q_PRIVATE_SLOT(d, void _k_slotDataReq(KIO::Job *job, QByteArray &data)) Q_PRIVATE_SLOT(d, void _k_slotMimetype(KIO::Job *job, const QString &type)) Q_PRIVATE_SLOT(d, void _k_slotCanResume(KIO::Job *job, KIO::filesize_t offset)) friend class ForwardingSlaveBasePrivate; ForwardingSlaveBasePrivate *const d; }; } #endif diff --git a/src/core/global.h b/src/core/global.h index 0b2aa746..11bdfca3 100644 --- a/src/core/global.h +++ b/src/core/global.h @@ -1,342 +1,342 @@ /* This file is part of the KDE libraries Copyright (C) 2000-2005 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. */ #ifndef KIO_GLOBAL_H #define KIO_GLOBAL_H #include "kiocore_export.h" -#include +#include #include // for QFile::Permissions #include #include "metadata.h" // for source compat #include "jobtracker.h" // for source compat class QUrl; class QTime; #if defined(Q_OS_WIN) && defined(Q_CC_MSVC) // on windows ssize_t is not defined, only SSIZE_T exists #include typedef SSIZE_T ssize_t; #endif /** * @short A namespace for KIO globals * */ namespace KIO { /// 64-bit file offset typedef qlonglong fileoffset_t; /// 64-bit file size typedef qulonglong filesize_t; /** * Converts @p size from bytes to the string representation. * * @param size size in bytes * @return converted size as a string - e.g. 123.4 KiB , 12.0 MiB */ KIOCORE_EXPORT QString convertSize(KIO::filesize_t size); /** * Converts a size to a string representation * Not unlike QString::number(...) * * @param size size in bytes * @return converted size as a string - e.g. 123456789 */ KIOCORE_EXPORT QString number(KIO::filesize_t size); /** * Converts size from kibi-bytes (2^10) to the string representation. * * @param kibSize size in kibi-bytes (2^10) * @return converted size as a string - e.g. 123.4 KiB , 12.0 MiB */ KIOCORE_EXPORT QString convertSizeFromKiB(KIO::filesize_t kibSize); /** * Calculates remaining time in seconds from total size, processed size and speed. * * @param totalSize total size in bytes * @param processedSize processed size in bytes * @param speed speed in bytes per second * @return calculated remaining time in seconds */ KIOCORE_EXPORT unsigned int calculateRemainingSeconds(KIO::filesize_t totalSize, KIO::filesize_t processedSize, KIO::filesize_t speed); /** * Convert @p seconds to a string representing number of days, hours, minutes and seconds * * @param seconds number of seconds to convert * @return string representation in a locale depending format */ KIOCORE_EXPORT QString convertSeconds(unsigned int seconds); /** * Calculates remaining time from total size, processed size and speed. * Warning: As QTime is limited to 23:59:59, use calculateRemainingSeconds() instead * * @param totalSize total size in bytes * @param processedSize processed size in bytes * @param speed speed in bytes per second * @return calculated remaining time */ #ifndef KIOCORE_NO_DEPRECATED KIOCORE_DEPRECATED_EXPORT QTime calculateRemaining(KIO::filesize_t totalSize, KIO::filesize_t processedSize, KIO::filesize_t speed); #endif /** * Helper for showing information about a set of files and directories * @param items the number of items (= @p files + @p dirs + number of symlinks :) * @param files the number of files * @param dirs the number of dirs * @param size the sum of the size of the @p files * @param showSize whether to show the size in the result * @return the summary string */ KIOCORE_EXPORT QString itemsSummaryString(uint items, uint files, uint dirs, KIO::filesize_t size, bool showSize); /** * Encodes (from the text displayed to the real filename) * This translates '/' into a "unicode fraction slash", QChar(0x2044). * Used by KIO::link, for instance. * @param str the file name to encode * @return the encoded file name */ KIOCORE_EXPORT QString encodeFileName(const QString &str); /** * Decodes (from the filename to the text displayed) * This doesn't do anything anymore, it used to do the opposite of encodeFileName * when encodeFileName was using %2F for '/'. * @param str the file name to decode * @return the decoded file name */ KIOCORE_EXPORT QString decodeFileName(const QString &str); /** * Given a directory path and a filename (which usually exists already), * this function returns a suggested name for a file that doesn't exist * in that directory. The existence is only checked for local urls though. * The suggested file name is of the form "foo 1", "foo 2" etc. * @since 5.0 */ KIOCORE_EXPORT QString suggestName(const QUrl &baseURL, const QString &oldName); /** * Error codes that can be emitted by KIO. */ enum Error { ERR_CANNOT_OPEN_FOR_READING = KJob::UserDefinedError + 1, ERR_CANNOT_OPEN_FOR_WRITING = KJob::UserDefinedError + 2, ERR_CANNOT_LAUNCH_PROCESS = KJob::UserDefinedError + 3, ERR_INTERNAL = KJob::UserDefinedError + 4, ERR_MALFORMED_URL = KJob::UserDefinedError + 5, ERR_UNSUPPORTED_PROTOCOL = KJob::UserDefinedError + 6, ERR_NO_SOURCE_PROTOCOL = KJob::UserDefinedError + 7, ERR_UNSUPPORTED_ACTION = KJob::UserDefinedError + 8, ERR_IS_DIRECTORY = KJob::UserDefinedError + 9, ///< ... where a file was expected ERR_IS_FILE = KJob::UserDefinedError + 10, ///< ... where a directory was expected (e.g. listing) ERR_DOES_NOT_EXIST = KJob::UserDefinedError + 11, ERR_FILE_ALREADY_EXIST = KJob::UserDefinedError + 12, ERR_DIR_ALREADY_EXIST = KJob::UserDefinedError + 13, ERR_UNKNOWN_HOST = KJob::UserDefinedError + 14, ERR_ACCESS_DENIED = KJob::UserDefinedError + 15, ERR_WRITE_ACCESS_DENIED = KJob::UserDefinedError + 16, ERR_CANNOT_ENTER_DIRECTORY = KJob::UserDefinedError + 17, ERR_PROTOCOL_IS_NOT_A_FILESYSTEM = KJob::UserDefinedError + 18, ERR_CYCLIC_LINK = KJob::UserDefinedError + 19, ERR_USER_CANCELED = KJob::KilledJobError, ERR_CYCLIC_COPY = KJob::UserDefinedError + 21, ERR_COULD_NOT_CREATE_SOCKET = KJob::UserDefinedError + 22, ///< @deprecated ERR_CANNOT_CREATE_SOCKET = KJob::UserDefinedError + 22, ERR_COULD_NOT_CONNECT = KJob::UserDefinedError + 23, ///< @deprecated ERR_CANNOT_CONNECT = KJob::UserDefinedError + 23, ERR_CONNECTION_BROKEN = KJob::UserDefinedError + 24, ERR_NOT_FILTER_PROTOCOL = KJob::UserDefinedError + 25, ERR_COULD_NOT_MOUNT = KJob::UserDefinedError + 26, ///< @deprecated ERR_CANNOT_MOUNT = KJob::UserDefinedError + 26, ERR_COULD_NOT_UNMOUNT = KJob::UserDefinedError + 27, ///< @deprecated ERR_CANNOT_UNMOUNT = KJob::UserDefinedError + 27, ERR_COULD_NOT_READ = KJob::UserDefinedError + 28, ///< @deprecated ERR_CANNOT_READ = KJob::UserDefinedError + 28, ERR_COULD_NOT_WRITE = KJob::UserDefinedError + 29, ///< @deprecated ERR_CANNOT_WRITE = KJob::UserDefinedError + 29, ERR_COULD_NOT_BIND = KJob::UserDefinedError + 30, ///< @deprecated ERR_CANNOT_BIND = KJob::UserDefinedError + 30, ERR_COULD_NOT_LISTEN = KJob::UserDefinedError + 31, ///< @deprecated ERR_CANNOT_LISTEN = KJob::UserDefinedError + 31, ERR_COULD_NOT_ACCEPT = KJob::UserDefinedError + 32, ///< @deprecated ERR_CANNOT_ACCEPT = KJob::UserDefinedError + 32, ERR_COULD_NOT_LOGIN = KJob::UserDefinedError + 33, ///< @deprecated ERR_CANNOT_LOGIN = KJob::UserDefinedError + 33, ERR_COULD_NOT_STAT = KJob::UserDefinedError + 34, ///< @deprecated ERR_CANNOT_STAT = KJob::UserDefinedError + 34, ERR_COULD_NOT_CLOSEDIR = KJob::UserDefinedError + 35, ///< @deprecated ERR_CANNOT_CLOSEDIR = KJob::UserDefinedError + 35, ERR_COULD_NOT_MKDIR = KJob::UserDefinedError + 37, ///< @deprecated ERR_CANNOT_MKDIR = KJob::UserDefinedError + 37, ERR_COULD_NOT_RMDIR = KJob::UserDefinedError + 38, ///< @deprecated ERR_CANNOT_RMDIR = KJob::UserDefinedError + 38, ERR_CANNOT_RESUME = KJob::UserDefinedError + 39, ERR_CANNOT_RENAME = KJob::UserDefinedError + 40, ERR_CANNOT_CHMOD = KJob::UserDefinedError + 41, ERR_CANNOT_DELETE = KJob::UserDefinedError + 42, // The text argument is the protocol that the dead slave supported. // This means for example: file, ftp, http, ... ERR_SLAVE_DIED = KJob::UserDefinedError + 43, ERR_OUT_OF_MEMORY = KJob::UserDefinedError + 44, ERR_UNKNOWN_PROXY_HOST = KJob::UserDefinedError + 45, ERR_COULD_NOT_AUTHENTICATE = KJob::UserDefinedError + 46, ///< @deprecated ERR_CANNOT_AUTHENTICATE = KJob::UserDefinedError + 46, ERR_ABORTED = KJob::UserDefinedError + 47, ///< Action got aborted from application side ERR_INTERNAL_SERVER = KJob::UserDefinedError + 48, ERR_SERVER_TIMEOUT = KJob::UserDefinedError + 49, ERR_SERVICE_NOT_AVAILABLE = KJob::UserDefinedError + 50, ERR_UNKNOWN = KJob::UserDefinedError + 51, // (was a warning) ERR_CHECKSUM_MISMATCH = 52, ERR_UNKNOWN_INTERRUPT = KJob::UserDefinedError + 53, ERR_CANNOT_DELETE_ORIGINAL = KJob::UserDefinedError + 54, ERR_CANNOT_DELETE_PARTIAL = KJob::UserDefinedError + 55, ERR_CANNOT_RENAME_ORIGINAL = KJob::UserDefinedError + 56, ERR_CANNOT_RENAME_PARTIAL = KJob::UserDefinedError + 57, ERR_NEED_PASSWD = KJob::UserDefinedError + 58, ERR_CANNOT_SYMLINK = KJob::UserDefinedError + 59, ERR_NO_CONTENT = KJob::UserDefinedError + 60, ///< Action succeeded but no content will follow. ERR_DISK_FULL = KJob::UserDefinedError + 61, ERR_IDENTICAL_FILES = KJob::UserDefinedError + 62, ///< src==dest when moving/copying ERR_SLAVE_DEFINED = KJob::UserDefinedError + 63, ///< for slave specified errors that can be ///< rich text. Email links will be handled ///< by the standard email app and all hrefs ///< will be handled by the standard browser. ///< This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "hostinfo.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include -#include -#include +#include +#include +#include #include -#include +#include #ifdef Q_OS_UNIX -# include +# include # include # include # include // for _PATH_RESCONF # ifndef _PATH_RESCONF # define _PATH_RESCONF "/etc/resolv.conf" # endif #endif #define TTL 300 namespace KIO { class HostInfoAgentPrivate : public QObject { Q_OBJECT public: HostInfoAgentPrivate(int cacheSize = 100); virtual ~HostInfoAgentPrivate() {} void lookupHost(const QString &hostName, QObject *receiver, const char *member); QHostInfo lookupCachedHostInfoFor(const QString &hostName); void cacheLookup(const QHostInfo &); void setCacheSize(int s) { dnsCache.setMaxCost(s); } void setTTL(int _ttl) { ttl = _ttl; } private Q_SLOTS: void queryFinished(const QHostInfo &); private: class Result; class Query; QHash openQueries; QCache > dnsCache; QDateTime resolvConfMTime; int ttl; }; class HostInfoAgentPrivate::Result : public QObject { Q_OBJECT Q_SIGNALS: void result(QHostInfo); private: friend class HostInfoAgentPrivate; }; class HostInfoAgentPrivate::Query : public QObject { Q_OBJECT public: Query(): m_watcher(), m_hostName() { connect(&m_watcher, SIGNAL(finished()), this, SLOT(relayFinished())); } void start(const QString &hostName) { m_hostName = hostName; QFuture future = QtConcurrent::run(&QHostInfo::fromName, hostName); m_watcher.setFuture(future); } QString hostName() const { return m_hostName; } Q_SIGNALS: void result(QHostInfo); private Q_SLOTS: void relayFinished() { emit result(m_watcher.result()); } private: QFutureWatcher m_watcher; QString m_hostName; }; class NameLookupThreadRequest { public: NameLookupThreadRequest(const QString &hostName) : m_hostName(hostName) { } QSemaphore *semaphore() { return &m_semaphore; } QHostInfo result() const { return m_hostInfo; } void setResult(const QHostInfo &hostInfo) { m_hostInfo = hostInfo; } QString hostName() const { return m_hostName; } int lookupId() const { return m_lookupId; } void setLookupId(int id) { m_lookupId = id; } private: Q_DISABLE_COPY(NameLookupThreadRequest) QString m_hostName; QSemaphore m_semaphore; QHostInfo m_hostInfo; int m_lookupId; }; } Q_DECLARE_METATYPE(QSharedPointer) namespace KIO { class NameLookUpThreadWorker : public QObject { Q_OBJECT public Q_SLOTS: void lookupHost(const QSharedPointer &request) { const QString hostName = request->hostName(); const int lookupId = QHostInfo::lookupHost(hostName, this, SLOT(lookupFinished(QHostInfo))); request->setLookupId(lookupId); m_lookups.insert(lookupId, request); } void abortLookup(const QSharedPointer &request) { QHostInfo::abortHostLookup(request->lookupId()); m_lookups.remove(request->lookupId()); } void lookupFinished(const QHostInfo &hostInfo) { QMap >::iterator it = m_lookups.find(hostInfo.lookupId()); if (it != m_lookups.end()) { (*it)->setResult(hostInfo); (*it)->semaphore()->release(); m_lookups.erase(it); } } private: QMap > m_lookups; }; class NameLookUpThread : public QThread { Q_OBJECT public: NameLookUpThread() : m_worker(nullptr) { qRegisterMetaType< QSharedPointer > (); start(); } ~NameLookUpThread() { quit(); wait(); } NameLookUpThreadWorker *worker() { return m_worker; } QSemaphore *semaphore() { return &m_semaphore; } void run() Q_DECL_OVERRIDE { NameLookUpThreadWorker worker; m_worker = &worker; m_semaphore.release(); exec(); } private: NameLookUpThreadWorker *m_worker; QSemaphore m_semaphore; }; } using namespace KIO; Q_GLOBAL_STATIC(HostInfoAgentPrivate, hostInfoAgentPrivate) Q_GLOBAL_STATIC(NameLookUpThread, nameLookUpThread) void HostInfo::lookupHost(const QString &hostName, QObject *receiver, const char *member) { hostInfoAgentPrivate()->lookupHost(hostName, receiver, member); } QHostInfo HostInfo::lookupHost(const QString &hostName, unsigned long timeout) { // Do not perform a reverse lookup here... QHostAddress address(hostName); QHostInfo hostInfo; if (!address.isNull()) { QList addressList; addressList << address; hostInfo.setAddresses(addressList); return hostInfo; } // Look up the name in the KIO/KHTML DNS cache... hostInfo = HostInfo::lookupCachedHostInfoFor(hostName); if (!hostInfo.hostName().isEmpty() && hostInfo.error() == QHostInfo::NoError) { return hostInfo; } // Failing all of the above, do the lookup... QSharedPointer request = QSharedPointer(new NameLookupThreadRequest(hostName)); nameLookUpThread()->semaphore()->acquire(); nameLookUpThread()->semaphore()->release(); QMetaObject::invokeMethod(nameLookUpThread()->worker(), "lookupHost", Qt::QueuedConnection, Q_ARG(QSharedPointer, request)); if (request->semaphore()->tryAcquire(1, timeout)) { hostInfo = request->result(); if (!hostInfo.hostName().isEmpty() && hostInfo.error() == QHostInfo::NoError) { HostInfo::cacheLookup(hostInfo); // cache the look up... } } else { QMetaObject::invokeMethod(nameLookUpThread()->worker(), "abortLookup", Qt::QueuedConnection, Q_ARG(QSharedPointer, request)); } //qDebug() << "Name look up succeeded for" << hostName; return hostInfo; } QHostInfo HostInfo::lookupCachedHostInfoFor(const QString &hostName) { return hostInfoAgentPrivate()->lookupCachedHostInfoFor(hostName); } void HostInfo::cacheLookup(const QHostInfo &info) { hostInfoAgentPrivate()->cacheLookup(info); } void HostInfo::prefetchHost(const QString &hostName) { hostInfoAgentPrivate()->lookupHost(hostName, nullptr, nullptr); } void HostInfo::setCacheSize(int s) { hostInfoAgentPrivate()->setCacheSize(s); } void HostInfo::setTTL(int ttl) { hostInfoAgentPrivate()->setTTL(ttl); } HostInfoAgentPrivate::HostInfoAgentPrivate(int cacheSize) : openQueries(), dnsCache(cacheSize), ttl(TTL) { qRegisterMetaType(); } void HostInfoAgentPrivate::lookupHost(const QString &hostName, QObject *receiver, const char *member) { #ifdef _PATH_RESCONF QFileInfo resolvConf(QFile::decodeName(_PATH_RESCONF)); QDateTime currentMTime = resolvConf.lastModified(); if (resolvConf.exists() && currentMTime != resolvConfMTime) { // /etc/resolv.conf has been modified // clear our cache resolvConfMTime = currentMTime; dnsCache.clear(); } #endif if (QPair *info = dnsCache.object(hostName)) { if (QTime::currentTime() <= info->second.addSecs(ttl)) { Result result; if (receiver) { QObject::connect(&result, SIGNAL(result(QHostInfo)), receiver, member); emit result.result(info->first); } return; } dnsCache.remove(hostName); } if (Query *query = openQueries.value(hostName)) { if (receiver) { connect(query, SIGNAL(result(QHostInfo)), receiver, member); } return; } Query *query = new Query(); openQueries.insert(hostName, query); connect(query, SIGNAL(result(QHostInfo)), this, SLOT(queryFinished(QHostInfo))); if (receiver) { connect(query, SIGNAL(result(QHostInfo)), receiver, member); } query->start(hostName); } QHostInfo HostInfoAgentPrivate::lookupCachedHostInfoFor(const QString &hostName) { QPair *info = dnsCache.object(hostName); if (info && info->second.addSecs(ttl) >= QTime::currentTime()) { return info->first; } return QHostInfo(); } void HostInfoAgentPrivate::cacheLookup(const QHostInfo &info) { if (info.hostName().isEmpty()) { return; } if (info.error() != QHostInfo::NoError) { return; } dnsCache.insert(info.hostName(), new QPair(info, QTime::currentTime())); } void HostInfoAgentPrivate::queryFinished(const QHostInfo &info) { Query *query = static_cast(sender()); openQueries.remove(query->hostName()); if (info.error() == QHostInfo::NoError) { dnsCache.insert(query->hostName(), new QPair(info, QTime::currentTime())); } query->deleteLater(); } #include "hostinfo.moc" diff --git a/src/core/hostinfo.h b/src/core/hostinfo.h index 9b44683f..703a5709 100644 --- a/src/core/hostinfo.h +++ b/src/core/hostinfo.h @@ -1,58 +1,58 @@ /* Copyright 2008 Roland Harnau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef HOSTINFO_H_ #define HOSTINFO_H_ -#include -#include +#include +#include #include "kiocore_export.h" class QHostInfo; namespace KIO { /** * @internal * WARNING: this could disappear at some point in time. * DO NOT USE outside KDE Frameworks */ namespace HostInfo { /// @internal KIOCORE_EXPORT void lookupHost(const QString &hostName, QObject *receiver, const char *member); /// @internal KIOCORE_EXPORT QHostInfo lookupHost(const QString &hostName, unsigned long timeout); /// @internal KIOCORE_EXPORT QHostInfo lookupCachedHostInfoFor(const QString &hostName); /// @internal KIOCORE_EXPORT void cacheLookup(const QHostInfo &info); // used by khtml's DNS prefetching feature /// @internal KIOCORE_EXPORT void prefetchHost(const QString &hostName); /// @internal KIOCORE_EXPORT void setCacheSize(int s); /// @internal KIOCORE_EXPORT void setTTL(int ttl); } } #endif diff --git a/src/core/job.cpp b/src/core/job.cpp index 824b3ebd..c5370009 100644 --- a/src/core/job.cpp +++ b/src/core/job.cpp @@ -1,418 +1,418 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 2000-2009 David Faure Waldo Bastian 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 "job.h" #include "job_p.h" #include -#include +#include #include #include #include #include #include "slave.h" #include "scheduler.h" #include "slavebase.h" using namespace KIO; Job::Job() : KCompositeJob(nullptr) , d_ptr(new JobPrivate) { d_ptr->q_ptr = this; setCapabilities(KJob::Killable | KJob::Suspendable); } Job::Job(JobPrivate &dd) : KCompositeJob(nullptr) , d_ptr(&dd) { d_ptr->q_ptr = this; setCapabilities(KJob::Killable | KJob::Suspendable); } Job::~Job() { delete d_ptr; } // Exists for historical reasons only KJobUiDelegate *Job::ui() const { return uiDelegate(); } JobUiDelegateExtension *Job::uiDelegateExtension() const { Q_D(const Job); return d->m_uiDelegateExtension; } void Job::setUiDelegateExtension(JobUiDelegateExtension *extension) { Q_D(Job); d->m_uiDelegateExtension = extension; } bool Job::addSubjob(KJob *jobBase) { //qDebug() << "addSubjob(" << jobBase << ") this=" << this; bool ok = KCompositeJob::addSubjob(jobBase); KIO::Job *job = dynamic_cast(jobBase); if (ok && job) { // Copy metadata into subjob (e.g. window-id, user-timestamp etc.) Q_D(Job); job->mergeMetaData(d->m_outgoingMetaData); // Forward information from that subjob. connect(job, SIGNAL(speed(KJob*,ulong)), SLOT(slotSpeed(KJob*,ulong))); job->setProperty("window", property("window")); // see KJobWidgets job->setProperty("userTimestamp", property("userTimestamp")); // see KJobWidgets job->setUiDelegateExtension(d->m_uiDelegateExtension); } return ok; } bool Job::removeSubjob(KJob *jobBase) { //qDebug() << "removeSubjob(" << jobBase << ") this=" << this << "subjobs=" << subjobs().count(); return KCompositeJob::removeSubjob(jobBase); } static QString url_description_string(const QUrl& url) { return url.scheme() == "data" ? QStringLiteral("data:[...]") : KStringHandler::csqueeze(url.toDisplayString(QUrl::PreferLocalFile), 100); } KIO::JobPrivate::~JobPrivate() { } void JobPrivate::emitMoving(KIO::Job *job, const QUrl &src, const QUrl &dest) { emit job->description(job, i18nc("@title job", "Moving"), qMakePair(i18nc("The source of a file operation", "Source"), url_description_string(src)), qMakePair(i18nc("The destination of a file operation", "Destination"), url_description_string(dest))); } void JobPrivate::emitCopying(KIO::Job *job, const QUrl &src, const QUrl &dest) { emit job->description(job, i18nc("@title job", "Copying"), qMakePair(i18nc("The source of a file operation", "Source"), url_description_string(src)), qMakePair(i18nc("The destination of a file operation", "Destination"), url_description_string(dest))); } void JobPrivate::emitCreatingDir(KIO::Job *job, const QUrl &dir) { emit job->description(job, i18nc("@title job", "Creating directory"), qMakePair(i18n("Directory"), url_description_string(dir))); } void JobPrivate::emitDeleting(KIO::Job *job, const QUrl &url) { emit job->description(job, i18nc("@title job", "Deleting"), qMakePair(i18n("File"), url_description_string(url))); } void JobPrivate::emitStating(KIO::Job *job, const QUrl &url) { emit job->description(job, i18nc("@title job", "Examining"), qMakePair(i18n("File"), url_description_string(url))); } void JobPrivate::emitTransferring(KIO::Job *job, const QUrl &url) { emit job->description(job, i18nc("@title job", "Transferring"), qMakePair(i18nc("The source of a file operation", "Source"), url_description_string(url))); } void JobPrivate::emitMounting(KIO::Job *job, const QString &dev, const QString &point) { emit job->description(job, i18nc("@title job", "Mounting"), qMakePair(i18n("Device"), dev), qMakePair(i18n("Mountpoint"), point)); } void JobPrivate::emitUnmounting(KIO::Job *job, const QString &point) { emit job->description(job, i18nc("@title job", "Unmounting"), qMakePair(i18n("Mountpoint"), point)); } bool Job::doKill() { // kill all subjobs, without triggering their result slot Q_FOREACH (KJob *it, subjobs()) { it->kill(KJob::Quietly); } clearSubjobs(); return true; } bool Job::doSuspend() { Q_FOREACH (KJob *it, subjobs()) { if (!it->suspend()) { return false; } } return true; } bool Job::doResume() { Q_FOREACH (KJob *it, subjobs()) { if (!it->resume()) { return false; } } return true; } void JobPrivate::slotSpeed(KJob *, unsigned long speed) { //qDebug() << speed; q_func()->emitSpeed(speed); } //Job::errorString is implemented in job_error.cpp void Job::setParentJob(Job *job) { Q_D(Job); Q_ASSERT(d->m_parentJob == nullptr); Q_ASSERT(job); d->m_parentJob = job; } Job *Job::parentJob() const { return d_func()->m_parentJob; } MetaData Job::metaData() const { return d_func()->m_incomingMetaData; } QString Job::queryMetaData(const QString &key) { return d_func()->m_incomingMetaData.value(key, QString()); } void Job::setMetaData(const KIO::MetaData &_metaData) { Q_D(Job); d->m_outgoingMetaData = _metaData; } void Job::addMetaData(const QString &key, const QString &value) { d_func()->m_outgoingMetaData.insert(key, value); } void Job::addMetaData(const QMap &values) { Q_D(Job); QMap::const_iterator it = values.begin(); for (; it != values.end(); ++it) { d->m_outgoingMetaData.insert(it.key(), it.value()); } } void Job::mergeMetaData(const QMap &values) { Q_D(Job); QMap::const_iterator it = values.begin(); for (; it != values.end(); ++it) // there's probably a faster way if (!d->m_outgoingMetaData.contains(it.key())) { d->m_outgoingMetaData.insert(it.key(), it.value()); } } MetaData Job::outgoingMetaData() const { return d_func()->m_outgoingMetaData; } PrivilegeOperationStatus JobPrivate::tryAskPrivilegeOpConfirmation() { if (m_confirmationAsked) { return OperationAllowed; } if (m_parentJob) { if (!m_parentJob->d_func()->m_privilegeExecutionEnabled) { return OperationNotAllowed; } if (!m_parentJob->d_func()->m_confirmationAsked) { PrivilegeOperationStatus opStatus = m_parentJob->d_func()->tryAskPrivilegeOpConfirmation(); if (opStatus == OperationAllowed) { // Copy meta-data from parent job m_incomingMetaData.insert(QStringLiteral("TestData"), m_parentJob->queryMetaData(QStringLiteral("TestData"))); m_confirmationAsked = true; } return opStatus; } else { return OperationAllowed; } } else { // In case of SimpleJob like chmod, chown, etc. which don't accept JobFlags if (!m_privilegeExecutionEnabled) { return OperationNotAllowed; } } switch (m_operationType) { case ChangeAttr: m_caption = i18n("Change Attribute"); m_message = i18n("Root privileges are required to change file attributes. " "Do you want to continue?"); break; case Copy: m_caption = i18n("Copy Files"); m_message = i18n("Root privileges are required to complete the copy operation. " "Do you want to continue?"); break; case Delete: m_caption = i18n("Delete Files"); m_message = i18n("Root privileges are required to complete the delete operation. " "However, doing so may damage your system. Do you want to continue?"); break; case MkDir: m_caption = i18n("Create Folder"); m_message = i18n("Root privileges are required to create this folder. " "Do you want to continue?"); break; case Move: m_caption = i18n("Move Items"); m_message = i18n("Root privileges are required to complete the move operation. " "Do you want to continue?"); break; case Rename: m_caption = i18n("Rename"); m_message = i18n("Root privileges are required to complete renaming. " "Do you want to continue?"); break; case Symlink: m_caption = i18n("Create Symlink"); m_message = i18n("Root privileges are required to create a symlink. " "Do you want to continue?"); break; case Transfer: m_caption = i18n("Transfer data"); m_message = i18n("Root privileges are required to complete transferring data. " "Do you want to continue?"); default: break; } if (m_outgoingMetaData.value(QStringLiteral("UnitTesting")) == QLatin1String("true")) { // Set meta-data for the top-level job m_incomingMetaData.insert(QStringLiteral("TestData"), QStringLiteral("PrivilegeOperationAllowed")); return OperationAllowed; } if (!m_uiDelegateExtension) { return OperationNotAllowed; } int status = m_uiDelegateExtension->requestMessageBox(JobUiDelegateExtension::WarningContinueCancel, m_message, m_caption, i18n("Continue"), i18n("Cancel")); m_confirmationAsked = true; if (status == SlaveBase::Cancel) { return OperationCanceled; } return OperationAllowed; } ////////////////////////// class KIO::DirectCopyJobPrivate: public KIO::SimpleJobPrivate { public: DirectCopyJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs) : SimpleJobPrivate(url, command, packedArgs) {} /** * @internal * Called by the scheduler when a @p slave gets to * work on this job. * @param slave the slave that starts working on this job */ void start(Slave *slave) Q_DECL_OVERRIDE; Q_DECLARE_PUBLIC(DirectCopyJob) }; DirectCopyJob::DirectCopyJob(const QUrl &url, const QByteArray &packedArgs) : SimpleJob(*new DirectCopyJobPrivate(url, CMD_COPY, packedArgs)) { setUiDelegate(KIO::createDefaultJobUiDelegate()); } DirectCopyJob::~DirectCopyJob() { } void DirectCopyJobPrivate::start(Slave *slave) { Q_Q(DirectCopyJob); q->connect(slave, SIGNAL(canResume(KIO::filesize_t)), SLOT(slotCanResume(KIO::filesize_t))); SimpleJobPrivate::start(slave); } void DirectCopyJob::slotCanResume(KIO::filesize_t offset) { emit canResume(this, offset); } ////////////////////////// SimpleJob *KIO::file_delete(const QUrl &src, JobFlags flags) { KIO_ARGS << src << qint8(true); // isFile SimpleJob *job = SimpleJobPrivate::newJob(src, CMD_DEL, packedArgs, flags); if (job->uiDelegateExtension()) { job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::RemoveContent); } return job; } ////////// //// #include "moc_job_base.cpp" #include "moc_job_p.cpp" diff --git a/src/core/kacl.h b/src/core/kacl.h index f78b3ad2..f7c79649 100644 --- a/src/core/kacl.h +++ b/src/core/kacl.h @@ -1,205 +1,205 @@ /* This file is part of the KDE project Copyright (C) 2005 - 2007 Till Adam 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. */ #ifndef KACL_H #define KACL_H #include #include "kiocore_export.h" -#include -#include +#include +#include typedef QPair ACLUserPermissions; typedef QList ACLUserPermissionsList; typedef QList::iterator ACLUserPermissionsIterator; typedef QList::const_iterator ACLUserPermissionsConstIterator; typedef QPair ACLGroupPermissions; typedef QList ACLGroupPermissionsList; typedef QList::iterator ACLGroupPermissionsIterator; typedef QList::const_iterator ACLGroupPermissionsConstIterator; /** * @class KACL kacl.h * * The KACL class encapsulates a POSIX Access Control List. It follows the * little standard that couldn't, 1003.1e/1003.2c, which died in draft status. * @short a POSIX ACL encapsulation * @author Till Adam */ class KIOCORE_EXPORT KACL { public: /** * Creates a new KACL from @p aclString. If the string is a valid acl * string, isValid() will afterwards return true. */ KACL(const QString &aclString); /** Copy ctor */ KACL(const KACL &rhs); /** * Creates a new KACL from the basic permissions passed in @p basicPermissions. * isValid() will return true, afterwards. */ KACL(mode_t basicPermissions); /** * Creates an empty KACL. Until a valid acl string is set via setACL, * isValid() will return false. */ KACL(); virtual ~KACL(); KACL &operator=(const KACL &rhs); bool operator==(const KACL &rhs) const; bool operator!=(const KACL &rhs) const; /** * Returns whether the KACL object represents a valid acl. * @return whether the KACL object represents a valid acl. */ bool isValid() const; /** The standard (non-extended) part of an ACL. These map directly to * standard unix file permissions. Setting them will never make a valid * ACL invalid. */ /** @return the owner's permissions entry */ unsigned short ownerPermissions() const; /** Set the owner's permissions entry. * @return success or failure */ bool setOwnerPermissions(unsigned short); /** @return the owning group's permissions entry */ unsigned short owningGroupPermissions() const; /** Set the owning group's permissions entry. * @return success or failure */ bool setOwningGroupPermissions(unsigned short); /** @return the permissions entry for others */ unsigned short othersPermissions() const; /** Set the permissions entry for others. * @return success or failure */ bool setOthersPermissions(unsigned short); /** @return the basic (owner/group/others) part of the ACL as a mode_t */ mode_t basePermissions() const; /** The interface to the extended ACL. This is a mask, permissions for * n named users and permissions for m named groups. */ /** * Return whether the ACL contains extended entries or can be expressed * using only basic file permissions. * @return whether the ACL contains extended entries */ bool isExtended() const; /** * Return the entry for the permissions mask if there is one and sets * @p exists to true. If there is no such entry, @p exists is set to false. * @return the permissions mask entry */ unsigned short maskPermissions(bool &exists) const; /** Set the permissions mask for the ACL. Permissions set for individual * entries will be masked with this, such that their effective permissions * are the result of the logical and of their entry and the mask. * @return success or failure */ bool setMaskPermissions(unsigned short); /** * Access to the permissions entry for a named user, if such an entry * exists. If @p exists is non-null, the boolean variable it points to * is set to true if a matching entry exists and to false otherwise. * @return the permissions for a user entry with the name in @p name */ unsigned short namedUserPermissions(const QString &name, bool *exists) const; /** Set the permissions for a user with the name @p name. Will fail * if the user doesn't exist, in which case the ACL will be unchanged. * @return success or failure. */ bool setNamedUserPermissions(const QString &name, unsigned short); /** Returns the list of all group permission entries. Each entry consists * of a name/permissions pair. This is a QPair, therefore access is provided * via the .first and .next members. * @return the list of all group permission entries. */ ACLUserPermissionsList allUserPermissions() const; /** Replace the list of all user permissions with @p list. If one * of the entries in the list does not exists, or setting of the ACL * entry fails for any reason, the ACL will be left unchanged. * @return success or failure */ bool setAllUserPermissions(const ACLUserPermissionsList &list); /** * Access to the permissions entry for a named group, if such an entry * exists. If @p exists is non-null, the boolean variable it points to is * set to true if a matching entry exists and to false otherwise. * @return the permissions for a group with the name in @p name */ unsigned short namedGroupPermissions(const QString &name, bool *exists) const; /** Set the permissions for a group with the name @p name. Will fail * if the group doesn't exist, in which case the ACL be unchanged. * @return success or failure. */ bool setNamedGroupPermissions(const QString &name, unsigned short); /** Returns the list of all group permission entries. Each entry consists * of a name/permissions pair. This is a QPair, therefor access is provided * via the .first and .next members. * @return the list of all group permission entries. */ ACLGroupPermissionsList allGroupPermissions() const; /** Replace the list of all user permissions with @p list. If one * of the entries in the list does not exists, or setting of the ACL * entry fails for any reason, the ACL will be left unchanged. * @return success or failure */ bool setAllGroupPermissions(const ACLGroupPermissionsList &); /** Sets the whole list from a string. If the string in @p aclStr represents * a valid ACL, it will be set, otherwise the ACL remains unchanged. * @return whether setting the ACL was successful. */ bool setACL(const QString &aclStr); /** Return a string representation of the ACL. * @return a string version of the ACL in the format compatible with libacl and * POSIX 1003.1e. Implementations conforming to that standard should be able * to take such strings as input. */ QString asString() const; protected: virtual void virtual_hook(int id, void *data); private: class KACLPrivate; KACLPrivate *const d; KIOCORE_EXPORT friend QDataStream &operator<< (QDataStream &s, const KACL &a); KIOCORE_EXPORT friend QDataStream &operator>> (QDataStream &s, KACL &a); }; KIOCORE_EXPORT QDataStream &operator<< (QDataStream &s, const KACL &a); KIOCORE_EXPORT QDataStream &operator>> (QDataStream &s, KACL &a); #endif diff --git a/src/core/kcoredirlister.cpp b/src/core/kcoredirlister.cpp index 139a79e5..73ba7f6e 100644 --- a/src/core/kcoredirlister.cpp +++ b/src/core/kcoredirlister.cpp @@ -1,2880 +1,2880 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis 2000 Carsten Pfeiffer 2003-2005 David Faure 2001-2006 Michael Brade 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 "kcoredirlister.h" #include "kcoredirlister_p.h" #include #include #include "kprotocolmanager.h" #include "kmountpoint.h" #include "kiocoredebug.h" #include "../pathhelpers_p.h" -#include +#include #include #include #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(KIO_CORE_DIRLISTER) Q_LOGGING_CATEGORY(KIO_CORE_DIRLISTER, "kf5.kio.core.dirlister", QtWarningMsg) // Enable this to get printDebug() called often, to see the contents of the cache //#define DEBUG_CACHE // Make really sure it doesn't get activated in the final build #ifdef NDEBUG #undef DEBUG_CACHE #endif Q_GLOBAL_STATIC(KCoreDirListerCache, kDirListerCache) KCoreDirListerCache::KCoreDirListerCache() : itemsCached(10), // keep the last 10 directories around m_cacheHiddenFiles(10) // keep the last 10 ".hidden" files around { qCDebug(KIO_CORE_DIRLISTER); connect(&pendingUpdateTimer, SIGNAL(timeout()), this, SLOT(processPendingUpdates())); pendingUpdateTimer.setSingleShot(true); connect(KDirWatch::self(), SIGNAL(dirty(QString)), this, SLOT(slotFileDirty(QString))); connect(KDirWatch::self(), SIGNAL(created(QString)), this, SLOT(slotFileCreated(QString))); connect(KDirWatch::self(), SIGNAL(deleted(QString)), this, SLOT(slotFileDeleted(QString))); kdirnotify = new org::kde::KDirNotify(QString(), QString(), QDBusConnection::sessionBus(), this); connect(kdirnotify, &org::kde::KDirNotify::FileRenamedWithLocalPath, this, &KCoreDirListerCache::slotFileRenamed); connect(kdirnotify, SIGNAL(FilesAdded(QString)), SLOT(slotFilesAdded(QString))); connect(kdirnotify, SIGNAL(FilesChanged(QStringList)), SLOT(slotFilesChanged(QStringList))); connect(kdirnotify, SIGNAL(FilesRemoved(QStringList)), SLOT(slotFilesRemoved(QStringList))); // Probably not needed in KF5 anymore: // The use of KUrl::url() in ~DirItem (sendSignal) crashes if the static for QRegExpEngine got deleted already, // so we need to destroy the KCoreDirListerCache before that. //qAddPostRoutine(kDirListerCache.destroy); } KCoreDirListerCache::~KCoreDirListerCache() { qCDebug(KIO_CORE_DIRLISTER); qDeleteAll(itemsInUse); itemsInUse.clear(); itemsCached.clear(); directoryData.clear(); m_cacheHiddenFiles.clear(); if (KDirWatch::exists()) { KDirWatch::self()->disconnect(this); } } // setting _reload to true will emit the old files and // call updateDirectory bool KCoreDirListerCache::listDir(KCoreDirLister *lister, const QUrl &_u, bool _keep, bool _reload) { QUrl _url(_u); _url.setPath(QDir::cleanPath(_url.path())); // kill consecutive slashes if (!_url.host().isEmpty() && KProtocolInfo::protocolClass(_url.scheme()) == QLatin1String(":local") && _url.scheme() != QLatin1String("file")) { // ":local" protocols ignore the hostname, so strip it out preventively - #160057 // kio_file is special cased since it does honor the hostname (by redirecting to e.g. smb) _url.setHost(QString()); if (_keep == false) { emit lister->redirection(_url); } } // like this we don't have to worry about trailing slashes any further _url = _url.adjusted(QUrl::StripTrailingSlash); const QString urlStr = _url.toString(); QString resolved; if (_url.isLocalFile()) { // Resolve symlinks (#213799) const QString local = _url.toLocalFile(); resolved = QFileInfo(local).canonicalFilePath(); if (local != resolved) { canonicalUrls[QUrl::fromLocalFile(resolved)].append(_url); } // TODO: remove entry from canonicalUrls again in forgetDirs // Note: this is why we use a QStringList value in there rather than a QSet: // we can just remove one entry and not have to worry about other dirlisters // (the non-unicity of the stringlist gives us the refcounting, basically). } if (!validUrl(lister, _url)) { qCDebug(KIO_CORE_DIRLISTER) << lister << "url=" << _url << "not a valid url"; return false; } qCDebug(KIO_CORE_DIRLISTER) << lister << "url=" << _url << "keep=" << _keep << "reload=" << _reload; #ifdef DEBUG_CACHE printDebug(); #endif if (!_keep) { // stop any running jobs for lister stop(lister, true /*silent*/); // clear our internal list for lister forgetDirs(lister); lister->d->rootFileItem = KFileItem(); } else if (lister->d->lstDirs.contains(_url)) { // stop the job listing _url for this lister stopListingUrl(lister, _url, true /*silent*/); // remove the _url as well, it will be added in a couple of lines again! // forgetDirs with three args does not do this // TODO: think about moving this into forgetDirs lister->d->lstDirs.removeAll(_url); // clear _url for lister forgetDirs(lister, _url, true); if (lister->d->url == _url) { lister->d->rootFileItem = KFileItem(); } } lister->d->complete = false; lister->d->lstDirs.append(_url); if (lister->d->url.isEmpty() || !_keep) { // set toplevel URL only if not set yet lister->d->url = _url; } DirItem *itemU = itemsInUse.value(urlStr); KCoreDirListerCacheDirectoryData &dirData = directoryData[urlStr]; // find or insert if (dirData.listersCurrentlyListing.isEmpty()) { // if there is an update running for _url already we get into // the following case - it will just be restarted by updateDirectory(). dirData.listersCurrentlyListing.append(lister); DirItem *itemFromCache = nullptr; if (itemU || (!_reload && (itemFromCache = itemsCached.take(urlStr)))) { if (itemU) { qCDebug(KIO_CORE_DIRLISTER) << "Entry already in use:" << _url; // if _reload is set, then we'll emit cached items and then updateDirectory. } else { qCDebug(KIO_CORE_DIRLISTER) << "Entry in cache:" << _url; itemsInUse.insert(urlStr, itemFromCache); itemU = itemFromCache; } if (lister->d->autoUpdate) { itemU->incAutoUpdate(); } if (itemFromCache && itemFromCache->watchedWhileInCache) { itemFromCache->watchedWhileInCache = false;; itemFromCache->decAutoUpdate(); } emit lister->started(_url); // List items from the cache in a delayed manner, just like things would happen // if we were not using the cache. new KCoreDirLister::Private::CachedItemsJob(lister, _url, _reload); } else { // dir not in cache or _reload is true if (_reload) { qCDebug(KIO_CORE_DIRLISTER) << "Reloading directory:" << _url; itemsCached.remove(urlStr); } else { qCDebug(KIO_CORE_DIRLISTER) << "Listing directory:" << _url; } itemU = new DirItem(_url, resolved); itemsInUse.insert(urlStr, itemU); if (lister->d->autoUpdate) { itemU->incAutoUpdate(); } // // we have a limit of MAX_JOBS_PER_LISTER concurrently running jobs // if ( lister->d->numJobs() >= MAX_JOBS_PER_LISTER ) // { // pendingUpdates.insert( _url ); // } // else { KIO::ListJob *job = KIO::listDir(_url, KIO::HideProgressInfo); runningListJobs.insert(job, KIO::UDSEntryList()); lister->jobStarted(job); lister->d->connectJob(job); connect(job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), this, SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList))); connect(job, SIGNAL(result(KJob*)), this, SLOT(slotResult(KJob*))); connect(job, SIGNAL(redirection(KIO::Job*,QUrl)), this, SLOT(slotRedirection(KIO::Job*,QUrl))); emit lister->started(_url); } qCDebug(KIO_CORE_DIRLISTER) << "Entry now being listed by" << dirData.listersCurrentlyListing; } } else { qCDebug(KIO_CORE_DIRLISTER) << "Entry currently being listed:" << _url << "by" << dirData.listersCurrentlyListing; #ifdef DEBUG_CACHE printDebug(); #endif emit lister->started(_url); // Maybe listersCurrentlyListing/listersCurrentlyHolding should be QSets? Q_ASSERT(!dirData.listersCurrentlyListing.contains(lister)); dirData.listersCurrentlyListing.append(lister); KIO::ListJob *job = jobForUrl(urlStr); // job will be 0 if we were listing from cache rather than listing from a kio job. if (job) { lister->jobStarted(job); lister->d->connectJob(job); } Q_ASSERT(itemU); // List existing items in a delayed manner, just like things would happen // if we were not using the cache. qCDebug(KIO_CORE_DIRLISTER) << "Listing" << itemU->lstItems.count() << "cached items soon"; KCoreDirLister::Private::CachedItemsJob* cachedItemsJob = new KCoreDirLister::Private::CachedItemsJob(lister, _url, _reload); if (job) { // The ListJob will take care of emitting completed. // ### If it finishes before the CachedItemsJob, then we'll emit cached items after completed(), not sure how bad this is. cachedItemsJob->setEmitCompleted(false); } #ifdef DEBUG_CACHE printDebug(); #endif } return true; } KCoreDirLister::Private::CachedItemsJob *KCoreDirLister::Private::cachedItemsJobForUrl(const QUrl &url) const { Q_FOREACH (CachedItemsJob *job, m_cachedItemsJobs) { if (job->url() == url) { return job; } } return nullptr; } KCoreDirLister::Private::CachedItemsJob::CachedItemsJob(KCoreDirLister *lister, const QUrl &url, bool reload) : KJob(lister), m_lister(lister), m_url(url), m_reload(reload), m_emitCompleted(true) { qCDebug(KIO_CORE_DIRLISTER) << "Creating CachedItemsJob" << this << "for lister" << lister << url; if (lister->d->cachedItemsJobForUrl(url)) { qCWarning(KIO_CORE) << "Lister" << lister << "has a cached items job already for" << url; } lister->d->m_cachedItemsJobs.append(this); setAutoDelete(true); start(); } // Called by start() via QueuedConnection void KCoreDirLister::Private::CachedItemsJob::done() { if (!m_lister) { // job was already killed, but waiting deletion due to deleteLater return; } kDirListerCache()->emitItemsFromCache(this, m_lister, m_url, m_reload, m_emitCompleted); emitResult(); } bool KCoreDirLister::Private::CachedItemsJob::doKill() { qCDebug(KIO_CORE_DIRLISTER) << this; kDirListerCache()->forgetCachedItemsJob(this, m_lister, m_url); if (!property("_kdlc_silent").toBool()) { emit m_lister->canceled(m_url); emit m_lister->canceled(); } m_lister = nullptr; return true; } void KCoreDirListerCache::emitItemsFromCache(KCoreDirLister::Private::CachedItemsJob *cachedItemsJob, KCoreDirLister *lister, const QUrl &_url, bool _reload, bool _emitCompleted) { const QString urlStr = _url.toString(); KCoreDirLister::Private *kdl = lister->d; kdl->complete = false; DirItem *itemU = kDirListerCache()->itemsInUse.value(urlStr); if (!itemU) { qCWarning(KIO_CORE) << "Can't find item for directory" << urlStr << "anymore"; } else { const NonMovableFileItemList items = itemU->lstItems; const KFileItem rootItem = itemU->rootItem; _reload = _reload || !itemU->complete; if (kdl->rootFileItem.isNull() && !rootItem.isNull() && kdl->url == _url) { kdl->rootFileItem = rootItem; } if (!items.isEmpty()) { qCDebug(KIO_CORE_DIRLISTER) << "emitting" << items.count() << "for lister" << lister; kdl->addNewItems(_url, items); kdl->emitItems(); } } forgetCachedItemsJob(cachedItemsJob, lister, _url); // Emit completed, unless we were told not to, // or if listDir() was called while another directory listing for this dir was happening, // so we "joined" it. We detect that using jobForUrl to ensure it's a real ListJob, // not just a lister-specific CachedItemsJob (which wouldn't emit completed for us). if (_emitCompleted) { kdl->complete = true; emit lister->completed(_url); emit lister->completed(); if (_reload) { updateDirectory(_url); } } } void KCoreDirListerCache::forgetCachedItemsJob(KCoreDirLister::Private::CachedItemsJob *cachedItemsJob, KCoreDirLister *lister, const QUrl &_url) { // Modifications to data structures only below this point; // so that addNewItems is called with a consistent state const QString urlStr = _url.toString(); lister->d->m_cachedItemsJobs.removeAll(cachedItemsJob); KCoreDirListerCacheDirectoryData &dirData = directoryData[urlStr]; Q_ASSERT(dirData.listersCurrentlyListing.contains(lister)); KIO::ListJob *listJob = jobForUrl(urlStr); if (!listJob) { Q_ASSERT(!dirData.listersCurrentlyHolding.contains(lister)); qCDebug(KIO_CORE_DIRLISTER) << "Moving from listing to holding, because no more job" << lister << urlStr; dirData.listersCurrentlyHolding.append(lister); dirData.listersCurrentlyListing.removeAll(lister); } else { qCDebug(KIO_CORE_DIRLISTER) << "Still having a listjob" << listJob << ", so not moving to currently-holding."; } } bool KCoreDirListerCache::validUrl(KCoreDirLister *lister, const QUrl &url) const { if (!url.isValid()) { qCWarning(KIO_CORE) << url.errorString(); lister->handleErrorMessage(i18n("Malformed URL\n%1", url.errorString())); return false; } if (!KProtocolManager::supportsListing(url)) { lister->handleErrorMessage(i18n("URL cannot be listed\n%1", url.toString())); return false; } return true; } void KCoreDirListerCache::stop(KCoreDirLister *lister, bool silent) { #ifdef DEBUG_CACHE //printDebug(); #endif qCDebug(KIO_CORE_DIRLISTER) << "lister:" << lister << "silent=" << silent; const QList urls = lister->d->lstDirs; Q_FOREACH (const QUrl &url, urls) { stopListingUrl(lister, url, silent); } #if 0 // test code QHash::iterator dirit = directoryData.begin(); const QHash::iterator dirend = directoryData.end(); for (; dirit != dirend; ++dirit) { KCoreDirListerCacheDirectoryData &dirData = dirit.value(); if (dirData.listersCurrentlyListing.contains(lister)) { qCDebug(KIO_CORE_DIRLISTER) << "ERROR: found lister" << lister << "in list - for" << dirit.key(); Q_ASSERT(false); } } #endif } void KCoreDirListerCache::stopListingUrl(KCoreDirLister *lister, const QUrl &_u, bool silent) { QUrl url(_u); url = url.adjusted(QUrl::StripTrailingSlash); const QString urlStr = url.toString(); KCoreDirLister::Private::CachedItemsJob *cachedItemsJob = lister->d->cachedItemsJobForUrl(url); if (cachedItemsJob) { if (silent) { cachedItemsJob->setProperty("_kdlc_silent", true); } cachedItemsJob->kill(); // removes job from list, too } // TODO: consider to stop all the "child jobs" of url as well qCDebug(KIO_CORE_DIRLISTER) << lister << " url=" << url; QHash::iterator dirit = directoryData.find(urlStr); if (dirit == directoryData.end()) { return; } KCoreDirListerCacheDirectoryData &dirData = dirit.value(); if (dirData.listersCurrentlyListing.contains(lister)) { qCDebug(KIO_CORE_DIRLISTER) << " found lister" << lister << "in list - for" << urlStr; if (dirData.listersCurrentlyListing.count() == 1) { // This was the only dirlister interested in the list job -> kill the job stopListJob(urlStr, silent); } else { // Leave the job running for the other dirlisters, just unsubscribe us. dirData.listersCurrentlyListing.removeAll(lister); if (!silent) { emit lister->canceled(); emit lister->canceled(url); } } } } // Helper for stop() and stopListingUrl() void KCoreDirListerCache::stopListJob(const QString &url, bool silent) { // Old idea: if it's an update job, let's just leave the job running. // After all, update jobs do run for "listersCurrentlyHolding", // so there's no reason to kill them just because @p lister is now a holder. // However it could be a long-running non-local job (e.g. filenamesearch), which // the user wants to abort, and which will never be used for updating... // And in any case slotEntries/slotResult is not meant to be called by update jobs. // So, change of plan, let's kill it after all, in a way that triggers slotResult/slotUpdateResult. KIO::ListJob *job = jobForUrl(url); if (job) { qCDebug(KIO_CORE_DIRLISTER) << "Killing list job" << job << "for" << url; if (silent) { job->setProperty("_kdlc_silent", true); } job->kill(KJob::EmitResult); } } void KCoreDirListerCache::setAutoUpdate(KCoreDirLister *lister, bool enable) { // IMPORTANT: this method does not check for the current autoUpdate state! for (QList::const_iterator it = lister->d->lstDirs.constBegin(); it != lister->d->lstDirs.constEnd(); ++it) { DirItem *dirItem = itemsInUse.value((*it).toString()); Q_ASSERT(dirItem); if (enable) { dirItem->incAutoUpdate(); } else { dirItem->decAutoUpdate(); } } } void KCoreDirListerCache::forgetDirs(KCoreDirLister *lister) { qCDebug(KIO_CORE_DIRLISTER) << lister; emit lister->clear(); // clear lister->d->lstDirs before calling forgetDirs(), so that // it doesn't contain things that itemsInUse doesn't. When emitting // the canceled signals, lstDirs must not contain anything that // itemsInUse does not contain. (otherwise it might crash in findByName()). const QList lstDirsCopy = lister->d->lstDirs; lister->d->lstDirs.clear(); qCDebug(KIO_CORE_DIRLISTER) << "Iterating over dirs" << lstDirsCopy; for (QList::const_iterator it = lstDirsCopy.begin(); it != lstDirsCopy.end(); ++it) { forgetDirs(lister, *it, false); } } static bool manually_mounted(const QString &path, const KMountPoint::List &possibleMountPoints) { KMountPoint::Ptr mp = possibleMountPoints.findByPath(path); if (!mp) { // not listed in fstab -> yes, manually mounted if (possibleMountPoints.isEmpty()) { // no fstab at all -> don't assume anything return false; } return true; } const bool supermount = mp->mountType() == QLatin1String("supermount"); if (supermount) { return true; } // noauto -> manually mounted. Otherwise, mounted at boot time, won't be unmounted any time soon hopefully. return mp->mountOptions().contains(QStringLiteral("noauto")); } void KCoreDirListerCache::forgetDirs(KCoreDirLister *lister, const QUrl &_url, bool notify) { qCDebug(KIO_CORE_DIRLISTER) << lister << " _url: " << _url; QUrl url(_url); url = url.adjusted(QUrl::StripTrailingSlash); const QString urlStr = url.toString(); DirectoryDataHash::iterator dit = directoryData.find(urlStr); if (dit == directoryData.end()) { return; } KCoreDirListerCacheDirectoryData &dirData = *dit; dirData.listersCurrentlyHolding.removeAll(lister); // This lister doesn't care for updates running in anymore KIO::ListJob *job = jobForUrl(urlStr); if (job) { lister->d->jobDone(job); } DirItem *item = itemsInUse.value(urlStr); Q_ASSERT(item); bool insertIntoCache = false; if (dirData.listersCurrentlyHolding.isEmpty() && dirData.listersCurrentlyListing.isEmpty()) { // item not in use anymore -> move into cache if complete directoryData.erase(dit); itemsInUse.remove(urlStr); // this job is a running update which nobody cares about anymore if (job) { killJob(job); qCDebug(KIO_CORE_DIRLISTER) << "Killing update job for " << urlStr; // Well, the user of KCoreDirLister doesn't really care that we're stopping // a background-running job from a previous URL (in listDir) -> commented out. // stop() already emitted canceled. //emit lister->canceled( url ); if (lister->d->numJobs() == 0) { lister->d->complete = true; //emit lister->canceled(); } } if (notify) { lister->d->lstDirs.removeAll(url); emit lister->clear(url); } insertIntoCache = item->complete; if (insertIntoCache) { // TODO(afiestas): remove use of KMountPoint+manually_mounted and port to Solid: // 1) find Volume for the local path "item->url.toLocalFile()" (which could be anywhere // under the mount point) -- probably needs a new operator in libsolid query parser // 2) [**] becomes: if (Drive is hotpluggable or Volume is removable) "set to dirty" else "keep watch" const KMountPoint::List possibleMountPoints = KMountPoint::possibleMountPoints(KMountPoint::NeedMountOptions); // Should we forget the dir for good, or keep a watch on it? // Generally keep a watch, except when it would prevent // unmounting a removable device (#37780) const bool isLocal = item->url.isLocalFile(); bool isManuallyMounted = false; bool containsManuallyMounted = false; if (isLocal) { isManuallyMounted = manually_mounted(item->url.toLocalFile(), possibleMountPoints); if (!isManuallyMounted) { // Look for a manually-mounted directory inside // If there's one, we can't keep a watch either, FAM would prevent unmounting the CDROM // I hope this isn't too slow NonMovableFileItemList::const_iterator kit = item->lstItems.constBegin(); NonMovableFileItemList::const_iterator kend = item->lstItems.constEnd(); for (; kit != kend && !containsManuallyMounted; ++kit) if ((*kit).isDir() && manually_mounted((*kit).url().toLocalFile(), possibleMountPoints)) { containsManuallyMounted = true; } } } if (isManuallyMounted || containsManuallyMounted) { // [**] qCDebug(KIO_CORE_DIRLISTER) << "Not adding a watch on " << item->url << " because it " << ( isManuallyMounted ? "is manually mounted" : "contains a manually mounted subdir" ); item->complete = false; // set to "dirty" } else { item->incAutoUpdate(); // keep watch item->watchedWhileInCache = true; } } else { delete item; item = nullptr; } } if (item && lister->d->autoUpdate) { item->decAutoUpdate(); } // Inserting into QCache must be done last, since it might delete the item if (item && insertIntoCache) { qCDebug(KIO_CORE_DIRLISTER) << lister << "item moved into cache:" << url; itemsCached.insert(urlStr, item); } } void KCoreDirListerCache::updateDirectory(const QUrl &_dir) { qCDebug(KIO_CORE_DIRLISTER) << _dir; const QUrl dir = _dir.adjusted(QUrl::StripTrailingSlash); if (!checkUpdate(dir)) { if (dir.isLocalFile() && findByUrl(nullptr, dir)) { pendingUpdates.insert(dir.toLocalFile()); if (!pendingUpdateTimer.isActive()) { pendingUpdateTimer.start(500); } } return; } // A job can be running to // - only list a new directory: the listers are in listersCurrentlyListing // - only update a directory: the listers are in listersCurrentlyHolding // - update a currently running listing: the listers are in both QString urlStr = dir.toString(); KCoreDirListerCacheDirectoryData &dirData = directoryData[urlStr]; QList listers = dirData.listersCurrentlyListing; QList holders = dirData.listersCurrentlyHolding; qCDebug(KIO_CORE_DIRLISTER) << urlStr << "listers=" << listers << "holders=" << holders; bool killed = false; KIO::ListJob *job = jobForUrl(urlStr); if (job) { // the job is running already, tell it to do another update at the end // (don't kill it, we would keep doing that during a long download to a slow sshfs mount) job->setProperty("need_another_update", true); return; } else { // Emit any cached items. // updateDirectory() is about the diff compared to the cached items... Q_FOREACH (KCoreDirLister *kdl, listers) { KCoreDirLister::Private::CachedItemsJob *cachedItemsJob = kdl->d->cachedItemsJobForUrl(dir); if (cachedItemsJob) { cachedItemsJob->setEmitCompleted(false); cachedItemsJob->done(); // removes from cachedItemsJobs list delete cachedItemsJob; killed = true; } } } qCDebug(KIO_CORE_DIRLISTER) << "Killed=" << killed; // we don't need to emit canceled signals since we only replaced the job, // the listing is continuing. if (!(listers.isEmpty() || killed)) { qCWarning(KIO_CORE) << "The unexpected happened."; qCWarning(KIO_CORE) << "listers for" << dir << "=" << listers; qCWarning(KIO_CORE) << "job=" << job; Q_FOREACH(KCoreDirLister *kdl, listers) { qCDebug(KIO_CORE_DIRLISTER) << "lister" << kdl << "m_cachedItemsJobs=" << kdl->d->m_cachedItemsJobs; } #ifndef NDEBUG printDebug(); #endif } Q_ASSERT(listers.isEmpty() || killed); job = KIO::listDir(dir, KIO::HideProgressInfo); runningListJobs.insert(job, KIO::UDSEntryList()); connect(job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), this, SLOT(slotUpdateEntries(KIO::Job*,KIO::UDSEntryList))); connect(job, SIGNAL(result(KJob*)), this, SLOT(slotUpdateResult(KJob*))); qCDebug(KIO_CORE_DIRLISTER) << "update started in" << dir; foreach (KCoreDirLister *kdl, listers) { kdl->jobStarted(job); } if (!holders.isEmpty()) { if (!killed) { foreach (KCoreDirLister *kdl, holders) { kdl->jobStarted(job); emit kdl->started(dir); } } else { foreach (KCoreDirLister *kdl, holders) { kdl->jobStarted(job); } } } } bool KCoreDirListerCache::checkUpdate(const QUrl &_dir) { QString dir = _dir.toString(); if (!itemsInUse.contains(dir)) { DirItem *item = itemsCached[dir]; if (item && item->complete) { item->complete = false; item->watchedWhileInCache = false; item->decAutoUpdate(); // Hmm, this debug output might include login/password from the _dir URL. qCDebug(KIO_CORE_DIRLISTER) << "directory " << _dir << " not in use, marked dirty."; } //else qCDebug(KIO_CORE_DIRLISTER) << "aborted, directory " << _dir << " not in cache."; return false; } else { return true; } } KFileItem KCoreDirListerCache::itemForUrl(const QUrl &url) const { KFileItem *item = findByUrl(nullptr, url); if (item) { return *item; } else { return KFileItem(); } } KCoreDirListerCache::DirItem *KCoreDirListerCache::dirItemForUrl(const QUrl &dir) const { const QString urlStr = dir.toString(QUrl::StripTrailingSlash); DirItem *item = itemsInUse.value(urlStr); if (!item) { item = itemsCached[urlStr]; } return item; } NonMovableFileItemList *KCoreDirListerCache::itemsForDir(const QUrl &dir) const { DirItem *item = dirItemForUrl(dir); return item ? &item->lstItems : nullptr; } KFileItem KCoreDirListerCache::findByName(const KCoreDirLister *lister, const QString &_name) const { Q_ASSERT(lister); for (QList::const_iterator it = lister->d->lstDirs.constBegin(); it != lister->d->lstDirs.constEnd(); ++it) { DirItem *dirItem = itemsInUse.value((*it).toString()); Q_ASSERT(dirItem); const KFileItem item = dirItem->lstItems.findByName(_name); if (!item.isNull()) { return item; } } return KFileItem(); } KFileItem *KCoreDirListerCache::findByUrl(const KCoreDirLister *lister, const QUrl &_u) const { QUrl url(_u); url = url.adjusted(QUrl::StripTrailingSlash); const QUrl parentDir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); DirItem *dirItem = dirItemForUrl(parentDir); if (dirItem) { // If lister is set, check that it contains this dir if (!lister || lister->d->lstDirs.contains(parentDir)) { NonMovableFileItemList::iterator it = dirItem->lstItems.begin(); const NonMovableFileItemList::iterator end = dirItem->lstItems.end(); for (; it != end; ++it) { if ((*it).url() == url) { return &*it; } } } } // Maybe _u is a directory itself? (see KDirModelTest::testChmodDirectory) // We check this last, though, we prefer returning a kfileitem with an actual // name if possible (and we make it '.' for root items later). dirItem = dirItemForUrl(url); if (dirItem && !dirItem->rootItem.isNull() && dirItem->rootItem.url() == url) { // If lister is set, check that it contains this dir if (!lister || lister->d->lstDirs.contains(url)) { return &dirItem->rootItem; } } return nullptr; } void KCoreDirListerCache::slotFilesAdded(const QString &dir /*url*/) // from KDirNotify signals { QUrl urlDir(dir); itemsAddedInDirectory(urlDir); } void KCoreDirListerCache::itemsAddedInDirectory(const QUrl &urlDir) { qCDebug(KIO_CORE_DIRLISTER) << urlDir; // output urls, not qstrings, since they might contain a password Q_FOREACH (const QUrl &u, directoriesForCanonicalPath(urlDir)) { updateDirectory(u); } } void KCoreDirListerCache::slotFilesRemoved(const QStringList &fileList) // from KDirNotify signals { // TODO: handling of symlinks-to-directories isn't done here, // because I'm not sure how to do it and keep the performance ok... slotFilesRemoved(QUrl::fromStringList(fileList)); } void KCoreDirListerCache::slotFilesRemoved(const QList &fileList) { qCDebug(KIO_CORE_DIRLISTER) << fileList.count(); // Group notifications by parent dirs (usually there would be only one parent dir) QMap removedItemsByDir; QList deletedSubdirs; for (QList::const_iterator it = fileList.begin(); it != fileList.end(); ++it) { QUrl url(*it); DirItem *dirItem = dirItemForUrl(url); // is it a listed directory? if (dirItem) { deletedSubdirs.append(url); if (!dirItem->rootItem.isNull()) { removedItemsByDir[url.toString()].append(dirItem->rootItem); } } const QUrl parentDir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); dirItem = dirItemForUrl(parentDir); if (!dirItem) { continue; } for (NonMovableFileItemList::iterator fit = dirItem->lstItems.begin(), fend = dirItem->lstItems.end(); fit != fend; ++fit) { if ((*fit).url() == url) { const KFileItem fileitem = *fit; removedItemsByDir[parentDir.toString()].append(fileitem); // If we found a fileitem, we can test if it's a dir. If not, we'll go to deleteDir just in case. if (fileitem.isNull() || fileitem.isDir()) { deletedSubdirs.append(url); } dirItem->lstItems.erase(fit); // remove fileitem from list break; } } } QMap::const_iterator rit = removedItemsByDir.constBegin(); for (; rit != removedItemsByDir.constEnd(); ++rit) { // Tell the views about it before calling deleteDir. // They might need the subdirs' file items (see the dirtree). DirectoryDataHash::const_iterator dit = directoryData.constFind(rit.key()); if (dit != directoryData.constEnd()) { itemsDeleted((*dit).listersCurrentlyHolding, rit.value()); } } Q_FOREACH (const QUrl &url, deletedSubdirs) { // in case of a dir, check if we have any known children, there's much to do in that case // (stopping jobs, removing dirs from cache etc.) deleteDir(url); } } void KCoreDirListerCache::slotFilesChanged(const QStringList &fileList) // from KDirNotify signals { qCDebug(KIO_CORE_DIRLISTER) << fileList; QList dirsToUpdate; QStringList::const_iterator it = fileList.begin(); for (; it != fileList.end(); ++it) { QUrl url(*it); KFileItem *fileitem = findByUrl(nullptr, url); if (!fileitem) { qCDebug(KIO_CORE_DIRLISTER) << "item not found for" << url; continue; } if (url.isLocalFile()) { pendingUpdates.insert(url.toLocalFile()); // delegate the work to processPendingUpdates } else { pendingRemoteUpdates.insert(fileitem); // For remote files, we won't be able to figure out the new information, // we have to do a update (directory listing) const QUrl dir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); if (!dirsToUpdate.contains(dir)) { dirsToUpdate.prepend(dir); } } } QList::const_iterator itdir = dirsToUpdate.constBegin(); for (; itdir != dirsToUpdate.constEnd(); ++itdir) { updateDirectory(*itdir); } // ## TODO problems with current jobs listing/updating that dir // ( see kde-2.2.2's kdirlister ) processPendingUpdates(); } void KCoreDirListerCache::slotFileRenamed(const QString &_src, const QString &_dst, const QString &dstPath) // from KDirNotify signals { QUrl src(_src); QUrl dst(_dst); qCDebug(KIO_CORE_DIRLISTER) << src << "->" << dst; #ifdef DEBUG_CACHE printDebug(); #endif QUrl oldurl = src.adjusted(QUrl::StripTrailingSlash); KFileItem *fileitem = findByUrl(nullptr, oldurl); if (!fileitem) { qCDebug(KIO_CORE_DIRLISTER) << "Item not found:" << oldurl; return; } const KFileItem oldItem = *fileitem; // Dest already exists? Was overwritten then (testcase: #151851) // We better emit it as deleted -before- doing the renaming, otherwise // the "update" mechanism will emit the old one as deleted and // kdirmodel will delete the new (renamed) one! KFileItem *existingDestItem = findByUrl(nullptr, dst); if (existingDestItem) { qCDebug(KIO_CORE_DIRLISTER) << dst << "already existed, let's delete it"; slotFilesRemoved(QList() << dst); } // If the item had a UDS_URL as well as UDS_NAME set, the user probably wants // to be updating the name only (since they can't see the URL). // Check to see if a URL exists, and if so, if only the file part has changed, // only update the name and not the underlying URL. bool nameOnly = !fileitem->entry().stringValue(KIO::UDSEntry::UDS_URL).isEmpty(); nameOnly = nameOnly && src.adjusted(QUrl::RemoveFilename) == dst.adjusted(QUrl::RemoveFilename); if (!nameOnly && fileitem->isDir()) { renameDir(oldurl, dst); // #172945 - if the fileitem was the root item of a DirItem that was just removed from the cache, // then it's a dangling pointer now... fileitem = findByUrl(nullptr, oldurl); if (!fileitem) { //deleted from cache altogether, #188807 return; } } // Now update the KFileItem representing that file or dir (not exclusive with the above!) if (!oldItem.isLocalFile() && !oldItem.localPath().isEmpty() && dstPath.isEmpty()) { // it uses UDS_LOCAL_PATH and we don't know the new path? needs an update then slotFilesChanged(QStringList() << src.toString()); } else { if (nameOnly) { fileitem->setName(dst.fileName()); } else { fileitem->setUrl(dst); } if (!dstPath.isEmpty()) { fileitem->setLocalPath(dstPath); } fileitem->refreshMimeType(); fileitem->determineMimeType(); QSet listers = emitRefreshItem(oldItem, *fileitem); Q_FOREACH (KCoreDirLister *kdl, listers) { kdl->d->emitItems(); } } #ifdef DEBUG_CACHE printDebug(); #endif } QSet KCoreDirListerCache::emitRefreshItem(const KFileItem &oldItem, const KFileItem &fileitem) { qCDebug(KIO_CORE_DIRLISTER) << "old:" << oldItem.name() << oldItem.url() << "new:" << fileitem.name() << fileitem.url(); // Look whether this item was shown in any view, i.e. held by any dirlister const QUrl parentDir = oldItem.url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); const QString parentDirURL = parentDir.toString(); DirectoryDataHash::iterator dit = directoryData.find(parentDirURL); QList listers; // Also look in listersCurrentlyListing, in case the user manages to rename during a listing if (dit != directoryData.end()) { listers += (*dit).listersCurrentlyHolding + (*dit).listersCurrentlyListing; } if (oldItem.isDir()) { // For a directory, look for dirlisters where it's the root item. dit = directoryData.find(oldItem.url().toString()); if (dit != directoryData.end()) { listers += (*dit).listersCurrentlyHolding + (*dit).listersCurrentlyListing; } } QSet listersToRefresh; Q_FOREACH (KCoreDirLister *kdl, listers) { // For a directory, look for dirlisters where it's the root item. QUrl directoryUrl(oldItem.url()); if (oldItem.isDir() && kdl->d->rootFileItem == oldItem) { const KFileItem oldRootItem = kdl->d->rootFileItem; kdl->d->rootFileItem = fileitem; kdl->d->addRefreshItem(directoryUrl, oldRootItem, fileitem); } else { directoryUrl = directoryUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); kdl->d->addRefreshItem(directoryUrl, oldItem, fileitem); } listersToRefresh.insert(kdl); } return listersToRefresh; } QList KCoreDirListerCache::directoriesForCanonicalPath(const QUrl &dir) const { QList dirs; dirs << dir; dirs << canonicalUrls.value(dir).toSet().toList(); /* make unique; there are faster ways, but this is really small anyway */ if (dirs.count() > 1) { qCDebug(KIO_CORE_DIRLISTER) << dir << "known as" << dirs; } return dirs; } // private slots // Called by KDirWatch - usually when a dir we're watching has been modified, // but it can also be called for a file. void KCoreDirListerCache::slotFileDirty(const QString &path) { qCDebug(KIO_CORE_DIRLISTER) << path; QUrl url = QUrl::fromLocalFile(path).adjusted(QUrl::StripTrailingSlash); // File or dir? bool isDir; const KFileItem item = itemForUrl(url); if (!item.isNull()) { isDir = item.isDir(); } else { QFileInfo info(path); if (!info.exists()) { return; // error } isDir = info.isDir(); } if (isDir) { Q_FOREACH (const QUrl &dir, directoriesForCanonicalPath(url)) { handleFileDirty(dir); // e.g. for permission changes handleDirDirty(dir); } } else { Q_FOREACH (const QUrl &dir, directoriesForCanonicalPath(url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash))) { QUrl aliasUrl(dir); aliasUrl.setPath(concatPaths(aliasUrl.path(), url.fileName())); handleFileDirty(aliasUrl); } } } // Called by slotFileDirty void KCoreDirListerCache::handleDirDirty(const QUrl &url) { // A dir: launch an update job if anyone cares about it // This also means we can forget about pending updates to individual files in that dir const QString dir = url.toLocalFile(); QString dirPath = dir; if (!dirPath.endsWith('/')) { dirPath += '/'; } QMutableSetIterator pendingIt(pendingUpdates); while (pendingIt.hasNext()) { const QString updPath = pendingIt.next(); qCDebug(KIO_CORE_DIRLISTER) << "had pending update" << updPath; if (updPath.startsWith(dirPath) && updPath.indexOf('/', dirPath.length()) == -1) { // direct child item qCDebug(KIO_CORE_DIRLISTER) << "forgetting about individual update to" << updPath; pendingIt.remove(); } } if (checkUpdate(url) && !pendingDirectoryUpdates.contains(dir)) { pendingDirectoryUpdates.insert(dir); if (!pendingUpdateTimer.isActive()) { pendingUpdateTimer.start(200); } } } // Called by slotFileDirty void KCoreDirListerCache::handleFileDirty(const QUrl &url) { // A file: do we know about it already? KFileItem *existingItem = findByUrl(nullptr, url); const QUrl dir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); QString filePath = url.toLocalFile(); if (!existingItem) { // No - update the parent dir then handleDirDirty(dir); } // Delay updating the file, FAM is flooding us with events if (checkUpdate(dir) && !pendingUpdates.contains(filePath)) { pendingUpdates.insert(filePath); if (!pendingUpdateTimer.isActive()) { pendingUpdateTimer.start(200); } } } void KCoreDirListerCache::slotFileCreated(const QString &path) // from KDirWatch { qCDebug(KIO_CORE_DIRLISTER) << path; // XXX: how to avoid a complete rescan here? // We'd need to stat that one file separately and refresh the item(s) for it. QUrl fileUrl(QUrl::fromLocalFile(path)); itemsAddedInDirectory(fileUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash)); } void KCoreDirListerCache::slotFileDeleted(const QString &path) // from KDirWatch { qCDebug(KIO_CORE_DIRLISTER) << path; const QString fileName = QFileInfo(path).fileName(); QUrl dirUrl(QUrl::fromLocalFile(path)); QStringList fileUrls; Q_FOREACH (const QUrl &url, directoriesForCanonicalPath(dirUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash))) { QUrl urlInfo(url); urlInfo.setPath(concatPaths(urlInfo.path(), fileName)); fileUrls << urlInfo.toString(); } slotFilesRemoved(fileUrls); } void KCoreDirListerCache::slotEntries(KIO::Job *job, const KIO::UDSEntryList &entries) { QUrl url(joburl(static_cast(job))); url = url.adjusted(QUrl::StripTrailingSlash); QString urlStr = url.toString(); qCDebug(KIO_CORE_DIRLISTER) << "new entries for " << url; DirItem *dir = itemsInUse.value(urlStr); if (!dir) { qCWarning(KIO_CORE) << "Internal error: job is listing" << url << "but itemsInUse only knows about" << itemsInUse.keys(); Q_ASSERT(dir); return; } DirectoryDataHash::iterator dit = directoryData.find(urlStr); if (dit == directoryData.end()) { qCWarning(KIO_CORE) << "Internal error: job is listing" << url << "but directoryData doesn't know about that url, only about:" << directoryData.keys(); Q_ASSERT(dit != directoryData.end()); return; } KCoreDirListerCacheDirectoryData &dirData = *dit; if (dirData.listersCurrentlyListing.isEmpty()) { qCWarning(KIO_CORE) << "Internal error: job is listing" << url << "but directoryData says no listers are currently listing " << urlStr; #ifndef NDEBUG printDebug(); #endif Q_ASSERT(!dirData.listersCurrentlyListing.isEmpty()); return; } // check if anyone wants the mimetypes immediately bool delayedMimeTypes = true; foreach (KCoreDirLister *kdl, dirData.listersCurrentlyListing) { delayedMimeTypes &= kdl->d->delayedMimeTypes; } QSet filesToHide; bool dotHiddenChecked = false; KIO::UDSEntryList::const_iterator it = entries.begin(); const KIO::UDSEntryList::const_iterator end = entries.end(); for (; it != end; ++it) { const QString name = (*it).stringValue(KIO::UDSEntry::UDS_NAME); Q_ASSERT(!name.isEmpty()); if (name.isEmpty()) { continue; } if (name == QLatin1String(".")) { Q_ASSERT(dir->rootItem.isNull()); // Try to reuse an existing KFileItem (if we listed the parent dir) // rather than creating a new one. There are many reasons: // 1) renames and permission changes to the item would have to emit the signals // twice, otherwise, so that both views manage to recognize the item. // 2) with kio_ftp we can only know that something is a symlink when // listing the parent, so prefer that item, which has more info. // Note that it gives a funky name() to the root item, rather than "." ;) dir->rootItem = itemForUrl(url); if (dir->rootItem.isNull()) { dir->rootItem = KFileItem(*it, url, delayedMimeTypes, true); } foreach (KCoreDirLister *kdl, dirData.listersCurrentlyListing) if (kdl->d->rootFileItem.isNull() && kdl->d->url == url) { kdl->d->rootFileItem = dir->rootItem; } } else if (name != QLatin1String("..")) { KFileItem item(*it, url, delayedMimeTypes, true); // get the names of the files listed in ".hidden", if it exists and is a local file if (!dotHiddenChecked) { const QString localPath = item.localPath(); if (!localPath.isEmpty()) { const QString rootItemPath = QFileInfo(localPath).absolutePath(); filesToHide = filesInDotHiddenForDir(rootItemPath); } dotHiddenChecked = true; } // hide file if listed in ".hidden" if (filesToHide.contains(name)) { item.setHidden(); } qCDebug(KIO_CORE_DIRLISTER)<< "Adding item: " << item.url(); dir->lstItems.append(item); foreach (KCoreDirLister *kdl, dirData.listersCurrentlyListing) { kdl->d->addNewItem(url, item); } } } foreach (KCoreDirLister *kdl, dirData.listersCurrentlyListing) { kdl->d->emitItems(); } } void KCoreDirListerCache::slotResult(KJob *j) { #ifdef DEBUG_CACHE //printDebug(); #endif Q_ASSERT(j); KIO::ListJob *job = static_cast(j); runningListJobs.remove(job); QUrl jobUrl(joburl(job)); jobUrl = jobUrl.adjusted(QUrl::StripTrailingSlash); // need remove trailing slashes again, in case of redirections QString jobUrlStr = jobUrl.toString(); qCDebug(KIO_CORE_DIRLISTER) << "finished listing" << jobUrl; DirectoryDataHash::iterator dit = directoryData.find(jobUrlStr); if (dit == directoryData.end()) { qCWarning(KIO_CORE) << "Nothing found in directoryData for URL" << jobUrlStr; #ifndef NDEBUG printDebug(); #endif Q_ASSERT(dit != directoryData.end()); return; } KCoreDirListerCacheDirectoryData &dirData = *dit; if (dirData.listersCurrentlyListing.isEmpty()) { qCWarning(KIO_CORE) << "OOOOPS, nothing in directoryData.listersCurrentlyListing for" << jobUrlStr; // We're about to assert; dump the current state... #ifndef NDEBUG printDebug(); #endif Q_ASSERT(!dirData.listersCurrentlyListing.isEmpty()); } QList listers = dirData.listersCurrentlyListing; // move all listers to the holding list, do it before emitting // the signals to make sure it exists in KCoreDirListerCache in case someone // calls listDir during the signal emission Q_ASSERT(dirData.listersCurrentlyHolding.isEmpty()); dirData.moveListersWithoutCachedItemsJob(jobUrl); if (job->error()) { foreach (KCoreDirLister *kdl, listers) { kdl->d->jobDone(job); if (job->error() != KJob::KilledJobError) { kdl->handleError(job); } const bool silent = job->property("_kdlc_silent").toBool(); if (!silent) { emit kdl->canceled(jobUrl); } if (kdl->d->numJobs() == 0) { kdl->d->complete = true; if (!silent) { emit kdl->canceled(); } } } } else { DirItem *dir = itemsInUse.value(jobUrlStr); Q_ASSERT(dir); dir->complete = true; foreach (KCoreDirLister *kdl, listers) { kdl->d->jobDone(job); emit kdl->completed(jobUrl); if (kdl->d->numJobs() == 0) { kdl->d->complete = true; emit kdl->completed(); } } } // TODO: hmm, if there was an error and job is a parent of one or more // of the pending urls we should cancel it/them as well processPendingUpdates(); if (job->property("need_another_update").toBool()) { updateDirectory(jobUrl); } #ifdef DEBUG_CACHE printDebug(); #endif } void KCoreDirListerCache::slotRedirection(KIO::Job *j, const QUrl &url) { Q_ASSERT(j); KIO::ListJob *job = static_cast(j); QUrl oldUrl(job->url()); // here we really need the old url! QUrl newUrl(url); // strip trailing slashes oldUrl = oldUrl.adjusted(QUrl::StripTrailingSlash); newUrl = newUrl.adjusted(QUrl::StripTrailingSlash); if (oldUrl == newUrl) { qCDebug(KIO_CORE_DIRLISTER) << "New redirection url same as old, giving up."; return; } else if (newUrl.isEmpty()) { qCDebug(KIO_CORE_DIRLISTER) << "New redirection url is empty, giving up."; return; } const QString oldUrlStr = oldUrl.toString(); const QString newUrlStr = newUrl.toString(); qCDebug(KIO_CORE_DIRLISTER) << oldUrl << "->" << newUrl; #ifdef DEBUG_CACHE // Can't do that here. KCoreDirListerCache::joburl() will use the new url already, // while our data structures haven't been updated yet -> assert fail. //printDebug(); #endif // I don't think there can be dirItems that are children of oldUrl. // Am I wrong here? And even if so, we don't need to delete them, right? // DF: redirection happens before listDir emits any item. Makes little sense otherwise. // oldUrl cannot be in itemsCached because only completed items are moved there DirItem *dir = itemsInUse.take(oldUrlStr); Q_ASSERT(dir); DirectoryDataHash::iterator dit = directoryData.find(oldUrlStr); Q_ASSERT(dit != directoryData.end()); KCoreDirListerCacheDirectoryData oldDirData = *dit; directoryData.erase(dit); Q_ASSERT(!oldDirData.listersCurrentlyListing.isEmpty()); const QList listers = oldDirData.listersCurrentlyListing; Q_ASSERT(!listers.isEmpty()); foreach (KCoreDirLister *kdl, listers) { kdl->d->redirect(oldUrl, newUrl, false /*clear items*/); } // when a lister was stopped before the job emits the redirection signal, the old url will // also be in listersCurrentlyHolding const QList holders = oldDirData.listersCurrentlyHolding; foreach (KCoreDirLister *kdl, holders) { kdl->jobStarted(job); // do it like when starting a new list-job that will redirect later // TODO: maybe don't emit started if there's an update running for newUrl already? emit kdl->started(oldUrl); kdl->d->redirect(oldUrl, newUrl, false /*clear items*/); } DirItem *newDir = itemsInUse.value(newUrlStr); if (newDir) { qCDebug(KIO_CORE_DIRLISTER) << newUrl << "already in use"; // only in this case there can newUrl already be in listersCurrentlyListing or listersCurrentlyHolding delete dir; // get the job if one's running for newUrl already (can be a list-job or an update-job), but // do not return this 'job', which would happen because of the use of redirectionURL() KIO::ListJob *oldJob = jobForUrl(newUrlStr, job); // listers of newUrl with oldJob: forget about the oldJob and use the already running one // which will be converted to an updateJob KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrlStr]; QList &curListers = newDirData.listersCurrentlyListing; if (!curListers.isEmpty()) { qCDebug(KIO_CORE_DIRLISTER) << "and it is currently listed"; Q_ASSERT(oldJob); // ?! foreach (KCoreDirLister *kdl, curListers) { // listers of newUrl kdl->d->jobDone(oldJob); kdl->jobStarted(job); kdl->d->connectJob(job); } // append listers of oldUrl with newJob to listers of newUrl with oldJob foreach (KCoreDirLister *kdl, listers) { curListers.append(kdl); } } else { curListers = listers; } if (oldJob) { // kill the old job, be it a list-job or an update-job killJob(oldJob); } // holders of newUrl: use the already running job which will be converted to an updateJob QList &curHolders = newDirData.listersCurrentlyHolding; if (!curHolders.isEmpty()) { qCDebug(KIO_CORE_DIRLISTER) << "and it is currently held."; foreach (KCoreDirLister *kdl, curHolders) { // holders of newUrl kdl->jobStarted(job); emit kdl->started(newUrl); } // append holders of oldUrl to holders of newUrl foreach (KCoreDirLister *kdl, holders) { curHolders.append(kdl); } } else { curHolders = holders; } // emit old items: listers, holders. NOT: newUrlListers/newUrlHolders, they already have them listed // TODO: make this a separate method? foreach (KCoreDirLister *kdl, listers + holders) { if (kdl->d->rootFileItem.isNull() && kdl->d->url == newUrl) { kdl->d->rootFileItem = newDir->rootItem; } kdl->d->addNewItems(newUrl, newDir->lstItems); kdl->d->emitItems(); } } else if ((newDir = itemsCached.take(newUrlStr))) { qCDebug(KIO_CORE_DIRLISTER) << newUrl << "is unused, but already in the cache."; delete dir; itemsInUse.insert(newUrlStr, newDir); KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrlStr]; newDirData.listersCurrentlyListing = listers; newDirData.listersCurrentlyHolding = holders; // emit old items: listers, holders foreach (KCoreDirLister *kdl, listers + holders) { if (kdl->d->rootFileItem.isNull() && kdl->d->url == newUrl) { kdl->d->rootFileItem = newDir->rootItem; } kdl->d->addNewItems(newUrl, newDir->lstItems); kdl->d->emitItems(); } } else { qCDebug(KIO_CORE_DIRLISTER) << newUrl << "has not been listed yet."; dir->rootItem = KFileItem(); dir->lstItems.clear(); dir->redirect(newUrl); itemsInUse.insert(newUrlStr, dir); KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrlStr]; newDirData.listersCurrentlyListing = listers; newDirData.listersCurrentlyHolding = holders; if (holders.isEmpty()) { #ifdef DEBUG_CACHE printDebug(); #endif return; // only in this case the job doesn't need to be converted, } } // make the job an update job job->disconnect(this); connect(job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), this, SLOT(slotUpdateEntries(KIO::Job*,KIO::UDSEntryList))); connect(job, SIGNAL(result(KJob*)), this, SLOT(slotUpdateResult(KJob*))); // FIXME: autoUpdate-Counts!! #ifdef DEBUG_CACHE printDebug(); #endif } struct KCoreDirListerCache::ItemInUseChange { ItemInUseChange(const QString &old, const QString &newU, DirItem *di) : oldUrl(old), newUrl(newU), dirItem(di) {} QString oldUrl; QString newUrl; DirItem *dirItem; }; void KCoreDirListerCache::renameDir(const QUrl &oldUrl, const QUrl &newUrl) { qCDebug(KIO_CORE_DIRLISTER) << oldUrl << "->" << newUrl; //const QString oldUrlStr = oldUrl.url(KUrl::RemoveTrailingSlash); //const QString newUrlStr = newUrl.url(KUrl::RemoveTrailingSlash); // Not enough. Also need to look at any child dir, even sub-sub-sub-dir. //DirItem *dir = itemsInUse.take( oldUrlStr ); //emitRedirections( oldUrl, url ); QLinkedList itemsToChange; QSet listers; // Look at all dirs being listed/shown QHash::iterator itu = itemsInUse.begin(); const QHash::iterator ituend = itemsInUse.end(); for (; itu != ituend; ++itu) { DirItem *dir = itu.value(); QUrl oldDirUrl(itu.key()); qCDebug(KIO_CORE_DIRLISTER) << "itemInUse:" << oldDirUrl; // Check if this dir is oldUrl, or a subfolder of it if (oldDirUrl == oldUrl || oldUrl.isParentOf(oldDirUrl)) { // TODO should use KUrl::cleanpath like isParentOf does QString relPath = oldDirUrl.path().mid(oldUrl.path().length()+1); QUrl newDirUrl(newUrl); // take new base if (!relPath.isEmpty()) { newDirUrl.setPath(concatPaths(newDirUrl.path(), relPath)); // add unchanged relative path } qCDebug(KIO_CORE_DIRLISTER) << "new url=" << newDirUrl; // Update URL in dir item and in itemsInUse dir->redirect(newDirUrl); itemsToChange.append(ItemInUseChange(oldDirUrl.toString(QUrl::StripTrailingSlash), newDirUrl.toString(QUrl::StripTrailingSlash), dir)); // Rename all items under that dir for (NonMovableFileItemList::iterator kit = dir->lstItems.begin(), kend = dir->lstItems.end(); kit != kend; ++kit) { const KFileItem oldItem = *kit; const QUrl oldItemUrl((*kit).url()); const QString oldItemUrlStr(oldItemUrl.toString(QUrl::StripTrailingSlash)); QUrl newItemUrl(oldItemUrl); newItemUrl.setPath(concatPaths(newDirUrl.path(), oldItemUrl.fileName())); qCDebug(KIO_CORE_DIRLISTER) << "renaming" << oldItemUrl << "to" << newItemUrl; (*kit).setUrl(newItemUrl); listers |= emitRefreshItem(oldItem, *kit); } } } Q_FOREACH (KCoreDirLister *kdl, listers) { kdl->d->emitItems(); } // Do the changes to itemsInUse out of the loop to avoid messing up iterators, // and so that emitRefreshItem can find the stuff in the hash. foreach (const ItemInUseChange &i, itemsToChange) { itemsInUse.remove(i.oldUrl); itemsInUse.insert(i.newUrl, i.dirItem); } //Now that all the caches are updated and consistent, emit the redirection. foreach(const ItemInUseChange& i, itemsToChange) { emitRedirections(QUrl(i.oldUrl), QUrl(i.newUrl)); } // Is oldUrl a directory in the cache? // Remove any child of oldUrl from the cache - even if the renamed dir itself isn't in it! removeDirFromCache(oldUrl); // TODO rename, instead. } // helper for renameDir, not used for redirections from KIO::listDir(). void KCoreDirListerCache::emitRedirections(const QUrl &oldUrl, const QUrl &newUrl) { qCDebug(KIO_CORE_DIRLISTER) << oldUrl << "->" << newUrl; const QString oldUrlStr = oldUrl.toString(QUrl::StripTrailingSlash); const QString newUrlStr = newUrl.toString(QUrl::StripTrailingSlash); KIO::ListJob *job = jobForUrl(oldUrlStr); if (job) { killJob(job); } // Check if we were listing this dir. Need to abort and restart with new name in that case. DirectoryDataHash::iterator dit = directoryData.find(oldUrlStr); if (dit == directoryData.end()) { return; } const QList listers = (*dit).listersCurrentlyListing; const QList holders = (*dit).listersCurrentlyHolding; KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrlStr]; // Tell the world that the job listing the old url is dead. foreach (KCoreDirLister *kdl, listers) { if (job) { kdl->d->jobDone(job); } emit kdl->canceled(oldUrl); } newDirData.listersCurrentlyListing += listers; // Check if we are currently displaying this directory (odds opposite wrt above) foreach (KCoreDirLister *kdl, holders) { if (job) { kdl->d->jobDone(job); } } newDirData.listersCurrentlyHolding += holders; directoryData.erase(dit); if (!listers.isEmpty()) { updateDirectory(newUrl); // Tell the world about the new url foreach (KCoreDirLister *kdl, listers) { emit kdl->started(newUrl); } } // And notify the dirlisters of the redirection foreach (KCoreDirLister *kdl, holders) { kdl->d->redirect(oldUrl, newUrl, true /*keep items*/); } } void KCoreDirListerCache::removeDirFromCache(const QUrl &dir) { qCDebug(KIO_CORE_DIRLISTER) << dir; const QList cachedDirs = itemsCached.keys(); // seems slow, but there's no qcache iterator... foreach (const QString &cachedDir, cachedDirs) { const QUrl cachedDirUrl(cachedDir); if (dir == cachedDirUrl || dir.isParentOf(cachedDirUrl)) { itemsCached.remove(cachedDir); } } } void KCoreDirListerCache::slotUpdateEntries(KIO::Job *job, const KIO::UDSEntryList &list) { runningListJobs[static_cast(job)] += list; } void KCoreDirListerCache::slotUpdateResult(KJob *j) { Q_ASSERT(j); KIO::ListJob *job = static_cast(j); QUrl jobUrl(joburl(job)); jobUrl = jobUrl.adjusted(QUrl::StripTrailingSlash); // need remove trailing slashes again, in case of redirections QString jobUrlStr(jobUrl.toString()); qCDebug(KIO_CORE_DIRLISTER) << "finished update" << jobUrl; KCoreDirListerCacheDirectoryData &dirData = directoryData[jobUrlStr]; // Collect the dirlisters which were listing the URL using that ListJob // plus those that were already holding that URL - they all get updated. dirData.moveListersWithoutCachedItemsJob(jobUrl); QList listers = dirData.listersCurrentlyHolding; listers += dirData.listersCurrentlyListing; // once we are updating dirs that are only in the cache this will fail! Q_ASSERT(!listers.isEmpty()); if (job->error()) { foreach (KCoreDirLister *kdl, listers) { kdl->d->jobDone(job); //don't bother the user //kdl->handleError( job ); const bool silent = job->property("_kdlc_silent").toBool(); if (!silent) { emit kdl->canceled(jobUrl); } if (kdl->d->numJobs() == 0) { kdl->d->complete = true; if (!silent) { emit kdl->canceled(); } } } runningListJobs.remove(job); // TODO: if job is a parent of one or more // of the pending urls we should cancel them processPendingUpdates(); return; } DirItem *dir = itemsInUse.value(jobUrlStr, nullptr); if (!dir) { qCWarning(KIO_CORE) << "Internal error: itemsInUse did not contain" << jobUrlStr; #ifndef NDEBUG printDebug(); #endif Q_ASSERT(dir); } else { dir->complete = true; } // check if anyone wants the mimetypes immediately bool delayedMimeTypes = true; foreach (KCoreDirLister *kdl, listers) { delayedMimeTypes &= kdl->d->delayedMimeTypes; } typedef QHash FileItemHash; // fileName -> KFileItem* FileItemHash fileItems; // Fill the hash from the old list of items. We'll remove entries as we see them // in the new listing, and the resulting hash entries will be the deleted items. for (NonMovableFileItemList::iterator kit = dir->lstItems.begin(), kend = dir->lstItems.end(); kit != kend; ++kit) { fileItems.insert((*kit).name(), &*kit); } QSet filesToHide; bool dotHiddenChecked = false; const KIO::UDSEntryList &buf = runningListJobs.value(job); KIO::UDSEntryList::const_iterator it = buf.constBegin(); const KIO::UDSEntryList::const_iterator end = buf.constEnd(); for (; it != end; ++it) { // Form the complete url KFileItem item(*it, jobUrl, delayedMimeTypes, true); const QString name = item.name(); Q_ASSERT(!name.isEmpty()); // A kioslave setting an empty UDS_NAME is utterly broken, fix the kioslave! // we duplicate the check for dotdot here, to avoid iterating over // all items again and checking in matchesFilter() that way. if (name.isEmpty() || name == QLatin1String("..")) { continue; } if (name == QLatin1String(".")) { // if the update was started before finishing the original listing // there is no root item yet if (dir->rootItem.isNull()) { dir->rootItem = item; foreach (KCoreDirLister *kdl, listers) if (kdl->d->rootFileItem.isNull() && kdl->d->url == jobUrl) { kdl->d->rootFileItem = dir->rootItem; } } continue; } else { // get the names of the files listed in ".hidden", if it exists and is a local file if (!dotHiddenChecked) { const QString localPath = item.localPath(); if (!localPath.isEmpty()) { const QString rootItemPath = QFileInfo(localPath).absolutePath(); filesToHide = filesInDotHiddenForDir(rootItemPath); } dotHiddenChecked = true; } } // hide file if listed in ".hidden" if (filesToHide.contains(name)) { item.setHidden(); } // Find this item FileItemHash::iterator fiit = fileItems.find(item.name()); if (fiit != fileItems.end()) { KFileItem* tmp = fiit.value(); QSet::iterator pru_it = pendingRemoteUpdates.find(tmp); const bool inPendingRemoteUpdates = (pru_it != pendingRemoteUpdates.end()); // check if something changed for this file, using KFileItem::cmp() if (!tmp->cmp(item) || inPendingRemoteUpdates) { if (inPendingRemoteUpdates) { pendingRemoteUpdates.erase(pru_it); } qCDebug(KIO_CORE_DIRLISTER) << "file changed:" << tmp->name(); const KFileItem oldItem = *tmp; *tmp = item; foreach (KCoreDirLister *kdl, listers) { kdl->d->addRefreshItem(jobUrl, oldItem, *tmp); } } // Seen, remove fileItems.erase(fiit); } else { // this is a new file qCDebug(KIO_CORE_DIRLISTER) << "new file:" << name; dir->lstItems.append(item); foreach (KCoreDirLister *kdl, listers) { kdl->d->addNewItem(jobUrl, item); } } } runningListJobs.remove(job); if (!fileItems.isEmpty()) { deleteUnmarkedItems(listers, dir->lstItems, fileItems); } foreach (KCoreDirLister *kdl, listers) { kdl->d->emitItems(); kdl->d->jobDone(job); emit kdl->completed(jobUrl); if (kdl->d->numJobs() == 0) { kdl->d->complete = true; emit kdl->completed(); } } // TODO: hmm, if there was an error and job is a parent of one or more // of the pending urls we should cancel it/them as well processPendingUpdates(); if (job->property("need_another_update").toBool()) { updateDirectory(jobUrl); } } // private KIO::ListJob *KCoreDirListerCache::jobForUrl(const QString &url, KIO::ListJob *not_job) { QMap< KIO::ListJob *, KIO::UDSEntryList >::const_iterator it = runningListJobs.constBegin(); while (it != runningListJobs.constEnd()) { KIO::ListJob *job = it.key(); QString jobUrlStr = joburl(job).toString(QUrl::StripTrailingSlash); if (jobUrlStr == url && job != not_job) { return job; } ++it; } return nullptr; } const QUrl &KCoreDirListerCache::joburl(KIO::ListJob *job) { if (job->redirectionUrl().isValid()) { return job->redirectionUrl(); } else { return job->url(); } } void KCoreDirListerCache::killJob(KIO::ListJob *job) { runningListJobs.remove(job); job->disconnect(this); job->kill(); } void KCoreDirListerCache::deleteUnmarkedItems(const QList &listers, NonMovableFileItemList &lstItems, const QHash &itemsToDelete) { // Make list of deleted items (for emitting) KFileItemList deletedItems; QHashIterator kit(itemsToDelete); while (kit.hasNext()) { const KFileItem& item = *kit.next().value(); deletedItems.append(item); qCDebug(KIO_CORE_DIRLISTER) << "deleted:" << item.name() << &item; } // Delete all remaining items QMutableListIterator it(lstItems); while (it.hasNext()) { if (itemsToDelete.contains(it.next().name())) { it.remove(); } } itemsDeleted(listers, deletedItems); } void KCoreDirListerCache::itemsDeleted(const QList &listers, const KFileItemList &deletedItems) { Q_FOREACH (KCoreDirLister *kdl, listers) { kdl->d->emitItemsDeleted(deletedItems); } Q_FOREACH (const KFileItem &item, deletedItems) { if (item.isDir()) { deleteDir(item.url()); } } } void KCoreDirListerCache::deleteDir(const QUrl &_dirUrl) { qCDebug(KIO_CORE_DIRLISTER) << _dirUrl; // unregister and remove the children of the deleted item. // Idea: tell all the KCoreDirListers that they should forget the dir // and then remove it from the cache. QUrl dirUrl(_dirUrl.adjusted(QUrl::StripTrailingSlash)); // Separate itemsInUse iteration and calls to forgetDirs (which modify itemsInUse) QList affectedItems; QHash::iterator itu = itemsInUse.begin(); const QHash::iterator ituend = itemsInUse.end(); for (; itu != ituend; ++itu) { const QUrl deletedUrl(itu.key()); if (dirUrl == deletedUrl || dirUrl.isParentOf(deletedUrl)) { affectedItems.append(deletedUrl); } } foreach (const QUrl &deletedUrl, affectedItems) { const QString deletedUrlStr = deletedUrl.toString(); // stop all jobs for deletedUrlStr DirectoryDataHash::iterator dit = directoryData.find(deletedUrlStr); if (dit != directoryData.end()) { // we need a copy because stop modifies the list QList listers = (*dit).listersCurrentlyListing; foreach (KCoreDirLister *kdl, listers) { stopListingUrl(kdl, deletedUrl); } // tell listers holding deletedUrl to forget about it // this will stop running updates for deletedUrl as well // we need a copy because forgetDirs modifies the list QList holders = (*dit).listersCurrentlyHolding; foreach (KCoreDirLister *kdl, holders) { // lister's root is the deleted item if (kdl->d->url == deletedUrl) { // tell the view first. It might need the subdirs' items (which forgetDirs will delete) if (!kdl->d->rootFileItem.isNull()) { emit kdl->itemsDeleted(KFileItemList() << kdl->d->rootFileItem); } forgetDirs(kdl); kdl->d->rootFileItem = KFileItem(); } else { const bool treeview = kdl->d->lstDirs.count() > 1; if (!treeview) { emit kdl->clear(); kdl->d->lstDirs.clear(); } else { kdl->d->lstDirs.removeAll(deletedUrl); } forgetDirs(kdl, deletedUrl, treeview); } } } // delete the entry for deletedUrl - should not be needed, it's in // items cached now int count = itemsInUse.remove(deletedUrlStr); Q_ASSERT(count == 0); Q_UNUSED(count); //keep gcc "unused variable" complaining quiet when in release mode } // remove the children from the cache removeDirFromCache(dirUrl); } // delayed updating of files, FAM is flooding us with events void KCoreDirListerCache::processPendingUpdates() { QSet listers; foreach (const QString &file, pendingUpdates) { // always a local path qCDebug(KIO_CORE_DIRLISTER) << file; QUrl u = QUrl::fromLocalFile(file); KFileItem *item = findByUrl(nullptr, u); // search all items if (item) { // we need to refresh the item, because e.g. the permissions can have changed. KFileItem oldItem = *item; item->refresh(); if (!oldItem.cmp(*item)) { listers |= emitRefreshItem(oldItem, *item); } } } pendingUpdates.clear(); Q_FOREACH (KCoreDirLister *kdl, listers) { kdl->d->emitItems(); } // Directories in need of updating foreach (const QString &dir, pendingDirectoryUpdates) { updateDirectory(QUrl::fromLocalFile(dir)); } pendingDirectoryUpdates.clear(); } #ifndef NDEBUG void KCoreDirListerCache::printDebug() { qCDebug(KIO_CORE_DIRLISTER) << "Items in use:"; QHash::const_iterator itu = itemsInUse.constBegin(); const QHash::const_iterator ituend = itemsInUse.constEnd(); for (; itu != ituend; ++itu) { qCDebug(KIO_CORE_DIRLISTER) << " " << itu.key() << "URL:" << itu.value()->url << "rootItem:" << (!itu.value()->rootItem.isNull() ? itu.value()->rootItem.url() : QUrl()) << "autoUpdates refcount:" << itu.value()->autoUpdates << "complete:" << itu.value()->complete << QStringLiteral("with %1 items.").arg(itu.value()->lstItems.count()); } QList listersWithoutJob; qCDebug(KIO_CORE_DIRLISTER) << "Directory data:"; DirectoryDataHash::const_iterator dit = directoryData.constBegin(); for (; dit != directoryData.constEnd(); ++dit) { QString list; foreach (KCoreDirLister *listit, (*dit).listersCurrentlyListing) { list += " 0x" + QString::number(reinterpret_cast(listit), 16); } qCDebug(KIO_CORE_DIRLISTER) << " " << dit.key() << (*dit).listersCurrentlyListing.count() << "listers:" << list; foreach (KCoreDirLister *listit, (*dit).listersCurrentlyListing) { if (!listit->d->m_cachedItemsJobs.isEmpty()) { qCDebug(KIO_CORE_DIRLISTER) << " Lister" << listit << "has CachedItemsJobs" << listit->d->m_cachedItemsJobs; } else if (KIO::ListJob *listJob = jobForUrl(dit.key())) { qCDebug(KIO_CORE_DIRLISTER) << " Lister" << listit << "has ListJob" << listJob; } else { listersWithoutJob.append(listit); } } list.clear(); foreach (KCoreDirLister *listit, (*dit).listersCurrentlyHolding) { list += " 0x" + QString::number(reinterpret_cast(listit), 16); } qCDebug(KIO_CORE_DIRLISTER) << " " << dit.key() << (*dit).listersCurrentlyHolding.count() << "holders:" << list; } QMap< KIO::ListJob *, KIO::UDSEntryList >::Iterator jit = runningListJobs.begin(); qCDebug(KIO_CORE_DIRLISTER) << "Jobs:"; for (; jit != runningListJobs.end(); ++jit) { qCDebug(KIO_CORE_DIRLISTER) << " " << jit.key() << "listing" << joburl(jit.key()) << ":" << (*jit).count() << "entries."; } qCDebug(KIO_CORE_DIRLISTER) << "Items in cache:"; const QList cachedDirs = itemsCached.keys(); foreach (const QString &cachedDir, cachedDirs) { DirItem *dirItem = itemsCached.object(cachedDir); qCDebug(KIO_CORE_DIRLISTER) << " " << cachedDir << "rootItem:" << (!dirItem->rootItem.isNull() ? dirItem->rootItem.url().toString() : QStringLiteral("NULL")) << "with" << dirItem->lstItems.count() << "items."; } // Abort on listers without jobs -after- showing the full dump. Easier debugging. Q_FOREACH (KCoreDirLister *listit, listersWithoutJob) { qCWarning(KIO_CORE) << "Fatal Error: HUH? Lister" << listit << "is supposed to be listing, but has no job!"; abort(); } } #endif KCoreDirLister::KCoreDirLister(QObject *parent) : QObject(parent), d(new Private(this)) { qCDebug(KIO_CORE_DIRLISTER) << "+KCoreDirLister"; d->complete = true; setAutoUpdate(true); setDirOnlyMode(false); setShowingDotFiles(false); } KCoreDirLister::~KCoreDirLister() { qCDebug(KIO_CORE_DIRLISTER) << "~KCoreDirLister" << this; // Stop all running jobs, remove lister from lists if (!kDirListerCache.isDestroyed()) { stop(); kDirListerCache()->forgetDirs(this); } delete d; } bool KCoreDirLister::openUrl(const QUrl &_url, OpenUrlFlags _flags) { // emit the current changes made to avoid an inconsistent treeview if (d->hasPendingChanges && (_flags & Keep)) { emitChanges(); } d->hasPendingChanges = false; return kDirListerCache()->listDir(this, _url, _flags & Keep, _flags & Reload); } void KCoreDirLister::stop() { kDirListerCache()->stop(this); } void KCoreDirLister::stop(const QUrl &_url) { kDirListerCache()->stopListingUrl(this, _url); } bool KCoreDirLister::autoUpdate() const { return d->autoUpdate; } void KCoreDirLister::setAutoUpdate(bool _enable) { if (d->autoUpdate == _enable) { return; } d->autoUpdate = _enable; kDirListerCache()->setAutoUpdate(this, _enable); } bool KCoreDirLister::showingDotFiles() const { return d->settings.isShowingDotFiles; } void KCoreDirLister::setShowingDotFiles(bool _showDotFiles) { if (d->settings.isShowingDotFiles == _showDotFiles) { return; } d->prepareForSettingsChange(); d->settings.isShowingDotFiles = _showDotFiles; } bool KCoreDirLister::dirOnlyMode() const { return d->settings.dirOnlyMode; } void KCoreDirLister::setDirOnlyMode(bool _dirsOnly) { if (d->settings.dirOnlyMode == _dirsOnly) { return; } d->prepareForSettingsChange(); d->settings.dirOnlyMode = _dirsOnly; } QUrl KCoreDirLister::url() const { return d->url; } QList KCoreDirLister::directories() const { return d->lstDirs; } void KCoreDirLister::emitChanges() { d->emitChanges(); } void KCoreDirLister::Private::emitChanges() { if (!hasPendingChanges) { return; } // reset 'hasPendingChanges' now, in case of recursion // (testcase: enabling recursive scan in ktorrent, #174920) hasPendingChanges = false; const Private::FilterSettings newSettings = settings; settings = oldSettings; // temporarily // Fill hash with all items that are currently visible QSet oldVisibleItems; Q_FOREACH (const QUrl &dir, lstDirs) { NonMovableFileItemList *itemList = kDirListerCache()->itemsForDir(dir); if (!itemList) { continue; } foreach (const KFileItem &item, *itemList) { if (isItemVisible(item) && m_parent->matchesMimeFilter(item)) { oldVisibleItems.insert(item.name()); } } } settings = newSettings; Q_FOREACH (const QUrl &dir, lstDirs) { KFileItemList deletedItems; NonMovableFileItemList *itemList = kDirListerCache()->itemsForDir(dir); if (!itemList) { continue; } NonMovableFileItemList::iterator kit = itemList->begin(); const NonMovableFileItemList::iterator kend = itemList->end(); for (; kit != kend; ++kit) { KFileItem &item = *kit; const QString text = item.text(); if (text == QLatin1String(".") || text == QLatin1String("..")) { continue; } const bool wasVisible = oldVisibleItems.contains(item.name()); const bool nowVisible = isItemVisible(item) && m_parent->matchesMimeFilter(item); if (nowVisible && !wasVisible) { addNewItem(dir, item); // takes care of emitting newItem or itemsFilteredByMime } else if (!nowVisible && wasVisible) { deletedItems.append(*kit); } } if (!deletedItems.isEmpty()) { emit m_parent->itemsDeleted(deletedItems); } emitItems(); } oldSettings = settings; } void KCoreDirLister::updateDirectory(const QUrl &_u) { kDirListerCache()->updateDirectory(_u); } bool KCoreDirLister::isFinished() const { return d->complete; } KFileItem KCoreDirLister::rootItem() const { return d->rootFileItem; } KFileItem KCoreDirLister::findByUrl(const QUrl &_url) const { KFileItem *item = kDirListerCache()->findByUrl(this, _url); if (item) { return *item; } else { return KFileItem(); } } KFileItem KCoreDirLister::findByName(const QString &_name) const { return kDirListerCache()->findByName(this, _name); } // ================ public filter methods ================ // void KCoreDirLister::setNameFilter(const QString &nameFilter) { if (d->nameFilter == nameFilter) { return; } d->prepareForSettingsChange(); d->settings.lstFilters.clear(); d->nameFilter = nameFilter; // Split on white space const QStringList list = nameFilter.split(' ', QString::SkipEmptyParts); for (QStringList::const_iterator it = list.begin(); it != list.end(); ++it) { d->settings.lstFilters.append(QRegExp(*it, Qt::CaseInsensitive, QRegExp::Wildcard)); } } QString KCoreDirLister::nameFilter() const { return d->nameFilter; } void KCoreDirLister::setMimeFilter(const QStringList &mimeFilter) { if (d->settings.mimeFilter == mimeFilter) { return; } d->prepareForSettingsChange(); if (mimeFilter.contains(QStringLiteral("application/octet-stream")) || mimeFilter.contains(QStringLiteral("all/allfiles"))) { // all files d->settings.mimeFilter.clear(); } else { d->settings.mimeFilter = mimeFilter; } } void KCoreDirLister::setMimeExcludeFilter(const QStringList &mimeExcludeFilter) { if (d->settings.mimeExcludeFilter == mimeExcludeFilter) { return; } d->prepareForSettingsChange(); d->settings.mimeExcludeFilter = mimeExcludeFilter; } void KCoreDirLister::clearMimeFilter() { d->prepareForSettingsChange(); d->settings.mimeFilter.clear(); d->settings.mimeExcludeFilter.clear(); } QStringList KCoreDirLister::mimeFilters() const { return d->settings.mimeFilter; } bool KCoreDirLister::matchesFilter(const QString &name) const { return doNameFilter(name, d->settings.lstFilters); } bool KCoreDirLister::matchesMimeFilter(const QString &mime) const { return doMimeFilter(mime, d->settings.mimeFilter) && d->doMimeExcludeFilter(mime, d->settings.mimeExcludeFilter); } // ================ protected methods ================ // bool KCoreDirLister::matchesFilter(const KFileItem &item) const { Q_ASSERT(!item.isNull()); if (item.text() == QLatin1String("..")) { return false; } if (!d->settings.isShowingDotFiles && item.isHidden()) { return false; } if (item.isDir() || d->settings.lstFilters.isEmpty()) { return true; } return matchesFilter(item.text()); } bool KCoreDirLister::matchesMimeFilter(const KFileItem &item) const { Q_ASSERT(!item.isNull()); // Don't lose time determining the mimetype if there is no filter if (d->settings.mimeFilter.isEmpty() && d->settings.mimeExcludeFilter.isEmpty()) { return true; } return matchesMimeFilter(item.mimetype()); } bool KCoreDirLister::doNameFilter(const QString &name, const QList &filters) const { for (QList::const_iterator it = filters.begin(); it != filters.end(); ++it) if ((*it).exactMatch(name)) { return true; } return false; } bool KCoreDirLister::doMimeFilter(const QString &mime, const QStringList &filters) const { if (filters.isEmpty()) { return true; } QMimeDatabase db; const QMimeType mimeptr = db.mimeTypeForName(mime); if (!mimeptr.isValid()) { return false; } qCDebug(KIO_CORE_DIRLISTER) << "doMimeFilter: investigating:" << mimeptr.name(); QStringList::const_iterator it = filters.begin(); for (; it != filters.end(); ++it) if (mimeptr.inherits(*it)) { return true; } //else qCDebug(KIO_CORE_DIRLISTER) << "doMimeFilter: compared without result to "<<*it; return false; } bool KCoreDirLister::Private::doMimeExcludeFilter(const QString &mime, const QStringList &filters) const { if (filters.isEmpty()) { return true; } QStringList::const_iterator it = filters.begin(); for (; it != filters.end(); ++it) if ((*it) == mime) { return false; } return true; } void KCoreDirLister::handleError(KIO::Job *job) { qCWarning(KIO_CORE) << job->errorString(); } void KCoreDirLister::handleErrorMessage(const QString &message) { qCWarning(KIO_CORE) << message; } // ================= private methods ================= // void KCoreDirLister::Private::addNewItem(const QUrl &directoryUrl, const KFileItem &item) { if (!isItemVisible(item)) { return; // No reason to continue... bailing out here prevents a mimetype scan. } qCDebug(KIO_CORE_DIRLISTER) << "in" << directoryUrl << "item:" << item.url(); if (m_parent->matchesMimeFilter(item)) { if (!lstNewItems) { lstNewItems = new NewItemsHash; } Q_ASSERT(!item.isNull()); (*lstNewItems)[directoryUrl].append(item); // items not filtered } else { if (!lstMimeFilteredItems) { lstMimeFilteredItems = new KFileItemList; } Q_ASSERT(!item.isNull()); lstMimeFilteredItems->append(item); // only filtered by mime } } void KCoreDirLister::Private::addNewItems(const QUrl &directoryUrl, const NonMovableFileItemList &items) { // TODO: make this faster - test if we have a filter at all first // DF: was this profiled? The matchesFoo() functions should be fast, w/o filters... // Of course if there is no filter and we can do a range-insertion instead of a loop, that might be good. NonMovableFileItemList::const_iterator kit = items.begin(); const NonMovableFileItemList::const_iterator kend = items.end(); for (; kit != kend; ++kit) { addNewItem(directoryUrl, *kit); } } void KCoreDirLister::Private::addRefreshItem(const QUrl &directoryUrl, const KFileItem &oldItem, const KFileItem &item) { const bool refreshItemWasFiltered = !isItemVisible(oldItem) || !m_parent->matchesMimeFilter(oldItem); if (isItemVisible(item) && m_parent->matchesMimeFilter(item)) { if (refreshItemWasFiltered) { if (!lstNewItems) { lstNewItems = new NewItemsHash; } Q_ASSERT(!item.isNull()); (*lstNewItems)[directoryUrl].append(item); } else { if (!lstRefreshItems) { lstRefreshItems = new QList >; } Q_ASSERT(!item.isNull()); lstRefreshItems->append(qMakePair(oldItem, item)); } } else if (!refreshItemWasFiltered) { if (!lstRemoveItems) { lstRemoveItems = new KFileItemList; } // notify the user that the mimetype of a file changed that doesn't match // a filter or does match an exclude filter // This also happens when renaming foo to .foo and dot files are hidden (#174721) Q_ASSERT(!oldItem.isNull()); lstRemoveItems->append(oldItem); } } void KCoreDirLister::Private::emitItems() { NewItemsHash *tmpNew = lstNewItems; lstNewItems = nullptr; KFileItemList *tmpMime = lstMimeFilteredItems; lstMimeFilteredItems = nullptr; QList > *tmpRefresh = lstRefreshItems; lstRefreshItems = nullptr; KFileItemList *tmpRemove = lstRemoveItems; lstRemoveItems = nullptr; if (tmpNew) { QHashIterator it(*tmpNew); while (it.hasNext()) { it.next(); emit m_parent->itemsAdded(it.key(), it.value()); emit m_parent->newItems(it.value()); // compat } delete tmpNew; } if (tmpMime) { emit m_parent->itemsFilteredByMime(*tmpMime); delete tmpMime; } if (tmpRefresh) { emit m_parent->refreshItems(*tmpRefresh); delete tmpRefresh; } if (tmpRemove) { emit m_parent->itemsDeleted(*tmpRemove); delete tmpRemove; } } bool KCoreDirLister::Private::isItemVisible(const KFileItem &item) const { // Note that this doesn't include mime filters, because // of the itemsFilteredByMime signal. Filtered-by-mime items are // considered "visible", they are just visible via a different signal... return (!settings.dirOnlyMode || item.isDir()) && m_parent->matchesFilter(item); } void KCoreDirLister::Private::emitItemsDeleted(const KFileItemList &_items) { KFileItemList items = _items; QMutableListIterator it(items); while (it.hasNext()) { const KFileItem &item = it.next(); if (!isItemVisible(item) || !m_parent->matchesMimeFilter(item)) { it.remove(); } } if (!items.isEmpty()) { emit m_parent->itemsDeleted(items); } } // ================ private slots ================ // void KCoreDirLister::Private::_k_slotInfoMessage(KJob *, const QString &message) { emit m_parent->infoMessage(message); } void KCoreDirLister::Private::_k_slotPercent(KJob *job, unsigned long pcnt) { jobData[static_cast(job)].percent = pcnt; int result = 0; KIO::filesize_t size = 0; QMap< KIO::ListJob *, Private::JobData >::Iterator dataIt = jobData.begin(); while (dataIt != jobData.end()) { result += (*dataIt).percent * (*dataIt).totalSize; size += (*dataIt).totalSize; ++dataIt; } if (size != 0) { result /= size; } else { result = 100; } emit m_parent->percent(result); } void KCoreDirLister::Private::_k_slotTotalSize(KJob *job, qulonglong size) { jobData[static_cast(job)].totalSize = size; KIO::filesize_t result = 0; QMap< KIO::ListJob *, Private::JobData >::Iterator dataIt = jobData.begin(); while (dataIt != jobData.end()) { result += (*dataIt).totalSize; ++dataIt; } emit m_parent->totalSize(result); } void KCoreDirLister::Private::_k_slotProcessedSize(KJob *job, qulonglong size) { jobData[static_cast(job)].processedSize = size; KIO::filesize_t result = 0; QMap< KIO::ListJob *, Private::JobData >::Iterator dataIt = jobData.begin(); while (dataIt != jobData.end()) { result += (*dataIt).processedSize; ++dataIt; } emit m_parent->processedSize(result); } void KCoreDirLister::Private::_k_slotSpeed(KJob *job, unsigned long spd) { jobData[static_cast(job)].speed = spd; int result = 0; QMap< KIO::ListJob *, Private::JobData >::Iterator dataIt = jobData.begin(); while (dataIt != jobData.end()) { result += (*dataIt).speed; ++dataIt; } emit m_parent->speed(result); } uint KCoreDirLister::Private::numJobs() { #ifdef DEBUG_CACHE // This code helps detecting stale entries in the jobData map. qCDebug(KIO_CORE_DIRLISTER) << m_parent << "numJobs:" << jobData.count(); QMapIterator it(jobData); while (it.hasNext()) { it.next(); qCDebug(KIO_CORE_DIRLISTER) << (void*)it.key(); qCDebug(KIO_CORE_DIRLISTER) << it.key(); } #endif return jobData.count(); } void KCoreDirLister::Private::jobDone(KIO::ListJob *job) { jobData.remove(job); } void KCoreDirLister::jobStarted(KIO::ListJob *job) { Private::JobData data; data.speed = 0; data.percent = 0; data.processedSize = 0; data.totalSize = 0; d->jobData.insert(job, data); d->complete = false; } void KCoreDirLister::Private::connectJob(KIO::ListJob *job) { m_parent->connect(job, SIGNAL(infoMessage(KJob*,QString,QString)), m_parent, SLOT(_k_slotInfoMessage(KJob*,QString))); m_parent->connect(job, SIGNAL(percent(KJob*,ulong)), m_parent, SLOT(_k_slotPercent(KJob*,ulong))); m_parent->connect(job, SIGNAL(totalSize(KJob*,qulonglong)), m_parent, SLOT(_k_slotTotalSize(KJob*,qulonglong))); m_parent->connect(job, SIGNAL(processedSize(KJob*,qulonglong)), m_parent, SLOT(_k_slotProcessedSize(KJob*,qulonglong))); m_parent->connect(job, SIGNAL(speed(KJob*,ulong)), m_parent, SLOT(_k_slotSpeed(KJob*,ulong))); } KFileItemList KCoreDirLister::items(WhichItems which) const { return itemsForDir(url(), which); } KFileItemList KCoreDirLister::itemsForDir(const QUrl &dir, WhichItems which) const { NonMovableFileItemList *allItems = kDirListerCache()->itemsForDir(dir); if (!allItems) { return KFileItemList(); } if (which == AllItems) { return allItems->toKFileItemList(); } else { // only items passing the filters KFileItemList result; NonMovableFileItemList::const_iterator kit = allItems->constBegin(); const NonMovableFileItemList::const_iterator kend = allItems->constEnd(); for (; kit != kend; ++kit) { const KFileItem &item = *kit; if (d->isItemVisible(item) && matchesMimeFilter(item)) { result.append(item); } } return result; } } bool KCoreDirLister::delayedMimeTypes() const { return d->delayedMimeTypes; } void KCoreDirLister::setDelayedMimeTypes(bool delayedMimeTypes) { d->delayedMimeTypes = delayedMimeTypes; } // called by KCoreDirListerCache::slotRedirection void KCoreDirLister::Private::redirect(const QUrl &oldUrl, const QUrl &newUrl, bool keepItems) { if (url.matches(oldUrl, QUrl::StripTrailingSlash)) { if (!keepItems) { rootFileItem = KFileItem(); } else { rootFileItem.setUrl(newUrl); } url = newUrl; } const int idx = lstDirs.indexOf(oldUrl); if (idx == -1) { qCWarning(KIO_CORE) << "Unexpected redirection from" << oldUrl << "to" << newUrl << "but this dirlister is currently listing/holding" << lstDirs; } else { lstDirs[ idx ] = newUrl; } if (lstDirs.count() == 1) { if (!keepItems) { emit m_parent->clear(); } emit m_parent->redirection(newUrl); } else { if (!keepItems) { emit m_parent->clear(oldUrl); } } emit m_parent->redirection(oldUrl, newUrl); } void KCoreDirListerCacheDirectoryData::moveListersWithoutCachedItemsJob(const QUrl &url) { // Move dirlisters from listersCurrentlyListing to listersCurrentlyHolding, // but not those that are still waiting on a CachedItemsJob... // Unit-testing note: // Run kdirmodeltest in valgrind to hit the case where an update // is triggered while a lister has a CachedItemsJob (different timing...) QMutableListIterator lister_it(listersCurrentlyListing); while (lister_it.hasNext()) { KCoreDirLister *kdl = lister_it.next(); if (!kdl->d->cachedItemsJobForUrl(url)) { // OK, move this lister from "currently listing" to "currently holding". // Huh? The KCoreDirLister was present twice in listersCurrentlyListing, or was in both lists? Q_ASSERT(!listersCurrentlyHolding.contains(kdl)); if (!listersCurrentlyHolding.contains(kdl)) { listersCurrentlyHolding.append(kdl); } lister_it.remove(); } else { qCDebug(KIO_CORE_DIRLISTER) << "Not moving" << kdl << "to listersCurrentlyHolding because it still has job" << kdl->d->m_cachedItemsJobs; } } } KFileItem KCoreDirLister::cachedItemForUrl(const QUrl &url) { if (kDirListerCache.exists()) { return kDirListerCache()->itemForUrl(url); } else { return {}; } } QSet KCoreDirListerCache::filesInDotHiddenForDir(const QString& dir) { const QString path = dir + "/.hidden"; QFile dotHiddenFile(path); if (dotHiddenFile.exists()) { const QDateTime mtime = QFileInfo(dotHiddenFile).lastModified(); const CacheHiddenFile *cachedDotHiddenFile = m_cacheHiddenFiles.object(path); if (cachedDotHiddenFile && mtime <= cachedDotHiddenFile->mtime) { // ".hidden" is in cache and still valid (the file was not modified since then), // so return it return cachedDotHiddenFile->listedFiles; } else { // read the ".hidden" file, then cache it and return it if (dotHiddenFile.open(QIODevice::ReadOnly | QIODevice::Text)) { QSet filesToHide; QTextStream stream(&dotHiddenFile); while (!stream.atEnd()) { QString name = stream.readLine(); if (!name.isEmpty()) { filesToHide.insert(name); } } m_cacheHiddenFiles.insert(path, new CacheHiddenFile(mtime, filesToHide)); return filesToHide; } } } return QSet(); } #include "moc_kcoredirlister.cpp" #include "moc_kcoredirlister_p.cpp" diff --git a/src/core/kcoredirlister.h b/src/core/kcoredirlister.h index ae430f19..4d20fe81 100644 --- a/src/core/kcoredirlister.h +++ b/src/core/kcoredirlister.h @@ -1,632 +1,632 @@ /* This file is part of the KDE project Copyright (C) 1999 David Faure 2001, 2002, 2004-2006 Michael Brade 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. */ #ifndef KCOREDIRLISTER_H #define KCOREDIRLISTER_H #include "kfileitem.h" #include "kdirnotify.h" // TODO SIC: remove -#include -#include +#include +#include #include class KJob; namespace KIO { class Job; class ListJob; } /** * @class KCoreDirLister kcoredirlister.h * * @short Helper class for the kiojob used to list and update a directory. * * The dir lister deals with the kiojob used to list and update a directory * and has signals for the user of this class (e.g. konqueror view or * kdesktop) to create/destroy its items when asked. * * This class is independent from the graphical representation of the dir * (icon container, tree view, ...) and it stores the items (as KFileItems). * * Typical usage : * @li Create an instance. * @li Connect to at least update, clear, itemsAdded, and itemsDeleted. * @li Call openUrl - the signals will be called. * @li Reuse the instance when opening a new url (openUrl). * @li Destroy the instance when not needed anymore (usually destructor). * * Advanced usage : call openUrl with OpenUrlFlag::Keep to list directories * without forgetting the ones previously read (e.g. for a tree view) * * @author Michael Brade */ class KIOCORE_EXPORT KCoreDirLister : public QObject { friend class KCoreDirListerCache; friend struct KCoreDirListerCacheDirectoryData; Q_OBJECT Q_PROPERTY(bool autoUpdate READ autoUpdate WRITE setAutoUpdate) Q_PROPERTY(bool showingDotFiles READ showingDotFiles WRITE setShowingDotFiles) Q_PROPERTY(bool dirOnlyMode READ dirOnlyMode WRITE setDirOnlyMode) Q_PROPERTY(bool delayedMimeTypes READ delayedMimeTypes WRITE setDelayedMimeTypes) Q_PROPERTY(QString nameFilter READ nameFilter WRITE setNameFilter) Q_PROPERTY(QStringList mimeFilter READ mimeFilters WRITE setMimeFilter RESET clearMimeFilter) public: enum OpenUrlFlag { NoFlags = 0x0, ///< No additional flags specified. Keep = 0x1, ///< Previous directories aren't forgotten ///< (they are still watched by kdirwatch and their items ///< are kept for this KCoreDirLister). This is useful for e.g. ///< a treeview. Reload = 0x2 ///< Indicates whether to use the cache or to reread ///< the directory from the disk. ///< Use only when opening a dir not yet listed by this lister ///< without using the cache. Otherwise use updateDirectory. }; Q_DECLARE_FLAGS(OpenUrlFlags, OpenUrlFlag) /** * Create a directory lister. */ KCoreDirLister(QObject *parent = nullptr); /** * Destroy the directory lister. */ virtual ~KCoreDirLister(); /** * Run the directory lister on the given url. * * This method causes KCoreDirLister to emit _all_ the items of @p _url, in any case. * Depending on _flags, either clear() or clear(const QUrl &) will be * emitted first. * * The newItems() signal may be emitted more than once to supply you * with KFileItems, up until the signal completed() is emitted * (and isFinished() returns true). * * @param _url the directory URL. * @param _flags whether to keep previous directories, and whether to reload, see OpenUrlFlags * @return true if successful, * false otherwise (e.g. invalid @p _url) */ virtual bool openUrl(const QUrl &_url, OpenUrlFlags _flags = NoFlags); /** * Stop listing all directories currently being listed. * * Emits canceled() if there was at least one job running. * Emits canceled( const QUrl& ) for each stopped job if * there are at least two directories being watched by KCoreDirLister. */ virtual void stop(); /** * Stop listing the given directory. * * Emits canceled() if the killed job was the last running one. * Emits canceled( const QUrl& ) for the killed job if * there are at least two directories being watched by KCoreDirLister. * No signal is emitted if there was no job running for @p _url. * @param _url the directory URL */ virtual void stop(const QUrl &_url); /** * @return true if the "delayed mimetypes" feature was enabled * @see setDelayedMimeTypes */ bool delayedMimeTypes() const; /** * Delayed mimetypes feature: * If enabled, mime types will be fetched on demand, which leads to a * faster initial directory listing, where icons get progressively replaced * with the correct one while KMimeTypeResolver is going through the items * with unknown or imprecise mimetype (e.g. files with no extension or an * unknown extension). */ void setDelayedMimeTypes(bool delayedMimeTypes); /** * Checks whether KDirWatch will automatically update directories. This is * enabled by default. * @return true if KDirWatch is used to automatically update directories. */ bool autoUpdate() const; /** * Enable/disable automatic directory updating, when a directory changes * (using KDirWatch). * @param enable true to enable, false to disable */ virtual void setAutoUpdate(bool enable); /** * Checks whether hidden files (files beginning with a dot) will be * shown. * By default this option is disabled (hidden files will be not shown). * @return true if dot files are shown, false otherwise * @see setShowingDotFiles() */ bool showingDotFiles() const; /** * Changes the "is viewing dot files" setting. * You need to call emitChanges() afterwards. * By default this option is disabled (hidden files will not be shown). * @param _showDotFiles true to enable showing hidden files, false to * disable * @see showingDotFiles() */ virtual void setShowingDotFiles(bool _showDotFiles); /** * Checks whether the KCoreDirLister only lists directories or all * files. * By default this option is disabled (all files will be shown). * @return true if setDirOnlyMode(true) was called */ bool dirOnlyMode() const; /** * Call this to list only directories. * You need to call emitChanges() afterwards. * By default this option is disabled (all files will be shown). * @param dirsOnly true to list only directories */ virtual void setDirOnlyMode(bool dirsOnly); /** * Returns the top level URL that is listed by this KCoreDirLister. * It might be different from the one given with openUrl() if there was a * redirection. If you called openUrl() with OpenUrlFlag::Keep this is the * first url opened (e.g. in a treeview this is the root). * * @return the url used by this instance to list the files. */ QUrl url() const; /** * Returns all URLs that are listed by this KCoreDirLister. This is only * useful if you called openUrl() with OpenUrlFlag::Keep, as it happens in a * treeview, for example. (Note that the base url is included in the list * as well, of course.) * * @return the list of all listed URLs */ QList directories() const; /** * Actually emit the changes made with setShowingDotFiles, setDirOnlyMode, * setNameFilter and setMimeFilter. */ virtual void emitChanges(); /** * Update the directory @p _dir. This method causes KCoreDirLister to _only_ emit * the items of @p _dir that actually changed compared to the current state in the * cache and updates the cache. * * The current implementation calls updateDirectory automatically for * local files, using KDirWatch (if autoUpdate() is true), but it might be * useful to force an update manually. * * @param _dir the directory URL */ virtual void updateDirectory(const QUrl &_dir); /** * Returns true if no io operation is currently in progress. * @return true if finished, false otherwise */ bool isFinished() const; /** * Returns the file item of the URL. * * Can return an empty KFileItem. * @return the file item for url() itself (".") */ KFileItem rootItem() const; /** * Find an item by its URL. * @param _url the item URL * @return the KFileItem */ virtual KFileItem findByUrl(const QUrl &_url) const; /** * Find an item by its name. * @param name the item name * @return the KFileItem */ virtual KFileItem findByName(const QString &name) const; /** * Set a name filter to only list items matching this name, e.g. "*.cpp". * * You can set more than one filter by separating them with whitespace, e.g * "*.cpp *.h". * Note: the directory is not automatically reloaded. * You need to call emitChanges() afterwards. * * @param filter the new filter, QString() to disable filtering * @see matchesFilter */ virtual void setNameFilter(const QString &filter); /** * Returns the current name filter, as set via setNameFilter() * @return the current name filter, can be QString() if filtering * is turned off */ QString nameFilter() const; /** * Set mime-based filter to only list items matching the given mimetypes. * * NOTE: setting the filter does not automatically reload directory. * Also calling this function will not affect any named filter already set. * * You need to call emitChanges() afterwards. * * @param mimeList a list of mime-types. * * @see clearMimeFilter * @see matchesMimeFilter */ virtual void setMimeFilter(const QStringList &mimeList); /** * Filtering should be done with KFileFilter. This will be implemented in a later * revision of KCoreDirLister. This method may be removed then. * * Set mime-based exclude filter to only list items not matching the given mimetypes * * NOTE: setting the filter does not automatically reload directory. * Also calling this function will not affect any named filter already set. * * @param mimeList a list of mime-types. * @see clearMimeFilter * @see matchesMimeFilter * @internal */ void setMimeExcludeFilter(const QStringList &mimeList); /** * Clears the mime based filter. * * You need to call emitChanges() afterwards. * * @see setMimeFilter */ virtual void clearMimeFilter(); /** * Returns the list of mime based filters, as set via setMimeFilter(). * @return the list of mime based filters. Empty, when no mime filter is set. */ QStringList mimeFilters() const; /** * Checks whether @p name matches a filter in the list of name filters. * @return true if @p name matches a filter in the list, * otherwise false. * @see setNameFilter */ bool matchesFilter(const QString &name) const; /** * Checks whether @p mime matches a filter in the list of mime types * @param mime the mimetype to find in the filter list. * @return true if @p name matches a filter in the list, * otherwise false. * @see setMimeFilter. */ bool matchesMimeFilter(const QString &mime) const; /** * Used by items() and itemsForDir() to specify whether you want * all items for a directory or just the filtered ones. */ enum WhichItems { AllItems = 0, FilteredItems = 1 }; /** * Returns the items listed for the current url(). * This method will NOT start listing a directory, you should only call * this when receiving the finished() signal. * * The items in the KFileItemList are copies of the items used * by KCoreDirLister. * * @param which specifies whether the returned list will contain all entries * or only the ones that passed the nameFilter(), mimeFilter(), * etc. Note that the latter causes iteration over all the * items, filtering them. If this is too slow for you, use the * newItems() signal, sending out filtered items in chunks. * @return the items listed for the current url(). */ KFileItemList items(WhichItems which = FilteredItems) const; /** * Returns the items listed for the given @p dir. * This method will NOT start listing @p dir, you should only call * this when receiving the finished() signal. * * The items in the KFileItemList are copies of the items used * by KCoreDirLister. * * @param dir specifies the url for which the items should be returned. This * is only useful if you use KCoreDirLister with multiple URLs * i.e. using bool OpenUrlFlag::Keep in openUrl(). * @param which specifies whether the returned list will contain all entries * or only the ones that passed the nameFilter, mimeFilter, etc. * Note that the latter causes iteration over all the items, * filtering them. If this is too slow for you, use the * newItems() signal, sending out filtered items in chunks. * @return the items listed for @p dir. */ KFileItemList itemsForDir(const QUrl &dir, WhichItems which = FilteredItems) const; /** * Return the KFileItem for the given URL, if we listed it recently * and it's still in the cache - which is always the case if a directory * view is currently showing this item. If not, then it might be in the * cache, or it might not, in which case you get a null KFileItem. * If you really need a KFileItem for this URL in all cases, then use * KIO::stat() instead. * * @since 4.2 */ static KFileItem cachedItemForUrl(const QUrl &url); Q_SIGNALS: /** * Tell the view that we started to list @p _url. NOTE: this does _not_ imply that there * is really a job running! I.e. KCoreDirLister::jobs() may return an empty list. In this case * the items are taken from the cache. * * The view knows that openUrl should start it, so it might seem useless, * but the view also needs to know when an automatic update happens. * @param _url the URL to list */ void started(const QUrl &_url); /** * Tell the view that listing is finished. There are no jobs running anymore. */ void completed(); /** * Tell the view that the listing of the directory @p _url is finished. * There might be other running jobs left. * @param _url the directory URL */ void completed(const QUrl &_url); /** * Tell the view that the user canceled the listing. No running jobs are left. */ void canceled(); /** * Tell the view that the listing of the directory @p _url was canceled. * There might be other running jobs left. * @param _url the directory URL */ void canceled(const QUrl &_url); /** * Signal a redirection. * Only emitted if there's just one directory to list, i.e. most * probably openUrl() has been called without OpenUrlFlag::Keep. * @param _url the new URL */ void redirection(const QUrl &_url); /** * Signal a redirection. * @param oldUrl the original URL * @param newUrl the new URL */ void redirection(const QUrl &oldUrl, const QUrl &newUrl); /** * Signal to clear all items. * Make sure to connect to this signal to avoid doubled items. */ void clear(); /** * Signal to empty the directory @p _url. * It is only emitted if the lister is holding more than one directory. * @param _url the directory that will be emptied */ void clear(const QUrl &_url); /** * Signal new items. * * @param items a list of new items */ void newItems(const KFileItemList &items); /** * Signal that new items were found during directory listing. * Alternative signal emitted at the same time as newItems(), * but itemsAdded also passes the url of the parent directory. * * @param items a list of new items * @since 4.2 */ void itemsAdded(const QUrl &directoryUrl, const KFileItemList &items); /** * Send a list of items filtered-out by mime-type. * @param items the list of filtered items */ void itemsFilteredByMime(const KFileItemList &items); /** * Signal that items have been deleted * * @since 4.1.2 * @param items the list of deleted items */ void itemsDeleted(const KFileItemList &items); /** * Signal an item to refresh (its mimetype/icon/name has changed). * Note: KFileItem::refresh has already been called on those items. * @param items the items to refresh. This is a list of pairs, where * the first item in the pair is the OLD item, and the second item is the * NEW item. This allows to track which item has changed, especially after * a renaming. */ void refreshItems(const QList > &items); /** * Emitted to display information about running jobs. * Examples of message are "Resolving host", "Connecting to host...", etc. * @param msg the info message */ void infoMessage(const QString &msg); /** * Progress signal showing the overall progress of the KCoreDirLister. * This allows using a progress bar very easily. (see QProgressBar) * @param percent the progress in percent */ void percent(int percent); /** * Emitted when we know the size of the jobs. * @param size the total size in bytes */ void totalSize(KIO::filesize_t size); /** * Regularly emitted to show the progress of this KCoreDirLister. * @param size the processed size in bytes */ void processedSize(KIO::filesize_t size); /** * Emitted to display information about the speed of the jobs. * @param bytes_per_second the speed in bytes/s */ void speed(int bytes_per_second); protected: /// @deprecated and unused, ignore this enum Changes { NONE = 0, NAME_FILTER = 1, MIME_FILTER = 2, DOT_FILES = 4, DIR_ONLY_MODE = 8 }; /** * Called for every new item before emitting newItems(). * You may reimplement this method in a subclass to implement your own * filtering. * The default implementation filters out ".." and everything not matching * the name filter(s) * @return true if the item is "ok". * false if the item shall not be shown in a view, e.g. * files not matching a pattern *.cpp ( KFileItem::isHidden()) * @see matchesFilter * @see setNameFilter */ virtual bool matchesFilter(const KFileItem &) const; /** * Called for every new item before emitting newItems(). * You may reimplement this method in a subclass to implement your own * filtering. * The default implementation filters out ".." and everything not matching * the name filter(s) * @return true if the item is "ok". * false if the item shall not be shown in a view, e.g. * files not matching a pattern *.cpp ( KFileItem::isHidden()) * @see matchesMimeFilter * @see setMimeFilter */ virtual bool matchesMimeFilter(const KFileItem &) const; /** * Called by the public matchesFilter() to do the * actual filtering. Those methods may be reimplemented to customize * filtering. * @param name the name to filter * @param filters a list of regular expressions for filtering */ virtual bool doNameFilter(const QString &name, const QList &filters) const; /** * Called by the public matchesMimeFilter() to do the * actual filtering. Those methods may be reimplemented to customize * filtering. * @param mime the mime type to filter * @param filters the list of mime types to filter */ virtual bool doMimeFilter(const QString &mime, const QStringList &filters) const; /** * Reimplement to customize error handling */ virtual void handleError(KIO::Job *); /** * Reimplement to customize error handling * @since 5.0 */ virtual void handleErrorMessage(const QString &message); /** * Reimplemented by KDirLister to associate windows with jobs * @since 5.0 */ virtual void jobStarted(KIO::ListJob *); private: class Private; Private *const d; friend class Private; Q_PRIVATE_SLOT(d, void _k_slotInfoMessage(KJob *, const QString &)) Q_PRIVATE_SLOT(d, void _k_slotPercent(KJob *, unsigned long)) Q_PRIVATE_SLOT(d, void _k_slotTotalSize(KJob *, qulonglong)) Q_PRIVATE_SLOT(d, void _k_slotProcessedSize(KJob *, qulonglong)) Q_PRIVATE_SLOT(d, void _k_slotSpeed(KJob *, unsigned long)) }; Q_DECLARE_OPERATORS_FOR_FLAGS(KCoreDirLister::OpenUrlFlags) #endif diff --git a/src/core/kcoredirlister_p.h b/src/core/kcoredirlister_p.h index 774ed45d..671fc0f3 100644 --- a/src/core/kcoredirlister_p.h +++ b/src/core/kcoredirlister_p.h @@ -1,586 +1,586 @@ /* This file is part of the KDE project Copyright (C) 2002-2006 Michael Brade 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. */ #ifndef kdirlister_p_h #define kdirlister_p_h #include "kfileitem.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include #include #include /** * KCoreDirListerCache stores pointers to KFileItems internally and expects that * these pointers remain valid, even if the number of items in the list 'lstItems' * in KCoreDirLister::DirItem changes. Since such changes in a QList * may change the internal memory layout of the QList, pointers to KFileItems in a * QList might become invalid. * * Therefore, we make 'lstItems' a QList, where * NonMovableFileItem is a class that inherits KFileItem, but is not declared as a * Q_MOVABLE_TYPE. This forces QList to never move these items in memory, which is * achieved by allocating space for each individual item, and store pointers to * these items in the contiguous region in memory. * * TODO: Try to get rid of the raw KFileItem pointers in KCoreDirListerCache, and * replace all occurrences of NonMovableFileItem(List) with KFileItem(List). */ class NonMovableFileItem : public KFileItem { public: NonMovableFileItem(const KFileItem &item) : KFileItem(item) {} }; class NonMovableFileItemList : public QList { public: NonMovableFileItemList() {} KFileItem findByName(const QString &fileName) const { const_iterator it = begin(); const const_iterator itend = end(); for (; it != itend; ++it) { if ((*it).name() == fileName) { return *it; } } return KFileItem(); } KFileItemList toKFileItemList() const { KFileItemList result; result.reserve(count()); foreach (const NonMovableFileItem &item, *this) { result.append(item); } return result; } }; class KCoreDirLister; namespace KIO { class Job; class ListJob; } class OrgKdeKDirNotifyInterface; struct KCoreDirListerCacheDirectoryData; class Q_DECL_HIDDEN KCoreDirLister::Private { public: Private(KCoreDirLister *parent) : m_parent(parent) { complete = false; autoUpdate = false; delayedMimeTypes = false; rootFileItem = KFileItem(); lstNewItems = nullptr; lstRefreshItems = nullptr; lstMimeFilteredItems = nullptr; lstRemoveItems = nullptr; hasPendingChanges = false; } void _k_emitCachedItems(const QUrl &, bool, bool); void _k_slotInfoMessage(KJob *, const QString &); void _k_slotPercent(KJob *, unsigned long); void _k_slotTotalSize(KJob *, qulonglong); void _k_slotProcessedSize(KJob *, qulonglong); void _k_slotSpeed(KJob *, unsigned long); bool doMimeExcludeFilter(const QString &mimeExclude, const QStringList &filters) const; void connectJob(KIO::ListJob *); void jobDone(KIO::ListJob *); uint numJobs(); void addNewItem(const QUrl &directoryUrl, const KFileItem &item); void addNewItems(const QUrl &directoryUrl, const NonMovableFileItemList &items); void addRefreshItem(const QUrl &directoryUrl, const KFileItem &oldItem, const KFileItem &item); void emitItems(); void emitItemsDeleted(const KFileItemList &items); /** * Redirect this dirlister from oldUrl to newUrl. * @param keepItems if true, keep the fileitems (e.g. when renaming an existing dir); * if false, clear out everything (e.g. when redirecting during listing). */ void redirect(const QUrl &oldUrl, const QUrl &newUrl, bool keepItems); /** * Should this item be visible according to the current filter settings? */ bool isItemVisible(const KFileItem &item) const; void prepareForSettingsChange() { if (!hasPendingChanges) { hasPendingChanges = true; oldSettings = settings; } } void emitChanges(); class CachedItemsJob; CachedItemsJob *cachedItemsJobForUrl(const QUrl &url) const; KCoreDirLister *m_parent; /** * List of dirs handled by this dirlister. The first entry is the base URL. * For a tree view, it contains all the dirs shown. */ QList lstDirs; // toplevel URL QUrl url; bool complete: 1; bool autoUpdate: 1; bool delayedMimeTypes: 1; bool hasPendingChanges: 1; // i.e. settings != oldSettings struct JobData { long unsigned int percent, speed; KIO::filesize_t processedSize, totalSize; }; QMap jobData; // file item for the root itself (".") KFileItem rootFileItem; typedef QHash NewItemsHash; NewItemsHash *lstNewItems; QList > *lstRefreshItems; KFileItemList *lstMimeFilteredItems, *lstRemoveItems; QList m_cachedItemsJobs; QString nameFilter; // parsed into lstFilters struct FilterSettings { FilterSettings() : isShowingDotFiles(false), dirOnlyMode(false) {} bool isShowingDotFiles; bool dirOnlyMode; QList lstFilters; QStringList mimeFilter; QStringList mimeExcludeFilter; }; FilterSettings settings; FilterSettings oldSettings; friend class KCoreDirListerCache; }; /** * Design of the cache: * There is a single KCoreDirListerCache for the whole process. * It holds all the items used by the dir listers (itemsInUse) * as well as a cache of the recently used items (itemsCached). * Those items are grouped by directory (a DirItem represents a whole directory). * * KCoreDirListerCache also runs all the jobs for listing directories, whether they are for * normal listing or for updates. * For faster lookups, it also stores a hash table, which gives for a directory URL: * - the dirlisters holding that URL (listersCurrentlyHolding) * - the dirlisters currently listing that URL (listersCurrentlyListing) */ class KCoreDirListerCache : public QObject { Q_OBJECT public: KCoreDirListerCache(); // only called by K_GLOBAL_STATIC ~KCoreDirListerCache(); void updateDirectory(const QUrl &dir); KFileItem itemForUrl(const QUrl &url) const; NonMovableFileItemList *itemsForDir(const QUrl &dir) const; bool listDir(KCoreDirLister *lister, const QUrl &_url, bool _keep, bool _reload); // stop all running jobs for lister void stop(KCoreDirLister *lister, bool silent = false); // stop just the job listing url for lister void stopListingUrl(KCoreDirLister *lister, const QUrl &_url, bool silent = false); void setAutoUpdate(KCoreDirLister *lister, bool enable); void forgetDirs(KCoreDirLister *lister); void forgetDirs(KCoreDirLister *lister, const QUrl &_url, bool notify); KFileItem findByName(const KCoreDirLister *lister, const QString &_name) const; // findByUrl returns a pointer so that it's possible to modify the item. // See itemForUrl for the version that returns a readonly kfileitem. // @param lister can be 0. If set, it is checked that the url is held by the lister KFileItem *findByUrl(const KCoreDirLister *lister, const QUrl &url) const; // Called by CachedItemsJob: // Emits the cached items, for this lister and this url void emitItemsFromCache(KCoreDirLister::Private::CachedItemsJob *job, KCoreDirLister *lister, const QUrl &_url, bool _reload, bool _emitCompleted); // Called by CachedItemsJob: void forgetCachedItemsJob(KCoreDirLister::Private::CachedItemsJob *job, KCoreDirLister *lister, const QUrl &url); public Q_SLOTS: /** * Notify that files have been added in @p directory * The receiver will list that directory again to find * the new items (since it needs more than just the names anyway). * Connected to the DBus signal from the KDirNotify interface. */ void slotFilesAdded(const QString &urlDirectory); /** * Notify that files have been deleted. * This call passes the exact urls of the deleted files * so that any view showing them can simply remove them * or be closed (if its current dir was deleted) * Connected to the DBus signal from the KDirNotify interface. */ void slotFilesRemoved(const QStringList &fileList); /** * Notify that files have been changed. * At the moment, this is only used for new icon, but it could be * used for size etc. as well. * Connected to the DBus signal from the KDirNotify interface. */ void slotFilesChanged(const QStringList &fileList); void slotFileRenamed(const QString &srcUrl, const QString &dstUrl, const QString &dstPath); private Q_SLOTS: void slotFileDirty(const QString &_file); void slotFileCreated(const QString &_file); void slotFileDeleted(const QString &_file); void slotEntries(KIO::Job *job, const KIO::UDSEntryList &entries); void slotResult(KJob *j); void slotRedirection(KIO::Job *job, const QUrl &url); void slotUpdateEntries(KIO::Job *job, const KIO::UDSEntryList &entries); void slotUpdateResult(KJob *job); void processPendingUpdates(); private: void itemsAddedInDirectory(const QUrl &url); class DirItem; DirItem *dirItemForUrl(const QUrl &dir) const; bool validUrl(KCoreDirLister *lister, const QUrl &_url) const; void stopListJob(const QString &url, bool silent); KIO::ListJob *jobForUrl(const QString &url, KIO::ListJob *not_job = nullptr); const QUrl &joburl(KIO::ListJob *job); void killJob(KIO::ListJob *job); // Called when something tells us that the directory @p url has changed. // Returns true if @p url is held by some lister (meaning: do the update now) // otherwise mark the cached item as not-up-to-date for later and return false bool checkUpdate(const QUrl &url); // Helper method for slotFileDirty void handleFileDirty(const QUrl &url); void handleDirDirty(const QUrl &url); // when there were items deleted from the filesystem all the listers holding // the parent directory need to be notified, the items have to be deleted // and removed from the cache including all the children. void deleteUnmarkedItems(const QList&, NonMovableFileItemList &lstItems, const QHash &itemsToDelete); // Helper method called when we know that a list of items was deleted void itemsDeleted(const QList &listers, const KFileItemList &deletedItems); void slotFilesRemoved(const QList &urls); // common for slotRedirection and slotFileRenamed void renameDir(const QUrl &oldUrl, const QUrl &url); // common for deleteUnmarkedItems and slotFilesRemoved void deleteDir(const QUrl &dirUrl); // remove directory from cache (itemsCached), including all child dirs void removeDirFromCache(const QUrl &dir); // helper for renameDir void emitRedirections(const QUrl &oldUrl, const QUrl &url); /** * Emits refreshItem() in the directories that cared for oldItem. * The caller has to remember to call emitItems in the set of dirlisters returned * (but this allows to buffer change notifications) */ QSet emitRefreshItem(const KFileItem &oldItem, const KFileItem &fileitem); /** * When KDirWatch tells us that something changed in "dir", we need to * also notify the dirlisters that are listing a symlink to "dir" (#213799) */ QList directoriesForCanonicalPath(const QUrl &dir) const; /** * Returns the names listed in dir's ".hidden" file, if it exists. * If a file named ".hidden" exists in the @p dir directory, this method * returns all the file names listed in that file. If it doesn't exist, an * empty set is returned. * @param dir path to the target directory. * @return names listed in the directory's ".hidden" file (empty if it doesn't exist). */ QSet filesInDotHiddenForDir(const QString& dir); #ifndef NDEBUG void printDebug(); #endif class DirItem { public: DirItem(const QUrl &dir, const QString &canonicalPath) : url(dir), m_canonicalPath(canonicalPath) { autoUpdates = 0; complete = false; watchedWhileInCache = false; } ~DirItem() { if (autoUpdates) { if (KDirWatch::exists() && url.isLocalFile()) { KDirWatch::self()->removeDir(m_canonicalPath); } // Since sendSignal goes through D-Bus, QCoreApplication has to be available // which might not be the case anymore from a global static dtor like the // lister cache if (QCoreApplication::instance()) { sendSignal(false, url); } } lstItems.clear(); } void sendSignal(bool entering, const QUrl &url) { // Note that "entering" means "start watching", and "leaving" means "stop watching" // (i.e. it's not when the user leaves the directory, it's when the directory is removed from the cache) if (entering) { org::kde::KDirNotify::emitEnteredDirectory(url); } else { org::kde::KDirNotify::emitLeftDirectory(url); } } void redirect(const QUrl &newUrl) { if (autoUpdates) { if (url.isLocalFile()) { KDirWatch::self()->removeDir(m_canonicalPath); } sendSignal(false, url); if (newUrl.isLocalFile()) { m_canonicalPath = QFileInfo(newUrl.toLocalFile()).canonicalFilePath(); KDirWatch::self()->addDir(m_canonicalPath); } sendSignal(true, newUrl); } url = newUrl; if (!rootItem.isNull()) { rootItem.setUrl(newUrl); } } void incAutoUpdate() { if (autoUpdates++ == 0) { if (url.isLocalFile()) { KDirWatch::self()->addDir(m_canonicalPath); } sendSignal(true, url); } } void decAutoUpdate() { if (--autoUpdates == 0) { if (url.isLocalFile()) { KDirWatch::self()->removeDir(m_canonicalPath); } sendSignal(false, url); } else if (autoUpdates < 0) { autoUpdates = 0; } } // number of KCoreDirListers using autoUpdate for this dir short autoUpdates; // this directory is up-to-date bool complete; // the directory is watched while being in the cache (useful for proper incAutoUpdate/decAutoUpdate count) bool watchedWhileInCache; // the complete url of this directory QUrl url; // the local path, with symlinks resolved, so that KDirWatch works QString m_canonicalPath; // KFileItem representing the root of this directory. // Remember that this is optional. FTP sites don't return '.' in // the list, so they give no root item KFileItem rootItem; NonMovableFileItemList lstItems; }; // definition of the cache of ".hidden" files struct CacheHiddenFile { CacheHiddenFile(const QDateTime& mtime, const QSet& listedFiles) : mtime(mtime), listedFiles(listedFiles) { } QDateTime mtime; QSet listedFiles; }; //static const unsigned short MAX_JOBS_PER_LISTER; QMap runningListJobs; // an item is a complete directory QHash itemsInUse; QCache itemsCached; // cache of ".hidden" files QCache").arg(anchor)) { index = mParsed.lastIndexOf(QLatin1String(" Copyright (C) 2001 Stephan Kulow Copyright (C) 2003 Cornelius Schumacher 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 #include -#include +#include #include #include class HelpProtocol : public KIO::SlaveBase { public: HelpProtocol(bool ghelp, const QByteArray &pool, const QByteArray &app); virtual ~HelpProtocol() { } void get(const QUrl &url) Q_DECL_OVERRIDE; void mimetype(const QUrl &url) Q_DECL_OVERRIDE; private: QString langLookup(const QString &fname); void emitFile(const QUrl &url); void get_file(const QString &path); QString lookupFile(const QString &fname, const QString &query, bool &redirect); void sendError(const QString &t); QString mParsed; bool mGhelp; }; #endif diff --git a/src/ioslaves/help/main.cpp b/src/ioslaves/help/main.cpp index 4487b6a8..f09987aa 100644 --- a/src/ioslaves/help/main.cpp +++ b/src/ioslaves/help/main.cpp @@ -1,66 +1,66 @@ #ifdef _WIN32 #define LIBXML_DLL_IMPORT __declspec(dllimport) #else extern "C" int xmlLoadExtDtdDefaultValue; #endif #include "kio_help.h" #include #include -#include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Pseudo plugin class to embed meta data class KIOPluginForMetaData : public QObject { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kde.kio.slave.help" FILE "help.json") }; extern "C" { Q_DECL_EXPORT int kdemain(int argc, char **argv) { KDocTools::setupStandardDirs(); //qDebug() << "Starting " << getpid(); if (argc != 4) { fprintf(stderr, "Usage: kio_help protocol domain-socket1 domain-socket2\n"); exit(-1); } LIBXML_TEST_VERSION xmlSubstituteEntitiesDefault(1); xmlLoadExtDtdDefaultValue = 1; exsltRegisterAll(); HelpProtocol slave(false, argv[2], argv[3]); slave.dispatchLoop(); //qDebug() << "Done"; return 0; } } // needed for JSON file embedding #include "main.moc" diff --git a/src/ioslaves/help/main_ghelp.cpp b/src/ioslaves/help/main_ghelp.cpp index 25c730a6..39cfee6b 100644 --- a/src/ioslaves/help/main_ghelp.cpp +++ b/src/ioslaves/help/main_ghelp.cpp @@ -1,65 +1,65 @@ #ifdef _WIN32 #define LIBXML_DLL_IMPORT __declspec(dllimport) #else extern "C" int xmlLoadExtDtdDefaultValue; #endif #include "kio_help.h" #include #include -#include +#include #include #include #include #include #include #include #include #include #include #include #include #include // Pseudo plugin class to embed meta data class KIOPluginForMetaData : public QObject { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kde.kio.slave.ghelp" FILE "ghelp.json") }; extern "C" { Q_DECL_EXPORT int kdemain(int argc, char **argv) { KDocTools::setupStandardDirs(); //qDebug() << "Starting " << getpid(); if (argc != 4) { fprintf(stderr, "Usage: kio_ghelp protocol domain-socket1 domain-socket2\n"); exit(-1); } LIBXML_TEST_VERSION xmlSubstituteEntitiesDefault(1); xmlLoadExtDtdDefaultValue = 1; exsltRegisterAll(); HelpProtocol slave(true, argv[2], argv[3]); slave.dispatchLoop(); //qDebug() << "Done"; return 0; } } // needed for JSON file embedding #include "main_ghelp.moc" diff --git a/src/ioslaves/help/xslt_help.cpp b/src/ioslaves/help/xslt_help.cpp index e83cfcf4..f9db4321 100644 --- a/src/ioslaves/help/xslt_help.cpp +++ b/src/ioslaves/help/xslt_help.cpp @@ -1,100 +1,100 @@ #include "xslt_help.h" #include #include #include #include #include #include #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include #include static bool readCache(const QString &filename, const QString &cache, QString &output) { //qDebug() << filename << cache; if (!compareTimeStamps(filename, cache)) { return false; } if (!compareTimeStamps(KDocTools::locateFileInDtdResource(QStringLiteral("customization/kde-chunk.xsl")), cache)) { return false; } //qDebug() << "create filter"; KFilterDev fd(cache); if (!fd.open(QIODevice::ReadOnly)) { QFile::remove(cache); return false; } //qDebug() << "reading"; char buffer[32000]; int n; QByteArray text; // Also end loop in case of error, when -1 is returned while ((n = fd.read(buffer, 31900)) > 0) { buffer[n] = 0; text += buffer; } //qDebug() << "read " << text.length(); fd.close(); output = QString::fromUtf8(text); if (n == -1) { return false; } //qDebug() << "finished "; return true; } QString lookForCache(const QString &filename) { //qDebug() << "lookForCache" << filename; Q_ASSERT(filename.endsWith(QLatin1String(".docbook"))); Q_ASSERT(QDir::isAbsolutePath(filename)); QString cache = filename.left(filename.length() - 7); QString output; if (readCache(filename, cache + "cache.bz2", output)) { return output; } #ifdef Q_OS_WIN QFileInfo fi(filename); // make sure filenames do not contain the base path, otherwise // accessing user data from another location invalids cached files. // Accessing user data under a different path is possible // when using usb sticks - this may affect unix/mac systems also const QString installPath = KDocTools::documentationDirs().last(); cache = '/' + fi.absolutePath().remove(installPath, Qt::CaseInsensitive).replace('/', '_') + '_' + fi.baseName() + '.'; #endif if (readCache(filename, QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1Char('/') + "kio_help" + cache + "cache.bz2", output)) { return output; } return QString(); } bool compareTimeStamps(const QString &older, const QString &newer) { QFileInfo _older(older); QFileInfo _newer(newer); Q_ASSERT(_older.exists()); if (!_newer.exists()) { return false; } return (_newer.lastModified() > _older.lastModified()); } diff --git a/src/ioslaves/help/xslt_help.h b/src/ioslaves/help/xslt_help.h index 403fb212..c2a5fc7f 100644 --- a/src/ioslaves/help/xslt_help.h +++ b/src/ioslaves/help/xslt_help.h @@ -1,14 +1,14 @@ #ifndef _MEIN_XSLT_HELP_H_ #define _MEIN_XSLT_HELP_H_ -#include +#include QString lookForCache(const QString &filename); /** * Compares two files and returns true if @param newer exists and is newer than * @param older **/ bool compareTimeStamps(const QString &older, const QString &newer); #endif diff --git a/src/ioslaves/http/http.cpp b/src/ioslaves/http/http.cpp index 1558d008..bf04b100 100644 --- a/src/ioslaves/http/http.cpp +++ b/src/ioslaves/http/http.cpp @@ -1,5650 +1,5650 @@ /* Copyright (C) 2000-2003 Waldo Bastian Copyright (C) 2000-2002 George Staikos Copyright (C) 2000-2002 Dawit Alemayehu Copyright (C) 2001,2002 Hamish Rodda Copyright (C) 2007 Nick Shaforostoff Copyright (C) 2007 Daniel Nicoletti Copyright (C) 2008,2009 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License (LGPL) 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. */ // TODO delete / do not save very big files; "very big" to be defined #define QT_NO_CAST_FROM_ASCII #include "http.h" #include #include // must be explicitly included for MacOSX -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "httpauthentication.h" #include "kioglobal_p.h" #include Q_DECLARE_LOGGING_CATEGORY(KIO_HTTP) Q_LOGGING_CATEGORY(KIO_HTTP, "kf5.kio.kio_http", QtWarningMsg) // disable debug by default // HeaderTokenizer declarations #include "parsinghelpers.h" //string parsing helpers and HeaderTokenizer implementation #include "parsinghelpers.cpp" // Pseudo plugin class to embed meta data class KIOPluginForMetaData : public QObject { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kde.kio.slave.http" FILE "http.json") }; static bool supportedProxyScheme(const QString &scheme) { // scheme is supposed to be lowercase return (scheme.startsWith(QLatin1String("http")) || scheme == QLatin1String("socks")); } // see filenameFromUrl(): a sha1 hash is 160 bits static const int s_hashedUrlBits = 160; // this number should always be divisible by eight static const int s_hashedUrlNibbles = s_hashedUrlBits / 4; static const int s_MaxInMemPostBufSize = 256 * 1024; // Write anyting over 256 KB to file... using namespace KIO; extern "C" Q_DECL_EXPORT int kdemain(int argc, char **argv) { QCoreApplication app(argc, argv); // needed for QSocketNotifier app.setApplicationName(QStringLiteral("kio_http")); if (argc != 4) { fprintf(stderr, "Usage: kio_http protocol domain-socket1 domain-socket2\n"); exit(-1); } HTTPProtocol slave(argv[1], argv[2], argv[3]); slave.dispatchLoop(); return 0; } /*********************************** Generic utility functions ********************/ static QString toQString(const QByteArray &value) { return QString::fromLatin1(value.constData(), value.size()); } static bool isCrossDomainRequest(const QString &fqdn, const QString &originURL) { //TODO read the RFC if (originURL == QLatin1String("true")) { // Backwards compatibility return true; } QUrl url(originURL); // Document Origin domain QString a = url.host(); // Current request domain QString b = fqdn; if (a == b) { return false; } QStringList la = a.split(QLatin1Char('.'), QString::SkipEmptyParts); QStringList lb = b.split(QLatin1Char('.'), QString::SkipEmptyParts); if (qMin(la.count(), lb.count()) < 2) { return true; // better safe than sorry... } while (la.count() > 2) { la.pop_front(); } while (lb.count() > 2) { lb.pop_front(); } return la != lb; } /* Eliminates any custom header that could potentially alter the request */ static QString sanitizeCustomHTTPHeader(const QString &_header) { QString sanitizedHeaders; const QStringList headers = _header.split(QRegExp(QStringLiteral("[\r\n]"))); for (QStringList::ConstIterator it = headers.begin(); it != headers.end(); ++it) { // Do not allow Request line to be specified and ignore // the other HTTP headers. if (!(*it).contains(QLatin1Char(':')) || (*it).startsWith(QLatin1String("host"), Qt::CaseInsensitive) || (*it).startsWith(QLatin1String("proxy-authorization"), Qt::CaseInsensitive) || (*it).startsWith(QLatin1String("via"), Qt::CaseInsensitive)) { continue; } sanitizedHeaders += (*it); sanitizedHeaders += QLatin1String("\r\n"); } sanitizedHeaders.chop(2); return sanitizedHeaders; } static bool isPotentialSpoofingAttack(const HTTPProtocol::HTTPRequest &request, const KConfigGroup *config) { qCDebug(KIO_HTTP) << request.url << "response code: " << request.responseCode << "previous response code:" << request.prevResponseCode; if (config->readEntry("no-spoof-check", false)) { return false; } if (request.url.userName().isEmpty()) { return false; } // We already have cached authentication. if (config->readEntry(QStringLiteral("cached-www-auth"), false)) { return false; } const QString userName = config->readEntry(QStringLiteral("LastSpoofedUserName"), QString()); return ((userName.isEmpty() || userName != request.url.userName()) && request.responseCode != 401 && request.prevResponseCode != 401); } // for a given response code, conclude if the response is going to/likely to have a response body static bool canHaveResponseBody(int responseCode, KIO::HTTP_METHOD method) { /* RFC 2616 says... 1xx: false 200: method HEAD: false, otherwise:true 201: true 202: true 203: see 200 204: false 205: false 206: true 300: see 200 301: see 200 302: see 200 303: see 200 304: false 305: probably like 300, RFC seems to expect disconnection afterwards... 306: (reserved), for simplicity do it just like 200 307: see 200 4xx: see 200 5xx :see 200 */ if (responseCode >= 100 && responseCode < 200) { return false; } switch (responseCode) { case 201: case 202: case 206: // RFC 2616 does not mention HEAD in the description of the above. if the assert turns out // to be a problem the response code should probably be treated just like 200 and friends. Q_ASSERT(method != HTTP_HEAD); return true; case 204: case 205: case 304: return false; default: break; } // safe (and for most remaining response codes exactly correct) default return method != HTTP_HEAD; } static bool isEncryptedHttpVariety(const QByteArray &p) { return p == "https" || p == "webdavs"; } static bool isValidProxy(const QUrl &u) { return u.isValid() && !u.host().isEmpty(); } static bool isHttpProxy(const QUrl &u) { return isValidProxy(u) && u.scheme() == QLatin1String("http"); } static QIODevice *createPostBufferDeviceFor(KIO::filesize_t size) { QIODevice *device; if (size > static_cast(s_MaxInMemPostBufSize)) { device = new QTemporaryFile; } else { device = new QBuffer; } if (!device->open(QIODevice::ReadWrite)) { return nullptr; } return device; } QByteArray HTTPProtocol::HTTPRequest::methodString() const { if (!methodStringOverride.isEmpty()) { return (methodStringOverride).toLatin1(); } switch (method) { case HTTP_GET: return "GET"; case HTTP_PUT: return "PUT"; case HTTP_POST: return "POST"; case HTTP_HEAD: return "HEAD"; case HTTP_DELETE: return "DELETE"; case HTTP_OPTIONS: return "OPTIONS"; case DAV_PROPFIND: return "PROPFIND"; case DAV_PROPPATCH: return "PROPPATCH"; case DAV_MKCOL: return "MKCOL"; case DAV_COPY: return "COPY"; case DAV_MOVE: return "MOVE"; case DAV_LOCK: return "LOCK"; case DAV_UNLOCK: return "UNLOCK"; case DAV_SEARCH: return "SEARCH"; case DAV_SUBSCRIBE: return "SUBSCRIBE"; case DAV_UNSUBSCRIBE: return "UNSUBSCRIBE"; case DAV_POLL: return "POLL"; case DAV_NOTIFY: return "NOTIFY"; case DAV_REPORT: return "REPORT"; default: Q_ASSERT(false); return QByteArray(); } } static QString formatHttpDate(const QDateTime &date) { return QLocale::c().toString(date, QStringLiteral("ddd, dd MMM yyyy hh:mm:ss 'GMT'")); } static bool isAuthenticationRequired(int responseCode) { return (responseCode == 401) || (responseCode == 407); } static void changeProtocolToHttp(QUrl* url) { const QString protocol(url->scheme()); if (protocol == QLatin1String("webdavs")) { url->setScheme(QStringLiteral("https")); } else if (protocol == QLatin1String("webdav")) { url->setScheme(QStringLiteral("http")); } } #define NO_SIZE ((KIO::filesize_t) -1) #if HAVE_STRTOLL #define STRTOLL strtoll #else #define STRTOLL strtol #endif /************************************** HTTPProtocol **********************************************/ HTTPProtocol::HTTPProtocol(const QByteArray &protocol, const QByteArray &pool, const QByteArray &app) : TCPSlaveBase(protocol, pool, app, isEncryptedHttpVariety(protocol)) , m_iSize(NO_SIZE) , m_iPostDataSize(NO_SIZE) , m_isBusy(false) , m_POSTbuf(nullptr) , m_maxCacheAge(DEFAULT_MAX_CACHE_AGE) , m_maxCacheSize(DEFAULT_MAX_CACHE_SIZE) , m_protocol(protocol) , m_wwwAuth(nullptr) , m_triedWwwCredentials(NoCredentials) , m_proxyAuth(nullptr) , m_triedProxyCredentials(NoCredentials) , m_socketProxyAuth(nullptr) , m_networkConfig(nullptr) , m_kioError(0) , m_isLoadingErrorPage(false) , m_remoteRespTimeout(DEFAULT_RESPONSE_TIMEOUT) , m_iEOFRetryCount(0) { reparseConfiguration(); setBlocking(true); connect(socket(), SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), this, SLOT(proxyAuthenticationForSocket(QNetworkProxy,QAuthenticator*))); } HTTPProtocol::~HTTPProtocol() { httpClose(false); } void HTTPProtocol::reparseConfiguration() { qCDebug(KIO_HTTP); delete m_proxyAuth; delete m_wwwAuth; m_proxyAuth = nullptr; m_wwwAuth = nullptr; m_request.proxyUrl.clear(); //TODO revisit m_request.proxyUrls.clear(); TCPSlaveBase::reparseConfiguration(); } void HTTPProtocol::resetConnectionSettings() { m_isEOF = false; m_kioError = 0; m_isLoadingErrorPage = false; } quint16 HTTPProtocol::defaultPort() const { return isEncryptedHttpVariety(m_protocol) ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT; } void HTTPProtocol::resetResponseParsing() { m_isRedirection = false; m_isChunked = false; m_iSize = NO_SIZE; clearUnreadBuffer(); m_responseHeaders.clear(); m_contentEncodings.clear(); m_transferEncodings.clear(); m_contentMD5.clear(); m_mimeType.clear(); setMetaData(QStringLiteral("request-id"), m_request.id); } void HTTPProtocol::resetSessionSettings() { // Follow HTTP/1.1 spec and enable keep-alive by default // unless the remote side tells us otherwise or we determine // the persistent link has been terminated by the remote end. m_request.isKeepAlive = true; m_request.keepAliveTimeout = 0; m_request.redirectUrl = QUrl(); m_request.useCookieJar = config()->readEntry("Cookies", false); m_request.cacheTag.useCache = config()->readEntry("UseCache", true); m_request.preferErrorPage = config()->readEntry("errorPage", true); const bool noAuth = config()->readEntry("no-auth", false); m_request.doNotWWWAuthenticate = config()->readEntry("no-www-auth", noAuth); m_request.doNotProxyAuthenticate = config()->readEntry("no-proxy-auth", noAuth); m_strCacheDir = config()->readPathEntry("CacheDir", QString()); m_maxCacheAge = config()->readEntry("MaxCacheAge", DEFAULT_MAX_CACHE_AGE); m_request.windowId = config()->readEntry("window-id"); m_request.methodStringOverride = metaData(QStringLiteral("CustomHTTPMethod")); m_request.sentMethodString.clear(); qCDebug(KIO_HTTP) << "Window Id =" << m_request.windowId; qCDebug(KIO_HTTP) << "ssl_was_in_use =" << metaData(QStringLiteral("ssl_was_in_use")); m_request.referrer.clear(); // RFC 2616: do not send the referrer if the referrer page was served using SSL and // the current page does not use SSL. if (config()->readEntry("SendReferrer", true) && (isEncryptedHttpVariety(m_protocol) || metaData(QStringLiteral("ssl_was_in_use")) != QLatin1String("TRUE"))) { QUrl refUrl(metaData(QStringLiteral("referrer"))); if (refUrl.isValid()) { // Sanitize QString protocol = refUrl.scheme(); if (protocol.startsWith(QLatin1String("webdav"))) { protocol.replace(0, 6, QStringLiteral("http")); refUrl.setScheme(protocol); } if (protocol.startsWith(QLatin1String("http"))) { m_request.referrer = toQString(refUrl.toEncoded(QUrl::RemoveUserInfo | QUrl::RemoveFragment)); } } } if (config()->readEntry("SendLanguageSettings", true)) { m_request.charsets = config()->readEntry("Charsets", DEFAULT_PARTIAL_CHARSET_HEADER); if (!m_request.charsets.contains(QLatin1String("*;"), Qt::CaseInsensitive)) { m_request.charsets += QLatin1String(",*;q=0.5"); } m_request.languages = config()->readEntry("Languages", DEFAULT_LANGUAGE_HEADER); } else { m_request.charsets.clear(); m_request.languages.clear(); } // Adjust the offset value based on the "range-start" meta-data. QString resumeOffset = metaData(QStringLiteral("range-start")); if (resumeOffset.isEmpty()) { resumeOffset = metaData(QStringLiteral("resume")); // old name } if (!resumeOffset.isEmpty()) { m_request.offset = resumeOffset.toULongLong(); } else { m_request.offset = 0; } // Same procedure for endoffset. QString resumeEndOffset = metaData(QStringLiteral("range-end")); if (resumeEndOffset.isEmpty()) { resumeEndOffset = metaData(QStringLiteral("resume_until")); // old name } if (!resumeEndOffset.isEmpty()) { m_request.endoffset = resumeEndOffset.toULongLong(); } else { m_request.endoffset = 0; } m_request.disablePassDialog = config()->readEntry("DisablePassDlg", false); m_request.allowTransferCompression = config()->readEntry("AllowCompressedPage", true); m_request.id = metaData(QStringLiteral("request-id")); // Store user agent for this host. if (config()->readEntry("SendUserAgent", true)) { m_request.userAgent = metaData(QStringLiteral("UserAgent")); } else { m_request.userAgent.clear(); } m_request.cacheTag.etag.clear(); m_request.cacheTag.servedDate = QDateTime(); m_request.cacheTag.lastModifiedDate = QDateTime(); m_request.cacheTag.expireDate = QDateTime(); m_request.responseCode = 0; m_request.prevResponseCode = 0; delete m_wwwAuth; m_wwwAuth = nullptr; delete m_socketProxyAuth; m_socketProxyAuth = nullptr; m_blacklistedWwwAuthMethods.clear(); m_triedWwwCredentials = NoCredentials; m_blacklistedProxyAuthMethods.clear(); m_triedProxyCredentials = NoCredentials; // Obtain timeout values m_remoteRespTimeout = responseTimeout(); // Bounce back the actual referrer sent setMetaData(QStringLiteral("referrer"), m_request.referrer); // Reset the post data size m_iPostDataSize = NO_SIZE; // Reset the EOF retry counter m_iEOFRetryCount = 0; } void HTTPProtocol::setHost(const QString &host, quint16 port, const QString &user, const QString &pass) { // Reset the webdav-capable flags for this host if (m_request.url.host() != host) { m_davHostOk = m_davHostUnsupported = false; } m_request.url.setHost(host); // is it an IPv6 address? if (host.indexOf(QLatin1Char(':')) == -1) { m_request.encoded_hostname = toQString(QUrl::toAce(host)); } else { int pos = host.indexOf(QLatin1Char('%')); if (pos == -1) { m_request.encoded_hostname = QLatin1Char('[') + host + QLatin1Char(']'); } else // don't send the scope-id in IPv6 addresses to the server { m_request.encoded_hostname = QLatin1Char('[') + host.left(pos) + QLatin1Char(']'); } } m_request.url.setPort((port > 0 && port != defaultPort()) ? port : -1); m_request.url.setUserName(user); m_request.url.setPassword(pass); // On new connection always clear previous proxy information... m_request.proxyUrl.clear(); m_request.proxyUrls.clear(); qCDebug(KIO_HTTP) << "Hostname is now:" << m_request.url.host() << "(" << m_request.encoded_hostname << ")"; } bool HTTPProtocol::maybeSetRequestUrl(const QUrl &u) { qCDebug(KIO_HTTP) << u; m_request.url = u; m_request.url.setPort(u.port(defaultPort()) != defaultPort() ? u.port() : -1); if (u.host().isEmpty()) { error(KIO::ERR_UNKNOWN_HOST, i18n("No host specified.")); return false; } if (u.path().isEmpty()) { QUrl newUrl(u); newUrl.setPath(QStringLiteral("/")); redirection(newUrl); finished(); return false; } return true; } void HTTPProtocol::proceedUntilResponseContent(bool dataInternal /* = false */) { qCDebug(KIO_HTTP); const bool status = proceedUntilResponseHeader() && readBody(dataInternal || m_kioError); // If not an error condition or internal request, close // the connection based on the keep alive settings... if (!m_kioError && !dataInternal) { httpClose(m_request.isKeepAlive); } // if data is required internally or we got error, don't finish, // it is processed before we finish() if (dataInternal || !status) { return; } if (!sendHttpError()) { finished(); } } bool HTTPProtocol::proceedUntilResponseHeader() { qCDebug(KIO_HTTP); // Retry the request until it succeeds or an unrecoverable error occurs. // Recoverable errors are, for example: // - Proxy or server authentication required: Ask for credentials and try again, // this time with an authorization header in the request. // - Server-initiated timeout on keep-alive connection: Reconnect and try again while (true) { if (!sendQuery()) { return false; } if (readResponseHeader()) { // Success, finish the request. break; } // If not loading error page and the response code requires us to resend the query, // then throw away any error message that might have been sent by the server. if (!m_isLoadingErrorPage && isAuthenticationRequired(m_request.responseCode)) { // This gets rid of any error page sent with 401 or 407 authentication required response... readBody(true); } // no success, close the cache file so the cache state is reset - that way most other code // doesn't have to deal with the cache being in various states. cacheFileClose(); if (m_kioError || m_isLoadingErrorPage) { // Unrecoverable error, abort everything. // Also, if we've just loaded an error page there is nothing more to do. // In that case we abort to avoid loops; some webservers manage to send 401 and // no authentication request. Or an auth request we don't understand. setMetaData(QStringLiteral("responsecode"), QString::number(m_request.responseCode)); return false; } if (!m_request.isKeepAlive) { httpCloseConnection(); m_request.isKeepAlive = true; m_request.keepAliveTimeout = 0; } } // Do not save authorization if the current response code is // 4xx (client error) or 5xx (server error). qCDebug(KIO_HTTP) << "Previous Response:" << m_request.prevResponseCode; qCDebug(KIO_HTTP) << "Current Response:" << m_request.responseCode; setMetaData(QStringLiteral("responsecode"), QString::number(m_request.responseCode)); setMetaData(QStringLiteral("content-type"), m_mimeType); // At this point sendBody() should have delivered any POST data. clearPostDataBuffer(); return true; } void HTTPProtocol::stat(const QUrl &url) { qCDebug(KIO_HTTP) << url; if (!maybeSetRequestUrl(url)) { return; } resetSessionSettings(); if (m_protocol != "webdav" && m_protocol != "webdavs") { QString statSide = metaData(QStringLiteral("statSide")); if (statSide != QLatin1String("source")) { // When uploading we assume the file doesn't exit error(ERR_DOES_NOT_EXIST, url.toDisplayString()); return; } // When downloading we assume it exists UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, url.fileName()); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); // a file entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH); // readable by everybody statEntry(entry); finished(); return; } davStatList(url); } void HTTPProtocol::listDir(const QUrl &url) { qCDebug(KIO_HTTP) << url; if (!maybeSetRequestUrl(url)) { return; } resetSessionSettings(); davStatList(url, false); } void HTTPProtocol::davSetRequest(const QByteArray &requestXML) { // insert the document into the POST buffer, kill trailing zero byte cachePostData(requestXML); } void HTTPProtocol::davStatList(const QUrl &url, bool stat) { UDSEntry entry; // check to make sure this host supports WebDAV if (!davHostOk()) { return; } QMimeDatabase db; // Maybe it's a disguised SEARCH... QString query = metaData(QStringLiteral("davSearchQuery")); if (!query.isEmpty()) { QByteArray request = "\r\n"; request.append("\r\n"); request.append(query.toUtf8()); request.append("\r\n"); davSetRequest(request); } else { // We are only after certain features... QByteArray request; request = "" ""; // insert additional XML request from the davRequestResponse metadata if (hasMetaData(QStringLiteral("davRequestResponse"))) { request += metaData(QStringLiteral("davRequestResponse")).toUtf8(); } else { // No special request, ask for default properties request += "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; } request += ""; davSetRequest(request); } // WebDAV Stat or List... m_request.method = query.isEmpty() ? DAV_PROPFIND : DAV_SEARCH; m_request.url.setQuery(QString()); m_request.cacheTag.policy = CC_Reload; m_request.davData.depth = stat ? 0 : 1; if (!stat) { if (!m_request.url.path().endsWith(QLatin1Char('/'))) { m_request.url.setPath(m_request.url.path() + QLatin1Char('/')); } } proceedUntilResponseContent(true); infoMessage(QLatin1String("")); // Has a redirection already been called? If so, we're done. if (m_isRedirection || m_kioError) { if (m_isRedirection) { davFinished(); } return; } QDomDocument multiResponse; multiResponse.setContent(m_webDavDataBuf, true); bool hasResponse = false; qCDebug(KIO_HTTP) << endl << multiResponse.toString(2); for (QDomNode n = multiResponse.documentElement().firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement thisResponse = n.toElement(); if (thisResponse.isNull()) { continue; } hasResponse = true; QDomElement href = thisResponse.namedItem(QStringLiteral("href")).toElement(); if (!href.isNull()) { entry.clear(); const QUrl thisURL(href.text()); // href.text() is a percent-encoded url. if (thisURL.isValid()) { const QUrl adjustedThisURL = thisURL.adjusted(QUrl::StripTrailingSlash); const QUrl adjustedUrl = url.adjusted(QUrl::StripTrailingSlash); // base dir of a listDir(): name should be "." QString name; if (!stat && adjustedThisURL.path() == adjustedUrl.path()) { name = QLatin1Char('.'); } else { name = adjustedThisURL.fileName(); } entry.insert(KIO::UDSEntry::UDS_NAME, name.isEmpty() ? href.text() : name); } QDomNodeList propstats = thisResponse.elementsByTagName(QStringLiteral("propstat")); davParsePropstats(propstats, entry); // Since a lot of webdav servers seem not to send the content-type information // for the requested directory listings, we attempt to guess the mime-type from // the resource name so long as the resource is not a directory. if (entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE).isEmpty() && entry.numberValue(KIO::UDSEntry::UDS_FILE_TYPE) != S_IFDIR) { QMimeType mime = db.mimeTypeForFile(thisURL.path(), QMimeDatabase::MatchExtension); if (mime.isValid() && !mime.isDefault()) { qCDebug(KIO_HTTP) << "Setting" << mime.name() << "as guessed mime type for" << thisURL.path(); entry.insert(KIO::UDSEntry::UDS_GUESSED_MIME_TYPE, mime.name()); } } if (stat) { // return an item statEntry(entry); davFinished(); return; } listEntry(entry); } else { qCDebug(KIO_HTTP) << "Error: no URL contained in response to PROPFIND on" << url; } } if (stat || !hasResponse) { error(ERR_DOES_NOT_EXIST, url.toDisplayString()); return; } davFinished(); } void HTTPProtocol::davGeneric(const QUrl &url, KIO::HTTP_METHOD method, qint64 size) { qCDebug(KIO_HTTP) << url; if (!maybeSetRequestUrl(url)) { return; } resetSessionSettings(); // check to make sure this host supports WebDAV if (!davHostOk()) { return; } // WebDAV method m_request.method = method; m_request.url.setQuery(QString()); m_request.cacheTag.policy = CC_Reload; m_iPostDataSize = (size > -1 ? static_cast(size) : NO_SIZE); proceedUntilResponseContent(); } int HTTPProtocol::codeFromResponse(const QString &response) { const int firstSpace = response.indexOf(QLatin1Char(' ')); const int secondSpace = response.indexOf(QLatin1Char(' '), firstSpace + 1); return response.midRef(firstSpace + 1, secondSpace - firstSpace - 1).toInt(); } void HTTPProtocol::davParsePropstats(const QDomNodeList &propstats, UDSEntry &entry) { QString mimeType; bool foundExecutable = false; bool isDirectory = false; uint lockCount = 0; uint supportedLockCount = 0; qlonglong quotaUsed = -1; qlonglong quotaAvailable = -1; for (int i = 0; i < propstats.count(); i++) { QDomElement propstat = propstats.item(i).toElement(); QDomElement status = propstat.namedItem(QStringLiteral("status")).toElement(); if (status.isNull()) { // error, no status code in this propstat qCDebug(KIO_HTTP) << "Error, no status code in this propstat"; return; } int code = codeFromResponse(status.text()); if (code != 200) { qCDebug(KIO_HTTP) << "Got status code" << code << "(this may mean that some properties are unavailable)"; continue; } QDomElement prop = propstat.namedItem(QStringLiteral("prop")).toElement(); if (prop.isNull()) { qCDebug(KIO_HTTP) << "Error: no prop segment in this propstat."; return; } if (hasMetaData(QStringLiteral("davRequestResponse"))) { QDomDocument doc; doc.appendChild(prop); entry.insert(KIO::UDSEntry::UDS_XML_PROPERTIES, doc.toString()); } for (QDomNode n = prop.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement property = n.toElement(); if (property.isNull()) { continue; } if (property.namespaceURI() != QLatin1String("DAV:")) { // break out - we're only interested in properties from the DAV namespace continue; } if (property.tagName() == QLatin1String("creationdate")) { // Resource creation date. Should be is ISO 8601 format. entry.insert(KIO::UDSEntry::UDS_CREATION_TIME, parseDateTime(property.text(), property.attribute(QStringLiteral("dt"))).toTime_t()); } else if (property.tagName() == QLatin1String("getcontentlength")) { // Content length (file size) entry.insert(KIO::UDSEntry::UDS_SIZE, property.text().toULong()); } else if (property.tagName() == QLatin1String("displayname")) { // Name suitable for presentation to the user setMetaData(QStringLiteral("davDisplayName"), property.text()); } else if (property.tagName() == QLatin1String("source")) { // Source template location QDomElement source = property.namedItem(QStringLiteral("link")).toElement() .namedItem(QStringLiteral("dst")).toElement(); if (!source.isNull()) { setMetaData(QStringLiteral("davSource"), source.text()); } } else if (property.tagName() == QLatin1String("getcontentlanguage")) { // equiv. to Content-Language header on a GET setMetaData(QStringLiteral("davContentLanguage"), property.text()); } else if (property.tagName() == QLatin1String("getcontenttype")) { // Content type (mime type) // This may require adjustments for other server-side webdav implementations // (tested with Apache + mod_dav 1.0.3) if (property.text() == QLatin1String("httpd/unix-directory")) { isDirectory = true; } else { mimeType = property.text(); } } else if (property.tagName() == QLatin1String("executable")) { // File executable status if (property.text() == QLatin1String("T")) { foundExecutable = true; } } else if (property.tagName() == QLatin1String("getlastmodified")) { // Last modification date entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, parseDateTime(property.text(), property.attribute(QStringLiteral("dt"))).toTime_t()); } else if (property.tagName() == QLatin1String("getetag")) { // Entity tag setMetaData(QStringLiteral("davEntityTag"), property.text()); } else if (property.tagName() == QLatin1String("supportedlock")) { // Supported locking specifications for (QDomNode n2 = property.firstChild(); !n2.isNull(); n2 = n2.nextSibling()) { QDomElement lockEntry = n2.toElement(); if (lockEntry.tagName() == QLatin1String("lockentry")) { QDomElement lockScope = lockEntry.namedItem(QStringLiteral("lockscope")).toElement(); QDomElement lockType = lockEntry.namedItem(QStringLiteral("locktype")).toElement(); if (!lockScope.isNull() && !lockType.isNull()) { // Lock type was properly specified supportedLockCount++; const QString lockCountStr = QString::number(supportedLockCount); const QString scope = lockScope.firstChild().toElement().tagName(); const QString type = lockType.firstChild().toElement().tagName(); setMetaData(QLatin1String("davSupportedLockScope") + lockCountStr, scope); setMetaData(QLatin1String("davSupportedLockType") + lockCountStr, type); } } } } else if (property.tagName() == QLatin1String("lockdiscovery")) { // Lists the available locks davParseActiveLocks(property.elementsByTagName(QStringLiteral("activelock")), lockCount); } else if (property.tagName() == QLatin1String("resourcetype")) { // Resource type. "Specifies the nature of the resource." if (!property.namedItem(QStringLiteral("collection")).toElement().isNull()) { // This is a collection (directory) isDirectory = true; } } else if (property.tagName() == QLatin1String("quota-used-bytes")) { // Quota-used-bytes. "Contains the amount of storage already in use." quotaUsed = property.text().toLongLong(); } else if (property.tagName() == QLatin1String("quota-available-bytes")) { // Quota-available-bytes. "Indicates the maximum amount of additional storage available." quotaAvailable = property.text().toLongLong(); } else { qCDebug(KIO_HTTP) << "Found unknown webdav property:" << property.tagName(); } } } setMetaData(QStringLiteral("davLockCount"), QString::number(lockCount)); setMetaData(QStringLiteral("davSupportedLockCount"), QString::number(supportedLockCount)); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, isDirectory ? S_IFDIR : S_IFREG); if (foundExecutable || isDirectory) { // File was executable, or is a directory. entry.insert(KIO::UDSEntry::UDS_ACCESS, 0700); } else { entry.insert(KIO::UDSEntry::UDS_ACCESS, 0600); } if (!isDirectory && !mimeType.isEmpty()) { entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, mimeType); } if (quotaUsed >= 0 && quotaAvailable >= 0) { // Only used and available storage properties exist, the total storage size has to be calculated. setMetaData(QStringLiteral("total"), QString::number(quotaUsed + quotaAvailable)); setMetaData(QStringLiteral("available"), QString::number(quotaAvailable)); } } void HTTPProtocol::davParseActiveLocks(const QDomNodeList &activeLocks, uint &lockCount) { for (int i = 0; i < activeLocks.count(); i++) { const QDomElement activeLock = activeLocks.item(i).toElement(); lockCount++; // required const QDomElement lockScope = activeLock.namedItem(QStringLiteral("lockscope")).toElement(); const QDomElement lockType = activeLock.namedItem(QStringLiteral("locktype")).toElement(); const QDomElement lockDepth = activeLock.namedItem(QStringLiteral("depth")).toElement(); // optional const QDomElement lockOwner = activeLock.namedItem(QStringLiteral("owner")).toElement(); const QDomElement lockTimeout = activeLock.namedItem(QStringLiteral("timeout")).toElement(); const QDomElement lockToken = activeLock.namedItem(QStringLiteral("locktoken")).toElement(); if (!lockScope.isNull() && !lockType.isNull() && !lockDepth.isNull()) { // lock was properly specified lockCount++; const QString lockCountStr = QString::number(lockCount); const QString scope = lockScope.firstChild().toElement().tagName(); const QString type = lockType.firstChild().toElement().tagName(); const QString depth = lockDepth.text(); setMetaData(QLatin1String("davLockScope") + lockCountStr, scope); setMetaData(QLatin1String("davLockType") + lockCountStr, type); setMetaData(QLatin1String("davLockDepth") + lockCountStr, depth); if (!lockOwner.isNull()) { setMetaData(QLatin1String("davLockOwner") + lockCountStr, lockOwner.text()); } if (!lockTimeout.isNull()) { setMetaData(QLatin1String("davLockTimeout") + lockCountStr, lockTimeout.text()); } if (!lockToken.isNull()) { QDomElement tokenVal = lockScope.namedItem(QStringLiteral("href")).toElement(); if (!tokenVal.isNull()) { setMetaData(QLatin1String("davLockToken") + lockCountStr, tokenVal.text()); } } } } } QDateTime HTTPProtocol::parseDateTime(const QString &input, const QString &type) { if (type == QLatin1String("dateTime.tz")) { return QDateTime::fromString(input, Qt::ISODate); } else if (type == QLatin1String("dateTime.rfc1123")) { return QDateTime::fromString(input, Qt::RFC2822Date); } // format not advertised... try to parse anyway QDateTime time = QDateTime::fromString(input, Qt::RFC2822Date); if (time.isValid()) { return time; } return QDateTime::fromString(input, Qt::ISODate); } QString HTTPProtocol::davProcessLocks() { if (hasMetaData(QStringLiteral("davLockCount"))) { QString response = QStringLiteral("If:"); int numLocks = metaData(QStringLiteral("davLockCount")).toInt(); bool bracketsOpen = false; for (int i = 0; i < numLocks; i++) { const QString countStr = QString::number(i); if (hasMetaData(QLatin1String("davLockToken") + countStr)) { if (hasMetaData(QLatin1String("davLockURL") + countStr)) { if (bracketsOpen) { response += QLatin1Char(')'); bracketsOpen = false; } response += QLatin1String(" <") + metaData(QLatin1String("davLockURL") + countStr) + QLatin1Char('>'); } if (!bracketsOpen) { response += QLatin1String(" ("); bracketsOpen = true; } else { response += QLatin1Char(' '); } if (hasMetaData(QLatin1String("davLockNot") + countStr)) { response += QLatin1String("Not "); } response += QLatin1Char('<') + metaData(QLatin1String("davLockToken") + countStr) + QLatin1Char('>'); } } if (bracketsOpen) { response += QLatin1Char(')'); } response += QLatin1String("\r\n"); return response; } return QString(); } bool HTTPProtocol::davHostOk() { // FIXME needs to be reworked. Switched off for now. return true; // cached? if (m_davHostOk) { qCDebug(KIO_HTTP) << "true"; return true; } else if (m_davHostUnsupported) { qCDebug(KIO_HTTP) << " false"; davError(-2); return false; } m_request.method = HTTP_OPTIONS; // query the server's capabilities generally, not for a specific URL m_request.url.setPath(QStringLiteral("*")); m_request.url.setQuery(QString()); m_request.cacheTag.policy = CC_Reload; // clear davVersions variable, which holds the response to the DAV: header m_davCapabilities.clear(); proceedUntilResponseHeader(); if (m_davCapabilities.count()) { for (int i = 0; i < m_davCapabilities.count(); i++) { bool ok; uint verNo = m_davCapabilities[i].toUInt(&ok); if (ok && verNo > 0 && verNo < 3) { m_davHostOk = true; qCDebug(KIO_HTTP) << "Server supports DAV version" << verNo; } } if (m_davHostOk) { return true; } } m_davHostUnsupported = true; davError(-2); return false; } // This function is for closing proceedUntilResponseHeader(); requests // Required because there may or may not be further info expected void HTTPProtocol::davFinished() { // TODO: Check with the DAV extension developers httpClose(m_request.isKeepAlive); finished(); } void HTTPProtocol::mkdir(const QUrl &url, int) { qCDebug(KIO_HTTP) << url; if (!maybeSetRequestUrl(url)) { return; } resetSessionSettings(); m_request.method = DAV_MKCOL; m_request.url.setQuery(QString()); m_request.cacheTag.policy = CC_Reload; proceedUntilResponseContent(true); if (m_request.responseCode == 201) { davFinished(); } else { davError(); } } void HTTPProtocol::get(const QUrl &url) { qCDebug(KIO_HTTP) << url; if (!maybeSetRequestUrl(url)) { return; } resetSessionSettings(); m_request.method = HTTP_GET; QString tmp(metaData(QStringLiteral("cache"))); if (!tmp.isEmpty()) { m_request.cacheTag.policy = parseCacheControl(tmp); } else { m_request.cacheTag.policy = DEFAULT_CACHE_CONTROL; } proceedUntilResponseContent(); } void HTTPProtocol::put(const QUrl &url, int, KIO::JobFlags flags) { qCDebug(KIO_HTTP) << url; if (!maybeSetRequestUrl(url)) { return; } resetSessionSettings(); // Webdav hosts are capable of observing overwrite == false if (m_protocol.startsWith("webdav")) { // krazy:exclude=strings if (!(flags & KIO::Overwrite)) { // check to make sure this host supports WebDAV if (!davHostOk()) { return; } // Checks if the destination exists and return an error if it does. if (!davStatDestination()) { error(ERR_FILE_ALREADY_EXIST, QString()); return; } // force re-authentication... delete m_wwwAuth; m_wwwAuth = nullptr; } } m_request.method = HTTP_PUT; m_request.cacheTag.policy = CC_Reload; proceedUntilResponseContent(); } void HTTPProtocol::copy(const QUrl &src, const QUrl &dest, int, KIO::JobFlags flags) { qCDebug(KIO_HTTP) << src << "->" << dest; const bool isSourceLocal = src.isLocalFile(); const bool isDestinationLocal = dest.isLocalFile(); if (isSourceLocal && !isDestinationLocal) { copyPut(src, dest, flags); } else { if (!maybeSetRequestUrl(dest) || !maybeSetRequestUrl(src)) { return; } resetSessionSettings(); // destination has to be "http(s)://..." QUrl newDest (dest); changeProtocolToHttp(&newDest); m_request.method = DAV_COPY; m_request.davData.desturl = newDest.url(); m_request.davData.overwrite = (flags & KIO::Overwrite); m_request.url.setQuery(QString()); m_request.cacheTag.policy = CC_Reload; proceedUntilResponseHeader(); // The server returns a HTTP/1.1 201 Created or 204 No Content on successful completion if (m_request.responseCode == 201 || m_request.responseCode == 204) { davFinished(); } else { davError(); } } } void HTTPProtocol::rename(const QUrl &src, const QUrl &dest, KIO::JobFlags flags) { qCDebug(KIO_HTTP) << src << "->" << dest; if (!maybeSetRequestUrl(dest) || !maybeSetRequestUrl(src)) { return; } resetSessionSettings(); // destination has to be "http://..." QUrl newDest(dest); changeProtocolToHttp(&newDest); m_request.method = DAV_MOVE; m_request.davData.desturl = newDest.toString(); m_request.davData.overwrite = (flags & KIO::Overwrite); m_request.url.setQuery(QString()); m_request.cacheTag.policy = CC_Reload; proceedUntilResponseHeader(); // Work around strict Apache-2 WebDAV implementation which refuses to cooperate // with webdav://host/directory, instead requiring webdav://host/directory/ // (strangely enough it accepts Destination: without a trailing slash) // See BR# 209508 and BR#187970 if (m_request.responseCode == 301) { m_request.url = m_request.redirectUrl; m_request.method = DAV_MOVE; m_request.davData.desturl = newDest.toString(); m_request.davData.overwrite = (flags & KIO::Overwrite); m_request.url.setQuery(QString()); m_request.cacheTag.policy = CC_Reload; // force re-authentication... delete m_wwwAuth; m_wwwAuth = nullptr; proceedUntilResponseHeader(); } if (m_request.responseCode == 201) { davFinished(); } else { davError(); } } void HTTPProtocol::del(const QUrl &_url, bool isFile) { qCDebug(KIO_HTTP) << _url; QUrl url(_url); if (!isFile && !url.path().endsWith(QLatin1Char('/'))) { url.setPath(url.path() + QLatin1Char('/')); } if (!maybeSetRequestUrl(url)) { return; } resetSessionSettings(); m_request.method = HTTP_DELETE; m_request.cacheTag.policy = CC_Reload; if (m_protocol.startsWith("webdav")) { //krazy:exclude=strings due to QByteArray m_request.url.setQuery(QString()); if (!proceedUntilResponseHeader()) { return; } // The server returns a HTTP/1.1 200 Ok or HTTP/1.1 204 No Content // on successful completion. if (m_request.responseCode == 200 || m_request.responseCode == 204 || m_isRedirection) { davFinished(); } else { davError(); } return; } proceedUntilResponseContent(); } void HTTPProtocol::post(const QUrl &url, qint64 size) { qCDebug(KIO_HTTP) << url; if (!maybeSetRequestUrl(url)) { return; } resetSessionSettings(); m_request.method = HTTP_POST; m_request.cacheTag.policy = CC_Reload; m_iPostDataSize = (size > -1 ? static_cast(size) : NO_SIZE); proceedUntilResponseContent(); } void HTTPProtocol::davLock(const QUrl &url, const QString &scope, const QString &type, const QString &owner) { qCDebug(KIO_HTTP) << url; if (!maybeSetRequestUrl(url)) { return; } resetSessionSettings(); m_request.method = DAV_LOCK; m_request.url.setQuery(QString()); m_request.cacheTag.policy = CC_Reload; /* Create appropriate lock XML request. */ QDomDocument lockReq; QDomElement lockInfo = lockReq.createElementNS(QStringLiteral("DAV:"), QStringLiteral("lockinfo")); lockReq.appendChild(lockInfo); QDomElement lockScope = lockReq.createElement(QStringLiteral("lockscope")); lockInfo.appendChild(lockScope); lockScope.appendChild(lockReq.createElement(scope)); QDomElement lockType = lockReq.createElement(QStringLiteral("locktype")); lockInfo.appendChild(lockType); lockType.appendChild(lockReq.createElement(type)); if (!owner.isNull()) { QDomElement ownerElement = lockReq.createElement(QStringLiteral("owner")); lockReq.appendChild(ownerElement); QDomElement ownerHref = lockReq.createElement(QStringLiteral("href")); ownerElement.appendChild(ownerHref); ownerHref.appendChild(lockReq.createTextNode(owner)); } // insert the document into the POST buffer cachePostData(lockReq.toByteArray()); proceedUntilResponseContent(true); if (m_request.responseCode == 200) { // success QDomDocument multiResponse; multiResponse.setContent(m_webDavDataBuf, true); QDomElement prop = multiResponse.documentElement().namedItem(QStringLiteral("prop")).toElement(); QDomElement lockdiscovery = prop.namedItem(QStringLiteral("lockdiscovery")).toElement(); uint lockCount = 0; davParseActiveLocks(lockdiscovery.elementsByTagName(QStringLiteral("activelock")), lockCount); setMetaData(QStringLiteral("davLockCount"), QString::number(lockCount)); finished(); } else { davError(); } } void HTTPProtocol::davUnlock(const QUrl &url) { qCDebug(KIO_HTTP) << url; if (!maybeSetRequestUrl(url)) { return; } resetSessionSettings(); m_request.method = DAV_UNLOCK; m_request.url.setQuery(QString()); m_request.cacheTag.policy = CC_Reload; proceedUntilResponseContent(true); if (m_request.responseCode == 200) { finished(); } else { davError(); } } QString HTTPProtocol::davError(int code /* = -1 */, const QString &_url) { bool callError = false; if (code == -1) { code = m_request.responseCode; callError = true; } if (code == -2) { callError = true; } QString url = _url; if (!url.isNull()) { url = m_request.url.toDisplayString(); } QString action, errorString; int errorCode = ERR_SLAVE_DEFINED; // for 412 Precondition Failed QString ow = i18n("Otherwise, the request would have succeeded."); switch (m_request.method) { case DAV_PROPFIND: action = i18nc("request type", "retrieve property values"); break; case DAV_PROPPATCH: action = i18nc("request type", "set property values"); break; case DAV_MKCOL: action = i18nc("request type", "create the requested folder"); break; case DAV_COPY: action = i18nc("request type", "copy the specified file or folder"); break; case DAV_MOVE: action = i18nc("request type", "move the specified file or folder"); break; case DAV_SEARCH: action = i18nc("request type", "search in the specified folder"); break; case DAV_LOCK: action = i18nc("request type", "lock the specified file or folder"); break; case DAV_UNLOCK: action = i18nc("request type", "unlock the specified file or folder"); break; case HTTP_DELETE: action = i18nc("request type", "delete the specified file or folder"); break; case HTTP_OPTIONS: action = i18nc("request type", "query the server's capabilities"); break; case HTTP_GET: action = i18nc("request type", "retrieve the contents of the specified file or folder"); break; case DAV_REPORT: action = i18nc("request type", "run a report in the specified folder"); break; case HTTP_PUT: case HTTP_POST: case HTTP_HEAD: default: // this should not happen, this function is for webdav errors only Q_ASSERT(0); } // default error message if the following code fails errorString = i18nc("%1: code, %2: request type", "An unexpected error (%1) occurred " "while attempting to %2.", code, action); switch (code) { case -2: // internal error: OPTIONS request did not specify DAV compliance // ERR_UNSUPPORTED_PROTOCOL errorString = i18n("The server does not support the WebDAV protocol."); break; case 207: // 207 Multi-status { // our error info is in the returned XML document. // retrieve the XML document // there was an error retrieving the XML document. if (!readBody(true) && m_kioError) { return QString(); } QStringList errors; QDomDocument multiResponse; multiResponse.setContent(m_webDavDataBuf, true); QDomElement multistatus = multiResponse.documentElement().namedItem(QStringLiteral("multistatus")).toElement(); QDomNodeList responses = multistatus.elementsByTagName(QStringLiteral("response")); for (int i = 0; i < responses.count(); i++) { int errCode; QString errUrl; QDomElement response = responses.item(i).toElement(); QDomElement code = response.namedItem(QStringLiteral("status")).toElement(); if (!code.isNull()) { errCode = codeFromResponse(code.text()); QDomElement href = response.namedItem(QStringLiteral("href")).toElement(); if (!href.isNull()) { errUrl = href.text(); } errors << davError(errCode, errUrl); } } //kError = ERR_SLAVE_DEFINED; errorString = i18nc("%1: request type, %2: url", "An error occurred while attempting to %1, %2. A " "summary of the reasons is below.", action, url); errorString += QLatin1String("
    "); Q_FOREACH (const QString &error, errors) { errorString += QLatin1String("
  • ") + error + QLatin1String("
  • "); } errorString += QLatin1String("
"); } break; case 403: case 500: // hack: Apache mod_dav returns this instead of 403 (!) // 403 Forbidden // ERR_ACCESS_DENIED errorString = i18nc("%1: request type", "Access was denied while attempting to %1.", action); break; case 405: // 405 Method Not Allowed if (m_request.method == DAV_MKCOL) { // ERR_DIR_ALREADY_EXIST errorString = url; errorCode = ERR_DIR_ALREADY_EXIST; } break; case 409: // 409 Conflict // ERR_ACCESS_DENIED errorString = i18n("A resource cannot be created at the destination " "until one or more intermediate collections (folders) " "have been created."); break; case 412: // 412 Precondition failed if (m_request.method == DAV_COPY || m_request.method == DAV_MOVE) { // ERR_ACCESS_DENIED errorString = i18n("The server was unable to maintain the liveness of " "the properties listed in the propertybehavior XML " "element\n or you attempted to overwrite a file while " "requesting that files are not overwritten.\n %1", ow); } else if (m_request.method == DAV_LOCK) { // ERR_ACCESS_DENIED errorString = i18n("The requested lock could not be granted. %1", ow); } break; case 415: // 415 Unsupported Media Type // ERR_ACCESS_DENIED errorString = i18n("The server does not support the request type of the body."); break; case 423: // 423 Locked // ERR_ACCESS_DENIED errorString = i18nc("%1: request type", "Unable to %1 because the resource is locked.", action); break; case 425: // 424 Failed Dependency errorString = i18n("This action was prevented by another error."); break; case 502: // 502 Bad Gateway if (m_request.method == DAV_COPY || m_request.method == DAV_MOVE) { // ERR_WRITE_ACCESS_DENIED errorString = i18nc("%1: request type", "Unable to %1 because the destination server refuses " "to accept the file or folder.", action); } break; case 507: // 507 Insufficient Storage // ERR_DISK_FULL errorString = i18n("The destination resource does not have sufficient space " "to record the state of the resource after the execution " "of this method."); break; default: break; } // if ( kError != ERR_SLAVE_DEFINED ) //errorString += " (" + url + ')'; if (callError) { error(errorCode, errorString); } return errorString; } // HTTP generic error static int httpGenericError(const HTTPProtocol::HTTPRequest &request, QString *errorString) { Q_ASSERT(errorString); int errorCode = 0; errorString->clear(); if (request.responseCode == 204) { errorCode = ERR_NO_CONTENT; } return errorCode; } // HTTP DELETE specific errors static int httpDelError(const HTTPProtocol::HTTPRequest &request, QString *errorString) { Q_ASSERT(errorString); int errorCode = 0; const int responseCode = request.responseCode; errorString->clear(); switch (responseCode) { case 204: errorCode = ERR_NO_CONTENT; break; default: break; } if (!errorCode && (responseCode < 200 || responseCode > 400) && responseCode != 404) { errorCode = ERR_SLAVE_DEFINED; *errorString = i18n("The resource cannot be deleted."); } return errorCode; } // HTTP PUT specific errors static int httpPutError(const HTTPProtocol::HTTPRequest &request, QString *errorString) { Q_ASSERT(errorString); int errorCode = 0; const int responseCode = request.responseCode; const QString action(i18nc("request type", "upload %1", request.url.toDisplayString())); switch (responseCode) { case 403: case 405: case 500: // hack: Apache mod_dav returns this instead of 403 (!) // 403 Forbidden // 405 Method Not Allowed // ERR_ACCESS_DENIED *errorString = i18nc("%1: request type", "Access was denied while attempting to %1.", action); errorCode = ERR_SLAVE_DEFINED; break; case 409: // 409 Conflict // ERR_ACCESS_DENIED *errorString = i18n("A resource cannot be created at the destination " "until one or more intermediate collections (folders) " "have been created."); errorCode = ERR_SLAVE_DEFINED; break; case 423: // 423 Locked // ERR_ACCESS_DENIED *errorString = i18nc("%1: request type", "Unable to %1 because the resource is locked.", action); errorCode = ERR_SLAVE_DEFINED; break; case 502: // 502 Bad Gateway // ERR_WRITE_ACCESS_DENIED; *errorString = i18nc("%1: request type", "Unable to %1 because the destination server refuses " "to accept the file or folder.", action); errorCode = ERR_SLAVE_DEFINED; break; case 507: // 507 Insufficient Storage // ERR_DISK_FULL *errorString = i18n("The destination resource does not have sufficient space " "to record the state of the resource after the execution " "of this method."); errorCode = ERR_SLAVE_DEFINED; break; default: break; } if (!errorCode && (responseCode < 200 || responseCode > 400) && responseCode != 404) { errorCode = ERR_SLAVE_DEFINED; *errorString = i18nc("%1: response code, %2: request type", "An unexpected error (%1) occurred while attempting to %2.", responseCode, action); } return errorCode; } bool HTTPProtocol::sendHttpError() { QString errorString; int errorCode = 0; switch (m_request.method) { case HTTP_GET: case HTTP_POST: errorCode = httpGenericError(m_request, &errorString); break; case HTTP_PUT: errorCode = httpPutError(m_request, &errorString); break; case HTTP_DELETE: errorCode = httpDelError(m_request, &errorString); break; default: break; } // Force any message previously shown by the client to be cleared. infoMessage(QLatin1String("")); if (errorCode) { error(errorCode, errorString); return true; } return false; } bool HTTPProtocol::sendErrorPageNotification() { if (!m_request.preferErrorPage) { return false; } if (m_isLoadingErrorPage) { qCWarning(KIO_HTTP) << "called twice during one request, something is probably wrong."; } m_isLoadingErrorPage = true; SlaveBase::errorPage(); return true; } bool HTTPProtocol::isOffline() { if (!m_networkConfig) { m_networkConfig = new QNetworkConfigurationManager(this); } return !m_networkConfig->isOnline(); } void HTTPProtocol::multiGet(const QByteArray &data) { QDataStream stream(data); quint32 n; stream >> n; qCDebug(KIO_HTTP) << n; HTTPRequest saveRequest; if (m_isBusy) { saveRequest = m_request; } resetSessionSettings(); for (unsigned i = 0; i < n; ++i) { QUrl url; stream >> url >> mIncomingMetaData; if (!maybeSetRequestUrl(url)) { continue; } //### should maybe call resetSessionSettings() if the server/domain is // different from the last request! qCDebug(KIO_HTTP) << url; m_request.method = HTTP_GET; m_request.isKeepAlive = true; //readResponseHeader clears it if necessary QString tmp = metaData(QStringLiteral("cache")); if (!tmp.isEmpty()) { m_request.cacheTag.policy = parseCacheControl(tmp); } else { m_request.cacheTag.policy = DEFAULT_CACHE_CONTROL; } m_requestQueue.append(m_request); } if (m_isBusy) { m_request = saveRequest; } #if 0 if (!m_isBusy) { m_isBusy = true; QMutableListIterator it(m_requestQueue); while (it.hasNext()) { m_request = it.next(); it.remove(); proceedUntilResponseContent(); } m_isBusy = false; } #endif if (!m_isBusy) { m_isBusy = true; QMutableListIterator it(m_requestQueue); // send the requests while (it.hasNext()) { m_request = it.next(); sendQuery(); // save the request state so we can pick it up again in the collection phase it.setValue(m_request); qCDebug(KIO_HTTP) << "check one: isKeepAlive =" << m_request.isKeepAlive; if (m_request.cacheTag.ioMode != ReadFromCache) { m_server.initFrom(m_request); } } // collect the responses //### for the moment we use a hack: instead of saving and restoring request-id // we just count up like ParallelGetJobs does. int requestId = 0; Q_FOREACH (const HTTPRequest &r, m_requestQueue) { m_request = r; qCDebug(KIO_HTTP) << "check two: isKeepAlive =" << m_request.isKeepAlive; setMetaData(QStringLiteral("request-id"), QString::number(requestId++)); sendAndKeepMetaData(); if (!(readResponseHeader() && readBody())) { return; } // the "next job" signal for ParallelGetJob is data of size zero which // readBody() sends without our intervention. qCDebug(KIO_HTTP) << "check three: isKeepAlive =" << m_request.isKeepAlive; httpClose(m_request.isKeepAlive); //actually keep-alive is mandatory for pipelining } finished(); m_requestQueue.clear(); m_isBusy = false; } } ssize_t HTTPProtocol::write(const void *_buf, size_t nbytes) { size_t sent = 0; const char *buf = static_cast(_buf); while (sent < nbytes) { int n = TCPSlaveBase::write(buf + sent, nbytes - sent); if (n < 0) { // some error occurred return -1; } sent += n; } return sent; } void HTTPProtocol::clearUnreadBuffer() { m_unreadBuf.clear(); } // Note: the implementation of unread/readBuffered assumes that unread will only // be used when there is extra data we don't want to handle, and not to wait for more data. void HTTPProtocol::unread(char *buf, size_t size) { // implement LIFO (stack) semantics const int newSize = m_unreadBuf.size() + size; m_unreadBuf.resize(newSize); for (size_t i = 0; i < size; i++) { m_unreadBuf.data()[newSize - i - 1] = buf[i]; } if (size) { //hey, we still have data, closed connection or not! m_isEOF = false; } } size_t HTTPProtocol::readBuffered(char *buf, size_t size, bool unlimited) { size_t bytesRead = 0; if (!m_unreadBuf.isEmpty()) { const int bufSize = m_unreadBuf.size(); bytesRead = qMin((int)size, bufSize); for (size_t i = 0; i < bytesRead; i++) { buf[i] = m_unreadBuf.constData()[bufSize - i - 1]; } m_unreadBuf.truncate(bufSize - bytesRead); // If we have an unread buffer and the size of the content returned by the // server is unknown, e.g. chuncked transfer, return the bytes read here since // we may already have enough data to complete the response and don't want to // wait for more. See BR# 180631. if (unlimited) { return bytesRead; } } if (bytesRead < size) { int rawRead = TCPSlaveBase::read(buf + bytesRead, size - bytesRead); if (rawRead < 1) { m_isEOF = true; return bytesRead; } bytesRead += rawRead; } return bytesRead; } //### this method will detect an n*(\r\n) sequence if it crosses invocations. // it will look (n*2 - 1) bytes before start at most and never before buf, naturally. // supported number of newlines are one and two, in line with HTTP syntax. // return true if numNewlines newlines were found. bool HTTPProtocol::readDelimitedText(char *buf, int *idx, int end, int numNewlines) { Q_ASSERT(numNewlines >= 1 && numNewlines <= 2); char mybuf[64]; //somewhere close to the usual line length to avoid unread()ing too much int pos = *idx; while (pos < end && !m_isEOF) { int step = qMin((int)sizeof(mybuf), end - pos); if (m_isChunked) { //we might be reading the end of the very last chunk after which there is no data. //don't try to read any more bytes than there are because it causes stalls //(yes, it shouldn't stall but it does) step = 1; } size_t bufferFill = readBuffered(mybuf, step); for (size_t i = 0; i < bufferFill; ++i, ++pos) { // we copy the data from mybuf to buf immediately and look for the newlines in buf. // that way we don't miss newlines split over several invocations of this method. buf[pos] = mybuf[i]; // did we just copy one or two times the (usually) \r\n delimiter? // until we find even more broken webservers in the wild let's assume that they either // send \r\n (RFC compliant) or \n (broken) as delimiter... if (buf[pos] == '\n') { bool found = numNewlines == 1; if (!found) { // looking for two newlines // Detect \n\n and \n\r\n. The other cases (\r\n\n, \r\n\r\n) are covered by the first two. found = ((pos >= 1 && buf[pos - 1] == '\n') || (pos >= 2 && buf[pos - 2] == '\n' && buf[pos - 1] == '\r')); } if (found) { i++; // unread bytes *after* CRLF unread(&mybuf[i], bufferFill - i); *idx = pos + 1; return true; } } } } *idx = pos; return false; } static bool isCompatibleNextUrl(const QUrl &previous, const QUrl &now) { if (previous.host() != now.host() || previous.port() != now.port()) { return false; } if (previous.userName().isEmpty() && previous.password().isEmpty()) { return true; } return previous.userName() == now.userName() && previous.password() == now.password(); } bool HTTPProtocol::httpShouldCloseConnection() { qCDebug(KIO_HTTP); if (!isConnected()) { return false; } if (!m_request.proxyUrls.isEmpty() && !isAutoSsl()) { Q_FOREACH (const QString &url, m_request.proxyUrls) { if (url != QLatin1String("DIRECT")) { if (isCompatibleNextUrl(m_server.proxyUrl, QUrl(url))) { return false; } } } return true; } return !isCompatibleNextUrl(m_server.url, m_request.url); } bool HTTPProtocol::httpOpenConnection() { qCDebug(KIO_HTTP); m_server.clear(); // Only save proxy auth information after proxy authentication has // actually taken place, which will set up exactly this connection. disconnect(socket(), SIGNAL(connected()), this, SLOT(saveProxyAuthenticationForSocket())); clearUnreadBuffer(); int connectError = 0; QString errorString; // Get proxy information... if (m_request.proxyUrls.isEmpty()) { m_request.proxyUrls = config()->readEntry("ProxyUrls", QStringList()); qCDebug(KIO_HTTP) << "Proxy URLs:" << m_request.proxyUrls; } if (m_request.proxyUrls.isEmpty()) { QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy); connectError = connectToHost(m_request.url.host(), m_request.url.port(defaultPort()), &errorString); } else { QList badProxyUrls; Q_FOREACH (const QString &proxyUrl, m_request.proxyUrls) { if (proxyUrl == QLatin1String("DIRECT")) { QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy); connectError = connectToHost(m_request.url.host(), m_request.url.port(defaultPort()), &errorString); if (connectError == 0) { //qDebug() << "Connected DIRECT: host=" << m_request.url.host() << "port=" << m_request.url.port(defaultPort()); break; } else { continue; } } const QUrl url(proxyUrl); const QString proxyScheme(url.scheme()); if (!supportedProxyScheme(proxyScheme)) { connectError = ERR_CANNOT_CONNECT; errorString = url.toDisplayString(); badProxyUrls << url; continue; } QNetworkProxy::ProxyType proxyType = QNetworkProxy::NoProxy; if (proxyScheme == QLatin1String("socks")) { proxyType = QNetworkProxy::Socks5Proxy; } else if (isAutoSsl()) { proxyType = QNetworkProxy::HttpProxy; } qCDebug(KIO_HTTP) << "Connecting to proxy: address=" << proxyUrl << "type=" << proxyType; if (proxyType == QNetworkProxy::NoProxy) { QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy); connectError = connectToHost(url.host(), url.port(), &errorString); if (connectError == 0) { m_request.proxyUrl = url; //qDebug() << "Connected to proxy: host=" << url.host() << "port=" << url.port(); break; } else { if (connectError == ERR_UNKNOWN_HOST) { connectError = ERR_UNKNOWN_PROXY_HOST; } //qDebug() << "Failed to connect to proxy:" << proxyUrl; badProxyUrls << url; } } else { QNetworkProxy proxy(proxyType, url.host(), url.port(), url.userName(), url.password()); QNetworkProxy::setApplicationProxy(proxy); connectError = connectToHost(m_request.url.host(), m_request.url.port(defaultPort()), &errorString); if (connectError == 0) { qCDebug(KIO_HTTP) << "Tunneling thru proxy: host=" << url.host() << "port=" << url.port(); break; } else { if (connectError == ERR_UNKNOWN_HOST) { connectError = ERR_UNKNOWN_PROXY_HOST; } qCDebug(KIO_HTTP) << "Failed to connect to proxy:" << proxyUrl; badProxyUrls << url; QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy); } } } if (!badProxyUrls.isEmpty()) { //TODO: Notify the client of BAD proxy addresses (needed for PAC setups). } } if (connectError != 0) { error(connectError, errorString); return false; } // Disable Nagle's algorithm, i.e turn on TCP_NODELAY. KTcpSocket *sock = qobject_cast(socket()); if (sock) { qCDebug(KIO_HTTP) << "TCP_NODELAY:" << sock->socketOption(QAbstractSocket::LowDelayOption); sock->setSocketOption(QAbstractSocket::LowDelayOption, 1); } m_server.initFrom(m_request); connected(); return true; } bool HTTPProtocol::satisfyRequestFromCache(bool *cacheHasPage) { qCDebug(KIO_HTTP); if (m_request.cacheTag.useCache) { const bool offline = isOffline(); if (offline && m_request.cacheTag.policy != KIO::CC_Reload) { m_request.cacheTag.policy = KIO::CC_CacheOnly; } const bool isCacheOnly = m_request.cacheTag.policy == KIO::CC_CacheOnly; const CacheTag::CachePlan plan = m_request.cacheTag.plan(m_maxCacheAge); bool openForReading = false; if (plan == CacheTag::UseCached || plan == CacheTag::ValidateCached) { openForReading = cacheFileOpenRead(); if (!openForReading && (isCacheOnly || offline)) { // cache-only or offline -> we give a definite answer and it is "no" *cacheHasPage = false; if (isCacheOnly) { error(ERR_DOES_NOT_EXIST, m_request.url.toDisplayString()); } else if (offline) { error(ERR_CANNOT_CONNECT, m_request.url.toDisplayString()); } return true; } } if (openForReading) { m_request.cacheTag.ioMode = ReadFromCache; *cacheHasPage = true; // return false if validation is required, so a network request will be sent return m_request.cacheTag.plan(m_maxCacheAge) == CacheTag::UseCached; } } *cacheHasPage = false; return false; } QString HTTPProtocol::formatRequestUri() const { // Only specify protocol, host and port when they are not already clear, i.e. when // we handle HTTP proxying ourself and the proxy server needs to know them. // Sending protocol/host/port in other cases confuses some servers, and it's not their fault. if (isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) { QUrl u; QString protocol = m_request.url.scheme(); if (protocol.startsWith(QLatin1String("webdav"))) { protocol.replace(0, qstrlen("webdav"), QStringLiteral("http")); } u.setScheme(protocol); u.setHost(m_request.url.host()); // if the URL contained the default port it should have been stripped earlier Q_ASSERT(m_request.url.port() != defaultPort()); u.setPort(m_request.url.port()); u.setPath(m_request.url.path(QUrl::FullyEncoded)); u.setQuery(m_request.url.query(QUrl::FullyEncoded)); return u.toString(QUrl::FullyEncoded); } else { QString result = m_request.url.path(QUrl::FullyEncoded); if (m_request.url.hasQuery()) { result += QLatin1Char('?') + m_request.url.query(QUrl::FullyEncoded); } return result; } } /** * This function is responsible for opening up the connection to the remote * HTTP server and sending the header. If this requires special * authentication or other such fun stuff, then it will handle it. This * function will NOT receive anything from the server, however. This is in * contrast to previous incarnations of 'httpOpen' as this method used to be * called. * * The basic process now is this: * * 1) Open up the socket and port * 2) Format our request/header * 3) Send the header to the remote server * 4) Call sendBody() if the HTTP method requires sending body data */ bool HTTPProtocol::sendQuery() { qCDebug(KIO_HTTP); // Cannot have an https request without autoSsl! This can // only happen if the current installation does not support SSL... if (isEncryptedHttpVariety(m_protocol) && !isAutoSsl()) { error(ERR_UNSUPPORTED_PROTOCOL, toQString(m_protocol)); return false; } // Check the reusability of the current connection. if (httpShouldCloseConnection()) { httpCloseConnection(); } // Create a new connection to the remote machine if we do // not already have one... // NB: the !m_socketProxyAuth condition is a workaround for a proxied Qt socket sometimes // looking disconnected after receiving the initial 407 response. // I guess the Qt socket fails to hide the effect of proxy-connection: close after receiving // the 407 header. if ((!isConnected() && !m_socketProxyAuth)) { if (!httpOpenConnection()) { qCDebug(KIO_HTTP) << "Couldn't connect, oopsie!"; return false; } } m_request.cacheTag.ioMode = NoCache; m_request.cacheTag.servedDate = QDateTime(); m_request.cacheTag.lastModifiedDate = QDateTime(); m_request.cacheTag.expireDate = QDateTime(); QString header; bool hasBodyData = false; bool hasDavData = false; { m_request.sentMethodString = m_request.methodString(); header = toQString(m_request.sentMethodString) + QLatin1Char(' '); QString davHeader; // Fill in some values depending on the HTTP method to guide further processing switch (m_request.method) { case HTTP_GET: { bool cacheHasPage = false; if (satisfyRequestFromCache(&cacheHasPage)) { qCDebug(KIO_HTTP) << "cacheHasPage =" << cacheHasPage; return cacheHasPage; } if (!cacheHasPage) { // start a new cache file later if appropriate m_request.cacheTag.ioMode = WriteToCache; } break; } case HTTP_HEAD: break; case HTTP_PUT: case HTTP_POST: hasBodyData = true; break; case HTTP_DELETE: case HTTP_OPTIONS: break; case DAV_PROPFIND: hasDavData = true; davHeader = QStringLiteral("Depth: "); if (hasMetaData(QStringLiteral("davDepth"))) { qCDebug(KIO_HTTP) << "Reading DAV depth from metadata:" << metaData( QStringLiteral("davDepth") ); davHeader += metaData(QStringLiteral("davDepth")); } else { if (m_request.davData.depth == 2) { davHeader += QLatin1String("infinity"); } else { davHeader += QString::number(m_request.davData.depth); } } davHeader += QLatin1String("\r\n"); break; case DAV_PROPPATCH: hasDavData = true; break; case DAV_MKCOL: break; case DAV_COPY: case DAV_MOVE: davHeader = QLatin1String("Destination: ") + m_request.davData.desturl; // infinity depth means copy recursively // (optional for copy -> but is the desired action) davHeader += QLatin1String("\r\nDepth: infinity\r\nOverwrite: "); davHeader += QLatin1Char(m_request.davData.overwrite ? 'T' : 'F'); davHeader += QLatin1String("\r\n"); break; case DAV_LOCK: davHeader = QStringLiteral("Timeout: "); { uint timeout = 0; if (hasMetaData(QStringLiteral("davTimeout"))) { timeout = metaData(QStringLiteral("davTimeout")).toUInt(); } if (timeout == 0) { davHeader += QLatin1String("Infinite"); } else { davHeader += QLatin1String("Seconds-") + QString::number(timeout); } } davHeader += QLatin1String("\r\n"); hasDavData = true; break; case DAV_UNLOCK: davHeader = QLatin1String("Lock-token: ") + metaData(QStringLiteral("davLockToken")) + QLatin1String("\r\n"); break; case DAV_SEARCH: case DAV_REPORT: hasDavData = true; /* fall through */ case DAV_SUBSCRIBE: case DAV_UNSUBSCRIBE: case DAV_POLL: break; default: error(ERR_UNSUPPORTED_ACTION, QString()); return false; } // DAV_POLL; DAV_NOTIFY header += formatRequestUri() + QLatin1String(" HTTP/1.1\r\n"); /* start header */ /* support for virtual hosts and required by HTTP 1.1 */ header += QLatin1String("Host: ") + m_request.encoded_hostname; if (m_request.url.port(defaultPort()) != defaultPort()) { header += QLatin1Char(':') + QString::number(m_request.url.port()); } header += QLatin1String("\r\n"); // Support old HTTP/1.0 style keep-alive header for compatibility // purposes as well as performance improvements while giving end // users the ability to disable this feature for proxy servers that // don't support it, e.g. junkbuster proxy server. if (isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) { header += QLatin1String("Proxy-Connection: "); } else { header += QLatin1String("Connection: "); } if (m_request.isKeepAlive) { header += QLatin1String("keep-alive\r\n"); } else { header += QLatin1String("close\r\n"); } if (!m_request.userAgent.isEmpty()) { header += QLatin1String("User-Agent: "); header += m_request.userAgent; header += QLatin1String("\r\n"); } if (!m_request.referrer.isEmpty()) { header += QLatin1String("Referer: "); //Don't try to correct spelling! header += m_request.referrer; header += QLatin1String("\r\n"); } if (m_request.endoffset > m_request.offset) { header += QLatin1String("Range: bytes="); header += KIO::number(m_request.offset); header += QLatin1Char('-'); header += KIO::number(m_request.endoffset); header += QLatin1String("\r\n"); qCDebug(KIO_HTTP) << "kio_http : Range =" << KIO::number(m_request.offset) << "-" << KIO::number(m_request.endoffset); } else if (m_request.offset > 0 && m_request.endoffset == 0) { header += QLatin1String("Range: bytes="); header += KIO::number(m_request.offset); header += QLatin1String("-\r\n"); qCDebug(KIO_HTTP) << "kio_http: Range =" << KIO::number(m_request.offset); } if (!m_request.cacheTag.useCache || m_request.cacheTag.policy == CC_Reload) { /* No caching for reload */ header += QLatin1String("Pragma: no-cache\r\n"); /* for HTTP/1.0 caches */ header += QLatin1String("Cache-control: no-cache\r\n"); /* for HTTP >=1.1 caches */ } else if (m_request.cacheTag.plan(m_maxCacheAge) == CacheTag::ValidateCached) { qCDebug(KIO_HTTP) << "needs validation, performing conditional get."; /* conditional get */ if (!m_request.cacheTag.etag.isEmpty()) { header += QLatin1String("If-None-Match: ") + m_request.cacheTag.etag + QLatin1String("\r\n"); } if (m_request.cacheTag.lastModifiedDate.isValid()) { const QString httpDate = formatHttpDate(m_request.cacheTag.lastModifiedDate); header += QStringLiteral("If-Modified-Since: ") + httpDate + QStringLiteral("\r\n"); setMetaData(QStringLiteral("modified"), httpDate); } } header += QLatin1String("Accept: "); const QString acceptHeader = metaData(QStringLiteral("accept")); if (!acceptHeader.isEmpty()) { header += acceptHeader; } else { header += QLatin1String(DEFAULT_ACCEPT_HEADER); } header += QLatin1String("\r\n"); if (m_request.allowTransferCompression) { header += QLatin1String("Accept-Encoding: gzip, deflate, x-gzip, x-deflate\r\n"); } if (!m_request.charsets.isEmpty()) { header += QLatin1String("Accept-Charset: ") + m_request.charsets + QLatin1String("\r\n"); } if (!m_request.languages.isEmpty()) { header += QLatin1String("Accept-Language: ") + m_request.languages + QLatin1String("\r\n"); } QString cookieStr; const QString cookieMode = metaData(QStringLiteral("cookies")).toLower(); if (cookieMode == QLatin1String("none")) { m_request.cookieMode = HTTPRequest::CookiesNone; } else if (cookieMode == QLatin1String("manual")) { m_request.cookieMode = HTTPRequest::CookiesManual; cookieStr = metaData(QStringLiteral("setcookies")); } else { m_request.cookieMode = HTTPRequest::CookiesAuto; if (m_request.useCookieJar) { cookieStr = findCookies(m_request.url.toString()); } } if (!cookieStr.isEmpty()) { header += cookieStr + QLatin1String("\r\n"); } const QString customHeader = metaData(QStringLiteral("customHTTPHeader")); if (!customHeader.isEmpty()) { header += sanitizeCustomHTTPHeader(customHeader); header += QLatin1String("\r\n"); } const QString contentType = metaData(QStringLiteral("content-type")); if (!contentType.isEmpty()) { if (!contentType.startsWith(QLatin1String("content-type"), Qt::CaseInsensitive)) { header += QLatin1String("Content-Type: "); } header += contentType; header += QLatin1String("\r\n"); } // DoNotTrack feature... if (config()->readEntry("DoNotTrack", false)) { header += QLatin1String("DNT: 1\r\n"); } // Remember that at least one failed (with 401 or 407) request/response // roundtrip is necessary for the server to tell us that it requires // authentication. However, we proactively add authentication headers if when // we have cached credentials to avoid the extra roundtrip where possible. header += authenticationHeader(); if (m_protocol == "webdav" || m_protocol == "webdavs") { header += davProcessLocks(); // add extra webdav headers, if supplied davHeader += metaData(QStringLiteral("davHeader")); // Set content type of webdav data if (hasDavData) { davHeader += QStringLiteral("Content-Type: text/xml; charset=utf-8\r\n"); } // add extra header elements for WebDAV header += davHeader; } } qCDebug(KIO_HTTP) << "============ Sending Header:"; Q_FOREACH (const QString &s, header.split(QLatin1String("\r\n"), QString::SkipEmptyParts)) { qCDebug(KIO_HTTP) << s; } // End the header iff there is no payload data. If we do have payload data // sendBody() will add another field to the header, Content-Length. if (!hasBodyData && !hasDavData) { header += QStringLiteral("\r\n"); } // Now that we have our formatted header, let's send it! // Clear out per-connection settings... resetConnectionSettings(); // Send the data to the remote machine... const QByteArray headerBytes = header.toLatin1(); ssize_t written = write(headerBytes.constData(), headerBytes.length()); bool sendOk = (written == (ssize_t) headerBytes.length()); if (!sendOk) { qCDebug(KIO_HTTP) << "Connection broken! (" << m_request.url.host() << ")" << " -- intended to write" << headerBytes.length() << "bytes but wrote" << (int)written << "."; // The server might have closed the connection due to a timeout, or maybe // some transport problem arose while the connection was idle. if (m_request.isKeepAlive) { httpCloseConnection(); return true; // Try again } qCDebug(KIO_HTTP) << "sendOk == false. Connection broken !" << " -- intended to write" << headerBytes.length() << "bytes but wrote" << (int)written << "."; error(ERR_CONNECTION_BROKEN, m_request.url.host()); return false; } else { qCDebug(KIO_HTTP) << "sent it!"; } bool res = true; if (hasBodyData || hasDavData) { res = sendBody(); } infoMessage(i18n("%1 contacted. Waiting for reply...", m_request.url.host())); return res; } void HTTPProtocol::forwardHttpResponseHeader(bool forwardImmediately) { // Send the response header if it was requested... if (!config()->readEntry("PropagateHttpHeader", false)) { return; } setMetaData(QStringLiteral("HTTP-Headers"), m_responseHeaders.join(QString(QLatin1Char('\n')))); if (forwardImmediately) { sendMetaData(); } } bool HTTPProtocol::parseHeaderFromCache() { qCDebug(KIO_HTTP); if (!cacheFileReadTextHeader2()) { return false; } Q_FOREACH (const QString &str, m_responseHeaders) { const QString header = str.trimmed(); if (header.startsWith(QLatin1String("content-type:"), Qt::CaseInsensitive)) { int pos = header.indexOf(QLatin1String("charset="), Qt::CaseInsensitive); if (pos != -1) { const QString charset = header.mid(pos + 8).toLower(); m_request.cacheTag.charset = charset; setMetaData(QStringLiteral("charset"), charset); } } else if (header.startsWith(QLatin1String("content-language:"), Qt::CaseInsensitive)) { const QString language = header.mid(17).trimmed().toLower(); setMetaData(QStringLiteral("content-language"), language); } else if (header.startsWith(QLatin1String("content-disposition:"), Qt::CaseInsensitive)) { parseContentDisposition(header.mid(20).toLower()); } } if (m_request.cacheTag.lastModifiedDate.isValid()) { setMetaData(QStringLiteral("modified"), formatHttpDate(m_request.cacheTag.lastModifiedDate)); } // this header comes from the cache, so the response must have been cacheable :) setCacheabilityMetadata(true); qCDebug(KIO_HTTP) << "Emitting mimeType" << m_mimeType; forwardHttpResponseHeader(false); mimeType(m_mimeType); // IMPORTANT: Do not remove the call below or the http response headers will // not be available to the application if this slave is put on hold. forwardHttpResponseHeader(); return true; } void HTTPProtocol::fixupResponseMimetype() { if (m_mimeType.isEmpty()) { return; } qCDebug(KIO_HTTP) << "before fixup" << m_mimeType; // Convert some common mimetypes to standard mimetypes if (m_mimeType == QLatin1String("application/x-targz")) { m_mimeType = QStringLiteral("application/x-compressed-tar"); } else if (m_mimeType == QLatin1String("image/x-png")) { m_mimeType = QStringLiteral("image/png"); } else if (m_mimeType == QLatin1String("audio/x-mp3") || m_mimeType == QLatin1String("audio/x-mpeg") || m_mimeType == QLatin1String("audio/mp3")) { m_mimeType = QStringLiteral("audio/mpeg"); } else if (m_mimeType == QLatin1String("audio/microsoft-wave")) { m_mimeType = QStringLiteral("audio/x-wav"); } else if (m_mimeType == QLatin1String("image/x-ms-bmp")) { m_mimeType = QStringLiteral("image/bmp"); } // Crypto ones.... else if (m_mimeType == QLatin1String("application/pkix-cert") || m_mimeType == QLatin1String("application/binary-certificate")) { m_mimeType = QStringLiteral("application/x-x509-ca-cert"); } // Prefer application/x-compressed-tar or x-gzpostscript over application/x-gzip. else if (m_mimeType == QLatin1String("application/x-gzip")) { if ((m_request.url.path().endsWith(QLatin1String(".tar.gz"))) || (m_request.url.path().endsWith(QLatin1String(".tar")))) { m_mimeType = QStringLiteral("application/x-compressed-tar"); } if ((m_request.url.path().endsWith(QLatin1String(".ps.gz")))) { m_mimeType = QStringLiteral("application/x-gzpostscript"); } } // Prefer application/x-xz-compressed-tar over application/x-xz for LMZA compressed // tar files. Arch Linux AUR servers notoriously send the wrong mimetype for this. else if (m_mimeType == QLatin1String("application/x-xz")) { if (m_request.url.path().endsWith(QLatin1String(".tar.xz")) || m_request.url.path().endsWith(QLatin1String(".txz"))) { m_mimeType = QStringLiteral("application/x-xz-compressed-tar"); } } // Some webservers say "text/plain" when they mean "application/x-bzip" else if ((m_mimeType == QLatin1String("text/plain")) || (m_mimeType == QLatin1String("application/octet-stream"))) { const QString ext = QFileInfo(m_request.url.path()).suffix().toUpper(); if (ext == QLatin1String("BZ2")) { m_mimeType = QStringLiteral("application/x-bzip"); } else if (ext == QLatin1String("PEM")) { m_mimeType = QStringLiteral("application/x-x509-ca-cert"); } else if (ext == QLatin1String("SWF")) { m_mimeType = QStringLiteral("application/x-shockwave-flash"); } else if (ext == QLatin1String("PLS")) { m_mimeType = QStringLiteral("audio/x-scpls"); } else if (ext == QLatin1String("WMV")) { m_mimeType = QStringLiteral("video/x-ms-wmv"); } else if (ext == QLatin1String("WEBM")) { m_mimeType = QStringLiteral("video/webm"); } else if (ext == QLatin1String("DEB")) { m_mimeType = QStringLiteral("application/x-deb"); } } qCDebug(KIO_HTTP) << "after fixup" << m_mimeType; } void HTTPProtocol::fixupResponseContentEncoding() { // WABA: Correct for tgz files with a gzip-encoding. // They really shouldn't put gzip in the Content-Encoding field! // Web-servers really shouldn't do this: They let Content-Size refer // to the size of the tgz file, not to the size of the tar file, // while the Content-Type refers to "tar" instead of "tgz". if (!m_contentEncodings.isEmpty() && m_contentEncodings.last() == QLatin1String("gzip")) { if (m_mimeType == QLatin1String("application/x-tar")) { m_contentEncodings.removeLast(); m_mimeType = QStringLiteral("application/x-compressed-tar"); } else if (m_mimeType == QLatin1String("application/postscript")) { // LEONB: Adding another exception for psgz files. // Could we use the mimelnk files instead of hardcoding all this? m_contentEncodings.removeLast(); m_mimeType = QStringLiteral("application/x-gzpostscript"); } else if ((m_request.allowTransferCompression && m_mimeType == QLatin1String("text/html")) || (m_request.allowTransferCompression && m_mimeType != QLatin1String("application/x-compressed-tar") && m_mimeType != QLatin1String("application/x-tgz") && // deprecated name m_mimeType != QLatin1String("application/x-targz") && // deprecated name m_mimeType != QLatin1String("application/x-gzip"))) { // Unzip! } else { m_contentEncodings.removeLast(); m_mimeType = QStringLiteral("application/x-gzip"); } } // We can't handle "bzip2" encoding (yet). So if we get something with // bzip2 encoding, we change the mimetype to "application/x-bzip". // Note for future changes: some web-servers send both "bzip2" as // encoding and "application/x-bzip[2]" as mimetype. That is wrong. // currently that doesn't bother us, because we remove the encoding // and set the mimetype to x-bzip anyway. if (!m_contentEncodings.isEmpty() && m_contentEncodings.last() == QLatin1String("bzip2")) { m_contentEncodings.removeLast(); m_mimeType = QStringLiteral("application/x-bzip"); } } #ifdef Q_CC_MSVC // strncasecmp does not exist on windows, have to use _strnicmp static inline int strncasecmp(const char *c1, const char* c2, size_t max) { return _strnicmp(c1, c2, max); } #endif //Return true if the term was found, false otherwise. Advance *pos. //If (*pos + strlen(term) >= end) just advance *pos to end and return false. //This means that users should always search for the shortest terms first. static bool consume(const char input[], int *pos, int end, const char *term) { // note: gcc/g++ is quite good at optimizing away redundant strlen()s int idx = *pos; if (idx + (int)strlen(term) >= end) { *pos = end; return false; } if (strncasecmp(&input[idx], term, strlen(term)) == 0) { *pos = idx + strlen(term); return true; } return false; } /** * This function will read in the return header from the server. It will * not read in the body of the return message. It will also not transmit * the header to our client as the client doesn't need to know the gory * details of HTTP headers. */ bool HTTPProtocol::readResponseHeader() { resetResponseParsing(); if (m_request.cacheTag.ioMode == ReadFromCache && m_request.cacheTag.plan(m_maxCacheAge) == CacheTag::UseCached) { // parseHeaderFromCache replaces this method in case of cached content return parseHeaderFromCache(); } try_again: qCDebug(KIO_HTTP); bool upgradeRequired = false; // Server demands that we upgrade to something // This is also true if we ask to upgrade and // the server accepts, since we are now // committed to doing so bool noHeadersFound = false; m_request.cacheTag.charset.clear(); m_responseHeaders.clear(); static const int maxHeaderSize = 128 * 1024; char buffer[maxHeaderSize]; bool cont = false; bool bCanResume = false; if (!isConnected()) { qCDebug(KIO_HTTP) << "No connection."; return false; // Reestablish connection and try again } #if 0 // NOTE: This is unnecessary since TCPSlaveBase::read does the same exact // thing. Plus, if we are unable to read from the socket we need to resend // the request as done below, not error out! Do not assume remote server // will honor persistent connections!! if (!waitForResponse(m_remoteRespTimeout)) { qCDebug(KIO_HTTP) << "Got socket error:" << socket()->errorString(); // No response error error(ERR_SERVER_TIMEOUT, m_request.url.host()); return false; } #endif int bufPos = 0; bool foundDelimiter = readDelimitedText(buffer, &bufPos, maxHeaderSize, 1); if (!foundDelimiter && bufPos < maxHeaderSize) { qCDebug(KIO_HTTP) << "EOF while waiting for header start."; if (m_request.isKeepAlive && m_iEOFRetryCount < 2) { m_iEOFRetryCount++; httpCloseConnection(); // Try to reestablish connection. return false; // Reestablish connection and try again. } if (m_request.method == HTTP_HEAD) { // HACK // Some web-servers fail to respond properly to a HEAD request. // We compensate for their failure to properly implement the HTTP standard // by assuming that they will be sending html. qCDebug(KIO_HTTP) << "HEAD -> returned mimetype:" << DEFAULT_MIME_TYPE; mimeType(QStringLiteral(DEFAULT_MIME_TYPE)); return true; } qCDebug(KIO_HTTP) << "Connection broken !"; error(ERR_CONNECTION_BROKEN, m_request.url.host()); return false; } if (!foundDelimiter) { //### buffer too small for first line of header(!) Q_ASSERT(0); } qCDebug(KIO_HTTP) << "============ Received Status Response:"; qCDebug(KIO_HTTP) << QByteArray(buffer, bufPos).trimmed(); HTTP_REV httpRev = HTTP_None; int idx = 0; if (idx != bufPos && buffer[idx] == '<') { qCDebug(KIO_HTTP) << "No valid HTTP header found! Document starts with XML/HTML tag"; // document starts with a tag, assume HTML instead of text/plain m_mimeType = QStringLiteral("text/html"); m_request.responseCode = 200; // Fake it httpRev = HTTP_Unknown; m_request.isKeepAlive = false; noHeadersFound = true; // put string back unread(buffer, bufPos); goto endParsing; } // "HTTP/1.1" or similar if (consume(buffer, &idx, bufPos, "ICY ")) { httpRev = SHOUTCAST; m_request.isKeepAlive = false; } else if (consume(buffer, &idx, bufPos, "HTTP/")) { if (consume(buffer, &idx, bufPos, "1.0")) { httpRev = HTTP_10; m_request.isKeepAlive = false; } else if (consume(buffer, &idx, bufPos, "1.1")) { httpRev = HTTP_11; } } if (httpRev == HTTP_None && bufPos != 0) { // Remote server does not seem to speak HTTP at all // Put the crap back into the buffer and hope for the best qCDebug(KIO_HTTP) << "DO NOT WANT." << bufPos; unread(buffer, bufPos); if (m_request.responseCode) { m_request.prevResponseCode = m_request.responseCode; } m_request.responseCode = 200; // Fake it httpRev = HTTP_Unknown; m_request.isKeepAlive = false; noHeadersFound = true; goto endParsing; } // response code //### maybe wrong if we need several iterations for this response... //### also, do multiple iterations (cf. try_again) to parse one header work w/ pipelining? if (m_request.responseCode) { m_request.prevResponseCode = m_request.responseCode; } skipSpace(buffer, &idx, bufPos); //TODO saner handling of invalid response code strings if (idx != bufPos) { m_request.responseCode = atoi(&buffer[idx]); } else { m_request.responseCode = 200; } // move idx to start of (yet to be fetched) next line, skipping the "OK" idx = bufPos; // (don't bother parsing the "OK", what do we do if it isn't there anyway?) // immediately act on most response codes... // Protect users against bogus username intended to fool them into visiting // sites they had no intention of visiting. if (isPotentialSpoofingAttack(m_request, config())) { qCDebug(KIO_HTTP) << "**** POTENTIAL ADDRESS SPOOFING:" << m_request.url; const int result = messageBox(WarningYesNo, i18nc("@info Security check on url being accessed", "

You are about to log in to the site \"%1\" " "with the username \"%2\", but the website " "does not require authentication. " "This may be an attempt to trick you.

" "

Is \"%1\" the site you want to visit?

", m_request.url.host(), m_request.url.userName()), i18nc("@title:window", "Confirm Website Access")); if (result == SlaveBase::No) { error(ERR_USER_CANCELED, m_request.url.toDisplayString()); return false; } setMetaData(QStringLiteral("{internal~currenthost}LastSpoofedUserName"), m_request.url.userName()); } if (m_request.responseCode != 200 && m_request.responseCode != 304) { m_request.cacheTag.ioMode = NoCache; if (m_request.responseCode >= 500 && m_request.responseCode <= 599) { // Server side errors if (m_request.method == HTTP_HEAD) { ; // Ignore error } else { if (!sendErrorPageNotification()) { error(ERR_INTERNAL_SERVER, m_request.url.toDisplayString()); return false; } } } else if (m_request.responseCode == 416) { // Range not supported m_request.offset = 0; return false; // Try again. } else if (m_request.responseCode == 426) { // Upgrade Required upgradeRequired = true; } else if (m_request.responseCode >= 400 && m_request.responseCode <= 499 && !isAuthenticationRequired(m_request.responseCode)) { // Any other client errors // Tell that we will only get an error page here. if (!sendErrorPageNotification()) { if (m_request.responseCode == 403) { error(ERR_ACCESS_DENIED, m_request.url.toDisplayString()); } else { error(ERR_DOES_NOT_EXIST, m_request.url.toDisplayString()); } } } else if (m_request.responseCode >= 301 && m_request.responseCode <= 308) { // NOTE: According to RFC 2616 (section 10.3.[2-4,8]), 301 and 302 // redirects for a POST operation should not be convered to a GET // request. That should only be done for a 303 response. However, // because almost all other client implementations do exactly that // in violation of the spec, many servers have simply adapted to // this way of doing things! Thus, we are forced to do the same // thing here. Otherwise, we loose compatibility and might not be // able to correctly retrieve sites that redirect. switch (m_request.responseCode) { case 301: // Moved Permanently setMetaData(QStringLiteral("permanent-redirect"), QStringLiteral("true")); // fall through case 302: // Found if (m_request.sentMethodString == "POST") { m_request.method = HTTP_GET; // FORCE a GET setMetaData(QStringLiteral("redirect-to-get"), QStringLiteral("true")); } break; case 303: // See Other if (m_request.method != HTTP_HEAD) { m_request.method = HTTP_GET; // FORCE a GET setMetaData(QStringLiteral("redirect-to-get"), QStringLiteral("true")); } break; case 308: // Permanent Redirect setMetaData(QStringLiteral("permanent-redirect"), QStringLiteral("true")); break; default: break; } } else if (m_request.responseCode == 204) { // No content // error(ERR_NO_CONTENT, i18n("Data have been successfully sent.")); // Short circuit and do nothing! // The original handling here was wrong, this is not an error: eg. in the // example of a 204 No Content response to a PUT completing. // return false; } else if (m_request.responseCode == 206) { if (m_request.offset) { bCanResume = true; } } else if (m_request.responseCode == 102) { // Processing (for WebDAV) /*** * This status code is given when the server expects the * command to take significant time to complete. So, inform * the user. */ infoMessage(i18n("Server processing request, please wait...")); cont = true; } else if (m_request.responseCode == 100) { // We got 'Continue' - ignore it cont = true; } } // (m_request.responseCode != 200 && m_request.responseCode != 304) endParsing: bool authRequiresAnotherRoundtrip = false; // Skip the whole header parsing if we got no HTTP headers at all if (!noHeadersFound) { // Auth handling const bool wasAuthError = isAuthenticationRequired(m_request.prevResponseCode); const bool isAuthError = isAuthenticationRequired(m_request.responseCode); const bool sameAuthError = (m_request.responseCode == m_request.prevResponseCode); qCDebug(KIO_HTTP) << "wasAuthError=" << wasAuthError << "isAuthError=" << isAuthError << "sameAuthError=" << sameAuthError; // Not the same authorization error as before and no generic error? // -> save the successful credentials. if (wasAuthError && (m_request.responseCode < 400 || (isAuthError && !sameAuthError))) { saveAuthenticationData(); } // done with the first line; now tokenize the other lines // TODO review use of STRTOLL vs. QByteArray::toInt() foundDelimiter = readDelimitedText(buffer, &bufPos, maxHeaderSize, 2); qCDebug(KIO_HTTP) << " -- full response:" << endl << QByteArray(buffer, bufPos).trimmed(); // Use this to see newlines: //qCDebug(KIO_HTTP) << " -- full response:" << endl << QByteArray(buffer, bufPos).replace("\r", "\\r").replace("\n", "\\n\n"); Q_ASSERT(foundDelimiter); //NOTE because tokenizer will overwrite newlines in case of line continuations in the header // unread(buffer, bufSize) will not generally work anymore. we don't need it either. // either we have a http response line -> try to parse the header, fail if it doesn't work // or we have garbage -> fail. HeaderTokenizer tokenizer(buffer); tokenizer.tokenize(idx, sizeof(buffer)); // Note that not receiving "accept-ranges" means that all bets are off // wrt the server supporting ranges. TokenIterator tIt = tokenizer.iterator("accept-ranges"); if (tIt.hasNext() && tIt.next().toLower().startsWith("none")) { // krazy:exclude=strings bCanResume = false; } tIt = tokenizer.iterator("keep-alive"); while (tIt.hasNext()) { QByteArray ka = tIt.next().trimmed().toLower(); if (ka.startsWith("timeout=")) { // krazy:exclude=strings int ka_timeout = ka.mid(qstrlen("timeout=")).trimmed().toInt(); if (ka_timeout > 0) { m_request.keepAliveTimeout = ka_timeout; } if (httpRev == HTTP_10) { m_request.isKeepAlive = true; } break; // we want to fetch ka timeout only } } // get the size of our data tIt = tokenizer.iterator("content-length"); if (tIt.hasNext()) { m_iSize = STRTOLL(tIt.next().constData(), nullptr, 10); } tIt = tokenizer.iterator("content-location"); if (tIt.hasNext()) { setMetaData(QStringLiteral("content-location"), toQString(tIt.next().trimmed())); } // which type of data do we have? QString mediaValue; QString mediaAttribute; tIt = tokenizer.iterator("content-type"); if (tIt.hasNext()) { QList l = tIt.next().split(';'); if (!l.isEmpty()) { // Assign the mime-type. m_mimeType = toQString(l.first().trimmed().toLower()); if (m_mimeType.startsWith(QLatin1Char('"'))) { m_mimeType.remove(0, 1); } if (m_mimeType.endsWith(QLatin1Char('"'))) { m_mimeType.chop(1); } qCDebug(KIO_HTTP) << "Content-type:" << m_mimeType; l.removeFirst(); } // If we still have text, then it means we have a mime-type with a // parameter (eg: charset=iso-8851) ; so let's get that... Q_FOREACH (const QByteArray &statement, l) { const int index = statement.indexOf('='); if (index <= 0) { mediaAttribute = toQString(statement.mid(0, index)); } else { mediaAttribute = toQString(statement.mid(0, index)); mediaValue = toQString(statement.mid(index + 1)); } mediaAttribute = mediaAttribute.trimmed(); mediaValue = mediaValue.trimmed(); bool quoted = false; if (mediaValue.startsWith(QLatin1Char('"'))) { quoted = true; mediaValue.remove(0, 1); } if (mediaValue.endsWith(QLatin1Char('"'))) { mediaValue.chop(1); } qCDebug(KIO_HTTP) << "Encoding-type:" << mediaAttribute << "=" << mediaValue; if (mediaAttribute == QLatin1String("charset")) { mediaValue = mediaValue.toLower(); m_request.cacheTag.charset = mediaValue; setMetaData(QStringLiteral("charset"), mediaValue); } else { setMetaData(QLatin1String("media-") + mediaAttribute, mediaValue); if (quoted) { setMetaData(QLatin1String("media-") + mediaAttribute + QLatin1String("-kio-quoted"), QStringLiteral("true")); } } } } // content? tIt = tokenizer.iterator("content-encoding"); while (tIt.hasNext()) { // This is so wrong !! No wonder kio_http is stripping the // gzip encoding from downloaded files. This solves multiple // bug reports and caitoo's problem with downloads when such a // header is encountered... // A quote from RFC 2616: // " When present, its (Content-Encoding) value indicates what additional // content have been applied to the entity body, and thus what decoding // mechanism must be applied to obtain the media-type referenced by the // Content-Type header field. Content-Encoding is primarily used to allow // a document to be compressed without loosing the identity of its underlying // media type. Simply put if it is specified, this is the actual mime-type // we should use when we pull the resource !!! addEncoding(toQString(tIt.next()), m_contentEncodings); } // Refer to RFC 2616 sec 15.5/19.5.1 and RFC 2183 tIt = tokenizer.iterator("content-disposition"); if (tIt.hasNext()) { parseContentDisposition(toQString(tIt.next())); } tIt = tokenizer.iterator("content-language"); if (tIt.hasNext()) { QString language = toQString(tIt.next().trimmed()); if (!language.isEmpty()) { setMetaData(QStringLiteral("content-language"), language); } } tIt = tokenizer.iterator("proxy-connection"); if (tIt.hasNext() && isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) { QByteArray pc = tIt.next().toLower(); if (pc.startsWith("close")) { // krazy:exclude=strings m_request.isKeepAlive = false; } else if (pc.startsWith("keep-alive")) { // krazy:exclude=strings m_request.isKeepAlive = true; } } tIt = tokenizer.iterator("link"); if (tIt.hasNext()) { // We only support Link: ; rel="type" so far QStringList link = toQString(tIt.next()).split(QLatin1Char(';'), QString::SkipEmptyParts); if (link.count() == 2) { QString rel = link[1].trimmed(); if (rel.startsWith(QLatin1String("rel=\""))) { rel = rel.mid(5, rel.length() - 6); if (rel.toLower() == QLatin1String("pageservices")) { //### the remove() part looks fishy! QString url = link[0].remove(QRegExp(QStringLiteral("[<>]"))).trimmed(); setMetaData(QStringLiteral("PageServices"), url); } } } } tIt = tokenizer.iterator("p3p"); if (tIt.hasNext()) { // P3P privacy policy information QStringList policyrefs, compact; while (tIt.hasNext()) { QStringList policy = toQString(tIt.next().simplified()) .split(QLatin1Char('='), QString::SkipEmptyParts); if (policy.count() == 2) { if (policy[0].toLower() == QLatin1String("policyref")) { policyrefs << policy[1].remove(QRegExp(QStringLiteral("[\")\']"))).trimmed(); } else if (policy[0].toLower() == QLatin1String("cp")) { // We convert to cp\ncp\ncp\n[...]\ncp to be consistent with // other metadata sent in strings. This could be a bit more // efficient but I'm going for correctness right now. const QString s = policy[1].remove(QRegExp(QStringLiteral("[\")\']"))); const QStringList cps = s.split(QLatin1Char(' '), QString::SkipEmptyParts); compact << cps; } } } if (!policyrefs.isEmpty()) { setMetaData(QStringLiteral("PrivacyPolicy"), policyrefs.join(QStringLiteral("\n"))); } if (!compact.isEmpty()) { setMetaData(QStringLiteral("PrivacyCompactPolicy"), compact.join(QStringLiteral("\n"))); } } // continue only if we know that we're at least HTTP/1.0 if (httpRev == HTTP_11 || httpRev == HTTP_10) { // let them tell us if we should stay alive or not tIt = tokenizer.iterator("connection"); while (tIt.hasNext()) { QByteArray connection = tIt.next().toLower(); if (!(isHttpProxy(m_request.proxyUrl) && !isAutoSsl())) { if (connection.startsWith("close")) { // krazy:exclude=strings m_request.isKeepAlive = false; } else if (connection.startsWith("keep-alive")) { // krazy:exclude=strings m_request.isKeepAlive = true; } } if (connection.startsWith("upgrade")) { // krazy:exclude=strings if (m_request.responseCode == 101) { // Ok, an upgrade was accepted, now we must do it upgradeRequired = true; } else if (upgradeRequired) { // 426 // Nothing to do since we did it above already } } } // what kind of encoding do we have? transfer? tIt = tokenizer.iterator("transfer-encoding"); while (tIt.hasNext()) { // If multiple encodings have been applied to an entity, the // transfer-codings MUST be listed in the order in which they // were applied. addEncoding(toQString(tIt.next().trimmed()), m_transferEncodings); } // md5 signature tIt = tokenizer.iterator("content-md5"); if (tIt.hasNext()) { m_contentMD5 = toQString(tIt.next().trimmed()); } // *** Responses to the HTTP OPTIONS method follow // WebDAV capabilities tIt = tokenizer.iterator("dav"); while (tIt.hasNext()) { m_davCapabilities << toQString(tIt.next()); } // *** Responses to the HTTP OPTIONS method finished } // Now process the HTTP/1.1 upgrade QStringList upgradeOffers; tIt = tokenizer.iterator("upgrade"); if (tIt.hasNext()) { // Now we have to check to see what is offered for the upgrade QString offered = toQString(tIt.next()); upgradeOffers = offered.split(QRegExp(QStringLiteral("[ \n,\r\t]")), QString::SkipEmptyParts); } Q_FOREACH (const QString &opt, upgradeOffers) { if (opt == QLatin1String("TLS/1.0")) { if (!startSsl() && upgradeRequired) { error(ERR_UPGRADE_REQUIRED, opt); return false; } } else if (opt == QLatin1String("HTTP/1.1")) { httpRev = HTTP_11; } else if (upgradeRequired) { // we are told to do an upgrade we don't understand error(ERR_UPGRADE_REQUIRED, opt); return false; } } // Harvest cookies (mmm, cookie fields!) QByteArray cookieStr; // In case we get a cookie. tIt = tokenizer.iterator("set-cookie"); while (tIt.hasNext()) { cookieStr += "Set-Cookie: "; cookieStr += tIt.next(); cookieStr += '\n'; } if (!cookieStr.isEmpty()) { if ((m_request.cookieMode == HTTPRequest::CookiesAuto) && m_request.useCookieJar) { // Give cookies to the cookiejar. const QString domain = config()->readEntry("cross-domain"); if (!domain.isEmpty() && isCrossDomainRequest(m_request.url.host(), domain)) { cookieStr = "Cross-Domain\n" + cookieStr; } addCookies(m_request.url.toString(), cookieStr); } else if (m_request.cookieMode == HTTPRequest::CookiesManual) { // Pass cookie to application setMetaData(QStringLiteral("setcookies"), QString::fromUtf8(cookieStr)); // ## is encoding ok? } } // We need to reread the header if we got a '100 Continue' or '102 Processing' // This may be a non keepalive connection so we handle this kind of loop internally if (cont) { qCDebug(KIO_HTTP) << "cont; returning to mark try_again"; goto try_again; } if (!m_isChunked && (m_iSize == NO_SIZE) && m_request.isKeepAlive && canHaveResponseBody(m_request.responseCode, m_request.method)) { qCDebug(KIO_HTTP) << "Ignoring keep-alive: otherwise unable to determine response body length."; m_request.isKeepAlive = false; } // TODO cache the proxy auth data (not doing this means a small performance regression for now) // we may need to send (Proxy or WWW) authorization data if ((!m_request.doNotWWWAuthenticate && m_request.responseCode == 401) || (!m_request.doNotProxyAuthenticate && m_request.responseCode == 407)) { authRequiresAnotherRoundtrip = handleAuthenticationHeader(&tokenizer); if (m_kioError) { // If error is set, then handleAuthenticationHeader failed. return false; } } else { authRequiresAnotherRoundtrip = false; } QString locationStr; // In fact we should do redirection only if we have a redirection response code (300 range) tIt = tokenizer.iterator("location"); if (tIt.hasNext() && m_request.responseCode > 299 && m_request.responseCode < 400) { locationStr = QString::fromUtf8(tIt.next().trimmed()); } // We need to do a redirect if (!locationStr.isEmpty()) { QUrl u = m_request.url.resolved(QUrl(locationStr)); if (!u.isValid()) { error(ERR_MALFORMED_URL, u.toDisplayString()); return false; } // preserve #ref: (bug 124654) // if we were at http://host/resource1#ref, we sent a GET for "/resource1" // if we got redirected to http://host/resource2, then we have to re-add // the fragment: if (m_request.url.hasFragment() && !u.hasFragment() && (m_request.url.host() == u.host()) && (m_request.url.scheme() == u.scheme())) { u.setFragment(m_request.url.fragment()); } m_isRedirection = true; if (!m_request.id.isEmpty()) { sendMetaData(); } // If we're redirected to a http:// url, remember that we're doing webdav... if (m_protocol == "webdav" || m_protocol == "webdavs") { if (u.scheme() == QLatin1String("http")) { u.setScheme(QStringLiteral("webdav")); } else if (u.scheme() == QLatin1String("https")) { u.setScheme(QStringLiteral("webdavs")); } m_request.redirectUrl = u; } qCDebug(KIO_HTTP) << "Re-directing from" << m_request.url << "to" << u; redirection(u); // It would be hard to cache the redirection response correctly. The possible benefit // is small (if at all, assuming fast disk and slow network), so don't do it. cacheFileClose(); setCacheabilityMetadata(false); } // Inform the job that we can indeed resume... if (bCanResume && m_request.offset) { //TODO turn off caching??? canResume(); } else { m_request.offset = 0; } // Correct a few common wrong content encodings fixupResponseContentEncoding(); // Correct some common incorrect pseudo-mimetypes fixupResponseMimetype(); // parse everything related to expire and other dates, and cache directives; also switch // between cache reading and writing depending on cache validation result. cacheParseResponseHeader(tokenizer); } if (m_request.cacheTag.ioMode == ReadFromCache) { if (m_request.cacheTag.policy == CC_Verify && m_request.cacheTag.plan(m_maxCacheAge) != CacheTag::UseCached) { qCDebug(KIO_HTTP) << "Reading resource from cache even though the cache plan is not " "UseCached; the server is probably sending wrong expiry information."; } // parseHeaderFromCache replaces this method in case of cached content return parseHeaderFromCache(); } if (config()->readEntry("PropagateHttpHeader", false) || m_request.cacheTag.ioMode == WriteToCache) { // store header lines if they will be used; note that the tokenizer removing // line continuation special cases is probably more good than bad. int nextLinePos = 0; int prevLinePos = 0; bool haveMore = true; while (haveMore) { haveMore = nextLine(buffer, &nextLinePos, bufPos); int prevLineEnd = nextLinePos; while (buffer[prevLineEnd - 1] == '\r' || buffer[prevLineEnd - 1] == '\n') { prevLineEnd--; } m_responseHeaders.append(QString::fromLatin1(&buffer[prevLinePos], prevLineEnd - prevLinePos)); prevLinePos = nextLinePos; } // IMPORTANT: Do not remove this line because forwardHttpResponseHeader // is called below. This line is here to ensure the response headers are // available to the client before it receives mimetype information. // The support for putting ioslaves on hold in the KIO-QNAM integration // will break if this line is removed. setMetaData(QStringLiteral("HTTP-Headers"), m_responseHeaders.join(QString(QLatin1Char('\n')))); } // Let the app know about the mime-type iff this is not a redirection and // the mime-type string is not empty. if (!m_isRedirection && m_request.responseCode != 204 && (!m_mimeType.isEmpty() || m_request.method == HTTP_HEAD) && !m_kioError && (m_isLoadingErrorPage || !authRequiresAnotherRoundtrip)) { qCDebug(KIO_HTTP) << "Emitting mimetype " << m_mimeType; mimeType(m_mimeType); } // IMPORTANT: Do not move the function call below before doing any // redirection. Otherwise it might mess up some sites, see BR# 150904. forwardHttpResponseHeader(); if (m_request.method == HTTP_HEAD) { return true; } return !authRequiresAnotherRoundtrip; // return true if no more credentials need to be sent } void HTTPProtocol::parseContentDisposition(const QString &disposition) { const QMap parameters = contentDispositionParser(disposition); QMap::const_iterator i = parameters.constBegin(); while (i != parameters.constEnd()) { setMetaData(QLatin1String("content-disposition-") + i.key(), i.value()); qCDebug(KIO_HTTP) << "Content-Disposition:" << i.key() << "=" << i.value(); ++i; } } void HTTPProtocol::addEncoding(const QString &_encoding, QStringList &encs) { QString encoding = _encoding.trimmed().toLower(); // Identity is the same as no encoding if (encoding == QLatin1String("identity")) { return; } else if (encoding == QLatin1String("8bit")) { // Strange encoding returned by http://linac.ikp.physik.tu-darmstadt.de return; } else if (encoding == QLatin1String("chunked")) { m_isChunked = true; // Anyone know of a better way to handle unknown sizes possibly/ideally with unsigned ints? //if ( m_cmd != CMD_COPY ) m_iSize = NO_SIZE; } else if ((encoding == QLatin1String("x-gzip")) || (encoding == QLatin1String("gzip"))) { encs.append(QStringLiteral("gzip")); } else if ((encoding == QLatin1String("x-bzip2")) || (encoding == QLatin1String("bzip2"))) { encs.append(QStringLiteral("bzip2")); // Not yet supported! } else if ((encoding == QLatin1String("x-deflate")) || (encoding == QLatin1String("deflate"))) { encs.append(QStringLiteral("deflate")); } else { qCDebug(KIO_HTTP) << "Unknown encoding encountered. " << "Please write code. Encoding =" << encoding; } } void HTTPProtocol::cacheParseResponseHeader(const HeaderTokenizer &tokenizer) { if (!m_request.cacheTag.useCache) { return; } // might have to add more response codes if (m_request.responseCode != 200 && m_request.responseCode != 304) { return; } m_request.cacheTag.servedDate = QDateTime(); m_request.cacheTag.lastModifiedDate = QDateTime(); m_request.cacheTag.expireDate = QDateTime(); const QDateTime currentDate = QDateTime::currentDateTime(); bool mayCache = m_request.cacheTag.ioMode != NoCache; TokenIterator tIt = tokenizer.iterator("last-modified"); if (tIt.hasNext()) { m_request.cacheTag.lastModifiedDate = QDateTime::fromString(toQString(tIt.next()), Qt::RFC2822Date); //### might be good to canonicalize the date by using QDateTime::toString() if (m_request.cacheTag.lastModifiedDate.isValid()) { setMetaData(QStringLiteral("modified"), toQString(tIt.current())); } } // determine from available information when the response was served by the origin server { QDateTime dateHeader; tIt = tokenizer.iterator("date"); if (tIt.hasNext()) { dateHeader = QDateTime::fromString(toQString(tIt.next()), Qt::RFC2822Date); // -1 on error } qint64 ageHeader = 0; tIt = tokenizer.iterator("age"); if (tIt.hasNext()) { ageHeader = tIt.next().toLongLong(); // 0 on error } if (dateHeader.isValid()) { m_request.cacheTag.servedDate = dateHeader; } else if (ageHeader) { m_request.cacheTag.servedDate = currentDate.addSecs(-ageHeader); } else { m_request.cacheTag.servedDate = currentDate; } } bool hasCacheDirective = false; // determine when the response "expires", i.e. becomes stale and needs revalidation { // (we also parse other cache directives here) qint64 maxAgeHeader = 0; tIt = tokenizer.iterator("cache-control"); while (tIt.hasNext()) { QByteArray cacheStr = tIt.next().toLower(); if (cacheStr.startsWith("no-cache") || cacheStr.startsWith("no-store")) { // krazy:exclude=strings // Don't put in cache mayCache = false; hasCacheDirective = true; } else if (cacheStr.startsWith("max-age=")) { // krazy:exclude=strings QByteArray ba = cacheStr.mid(qstrlen("max-age=")).trimmed(); bool ok = false; maxAgeHeader = ba.toLongLong(&ok); if (ok) { hasCacheDirective = true; } } } QDateTime expiresHeader; tIt = tokenizer.iterator("expires"); if (tIt.hasNext()) { expiresHeader = QDateTime::fromString(toQString(tIt.next()), Qt::RFC2822Date); qCDebug(KIO_HTTP) << "parsed expire date from 'expires' header:" << tIt.current(); } if (maxAgeHeader) { m_request.cacheTag.expireDate = m_request.cacheTag.servedDate.addSecs(maxAgeHeader); } else if (expiresHeader.isValid()) { m_request.cacheTag.expireDate = expiresHeader; } else { // heuristic expiration date if (m_request.cacheTag.lastModifiedDate.isValid()) { // expAge is following the RFC 2616 suggestion for heuristic expiration qint64 expAge = (m_request.cacheTag.lastModifiedDate.secsTo(m_request.cacheTag.servedDate)) / 10; // not in the RFC: make sure not to have a huge heuristic cache lifetime expAge = qMin(expAge, qint64(3600 * 24)); m_request.cacheTag.expireDate = m_request.cacheTag.servedDate.addSecs(expAge); } else { m_request.cacheTag.expireDate = m_request.cacheTag.servedDate.addSecs(DEFAULT_CACHE_EXPIRE); } } // make sure that no future clock monkey business causes the cache entry to un-expire if (m_request.cacheTag.expireDate < currentDate) { m_request.cacheTag.expireDate.setMSecsSinceEpoch(0); // January 1, 1970 :) } } tIt = tokenizer.iterator("etag"); if (tIt.hasNext()) { QString prevEtag = m_request.cacheTag.etag; m_request.cacheTag.etag = toQString(tIt.next()); if (m_request.cacheTag.etag != prevEtag && m_request.responseCode == 304) { qCDebug(KIO_HTTP) << "304 Not Modified but new entity tag - I don't think this is legal HTTP."; } } // whoops.. we received a warning tIt = tokenizer.iterator("warning"); if (tIt.hasNext()) { //Don't use warning() here, no need to bother the user. //Those warnings are mostly about caches. infoMessage(toQString(tIt.next())); } // Cache management (HTTP 1.0) tIt = tokenizer.iterator("pragma"); while (tIt.hasNext()) { if (tIt.next().toLower().startsWith("no-cache")) { // krazy:exclude=strings mayCache = false; hasCacheDirective = true; } } // The deprecated Refresh Response tIt = tokenizer.iterator("refresh"); if (tIt.hasNext()) { mayCache = false; setMetaData(QStringLiteral("http-refresh"), toQString(tIt.next().trimmed())); } // We don't cache certain text objects if (m_mimeType.startsWith(QLatin1String("text/")) && (m_mimeType != QLatin1String("text/css")) && (m_mimeType != QLatin1String("text/x-javascript")) && !hasCacheDirective) { // Do not cache secure pages or pages // originating from password protected sites // unless the webserver explicitly allows it. if (isUsingSsl() || m_wwwAuth) { mayCache = false; } } // note that we've updated cacheTag, so the plan() is with current data if (m_request.cacheTag.plan(m_maxCacheAge) == CacheTag::ValidateCached) { qCDebug(KIO_HTTP) << "Cache needs validation"; if (m_request.responseCode == 304) { qCDebug(KIO_HTTP) << "...was revalidated by response code but not by updated expire times. " "We're going to set the expire date to 60 seconds in the future..."; m_request.cacheTag.expireDate = currentDate.addSecs(60); if (m_request.cacheTag.policy == CC_Verify && m_request.cacheTag.plan(m_maxCacheAge) != CacheTag::UseCached) { // "apparently" because we /could/ have made an error ourselves, but the errors I // witnessed were all the server's fault. qCDebug(KIO_HTTP) << "this proxy or server apparently sends bogus expiry information."; } } } // validation handling if (mayCache && m_request.responseCode == 200 && !m_mimeType.isEmpty()) { qCDebug(KIO_HTTP) << "Cache, adding" << m_request.url; // ioMode can still be ReadFromCache here if we're performing a conditional get // aka validation m_request.cacheTag.ioMode = WriteToCache; if (!cacheFileOpenWrite()) { qCDebug(KIO_HTTP) << "Error creating cache entry for" << m_request.url << "!"; } m_maxCacheSize = config()->readEntry("MaxCacheSize", DEFAULT_MAX_CACHE_SIZE); } else if (m_request.responseCode == 304 && m_request.cacheTag.file) { if (!mayCache) { qCDebug(KIO_HTTP) << "This webserver is confused about the cacheability of the data it sends."; } // the cache file should still be open for reading, see satisfyRequestFromCache(). Q_ASSERT(m_request.cacheTag.file->openMode() == QIODevice::ReadOnly); Q_ASSERT(m_request.cacheTag.ioMode == ReadFromCache); } else { cacheFileClose(); } setCacheabilityMetadata(mayCache); } void HTTPProtocol::setCacheabilityMetadata(bool cachingAllowed) { if (!cachingAllowed) { setMetaData(QStringLiteral("no-cache"), QStringLiteral("true")); setMetaData(QStringLiteral("expire-date"), QStringLiteral("1")); // Expired } else { QString tmp; tmp.setNum(m_request.cacheTag.expireDate.toTime_t()); setMetaData(QStringLiteral("expire-date"), tmp); // slightly changed semantics from old creationDate, probably more correct now tmp.setNum(m_request.cacheTag.servedDate.toTime_t()); setMetaData(QStringLiteral("cache-creation-date"), tmp); } } bool HTTPProtocol::sendCachedBody() { infoMessage(i18n("Sending data to %1", m_request.url.host())); const qint64 size = m_POSTbuf->size(); QByteArray cLength("Content-Length: "); cLength += QByteArray::number(size); cLength += "\r\n\r\n"; //qDebug() << "sending cached data (size=" << size << ")"; // Send the content length... bool sendOk = (write(cLength.data(), cLength.size()) == (ssize_t) cLength.size()); if (!sendOk) { qCDebug(KIO_HTTP) << "Connection broken when sending " << "content length: (" << m_request.url.host() << ")"; error(ERR_CONNECTION_BROKEN, m_request.url.host()); return false; } totalSize(size); // Make sure the read head is at the beginning... m_POSTbuf->reset(); KIO::filesize_t totalBytesSent = 0; // Send the data... while (!m_POSTbuf->atEnd()) { const QByteArray buffer = m_POSTbuf->read(65536); const ssize_t bytesSent = write(buffer.data(), buffer.size()); if (bytesSent != static_cast(buffer.size())) { qCDebug(KIO_HTTP) << "Connection broken when sending message body: (" << m_request.url.host() << ")"; error(ERR_CONNECTION_BROKEN, m_request.url.host()); return false; } totalBytesSent += bytesSent; processedSize(totalBytesSent); } return true; } bool HTTPProtocol::sendBody() { // If we have cached data, the it is either a repost or a DAV request so send // the cached data... if (m_POSTbuf) { return sendCachedBody(); } if (m_iPostDataSize == NO_SIZE) { // Try the old approach of retireving content data from the job // before giving up. if (retrieveAllData()) { return sendCachedBody(); } error(ERR_POST_NO_SIZE, m_request.url.host()); return false; } qCDebug(KIO_HTTP) << "sending data (size=" << m_iPostDataSize << ")"; infoMessage(i18n("Sending data to %1", m_request.url.host())); QByteArray cLength("Content-Length: "); cLength += QByteArray::number(m_iPostDataSize); cLength += "\r\n\r\n"; qCDebug(KIO_HTTP) << cLength.trimmed(); // Send the content length... bool sendOk = (write(cLength.data(), cLength.size()) == (ssize_t) cLength.size()); if (!sendOk) { // The server might have closed the connection due to a timeout, or maybe // some transport problem arose while the connection was idle. if (m_request.isKeepAlive) { httpCloseConnection(); return true; // Try again } qCDebug(KIO_HTTP) << "Connection broken while sending POST content size to" << m_request.url.host(); error(ERR_CONNECTION_BROKEN, m_request.url.host()); return false; } // Send the amount totalSize(m_iPostDataSize); // If content-length is 0, then do nothing but simply return true. if (m_iPostDataSize == 0) { return true; } sendOk = true; KIO::filesize_t bytesSent = 0; while (true) { dataReq(); QByteArray buffer; const int bytesRead = readData(buffer); // On done... if (bytesRead == 0) { sendOk = (bytesSent == m_iPostDataSize); break; } // On error return false... if (bytesRead < 0) { error(ERR_ABORTED, m_request.url.host()); sendOk = false; break; } // Cache the POST data in case of a repost request. cachePostData(buffer); // This will only happen if transmitting the data fails, so we will simply // cache the content locally for the potential re-transmit... if (!sendOk) { continue; } if (write(buffer.data(), bytesRead) == static_cast(bytesRead)) { bytesSent += bytesRead; processedSize(bytesSent); // Send update status... continue; } qCDebug(KIO_HTTP) << "Connection broken while sending POST content to" << m_request.url.host(); error(ERR_CONNECTION_BROKEN, m_request.url.host()); sendOk = false; } return sendOk; } void HTTPProtocol::httpClose(bool keepAlive) { qCDebug(KIO_HTTP) << "keepAlive =" << keepAlive; cacheFileClose(); // Only allow persistent connections for GET requests. // NOTE: we might even want to narrow this down to non-form // based submit requests which will require a meta-data from // khtml. if (keepAlive) { if (!m_request.keepAliveTimeout) { m_request.keepAliveTimeout = DEFAULT_KEEP_ALIVE_TIMEOUT; } else if (m_request.keepAliveTimeout > 2 * DEFAULT_KEEP_ALIVE_TIMEOUT) { m_request.keepAliveTimeout = 2 * DEFAULT_KEEP_ALIVE_TIMEOUT; } qCDebug(KIO_HTTP) << "keep alive (" << m_request.keepAliveTimeout << ")"; QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); stream << int(99); // special: Close connection setTimeoutSpecialCommand(m_request.keepAliveTimeout, data); return; } httpCloseConnection(); } void HTTPProtocol::closeConnection() { qCDebug(KIO_HTTP); httpCloseConnection(); } void HTTPProtocol::httpCloseConnection() { qCDebug(KIO_HTTP); m_server.clear(); disconnectFromHost(); clearUnreadBuffer(); setTimeoutSpecialCommand(-1); // Cancel any connection timeout } void HTTPProtocol::slave_status() { qCDebug(KIO_HTTP); if (!isConnected()) { httpCloseConnection(); } slaveStatus(m_server.url.host(), isConnected()); } void HTTPProtocol::mimetype(const QUrl &url) { qCDebug(KIO_HTTP) << url; if (!maybeSetRequestUrl(url)) { return; } resetSessionSettings(); m_request.method = HTTP_HEAD; m_request.cacheTag.policy = CC_Cache; if (proceedUntilResponseHeader()) { httpClose(m_request.isKeepAlive); finished(); } qCDebug(KIO_HTTP) << m_mimeType; } void HTTPProtocol::special(const QByteArray &data) { qCDebug(KIO_HTTP); int tmp; QDataStream stream(data); stream >> tmp; switch (tmp) { case 1: { // HTTP POST QUrl url; qint64 size; stream >> url >> size; post(url, size); break; } case 2: { // cache_update QUrl url; bool no_cache; qint64 expireDate; stream >> url >> no_cache >> expireDate; if (no_cache) { QString filename = cacheFilePathFromUrl(url); // there is a tiny risk of deleting the wrong file due to hash collisions here. // this is an unimportant performance issue. // FIXME on Windows we may be unable to delete the file if open QFile::remove(filename); finished(); break; } // let's be paranoid and inefficient here... HTTPRequest savedRequest = m_request; m_request.url = url; if (cacheFileOpenRead()) { m_request.cacheTag.expireDate.setTime_t(expireDate); cacheFileClose(); // this sends an update command to the cache cleaner process } m_request = savedRequest; finished(); break; } case 5: { // WebDAV lock QUrl url; QString scope, type, owner; stream >> url >> scope >> type >> owner; davLock(url, scope, type, owner); break; } case 6: { // WebDAV unlock QUrl url; stream >> url; davUnlock(url); break; } case 7: { // Generic WebDAV QUrl url; int method; qint64 size; stream >> url >> method >> size; davGeneric(url, (KIO::HTTP_METHOD) method, size); break; } case 99: { // Close Connection httpCloseConnection(); break; } default: // Some command we don't understand. // Just ignore it, it may come from some future version of KDE. break; } } /** * Read a chunk from the data stream. */ int HTTPProtocol::readChunked() { if ((m_iBytesLeft == 0) || (m_iBytesLeft == NO_SIZE)) { // discard CRLF from previous chunk, if any, and read size of next chunk int bufPos = 0; m_receiveBuf.resize(4096); bool foundCrLf = readDelimitedText(m_receiveBuf.data(), &bufPos, m_receiveBuf.size(), 1); if (foundCrLf && bufPos == 2) { // The previous read gave us the CRLF from the previous chunk. As bufPos includes // the trailing CRLF it has to be > 2 to possibly include the next chunksize. bufPos = 0; foundCrLf = readDelimitedText(m_receiveBuf.data(), &bufPos, m_receiveBuf.size(), 1); } if (!foundCrLf) { qCDebug(KIO_HTTP) << "Failed to read chunk header."; return -1; } Q_ASSERT(bufPos > 2); long long nextChunkSize = STRTOLL(m_receiveBuf.data(), nullptr, 16); if (nextChunkSize < 0) { qCDebug(KIO_HTTP) << "Negative chunk size"; return -1; } m_iBytesLeft = nextChunkSize; qCDebug(KIO_HTTP) << "Chunk size =" << m_iBytesLeft << "bytes"; if (m_iBytesLeft == 0) { // Last chunk; read and discard chunk trailer. // The last trailer line ends with CRLF and is followed by another CRLF // so we have CRLFCRLF like at the end of a standard HTTP header. // Do not miss a CRLFCRLF spread over two of our 4K blocks: keep three previous bytes. //NOTE the CRLF after the chunksize also counts if there is no trailer. Copy it over. char trash[4096]; trash[0] = m_receiveBuf.constData()[bufPos - 2]; trash[1] = m_receiveBuf.constData()[bufPos - 1]; int trashBufPos = 2; bool done = false; while (!done && !m_isEOF) { if (trashBufPos > 3) { // shift everything but the last three bytes out of the buffer for (int i = 0; i < 3; i++) { trash[i] = trash[trashBufPos - 3 + i]; } trashBufPos = 3; } done = readDelimitedText(trash, &trashBufPos, 4096, 2); } if (m_isEOF && !done) { qCDebug(KIO_HTTP) << "Failed to read chunk trailer."; return -1; } return 0; } } int bytesReceived = readLimited(); if (!m_iBytesLeft) { m_iBytesLeft = NO_SIZE; // Don't stop, continue with next chunk } return bytesReceived; } int HTTPProtocol::readLimited() { if (!m_iBytesLeft) { return 0; } m_receiveBuf.resize(4096); int bytesToReceive; if (m_iBytesLeft > KIO::filesize_t(m_receiveBuf.size())) { bytesToReceive = m_receiveBuf.size(); } else { bytesToReceive = m_iBytesLeft; } const int bytesReceived = readBuffered(m_receiveBuf.data(), bytesToReceive, false); if (bytesReceived <= 0) { return -1; // Error: connection lost } m_iBytesLeft -= bytesReceived; return bytesReceived; } int HTTPProtocol::readUnlimited() { if (m_request.isKeepAlive) { qCDebug(KIO_HTTP) << "Unbounded datastream on a Keep-alive connection!"; m_request.isKeepAlive = false; } m_receiveBuf.resize(4096); int result = readBuffered(m_receiveBuf.data(), m_receiveBuf.size()); if (result > 0) { return result; } m_isEOF = true; m_iBytesLeft = 0; return 0; } void HTTPProtocol::slotData(const QByteArray &_d) { if (!_d.size()) { m_isEOD = true; return; } if (m_iContentLeft != NO_SIZE) { if (m_iContentLeft >= KIO::filesize_t(_d.size())) { m_iContentLeft -= _d.size(); } else { m_iContentLeft = NO_SIZE; } } QByteArray d = _d; if (!m_dataInternal) { // If a broken server does not send the mime-type, // we try to id it from the content before dealing // with the content itself. if (m_mimeType.isEmpty() && !m_isRedirection && !(m_request.responseCode >= 300 && m_request.responseCode <= 399)) { qCDebug(KIO_HTTP) << "Determining mime-type from content..."; int old_size = m_mimeTypeBuffer.size(); m_mimeTypeBuffer.resize(old_size + d.size()); memcpy(m_mimeTypeBuffer.data() + old_size, d.data(), d.size()); if ((m_iBytesLeft != NO_SIZE) && (m_iBytesLeft > 0) && (m_mimeTypeBuffer.size() < 1024)) { m_cpMimeBuffer = true; return; // Do not send up the data since we do not yet know its mimetype! } qCDebug(KIO_HTTP) << "Mimetype buffer size:" << m_mimeTypeBuffer.size(); QMimeDatabase db; QMimeType mime = db.mimeTypeForFileNameAndData(m_request.url.adjusted(QUrl::StripTrailingSlash).path(), m_mimeTypeBuffer); if (mime.isValid() && !mime.isDefault()) { m_mimeType = mime.name(); qCDebug(KIO_HTTP) << "Mimetype from content:" << m_mimeType; } if (m_mimeType.isEmpty()) { m_mimeType = QStringLiteral(DEFAULT_MIME_TYPE); qCDebug(KIO_HTTP) << "Using default mimetype:" << m_mimeType; } //### we could also open the cache file here if (m_cpMimeBuffer) { d.resize(0); d.resize(m_mimeTypeBuffer.size()); memcpy(d.data(), m_mimeTypeBuffer.data(), d.size()); } mimeType(m_mimeType); m_mimeTypeBuffer.resize(0); } //qDebug() << "Sending data of size" << d.size(); data(d); if (m_request.cacheTag.ioMode == WriteToCache) { cacheFileWritePayload(d); } } else { uint old_size = m_webDavDataBuf.size(); m_webDavDataBuf.resize(old_size + d.size()); memcpy(m_webDavDataBuf.data() + old_size, d.data(), d.size()); } } /** * This function is our "receive" function. It is responsible for * downloading the message (not the header) from the HTTP server. It * is called either as a response to a client's KIOJob::dataEnd() * (meaning that the client is done sending data) or by 'sendQuery()' * (if we are in the process of a PUT/POST request). It can also be * called by a webDAV function, to receive stat/list/property/etc. * data; in this case the data is stored in m_webDavDataBuf. */ bool HTTPProtocol::readBody(bool dataInternal /* = false */) { // special case for reading cached body since we also do it in this function. oh well. if (!canHaveResponseBody(m_request.responseCode, m_request.method) && !(m_request.cacheTag.ioMode == ReadFromCache && m_request.responseCode == 304 && m_request.method != HTTP_HEAD)) { return true; } m_isEOD = false; // Note that when dataInternal is true, we are going to: // 1) save the body data to a member variable, m_webDavDataBuf // 2) _not_ advertise the data, speed, size, etc., through the // corresponding functions. // This is used for returning data to WebDAV. m_dataInternal = dataInternal; if (dataInternal) { m_webDavDataBuf.clear(); } // Check if we need to decode the data. // If we are in copy mode, then use only transfer decoding. bool useMD5 = !m_contentMD5.isEmpty(); // Deal with the size of the file. KIO::filesize_t sz = m_request.offset; if (sz) { m_iSize += sz; } if (!m_isRedirection) { // Update the application with total size except when // it is compressed, or when the data is to be handled // internally (webDAV). If compressed we have to wait // until we uncompress to find out the actual data size if (!dataInternal) { if ((m_iSize > 0) && (m_iSize != NO_SIZE)) { totalSize(m_iSize); infoMessage(i18n("Retrieving %1 from %2...", KIO::convertSize(m_iSize), m_request.url.host())); } else { totalSize(0); } } if (m_request.cacheTag.ioMode == ReadFromCache) { qCDebug(KIO_HTTP) << "reading data from cache..."; m_iContentLeft = NO_SIZE; QByteArray d; while (true) { d = cacheFileReadPayload(MAX_IPC_SIZE); if (d.isEmpty()) { break; } slotData(d); sz += d.size(); if (!dataInternal) { processedSize(sz); } } m_receiveBuf.resize(0); if (!dataInternal) { data(QByteArray()); } return true; } } if (m_iSize != NO_SIZE) { m_iBytesLeft = m_iSize - sz; } else { m_iBytesLeft = NO_SIZE; } m_iContentLeft = m_iBytesLeft; if (m_isChunked) { m_iBytesLeft = NO_SIZE; } qCDebug(KIO_HTTP) << KIO::number(m_iBytesLeft) << "bytes left."; // Main incoming loop... Gather everything while we can... m_cpMimeBuffer = false; m_mimeTypeBuffer.resize(0); HTTPFilterChain chain; // redirection ignores the body if (!m_isRedirection) { QObject::connect(&chain, SIGNAL(output(QByteArray)), this, SLOT(slotData(QByteArray))); } QObject::connect(&chain, SIGNAL(error(QString)), this, SLOT(slotFilterError(QString))); // decode all of the transfer encodings while (!m_transferEncodings.isEmpty()) { QString enc = m_transferEncodings.takeLast(); if (enc == QLatin1String("gzip")) { chain.addFilter(new HTTPFilterGZip); } else if (enc == QLatin1String("deflate")) { chain.addFilter(new HTTPFilterDeflate); } } // From HTTP 1.1 Draft 6: // The MD5 digest is computed based on the content of the entity-body, // including any content-coding that has been applied, but not including // any transfer-encoding applied to the message-body. If the message is // received with a transfer-encoding, that encoding MUST be removed // prior to checking the Content-MD5 value against the received entity. HTTPFilterMD5 *md5Filter = nullptr; if (useMD5) { md5Filter = new HTTPFilterMD5; chain.addFilter(md5Filter); } // now decode all of the content encodings // -- Why ?? We are not // -- a proxy server, be a client side implementation!! The applications // -- are capable of determinig how to extract the encoded implementation. // WB: That's a misunderstanding. We are free to remove the encoding. // WB: Some braindead www-servers however, give .tgz files an encoding // WB: of "gzip" (or even "x-gzip") and a content-type of "applications/tar" // WB: They shouldn't do that. We can work around that though... while (!m_contentEncodings.isEmpty()) { QString enc = m_contentEncodings.takeLast(); if (enc == QLatin1String("gzip")) { chain.addFilter(new HTTPFilterGZip); } else if (enc == QLatin1String("deflate")) { chain.addFilter(new HTTPFilterDeflate); } } while (!m_isEOF) { int bytesReceived; if (m_isChunked) { bytesReceived = readChunked(); } else if (m_iSize != NO_SIZE) { bytesReceived = readLimited(); } else { bytesReceived = readUnlimited(); } // make sure that this wasn't an error, first qCDebug(KIO_HTTP) << "bytesReceived:" << bytesReceived << " m_iSize:" << (int)m_iSize << " Chunked:" << m_isChunked << " BytesLeft:"<< (int)m_iBytesLeft; if (bytesReceived == -1) { if (m_iContentLeft == 0) { // gzip'ed data sometimes reports a too long content-length. // (The length of the unzipped data) m_iBytesLeft = 0; break; } // Oh well... log an error and bug out qCDebug(KIO_HTTP) << "bytesReceived==-1 sz=" << (int)sz << " Connection broken !"; error(ERR_CONNECTION_BROKEN, m_request.url.host()); return false; } // I guess that nbytes == 0 isn't an error.. but we certainly // won't work with it! if (bytesReceived > 0) { // Important: truncate the buffer to the actual size received! // Otherwise garbage will be passed to the app m_receiveBuf.truncate(bytesReceived); chain.slotInput(m_receiveBuf); if (m_kioError) { return false; } sz += bytesReceived; if (!dataInternal) { processedSize(sz); } } m_receiveBuf.resize(0); // res if (m_iBytesLeft && m_isEOD && !m_isChunked) { // gzip'ed data sometimes reports a too long content-length. // (The length of the unzipped data) m_iBytesLeft = 0; } if (m_iBytesLeft == 0) { qCDebug(KIO_HTTP) << "EOD received! Left ="<< KIO::number(m_iBytesLeft); break; } } chain.slotInput(QByteArray()); // Flush chain. if (useMD5) { QString calculatedMD5 = md5Filter->md5(); if (m_contentMD5 != calculatedMD5) qCWarning(KIO_HTTP) << "MD5 checksum MISMATCH! Expected:" << calculatedMD5 << ", Got:" << m_contentMD5; } // Close cache entry if (m_iBytesLeft == 0) { cacheFileClose(); // no-op if not necessary } if (!dataInternal && sz <= 1) { if (m_request.responseCode >= 500 && m_request.responseCode <= 599) { error(ERR_INTERNAL_SERVER, m_request.url.host()); return false; } else if (m_request.responseCode >= 400 && m_request.responseCode <= 499 && !isAuthenticationRequired(m_request.responseCode)) { error(ERR_DOES_NOT_EXIST, m_request.url.host()); return false; } } if (!dataInternal && !m_isRedirection) { data(QByteArray()); } return true; } void HTTPProtocol::slotFilterError(const QString &text) { error(KIO::ERR_SLAVE_DEFINED, text); } void HTTPProtocol::error(int _err, const QString &_text) { // Close the connection only on connection errors. Otherwise, honor the // keep alive flag. if (_err == ERR_CONNECTION_BROKEN || _err == ERR_CANNOT_CONNECT) { httpClose(false); } else { httpClose(m_request.isKeepAlive); } if (!m_request.id.isEmpty()) { forwardHttpResponseHeader(); sendMetaData(); } // It's over, we don't need it anymore clearPostDataBuffer(); SlaveBase::error(_err, _text); m_kioError = _err; } void HTTPProtocol::addCookies(const QString &url, const QByteArray &cookieHeader) { qlonglong windowId = m_request.windowId.toLongLong(); QDBusInterface kcookiejar(QStringLiteral("org.kde.kcookiejar5"), QStringLiteral("/modules/kcookiejar"), QStringLiteral("org.kde.KCookieServer")); (void)kcookiejar.call(QDBus::NoBlock, QStringLiteral("addCookies"), url, cookieHeader, windowId); } QString HTTPProtocol::findCookies(const QString &url) { qlonglong windowId = m_request.windowId.toLongLong(); QDBusInterface kcookiejar(QStringLiteral("org.kde.kcookiejar5"), QStringLiteral("/modules/kcookiejar"), QStringLiteral("org.kde.KCookieServer")); QDBusReply reply = kcookiejar.call(QStringLiteral("findCookies"), url, windowId); if (!reply.isValid()) { qCWarning(KIO_HTTP) << "Can't communicate with kded_kcookiejar!"; return QString(); } return reply; } /******************************* CACHING CODE ****************************/ HTTPProtocol::CacheTag::CachePlan HTTPProtocol::CacheTag::plan(int maxCacheAge) const { //notable omission: we're not checking cache file presence or integrity switch (policy) { case KIO::CC_Refresh: // Conditional GET requires the presence of either an ETag or // last modified date. if (lastModifiedDate.isValid() || !etag.isEmpty()) { return ValidateCached; } break; case KIO::CC_Reload: return IgnoreCached; case KIO::CC_CacheOnly: case KIO::CC_Cache: return UseCached; default: break; } Q_ASSERT((policy == CC_Verify || policy == CC_Refresh)); QDateTime currentDate = QDateTime::currentDateTime(); if ((servedDate.isValid() && (currentDate > servedDate.addSecs(maxCacheAge))) || (expireDate.isValid() && (currentDate > expireDate))) { return ValidateCached; } return UseCached; } // !START SYNC! // The following code should be kept in sync // with the code in http_cache_cleaner.cpp // we use QDataStream; this is just an illustration struct BinaryCacheFileHeader { quint8 version[2]; quint8 compression; // for now fixed to 0 quint8 reserved; // for now; also alignment qint32 useCount; qint64 servedDate; qint64 lastModifiedDate; qint64 expireDate; qint32 bytesCached; // packed size should be 36 bytes; we explicitly set it here to make sure that no compiler // padding ruins it. We write the fields to disk without any padding. static const int size = 36; }; enum CacheCleanerCommandCode { InvalidCommand = 0, CreateFileNotificationCommand, UpdateFileCommand }; // illustration for cache cleaner update "commands" struct CacheCleanerCommand { BinaryCacheFileHeader header; quint32 commandCode; // filename in ASCII, binary isn't worth the coding and decoding quint8 filename[s_hashedUrlNibbles]; }; QByteArray HTTPProtocol::CacheTag::serialize() const { QByteArray ret; QDataStream stream(&ret, QIODevice::WriteOnly); stream << quint8('A'); stream << quint8('\n'); stream << quint8(0); stream << quint8(0); stream << fileUseCount; stream << servedDate.toMSecsSinceEpoch() / 1000; stream << lastModifiedDate.toMSecsSinceEpoch() / 1000; stream << expireDate.toMSecsSinceEpoch() / 1000; stream << bytesCached; Q_ASSERT(ret.size() == BinaryCacheFileHeader::size); return ret; } static bool compareByte(QDataStream *stream, quint8 value) { quint8 byte; *stream >> byte; return byte == value; } // If starting a new file cacheFileWriteVariableSizeHeader() must have been called *before* // calling this! This is to fill in the headerEnd field. // If the file is not new headerEnd has already been read from the file and in fact the variable // size header *may* not be rewritten because a size change would mess up the file layout. bool HTTPProtocol::CacheTag::deserialize(const QByteArray &d) { if (d.size() != BinaryCacheFileHeader::size) { return false; } QDataStream stream(d); stream.setVersion(QDataStream::Qt_4_5); bool ok = true; ok = ok && compareByte(&stream, 'A'); ok = ok && compareByte(&stream, '\n'); ok = ok && compareByte(&stream, 0); ok = ok && compareByte(&stream, 0); if (!ok) { return false; } stream >> fileUseCount; qint64 servedDateMs; stream >> servedDateMs; servedDate = QDateTime::fromMSecsSinceEpoch(servedDateMs * 1000); qint64 lastModifiedDateMs; stream >> lastModifiedDateMs; lastModifiedDate = QDateTime::fromMSecsSinceEpoch(lastModifiedDateMs * 1000); qint64 expireDateMs; stream >> expireDateMs; expireDate = QDateTime::fromMSecsSinceEpoch(expireDateMs * 1000); stream >> bytesCached; return true; } /* Text part of the header, directly following the binary first part: URL\n etag\n mimetype\n header line\n header line\n ... \n */ static QUrl storableUrl(const QUrl &url) { QUrl ret(url); ret.setPassword(QString()); ret.setFragment(QString()); return ret; } static void writeLine(QIODevice *dev, const QByteArray &line) { static const char linefeed = '\n'; dev->write(line); dev->write(&linefeed, 1); } void HTTPProtocol::cacheFileWriteTextHeader() { QFile *&file = m_request.cacheTag.file; Q_ASSERT(file); Q_ASSERT(file->openMode() & QIODevice::WriteOnly); file->seek(BinaryCacheFileHeader::size); writeLine(file, storableUrl(m_request.url).toEncoded()); writeLine(file, m_request.cacheTag.etag.toLatin1()); writeLine(file, m_mimeType.toLatin1()); writeLine(file, m_responseHeaders.join(QString(QLatin1Char('\n'))).toLatin1()); // join("\n") adds no \n to the end, but writeLine() does. // Add another newline to mark the end of text. writeLine(file, QByteArray()); } static bool readLineChecked(QIODevice *dev, QByteArray *line) { *line = dev->readLine(MAX_IPC_SIZE); // if nothing read or the line didn't fit into 8192 bytes(!) if (line->isEmpty() || !line->endsWith('\n')) { return false; } // we don't actually want the newline! line->chop(1); return true; } bool HTTPProtocol::cacheFileReadTextHeader1(const QUrl &desiredUrl) { QFile *&file = m_request.cacheTag.file; Q_ASSERT(file); Q_ASSERT(file->openMode() == QIODevice::ReadOnly); QByteArray readBuf; bool ok = readLineChecked(file, &readBuf); if (storableUrl(desiredUrl).toEncoded() != readBuf) { qCDebug(KIO_HTTP) << "You have witnessed a very improbable hash collision!"; return false; } ok = ok && readLineChecked(file, &readBuf); m_request.cacheTag.etag = toQString(readBuf); return ok; } bool HTTPProtocol::cacheFileReadTextHeader2() { QFile *&file = m_request.cacheTag.file; Q_ASSERT(file); Q_ASSERT(file->openMode() == QIODevice::ReadOnly); bool ok = true; QByteArray readBuf; #ifndef NDEBUG // we assume that the URL and etag have already been read qint64 oldPos = file->pos(); file->seek(BinaryCacheFileHeader::size); ok = ok && readLineChecked(file, &readBuf); ok = ok && readLineChecked(file, &readBuf); Q_ASSERT(file->pos() == oldPos); #endif ok = ok && readLineChecked(file, &readBuf); m_mimeType = toQString(readBuf); m_responseHeaders.clear(); // read as long as no error and no empty line found while (true) { ok = ok && readLineChecked(file, &readBuf); if (ok && !readBuf.isEmpty()) { m_responseHeaders.append(toQString(readBuf)); } else { break; } } return ok; // it may still be false ;) } static QString filenameFromUrl(const QUrl &url) { QCryptographicHash hash(QCryptographicHash::Sha1); hash.addData(storableUrl(url).toEncoded()); return toQString(hash.result().toHex()); } QString HTTPProtocol::cacheFilePathFromUrl(const QUrl &url) const { QString filePath = m_strCacheDir; if (!filePath.endsWith(QLatin1Char('/'))) { filePath.append(QLatin1Char('/')); } filePath.append(filenameFromUrl(url)); return filePath; } bool HTTPProtocol::cacheFileOpenRead() { qCDebug(KIO_HTTP); QString filename = cacheFilePathFromUrl(m_request.url); QFile *&file = m_request.cacheTag.file; if (file) { qCDebug(KIO_HTTP) << "File unexpectedly open; old file is" << file->fileName() << "new name is" << filename; Q_ASSERT(file->fileName() == filename); } Q_ASSERT(!file); file = new QFile(filename); if (file->open(QIODevice::ReadOnly)) { QByteArray header = file->read(BinaryCacheFileHeader::size); if (!m_request.cacheTag.deserialize(header)) { qCDebug(KIO_HTTP) << "Cache file header is invalid."; file->close(); } } if (file->isOpen() && !cacheFileReadTextHeader1(m_request.url)) { file->close(); } if (!file->isOpen()) { cacheFileClose(); return false; } return true; } bool HTTPProtocol::cacheFileOpenWrite() { qCDebug(KIO_HTTP); QString filename = cacheFilePathFromUrl(m_request.url); // if we open a cache file for writing while we have a file open for reading we must have // found out that the old cached content is obsolete, so delete the file. QFile *&file = m_request.cacheTag.file; if (file) { // ensure that the file is in a known state - either open for reading or null Q_ASSERT(!qobject_cast(file)); Q_ASSERT((file->openMode() & QIODevice::WriteOnly) == 0); Q_ASSERT(file->fileName() == filename); qCDebug(KIO_HTTP) << "deleting expired cache entry and recreating."; file->remove(); delete file; file = nullptr; } // note that QTemporaryFile will automatically append random chars to filename file = new QTemporaryFile(filename); file->open(QIODevice::WriteOnly); // if we have started a new file we have not initialized some variables from disk data. m_request.cacheTag.fileUseCount = 0; // the file has not been *read* yet m_request.cacheTag.bytesCached = 0; if ((file->openMode() & QIODevice::WriteOnly) == 0) { qCDebug(KIO_HTTP) << "Could not open file for writing: QTemporaryFile(" << filename << ")" << "due to error" << file->error(); cacheFileClose(); return false; } return true; } static QByteArray makeCacheCleanerCommand(const HTTPProtocol::CacheTag &cacheTag, CacheCleanerCommandCode cmd) { QByteArray ret = cacheTag.serialize(); QDataStream stream(&ret, QIODevice::ReadWrite); stream.setVersion(QDataStream::Qt_4_5); stream.skipRawData(BinaryCacheFileHeader::size); // append the command code stream << quint32(cmd); // append the filename QString fileName = cacheTag.file->fileName(); int basenameStart = fileName.lastIndexOf(QLatin1Char('/')) + 1; QByteArray baseName = fileName.mid(basenameStart, s_hashedUrlNibbles).toLatin1(); stream.writeRawData(baseName.constData(), baseName.size()); Q_ASSERT(ret.size() == BinaryCacheFileHeader::size + sizeof(quint32) + s_hashedUrlNibbles); return ret; } //### not yet 100% sure when and when not to call this void HTTPProtocol::cacheFileClose() { qCDebug(KIO_HTTP); QFile *&file = m_request.cacheTag.file; if (!file) { return; } m_request.cacheTag.ioMode = NoCache; QByteArray ccCommand; QTemporaryFile *tempFile = qobject_cast(file); if (file->openMode() & QIODevice::WriteOnly) { Q_ASSERT(tempFile); if (m_request.cacheTag.bytesCached && !m_kioError) { QByteArray header = m_request.cacheTag.serialize(); tempFile->seek(0); tempFile->write(header); ccCommand = makeCacheCleanerCommand(m_request.cacheTag, CreateFileNotificationCommand); QString oldName = tempFile->fileName(); QString newName = oldName; int basenameStart = newName.lastIndexOf(QLatin1Char('/')) + 1; // remove the randomized name part added by QTemporaryFile newName.chop(newName.length() - basenameStart - s_hashedUrlNibbles); qCDebug(KIO_HTTP) << "Renaming temporary file" << oldName << "to" << newName; // on windows open files can't be renamed tempFile->setAutoRemove(false); delete tempFile; file = nullptr; if (!QFile::rename(oldName, newName)) { // ### currently this hides a minor bug when force-reloading a resource. We // should not even open a new file for writing in that case. qCDebug(KIO_HTTP) << "Renaming temporary file failed, deleting it instead."; QFile::remove(oldName); ccCommand.clear(); // we have nothing of value to tell the cache cleaner } } else { // oh, we've never written payload data to the cache file. // the temporary file is closed and removed and no proper cache entry is created. } } else if (file->openMode() == QIODevice::ReadOnly) { Q_ASSERT(!tempFile); ccCommand = makeCacheCleanerCommand(m_request.cacheTag, UpdateFileCommand); } delete file; file = nullptr; if (!ccCommand.isEmpty()) { sendCacheCleanerCommand(ccCommand); } } void HTTPProtocol::sendCacheCleanerCommand(const QByteArray &command) { qCDebug(KIO_HTTP); if (!qEnvironmentVariableIsEmpty("KIO_DISABLE_CACHE_CLEANER")) // for autotests return; Q_ASSERT(command.size() == BinaryCacheFileHeader::size + s_hashedUrlNibbles + sizeof(quint32)); if (m_cacheCleanerConnection.state() != QLocalSocket::ConnectedState) { QString socketFileName = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + QLatin1Char('/') + QLatin1String("kio_http_cache_cleaner"); m_cacheCleanerConnection.connectToServer(socketFileName, QIODevice::WriteOnly); if (m_cacheCleanerConnection.state() == QLocalSocket::UnconnectedState) { // An error happened. // Most likely the cache cleaner is not running, let's start it. // search paths const QStringList searchPaths = QStringList() << QCoreApplication::applicationDirPath() // then look where our application binary is located << QLibraryInfo::location(QLibraryInfo::LibraryExecutablesPath) // look where libexec path is (can be set in qt.conf) << QFile::decodeName(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5); // look at our installation location const QString exe = QStandardPaths::findExecutable(QStringLiteral("kio_http_cache_cleaner"), searchPaths); if (exe.isEmpty()) { qCWarning(KIO_HTTP) << "kio_http_cache_cleaner not found in" << searchPaths; return; } qCDebug(KIO_HTTP) << "starting" << exe; QProcess::startDetached(exe, QStringList()); for (int i = 0 ; i < 30 && m_cacheCleanerConnection.state() == QLocalSocket::UnconnectedState; ++i) { // Server is not listening yet; let's hope it does so under 3 seconds QThread::msleep(100); m_cacheCleanerConnection.connectToServer(socketFileName, QIODevice::WriteOnly); if (m_cacheCleanerConnection.state() != QLocalSocket::UnconnectedState) { break; // connecting or connected, sounds good } } } if (!m_cacheCleanerConnection.waitForConnected(1500)) { // updating the stats is not vital, so we just give up. qCDebug(KIO_HTTP) << "Could not connect to cache cleaner, not updating stats of this cache file."; return; } qCDebug(KIO_HTTP) << "Successfully connected to cache cleaner."; } Q_ASSERT(m_cacheCleanerConnection.state() == QLocalSocket::ConnectedState); m_cacheCleanerConnection.write(command); m_cacheCleanerConnection.flush(); } QByteArray HTTPProtocol::cacheFileReadPayload(int maxLength) { Q_ASSERT(m_request.cacheTag.file); Q_ASSERT(m_request.cacheTag.ioMode == ReadFromCache); Q_ASSERT(m_request.cacheTag.file->openMode() == QIODevice::ReadOnly); QByteArray ret = m_request.cacheTag.file->read(maxLength); if (ret.isEmpty()) { cacheFileClose(); } return ret; } void HTTPProtocol::cacheFileWritePayload(const QByteArray &d) { if (!m_request.cacheTag.file) { return; } // If the file being downloaded is so big that it exceeds the max cache size, // do not cache it! See BR# 244215. NOTE: this can be improved upon in the // future... if (m_iSize >= KIO::filesize_t(m_maxCacheSize * 1024)) { qCDebug(KIO_HTTP) << "Caching disabled because content size is too big."; cacheFileClose(); return; } Q_ASSERT(m_request.cacheTag.ioMode == WriteToCache); Q_ASSERT(m_request.cacheTag.file->openMode() & QIODevice::WriteOnly); if (d.isEmpty()) { cacheFileClose(); } //TODO: abort if file grows too big! // write the variable length text header as soon as we start writing to the file if (!m_request.cacheTag.bytesCached) { cacheFileWriteTextHeader(); } m_request.cacheTag.bytesCached += d.size(); m_request.cacheTag.file->write(d); } void HTTPProtocol::cachePostData(const QByteArray &data) { if (!m_POSTbuf) { m_POSTbuf = createPostBufferDeviceFor(qMax(m_iPostDataSize, static_cast(data.size()))); if (!m_POSTbuf) { return; } } m_POSTbuf->write(data.constData(), data.size()); } void HTTPProtocol::clearPostDataBuffer() { if (!m_POSTbuf) { return; } delete m_POSTbuf; m_POSTbuf = nullptr; } bool HTTPProtocol::retrieveAllData() { if (!m_POSTbuf) { m_POSTbuf = createPostBufferDeviceFor(s_MaxInMemPostBufSize + 1); } if (!m_POSTbuf) { error(ERR_OUT_OF_MEMORY, m_request.url.host()); return false; } while (true) { dataReq(); QByteArray buffer; const int bytesRead = readData(buffer); if (bytesRead < 0) { error(ERR_ABORTED, m_request.url.host()); return false; } if (bytesRead == 0) { break; } m_POSTbuf->write(buffer.constData(), buffer.size()); } return true; } // The above code should be kept in sync // with the code in http_cache_cleaner.cpp // !END SYNC! //************************** AUTHENTICATION CODE ********************/ QString HTTPProtocol::authenticationHeader() { QByteArray ret; // If the internal meta-data "cached-www-auth" is set, then check for cached // authentication data and preemtively send the authentication header if a // matching one is found. if (!m_wwwAuth && config()->readEntry("cached-www-auth", false)) { KIO::AuthInfo authinfo; authinfo.url = m_request.url; authinfo.realmValue = config()->readEntry("www-auth-realm", QString()); // If no relam metadata, then make sure path matching is turned on. authinfo.verifyPath = (authinfo.realmValue.isEmpty()); const bool useCachedAuth = (m_request.responseCode == 401 || !config()->readEntry("no-preemptive-auth-reuse", false)); if (useCachedAuth && checkCachedAuthentication(authinfo)) { const QByteArray cachedChallenge = config()->readEntry("www-auth-challenge", QByteArray()); if (!cachedChallenge.isEmpty()) { m_wwwAuth = KAbstractHttpAuthentication::newAuth(cachedChallenge, config()); if (m_wwwAuth) { qCDebug(KIO_HTTP) << "creating www authentcation header from cached info"; m_wwwAuth->setChallenge(cachedChallenge, m_request.url, m_request.sentMethodString); m_wwwAuth->generateResponse(authinfo.username, authinfo.password); } } } } // If the internal meta-data "cached-proxy-auth" is set, then check for cached // authentication data and preemtively send the authentication header if a // matching one is found. if (!m_proxyAuth && config()->readEntry("cached-proxy-auth", false)) { KIO::AuthInfo authinfo; authinfo.url = m_request.proxyUrl; authinfo.realmValue = config()->readEntry("proxy-auth-realm", QString()); // If no relam metadata, then make sure path matching is turned on. authinfo.verifyPath = (authinfo.realmValue.isEmpty()); if (checkCachedAuthentication(authinfo)) { const QByteArray cachedChallenge = config()->readEntry("proxy-auth-challenge", QByteArray()); if (!cachedChallenge.isEmpty()) { m_proxyAuth = KAbstractHttpAuthentication::newAuth(cachedChallenge, config()); if (m_proxyAuth) { qCDebug(KIO_HTTP) << "creating proxy authentcation header from cached info"; m_proxyAuth->setChallenge(cachedChallenge, m_request.proxyUrl, m_request.sentMethodString); m_proxyAuth->generateResponse(authinfo.username, authinfo.password); } } } } // the authentication classes don't know if they are for proxy or webserver authentication... if (m_wwwAuth && !m_wwwAuth->isError()) { ret += "Authorization: "; ret += m_wwwAuth->headerFragment(); } if (m_proxyAuth && !m_proxyAuth->isError()) { ret += "Proxy-Authorization: "; ret += m_proxyAuth->headerFragment(); } return toQString(ret); // ## encoding ok? } static QString protocolForProxyType(QNetworkProxy::ProxyType type) { switch (type) { case QNetworkProxy::DefaultProxy: break; case QNetworkProxy::Socks5Proxy: return QStringLiteral("socks"); case QNetworkProxy::NoProxy: break; case QNetworkProxy::HttpProxy: case QNetworkProxy::HttpCachingProxy: case QNetworkProxy::FtpCachingProxy: break; } return QStringLiteral("http"); } void HTTPProtocol::proxyAuthenticationForSocket(const QNetworkProxy &proxy, QAuthenticator *authenticator) { qCDebug(KIO_HTTP) << "realm:" << authenticator->realm() << "user:" << authenticator->user(); // Set the proxy URL... m_request.proxyUrl.setScheme(protocolForProxyType(proxy.type())); m_request.proxyUrl.setUserName(proxy.user()); m_request.proxyUrl.setHost(proxy.hostName()); m_request.proxyUrl.setPort(proxy.port()); AuthInfo info; info.url = m_request.proxyUrl; info.realmValue = authenticator->realm(); info.username = authenticator->user(); info.verifyPath = info.realmValue.isEmpty(); const bool haveCachedCredentials = checkCachedAuthentication(info); const bool retryAuth = (m_socketProxyAuth != nullptr); // if m_socketProxyAuth is a valid pointer then authentication has been attempted before, // and it was not successful. see below and saveProxyAuthenticationForSocket(). if (!haveCachedCredentials || retryAuth) { // Save authentication info if the connection succeeds. We need to disconnect // this after saving the auth data (or an error) so we won't save garbage afterwards! connect(socket(), SIGNAL(connected()), this, SLOT(saveProxyAuthenticationForSocket())); //### fillPromptInfo(&info); info.prompt = i18n("You need to supply a username and a password for " "the proxy server listed below before you are allowed " "to access any sites."); info.keepPassword = true; info.commentLabel = i18n("Proxy:"); info.comment = i18n("%1 at %2", info.realmValue.toHtmlEscaped(), m_request.proxyUrl.host()); const QString errMsg((retryAuth ? i18n("Proxy Authentication Failed.") : QString())); const int errorCode = openPasswordDialogV2(info, errMsg); if (errorCode) { qCDebug(KIO_HTTP) << "proxy auth cancelled by user, or communication error"; error(errorCode, QString()); delete m_proxyAuth; m_proxyAuth = nullptr; return; } } authenticator->setUser(info.username); authenticator->setPassword(info.password); authenticator->setOption(QStringLiteral("keepalive"), info.keepPassword); if (m_socketProxyAuth) { *m_socketProxyAuth = *authenticator; } else { m_socketProxyAuth = new QAuthenticator(*authenticator); } if (!m_request.proxyUrl.userName().isEmpty()) { m_request.proxyUrl.setUserName(info.username); } } void HTTPProtocol::saveProxyAuthenticationForSocket() { qCDebug(KIO_HTTP) << "Saving authenticator"; disconnect(socket(), SIGNAL(connected()), this, SLOT(saveProxyAuthenticationForSocket())); Q_ASSERT(m_socketProxyAuth); if (m_socketProxyAuth) { qCDebug(KIO_HTTP) << "realm:" << m_socketProxyAuth->realm() << "user:" << m_socketProxyAuth->user(); KIO::AuthInfo a; a.verifyPath = true; a.url = m_request.proxyUrl; a.realmValue = m_socketProxyAuth->realm(); a.username = m_socketProxyAuth->user(); a.password = m_socketProxyAuth->password(); a.keepPassword = m_socketProxyAuth->option(QStringLiteral("keepalive")).toBool(); cacheAuthentication(a); } delete m_socketProxyAuth; m_socketProxyAuth = nullptr; } void HTTPProtocol::saveAuthenticationData() { KIO::AuthInfo authinfo; bool alreadyCached = false; KAbstractHttpAuthentication *auth = nullptr; switch (m_request.prevResponseCode) { case 401: auth = m_wwwAuth; alreadyCached = config()->readEntry("cached-www-auth", false); break; case 407: auth = m_proxyAuth; alreadyCached = config()->readEntry("cached-proxy-auth", false); break; default: Q_ASSERT(false); // should never happen! } // Prevent recaching of the same credentials over and over again. if (auth && (!auth->realm().isEmpty() || !alreadyCached)) { auth->fillKioAuthInfo(&authinfo); if (auth == m_wwwAuth) { setMetaData(QStringLiteral("{internal~currenthost}cached-www-auth"), QStringLiteral("true")); if (!authinfo.realmValue.isEmpty()) { setMetaData(QStringLiteral("{internal~currenthost}www-auth-realm"), authinfo.realmValue); } if (!authinfo.digestInfo.isEmpty()) { setMetaData(QStringLiteral("{internal~currenthost}www-auth-challenge"), authinfo.digestInfo); } } else { setMetaData(QStringLiteral("{internal~allhosts}cached-proxy-auth"), QStringLiteral("true")); if (!authinfo.realmValue.isEmpty()) { setMetaData(QStringLiteral("{internal~allhosts}proxy-auth-realm"), authinfo.realmValue); } if (!authinfo.digestInfo.isEmpty()) { setMetaData(QStringLiteral("{internal~allhosts}proxy-auth-challenge"), authinfo.digestInfo); } } qCDebug(KIO_HTTP) << "Cache authentication info ?" << authinfo.keepPassword; if (authinfo.keepPassword) { cacheAuthentication(authinfo); qCDebug(KIO_HTTP) << "Cached authentication for" << m_request.url; } } // Update our server connection state which includes www and proxy username and password. m_server.updateCredentials(m_request); } bool HTTPProtocol::handleAuthenticationHeader(const HeaderTokenizer *tokenizer) { KIO::AuthInfo authinfo; QList authTokens; KAbstractHttpAuthentication **auth; QList *blacklistedAuthTokens; TriedCredentials *triedCredentials; if (m_request.responseCode == 401) { auth = &m_wwwAuth; blacklistedAuthTokens = &m_blacklistedWwwAuthMethods; triedCredentials = &m_triedWwwCredentials; authTokens = tokenizer->iterator("www-authenticate").all(); authinfo.url = m_request.url; authinfo.username = m_server.url.userName(); authinfo.prompt = i18n("You need to supply a username and a " "password to access this site."); authinfo.commentLabel = i18n("Site:"); } else { // make sure that the 407 header hasn't escaped a lower layer when it shouldn't. // this may break proxy chains which were never tested anyway, and AFAIK they are // rare to nonexistent in the wild. Q_ASSERT(QNetworkProxy::applicationProxy().type() == QNetworkProxy::NoProxy); auth = &m_proxyAuth; blacklistedAuthTokens = &m_blacklistedProxyAuthMethods; triedCredentials = &m_triedProxyCredentials; authTokens = tokenizer->iterator("proxy-authenticate").all(); authinfo.url = m_request.proxyUrl; authinfo.username = m_request.proxyUrl.userName(); authinfo.prompt = i18n("You need to supply a username and a password for " "the proxy server listed below before you are allowed " "to access any sites."); authinfo.commentLabel = i18n("Proxy:"); } bool authRequiresAnotherRoundtrip = false; // Workaround brain dead server responses that violate the spec and // incorrectly return a 401/407 without the required WWW/Proxy-Authenticate // header fields. See bug 215736... if (!authTokens.isEmpty()) { QString errorMsg; authRequiresAnotherRoundtrip = true; if (m_request.responseCode == m_request.prevResponseCode && *auth) { // Authentication attempt failed. Retry... if ((*auth)->wasFinalStage()) { errorMsg = (m_request.responseCode == 401 ? i18n("Authentication Failed.") : i18n("Proxy Authentication Failed.")); // The authentication failed in its final stage. If the chosen method didn't use a password or // if it failed with both the supplied and prompted password then blacklist this method and try // again with another one if possible. if (!(*auth)->needCredentials() || *triedCredentials > JobCredentials) { QByteArray scheme((*auth)->scheme().trimmed()); qCDebug(KIO_HTTP) << "Blacklisting auth" << scheme; blacklistedAuthTokens->append(scheme); } delete *auth; *auth = nullptr; } else { // Create authentication header // WORKAROUND: The following piece of code prevents brain dead IIS // servers that send back multiple "WWW-Authenticate" headers from // screwing up our authentication logic during the challenge // phase (Type 2) of NTLM authenticaiton. QMutableListIterator it(authTokens); const QByteArray authScheme((*auth)->scheme().trimmed()); while (it.hasNext()) { if (qstrnicmp(authScheme.constData(), it.next().constData(), authScheme.length()) != 0) { it.remove(); } } } } QList::iterator it = authTokens.begin(); while (it != authTokens.end()) { QByteArray scheme = *it; // Separate the method name from any additional parameters (for ex. nonce or realm). int index = it->indexOf(' '); if (index > 0) { scheme.truncate(index); } if (blacklistedAuthTokens->contains(scheme)) { it = authTokens.erase(it); } else { it++; } } try_next_auth_scheme: QByteArray bestOffer = KAbstractHttpAuthentication::bestOffer(authTokens); if (*auth) { const QByteArray authScheme((*auth)->scheme().trimmed()); if (qstrnicmp(authScheme.constData(), bestOffer.constData(), authScheme.length()) != 0) { // huh, the strongest authentication scheme offered has changed. delete *auth; *auth = nullptr; } } if (!(*auth)) { *auth = KAbstractHttpAuthentication::newAuth(bestOffer, config()); } if (*auth) { qCDebug(KIO_HTTP) << "Trying authentication scheme:" << (*auth)->scheme(); // remove trailing space from the method string, or digest auth will fail (*auth)->setChallenge(bestOffer, authinfo.url, m_request.sentMethodString); QString username, password; bool generateAuthHeader = true; if ((*auth)->needCredentials()) { // use credentials supplied by the application if available if (!m_request.url.userName().isEmpty() && !m_request.url.password().isEmpty() && *triedCredentials == NoCredentials) { username = m_request.url.userName(); password = m_request.url.password(); // don't try this password any more *triedCredentials = JobCredentials; } else { // try to get credentials from kpasswdserver's cache, then try asking the user. authinfo.verifyPath = false; // we have realm, no path based checking please! authinfo.realmValue = (*auth)->realm(); if (authinfo.realmValue.isEmpty() && !(*auth)->supportsPathMatching()) { authinfo.realmValue = QLatin1String((*auth)->scheme()); } // Save the current authinfo url because it can be modified by the call to // checkCachedAuthentication. That way we can restore it if the call // modified it. const QUrl reqUrl = authinfo.url; if (!errorMsg.isEmpty() || !checkCachedAuthentication(authinfo)) { // Reset url to the saved url... authinfo.url = reqUrl; authinfo.keepPassword = true; authinfo.comment = i18n("%1 at %2", authinfo.realmValue.toHtmlEscaped(), authinfo.url.host()); const int errorCode = openPasswordDialogV2(authinfo, errorMsg); if (errorCode) { generateAuthHeader = false; authRequiresAnotherRoundtrip = false; if (!sendErrorPageNotification()) { error(ERR_ACCESS_DENIED, reqUrl.host()); } qCDebug(KIO_HTTP) << "looks like the user canceled the authentication dialog"; delete *auth; *auth = nullptr; } *triedCredentials = UserInputCredentials; } else { *triedCredentials = CachedCredentials; } username = authinfo.username; password = authinfo.password; } } if (generateAuthHeader) { (*auth)->generateResponse(username, password); (*auth)->setCachePasswordEnabled(authinfo.keepPassword); qCDebug(KIO_HTTP) << "isError=" << (*auth)->isError() << "needCredentials=" << (*auth)->needCredentials() << "forceKeepAlive=" << (*auth)->forceKeepAlive() << "forceDisconnect=" << (*auth)->forceDisconnect(); if ((*auth)->isError()) { QByteArray scheme((*auth)->scheme().trimmed()); qCDebug(KIO_HTTP) << "Blacklisting auth" << scheme; authTokens.removeOne(scheme); blacklistedAuthTokens->append(scheme); if (!authTokens.isEmpty()) { goto try_next_auth_scheme; } else { if (!sendErrorPageNotification()) { error(ERR_UNSUPPORTED_ACTION, i18n("Authorization failed.")); } authRequiresAnotherRoundtrip = false; } //### return false; ? } else if ((*auth)->forceKeepAlive()) { //### think this through for proxied / not proxied m_request.isKeepAlive = true; } else if ((*auth)->forceDisconnect()) { //### think this through for proxied / not proxied m_request.isKeepAlive = false; httpCloseConnection(); } } } else { authRequiresAnotherRoundtrip = false; if (!sendErrorPageNotification()) { error(ERR_UNSUPPORTED_ACTION, i18n("Unknown Authorization method.")); } } } return authRequiresAnotherRoundtrip; } void HTTPProtocol::copyPut(const QUrl& src, const QUrl& dest, JobFlags flags) { qCDebug(KIO_HTTP) << src << "->" << dest; if (!maybeSetRequestUrl(dest)) { return; } resetSessionSettings(); if (!(flags & KIO::Overwrite)) { // check to make sure this host supports WebDAV if (!davHostOk()) { return; } // Checks if the destination exists and return an error if it does. if (!davStatDestination()) { return; } } m_POSTbuf = new QFile (src.toLocalFile()); if (!m_POSTbuf->open(QFile::ReadOnly)) { error(KIO::ERR_CANNOT_OPEN_FOR_READING, QString()); return; } m_request.method = HTTP_PUT; m_request.cacheTag.policy = CC_Reload; proceedUntilResponseContent(); } bool HTTPProtocol::davStatDestination() { const QByteArray request ("" "" "" "" "" "" ""); davSetRequest(request); // WebDAV Stat or List... m_request.method = DAV_PROPFIND; m_request.url.setQuery(QString()); m_request.cacheTag.policy = CC_Reload; m_request.davData.depth = 0; proceedUntilResponseContent(true); if (!m_request.isKeepAlive) { httpCloseConnection(); // close connection if server requested it. m_request.isKeepAlive = true; // reset the keep alive flag. } if (m_request.responseCode == 207) { error(ERR_FILE_ALREADY_EXIST, QString()); return false; } // force re-authentication... delete m_wwwAuth; m_wwwAuth = nullptr; return true; } void HTTPProtocol::fileSystemFreeSpace(const QUrl &url) { qCDebug(KIO_HTTP) << url; if (!maybeSetRequestUrl(url)) { return; } resetSessionSettings(); davStatList(url); } void HTTPProtocol::virtual_hook(int id, void *data) { switch(id) { case SlaveBase::GetFileSystemFreeSpace: { QUrl *url = static_cast(data); fileSystemFreeSpace(*url); } break; default: SlaveBase::virtual_hook(id, data); } } // needed for JSON file embedding #include "http.moc" diff --git a/src/ioslaves/http/http.h b/src/ioslaves/http/http.h index 5c326ce1..cb01801e 100644 --- a/src/ioslaves/http/http.h +++ b/src/ioslaves/http/http.h @@ -1,615 +1,615 @@ /* Copyright (C) 2000,2001 Dawit Alemayehu Copyright (C) 2000,2001 Waldo Bastian Copyright (C) 2000,2001 George Staikos Copyright (C) 2001,2002 Hamish Rodda Copyright (C) 2007 Daniel Nicoletti Copyright (C) 2008,2009 Andreas Hartmetz 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. */ #ifndef HTTP_H #define HTTP_H -#include -#include -#include -#include +#include +#include +#include +#include #include #include "kio/tcpslavebase.h" #include "httpmethod_p.h" class QDomNodeList; class QFile; class QIODevice; class QNetworkConfigurationManager; namespace KIO { class AuthInfo; } class HeaderTokenizer; class KAbstractHttpAuthentication; class HTTPProtocol : public QObject, public KIO::TCPSlaveBase { Q_OBJECT public: HTTPProtocol(const QByteArray &protocol, const QByteArray &pool, const QByteArray &app); virtual ~HTTPProtocol(); /** HTTP version **/ enum HTTP_REV {HTTP_None, HTTP_Unknown, HTTP_10, HTTP_11, SHOUTCAST}; /** Authorization method used **/ enum AUTH_SCHEME {AUTH_None, AUTH_Basic, AUTH_NTLM, AUTH_Digest, AUTH_Negotiate}; /** DAV-specific request elements for the current connection **/ struct DAVRequest { DAVRequest() { overwrite = false; depth = 0; } QString desturl; bool overwrite; int depth; }; enum CacheIOMode { NoCache = 0, ReadFromCache = 1, WriteToCache = 2 }; struct CacheTag { CacheTag() { useCache = false; ioMode = NoCache; bytesCached = 0; file = nullptr; } enum CachePlan { UseCached = 0, ValidateCached, IgnoreCached }; // int maxCacheAge refers to seconds CachePlan plan(int maxCacheAge) const; QByteArray serialize() const; bool deserialize(const QByteArray &); KIO::CacheControl policy; // ### initialize in the constructor? bool useCache; // Whether the cache should be used enum CacheIOMode ioMode; // Write to cache file, read from it, or don't use it. quint32 fileUseCount; quint32 bytesCached; QString etag; // entity tag header as described in the HTTP standard. QFile *file; // file on disk - either a QTemporaryFile (write) or QFile (read) QDateTime servedDate; // Date when the resource was served by the origin server QDateTime lastModifiedDate; // Last modified. QDateTime expireDate; // Date when the cache entry will expire QString charset; }; /** The request for the current connection **/ struct HTTPRequest { HTTPRequest() { method = KIO::HTTP_UNKNOWN; offset = 0; endoffset = 0; allowTransferCompression = false; disablePassDialog = false; doNotWWWAuthenticate = false; doNotProxyAuthenticate = false; preferErrorPage = false; useCookieJar = false; } QByteArray methodString() const; QUrl url; QString encoded_hostname; //### can be calculated on-the-fly // Persistent connections bool isKeepAlive; int keepAliveTimeout; // Timeout in seconds. KIO::HTTP_METHOD method; QString methodStringOverride; // Overrides method if non-empty. QByteArray sentMethodString; // Stores http method actually sent KIO::filesize_t offset; KIO::filesize_t endoffset; QString windowId; // Window Id this request is related to. // Header fields QString referrer; QString charsets; QString languages; QString userAgent; // Previous and current response codes unsigned int responseCode; unsigned int prevResponseCode; // Miscellaneous QString id; DAVRequest davData; QUrl redirectUrl; QUrl proxyUrl; QStringList proxyUrls; bool isPersistentProxyConnection; bool allowTransferCompression; bool disablePassDialog; bool doNotWWWAuthenticate; bool doNotProxyAuthenticate; // Indicates whether an error page or error message is preferred. bool preferErrorPage; // Use the cookie jar (or pass cookies to the application as metadata instead) bool useCookieJar; // Cookie flags enum { CookiesAuto, CookiesManual, CookiesNone } cookieMode; CacheTag cacheTag; }; /** State of the current connection to the server **/ struct HTTPServerState { HTTPServerState() { isKeepAlive = false; isPersistentProxyConnection = false; } void initFrom(const HTTPRequest &request) { url = request.url; encoded_hostname = request.encoded_hostname; isKeepAlive = request.isKeepAlive; proxyUrl = request.proxyUrl; isPersistentProxyConnection = request.isPersistentProxyConnection; } void updateCredentials(const HTTPRequest &request) { if (url.host() == request.url.host() && url.port() == request.url.port()) { url.setUserName(request.url.userName()); url.setPassword(request.url.password()); } if (proxyUrl.host() == request.proxyUrl.host() && proxyUrl.port() == request.proxyUrl.port()) { proxyUrl.setUserName(request.proxyUrl.userName()); proxyUrl.setPassword(request.proxyUrl.password()); } } void clear() { url.clear(); encoded_hostname.clear(); proxyUrl.clear(); isKeepAlive = false; isPersistentProxyConnection = false; } QUrl url; QString encoded_hostname; QUrl proxyUrl; bool isKeepAlive; bool isPersistentProxyConnection; }; //---------------------- Re-implemented methods ---------------- virtual void setHost(const QString &host, quint16 port, const QString &user, const QString &pass) Q_DECL_OVERRIDE; void slave_status() Q_DECL_OVERRIDE; void get(const QUrl &url) Q_DECL_OVERRIDE; void put(const QUrl &url, int _mode, KIO::JobFlags flags) Q_DECL_OVERRIDE; //----------------- Re-implemented methods for WebDAV ----------- void listDir(const QUrl &url) Q_DECL_OVERRIDE; void mkdir(const QUrl &url, int _permissions) Q_DECL_OVERRIDE; void rename(const QUrl &src, const QUrl &dest, KIO::JobFlags flags) Q_DECL_OVERRIDE; void copy(const QUrl &src, const QUrl &dest, int _permissions, KIO::JobFlags flags) Q_DECL_OVERRIDE; void del(const QUrl &url, bool _isfile) Q_DECL_OVERRIDE; // ask the host whether it supports WebDAV & cache this info bool davHostOk(); // send generic DAV request void davGeneric(const QUrl &url, KIO::HTTP_METHOD method, qint64 size = -1); // Send requests to lock and unlock resources void davLock(const QUrl &url, const QString &scope, const QString &type, const QString &owner); void davUnlock(const QUrl &url); // Calls httpClose() and finished() void davFinished(); // Handle error conditions QString davError(int code = -1, const QString &url = QString()); //---------------------------- End WebDAV ----------------------- /** * Special commands supported by this slave : * 1 - HTTP POST * 2 - Cache has been updated * 3 - SSL Certificate Cache has been updated * 4 - HTTP multi get * 5 - DAV LOCK (see * 6 - DAV UNLOCK README.webdav) */ void special(const QByteArray &data) Q_DECL_OVERRIDE; void mimetype(const QUrl &url) Q_DECL_OVERRIDE; void stat(const QUrl &url) Q_DECL_OVERRIDE; void reparseConfiguration() Q_DECL_OVERRIDE; /** * Forced close of connection */ void closeConnection() Q_DECL_OVERRIDE; void post(const QUrl &url, qint64 size = -1); void multiGet(const QByteArray &data) Q_DECL_OVERRIDE; bool maybeSetRequestUrl(const QUrl &); /** * Generate and send error message based on response code. */ bool sendHttpError(); /** * Call SlaveBase::errorPage() and remember that we've called it */ bool sendErrorPageNotification(); /** * Check network status */ bool isOffline(); protected Q_SLOTS: void slotData(const QByteArray &); void slotFilterError(const QString &text); void error(int errid, const QString &text); void proxyAuthenticationForSocket(const QNetworkProxy &, QAuthenticator *); void saveProxyAuthenticationForSocket(); protected: int readChunked(); ///< Read a chunk int readLimited(); ///< Read maximum m_iSize bytes. int readUnlimited(); ///< Read as much as possible. /** * A thin wrapper around TCPSlaveBase::write() that will retry writing as * long as no error occurs. */ ssize_t write(const void *buf, size_t nbytes); using SlaveBase::write; /** * Add an encoding on to the appropriate stack this * is nececesary because transfer encodings and * content encodings must be handled separately. */ void addEncoding(const QString &, QStringList &); quint16 defaultPort() const; // The methods between here and sendQuery() are helpers for sendQuery(). /** * Return true if the request is already "done", false otherwise. * * @p cacheHasPage will be set to true if the page was found, false otherwise. */ bool satisfyRequestFromCache(bool *cacheHasPage); QString formatRequestUri() const; /** * create HTTP authentications response(s), if any */ QString authenticationHeader(); bool sendQuery(); /** * Close transfer */ void httpClose(bool keepAlive); /** * Open connection */ bool httpOpenConnection(); /** * Close connection */ void httpCloseConnection(); /** * Check whether to keep or close the connection. */ bool httpShouldCloseConnection(); void forwardHttpResponseHeader(bool forwardImmediately = true); /** * fix common mimetype errors by webservers. * * Helper for readResponseHeader(). */ void fixupResponseMimetype(); /** * fix common content-encoding errors by webservers. * * Helper for readResponseHeader(). */ void fixupResponseContentEncoding(); bool readResponseHeader(); bool parseHeaderFromCache(); void parseContentDisposition(const QString &disposition); bool sendBody(); bool sendCachedBody(); // where dataInternal == true, the content is to be made available // to an internal function. bool readBody(bool dataInternal = false); /** * Performs a WebDAV stat or list */ void davSetRequest(const QByteArray &requestXML); void davStatList(const QUrl &url, bool stat = true); void davParsePropstats(const QDomNodeList &propstats, KIO::UDSEntry &entry); void davParseActiveLocks(const QDomNodeList &activeLocks, uint &lockCount); /** * Parses a date & time string */ QDateTime parseDateTime(const QString &input, const QString &type); /** * Returns the error code from a "HTTP/1.1 code Code Name" string */ int codeFromResponse(const QString &response); /** * Extracts locks from metadata * Returns the appropriate If: header */ QString davProcessLocks(); /** * Send a cookie to the cookiejar */ void addCookies(const QString &url, const QByteArray &cookieHeader); /** * Look for cookies in the cookiejar */ QString findCookies(const QString &url); void cacheParseResponseHeader(const HeaderTokenizer &tokenizer); QString cacheFilePathFromUrl(const QUrl &url) const; bool cacheFileOpenRead(); bool cacheFileOpenWrite(); void cacheFileClose(); void sendCacheCleanerCommand(const QByteArray &command); QByteArray cacheFileReadPayload(int maxLength); void cacheFileWritePayload(const QByteArray &d); void cacheFileWriteTextHeader(); /** * check URL to guard against hash collisions, and load the etag for validation */ bool cacheFileReadTextHeader1(const QUrl &desiredUrl); /** * load the rest of the text fields */ bool cacheFileReadTextHeader2(); void setCacheabilityMetadata(bool cachingAllowed); /** * Do everything proceedUntilResponseHeader does, and also get the response body. * This is being used as a replacement for proceedUntilResponseHeader() in * situations where we actually expect the response to have a body / payload data. * * where dataInternal == true, the content is to be made available * to an internal function. */ void proceedUntilResponseContent(bool dataInternal = false); /** * Ensure we are connected, send our query, and get the response header. */ bool proceedUntilResponseHeader(); /** * Resets any per session settings. */ void resetSessionSettings(); /** * Resets variables related to parsing a response. */ void resetResponseParsing(); /** * Resets any per connection settings. These are different from * per-session settings in that they must be invalidated every time * a request is made, e.g. a retry to re-send the header to the * server, as compared to only when a new request arrives. */ void resetConnectionSettings(); /** * Caches the POST data in a temporary buffer. * * Depending on size of content, the temporary buffer might be * created either in memory or on disk as (a temporary file). */ void cachePostData(const QByteArray &); /** * Clears the POST data buffer. * * Note that calling this function results in the POST data buffer * getting completely deleted. */ void clearPostDataBuffer(); /** * Returns true on successful retrieval of all content data. */ bool retrieveAllData(); /** * Saves HTTP authentication data. */ void saveAuthenticationData(); /** * Handles HTTP authentication. */ bool handleAuthenticationHeader(const HeaderTokenizer *tokenizer); /** * Handles file -> webdav put requests. */ void copyPut(const QUrl& src, const QUrl& dest, KIO::JobFlags flags); /** * Stats a remote DAV file and returns true if it exists. */ bool davStatDestination(); void virtual_hook(int id, void *data) Q_DECL_OVERRIDE; private: void fileSystemFreeSpace(const QUrl &url); // KF6 TODO: Once a virtual fileSystemFreeSpace method in SlaveBase exists, override it protected: /* This stores information about the credentials already tried * during the authentication stage (in case the auth method uses * a username and password). Initially the job-provided credentials * are used (if any). In case of failure the credential cache is * queried and if this fails the user is asked to provide credentials * interactively (unless forbidden by metadata) */ enum TriedCredentials { NoCredentials = 0, JobCredentials, CachedCredentials, UserInputCredentials }; HTTPServerState m_server; HTTPRequest m_request; QList m_requestQueue; // Processing related KIO::filesize_t m_iSize; ///< Expected size of message KIO::filesize_t m_iPostDataSize; KIO::filesize_t m_iBytesLeft; ///< # of bytes left to receive in this message. KIO::filesize_t m_iContentLeft; ///< # of content bytes left QByteArray m_receiveBuf; ///< Receive buffer bool m_dataInternal; ///< Data is for internal consumption bool m_isChunked; ///< Chunked transfer encoding bool m_isBusy; ///< Busy handling request queue. bool m_isEOF; bool m_isEOD; //--- Settings related to a single response only bool m_isRedirection; ///< Indicates current request is a redirection QStringList m_responseHeaders; ///< All headers // Language/Encoding related QStringList m_transferEncodings; QStringList m_contentEncodings; QString m_contentMD5; QString m_mimeType; // TODO QByteArray? //--- WebDAV // Data structure to hold data which will be passed to an internal func. QByteArray m_webDavDataBuf; QStringList m_davCapabilities; bool m_davHostOk; bool m_davHostUnsupported; //---------- // Mimetype determination bool m_cpMimeBuffer; QByteArray m_mimeTypeBuffer; // Holds the POST data so it won't get lost on if we // happend to get a 401/407 response when submitting // a form. QIODevice *m_POSTbuf; // Cache related int m_maxCacheAge; ///< Maximum age of a cache entry in seconds. long m_maxCacheSize; ///< Maximum cache size in Kb. QString m_strCacheDir; ///< Location of the cache. QLocalSocket m_cacheCleanerConnection; ///< Connection to the cache cleaner process // Operation mode QByteArray m_protocol; KAbstractHttpAuthentication *m_wwwAuth; QList m_blacklistedWwwAuthMethods; TriedCredentials m_triedWwwCredentials; KAbstractHttpAuthentication *m_proxyAuth; QList m_blacklistedProxyAuthMethods; TriedCredentials m_triedProxyCredentials; // For proxy auth when it's handled by the Qt/KDE socket classes QAuthenticator *m_socketProxyAuth; // To know if we are online or not QNetworkConfigurationManager *m_networkConfig; // The current KIO error on this request / response pair - zero / KJob::NoError if no error int m_kioError; // Whether we are loading an error page (body of a reply with error response code) bool m_isLoadingErrorPage; // Values that determine the remote connection timeouts. int m_remoteRespTimeout; // EOF Retry count quint8 m_iEOFRetryCount; QByteArray m_unreadBuf; void clearUnreadBuffer(); void unread(char *buf, size_t size); size_t readBuffered(char *buf, size_t size, bool unlimited = true); bool readDelimitedText(char *buf, int *idx, int end, int numNewlines); }; #endif diff --git a/src/ioslaves/http/http_cache_cleaner.cpp b/src/ioslaves/http/http_cache_cleaner.cpp index 5093b10c..eea14d75 100644 --- a/src/ioslaves/http/http_cache_cleaner.cpp +++ b/src/ioslaves/http/http_cache_cleaner.cpp @@ -1,850 +1,850 @@ /* This file is part of KDE Copyright (C) 1999-2000 Waldo Bastian (bastian@kde.org) Copyright (C) 2009 Andreas Hartmetz (ahartmetz@gmail.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ //---------------------------------------------------------------------------- // // KDE HTTP Cache cleanup tool #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include -#include -#include +#include +#include #include #include #include #include #include #include #include #include #include #include QDateTime g_currentDate; int g_maxCacheAge; qint64 g_maxCacheSize; static const char appFullName[] = "org.kio5.kio_http_cache_cleaner"; static const char appName[] = "kio_http_cache_cleaner"; // !START OF SYNC! // Keep the following in sync with the cache code in http.cpp static const int s_hashedUrlBits = 160; // this number should always be divisible by eight static const int s_hashedUrlNibbles = s_hashedUrlBits / 4; static const int s_hashedUrlBytes = s_hashedUrlBits / 8; static const char version[] = "A\n"; // never instantiated, on-disk / wire format only struct SerializedCacheFileInfo { // from http.cpp quint8 version[2]; quint8 compression; // for now fixed to 0 quint8 reserved; // for now; also alignment static const int useCountOffset = 4; qint32 useCount; qint64 servedDate; qint64 lastModifiedDate; qint64 expireDate; qint32 bytesCached; static const int size = 36; QString url; QString etag; QString mimeType; QStringList responseHeaders; // including status response like "HTTP 200 OK" }; struct MiniCacheFileInfo { // data from cache entry file, or from scoreboard file qint32 useCount; // from filesystem QDateTime lastUsedDate; qint64 sizeOnDisk; // we want to delete the least "useful" files and we'll have to sort a list for that... bool operator<(const MiniCacheFileInfo &other) const; void debugPrint() const { // qDebug() << "useCount:" << useCount // << "\nlastUsedDate:" << lastUsedDate.toString(Qt::ISODate) // << "\nsizeOnDisk:" << sizeOnDisk << '\n'; } }; struct CacheFileInfo : MiniCacheFileInfo { quint8 version[2]; quint8 compression; // for now fixed to 0 quint8 reserved; // for now; also alignment QDateTime servedDate; QDateTime lastModifiedDate; QDateTime expireDate; qint32 bytesCached; QString baseName; QString url; QString etag; QString mimeType; QStringList responseHeaders; // including status response like "HTTP 200 OK" void prettyPrint() const { QTextStream out(stdout, QIODevice::WriteOnly); out << "File " << baseName << " version " << version[0] << version[1]; out << "\n cached bytes " << bytesCached << " useCount " << useCount; out << "\n servedDate " << servedDate.toString(Qt::ISODate); out << "\n lastModifiedDate " << lastModifiedDate.toString(Qt::ISODate); out << "\n expireDate " << expireDate.toString(Qt::ISODate); out << "\n entity tag " << etag; out << "\n encoded URL " << url; out << "\n mimetype " << mimeType; out << "\nResponse headers follow...\n"; Q_FOREACH (const QString &h, responseHeaders) { out << h << '\n'; } } }; bool MiniCacheFileInfo::operator<(const MiniCacheFileInfo &other) const { const int thisUseful = useCount / qMax(lastUsedDate.secsTo(g_currentDate), qint64(1)); const int otherUseful = other.useCount / qMax(other.lastUsedDate.secsTo(g_currentDate), qint64(1)); return thisUseful < otherUseful; } bool CacheFileInfoPtrLessThan(const CacheFileInfo *cf1, const CacheFileInfo *cf2) { return *cf1 < *cf2; } enum OperationMode { CleanCache = 0, DeleteCache, FileInfo }; static bool readBinaryHeader(const QByteArray &d, CacheFileInfo *fi) { if (d.size() < SerializedCacheFileInfo::size) { // qDebug() << "readBinaryHeader(): file too small?"; return false; } QDataStream stream(d); stream.setVersion(QDataStream::Qt_4_5); stream >> fi->version[0]; stream >> fi->version[1]; if (fi->version[0] != version[0] || fi->version[1] != version[1]) { // qDebug() << "readBinaryHeader(): wrong magic bytes"; return false; } stream >> fi->compression; stream >> fi->reserved; stream >> fi->useCount; stream >> fi->servedDate; stream >> fi->lastModifiedDate; stream >> fi->expireDate; stream >> fi->bytesCached; return true; } static QString filenameFromUrl(const QByteArray &url) { QCryptographicHash hash(QCryptographicHash::Sha1); hash.addData(url); return QString::fromLatin1(hash.result().toHex()); } static QString cacheDir() { return QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/kio_http"; } static QString filePath(const QString &baseName) { QString cacheDirName = cacheDir(); if (!cacheDirName.endsWith('/')) { cacheDirName.append('/'); } return cacheDirName + baseName; } static bool readLineChecked(QIODevice *dev, QByteArray *line) { *line = dev->readLine(8192); // if nothing read or the line didn't fit into 8192 bytes(!) if (line->isEmpty() || !line->endsWith('\n')) { return false; } // we don't actually want the newline! line->chop(1); return true; } static bool readTextHeader(QFile *file, CacheFileInfo *fi, OperationMode mode) { bool ok = true; QByteArray readBuf; ok = ok && readLineChecked(file, &readBuf); fi->url = QString::fromLatin1(readBuf); if (filenameFromUrl(readBuf) != QFileInfo(*file).baseName()) { // qDebug() << "You have witnessed a very improbable hash collision!"; return false; } // only read the necessary info for cache cleaning. Saves time and (more importantly) memory. if (mode != FileInfo) { return true; } ok = ok && readLineChecked(file, &readBuf); fi->etag = QString::fromLatin1(readBuf); ok = ok && readLineChecked(file, &readBuf); fi->mimeType = QString::fromLatin1(readBuf); // read as long as no error and no empty line found while (true) { ok = ok && readLineChecked(file, &readBuf); if (ok && !readBuf.isEmpty()) { fi->responseHeaders.append(QString::fromLatin1(readBuf)); } else { break; } } return ok; // it may still be false ;) } // TODO common include file with http.cpp? enum CacheCleanerCommand { InvalidCommand = 0, CreateFileNotificationCommand, UpdateFileCommand }; static bool readCacheFile(const QString &baseName, CacheFileInfo *fi, OperationMode mode) { QFile file(filePath(baseName)); if (!file.open(QIODevice::ReadOnly)) { return false; } fi->baseName = baseName; QByteArray header = file.read(SerializedCacheFileInfo::size); // do *not* modify/delete the file if we're in file info mode. if (!(readBinaryHeader(header, fi) && readTextHeader(&file, fi, mode)) && mode != FileInfo) { // qDebug() << "read(Text|Binary)Header() returned false, deleting file" << baseName; file.remove(); return false; } // get meta-information from the filesystem QFileInfo fileInfo(file); fi->lastUsedDate = fileInfo.lastModified(); fi->sizeOnDisk = fileInfo.size(); return true; } class Scoreboard; class CacheIndex { public: explicit CacheIndex(const QString &baseName) { QByteArray ba = baseName.toLatin1(); const int sz = ba.size(); const char *input = ba.constData(); Q_ASSERT(sz == s_hashedUrlNibbles); int translated = 0; for (int i = 0; i < sz; i++) { int c = input[i]; if (c >= '0' && c <= '9') { translated |= c - '0'; } else if (c >= 'a' && c <= 'f') { translated |= c - 'a' + 10; } else { Q_ASSERT(false); } if (i & 1) { // odd index m_index[i >> 1] = translated; translated = 0; } else { translated = translated << 4; } } computeHash(); } bool operator==(const CacheIndex &other) const { const bool isEqual = memcmp(m_index, other.m_index, s_hashedUrlBytes) == 0; if (isEqual) { Q_ASSERT(m_hash == other.m_hash); } return isEqual; } private: explicit CacheIndex(const QByteArray &index) { Q_ASSERT(index.length() >= s_hashedUrlBytes); memcpy(m_index, index.constData(), s_hashedUrlBytes); computeHash(); } void computeHash() { uint hash = 0; const int ints = s_hashedUrlBytes / sizeof(uint); for (int i = 0; i < ints; i++) { hash ^= reinterpret_cast(&m_index[0])[i]; } if (const int bytesLeft = s_hashedUrlBytes % sizeof(uint)) { // dead code until a new url hash algorithm or architecture with sizeof(uint) != 4 appears. // we have the luxury of ignoring endianness because the hash is never written to disk. // just merge the bits into the hash in some way. const int offset = ints * sizeof(uint); for (int i = 0; i < bytesLeft; i++) { hash ^= static_cast(m_index[offset + i]) << (i * 8); } } m_hash = hash; } friend uint qHash(const CacheIndex &); friend class Scoreboard; quint8 m_index[s_hashedUrlBytes]; // packed binary version of the hexadecimal name uint m_hash; }; uint qHash(const CacheIndex &ci) { return ci.m_hash; } static CacheCleanerCommand readCommand(const QByteArray &cmd, CacheFileInfo *fi) { readBinaryHeader(cmd, fi); QDataStream stream(cmd); stream.skipRawData(SerializedCacheFileInfo::size); quint32 ret; stream >> ret; QByteArray baseName; baseName.resize(s_hashedUrlNibbles); stream.readRawData(baseName.data(), s_hashedUrlNibbles); Q_ASSERT(stream.atEnd()); fi->baseName = QString::fromLatin1(baseName); Q_ASSERT(ret == CreateFileNotificationCommand || ret == UpdateFileCommand); return static_cast(ret); } // never istantiated, on-disk format only struct ScoreboardEntry { // from scoreboard file quint8 index[s_hashedUrlBytes]; static const int indexSize = s_hashedUrlBytes; qint32 useCount; // from scoreboard file, but compared with filesystem to see if scoreboard has current data qint64 lastUsedDate; qint32 sizeOnDisk; static const int size = 36; // we want to delete the least "useful" files and we'll have to sort a list for that... bool operator<(const MiniCacheFileInfo &other) const; }; class Scoreboard { public: Scoreboard() { // read in the scoreboard... QFile sboard(filePath(QStringLiteral("scoreboard"))); if (sboard.open(QIODevice::ReadOnly)) { while (true) { QByteArray baIndex = sboard.read(ScoreboardEntry::indexSize); QByteArray baRest = sboard.read(ScoreboardEntry::size - ScoreboardEntry::indexSize); if (baIndex.size() + baRest.size() != ScoreboardEntry::size) { break; } const QString entryBasename = QString::fromLatin1(baIndex.toHex()); MiniCacheFileInfo mcfi; if (readAndValidateMcfi(baRest, entryBasename, &mcfi)) { m_scoreboard.insert(CacheIndex(baIndex), mcfi); } } } } void writeOut() { // write out the scoreboard QFile sboard(filePath(QStringLiteral("scoreboard"))); if (!sboard.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return; } QDataStream stream(&sboard); QHash::ConstIterator it = m_scoreboard.constBegin(); for (; it != m_scoreboard.constEnd(); ++it) { const char *indexData = reinterpret_cast(it.key().m_index); stream.writeRawData(indexData, s_hashedUrlBytes); stream << it.value().useCount; stream << it.value().lastUsedDate; stream << it.value().sizeOnDisk; } } bool fillInfo(const QString &baseName, MiniCacheFileInfo *mcfi) { QHash::ConstIterator it = m_scoreboard.constFind(CacheIndex(baseName)); if (it == m_scoreboard.constEnd()) { return false; } *mcfi = it.value(); return true; } qint64 runCommand(const QByteArray &cmd) { // execute the command; return number of bytes if a new file was created, zero otherwise. Q_ASSERT(cmd.size() == 80); CacheFileInfo fi; const CacheCleanerCommand ccc = readCommand(cmd, &fi); QString fileName = filePath(fi.baseName); switch (ccc) { case CreateFileNotificationCommand: // qDebug() << "CreateNotificationCommand for" << fi.baseName; if (!readBinaryHeader(cmd, &fi)) { return 0; } break; case UpdateFileCommand: { // qDebug() << "UpdateFileCommand for" << fi.baseName; QFile file(fileName); file.open(QIODevice::ReadWrite); CacheFileInfo fiFromDisk; QByteArray header = file.read(SerializedCacheFileInfo::size); if (!readBinaryHeader(header, &fiFromDisk) || fiFromDisk.bytesCached != fi.bytesCached) { return 0; } // adjust the use count, to make sure that we actually count up. (slaves read the file // asynchronously...) const quint32 newUseCount = fiFromDisk.useCount + 1; QByteArray newHeader = cmd.mid(0, SerializedCacheFileInfo::size); { QDataStream stream(&newHeader, QIODevice::WriteOnly); stream.skipRawData(SerializedCacheFileInfo::useCountOffset); stream << newUseCount; } file.seek(0); file.write(newHeader); file.close(); if (!readBinaryHeader(newHeader, &fi)) { return 0; } break; } default: // qDebug() << "received invalid command"; return 0; } QFileInfo fileInfo(fileName); fi.lastUsedDate = fileInfo.lastModified(); fi.sizeOnDisk = fileInfo.size(); fi.debugPrint(); // a CacheFileInfo is-a MiniCacheFileInfo which enables the following assignment... add(fi); // finally, return cache dir growth (only relevant if a file was actually created!) return ccc == CreateFileNotificationCommand ? fi.sizeOnDisk : 0; } void add(const CacheFileInfo &fi) { m_scoreboard[CacheIndex(fi.baseName)] = fi; } void remove(const QString &basename) { m_scoreboard.remove(CacheIndex(basename)); } // keep memory usage reasonably low - otherwise entries of nonexistent files don't hurt. void maybeRemoveStaleEntries(const QList &fiList) { // don't bother when there are a few bogus entries if (m_scoreboard.count() < fiList.count() + 100) { return; } // qDebug() << "we have too many fake/stale entries, cleaning up..."; QSet realFiles; Q_FOREACH (CacheFileInfo *fi, fiList) { realFiles.insert(CacheIndex(fi->baseName)); } QHash::Iterator it = m_scoreboard.begin(); while (it != m_scoreboard.end()) { if (realFiles.contains(it.key())) { ++it; } else { it = m_scoreboard.erase(it); } } } private: bool readAndValidateMcfi(const QByteArray &rawData, const QString &basename, MiniCacheFileInfo *mcfi) { QDataStream stream(rawData); stream >> mcfi->useCount; // check those against filesystem stream >> mcfi->lastUsedDate; stream >> mcfi->sizeOnDisk; QFileInfo fileInfo(filePath(basename)); if (!fileInfo.exists()) { return false; } bool ok = true; ok = ok && fileInfo.lastModified() == mcfi->lastUsedDate; ok = ok && fileInfo.size() == mcfi->sizeOnDisk; if (!ok) { // size or last-modified date not consistent with entry file; reload useCount // note that avoiding to open the file is the whole purpose of the scoreboard - we only // open the file if we really have to. QFile entryFile(fileInfo.absoluteFilePath()); if (!entryFile.open(QIODevice::ReadOnly)) { return false; } if (entryFile.size() < SerializedCacheFileInfo::size) { return false; } QDataStream stream(&entryFile); stream.skipRawData(SerializedCacheFileInfo::useCountOffset); stream >> mcfi->useCount; mcfi->lastUsedDate = fileInfo.lastModified(); mcfi->sizeOnDisk = fileInfo.size(); ok = true; } return ok; } QHash m_scoreboard; }; // Keep the above in sync with the cache code in http.cpp // !END OF SYNC! // remove files and directories used by earlier versions of the HTTP cache. static void removeOldFiles() { const char *oldDirs = "0abcdefghijklmnopqrstuvwxyz"; const int n = strlen(oldDirs); QDir cacheRootDir(filePath(QString())); for (int i = 0; i < n; i++) { QString dirName = QString::fromLatin1(&oldDirs[i], 1); // delete files in directory... Q_FOREACH (const QString &baseName, QDir(filePath(dirName)).entryList()) { QFile::remove(filePath(dirName + '/' + baseName)); } // delete the (now hopefully empty!) directory itself cacheRootDir.rmdir(dirName); } QFile::remove(filePath(QStringLiteral("cleaned"))); } class CacheCleaner { public: CacheCleaner(const QDir &cacheDir) : m_totalSizeOnDisk(0) { // qDebug(); m_fileNameList = cacheDir.entryList(); } // Delete some of the files that need to be deleted. Return true when done, false otherwise. // This makes interleaved cleaning / serving ioslaves possible. bool processSlice(Scoreboard *scoreboard = nullptr) { QTime t; t.start(); // phase one: gather information about cache files if (!m_fileNameList.isEmpty()) { while (t.elapsed() < 100 && !m_fileNameList.isEmpty()) { QString baseName = m_fileNameList.takeFirst(); // check if the filename is of the $s_hashedUrlNibbles letters, 0...f type if (baseName.length() < s_hashedUrlNibbles) { continue; } bool nameOk = true; for (int i = 0; i < s_hashedUrlNibbles && nameOk; i++) { QChar c = baseName[i]; nameOk = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); } if (!nameOk) { continue; } if (baseName.length() > s_hashedUrlNibbles) { if (QFileInfo(filePath(baseName)).lastModified().secsTo(g_currentDate) > 15 * 60) { // it looks like a temporary file that hasn't been touched in > 15 minutes... QFile::remove(filePath(baseName)); } // the temporary file might still be written to, leave it alone continue; } CacheFileInfo *fi = new CacheFileInfo(); fi->baseName = baseName; bool gotInfo = false; if (scoreboard) { gotInfo = scoreboard->fillInfo(baseName, fi); } if (!gotInfo) { gotInfo = readCacheFile(baseName, fi, CleanCache); if (gotInfo && scoreboard) { scoreboard->add(*fi); } } if (gotInfo) { m_fiList.append(fi); m_totalSizeOnDisk += fi->sizeOnDisk; } else { delete fi; } } // qDebug() << "total size of cache files is" << m_totalSizeOnDisk; if (m_fileNameList.isEmpty()) { // final step of phase one std::sort(m_fiList.begin(), m_fiList.end(), CacheFileInfoPtrLessThan); } return false; } // phase two: delete files until cache is under maximum allowed size // TODO: delete files larger than allowed for a single file while (t.elapsed() < 100) { if (m_totalSizeOnDisk <= g_maxCacheSize || m_fiList.isEmpty()) { // qDebug() << "total size of cache files after cleaning is" << m_totalSizeOnDisk; if (scoreboard) { scoreboard->maybeRemoveStaleEntries(m_fiList); scoreboard->writeOut(); } qDeleteAll(m_fiList); m_fiList.clear(); return true; } CacheFileInfo *fi = m_fiList.takeFirst(); QString filename = filePath(fi->baseName); if (QFile::remove(filename)) { m_totalSizeOnDisk -= fi->sizeOnDisk; if (scoreboard) { scoreboard->remove(fi->baseName); } } delete fi; } return false; } private: QStringList m_fileNameList; QList m_fiList; qint64 m_totalSizeOnDisk; }; int main(int argc, char **argv) { QCoreApplication app(argc, argv); app.setApplicationVersion(QStringLiteral("5.0")); KLocalizedString::setApplicationDomain("kio5"); QCommandLineParser parser; parser.addVersionOption(); parser.setApplicationDescription(QCoreApplication::translate("main", "KDE HTTP cache maintenance tool")); parser.addHelpOption(); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("clear-all"), QCoreApplication::translate("main", "Empty the cache"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("file-info"), QCoreApplication::translate("main", "Display information about cache file"), QStringLiteral("filename"))); parser.process(app); OperationMode mode = CleanCache; if (parser.isSet(QStringLiteral("clear-all"))) { mode = DeleteCache; } else if (parser.isSet(QStringLiteral("file-info"))) { mode = FileInfo; } // file info mode: no scanning of directories, just output info and exit. if (mode == FileInfo) { CacheFileInfo fi; if (!readCacheFile(parser.value(QStringLiteral("file-info")), &fi, mode)) { return 1; } fi.prettyPrint(); return 0; } // make sure we're the only running instance of the cleaner service if (mode == CleanCache) { if (!QDBusConnection::sessionBus().isConnected()) { QDBusError error(QDBusConnection::sessionBus().lastError()); fprintf(stderr, "%s: Could not connect to D-Bus! (%s: %s)\n", appName, qPrintable(error.name()), qPrintable(error.message())); return 1; } if (!QDBusConnection::sessionBus().registerService(appFullName)) { fprintf(stderr, "%s: Already running!\n", appName); return 0; } } g_currentDate = QDateTime::currentDateTime(); g_maxCacheAge = KProtocolManager::maxCacheAge(); g_maxCacheSize = mode == DeleteCache ? -1 : KProtocolManager::maxCacheSize() * 1024; QString cacheDirName = cacheDir(); QDir().mkpath(cacheDirName); QDir cacheDir(cacheDirName); if (!cacheDir.exists()) { fprintf(stderr, "%s: '%s' does not exist.\n", appName, qPrintable(cacheDirName)); return 0; } removeOldFiles(); if (mode == DeleteCache) { QTime t; t.start(); cacheDir.refresh(); //qDebug() << "time to refresh the cacheDir QDir:" << t.elapsed(); CacheCleaner cleaner(cacheDir); while (!cleaner.processSlice()) { } return 0; } QLocalServer lServer; QString socketFileName = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + QLatin1Char('/') + "kio_http_cache_cleaner"; // we need to create the file by opening the socket, otherwise it won't work QFile::remove(socketFileName); if (!lServer.listen(socketFileName)) { qWarning() << "Error listening on" << socketFileName; } QList sockets; qint64 newBytesCounter = LLONG_MAX; // force cleaner run on startup Scoreboard scoreboard; CacheCleaner *cleaner = nullptr; while (true) { g_currentDate = QDateTime::currentDateTime(); if (cleaner) { QCoreApplication::processEvents(QEventLoop::AllEvents, 100); } else { // We will not immediately know when a socket was disconnected. Causes: // - WaitForMoreEvents does not make processEvents() return when a socket disconnects // - WaitForMoreEvents *and* a timeout is not possible. QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); } if (!lServer.isListening()) { return 1; } lServer.waitForNewConnection(1); while (QLocalSocket *sock = lServer.nextPendingConnection()) { sock->waitForConnected(); sockets.append(sock); } for (int i = 0; i < sockets.size(); i++) { QLocalSocket *sock = sockets[i]; if (sock->state() != QLocalSocket::ConnectedState) { if (sock->state() != QLocalSocket::UnconnectedState) { sock->waitForDisconnected(); } delete sock; sockets.removeAll(sock); i--; continue; } sock->waitForReadyRead(0); while (true) { QByteArray recv = sock->read(80); if (recv.isEmpty()) { break; } Q_ASSERT(recv.size() == 80); newBytesCounter += scoreboard.runCommand(recv); } } // interleave cleaning with serving ioslaves to reduce "garbage collection pauses" if (cleaner) { if (cleaner->processSlice(&scoreboard)) { // that was the last slice, done delete cleaner; cleaner = nullptr; } } else if (newBytesCounter > (g_maxCacheSize / 8)) { cacheDir.refresh(); cleaner = new CacheCleaner(cacheDir); newBytesCounter = 0; } } return 0; } diff --git a/src/ioslaves/http/httpauthentication.cpp b/src/ioslaves/http/httpauthentication.cpp index 92ffd034..06c599de 100644 --- a/src/ioslaves/http/httpauthentication.cpp +++ b/src/ioslaves/http/httpauthentication.cpp @@ -1,928 +1,928 @@ /* This file is part of the KDE libraries Copyright (C) 2008, 2009 Andreas Hartmetz 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 "httpauthentication.h" #if HAVE_LIBGSSAPI #if GSSAPI_MIT #include #else #include #endif /* GSSAPI_MIT */ // Catch uncompatible crap (BR86019) #if defined(GSS_RFC_COMPLIANT_OIDS) && (GSS_RFC_COMPLIANT_OIDS == 0) #include #define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name #endif #endif /* HAVE_LIBGSSAPI */ #include #include #include #include #include -#include -#include +#include +#include Q_LOGGING_CATEGORY(KIO_HTTP_AUTH, "kf5.kio.kio_http.auth") static bool isWhiteSpace(char ch) { return (ch == ' ' || ch == '\t' || ch == '\v' || ch == '\f'); } static bool isWhiteSpaceOrComma(char ch) { return (ch == ',' || isWhiteSpace(ch)); } static bool containsScheme(const char input[], int start, int end) { // skip any comma or white space while (start < end && isWhiteSpaceOrComma(input[start])) { start++; } while (start < end) { if (isWhiteSpace(input[start])) { return true; } start++; } return false; } // keys on even indexes, values on odd indexes. Reduces code expansion for the templated // alternatives. // If "ba" starts with empty content it will be removed from ba to simplify later calls static QList parseChallenge(QByteArray &ba, QByteArray *scheme, QByteArray *nextAuth = nullptr) { QList values; const char *b = ba.constData(); int len = ba.count(); int start = 0, end = 0, pos = 0, pos2 = 0; // parse scheme while (start < len && isWhiteSpaceOrComma(b[start])) { start++; } end = start; while (end < len && !isWhiteSpace(b[end])) { end++; } // drop empty stuff from the given string, it would have to be skipped over and over again if (start != 0) { ba = ba.mid(start); end -= start; len -= start; start = 0; b = ba.constData(); } Q_ASSERT(scheme); *scheme = ba.left(end); while (end < len) { start = end; while (end < len && b[end] != '=') { end++; } pos = end; // save the end position while (end - 1 > start && isWhiteSpace(b[end - 1])) { // trim whitespace end--; } pos2 = start; while (pos2 < end && isWhiteSpace(b[pos2])) { // skip whitespace pos2++; } if (containsScheme(b, start, end) || (b[pos2] == ',' && b[pos] != '=' && pos == len)) { if (nextAuth) { *nextAuth = QByteArray(b + start); } break; // break on start of next scheme. } while (start < len && isWhiteSpaceOrComma(b[start])) { start++; } values.append(QByteArray(b + start, end - start)); end = pos; // restore the end position if (end == len) { break; } // parse value start = end + 1; //skip '=' while (start < len && isWhiteSpace(b[start])) { start++; } if (b[start] == '"') { //quoted string bool hasBs = false; bool hasErr = false; end = ++start; while (end < len) { if (b[end] == '\\') { end++; if (end + 1 >= len) { hasErr = true; break; } else { hasBs = true; end++; } } else if (b[end] == '"') { break; } else { end++; } } if (hasErr || (end == len)) { // remove the key we already inserted // qDebug() << "error in quoted text for key" << values.last(); values.removeLast(); break; } QByteArray value = QByteArray(b + start, end - start); if (hasBs) { // skip over the next character, it might be an escaped backslash int i = -1; while ((i = value.indexOf('\\', i + 1)) >= 0) { value.remove(i, 1); } } values.append(value); end++; } else { //unquoted string end = start; while (end < len && b[end] != ',' && !isWhiteSpace(b[end])) { end++; } values.append(QByteArray(b + start, end - start)); } //the quoted string has ended, but only a comma ends a key-value pair while (end < len && isWhiteSpace(b[end])) { end++; } // garbage, here should be end or field delimiter (comma) if (end < len && b[end] != ',') { // qDebug() << "unexpected character" << b[end] << "found in WWW-authentication header where token boundary (,) was expected"; break; } } // ensure every key has a value // WARNING: Do not remove the > 1 check or parsing a Type 1 NTLM // authentication challenge will surely fail. if (values.count() > 1 && values.count() % 2) { values.removeLast(); } return values; } static QByteArray valueForKey(const QList &ba, const QByteArray &key) { for (int i = 0, count = ba.count(); (i + 1) < count; i += 2) { if (ba[i] == key) { return ba[i + 1]; } } return QByteArray(); } KAbstractHttpAuthentication::KAbstractHttpAuthentication(KConfigGroup *config) : m_config(config), m_finalAuthStage(false) { reset(); } KAbstractHttpAuthentication::~KAbstractHttpAuthentication() { } QByteArray KAbstractHttpAuthentication::bestOffer(const QList &offers) { // choose the most secure auth scheme offered QByteArray negotiateOffer; QByteArray digestOffer; QByteArray ntlmOffer; QByteArray basicOffer; Q_FOREACH (const QByteArray &offer, offers) { const QByteArray scheme = offer.mid(0, offer.indexOf(' ')).toLower(); #if HAVE_LIBGSSAPI if (scheme == "negotiate") { // krazy:exclude=strings negotiateOffer = offer; } else #endif if (scheme == "digest") { // krazy:exclude=strings digestOffer = offer; } else if (scheme == "ntlm") { // krazy:exclude=strings ntlmOffer = offer; } else if (scheme == "basic") { // krazy:exclude=strings basicOffer = offer; } } if (!negotiateOffer.isEmpty()) { return negotiateOffer; } if (!digestOffer.isEmpty()) { return digestOffer; } if (!ntlmOffer.isEmpty()) { return ntlmOffer; } return basicOffer; //empty or not... } KAbstractHttpAuthentication *KAbstractHttpAuthentication::newAuth(const QByteArray &offer, KConfigGroup *config) { const QByteArray scheme = offer.mid(0, offer.indexOf(' ')).toLower(); #if HAVE_LIBGSSAPI if (scheme == "negotiate") { // krazy:exclude=strings return new KHttpNegotiateAuthentication(config); } else #endif if (scheme == "digest") { // krazy:exclude=strings return new KHttpDigestAuthentication(); } else if (scheme == "ntlm") { // krazy:exclude=strings return new KHttpNtlmAuthentication(config); } else if (scheme == "basic") { // krazy:exclude=strings return new KHttpBasicAuthentication(); } return nullptr; } QList< QByteArray > KAbstractHttpAuthentication::splitOffers(const QList< QByteArray > &offers) { // first detect if one entry may contain multiple offers QList alloffers; foreach (QByteArray offer, offers) { QByteArray scheme, cont; parseChallenge(offer, &scheme, &cont); while (!cont.isEmpty()) { offer.chop(cont.length()); alloffers << offer; offer = cont; cont.clear(); parseChallenge(offer, &scheme, &cont); } alloffers << offer; } return alloffers; } void KAbstractHttpAuthentication::reset() { m_scheme.clear(); m_challenge.clear(); m_challengeText.clear(); m_resource.clear(); m_httpMethod.clear(); m_isError = false; m_needCredentials = true; m_forceKeepAlive = false; m_forceDisconnect = false; m_keepPassword = false; m_headerFragment.clear(); m_username.clear(); m_password.clear(); } void KAbstractHttpAuthentication::setChallenge(const QByteArray &c, const QUrl &resource, const QByteArray &httpMethod) { reset(); m_challengeText = c.trimmed(); m_challenge = parseChallenge(m_challengeText, &m_scheme); Q_ASSERT(m_scheme.toLower() == scheme().toLower()); m_resource = resource; m_httpMethod = httpMethod; } QString KAbstractHttpAuthentication::realm() const { const QByteArray realm = valueForKey(m_challenge, "realm"); // TODO: Find out what this is supposed to address. The site mentioned below does not exist. if (QLocale().uiLanguages().contains(QStringLiteral("ru"))) { //for sites like lib.homelinux.org return QTextCodec::codecForName("CP1251")->toUnicode(realm); } return QString::fromLatin1(realm.constData(), realm.length()); } void KAbstractHttpAuthentication::authInfoBoilerplate(KIO::AuthInfo *a) const { a->url = m_resource; a->username = m_username; a->password = m_password; a->verifyPath = supportsPathMatching(); a->realmValue = realm(); a->digestInfo = QLatin1String(authDataToCache()); a->keepPassword = m_keepPassword; } void KAbstractHttpAuthentication::generateResponseCommon(const QString &user, const QString &password) { if (m_scheme.isEmpty() || m_httpMethod.isEmpty()) { m_isError = true; return; } if (m_needCredentials) { m_username = user; m_password = password; } m_isError = false; m_forceKeepAlive = false; m_forceDisconnect = false; m_finalAuthStage = true; } QByteArray KHttpBasicAuthentication::scheme() const { return "Basic"; } void KHttpBasicAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const { authInfoBoilerplate(ai); } void KHttpBasicAuthentication::generateResponse(const QString &user, const QString &password) { generateResponseCommon(user, password); if (m_isError) { return; } m_headerFragment = "Basic "; m_headerFragment += QByteArray(m_username.toLatin1() + ':' + m_password.toLatin1()).toBase64(); m_headerFragment += "\r\n"; } QByteArray KHttpDigestAuthentication::scheme() const { return "Digest"; } void KHttpDigestAuthentication::setChallenge(const QByteArray &c, const QUrl &resource, const QByteArray &httpMethod) { QString oldUsername; QString oldPassword; if (valueForKey(m_challenge, "stale").toLower() == "true") { // stale nonce: the auth failure that triggered this round of authentication is an artifact // of digest authentication. the credentials are probably still good, so keep them. oldUsername = m_username; oldPassword = m_password; } KAbstractHttpAuthentication::setChallenge(c, resource, httpMethod); if (!oldUsername.isEmpty() && !oldPassword.isEmpty()) { // keep credentials *and* don't ask for new ones m_needCredentials = false; m_username = oldUsername; m_password = oldPassword; } } void KHttpDigestAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const { authInfoBoilerplate(ai); } struct DigestAuthInfo { QByteArray nc; QByteArray qop; QByteArray realm; QByteArray nonce; QByteArray method; QByteArray cnonce; QByteArray username; QByteArray password; QList digestURIs; QByteArray algorithm; QByteArray entityBody; }; //calculateResponse() from the original HTTPProtocol static QByteArray calculateResponse(const DigestAuthInfo &info, const QUrl &resource) { QCryptographicHash md(QCryptographicHash::Md5); QByteArray HA1; QByteArray HA2; // Calculate H(A1) QByteArray authStr = info.username; authStr += ':'; authStr += info.realm; authStr += ':'; authStr += info.password; md.addData(authStr); if (info.algorithm.toLower() == "md5-sess") { authStr = md.result().toHex(); authStr += ':'; authStr += info.nonce; authStr += ':'; authStr += info.cnonce; md.reset(); md.addData(authStr); } HA1 = md.result().toHex(); // qDebug() << "A1 => " << HA1; // Calcualte H(A2) authStr = info.method; authStr += ':'; authStr += resource.path(QUrl::FullyEncoded).toLatin1(); if (resource.hasQuery()) { authStr += '?' + resource.query(QUrl::FullyEncoded).toLatin1(); } if (info.qop == "auth-int") { authStr += ':'; md.reset(); md.addData(info.entityBody); authStr += md.result().toHex(); } md.reset(); md.addData(authStr); HA2 = md.result().toHex(); // qDebug() << "A2 => " << HA2; // Calcualte the response. authStr = HA1; authStr += ':'; authStr += info.nonce; authStr += ':'; if (!info.qop.isEmpty()) { authStr += info.nc; authStr += ':'; authStr += info.cnonce; authStr += ':'; authStr += info.qop; authStr += ':'; } authStr += HA2; md.reset(); md.addData(authStr); const QByteArray response = md.result().toHex(); // qDebug() << "Response =>" << response; return response; } void KHttpDigestAuthentication::generateResponse(const QString &user, const QString &password) { generateResponseCommon(user, password); if (m_isError) { return; } // magic starts here (this part is slightly modified from the original in HTTPProtocol) DigestAuthInfo info; info.username = m_username.toLatin1(); //### charset breakage info.password = m_password.toLatin1(); //### // info.entityBody = p; // FIXME: send digest of data for POST action ?? info.realm = ""; info.nonce = ""; info.qop = ""; // cnonce is recommended to contain about 64 bits of entropy #ifdef ENABLE_HTTP_AUTH_NONCE_SETTER info.cnonce = m_nonce; #else info.cnonce = KRandom::randomString(16).toLatin1(); #endif // HACK: Should be fixed according to RFC 2617 section 3.2.2 info.nc = "00000001"; // Set the method used... info.method = m_httpMethod; // Parse the Digest response.... info.realm = valueForKey(m_challenge, "realm"); info.algorithm = valueForKey(m_challenge, "algorithm"); if (info.algorithm.isEmpty()) { info.algorithm = valueForKey(m_challenge, "algorith"); } if (info.algorithm.isEmpty()) { info.algorithm = "MD5"; } Q_FOREACH (const QByteArray &path, valueForKey(m_challenge, "domain").split(' ')) { QUrl u = m_resource.resolved(QUrl(path)); if (u.isValid()) { info.digestURIs.append(u); } } info.nonce = valueForKey(m_challenge, "nonce"); QByteArray opaque = valueForKey(m_challenge, "opaque"); info.qop = valueForKey(m_challenge, "qop"); // NOTE: Since we do not have access to the entity body, we cannot support // the "auth-int" qop value ; so if the server returns a comma separated // list of qop values, prefer "auth".See RFC 2617 sec 3.2.2 for the details. // If "auth" is not present or it is set to "auth-int" only, then we simply // print a warning message and disregard the qop option altogether. if (info.qop.contains(',')) { const QList values = info.qop.split(','); if (info.qop.contains("auth")) { info.qop = "auth"; } else { qCWarning(KIO_HTTP_AUTH) << "Unsupported digest authentication qop parameters:" << values; info.qop.clear(); } } else if (info.qop == "auth-int") { qCWarning(KIO_HTTP_AUTH) << "Unsupported digest authentication qop parameter:" << info.qop; info.qop.clear(); } if (info.realm.isEmpty() || info.nonce.isEmpty()) { // ### proper error return m_isError = true; return; } // If the "domain" attribute was not specified and the current response code // is authentication needed, add the current request url to the list over which // this credential can be automatically applied. if (info.digestURIs.isEmpty() /*###&& (m_request.responseCode == 401 || m_request.responseCode == 407)*/) { info.digestURIs.append(m_resource); } else { // Verify whether or not we should send a cached credential to the // server based on the stored "domain" attribute... bool send = true; // Determine the path of the request url... QString requestPath = m_resource.adjusted(QUrl::RemoveFilename).path(); if (requestPath.isEmpty()) { requestPath = QLatin1Char('/'); } Q_FOREACH (const QUrl &u, info.digestURIs) { send &= (m_resource.scheme().toLower() == u.scheme().toLower()); send &= (m_resource.host().toLower() == u.host().toLower()); if (m_resource.port() > 0 && u.port() > 0) { send &= (m_resource.port() == u.port()); } QString digestPath = u.adjusted(QUrl::RemoveFilename).path(); if (digestPath.isEmpty()) { digestPath = QLatin1Char('/'); } send &= (requestPath.startsWith(digestPath)); if (send) { break; } } if (!send) { m_isError = true; return; } } // qDebug() << "RESULT OF PARSING:"; // qDebug() << " algorithm: " << info.algorithm; // qDebug() << " realm: " << info.realm; // qDebug() << " nonce: " << info.nonce; // qDebug() << " opaque: " << opaque; // qDebug() << " qop: " << info.qop; // Calculate the response... const QByteArray response = calculateResponse(info, m_resource); QByteArray auth = "Digest username=\""; auth += info.username; auth += "\", realm=\""; auth += info.realm; auth += "\""; auth += ", nonce=\""; auth += info.nonce; auth += "\", uri=\""; auth += m_resource.path(QUrl::FullyEncoded).toLatin1(); if (m_resource.hasQuery()) { auth += '?' + m_resource.query(QUrl::FullyEncoded).toLatin1(); } if (!info.algorithm.isEmpty()) { auth += "\", algorithm="; auth += info.algorithm; } if (!info.qop.isEmpty()) { auth += ", qop="; auth += info.qop; auth += ", cnonce=\""; auth += info.cnonce; auth += "\", nc="; auth += info.nc; } auth += ", response=\""; auth += response; if (!opaque.isEmpty()) { auth += "\", opaque=\""; auth += opaque; } auth += "\"\r\n"; // magic ends here // note that auth already contains \r\n m_headerFragment = auth; } #ifdef ENABLE_HTTP_AUTH_NONCE_SETTER void KHttpDigestAuthentication::setDigestNonceValue(const QByteArray &nonce) { m_nonce = nonce; } #endif QByteArray KHttpNtlmAuthentication::scheme() const { return "NTLM"; } void KHttpNtlmAuthentication::setChallenge(const QByteArray &c, const QUrl &resource, const QByteArray &httpMethod) { QString oldUsername, oldPassword; if (!m_finalAuthStage && !m_username.isEmpty() && !m_password.isEmpty()) { oldUsername = m_username; oldPassword = m_password; } KAbstractHttpAuthentication::setChallenge(c, resource, httpMethod); if (!oldUsername.isEmpty() && !oldPassword.isEmpty()) { m_username = oldUsername; m_password = oldPassword; } // The type 1 message we're going to send needs no credentials; // they come later in the type 3 message. m_needCredentials = !m_challenge.isEmpty(); } void KHttpNtlmAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const { authInfoBoilerplate(ai); // Every auth scheme is supposed to supply a realm according to the RFCs. Of course this doesn't // prevent Microsoft from not doing it... Dummy value! // we don't have the username yet which may (may!) contain a domain, so we really have no choice ai->realmValue = QStringLiteral("NTLM"); } void KHttpNtlmAuthentication::generateResponse(const QString &_user, const QString &password) { generateResponseCommon(_user, password); if (m_isError) { return; } QByteArray buf; if (m_challenge.isEmpty()) { m_finalAuthStage = false; // first, send type 1 message (with empty domain, workstation..., but it still works) switch (m_stage1State) { case Init: if (!KNTLM::getNegotiate(buf)) { qCWarning(KIO_HTTP_AUTH) << "Error while constructing Type 1 NTLMv1 authentication request"; m_isError = true; return; } m_stage1State = SentNTLMv1; break; case SentNTLMv1: if (!KNTLM::getNegotiate(buf, QString(), QString(), KNTLM::Negotiate_NTLM2_Key | KNTLM::Negotiate_Always_Sign | KNTLM::Negotiate_Unicode | KNTLM::Request_Target | KNTLM::Negotiate_NTLM)) { qCWarning(KIO_HTTP_AUTH) << "Error while constructing Type 1 NTLMv2 authentication request"; m_isError = true; return; } m_stage1State = SentNTLMv2; break; default: qCWarning(KIO_HTTP_AUTH) << "Error - Type 1 NTLM already sent - no Type 2 response received."; m_isError = true; return; } } else { m_finalAuthStage = true; // we've (hopefully) received a valid type 2 message: send type 3 message as last step QString user, domain; if (m_username.contains(QLatin1Char('\\'))) { domain = m_username.section(QLatin1Char('\\'), 0, 0); user = m_username.section(QLatin1Char('\\'), 1); } else { user = m_username; } m_forceKeepAlive = true; const QByteArray challenge = QByteArray::fromBase64(m_challenge[0]); KNTLM::AuthFlags flags = KNTLM::Add_LM; if ((!m_config || !m_config->readEntry("EnableNTLMv2Auth", false)) && (m_stage1State != SentNTLMv2)) { flags |= KNTLM::Force_V1; } if (!KNTLM::getAuth(buf, challenge, user, m_password, domain, QStringLiteral("WORKSTATION"), flags)) { qCWarning(KIO_HTTP_AUTH) << "Error while constructing Type 3 NTLM authentication request"; m_isError = true; return; } } m_headerFragment = "NTLM "; m_headerFragment += buf.toBase64(); m_headerFragment += "\r\n"; return; } ////////////////////////// #if HAVE_LIBGSSAPI // just an error message formatter static QByteArray gssError(int major_status, int minor_status) { OM_uint32 new_status; OM_uint32 msg_ctx = 0; gss_buffer_desc major_string; gss_buffer_desc minor_string; OM_uint32 ret; QByteArray errorstr; do { ret = gss_display_status(&new_status, major_status, GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &major_string); errorstr += (const char *)major_string.value; errorstr += ' '; ret = gss_display_status(&new_status, minor_status, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &minor_string); errorstr += (const char *)minor_string.value; errorstr += ' '; } while (!GSS_ERROR(ret) && msg_ctx != 0); return errorstr; } QByteArray KHttpNegotiateAuthentication::scheme() const { return "Negotiate"; } void KHttpNegotiateAuthentication::setChallenge(const QByteArray &c, const QUrl &resource, const QByteArray &httpMethod) { KAbstractHttpAuthentication::setChallenge(c, resource, httpMethod); // GSSAPI knows how to get the credentials on its own m_needCredentials = false; } void KHttpNegotiateAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const { authInfoBoilerplate(ai); //### does GSSAPI supply anything realm-like? dummy value for now. ai->realmValue = QLatin1String("Negotiate"); } void KHttpNegotiateAuthentication::generateResponse(const QString &user, const QString &password) { generateResponseCommon(user, password); if (m_isError) { return; } OM_uint32 major_status, minor_status; gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; gss_name_t server; gss_ctx_id_t ctx; gss_OID mech_oid; static gss_OID_desc krb5_oid_desc = {9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"}; static gss_OID_desc spnego_oid_desc = {6, (void *) "\x2b\x06\x01\x05\x05\x02"}; gss_OID_set mech_set; gss_OID tmp_oid; ctx = GSS_C_NO_CONTEXT; mech_oid = &krb5_oid_desc; // see whether we can use the SPNEGO mechanism major_status = gss_indicate_mechs(&minor_status, &mech_set); if (GSS_ERROR(major_status)) { qCDebug(KIO_HTTP_AUTH) << "gss_indicate_mechs failed:" << gssError(major_status, minor_status); } else { for (uint i = 0; i < mech_set->count; i++) { tmp_oid = &mech_set->elements[i]; if (tmp_oid->length == spnego_oid_desc.length && !memcmp(tmp_oid->elements, spnego_oid_desc.elements, tmp_oid->length)) { // qDebug() << "found SPNEGO mech"; mech_oid = &spnego_oid_desc; break; } } gss_release_oid_set(&minor_status, &mech_set); } // the service name is "HTTP/f.q.d.n" QByteArray servicename = "HTTP@"; servicename += m_resource.host().toLatin1(); input_token.value = (void *)servicename.data(); input_token.length = servicename.length() + 1; major_status = gss_import_name(&minor_status, &input_token, GSS_C_NT_HOSTBASED_SERVICE, &server); input_token.value = nullptr; input_token.length = 0; if (GSS_ERROR(major_status)) { qCDebug(KIO_HTTP_AUTH) << "gss_import_name failed:" << gssError(major_status, minor_status); m_isError = true; return; } OM_uint32 req_flags; if (m_config && m_config->readEntry("DelegateCredentialsOn", false)) { req_flags = GSS_C_DELEG_FLAG; } else { req_flags = 0; } // GSSAPI knows how to get the credentials its own way, so don't ask for any major_status = gss_init_sec_context(&minor_status, GSS_C_NO_CREDENTIAL, &ctx, server, mech_oid, req_flags, GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, GSS_C_NO_BUFFER, nullptr, &output_token, nullptr, nullptr); if (GSS_ERROR(major_status) || (output_token.length == 0)) { qCDebug(KIO_HTTP_AUTH) << "gss_init_sec_context failed:" << gssError(major_status, minor_status); gss_release_name(&minor_status, &server); if (ctx != GSS_C_NO_CONTEXT) { gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER); ctx = GSS_C_NO_CONTEXT; } m_isError = true; return; } m_headerFragment = "Negotiate "; m_headerFragment += QByteArray::fromRawData(static_cast(output_token.value), output_token.length).toBase64(); m_headerFragment += "\r\n"; // free everything gss_release_name(&minor_status, &server); if (ctx != GSS_C_NO_CONTEXT) { gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER); ctx = GSS_C_NO_CONTEXT; } gss_release_buffer(&minor_status, &output_token); } #endif // HAVE_LIBGSSAPI diff --git a/src/ioslaves/http/httpauthentication.h b/src/ioslaves/http/httpauthentication.h index 5a9ec5aa..9c743f9d 100644 --- a/src/ioslaves/http/httpauthentication.h +++ b/src/ioslaves/http/httpauthentication.h @@ -1,295 +1,295 @@ /* This file is part of the KDE libraries Copyright (C) 2008, 2009 Andreas Hartmetz 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. */ #ifndef HTTPAUTHENTICATION_H #define HTTPAUTHENTICATION_H #include -#include -#include -#include +#include +#include +#include #include #include Q_DECLARE_LOGGING_CATEGORY(KIO_HTTP_AUTH) namespace KIO { class AuthInfo; } class KConfigGroup; class KAbstractHttpAuthentication { public: KAbstractHttpAuthentication(KConfigGroup *config = nullptr); virtual ~KAbstractHttpAuthentication(); /** * Choose the best authentication mechanism from the offered ones * * This will return the most secure mechanism from the list of * mechanisms retuned by the server. */ static QByteArray bestOffer(const QList &offers); /** * Returns authentication object instance appropriate for @p offer. * * @param offer the header from which an authentication object is created. * @param config the config object to read stored authentication information. */ static KAbstractHttpAuthentication *newAuth(const QByteArray &offer, KConfigGroup *config = nullptr); /** * Split all headers containing multiple authentication offers. * * @param offers the offers from multiple HTTP authentication header lines. * @return a list where each entry contains only a single offer */ static QList splitOffers(const QList &offers); /** * reset to state after default construction. */ void reset(); /** * the authentication scheme: "Negotiate", "Digest", "Basic", "NTLM" */ virtual QByteArray scheme() const = 0; /** * initiate authentication with challenge string (from HTTP header) */ virtual void setChallenge(const QByteArray &c, const QUrl &resource, const QByteArray &httpMethod); /** * return value updated by setChallenge() * * if this is false user and password passed to generateResponse * will be ignored and may be empty. */ bool needCredentials() const { return m_needCredentials; } /** * KIO compatible data to find cached credentials. * * Note that username and/or password as well as UI text will NOT be filled in. */ virtual void fillKioAuthInfo(KIO::AuthInfo *ai) const = 0; /** * what to do in response to challenge */ virtual void generateResponse(const QString &user, const QString &password) = 0; /** * returns true when the final stage of authentication is reached. * * Unless the authentication scheme requires multiple stages like NTLM this * function will always return true. */ bool wasFinalStage() const { return m_finalAuthStage; } /** * Returns true if the authentication scheme supports path matching to identify * resources that belong to the same protection space (realm). * * See RFC 2617. */ virtual bool supportsPathMatching() const { return false; } // the following accessors return useful data after generateResponse() has been called. // clients process the following fields top to bottom: highest priority is on top // malformed challenge and similar problems - it is advisable to reconnect bool isError() const { return m_isError; } /** * force keep-alive connection because the authentication method requires it */ bool forceKeepAlive() const { return m_forceKeepAlive; } /** * force disconnection because the authentication method requires it */ bool forceDisconnect() const { return m_forceDisconnect; } /** * insert this into the next request header after "Authorization: " * or "Proxy-Authorization: " */ QByteArray headerFragment() const { return m_headerFragment; } /** * Returns the realm sent by the server. * * This is mainly for GUI shown to the user. This is the identification of * the protected area on the server (e.g. "Konquis home directory" or * "KDE files"). */ QString realm() const; /** * Sets the cache password flag to @p enable. */ void setCachePasswordEnabled(bool enable) { m_keepPassword = enable; } #ifdef ENABLE_HTTP_AUTH_NONCE_SETTER // NOTE: FOR USE in unit testing ONLY. virtual void setDigestNonceValue(const QByteArray &) {} #endif protected: void authInfoBoilerplate(KIO::AuthInfo *a) const; /** * Returns any authentication data that should be cached for future use. * * NOTE: Do not reimplement this function for connection based authentication * schemes such as NTLM. */ virtual QByteArray authDataToCache() const { return QByteArray(); } void generateResponseCommon(const QString &user, const QString &password); KConfigGroup *m_config; QByteArray m_scheme; ///< this is parsed from the header and not necessarily == scheme(). QByteArray m_challengeText; QList m_challenge; QUrl m_resource; QByteArray m_httpMethod; bool m_isError; bool m_needCredentials; bool m_forceKeepAlive; bool m_forceDisconnect; bool m_finalAuthStage; bool m_keepPassword; QByteArray m_headerFragment; QString m_username; QString m_password; }; class KHttpBasicAuthentication : public KAbstractHttpAuthentication { public: QByteArray scheme() const Q_DECL_OVERRIDE; void fillKioAuthInfo(KIO::AuthInfo *ai) const Q_DECL_OVERRIDE; void generateResponse(const QString &user, const QString &password) Q_DECL_OVERRIDE; bool supportsPathMatching() const Q_DECL_OVERRIDE { return true; } protected: QByteArray authDataToCache() const Q_DECL_OVERRIDE { return m_challengeText; } private: friend class KAbstractHttpAuthentication; KHttpBasicAuthentication(KConfigGroup *config = nullptr) : KAbstractHttpAuthentication(config) {} }; class KHttpDigestAuthentication : public KAbstractHttpAuthentication { public: QByteArray scheme() const Q_DECL_OVERRIDE; void setChallenge(const QByteArray &c, const QUrl &resource, const QByteArray &httpMethod) Q_DECL_OVERRIDE; void fillKioAuthInfo(KIO::AuthInfo *ai) const Q_DECL_OVERRIDE; void generateResponse(const QString &user, const QString &password) Q_DECL_OVERRIDE; bool supportsPathMatching() const Q_DECL_OVERRIDE { return true; } #ifdef ENABLE_HTTP_AUTH_NONCE_SETTER void setDigestNonceValue(const QByteArray &) Q_DECL_OVERRIDE; #endif protected: QByteArray authDataToCache() const Q_DECL_OVERRIDE { return m_challengeText; } private: friend class KAbstractHttpAuthentication; KHttpDigestAuthentication(KConfigGroup *config = nullptr) : KAbstractHttpAuthentication(config) {} #ifdef ENABLE_HTTP_AUTH_NONCE_SETTER QByteArray m_nonce; #endif }; class KHttpNtlmAuthentication : public KAbstractHttpAuthentication { public: QByteArray scheme() const Q_DECL_OVERRIDE; void setChallenge(const QByteArray &c, const QUrl &resource, const QByteArray &httpMethod) Q_DECL_OVERRIDE; void fillKioAuthInfo(KIO::AuthInfo *ai) const Q_DECL_OVERRIDE; void generateResponse(const QString &user, const QString &password) Q_DECL_OVERRIDE; private: friend class KAbstractHttpAuthentication; KHttpNtlmAuthentication(KConfigGroup *config = nullptr) : KAbstractHttpAuthentication(config), m_stage1State(Init) {} enum Stage1State { Init = 0, SentNTLMv1, SentNTLMv2 }; Stage1State m_stage1State; }; #if HAVE_LIBGSSAPI class KHttpNegotiateAuthentication : public KAbstractHttpAuthentication { public: QByteArray scheme() const Q_DECL_OVERRIDE; void setChallenge(const QByteArray &c, const QUrl &resource, const QByteArray &httpMethod) Q_DECL_OVERRIDE; void fillKioAuthInfo(KIO::AuthInfo *ai) const Q_DECL_OVERRIDE; void generateResponse(const QString &user, const QString &password) Q_DECL_OVERRIDE; private: friend class KAbstractHttpAuthentication; KHttpNegotiateAuthentication(KConfigGroup *config = nullptr) : KAbstractHttpAuthentication(config) {} }; #endif // HAVE_LIBGSSAPI #endif // HTTPAUTHENTICATION_H diff --git a/src/ioslaves/http/kcookiejar/kcookiejar.cpp b/src/ioslaves/http/kcookiejar/kcookiejar.cpp index 4204cbf7..be176195 100644 --- a/src/ioslaves/http/kcookiejar/kcookiejar.cpp +++ b/src/ioslaves/http/kcookiejar/kcookiejar.cpp @@ -1,1611 +1,1611 @@ /* This file is part of the KDE File Manager Copyright (C) 1998-2000 Waldo Bastian (bastian@kde.org) Copyright (C) 2000,2001 Dawit Alemayehu (adawit@kde.org) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ //---------------------------------------------------------------------------- // // KDE File Manager -- HTTP Cookies // // The cookie protocol is a mess. RFC2109 is a joke since nobody seems to // use it. Apart from that it is badly written. // We try to implement Netscape Cookies and try to behave us according to // RFC2109 as much as we can. // // We assume cookies do not contain any spaces (Netscape spec.) // According to RFC2109 this is allowed though. // #include "kcookiejar.h" #include #include #include #include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include Q_LOGGING_CATEGORY(KIO_COOKIEJAR, "kf5.kio.cookiejar") // BR87227 // Waba: Should the number of cookies be limited? // I am not convinced of the need of such limit // Mozilla seems to limit to 20 cookies / domain // but it is unclear which policy it uses to expire // cookies when it exceeds that amount #undef MAX_COOKIE_LIMIT #define MAX_COOKIES_PER_HOST 25 #define READ_BUFFER_SIZE 8192 #define IP_ADDRESS_EXPRESSION "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" // Note with respect to QLatin1String( ).... // Cookies are stored as 8 bit data and passed to kio_http as Latin1 // regardless of their actual encoding. #define QL1S(x) QLatin1String(x) #define QL1C(x) QLatin1Char(x) static QString removeWeekday(const QString &value) { const int index = value.indexOf(QL1C(' ')); if (index > -1) { int pos = 0; const QString weekday = value.left(index); const QLocale cLocale = QLocale::c(); for (int i = 1; i < 8; ++i) { // No need to check for long names since the short names are // prefixes of the long names if (weekday.startsWith(cLocale.dayName(i, QLocale::ShortFormat), Qt::CaseInsensitive)) { pos = index + 1; break; } } if (pos > 0) { return value.mid(pos); } } return value; } static QDateTime parseDate(const QString &_value) { // Handle sites sending invalid weekday as part of the date. #298660 const QString value(removeWeekday(_value)); // Check if expiration date matches RFC dates as specified under // RFC 2616 sec 3.3.1 & RFC 6265 sec 4.1.1 QDateTime dt = QDateTime::fromString(value, Qt::RFC2822Date); if (!dt.isValid()) { static const char *const date_formats[] = { // Other formats documented in RFC 2616 sec 3.3.1 // Note: the RFC says timezone information MUST be "GMT", hence the hardcoded timezone string "MMM dd HH:mm:ss yyyy", /* ANSI C's asctime() format (#145244): Jan 01 00:00:00 1970 GMT */ "dd-MMM-yy HH:mm:ss 'GMT'", /* RFC 850 date: 06-Dec-39 00:30:42 GMT */ // Non-standard formats "MMM dd yyyy HH:mm:ss", /* A variation on ANSI C format seen @ amazon.com: Jan 01 1970 00:00:00 GMT */ "dd-MMM-yyyy HH:mm:ss 'GMT'", /* cookies.test: Y2K38 problem: 06-Dec-2039 00:30:42 GMT */ "MMM dd HH:mm:ss yyyy 'GMT'", /* cookies.test: Non-standard expiration dates: Sep 12 07:00:00 2020 GMT */ "MMM dd yyyy HH:mm:ss 'GMT'", /* cookies.test: Non-standard expiration dates: Sep 12 2020 07:00:00 GMT */ nullptr }; // Only English month names are allowed, thus use the C locale. const QLocale cLocale = QLocale::c(); for (int i = 0; date_formats[i]; ++i) { dt = cLocale.toDateTime(value, QL1S(date_formats[i])); if (dt.isValid()) { break; } } } return dt.toUTC(); // Per RFC 2616 sec 3.3.1 always convert to UTC. } static qint64 toEpochSecs(const QDateTime &dt) { return (dt.toMSecsSinceEpoch() / 1000); // convert to seconds... } static qint64 epoch() { return toEpochSecs(QDateTime::currentDateTimeUtc()); } QString KCookieJar::adviceToStr(KCookieAdvice _advice) { switch (_advice) { case KCookieAccept: return QStringLiteral("Accept"); case KCookieAcceptForSession: return QStringLiteral("AcceptForSession"); case KCookieReject: return QStringLiteral("Reject"); case KCookieAsk: return QStringLiteral("Ask"); default: return QStringLiteral("Dunno"); } } KCookieAdvice KCookieJar::strToAdvice(const QString &_str) { if (_str.isEmpty()) { return KCookieDunno; } QString advice = _str.toLower(); if (advice == QL1S("accept")) { return KCookieAccept; } else if (advice == QL1S("acceptforsession")) { return KCookieAcceptForSession; } else if (advice == QL1S("reject")) { return KCookieReject; } else if (advice == QL1S("ask")) { return KCookieAsk; } return KCookieDunno; } // KHttpCookie /////////////////////////////////////////////////////////////////////////// // // Cookie constructor // KHttpCookie::KHttpCookie(const QString &_host, const QString &_domain, const QString &_path, const QString &_name, const QString &_value, qint64 _expireDate, int _protocolVersion, bool _secure, bool _httpOnly, bool _explicitPath) : mHost(_host), mDomain(_domain), mPath(_path.isEmpty() ? QString() : _path), mName(_name), mValue(_value), mExpireDate(_expireDate), mProtocolVersion(_protocolVersion), mSecure(_secure), mCrossDomain(false), mHttpOnly(_httpOnly), mExplicitPath(_explicitPath), mUserSelectedAdvice(KCookieDunno) { } // // Checks if a cookie has been expired // bool KHttpCookie::isExpired(qint64 currentDate) const { if (currentDate == -1) { currentDate = epoch(); } return (mExpireDate != 0) && (mExpireDate < currentDate); } // // Returns a string for a HTTP-header // QString KHttpCookie::cookieStr(bool useDOMFormat) const { QString result; if (useDOMFormat || (mProtocolVersion == 0)) { if (mName.isEmpty()) { result = mValue; } else { result = mName + QL1C('=') + mValue; } } else { result = mName + QL1C('=') + mValue; if (mExplicitPath) { result += QL1S("; $Path=\"") + mPath + QL1C('"'); } if (!mDomain.isEmpty()) { result += QL1S("; $Domain=\"") + mDomain + QL1C('"'); } if (!mPorts.isEmpty()) { if (mPorts.length() == 2 && mPorts.at(0) == -1) { result += QL1S("; $Port"); } else { QString portNums; Q_FOREACH (int port, mPorts) { portNums += QString::number(port) + QL1C(' '); } result += QL1S("; $Port=\"") + portNums.trimmed() + QL1C('"'); } } } return result; } // // Returns whether this cookie should be send to this location. bool KHttpCookie::match(const QString &fqdn, const QStringList &domains, const QString &path, int port) const { // Cookie domain match check if (mDomain.isEmpty()) { if (fqdn != mHost) { return false; } } else if (!domains.contains(mDomain)) { if (mDomain[0] == '.') { return false; } // Maybe the domain needs an extra dot. const QString domain = QL1C('.') + mDomain; if (!domains.contains(domain)) if (fqdn != mDomain) { return false; } } else if (mProtocolVersion != 0 && port != -1 && !mPorts.isEmpty() && !mPorts.contains(port)) { return false; } // Cookie path match check if (mPath.isEmpty()) { return true; } // According to the netscape spec http://www.acme.com/foobar, // http://www.acme.com/foo.bar and http://www.acme.com/foo/bar // should all match http://www.acme.com/foo... // We only match http://www.acme.com/foo/bar if (path.startsWith(mPath) && ( (path.length() == mPath.length()) || // Paths are exact match mPath.endsWith(QL1C('/')) || // mPath ended with a slash (path[mPath.length()] == QL1C('/')) // A slash follows )) { return true; // Path of URL starts with cookie-path } return false; } // KCookieJar /////////////////////////////////////////////////////////////////////////// // // Constructs a new cookie jar // // One jar should be enough for all cookies. // KCookieJar::KCookieJar() { m_globalAdvice = KCookieDunno; m_configChanged = false; m_cookiesChanged = false; KConfig cfg(QStringLiteral("kf5/kcookiejar/domain_info"), KConfig::NoGlobals, QStandardPaths::GenericDataLocation); KConfigGroup group(&cfg, QString()); m_gTLDs = QSet::fromList(group.readEntry("gTLDs", QStringList())); m_twoLevelTLD = QSet::fromList(group.readEntry("twoLevelTLD", QStringList())); } // // Destructs the cookie jar // // Poor little cookies, they will all be eaten by the cookie monster! // KCookieJar::~KCookieJar() { qDeleteAll(m_cookieDomains); // Not much to do here } // cookiePtr is modified: the window ids of the existing cookie in the list are added to it static void removeDuplicateFromList(KHttpCookieList *list, KHttpCookie &cookiePtr, bool nameMatchOnly = false, bool updateWindowId = false) { QString domain1 = cookiePtr.domain(); if (domain1.isEmpty()) { domain1 = cookiePtr.host(); } QMutableListIterator cookieIterator(*list); while (cookieIterator.hasNext()) { const KHttpCookie &cookie = cookieIterator.next(); QString domain2 = cookie.domain(); if (domain2.isEmpty()) { domain2 = cookie.host(); } if (cookiePtr.name() == cookie.name() && (nameMatchOnly || (domain1 == domain2 && cookiePtr.path() == cookie.path()))) { if (updateWindowId) { Q_FOREACH (WId windowId, cookie.windowIds()) { if (windowId && (!cookiePtr.windowIds().contains(windowId))) { cookiePtr.windowIds().append(windowId); } } } cookieIterator.remove(); break; } } } // // Looks for cookies in the cookie jar which are appropriate for _url. // Returned is a string containing all appropriate cookies in a format // which can be added to a HTTP-header without any additional processing. // QString KCookieJar::findCookies(const QString &_url, bool useDOMFormat, WId windowId, KHttpCookieList *pendingCookies) { QString cookieStr, fqdn, path; QStringList domains; int port = -1; if (!parseUrl(_url, fqdn, path, &port)) { return cookieStr; } const bool secureRequest = (_url.startsWith(QL1S("https://"), Qt::CaseInsensitive) || _url.startsWith(QL1S("webdavs://"), Qt::CaseInsensitive)); if (port == -1) { port = (secureRequest ? 443 : 80); } extractDomains(fqdn, domains); KHttpCookieList allCookies; for (QStringList::ConstIterator it = domains.constBegin(), itEnd = domains.constEnd();; ++it) { KHttpCookieList *cookieList = nullptr; if (it == itEnd) { cookieList = pendingCookies; // Add pending cookies pendingCookies = nullptr; if (!cookieList) { break; } } else { if ((*it).isNull()) { cookieList = m_cookieDomains.value(QL1S("")); } else { cookieList = m_cookieDomains.value(*it); } if (!cookieList) { continue; // No cookies for this domain } } QMutableListIterator cookieIt(*cookieList); while (cookieIt.hasNext()) { KHttpCookie &cookie = cookieIt.next(); if (cookieAdvice(cookie) == KCookieReject) { continue; } if (!cookie.match(fqdn, domains, path, port)) { continue; } if (cookie.isSecure() && !secureRequest) { continue; } if (cookie.isHttpOnly() && useDOMFormat) { continue; } // Do not send expired cookies. if (cookie.isExpired()) { // NOTE: there is no need to delete the cookie here because the // cookieserver will invoke its saveCookieJar function as a result // of the state change below. This will then result in the cookie // being deleting at that point. m_cookiesChanged = true; continue; } if (windowId && (cookie.windowIds().indexOf(windowId) == -1)) { cookie.windowIds().append(windowId); } if (it == itEnd) { // Only needed when processing pending cookies removeDuplicateFromList(&allCookies, cookie); } allCookies.append(cookie); } if (it == itEnd) { break; // Finished. } } int protVersion = 0; Q_FOREACH (const KHttpCookie &cookie, allCookies) { if (cookie.protocolVersion() > protVersion) { protVersion = cookie.protocolVersion(); } } if (!allCookies.isEmpty()) { if (!useDOMFormat) { cookieStr = QStringLiteral("Cookie: "); } if (protVersion > 0) { cookieStr = cookieStr + QStringLiteral("$Version=") + QString::number(protVersion) + QStringLiteral("; "); } Q_FOREACH (const KHttpCookie &cookie, allCookies) { cookieStr = cookieStr + cookie.cookieStr(useDOMFormat) + QStringLiteral("; "); } cookieStr.truncate(cookieStr.length() - 2); // Remove the trailing ';' } return cookieStr; } // // This function parses a string like 'my_name="my_value";' and returns // 'my_name' in Name and 'my_value' in Value. // // A pointer to the end of the parsed part is returned. // This pointer points either to: // '\0' - The end of the string has reached. // ';' - Another my_name="my_value" pair follows // ',' - Another cookie follows // '\n' - Another header follows static const char *parseNameValue(const char *header, QString &Name, QString &Value, bool keepQuotes = false, bool rfcQuotes = false) { const char *s = header; // Parse 'my_name' part for (; (*s != '='); s++) { if ((*s == '\0') || (*s == ';') || (*s == '\n')) { // No '=' sign -> use string as the value, name is empty // (behavior found in Mozilla and IE) Name = QL1S(""); Value = QL1S(header); Value.truncate(s - header); Value = Value.trimmed(); return s; } } Name = QL1S(header); Name.truncate(s - header); Name = Name.trimmed(); // *s == '=' s++; // Skip any whitespace for (; (*s == ' ') || (*s == '\t'); s++) { if ((*s == '\0') || (*s == ';') || (*s == '\n')) { // End of Name Value = QLatin1String(""); return s; } } if ((rfcQuotes || !keepQuotes) && (*s == '\"')) { // Parse '"my_value"' part (quoted value) if (keepQuotes) { header = s++; } else { header = ++s; // skip " } for (; (*s != '\"'); s++) { if ((*s == '\0') || (*s == '\n')) { // End of Name Value = QL1S(header); Value.truncate(s - header); return s; } } Value = QL1S(header); // *s == '\"'; if (keepQuotes) { Value.truncate(++s - header); } else { Value.truncate(s++ - header); } // Skip any remaining garbage for (;; s++) { if ((*s == '\0') || (*s == ';') || (*s == '\n')) { break; } } } else { // Parse 'my_value' part (unquoted value) header = s; while ((*s != '\0') && (*s != ';') && (*s != '\n')) { s++; } // End of Name Value = QL1S(header); Value.truncate(s - header); Value = Value.trimmed(); } return s; } void KCookieJar::stripDomain(const QString &_fqdn, QString &_domain) const { QStringList domains; extractDomains(_fqdn, domains); if (domains.count() > 3) { _domain = domains[3]; } else if (domains.count() > 0) { _domain = domains[0]; } else { _domain = QL1S(""); } } QString KCookieJar::stripDomain(const KHttpCookie &cookie) const { QString domain; // We file the cookie under this domain. if (cookie.domain().isEmpty()) { stripDomain(cookie.host(), domain); } else { domain = cookie.domain(); } return domain; } bool KCookieJar::parseUrl(const QString &_url, QString &_fqdn, QString &_path, int *port) { QUrl kurl(_url); if (!kurl.isValid() || kurl.scheme().isEmpty()) { return false; } _fqdn = kurl.host().toLower(); // Cookie spoofing protection. Since there is no way a path separator, // a space or the escape encoding character is allowed in the hostname // according to RFC 2396, reject attempts to include such things there! if (_fqdn.contains(QL1C('/')) || _fqdn.contains(QL1C('%'))) { return false; // deny everything!! } // Set the port number from the protocol when one is found... if (port) { *port = kurl.port(); } _path = kurl.path(); if (_path.isEmpty()) { _path = QStringLiteral("/"); } return true; } // not static because it uses m_twoLevelTLD void KCookieJar::extractDomains(const QString &_fqdn, QStringList &_domains) const { if (_fqdn.isEmpty()) { _domains.append(QStringLiteral("localhost")); return; } // Return numeric IPv6 addresses as is... if (_fqdn[0] == '[') { _domains.append(_fqdn); return; } // Return numeric IPv4 addresses as is... if (_fqdn[0] >= '0' && _fqdn[0] <= '9' && _fqdn.indexOf(QRegExp(IP_ADDRESS_EXPRESSION)) > -1) { _domains.append(_fqdn); return; } // Always add the FQDN at the start of the list for // hostname == cookie-domainname checks! _domains.append(_fqdn); _domains.append(QL1C('.') + _fqdn); QStringList partList = _fqdn.split(QL1C('.'), QString::SkipEmptyParts); if (partList.count()) { partList.erase(partList.begin()); // Remove hostname } while (partList.count()) { if (partList.count() == 1) { break; // We only have a TLD left. } if ((partList.count() == 2) && m_twoLevelTLD.contains(partList[1].toLower())) { // This domain uses two-level TLDs in the form xxxx.yy break; } if ((partList.count() == 2) && (partList[1].length() == 2)) { // If this is a TLD, we should stop. (e.g. co.uk) // We assume this is a TLD if it ends with .xx.yy or .x.yy if (partList[0].length() <= 2) { break; // This is a TLD. } // Catch some TLDs that we miss with the previous check // e.g. com.au, org.uk, mil.co if (m_gTLDs.contains(partList[0].toLower())) { break; } } QString domain = partList.join(QLatin1Char('.')); _domains.append(domain); _domains.append(QL1C('.') + domain); partList.erase(partList.begin()); // Remove part } } // // This function parses cookie_headers and returns a linked list of // KHttpCookie objects for all cookies found in cookie_headers. // If no cookies could be found 0 is returned. // // cookie_headers should be a concatenation of all lines of a HTTP-header // which start with "Set-Cookie". The lines should be separated by '\n's. // KHttpCookieList KCookieJar::makeCookies(const QString &_url, const QByteArray &cookie_headers, WId windowId) { QString fqdn, path; if (!parseUrl(_url, fqdn, path)) { return KHttpCookieList(); // Error parsing _url } QString Name, Value; KHttpCookieList cookieList, cookieList2; bool isRFC2965 = false; bool crossDomain = false; const char *cookieStr = cookie_headers.constData(); QString defaultPath; const int index = path.lastIndexOf(QL1C('/')); if (index > 0) { defaultPath = path.left(index); } // Check for cross-domain flag from kio_http if (qstrncmp(cookieStr, "Cross-Domain\n", 13) == 0) { cookieStr += 13; crossDomain = true; } // The hard stuff :) for (;;) { // check for "Set-Cookie" if (qstrnicmp(cookieStr, "Set-Cookie:", 11) == 0) { cookieStr = parseNameValue(cookieStr + 11, Name, Value, true); // Host = FQDN // Default domain = "" // Default path according to rfc2109 KHttpCookie cookie(fqdn, QL1S(""), defaultPath, Name, Value); if (windowId) { cookie.mWindowIds.append(windowId); } cookie.mCrossDomain = crossDomain; // Insert cookie in chain cookieList.append(cookie); } else if (qstrnicmp(cookieStr, "Set-Cookie2:", 12) == 0) { // Attempt to follow rfc2965 isRFC2965 = true; cookieStr = parseNameValue(cookieStr + 12, Name, Value, true, true); // Host = FQDN // Default domain = "" // Default path according to rfc2965 KHttpCookie cookie(fqdn, QL1S(""), defaultPath, Name, Value); if (windowId) { cookie.mWindowIds.append(windowId); } cookie.mCrossDomain = crossDomain; // Insert cookie in chain cookieList2.append(cookie); } else { // This is not the start of a cookie header, skip till next line. while (*cookieStr && *cookieStr != '\n') { cookieStr++; } if (*cookieStr == '\n') { cookieStr++; } if (!*cookieStr) { break; // End of cookie_headers } else { continue; // end of this header, continue with next. } } while ((*cookieStr == ';') || (*cookieStr == ' ')) { cookieStr++; // Name-Value pair follows cookieStr = parseNameValue(cookieStr, Name, Value); KHttpCookie &lastCookie = (isRFC2965 ? cookieList2.last() : cookieList.last()); if (Name.compare(QL1S("domain"), Qt::CaseInsensitive) == 0) { QString dom = Value.toLower(); // RFC2965 3.2.2: If an explicitly specified value does not // start with a dot, the user agent supplies a leading dot if (dom.length() && dom[0] != '.') { dom.prepend("."); } // remove a trailing dot if (dom.length() > 2 && dom[dom.length() - 1] == '.') { dom = dom.left(dom.length() - 1); } if (dom.count(QL1C('.')) > 1 || dom == QLatin1String(".local")) { lastCookie.mDomain = dom; } } else if (Name.compare(QL1S("max-age"), Qt::CaseInsensitive) == 0) { int max_age = Value.toInt(); if (max_age == 0) { lastCookie.mExpireDate = 1; } else { lastCookie.mExpireDate = toEpochSecs(QDateTime::currentDateTimeUtc().addSecs(max_age)); } } else if (Name.compare(QL1S("expires"), Qt::CaseInsensitive) == 0) { const QDateTime dt = parseDate(Value); if (dt.isValid()) { lastCookie.mExpireDate = toEpochSecs(dt); if (lastCookie.mExpireDate == 0) { lastCookie.mExpireDate = 1; } } } else if (Name.compare(QL1S("path"), Qt::CaseInsensitive) == 0) { if (Value.isEmpty()) { lastCookie.mPath.clear(); // Catch "" <> QString() } else { lastCookie.mPath = QUrl::fromPercentEncoding(Value.toLatin1()); } lastCookie.mExplicitPath = true; } else if (Name.compare(QL1S("version"), Qt::CaseInsensitive) == 0) { lastCookie.mProtocolVersion = Value.toInt(); } else if (Name.compare(QL1S("secure"), Qt::CaseInsensitive) == 0 || (Name.isEmpty() && Value.compare(QL1S("secure"), Qt::CaseInsensitive) == 0)) { lastCookie.mSecure = true; } else if (Name.compare(QL1S("httponly"), Qt::CaseInsensitive) == 0 || (Name.isEmpty() && Value.compare(QL1S("httponly"), Qt::CaseInsensitive) == 0)) { lastCookie.mHttpOnly = true; } else if (isRFC2965 && (Name.compare(QL1S("port"), Qt::CaseInsensitive) == 0 || (Name.isEmpty() && Value.compare(QL1S("port"), Qt::CaseInsensitive) == 0))) { // Based on the port selection rule of RFC 2965 section 3.3.4... if (Name.isEmpty()) { // We intentionally append a -1 first in order to distinguish // between only a 'Port' vs a 'Port="80 443"' in the sent cookie. lastCookie.mPorts.append(-1); const bool secureRequest = (_url.startsWith(QL1S("https://"), Qt::CaseInsensitive) || _url.startsWith(QL1S("webdavs://"), Qt::CaseInsensitive)); if (secureRequest) { lastCookie.mPorts.append(443); } else { lastCookie.mPorts.append(80); } } else { bool ok; const QStringList portNums = Value.split(QL1C(' '), QString::SkipEmptyParts); Q_FOREACH (const QString &portNum, portNums) { const int port = portNum.toInt(&ok); if (ok) { lastCookie.mPorts.append(port); } } } } } if (*cookieStr == '\0') { break; // End of header } // Skip ';' or '\n' cookieStr++; } // RFC2965 cookies come last so that they override netscape cookies. while (!cookieList2.isEmpty()) { KHttpCookie &lastCookie = cookieList2.first(); removeDuplicateFromList(&cookieList, lastCookie, true); cookieList.append(lastCookie); cookieList2.removeFirst(); } return cookieList; } /** * Parses cookie_domstr and returns a linked list of KHttpCookie objects. * cookie_domstr should be a semicolon-delimited list of "name=value" * pairs. Any whitespace before "name" or around '=' is discarded. * If no cookies are found, 0 is returned. */ KHttpCookieList KCookieJar::makeDOMCookies(const QString &_url, const QByteArray &cookie_domstring, WId windowId) { // A lot copied from above KHttpCookieList cookieList; const char *cookieStr = cookie_domstring.data(); QString fqdn; QString path; if (!parseUrl(_url, fqdn, path)) { // Error parsing _url return KHttpCookieList(); } QString Name; QString Value; // This time it's easy while (*cookieStr) { cookieStr = parseNameValue(cookieStr, Name, Value); // Host = FQDN // Default domain = "" // Default path = "" KHttpCookie cookie(fqdn, QString(), QString(), Name, Value); if (windowId) { cookie.mWindowIds.append(windowId); } cookieList.append(cookie); if (*cookieStr != '\0') { cookieStr++; // Skip ';' or '\n' } } return cookieList; } // KHttpCookieList sorting /////////////////////////////////////////////////////////////////////////// // We want the longest path first static bool compareCookies(const KHttpCookie &item1, const KHttpCookie &item2) { return item1.path().length() > item2.path().length(); } #ifdef MAX_COOKIE_LIMIT static void makeRoom(KHttpCookieList *cookieList, KHttpCookiePtr &cookiePtr) { // Too many cookies: throw one away, try to be somewhat clever KHttpCookiePtr lastCookie = 0; for (KHttpCookiePtr cookie = cookieList->first(); cookie; cookie = cookieList->next()) { if (compareCookies(cookie, cookiePtr)) { break; } lastCookie = cookie; } if (!lastCookie) { lastCookie = cookieList->first(); } cookieList->removeRef(lastCookie); } #endif // // This function hands a KHttpCookie object over to the cookie jar. // void KCookieJar::addCookie(KHttpCookie &cookie) { QStringList domains; // We always need to do this to make sure that the // that cookies of type hostname == cookie-domainname // are properly removed and/or updated as necessary! extractDomains(cookie.host(), domains); // If the cookie specifies a domain, check whether it is valid. Otherwise, // accept the cookie anyways but removes the domain="" value to prevent // cross-site cookie injection. if (!cookie.domain().isEmpty()) { if (!domains.contains(cookie.domain()) && !cookie.domain().endsWith(QL1C('.') + cookie.host())) { cookie.fixDomain(QString()); } } QStringListIterator it(domains); while (it.hasNext()) { const QString &key = it.next(); KHttpCookieList *list; if (key.isNull()) { list = m_cookieDomains.value(QL1S("")); } else { list = m_cookieDomains.value(key); } if (list) { removeDuplicateFromList(list, cookie, false, true); } } const QString domain = stripDomain(cookie); KHttpCookieList *cookieList; if (domain.isNull()) { cookieList = m_cookieDomains.value(QL1S("")); } else { cookieList = m_cookieDomains.value(domain); } if (!cookieList) { // Make a new cookie list cookieList = new KHttpCookieList(); // All cookies whose domain is not already // known to us should be added with KCookieDunno. // KCookieDunno means that we use the global policy. cookieList->setAdvice(KCookieDunno); m_cookieDomains.insert(domain, cookieList); // Update the list of domains m_domainList.append(domain); } // Add the cookie to the cookie list // The cookie list is sorted 'longest path first' if (!cookie.isExpired()) { #ifdef MAX_COOKIE_LIMIT if (cookieList->count() >= MAX_COOKIES_PER_HOST) { makeRoom(cookieList, cookie); // Delete a cookie } #endif cookieList->push_back(cookie); // Use a stable sort so that unit tests are reliable. // In practice it doesn't matter though. qStableSort(cookieList->begin(), cookieList->end(), compareCookies); m_cookiesChanged = true; } } // // This function advices whether a single KHttpCookie object should // be added to the cookie jar. // KCookieAdvice KCookieJar::cookieAdvice(const KHttpCookie &cookie) const { if (m_rejectCrossDomainCookies && cookie.isCrossDomain()) { return KCookieReject; } if (cookie.getUserSelectedAdvice() != KCookieDunno) { return cookie.getUserSelectedAdvice(); } if (m_autoAcceptSessionCookies && cookie.expireDate() == 0) { return KCookieAccept; } QStringList domains; extractDomains(cookie.host(), domains); KCookieAdvice advice = KCookieDunno; QStringListIterator it(domains); while (advice == KCookieDunno && it.hasNext()) { const QString &domain = it.next(); if (domain.startsWith(QL1C('.')) || cookie.host() == domain) { KHttpCookieList *cookieList = m_cookieDomains.value(domain); if (cookieList) { advice = cookieList->getAdvice(); } } } if (advice == KCookieDunno) { advice = m_globalAdvice; } return advice; } // // This function tells whether a single KHttpCookie object should // be considered persistent. Persistent cookies do not get deleted // at the end of the session and are saved on disk. // bool KCookieJar::cookieIsPersistent(const KHttpCookie &cookie) const { if (cookie.expireDate() == 0) { return false; } KCookieAdvice advice = cookieAdvice(cookie); if (advice == KCookieReject || advice == KCookieAcceptForSession) { return false; } return true; } // // This function gets the advice for all cookies originating from // _domain. // KCookieAdvice KCookieJar::getDomainAdvice(const QString &_domain) const { KHttpCookieList *cookieList = m_cookieDomains.value(_domain); KCookieAdvice advice; if (cookieList) { advice = cookieList->getAdvice(); } else { advice = KCookieDunno; } return advice; } // // This function sets the advice for all cookies originating from // _domain. // void KCookieJar::setDomainAdvice(const QString &_domain, KCookieAdvice _advice) { QString domain(_domain); KHttpCookieList *cookieList = m_cookieDomains.value(domain); if (cookieList) { if (cookieList->getAdvice() != _advice) { m_configChanged = true; // domain is already known cookieList->setAdvice(_advice); } if ((cookieList->isEmpty()) && (_advice == KCookieDunno)) { // This deletes cookieList! delete m_cookieDomains.take(domain); m_domainList.removeAll(domain); } } else { // domain is not yet known if (_advice != KCookieDunno) { // We should create a domain entry m_configChanged = true; // Make a new cookie list cookieList = new KHttpCookieList(); cookieList->setAdvice(_advice); m_cookieDomains.insert(domain, cookieList); // Update the list of domains m_domainList.append(domain); } } } // // This function sets the advice for all cookies originating from // the same domain as _cookie // void KCookieJar::setDomainAdvice(const KHttpCookie &cookie, KCookieAdvice _advice) { QString domain; stripDomain(cookie.host(), domain); // We file the cookie under this domain. setDomainAdvice(domain, _advice); } // // This function sets the global advice for cookies // void KCookieJar::setGlobalAdvice(KCookieAdvice _advice) { if (m_globalAdvice != _advice) { m_configChanged = true; } m_globalAdvice = _advice; } // // Get a list of all domains known to the cookie jar. // const QStringList &KCookieJar::getDomainList() { return m_domainList; } // // Get a list of all cookies in the cookie jar originating from _domain. // KHttpCookieList *KCookieJar::getCookieList(const QString &_domain, const QString &_fqdn) { QString domain; if (_domain.isEmpty()) { stripDomain(_fqdn, domain); } else { domain = _domain; } return m_cookieDomains.value(domain); } // // Eat a cookie out of the jar. // cookieIterator should be one of the cookies returned by getCookieList() // void KCookieJar::eatCookie(KHttpCookieList::iterator cookieIterator) { const KHttpCookie &cookie = *cookieIterator; const QString domain = stripDomain(cookie); // We file the cookie under this domain. KHttpCookieList *cookieList = m_cookieDomains.value(domain); if (cookieList) { // This deletes cookie! cookieList->erase(cookieIterator); if ((cookieList->isEmpty()) && (cookieList->getAdvice() == KCookieDunno)) { // This deletes cookieList! delete m_cookieDomains.take(domain); m_domainList.removeAll(domain); } } } void KCookieJar::eatCookiesForDomain(const QString &domain) { KHttpCookieList *cookieList = m_cookieDomains.value(domain); if (!cookieList || cookieList->isEmpty()) { return; } cookieList->clear(); if (cookieList->getAdvice() == KCookieDunno) { // This deletes cookieList! delete m_cookieDomains.take(domain); m_domainList.removeAll(domain); } m_cookiesChanged = true; } void KCookieJar::eatSessionCookies(long windowId) { if (!windowId) { return; } Q_FOREACH (const QString &domain, m_domainList) { eatSessionCookies(domain, windowId, false); } } void KCookieJar::eatAllCookies() { Q_FOREACH (const QString &domain, m_domainList) { eatCookiesForDomain(domain); // This might remove domain from m_domainList! } } void KCookieJar::eatSessionCookies(const QString &fqdn, WId windowId, bool isFQDN) { KHttpCookieList *cookieList; if (!isFQDN) { cookieList = m_cookieDomains.value(fqdn); } else { QString domain; stripDomain(fqdn, domain); cookieList = m_cookieDomains.value(domain); } if (cookieList) { QMutableListIterator cookieIterator(*cookieList); while (cookieIterator.hasNext()) { KHttpCookie &cookie = cookieIterator.next(); if (cookieIsPersistent(cookie)) { continue; } QList &ids = cookie.windowIds(); #ifndef NDEBUG if (ids.contains(windowId)) { if (ids.count() > 1) { qCDebug(KIO_COOKIEJAR) << "removing window id" << windowId << "from session cookie"; } else { qCDebug(KIO_COOKIEJAR) << "deleting session cookie"; } } #endif if (!ids.removeAll(windowId) || !ids.isEmpty()) { continue; } cookieIterator.remove(); } } } static QString hostWithPort(const KHttpCookie *cookie) { const QList &ports = cookie->ports(); if (ports.isEmpty()) { return cookie->host(); } QStringList portList; Q_FOREACH (int port, ports) { portList << QString::number(port); } return (cookie->host() + QL1C(':') + portList.join(QLatin1Char(','))); } // // Saves all cookies to the file '_filename'. // On succes 'true' is returned. // On failure 'false' is returned. bool KCookieJar::saveCookies(const QString &_filename) { QSaveFile cookieFile(_filename); if (!cookieFile.open(QIODevice::WriteOnly)) { return false; } QTextStream ts(&cookieFile); ts << "# KDE Cookie File v2\n#\n"; QString s; s.sprintf("%-20s %-20s %-12s %-10s %-4s %-20s %-4s %s\n", "# Host", "Domain", "Path", "Exp.date", "Prot", "Name", "Sec", "Value"); ts << s.toLatin1().constData(); QStringListIterator it(m_domainList); while (it.hasNext()) { const QString &domain = it.next(); bool domainPrinted = false; KHttpCookieList *cookieList = m_cookieDomains.value(domain); if (!cookieList) { continue; } QMutableListIterator cookieIterator(*cookieList); while (cookieIterator.hasNext()) { const KHttpCookie &cookie = cookieIterator.next(); if (cookie.isExpired()) { // Delete expired cookies cookieIterator.remove(); continue; } if (cookieIsPersistent(cookie)) { // Only save cookies that are not "session-only cookies" if (!domainPrinted) { domainPrinted = true; ts << '[' << domain.toLocal8Bit().data() << "]\n"; } // Store persistent cookies const QString path = QL1S("\"") + cookie.path() + QL1C('"'); const QString domain = QL1S("\"") + cookie.domain() + QL1C('"'); const QString host = hostWithPort(&cookie); // TODO: replace with direct QTextStream output ? s.sprintf("%-20s %-20s %-12s %10lld %3d %-20s %-4i %s\n", host.toLatin1().constData(), domain.toLatin1().constData(), path.toLatin1().constData(), cookie.expireDate(), cookie.protocolVersion(), cookie.name().isEmpty() ? cookie.value().toLatin1().constData() : cookie.name().toLatin1().constData(), (cookie.isSecure() ? 1 : 0) + (cookie.isHttpOnly() ? 2 : 0) + (cookie.hasExplicitPath() ? 4 : 0) + (cookie.name().isEmpty() ? 8 : 0), cookie.value().toLatin1().constData()); ts << s.toLatin1().constData(); } } } if (cookieFile.commit()) { QFile::setPermissions(_filename, QFile::ReadUser | QFile::WriteUser); return true; } return false; } static const char *parseField(char *&buffer, bool keepQuotes = false) { char *result; if (!keepQuotes && (*buffer == '\"')) { // Find terminating " buffer++; result = buffer; while ((*buffer != '\"') && (*buffer)) { buffer++; } } else { // Find first white space result = buffer; while ((*buffer != ' ') && (*buffer != '\t') && (*buffer != '\n') && (*buffer)) { buffer++; } } if (!*buffer) { return result; // } *buffer++ = '\0'; // Skip white-space while ((*buffer == ' ') || (*buffer == '\t') || (*buffer == '\n')) { buffer++; } return result; } static QString extractHostAndPorts(const QString &str, QList *ports = nullptr) { if (str.isEmpty()) { return str; } const int index = str.indexOf(QL1C(':')); if (index == -1) { return str; } const QString host = str.left(index); if (ports) { bool ok; QStringList portList = str.mid(index + 1).split(QL1C(',')); Q_FOREACH (const QString &portStr, portList) { const int portNum = portStr.toInt(&ok); if (ok) { ports->append(portNum); } } } return host; } // // Reloads all cookies from the file '_filename'. // On succes 'true' is returned. // On failure 'false' is returned. bool KCookieJar::loadCookies(const QString &_filename) { QFile cookieFile(_filename); if (!cookieFile.open(QIODevice::ReadOnly)) { return false; } int version = 1; bool success = false; char *buffer = new char[READ_BUFFER_SIZE]; qint64 len = cookieFile.readLine(buffer, READ_BUFFER_SIZE - 1); if (len != -1) { if (qstrcmp(buffer, "# KDE Cookie File\n") == 0) { success = true; } else if (qstrcmp(buffer, "# KDE Cookie File v") > 0) { bool ok = false; const int verNum = QByteArray(buffer + 19, len - 19).trimmed().toInt(&ok); if (ok) { version = verNum; success = true; } } } if (success) { const qint64 currentTime = epoch(); QList ports; while (cookieFile.readLine(buffer, READ_BUFFER_SIZE - 1) != -1) { char *line = buffer; // Skip lines which begin with '#' or '[' if ((line[0] == '#') || (line[0] == '[')) { continue; } const QString host = extractHostAndPorts(QL1S(parseField(line)), &ports); const QString domain = QL1S(parseField(line)); if (host.isEmpty() && domain.isEmpty()) { continue; } const QString path = QL1S(parseField(line)); const QString expStr = QL1S(parseField(line)); if (expStr.isEmpty()) { continue; } const qint64 expDate = expStr.toLongLong(); const QString verStr = QL1S(parseField(line)); if (verStr.isEmpty()) { continue; } int protVer = verStr.toInt(); QString name = QL1S(parseField(line)); bool keepQuotes = false; bool secure = false; bool httpOnly = false; bool explicitPath = false; const char *value = nullptr; if ((version == 2) || (protVer >= 200)) { if (protVer >= 200) { protVer -= 200; } int i = atoi(parseField(line)); secure = i & 1; httpOnly = i & 2; explicitPath = i & 4; if (i & 8) { name = QLatin1String(""); } line[strlen(line) - 1] = '\0'; // Strip LF. value = line; } else { if (protVer >= 100) { protVer -= 100; keepQuotes = true; } value = parseField(line, keepQuotes); secure = QByteArray(parseField(line)).toShort(); } // Expired or parse error if (!value || expDate == 0 || expDate < currentTime) { continue; } KHttpCookie cookie(host, domain, path, name, value, expDate, protVer, secure, httpOnly, explicitPath); if (ports.count()) { cookie.mPorts = ports; } addCookie(cookie); } } delete [] buffer; m_cookiesChanged = false; return success; } // // Save the cookie configuration // void KCookieJar::saveConfig(KConfig *_config) { if (!m_configChanged) { return; } KConfigGroup dlgGroup(_config, "Cookie Dialog"); dlgGroup.writeEntry("PreferredPolicy", static_cast(m_preferredPolicy)); dlgGroup.writeEntry("ShowCookieDetails", m_showCookieDetails); KConfigGroup policyGroup(_config, "Cookie Policy"); policyGroup.writeEntry("CookieGlobalAdvice", adviceToStr(m_globalAdvice)); QStringList domainSettings; QStringListIterator it(m_domainList); while (it.hasNext()) { const QString &domain = it.next(); KCookieAdvice advice = getDomainAdvice(domain); if (advice != KCookieDunno) { const QString value = domain + QL1C(':') + adviceToStr(advice); domainSettings.append(value); } } policyGroup.writeEntry("CookieDomainAdvice", domainSettings); _config->sync(); m_configChanged = false; } // // Load the cookie configuration // void KCookieJar::loadConfig(KConfig *_config, bool reparse) { if (reparse) { _config->reparseConfiguration(); } KConfigGroup dlgGroup(_config, "Cookie Dialog"); m_showCookieDetails = dlgGroup.readEntry("ShowCookieDetails", false); m_preferredPolicy = static_cast(dlgGroup.readEntry("PreferredPolicy", 0)); KConfigGroup policyGroup(_config, "Cookie Policy"); const QStringList domainSettings = policyGroup.readEntry("CookieDomainAdvice", QStringList()); // Warning: those default values are duplicated in the kcm (kio/kcookiespolicies.cpp) m_rejectCrossDomainCookies = policyGroup.readEntry("RejectCrossDomainCookies", true); m_autoAcceptSessionCookies = policyGroup.readEntry("AcceptSessionCookies", true); m_globalAdvice = strToAdvice(policyGroup.readEntry("CookieGlobalAdvice", QStringLiteral("Accept"))); // Reset current domain settings first. Q_FOREACH (const QString &domain, m_domainList) { setDomainAdvice(domain, KCookieDunno); } // Now apply the domain settings read from config file... for (QStringList::ConstIterator it = domainSettings.constBegin(), itEnd = domainSettings.constEnd(); it != itEnd; ++it) { const QString &value = *it; const int sepPos = value.lastIndexOf(QL1C(':')); if (sepPos <= 0) { continue; } const QString domain(value.left(sepPos)); KCookieAdvice advice = strToAdvice(value.mid(sepPos + 1)); setDomainAdvice(domain, advice); } } QDebug operator<<(QDebug dbg, const KHttpCookie &cookie) { dbg.nospace() << cookie.cookieStr(false); return dbg.space(); } QDebug operator<<(QDebug dbg, const KHttpCookieList &list) { Q_FOREACH (const KHttpCookie &cookie, list) { dbg << cookie; } return dbg; } diff --git a/src/ioslaves/http/kcookiejar/kcookiejar.h b/src/ioslaves/http/kcookiejar/kcookiejar.h index e127ec04..88bd099f 100644 --- a/src/ioslaves/http/kcookiejar/kcookiejar.h +++ b/src/ioslaves/http/kcookiejar/kcookiejar.h @@ -1,475 +1,475 @@ /* This file is part of the KDE File Manager Copyright (C) 1998 Waldo Bastian (bastian@kde.org) 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, or (at your option) version 3. This software 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 library; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //---------------------------------------------------------------------------- // // KDE File Manager -- HTTP Cookies #ifndef KCOOKIEJAR_H #define KCOOKIEJAR_H -#include -#include -#include -#include +#include +#include +#include +#include #include //WId #include Q_DECLARE_LOGGING_CATEGORY(KIO_COOKIEJAR) class KConfig; class KCookieJar; class KHttpCookie; class KHttpCookieList; typedef KHttpCookie *KHttpCookiePtr; enum KCookieAdvice { KCookieDunno = 0, KCookieAccept, KCookieAcceptForSession, KCookieReject, KCookieAsk }; class KHttpCookie { friend class KCookieJar; friend class KHttpCookieList; friend QDebug operator<<(QDebug, const KHttpCookie &); // for cookieStr() protected: QString mHost; QString mDomain; QString mPath; QString mName; QString mValue; qint64 mExpireDate; int mProtocolVersion; bool mSecure; bool mCrossDomain; bool mHttpOnly; bool mExplicitPath; QList mWindowIds; QList mPorts; KCookieAdvice mUserSelectedAdvice; QString cookieStr(bool useDOMFormat) const; public: explicit KHttpCookie(const QString &_host = QString(), const QString &_domain = QString(), const QString &_path = QString(), const QString &_name = QString(), const QString &_value = QString(), qint64 _expireDate = 0, int _protocolVersion = 0, bool _secure = false, bool _httpOnly = false, bool _explicitPath = false); QString domain() const { return mDomain; } QString host() const { return mHost; } QString path() const { return mPath; } QString name() const { return mName; } QString value() const { return mValue; } QList &windowIds() { return mWindowIds; } const QList &windowIds() const { return mWindowIds; } const QList &ports() const { return mPorts; } void fixDomain(const QString &domain) { mDomain = domain; } qint64 expireDate() const { return mExpireDate; } int protocolVersion() const { return mProtocolVersion; } bool isSecure() const { return mSecure; } /** * If currentDate is -1, the default, then the current timestamp in UTC * is used for comparison against this cookie's expiration date. */ bool isExpired(qint64 currentDate = -1) const; bool isCrossDomain() const { return mCrossDomain; } bool isHttpOnly() const { return mHttpOnly; } bool hasExplicitPath() const { return mExplicitPath; } bool match(const QString &fqdn, const QStringList &domainList, const QString &path, int port = -1) const; KCookieAdvice getUserSelectedAdvice() const { return mUserSelectedAdvice; } void setUserSelectedAdvice(KCookieAdvice advice) { mUserSelectedAdvice = advice; } }; QDebug operator<<(QDebug, const KHttpCookie &); class KHttpCookieList : public QList { public: KHttpCookieList() : QList(), advice(KCookieDunno) { } virtual ~KHttpCookieList() { } KCookieAdvice getAdvice() const { return advice; } void setAdvice(KCookieAdvice _advice) { advice = _advice; } private: KCookieAdvice advice; }; QDebug operator<<(QDebug, const KHttpCookieList &); class KCookieJar { public: /** * Constructs a new cookie jar * * One jar should be enough for all cookies. */ KCookieJar(); /** * Destructs the cookie jar * * Poor little cookies, they will all be eaten by the cookie monster! */ ~KCookieJar(); /** * Returns whether the cookiejar has been changed */ bool changed() const { return m_cookiesChanged || m_configChanged; } /** * Store all the cookies in a safe(?) place */ bool saveCookies(const QString &_filename); /** * Load all the cookies from file and add them to the cookie jar. */ bool loadCookies(const QString &_filename); /** * Save the cookie configuration */ void saveConfig(KConfig *_config); /** * Load the cookie configuration */ void loadConfig(KConfig *_config, bool reparse = false); /** * Looks for cookies in the cookie jar which are appropriate for _url. * Returned is a string containing all appropriate cookies in a format * which can be added to a HTTP-header without any additional processing. * * If @p useDOMFormat is true, the string is formatted in a format * in compliance with the DOM standard. * @p pendingCookies contains a list of cookies that have not been * approved yet by the user but that will be included in the result * none the less. */ QString findCookies(const QString &_url, bool useDOMFormat, WId windowId, KHttpCookieList *pendingCookies = nullptr); /** * This function parses cookie_headers and returns a linked list of * valid KHttpCookie objects for all cookies found in cookie_headers. * If no cookies could be found 0 is returned. * * cookie_headers should be a concatenation of all lines of a HTTP-header * which start with "Set-Cookie". The lines should be separated by '\n's. */ KHttpCookieList makeCookies(const QString &_url, const QByteArray &cookie_headers, WId windowId); /** * This function parses cookie_headers and returns a linked list of * valid KHttpCookie objects for all cookies found in cookie_headers. * If no cookies could be found 0 is returned. * * cookie_domstr should be a concatenation of "name=value" pairs, separated * by a semicolon ';'. */ KHttpCookieList makeDOMCookies(const QString &_url, const QByteArray &cookie_domstr, WId windowId); /** * This function hands a KHttpCookie object over to the cookie jar. */ void addCookie(KHttpCookie &cookie); /** * This function tells whether a single KHttpCookie object should * be considered persistent. Persistent cookies do not get deleted * at the end of the session and are saved on disk. */ bool cookieIsPersistent(const KHttpCookie &cookie) const; /** * This function advices whether a single KHttpCookie object should * be added to the cookie jar. * * Possible return values are: * - KCookieAccept, the cookie should be added * - KCookieAcceptForSession, the cookie should be added as session cookie * - KCookieReject, the cookie should not be added * - KCookieAsk, the user should decide what to do * * Before sending cookies back to a server this function is consulted, * so that cookies having advice KCookieReject are not sent back. */ KCookieAdvice cookieAdvice(const KHttpCookie &cookie) const; /** * This function gets the advice for all cookies originating from * _domain. * * - KCookieDunno, no specific advice for _domain * - KCookieAccept, accept all cookies for _domain * - KCookieAcceptForSession, accept all cookies for _domain as session cookies * - KCookieReject, reject all cookies for _domain * - KCookieAsk, the user decides what to do with cookies for _domain */ KCookieAdvice getDomainAdvice(const QString &_domain) const; /** * This function sets the advice for all cookies originating from * _domain. * * _advice can have the following values: * - KCookieDunno, no specific advice for _domain * - KCookieAccept, accept all cookies for _domain * - KCookieAcceptForSession, accept all cookies for _domain as session cookies * - KCookieReject, reject all cookies for _domain * - KCookieAsk, the user decides what to do with cookies for _domain */ void setDomainAdvice(const QString &_domain, KCookieAdvice _advice); /** * This function sets the advice for all cookies originating from * the same domain as _cookie * * _advice can have the following values: * - KCookieDunno, no specific advice for _domain * - KCookieAccept, accept all cookies for _domain * - KCookieAcceptForSession, accept all cookies for _domain as session cookies * - KCookieReject, reject all cookies for _domain * - KCookieAsk, the user decides what to do with cookies for _domain */ void setDomainAdvice(const KHttpCookie &_cookie, KCookieAdvice _advice); /** * Get the global advice for cookies * * The returned advice can have the following values: * - KCookieAccept, accept cookies * - KCookieAcceptForSession, accept cookies as session cookies * - KCookieReject, reject cookies * - KCookieAsk, the user decides what to do with cookies * * The global advice is used if the domain has no advice set. */ KCookieAdvice getGlobalAdvice() const { return m_globalAdvice; } /** * This function sets the global advice for cookies * * _advice can have the following values: * - KCookieAccept, accept cookies * - KCookieAcceptForSession, accept cookies as session cookies * - KCookieReject, reject cookies * - KCookieAsk, the user decides what to do with cookies * * The global advice is used if the domain has no advice set. */ void setGlobalAdvice(KCookieAdvice _advice); /** * Get a list of all domains known to the cookie jar. * A domain is known to the cookie jar if: * - It has a cookie originating from the domain * - It has a specific advice set for the domain */ const QStringList &getDomainList(); /** * Get a list of all cookies in the cookie jar originating from _domain. */ KHttpCookieList *getCookieList(const QString &_domain, const QString &_fqdn); /** * Remove & delete a cookie from the jar. * * cookieIterator should be one of the entries in a KHttpCookieList. * Update your KHttpCookieList by calling getCookieList after * calling this function. */ void eatCookie(KHttpCookieList::iterator cookieIterator); /** * Remove & delete all cookies for @p domain. */ void eatCookiesForDomain(const QString &domain); /** * Remove & delete all cookies */ void eatAllCookies(); /** * Removes all end of session cookies set by the * session @p windId. */ void eatSessionCookies(long windowId); /** * Removes all end of session cookies set by the * session @p windId. */ void eatSessionCookies(const QString &fqdn, WId windowId, bool isFQDN = true); /** * Parses _url and returns the FQDN (_fqdn) and path (_path). */ static bool parseUrl(const QString &_url, QString &_fqdn, QString &_path, int *port = nullptr); /** * Returns a list of domains in @p _domainList relevant for this host. * The list is sorted with the FQDN listed first and the top-most * domain listed last */ void extractDomains(const QString &_fqdn, QStringList &_domainList) const; static QString adviceToStr(KCookieAdvice _advice); static KCookieAdvice strToAdvice(const QString &_str); enum KCookieDefaultPolicy { ApplyToShownCookiesOnly = 0, ApplyToCookiesFromDomain = 1, ApplyToAllCookies = 2 }; /** Returns the user's choice in the cookie window */ KCookieDefaultPolicy preferredDefaultPolicy() const { return m_preferredPolicy; } /** Returns the */ bool showCookieDetails() const { return m_showCookieDetails; } /** * Sets the user's default preference cookie policy. */ void setPreferredDefaultPolicy(KCookieDefaultPolicy value) { m_preferredPolicy = value; } /** * Sets the user's preference of level of detail displayed * by the cookie dialog. */ void setShowCookieDetails(bool value) { m_showCookieDetails = value; } protected: void stripDomain(const QString &_fqdn, QString &_domain) const; QString stripDomain(const KHttpCookie &cookie) const; protected: QStringList m_domainList; KCookieAdvice m_globalAdvice; QHash m_cookieDomains; QSet m_twoLevelTLD; QSet m_gTLDs; bool m_configChanged; bool m_cookiesChanged; bool m_showCookieDetails; bool m_rejectCrossDomainCookies; bool m_autoAcceptSessionCookies; KCookieDefaultPolicy m_preferredPolicy; }; #endif diff --git a/src/ioslaves/http/kcookiejar/kcookieserver.cpp b/src/ioslaves/http/kcookiejar/kcookieserver.cpp index 2e9bd7b9..70547c24 100644 --- a/src/ioslaves/http/kcookiejar/kcookieserver.cpp +++ b/src/ioslaves/http/kcookiejar/kcookieserver.cpp @@ -1,602 +1,602 @@ /* This file is part of KDE Copyright (C) 1998-2000 Waldo Bastian (bastian@kde.org) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ //---------------------------------------------------------------------------- // // KDE Cookie Server #include "kcookieserver.h" #define SAVE_DELAY 3 // Save after 3 minutes -#include -#include +#include +#include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(KIO_COOKIEJAR) #include "kcookiejar.h" #include "kcookiewin.h" #include "kcookieserveradaptor.h" K_PLUGIN_FACTORY_WITH_JSON(KdedCookieServerFactory, "kcookiejar.json", registerPlugin();) static QDir getOrCreateCookieJarDir() { const QDir dataDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)); const QString kcookiejarDirName = dataDir.absoluteFilePath(QStringLiteral("kcookiejar")); if (dataDir.exists(QStringLiteral("kcookiejar"))) { const QFileInfo cookiejarDirInfo(kcookiejarDirName); if (!cookiejarDirInfo.isDir()) { QFile kcookieBogusFile(kcookiejarDirName); if (!kcookieBogusFile.remove()) { QMessageBox::warning(nullptr, i18n("Cannot Save Cookies"), i18n("Could not remove %1, check permissions", cookiejarDirInfo.absoluteFilePath())); } } else { return QDir(kcookiejarDirName); } } if (!dataDir.mkpath(QStringLiteral("kcookiejar"))) { QMessageBox::warning(nullptr, i18n("Cannot Save Cookies"), i18n("Could not create directory %1", kcookiejarDirName)); } return QDir(kcookiejarDirName); } // Cookie field indexes enum CookieDetails { CF_DOMAIN = 0, CF_PATH, CF_NAME, CF_HOST, CF_VALUE, CF_EXPIRE, CF_PROVER, CF_SECURE }; class CookieRequest { public: QDBusMessage reply; QString url; bool DOM; qlonglong windowId; }; template class QList; class RequestList : public QList { public: RequestList() : QList() { } }; KCookieServer::KCookieServer(QObject *parent, const QList &) : KDEDModule(parent) { (void)new KCookieServerAdaptor(this); mCookieJar = new KCookieJar; mPendingCookies = new KHttpCookieList; mRequestList = new RequestList; mAdvicePending = false; mTimer = new QTimer(); mTimer->setSingleShot(true); connect(mTimer, SIGNAL(timeout()), SLOT(slotSave())); mConfig = new KConfig(QStringLiteral("kcookiejarrc")); mCookieJar->loadConfig(mConfig); mFilename = getOrCreateCookieJarDir().absoluteFilePath(QStringLiteral("cookies")); mCookieJar->loadCookies(mFilename); connect(this, SIGNAL(windowUnregistered(qlonglong)), this, SLOT(slotDeleteSessionCookies(qlonglong))); } KCookieServer::~KCookieServer() { slotSave(); delete mCookieJar; delete mTimer; delete mPendingCookies; delete mConfig; } bool KCookieServer::cookiesPending(const QString &url, KHttpCookieList *cookieList) { QString fqdn; QString path; // Check whether 'url' has cookies on the pending list if (mPendingCookies->isEmpty()) { return false; } if (!KCookieJar::parseUrl(url, fqdn, path)) { return false; } QStringList domains; mCookieJar->extractDomains(fqdn, domains); Q_FOREACH (const KHttpCookie &cookie, *mPendingCookies) { if (cookie.match(fqdn, domains, path)) { if (!cookieList) { return true; } cookieList->append(cookie); } } if (!cookieList) { return false; } return cookieList->isEmpty(); } void KCookieServer::addCookies(const QString &url, const QByteArray &cookieHeader, qlonglong windowId, bool useDOMFormat) { KHttpCookieList cookieList; if (useDOMFormat) { cookieList = mCookieJar->makeDOMCookies(url, cookieHeader, windowId); } else { cookieList = mCookieJar->makeCookies(url, cookieHeader, windowId); } checkCookies(&cookieList, windowId); *mPendingCookies += cookieList; if (!mAdvicePending) { mAdvicePending = true; while (!mPendingCookies->isEmpty()) { checkCookies(nullptr, windowId); } mAdvicePending = false; } } void KCookieServer::checkCookies(KHttpCookieList *cookieList) { checkCookies(cookieList, 0); } void KCookieServer::checkCookies(KHttpCookieList *cookieList, qlonglong windowId) { KHttpCookieList *list; if (cookieList) { list = cookieList; } else { list = mPendingCookies; } QMutableListIterator cookieIterator(*list); while (cookieIterator.hasNext()) { KHttpCookie &cookie = cookieIterator.next(); const KCookieAdvice advice = mCookieJar->cookieAdvice(cookie); switch (advice) { case KCookieAccept: case KCookieAcceptForSession: mCookieJar->addCookie(cookie); cookieIterator.remove(); break; case KCookieReject: cookieIterator.remove(); break; case KCookieDunno: case KCookieAsk: break; } } if (cookieList || list->isEmpty()) { return; } // Collect all pending cookies with the same host as the first pending cookie const KHttpCookie ¤tCookie = mPendingCookies->first(); KHttpCookieList currentList; currentList.append(currentCookie); const QString currentHost = currentCookie.host(); QList shownCookies; shownCookies << 0; for (int i = 1 /*first already done*/; i < mPendingCookies->count(); ++i) { const KHttpCookie &cookie = (*mPendingCookies)[i]; if (cookie.host() == currentHost) { currentList.append(cookie); shownCookies << i; } } //qDebug() << shownCookies; KCookieWin *kw = new KCookieWin(nullptr, currentList, mCookieJar->preferredDefaultPolicy(), mCookieJar->showCookieDetails()); if (windowId > 0) { KWindowSystem::setMainWindow(kw, windowId); } KCookieAdvice userAdvice = kw->advice(mCookieJar, currentCookie); delete kw; // Save the cookie config if it has changed mCookieJar->saveConfig(mConfig); // Apply the user's choice to all cookies that are currently // queued for this host (or just the first one, if the user asks for that). QMutableListIterator cookieIterator2(*mPendingCookies); int pendingCookieIndex = -1; while (cookieIterator2.hasNext()) { ++pendingCookieIndex; KHttpCookie &cookie = cookieIterator2.next(); if (cookie.host() != currentHost) { continue; } if (mCookieJar->preferredDefaultPolicy() == KCookieJar::ApplyToShownCookiesOnly && !shownCookies.contains(pendingCookieIndex)) { // User chose "only those cookies", and this one was added while the dialog was up -> skip break; } switch (userAdvice) { case KCookieAccept: case KCookieAcceptForSession: // Store the user's choice for the cookie. // This is only used to check later if the cookie should expire // at the end of the session. The choice is not saved on disk. cookie.setUserSelectedAdvice(userAdvice); mCookieJar->addCookie(cookie); cookieIterator2.remove(); break; case KCookieReject: cookieIterator2.remove(); break; case KCookieDunno: case KCookieAsk: qCWarning(KIO_COOKIEJAR) << "userAdvice not accept or reject, this should never happen!"; break; } } // Check if we can handle any request QMutableListIterator requestIterator(*mRequestList); while (requestIterator.hasNext()) { CookieRequest *request = requestIterator.next(); if (!cookiesPending(request->url)) { const QString res = mCookieJar->findCookies(request->url, request->DOM, request->windowId); QDBusConnection::sessionBus().send(request->reply.createReply(res)); delete request; requestIterator.remove(); } } saveCookieJar(); } void KCookieServer::slotSave() { if (mCookieJar->changed()) { mCookieJar->saveCookies(mFilename); } } void KCookieServer::saveCookieJar() { if (mTimer->isActive()) { return; } mTimer->start(1000 * 60 * SAVE_DELAY); } void KCookieServer::putCookie(QStringList &out, const KHttpCookie &cookie, const QList &fields) { foreach (int i, fields) { switch (i) { case CF_DOMAIN : out << cookie.domain(); break; case CF_NAME : out << cookie.name(); break; case CF_PATH : out << cookie.path(); break; case CF_HOST : out << cookie.host(); break; case CF_VALUE : out << cookie.value(); break; case CF_EXPIRE : out << QString::number(cookie.expireDate()); break; case CF_PROVER : out << QString::number(cookie.protocolVersion()); break; case CF_SECURE : out << QString::number(cookie.isSecure() ? 1 : 0); break; default : out << QString(); } } } bool KCookieServer::cookieMatches(const KHttpCookie &c, const QString &domain, const QString &fqdn, const QString &path, const QString &name) { const bool hasDomain = !domain.isEmpty(); return (((hasDomain && c.domain() == domain) || fqdn == c.host()) && (c.path() == path) && (c.name() == name) && (!c.isExpired())); } // DBUS function QString KCookieServer::listCookies(const QString &url) { return findCookies(url, 0); } // DBUS function QString KCookieServer::findCookies(const QString &url, qlonglong windowId) { if (cookiesPending(url)) { CookieRequest *request = new CookieRequest; message().setDelayedReply(true); request->reply = message(); request->url = url; request->DOM = false; request->windowId = windowId; mRequestList->append(request); return QString(); // Talk to you later :-) } QString cookies = mCookieJar->findCookies(url, false, windowId); saveCookieJar(); return cookies; } // DBUS function QStringList KCookieServer::findDomains() { QStringList result; Q_FOREACH (const QString &domain, mCookieJar->getDomainList()) { // Ignore domains that have policy set for but contain // no cookies whatsoever... const KHttpCookieList *list = mCookieJar->getCookieList(domain, QLatin1String("")); if (list && !list->isEmpty()) { result << domain; } } return result; } // DBUS function QStringList KCookieServer::findCookies(const QList &fields, const QString &_domain, const QString &fqdn, const QString &path, const QString &name) { QStringList result; const bool allCookies = name.isEmpty(); const QStringList domainList = _domain.split(QLatin1Char(' ')); if (allCookies) { Q_FOREACH (const QString &domain, domainList) { const KHttpCookieList *list = mCookieJar->getCookieList(domain, fqdn); if (!list) { continue; } Q_FOREACH (const KHttpCookie &cookie, *list) { if (cookie.isExpired()) { continue; } putCookie(result, cookie, fields); } } } else { Q_FOREACH (const QString &domain, domainList) { const KHttpCookieList *list = mCookieJar->getCookieList(domain, fqdn); if (!list) { continue; } Q_FOREACH (const KHttpCookie &cookie, *list) { if (cookie.isExpired()) { continue; } if (cookieMatches(cookie, domain, fqdn, path, name)) { putCookie(result, cookie, fields); break; } } } } return result; } // DBUS function QString KCookieServer::findDOMCookies(const QString &url) { return findDOMCookies(url, 0); } // DBUS function QString KCookieServer::findDOMCookies(const QString &url, qlonglong windowId) { // We don't wait for pending cookies because it locks up konqueror // which can cause a deadlock if it happens to have a popup-menu up. // Instead we just return pending cookies as if they had been accepted already. KHttpCookieList pendingCookies; cookiesPending(url, &pendingCookies); return mCookieJar->findCookies(url, true, windowId, &pendingCookies); } // DBUS function void KCookieServer::addCookies(const QString &arg1, const QByteArray &arg2, qlonglong arg3) { addCookies(arg1, arg2, arg3, false); } // DBUS function void KCookieServer::deleteCookie(const QString &domain, const QString &fqdn, const QString &path, const QString &name) { KHttpCookieList *cookieList = mCookieJar->getCookieList(domain, fqdn); if (cookieList && !cookieList->isEmpty()) { KHttpCookieList::Iterator itEnd = cookieList->end(); for (KHttpCookieList::Iterator it = cookieList->begin(); it != itEnd; ++it) { if (cookieMatches(*it, domain, fqdn, path, name)) { mCookieJar->eatCookie(it); saveCookieJar(); break; } } } } // DBUS function void KCookieServer::deleteCookiesFromDomain(const QString &domain) { mCookieJar->eatCookiesForDomain(domain); saveCookieJar(); } // Qt function void KCookieServer::slotDeleteSessionCookies(qlonglong windowId) { deleteSessionCookies(windowId); } // DBUS function void KCookieServer::deleteSessionCookies(qlonglong windowId) { mCookieJar->eatSessionCookies(windowId); saveCookieJar(); } void KCookieServer::deleteSessionCookiesFor(const QString &fqdn, qlonglong windowId) { mCookieJar->eatSessionCookies(fqdn, windowId); saveCookieJar(); } // DBUS function void KCookieServer::deleteAllCookies() { mCookieJar->eatAllCookies(); saveCookieJar(); } // DBUS function void KCookieServer::addDOMCookies(const QString &url, const QByteArray &cookieHeader, qlonglong windowId) { addCookies(url, cookieHeader, windowId, true); } // DBUS function bool KCookieServer::setDomainAdvice(const QString &url, const QString &advice) { QString fqdn; QString dummy; if (KCookieJar::parseUrl(url, fqdn, dummy)) { QStringList domains; mCookieJar->extractDomains(fqdn, domains); mCookieJar->setDomainAdvice(domains[domains.count() > 3 ? 3 : 0], KCookieJar::strToAdvice(advice)); // Save the cookie config if it has changed mCookieJar->saveConfig(mConfig); return true; } return false; } // DBUS function QString KCookieServer::getDomainAdvice(const QString &url) { KCookieAdvice advice = KCookieDunno; QString fqdn; QString dummy; if (KCookieJar::parseUrl(url, fqdn, dummy)) { QStringList domains; mCookieJar->extractDomains(fqdn, domains); QStringListIterator it(domains); while ((advice == KCookieDunno) && it.hasNext()) { // Always check advice in both ".domain" and "domain". Note // that we only want to check "domain" if it matches the // fqdn of the requested URL. const QString &domain = it.next(); if (domain.at(0) == '.' || domain == fqdn) { advice = mCookieJar->getDomainAdvice(domain); } } if (advice == KCookieDunno) { advice = mCookieJar->getGlobalAdvice(); } } return KCookieJar::adviceToStr(advice); } // DBUS function void KCookieServer::reloadPolicy() { mCookieJar->loadConfig(mConfig, true); } // DBUS function void KCookieServer::shutdown() { deleteLater(); } #include "kcookieserver.moc" diff --git a/src/ioslaves/http/kcookiejar/kcookieserver.h b/src/ioslaves/http/kcookiejar/kcookieserver.h index de8e2c32..51d6b9b7 100644 --- a/src/ioslaves/http/kcookiejar/kcookieserver.h +++ b/src/ioslaves/http/kcookiejar/kcookieserver.h @@ -1,115 +1,115 @@ /* This file is part of the KDE File Manager Copyright (C) 1998 Waldo Bastian (bastian@kde.org) 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, or (at your option) version 3. This software 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 library; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //---------------------------------------------------------------------------- // // KDE Cookie Server #ifndef KCOOKIESERVER_H #define KCOOKIESERVER_H -#include +#include #include #include -#include +#include class KHttpCookieList; class KCookieJar; class KHttpCookie; class QTimer; class RequestList; class KConfig; // IMPORTANT: Do *NOT* replace qlonglong with WId in this class. // // KCookieServer is exposed over DBus and DBus does not know how to handle the // WId type. If a method has a WId argument it is not exposed and one gets a // warning at compile time: // // "addFunction: Unregistered input type in parameter list: WId" class KCookieServer : public KDEDModule, protected QDBusContext { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.KCookieServer") public: KCookieServer(QObject *parent, const QList &); ~KCookieServer(); public Q_SLOTS: // KDE5 TODO: don't overload names here, it prevents calling e.g. findCookies from the command-line using qdbus. QString listCookies(const QString &url); QString findCookies(const QString &url, qlonglong windowId); QStringList findDomains(); // KDE5: rename QStringList findCookies(const QList &fields, const QString &domain, const QString &fqdn, const QString &path, const QString &name); QString findDOMCookies(const QString &url); QString findDOMCookies(const QString &url, qlonglong windowId); // KDE5: merge with above, using default value (windowId = 0) void addCookies(const QString &url, const QByteArray &cookieHeader, qlonglong windowId); void deleteCookie(const QString &domain, const QString &fqdn, const QString &path, const QString &name); void deleteCookiesFromDomain(const QString &domain); void deleteSessionCookies(qlonglong windowId); void deleteSessionCookiesFor(const QString &fqdn, qlonglong windowId); void deleteAllCookies(); void addDOMCookies(const QString &url, const QByteArray &cookieHeader, qlonglong windowId); /** * Sets the cookie policy for the domain associated with the specified URL. */ bool setDomainAdvice(const QString &url, const QString &advice); /** * Returns the cookie policy in effect for the specified URL. */ QString getDomainAdvice(const QString &url); void reloadPolicy(); void shutdown(); public: bool cookiesPending(const QString &url, KHttpCookieList *cookieList = nullptr); void addCookies(const QString &url, const QByteArray &cookieHeader, qlonglong windowId, bool useDOMFormat); void checkCookies(KHttpCookieList *cookieList); // TODO: KDE5 merge with above function and make all these public functions // private since they are not used externally. void checkCookies(KHttpCookieList *cookieList, qlonglong windowId); private Q_SLOTS: void slotSave(); void slotDeleteSessionCookies(qlonglong windowId); private: KCookieJar *mCookieJar; KHttpCookieList *mPendingCookies; RequestList *mRequestList; QTimer *mTimer; bool mAdvicePending; KConfig *mConfig; QString mFilename; private: virtual int newInstance(QList) { return 0; } bool cookieMatches(const KHttpCookie &, const QString &, const QString &, const QString &, const QString &); void putCookie(QStringList &, const KHttpCookie &, const QList &); void saveCookieJar(); }; #endif diff --git a/src/ioslaves/http/parsinghelpers.h b/src/ioslaves/http/parsinghelpers.h index b927cdae..a49b0bad 100644 --- a/src/ioslaves/http/parsinghelpers.h +++ b/src/ioslaves/http/parsinghelpers.h @@ -1,90 +1,90 @@ /* This file is part of the KDE libraries Copyright (C) 2008 Andreas Hartmetz 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. */ #ifndef PARSINGHELPERS_H #define PARSINGHELPERS_H -#include -#include -#include +#include +#include +#include struct HeaderField { HeaderField(bool multiValued) { isMultiValued = multiValued; } // QHash requires a default constructor HeaderField() { isMultiValued = false; } bool isMultiValued; QList > beginEnd; }; class HeaderTokenizer; class TokenIterator { public: inline bool hasNext() const { return m_currentToken < m_tokens.count(); } QByteArray next(); QByteArray current() const; QList all() const; private: friend class HeaderTokenizer; QList > m_tokens; int m_currentToken; const char *m_buffer; TokenIterator(const QList > &tokens, const char *buffer) : m_tokens(tokens), m_currentToken(0), m_buffer(buffer) {} }; class HeaderTokenizer : public QHash { public: HeaderTokenizer(char *buffer); // note that buffer is not const - in the parsed area CR/LF will be overwritten // with spaces if there is a line continuation. /// @return: index of first char after header or end int tokenize(int begin, int end); // after tokenize() has been called use the QHash part of this class to // ask for a list of begin-end indexes in buffer for header values. TokenIterator iterator(const char *key) const; private: char *m_buffer; struct HeaderFieldTemplate { const char *name; bool isMultiValued; }; QList > m_nullTokens; //long-lived, allows us to pass out references. }; #endif //PARSINGHELPERS_H diff --git a/src/ioslaves/remote/CMakeLists.txt b/src/ioslaves/remote/CMakeLists.txt index 585ae394..21615385 100644 --- a/src/ioslaves/remote/CMakeLists.txt +++ b/src/ioslaves/remote/CMakeLists.txt @@ -1,19 +1,19 @@ add_subdirectory( kdedmodule ) set(kio_remote_SRCS kio_remote.cpp remoteimpl.cpp ) ecm_qt_declare_logging_category(kio_remote_SRCS HEADER debug.h IDENTIFIER KIOREMOTE_LOG CATEGORY_NAME kf5.kio.kio_remote DEFAULT_SEVERITY Info) kcoreaddons_add_plugin(kio_remote SOURCES ${kio_remote_SRCS} INSTALL_NAMESPACE kf5/kio) -target_link_libraries(kio_remote KF5::KIOCore KF5::I18n) +target_link_libraries(kio_remote KF5::KIOCore KF5::I18n Qt5::Network) set_target_properties(kio_remote PROPERTIES OUTPUT_NAME "remote") diff --git a/src/ioslaves/trash/CMakeLists.txt b/src/ioslaves/trash/CMakeLists.txt index 4441a2b4..c09217e7 100644 --- a/src/ioslaves/trash/CMakeLists.txt +++ b/src/ioslaves/trash/CMakeLists.txt @@ -1,86 +1,87 @@ # find_package(Strigi) # set_package_properties(Strigi PROPERTIES DESCRIPTION "Desktop indexing and search support" # URL "http://strigi.sourceforge.net" # TYPE ${STRIGI_TYPE} # ) # if (WIN32) # set (STRIGI_TYPE "OPTIONAL") # else () # set (STRIGI_TYPE "REQUIRED") # endif () if(BUILD_TESTING) if(WIN32) message(AUTHOR_WARNING "kio_trash unit tests are broken on Windows, disabling them") else() add_subdirectory(tests) endif() endif() ########### next target ############### if(WIN32) set(kio_trash_PART_SRCS kio_trash_win.cpp kiotrashdebug.cpp) else() # Files that are shared with the KCM. Unix specific. set (trashcommon_unix_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/trashimpl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/discspaceutil.cpp ${CMAKE_CURRENT_SOURCE_DIR}/trashsizecache.cpp ${CMAKE_CURRENT_SOURCE_DIR}/kinterprocesslock.cpp ${CMAKE_CURRENT_SOURCE_DIR}/kiotrashdebug.cpp ) set(kio_trash_PART_SRCS kio_trash.cpp ${trashcommon_unix_SRCS}) endif() add_library(kio_trash MODULE ${kio_trash_PART_SRCS}) target_link_libraries(kio_trash KF5::Solid KF5::KIOCore Qt5::DBus + Qt5::Network KF5::I18n KF5::ConfigCore KF5::ConfigGui ) if(APPLE) target_link_libraries(kio_trash "-framework DiskArbitration -framework CoreFoundation") endif(APPLE) set_target_properties(kio_trash PROPERTIES OUTPUT_NAME "trash") set_target_properties(kio_trash PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kio") install(TARGETS kio_trash DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kio ) set(ktrash_SRCS ktrash.cpp ) add_executable(ktrash5 ${ktrash_SRCS}) target_compile_definitions(ktrash5 PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}") ecm_mark_nongui_executable(ktrash5) target_link_libraries(ktrash5 KF5::KIOCore KF5::I18n KF5::ConfigCore KF5::ConfigGui ) install(TARGETS ktrash5 ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) ########### next target ############### # currently not on win32, TODO! if(NOT WIN32 AND NOT KIOCORE_ONLY) set(kcm_trash_PART_SRCS kcmtrash.cpp ${trashcommon_unix_SRCS}) add_library(kcm_trash MODULE ${kcm_trash_PART_SRCS}) target_link_libraries(kcm_trash Qt5::DBus KF5::I18n KF5::ConfigWidgets KF5::KIOCore KF5::Solid) if(APPLE) target_link_libraries(kcm_trash "-framework DiskArbitration -framework CoreFoundation") endif(APPLE) install(TARGETS kcm_trash DESTINATION ${KDE_INSTALL_PLUGINDIR}) endif() ########### install files ############### install( FILES kcmtrash.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) diff --git a/src/ioslaves/trash/discspaceutil.cpp b/src/ioslaves/trash/discspaceutil.cpp index d369ce0c..1e915263 100644 --- a/src/ioslaves/trash/discspaceutil.cpp +++ b/src/ioslaves/trash/discspaceutil.cpp @@ -1,96 +1,96 @@ /* This file is part of the KDE project Copyright (C) 2008 Tobias Koenig 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 "discspaceutil.h" #include "kiotrashdebug.h" -#include -#include +#include +#include #include #include #include // QT_LSTAT, QT_STAT, QT_STATBUF DiscSpaceUtil::DiscSpaceUtil(const QString &directory) : mDirectory(directory), mFullSize(0) { calculateFullSize(); } qulonglong DiscSpaceUtil::sizeOfPath(const QString &path) { QFileInfo info(path); if (!info.exists()) { return 0; } if (info.isSymLink()) { // QFileInfo::size does not return the actual size of a symlink. #253776 QT_STATBUF buff; return static_cast(QT_LSTAT(QFile::encodeName(path), &buff) == 0 ? buff.st_size : 0); } else if (info.isFile()) { return info.size(); } else if (info.isDir()) { QDirIterator it(path, QDirIterator::NoIteratorFlags); qulonglong sum = 0; while (it.hasNext()) { const QFileInfo info = it.next(); if (info.fileName() != QLatin1String(".") && info.fileName() != QLatin1String("..")) { sum += sizeOfPath(info.absoluteFilePath()); } } return sum; } else { return 0; } } double DiscSpaceUtil::usage(qulonglong size) const { if (mFullSize == 0) { return 0; } return (((double)size * 100) / (double)mFullSize); } qulonglong DiscSpaceUtil::size() const { return mFullSize; } QString DiscSpaceUtil::mountPoint() const { return mMountPoint; } void DiscSpaceUtil::calculateFullSize() { KDiskFreeSpaceInfo info = KDiskFreeSpaceInfo::freeSpaceInfo(mDirectory); if (info.isValid()) { mFullSize = info.size(); mMountPoint = info.mountPoint(); } } diff --git a/src/ioslaves/trash/discspaceutil.h b/src/ioslaves/trash/discspaceutil.h index e12ca8ca..b22c4167 100644 --- a/src/ioslaves/trash/discspaceutil.h +++ b/src/ioslaves/trash/discspaceutil.h @@ -1,72 +1,72 @@ /* This file is part of the KDE project Copyright (C) 2008 Tobias Koenig 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. */ #ifndef DISCSPACEUTIL_H #define DISCSPACEUTIL_H -#include +#include /** * A small utility class to access and calculate * size and usage of mount points. */ class DiscSpaceUtil { public: /** * Creates a new disc space util. * * @param directory A directory the util shall work on. */ explicit DiscSpaceUtil(const QString &directory); /** * Returns the usage of the directory pass in the constructor on this * mount point in percent. * * @param size The current size of the directory. */ double usage(qulonglong size) const; /** * Returns the size of the partition in bytes. */ qulonglong size() const; /** * Returns the mount point of the directory. */ QString mountPoint() const; /** * Returns the size of the given path in bytes. */ static qulonglong sizeOfPath(const QString &path); private: void calculateFullSize(); QString mDirectory; qulonglong mFullSize; QString mMountPoint; }; #endif diff --git a/src/ioslaves/trash/kinterprocesslock.cpp b/src/ioslaves/trash/kinterprocesslock.cpp index 10a538af..6bd203ac 100644 --- a/src/ioslaves/trash/kinterprocesslock.cpp +++ b/src/ioslaves/trash/kinterprocesslock.cpp @@ -1,94 +1,94 @@ /* This file is part of the KDE Copyright (C) 2009 Tobias Koenig (tokoe@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This software 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 library; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kinterprocesslock.h" #include "kiotrashdebug.h" -#include -#include +#include +#include -#include +#include class KInterProcessLockPrivate { Q_DECLARE_PUBLIC(KInterProcessLock) KInterProcessLock *q_ptr; public: KInterProcessLockPrivate(const QString &resource, KInterProcessLock *parent) : m_resource(resource), m_parent(parent) { m_serviceName = QStringLiteral("org.kde.private.lock-%1").arg(m_resource); m_parent->connect(QDBusConnection::sessionBus().interface(), SIGNAL(serviceRegistered(QString)), m_parent, SLOT(_k_serviceRegistered(QString))); } ~KInterProcessLockPrivate() { } void _k_serviceRegistered(const QString &service) { if (service == m_serviceName) { emit m_parent->lockGranted(m_parent); } } QString m_resource; QString m_serviceName; KInterProcessLock *m_parent; }; KInterProcessLock::KInterProcessLock(const QString &resource) : d_ptr(new KInterProcessLockPrivate(resource, this)) { d_ptr->q_ptr = this; } KInterProcessLock::~KInterProcessLock() { delete d_ptr; } QString KInterProcessLock::resource() const { return d_ptr->m_resource; } void KInterProcessLock::lock() { QDBusConnection::sessionBus().interface()->registerService(d_ptr->m_serviceName, QDBusConnectionInterface::QueueService, QDBusConnectionInterface::DontAllowReplacement); } void KInterProcessLock::unlock() { QDBusConnection::sessionBus().interface()->unregisterService(d_ptr->m_serviceName); } void KInterProcessLock::waitForLockGranted() { QEventLoop loop; connect(this, SIGNAL(lockGranted(KInterProcessLock*)), &loop, SLOT(quit())); loop.exec(); } #include "moc_kinterprocesslock.cpp" diff --git a/src/ioslaves/trash/kinterprocesslock.h b/src/ioslaves/trash/kinterprocesslock.h index b7e1c26d..1276e2f8 100644 --- a/src/ioslaves/trash/kinterprocesslock.h +++ b/src/ioslaves/trash/kinterprocesslock.h @@ -1,118 +1,118 @@ /* This file is part of the KDE Copyright (C) 2009 Tobias Koenig (tokoe@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This software 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 library; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KINTERPROCESSLOCK_H #define KINTERPROCESSLOCK_H -#include +#include class KInterProcessLockPrivate; /** * @short A class for serializing access to a resource that is shared between multiple processes. * * This class can be used to serialize access to a resource between * multiple processes. Instead of using lock files, which could * become stale easily, the registration of dummy dbus services is used * to allow only one process at a time to access the resource. * * Example: * * @code * * KInterProcessLock *lock = new KInterProcessLock("myresource"); * connect(lock, SIGNAL(lockGranted(KInterProcessLock *)), * this, SLOT(doCriticalTask(KInterProcessLock *))); * lock->lock(); * * ... * * ... ::doCriticalTask(KInterProcessLock *lock) * { * // change common resource * * lock->unlock(); * } * * @endcode * * @author Tobias Koenig */ class KInterProcessLock : public QObject { Q_OBJECT Q_DECLARE_PRIVATE(KInterProcessLock) public: /** * Creates a new inter process lock object. * * @param resource The identifier of the resource that shall be locked. * This identifier can be any string, however it must be unique for * the resource and every client that wants to access the resource must * know it. */ KInterProcessLock(const QString &resource); /** * Destroys the inter process lock object. */ ~KInterProcessLock(); /** * Returns the identifier of the resource the lock is set on. */ QString resource() const; /** * Requests the lock. * * The lock is granted as soon as the lockGranted() signal is emitted. */ void lock(); /** * Releases the lock. * * @note This method should be called as soon as the critical area is left * in your code path and the lock is no longer needed. */ void unlock(); /** * Waits for the granting of a lock by starting an internal event loop. */ void waitForLockGranted(); Q_SIGNALS: /** * This signal is emitted when the requested lock has been granted. * * @param lock The lock that has been granted. */ void lockGranted(KInterProcessLock *lock); private: KInterProcessLockPrivate *const d_ptr; Q_PRIVATE_SLOT(d_func(), void _k_serviceRegistered(const QString &)) }; #endif diff --git a/src/ioslaves/trash/tests/CMakeLists.txt b/src/ioslaves/trash/tests/CMakeLists.txt index f9138925..5d0fd84b 100644 --- a/src/ioslaves/trash/tests/CMakeLists.txt +++ b/src/ioslaves/trash/tests/CMakeLists.txt @@ -1,33 +1,33 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/.. ) ########### next target ############### set(testtrash_SRCS testtrash.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../trashimpl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../trashsizecache.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../discspaceutil.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../kinterprocesslock.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../kiotrashdebug.cpp ) include(ECMAddTests) ecm_add_test(${testtrash_SRCS} TEST_NAME testtrash - LINK_LIBRARIES KF5::I18n Qt5::DBus KF5::KIOCore KF5::Solid Qt5::Test + LINK_LIBRARIES KF5::I18n Qt5::DBus KF5::KIOCore KF5::Solid Qt5::Test Qt5::Network ) if(APPLE) target_link_libraries(testtrash "-framework DiskArbitration -framework CoreFoundation") endif(APPLE) # other tests like dropjob and fileundomanager use the trash in ~/.qttest, which this test cleans up set_tests_properties(testtrash PROPERTIES RUN_SERIAL TRUE) ### next target ### add_executable(lockingtest lockingtest.cpp ../kinterprocesslock.cpp) ecm_mark_nongui_executable(lockingtest) target_link_libraries(lockingtest Qt5::Core Qt5::DBus) diff --git a/src/ioslaves/trash/tests/lockingtest.cpp b/src/ioslaves/trash/tests/lockingtest.cpp index df407c3f..ca9ca5f6 100644 --- a/src/ioslaves/trash/tests/lockingtest.cpp +++ b/src/ioslaves/trash/tests/lockingtest.cpp @@ -1,55 +1,55 @@ /* This file is part of the KDE Copyright (C) 2009 Tobias Koenig (tokoe@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This software 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 library; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include -#include +#include +#include #include "kinterprocesslock.h" #ifdef Q_OS_WIN #include #else #include #endif int main(int argc, char **argv) { QCoreApplication app(argc, argv); KInterProcessLock lock(QStringLiteral("mytrash")); qDebug("retrieve lock..."); lock.lock(); qDebug("waiting..."); lock.waitForLockGranted(); qDebug("retrieved lock"); qDebug("sleeping..."); #ifdef Q_OS_WIN Sleep(10 * 1000); #else sleep(10); #endif if (argc != 2) { lock.unlock(); qDebug("release lock"); } return 0; } diff --git a/src/ioslaves/trash/trashsizecache.h b/src/ioslaves/trash/trashsizecache.h index b180eeef..0eb2d029 100644 --- a/src/ioslaves/trash/trashsizecache.h +++ b/src/ioslaves/trash/trashsizecache.h @@ -1,74 +1,74 @@ /* This file is part of the KDE project Copyright (C) 2009 Tobias Koenig Copyright (C) 2014 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 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. */ #ifndef TRASHSIZECACHE_H #define TRASHSIZECACHE_H -#include +#include #include /** * @short A class that encapsulates the directory size cache. * * The directory size cache is used to speed up the determination of the trash size. * * Since version 1.0, http://standards.freedesktop.org/trash-spec/trashspec-latest.html specifies this cache * as a standard way to cache this information. * */ class TrashSizeCache { public: /** * Creates a new trash size cache object for the given trash @p path. */ TrashSizeCache(const QString &path); /** * Adds a directory to the cache. * @param directoryName fileId of the directory * @param directorySize size in bytes */ void add(const QString &directoryName, qulonglong directorySize); /** * Removes a directory from the cache. */ void remove(const QString &directoryName); /** * Sets the trash size to 0 bytes. */ void clear(); /** * Calculates and returns the current trash size. */ qulonglong calculateSize(); private: QString mTrashSizeCachePath; QString mTrashPath; }; #endif diff --git a/src/kcms/kio/kcookiesmain.cpp b/src/kcms/kio/kcookiesmain.cpp index 1ba1975f..1b5f941e 100644 --- a/src/kcms/kio/kcookiesmain.cpp +++ b/src/kcms/kio/kcookiesmain.cpp @@ -1,97 +1,97 @@ /* kcookiesmain.cpp - Cookies configuration * * First version of cookies configuration: * Copyright (C) Waldo Bastian * This dialog box: * Copyright (C) David Faure * */ // Own #include "kcookiesmain.h" // Local #include "kcookiespolicies.h" #include "kcookiesmanagement.h" // Qt #include -#include +#include // KDE #include #include #include K_PLUGIN_FACTORY_DECLARATION (KioConfigFactory) KCookiesMain::KCookiesMain (QWidget* parent, const QVariantList&) : KCModule (parent) { management = nullptr; bool managerOK = true; QVBoxLayout* layout = new QVBoxLayout (this); tab = new QTabWidget (this); layout->addWidget (tab); policies = new KCookiesPolicies (this); tab->addTab (policies, i18n ("&Policy")); connect (policies, SIGNAL (changed (bool)), SIGNAL (changed (bool))); if (managerOK) { management = new KCookiesManagement (this); tab->addTab (management, i18n ("&Management")); connect (management, SIGNAL (changed (bool)), SIGNAL (changed (bool))); } } KCookiesMain::~KCookiesMain() { } void KCookiesMain::save() { policies->save(); if (management) management->save(); } void KCookiesMain::load() { policies->load(); if (management) management->load(); } void KCookiesMain::defaults() { KCModule* module = static_cast (tab->currentWidget()); if (module == policies) policies->defaults(); else if (management) management->defaults(); } QString KCookiesMain::quickHelp() const { return i18n ("

Cookies

Cookies contain information that KDE applications" " using the HTTP protocol (like Konqueror) store on your" " computer, initiated by a remote Internet server. This means that" " a web server can store information about you and your browsing activities" " on your machine for later use. You might consider this an invasion of" " privacy.

However, cookies are useful in certain situations. For example, they" " are often used by Internet shops, so you can 'put things into a shopping basket'." " Some sites require you have a browser that supports cookies.

" " Because most people want a compromise between privacy and the benefits cookies offer," " the HTTP kioslave offers you the ability to customize the way it handles cookies. So you might want" " to set the default policy to ask you whenever a server wants to set a cookie," " allowing you to decide. For your favorite shopping web sites that you trust, you might" " want to set the policy to accept, then you can access the web sites without being prompted" " every time a cookie is received.

"); } diff --git a/src/kcms/kio/kcookiesmanagement.cpp b/src/kcms/kio/kcookiesmanagement.cpp index 1ba5d192..a7765660 100644 --- a/src/kcms/kio/kcookiesmanagement.cpp +++ b/src/kcms/kio/kcookiesmanagement.cpp @@ -1,428 +1,428 @@ /** * kcookiesmanagement.cpp - Cookies manager * * Copyright 2000-2001 Marco Pinelli * Copyright 2000-2001 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. */ // Own #include "kcookiesmanagement.h" // Qt -#include -#include +#include +#include #include -#include +#include // KDE #include #include #include #include #include #include #include #include #include // Local #include "kcookiesmain.h" #include "kcookiespolicies.h" QString tolerantFromAce(const QByteArray& _domain); struct CookieProp { QString host; QString name; QString value; QString domain; QString path; QString expireDate; QString secure; bool allLoaded; }; CookieListViewItem::CookieListViewItem(QTreeWidget *parent, const QString &dom) :QTreeWidgetItem(parent) { init( nullptr, dom ); } CookieListViewItem::CookieListViewItem(QTreeWidgetItem *parent, CookieProp *cookie) :QTreeWidgetItem(parent) { init( cookie ); } CookieListViewItem::~CookieListViewItem() { delete mCookie; } void CookieListViewItem::init( CookieProp* cookie, const QString &domain, bool cookieLoaded ) { mCookie = cookie; mDomain = domain; mCookiesLoaded = cookieLoaded; if (mCookie) { if (mDomain.isEmpty()) setText(0, tolerantFromAce(mCookie->host.toLatin1())); else setText(0, tolerantFromAce(mDomain.toLatin1())); setText(1, mCookie->name); } else { QString siteName; if (mDomain.startsWith(QLatin1Char('.'))) siteName = mDomain.mid(1); else siteName = mDomain; setText(0, tolerantFromAce(siteName.toLatin1())); } } CookieProp* CookieListViewItem::leaveCookie() { CookieProp *ret = mCookie; mCookie = nullptr; return ret; } KCookiesManagement::KCookiesManagement(QWidget *parent) : KCModule(parent), mDeleteAllFlag(false), mMainWidget(parent) { mUi.setupUi(this); mUi.searchLineEdit->setTreeWidget(mUi.cookiesTreeWidget); mUi.cookiesTreeWidget->setColumnWidth(0, 150); connect(mUi.cookiesTreeWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), SLOT(on_configPolicyButton_clicked())); } KCookiesManagement::~KCookiesManagement() { } void KCookiesManagement::load() { defaults(); } void KCookiesManagement::save() { // If delete all cookies was requested! if(mDeleteAllFlag) { QDBusInterface kded(QStringLiteral("org.kde.kcookiejar5"), QStringLiteral("/modules/kcookiejar"), QStringLiteral("org.kde.KCookieServer"), QDBusConnection::sessionBus()); QDBusReply reply = kded.call( QStringLiteral("deleteAllCookies") ); if (!reply.isValid()) { const QString caption = i18n ("D-Bus Communication Error"); const QString message = i18n ("Unable to delete all the cookies as requested."); KMessageBox::sorry (this, message, caption); return; } mDeleteAllFlag = false; // deleted[Cookies|Domains] have been cleared yet } // Certain groups of cookies were deleted... QMutableStringListIterator it (mDeletedDomains); while (it.hasNext()) { QDBusInterface kded(QStringLiteral("org.kde.kcookiejar5"), QStringLiteral("/modules/kcookiejar"), QStringLiteral("org.kde.KCookieServer"), QDBusConnection::sessionBus()); QDBusReply reply = kded.call( QStringLiteral("deleteCookiesFromDomain"),( it.next() ) ); if (!reply.isValid()) { const QString caption = i18n ("D-Bus Communication Error"); const QString message = i18n ("Unable to delete cookies as requested."); KMessageBox::sorry (this, message, caption); return; } it.remove(); } // Individual cookies were deleted... bool success = true; // Maybe we can go on... QMutableHashIterator cookiesDom(mDeletedCookies); while(cookiesDom.hasNext()) { cookiesDom.next(); CookiePropList list = cookiesDom.value(); foreach(CookieProp *cookie, list) { QDBusInterface kded(QStringLiteral("org.kde.kcookiejar5"), QStringLiteral("/modules/kcookiejar"), QStringLiteral("org.kde.KCookieServer"), QDBusConnection::sessionBus()); QDBusReply reply = kded.call( QStringLiteral("deleteCookie"), cookie->domain, cookie->host, cookie->path, cookie->name ); if (!reply.isValid()) { success = false; break; } list.removeOne(cookie); } if (!success) break; mDeletedCookies.remove(cookiesDom.key()); } emit changed( false ); } void KCookiesManagement::defaults() { reset(); on_reloadButton_clicked(); } void KCookiesManagement::reset(bool deleteAll) { if (!deleteAll) mDeleteAllFlag = false; clearCookieDetails(); mDeletedDomains.clear(); mDeletedCookies.clear(); mUi.cookiesTreeWidget->clear(); mUi.deleteButton->setEnabled(false); mUi.deleteAllButton->setEnabled(false); mUi.configPolicyButton->setEnabled(false); } void KCookiesManagement::clearCookieDetails() { mUi.nameLineEdit->clear(); mUi.valueLineEdit->clear(); mUi.domainLineEdit->clear(); mUi.pathLineEdit->clear(); mUi.expiresLineEdit->clear(); mUi.secureLineEdit->clear(); } QString KCookiesManagement::quickHelp() const { return i18n("

Cookie Management Quick Help

" ); } void KCookiesManagement::on_reloadButton_clicked() { QDBusInterface kded(QStringLiteral("org.kde.kcookiejar5"), QStringLiteral("/modules/kcookiejar"), QStringLiteral("org.kde.KCookieServer"), QDBusConnection::sessionBus()); QDBusReply reply = kded.call( QStringLiteral("findDomains") ); if (!reply.isValid()) { const QString caption = i18n ("Information Lookup Failure"); const QString message = i18n ("Unable to retrieve information about the " "cookies stored on your computer."); KMessageBox::sorry (this, message, caption); return; } if (mUi.cookiesTreeWidget->topLevelItemCount() > 0) reset(); CookieListViewItem *dom; const QStringList domains (reply.value()); Q_FOREACH(const QString& domain, domains) { const QString siteName = (domain.startsWith(QLatin1Char('.')) ? domain.mid(1) : domain); if (mUi.cookiesTreeWidget->findItems(siteName, Qt::MatchFixedString).isEmpty()) { dom = new CookieListViewItem(mUi.cookiesTreeWidget, domain); dom->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); } } // are there any cookies? mUi.deleteAllButton->setEnabled(mUi.cookiesTreeWidget->topLevelItemCount() > 0); mUi.cookiesTreeWidget->sortItems(0, Qt::AscendingOrder); emit changed(false); } Q_DECLARE_METATYPE( QList ) void KCookiesManagement::on_cookiesTreeWidget_itemExpanded(QTreeWidgetItem *item) { CookieListViewItem* cookieDom = static_cast(item); if (!cookieDom || cookieDom->cookiesLoaded()) return; QStringList cookies; QList fields; fields << 0 << 1 << 2 << 3; // Always check for cookies in both "foo.bar" and ".foo.bar" domains... const QString domain = cookieDom->domain() + QLatin1String(" .") + cookieDom->domain(); QDBusInterface kded(QStringLiteral("org.kde.kcookiejar5"), QStringLiteral("/modules/kcookiejar"), QStringLiteral("org.kde.KCookieServer"), QDBusConnection::sessionBus()); QDBusReply reply = kded.call(QStringLiteral("findCookies"), QVariant::fromValue(fields), domain, QString(), QString(), QString()); if (reply.isValid()) cookies.append(reply.value()); QStringListIterator it(cookies); while (it.hasNext()) { CookieProp *details = new CookieProp; details->domain = it.next(); details->path = it.next(); details->name = it.next(); details->host = it.next(); details->allLoaded = false; new CookieListViewItem(item, details); } if (!cookies.isEmpty()) { static_cast(item)->setCookiesLoaded(); mUi.searchLineEdit->updateSearch(); } } bool KCookiesManagement::cookieDetails(CookieProp *cookie) { QList fields; fields << 4 << 5 << 7; QDBusInterface kded(QStringLiteral("org.kde.kcookiejar5"), QStringLiteral("/modules/kcookiejar"), QStringLiteral("org.kde.KCookieServer"), QDBusConnection::sessionBus()); QDBusReply reply = kded.call( QStringLiteral("findCookies"), QVariant::fromValue( fields ), cookie->domain, cookie->host, cookie->path, cookie->name); if (!reply.isValid()) return false; const QStringList fieldVal = reply.value(); QStringList::const_iterator c = fieldVal.begin(); if (c == fieldVal.end()) // empty list, do not crash return false; bool ok; cookie->value = *c++; qint64 tmp = (*c++).toLongLong(&ok); if (!ok || tmp == 0) cookie->expireDate = i18n("End of session"); else { QDateTime expDate; expDate.setTime_t(tmp); cookie->expireDate = QLocale().toString((expDate), QLocale::ShortFormat); } tmp = (*c).toUInt(&ok); cookie->secure = i18n((ok && tmp) ? "Yes" : "No"); cookie->allLoaded = true; return true; } void KCookiesManagement::on_cookiesTreeWidget_currentItemChanged(QTreeWidgetItem* item) { if (item) { CookieListViewItem* cookieItem = static_cast(item); CookieProp *cookie = cookieItem->cookie(); if (cookie) { if (cookie->allLoaded || cookieDetails(cookie)) { mUi.nameLineEdit->setText(cookie->name); mUi.valueLineEdit->setText(cookie->value); mUi.domainLineEdit->setText(cookie->domain); mUi.pathLineEdit->setText(cookie->path); mUi.expiresLineEdit->setText(cookie->expireDate); mUi.secureLineEdit->setText(cookie->secure); } mUi.configPolicyButton->setEnabled(false); } else { clearCookieDetails(); mUi.configPolicyButton->setEnabled(true); } } else { mUi.configPolicyButton->setEnabled(false); } mUi.deleteButton->setEnabled(item != nullptr); } void KCookiesManagement::on_configPolicyButton_clicked() { // Get current item CookieListViewItem *item = static_cast(mUi.cookiesTreeWidget->currentItem()); Q_ASSERT(item); // the button is disabled otherwise if (item) { KCookiesMain* mainDlg = qobject_cast(mMainWidget); // must be present or something is really wrong. Q_ASSERT(mainDlg); KCookiesPolicies* policyDlg = mainDlg->policyDlg(); // must be present unless someone rewrote the widget in which case // this needs to be re-written as well. Q_ASSERT(policyDlg); policyDlg->setPolicy(item->domain()); } } void KCookiesManagement::on_deleteButton_clicked() { QTreeWidgetItem* currentItem = mUi.cookiesTreeWidget->currentItem(); Q_ASSERT(currentItem); // the button is disabled otherwise CookieListViewItem *item = static_cast( currentItem ); if (item->cookie()) { CookieListViewItem *parent = static_cast(item->parent()); CookiePropList list = mDeletedCookies.value(parent->domain()); list.append(item->leaveCookie()); mDeletedCookies.insert(parent->domain(), list); delete item; if (parent->childCount() == 0) delete parent; } else { mDeletedDomains.append(item->domain()); delete item; } currentItem = mUi.cookiesTreeWidget->currentItem(); if (currentItem) { mUi.cookiesTreeWidget->setCurrentItem( currentItem ); } else clearCookieDetails(); mUi.deleteAllButton->setEnabled(mUi.cookiesTreeWidget->topLevelItemCount() > 0); emit changed( true ); } void KCookiesManagement::on_deleteAllButton_clicked() { mDeleteAllFlag = true; reset(true); emit changed(true); } diff --git a/src/kcms/kio/kcookiesmanagement.h b/src/kcms/kio/kcookiesmanagement.h index 56f530c8..c8bddc4a 100644 --- a/src/kcms/kio/kcookiesmanagement.h +++ b/src/kcms/kio/kcookiesmanagement.h @@ -1,93 +1,93 @@ /** * kcookiesmanagement.h - Cookies manager * * Copyright 2000-2001 Marco Pinelli * Copyright 2000-2001 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. */ #ifndef KCOOKIESMANAGEMENT_H #define KCOOKIESMANAGEMENT_H -#include -#include +#include +#include #include -#include +#include #include #include "ui_kcookiesmanagement.h" struct CookieProp; class CookieListViewItem : public QTreeWidgetItem { public: CookieListViewItem(QTreeWidget *parent, const QString &dom); CookieListViewItem(QTreeWidgetItem *parent, CookieProp *cookie); ~CookieListViewItem(); QString domain() const { return mDomain; } CookieProp* cookie() const { return mCookie; } CookieProp* leaveCookie(); void setCookiesLoaded() { mCookiesLoaded = true; } bool cookiesLoaded() const { return mCookiesLoaded; } private: void init( CookieProp* cookie, const QString &domain = QString(), bool cookieLoaded=false ); CookieProp *mCookie; QString mDomain; bool mCookiesLoaded; }; class KCookiesManagement : public KCModule { Q_OBJECT public: KCookiesManagement(/*const KComponentData &componentData,*/ QWidget *parent ); ~KCookiesManagement(); 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 on_deleteButton_clicked(); void on_deleteAllButton_clicked(); void on_reloadButton_clicked(); void on_cookiesTreeWidget_itemExpanded(QTreeWidgetItem*); void on_cookiesTreeWidget_currentItemChanged(QTreeWidgetItem*); void on_configPolicyButton_clicked(); private: void reset (bool deleteAll = false); bool cookieDetails(CookieProp *cookie); void clearCookieDetails(); bool policyenabled(); bool mDeleteAllFlag; QWidget* mMainWidget; Ui::KCookiesManagementUI mUi; QStringList mDeletedDomains; typedef QList CookiePropList; QHash mDeletedCookies; }; #endif // KCOOKIESMANAGEMENT_H diff --git a/src/kcms/kio/kcookiespolicies.cpp b/src/kcms/kio/kcookiespolicies.cpp index dafeba36..64d3d85c 100644 --- a/src/kcms/kio/kcookiespolicies.cpp +++ b/src/kcms/kio/kcookiespolicies.cpp @@ -1,457 +1,457 @@ /** * kcookiespolicies.cpp - Cookies configuration * * Original Authors * Copyright (c) Waldo Bastian * Copyright (c) 1999 David Faure * Copyright (c) 2008 Urs Wolfer * * Re-written by: * Copyright (c) 2000- 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. */ // Own #include "kcookiespolicies.h" // Local #include "ksaveioconfig.h" // Qt #include #include -#include +#include // KDE #include #include #include #include #include #include // QUrl::fromAce/toAce don't accept a domain that starts with a '.', like we do here. // So we use these wrappers. QString tolerantFromAce (const QByteArray& _domain) { QByteArray domain (_domain); const bool hasDot = domain.startsWith ('.'); if (hasDot) domain.remove (0, 1); QString ret = QUrl::fromAce(domain); if (hasDot) { ret.prepend ('.'); } return ret; } static QByteArray tolerantToAce (const QString& _domain) { QString domain (_domain); const bool hasDot = domain.startsWith ('.'); if (hasDot) domain.remove (0, 1); QByteArray ret = QUrl::toAce (domain); if (hasDot) { ret.prepend ('.'); } return ret; } KCookiesPolicies::KCookiesPolicies (QWidget* parent) : KCModule (parent), mSelectedItemsCount(0) { mUi.setupUi (this); mUi.kListViewSearchLine->setTreeWidget (mUi.policyTreeWidget); QList columns; columns.append (0); mUi.kListViewSearchLine->setSearchColumns (columns); mUi.pbNew->setIcon (QIcon::fromTheme(QStringLiteral("list-add"))); mUi.pbChange->setIcon (QIcon::fromTheme(QStringLiteral("edit-rename"))); mUi.pbDelete->setIcon (QIcon::fromTheme(QStringLiteral("list-remove"))); mUi.pbDeleteAll->setIcon (QIcon::fromTheme(QStringLiteral("edit-delete"))); // Connect the main swicth :) Enable/disable cookie support connect (mUi.cbEnableCookies, SIGNAL (toggled (bool)), SLOT (cookiesEnabled (bool))); connect (mUi.cbEnableCookies, SIGNAL (toggled (bool)), SLOT (configChanged())); // Connect the preference check boxes... connect (mUi.cbRejectCrossDomainCookies, SIGNAL (toggled (bool)), SLOT (configChanged())); connect (mUi.cbAutoAcceptSessionCookies, SIGNAL (toggled (bool)), SLOT (configChanged())); connect (mUi.rbPolicyAsk, SIGNAL (toggled (bool)), SLOT (configChanged())); connect (mUi.rbPolicyAccept, SIGNAL (toggled (bool)), SLOT (configChanged())); connect (mUi.rbPolicyAcceptForSession, SIGNAL(toggled(bool)), SLOT(configChanged())); connect (mUi.rbPolicyReject, SIGNAL (toggled (bool)), SLOT (configChanged())); // Connect signals from the domain specific policy listview. connect (mUi.policyTreeWidget, SIGNAL (itemSelectionChanged()), SLOT (selectionChanged())); connect (mUi.policyTreeWidget, SIGNAL (itemDoubleClicked (QTreeWidgetItem*, int)), SLOT (changePressed())); // Connect the buttons... connect (mUi.pbNew, SIGNAL (clicked()), SLOT (addPressed())); connect (mUi.pbChange, SIGNAL (clicked()), SLOT (changePressed())); connect (mUi.pbDelete, SIGNAL (clicked()), SLOT (deletePressed())); connect (mUi.pbDeleteAll, SIGNAL (clicked()), SLOT (deleteAllPressed())); } KCookiesPolicies::~KCookiesPolicies() { } void KCookiesPolicies::configChanged () { //kDebug() << "KCookiesPolicies::configChanged..."; emit changed (true); } void KCookiesPolicies::cookiesEnabled (bool enable) { mUi.bgDefault->setEnabled (enable); mUi.bgPreferences->setEnabled (enable); mUi.gbDomainSpecific->setEnabled (enable); } void KCookiesPolicies::setPolicy (const QString& domain) { QTreeWidgetItemIterator it (mUi.policyTreeWidget); bool hasExistingPolicy = false; while (*it) { if ((*it)->text(0) == domain) { hasExistingPolicy = true; break; } ++it; } if (hasExistingPolicy) { changePressed((*it), false); } else { addPressed(domain); } } void KCookiesPolicies::changePressed() { changePressed(mUi.policyTreeWidget->currentItem()); } void KCookiesPolicies::addPressed() { addPressed(QString()); } void KCookiesPolicies::changePressed(QTreeWidgetItem* item, bool state) { Q_ASSERT(item); const QString oldDomain(item->text (0)); KCookiesPolicySelectionDlg pdlg (this); pdlg.setWindowTitle (i18nc ("@title:window", "Change Cookie Policy")); pdlg.setPolicy (KCookieAdvice::strToAdvice (mDomainPolicyMap.value(oldDomain))); pdlg.setEnableHostEdit (state, oldDomain); if (pdlg.exec() && !pdlg.domain().isEmpty()) { const QString newDomain = tolerantFromAce (pdlg.domain().toLatin1()); int advice = pdlg.advice(); if (newDomain == oldDomain || !handleDuplicate (newDomain, advice)) { mDomainPolicyMap[newDomain] = KCookieAdvice::adviceToStr(advice); item->setText(0, newDomain); item->setText(1, i18n (mDomainPolicyMap.value(newDomain))); configChanged(); } } } void KCookiesPolicies::addPressed(const QString& domain, bool state) { KCookiesPolicySelectionDlg pdlg (this); pdlg.setWindowTitle (i18nc ("@title:window", "New Cookie Policy")); pdlg.setEnableHostEdit(state, domain); if (mUi.rbPolicyAccept->isChecked()) pdlg.setPolicy (KCookieAdvice::Reject); else pdlg.setPolicy (KCookieAdvice::Accept); if (pdlg.exec() && !pdlg.domain().isEmpty()) { const QString domain = tolerantFromAce (pdlg.domain().toLatin1()); int advice = pdlg.advice(); if (!handleDuplicate (domain, advice)) { const char* strAdvice = KCookieAdvice::adviceToStr (advice); QTreeWidgetItem* item = new QTreeWidgetItem (mUi.policyTreeWidget, QStringList() << domain << i18n (strAdvice)); mDomainPolicyMap.insert (item->text(0), strAdvice); configChanged(); updateButtons(); } } } bool KCookiesPolicies::handleDuplicate (const QString& domain, int advice) { QTreeWidgetItem* item = mUi.policyTreeWidget->topLevelItem (0); while (item != nullptr) { if (item->text (0) == domain) { const int res = KMessageBox::warningContinueCancel (this, i18n ("A policy already exists for" "
%1
" "Do you want to replace it?
", domain), i18nc ("@title:window", "Duplicate Policy"), KGuiItem (i18n ("Replace"))); if (res == KMessageBox::Continue) { mDomainPolicyMap[domain] = KCookieAdvice::adviceToStr(advice); item->setText (0, domain); item->setText (1, i18n (mDomainPolicyMap.value(domain))); configChanged(); return true; } else return true; // User Cancelled!! } item = mUi.policyTreeWidget->itemBelow (item); } return false; } void KCookiesPolicies::deletePressed() { QTreeWidgetItem* nextItem = nullptr; Q_FOREACH (QTreeWidgetItem * item, mUi.policyTreeWidget->selectedItems()) { nextItem = mUi.policyTreeWidget->itemBelow (item); if (!nextItem) nextItem = mUi.policyTreeWidget->itemAbove (item); mDomainPolicyMap.remove (item->text(0)); delete item; } if (nextItem) nextItem->setSelected (true); updateButtons(); configChanged(); } void KCookiesPolicies::deleteAllPressed() { mDomainPolicyMap.clear(); mUi.policyTreeWidget->clear(); updateButtons(); configChanged(); } void KCookiesPolicies::updateButtons() { bool hasItems = mUi.policyTreeWidget->topLevelItemCount() > 0; mUi.pbChange->setEnabled((hasItems && mSelectedItemsCount == 1)); mUi.pbDelete->setEnabled((hasItems && mSelectedItemsCount > 0)); mUi.pbDeleteAll->setEnabled(hasItems); } void KCookiesPolicies::updateDomainList (const QStringList& domainConfig) { mUi.policyTreeWidget->clear(); QStringList::ConstIterator it = domainConfig.begin(); for (; it != domainConfig.end(); ++it) { QString domain; KCookieAdvice::Value advice = KCookieAdvice::Dunno; splitDomainAdvice (*it, domain, advice); if (!domain.isEmpty()) { QStringList items; items << tolerantFromAce(domain.toLatin1()) << i18n(KCookieAdvice::adviceToStr(advice)); QTreeWidgetItem* item = new QTreeWidgetItem (mUi.policyTreeWidget, items); mDomainPolicyMap[item->text(0)] = KCookieAdvice::adviceToStr(advice); } } mUi.policyTreeWidget->sortItems(0, Qt::AscendingOrder); } void KCookiesPolicies::selectionChanged () { mSelectedItemsCount = mUi.policyTreeWidget->selectedItems().count(); updateButtons (); } void KCookiesPolicies::load() { mSelectedItemsCount = 0; KConfig cfg (QStringLiteral("kcookiejarrc")); KConfigGroup group = cfg.group ("Cookie Policy"); bool enableCookies = group.readEntry ("Cookies", true); mUi.cbEnableCookies->setChecked (enableCookies); cookiesEnabled (enableCookies); // Warning: the default values are duplicated in kcookiejar.cpp KCookieAdvice::Value advice = KCookieAdvice::strToAdvice (group.readEntry ( "CookieGlobalAdvice", "Accept")); switch (advice) { case KCookieAdvice::Accept: mUi.rbPolicyAccept->setChecked (true); break; case KCookieAdvice::AcceptForSession: mUi.rbPolicyAcceptForSession->setChecked (true); break; case KCookieAdvice::Reject: mUi.rbPolicyReject->setChecked (true); break; case KCookieAdvice::Ask: case KCookieAdvice::Dunno: default: mUi.rbPolicyAsk->setChecked (true); } bool enable = group.readEntry ("RejectCrossDomainCookies", true); mUi.cbRejectCrossDomainCookies->setChecked (enable); bool sessionCookies = group.readEntry ("AcceptSessionCookies", true); mUi.cbAutoAcceptSessionCookies->setChecked (sessionCookies); updateDomainList (group.readEntry ("CookieDomainAdvice", QStringList())); if (enableCookies) { updateButtons(); } } void KCookiesPolicies::save() { KConfig cfg (QStringLiteral("kcookiejarrc")); KConfigGroup group = cfg.group ("Cookie Policy"); bool state = mUi.cbEnableCookies->isChecked(); group.writeEntry ("Cookies", state); state = mUi.cbRejectCrossDomainCookies->isChecked(); group.writeEntry ("RejectCrossDomainCookies", state); state = mUi.cbAutoAcceptSessionCookies->isChecked(); group.writeEntry ("AcceptSessionCookies", state); QString advice; if (mUi.rbPolicyAccept->isChecked()) advice = KCookieAdvice::adviceToStr (KCookieAdvice::Accept); else if (mUi.rbPolicyAcceptForSession->isChecked()) advice = KCookieAdvice::adviceToStr (KCookieAdvice::AcceptForSession); else if (mUi.rbPolicyReject->isChecked()) advice = KCookieAdvice::adviceToStr (KCookieAdvice::Reject); else advice = KCookieAdvice::adviceToStr (KCookieAdvice::Ask); group.writeEntry ("CookieGlobalAdvice", advice); QStringList domainConfig; QMapIterator it (mDomainPolicyMap); while (it.hasNext()) { it.next(); QString policy = tolerantToAce(it.key()); policy += QLatin1Char (':'); policy += QLatin1String (it.value()); domainConfig << policy; } group.writeEntry ("CookieDomainAdvice", domainConfig); group.sync(); // Update the cookiejar... if (!mUi.cbEnableCookies->isChecked()) { QDBusInterface kded (QStringLiteral("org.kde.kcookiejar5"), QStringLiteral("/modules/kcookiejar"), QStringLiteral("org.kde.KCookieServer"), QDBusConnection::sessionBus()); kded.call (QStringLiteral("shutdown")); } else { QDBusInterface kded (QStringLiteral("org.kde.kcookiejar5"), QStringLiteral("/modules/kcookiejar"), QStringLiteral("org.kde.KCookieServer"), QDBusConnection::sessionBus()); QDBusReply reply = kded.call (QStringLiteral("reloadPolicy")); if (!reply.isValid()) KMessageBox::sorry (nullptr, i18n ("Unable to communicate with the cookie handler service.\n" "Any changes you made will not take effect until the service " "is restarted.")); } // Force running io-slave to reload configurations... KSaveIOConfig::updateRunningIOSlaves (this); emit changed (false); } void KCookiesPolicies::defaults() { mUi.cbEnableCookies->setChecked (true); mUi.rbPolicyAsk->setChecked (true); mUi.rbPolicyAccept->setChecked (false); mUi.rbPolicyAcceptForSession->setChecked (false); mUi.rbPolicyReject->setChecked (false); mUi.cbRejectCrossDomainCookies->setChecked (true); mUi.cbAutoAcceptSessionCookies->setChecked (false); mUi.policyTreeWidget->clear(); mDomainPolicyMap.clear(); cookiesEnabled (mUi.cbEnableCookies->isChecked()); updateButtons(); } void KCookiesPolicies::splitDomainAdvice (const QString& cfg, QString& domain, KCookieAdvice::Value& advice) { int sepPos = cfg.lastIndexOf (':'); // Ignore any policy that does not contain a domain... if (sepPos <= 0) return; domain = cfg.left (sepPos); advice = KCookieAdvice::strToAdvice (cfg.mid (sepPos + 1)); } QString KCookiesPolicies::quickHelp() const { return i18n ("

Cookies

Cookies contain information that KDE" " application using the HTTP protocol (like Konqueror) stores" " on your computer from a remote Internet server. This means" " that a web server can store information about you and your" " browsing activities on your machine for later use. You might" " consider this an invasion of privacy.

However, cookies are" " useful in certain situations. For example, they are often used" " by Internet shops, so you can 'put things into a shopping" " basket'. Some sites require you have a browser that supports" " cookies.

Because most people want a compromise between privacy" " and the benefits cookies offer, KDE offers you the ability to" " customize the way it handles cookies. You might, for example" " want to set KDE's default policy to ask you whenever a server" " wants to set a cookie or simply reject or accept everything." " For example, you might choose to accept all cookies from your" " favorite shopping web site. For this all you have to do is" " either browse to that particular site and when you are presented" " with the cookie dialog box, click on This domain under" " the 'apply to' tab and choose accept or simply specify the name" " of the site in the Domain Specific Policy tab and set" " it to accept. This enables you to receive cookies from trusted" " web sites without being asked every time KDE receives a cookie.

" ); } diff --git a/src/kcms/kio/kcookiespolicies.h b/src/kcms/kio/kcookiespolicies.h index ea3ec9a6..c32944b2 100644 --- a/src/kcms/kio/kcookiespolicies.h +++ b/src/kcms/kio/kcookiespolicies.h @@ -1,78 +1,78 @@ /** * kcookiespolicies.h - Cookies configuration * * Original Authors * Copyright (c) Waldo Bastian * Copyright (c) 1999 David Faure * * Re-written by: * Copyright (c) 2000- 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. */ #ifndef KCOOKIESPOLICIES_H #define KCOOKIESPOLICIES_H -#include +#include #include #include "kcookiespolicyselectiondlg.h" #include "ui_kcookiespolicies.h" class QTreeWidgetItem; class KCookiesPolicies : public KCModule { Q_OBJECT public: KCookiesPolicies(/*const KComponentData &componentData,*/ QWidget *parent); ~KCookiesPolicies(); void load() Q_DECL_OVERRIDE; void save() Q_DECL_OVERRIDE; void defaults() Q_DECL_OVERRIDE; QString quickHelp() const Q_DECL_OVERRIDE; void setPolicy(const QString& domain); protected Q_SLOTS: void cookiesEnabled( bool ); void configChanged(); void selectionChanged(); void updateButtons(); void deleteAllPressed(); void deletePressed(); void changePressed(); void addPressed(); void changePressed(QTreeWidgetItem*, bool state = true); void addPressed(const QString&, bool state = true); private: void updateDomainList(const QStringList& list); bool handleDuplicate( const QString& domain, int ); void splitDomainAdvice (const QString& configStr, QString &domain, KCookieAdvice::Value &advice); private: quint64 mSelectedItemsCount; Ui::KCookiePoliciesUI mUi; QMap mDomainPolicyMap; }; #endif // KCOOKIESPOLICIES_H diff --git a/src/kcms/kio/ksaveioconfig.cpp b/src/kcms/kio/ksaveioconfig.cpp index 2b96e9cd..305263e2 100644 --- a/src/kcms/kio/ksaveioconfig.cpp +++ b/src/kcms/kio/ksaveioconfig.cpp @@ -1,239 +1,239 @@ /* Copyright (C) 2001 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. */ // Own #include "ksaveioconfig.h" // Qt -#include +#include // KDE #include #include #include #include #include class KSaveIOConfigPrivate { public: KSaveIOConfigPrivate (); ~KSaveIOConfigPrivate (); KConfig* config = nullptr; KConfig* http_config = nullptr; }; Q_GLOBAL_STATIC(KSaveIOConfigPrivate, d) KSaveIOConfigPrivate::KSaveIOConfigPrivate () { } KSaveIOConfigPrivate::~KSaveIOConfigPrivate () { delete config; delete http_config; } static KConfig* config() { if (!d->config) d->config = new KConfig(QStringLiteral("kioslaverc"), KConfig::NoGlobals); return d->config; } static KConfig* http_config() { if (!d->http_config) d->http_config = new KConfig(QStringLiteral("kio_httprc"), KConfig::NoGlobals); return d->http_config; } int KSaveIOConfig::proxyDisplayUrlFlags() { KConfigGroup cfg (config(), QString()); return cfg.readEntry("ProxyUrlDisplayFlags", 0); } void KSaveIOConfig::setProxyDisplayUrlFlags (int flags) { KConfigGroup cfg (config(), QString()); cfg.writeEntry("ProxyUrlDisplayFlags", flags); cfg.sync(); } void KSaveIOConfig::reparseConfiguration () { delete d->config; d->config = nullptr; delete d->http_config; d->http_config = nullptr; } void KSaveIOConfig::setReadTimeout( int _timeout ) { KConfigGroup cfg (config(), QString()); cfg.writeEntry("ReadTimeout", qMax(MIN_TIMEOUT_VALUE,_timeout)); cfg.sync(); } void KSaveIOConfig::setConnectTimeout( int _timeout ) { KConfigGroup cfg (config(), QString()); cfg.writeEntry("ConnectTimeout", qMax(MIN_TIMEOUT_VALUE,_timeout)); cfg.sync(); } void KSaveIOConfig::setProxyConnectTimeout( int _timeout ) { KConfigGroup cfg (config(), QString()); cfg.writeEntry("ProxyConnectTimeout", qMax(MIN_TIMEOUT_VALUE,_timeout)); cfg.sync(); } void KSaveIOConfig::setResponseTimeout( int _timeout ) { KConfigGroup cfg (config(), QString()); cfg.writeEntry("ResponseTimeout", qMax(MIN_TIMEOUT_VALUE,_timeout)); cfg.sync(); } void KSaveIOConfig::setMarkPartial( bool _mode ) { KConfigGroup cfg (config(), QString()); cfg.writeEntry( "MarkPartial", _mode ); cfg.sync(); } void KSaveIOConfig::setMinimumKeepSize( int _size ) { KConfigGroup cfg (config(), QString()); cfg.writeEntry( "MinimumKeepSize", _size ); cfg.sync(); } void KSaveIOConfig::setAutoResume( bool _mode ) { KConfigGroup cfg (config(), QString()); cfg.writeEntry( "AutoResume", _mode ); cfg.sync(); } void KSaveIOConfig::setUseCache( bool _mode ) { KConfigGroup cfg (http_config(), QString()); cfg.writeEntry( "UseCache", _mode ); cfg.sync(); } void KSaveIOConfig::setMaxCacheSize( int cache_size ) { KConfigGroup cfg (http_config(), QString()); cfg.writeEntry( "MaxCacheSize", cache_size ); cfg.sync(); } void KSaveIOConfig::setCacheControl(KIO::CacheControl policy) { KConfigGroup cfg (http_config(), QString()); QString tmp = KIO::getCacheControlString(policy); cfg.writeEntry("cache", tmp); cfg.sync(); } void KSaveIOConfig::setMaxCacheAge( int cache_age ) { KConfigGroup cfg (http_config(), QString()); cfg.writeEntry( "MaxCacheAge", cache_age ); cfg.sync(); } void KSaveIOConfig::setUseReverseProxy( bool mode ) { KConfigGroup cfg (config(), "Proxy Settings"); cfg.writeEntry("ReversedException", mode); cfg.sync(); } void KSaveIOConfig::setProxyType(KProtocolManager::ProxyType type) { KConfigGroup cfg (config(), "Proxy Settings"); cfg.writeEntry("ProxyType", static_cast(type)); cfg.sync(); } QString KSaveIOConfig::noProxyFor() { KConfigGroup cfg(config(), "Proxy Settings"); return cfg.readEntry("NoProxyFor"); } void KSaveIOConfig::setNoProxyFor( const QString& _noproxy ) { KConfigGroup cfg (config(), "Proxy Settings"); cfg.writeEntry("NoProxyFor", _noproxy); cfg.sync(); } void KSaveIOConfig::setProxyFor( const QString& protocol, const QString& _proxy ) { KConfigGroup cfg (config(), "Proxy Settings"); cfg.writeEntry(protocol.toLower() + "Proxy", _proxy); cfg.sync(); } void KSaveIOConfig::setProxyConfigScript( const QString& _url ) { KConfigGroup cfg (config(), "Proxy Settings"); cfg.writeEntry("Proxy Config Script", _url); cfg.sync(); } void KSaveIOConfig::updateRunningIOSlaves (QWidget *parent) { // Inform all running io-slaves about the changes... // if we cannot update, ioslaves inform the end user... QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KIO/Scheduler"), QStringLiteral("org.kde.KIO.Scheduler"), QStringLiteral("reparseSlaveConfiguration")); message << QString(); if (!QDBusConnection::sessionBus().send(message)) { KMessageBox::information (parent, i18n("You have to restart the running applications " "for these changes to take effect."), i18nc("@title:window", "Update Failed")); } } void KSaveIOConfig::updateProxyScout(QWidget * parent) { // Inform the proxyscout kded module about changes if we cannot update, // ioslaves inform the end user... QDBusInterface kded(QStringLiteral("org.kde.kcookiejar5"), QStringLiteral("/modules/proxyscout"), QStringLiteral("org.kde.KPAC.ProxyScout")); QDBusReply reply = kded.call(QStringLiteral("reset")); if (!reply.isValid()) { KMessageBox::information (parent, i18n("You have to restart KDE for these changes to take effect."), i18nc("@title:window", "Update Failed")); } } diff --git a/src/kcms/kio/main.cpp b/src/kcms/kio/main.cpp index 484a6df8..26cef61a 100644 --- a/src/kcms/kio/main.cpp +++ b/src/kcms/kio/main.cpp @@ -1,46 +1,46 @@ // (c) Torben Weis 1998 // (c) David Faure 1998 /* * main.cpp for creating the konqueror kio kcm modules * * Copyright (C) 2000,2001,2009 Alexander Neundorf * * 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. */ // Qt -#include +#include // KDE #include // Local #include "kcookiesmain.h" #include "netpref.h" #include "smbrodlg.h" #include "useragentdlg.h" #include "kproxydlg.h" #include "cache.h" K_PLUGIN_FACTORY(KioConfigFactory, registerPlugin(QStringLiteral("useragent")); registerPlugin(QStringLiteral("smb")); registerPlugin(QStringLiteral("netpref")); registerPlugin(QStringLiteral("proxy")); registerPlugin(QStringLiteral("cookie")); registerPlugin(QStringLiteral("cache")); ) #include "main.moc" diff --git a/src/kcms/kio/smbrodlg.cpp b/src/kcms/kio/smbrodlg.cpp index 5e145659..dc2da619 100644 --- a/src/kcms/kio/smbrodlg.cpp +++ b/src/kcms/kio/smbrodlg.cpp @@ -1,185 +1,185 @@ /* This file is part of the KDE project Copyright (C) 2000 Alexander Neundorf 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. */ // Own #include "smbrodlg.h" // Qt -#include +#include #include #include // KDE #include #include #include #include #include #include //#include #include K_PLUGIN_FACTORY_DECLARATION(KioConfigFactory) SMBRoOptions::SMBRoOptions(QWidget *parent, const QVariantList &) : KCModule(parent) { QGridLayout *layout = new QGridLayout(this ); QLabel *label=new QLabel(i18n("These settings apply to network browsing only."),this); layout->addWidget(label,0,0, 1, 2 ); m_userLe=new QLineEdit(this); label=new QLabel(i18n("Default user name:"),this); label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); label->setBuddy( m_userLe ); layout->addWidget(label,1,0); layout->addWidget(m_userLe,1,1); m_passwordLe=new QLineEdit(this); m_passwordLe->setEchoMode(QLineEdit::Password); label=new QLabel(i18n("Default password:"),this); label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); label->setBuddy( m_passwordLe ); layout->addWidget(label,2,0); layout->addWidget(m_passwordLe,2,1); /* m_workgroupLe=new QLineEdit(this); label=new QLabel(m_workgroupLe,i18n("Workgroup:"),this); layout->addWidget(label,3,0); layout->addWidget(m_workgroupLe,3,1); m_showHiddenShares=new QCheckBox(i18n("Show hidden shares"),this); layout->addWidget(m_showHiddenShares,4,0, 1, 2 ); m_encodingList = new KComboBox( false, this ); QStringList _strList = KCharsets::charsets()->availableEncodingNames(); m_encodingList->addItems( _strList ); label = new QLabel( i18n( "MS Windows encoding:" ), this ); label->setBuddy( m_encodingList ); layout->addWidget( label, 3, 0 ); layout->addWidget( m_encodingList, 3, 1 ); */ layout->addWidget(new QWidget(this),4,0); // connect(m_showHiddenShares, SIGNAL(toggled(bool)), this, SLOT(changed())); connect(m_userLe, SIGNAL(textChanged(QString)), this, SLOT(changed())); connect(m_passwordLe, SIGNAL(textChanged(QString)), this, SLOT(changed())); // connect(m_workgroupLe, SIGNAL(textChanged(QString)), this, SLOT(changed())); // connect( m_encodingList, SIGNAL(activated(QString)), this , SLOT(changed()) ); layout->setRowStretch(4, 1); } SMBRoOptions::~SMBRoOptions() { } void SMBRoOptions::load() { KConfig *cfg = new KConfig(QStringLiteral("kioslaverc")); QString tmp; KConfigGroup group = cfg->group("Browser Settings/SMBro" ); m_userLe->setText(group.readEntry("User")); // m_workgroupLe->setText(group.readEntry("Workgroup")); // m_showHiddenShares->setChecked(group.readEntry("ShowHiddenShares", QVariant(false)).toBool()); //QStringList _strList = KCharsets::charsets()->availableEncodingNames(); //QString m_encoding = QTextCodec::codecForLocale()->name(); //m_encodingList->setCurrentIndex( _strList.indexOf( group.readEntry( "Encoding", m_encoding.toLower() ) ) ); // unscramble QString scrambled = group.readEntry( "Password" ); QString password = QLatin1String(""); for (int i=0; isetText(password); delete cfg; } void SMBRoOptions::save() { KConfig *cfg = new KConfig(QStringLiteral("kioslaverc")); KConfigGroup group = cfg->group("Browser Settings/SMBro" ); group.writeEntry( "User", m_userLe->text()); // group.writeEntry( "Workgroup", m_workgroupLe->text()); // group.writeEntry( "ShowHiddenShares", m_showHiddenShares->isChecked()); // group.writeEntry( "Encoding", m_encodingList->currentText() ); //taken from Nicola Brodu's smb ioslave //it's not really secure, but at //least better than storing the plain password QString password(m_passwordLe->text()); QString scrambled; for (int i=0; i> 10; unsigned int a2 = (num & 0x3E0) >> 5; unsigned int a3 = (num & 0x1F); scrambled += (char)(a1+'0'); scrambled += (char)(a2+'A'); scrambled += (char)(a3+'0'); } group.writeEntry( "Password", scrambled); delete cfg; } void SMBRoOptions::defaults() { m_userLe->setText(QLatin1String("")); m_passwordLe->setText(QLatin1String("")); // m_workgroupLe->setText(""); // m_showHiddenShares->setChecked(false); } void SMBRoOptions::changed() { emit KCModule::changed(true); } QString SMBRoOptions::quickHelp() const { return i18n("

Windows Shares

Applications using the " "SMB kioslave (like Konqueror) are able to access shared Microsoft " "Windows file systems, if properly configured.

You can specify " "here the credentials used to access the shared resources. " "Passwords will be stored locally, and scrambled so as to render them " "unreadable to the human eye. For security reasons, you may not want to " "do that, as entries with passwords are clearly indicated as such.

"); } diff --git a/src/kioexec/main.cpp b/src/kioexec/main.cpp index 615dfbf3..69c20711 100644 --- a/src/kioexec/main.cpp +++ b/src/kioexec/main.cpp @@ -1,313 +1,313 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2005 David Faure Copyright (C) 2001 Waldo Bastian 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "main.h" #include "kio_version.h" #include "kioexecdinterface.h" -#include -#include -#include +#include +#include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_X11 #include #endif static const char description[] = I18N_NOOP("KIO Exec - Opens remote files, watches modifications, asks for upload"); KIOExec::KIOExec(const QStringList &args, bool tempFiles, const QString &suggestedFileName) : mExited(false) , mTempFiles(tempFiles) , mUseDaemon(false) , mSuggestedFileName(suggestedFileName) , expectedCounter(0) , command(args.first()) , jobCounter(0) { qDebug() << "command=" << command << "args=" << args; for (int i = 1; i < args.count(); i++) { const QUrl urlArg = QUrl::fromUserInput(args.value(i)); if (!urlArg.isValid()) { KMessageBox::error(nullptr, i18n("Invalid URL: %1", args.value(i))); exit(1); } KIO::StatJob* mostlocal = KIO::mostLocalUrl(urlArg); bool b = mostlocal->exec(); if (!b) { KMessageBox::error(nullptr, i18n("File not found: %1", urlArg.toDisplayString())); exit(1); } Q_ASSERT(b); const QUrl url = mostlocal->mostLocalUrl(); //kDebug() << "url=" << url.url() << " filename=" << url.fileName(); // A local file, not an URL ? // => It is not encoded and not shell escaped, too. if (url.isLocalFile()) { FileInfo file; file.path = url.toLocalFile(); file.url = url; fileList.append(file); } else { // It is an URL if (!url.isValid()) { KMessageBox::error(nullptr, i18n("The URL %1\nis malformed" , url.url())); } else if (mTempFiles) { KMessageBox::error(nullptr, i18n("Remote URL %1\nnot allowed with --tempfiles switch" , url.toDisplayString())); } else { // We must fetch the file QString fileName = KIO::encodeFileName(url.fileName()); if (!suggestedFileName.isEmpty()) fileName = suggestedFileName; // Build the destination filename, in ~/.cache/kioexec/krun/ // Unlike KDE-1.1, we put the filename at the end so that the extension is kept // (Some programs rely on it) QString krun_writable = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QStringLiteral("/krun/%1_%2/").arg(QCoreApplication::applicationPid()).arg(jobCounter++); QDir().mkpath(krun_writable); // error handling will be done by the job QString tmp = krun_writable + fileName; FileInfo file; file.path = tmp; file.url = url; fileList.append(file); expectedCounter++; const QUrl dest = QUrl::fromLocalFile(tmp); qDebug() << "Copying" << url << " to" << dest; KIO::Job *job = KIO::file_copy(url, dest); jobList.append(job); connect(job, &KJob::result, this, &KIOExec::slotResult); } } } if (mTempFiles) { slotRunApp(); return; } counter = 0; if (counter == expectedCounter) { slotResult(nullptr); } } void KIOExec::slotResult(KJob *job) { if (job) { KIO::FileCopyJob *copyJob = static_cast(job); const QString path = copyJob->destUrl().path(); if (job->error()) { // That error dialog would be queued, i.e. not immediate... //job->showErrorDialog(); if ((job->error() != KIO::ERR_USER_CANCELED)) KMessageBox::error(nullptr, job->errorString()); auto it = std::find_if(fileList.begin(), fileList.end(), [&path](const FileInfo &i) { return (i.path == path); }); if (it != fileList.end()) { fileList.erase(it); } else { qDebug() << path << " not found in list"; } } else { // Tell kioexecd to watch the file for changes. const QString dest = copyJob->srcUrl().toString(); qDebug() << "Telling kioexecd to watch path" << path << "dest" << dest; OrgKdeKIOExecdInterface kioexecd(QStringLiteral("org.kde.kioexecd"), QStringLiteral("/modules/kioexecd"), QDBusConnection::sessionBus()); kioexecd.watch(path, dest); mUseDaemon = !kioexecd.lastError().isValid(); if (!mUseDaemon) { qDebug() << "Not using kioexecd"; } } } counter++; if (counter < expectedCounter) { return; } qDebug() << "All files downloaded, will call slotRunApp shortly"; // We know we can run the app now - but let's finish the job properly first. QTimer::singleShot(0, this, &KIOExec::slotRunApp); jobList.clear(); } void KIOExec::slotRunApp() { if (fileList.isEmpty()) { qDebug() << "No files downloaded -> exiting"; mExited = true; QApplication::exit(1); return; } KService service(QStringLiteral("dummy"), command, QString()); QList list; // Store modification times QList::Iterator it = fileList.begin(); for (; it != fileList.end() ; ++it) { QFileInfo info(QFile::encodeName(it->path)); it->time = info.lastModified(); QUrl url = QUrl::fromLocalFile(it->path); list << url; } KIO::DesktopExecParser execParser(service, list); QStringList params = execParser.resultingArguments(); qDebug() << "EXEC " << params.join(QStringLiteral(" ")); // propagate the startup identification to the started process KStartupInfoId id; QByteArray startupId; #if HAVE_X11 if (QX11Info::isPlatformX11()) { startupId = QX11Info::nextStartupId(); } #endif id.initId(startupId); id.setupStartupEnv(); QString exe(params.takeFirst()); const int exit_code = QProcess::execute(exe, params); KStartupInfo::resetStartupEnv(); qDebug() << "EXEC done"; // Test whether one of the files changed for (it = fileList.begin(); it != fileList.end(); ++it) { QString src = it->path; const QUrl dest = it->url; QFileInfo info(src); const bool uploadChanges = !mUseDaemon && !dest.isLocalFile(); if (info.exists() && (it->time != info.lastModified())) { if (mTempFiles) { if (KMessageBox::questionYesNo(nullptr, i18n("The supposedly temporary file\n%1\nhas been modified.\nDo you still want to delete it?", dest.toDisplayString(QUrl::PreferLocalFile)), i18n("File Changed"), KStandardGuiItem::del(), KGuiItem(i18n("Do Not Delete"))) != KMessageBox::Yes) continue; // don't delete the temp file } else if (uploadChanges) { // no upload when it's already a local file or kioexecd already did it. if (KMessageBox::questionYesNo(nullptr, i18n("The file\n%1\nhas been modified.\nDo you want to upload the changes?" , dest.toDisplayString()), i18n("File Changed"), KGuiItem(i18n("Upload")), KGuiItem(i18n("Do Not Upload"))) == KMessageBox::Yes) { qDebug() << "src='" << src << "' dest='" << dest << "'"; // Do it the synchronous way. KIO::CopyJob* job = KIO::copy(QUrl::fromLocalFile(src), dest); if (!job->exec()) { KMessageBox::error(nullptr, job->errorText()); continue; // don't delete the temp file } } } } if ((uploadChanges || mTempFiles) && exit_code == 0) { // Wait for a reasonable time so that even if the application forks on startup (like OOo or amarok) // it will have time to start up and read the file before it gets deleted. #130709. qDebug() << "sleeping..."; QThread::currentThread()->sleep(180); // 3 mn const QString parentDir = info.path(); qDebug() << "about to delete" << parentDir << "containing" << info.fileName(); QFile(QFile::encodeName(src)).remove(); QDir().rmdir(parentDir); } } mExited = true; QApplication::exit(exit_code); } int main(int argc, char **argv) { QApplication app(argc, argv); KAboutData aboutData(QStringLiteral("kioexec"), i18n("KIOExec"), QStringLiteral(KIO_VERSION_STRING), i18n(description), KAboutLicense::GPL, i18n("(c) 1998-2000,2003 The KFM/Konqueror Developers")); aboutData.addAuthor(i18n("David Faure"), QString(), QStringLiteral("faure@kde.org")); aboutData.addAuthor(i18n("Stephan Kulow"), QString(), QStringLiteral("coolo@kde.org")); aboutData.addAuthor(i18n("Bernhard Rosenkraenzer"), QString(), QStringLiteral("bero@arklinux.org")); aboutData.addAuthor(i18n("Waldo Bastian"), QString(), QStringLiteral("bastian@kde.org")); aboutData.addAuthor(i18n("Oswald Buddenhagen"), QString(), QStringLiteral("ossi@kde.org")); KAboutData::setApplicationData(aboutData); KDBusService service(KDBusService::Multiple); QCommandLineParser parser; parser.addHelpOption(); parser.addVersionOption(); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("tempfiles") , i18n("Treat URLs as local files and delete them afterwards"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("suggestedfilename"), i18n("Suggested file name for the downloaded file"), "filename")); parser.addPositionalArgument(QStringLiteral("command"), i18n("Command to execute")); parser.addPositionalArgument(QStringLiteral("urls"), i18n("URL(s) or local file(s) used for 'command'")); app.setQuitOnLastWindowClosed(false); aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); if (parser.positionalArguments().count() < 1) { parser.showHelp(-1); return -1; } const bool tempfiles = parser.isSet(QStringLiteral("tempfiles")); const QString suggestedfilename = parser.value(QStringLiteral("suggestedfilename")); KIOExec exec(parser.positionalArguments(), tempfiles, suggestedfilename); // Don't go into the event loop if we already want to exit (#172197) if (exec.exited()) { return 0; } return app.exec(); } diff --git a/src/kioexec/main.h b/src/kioexec/main.h index e5f83b64..8e11874a 100644 --- a/src/kioexec/main.h +++ b/src/kioexec/main.h @@ -1,75 +1,75 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2005 David Faure Copyright (C) 2001 Waldo Bastian 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIOEXEC_MAIN_H #define KIOEXEC_MAIN_H -#include -#include +#include +#include #include -#include +#include #include #include namespace KIO { class Job; } class KJob; class QCommandLineParser; class KIOExec : public QObject { Q_OBJECT public: KIOExec(const QStringList &args, bool tempFiles, const QString &suggestedFileName); bool exited() const { return mExited; } public Q_SLOTS: void slotResult(KJob *); void slotRunApp(); protected: bool mExited; bool mTempFiles; bool mUseDaemon; QString mSuggestedFileName; int counter; int expectedCounter; QString command; struct FileInfo { QString path; QUrl url; QDateTime time; }; QList fileList; int jobCounter; QList jobList; }; #endif diff --git a/src/kioslave/kioslave.cpp b/src/kioslave/kioslave.cpp index be5b34c0..0736d58d 100644 --- a/src/kioslave/kioslave.cpp +++ b/src/kioslave/kioslave.cpp @@ -1,131 +1,131 @@ /* * This file is part of the KDE libraries * Copyright (c) 1999-2000 Waldo Bastian * (c) 1999 Mario Weilguni * (c) 2001 Lubos Lunak * * 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 #include #include #include #include -#include -#include -#include +#include +#include +#include #include #if defined(Q_OS_WIN) || defined(Q_OS_MAC) #define USE_KPROCESS_FOR_KIOSLAVES #endif #ifdef USE_KPROCESS_FOR_KIOSLAVES -#include -#include -#include +#include +#include +#include #ifdef Q_OS_WIN #include #include #endif #endif #ifndef Q_OS_WIN /* These are to link libkio even if 'smart' linker is used */ #include extern "C" KIO::AuthInfo *_kioslave_init_kio() { return new KIO::AuthInfo(); } #endif int main(int argc, char **argv) { if (argc < 5) { fprintf(stderr, "Usage: kioslave \n\nThis program is part of KDE.\n"); return 1; } #ifndef _WIN32_WCE setlocale(LC_ALL, ""); #endif QString libname = QFile::decodeName(argv[1]); if (libname.isEmpty()) { fprintf(stderr, "library path is empty.\n"); return 1; } // Use KPluginLoader to locate the library when using a relative path // But we need to use QLibrary to actually load it, because of resolve()! QString libpath = KPluginLoader::findPlugin(libname); if (libpath.isEmpty()) { fprintf(stderr, "could not locate %s, check QT_PLUGIN_PATH\n", qPrintable(libname)); return 1; } qDebug() << "trying to load" << libname << "from" << libpath; QLibrary lib(libpath); if (!lib.load()) { fprintf(stderr, "could not open %s: %s\n", qPrintable(libname), qPrintable(lib.errorString())); return 1; } QFunctionPointer sym = lib.resolve("kdemain"); if (!sym) { fprintf(stderr, "Could not find kdemain: %s\n", qPrintable(lib.errorString())); return 1; } #ifdef Q_OS_WIN // enter debugger in case debugging is actived QString slaveDebugWait(QString::fromLocal8Bit(qgetenv("KDE_SLAVE_DEBUG_WAIT"))); if (slaveDebugWait == QLatin1String("all") || slaveDebugWait == argv[2]) { # ifdef Q_CC_MSVC // msvc debugger or windbg supports jit debugging, the latter requires setting up windbg jit with windbg -i DebugBreak(); # else // gdb does not support win32 jit debug support, so implement it by ourself WCHAR buf[1024]; GetModuleFileName(NULL, buf, 1024); QStringList params; params << QString::fromUtf16((const unsigned short *)buf); params << QString::number(GetCurrentProcessId()); QProcess::startDetached("gdb", params); Sleep(1000); # endif } # if defined(Q_CC_MSVC) && !defined(_WIN32_WCE) else { QString slaveDebugPopup(QString::fromLocal8Bit(qgetenv("KDE_SLAVE_DEBUG_POPUP"))); if (slaveDebugPopup == QLatin1String("all") || slaveDebugPopup == argv[2]) { // A workaround for OSes where DebugBreak() does not work in administrative mode (actually Vista with msvc 2k5) // - display a native message box so developer can attach the debugger to the KIO slave process and click OK. MessageBoxA(NULL, QString("Please attach the debugger to process #%1 (%2)").arg(getpid()).arg(argv[0]).toLatin1(), QString("\"%1\" KIO Slave Debugging").arg(argv[2]).toLatin1(), MB_OK | MB_ICONINFORMATION | MB_TASKMODAL); } } # endif #endif // Q_OS_WIN int (*func)(int, char *[]) = (int (*)(int, char *[])) sym; exit(func(argc - 1, argv + 1)); /* Launch! */ } diff --git a/src/kntlm/des.cpp b/src/kntlm/des.cpp index 122cd50e..af9b240f 100644 --- a/src/kntlm/des.cpp +++ b/src/kntlm/des.cpp @@ -1,547 +1,547 @@ /* * Sofware DES functions * * Copyright 1988-1991 Phil Karn * Copyright 2003 Nikos Mavroyanopoulos * * Taken from libmcrypt (http://mcrypt.hellug.gr/lib/index.html). * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA */ /* Sofware DES functions * written 12 Dec 1986 by Phil Karn, KA9Q; large sections adapted from * the 1977 public-domain program by Jim Gillogly * Modified for additional speed - 6 December 1988 Phil Karn * Modified for parameterized key schedules - Jan 1991 Phil Karn * Callers now allocate a key schedule as follows: * kn = (char (*)[8])malloc(sizeof(char) * 8 * 16); * or * char kn[16][8]; */ /* modified in order to use the libmcrypt API by Nikos Mavroyanopoulos * All modifications are placed under the license of libmcrypt. */ #include "des.h" #include -#include +#include static void permute_ip(unsigned char *inblock, DES_KEY *key, unsigned char *outblock); static void permute_fp(unsigned char *inblock, DES_KEY *key, unsigned char *outblock); static void perminit_ip(DES_KEY *key); static void spinit(DES_KEY *key); static void perminit_fp(DES_KEY *key); static quint32 f(DES_KEY *key, quint32 r, char *subkey); /* Tables defined in the Data Encryption Standard documents */ /* initial permutation IP */ static const char ip[] = { 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7 }; /* final permutation IP^-1 */ static const char fp[] = { 40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25 }; /* expansion operation matrix * This is for reference only; it is unused in the code * as the f() function performs it implicitly for speed */ #ifdef notdef static const char ei[] = { 32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1 }; #endif /* permuted choice table (key) */ static const char pc1[] = { 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36, 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4 }; /* number left rotations of pc1 */ static const char totrot[] = { 1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28 }; /* permuted choice key (table) */ static const char pc2[] = { 14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, 41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32 }; /* The (in)famous S-boxes */ static const char si[8][64] = { /* S1 */ { 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 }, /* S2 */ { 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 }, /* S3 */ { 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 }, /* S4 */ { 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 }, /* S5 */ { 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 }, /* S6 */ { 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 }, /* S7 */ { 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 }, /* S8 */ { 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 }, }; /* 32-bit permutation function P used on the output of the S-boxes */ static const char p32i[] = { 16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25 }; /* End of DES-defined tables */ /* Lookup tables initialized once only at startup by desinit() */ /* bit 0 is left-most in byte */ static const int bytebit[] = { 0200, 0100, 040, 020, 010, 04, 02, 01 }; static const int nibblebit[] = { 010, 04, 02, 01 }; /* Allocate space and initialize DES lookup arrays * mode == 0: standard Data Encryption Algorithm */ static int desinit(DES_KEY *key) { spinit(key); perminit_ip(key); perminit_fp(key); return 0; } /* Set key (initialize key schedule array) */ int ntlm_des_set_key(DES_KEY *dkey, char *user_key, int /*len*/) { char pc1m[56]; /* place to modify pc1 into */ char pcr[56]; /* place to rotate pc1 into */ int i, j, l; int m; memset(dkey, 0, sizeof(DES_KEY)); desinit(dkey); /* Clear key schedule */ for (j = 0; j < 56; ++j) { /* convert pc1 to bits of key */ l = pc1[j] - 1; /* integer bit location */ m = l & 07; /* find bit */ pc1m[j] = (user_key[l >> 3] & /* find which key byte l is in */ bytebit[m]) /* and which bit of that byte */ ? 1 : 0; /* and store 1-bit result */ } for (i = 0; i < 16; ++i) { /* key chunk for each iteration */ for (j = 0; j < 56; ++j) { /* rotate pc1 the right amount */ pcr[j] = pc1m[(l = j + totrot[i]) < (j < 28 ? 28 : 56) ? l : l - 28]; } /* rotate left and right halves independently */ for (j = 0; j < 48; ++j) { /* select bits individually */ /* check bit that goes to kn[j] */ if (pcr[pc2[j] - 1]) { /* mask it in if it's there */ l = j % 6; dkey->kn[i][j / 6] |= bytebit[l] >> 2; } } } return 0; } /* In-place encryption of 64-bit block */ static void ntlm_des_encrypt(DES_KEY *key, unsigned char *block) { quint32 left, right; char *knp; quint32 work[2]; /* Working data storage */ permute_ip(block, key, (unsigned char *) work); /* Initial Permutation */ left = qFromBigEndian(work[0]); right = qFromBigEndian(work[1]); /* Do the 16 rounds. * The rounds are numbered from 0 to 15. On even rounds * the right half is fed to f() and the result exclusive-ORs * the left half; on odd rounds the reverse is done. */ knp = &key->kn[0][0]; left ^= f(key, right, knp); knp += 8; right ^= f(key, left, knp); knp += 8; left ^= f(key, right, knp); knp += 8; right ^= f(key, left, knp); knp += 8; left ^= f(key, right, knp); knp += 8; right ^= f(key, left, knp); knp += 8; left ^= f(key, right, knp); knp += 8; right ^= f(key, left, knp); knp += 8; left ^= f(key, right, knp); knp += 8; right ^= f(key, left, knp); knp += 8; left ^= f(key, right, knp); knp += 8; right ^= f(key, left, knp); knp += 8; left ^= f(key, right, knp); knp += 8; right ^= f(key, left, knp); knp += 8; left ^= f(key, right, knp); knp += 8; right ^= f(key, left, knp); /* Left/right half swap, plus byte swap if little-endian */ work[1] = qToBigEndian(left); work[0] = qToBigEndian(right); permute_fp((unsigned char *) work, key, block); /* Inverse initial permutation */ } /* Permute inblock with perm */ static void permute_ip(unsigned char *inblock, DES_KEY *key, unsigned char *outblock) { unsigned char *ib, *ob; /* ptr to input or output block */ char *p, *q; int j; /* Clear output block */ memset(outblock, 0, 8); ib = inblock; for (j = 0; j < 16; j += 2, ++ib) { /* for each input nibble */ ob = outblock; p = key->iperm[j][(*ib >> 4) & 0xf]; q = key->iperm[j + 1][*ib & 0xf]; /* and each output byte, OR the masks together */ *ob++ |= *p++ | *q++; *ob++ |= *p++ | *q++; *ob++ |= *p++ | *q++; *ob++ |= *p++ | *q++; *ob++ |= *p++ | *q++; *ob++ |= *p++ | *q++; *ob++ |= *p++ | *q++; *ob++ |= *p++ | *q++; } } /* Permute inblock with perm */ static void permute_fp(unsigned char *inblock, DES_KEY *key, unsigned char *outblock) { unsigned char *ib, *ob; /* ptr to input or output block */ char *p, *q; int j; /* Clear output block */ memset(outblock, 0, 8); ib = inblock; for (j = 0; j < 16; j += 2, ++ib) { /* for each input nibble */ ob = outblock; p = key->fperm[j][(*ib >> 4) & 0xf]; q = key->fperm[j + 1][*ib & 0xf]; /* and each output byte, OR the masks together */ *ob++ |= *p++ | *q++; *ob++ |= *p++ | *q++; *ob++ |= *p++ | *q++; *ob++ |= *p++ | *q++; *ob++ |= *p++ | *q++; *ob++ |= *p++ | *q++; *ob++ |= *p++ | *q++; *ob++ |= *p++ | *q++; } } /* The nonlinear function f(r,k), the heart of DES */ static quint32 f(DES_KEY *key, quint32 r, char *subkey) { quint32 *spp; quint32 rval, rt; int er; #ifdef TRACE printf("f(%08lx, %02x %02x %02x %02x %02x %02x %02x %02x) = ", r, subkey[0], subkey[1], subkey[2], subkey[3], subkey[4], subkey[5], subkey[6], subkey[7]); #endif /* Run E(R) ^ K through the combined S & P boxes. * This code takes advantage of a convenient regularity in * E, namely that each group of 6 bits in E(R) feeding * a single S-box is a contiguous segment of R. */ subkey += 7; /* Compute E(R) for each block of 6 bits, and run thru boxes */ er = ((int) r << 1) | ((r & 0x80000000) ? 1 : 0); spp = &key->sp[7][0]; rval = spp[(er ^ *subkey--) & 0x3f]; spp -= 64; rt = (quint32) r >> 3; rval |= spp[((int) rt ^ *subkey--) & 0x3f]; spp -= 64; rt >>= 4; rval |= spp[((int) rt ^ *subkey--) & 0x3f]; spp -= 64; rt >>= 4; rval |= spp[((int) rt ^ *subkey--) & 0x3f]; spp -= 64; rt >>= 4; rval |= spp[((int) rt ^ *subkey--) & 0x3f]; spp -= 64; rt >>= 4; rval |= spp[((int) rt ^ *subkey--) & 0x3f]; spp -= 64; rt >>= 4; rval |= spp[((int) rt ^ *subkey--) & 0x3f]; spp -= 64; rt >>= 4; rt |= (r & 1) << 5; rval |= spp[((int) rt ^ *subkey) & 0x3f]; #ifdef TRACE printf(" %08lx\n", rval); #endif return rval; } /* initialize a perm array */ static void perminit_ip(DES_KEY *key) { int l, j, k; int i, m; /* Clear the permutation array */ memset(key->iperm, 0, 16 * 16 * 8); for (i = 0; i < 16; ++i) /* each input nibble position */ for (j = 0; j < 16; ++j) /* each possible input nibble */ for (k = 0; k < 64; ++k) { /* each output bit position */ l = ip[k] - 1; /* where does this bit come from */ if ((l >> 2) != i) { /* does it come from input posn? */ continue; /* if not, bit k is 0 */ } if (!(j & nibblebit[l & 3])) { continue; /* any such bit in input? */ } m = k & 07; /* which bit is this in the byte */ key->iperm[i][j][k >> 3] |= bytebit[m]; } } static void perminit_fp(DES_KEY *key) { int l, j, k; int i, m; /* Clear the permutation array */ memset(key->fperm, 0, 16 * 16 * 8); for (i = 0; i < 16; ++i) /* each input nibble position */ for (j = 0; j < 16; ++j) /* each possible input nibble */ for (k = 0; k < 64; ++k) { /* each output bit position */ l = fp[k] - 1; /* where does this bit come from */ if ((l >> 2) != i) { /* does it come from input posn? */ continue; /* if not, bit k is 0 */ } if (!(j & nibblebit[l & 3])) { continue; /* any such bit in input? */ } m = k & 07; /* which bit is this in the byte */ key->fperm[i][j][k >> 3] |= bytebit[m]; } } /* Initialize the lookup table for the combined S and P boxes */ static void spinit(DES_KEY *key) { char pbox[32]; int p, i, s, j, rowcol; quint32 val; /* Compute pbox, the inverse of p32i. * This is easier to work with */ for (p = 0; p < 32; ++p) { for (i = 0; i < 32; ++i) { if (p32i[i] - 1 == p) { pbox[p] = i; break; } } } for (s = 0; s < 8; ++s) { /* For each S-box */ for (i = 0; i < 64; ++i) { /* For each possible input */ val = 0; /* The row number is formed from the first and last * bits; the column number is from the middle 4 */ rowcol = (i & 32) | ((i & 1) ? 16 : 0) | ((i >> 1) & 0xf); for (j = 0; j < 4; j++) { /* For each output bit */ if (si[s][rowcol] & (8 >> j)) { val |= 1L << (31 - pbox[4 * s + j]); } } key->sp[s][i] = val; } } } int ntlm_des_ecb_encrypt(const void *plaintext, int len, DES_KEY *akey, unsigned char output[8]) { int j; const unsigned char *plain = (const unsigned char *) plaintext; for (j = 0; j < len / 8; ++j) { memcpy(&output[j * 8], &plain[j * 8], 8); ntlm_des_encrypt(akey, &output[j * 8]); } if (j == 0 && len != 0) { return -1; /* no blocks were encrypted */ } return 0; } diff --git a/src/kntlm/kntlm.cpp b/src/kntlm/kntlm.cpp index f801bce4..fd409181 100644 --- a/src/kntlm/kntlm.cpp +++ b/src/kntlm/kntlm.cpp @@ -1,417 +1,417 @@ /* This file is part of the KDE libraries Copyright (c) 2004 Szombathelyi Gy�gy The implementation is based on the documentation and sample code at http://davenport.sourceforge.net/ntlm.html The DES encryption functions are from libntlm at http://josefsson.org/libntlm/ 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 "kntlm.h" #include "des.h" #include -#include -#include -#include +#include +#include +#include #include static const char NTLM_SIGNATURE[] = "NTLMSSP"; static Q_CONSTEXPR int NTLM_BLOB_SIZE = 28; static QByteArray QString2UnicodeLE(const QString &target) { QByteArray unicode(target.length() * 2, 0); for (int i = 0; i < target.length(); i++) { ((quint16 *) unicode.data()) [ i ] = qToLittleEndian(target[i].unicode()); } return unicode; } static QString UnicodeLE2QString(const QChar *data, uint len) { QString ret; for (uint i = 0; i < len; i++) { ret += qFromLittleEndian(data[ i ].unicode()); } return ret; } static QByteArray getBuf(const QByteArray &buf, const KNTLM::SecBuf &secbuf) { quint32 offset = qFromLittleEndian(secbuf.offset); quint16 len = qFromLittleEndian(secbuf.len); //watch for buffer overflows if (offset > (quint32) buf.size() || offset + len > (quint32) buf.size()) { return QByteArray(); } return QByteArray(buf.data() + offset, len); } static void addBuf(QByteArray &buf, KNTLM::SecBuf &secbuf, const QByteArray &data) { quint32 offset = (buf.size() + 1) & 0xfffffffe; quint16 len = data.size(); quint16 maxlen = data.size(); secbuf.offset = qToLittleEndian((quint32) offset); secbuf.len = qToLittleEndian(len); secbuf.maxlen = qToLittleEndian(maxlen); buf.resize(offset + len); memcpy(buf.data() + offset, data.data(), data.size()); } static QString getString(const QByteArray &buf, const KNTLM::SecBuf &secbuf, bool unicode) { //watch for buffer overflows quint32 offset = qFromLittleEndian((quint32) secbuf.offset); quint16 len = qFromLittleEndian(secbuf.len); if (offset > (quint32) buf.size() || offset + len > (quint32) buf.size()) { return QString(); } const char *c = buf.data() + offset; if (unicode) { return UnicodeLE2QString((QChar *) c, len >> 1); } return QString::fromLatin1(c, len); } static void addString(QByteArray &buf, KNTLM::SecBuf &secbuf, const QString &str, bool unicode = false) { if (unicode) { addBuf(buf, secbuf, QString2UnicodeLE(str)); return; } addBuf(buf, secbuf, str.toLatin1()); } /* * turns a 56 bit key into the 64 bit, odd parity key and sets the key. * The key schedule ks is also set. */ static void convertKey(unsigned char *key_56, void *ks) { unsigned char key[8]; key[0] = key_56[0]; key[1] = ((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1); key[2] = ((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2); key[3] = ((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3); key[4] = ((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4); key[5] = ((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5); key[6] = ((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6); key[7] = (key_56[6] << 1) & 0xFF; for (uint i = 0; i < 8; i++) { unsigned char b = key[i]; bool needsParity = ((((b >> 7) ^ (b >> 6) ^ (b >> 5) ^ (b >> 4) ^ (b >> 3) ^ (b >> 2) ^ (b >> 1)) & 0x01) == 0); if (needsParity) { key[i] |= 0x01; } else { key[i] &= 0xfe; } } ntlm_des_set_key((DES_KEY *) ks, (char *) &key, sizeof(key)); memset(&key, 0, sizeof(key)); } static QByteArray createBlob(const QByteArray &targetinfo) { QByteArray blob(NTLM_BLOB_SIZE + 4 + targetinfo.size(), 0); KNTLM::Blob *bl = (KNTLM::Blob *) blob.data(); bl->signature = qToBigEndian((quint32) 0x01010000); quint64 now = QDateTime::currentDateTimeUtc().toTime_t(); now += (quint64) 3600 * (quint64) 24 * (quint64) 134774; now *= (quint64) 10000000; bl->timestamp = qToLittleEndian(now); for (uint i = 0; i < 8; i++) { bl->challenge[i] = KRandom::random() % 0xff; } memcpy(blob.data() + NTLM_BLOB_SIZE, targetinfo.data(), targetinfo.size()); return blob; } static QByteArray hmacMD5(const QByteArray &data, const QByteArray &key) { QByteArray ret; QByteArray ipad(64, 0x36); QByteArray opad(64, 0x5c); Q_ASSERT(key.size() <= 64); for (int i = qMin(key.size(), 64) - 1; i >= 0; i--) { ipad.data()[i] ^= key[i]; opad.data()[i] ^= key[i]; } QByteArray content(ipad + data); QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(content); content = opad + md5.result(); md5.reset(); md5.addData(content); return md5.result(); } /*************************************** KNTLM implementation ***************************************/ bool KNTLM::getNegotiate(QByteArray &negotiate, const QString &domain, const QString &workstation, quint32 flags) { QByteArray rbuf(sizeof(Negotiate), 0); memcpy(rbuf.data(), NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE)); ((Negotiate *) rbuf.data())->msgType = qToLittleEndian((quint32) 1); if (!domain.isEmpty()) { flags |= Negotiate_Domain_Supplied; addString(rbuf, ((Negotiate *) rbuf.data())->domain, domain); } if (!workstation.isEmpty()) { flags |= Negotiate_WS_Supplied; addString(rbuf, ((Negotiate *) rbuf.data())->workstation, workstation); } ((Negotiate *) rbuf.data())->flags = qToLittleEndian(flags); negotiate = rbuf; return true; } bool KNTLM::getAuth(QByteArray &auth, const QByteArray &challenge, const QString &user, const QString &password, const QString &domain, const QString &workstation, AuthFlags authflags) { QByteArray rbuf(sizeof(Auth), 0); Challenge *ch = (Challenge *) challenge.data(); QByteArray response; const uint chsize = challenge.size(); bool unicode = false; QString dom; //challenge structure too small if (chsize < 32) { return false; } unicode = qFromLittleEndian(ch->flags) & Negotiate_Unicode; // If the domain is NULL (i.e. QString()) use the target domain. If the domain is empty // (i.e. QString("")) use an empty domain. if (domain.isNull()) { dom = getString(challenge, ch->targetName, unicode); } else { dom = domain; } memcpy(rbuf.data(), NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE)); ((Auth *) rbuf.data())->msgType = qToLittleEndian((quint32) 3); ((Auth *) rbuf.data())->flags = ch->flags; QByteArray targetInfo; if (chsize >= sizeof(Challenge)) { targetInfo = getBuf(challenge, ch->targetInfo); } if (!(authflags & Force_V1) && ((authflags & Force_V2) || (!targetInfo.isEmpty() && (qFromLittleEndian(ch->flags) & Negotiate_Target_Info))) /* may support NTLMv2 */) { bool ret = false; if (qFromLittleEndian(ch->flags) & Negotiate_NTLM) { if (targetInfo.isEmpty()) { return false; } response = getNTLMv2Response(dom, user, password, targetInfo, ch->challengeData); addBuf(rbuf, ((Auth *) rbuf.data())->ntResponse, response); ret = true; } if (authflags & Add_LM) { response = getLMv2Response(dom, user, password, ch->challengeData); addBuf(rbuf, ((Auth *) rbuf.data())->lmResponse, response); ret = true; } if (!ret) { return false; } } else { //if no targetinfo structure and NTLMv2 or LMv2 not forced, or v1 forced, try the older methods bool ret = false; if (qFromLittleEndian(ch->flags) & Negotiate_NTLM) { response = getNTLMResponse(password, ch->challengeData); addBuf(rbuf, ((Auth *) rbuf.data())->ntResponse, response); ret = true; } if (authflags & Add_LM) { response = getLMResponse(password, ch->challengeData); addBuf(rbuf, ((Auth *) rbuf.data())->lmResponse, response); ret = true; } if (!ret) { return false; } } if (!dom.isEmpty()) { addString(rbuf, ((Auth *) rbuf.data())->domain, dom, unicode); } addString(rbuf, ((Auth *) rbuf.data())->user, user, unicode); if (!workstation.isEmpty()) { addString(rbuf, ((Auth *) rbuf.data())->workstation, workstation, unicode); } auth = rbuf; return true; } QByteArray KNTLM::getLMResponse(const QString &password, const unsigned char *challenge) { QByteArray hash, answer; hash = lmHash(password); hash.resize(21); memset(hash.data() + 16, 0, 5); answer = lmResponse(hash, challenge); hash.fill(0); return answer; } QByteArray KNTLM::lmHash(const QString &password) { QByteArray keyBytes(14, 0); QByteArray hash(16, 0); DES_KEY ks; const char *magic = "KGS!@#$%"; strncpy(keyBytes.data(), password.toUpper().toLocal8Bit().constData(), 14); convertKey((unsigned char *) keyBytes.data(), &ks); ntlm_des_ecb_encrypt(magic, 8, &ks, (unsigned char *) hash.data()); convertKey((unsigned char *) keyBytes.data() + 7, &ks); ntlm_des_ecb_encrypt(magic, 8, &ks, (unsigned char *) hash.data() + 8); keyBytes.fill(0); memset(&ks, 0, sizeof(ks)); return hash; } QByteArray KNTLM::lmResponse(const QByteArray &hash, const unsigned char *challenge) { DES_KEY ks; QByteArray answer(24, 0); convertKey((unsigned char *) hash.data(), &ks); ntlm_des_ecb_encrypt(challenge, 8, &ks, (unsigned char *) answer.data()); convertKey((unsigned char *) hash.data() + 7, &ks); ntlm_des_ecb_encrypt(challenge, 8, &ks, (unsigned char *) answer.data() + 8); convertKey((unsigned char *) hash.data() + 14, &ks); ntlm_des_ecb_encrypt(challenge, 8, &ks, (unsigned char *) answer.data() + 16); memset(&ks, 0, sizeof(ks)); return answer; } QByteArray KNTLM::getNTLMResponse(const QString &password, const unsigned char *challenge) { QByteArray hash = ntlmHash(password); hash.resize(21); memset(hash.data() + 16, 0, 5); QByteArray answer = lmResponse(hash, challenge); hash.fill(0); return answer; } QByteArray KNTLM::ntlmHash(const QString &password) { QByteArray unicode; unicode = QString2UnicodeLE(password); return QCryptographicHash::hash(unicode, QCryptographicHash::Md4); } QByteArray KNTLM::getNTLMv2Response(const QString &target, const QString &user, const QString &password, const QByteArray &targetInformation, const unsigned char *challenge) { QByteArray hash = ntlmv2Hash(target, user, password); QByteArray blob = createBlob(targetInformation); return lmv2Response(hash, blob, challenge); } QByteArray KNTLM::getLMv2Response(const QString &target, const QString &user, const QString &password, const unsigned char *challenge) { QByteArray hash = ntlmv2Hash(target, user, password); QByteArray clientChallenge(8, 0); for (uint i = 0; i < 8; i++) { clientChallenge.data() [i] = KRandom::random() % 0xff; } return lmv2Response(hash, clientChallenge, challenge); } QByteArray KNTLM::ntlmv2Hash(const QString &target, const QString &user, const QString &password) { const QByteArray hash = ntlmHash(password); const QByteArray key = QString2UnicodeLE(user.toUpper() + target); return hmacMD5(key, hash); } QByteArray KNTLM::lmv2Response(const QByteArray &hash, const QByteArray &clientData, const unsigned char *challenge) { QByteArray data(8 + clientData.size(), 0); memcpy(data.data(), challenge, 8); memcpy(data.data() + 8, clientData.data(), clientData.size()); QByteArray mac = hmacMD5(data, hash); mac.resize(16 + clientData.size()); memcpy(mac.data() + 16, clientData.data(), clientData.size()); return mac; } diff --git a/src/kntlm/kntlm.h b/src/kntlm/kntlm.h index 10f7ef4c..d9cfc652 100644 --- a/src/kntlm/kntlm.h +++ b/src/kntlm/kntlm.h @@ -1,222 +1,222 @@ /* This file is part of the KDE libraries. Copyright (c) 2004 Szombathelyi György 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. */ #ifndef KNTLM_H #define KNTLM_H -#include -#include +#include +#include #include "kntlm_export.h" /** * @short KNTLM class implements the NTLM authentication protocol. * * The KNTLM class is useful for creating the authentication structures which * can be used for various servers which implements NTLM type authentication. * A comprehensive description of the NTLM authentication protocol can be found * at http://davenport.sourceforge.net/ntlm.html * The class also contains methods to create the LanManager and NT (MD4) hashes * of a password. * This class doesn't maintain any state information, so all methods are static. */ class KNTLM_EXPORT KNTLM { public: enum Flags { Negotiate_Unicode = 0x00000001, Negotiate_OEM = 0x00000002, Request_Target = 0x00000004, Negotiate_Sign = 0x00000010, Negotiate_Seal = 0x00000020, Negotiate_Datagram_Style = 0x00000040, Negotiate_LM_Key = 0x00000080, Negotiate_Netware = 0x00000100, Negotiate_NTLM = 0x00000200, Negotiate_Domain_Supplied = 0x00001000, Negotiate_WS_Supplied = 0x00002000, Negotiate_Local_Call = 0x00004000, Negotiate_Always_Sign = 0x00008000, Target_Type_Domain = 0x00010000, Target_Type_Server = 0x00020000, Target_Type_Share = 0x00040000, Negotiate_NTLM2_Key = 0x00080000, Request_Init_Response = 0x00100000, Request_Accept_Response = 0x00200000, Request_NonNT_Key = 0x00400000, Negotiate_Target_Info = 0x00800000, Negotiate_128 = 0x20000000, Negotiate_Key_Exchange = 0x40000000, Negotiate_56 = 0x80000000 }; enum AuthFlag { Force_V1 = 0x1, Force_V2 = 0x2, Add_LM = 0x4 }; Q_DECLARE_FLAGS(AuthFlags, AuthFlag) typedef struct { quint16 len; quint16 maxlen; quint32 offset; } SecBuf; /** * The NTLM Type 1 structure */ typedef struct { char signature[8]; /* "NTLMSSP\0" */ quint32 msgType; /* 1 */ quint32 flags; SecBuf domain; SecBuf workstation; } Negotiate; /** * The NTLM Type 2 structure */ typedef struct { char signature[8]; quint32 msgType; /* 2 */ SecBuf targetName; quint32 flags; quint8 challengeData[8]; quint32 context[2]; SecBuf targetInfo; } Challenge; /** * The NTLM Type 3 structure */ typedef struct { char signature[8]; quint32 msgType; /* 3 */ SecBuf lmResponse; SecBuf ntResponse; SecBuf domain; SecBuf user; SecBuf workstation; SecBuf sessionKey; quint32 flags; } Auth; typedef struct { quint32 signature; quint32 reserved; quint64 timestamp; quint8 challenge[8]; quint8 unknown[4]; //Target info block - variable length } Blob; /** * Creates the initial message (type 1) which should be sent to the server. * * @param negotiate - a buffer where the Type 1 message will returned. * @param domain - the domain name which should be send with the message. * @param workstation - the workstation name which should be send with the message. * @param flags - various flags, in most cases the defaults will good. * * @return true if creating the structure succeeds, false otherwise. */ static bool getNegotiate(QByteArray &negotiate, const QString &domain = QString(), const QString &workstation = QString(), quint32 flags = Negotiate_Unicode | Request_Target | Negotiate_NTLM); /** * Creates the type 3 message which should be sent to the server after * the challenge (type 2) received. * * @param auth - a buffer where the Type 3 message will returned. * @param challenge - the Type 2 message returned by the server. * @param user - user's name. * @param password - user's password. * @param domain - the target domain. If left NULL (i.e. QString()), it will be extracted * from the challenge. If set to an empty string (QString("")) an empty domain will be used. * @param workstation - the user's workstation. * @param authflags - AuthFlags flags that changes the response generation behavior. * Force_V1 or Force_V2 forces (NT)LMv1 or (NT)LMv2 responses generation, otherwise it's * autodetected from the challenge. Add_LM adds LMv1 or LMv2 responses additional to the * NTLM response. * * @return true if auth filled with the Type 3 message, false if an error occurred * (challenge data invalid, NTLMv2 authentication forced, but the challenge data says * no NTLMv2 supported, or no NTLM supported at all, and Add_LM not specified). */ static bool getAuth(QByteArray &auth, const QByteArray &challenge, const QString &user, const QString &password, const QString &domain = QString(), const QString &workstation = QString(), AuthFlags authflags = Add_LM); /** * Returns the LanManager response from the password and the server challenge. */ static QByteArray getLMResponse(const QString &password, const unsigned char *challenge); /** * Calculates the LanManager hash of the specified password. */ static QByteArray lmHash(const QString &password); /** * Calculates the LanManager response from the LanManager hash and the server challenge. */ static QByteArray lmResponse(const QByteArray &hash, const unsigned char *challenge); /** * Returns the NTLM response from the password and the server challenge. */ static QByteArray getNTLMResponse(const QString &password, const unsigned char *challenge); /** * Returns the NTLM hash (MD4) from the password. */ static QByteArray ntlmHash(const QString &password); /** * Calculates the NTLMv2 response. */ static QByteArray getNTLMv2Response(const QString &target, const QString &user, const QString &password, const QByteArray &targetInformation, const unsigned char *challenge); /** * Calculates the LMv2 response. */ static QByteArray getLMv2Response(const QString &target, const QString &user, const QString &password, const unsigned char *challenge); /** * Returns the NTLMv2 hash. */ static QByteArray ntlmv2Hash(const QString &target, const QString &user, const QString &password); /** * Calculates the LMv2 response. */ static QByteArray lmv2Response(const QByteArray &hash, const QByteArray &clientData, const unsigned char *challenge); }; Q_DECLARE_OPERATORS_FOR_FLAGS(KNTLM::AuthFlags) #endif /* KNTLM_H */ diff --git a/src/kpac/discovery.cpp b/src/kpac/discovery.cpp index cf6a6ab2..43a47c5f 100644 --- a/src/kpac/discovery.cpp +++ b/src/kpac/discovery.cpp @@ -1,147 +1,147 @@ /* Copyright (c) 2003 Malte Starostik 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 //#include #include #include #if HAVE_SYS_TYPES_H #include #endif #if HAVE_NETINET_IN_H #include #endif #include #if HAVE_ARPA_NAMESER8_COMPAT_H #include #else #if HAVE_ARPA_NAMESER_COMPAT_H #include #endif #endif #if HAVE_SYS_PARAM_H // Basically, the BSDs need this before resolv.h #include #endif #include #include -#include -#include -#include +#include +#include +#include #include #include #include #include "moc_discovery.cpp" namespace KPAC { Discovery::Discovery(QObject *parent) : Downloader(parent), m_helper(new QProcess(this)) { m_helper->setProcessChannelMode(QProcess::SeparateChannels); connect(m_helper, SIGNAL(readyReadStandardOutput()), SLOT(helperOutput())); connect(m_helper, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(failed())); m_helper->start(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/kpac_dhcp_helper", QStringList()); if (!m_helper->waitForStarted()) { QTimer::singleShot(0, this, SLOT(failed())); } } bool Discovery::initDomainName() { m_domainName = QHostInfo::localDomainName(); return !m_domainName.isEmpty(); } bool Discovery::checkDomain() const { // If a domain has a SOA record, don't traverse any higher. // Returns true if no SOA can be found (domain is "ok" to use) // Stick to old resolver interface for portability reasons. union { HEADER header; unsigned char buf[ PACKETSZ ]; } response; int len = res_query(m_domainName.toLocal8Bit(), C_IN, T_SOA, response.buf, sizeof(response.buf)); if (len <= int(sizeof(response.header)) || ntohs(response.header.ancount) != 1) { return true; } unsigned char *pos = response.buf + sizeof(response.header); unsigned char *end = response.buf + len; // skip query section pos += dn_skipname(pos, end) + QFIXEDSZ; if (pos >= end) { return true; } // skip answer domain pos += dn_skipname(pos, end); short type; GETSHORT(type, pos); return type != T_SOA; } void Discovery::failed() { setError(i18n("Could not find a usable proxy configuration script")); // If this is the first DNS query, initialize our host name or abort // on failure. Otherwise abort if the current domain (which was already // queried for a host called "wpad" contains a SOA record) const bool firstQuery = m_domainName.isEmpty(); if ((firstQuery && !initDomainName()) || (!firstQuery && !checkDomain())) { emit result(false); return; } const int dot = m_domainName.indexOf('.'); if (dot > -1 || firstQuery) { QString address(QStringLiteral("http://wpad.")); address += m_domainName; address += QLatin1String("/wpad.dat"); if (dot > -1) { m_domainName.remove(0, dot + 1); // remove one domain level } download(QUrl(address)); return; } emit result(false); } void Discovery::helperOutput() { m_helper->disconnect(this); const QByteArray line = m_helper->readLine(); const QUrl url(QString::fromLocal8Bit(line.constData(), line.length()).trimmed()); download(url); } } diff --git a/src/kpac/downloader.cpp b/src/kpac/downloader.cpp index d2f45eea..f5270aca 100644 --- a/src/kpac/downloader.cpp +++ b/src/kpac/downloader.cpp @@ -1,101 +1,101 @@ /* Copyright (c) 2003 Malte Starostik 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 "downloader.h" #include #include -#include +#include #include #include namespace KPAC { Downloader::Downloader(QObject *parent) : QObject(parent) { } void Downloader::download(const QUrl &url) { m_data.resize(0); m_script.clear(); m_scriptURL = url; KIO::TransferJob *job = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo); connect(job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(data(KIO::Job*,QByteArray))); connect(job, SIGNAL(redirection(KIO::Job*,QUrl)), SLOT(redirection(KIO::Job*,QUrl))); connect(job, SIGNAL(result(KJob*)), SLOT(result(KJob*))); } void Downloader::failed() { emit result(false); } void Downloader::setError(const QString &error) { m_error = error; } void Downloader::redirection(KIO::Job *, const QUrl &url) { m_scriptURL = url; } void Downloader::data(KIO::Job *, const QByteArray &data) { unsigned offset = m_data.size(); m_data.resize(offset + data.size()); std::memcpy(m_data.data() + offset, data.data(), data.size()); } static bool hasErrorPage(KJob *job) { KIO::TransferJob *tJob = qobject_cast(job); return (tJob && tJob->isErrorPage()); } void Downloader::result(KJob *job) { if (!job->error() && !hasErrorPage(job)) { const QString charset = static_cast(job)->queryMetaData(QStringLiteral("charset")); QTextCodec *codec = QTextCodec::codecForName(charset.toLatin1()); if (!codec) { codec = QTextCodec::codecForUtfText(m_data); Q_ASSERT(codec); } m_script = codec->toUnicode(m_data); emit result(true); } else { if (job->error()) setError(i18n("Could not download the proxy configuration script:\n%1", job->errorString())); else { setError(i18n("Could not download the proxy configuration script")); // error page } failed(); } } } diff --git a/src/kpac/downloader.h b/src/kpac/downloader.h index 70f3867b..fd56b296 100644 --- a/src/kpac/downloader.h +++ b/src/kpac/downloader.h @@ -1,76 +1,76 @@ /* Copyright (c) 2003 Malte Starostik 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. */ #ifndef KPAC_DOWNLOADER_H #define KPAC_DOWNLOADER_H -#include +#include #include class KJob; namespace KIO { class Job; } namespace KPAC { class Downloader : public QObject { Q_OBJECT public: Downloader(QObject *); void download(const QUrl &); const QUrl &scriptUrl() { return m_scriptURL; } const QString &script() { return m_script; } const QString &error() { return m_error; } Q_SIGNALS: void result(bool); protected: virtual void failed(); void setError(const QString &); private Q_SLOTS: void redirection(KIO::Job *, const QUrl &); void data(KIO::Job *, const QByteArray &); void result(KJob *); private: QByteArray m_data; QUrl m_scriptURL; QString m_script; QString m_error; }; } #endif // KPAC_DOWNLOADER_H diff --git a/src/kpac/proxyscout.cpp b/src/kpac/proxyscout.cpp index 8b5b8d56..47471742 100644 --- a/src/kpac/proxyscout.cpp +++ b/src/kpac/proxyscout.cpp @@ -1,363 +1,363 @@ /* Copyright (c) 2003 Malte Starostik Copyright (c) 2011 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 "proxyscout.h" #include "config-kpac.h" #include "discovery.h" #include "script.h" #include #include #include #include #include #ifdef HAVE_KF5NOTIFICATIONS #include #endif #include -#include +#include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(ProxyScoutFactory, "proxyscout.json", registerPlugin();) namespace KPAC { enum ProxyType { Unknown = -1, Proxy, Socks, Direct }; static ProxyType proxyTypeFor(const QString &mode) { if (mode.compare(QLatin1String("PROXY"), Qt::CaseInsensitive) == 0) { return Proxy; } if (mode.compare(QLatin1String("DIRECT"), Qt::CaseInsensitive) == 0) { return Direct; } if (mode.compare(QLatin1String("SOCKS"), Qt::CaseInsensitive) == 0 || mode.compare(QLatin1String("SOCKS5"), Qt::CaseInsensitive) == 0) { return Socks; } return Unknown; } ProxyScout::QueuedRequest::QueuedRequest(const QDBusMessage &reply, const QUrl &u, bool sendall) : transaction(reply), url(u), sendAll(sendall) { } ProxyScout::ProxyScout(QObject *parent, const QList &) : KDEDModule(parent), m_componentName(QStringLiteral("proxyscout")), m_downloader(nullptr), m_script(nullptr), m_suspendTime(0), m_watcher(nullptr), m_networkConfig(new QNetworkConfigurationManager(this)) { connect(m_networkConfig, SIGNAL(configurationChanged(QNetworkConfiguration)), SLOT(disconnectNetwork(QNetworkConfiguration))); } ProxyScout::~ProxyScout() { delete m_script; } QStringList ProxyScout::proxiesForUrl(const QString &checkUrl, const QDBusMessage &msg) { QUrl url(checkUrl); if (m_suspendTime) { if (std::time(nullptr) - m_suspendTime < 300) { return QStringList(QStringLiteral("DIRECT")); } m_suspendTime = 0; } // Never use a proxy for the script itself if (m_downloader && url.matches(m_downloader->scriptUrl(), QUrl::StripTrailingSlash)) { return QStringList(QStringLiteral("DIRECT")); } if (m_script) { return handleRequest(url); } if (m_downloader || startDownload()) { msg.setDelayedReply(true); m_requestQueue.append(QueuedRequest(msg, url, true)); return QStringList(); // return value will be ignored } return QStringList(QStringLiteral("DIRECT")); } QString ProxyScout::proxyForUrl(const QString &checkUrl, const QDBusMessage &msg) { QUrl url(checkUrl); if (m_suspendTime) { if (std::time(nullptr) - m_suspendTime < 300) { return QStringLiteral("DIRECT"); } m_suspendTime = 0; } // Never use a proxy for the script itself if (m_downloader && url.matches(m_downloader->scriptUrl(), QUrl::StripTrailingSlash)) { return QStringLiteral("DIRECT"); } if (m_script) { return handleRequest(url).first(); } if (m_downloader || startDownload()) { msg.setDelayedReply(true); m_requestQueue.append(QueuedRequest(msg, url)); return QString(); // return value will be ignored } return QStringLiteral("DIRECT"); } void ProxyScout::blackListProxy(const QString &proxy) { m_blackList[ proxy ] = std::time(nullptr); } void ProxyScout::reset() { delete m_script; m_script = nullptr; delete m_downloader; m_downloader = nullptr; delete m_watcher; m_watcher = nullptr; m_blackList.clear(); m_suspendTime = 0; KProtocolManager::reparseConfiguration(); } bool ProxyScout::startDownload() { switch (KProtocolManager::proxyType()) { case KProtocolManager::WPADProxy: if (m_downloader && !qobject_cast(m_downloader)) { delete m_downloader; m_downloader = nullptr; } if (!m_downloader) { m_downloader = new Discovery(this); connect(m_downloader, SIGNAL(result(bool)), this, SLOT(downloadResult(bool))); } break; case KProtocolManager::PACProxy: { if (m_downloader && !qobject_cast(m_downloader)) { delete m_downloader; m_downloader = nullptr; } if (!m_downloader) { m_downloader = new Downloader(this); connect(m_downloader, SIGNAL(result(bool)), this, SLOT(downloadResult(bool))); } const QUrl url(KProtocolManager::proxyConfigScript()); if (url.isLocalFile()) { if (!m_watcher) { m_watcher = new QFileSystemWatcher(this); connect(m_watcher, SIGNAL(fileChanged(QString)), SLOT(proxyScriptFileChanged(QString))); } proxyScriptFileChanged(url.path()); } else { delete m_watcher; m_watcher = nullptr; m_downloader->download(url); } break; } default: return false; } return true; } void ProxyScout::disconnectNetwork(const QNetworkConfiguration &config) { // NOTE: We only care of Defined state because we only want //to redo WPAD when a network interface is brought out of //hibernation or restarted for whatever reason. if (config.state() == QNetworkConfiguration::Defined) { reset(); } } void ProxyScout::downloadResult(bool success) { if (success) { try { if (!m_script) { m_script = new Script(m_downloader->script()); } } catch (const Script::Error &e) { qWarning() << "Error:" << e.message(); #ifdef HAVE_KF5NOTIFICATIONS KNotification *notify = new KNotification(QStringLiteral("script-error")); notify->setText(i18n("The proxy configuration script is invalid:\n%1", e.message())); notify->setComponentName(m_componentName); notify->sendEvent(); #endif success = false; } } else { #ifdef HAVE_KF5NOTIFICATIONS KNotification *notify = new KNotification(QStringLiteral("download-error")); notify->setText(m_downloader->error()); notify->setComponentName(m_componentName); notify->sendEvent(); #endif } if (success) { for (RequestQueue::Iterator it = m_requestQueue.begin(), itEnd = m_requestQueue.end(); it != itEnd; ++it) { if ((*it).sendAll) { const QVariant result(handleRequest((*it).url)); QDBusConnection::sessionBus().send((*it).transaction.createReply(result)); } else { const QVariant result(handleRequest((*it).url).first()); QDBusConnection::sessionBus().send((*it).transaction.createReply(result)); } } } else { for (RequestQueue::Iterator it = m_requestQueue.begin(), itEnd = m_requestQueue.end(); it != itEnd; ++it) { QDBusConnection::sessionBus().send((*it).transaction.createReply(QLatin1String("DIRECT"))); } } m_requestQueue.clear(); // Suppress further attempts for 5 minutes if (!success) { m_suspendTime = std::time(nullptr); } } void ProxyScout::proxyScriptFileChanged(const QString &path) { // Should never get called if we do not have a watcher... Q_ASSERT(m_watcher); // Remove the current file being watched... if (!m_watcher->files().isEmpty()) { m_watcher->removePaths(m_watcher->files()); } // NOTE: QFileSystemWatcher only adds a path if it either exists or // is not already being monitored. m_watcher->addPath(path); // Reload... m_downloader->download(QUrl::fromLocalFile(path)); } QStringList ProxyScout::handleRequest(const QUrl &url) { try { QStringList proxyList; const QString result = m_script->evaluate(url).trimmed(); const QStringList proxies = result.split(QLatin1Char(';'), QString::SkipEmptyParts); const int size = proxies.count(); for (int i = 0; i < size; ++i) { QString mode, address; const QString proxy = proxies.at(i).trimmed(); const int index = proxy.indexOf(QLatin1Char(' ')); if (index == -1) { // Only "DIRECT" should match this! mode = proxy; address = proxy; } else { mode = proxy.left(index); address = proxy.mid(index + 1).trimmed(); } const ProxyType type = proxyTypeFor(mode); if (type == Unknown) { continue; } if (type == Proxy || type == Socks) { const int index = address.indexOf(QLatin1Char(':')); if (index == -1 || !KProtocolInfo::isKnownProtocol(address.left(index))) { const QString protocol((type == Proxy ? QStringLiteral("http://") : QStringLiteral("socks://"))); const QUrl url(protocol + address); if (url.isValid()) { address = url.toString(); } else { continue; } } } if (type == Direct || !m_blackList.contains(address)) { proxyList << address; } else if (std::time(nullptr) - m_blackList[address] > 1800) { // 30 minutes // black listing expired m_blackList.remove(address); proxyList << address; } } if (!proxyList.isEmpty()) { // qDebug() << proxyList; return proxyList; } // FIXME: blacklist } catch (const Script::Error &e) { qCritical() << e.message(); #ifdef HAVE_KF5NOTIFICATIONS KNotification *n = new KNotification(QStringLiteral("evaluation-error")); n->setText(i18n("The proxy configuration script returned an error:\n%1", e.message())); n->setComponentName(m_componentName); n->sendEvent(); #endif } return QStringList(QStringLiteral("DIRECT")); } } #include "proxyscout.moc" diff --git a/src/kpac/proxyscout.h b/src/kpac/proxyscout.h index 14709b05..fe970d71 100644 --- a/src/kpac/proxyscout.h +++ b/src/kpac/proxyscout.h @@ -1,85 +1,85 @@ /* Copyright (c) 2003 Malte Starostik 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. */ #ifndef KPAC_PROXYSCOUT_H #define KPAC_PROXYSCOUT_H #include #include -#include +#include #include class QFileSystemWatcher; class QNetworkConfiguration; class QNetworkConfigurationManager; namespace KPAC { class Downloader; class Script; class ProxyScout : public KDEDModule { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.KPAC.ProxyScout") public: ProxyScout(QObject *parent, const QList &); virtual ~ProxyScout(); public Q_SLOTS: Q_SCRIPTABLE QString proxyForUrl(const QString &checkUrl, const QDBusMessage &); Q_SCRIPTABLE QStringList proxiesForUrl(const QString &checkUrl, const QDBusMessage &); Q_SCRIPTABLE Q_NOREPLY void blackListProxy(const QString &proxy); Q_SCRIPTABLE Q_NOREPLY void reset(); private Q_SLOTS: void disconnectNetwork(const QNetworkConfiguration &config); void downloadResult(bool); void proxyScriptFileChanged(const QString &); private: bool startDownload(); QStringList handleRequest(const QUrl &url); QString m_componentName; Downloader *m_downloader; Script *m_script; struct QueuedRequest { QueuedRequest() {} QueuedRequest(const QDBusMessage &, const QUrl &, bool sendall = false); QDBusMessage transaction; QUrl url; bool sendAll; }; typedef QList< QueuedRequest > RequestQueue; RequestQueue m_requestQueue; typedef QMap< QString, qint64 > BlackList; BlackList m_blackList; qint64 m_suspendTime; QFileSystemWatcher *m_watcher; QNetworkConfigurationManager *m_networkConfig; }; } #endif // KPAC_PROXYSCOUT_H diff --git a/src/kpac/script.cpp b/src/kpac/script.cpp index 57105770..3fc01106 100644 --- a/src/kpac/script.cpp +++ b/src/kpac/script.cpp @@ -1,776 +1,776 @@ /* Copyright (c) 2003 Malte Starostik Copyright (c) 2011 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 "script.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include -#include -#include -#include +#include +#include +#include -#include -#include -#include -#include +#include +#include +#include +#include #include #include #define QL1S(x) QLatin1String(x) namespace { static int findString(const QString &s, const char *const *values) { int index = 0; const QString lower = s.toLower(); for (const char *const *p = values; *p; ++p, ++index) { if (s.compare(QLatin1String(*p), Qt::CaseInsensitive) == 0) { return index; } } return -1; } static const QDateTime getTime(QScriptContext *context) { const QString tz = context->argument(context->argumentCount() - 1).toString(); if (tz.compare(QLatin1String("gmt"), Qt::CaseInsensitive) == 0) { return QDateTime::currentDateTimeUtc(); } return QDateTime::currentDateTime(); } template static bool checkRange(T value, T min, T max) { return ((min <= max && value >= min && value <= max) || (min > max && (value <= min || value >= max))); } static bool isLocalHostAddress(const QHostAddress &address) { if (address == QHostAddress::LocalHost) { return true; } if (address == QHostAddress::LocalHostIPv6) { return true; } return false; } static bool isIPv6Address(const QHostAddress &address) { return address.protocol() == QAbstractSocket::IPv6Protocol; } static bool isIPv4Address(const QHostAddress &address) { return (address.protocol() == QAbstractSocket::IPv4Protocol); } static bool isSpecialAddress(const QHostAddress &address) { // Catch all the special addresses and return false. if (address == QHostAddress::Null) { return true; } if (address == QHostAddress::Any) { return true; } if (address == QHostAddress::AnyIPv6) { return true; } if (address == QHostAddress::Broadcast) { return true; } return false; } static bool addressLessThanComparison(const QHostAddress &addr1, const QHostAddress &addr2) { if (addr1.protocol() == QAbstractSocket::IPv4Protocol && addr2.protocol() == QAbstractSocket::IPv4Protocol) { return addr1.toIPv4Address() < addr2.toIPv4Address(); } if (addr1.protocol() == QAbstractSocket::IPv6Protocol && addr2.protocol() == QAbstractSocket::IPv6Protocol) { const Q_IPV6ADDR ipv6addr1 = addr1.toIPv6Address(); const Q_IPV6ADDR ipv6addr2 = addr2.toIPv6Address(); for (int i = 0; i < 16; ++i) { if (ipv6addr1[i] != ipv6addr2[i]) { return ((ipv6addr1[i] & 0xff) - (ipv6addr2[i] & 0xff)); } } } return false; } static QString addressListToString(const QList &addressList, const QHash &actualEntryMap) { QString result; Q_FOREACH (const QHostAddress &address, addressList) { if (!result.isEmpty()) { result += QLatin1Char(';'); } result += actualEntryMap.value(address.toString()); } return result; } class Address { public: struct Error {}; static Address resolve(const QString &host) { return Address(host); } QList addresses() const { return m_addressList; } QHostAddress address() const { if (m_addressList.isEmpty()) { return QHostAddress(); } return m_addressList.first(); } private: Address(const QString &host) { // Always try to see if it's already an IP first, to avoid Qt doing a // needless reverse lookup QHostAddress address(host); if (address.isNull()) { QHostInfo hostInfo = KIO::HostInfo::lookupCachedHostInfoFor(host); if (hostInfo.hostName().isEmpty() || hostInfo.error() != QHostInfo::NoError) { hostInfo = QHostInfo::fromName(host); KIO::HostInfo::cacheLookup(hostInfo); } m_addressList = hostInfo.addresses(); } else { m_addressList.clear(); m_addressList.append(address); } } QList m_addressList; }; // isPlainHostName(host) // @returns true if @p host doesn't contains a domain part QScriptValue IsPlainHostName(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() != 1) { return engine->undefinedValue(); } return engine->toScriptValue(context->argument(0).toString().indexOf(QLatin1Char('.')) == -1); } // dnsDomainIs(host, domain) // @returns true if the domain part of @p host matches @p domain QScriptValue DNSDomainIs(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() != 2) { return engine->undefinedValue(); } const QString host = context->argument(0).toString(); const QString domain = context->argument(1).toString(); return engine->toScriptValue(host.endsWith(domain, Qt::CaseInsensitive)); } // localHostOrDomainIs(host, fqdn) // @returns true if @p host is unqualified or equals @p fqdn QScriptValue LocalHostOrDomainIs(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() != 2) { return engine->undefinedValue(); } const QString host = context->argument(0).toString(); if (!host.contains(QLatin1Char('.'))) { return engine->toScriptValue(true); } const QString fqdn = context->argument(1).toString(); return engine->toScriptValue((host.compare(fqdn, Qt::CaseInsensitive) == 0)); } // isResolvable(host) // @returns true if host is resolvable to a IPv4 address. QScriptValue IsResolvable(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() != 1) { return engine->undefinedValue(); } try { const Address info = Address::resolve(context->argument(0).toString()); bool hasResolvableIPv4Address = false; Q_FOREACH (const QHostAddress &address, info.addresses()) { if (!isSpecialAddress(address) && isIPv4Address(address)) { hasResolvableIPv4Address = true; break; } } return engine->toScriptValue(hasResolvableIPv4Address); } catch (const Address::Error &) { return engine->toScriptValue(false); } } // isInNet(host, subnet, mask) // @returns true if the IPv4 address of host is within the specified subnet // and mask, false otherwise. QScriptValue IsInNet(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() != 3) { return engine->undefinedValue(); } try { const Address info = Address::resolve(context->argument(0).toString()); bool isInSubNet = false; QString subnetStr = context->argument(1).toString(); subnetStr += QLatin1Char('/'); subnetStr += context->argument(2).toString(); const QPair subnet = QHostAddress::parseSubnet(subnetStr); Q_FOREACH (const QHostAddress &address, info.addresses()) { if (!isSpecialAddress(address) && isIPv4Address(address) && address.isInSubnet(subnet)) { isInSubNet = true; break; } } return engine->toScriptValue(isInSubNet); } catch (const Address::Error &) { return engine->toScriptValue(false); } } // dnsResolve(host) // @returns the IPv4 address for host or an empty string if host is not resolvable. QScriptValue DNSResolve(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() != 1) { return engine->undefinedValue(); } try { const Address info = Address::resolve(context->argument(0).toString()); QString resolvedAddress(QLatin1String("")); Q_FOREACH (const QHostAddress &address, info.addresses()) { if (!isSpecialAddress(address) && isIPv4Address(address)) { resolvedAddress = address.toString(); break; } } return engine->toScriptValue(resolvedAddress); } catch (const Address::Error &) { return engine->toScriptValue(QString(QLatin1String(""))); } } // myIpAddress() // @returns the local machine's IPv4 address. Note that this will return // the address for the first interfaces that match its criteria even if the // machine has multiple interfaces. QScriptValue MyIpAddress(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount()) { return engine->undefinedValue(); } QString ipAddress; const QList addresses = QNetworkInterface::allAddresses(); Q_FOREACH (const QHostAddress& address, addresses) { if (isIPv4Address(address) && !isSpecialAddress(address) && !isLocalHostAddress(address)) { ipAddress = address.toString(); break; } } return engine->toScriptValue(ipAddress); } // dnsDomainLevels(host) // @returns the number of dots ('.') in @p host QScriptValue DNSDomainLevels(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() != 1) { return engine->undefinedValue(); } const QString host = context->argument(0).toString(); if (host.isNull()) { return engine->toScriptValue(0); } return engine->toScriptValue(host.count(QLatin1Char('.'))); } // shExpMatch(str, pattern) // @returns true if @p str matches the shell @p pattern QScriptValue ShExpMatch(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() != 2) { return engine->undefinedValue(); } QRegExp pattern(context->argument(1).toString(), Qt::CaseSensitive, QRegExp::Wildcard); return engine->toScriptValue(pattern.exactMatch(context->argument(0).toString())); } // weekdayRange(day [, "GMT" ]) // weekdayRange(day1, day2 [, "GMT" ]) // @returns true if the current day equals day or between day1 and day2 resp. // If the last argument is "GMT", GMT timezone is used, otherwise local time QScriptValue WeekdayRange(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() < 1 || context->argumentCount() > 3) { return engine->undefinedValue(); } static const char *const days[] = { "sun", "mon", "tue", "wed", "thu", "fri", "sat", nullptr }; const int d1 = findString(context->argument(0).toString(), days); if (d1 == -1) { return engine->undefinedValue(); } int d2 = findString(context->argument(1).toString(), days); if (d2 == -1) { d2 = d1; } // Adjust the days of week coming from QDateTime since it starts // counting with Monday as 1 and ends with Sunday as day 7. int dayOfWeek = getTime(context).date().dayOfWeek(); if (dayOfWeek == 7) { dayOfWeek = 0; } return engine->toScriptValue(checkRange(dayOfWeek, d1, d2)); } // dateRange(day [, "GMT" ]) // dateRange(day1, day2 [, "GMT" ]) // dateRange(month [, "GMT" ]) // dateRange(month1, month2 [, "GMT" ]) // dateRange(year [, "GMT" ]) // dateRange(year1, year2 [, "GMT" ]) // dateRange(day1, month1, day2, month2 [, "GMT" ]) // dateRange(month1, year1, month2, year2 [, "GMT" ]) // dateRange(day1, month1, year1, day2, month2, year2 [, "GMT" ]) // @returns true if the current date (GMT or local time according to // presence of "GMT" as last argument) is within the given range QScriptValue DateRange(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() < 1 || context->argumentCount() > 7) { return engine->undefinedValue(); } static const char *const months[] = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec", nullptr }; QVector values; for (int i = 0; i < context->argumentCount(); ++i) { int value = -1; if (context->argument(i).isNumber()) { value = context->argument(i).toInt32(); } else { // QDate starts counting months from 1, so we add 1 here. value = findString(context->argument(i).toString(), months) + 1; } if (value > 0) { values.append(value); } else { break; } } const QDate now = getTime(context).date(); // day1, month1, year1, day2, month2, year2 if (values.size() == 6) { const QDate d1(values[2], values[1], values[0]); const QDate d2(values[5], values[4], values[3]); return engine->toScriptValue(checkRange(now, d1, d2)); } // day1, month1, day2, month2 else if (values.size() == 4 && values[ 1 ] < 13 && values[ 3 ] < 13) { const QDate d1(now.year(), values[1], values[0]); const QDate d2(now.year(), values[3], values[2]); return engine->toScriptValue(checkRange(now, d1, d2)); } // month1, year1, month2, year2 else if (values.size() == 4) { const QDate d1(values[1], values[0], now.day()); const QDate d2(values[3], values[2], now.day()); return engine->toScriptValue(checkRange(now, d1, d2)); } // year1, year2 else if (values.size() == 2 && values[0] >= 1000 && values[1] >= 1000) { return engine->toScriptValue(checkRange(now.year(), values[0], values[1])); } // day1, day2 else if (values.size() == 2 && context->argument(0).isNumber() && context->argument(1).isNumber()) { return engine->toScriptValue(checkRange(now.day(), values[0], values[1])); } // month1, month2 else if (values.size() == 2) { return engine->toScriptValue(checkRange(now.month(), values[0], values[1])); } // year else if (values.size() == 1 && values[ 0 ] >= 1000) { return engine->toScriptValue(checkRange(now.year(), values[0], values[0])); } // day else if (values.size() == 1 && context->argument(0).isNumber()) { return engine->toScriptValue(checkRange(now.day(), values[0], values[0])); } // month else if (values.size() == 1) { return engine->toScriptValue(checkRange(now.month(), values[0], values[0])); } return engine->undefinedValue(); } // timeRange(hour [, "GMT" ]) // timeRange(hour1, hour2 [, "GMT" ]) // timeRange(hour1, min1, hour2, min2 [, "GMT" ]) // timeRange(hour1, min1, sec1, hour2, min2, sec2 [, "GMT" ]) // @returns true if the current time (GMT or local based on presence // of "GMT" argument) is within the given range QScriptValue TimeRange(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() < 1 || context->argumentCount() > 7) { return engine->undefinedValue(); } QVector values; for (int i = 0; i < context->argumentCount(); ++i) { if (!context->argument(i).isNumber()) { break; } values.append(context->argument(i).toNumber()); } const QTime now = getTime(context).time(); // hour1, min1, sec1, hour2, min2, sec2 if (values.size() == 6) { const QTime t1(values[0], values[1], values[2]); const QTime t2(values[3], values[4], values[5]); return engine->toScriptValue(checkRange(now, t1, t2)); } // hour1, min1, hour2, min2 else if (values.size() == 4) { const QTime t1(values[0], values[1]); const QTime t2(values[2], values[3]); return engine->toScriptValue(checkRange(now, t1, t2)); } // hour1, hour2 else if (values.size() == 2) { return engine->toScriptValue(checkRange(now.hour(), values[0], values[1])); } // hour else if (values.size() == 1) { return engine->toScriptValue(checkRange(now.hour(), values[0], values[0])); } return engine->undefinedValue(); } /* * Implementation of Microsoft's IPv6 Extension for PAC * * Documentation: * http://msdn.microsoft.com/en-us/library/gg308477(v=vs.85).aspx * http://msdn.microsoft.com/en-us/library/gg308478(v=vs.85).aspx * http://msdn.microsoft.com/en-us/library/gg308474(v=vs.85).aspx * http://blogs.msdn.com/b/wndp/archive/2006/07/13/ipv6-pac-extensions-v0-9.aspx */ // isResolvableEx(host) // @returns true if host is resolvable to an IPv4 or IPv6 address. QScriptValue IsResolvableEx(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() != 1) { return engine->undefinedValue(); } try { const Address info = Address::resolve(context->argument(0).toString()); bool hasResolvableIPAddress = false; Q_FOREACH (const QHostAddress &address, info.addresses()) { if (isIPv4Address(address) || isIPv6Address(address)) { hasResolvableIPAddress = true; break; } } return engine->toScriptValue(hasResolvableIPAddress); } catch (const Address::Error &) { return engine->toScriptValue(false); } } // isInNetEx(ipAddress, ipPrefix ) // @returns true if ipAddress is within the specified ipPrefix. QScriptValue IsInNetEx(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() != 2) { return engine->undefinedValue(); } try { const Address info = Address::resolve(context->argument(0).toString()); bool isInSubNet = false; const QString subnetStr = context->argument(1).toString(); const QPair subnet = QHostAddress::parseSubnet(subnetStr); Q_FOREACH (const QHostAddress &address, info.addresses()) { if (isSpecialAddress(address)) { continue; } if (address.isInSubnet(subnet)) { isInSubNet = true; break; } } return engine->toScriptValue(isInSubNet); } catch (const Address::Error &) { return engine->toScriptValue(false); } } // dnsResolveEx(host) // @returns a semi-colon delimited string containing IPv6 and IPv4 addresses // for host or an empty string if host is not resolvable. QScriptValue DNSResolveEx(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() != 1) { return engine->undefinedValue(); } try { const Address info = Address::resolve(context->argument(0).toString()); QStringList addressList; QString resolvedAddress(QLatin1String("")); Q_FOREACH (const QHostAddress &address, info.addresses()) { if (!isSpecialAddress(address)) { addressList << address.toString(); } } if (!addressList.isEmpty()) { resolvedAddress = addressList.join(QStringLiteral(";")); } return engine->toScriptValue(resolvedAddress); } catch (const Address::Error &) { return engine->toScriptValue(QString(QLatin1String(""))); } } // myIpAddressEx() // @returns a semi-colon delimited string containing all IP addresses for localhost (IPv6 and/or IPv4), // or an empty string if unable to resolve localhost to an IP address. QScriptValue MyIpAddressEx(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount()) { return engine->undefinedValue(); } QStringList ipAddressList; const QList addresses = QNetworkInterface::allAddresses(); Q_FOREACH (const QHostAddress& address, addresses) { if (!isSpecialAddress(address) && !isLocalHostAddress(address)) { ipAddressList << address.toString(); } } return engine->toScriptValue(ipAddressList.join(QStringLiteral(";"))); } // sortIpAddressList(ipAddressList) // @returns a sorted ipAddressList. If both IPv4 and IPv6 addresses are present in // the list. The sorted IPv6 addresses will precede the sorted IPv4 addresses. QScriptValue SortIpAddressList(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() != 1) { return engine->undefinedValue(); } QHash actualEntryMap; QList ipV4List, ipV6List; const QStringList ipAddressList = context->argument(0).toString().split(QLatin1Char(';')); Q_FOREACH (const QString &ipAddress, ipAddressList) { QHostAddress address(ipAddress); switch (address.protocol()) { case QAbstractSocket::IPv4Protocol: ipV4List << address; actualEntryMap.insert(address.toString(), ipAddress); break; case QAbstractSocket::IPv6Protocol: ipV6List << address; actualEntryMap.insert(address.toString(), ipAddress); break; default: break; } } QString sortedAddress(QLatin1String("")); if (!ipV6List.isEmpty()) { std::sort(ipV6List.begin(), ipV6List.end(), addressLessThanComparison); sortedAddress += addressListToString(ipV6List, actualEntryMap); } if (!ipV4List.isEmpty()) { std::sort(ipV4List.begin(), ipV4List.end(), addressLessThanComparison); if (!sortedAddress.isEmpty()) { sortedAddress += QLatin1Char(';'); } sortedAddress += addressListToString(ipV4List, actualEntryMap); } return engine->toScriptValue(sortedAddress); } // getClientVersion // @return the version number of this engine for future extension. We too start // this at version 1.0. QScriptValue GetClientVersion(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount()) { return engine->undefinedValue(); } const QString version(QStringLiteral("1.0")); return engine->toScriptValue(version); } void registerFunctions(QScriptEngine *engine) { QScriptValue value = engine->globalObject(); value.setProperty(QStringLiteral("isPlainHostName"), engine->newFunction(IsPlainHostName)); value.setProperty(QStringLiteral("dnsDomainIs"), engine->newFunction(DNSDomainIs)); value.setProperty(QStringLiteral("localHostOrDomainIs"), engine->newFunction(LocalHostOrDomainIs)); value.setProperty(QStringLiteral("isResolvable"), engine->newFunction(IsResolvable)); value.setProperty(QStringLiteral("isInNet"), engine->newFunction(IsInNet)); value.setProperty(QStringLiteral("dnsResolve"), engine->newFunction(DNSResolve)); value.setProperty(QStringLiteral("myIpAddress"), engine->newFunction(MyIpAddress)); value.setProperty(QStringLiteral("dnsDomainLevels"), engine->newFunction(DNSDomainLevels)); value.setProperty(QStringLiteral("shExpMatch"), engine->newFunction(ShExpMatch)); value.setProperty(QStringLiteral("weekdayRange"), engine->newFunction(WeekdayRange)); value.setProperty(QStringLiteral("dateRange"), engine->newFunction(DateRange)); value.setProperty(QStringLiteral("timeRange"), engine->newFunction(TimeRange)); // Microsoft's IPv6 PAC Extensions... value.setProperty(QStringLiteral("isResolvableEx"), engine->newFunction(IsResolvableEx)); value.setProperty(QStringLiteral("isInNetEx"), engine->newFunction(IsInNetEx)); value.setProperty(QStringLiteral("dnsResolveEx"), engine->newFunction(DNSResolveEx)); value.setProperty(QStringLiteral("myIpAddressEx"), engine->newFunction(MyIpAddressEx)); value.setProperty(QStringLiteral("sortIpAddressList"), engine->newFunction(SortIpAddressList)); value.setProperty(QStringLiteral("getClientVersion"), engine->newFunction(GetClientVersion)); } } namespace KPAC { Script::Script(const QString &code) { m_engine = new QScriptEngine; registerFunctions(m_engine); QScriptProgram program(code); const QScriptValue result = m_engine->evaluate(program); if (m_engine->hasUncaughtException() || result.isError()) { throw Error(m_engine->uncaughtException().toString()); } } Script::~Script() { delete m_engine; } QString Script::evaluate(const QUrl &url) { QScriptValue func = m_engine->globalObject().property(QStringLiteral("FindProxyForURL")); if (!func.isValid()) { func = m_engine->globalObject().property(QStringLiteral("FindProxyForURLEx")); if (!func.isValid()) { throw Error(i18n("Could not find 'FindProxyForURL' or 'FindProxyForURLEx'")); return QString(); } } QUrl cleanUrl = url; cleanUrl.setUserInfo(QString()); if (cleanUrl.scheme() == QLatin1String("https")) { cleanUrl.setPath(QString()); cleanUrl.setQuery(QString()); } QScriptValueList args; args << cleanUrl.url(); args << cleanUrl.host(); QScriptValue result = func.call(QScriptValue(), args); if (result.isError()) { throw Error(i18n("Got an invalid reply when calling %1", func.toString())); } return result.toString(); } } diff --git a/src/kpac/script.h b/src/kpac/script.h index 73c44706..822ab024 100644 --- a/src/kpac/script.h +++ b/src/kpac/script.h @@ -1,58 +1,58 @@ /* Copyright (c) 2003 Malte Starostik Copyright (c) 2011 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. */ #ifndef KPAC_SCRIPT_H #define KPAC_SCRIPT_H -#include +#include class QUrl; class QScriptEngine; namespace KPAC { class Script { public: class Error { public: Error(const QString &message) : m_message(message) {} const QString &message() const { return m_message; } private: QString m_message; }; Script(const QString &code); ~Script(); QString evaluate(const QUrl &); private: QScriptEngine *m_engine; }; } #endif // KPAC_SCRIPT_H diff --git a/src/kssld/kssld.cpp b/src/kssld/kssld.cpp index 046647a1..550ed432 100644 --- a/src/kssld/kssld.cpp +++ b/src/kssld/kssld.cpp @@ -1,270 +1,270 @@ /* This file is part of the KDE libraries Copyright (c) 2007, 2008, 2010 Andreas Hartmetz 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 "kssld.h" #include "ksslcertificatemanager.h" #include "kssld_adaptor.h" #include #include -#include +#include -#include +#include #include #include K_PLUGIN_FACTORY_WITH_JSON(KSSLDFactory, "kssld.json", registerPlugin();) class KSSLDPrivate { public: KSSLDPrivate() : config(QStringLiteral("ksslcertificatemanager"), KConfig::SimpleConfig) { struct strErr { const char *str; KSslError::Error err; }; //hmmm, looks like these are all of the errors where it is possible to continue. const static strErr strError[] = { {"NoError", KSslError::NoError}, {"UnknownError", KSslError::UnknownError}, {"InvalidCertificateAuthority", KSslError::InvalidCertificateAuthorityCertificate}, {"InvalidCertificate", KSslError::InvalidCertificate}, {"CertificateSignatureFailed", KSslError::CertificateSignatureFailed}, {"SelfSignedCertificate", KSslError::SelfSignedCertificate}, {"RevokedCertificate", KSslError::RevokedCertificate}, {"InvalidCertificatePurpose", KSslError::InvalidCertificatePurpose}, {"RejectedCertificate", KSslError::RejectedCertificate}, {"UntrustedCertificate", KSslError::UntrustedCertificate}, {"ExpiredCertificate", KSslError::ExpiredCertificate}, {"HostNameMismatch", KSslError::HostNameMismatch} }; for (int i = 0; i < int(sizeof(strError) / sizeof(strErr)); i++) { QString s = QString::fromLatin1(strError[i].str); KSslError::Error e = strError[i].err; stringToSslError.insert(s, e); sslErrorToString.insert(e, s); } } KConfig config; QHash stringToSslError; QHash sslErrorToString; }; KSSLD::KSSLD(QObject *parent, const QVariantList &) : KDEDModule(parent), d(new KSSLDPrivate()) { new KSSLDAdaptor(this); pruneExpiredRules(); } KSSLD::~KSSLD() { delete d; } void KSSLD::setRule(const KSslCertificateRule &rule) { if (rule.hostName().isEmpty()) { return; } KConfigGroup group = d->config.group(rule.certificate().digest().toHex()); QStringList sl; QString dtString = QStringLiteral("ExpireUTC "); dtString.append(rule.expiryDateTime().toString(Qt::ISODate)); sl.append(dtString); if (rule.isRejected()) { sl.append(QStringLiteral("Reject")); } else { foreach (KSslError::Error e, rule.ignoredErrors()) { sl.append(d->sslErrorToString.value(e)); } } if (!group.hasKey("CertificatePEM")) { group.writeEntry("CertificatePEM", rule.certificate().toPem()); } #ifdef PARANOIA else if (group.readEntry("CertificatePEM") != rule.certificate().toPem()) { return; } #endif group.writeEntry(rule.hostName(), sl); group.sync(); } void KSSLD::clearRule(const KSslCertificateRule &rule) { clearRule(rule.certificate(), rule.hostName()); } void KSSLD::clearRule(const QSslCertificate &cert, const QString &hostName) { KConfigGroup group = d->config.group(cert.digest().toHex()); group.deleteEntry(hostName); if (group.keyList().size() < 2) { group.deleteGroup(); } group.sync(); } void KSSLD::pruneExpiredRules() { // expired rules are deleted when trying to load them, so we just try to load all rules. // be careful about iterating over KConfig(Group) while changing it foreach (const QString &groupName, d->config.groupList()) { QByteArray certDigest = groupName.toLatin1(); foreach (const QString &key, d->config.group(groupName).keyList()) { if (key == QLatin1String("CertificatePEM")) { continue; } KSslCertificateRule r = rule(QSslCertificate(certDigest), key); } } } // check a domain name with subdomains for well-formedness and count the dot-separated parts static QString normalizeSubdomains(const QString &hostName, int *namePartsCount) { QString ret; int partsCount = 0; bool wasPrevDot = true; // -> allow no dot at the beginning and count first name part const int length = hostName.length(); for (int i = 0; i < length; i++) { const QChar c = hostName.at(i); if (c == QLatin1Char('.')) { if (wasPrevDot || (i + 1 == hostName.length())) { // consecutive dots or a dot at the end are forbidden partsCount = 0; ret.clear(); break; } wasPrevDot = true; } else { if (wasPrevDot) { partsCount++; } wasPrevDot = false; } ret.append(c); } *namePartsCount = partsCount; return ret; } KSslCertificateRule KSSLD::rule(const QSslCertificate &cert, const QString &hostName) const { const QByteArray certDigest = cert.digest().toHex(); KConfigGroup group = d->config.group(certDigest); KSslCertificateRule ret(cert, hostName); bool foundHostName = false; int needlePartsCount; QString needle = normalizeSubdomains(hostName, &needlePartsCount); // Find a rule for the hostname, either... if (group.hasKey(needle)) { // directly (host, site.tld, a.site.tld etc) if (needlePartsCount >= 1) { foundHostName = true; } } else { // or with wildcards // "tld" <- "*." and "site.tld" <- "*.tld" are not valid matches, // "a.site.tld" <- "*.site.tld" is while (--needlePartsCount >= 2) { const int dotIndex = needle.indexOf(QLatin1Char('.')); Q_ASSERT(dotIndex > 0); // if this fails normalizeSubdomains() failed needle.remove(0, dotIndex - 1); needle[0] = QChar::fromLatin1('*'); if (group.hasKey(needle)) { foundHostName = true; break; } needle.remove(0, 2); // remove "*." } } if (!foundHostName) { //Don't make a rule with the failed wildcard pattern - use the original hostname. return KSslCertificateRule(cert, hostName); } //parse entry of the format "ExpireUTC , Reject" or //"ExpireUTC , HostNameMismatch, ExpiredCertificate, ..." QStringList sl = group.readEntry(needle, QStringList()); QDateTime expiryDt; // the rule is well-formed if it contains at least the expire date and one directive if (sl.size() >= 2) { QString dtString = sl.takeFirst(); if (dtString.startsWith(QLatin1String("ExpireUTC "))) { dtString.remove(0, 10/* length of "ExpireUTC " */); expiryDt = QDateTime::fromString(dtString, Qt::ISODate); } } if (!expiryDt.isValid() || expiryDt < QDateTime::currentDateTime()) { //the entry is malformed or expired so we remove it group.deleteEntry(needle); //the group is useless once only the CertificatePEM entry left if (group.keyList().size() < 2) { group.deleteGroup(); } return ret; } QList ignoredErrors; bool isRejected = false; foreach (const QString &s, sl) { if (s == QLatin1String("Reject")) { isRejected = true; ignoredErrors.clear(); break; } if (!d->stringToSslError.contains(s)) { continue; } ignoredErrors.append(d->stringToSslError.value(s)); } //Everything is checked and we can make ret valid ret.setExpiryDateTime(expiryDt); ret.setRejected(isRejected); ret.setIgnoredErrors(ignoredErrors); return ret; } #include "moc_kssld.cpp" #include "moc_kssld_adaptor.cpp" #include "kssld.moc" diff --git a/src/kssld/kssld.h b/src/kssld/kssld.h index d9f315b0..3fd66506 100644 --- a/src/kssld/kssld.h +++ b/src/kssld/kssld.h @@ -1,53 +1,53 @@ /* This file is part of the KDE libraries Copyright (c) 2007, 2008, 2010 Andreas Hartmetz 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. */ #ifndef KSSLD_H #define KSSLD_H #include -#include -#include +#include +#include class QString; class QSslCertificate; class KSslCertificateRule; class KSSLDPrivate; class KSSLD : public KDEDModule { Q_OBJECT public: KSSLD(QObject *parent, const QVariantList &); ~KSSLD(); void setRule(const KSslCertificateRule &rule); void clearRule(const KSslCertificateRule &rule); void clearRule(const QSslCertificate &cert, const QString &hostName); void pruneExpiredRules(); KSslCertificateRule rule(const QSslCertificate &cert, const QString &hostName) const; private: //AFAICS we don't need the d-pointer technique here but it makes the code look //more like the rest of kdelibs and it can be reused anywhere in kdelibs. KSSLDPrivate *const d; }; #endif //KSSLD_H diff --git a/src/kssld/kssld_adaptor.h b/src/kssld/kssld_adaptor.h index de2c69ed..af537c06 100644 --- a/src/kssld/kssld_adaptor.h +++ b/src/kssld/kssld_adaptor.h @@ -1,73 +1,73 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Andreas Hartmetz 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. */ #ifndef KSSLD_ADAPTOR_H #define KSSLD_ADAPTOR_H -#include -#include -#include -#include +#include +#include +#include +#include #include "kssld_dbusmetatypes.h" class KSSLDAdaptor: public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.KSSLD") public: KSSLDAdaptor(KSSLD *parent) : QDBusAbstractAdaptor(parent) { Q_ASSERT(parent); registerMetaTypesForKSSLD(); } private: inline KSSLD *p() { return static_cast(parent()); } public Q_SLOTS: inline Q_NOREPLY void setRule(const KSslCertificateRule &rule) { return p()->setRule(rule); } inline Q_NOREPLY void clearRule__rule(const KSslCertificateRule &rule) { return p()->clearRule(rule); } inline Q_NOREPLY void clearRule__certHost(const QSslCertificate &cert, const QString &hostName) { return p()->clearRule(cert, hostName); } inline KSslCertificateRule rule(const QSslCertificate &cert, const QString &hostName) { return p()->rule(cert, hostName); } }; #endif //KSSLD_ADAPTOR_H diff --git a/src/urifilters/fixhost/fixhosturifilter.cpp b/src/urifilters/fixhost/fixhosturifilter.cpp index 8f27d77d..ca07ab51 100644 --- a/src/urifilters/fixhost/fixhosturifilter.cpp +++ b/src/urifilters/fixhost/fixhosturifilter.cpp @@ -1,95 +1,95 @@ /* fixhostfilter.cpp This file is part of the KDE project Copyright (C) 2007 Lubos Lunak Copyright (C) 2010 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, see . */ #include "fixhosturifilter.h" -#include +#include #include #define QL1S(x) QLatin1String(x) #define QL1C(x) QLatin1Char(x) /** * IMPORTANT: If you change anything here, make sure you run the kurifiltertest * regression test (this should be included as part of "make test"). */ FixHostUriFilter::FixHostUriFilter(QObject *parent, const QVariantList & /*args*/) :KUriFilterPlugin(QStringLiteral("fixhosturifilter"), parent) { } static bool isHttpUrl(const QString& scheme) { return (scheme.compare(QL1S("http"), Qt::CaseInsensitive) == 0 || scheme.compare(QL1S("https"), Qt::CaseInsensitive) == 0 || scheme.compare(QL1S("webdav"), Qt::CaseInsensitive) == 0 || scheme.compare(QL1S("webdavs"), Qt::CaseInsensitive) == 0); } static bool hasCandidateHostName(const QString& host) { return (host.contains(QL1C('.')) && !host.startsWith(QL1S("www."), Qt::CaseInsensitive)); } bool FixHostUriFilter::filterUri( KUriFilterData& data ) const { QUrl url = data.uri(); const QString protocol = url.scheme(); const bool isHttp = isHttpUrl(protocol); if (isHttp || protocol == data.defaultUrlScheme()) { const QString host = url.host(); if (hasCandidateHostName(host) && !isResolvable(host)) { if (isHttp) { url.setHost((QL1S("www.") + host)); if (exists(url.host())) { setFilteredUri(data, url); setUriType(data, KUriFilterData::NetProtocol); return true; } } } } return false; } bool FixHostUriFilter::isResolvable(const QString& host) const { // Unlike exists, this function returns true if the lookup timeout out. QHostInfo info = resolveName(host, 1500); return (info.error() == QHostInfo::NoError || info.error() == QHostInfo::UnknownError); } bool FixHostUriFilter::exists(const QString& host) const { QHostInfo info = resolveName(host, 1500); return (info.error() == QHostInfo::NoError); } K_PLUGIN_FACTORY_WITH_JSON(FixHostUriFilterFactory, "fixhosturifilter.json", registerPlugin();) #include "fixhosturifilter.moc" diff --git a/src/urifilters/ikws/ikwsopts.cpp b/src/urifilters/ikws/ikwsopts.cpp index fa7d222e..4f6893cb 100644 --- a/src/urifilters/ikws/ikwsopts.cpp +++ b/src/urifilters/ikws/ikwsopts.cpp @@ -1,452 +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 #include //BEGIN ProvidersModel ProvidersModel::~ProvidersModel() { } 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); const QList providers = m_registry.findAll(); int defaultProviderIndex = providers.size(); //default is "None", it is last in the list for (SearchProvider *provider : providers) { if (defaultSearchEngine == provider->desktopEntryName()) defaultProviderIndex = providers.size(); } 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/kuriikwsfilter.cpp b/src/urifilters/ikws/kuriikwsfilter.cpp index 8cfa3d70..3c5e9520 100644 --- a/src/urifilters/ikws/kuriikwsfilter.cpp +++ b/src/urifilters/ikws/kuriikwsfilter.cpp @@ -1,172 +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 +#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 = 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 = 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); return true; } } return false; } #include "kuriikwsfilter.moc" diff --git a/src/urifilters/ikws/kuriikwsfiltereng.cpp b/src/urifilters/ikws/kuriikwsfiltereng.cpp index 46210a3e..ccb42c61 100644 --- a/src/urifilters/ikws/kuriikwsfiltereng.cpp +++ b/src/urifilters/ikws/kuriikwsfiltereng.cpp @@ -1,449 +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 #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 = 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 { 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 = 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 eb086127..46f8b0ff 100644 --- a/src/urifilters/ikws/kuriikwsfiltereng.h +++ b/src/urifilters/ikws/kuriikwsfiltereng.h @@ -1,80 +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 +#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&) = 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 3539c813..f1e1776e 100644 --- a/src/urifilters/ikws/kurisearchfilter.cpp +++ b/src/urifilters/ikws/kurisearchfilter.cpp @@ -1,96 +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 #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(); 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/localdomain/localdomainurifilter.cpp b/src/urifilters/localdomain/localdomainurifilter.cpp index dd9cbb82..a71520b6 100644 --- a/src/urifilters/localdomain/localdomainurifilter.cpp +++ b/src/urifilters/localdomain/localdomainurifilter.cpp @@ -1,89 +1,89 @@ /* localdomainfilter.cpp This file is part of the KDE project Copyright (C) 2002 Lubos Lunak Copyright (C) 2010 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, see . */ #include "localdomainurifilter.h" #include #include -#include -#include +#include +#include #include #define QL1C(x) QLatin1Char(x) #define QL1S(x) QLatin1String(x) #define HOSTPORT_PATTERN "[a-zA-Z0-9][a-zA-Z0-9+-]*(?:\\:[0-9]{1,5})?(?:/[\\w:@&=+$,-.!~*'()]*)*" namespace { QLoggingCategory category("org.kde.kurifilter-localdomain", QtWarningMsg); } /** * IMPORTANT: If you change anything here, make sure you run the kurifiltertest * regression test (this should be included as part of "make test"). */ LocalDomainUriFilter::LocalDomainUriFilter( QObject *parent, const QVariantList & /*args*/ ) :KUriFilterPlugin( QStringLiteral("localdomainurifilter"), parent ), m_hostPortPattern(QL1S(HOSTPORT_PATTERN)) { } bool LocalDomainUriFilter::filterUri(KUriFilterData& data) const { const QUrl url = data.uri(); const QString protocol = url.scheme(); // When checking for local domain just validate it is indeed a local domain, // but do not modify the hostname! See bug# if ((protocol.isEmpty() || !KProtocolInfo::isKnownProtocol(protocol)) && m_hostPortPattern.exactMatch(data.typedString())) { QString host (data.typedString().left(data.typedString().indexOf(QL1C('/')))); const int pos = host.indexOf(QL1C(':')); if (pos > -1) host.truncate(pos); // Remove port number if (exists(host)) { qCDebug(category) << "QHostInfo found a host called" << host; QString scheme (data.defaultUrlScheme()); if (scheme.isEmpty()) scheme = QStringLiteral("http://"); setFilteredUri(data, QUrl(scheme + data.typedString())); setUriType(data, KUriFilterData::NetProtocol); return true; } } return false; } bool LocalDomainUriFilter::exists(const QString& host) const { qCDebug(category) << "Checking if a host called" << host << "exists"; QHostInfo hostInfo = resolveName (host, 1500); return (hostInfo.error() == QHostInfo::NoError); } K_PLUGIN_FACTORY_WITH_JSON(LocalDomainUriFilterFactory, "localdomainurifilter.json", registerPlugin();) #include "localdomainurifilter.moc" diff --git a/src/urifilters/localdomain/localdomainurifilter.h b/src/urifilters/localdomain/localdomainurifilter.h index 2b3269a7..0a275cf3 100644 --- a/src/urifilters/localdomain/localdomainurifilter.h +++ b/src/urifilters/localdomain/localdomainurifilter.h @@ -1,51 +1,51 @@ /* localdomainurifilter.h This file is part of the KDE project Copyright (C) 2002 Lubos Lunak 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, see . */ #ifndef LOCALDOMAINURIFILTER_H #define LOCALDOMAINURIFILTER_H #include -#include +#include class QHostInfo; class QEventLoop; /** This filter takes care of hostnames in the local search domain. If you're in domain domain.org which has a host intranet.domain.org and the typed URI is just intranet, check if there's a host intranet.domain.org and if yes, it's a network URI. */ class LocalDomainUriFilter : public KUriFilterPlugin { Q_OBJECT public: LocalDomainUriFilter( QObject* parent, const QVariantList& args ); bool filterUri( KUriFilterData &data ) const Q_DECL_OVERRIDE; private: bool exists(const QString&) const; QRegExp m_hostPortPattern; }; #endif diff --git a/src/urifilters/shorturi/kshorturifilter.cpp b/src/urifilters/shorturi/kshorturifilter.cpp index 4b28c660..a60088d8 100644 --- a/src/urifilters/shorturi/kshorturifilter.cpp +++ b/src/urifilters/shorturi/kshorturifilter.cpp @@ -1,567 +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 #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... 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); 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" diff --git a/src/urifilters/shorturi/kshorturifilter.h b/src/urifilters/shorturi/kshorturifilter.h index 23fa0116..9db3db9f 100644 --- a/src/urifilters/shorturi/kshorturifilter.h +++ b/src/urifilters/shorturi/kshorturifilter.h @@ -1,108 +1,108 @@ /* 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. */ #ifndef KSHORTURIFILTER_H #define KSHORTURIFILTER_H -#include -#include -#include +#include +#include +#include #include /** * This is short URL filter class. * * @short A filter that converts short URLs into fully qualified ones. * * @author Dawit Alemayehu * @author Malte Starostik */ class KShortUriFilter : public KUriFilterPlugin { Q_OBJECT public: /** * Creates a Short URI filter object * * @param parent the parent of this class. * @param name the internal name for this object. */ explicit KShortUriFilter( QObject *parent = nullptr, const QVariantList &args = QVariantList() ); /** * Destructor */ virtual ~KShortUriFilter() {} /** * Converts short URIs into fully qualified valid URIs * whenever possible. * * Parses any given invalid URI to determine whether it * is a known short URI and converts it to its fully * qualified version. * * @param data the data to be filtered * @return true if the url has been filtered */ bool filterUri( KUriFilterData &data ) const Q_DECL_OVERRIDE; /** * Returns the name of the config module for * this plugin. * * @return the name of the config module. */ QString configName() const Q_DECL_OVERRIDE; /** * Returns an instance of the module used to configure * this object. * * @return the config module */ KCModule* configModule( QWidget*, const char* ) const Q_DECL_OVERRIDE; public Q_SLOTS: void configure(); private: struct URLHint { URLHint() {} URLHint( QString r, QString p, KUriFilterData::UriTypes t = KUriFilterData::NetProtocol ) : regexp(QRegExp(r)), prepend(p), type(t) {} QRegExp regexp; // if this matches, then... QString prepend; // ...prepend this to the url KUriFilterData::UriTypes type; }; QList m_urlHints; QString m_strDefaultUrlScheme; }; #endif diff --git a/src/widgets/accessmanager.cpp b/src/widgets/accessmanager.cpp index 60c19e7b..0ea9e152 100644 --- a/src/widgets/accessmanager.cpp +++ b/src/widgets/accessmanager.cpp @@ -1,585 +1,585 @@ /* * This file is part of the KDE project. * * Copyright (C) 2009 - 2012 Dawit Alemayehu * Copyright (C) 2008 - 2009 Urs Wolfer * Copyright (C) 2007 Trolltech ASA * * 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 "accessmanager.h" #include "accessmanagerreply_p.h" #include "job.h" #include "kjobwidgets.h" #include "scheduler.h" #include "kio_widgets_debug.h" #include #include #include #include -#include +#include #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #define QL1S(x) QLatin1String(x) #define QL1C(x) QLatin1Char(x) static const QNetworkRequest::Attribute gSynchronousNetworkRequestAttribute = QNetworkRequest::SynchronousRequestAttribute; static qint64 sizeFromRequest(const QNetworkRequest &req) { const QVariant size = req.header(QNetworkRequest::ContentLengthHeader); if (!size.isValid()) { return -1; } bool ok = false; const qlonglong value = size.toLongLong(&ok); return (ok ? value : -1); } namespace KIO { class Q_DECL_HIDDEN AccessManager::AccessManagerPrivate { public: AccessManagerPrivate() : externalContentAllowed(true), emitReadyReadOnMetaDataChange(false), window(nullptr) {} void setMetaDataForRequest(QNetworkRequest request, KIO::MetaData &metaData); bool externalContentAllowed; bool emitReadyReadOnMetaDataChange; KIO::MetaData requestMetaData; KIO::MetaData sessionMetaData; QPointer window; }; namespace Integration { class Q_DECL_HIDDEN CookieJar::CookieJarPrivate { public: CookieJarPrivate() : windowId((WId) - 1), isEnabled(true), isStorageDisabled(false) {} WId windowId; bool isEnabled; bool isStorageDisabled; }; } } using namespace KIO; AccessManager::AccessManager(QObject *parent) : QNetworkAccessManager(parent), d(new AccessManager::AccessManagerPrivate()) { // KDE Cookiejar (KCookieJar) integration... setCookieJar(new KIO::Integration::CookieJar); } AccessManager::~AccessManager() { delete d; } void AccessManager::setExternalContentAllowed(bool allowed) { d->externalContentAllowed = allowed; } bool AccessManager::isExternalContentAllowed() const { return d->externalContentAllowed; } #ifndef KIOWIDGETS_NO_DEPRECATED void AccessManager::setCookieJarWindowId(WId id) { QWidget *window = QWidget::find(id); if (!window) { return; } KIO::Integration::CookieJar *jar = qobject_cast (cookieJar()); if (jar) { jar->setWindowId(id); } d->window = window->isWindow() ? window : window->window(); } #endif void AccessManager::setWindow(QWidget *widget) { if (!widget) { return; } d->window = widget->isWindow() ? widget : widget->window(); if (!d->window) { return; } KIO::Integration::CookieJar *jar = qobject_cast (cookieJar()); if (jar) { jar->setWindowId(d->window->winId()); } } #ifndef KIOWIDGETS_NO_DEPRECATED WId AccessManager::cookieJarWindowid() const { KIO::Integration::CookieJar *jar = qobject_cast (cookieJar()); if (jar) { return jar->windowId(); } return 0; } #endif QWidget *AccessManager::window() const { return d->window; } KIO::MetaData &AccessManager::requestMetaData() { return d->requestMetaData; } KIO::MetaData &AccessManager::sessionMetaData() { return d->sessionMetaData; } void AccessManager::putReplyOnHold(QNetworkReply *reply) { KDEPrivate::AccessManagerReply *r = qobject_cast(reply); if (!r) { return; } r->putOnHold(); } void AccessManager::setEmitReadyReadOnMetaDataChange(bool enable) { d->emitReadyReadOnMetaDataChange = enable; } QNetworkReply *AccessManager::createRequest(Operation op, const QNetworkRequest &req, QIODevice *outgoingData) { const QUrl reqUrl(req.url()); if (!d->externalContentAllowed && !KDEPrivate::AccessManagerReply::isLocalRequest(reqUrl) && reqUrl.scheme() != QL1S("data")) { //qDebug() << "Blocked: " << reqUrl; return new KDEPrivate::AccessManagerReply(op, req, QNetworkReply::ContentAccessDenied, i18n("Blocked request."), this); } // Check if the internal ignore content disposition header is set. const bool ignoreContentDisposition = req.hasRawHeader("x-kdewebkit-ignore-disposition"); // Retrieve the KIO meta data... KIO::MetaData metaData; d->setMetaDataForRequest(req, metaData); KIO::SimpleJob *kioJob = nullptr; switch (op) { case HeadOperation: { //qDebug() << "HeadOperation:" << reqUrl; kioJob = KIO::mimetype(reqUrl, KIO::HideProgressInfo); break; } case GetOperation: { //qDebug() << "GetOperation:" << reqUrl; if (!reqUrl.path().isEmpty() || reqUrl.host().isEmpty()) { kioJob = KIO::storedGet(reqUrl, KIO::NoReload, KIO::HideProgressInfo); } else { kioJob = KIO::stat(reqUrl, KIO::HideProgressInfo); } // WORKAROUND: Avoid the brain damaged stuff QtWebKit does when a POST // operation is redirected! See BR# 268694. metaData.remove(QStringLiteral("content-type")); // Remove the content-type from a GET/HEAD request! break; } case PutOperation: { //qDebug() << "PutOperation:" << reqUrl; if (outgoingData) { Q_ASSERT(outgoingData->isReadable()); StoredTransferJob* storedJob = KIO::storedPut(outgoingData, reqUrl, -1, KIO::HideProgressInfo); storedJob->setAsyncDataEnabled(outgoingData->isSequential()); QVariant len = req.header(QNetworkRequest::ContentLengthHeader); if (len.isValid()) { storedJob->setTotalSize(len.toInt()); } kioJob = storedJob; } else { kioJob = KIO::put(reqUrl, -1, KIO::HideProgressInfo); } break; } case PostOperation: { kioJob = KIO::storedHttpPost(outgoingData, reqUrl, sizeFromRequest(req), KIO::HideProgressInfo); if (!metaData.contains(QStringLiteral("content-type"))) { const QVariant header = req.header(QNetworkRequest::ContentTypeHeader); if (header.isValid()) { metaData.insert(QStringLiteral("content-type"), (QStringLiteral("Content-Type: ") + header.toString())); } else { metaData.insert(QStringLiteral("content-type"), QStringLiteral("Content-Type: application/x-www-form-urlencoded")); } } break; } case DeleteOperation: { //qDebug() << "DeleteOperation:" << reqUrl; kioJob = KIO::http_delete(reqUrl, KIO::HideProgressInfo); break; } case CustomOperation: { const QByteArray &method = req.attribute(QNetworkRequest::CustomVerbAttribute).toByteArray(); //qDebug() << "CustomOperation:" << reqUrl << "method:" << method << "outgoing data:" << outgoingData; if (method.isEmpty()) { return new KDEPrivate::AccessManagerReply(op, req, QNetworkReply::ProtocolUnknownError, i18n("Unknown HTTP verb."), this); } const qint64 size = sizeFromRequest(req); if (size > 0) { kioJob = KIO::http_post(reqUrl, outgoingData, size, KIO::HideProgressInfo); } else { kioJob = KIO::get(reqUrl, KIO::NoReload, KIO::HideProgressInfo); } metaData.insert(QStringLiteral("CustomHTTPMethod"), method); break; } default: { qCWarning(KIO_WIDGETS) << "Unsupported KIO operation requested! Defering to QNetworkAccessManager..."; return QNetworkAccessManager::createRequest(op, req, outgoingData); } } // Set the job priority switch (req.priority()) { case QNetworkRequest::HighPriority: KIO::Scheduler::setJobPriority(kioJob, -5); break; case QNetworkRequest::LowPriority: KIO::Scheduler::setJobPriority(kioJob, 5); break; default: break; } KDEPrivate::AccessManagerReply *reply; /* NOTE: Here we attempt to handle synchronous XHR requests. Unfortunately, due to the fact that QNAM is both synchronous and multi-thread while KIO is completely the opposite (asynchronous and not thread safe), the code below might cause crashes like the one reported in bug# 287778 (nested event loops are inherently dangerous). Unfortunately, all attempts to address the crash has so far failed due to the many regressions they caused, e.g. bug# 231932 and 297954. Hence, until a solution is found, we have to live with the side effects of creating nested event loops. */ if (req.attribute(gSynchronousNetworkRequestAttribute).toBool()) { KJobWidgets::setWindow(kioJob, d->window); kioJob->setRedirectionHandlingEnabled(true); if (kioJob->exec()) { QByteArray data; if (StoredTransferJob *storedJob = qobject_cast< KIO::StoredTransferJob * >(kioJob)) { data = storedJob->data(); } reply = new KDEPrivate::AccessManagerReply(op, req, data, kioJob->url(), kioJob->metaData(), this); //qDebug() << "Synchronous XHR:" << reply << reqUrl; } else { qCWarning(KIO_WIDGETS) << "Failed to create a synchronous XHR for" << reqUrl; qCWarning(KIO_WIDGETS) << "REASON:" << kioJob->errorString(); reply = new KDEPrivate::AccessManagerReply(op, req, QNetworkReply::UnknownNetworkError, kioJob->errorText(), this); } } else { // Set the window on the KIO ui delegate if (d->window) { KJobWidgets::setWindow(kioJob, d->window); } // Disable internal automatic redirection handling kioJob->setRedirectionHandlingEnabled(false); // Set the job priority switch (req.priority()) { case QNetworkRequest::HighPriority: KIO::Scheduler::setJobPriority(kioJob, -5); break; case QNetworkRequest::LowPriority: KIO::Scheduler::setJobPriority(kioJob, 5); break; default: break; } // Set the meta data for this job... kioJob->setMetaData(metaData); // Create the reply... reply = new KDEPrivate::AccessManagerReply(op, req, kioJob, d->emitReadyReadOnMetaDataChange, this); //qDebug() << reply << reqUrl; } if (ignoreContentDisposition && reply) { //qDebug() << "Content-Disposition WILL BE IGNORED!"; reply->setIgnoreContentDisposition(ignoreContentDisposition); } return reply; } void AccessManager::AccessManagerPrivate::setMetaDataForRequest(QNetworkRequest request, KIO::MetaData &metaData) { // Add any meta data specified within request... QVariant userMetaData = request.attribute(static_cast(MetaData)); if (userMetaData.isValid() && userMetaData.type() == QVariant::Map) { metaData += userMetaData.toMap(); } metaData.insert(QStringLiteral("PropagateHttpHeader"), QStringLiteral("true")); if (request.hasRawHeader("User-Agent")) { metaData.insert(QStringLiteral("UserAgent"), request.rawHeader("User-Agent")); request.setRawHeader("User-Agent", QByteArray()); } if (request.hasRawHeader("Accept")) { metaData.insert(QStringLiteral("accept"), request.rawHeader("Accept")); request.setRawHeader("Accept", QByteArray()); } if (request.hasRawHeader("Accept-Charset")) { metaData.insert(QStringLiteral("Charsets"), request.rawHeader("Accept-Charset")); request.setRawHeader("Accept-Charset", QByteArray()); } if (request.hasRawHeader("Accept-Language")) { metaData.insert(QStringLiteral("Languages"), request.rawHeader("Accept-Language")); request.setRawHeader("Accept-Language", QByteArray()); } if (request.hasRawHeader("Referer")) { metaData.insert(QStringLiteral("referrer"), request.rawHeader("Referer")); request.setRawHeader("Referer", QByteArray()); } if (request.hasRawHeader("Content-Type")) { metaData.insert(QStringLiteral("content-type"), request.rawHeader("Content-Type")); request.setRawHeader("Content-Type", QByteArray()); } if (request.attribute(QNetworkRequest::AuthenticationReuseAttribute) == QNetworkRequest::Manual) { metaData.insert(QStringLiteral("no-preemptive-auth-reuse"), QStringLiteral("true")); } request.setRawHeader("Content-Length", QByteArray()); request.setRawHeader("Connection", QByteArray()); request.setRawHeader("If-None-Match", QByteArray()); request.setRawHeader("If-Modified-Since", QByteArray()); request.setRawHeader("x-kdewebkit-ignore-disposition", QByteArray()); QStringList customHeaders; Q_FOREACH (const QByteArray &key, request.rawHeaderList()) { const QByteArray value = request.rawHeader(key); if (value.length()) { customHeaders << (key + QStringLiteral(": ") + value); } } if (!customHeaders.isEmpty()) { metaData.insert(QStringLiteral("customHTTPHeader"), customHeaders.join(QStringLiteral("\r\n"))); } // Append per request meta data, if any... if (!requestMetaData.isEmpty()) { metaData += requestMetaData; // Clear per request meta data... requestMetaData.clear(); } // Append per session meta data, if any... if (!sessionMetaData.isEmpty()) { metaData += sessionMetaData; } } using namespace KIO::Integration; static QSsl::SslProtocol qSslProtocolFromString(const QString &str) { if (str.compare(QStringLiteral("SSLv3"), Qt::CaseInsensitive) == 0) { return QSsl::SslV3; } if (str.compare(QStringLiteral("SSLv2"), Qt::CaseInsensitive) == 0) { return QSsl::SslV2; } if (str.compare(QStringLiteral("TLSv1"), Qt::CaseInsensitive) == 0) { return QSsl::TlsV1_0; } return QSsl::AnyProtocol; } bool KIO::Integration::sslConfigFromMetaData(const KIO::MetaData &metadata, QSslConfiguration &sslconfig) { bool success = false; if (metadata.value(QStringLiteral("ssl_in_use")) == QStringLiteral("TRUE")) { const QSsl::SslProtocol sslProto = qSslProtocolFromString(metadata.value(QStringLiteral("ssl_protocol_version"))); QList cipherList; cipherList << QSslCipher(metadata.value(QStringLiteral("ssl_cipher_name")), sslProto); sslconfig.setCaCertificates(QSslCertificate::fromData(metadata.value(QStringLiteral("ssl_peer_chain")).toUtf8())); sslconfig.setCiphers(cipherList); sslconfig.setProtocol(sslProto); success = sslconfig.isNull(); } return success; } CookieJar::CookieJar(QObject *parent) : QNetworkCookieJar(parent), d(new CookieJar::CookieJarPrivate) { reparseConfiguration(); } CookieJar::~CookieJar() { delete d; } WId CookieJar::windowId() const { return d->windowId; } bool CookieJar::isCookieStorageDisabled() const { return d->isStorageDisabled; } QList CookieJar::cookiesForUrl(const QUrl &url) const { QList cookieList; if (!d->isEnabled) { return cookieList; } QDBusInterface kcookiejar(QStringLiteral("org.kde.kcookiejar5"), QStringLiteral("/modules/kcookiejar"), QStringLiteral("org.kde.KCookieServer")); QDBusReply reply = kcookiejar.call(QStringLiteral("findDOMCookies"), url.toString(QUrl::RemoveUserInfo), (qlonglong)d->windowId); if (!reply.isValid()) { qCWarning(KIO_WIDGETS) << "Unable to communicate with the cookiejar!"; return cookieList; } const QString cookieStr = reply.value(); const QStringList cookies = cookieStr.split(QStringLiteral("; "), QString::SkipEmptyParts); Q_FOREACH (const QString &cookie, cookies) { const int index = cookie.indexOf(QL1C('=')); const QString name = cookie.left(index); const QString value = cookie.right((cookie.length() - index - 1)); cookieList << QNetworkCookie(name.toUtf8(), value.toUtf8()); //qDebug() << "cookie: name=" << name << ", value=" << value; } return cookieList; } bool CookieJar::setCookiesFromUrl(const QList &cookieList, const QUrl &url) { if (!d->isEnabled) { return false; } QDBusInterface kcookiejar(QStringLiteral("org.kde.kcookiejar5"), QStringLiteral("/modules/kcookiejar"), QStringLiteral("org.kde.KCookieServer")); Q_FOREACH (const QNetworkCookie &cookie, cookieList) { QByteArray cookieHeader("Set-Cookie: "); if (d->isStorageDisabled && !cookie.isSessionCookie()) { QNetworkCookie sessionCookie(cookie); sessionCookie.setExpirationDate(QDateTime()); cookieHeader += sessionCookie.toRawForm(); } else { cookieHeader += cookie.toRawForm(); } kcookiejar.call(QStringLiteral("addCookies"), url.toString(QUrl::RemoveUserInfo), cookieHeader, (qlonglong)d->windowId); //qDebug() << "[" << d->windowId << "]" << cookieHeader << " from " << url; } return !kcookiejar.lastError().isValid(); } void CookieJar::setDisableCookieStorage(bool disable) { d->isStorageDisabled = disable; } void CookieJar::setWindowId(WId id) { d->windowId = id; } void CookieJar::reparseConfiguration() { KConfigGroup cfg = KSharedConfig::openConfig(QStringLiteral("kcookiejarrc"), KConfig::NoGlobals)->group("Cookie Policy"); d->isEnabled = cfg.readEntry("Cookies", true); } diff --git a/src/widgets/accessmanager.h b/src/widgets/accessmanager.h index 4ed18fa2..416202bf 100644 --- a/src/widgets/accessmanager.h +++ b/src/widgets/accessmanager.h @@ -1,368 +1,368 @@ /* * This file is part of the KDE project. * * Copyright (C) 2008 - 2009 Urs Wolfer * Copyright (C) 2009 - 2012 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. * */ #ifndef KIO_ACCESSMANAGER_H #define KIO_ACCESSMANAGER_H #include #include "kiowidgets_export.h" #include // WId -#include -#include -#include +#include +#include +#include class QWidget; namespace KIO { /** * @class KIO::AccessManager accessmanager.h * * @short A KDE implementation of QNetworkAccessManager. * * Use this class instead of QNetworkAccessManager if you want to integrate * with KDE's KIO and KCookieJar modules for network operations and cookie * handling respectively. * * Here is a simple example that shows how to set the QtWebKit module to use KDE's * KIO for its network operations: * @code * QWebView *view = new QWebView(this); * KIO::Integration::AccessManager *manager = new KIO::Integration::AccessManager(view); * view->page()->setNetworkAccessManager(manager); * @endcode * * To access member functions in the cookiejar class at a later point in your * code simply downcast the pointer returned by QWebPage::networkAccessManager * as follows: * @code * KIO::Integration::AccessManager *manager = qobject_cast(view->page()->accessManager()); * @endcode * * Please note that this class is in the KIO namespace for backward compatablity. * You should use KIO::Integration::AccessManager to access this class in your * code. * * IMPORTANTThis class is not a replacement for the standard KDE API. * It should ONLY be used to provide KDE integration in applications that * cannot use the standard KDE API directly. * * @author Urs Wolfer \ * @author Dawit Alemayehu \ * * @deprecated Use the KIO::Integration::AccessManager typedef to access this class instead. * @since 4.3 */ class KIOWIDGETS_EXPORT AccessManager : public QNetworkAccessManager { Q_OBJECT public: /*! * Extensions to QNetworkRequest::Attribute enums. * @since 4.3.2 */ enum Attribute { MetaData = QNetworkRequest::User, /** < Used to send KIO MetaData back and forth. type: QVariant::Map. */ KioError /**< Used to send KIO error codes that cannot be mapped into QNetworkReply::NetworkError. type: QVariant::Int */ }; /** * Constructor */ AccessManager(QObject *parent); /** * Destructor */ virtual ~AccessManager(); /** * Set @p allowed to false if you don't want any external content to be fetched. * By default external content is fetched. */ void setExternalContentAllowed(bool allowed); /** * Returns true if external content is going to be fetched. * * @see setExternalContentAllowed */ bool isExternalContentAllowed() const; /** * Sets the cookiejar's window id to @p id. * * This is a convenience function that allows you to set the cookiejar's * window id. Note that this function does nothing unless the cookiejar in * use is of type KIO::Integration::CookieJar. * * By default the cookiejar's window id is set to false. Make sure you call * this function and set the window id to its proper value when create an * instance of this object. Otherwise, the KDE cookiejar will not be able * to properly manage session based cookies. * * @see KIO::Integration::CookieJar::setWindowId. * @since 4.4 * @deprecated Use setWindow */ #ifndef KIOWIDGETS_NO_DEPRECATED KIOWIDGETS_DEPRECATED void setCookieJarWindowId(WId id); #endif /** * Sets the window associated with this network access manager. * * Note that @p widget will be used as a parent for dialogs in KIO as well * as the cookie jar. If @p widget is not a window, this function will * invoke @ref QWidget::window() to obtain the window for the given widget. * * @see KIO::Integration::CookieJar::setWindow. * @since 4.7 */ void setWindow(QWidget *widget); /** * Returns the cookiejar's window id. * * This is a convenience function that returns the window id associated * with the cookiejar. Note that this function will return a 0 if the * cookiejar is not of type KIO::Integration::CookieJar or a window id * has not yet been set. * * @see KIO::Integration::CookieJar::windowId. * @since 4.4 * @deprecated Use KIO::Integration::CookieJar::windowId */ #ifndef KIOWIDGETS_NO_DEPRECATED KIOWIDGETS_DEPRECATED WId cookieJarWindowid() const; #endif /** * Returns the window associated with this network access manager. * * @see setWindow * @since 4.7 */ QWidget *window() const; /** * Returns a reference to the temporary meta data container. * * See kdelibs/kio/DESIGN.metadata for list of supported KIO meta data. * * Use this function when you want to set per request KIO meta data that * will be removed after it has been sent once. * * @since 4.4 */ KIO::MetaData &requestMetaData(); /** * Returns a reference to the persistent meta data container. * * See kdelibs/kio/DESIGN.metadata for list of supported KIO meta data. * * Use this function when you want to set per session KIO meta data that * will be sent with every request. * * Unlike @p requestMetaData, the meta data values set using the reference * returned by this function will not be deleted and will be sent with every * request. * * @since 4.4 */ KIO::MetaData &sessionMetaData(); /** * Puts the ioslave associated with the given @p reply on hold. * * This function is intended to make possible the implementation of * the special case mentioned in KIO::get's documentation within the * KIO-QNAM integration. * * @see KIO::get. * @since 4.6 */ static void putReplyOnHold(QNetworkReply *reply); /** * Sets the network reply object to emit readyRead when it receives meta data. * * Meta data is any information that is not the actual content itself, e.g. * HTTP response headers of the HTTP protocol. * * Calling this function will force the code connecting to QNetworkReply's * readyRead signal to prematurely start dealing with the content that might * not yet have arrived. However, it is essential to make the put ioslave on * hold functionality of KIO work in libraries like QtWebKit. * * @see QNetworkReply::metaDataChanged * @since 4.7 */ void setEmitReadyReadOnMetaDataChange(bool); protected: /** * Reimplemented for internal reasons, the API is not affected. * * @see QNetworkAccessManager::createRequest * @internal */ QNetworkReply *createRequest(Operation op, const QNetworkRequest &req, QIODevice *outgoingData = nullptr) Q_DECL_OVERRIDE; private: class AccessManagerPrivate; AccessManagerPrivate *const d; }; namespace Integration { // KDE5: Move AccessManager into the KIO::Integration namespace. typedef KIO::AccessManager AccessManager; /** * Maps KIO SSL meta data into the given QSslConfiguration object. * * @since 4.5 * @return true if @p metadata contains ssl information and the mapping succeeded. */ KIOWIDGETS_EXPORT bool sslConfigFromMetaData(const KIO::MetaData &metadata, QSslConfiguration &sslconfig); /** * @class KIO::CookieJar accessmanager.h * * @short A KDE implementation of QNetworkCookieJar. * * Use this class in place of QNetworkCookieJar if you want to integrate with * KDE's cookiejar instead of the one that comes with Qt. * * Here is a simple example that shows how to set the QtWebKit module to use KDE's * cookiejar: * @code * QWebView *view = new QWebView(this); * KIO::Integration::CookieJar *cookieJar = new KIO::Integration::CookieJar; * cookieJar->setWindowId(view->window()->winId()); * view->page()->networkAccessManager()->setCookieJar(cookieJar); * @endcode * * To access member functions in the cookiejar class at a later point in your * code simply downcast the pointer returned by QNetworkAccessManager::cookieJar * as follows: * @code * KIO::Integration::CookieJar *cookieJar = qobject_cast(view->page()->accessManager()->cookieJar()); * @endcode * * IMPORTANTThis class is not a replacement for the standard KDE API. * It should ONLY be used to provide KDE integration in applications that * cannot use the standard KDE API directly. * * @see QNetworkAccessManager::setCookieJar for details. * * @author Dawit Alemayehu \ * @since 4.4 */ class KIOWIDGETS_EXPORT CookieJar : public QNetworkCookieJar { Q_OBJECT public: /** * Constructs a KNetworkCookieJar with parent @p parent. */ explicit CookieJar(QObject *parent = nullptr); /** * Destroys the KNetworkCookieJar. */ ~CookieJar(); /** * Returns the currently set window id. The default value is -1. */ WId windowId() const; /** * Sets the window id of the application. * * This value is used by KDE's cookiejar to manage session cookies, namely * to delete them when the last application referring to such cookies is * closed by the end user. * * @see QWidget::window() * @see QWidget::winId() * * @param id the value of @ref QWidget::winId() from the window that contains your widget. */ void setWindowId(WId id); /** * Reparse the KDE cookiejar configuration file. */ void reparseConfiguration(); /** * Reimplemented for internal reasons, the API is not affected. * * @see QNetworkCookieJar::cookiesForUrl * @internal */ QList cookiesForUrl(const QUrl &url) const Q_DECL_OVERRIDE; /** * Reimplemented for internal reasons, the API is not affected. * * @see QNetworkCookieJar::setCookiesFromUrl * @internal */ bool setCookiesFromUrl(const QList &cookieList, const QUrl &url) Q_DECL_OVERRIDE; /** * Returns true if persistent caching of cookies is disabled. * * @see setDisableCookieStorage * @since 4.6 */ bool isCookieStorageDisabled() const; /** * Prevent persistent storage of cookies. * * Call this function if you do not want cookies to be stored locally for * later access without disabling the cookiejar. All cookies will be discarded * once the sessions that are using the cookie are done. * * @since 4.6 */ void setDisableCookieStorage(bool disable); private: class CookieJarPrivate; CookieJarPrivate *const d; }; } } #endif // KIO_ACCESSMANAGER_H diff --git a/src/widgets/accessmanagerreply_p.cpp b/src/widgets/accessmanagerreply_p.cpp index 7b738263..066ccda8 100644 --- a/src/widgets/accessmanagerreply_p.cpp +++ b/src/widgets/accessmanagerreply_p.cpp @@ -1,491 +1,491 @@ /* * This file is part of the KDE project. * * Copyright (C) 2008 Alex Merry * Copyright (C) 2008 - 2009 Urs Wolfer * Copyright (C) 2009 - 2012 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 "accessmanagerreply_p.h" #include "accessmanager.h" #include "job.h" #include "scheduler.h" #include "kio_widgets_debug.h" #include #include #include -#include +#include #define QL1S(x) QLatin1String(x) #define QL1C(x) QLatin1Char(x) namespace KDEPrivate { AccessManagerReply::AccessManagerReply(const QNetworkAccessManager::Operation op, const QNetworkRequest &request, KIO::SimpleJob *kioJob, bool emitReadyReadOnMetaDataChange, QObject *parent) : QNetworkReply(parent), m_metaDataRead(false), m_ignoreContentDisposition(false), m_emitReadyReadOnMetaDataChange(emitReadyReadOnMetaDataChange), m_kioJob(kioJob) { setRequest(request); setOpenMode(QIODevice::ReadOnly); setUrl(request.url()); setOperation(op); setError(NoError, QString()); if (!request.sslConfiguration().isNull()) { setSslConfiguration(request.sslConfiguration()); } connect(kioJob, SIGNAL(redirection(KIO::Job*,QUrl)), SLOT(slotRedirection(KIO::Job*,QUrl))); connect(kioJob, SIGNAL(percent(KJob*,ulong)), SLOT(slotPercent(KJob*,ulong))); if (qobject_cast(kioJob)) { connect(kioJob, SIGNAL(result(KJob*)), SLOT(slotStatResult(KJob*))); } else { connect(kioJob, SIGNAL(result(KJob*)), SLOT(slotResult(KJob*))); connect(kioJob, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotData(KIO::Job*,QByteArray))); connect(kioJob, SIGNAL(mimetype(KIO::Job*,QString)), SLOT(slotMimeType(KIO::Job*,QString))); } } AccessManagerReply::AccessManagerReply(const QNetworkAccessManager::Operation op, const QNetworkRequest &request, const QByteArray &data, const QUrl &url, const KIO::MetaData &metaData, QObject *parent) : QNetworkReply(parent), m_data(data), m_ignoreContentDisposition(false), m_emitReadyReadOnMetaDataChange(false) { setRequest(request); setOpenMode(QIODevice::ReadOnly); setUrl((url.isValid() ? url : request.url())); setOperation(op); setHeaderFromMetaData(metaData); if (!request.sslConfiguration().isNull()) { setSslConfiguration(request.sslConfiguration()); } setError(NoError, QString()); emitFinished(true, Qt::QueuedConnection); } AccessManagerReply::AccessManagerReply(const QNetworkAccessManager::Operation op, const QNetworkRequest &request, QNetworkReply::NetworkError errorCode, const QString &errorMessage, QObject *parent) : QNetworkReply(parent) { setRequest(request); setOpenMode(QIODevice::ReadOnly); setUrl(request.url()); setOperation(op); setError(static_cast(errorCode), errorMessage); if (error() != QNetworkReply::NoError) { QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, Q_ARG(QNetworkReply::NetworkError, error())); } emitFinished(true, Qt::QueuedConnection); } AccessManagerReply::~AccessManagerReply() { } void AccessManagerReply::abort() { if (m_kioJob) { m_kioJob.data()->disconnect(this); } m_kioJob.clear(); m_data.clear(); m_metaDataRead = false; } qint64 AccessManagerReply::bytesAvailable() const { return (QNetworkReply::bytesAvailable() + m_data.length()); } qint64 AccessManagerReply::readData(char *data, qint64 maxSize) { const qint64 length = qMin(qint64(m_data.length()), maxSize); if (length) { memcpy(data, m_data.constData(), length); m_data.remove(0, length); } return length; } bool AccessManagerReply::ignoreContentDisposition(const KIO::MetaData &metaData) { if (m_ignoreContentDisposition) { return true; } if (!metaData.contains(QStringLiteral("content-disposition-type"))) { return true; } bool ok = false; const int statusCode = attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(&ok); if (!ok || statusCode < 200 || statusCode > 299) { return true; } return false; } void AccessManagerReply::setHeaderFromMetaData(const KIO::MetaData &_metaData) { if (_metaData.isEmpty()) { return; } KIO::MetaData metaData(_metaData); // Set the encryption attribute and values... QSslConfiguration sslConfig; const bool isEncrypted = KIO::Integration::sslConfigFromMetaData(metaData, sslConfig); setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, isEncrypted); if (isEncrypted) { setSslConfiguration(sslConfig); } // Set the raw header information... const QStringList httpHeaders(metaData.value(QStringLiteral("HTTP-Headers")).split(QL1C('\n'), QString::SkipEmptyParts)); if (httpHeaders.isEmpty()) { if (metaData.contains(QStringLiteral("charset"))) { QString mimeType = header(QNetworkRequest::ContentTypeHeader).toString(); mimeType += QStringLiteral(" ; charset="); mimeType += metaData.value(QStringLiteral("charset")); //qDebug() << "changed content-type to" << mimeType; setHeader(QNetworkRequest::ContentTypeHeader, mimeType.toUtf8()); } } else { Q_FOREACH (const QString &httpHeader, httpHeaders) { int index = httpHeader.indexOf(QL1C(':')); // Handle HTTP status line... if (index == -1) { // Except for the status line, all HTTP header must be an nvpair of // type ":" if (!httpHeader.startsWith(QLatin1String("HTTP/"), Qt::CaseInsensitive)) { continue; } QStringList statusLineAttrs(httpHeader.split(QL1C(' '), QString::SkipEmptyParts)); if (statusLineAttrs.count() > 1) { setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusLineAttrs.at(1)); } if (statusLineAttrs.count() > 2) { setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, statusLineAttrs.at(2)); } continue; } const QString headerName = httpHeader.left(index); QString headerValue = httpHeader.mid(index + 1); // Ignore cookie header since it is handled by the http ioslave. if (headerName.startsWith(QLatin1String("set-cookie"), Qt::CaseInsensitive)) { continue; } if (headerName.startsWith(QLatin1String("content-disposition"), Qt::CaseInsensitive) && ignoreContentDisposition(metaData)) { continue; } // Without overridding the corrected mime-type sent by kio_http, add // back the "charset=" portion of the content-type header if present. if (headerName.startsWith(QLatin1String("content-type"), Qt::CaseInsensitive)) { QString mimeType(header(QNetworkRequest::ContentTypeHeader).toString()); if (m_ignoreContentDisposition) { // If the server returned application/octet-stream, try to determine the // real content type from the disposition filename. if (mimeType == QStringLiteral("application/octet-stream")) { const QString fileName(metaData.value(QStringLiteral("content-disposition-filename"))); QMimeDatabase db; QMimeType mime = db.mimeTypeForFile((fileName.isEmpty() ? url().path() : fileName), QMimeDatabase::MatchExtension); mimeType = mime.name(); } metaData.remove(QStringLiteral("content-disposition-type")); metaData.remove(QStringLiteral("content-disposition-filename")); } if (!headerValue.contains(mimeType, Qt::CaseInsensitive)) { index = headerValue.indexOf(QL1C(';')); if (index == -1) { headerValue = mimeType; } else { headerValue.replace(0, index, mimeType); } //qDebug() << "Changed mime-type from" << mimeType << "to" << headerValue; } } setRawHeader(headerName.trimmed().toUtf8(), headerValue.trimmed().toUtf8()); } } // Set the returned meta data as attribute... setAttribute(static_cast(KIO::AccessManager::MetaData), metaData.toVariant()); } void AccessManagerReply::setIgnoreContentDisposition(bool on) { //qDebug() << on; m_ignoreContentDisposition = on; } void AccessManagerReply::putOnHold() { if (!m_kioJob || isFinished()) { return; } //qDebug() << m_kioJob << m_data; m_kioJob.data()->disconnect(this); m_kioJob.data()->putOnHold(); m_kioJob.clear(); KIO::Scheduler::publishSlaveOnHold(); } bool AccessManagerReply::isLocalRequest(const QUrl &url) { const QString scheme(url.scheme()); return (KProtocolInfo::isKnownProtocol(scheme) && KProtocolInfo::protocolClass(scheme).compare(QStringLiteral(":local"), Qt::CaseInsensitive) == 0); } void AccessManagerReply::readHttpResponseHeaders(KIO::Job *job) { if (!job || m_metaDataRead) { return; } KIO::MetaData metaData(job->metaData()); if (metaData.isEmpty()) { // Allow handling of local resources such as man pages and file url... if (isLocalRequest(url())) { setHeader(QNetworkRequest::ContentLengthHeader, job->totalAmount(KJob::Bytes)); setAttribute(QNetworkRequest::HttpStatusCodeAttribute, "200"); emit metaDataChanged(); } return; } setHeaderFromMetaData(metaData); m_metaDataRead = true; emit metaDataChanged(); } int AccessManagerReply::jobError(KJob *kJob) { const int errCode = kJob->error(); switch (errCode) { case 0: break; // No error; case KIO::ERR_SLAVE_DEFINED: case KIO::ERR_NO_CONTENT: // Sent by a 204 response is not an error condition. setError(QNetworkReply::NoError, kJob->errorText()); //qDebug() << "0 -> QNetworkReply::NoError"; break; case KIO::ERR_IS_DIRECTORY: // This error condition can happen if you click on an ftp link that points // to a directory instead of a file, e.g. ftp://ftp.kde.org/pub setHeader(QNetworkRequest::ContentTypeHeader, "inode/directory"); setError(QNetworkReply::NoError, kJob->errorText()); break; case KIO::ERR_CANNOT_CONNECT: setError(QNetworkReply::ConnectionRefusedError, kJob->errorText()); //qDebug() << "KIO::ERR_CANNOT_CONNECT -> QNetworkReply::ConnectionRefusedError"; break; case KIO::ERR_UNKNOWN_HOST: setError(QNetworkReply::HostNotFoundError, kJob->errorText()); //qDebug() << "KIO::ERR_UNKNOWN_HOST -> QNetworkReply::HostNotFoundError"; break; case KIO::ERR_SERVER_TIMEOUT: setError(QNetworkReply::TimeoutError, kJob->errorText()); //qDebug() << "KIO::ERR_SERVER_TIMEOUT -> QNetworkReply::TimeoutError"; break; case KIO::ERR_USER_CANCELED: case KIO::ERR_ABORTED: setError(QNetworkReply::OperationCanceledError, kJob->errorText()); //qDebug() << "KIO::ERR_ABORTED -> QNetworkReply::OperationCanceledError"; break; case KIO::ERR_UNKNOWN_PROXY_HOST: setError(QNetworkReply::ProxyNotFoundError, kJob->errorText()); //qDebug() << "KIO::UNKNOWN_PROXY_HOST -> QNetworkReply::ProxyNotFoundError"; break; case KIO::ERR_ACCESS_DENIED: setError(QNetworkReply::ContentAccessDenied, kJob->errorText()); //qDebug() << "KIO::ERR_ACCESS_DENIED -> QNetworkReply::ContentAccessDenied"; break; case KIO::ERR_WRITE_ACCESS_DENIED: setError(QNetworkReply::ContentOperationNotPermittedError, kJob->errorText()); //qDebug() << "KIO::ERR_WRITE_ACCESS_DENIED -> QNetworkReply::ContentOperationNotPermittedError"; break; case KIO::ERR_DOES_NOT_EXIST: setError(QNetworkReply::ContentNotFoundError, kJob->errorText()); //qDebug() << "KIO::ERR_DOES_NOT_EXIST -> QNetworkReply::ContentNotFoundError"; break; case KIO::ERR_CANNOT_AUTHENTICATE: setError(QNetworkReply::AuthenticationRequiredError, kJob->errorText()); //qDebug() << "KIO::ERR_CANNOT_AUTHENTICATE -> QNetworkReply::AuthenticationRequiredError"; break; case KIO::ERR_UNSUPPORTED_PROTOCOL: case KIO::ERR_NO_SOURCE_PROTOCOL: setError(QNetworkReply::ProtocolUnknownError, kJob->errorText()); //qDebug() << "KIO::ERR_UNSUPPORTED_PROTOCOL -> QNetworkReply::ProtocolUnknownError"; break; case KIO::ERR_CONNECTION_BROKEN: setError(QNetworkReply::RemoteHostClosedError, kJob->errorText()); //qDebug() << "KIO::ERR_CONNECTION_BROKEN -> QNetworkReply::RemoteHostClosedError"; break; case KIO::ERR_UNSUPPORTED_ACTION: setError(QNetworkReply::ProtocolInvalidOperationError, kJob->errorText()); //qDebug() << "KIO::ERR_UNSUPPORTED_ACTION -> QNetworkReply::ProtocolInvalidOperationError"; break; default: setError(QNetworkReply::UnknownNetworkError, kJob->errorText()); //qDebug() << KIO::rawErrorDetail(errCode, QString()) << "-> QNetworkReply::UnknownNetworkError"; } return errCode; } void AccessManagerReply::slotData(KIO::Job *kioJob, const QByteArray &data) { Q_UNUSED(kioJob); m_data += data; if (!data.isEmpty()) { emit readyRead(); } } void AccessManagerReply::slotMimeType(KIO::Job *kioJob, const QString &mimeType) { //qDebug() << kioJob << mimeType; setHeader(QNetworkRequest::ContentTypeHeader, mimeType.toUtf8()); readHttpResponseHeaders(kioJob); if (m_emitReadyReadOnMetaDataChange) { emit readyRead(); } } void AccessManagerReply::slotResult(KJob *kJob) { const int errcode = jobError(kJob); const QUrl redirectUrl = attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); if (!redirectUrl.isValid()) { setAttribute(static_cast(KIO::AccessManager::KioError), errcode); if (errcode && errcode != KIO::ERR_NO_CONTENT) { emit error(error()); } } // Make sure HTTP response headers are always set. if (!m_metaDataRead) { readHttpResponseHeaders(qobject_cast(kJob)); } emitFinished(true); } void AccessManagerReply::slotStatResult(KJob *kJob) { if (jobError(kJob)) { emit error(error()); emitFinished(true); return; } KIO::StatJob *statJob = qobject_cast(kJob); Q_ASSERT(statJob); KIO::UDSEntry entry = statJob->statResult(); QString mimeType = entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE); if (mimeType.isEmpty() && entry.isDir()) { mimeType = QStringLiteral("inode/directory"); } if (!mimeType.isEmpty()) { setHeader(QNetworkRequest::ContentTypeHeader, mimeType.toUtf8()); } emitFinished(true); } void AccessManagerReply::slotRedirection(KIO::Job *job, const QUrl &u) { if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("redirect"), url(), u)) { qCWarning(KIO_WIDGETS) << "Redirection from" << url() << "to" << u << "REJECTED by policy!"; setError(QNetworkReply::ContentAccessDenied, u.toString()); emit error(error()); return; } setAttribute(QNetworkRequest::RedirectionTargetAttribute, QUrl(u)); if (job->queryMetaData(QStringLiteral("redirect-to-get")) == QL1S("true")) { setOperation(QNetworkAccessManager::GetOperation); } } void AccessManagerReply::slotPercent(KJob *job, unsigned long percent) { qulonglong bytesTotal = job->totalAmount(KJob::Bytes); qulonglong bytesProcessed = (bytesTotal * percent) / 100; if (operation() == QNetworkAccessManager::PutOperation || operation() == QNetworkAccessManager::PostOperation) { emit uploadProgress(bytesProcessed, bytesTotal); return; } emit downloadProgress(bytesProcessed, bytesTotal); } void AccessManagerReply::emitFinished(bool state, Qt::ConnectionType type) { setFinished(state); emit QMetaObject::invokeMethod(this, "finished", type); } } diff --git a/src/widgets/accessmanagerreply_p.h b/src/widgets/accessmanagerreply_p.h index 4bd3b6a4..09681659 100644 --- a/src/widgets/accessmanagerreply_p.h +++ b/src/widgets/accessmanagerreply_p.h @@ -1,107 +1,107 @@ /* * This file is part of the KDE project. * * Copyright (C) 2008 - 2009 Urs Wolfer * Copyright (C) 2009 - 2012 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. * */ #ifndef KIO_ACCESSMANAGERREPLY_P_H #define KIO_ACCESSMANAGERREPLY_P_H -#include -#include +#include +#include namespace KIO { class Job; class SimpleJob; class MetaData; } class KJob; class QUrl; namespace KDEPrivate { /** * Used for KIO::AccessManager; KDE implementation of QNetworkReply. * * @since 4.3 * @author Urs Wolfer \ */ class AccessManagerReply : public QNetworkReply { Q_OBJECT public: explicit AccessManagerReply(const QNetworkAccessManager::Operation op, const QNetworkRequest &request, KIO::SimpleJob *kioJob, bool emitReadyReadOnMetaDataChange = false, QObject *parent = nullptr); explicit AccessManagerReply(const QNetworkAccessManager::Operation op, const QNetworkRequest &request, const QByteArray &data, const QUrl &url, const KIO::MetaData &metaData, QObject *parent = nullptr); explicit AccessManagerReply(const QNetworkAccessManager::Operation op, const QNetworkRequest &request, QNetworkReply::NetworkError errorCode, const QString &errorMessage, QObject *parent = nullptr); virtual ~AccessManagerReply(); qint64 bytesAvailable() const Q_DECL_OVERRIDE; void abort() Q_DECL_OVERRIDE; void setIgnoreContentDisposition(bool on); void putOnHold(); static bool isLocalRequest(const QUrl &url); protected: qint64 readData(char *data, qint64 maxSize) Q_DECL_OVERRIDE; bool ignoreContentDisposition(const KIO::MetaData &); void setHeaderFromMetaData(const KIO::MetaData &); void readHttpResponseHeaders(KIO::Job *); int jobError(KJob *kJob); void emitFinished(bool state, Qt::ConnectionType type = Qt::AutoConnection); private Q_SLOTS: void slotData(KIO::Job *kioJob, const QByteArray &data); void slotMimeType(KIO::Job *kioJob, const QString &mimeType); void slotResult(KJob *kJob); void slotStatResult(KJob *kJob); void slotRedirection(KIO::Job *job, const QUrl &url); void slotPercent(KJob *job, unsigned long percent); private: QByteArray m_data; bool m_metaDataRead; bool m_ignoreContentDisposition; bool m_emitReadyReadOnMetaDataChange; QPointer m_kioJob; }; } #endif // KIO_ACCESSMANAGERREPLY_P_H diff --git a/src/widgets/fileundomanager.h b/src/widgets/fileundomanager.h index 6cd57b8b..03de1bd2 100644 --- a/src/widgets/fileundomanager.h +++ b/src/widgets/fileundomanager.h @@ -1,227 +1,227 @@ /* This file is part of the KDE project Copyright (C) 2000 Simon Hausmann Copyright (C) 2006, 2008 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 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. */ #ifndef KIO_FILEUNDOMANAGER_H #define KIO_FILEUNDOMANAGER_H -#include -#include +#include +#include #include "kiowidgets_export.h" class QDateTime; namespace KIO { class Job; class CopyJob; class FileUndoManagerPrivate; class FileUndoManagerSingleton; class CommandRecorder; class UndoCommand; class UndoJob; /** * @class KIO::FileUndoManager fileundomanager.h * * FileUndoManager: makes it possible to undo kio jobs. * This class is a singleton, use self() to access its only instance. */ class KIOWIDGETS_EXPORT FileUndoManager : public QObject { Q_OBJECT public: /** * @return the FileUndoManager instance */ static FileUndoManager *self(); /** * Interface for the gui handling of FileUndoManager. * This includes three events currently: * - error when undoing a job * - confirm deletion before undoing a copy job * - confirm deletion when the copied file has been modified afterwards * * By default UiInterface shows message boxes in all three cases; * applications can reimplement this interface to provide different user interfaces. */ class KIOWIDGETS_EXPORT UiInterface { public: UiInterface(); virtual ~UiInterface(); /** * Sets whether to show progress info when running the KIO jobs for undoing. */ void setShowProgressInfo(bool b); /** * @returns whether progress info dialogs are shown while undoing. */ bool showProgressInfo() const; /** * Sets the parent widget to use for message boxes. */ void setParentWidget(QWidget *parentWidget); /** * @return the parent widget passed to the last call to undo(parentWidget), or 0. */ QWidget *parentWidget() const; /** * Called when an undo job errors; default implementation displays a message box. */ virtual void jobError(KIO::Job *job); /** * Called when we are about to remove those files. * Return true if we should proceed with deleting them. */ virtual bool confirmDeletion(const QList &files); /** * Called when dest was modified since it was copied from src. * Note that this is called after confirmDeletion. * Return true if we should proceed with deleting dest. */ virtual bool copiedFileWasModified(const QUrl &src, const QUrl &dest, const QDateTime &srcTime, const QDateTime &destTime); /** * \internal, for future extensions */ virtual void virtual_hook(int id, void *data); private: class UiInterfacePrivate; UiInterfacePrivate *d; }; /** * Set a new UiInterface implementation. * This deletes the previous one. * @param ui the UiInterface instance, which becomes owned by the undo manager. */ void setUiInterface(UiInterface *ui); /** * @return the UiInterface instance passed to setUiInterface. * This is useful for calling setParentWidget on it. Never delete it! */ UiInterface *uiInterface() const; /** * The type of job. * * Put: @since 4.7, represents the creation of a file from data in memory. * Used when pasting data from clipboard or drag-n-drop. * Mkpath: @since 5.4, represents a KIO::mkpath() job. * BatchRename: @since 5.42, represents a KIO::batchRename() job. Used when * renaming multiple files. */ enum CommandType { Copy, Move, Rename, Link, Mkdir, Trash, Put, Mkpath, BatchRename }; /** * Record this job while it's happening and add a command for it so that the user can undo it. * The signal jobRecordingStarted() is emitted. * @param op the type of job - which is also the type of command that will be created for it * @param src list of source urls. This is empty for Mkdir, Mkpath, Put operations. * @param dst destination url * @param job the job to record */ void recordJob(CommandType op, const QList &src, const QUrl &dst, KIO::Job *job); /** * Record this CopyJob while it's happening and add a command for it so that the user can undo it. * The signal jobRecordingStarted() is emitted. */ void recordCopyJob(KIO::CopyJob *copyJob); /** * @return true if undo is possible. Usually used for enabling/disabling the undo action. */ bool undoAvailable() const; /** * @return the current text for the undo action. */ QString undoText() const; /** * These two functions are useful when wrapping FileUndoManager and adding custom commands. * Each command has a unique ID. You can get a new serial number for a custom command * with newCommandSerialNumber(), and then when you want to undo, check if the command * FileUndoManager would undo is newer or older than your custom command. */ quint64 newCommandSerialNumber(); quint64 currentCommandSerialNumber() const; public Q_SLOTS: /** * Undoes the last command * Remember to call uiInterface()->setParentWidget(parentWidget) first, * if you have multiple mainwindows. * * This operation is asynchronous. * undoJobFinished will be emitted once the undo is complete. */ void undo(); Q_SIGNALS: /// Emitted when the value of undoAvailable() changes void undoAvailable(bool avail); /// Emitted when the value of undoText() changes void undoTextChanged(const QString &text); /// Emitted when an undo job finishes. Used for unit testing. void undoJobFinished(); /** * Emitted when a job recording has been started by FileUndoManager::recordJob() * or FileUndoManager::recordCopyJob(). After the job recording has been finished, * the signal jobRecordingFinished() will be emitted. * @since 4.2 */ void jobRecordingStarted(CommandType op); /** * Emitted when a job that has been recorded by FileUndoManager::recordJob() * or FileUndoManager::recordCopyJob has been finished. The command * is now available for an undo-operation. * @since 4.2 */ void jobRecordingFinished(CommandType op); private: FileUndoManager(); virtual ~FileUndoManager(); friend class FileUndoManagerSingleton; friend class UndoJob; friend class CommandRecorder; friend class FileUndoManagerPrivate; FileUndoManagerPrivate *d; }; } // namespace #endif diff --git a/src/widgets/fileundomanager_p.h b/src/widgets/fileundomanager_p.h index 91f88172..cdb51d33 100644 --- a/src/widgets/fileundomanager_p.h +++ b/src/widgets/fileundomanager_p.h @@ -1,179 +1,179 @@ /* This file is part of the KDE project Copyright (C) 2000 Simon Hausmann Copyright (C) 2006, 2008 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 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. */ #ifndef FILEUNDOMANAGER_P_H #define FILEUNDOMANAGER_P_H #include "fileundomanager.h" -#include +#include #include #include class KJob; namespace KIO { class FileUndoManagerAdaptor; struct BasicOperation { typedef QList Stack; BasicOperation() { m_valid = false; } bool m_valid; bool m_renamed; enum Type { File, Link, Directory }; Type m_type: 2; QUrl m_src; QUrl m_dst; QString m_target; QDateTime m_mtime; }; class UndoCommand { public: typedef QList Stack; UndoCommand() { m_valid = false; } // TODO: is ::TRASH missing? bool isMoveCommand() const { return m_type == FileUndoManager::Move || m_type == FileUndoManager::Rename; } bool m_valid; FileUndoManager::CommandType m_type; BasicOperation::Stack m_opStack; QList m_src; QUrl m_dst; quint64 m_serialNumber; }; // This class listens to a job, collects info while it's running (for copyjobs) // and when the job terminates, on success, it calls addCommand in the undomanager. class CommandRecorder : public QObject { Q_OBJECT public: CommandRecorder(FileUndoManager::CommandType op, const QList &src, const QUrl &dst, KIO::Job *job); virtual ~CommandRecorder(); private Q_SLOTS: void slotResult(KJob *job); void slotCopyingDone(KIO::Job *, const QUrl &from, const QUrl &to, const QDateTime &, bool directory, bool renamed); void slotCopyingLinkDone(KIO::Job *, const QUrl &from, const QString &target, const QUrl &to); void slotDirectoryCreated(const QUrl &url); void slotBatchRenamingDone(const QUrl &from, const QUrl &to); private: UndoCommand m_cmd; }; enum UndoState { MAKINGDIRS = 0, MOVINGFILES, STATINGFILE, REMOVINGDIRS, REMOVINGLINKS }; // The private class is, exceptionally, a real QObject // so that it can be the class with the DBUS adaptor forwarding its signals. class FileUndoManagerPrivate : public QObject { Q_OBJECT public: FileUndoManagerPrivate(FileUndoManager *qq); ~FileUndoManagerPrivate() { delete m_uiInterface; } void pushCommand(const UndoCommand &cmd); void addDirToUpdate(const QUrl &url); void stepMakingDirectories(); void stepMovingFiles(); void stepRemovingLinks(); void stepRemovingDirectories(); /// called by FileUndoManagerAdaptor QByteArray get() const; friend class UndoJob; /// called by UndoJob void stopUndo(bool step); friend class UndoCommandRecorder; /// called by UndoCommandRecorder void addCommand(const UndoCommand &cmd); bool m_lock; UndoCommand::Stack m_commands; UndoCommand m_current; KIO::Job *m_currentJob; UndoState m_undoState; QStack m_dirStack; QStack m_dirCleanupStack; QStack m_fileCleanupStack; // files and links QList m_dirsToUpdate; FileUndoManager::UiInterface *m_uiInterface; UndoJob *m_undoJob; quint64 m_nextCommandIndex; FileUndoManager *q; // DBUS interface Q_SIGNALS: /// DBUS signal void push(const QByteArray &command); /// DBUS signal void pop(); /// DBUS signal void lock(); /// DBUS signal void unlock(); public Q_SLOTS: // Those four slots are connected to DBUS signals void slotPush(QByteArray); void slotPop(); void slotLock(); void slotUnlock(); void undoStep(); void slotResult(KJob *); }; } // namespace #endif /* FILEUNDOMANAGER_P_H */ diff --git a/src/widgets/kabstractfileitemactionplugin.h b/src/widgets/kabstractfileitemactionplugin.h index fffb6c54..34d2aa52 100644 --- a/src/widgets/kabstractfileitemactionplugin.h +++ b/src/widgets/kabstractfileitemactionplugin.h @@ -1,113 +1,113 @@ /* This file is part of the KDE project Copyright (C) 2010 Sebastian Trueg Based on konq_popupmenuplugin.h Copyright 2008 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 as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), 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. */ #ifndef KABSTRACTFILEITEMACTION_PLUGIN_H #define KABSTRACTFILEITEMACTION_PLUGIN_H #include "kiowidgets_export.h" -#include +#include class QAction; class QMenu; class QWidget; class KFileItemListProperties; /** * @class KAbstractFileItemActionPlugin kabstractfileitemactionplugin.h * * @brief Base class for KFileItemAction plugins. * * KFileItemAction plugins allow dynamic features to be added to the context * menus for files and directories when browsing. * * Most filetype-based popup menu items can be implemented using servicemenus * linked to mime types, and that should be the preferred way of doing this. * However, complex scenarios such as showing submenus with a variable number of * actions or only showing an item if exactly two files are selected need to be * implemented as a KFileItemAction plugin. * * To create such a plugin, subclass KAbstractFileItemActionPlugin and implement * actions() to return the actions to want to add to the context menu. Then * create a plugin in the usual KPluginFactory based way: * \code * K_PLUGIN_FACTORY_WITH_JSON(MyActionPluginFactory, myactionplugin.json, registerPlugin();) * #include * \endcode * * A desktop file is necessary to register the plugin with the KDE plugin system: * * \code * [Desktop Entry] * Type=Service * Name=My fancy action plugin * X-KDE-Library=myactionplugin * ServiceTypes=KFileItemAction/Plugin * MimeType=some/mimetype; * \endcode * * Note the \p KFileItemAction/Plugin service type which is used by * KFileItemActions::addServicePluginActionsTo() to load all available plugins * and the \p MimeType field which specifies for which types of file items * the setup() method should be called. * * The desktop file contents must also be compiled into the plugin as JSON data. * The following CMake code builds and installs the plugin: * \code * set(myactionplugin_SRCS myactionplugin.cpp) * * kcoreaddons_add_plugin(myactionplugin SOURCES ${myactionplugin_SRCS} INSTALL_NAMESPACE "kf5/kfileitemaction") * kcoreaddons_desktop_to_json(myactionplugin myactionplugin.desktop) # generate the json file * * target_link_libraries(myactionplugin KF5::KIOWidgets) * \endcode * * @note the plugin should be installed in the "kf5/kfileitemaction" subfolder of $QT_PLUGIN_PATH. * * @author Sebastian Trueg * * @since 4.6.1 */ class KIOWIDGETS_EXPORT KAbstractFileItemActionPlugin : public QObject { Q_OBJECT public: KAbstractFileItemActionPlugin(QObject *parent); virtual ~KAbstractFileItemActionPlugin(); /** * Implement the actions method in the plugin in order to create actions. * * The returned actions should have the KAbstractFileItemActionPlugin object * as their parent. * * @param fileItemInfos Information about the selected file items. * @param parentWidget A parent widget for error messages or the like. * * @return A list of actions to be added to a contextual menu for the file * items. */ virtual QList actions(const KFileItemListProperties &fileItemInfos, QWidget *parentWidget) = 0; }; #endif diff --git a/src/widgets/kacleditwidget_p.h b/src/widgets/kacleditwidget_p.h index 0eecc523..fb8bdbbf 100644 --- a/src/widgets/kacleditwidget_p.h +++ b/src/widgets/kacleditwidget_p.h @@ -1,220 +1,220 @@ /*************************************************************************** * Copyright (C) 2005 by Sean Harmer * * 2005 - 2007 Till Adam * * * * This program 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 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 KACLEDITWIDGET_P_H #define KACLEDITWIDGET_P_H #include #if HAVE_POSIX_ACL || defined(Q_MOC_RUN) #include #include #include #include #include -#include +#include #include #include class KACLListViewItem; class QButtonGroup; class KACLListView; class QStackedWidget; class QCheckBox; class QAbstractButton; class QColorGroup; /** @author Sean Harmer */ class KACLListView : public QTreeWidget { Q_OBJECT friend class KACLListViewItem; public: enum Types { OWNER_IDX = 0, GROUP_IDX, OTHERS_IDX, MASK_IDX, NAMED_USER_IDX, NAMED_GROUP_IDX, LAST_IDX }; enum EntryType { User = 1, Group = 2, Others = 4, Mask = 8, NamedUser = 16, NamedGroup = 32, AllTypes = 63 }; KACLListView(QWidget *parent = nullptr); ~KACLListView(); bool hasMaskEntry() const { return m_hasMask; } bool hasDefaultEntries() const; bool allowDefaults() const { return m_allowDefaults; } void setAllowDefaults(bool v) { m_allowDefaults = v; } unsigned short maskPermissions() const; void setMaskPermissions(unsigned short maskPerms); acl_perm_t maskPartialPermissions() const; void setMaskPartialPermissions(acl_perm_t maskPerms); bool maskCanBeDeleted() const; bool defaultMaskCanBeDeleted() const; const KACLListViewItem *findDefaultItemByType(EntryType type) const; const KACLListViewItem *findItemByType(EntryType type, bool defaults = false) const; unsigned short calculateMaskValue(bool defaults) const; void calculateEffectiveRights(); QStringList allowedUsers(bool defaults, KACLListViewItem *allowedItem = nullptr); QStringList allowedGroups(bool defaults, KACLListViewItem *allowedItem = nullptr); KACL getACL(); KACL getDefaultACL(); QPixmap getYesPixmap() const { return *m_yesPixmap; } QPixmap getYesPartialPixmap() const { return *m_yesPartialPixmap; } public Q_SLOTS: void slotAddEntry(); void slotEditEntry(); void slotRemoveEntry(); void setACL(const KACL &anACL); void setDefaultACL(const KACL &anACL); protected Q_SLOTS: void slotItemClicked(QTreeWidgetItem *pItem, int col); void slotItemDoubleClicked(QTreeWidgetItem *item, int col); protected: void contentsMousePressEvent(QMouseEvent *e); private: void fillItemsFromACL(const KACL &pACL, bool defaults = false); KACL itemsToACL(bool defaults) const; KACL m_ACL; KACL m_defaultACL; unsigned short m_mask; bool m_hasMask; bool m_allowDefaults; QStringList m_allUsers; QStringList m_allGroups; QPixmap *m_yesPixmap; QPixmap *m_yesPartialPixmap; }; class EditACLEntryDialog : public QDialog { Q_OBJECT public: EditACLEntryDialog(KACLListView *listView, KACLListViewItem *item, const QStringList &users, const QStringList &groups, const QStringList &defaultUsers, const QStringList &defaultGroups, int allowedTypes = KACLListView::AllTypes, int allowedDefaultTypes = KACLListView::AllTypes, bool allowDefault = false); KACLListViewItem *item() const { return m_item; } public Q_SLOTS: void slotOk(); void slotSelectionChanged(QAbstractButton *); private Q_SLOTS: void slotUpdateAllowedUsersAndGroups(); void slotUpdateAllowedTypes(); private: KACLListView *m_listView; KACLListViewItem *m_item; QStringList m_users; QStringList m_groups; QStringList m_defaultUsers; QStringList m_defaultGroups; int m_allowedTypes; int m_allowedDefaultTypes; QButtonGroup *m_buttonGroup; KComboBox *m_usersCombo; KComboBox *m_groupsCombo; QStackedWidget *m_widgetStack; QCheckBox *m_defaultCB; QHash m_buttonIds; }; class KACLListViewItem : public QTreeWidgetItem { public: KACLListViewItem(QTreeWidget *parent, KACLListView::EntryType type, unsigned short value, bool defaultEntry, const QString &qualifier = QString()); virtual ~KACLListViewItem(); QString key() const; bool operator< (const QTreeWidgetItem &other) const Q_DECL_OVERRIDE; void calcEffectiveRights(); bool isDeletable() const; bool isAllowedToChangeType() const; void togglePerm(acl_perm_t perm); #if 0 virtual void paintCell(QPainter *p, const QColorGroup &cg, int column, int width, int alignment); #endif void updatePermPixmaps(); void repaint(); KACLListView::EntryType type; unsigned short value; bool isDefault; QString qualifier; bool isPartial; private: KACLListView *m_pACLListView; }; #endif #endif diff --git a/src/widgets/kautomount.h b/src/widgets/kautomount.h index b2cc7c57..5b3b0e45 100644 --- a/src/widgets/kautomount.h +++ b/src/widgets/kautomount.h @@ -1,116 +1,116 @@ /* This file is part of the KDE libraries Copyright (C) 2000 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. */ #ifndef KAUTOMOUNT_H #define KAUTOMOUNT_H -#include -#include +#include +#include #include #include "kiowidgets_export.h" #ifdef Q_OS_UNIX class KJob; namespace KIO { class Job; } class KAutoMountPrivate; /** * @class KAutoMount kautomount.h * * This class implements synchronous mounting of devices, * as well as showing a file-manager window after mounting a device, optionally. * It is a wrapper around the asychronous KIO::special() call for mount, * used by KDesktopFileActions. * * @short This class implements synchronous mounting of devices. */ class KIOWIDGETS_EXPORT KAutoMount : public QObject { Q_OBJECT public: /** * Mounts a device. * @param readonly if true, the device is mounted read-only * @param format the file system (e.g. vfat, ext2...) [optional, fstab is used otherwise] * @param device the path to the device (e.g. /dev/fd0) * @param mountpoint the directory where to mount the device [optional, fstab is used otherwise] * @param desktopFile the file the user clicked on - to notify KDirWatch of the fact that * it should emit fileDirty for it (to have the icon change) * @param show_filemanager_window if true, a file-manager window for that mountpoint is shown after * the mount, if successful. */ KAutoMount(bool readonly, const QByteArray &format, const QString &device, const QString &mountpoint, const QString &desktopFile, bool show_filemanager_window = true); Q_SIGNALS: /** Emitted when the directory has been mounted */ void finished(); /** Emitted in case the directory could not been mounted */ void error(); private: /** KAutoMount deletes itself. Don't delete it manually. */ ~KAutoMount(); Q_PRIVATE_SLOT(d, void slotResult(KJob *)) friend class KAutoMountPrivate; KAutoMountPrivate *const d; }; class KAutoUnmountPrivate; /** * This class implements synchronous unmounting of devices, * It is a wrapper around the asychronous KIO::special() call for unmount, * used by KDesktopFileActions. * * @short This class implements synchronous unmounting of devices, */ class KIOWIDGETS_EXPORT KAutoUnmount : public QObject { Q_OBJECT public: /** * Unmounts a device. * @param mountpoint the mount point - KAutoUnmount finds the device from that * @param desktopFile the file the user clicked on - to notify KDirWatch of the fact that * it should emit fileDirty for it (to have the icon change) */ KAutoUnmount(const QString &mountpoint, const QString &desktopFile); Q_SIGNALS: /** Emitted when the directory has been unmounted */ void finished(); /** Emitted in case the directory could not been unmounted */ void error(); private: /** KAutoUnmount deletes itself. Don't delete it manually. */ ~KAutoUnmount(); Q_PRIVATE_SLOT(d, void slotResult(KJob *)) friend class KAutoUnmountPrivate; KAutoUnmountPrivate *const d; }; #endif //Q_OS_UNIX #endif diff --git a/src/widgets/kdesktopfileactions.h b/src/widgets/kdesktopfileactions.h index 56b22bfa..c5476eff 100644 --- a/src/widgets/kdesktopfileactions.h +++ b/src/widgets/kdesktopfileactions.h @@ -1,117 +1,117 @@ /* This file is part of the KDE libraries * Copyright (C) 1999 Waldo Bastian * 2000, 2007 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. **/ #ifndef KDESKTOPFILEACTIONS_H #define KDESKTOPFILEACTIONS_H #include #include "kiowidgets_export.h" -#include -#include +#include +#include #include class KDesktopFile; class KService; /** * KDesktopFileActions provides a number of methods related to actions in desktop files. */ namespace KDesktopFileActions { /** * Returns a list of services for the given .desktop file that are handled * by kio itself. Namely mount/unmount for FSDevice files. * @return the list of services */ KIOWIDGETS_EXPORT QList builtinServices(const QUrl &url); /** * Returns a list of services defined by the user as possible actions * on the given .desktop file. May include separators (see KServiceAction::isSeparator) * which should appear in user-visible representations of those actions, * such as separators in a menu. * @param path the path to the desktop file describing the services * @param bLocalFiles true if those services are to be applied to local files only * (if false, services that don't have %u or %U in the Exec line won't be taken into account). * @return the list of user defined actions */ KIOWIDGETS_EXPORT QList userDefinedServices(const QString &path, bool bLocalFiles); /** * Overload of userDefinedServices but also allows you to pass a list of urls for this file. * This allows for the menu to be changed depending on the exact files via * the X-KDE-GetActionMenu extension. */ KIOWIDGETS_EXPORT QList userDefinedServices(const QString &path, const KDesktopFile &desktopFile, bool bLocalFiles, const QList &file_list = QList()); /** * Returns a list of services defined by the user as possible actions * on the given .desktop file represented by the KService instance. * May include separators (see KServiceAction::isSeparator) which should * appear in user-visible representations of those actions, * such as separators in a menu. * @param path the path to the desktop file describing the services * @param bLocalFiles true if those services are to be applied to local files only * (if false, services that don't have %u or %U in the Exec line won't be taken into account). * @param file_list list of urls; this allows for the menu to be changed depending on the exact files via * the X-KDE-GetActionMenu extension. * * @return the list of user defined actions */ KIOWIDGETS_EXPORT QList userDefinedServices(const KService &service, bool bLocalFiles, const QList &file_list = QList()); /** * Execute @p service on the list of @p urls. * @param urls the list of urls * @param service the service to execute */ KIOWIDGETS_EXPORT void executeService(const QList &urls, const KServiceAction &service); /** * Invokes the default action for the desktop entry. If the desktop * entry is not local, then only false is returned. Otherwise we * would create a security problem. Only types Link and Mimetype * could be followed. * * @param _url the url to run * @param _is_local true if the URL is local, false otherwise * @return true on success and false on failure. * @see KRun::runUrl */ KIOWIDGETS_EXPORT bool run(const QUrl &_url, bool _is_local); /** * Invokes the default action for the desktop entry. If the desktop * entry is not local, then only false is returned. Otherwise we * would create a security problem. Only types Link and Mimetype * could be followed. * * Use this function if a startup notification id has already been created. * * @param _url the url to run * @param _is_local true if the URL is local, false otherwise * @param asn Application startup notification id, if available * @return true on success and false on failure. * @see KRun::runUrl * @since 5.5 * @todo kf6: merge with run */ KIOWIDGETS_EXPORT bool runWithStartup(const QUrl &_url, bool _is_local, const QByteArray &asn); } #endif diff --git a/src/widgets/kdirmodel.h b/src/widgets/kdirmodel.h index ed2e7381..c66df755 100644 --- a/src/widgets/kdirmodel.h +++ b/src/widgets/kdirmodel.h @@ -1,286 +1,286 @@ /* This file is part of the KDE project Copyright (C) 2006 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 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. */ #ifndef KDIRMODEL_H #define KDIRMODEL_H -#include +#include #include "kiowidgets_export.h" #include class KDirLister; class KDirModelPrivate; class JobUrlCache; /** * @class KDirModel kdirmodel.h * * @short A model for a KIO-based directory tree. * * KDirModel implements the QAbstractItemModel interface (for use with Qt's model/view widgets) * around the directory listing for one directory or a tree of directories. * * Note that there are some cases when using QPersistentModelIndexes from this model will not give * expected results. QPersistentIndexes will remain valid and updated if its siblings are added or * removed. However, if the QPersistentIndex or one of its ancestors is moved, the QPersistentIndex will become * invalid. For example, if a file or directory is renamed after storing a QPersistentModelIndex for it, * the index (along with any stored children) will become invalid even though it is still in the model. The reason * for this is that moves of files and directories are treated as separate insert and remove actions. * * @see KDirSortFilterProxyModel * * @author David Faure * Based on work by Hamish Rodda and Pascal Letourneau */ class KIOWIDGETS_EXPORT KDirModel : public QAbstractItemModel { Q_OBJECT public: /** * @param parent parent qobject */ explicit KDirModel(QObject *parent = nullptr); ~KDirModel(); /** * Set the directory lister to use by this model, instead of the default KDirLister created internally. * The model takes ownership. */ void setDirLister(KDirLister *dirLister); /** * Return the directory lister used by this model. */ KDirLister *dirLister() const; /** * Return the fileitem for a given index. This is O(1), i.e. fast. */ KFileItem itemForIndex(const QModelIndex &index) const; /** * Return the index for a given kfileitem. This can be slow. * @deprecated use the method that takes a KFileItem by value */ #ifndef KIOWIDGETS_NO_DEPRECATED KIOWIDGETS_DEPRECATED QModelIndex indexForItem(const KFileItem *) const; #endif /** * Return the index for a given kfileitem. This can be slow. */ QModelIndex indexForItem(const KFileItem &) const; /** * Return the index for a given url. This can be slow. */ QModelIndex indexForUrl(const QUrl &url) const; /** * @short Lists subdirectories using fetchMore() as needed until the given @p url exists in the model. * * When the model is used by a treeview, call KDirLister::openUrl with the base url of the tree, * then the treeview will take care of calling fetchMore() when the user opens directories. * However if you want the tree to show a given URL (i.e. open the tree recursively until that URL), * call expandToUrl(). * Note that this is asynchronous; the necessary listing of subdirectories will take time so * the model will not immediately have this url available. * The model emits the signal expand() when an index has become available; this can be connected * to the treeview in order to let it open that index. * @param url the url of a subdirectory of the directory model (or a file in a subdirectory) */ void expandToUrl(const QUrl &url); /** * Notify the model that the item at this index has changed. * For instance because KMimeTypeResolver called determineMimeType on it. * This makes the model emit its dataChanged signal at this index, so that views repaint. * Note that for most things (renaming, changing size etc.), KDirLister's signals tell the model already. */ void itemChanged(const QModelIndex &index); /** * Forget all previews (optimization for turning previews off). * The items will again have their default appearance (not controlled by the model). * @since 5.28 */ void clearAllPreviews(); /** * Useful "default" columns. Views can use a proxy to have more control over this. */ enum ModelColumns { Name = 0, Size, ModifiedTime, Permissions, Owner, Group, Type, ColumnCount }; /// Possible return value for data(ChildCountRole), meaning the item isn't a directory, /// or we haven't calculated its child count yet enum { ChildCountUnknown = -1 }; enum AdditionalRoles { // Note: use printf "0x%08X\n" $(($RANDOM*$RANDOM)) // to define additional roles. FileItemRole = 0x07A263FF, ///< returns the KFileItem for a given index ChildCountRole = 0x2C4D0A40, ///< returns the number of items in a directory, or ChildCountUnknown HasJobRole = 0x01E555A5 ///< returns whether or not there is a job on an item (file/directory) }; enum DropsAllowedFlag { NoDrops = 0, DropOnDirectory = 1, ///< allow drops on any directory DropOnAnyFile = 2, ///< allow drops on any file DropOnLocalExecutable = 4 ///< allow drops on local executables, shell scripts and desktop files. Can be used with DropOnDirectory. }; Q_DECLARE_FLAGS(DropsAllowed, DropsAllowedFlag) /// Set whether dropping onto items should be allowed, and for which kind of item /// Drops are disabled by default. void setDropsAllowed(DropsAllowed dropsAllowed); /// Reimplemented from QAbstractItemModel. Returns true for empty directories. bool canFetchMore(const QModelIndex &parent) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. Returns ColumnCount. int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. Not implemented yet. bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. Lists the subdirectory. void fetchMore(const QModelIndex &parent) Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. Returns true for directories. bool hasChildren(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. Returns the column titles. QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. O(1) QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. QMimeData *mimeData(const QModelIndexList &indexes) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. QStringList mimeTypes() const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. QModelIndex sibling(int row, int column, const QModelIndex &index) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. /// Call this to set a new icon, e.g. a preview bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. Not implemented. @see KDirSortFilterProxyModel void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) Q_DECL_OVERRIDE; /** * Remove urls from the list if an ancestor is present on the list. This can * be used to delete only the ancestor url and skip a potential error of a non-existent url. * * For example, for a list of "/home/foo/a", "/home/foo/a/a.txt", "/home/foo/a/a/a.txt", "/home/foo/a/b/b.txt", * "home/foo/b/b.txt", this method will return the list "/home/foo/a", "/home/foo/b/b.txt". * * @return the list @p urls without parented urls inside. * @since 4.2 */ static QList simplifiedUrlList(const QList &urls); /** * This emits the needSequenceIcon signal, requesting another sequence icon * * If there is a KFilePreviewGenerator attached to this model, that generator will care * about creating another preview. * * @param index Index of the item that should get another icon * @param sequenceIndex Index in the sequence. If it is zero, the standard icon will be assigned. * For higher indices, arbitrary different meaningful icons will be generated. * @since 4.3 */ void requestSequenceIcon(const QModelIndex &index, int sequenceIndex); /** * Enable/Disable the displaying of an animated overlay that is shown for any destination * urls (in the view). When enabled, the animations (if any) will be drawn automatically. * * Only the files/folders that are visible and have jobs associated with them * will display the animation. * You would likely not want this enabled if you perform some kind of custom painting * that takes up a whole item, and will just make this(and what you paint) look funky. * * Default is disabled. * * Note: KFileItemDelegate needs to have it's method called with the same * value, when you make the call to this method. * * @since 4.5 */ void setJobTransfersVisible(bool value); /** * Returns whether or not displaying job transfers has been enabled. * @since 4.5 */ bool jobTransfersVisible() const; Q_SIGNALS: /** * Emitted for each subdirectory that is a parent of a url passed to expandToUrl * This allows to asynchronously open a tree view down to a given directory. * Also emitted for the final file, if expandToUrl is called with a file * (for instance so that it can be selected). */ void expand(const QModelIndex &index); /** * Emitted when another icon sequence index is requested * @param index Index of the item that should get another icon * @param sequenceIndex Index in the sequence. If it is zero, the standard icon should be assigned. * For higher indices, arbitrary different meaningful icons should be generated. * This is usually slowly counted up while the user hovers the icon. * If no meaningful alternative icons can be generated, this should be ignored. * @since 4.3 */ void needSequenceIcon(const QModelIndex &index, int sequenceIndex); private: // Make those private, they shouldn't be called by applications bool insertRows(int, int, const QModelIndex & = QModelIndex()) Q_DECL_OVERRIDE; bool insertColumns(int, int, const QModelIndex & = QModelIndex()) Q_DECL_OVERRIDE; bool removeRows(int, int, const QModelIndex & = QModelIndex()) Q_DECL_OVERRIDE; bool removeColumns(int, int, const QModelIndex & = QModelIndex()) Q_DECL_OVERRIDE; private: friend class KDirModelPrivate; KDirModelPrivate *const d; Q_PRIVATE_SLOT(d, void _k_slotNewItems(const QUrl &, const KFileItemList &)) Q_PRIVATE_SLOT(d, void _k_slotDeleteItems(const KFileItemList &)) Q_PRIVATE_SLOT(d, void _k_slotRefreshItems(const QList > &)) Q_PRIVATE_SLOT(d, void _k_slotClear()) Q_PRIVATE_SLOT(d, void _k_slotRedirection(const QUrl &, const QUrl &)) Q_PRIVATE_SLOT(d, void _k_slotJobUrlsChanged(const QStringList &)) }; Q_DECLARE_OPERATORS_FOR_FLAGS(KDirModel::DropsAllowed) #endif /* KDIRMODEL_H */ diff --git a/src/widgets/kfile.h b/src/widgets/kfile.h index 0311e918..d46d6d3a 100644 --- a/src/widgets/kfile.h +++ b/src/widgets/kfile.h @@ -1,116 +1,116 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Carsten Pfeiffer 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. */ #ifndef KFILE_H #define KFILE_H -#include +#include #include "kiowidgets_export.h" /** * @class KFile kfile.h * * KFile is a class which provides a namespace for some enumerated * values associated with the kfile library. You will never need to * construct a KFile object itself. */ class KIOWIDGETS_EXPORT KFile { Q_GADGET public: /** * Modes of operation for the dialog. * @li @p File - Get a single file name from the user. * @li @p Directory - Get a directory name from the user. * @li @p Files - Get multiple file names from the user. * @li @p ExistingOnly - Never return a filename which does not exist yet * @li @p LocalOnly - Don't return remote filenames */ enum Mode { File = 1, Directory = 2, Files = 4, ExistingOnly = 8, LocalOnly = 16, ModeMax = 65536 }; Q_DECLARE_FLAGS(Modes, Mode) Q_FLAG(Modes) enum FileView { Default = 0, Simple = 1, Detail = 2, SeparateDirs = 4, PreviewContents = 8, PreviewInfo = 16, Tree = 32, DetailTree = 64, FileViewMax = 65536 }; enum SelectionMode { Single = 1, Multi = 2, Extended = 4, NoSelection = 8 }; // // some bittests // // sorting specific static bool isSortByName(const QDir::SortFlags &sort); static bool isSortBySize(const QDir::SortFlags &sort); static bool isSortByDate(const QDir::SortFlags &sort); static bool isSortByType(const QDir::SortFlags &sort); static bool isSortDirsFirst(const QDir::SortFlags &sort); static bool isSortCaseInsensitive(const QDir::SortFlags &sort); // view specific static bool isDefaultView(const FileView &view); static bool isSimpleView(const FileView &view); static bool isDetailView(const FileView &view); static bool isSeparateDirs(const FileView &view); static bool isPreviewContents(const FileView &view); static bool isPreviewInfo(const FileView &view); static bool isTreeView(const FileView &view); static bool isDetailTreeView(const FileView &view); private: KFile(); // forbidden }; Q_DECLARE_OPERATORS_FOR_FLAGS(KFile::Modes) #endif // KFILE_H diff --git a/src/widgets/kopenwithdialog.cpp b/src/widgets/kopenwithdialog.cpp index 777dd09a..0be80c65 100644 --- a/src/widgets/kopenwithdialog.cpp +++ b/src/widgets/kopenwithdialog.cpp @@ -1,1164 +1,1164 @@ /* This file is part of the KDE libraries Copyright (C) 1997 Torben Weis Copyright (C) 1999 Dirk Mueller Portions copyright (C) 1999 Preston Brown Copyright (C) 2007 Pino Toscano 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 "kopenwithdialog.h" #include "kopenwithdialog_p.h" #include "kio_widgets_debug.h" #include #include #include -#include -#include +#include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include inline void writeEntry(KConfigGroup &group, const char *key, const KCompletion::CompletionMode &aValue, KConfigBase::WriteConfigFlags flags = KConfigBase::Normal) { group.writeEntry(key, int(aValue), flags); } namespace KDEPrivate { class AppNode { public: AppNode() : isDir(false), parent(nullptr), fetched(false) { } ~AppNode() { qDeleteAll(children); } QString icon; QString text; QString entryPath; QString exec; bool isDir; AppNode *parent; bool fetched; QList children; }; static bool AppNodeLessThan(KDEPrivate::AppNode *n1, KDEPrivate::AppNode *n2) { if (n1->isDir) { if (n2->isDir) { return n1->text.compare(n2->text, Qt::CaseInsensitive) < 0; } else { return true; } } else { if (n2->isDir) { return false; } else { return n1->text.compare(n2->text, Qt::CaseInsensitive) < 0; } } } } class KApplicationModelPrivate { public: KApplicationModelPrivate(KApplicationModel *qq) : q(qq), root(new KDEPrivate::AppNode()) { } ~KApplicationModelPrivate() { delete root; } void fillNode(const QString &entryPath, KDEPrivate::AppNode *node); KApplicationModel *q; KDEPrivate::AppNode *root; }; void KApplicationModelPrivate::fillNode(const QString &_entryPath, KDEPrivate::AppNode *node) { KServiceGroup::Ptr root = KServiceGroup::group(_entryPath); if (!root || !root->isValid()) { return; } const KServiceGroup::List list = root->entries(); for (KServiceGroup::List::ConstIterator it = list.begin(); it != list.end(); ++it) { QString icon; QString text; QString entryPath; QString exec; bool isDir = false; const KSycocaEntry::Ptr p = (*it); if (p->isType(KST_KService)) { const KService::Ptr service(static_cast(p.data())); if (service->noDisplay()) { continue; } icon = service->icon(); text = service->name(); exec = service->exec(); entryPath = service->entryPath(); } else if (p->isType(KST_KServiceGroup)) { const KServiceGroup::Ptr serviceGroup(static_cast(p.data())); if (serviceGroup->noDisplay() || serviceGroup->childCount() == 0) { continue; } icon = serviceGroup->icon(); text = serviceGroup->caption(); entryPath = serviceGroup->entryPath(); isDir = true; } else { qCWarning(KIO_WIDGETS) << "KServiceGroup: Unexpected object in list!"; continue; } KDEPrivate::AppNode *newnode = new KDEPrivate::AppNode(); newnode->icon = icon; newnode->text = text; newnode->entryPath = entryPath; newnode->exec = exec; newnode->isDir = isDir; newnode->parent = node; node->children.append(newnode); } qStableSort(node->children.begin(), node->children.end(), KDEPrivate::AppNodeLessThan); } KApplicationModel::KApplicationModel(QObject *parent) : QAbstractItemModel(parent), d(new KApplicationModelPrivate(this)) { d->fillNode(QString(), d->root); const int nRows = rowCount(); for (int i = 0; i < nRows; i++) { fetchAll(index(i, 0)); } } KApplicationModel::~KApplicationModel() { delete d; } bool KApplicationModel::canFetchMore(const QModelIndex &parent) const { if (!parent.isValid()) { return false; } KDEPrivate::AppNode *node = static_cast(parent.internalPointer()); return node->isDir && !node->fetched; } int KApplicationModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 1; } QVariant KApplicationModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } KDEPrivate::AppNode *node = static_cast(index.internalPointer()); switch (role) { case Qt::DisplayRole: return node->text; case Qt::DecorationRole: if (!node->icon.isEmpty()) { return QIcon::fromTheme(node->icon); } break; default: ; } return QVariant(); } void KApplicationModel::fetchMore(const QModelIndex &parent) { if (!parent.isValid()) { return; } KDEPrivate::AppNode *node = static_cast(parent.internalPointer()); if (!node->isDir) { return; } emit layoutAboutToBeChanged(); d->fillNode(node->entryPath, node); node->fetched = true; emit layoutChanged(); } void KApplicationModel::fetchAll(const QModelIndex &parent) { if (!parent.isValid() || !canFetchMore(parent)) { return; } fetchMore(parent); int childCount = rowCount(parent); for (int i = 0; i < childCount; i++) { const QModelIndex &child = parent.child(i, 0); // Recursively call the function for each child node. fetchAll(child); } } bool KApplicationModel::hasChildren(const QModelIndex &parent) const { if (!parent.isValid()) { return true; } KDEPrivate::AppNode *node = static_cast(parent.internalPointer()); return node->isDir; } QVariant KApplicationModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation != Qt::Horizontal || section != 0) { return QVariant(); } switch (role) { case Qt::DisplayRole: return i18n("Known Applications"); default: return QVariant(); } } QModelIndex KApplicationModel::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || column != 0) { return QModelIndex(); } KDEPrivate::AppNode *node = d->root; if (parent.isValid()) { node = static_cast(parent.internalPointer()); } if (row >= node->children.count()) { return QModelIndex(); } else { return createIndex(row, 0, node->children.at(row)); } } QModelIndex KApplicationModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } KDEPrivate::AppNode *node = static_cast(index.internalPointer()); if (node->parent->parent) { int id = node->parent->parent->children.indexOf(node->parent); if (id >= 0 && id < node->parent->parent->children.count()) { return createIndex(id, 0, node->parent); } else { return QModelIndex(); } } else { return QModelIndex(); } } int KApplicationModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return d->root->children.count(); } KDEPrivate::AppNode *node = static_cast(parent.internalPointer()); return node->children.count(); } QString KApplicationModel::entryPathFor(const QModelIndex &index) const { if (!index.isValid()) { return QString(); } KDEPrivate::AppNode *node = static_cast(index.internalPointer()); return node->entryPath; } QString KApplicationModel::execFor(const QModelIndex &index) const { if (!index.isValid()) { return QString(); } KDEPrivate::AppNode *node = static_cast(index.internalPointer()); return node->exec; } bool KApplicationModel::isDirectory(const QModelIndex &index) const { if (!index.isValid()) { return false; } KDEPrivate::AppNode *node = static_cast(index.internalPointer()); return node->isDir; } QTreeViewProxyFilter::QTreeViewProxyFilter(QObject *parent) : QSortFilterProxyModel(parent) { } bool QTreeViewProxyFilter::filterAcceptsRow(int sourceRow, const QModelIndex &parent) const { QModelIndex index = sourceModel()->index(sourceRow, 0, parent); if (!index.isValid()) { return false; } // Match the regexp only on leaf nodes if (!sourceModel()->hasChildren(index) && index.data().toString().contains(filterRegExp())) { return true; } //Show the non-leaf node also if the regexp matches one one of its children int rows = sourceModel()->rowCount(index); for (int crow = 0; crow < rows; crow++) { if (filterAcceptsRow(crow, index)) { return true; } } return false; } class KApplicationViewPrivate { public: KApplicationViewPrivate() : appModel(nullptr), m_proxyModel(nullptr) { } KApplicationModel *appModel; QSortFilterProxyModel *m_proxyModel; }; KApplicationView::KApplicationView(QWidget *parent) : QTreeView(parent), d(new KApplicationViewPrivate) { setHeaderHidden(true); } KApplicationView::~KApplicationView() { delete d; } void KApplicationView::setModels(KApplicationModel *model, QSortFilterProxyModel *proxyModel) { if (d->appModel) { disconnect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(slotSelectionChanged(QItemSelection,QItemSelection))); } QTreeView::setModel(proxyModel); // Here we set the proxy model d->m_proxyModel = proxyModel; // Also store it in a member property to avoid many casts later d->appModel = model; if (d->appModel) { connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(slotSelectionChanged(QItemSelection,QItemSelection))); } } QSortFilterProxyModel* KApplicationView::proxyModel() { return d->m_proxyModel; } bool KApplicationView::isDirSel() const { if (d->appModel) { QModelIndex index = selectionModel()->currentIndex(); index = d->m_proxyModel->mapToSource(index); return d->appModel->isDirectory(index); } return false; } void KApplicationView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { QTreeView::currentChanged(current, previous); if (d->appModel) { QModelIndex sourceCurrent = d->m_proxyModel->mapToSource(current); if(!d->appModel->isDirectory(sourceCurrent)) { QString exec = d->appModel->execFor(sourceCurrent); if (!exec.isEmpty()) { emit highlighted(d->appModel->entryPathFor(sourceCurrent), exec); } } } } void KApplicationView::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { Q_UNUSED(deselected) QItemSelection sourceSelected = d->m_proxyModel->mapSelectionToSource(selected); const QModelIndexList indexes = sourceSelected.indexes(); if (indexes.count() == 1) { QString exec = d->appModel->execFor(indexes.at(0)); emit this->selected(d->appModel->entryPathFor(indexes.at(0)), exec); } } /*************************************************************** * * KOpenWithDialog * ***************************************************************/ class KOpenWithDialogPrivate { public: KOpenWithDialogPrivate(KOpenWithDialog *qq) : q(qq), saveNewApps(false) { } KOpenWithDialog *q; /** * Determine mime type from URLs */ void setMimeType(const QList &_urls); void addToMimeAppsList(const QString &serviceId); /** * Create a dialog that asks for a application to open a given * URL(s) with. * * @param text appears as a label on top of the entry box. * @param value is the initial value of the line */ void init(const QString &text, const QString &value); /** * Called by checkAccept() in order to save the history of the combobox */ void saveComboboxHistory(); /** * Process the choices made by the user, and return true if everything is OK. * Called by KOpenWithDialog::accept(), i.e. when clicking on OK or typing Return. */ bool checkAccept(); // slots void _k_slotDbClick(); void _k_slotFileSelected(); bool saveNewApps; bool m_terminaldirty; KService::Ptr curService; KApplicationView *view; KUrlRequester *edit; QString m_command; QLabel *label; QString qMimeType; QString qMimeTypeComment; QCheckBox *terminal; QCheckBox *remember; QCheckBox *nocloseonexit; KService::Ptr m_pService; QDialogButtonBox *buttonBox; }; KOpenWithDialog::KOpenWithDialog(const QList &_urls, QWidget *parent) : QDialog(parent), d(new KOpenWithDialogPrivate(this)) { setObjectName(QStringLiteral("openwith")); setModal(true); setWindowTitle(i18n("Open With")); QString text; if (_urls.count() == 1) { text = i18n("Select the program that should be used to open %1. " "If the program is not listed, enter the name or click " "the browse button.", _urls.first().fileName().toHtmlEscaped()); } else // Should never happen ?? { text = i18n("Choose the name of the program with which to open the selected files."); } d->setMimeType(_urls); d->init(text, QString()); } KOpenWithDialog::KOpenWithDialog(const QList &_urls, const QString &_text, const QString &_value, QWidget *parent) : QDialog(parent), d(new KOpenWithDialogPrivate(this)) { setObjectName(QStringLiteral("openwith")); setModal(true); QString text = _text; if (text.isEmpty() && !_urls.isEmpty()) { if (_urls.count() == 1) { const QString fileName = KStringHandler::csqueeze(_urls.first().fileName()); text = i18n("Select the program you want to use to open the file
%1
", fileName.toHtmlEscaped()); } else { text = i18np("Select the program you want to use to open the file.", "Select the program you want to use to open the %1 files.", _urls.count()); } } setWindowTitle(i18n("Choose Application")); d->setMimeType(_urls); d->init(text, _value); } KOpenWithDialog::KOpenWithDialog(const QString &mimeType, const QString &value, QWidget *parent) : QDialog(parent), d(new KOpenWithDialogPrivate(this)) { setObjectName(QStringLiteral("openwith")); setModal(true); setWindowTitle(i18n("Choose Application for %1", mimeType)); QString text = i18n("Select the program for the file type: %1. " "If the program is not listed, enter the name or click " "the browse button.", mimeType); d->qMimeType = mimeType; QMimeDatabase db; d->qMimeTypeComment = db.mimeTypeForName(mimeType).comment(); d->init(text, value); if (d->remember) { d->remember->hide(); } } KOpenWithDialog::KOpenWithDialog(QWidget *parent) : QDialog(parent), d(new KOpenWithDialogPrivate(this)) { setObjectName(QStringLiteral("openwith")); setModal(true); setWindowTitle(i18n("Choose Application")); QString text = i18n("Select a program. " "If the program is not listed, enter the name or click " "the browse button."); d->qMimeType.clear(); d->init(text, QString()); } void KOpenWithDialogPrivate::setMimeType(const QList &_urls) { if (_urls.count() == 1) { QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl(_urls.first()); qMimeType = mime.name(); if (mime.isDefault()) { qMimeType.clear(); } else { qMimeTypeComment = mime.comment(); } } else { qMimeType.clear(); } } void KOpenWithDialogPrivate::init(const QString &_text, const QString &_value) { bool bReadOnly = !KAuthorized::authorize(QStringLiteral("shell_access")); m_terminaldirty = false; view = nullptr; m_pService = nullptr; curService = nullptr; QBoxLayout *topLayout = new QVBoxLayout; q->setLayout(topLayout); label = new QLabel(_text, q); label->setWordWrap(true); topLayout->addWidget(label); if (!bReadOnly) { // init the history combo and insert it into the URL-Requester KHistoryComboBox *combo = new KHistoryComboBox(); combo->setToolTip(i18n("Type to filter the applications below, or specify the name of a command.\nPress down arrow to navigate the results.")); KLineEdit *lineEdit = new KLineEdit(q); lineEdit->setClearButtonShown(true); combo->setLineEdit(lineEdit); combo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); combo->setDuplicatesEnabled(false); KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("Open-with settings")); int max = cg.readEntry("Maximum history", 15); combo->setMaxCount(max); int mode = cg.readEntry("CompletionMode", int(KCompletion::CompletionNone)); combo->setCompletionMode(static_cast(mode)); const QStringList list = cg.readEntry("History", QStringList()); combo->setHistoryItems(list, true); edit = new KUrlRequester(combo, q); edit->installEventFilter(q); } else { edit = new KUrlRequester(q); edit->lineEdit()->setReadOnly(true); edit->button()->hide(); } edit->setText(_value); edit->setWhatsThis(i18n( "Following the command, you can have several place holders which will be replaced " "with the actual values when the actual program is run:\n" "%f - a single file name\n" "%F - a list of files; use for applications that can open several local files at once\n" "%u - a single URL\n" "%U - a list of URLs\n" "%d - the directory of the file to open\n" "%D - a list of directories\n" "%i - the icon\n" "%m - the mini-icon\n" "%c - the comment")); topLayout->addWidget(edit); if (edit->comboBox()) { KUrlCompletion *comp = new KUrlCompletion(KUrlCompletion::ExeCompletion); edit->comboBox()->setCompletionObject(comp); edit->comboBox()->setAutoDeleteCompletionObject(true); } QObject::connect(edit, SIGNAL(textChanged(QString)), q, SLOT(slotTextChanged())); QObject::connect(edit, SIGNAL(urlSelected(QUrl)), q, SLOT(_k_slotFileSelected())); QTreeViewProxyFilter *proxyModel = new QTreeViewProxyFilter(view); KApplicationModel *appModel = new KApplicationModel(proxyModel); proxyModel->setSourceModel(appModel); proxyModel->setFilterKeyColumn(0); proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); view = new KApplicationView(q); view->setModels(appModel, proxyModel); topLayout->addWidget(view); topLayout->setStretchFactor(view, 1); QObject::connect(view, SIGNAL(selected(QString,QString)), q, SLOT(slotSelected(QString,QString))); QObject::connect(view, SIGNAL(highlighted(QString,QString)), q, SLOT(slotHighlighted(QString,QString))); QObject::connect(view, SIGNAL(doubleClicked(QModelIndex)), q, SLOT(_k_slotDbClick())); if (!qMimeType.isNull()) { remember = new QCheckBox(i18n("&Remember application association for all files of type\n\"%1\" (%2)", qMimeTypeComment, qMimeType)); // remember->setChecked(true); topLayout->addWidget(remember); } else { remember = nullptr; } //Advanced options KCollapsibleGroupBox *dialogExtension = new KCollapsibleGroupBox(q); dialogExtension->setTitle(i18n("Terminal options")); QVBoxLayout *dialogExtensionLayout = new QVBoxLayout; dialogExtensionLayout->setMargin(0); terminal = new QCheckBox(i18n("Run in &terminal"), q); if (bReadOnly) { terminal->hide(); } QObject::connect(terminal, SIGNAL(toggled(bool)), q, SLOT(slotTerminalToggled(bool))); dialogExtensionLayout->addWidget(terminal); QStyleOptionButton checkBoxOption; checkBoxOption.initFrom(terminal); int checkBoxIndentation = terminal->style()->pixelMetric(QStyle::PM_IndicatorWidth, &checkBoxOption, terminal); checkBoxIndentation += terminal->style()->pixelMetric(QStyle::PM_CheckBoxLabelSpacing, &checkBoxOption, terminal); QBoxLayout *nocloseonexitLayout = new QHBoxLayout(); nocloseonexitLayout->setMargin(0); QSpacerItem *spacer = new QSpacerItem(checkBoxIndentation, 0, QSizePolicy::Fixed, QSizePolicy::Minimum); nocloseonexitLayout->addItem(spacer); nocloseonexit = new QCheckBox(i18n("&Do not close when command exits"), q); nocloseonexit->setChecked(false); nocloseonexit->setDisabled(true); // check to see if we use konsole if not disable the nocloseonexit // because we don't know how to do this on other terminal applications KConfigGroup confGroup(KSharedConfig::openConfig(), QStringLiteral("General")); QString preferredTerminal = confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole")); if (bReadOnly || preferredTerminal != QStringLiteral("konsole")) { nocloseonexit->hide(); } nocloseonexitLayout->addWidget(nocloseonexit); dialogExtensionLayout->addLayout(nocloseonexitLayout); dialogExtension->setLayout(dialogExtensionLayout); topLayout->addWidget(dialogExtension); buttonBox = new QDialogButtonBox(q); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); q->connect(buttonBox, SIGNAL(accepted()), q, SLOT(accept())); q->connect(buttonBox, SIGNAL(rejected()), q, SLOT(reject())); topLayout->addWidget(buttonBox); q->setMinimumSize(q->minimumSizeHint()); //edit->setText( _value ); // The resize is what caused "can't click on items before clicking on Name header" in previous versions. // Probably due to the resizeEvent handler using width(). q->resize( q->minimumWidth(), 0.6*QApplication::desktop()->availableGeometry().height()); edit->setFocus(); q->slotTextChanged(); } // ---------------------------------------------------------------------- KOpenWithDialog::~KOpenWithDialog() { delete d; } // ---------------------------------------------------------------------- void KOpenWithDialog::slotSelected(const QString & /*_name*/, const QString &_exec) { d->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!_exec.isEmpty()); } // ---------------------------------------------------------------------- void KOpenWithDialog::slotHighlighted(const QString &entryPath, const QString &) { d->curService = KService::serviceByDesktopPath(entryPath); if (d->curService && !d->m_terminaldirty) { // ### indicate that default value was restored d->terminal->setChecked(d->curService->terminal()); QString terminalOptions = d->curService->terminalOptions(); d->nocloseonexit->setChecked((terminalOptions.contains(QLatin1String("--noclose")))); d->m_terminaldirty = false; // slotTerminalToggled changed it } } // ---------------------------------------------------------------------- void KOpenWithDialog::slotTextChanged() { // Forget about the service only when the selection is empty // otherwise changing text but hitting the same result clears curService bool selectionEmpty = !d->view->currentIndex().isValid(); if (d->curService && selectionEmpty) { d->curService = nullptr; } d->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!d->edit->text().isEmpty() || d->curService); //Update the filter regexp with the new text in the lineedit d->view->proxyModel()->setFilterFixedString(d->edit->text()); //Expand all the nodes when the search string is 3 characters long //If the search string doesn't match anything there will be no nodes to expand if (d->edit->text().size() > 2) { d->view->expandAll(); //Automatically select the first result (first leaf node) when the filter has match QModelIndex leafNodeIdx = d->view->model()->index(0, 0); while (d->view->model()->hasChildren(leafNodeIdx)) { leafNodeIdx = leafNodeIdx.child(0,0); } d->view->setCurrentIndex(leafNodeIdx); } else { d->view->collapseAll(); d->view->setCurrentIndex(d->view->rootIndex()); // Unset and deselect all the elements d->curService = nullptr; } } // ---------------------------------------------------------------------- void KOpenWithDialog::slotTerminalToggled(bool) { // ### indicate that default value was overridden d->m_terminaldirty = true; d->nocloseonexit->setDisabled(!d->terminal->isChecked()); } // ---------------------------------------------------------------------- void KOpenWithDialogPrivate::_k_slotDbClick() { // check if a directory is selected if (view->isDirSel()) { return; } q->accept(); } void KOpenWithDialogPrivate::_k_slotFileSelected() { // quote the path to avoid unescaped whitespace, backslashes, etc. edit->setText(KShell::quoteArg(edit->text())); } void KOpenWithDialog::setSaveNewApplications(bool b) { d->saveNewApps = b; } static QString simplifiedExecLineFromService(const QString &fullExec) { QString exec = fullExec; exec.remove(QStringLiteral("%u"), Qt::CaseInsensitive); exec.remove(QStringLiteral("%f"), Qt::CaseInsensitive); exec.remove(QStringLiteral("-caption %c")); exec.remove(QStringLiteral("-caption \"%c\"")); exec.remove(QStringLiteral("%i")); exec.remove(QStringLiteral("%m")); return exec.simplified(); } void KOpenWithDialogPrivate::addToMimeAppsList(const QString &serviceId /*menu id or storage id*/) { KSharedConfig::Ptr profile = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation); // Save the default application according to mime-apps-spec 1.0 KConfigGroup defaultApp(profile, "Default Applications"); defaultApp.writeXdgListEntry(qMimeType, QStringList(serviceId)); KConfigGroup addedApps(profile, "Added Associations"); QStringList apps = addedApps.readXdgListEntry(qMimeType); apps.removeAll(serviceId); apps.prepend(serviceId); // make it the preferred app addedApps.writeXdgListEntry(qMimeType, apps); profile->sync(); // Also make sure the "auto embed" setting for this mimetype is off KSharedConfig::Ptr fileTypesConfig = KSharedConfig::openConfig(QStringLiteral("filetypesrc"), KConfig::NoGlobals); fileTypesConfig->group("EmbedSettings").writeEntry(QStringLiteral("embed-") + qMimeType, false); fileTypesConfig->sync(); // qDebug() << "rebuilding ksycoca..."; // kbuildsycoca is the one reading mimeapps.list, so we need to run it now KBuildSycocaProgressDialog::rebuildKSycoca(q); // could be nullptr if the user canceled the dialog... m_pService = KService::serviceByStorageId(serviceId); } bool KOpenWithDialogPrivate::checkAccept() { const QString typedExec(edit->text()); QString fullExec(typedExec); QString serviceName; QString initialServiceName; QString preferredTerminal; QString configPath; QString serviceExec; m_pService = curService; if (!m_pService) { // No service selected - check the command line // Find out the name of the service from the command line, removing args and paths serviceName = KIO::DesktopExecParser::executableName(typedExec); if (serviceName.isEmpty()) { KMessageBox::error(q, i18n("Could not extract executable name from '%1', please type a valid program name.", serviceName)); return false; } initialServiceName = serviceName; // Also remember the executableName with a path, if any, for the // check that the executable exists. // qDebug() << "initialServiceName=" << initialServiceName; int i = 1; // We have app, app-2, app-3... Looks better for the user. bool ok = false; // Check if there's already a service by that name, with the same Exec line do { // qDebug() << "looking for service" << serviceName; KService::Ptr serv = KService::serviceByDesktopName(serviceName); ok = !serv; // ok if no such service yet // also ok if we find the exact same service (well, "kwrite" == "kwrite %U") if (serv && !serv->noDisplay() /* #297720 */) { if (serv->isApplication()) { /*// qDebug() << "typedExec=" << typedExec << "serv->exec=" << serv->exec() << "simplifiedExecLineFromService=" << simplifiedExecLineFromService(fullExec);*/ serviceExec = simplifiedExecLineFromService(serv->exec()); if (typedExec == serviceExec) { ok = true; m_pService = serv; // qDebug() << "OK, found identical service: " << serv->entryPath(); } else { // qDebug() << "Exec line differs, service says:" << serviceExec; configPath = serv->entryPath(); serviceExec = serv->exec(); } } else { // qDebug() << "Found, but not an application:" << serv->entryPath(); } } if (!ok) { // service was found, but it was different -> keep looking ++i; serviceName = initialServiceName + '-' + QString::number(i); } } while (!ok); } if (m_pService) { // Existing service selected serviceName = m_pService->name(); initialServiceName = serviceName; fullExec = m_pService->exec(); } else { const QString binaryName = KIO::DesktopExecParser::executablePath(typedExec); // qDebug() << "binaryName=" << binaryName; // Ensure that the typed binary name actually exists (#81190) if (QStandardPaths::findExecutable(binaryName).isEmpty()) { KMessageBox::error(q, i18n("'%1' not found, please type a valid program name.", binaryName)); return false; } } if (terminal->isChecked()) { KConfigGroup confGroup(KSharedConfig::openConfig(), QStringLiteral("General")); preferredTerminal = confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole")); m_command = preferredTerminal; // only add --noclose when we are sure it is konsole we're using if (preferredTerminal == QStringLiteral("konsole") && nocloseonexit->isChecked()) { m_command += QStringLiteral(" --noclose"); } m_command += QStringLiteral(" -e "); m_command += edit->text(); // qDebug() << "Setting m_command to" << m_command; } if (m_pService && terminal->isChecked() != m_pService->terminal()) { m_pService = nullptr; // It's not exactly this service we're running } const bool bRemember = remember && remember->isChecked(); // qDebug() << "bRemember=" << bRemember << "service found=" << m_pService; if (m_pService) { if (bRemember) { // Associate this app with qMimeType in mimeapps.list Q_ASSERT(!qMimeType.isEmpty()); // we don't show the remember checkbox otherwise addToMimeAppsList(m_pService->storageId()); } } else { const bool createDesktopFile = bRemember || saveNewApps; if (!createDesktopFile) { // Create temp service if (configPath.isEmpty()) { m_pService = new KService(initialServiceName, fullExec, QString()); } else { if (!typedExec.contains(QLatin1String("%u"), Qt::CaseInsensitive) && !typedExec.contains(QLatin1String("%f"), Qt::CaseInsensitive)) { int index = serviceExec.indexOf(QLatin1String("%u"), 0, Qt::CaseInsensitive); if (index == -1) { index = serviceExec.indexOf(QLatin1String("%f"), 0, Qt::CaseInsensitive); } if (index > -1) { fullExec += QLatin1Char(' '); fullExec += serviceExec.midRef(index, 2); } } // qDebug() << "Creating service with Exec=" << fullExec; m_pService = new KService(configPath); m_pService->setExec(fullExec); } if (terminal->isChecked()) { m_pService->setTerminal(true); // only add --noclose when we are sure it is konsole we're using if (preferredTerminal == QLatin1String("konsole") && nocloseonexit->isChecked()) { m_pService->setTerminalOptions(QStringLiteral("--noclose")); } } } else { // If we got here, we can't seem to find a service for what they wanted. Create one. QString menuId; #ifdef Q_OS_WIN32 // on windows, do not use the complete path, but only the default name. serviceName = QFileInfo(serviceName).fileName(); #endif QString newPath = KService::newServicePath(false /* ignored argument */, serviceName, &menuId); // qDebug() << "Creating new service" << serviceName << "(" << newPath << ")" << "menuId=" << menuId; KDesktopFile desktopFile(newPath); KConfigGroup cg = desktopFile.desktopGroup(); cg.writeEntry("Type", "Application"); cg.writeEntry("Name", initialServiceName); cg.writeEntry("Exec", fullExec); cg.writeEntry("NoDisplay", true); // don't make it appear in the K menu if (terminal->isChecked()) { cg.writeEntry("Terminal", true); // only add --noclose when we are sure it is konsole we're using if (preferredTerminal == QLatin1String("konsole") && nocloseonexit->isChecked()) { cg.writeEntry("TerminalOptions", "--noclose"); } } if (!qMimeType.isEmpty()) { cg.writeXdgListEntry("MimeType", QStringList() << qMimeType); } cg.sync(); if (!qMimeType.isEmpty()) { addToMimeAppsList(menuId); } else { m_pService = new KService(newPath); } } } saveComboboxHistory(); return true; } bool KOpenWithDialog::eventFilter(QObject *object, QEvent *event) { // Detect DownArrow to navigate the results in the QTreeView if (object == d->edit && event->type() == QEvent::ShortcutOverride) { QKeyEvent *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Down) { KHistoryComboBox *combo = static_cast(d->edit->comboBox()); // FIXME: Disable arrow down in CompletionPopup and CompletionPopupAuto only when the dropdown list is shown. // When popup completion mode is used the down arrow is used to navigate the dropdown list of results if (combo->completionMode() != KCompletion::CompletionPopup && combo->completionMode() != KCompletion::CompletionPopupAuto) { QModelIndex leafNodeIdx = d->view->model()->index(0, 0); // Check if we have at least one result or the focus is passed to the empty QTreeView if (d->view->model()->hasChildren(leafNodeIdx)) { d->view->setFocus(Qt::OtherFocusReason); QApplication::sendEvent(d->view, keyEvent); return true; } } } } return QDialog::eventFilter(object, event); } void KOpenWithDialog::accept() { if (d->checkAccept()) { QDialog::accept(); } } QString KOpenWithDialog::text() const { if (!d->m_command.isEmpty()) { return d->m_command; } else { return d->edit->text(); } } void KOpenWithDialog::hideNoCloseOnExit() { // uncheck the checkbox because the value could be used when "Run in Terminal" is selected d->nocloseonexit->setChecked(false); d->nocloseonexit->hide(); } void KOpenWithDialog::hideRunInTerminal() { d->terminal->hide(); hideNoCloseOnExit(); } KService::Ptr KOpenWithDialog::service() const { return d->m_pService; } void KOpenWithDialogPrivate::saveComboboxHistory() { KHistoryComboBox *combo = static_cast(edit->comboBox()); if (combo) { combo->addToHistory(edit->text()); KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("Open-with settings")); cg.writeEntry("History", combo->historyItems()); writeEntry(cg, "CompletionMode", combo->completionMode()); // don't store the completion-list, as it contains all of KUrlCompletion's // executables cg.sync(); } } #include "moc_kopenwithdialog.cpp" #include "moc_kopenwithdialog_p.cpp" diff --git a/src/widgets/kopenwithdialog_p.h b/src/widgets/kopenwithdialog_p.h index 71c7fc2a..a1411a90 100644 --- a/src/widgets/kopenwithdialog_p.h +++ b/src/widgets/kopenwithdialog_p.h @@ -1,110 +1,110 @@ /* This file is part of the KDE libraries Copyright (C) 2000 David Faure Copyright (C) 2007 Pino Toscano 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. */ #ifndef OPENWITHDIALOG_P_H #define OPENWITHDIALOG_P_H -#include +#include #include #include class KApplicationModelPrivate; /** * @internal */ class KApplicationModel : public QAbstractItemModel { Q_OBJECT public: KApplicationModel(QObject *parent = nullptr); virtual ~KApplicationModel(); bool canFetchMore(const QModelIndex &parent) const Q_DECL_OVERRIDE; int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; void fetchMore(const QModelIndex &parent) Q_DECL_OVERRIDE; // Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE; bool hasChildren(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE; int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; QString entryPathFor(const QModelIndex &index) const; QString execFor(const QModelIndex &index) const; bool isDirectory(const QModelIndex &index) const; void fetchAll(const QModelIndex &parent); private: friend class KApplicationModelPrivate; KApplicationModelPrivate *const d; Q_DISABLE_COPY(KApplicationModel) }; /** * @internal */ class QTreeViewProxyFilter : public QSortFilterProxyModel { Q_OBJECT public: QTreeViewProxyFilter(QObject *parent = nullptr); bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const Q_DECL_OVERRIDE; }; class KApplicationViewPrivate; /** * @internal */ class KApplicationView : public QTreeView { Q_OBJECT public: KApplicationView(QWidget *parent = nullptr); ~KApplicationView(); void setModels(KApplicationModel *model, QSortFilterProxyModel *proxyModel); QSortFilterProxyModel* proxyModel(); bool isDirSel() const; Q_SIGNALS: void selected(const QString &_name, const QString &_exec); void highlighted(const QString &_name, const QString &_exec); protected Q_SLOTS: void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) Q_DECL_OVERRIDE; private Q_SLOTS: void slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); private: friend class KApplicationViewPrivate; KApplicationViewPrivate *const d; Q_DISABLE_COPY(KApplicationView) }; #endif diff --git a/src/widgets/koverlayiconplugin.h b/src/widgets/koverlayiconplugin.h index 49279efe..3530713d 100644 --- a/src/widgets/koverlayiconplugin.h +++ b/src/widgets/koverlayiconplugin.h @@ -1,65 +1,65 @@ /* This file is part of the KDE project Copyright (C) 2015 Olivier Goffart 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 ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), 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. */ #ifndef KOVERLAYICONPLUGIN_H #define KOVERLAYICONPLUGIN_H #include "kiowidgets_export.h" -#include +#include class QUrl; /** * @class KOverlayIconPlugin koverlayiconplugin.h * * @brief Base class for overlay icon plugins. * * Enables the file manager to show custom overlay icons on files. * * To write a custom plugin you need to create a .desktop file for your plugin with * KDE-ServiceTypes=KOverlayIconPlugin * * @since 5.16 */ class KIOWIDGETS_EXPORT KOverlayIconPlugin : public QObject { Q_OBJECT public: explicit KOverlayIconPlugin(QObject *parent = nullptr); ~KOverlayIconPlugin(); /** * Returns a list of overlay icons to add to a file * This can be a path to an icon, or the icon name * * This function is called from the main thread and must not block. * It is recommended to have a cache. And if the item is not in cache * just return an empty list and call the overlaysChanged when the * information is available. */ virtual QStringList getOverlays(const QUrl &item) = 0; Q_SIGNALS: /** * Emit this signal when the list of overlay icons changed for a given URL */ void overlaysChanged(const QUrl &url, const QStringList &overlays); }; #endif diff --git a/src/widgets/kpropertiesdialog.h b/src/widgets/kpropertiesdialog.h index 44bfc1a2..1658df08 100644 --- a/src/widgets/kpropertiesdialog.h +++ b/src/widgets/kpropertiesdialog.h @@ -1,465 +1,465 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (c) 1999, 2000 Preston Brown Copyright (c) 2000 Simon Hausmann Copyright (c) 2000 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 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. */ #ifndef KPROPERTIESDIALOG_H #define KPROPERTIESDIALOG_H -#include +#include #include #include "kiowidgets_export.h" #include #include class KPropertiesDialogPlugin; class KJob; namespace KIO { class Job; } /** * @class KPropertiesDialog kpropertiesdialog.h * * The main properties dialog class. * A Properties Dialog is a dialog which displays various information * about a particular file or URL, or several files or URLs. * This main class holds various related classes, which are instantiated in * the form of tab entries in the tabbed dialog that this class provides. * The various tabs themselves will let the user view, and sometimes change, * information about the file or URL. * * \image html kpropertiesdialog.png "Example of KPropertiesDialog" * * The best way to display the properties dialog is to use showDialog(). * Otherwise, you should use (void)new KPropertiesDialog(...) * It will take care of deleting itself when closed. * * If you are looking for more flexibility, see KFileMetaInfo and * KFileMetaInfoWidget. * * This respects the "editfiletype", "run_desktop_files" and "shell_access" * Kiosk action restrictions (see KAuthorized::authorize()). */ class KIOWIDGETS_EXPORT KPropertiesDialog : public KPageDialog { Q_OBJECT public: /** * Determine whether there are any property pages available for the * given file items. * @param _items the list of items to check. * @return true if there are any property pages, otherwise false. */ static bool canDisplay(const KFileItemList &_items); /** * Brings up a Properties dialog, as shown above. * This is the normal constructor for * file-manager type applications, where you have a KFileItem instance * to work with. Normally you will use this * method rather than the one below. * * @param item file item whose properties should be displayed. * @param parent is the parent of the dialog widget. */ explicit KPropertiesDialog(const KFileItem &item, QWidget *parent = nullptr); /** * \overload * * You use this constructor for cases where you have a number of items, * rather than a single item. Be careful which methods you use * when passing a list of files or URLs, since some of them will only * work on the first item in a list. * * @param _items list of file items whose properties should be displayed. * @param parent is the parent of the dialog widget. */ explicit KPropertiesDialog(const KFileItemList &_items, QWidget *parent = nullptr); /** * Brings up a Properties dialog. Convenience constructor for * non-file-manager applications, where you have a QUrl rather than a * KFileItem or KFileItemList. * * @param url the URL whose properties should be displayed * @param parent is the parent of the dialog widget. * * For local files with a known mimetype, simply create a KFileItem * and pass it to the other constructor. */ explicit KPropertiesDialog(const QUrl &url, QWidget *parent = nullptr); /** * Brings up a Properties dialog. Convenience constructor for * non-file-manager applications, where you have a list of QUrls rather * than a KFileItemList. * * @param urls list of URLs whose properties should be displayed (must * contain at least one non-empty URL) * @param parent is the parent of the dialog widget. * * For local files with a known mimetype, simply create a KFileItemList * and pass it to the other constructor. * * @since 5.10 */ explicit KPropertiesDialog(const QList &urls, QWidget *parent = nullptr); /** * Creates a properties dialog for a new .desktop file (whose name * is not known yet), based on a template. Special constructor for * "File / New" in file-manager type applications. * * @param _tempUrl template used for reading only * @param _currentDir directory where the file will be written to * @param _defaultName something to put in the name field, * like mimetype.desktop * @param parent is the parent of the dialog widget. */ KPropertiesDialog(const QUrl &_tempUrl, const QUrl &_currentDir, const QString &_defaultName, QWidget *parent = nullptr); /** * Creates an empty properties dialog (for applications that want use * a standard dialog, but for things not doable via the plugin-mechanism). * * @param title is the string display as the "filename" in the caption of the dialog. * @param parent is the parent of the dialog widget. */ explicit KPropertiesDialog(const QString &title, QWidget *parent = nullptr); /** * Cleans up the properties dialog and frees any associated resources, * including the dialog itself. Note that when a properties dialog is * closed it cleans up and deletes itself. */ virtual ~KPropertiesDialog(); /** * Immediately displays a Properties dialog using constructor with * the same parameters. * On MS Windows, if @p item points to a local file, native (non modal) property * dialog is displayed (@p parent and @p modal are ignored in this case). * * @return true on successful dialog displaying (can be false on win32). */ static bool showDialog(const KFileItem &item, QWidget *parent = nullptr, bool modal = true); /** * Immediately displays a Properties dialog using constructor with * the same parameters. * On MS Windows, if @p _url points to a local file, native (non modal) property * dialog is displayed (@p parent and @p modal are ignored in this case). * * @return true on successful dialog displaying (can be false on win32). */ static bool showDialog(const QUrl &_url, QWidget *parent = nullptr, bool modal = true); /** * Immediately displays a Properties dialog using constructor with * the same parameters. * On MS Windows, if @p _items has one element and this element points * to a local file, native (non modal) property dialog is displayed * (@p parent and @p modal are ignored in this case). * * @return true on successful dialog displaying (can be false on win32). */ static bool showDialog(const KFileItemList &_items, QWidget *parent = nullptr, bool modal = true); /** * Immediately displays a Properties dialog using constructor with * the same parameters. * * On MS Windows, if @p _urls has one element and this element points * to a local file, native (non modal) property dialog is displayed * (@p parent and @p modal are ignored in this case). * * @param urls list of URLs whose properties should be displayed (must * contain at least one non-empty URL) * @param parent is the parent of the dialog widget. * @param modal tells the dialog whether it should be modal. * * @return true on successful dialog displaying (can be false on win32). * * @since 5.10 */ static bool showDialog(const QList &urls, QWidget *parent = nullptr, bool modal = true); /** * Adds a "3rd party" properties plugin to the dialog. Useful * for extending the properties mechanism. * * To create a new plugin type, inherit from the base class KPropertiesDialogPlugin * and implement all the methods. If you define a service .desktop file * for your plugin, you do not need to call insertPlugin(). * * @param plugin is a pointer to the KPropertiesDialogPlugin. The Properties * dialog will do destruction for you. The KPropertiesDialogPlugin \b must * have been created with the KPropertiesDialog as its parent. * @see KPropertiesDialogPlugin */ void insertPlugin(KPropertiesDialogPlugin *plugin); #ifndef KIOWIDGETS_NO_DEPRECATED /** * @deprecated since 5.0, use url() */ KIOWIDGETS_DEPRECATED QUrl kurl() const { return url(); } #endif /** * The URL of the file that has its properties being displayed. * This is only valid if the KPropertiesDialog was created/shown * for one file or URL. * * @return the single URL. */ QUrl url() const; /** * @return the file item for which the dialog is shown * * Warning: this method returns the first item of the list. * This means that you should use this only if you are sure the dialog is used * for a single item. Otherwise, you probably want items() instead. */ KFileItem &item(); /** * @return the items for which the dialog is shown */ KFileItemList items() const; /** * If the dialog is being built from a template, this method * returns the current directory. If no template, it returns QString(). * See the template form of the constructor. * * @return the current directory or QString() */ QUrl currentDir() const; /** * If the dialog is being built from a template, this method * returns the default name. If no template, it returns QString(). * See the template form of the constructor. * @return the default name or QString() */ QString defaultName() const; /** * Updates the item URL (either called by rename or because * a global apps/mimelnk desktop file is being saved) * Can only be called if the dialog applies to a single file or URL. * @param newUrl the new URL */ void updateUrl(const QUrl &newUrl); /** * Renames the item to the specified name. This can only be called if * the dialog applies to a single file or URL. * @param _name new filename, encoded. * \see FilePropsDialogPlugin::applyChanges */ void rename(const QString &_name); /** * To abort applying changes. */ void abortApplying(); /** * Shows the page that was previously set by * setFileSharingPage(), or does nothing if no page * was set yet. * \see setFileSharingPage */ void showFileSharingPage(); /** * Sets the file sharing page. * This page is shown when calling showFileSharingPage(). * * @param page the page to set * \see showFileSharingPage */ void setFileSharingPage(QWidget *page); /** * Call this to make the filename lineedit readonly, to prevent the user * from renaming the file. * \param ro true if the lineedit should be read only */ void setFileNameReadOnly(bool ro); using KPageDialog::buttonBox; public Q_SLOTS: /** * Called when the user presses 'Ok'. * @deprecated since 5.25, use accept() */ KIOWIDGETS_DEPRECATED virtual void slotOk(); /** * Called when the user presses 'Cancel'. * @deprecated since 5.25, use reject() */ KIOWIDGETS_DEPRECATED virtual void slotCancel(); /** * Called when the user presses 'Ok'. * @since 5.25 */ void accept() Q_DECL_OVERRIDE; /** * Called when the user presses 'Cancel' or Esc. * @since 5.25 */ void reject() Q_DECL_OVERRIDE; Q_SIGNALS: /** * This signal is emitted when the Properties Dialog is closed (for * example, with OK or Cancel buttons) */ void propertiesClosed(); /** * This signal is emitted when the properties changes are applied (for * example, with the OK button) */ void applied(); /** * This signal is emitted when the properties changes are aborted (for * example, with the Cancel button) */ void canceled(); /** * Emitted before changes to @p oldUrl are saved as @p newUrl. * The receiver may change @p newUrl to point to an alternative * save location. */ void saveAs(const QUrl &oldUrl, QUrl &newUrl); Q_SIGNALS: void leaveModality(); private: class KPropertiesDialogPrivate; KPropertiesDialogPrivate *const d; Q_DISABLE_COPY(KPropertiesDialog) }; /** * A Plugin in the Properties dialog * This is an abstract class. You must inherit from this class * to build a new kind of tabbed page for the KPropertiesDialog. * A plugin in itself is just a library containing code, not a dialog's page. * It's up to the plugin to insert pages into the parent dialog. * * To make a plugin available, define a service that implements the KPropertiesDialog/Plugin * servicetype, as well as the mimetypes for which the plugin should be created. * For instance, ServiceTypes=KPropertiesDialog/Plugin,text/html,application/x-mymimetype. * * You can also include X-KDE-Protocol=file if you want that plugin * to be loaded only for local files, for instance. */ class KIOWIDGETS_EXPORT KPropertiesDialogPlugin : public QObject { Q_OBJECT public: /** * Constructor * To insert tabs into the properties dialog, use the add methods provided by * KPageDialog (the properties dialog is a KPageDialog). */ KPropertiesDialogPlugin(KPropertiesDialog *_props); virtual ~KPropertiesDialogPlugin(); /** * Applies all changes to the file. * This function is called when the user presses 'Ok'. The last plugin inserted * is called first. */ virtual void applyChanges(); /** * Convenience method for most ::supports methods * @return true if the file is a local, regular, readable, desktop file * @deprecated use KFileItem::isDesktopFile */ #ifndef KIOWIDGETS_NO_DEPRECATED static KIOWIDGETS_DEPRECATED bool isDesktopFile(const KFileItem &_item); #endif void setDirty(bool b); bool isDirty() const; public Q_SLOTS: void setDirty(); // same as setDirty( true ). TODO KDE5: void setDirty(bool dirty=true); Q_SIGNALS: /** * Emit this signal when the user changed anything in the plugin's tabs. * The hosting PropertiesDialog will call applyChanges only if the * PropsPlugin has emitted this signal or if you have called setDirty() before. */ void changed(); protected: /** * Pointer to the dialog */ KPropertiesDialog *properties; /** * Returns the font height. */ int fontHeight() const; private: class KPropertiesDialogPluginPrivate; KPropertiesDialogPluginPrivate *const d; }; #endif diff --git a/src/widgets/krun.cpp b/src/widgets/krun.cpp index cd592605..0e531777 100644 --- a/src/widgets/krun.cpp +++ b/src/widgets/krun.cpp @@ -1,1640 +1,1640 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Torben Weis Copyright (C) 2006 David Faure Copyright (C) 2009 Michael Pyne 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 "krun.h" #include "krun_p.h" #include // HAVE_X11 #include "kio_widgets_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kio/job.h" #include "kio/global.h" #include "kio/scheduler.h" #include "kopenwithdialog.h" #include "krecentdocument.h" #include "kdesktopfileactions.h" #include "executablefileopendialog_p.h" #include #include #include #include #include #include #include #include #include #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include #include #include #include #include #if HAVE_X11 #include #elif defined(Q_OS_WIN) #include #endif #include #include KRun::KRunPrivate::KRunPrivate(KRun *parent) : q(parent), m_showingDialog(false) { } void KRun::KRunPrivate::startTimer() { m_timer->start(0); } // --------------------------------------------------------------------------- static QString schemeHandler(const QString &protocol) { // We have up to two sources of data, for protocols not handled by kioslaves (so called "helper") : // 1) the exec line of the .protocol file, if there's one // 2) the application associated with x-scheme-handler/ if there's one // If both exist, then: // A) if the .protocol file says "launch an application", then the new-style handler-app has priority // B) but if the .protocol file is for a kioslave (e.g. kio_http) then this has priority over // firefox or chromium saying x-scheme-handler/http. Gnome people want to send all HTTP urls // to a webbrowser, but we want mimetype-determination-in-calling-application by default // (the user can configure a BrowserApplication though) const KService::Ptr service = KMimeTypeTrader::self()->preferredService(QLatin1String("x-scheme-handler/") + protocol); if (service) { return service->exec(); // for helper protocols, the handler app has priority over the hardcoded one (see A above) } Q_ASSERT(KProtocolInfo::isHelperProtocol(protocol)); return KProtocolInfo::exec(protocol); } // --------------------------------------------------------------------------- bool KRun::isExecutableFile(const QUrl &url, const QString &mimetype) { if (!url.isLocalFile()) { return false; } QFileInfo file(url.toLocalFile()); if (file.isExecutable()) { // Got a prospective file to run QMimeDatabase db; QMimeType mimeType = db.mimeTypeForName(mimetype); if (mimeType.inherits(QStringLiteral("application/x-executable")) || #ifdef Q_OS_WIN mimeType.inherits(QLatin1String("application/x-ms-dos-executable")) || #endif mimeType.inherits(QStringLiteral("application/x-executable-script")) || mimeType.inherits(QStringLiteral("application/x-sharedlib")) ) { return true; } } return false; } void KRun::handleInitError(int kioErrorCode, const QString &errorMsg) { Q_UNUSED(kioErrorCode); d->m_showingDialog = true; KMessageBox::error(d->m_window, errorMsg); d->m_showingDialog = false; } void KRun::handleError(KJob *job) { Q_ASSERT(job); if (job) { d->m_showingDialog = true; job->uiDelegate()->showErrorMessage(); d->m_showingDialog = false; } } #ifndef KIOWIDGETS_NO_DEPRECATED bool KRun::runUrl(const QUrl &url, const QString &mimetype, QWidget *window, bool tempFile, bool runExecutables, const QString &suggestedFileName, const QByteArray &asn) { RunFlags flags = tempFile ? KRun::DeleteTemporaryFiles : RunFlags(); if (runExecutables) { flags |= KRun::RunExecutables; } return runUrl(url, mimetype, window, flags, suggestedFileName, asn); } #endif // This is called by foundMimeType, since it knows the mimetype of the URL bool KRun::runUrl(const QUrl &u, const QString &_mimetype, QWidget *window, RunFlags flags, const QString &suggestedFileName, const QByteArray &asn) { const bool runExecutables = flags.testFlag(KRun::RunExecutables); const bool tempFile = flags.testFlag(KRun::DeleteTemporaryFiles); bool noRun = false; bool noAuth = false; if (_mimetype == QLatin1String("inode/directory-locked")) { KMessageBox::error(window, i18n("Unable to enter %1.\nYou do not have access rights to this location.", u.toDisplayString().toHtmlEscaped())); return false; } else if (_mimetype == QLatin1String("application/x-desktop")) { if (u.isLocalFile() && runExecutables) { return KDesktopFileActions::runWithStartup(u, true, asn); } } else if (isExecutableFile(u, _mimetype)) { if (u.isLocalFile() && runExecutables) { if (KAuthorized::authorize(QStringLiteral("shell_access"))) { return (KRun::runCommand(KShell::quoteArg(u.toLocalFile()), QString(), QString(), window, asn, u.adjusted(QUrl::RemoveFilename).toLocalFile())); // just execute the url as a command // ## TODO implement deleting the file if tempFile==true } else { noAuth = true; } } else if (_mimetype == QLatin1String("application/x-executable")) { noRun = true; } } else if (isExecutable(_mimetype)) { if (!runExecutables) { noRun = true; } if (!KAuthorized::authorize(QStringLiteral("shell_access"))) { noAuth = true; } } if (noRun) { KMessageBox::sorry(window, i18n("The file %1 is an executable program. " "For safety it will not be started.", u.toDisplayString().toHtmlEscaped())); return false; } if (noAuth) { KMessageBox::error(window, i18n("You do not have permission to run %1.", u.toDisplayString().toHtmlEscaped())); return false; } QList lst; lst.append(u); KService::Ptr offer = KMimeTypeTrader::self()->preferredService(_mimetype); if (!offer) { #ifdef Q_OS_WIN // As KDE on windows doesnt know about the windows default applications offers will be empty in nearly all cases. // So we use QDesktopServices::openUrl to let windows decide how to open the file return QDesktopServices::openUrl(u); #else // Open-with dialog // TODO : pass the mimetype as a parameter, to show it (comment field) in the dialog ! // Hmm, in fact KOpenWithDialog::setServiceType already guesses the mimetype from the first URL of the list... return displayOpenWithDialog(lst, window, tempFile, suggestedFileName, asn); #endif } return KRun::runService(*offer, lst, window, tempFile, suggestedFileName, asn); } bool KRun::displayOpenWithDialog(const QList &lst, QWidget *window, bool tempFiles, const QString &suggestedFileName, const QByteArray &asn) { if (!KAuthorized::authorizeAction(QStringLiteral("openwith"))) { KMessageBox::sorry(window, i18n("You are not authorized to select an application to open this file.")); return false; } #ifdef Q_OS_WIN KConfigGroup cfgGroup(KSharedConfig::openConfig(), "KOpenWithDialog Settings"); if (cfgGroup.readEntry("Native", true)) { return KRun::KRunPrivate::displayNativeOpenWithDialog(lst, window, tempFiles, suggestedFileName, asn); } #endif KOpenWithDialog dialog(lst, QString(), QString(), window); dialog.setWindowModality(Qt::WindowModal); if (dialog.exec()) { KService::Ptr service = dialog.service(); if (!service) { //qDebug() << "No service set, running " << dialog.text(); service = KService::Ptr(new KService(QString() /*name*/, dialog.text(), QString() /*icon*/)); } return KRun::runService(*service, lst, window, tempFiles, suggestedFileName, asn); } return false; } #ifndef KIOWIDGETS_NO_DEPRECATED void KRun::shellQuote(QString &_str) { // Credits to Walter, says Bernd G. :) if (_str.isEmpty()) { // Don't create an explicit empty parameter return; } QChar q('\''); _str.replace(q, QLatin1String("'\\''")).prepend(q).append(q); } #endif QStringList KRun::processDesktopExec(const KService &_service, const QList &_urls, bool tempFiles, const QString &suggestedFileName) { KIO::DesktopExecParser parser(_service, _urls); parser.setUrlsAreTempFiles(tempFiles); parser.setSuggestedFileName(suggestedFileName); return parser.resultingArguments(); } #ifndef KIOWIDGETS_NO_DEPRECATED QString KRun::binaryName(const QString &execLine, bool removePath) { return removePath ? KIO::DesktopExecParser::executableName(execLine) : KIO::DesktopExecParser::executablePath(execLine); } #endif static qint64 runCommandInternal(KProcess *proc, const KService *service, const QString &executable, const QString &userVisibleName, const QString &iconName, QWidget *window, const QByteArray &asn) { if (window) { window = window->topLevelWidget(); } if (service && !service->entryPath().isEmpty() && !KDesktopFile::isAuthorizedDesktopFile(service->entryPath())) { qCWarning(KIO_WIDGETS) << "No authorization to execute " << service->entryPath(); KMessageBox::sorry(window, i18n("You are not authorized to execute this file.")); delete proc; return 0; } QString bin = KIO::DesktopExecParser::executableName(executable); #if HAVE_X11 // Startup notification doesn't work with QT/E, service isn't needed without Startup notification static bool isX11 = QGuiApplication::platformName() == QStringLiteral("xcb"); if (isX11) { bool silent; QByteArray wmclass; KStartupInfoId id; bool startup_notify = (asn != "0" && KRun::checkStartupNotify(QString() /*unused*/, service, &silent, &wmclass)); if (startup_notify) { id.initId(asn); id.setupStartupEnv(); KStartupInfoData data; data.setHostname(); data.setBin(bin); if (!userVisibleName.isEmpty()) { data.setName(userVisibleName); } else if (service && !service->name().isEmpty()) { data.setName(service->name()); } data.setDescription(i18n("Launching %1", data.name())); if (!iconName.isEmpty()) { data.setIcon(iconName); } else if (service && !service->icon().isEmpty()) { data.setIcon(service->icon()); } if (!wmclass.isEmpty()) { data.setWMClass(wmclass); } if (silent) { data.setSilent(KStartupInfoData::Yes); } data.setDesktop(KWindowSystem::currentDesktop()); // QTBUG-59017 Calling winId() on an embedded widget will break interaction // with it on high-dpi multi-screen setups (cf. also Bug 363548), hence using // its parent window instead if (window && window->window()) { data.setLaunchedBy(window->window()->winId()); } if (service && !service->entryPath().isEmpty()) { data.setApplicationId(service->entryPath()); } KStartupInfo::sendStartup(id, data); } qint64 pid = KProcessRunner::run(proc, executable, id); if (startup_notify && pid) { KStartupInfoData data; data.addPid(pid); KStartupInfo::sendChange(id, data); KStartupInfo::resetStartupEnv(); } return pid; } #else Q_UNUSED(userVisibleName); Q_UNUSED(iconName); #endif return KProcessRunner::run(proc, bin, KStartupInfoId()); } // This code is also used in klauncher. bool KRun::checkStartupNotify(const QString & /*binName*/, const KService *service, bool *silent_arg, QByteArray *wmclass_arg) { bool silent = false; QByteArray wmclass; if (service && service->property(QStringLiteral("StartupNotify")).isValid()) { silent = !service->property(QStringLiteral("StartupNotify")).toBool(); wmclass = service->property(QStringLiteral("StartupWMClass")).toString().toLatin1(); } else if (service && service->property(QStringLiteral("X-KDE-StartupNotify")).isValid()) { silent = !service->property(QStringLiteral("X-KDE-StartupNotify")).toBool(); wmclass = service->property(QStringLiteral("X-KDE-WMClass")).toString().toLatin1(); } else { // non-compliant app if (service) { if (service->isApplication()) { // doesn't have .desktop entries needed, start as non-compliant wmclass = "0"; // krazy:exclude=doublequote_chars } else { return false; // no startup notification at all } } else { #if 0 // Create startup notification even for apps for which there shouldn't be any, // just without any visual feedback. This will ensure they'll be positioned on the proper // virtual desktop, and will get user timestamp from the ASN ID. wmclass = '0'; silent = true; #else // That unfortunately doesn't work, when the launched non-compliant application // launches another one that is compliant and there is any delay inbetween (bnc:#343359) return false; #endif } } if (silent_arg) { *silent_arg = silent; } if (wmclass_arg) { *wmclass_arg = wmclass; } return true; } static qint64 runApplicationImpl(const KService &_service, const QList &_urls, QWidget *window, KRun::RunFlags flags, const QString &suggestedFileName, const QByteArray &asn) { QList urlsToRun = _urls; if ((_urls.count() > 1) && !_service.allowMultipleFiles()) { // We need to launch the application N times. That sucks. // We ignore the result for application 2 to N. // For the first file we launch the application in the // usual way. The reported result is based on this // application. QList::ConstIterator it = _urls.begin(); while (++it != _urls.end()) { QList singleUrl; singleUrl.append(*it); runApplicationImpl(_service, singleUrl, window, flags, suggestedFileName, QByteArray()); } urlsToRun.clear(); urlsToRun.append(_urls.first()); } KIO::DesktopExecParser execParser(_service, urlsToRun); execParser.setUrlsAreTempFiles(flags & KRun::DeleteTemporaryFiles); execParser.setSuggestedFileName(suggestedFileName); const QStringList args = execParser.resultingArguments(); if (args.isEmpty()) { KMessageBox::sorry(window, i18n("Error processing Exec field in %1", _service.entryPath())); return 0; } //qDebug() << "runTempService: KProcess args=" << args; KProcess * proc = new KProcess; *proc << args; enum DiscreteGpuCheck { NotChecked, Present, Absent }; static DiscreteGpuCheck s_gpuCheck = NotChecked; if (_service.runOnDiscreteGpu() && s_gpuCheck == NotChecked) { // Check whether we have a discrete gpu bool hasDiscreteGpu = false; QDBusInterface iface(QLatin1String("org.kde.Solid.PowerManagement"), QLatin1String("/org/kde/Solid/PowerManagement"), QLatin1String("org.kde.Solid.PowerManagement"), QDBusConnection::sessionBus()); if (iface.isValid()) { QDBusReply reply = iface.call(QLatin1String("hasDualGpu")); if (reply.isValid()) { hasDiscreteGpu = reply.value(); } } s_gpuCheck = hasDiscreteGpu ? Present : Absent; } if (_service.runOnDiscreteGpu() && s_gpuCheck == Present) { proc->setEnv(QLatin1String("DRI_PRIME"), QLatin1String("1")); } QString path(_service.path()); if (path.isEmpty() && !_urls.isEmpty() && _urls.first().isLocalFile()) { path = _urls.first().adjusted(QUrl::RemoveFilename).toLocalFile(); } proc->setWorkingDirectory(path); return runCommandInternal(proc, &_service, KIO::DesktopExecParser::executablePath(_service.exec()), _service.name(), _service.icon(), window, asn); } // WARNING: don't call this from DesktopExecParser, since klauncher uses that too... // TODO: make this async, see the job->exec() in there... static QList resolveURLs(const QList &_urls, const KService &_service) { // Check which protocols the application supports. // This can be a list of actual protocol names, or just KIO for KDE apps. QStringList appSupportedProtocols = KIO::DesktopExecParser::supportedProtocols(_service); QList urls(_urls); if (!appSupportedProtocols.contains(QStringLiteral("KIO"))) { for (QList::Iterator it = urls.begin(); it != urls.end(); ++it) { const QUrl url = *it; bool supported = KIO::DesktopExecParser::isProtocolInSupportedList(url, appSupportedProtocols); //qDebug() << "Looking at url=" << url << " supported=" << supported; if (!supported && KProtocolInfo::protocolClass(url.scheme()) == QLatin1String(":local")) { // Maybe we can resolve to a local URL? KIO::StatJob *job = KIO::mostLocalUrl(url); if (job->exec()) { // ## nasty nested event loop! const QUrl localURL = job->mostLocalUrl(); if (localURL != url) { *it = localURL; //qDebug() << "Changed to" << localURL; } } } } } return urls; } // Simple QDialog that resizes the given text edit after being shown to more // or less fit the enclosed text. class SecureMessageDialog : public QDialog { Q_OBJECT public: SecureMessageDialog(QWidget *parent) : QDialog(parent), m_textEdit(nullptr) { } void setTextEdit(QPlainTextEdit *textEdit) { m_textEdit = textEdit; } protected: void showEvent(QShowEvent *e) Q_DECL_OVERRIDE { // Now that we're shown, use our width to calculate a good // bounding box for the text, and resize m_textEdit appropriately. QDialog::showEvent(e); if (!m_textEdit) { return; } QSize fudge(20, 24); // About what it sounds like :-/ // Form rect with a lot of height for bounding. Use no more than // 5 lines. QRect curRect(m_textEdit->rect()); QFontMetrics metrics(fontMetrics()); curRect.setHeight(5 * metrics.lineSpacing()); curRect.setWidth(qMax(curRect.width(), 300)); // At least 300 pixels ok? QString text(m_textEdit->toPlainText()); curRect = metrics.boundingRect(curRect, Qt::TextWordWrap | Qt::TextSingleLine, text); // Scroll bars interfere. If we don't think there's enough room, enable // the vertical scrollbar however. m_textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); if (curRect.height() < m_textEdit->height()) { // then we've got room m_textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_textEdit->setMaximumHeight(curRect.height() + fudge.height()); } m_textEdit->setMinimumSize(curRect.size() + fudge); m_textEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); updateGeometry(); } private: QPlainTextEdit *m_textEdit; }; // Helper function to make the given .desktop file executable by ensuring // that a #!/usr/bin/env xdg-open line is added if necessary and the file has // the +x bit set for the user. Returns false if either fails. static bool makeFileExecutable(const QString &fileName) { // Open the file and read the first two characters, check if it's // #!. If not, create a new file, prepend appropriate lines, and copy // over. QFile desktopFile(fileName); if (!desktopFile.open(QFile::ReadOnly)) { qCWarning(KIO_WIDGETS) << "Error opening service" << fileName << desktopFile.errorString(); return false; } QByteArray header = desktopFile.peek(2); // First two chars of file if (header.size() == 0) { qCWarning(KIO_WIDGETS) << "Error inspecting service" << fileName << desktopFile.errorString(); return false; // Some kind of error } if (header != "#!") { // Add header QSaveFile saveFile; saveFile.setFileName(fileName); if (!saveFile.open(QIODevice::WriteOnly)) { qCWarning(KIO_WIDGETS) << "Unable to open replacement file for" << fileName << saveFile.errorString(); return false; } QByteArray shebang("#!/usr/bin/env xdg-open\n"); if (saveFile.write(shebang) != shebang.size()) { qCWarning(KIO_WIDGETS) << "Error occurred adding header for" << fileName << saveFile.errorString(); saveFile.cancelWriting(); return false; } // Now copy the one into the other and then close and reopen desktopFile QByteArray desktopData(desktopFile.readAll()); if (desktopData.isEmpty()) { qCWarning(KIO_WIDGETS) << "Unable to read service" << fileName << desktopFile.errorString(); saveFile.cancelWriting(); return false; } if (saveFile.write(desktopData) != desktopData.size()) { qCWarning(KIO_WIDGETS) << "Error copying service" << fileName << saveFile.errorString(); saveFile.cancelWriting(); return false; } desktopFile.close(); if (!saveFile.commit()) { // Figures.... qCWarning(KIO_WIDGETS) << "Error committing changes to service" << fileName << saveFile.errorString(); return false; } if (!desktopFile.open(QFile::ReadOnly)) { qCWarning(KIO_WIDGETS) << "Error re-opening service" << fileName << desktopFile.errorString(); return false; } } // Add header // corresponds to owner on unix, which will have to do since if the user // isn't the owner we can't change perms anyways. if (!desktopFile.setPermissions(QFile::ExeUser | desktopFile.permissions())) { qCWarning(KIO_WIDGETS) << "Unable to change permissions for" << fileName << desktopFile.errorString(); return false; } // whew return true; } // Helper function to make a .desktop file executable if prompted by the user. // returns true if KRun::run() should continue with execution, false if user declined // to make the file executable or we failed to make it executable. static bool makeServiceExecutable(const KService &service, QWidget *window) { if (!KAuthorized::authorize(QStringLiteral("run_desktop_files"))) { qCWarning(KIO_WIDGETS) << "No authorization to execute " << service.entryPath(); KMessageBox::sorry(window, i18n("You are not authorized to execute this service.")); return false; // Don't circumvent the Kiosk } SecureMessageDialog *baseDialog = new SecureMessageDialog(window); baseDialog->setWindowTitle(i18nc("Warning about executing unknown .desktop file", "Warning")); QVBoxLayout *topLayout = new QVBoxLayout; baseDialog->setLayout(topLayout); // Dialog will have explanatory text with a disabled lineedit with the // Exec= to make it visually distinct. QWidget *baseWidget = new QWidget(baseDialog); QHBoxLayout *mainLayout = new QHBoxLayout(baseWidget); QLabel *iconLabel = new QLabel(baseWidget); QPixmap warningIcon(KIconLoader::global()->loadIcon(QStringLiteral("dialog-warning"), KIconLoader::NoGroup, KIconLoader::SizeHuge)); mainLayout->addWidget(iconLabel); iconLabel->setPixmap(warningIcon); QVBoxLayout *contentLayout = new QVBoxLayout; QString warningMessage = i18nc("program name follows in a line edit below", "This will start the program:"); QLabel *message = new QLabel(warningMessage, baseWidget); contentLayout->addWidget(message); // We can use KStandardDirs::findExe to resolve relative pathnames // but that gets rid of the command line arguments. QString program = QFileInfo(service.exec()).canonicalFilePath(); if (program.isEmpty()) { // e.g. due to command line arguments program = service.exec(); } QPlainTextEdit *textEdit = new QPlainTextEdit(baseWidget); textEdit->setPlainText(program); textEdit->setReadOnly(true); contentLayout->addWidget(textEdit); QLabel *footerLabel = new QLabel(i18n("If you do not trust this program, click Cancel")); contentLayout->addWidget(footerLabel); contentLayout->addStretch(0); // Don't allow the text edit to expand mainLayout->addLayout(contentLayout); topLayout->addWidget(baseWidget); baseDialog->setTextEdit(textEdit); QDialogButtonBox *buttonBox = new QDialogButtonBox(baseDialog); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::cont()); buttonBox->button(QDialogButtonBox::Cancel)->setDefault(true); buttonBox->button(QDialogButtonBox::Cancel)->setFocus(); QObject::connect(buttonBox, SIGNAL(accepted()), baseDialog, SLOT(accept())); QObject::connect(buttonBox, SIGNAL(rejected()), baseDialog, SLOT(reject())); topLayout->addWidget(buttonBox); // Constrain maximum size. Minimum size set in // the dialog's show event. QSize screenSize = QApplication::desktop()->screen()->size(); baseDialog->resize(screenSize.width() / 4, 50); baseDialog->setMaximumHeight(screenSize.height() / 3); baseDialog->setMaximumWidth(screenSize.width() / 10 * 8); int result = baseDialog->exec(); if (result != QDialog::Accepted) { return false; } // Assume that service is an absolute path since we're being called (relative paths // would have been allowed unless Kiosk said no, therefore we already know where the // .desktop file is. Now add a header to it if it doesn't already have one // and add the +x bit. if (!::makeFileExecutable(service.entryPath())) { QString serviceName = service.name(); if (serviceName.isEmpty()) { serviceName = service.genericName(); } KMessageBox::sorry( window, i18n("Unable to make the service %1 executable, aborting execution", serviceName) ); return false; } return true; } bool KRun::run(const KService &_service, const QList &_urls, QWidget *window, bool tempFiles, const QString &suggestedFileName, const QByteArray &asn) { return runService(_service, _urls, window, tempFiles, suggestedFileName, asn) != 0; } qint64 KRun::runApplication(const KService &service, const QList &urls, QWidget *window, RunFlags flags, const QString &suggestedFileName, const QByteArray &asn) { if (!service.entryPath().isEmpty() && !KDesktopFile::isAuthorizedDesktopFile(service.entryPath()) && !::makeServiceExecutable(service, window)) { return 0; } if ((flags & DeleteTemporaryFiles) == 0) { // Remember we opened those urls, for the "recent documents" menu in kicker for (const QUrl &url : urls) { KRecentDocument::add(url, service.desktopEntryName()); } } return runApplicationImpl(service, urls, window, flags, suggestedFileName, asn); } qint64 KRun::runService(const KService &_service, const QList &_urls, QWidget *window, bool tempFiles, const QString &suggestedFileName, const QByteArray &asn) { if (!_service.entryPath().isEmpty() && !KDesktopFile::isAuthorizedDesktopFile(_service.entryPath()) && !::makeServiceExecutable(_service, window)) { return 0; } if (!tempFiles) { // Remember we opened those urls, for the "recent documents" menu in kicker for (const QUrl &url : _urls) { KRecentDocument::add(url, _service.desktopEntryName()); } } bool useKToolInvocation = !(tempFiles || _service.entryPath().isEmpty() || !suggestedFileName.isEmpty()); if (useKToolInvocation) { // Is klauncher installed? Let's try to start it, if it fails, then we won't use it. static int klauncherAvailable = -1; if (klauncherAvailable == -1) { KToolInvocation::ensureKdeinitRunning(); QDBusConnectionInterface *dbusDaemon = QDBusConnection::sessionBus().interface(); klauncherAvailable = dbusDaemon->isServiceRegistered(QStringLiteral("org.kde.klauncher5")); } if (klauncherAvailable == 0) { useKToolInvocation = false; } } if (!useKToolInvocation) { return runApplicationImpl(_service, _urls, window, tempFiles ? RunFlags(DeleteTemporaryFiles) : RunFlags(), suggestedFileName, asn); } // Resolve urls if needed, depending on what the app supports const QList urls = resolveURLs(_urls, _service); //qDebug() << "Running" << _service.entryPath() << _urls << "using klauncher"; QString error; int pid = 0; //TODO KF6: change KToolInvokation to take a qint64* QByteArray myasn = asn; // startServiceByDesktopPath() doesn't take QWidget*, add it to the startup info now if (window) { if (myasn.isEmpty()) { myasn = KStartupInfo::createNewStartupId(); } if (myasn != "0") { KStartupInfoId id; id.initId(myasn); KStartupInfoData data; // QTBUG-59017 Calling winId() on an embedded widget will break interaction // with it on high-dpi multi-screen setups (cf. also Bug 363548), hence using // its parent window instead if (window->window()) { data.setLaunchedBy(window->window()->winId()); } KStartupInfo::sendChange(id, data); } } int i = KToolInvocation::startServiceByDesktopPath( _service.entryPath(), QUrl::toStringList(urls), &error, nullptr, &pid, myasn ); if (i != 0) { //qDebug() << error; KMessageBox::sorry(window, error); return 0; } //qDebug() << "startServiceByDesktopPath worked fine"; return pid; } bool KRun::run(const QString &_exec, const QList &_urls, QWidget *window, const QString &_name, const QString &_icon, const QByteArray &asn) { KService::Ptr service(new KService(_name, _exec, _icon)); return runService(*service, _urls, window, false, QString(), asn); } bool KRun::runCommand(const QString &cmd, QWidget *window, const QString &workingDirectory) { if (cmd.isEmpty()) { qCWarning(KIO_WIDGETS) << "Command was empty, nothing to run"; return false; } const QStringList args = KShell::splitArgs(cmd); if (args.isEmpty()) { qCWarning(KIO_WIDGETS) << "Command could not be parsed."; return false; } const QString bin = args.first(); return KRun::runCommand(cmd, bin, bin /*iconName*/, window, QByteArray(), workingDirectory); } bool KRun::runCommand(const QString &cmd, const QString &execName, const QString &iconName, QWidget *window, const QByteArray &asn) { return runCommand(cmd, execName, iconName, window, asn, QString()); } bool KRun::runCommand(const QString &cmd, const QString &execName, const QString &iconName, QWidget *window, const QByteArray &asn, const QString &workingDirectory) { //qDebug() << "runCommand " << cmd << "," << execName; KProcess *proc = new KProcess; proc->setShellCommand(cmd); if (!workingDirectory.isEmpty()) { proc->setWorkingDirectory(workingDirectory); } QString bin = KIO::DesktopExecParser::executableName(execName); KService::Ptr service = KService::serviceByDesktopName(bin); return runCommandInternal(proc, service.data(), execName /*executable to check for in slotProcessExited*/, execName /*user-visible name*/, iconName, window, asn) != 0; } KRun::KRun(const QUrl &url, QWidget *window, bool showProgressInfo, const QByteArray &asn) : d(new KRunPrivate(this)) { d->m_timer = new QTimer(this); d->m_timer->setObjectName(QStringLiteral("KRun::timer")); d->m_timer->setSingleShot(true); d->init(url, window, showProgressInfo, asn); } void KRun::KRunPrivate::init(const QUrl &url, QWidget *window, bool showProgressInfo, const QByteArray &asn) { m_bFault = false; m_bAutoDelete = true; m_bProgressInfo = showProgressInfo; m_bFinished = false; m_job = nullptr; m_strURL = url; m_bScanFile = false; m_bIsDirectory = false; m_runExecutables = true; m_window = window; m_asn = asn; q->setEnableExternalBrowser(true); // Start the timer. This means we will return to the event // loop and do initialization afterwards. // Reason: We must complete the constructor before we do anything else. m_bCheckPrompt = false; m_bInit = true; q->connect(m_timer, SIGNAL(timeout()), q, SLOT(slotTimeout())); startTimer(); //qDebug() << "new KRun" << q << url << "timer=" << m_timer; } void KRun::init() { //qDebug() << "INIT called"; if (!d->m_strURL.isValid() || d->m_strURL.scheme().isEmpty()) { const QString error = !d->m_strURL.isValid() ? d->m_strURL.errorString() : d->m_strURL.toString(); handleInitError(KIO::ERR_MALFORMED_URL, i18n("Malformed URL\n%1", error)); qCWarning(KIO_WIDGETS) << "Malformed URL:" << error; d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("open"), QUrl(), d->m_strURL)) { QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, d->m_strURL.toDisplayString()); handleInitError(KIO::ERR_ACCESS_DENIED, msg); d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } if (!d->m_externalBrowser.isEmpty() && d->m_strURL.scheme().startsWith(QLatin1String("http"))) { if (d->runExecutable(d->m_externalBrowser)) { return; } } else if (d->m_strURL.isLocalFile() && (d->m_strURL.host().isEmpty() || (d->m_strURL.host() == QLatin1String("localhost")) || (d->m_strURL.host().compare(QHostInfo::localHostName(), Qt::CaseInsensitive) == 0))) { const QString localPath = d->m_strURL.toLocalFile(); if (!QFile::exists(localPath)) { handleInitError(KIO::ERR_DOES_NOT_EXIST, i18n("Unable to run the command specified. " "The file or folder %1 does not exist.", localPath.toHtmlEscaped())); d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl(d->m_strURL); //qDebug() << "MIME TYPE is " << mime.name(); if (!d->m_externalBrowser.isEmpty() && ( mime.inherits(QStringLiteral("text/html")) || mime.inherits(QStringLiteral("application/xhtml+xml")))) { if (d->runExecutable(d->m_externalBrowser)) { return; } } else if (mime.isDefault() && !QFileInfo(localPath).isReadable()) { // Unknown mimetype because the file is unreadable, no point in showing an open-with dialog (#261002) const QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, localPath); handleInitError(KIO::ERR_ACCESS_DENIED, msg); d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } else { mimeTypeDetermined(mime.name()); return; } } else if (KIO::DesktopExecParser::hasSchemeHandler(d->m_strURL)) { //qDebug() << "Using scheme handler"; const QString exec = schemeHandler(d->m_strURL.scheme()); if (exec.isEmpty()) { mimeTypeDetermined(KProtocolManager::defaultMimetype(d->m_strURL)); return; } else { if (run(exec, QList() << d->m_strURL, d->m_window, QString(), QString(), d->m_asn)) { d->m_bFinished = true; d->startTimer(); return; } } } #if 0 // removed for KF5 (for portability). Reintroduce a bool or flag if useful. // Did we already get the information that it is a directory ? if ((d->m_mode & QT_STAT_MASK) == QT_STAT_DIR) { mimeTypeDetermined("inode/directory"); return; } #endif // Let's see whether it is a directory if (!KProtocolManager::supportsListing(d->m_strURL)) { // No support for listing => it can't be a directory (example: http) if (!KProtocolManager::supportsReading(d->m_strURL)) { // No support for reading files either => we can't do anything (example: mailto URL, with no associated app) handleInitError(KIO::ERR_UNSUPPORTED_ACTION, i18n("Could not find any application or handler for %1", d->m_strURL.toDisplayString())); d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } scanFile(); return; } //qDebug() << "Testing directory (stating)"; // It may be a directory or a file, let's stat KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo; KIO::StatJob *job = KIO::stat(d->m_strURL, KIO::StatJob::SourceSide, 0 /* no details */, flags); KJobWidgets::setWindow(job, d->m_window); connect(job, SIGNAL(result(KJob*)), this, SLOT(slotStatResult(KJob*))); d->m_job = job; //qDebug() << "Job" << job << "is about stating" << d->m_strURL; } KRun::~KRun() { //qDebug() << this; d->m_timer->stop(); killJob(); //qDebug() << this << "done"; delete d; } bool KRun::KRunPrivate::runExecutable(const QString &_exec) { QList urls; urls.append(m_strURL); if (_exec.startsWith('!')) { QString exec = _exec.mid(1); // Literal command exec += QLatin1String(" %u"); if (q->run(exec, urls, m_window, QString(), QString(), m_asn)) { m_bFinished = true; startTimer(); return true; } } else { KService::Ptr service = KService::serviceByStorageId(_exec); if (service && q->runService(*service, urls, m_window, false, QString(), m_asn)) { m_bFinished = true; startTimer(); return true; } } return false; } void KRun::KRunPrivate::showPrompt() { ExecutableFileOpenDialog *dialog = new ExecutableFileOpenDialog(q->window()); dialog->setAttribute(Qt::WA_DeleteOnClose); connect(dialog, &ExecutableFileOpenDialog::finished, q, [this, dialog](int result){ onDialogFinished(result, dialog->isDontAskAgainChecked()); }); dialog->show(); } bool KRun::KRunPrivate::isPromptNeeded() { if (m_strURL == QUrl("remote:/x-wizard_service.desktop")) { return false; } const QMimeDatabase db; const QMimeType mime = db.mimeTypeForUrl(m_strURL); const bool isFileExecutable = (isExecutableFile(m_strURL, mime.name()) || mime.inherits(QStringLiteral("application/x-desktop"))); const bool isTextFile = mime.inherits(QStringLiteral("text/plain")); if (isFileExecutable && isTextFile) { KConfigGroup cfgGroup(KSharedConfig::openConfig(QStringLiteral("kiorc")), "Executable scripts"); const QString value = cfgGroup.readEntry("behaviourOnLaunch", "alwaysAsk"); if (value == QLatin1String("alwaysAsk")) { return true; } else { q->setRunExecutables(value == QLatin1String("execute")); } } return false; } void KRun::KRunPrivate::onDialogFinished(int result, bool isDontAskAgainSet) { if (result == ExecutableFileOpenDialog::Rejected) { m_bFinished = true; m_bInit = false; startTimer(); return; } q->setRunExecutables(result == ExecutableFileOpenDialog::ExecuteFile); if (isDontAskAgainSet) { QString output = result == ExecutableFileOpenDialog::OpenFile ? QStringLiteral("open") : QStringLiteral("execute"); KConfigGroup cfgGroup(KSharedConfig::openConfig(QStringLiteral("kiorc")), "Executable scripts"); cfgGroup.writeEntry("behaviourOnLaunch", output); } startTimer(); } void KRun::scanFile() { //qDebug() << d->m_strURL; // First, let's check for well-known extensions // Not when there is a query in the URL, in any case. if (!d->m_strURL.hasQuery()) { QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl(d->m_strURL); if (!mime.isDefault() || d->m_strURL.isLocalFile()) { //qDebug() << "Scanfile: MIME TYPE is " << mime.name(); mimeTypeDetermined(mime.name()); return; } } // No mimetype found, and the URL is not local (or fast mode not allowed). // We need to apply the 'KIO' method, i.e. either asking the server or // getting some data out of the file, to know what mimetype it is. if (!KProtocolManager::supportsReading(d->m_strURL)) { qCWarning(KIO_WIDGETS) << "#### NO SUPPORT FOR READING!"; d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } //qDebug() << this << "Scanning file" << d->m_strURL; KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo; KIO::TransferJob *job = KIO::get(d->m_strURL, KIO::NoReload /*reload*/, flags); KJobWidgets::setWindow(job, d->m_window); connect(job, SIGNAL(result(KJob*)), this, SLOT(slotScanFinished(KJob*))); connect(job, SIGNAL(mimetype(KIO::Job*,QString)), this, SLOT(slotScanMimeType(KIO::Job*,QString))); d->m_job = job; //qDebug() << "Job" << job << "is about getting from" << d->m_strURL; } // When arriving in that method there are 6 possible states: // must_show_prompt, must_init, must_scan_file, found_dir, done+error or done+success. void KRun::slotTimeout() { if (d->m_bCheckPrompt) { d->m_bCheckPrompt = false; if (d->isPromptNeeded()) { d->showPrompt(); return; } } if (d->m_bInit) { d->m_bInit = false; init(); return; } if (d->m_bFault) { emit error(); } if (d->m_bFinished) { emit finished(); } else { if (d->m_bScanFile) { d->m_bScanFile = false; scanFile(); return; } else if (d->m_bIsDirectory) { d->m_bIsDirectory = false; mimeTypeDetermined(QStringLiteral("inode/directory")); return; } } if (d->m_bAutoDelete) { deleteLater(); return; } } void KRun::slotStatResult(KJob *job) { d->m_job = nullptr; const int errCode = job->error(); if (errCode) { // ERR_NO_CONTENT is not an error, but an indication no further // actions needs to be taken. if (errCode != KIO::ERR_NO_CONTENT) { qCWarning(KIO_WIDGETS) << this << "ERROR" << job->error() << job->errorString(); handleError(job); //qDebug() << this << " KRun returning from showErrorDialog, starting timer to delete us"; d->m_bFault = true; } d->m_bFinished = true; // will emit the error and autodelete this d->startTimer(); } else { //qDebug() << "Finished"; KIO::StatJob *statJob = qobject_cast(job); if (!statJob) { qFatal("Fatal Error: job is a %s, should be a StatJob", typeid(*job).name()); } // Update our URL in case of a redirection setUrl(statJob->url()); const KIO::UDSEntry entry = statJob->statResult(); const mode_t mode = entry.numberValue(KIO::UDSEntry::UDS_FILE_TYPE); if ((mode & QT_STAT_MASK) == QT_STAT_DIR) { d->m_bIsDirectory = true; // it's a dir } else { d->m_bScanFile = true; // it's a file } d->m_localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); // mimetype already known? (e.g. print:/manager) const QString knownMimeType = entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE); if (!knownMimeType.isEmpty()) { mimeTypeDetermined(knownMimeType); d->m_bFinished = true; } // We should have found something assert(d->m_bScanFile || d->m_bIsDirectory); // Start the timer. Once we get the timer event this // protocol server is back in the pool and we can reuse it. // This gives better performance than starting a new slave d->startTimer(); } } void KRun::slotScanMimeType(KIO::Job *, const QString &mimetype) { if (mimetype.isEmpty()) { qCWarning(KIO_WIDGETS) << "get() didn't emit a mimetype! Probably a kioslave bug, please check the implementation of" << url().scheme(); } mimeTypeDetermined(mimetype); d->m_job = nullptr; } void KRun::slotScanFinished(KJob *job) { d->m_job = nullptr; const int errCode = job->error(); if (errCode) { // ERR_NO_CONTENT is not an error, but an indication no further // actions needs to be taken. if (errCode != KIO::ERR_NO_CONTENT) { qCWarning(KIO_WIDGETS) << this << "ERROR (stat):" << job->error() << ' ' << job->errorString(); handleError(job); d->m_bFault = true; } d->m_bFinished = true; // will emit the error and autodelete this d->startTimer(); } } void KRun::mimeTypeDetermined(const QString &mimeType) { // foundMimeType reimplementations might show a dialog box; // make sure some timer doesn't kill us meanwhile (#137678, #156447) Q_ASSERT(!d->m_showingDialog); d->m_showingDialog = true; foundMimeType(mimeType); d->m_showingDialog = false; // We cannot assume that we're finished here. Some reimplementations // start a KIO job and call setFinished only later. } void KRun::foundMimeType(const QString &type) { //qDebug() << "Resulting mime type is " << type; QMimeDatabase db; KIO::TransferJob *job = qobject_cast(d->m_job); if (job) { // Update our URL in case of a redirection setUrl(job->url()); job->putOnHold(); KIO::Scheduler::publishSlaveOnHold(); d->m_job = nullptr; } Q_ASSERT(!d->m_bFinished); // Support for preferred service setting, see setPreferredService if (!d->m_preferredService.isEmpty()) { //qDebug() << "Attempting to open with preferred service: " << d->m_preferredService; KService::Ptr serv = KService::serviceByDesktopName(d->m_preferredService); if (serv && serv->hasMimeType(type)) { QList lst; lst.append(d->m_strURL); if (KRun::runService(*serv, lst, d->m_window, false, QString(), d->m_asn)) { setFinished(true); return; } /// Note: if that service failed, we'll go to runUrl below to /// maybe find another service, even though an error dialog box was /// already displayed. That's good if runUrl tries another service, /// but it's not good if it tries the same one :} } } // Resolve .desktop files from media:/, remote:/, applications:/ etc. QMimeType mime = db.mimeTypeForName(type); if (!mime.isValid()) { qCWarning(KIO_WIDGETS) << "Unknown mimetype " << type; } else if (mime.inherits(QStringLiteral("application/x-desktop")) && !d->m_localPath.isEmpty()) { d->m_strURL = QUrl::fromLocalFile(d->m_localPath); } if (!KRun::runUrl(d->m_strURL, type, d->m_window, false /*tempfile*/, d->m_runExecutables, d->m_suggestedFileName, d->m_asn)) { d->m_bFault = true; } setFinished(true); } void KRun::killJob() { if (d->m_job) { //qDebug() << this << "m_job=" << d->m_job; d->m_job->kill(); d->m_job = nullptr; } } void KRun::abort() { if (d->m_bFinished) { return; } //qDebug() << this << "m_showingDialog=" << d->m_showingDialog; killJob(); // If we're showing an error message box, the rest will be done // after closing the msgbox -> don't autodelete nor emit signals now. if (d->m_showingDialog) { return; } d->m_bFault = true; d->m_bFinished = true; d->m_bInit = false; d->m_bScanFile = false; // will emit the error and autodelete this d->startTimer(); } QWidget *KRun::window() const { return d->m_window; } bool KRun::hasError() const { return d->m_bFault; } bool KRun::hasFinished() const { return d->m_bFinished; } bool KRun::autoDelete() const { return d->m_bAutoDelete; } void KRun::setAutoDelete(bool b) { d->m_bAutoDelete = b; } void KRun::setEnableExternalBrowser(bool b) { if (b) { d->m_externalBrowser = KConfigGroup(KSharedConfig::openConfig(), "General").readEntry("BrowserApplication"); } else { d->m_externalBrowser.clear(); } } void KRun::setPreferredService(const QString &desktopEntryName) { d->m_preferredService = desktopEntryName; } void KRun::setRunExecutables(bool b) { d->m_runExecutables = b; } void KRun::setSuggestedFileName(const QString &fileName) { d->m_suggestedFileName = fileName; } void KRun::setShowScriptExecutionPrompt(bool showPrompt) { d->m_bCheckPrompt = showPrompt; } QString KRun::suggestedFileName() const { return d->m_suggestedFileName; } bool KRun::isExecutable(const QString &serviceType) { return (serviceType == QLatin1String("application/x-desktop") || serviceType == QLatin1String("application/x-executable") || /* See https://bugs.freedesktop.org/show_bug.cgi?id=97226 */ serviceType == QLatin1String("application/x-sharedlib") || serviceType == QLatin1String("application/x-ms-dos-executable") || serviceType == QLatin1String("application/x-shellscript")); } void KRun::setUrl(const QUrl &url) { d->m_strURL = url; } QUrl KRun::url() const { return d->m_strURL; } void KRun::setError(bool error) { d->m_bFault = error; } void KRun::setProgressInfo(bool progressInfo) { d->m_bProgressInfo = progressInfo; } bool KRun::progressInfo() const { return d->m_bProgressInfo; } void KRun::setFinished(bool finished) { d->m_bFinished = finished; if (finished) { d->startTimer(); } } void KRun::setJob(KIO::Job *job) { d->m_job = job; } KIO::Job *KRun::job() { return d->m_job; } #ifndef KIOWIDGETS_NO_DEPRECATED QTimer &KRun::timer() { return *d->m_timer; } #endif #ifndef KIOWIDGETS_NO_DEPRECATED void KRun::setDoScanFile(bool scanFile) { d->m_bScanFile = scanFile; } #endif #ifndef KIOWIDGETS_NO_DEPRECATED bool KRun::doScanFile() const { return d->m_bScanFile; } #endif #ifndef KIOWIDGETS_NO_DEPRECATED void KRun::setIsDirecory(bool isDirectory) { d->m_bIsDirectory = isDirectory; } #endif bool KRun::isDirectory() const { return d->m_bIsDirectory; } #ifndef KIOWIDGETS_NO_DEPRECATED void KRun::setInitializeNextAction(bool initialize) { d->m_bInit = initialize; } #endif #ifndef KIOWIDGETS_NO_DEPRECATED bool KRun::initializeNextAction() const { return d->m_bInit; } #endif bool KRun::isLocalFile() const { return d->m_strURL.isLocalFile(); } /****************/ qint64 KProcessRunner::run(KProcess *p, const QString &executable, const KStartupInfoId &id) { return (new KProcessRunner(p, executable, id))->pid(); } KProcessRunner::KProcessRunner(KProcess *p, const QString &executable, const KStartupInfoId &id) : id(id) { m_pid = 0; process = p; m_executable = executable; connect(process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotProcessExited(int,QProcess::ExitStatus))); process->start(); if (!process->waitForStarted()) { //qDebug() << "wait for started failed, exitCode=" << process->exitCode() // << "exitStatus=" << process->exitStatus(); // Note that exitCode is 255 here (the first time), and 0 later on (bug?). slotProcessExited(255, process->exitStatus()); } else { m_pid = process->processId(); } } KProcessRunner::~KProcessRunner() { delete process; } qint64 KProcessRunner::pid() const { return m_pid; } void KProcessRunner::terminateStartupNotification() { #if HAVE_X11 if (!id.isNull()) { KStartupInfoData data; data.addPid(m_pid); // announce this pid for the startup notification has finished data.setHostname(); KStartupInfo::sendFinish(id, data); } #endif } void KProcessRunner::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus) { //qDebug() << m_executable << "exitCode=" << exitCode << "exitStatus=" << exitStatus; Q_UNUSED(exitStatus) terminateStartupNotification(); // do this before the messagebox if (exitCode != 0 && !m_executable.isEmpty()) { // Let's see if the error is because the exe doesn't exist. // When this happens, waitForStarted returns false, but not if kioexec // was involved, then we come here, that's why the code is here. // // We'll try to find the executable relatively to current directory, // (or with a full path, if m_executable is absolute), and then in the PATH. if (!QFile(m_executable).exists() && QStandardPaths::findExecutable(m_executable).isEmpty()) { const QString &error = i18n("Could not find the program '%1'", m_executable); if (qApp) { QTimer::singleShot(0, qApp, [=]() { QEventLoopLocker locker; KMessageBox::sorry(nullptr, error); }); } else { qWarning() << error; } } else { //qDebug() << process->readAllStandardError(); } } deleteLater(); } #include "moc_krun.cpp" #include "moc_krun_p.cpp" #include "krun.moc" diff --git a/src/widgets/krun.h b/src/widgets/krun.h index 8fdb10fa..5617de1d 100644 --- a/src/widgets/krun.h +++ b/src/widgets/krun.h @@ -1,651 +1,651 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2006 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 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. */ #ifndef KRUN_H #define KRUN_H #include "kiowidgets_export.h" -#include -#include -#include +#include +#include +#include class KService; class KJob; class QTimer; namespace KIO { class Job; } /** * @class KRun krun.h * * To open files with their associated applications in KDE, use KRun. * * It can execute any desktop entry, as well as any file, using * the default application or another application "bound" to the file type * (or URL protocol). * * In that example, the mimetype of the file is not known by the application, * so a KRun instance must be created. It will determine the mimetype by itself. * If the mimetype is known, or if you even know the service (application) to * use for this file, use one of the static methods. * * By default KRun uses auto deletion. It causes the KRun instance to delete * itself when the it finished its task. If you allocate the KRun * object on the stack you must disable auto deletion, otherwise it will crash. * * This respects the "shell_access", "openwith" and "run_desktop_files" Kiosk * action restrictions (see KAuthorized::authorize()). * * @short Opens files with their associated applications in KDE */ class KIOWIDGETS_EXPORT KRun : public QObject { Q_OBJECT public: /** * @param url the URL of the file or directory to 'run' * * @param window * The top-level widget of the app that invoked this object. * It is used to make sure private information like passwords * are properly handled per application. * * @param showProgressInfo * Whether to show progress information when determining the * type of the file (i.e. when using KIO::stat and KIO::mimetype) * Before you set this to false to avoid a dialog box, think about * a very slow FTP server... * It is always better to provide progress info in such cases. * * @param asn * Application startup notification id, if available (otherwise ""). * * Porting note: kdelibs 4 had mode_t mode and bool isLocalFile arguments after * window and before showProgressInfo. Removed in KF5. */ KRun(const QUrl &url, QWidget *window, bool showProgressInfo = true, const QByteArray &asn = QByteArray()); /** * Destructor. Don't call it yourself, since a KRun object auto-deletes * itself. */ virtual ~KRun(); /** * Abort this KRun. This kills any jobs launched by it, * and leads to deletion if auto-deletion is on. * This is much safer than deleting the KRun (in case it's * currently showing an error dialog box, for instance) */ void abort(); /** * Returns true if the KRun instance has an error. * @return true when an error occurred * @see error() */ bool hasError() const; /** * Returns true if the KRun instance has finished. * @return true if the KRun instance has finished * @see finished() */ bool hasFinished() const; /** * Checks whether auto delete is activated. * Auto-deletion causes the KRun instance to delete itself * when it finished its task. * By default auto deletion is on. * @return true if auto deletion is on, false otherwise */ bool autoDelete() const; /** * Enables or disabled auto deletion. * Auto deletion causes the KRun instance to delete itself * when it finished its task. If you allocate the KRun * object on the stack you must disable auto deletion. * By default auto deletion is on. * @param b true to enable auto deletion, false to disable */ void setAutoDelete(bool b); /** * Set the preferred service for opening this URL, after * its mimetype will have been found by KRun. IMPORTANT: the service is * only used if its configuration says it can handle this mimetype. * This is used for instance for the X-KDE-LastOpenedWith key in * the recent documents list, or for the app selection in * KParts::BrowserOpenOrSaveQuestion. * @param desktopEntryName the desktopEntryName of the service, e.g. "kate". */ void setPreferredService(const QString &desktopEntryName); /** * Sets whether executables, .desktop files or shell scripts should * be run by KRun. This is enabled by default. * @param b whether to run executable files or not. * @see isExecutable() */ void setRunExecutables(bool b); /** * Sets whether the external webbrowser setting should be honoured. * This is enabled by default. * This should only be disabled in webbrowser applications. * @param b whether to enable the external browser or not. */ void setEnableExternalBrowser(bool b); /** * Sets the file name to use in the case of downloading the file to a tempfile * in order to give to a non-url-aware application. Some apps rely on the extension * to determine the mimetype of the file. Usually the file name comes from the URL, * but in the case of the HTTP Content-Disposition header, we need to override the * file name. */ void setSuggestedFileName(const QString &fileName); /** * Sets whether a prompt should be shown before executing scripts or desktop files. * If enabled, KRun uses the "kiorc" configuration file to decide whether to open the * file, execute it or show a prompt. * @since 5.4 */ void setShowScriptExecutionPrompt(bool showPrompt); /** * Suggested file name given by the server (e.g. HTTP content-disposition) */ QString suggestedFileName() const; /** * Associated window, as passed to the constructor * @since 4.9.3 */ QWidget *window() const; /** * Open a list of URLs with a certain service (application). * * @param service the service to run * @param urls the list of URLs, can be empty (app launched * without argument) * @param window The top-level widget of the app that invoked this object. * @param tempFiles if true and urls are local files, they will be deleted * when the application exits. * @param suggestedFileName see setSuggestedFileName * @param asn Application startup notification id, if any (otherwise ""). * @return @c true on success, @c false on error * * @deprecated since 5.6, use runService instead. No change needed on the application side, * the only difference is the return value (qint64 instead of bool). */ static KIOWIDGETS_DEPRECATED bool run(const KService &service, const QList &urls, QWidget *window, bool tempFiles = false, const QString &suggestedFileName = QString(), const QByteArray &asn = QByteArray()); /** * Open a list of URLs with a certain service (application). * * Prefer runApplication(), unless you need to wait for the application * to register to D-Bus before this method returns (but that should rather * be done with D-Bus activation). * * @param service the service to run * @param urls the list of URLs, can be empty (app launched * without argument) * @param window The top-level widget of the app that invoked this object. * @param tempFiles if true and urls are local files, they will be deleted * when the application exits. * @param suggestedFileName see setSuggestedFileName * @param asn Application startup notification id, if any (otherwise ""). * @return 0 on error, the process ID on success * @since 5.6 */ static qint64 runService(const KService &service, const QList &urls, QWidget *window, bool tempFiles = false, const QString &suggestedFileName = QString(), const QByteArray &asn = QByteArray()); // TODO KF6: deprecate/remove enum RunFlag { DeleteTemporaryFiles = 0x1, ///< the URLs passed to the service will be deleted when it exits (if the URLs are local files) RunExecutables = 0x2, ///< Whether to run URLs that are executable scripts or binaries @see isExecutableFile() @since 5.31 }; Q_DECLARE_FLAGS(RunFlags, RunFlag) /** * Run an application (known from its .desktop file, i.e. as a KService) * * Unlike runService, this does not wait for the application to register to D-Bus * before returning. Such behavior is better done with D-Bus activation anyway. * * @param service the service to run * @param urls the list of URLs, can be empty (app launched * without argument) * @param window The top-level widget of the app that invoked this object. * @param flags various flags * @param suggestedFileName see setSuggestedFileName * @param asn Application startup notification id, if any (otherwise ""). * @return 0 on error, the process ID on success * @since 5.24 */ static qint64 runApplication(const KService &service, const QList &urls, QWidget *window, RunFlags flags = RunFlags(), const QString &suggestedFileName = QString(), const QByteArray &asn = QByteArray()); /** * Open a list of URLs with an executable. * * @param exec the name of the executable, for example * "/usr/bin/netscape %u". * Don't forget to include the %u if you know that the applications * supports URLs. Otherwise, non-local urls will first be downloaded * to a temp file (using kioexec). * @param urls the list of URLs to open, can be empty (app launched without argument) * @param window The top-level widget of the app that invoked this object. * @param name the logical name of the application, for example * "Netscape 4.06". * @param icon the icon which should be used by the application. * @param asn Application startup notification id, if any (otherwise ""). * @return @c true on success, @c false on error */ static bool run(const QString &exec, const QList &urls, QWidget *window, const QString &name = QString(), const QString &icon = QString(), const QByteArray &asn = QByteArray()); /** * Open the given URL. * * This function is used after the mime type * is found out. It will search for all services which can handle * the mime type and call run() afterwards. * @param url the URL to open * @param mimetype the mime type of the resource * @param window The top-level widget of the app that invoked this object. * @param tempFile if true and url is a local file, it will be deleted * when the launched application exits. * @param runExecutables if false then local .desktop files, * executables and shell scripts will not be run. * See also isExecutable(). * @param suggestedFileName see setSuggestedFileName * @param asn Application startup notification id, if any (otherwise ""). * @return @c true on success, @c false on error * @deprecated since 5.31, use runUrl() with RunFlags instead. */ #ifndef KIOWIDGETS_NO_DEPRECATED static bool KIOWIDGETS_DEPRECATED runUrl(const QUrl &url, const QString &mimetype, QWidget *window, bool tempFile = false, bool runExecutables = true, const QString &suggestedFileName = QString(), const QByteArray &asn = QByteArray()); #endif /** * Open the given URL. * * This function can be used after the mime type has been found out. * It will search for all services which can handle the mime type and call run() afterwards. * * @param url The URL to open. * @param mimetype The mime type of the resource. * @param window The top-level widget of the app that invoked this object. * @param flags Various run flags. * @param suggestedFileName See setSuggestedFileName() * @param asn Application startup notification id, if any (otherwise ""). * @return @c true on success, @c false on error * @since 5.31 */ static bool runUrl(const QUrl &url, const QString &mimetype, QWidget *window, RunFlags flags, const QString &suggestedFileName = QString(), const QByteArray &asn = QByteArray()); /** * Run the given shell command and notifies KDE of the starting * of the application. If the program to be called doesn't exist, * an error box will be displayed. * * Use only when you know the full command line. Otherwise use the other * static methods, or KRun's constructor. * * @param cmd must be a shell command. You must not append "&" * to it, since the function will do that for you. * @param window The top-level widget of the app that invoked this object. * @param workingDirectory directory to use for relative paths, so that * a command like "kwrite file.txt" finds file.txt from the right place * * @return @c true on success, @c false on error */ static bool runCommand(const QString &cmd, QWidget *window, const QString &workingDirectory = QString()); /** * Same as the other runCommand(), but it also takes the name of the * binary, to display an error message in case it couldn't find it. * * @param cmd must be a shell command. You must not append "&" * to it, since the function will do that for you. * @param execName the name of the executable * @param icon icon for app starting notification * @param window The top-level widget of the app that invoked this object. * @param asn Application startup notification id, if any (otherwise ""). * @return @c true on success, @c false on error */ static bool runCommand(const QString &cmd, const QString &execName, const QString &icon, QWidget *window, const QByteArray &asn = QByteArray()); /** * Overload that also takes a working directory, so that a command like * "kwrite file.txt" finds file.txt from the right place. * @param workingDirectory the working directory for the started process. The default * (if passing an empty string) is the user's document path. * @since 4.4 */ static bool runCommand(const QString &cmd, const QString &execName, const QString &icon, QWidget *window, const QByteArray &asn, const QString &workingDirectory); // TODO KDE5: merge the above with 5-args runCommand, using QString() /** * Display the Open-With dialog for those URLs, and run the chosen application. * @param lst the list of applications to run * @param window The top-level widget of the app that invoked this object. * @param tempFiles if true and lst are local files, they will be deleted * when the application exits. * @param suggestedFileName see setSuggestedFileName * @param asn Application startup notification id, if any (otherwise ""). * @return false if the dialog was canceled */ static bool displayOpenWithDialog(const QList &lst, QWidget *window, bool tempFiles = false, const QString &suggestedFileName = QString(), const QByteArray &asn = QByteArray()); /** * Quotes a string for the shell. * An empty string will @em not be quoted. * * @deprecated Use KShell::quoteArg() instead. @em Note that this function * behaves differently for empty arguments and returns the result * differently. * * @param str the string to quote. The quoted string will be written here */ #ifndef KIOWIDGETS_NO_DEPRECATED static KIOWIDGETS_DEPRECATED void shellQuote(QString &str); #endif /** * Processes a Exec= line as found in .desktop files. * @param _service the service to extract information from. * @param _urls The urls the service should open. * @param tempFiles if true and urls are local files, they will be deleted * when the application exits. * @param suggestedFileName see setSuggestedFileName * * @return a list of arguments suitable for QProcess. * @deprecated since 5.0, use KIO::DesktopExecParser */ #ifndef KIOWIDGETS_NO_DEPRECATED static KIOWIDGETS_DEPRECATED QStringList processDesktopExec(const KService &_service, const QList &_urls, bool tempFiles = false, const QString &suggestedFileName = QString()); #endif /** * Given a full command line (e.g. the Exec= line from a .desktop file), * extract the name of the binary being run. * @param execLine the full command line * @param removePath if true, remove a (relative or absolute) path. E.g. /usr/bin/ls becomes ls. * @return the name of the executable to run * @deprecated since 5.0, use KIO::DesktopExecParser::executableName if removePath was true, * or KIO::DesktopExecParser::executablePath if removePath was false. */ #ifndef KIOWIDGETS_NO_DEPRECATED static QString binaryName(const QString &execLine, bool removePath); #endif /** * Returns whether @p serviceType refers to an executable program instead * of a data file. */ static bool isExecutable(const QString &serviceType); /** * Returns whether the @p url of @p mimetype is executable. * To be executable the file must pass the following rules: * -# Must reside on the local filesystem. * -# Must be marked as executable for the user by the filesystem. * -# The mime type must inherit application/x-executable, application/x-executable-script or application/x-sharedlib. * To allow a script to run when the above rules are satisfied add the entry * @code * X-KDE-IsAlso=application/x-executable-script * @endcode * to the mimetype's desktop file. */ static bool isExecutableFile(const QUrl &url, const QString &mimetype); /** * @internal */ static bool checkStartupNotify(const QString &binName, const KService *service, bool *silent_arg, QByteArray *wmclass_arg); Q_SIGNALS: /** * Emitted when the operation finished. * This signal is emitted in all cases of completion, whether successful or with error. * @see hasFinished() */ void finished(); /** * Emitted when the operation had an error. * @see hasError() */ void error(); protected Q_SLOTS: /** * All following protected slots are used by subclasses of KRun! */ /** * This slot is called whenever the internal timer fired, * in order to move on to the next step. */ void slotTimeout(); // KDE5: rename to slotNextStep() or something like that /** * This slot is called when the scan job is finished. */ void slotScanFinished(KJob *); /** * This slot is called when the scan job has found out * the mime type. */ void slotScanMimeType(KIO::Job *, const QString &type); /** * Call this from subclasses when you have determined the mimetype. * It will call foundMimeType, but also sets up protection against deletion during message boxes. * @since 4.0.2 */ void mimeTypeDetermined(const QString &mimeType); /** * This slot is called when the 'stat' job has finished. */ virtual void slotStatResult(KJob *); protected: /** * All following protected methods are used by subclasses of KRun! */ /** * Initializes the krun object. */ virtual void init(); /** * Start scanning a file. */ virtual void scanFile(); /** * Called if the mimetype has been detected. The function runs * the application associated with this mimetype. * Reimplement this method to implement a different behavior, * like opening the component for displaying the URL embedded. * * Important: call setFinished(true) once you are done! * Usually at the end of the foundMimeType reimplementation, but if the * reimplementation is asynchronous (e.g. uses KIO jobs) then * it can be called later instead. */ virtual void foundMimeType(const QString &type); /** * Kills the file scanning job. */ virtual void killJob(); /** * Called when KRun detects an error during the init phase. * * The default implementation shows a message box. * @since 5.0 */ virtual void handleInitError(int kioErrorCode, const QString &errorMsg); /** * Called when a KIO job started by KRun gives an error. * * The default implementation shows a message box. */ virtual void handleError(KJob *job); /** * Sets the url. */ void setUrl(const QUrl &url); /** * Returns the url. */ QUrl url() const; /** * Sets whether an error has occurred. */ void setError(bool error); /** * Sets whether progress information shall be shown. */ void setProgressInfo(bool progressInfo); /** * Returns whether progress information are shown. */ bool progressInfo() const; /** * Marks this 'KRun' instance as finished. */ void setFinished(bool finished); /** * Sets the job. */ void setJob(KIO::Job *job); /** * Returns the job. */ KIO::Job *job(); /** * Returns the timer object. * @deprecated setFinished(true) now takes care of the timer().start(0), * so this can be removed. */ #ifndef KIOWIDGETS_NO_DEPRECATED KIOWIDGETS_DEPRECATED QTimer &timer(); #endif /** * Indicate that the next action is to scan the file. * @deprecated not useful in public API */ #ifndef KIOWIDGETS_NO_DEPRECATED KIOWIDGETS_DEPRECATED void setDoScanFile(bool scanFile); #endif /** * Returns whether the file shall be scanned. * @deprecated not useful in public API */ #ifndef KIOWIDGETS_NO_DEPRECATED KIOWIDGETS_DEPRECATED bool doScanFile() const; #endif /** * Sets whether it is a directory. * @deprecated typo in the name, and not useful as a public method */ #ifndef KIOWIDGETS_NO_DEPRECATED KIOWIDGETS_DEPRECATED void setIsDirecory(bool isDirectory); #endif /** * Returns whether it is a directory. */ bool isDirectory() const; /** * @deprecated not useful in public API */ #ifndef KIOWIDGETS_NO_DEPRECATED KIOWIDGETS_DEPRECATED void setInitializeNextAction(bool initialize); #endif /** * @deprecated not useful in public API */ #ifndef KIOWIDGETS_NO_DEPRECATED KIOWIDGETS_DEPRECATED bool initializeNextAction() const; #endif /** * Returns whether it is a local file. */ bool isLocalFile() const; private: class KRunPrivate; KRunPrivate *const d; }; #endif diff --git a/src/widgets/krun_p.h b/src/widgets/krun_p.h index 81e20759..4135c9d8 100644 --- a/src/widgets/krun_p.h +++ b/src/widgets/krun_p.h @@ -1,140 +1,140 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2006 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 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. */ #ifndef KRUN_P_H #define KRUN_P_H -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include class KProcess; #include "kstartupinfo.h" /** * @internal * This class watches a process launched by KRun. * It sends a notification when the process exits (for the taskbar) * and it will show an error message if necessary (e.g. "program not found"). */ class KProcessRunner : public QObject { Q_OBJECT public: static qint64 run(KProcess *p, const QString &executable, const KStartupInfoId &id); virtual ~KProcessRunner(); qint64 pid() const; protected Q_SLOTS: void slotProcessExited(int, QProcess::ExitStatus); private: KProcessRunner(KProcess *p, const QString &binName, const KStartupInfoId &id); void terminateStartupNotification(); KProcess *process; QString m_executable; // can be a full path KStartupInfoId id; qint64 m_pid; Q_DISABLE_COPY(KProcessRunner) }; /** * @internal */ class Q_DECL_HIDDEN KRun::KRunPrivate { public: KRunPrivate(KRun *parent); void init(const QUrl &url, QWidget *window, bool showProgressInfo, const QByteArray &asn); // This helper method makes debugging easier: a single breakpoint for all // the code paths that start the timer - at least from KRun itself. // TODO: add public method startTimer() and deprecate timer() accessor, // starting is the only valid use of the timer in subclasses (BrowserRun, KHTMLRun and KonqRun) void startTimer(); #ifdef Q_OS_WIN static bool displayNativeOpenWithDialog(const QList &lst, QWidget *window, bool tempFiles, const QString &suggestedFileName, const QByteArray &asn); #endif bool runExecutable(const QString &_exec); void showPrompt(); /** * Check whether we need to show a prompt(before executing a script or desktop file) */ bool isPromptNeeded(); void onDialogFinished(int result, bool isDontAskAgainSet); KRun *q; bool m_showingDialog; bool m_runExecutables; // Don't exit the app while a KRun is running. QEventLoopLocker m_eventLoopLocker; QString m_preferredService; QString m_externalBrowser; QString m_localPath; QString m_suggestedFileName; QPointer m_window; QByteArray m_asn; QUrl m_strURL; bool m_bFault; bool m_bAutoDelete; bool m_bProgressInfo; bool m_bFinished; KIO::Job *m_job; QTimer *m_timer; /** * Used to indicate that the next action is to scan the file. * This action is invoked from slotTimeout. */ bool m_bScanFile; bool m_bIsDirectory; /** * Used to indicate that the next action is to initialize. * This action is invoked from slotTimeout */ bool m_bInit; /** * Used to indicate that the next action is to check whether we need to * show a prompt(before executing a script or desktop file). * This action is invoked from slotTimeout. */ bool m_bCheckPrompt; }; #endif // KRUN_P_H diff --git a/src/widgets/kshellcompletion.cpp b/src/widgets/kshellcompletion.cpp index f6c427a4..46610359 100644 --- a/src/widgets/kshellcompletion.cpp +++ b/src/widgets/kshellcompletion.cpp @@ -1,320 +1,320 @@ /* This file is part of the KDE libraries Copyright (C) 2000 David Smith 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 "kshellcompletion.h" #include -#include -#include -#include +#include +#include +#include #include #include class KShellCompletionPrivate { public: KShellCompletionPrivate() : m_word_break_char(' ') , m_quote_char1('\"') , m_quote_char2('\'') , m_escape_char('\\') { } void splitText(const QString &text, QString &text_start, QString &text_compl) const; bool quoteText(QString *text, bool force, bool skip_last) const; QString unquote(const QString &text) const; QString m_text_start; // part of the text that was not completed QString m_text_compl; // part of the text that was completed (unchanged) QChar m_word_break_char; QChar m_quote_char1; QChar m_quote_char2; QChar m_escape_char; }; KShellCompletion::KShellCompletion() : KUrlCompletion(), d(new KShellCompletionPrivate) { } KShellCompletion::~KShellCompletion() { delete d; } /* * makeCompletion() * * Entry point for file name completion */ QString KShellCompletion::makeCompletion(const QString &text) { // Split text at the last unquoted space // d->splitText(text, d->m_text_start, d->m_text_compl); // Remove quotes from the text to be completed // QString tmp = d->unquote(d->m_text_compl); d->m_text_compl = tmp; // Do exe-completion if there was no unquoted space // bool is_exe_completion = true; for (int i = 0; i < d->m_text_start.length(); i++) { if (d->m_text_start[i] != d->m_word_break_char) { is_exe_completion = false; break; } } Mode mode = (is_exe_completion ? ExeCompletion : FileCompletion); setMode(mode); // Make completion on the last part of text // return KUrlCompletion::makeCompletion(d->m_text_compl); } /* * postProcessMatch, postProcessMatches * * Called by KCompletion before emitting match() and matches() * * Add add the part of the text that was not completed * Add quotes when needed */ void KShellCompletion::postProcessMatch(QString *match) const { KUrlCompletion::postProcessMatch(match); if (match->isNull()) { return; } if (match->endsWith(QLatin1Char('/'))) { d->quoteText(match, false, true); // don't quote the trailing '/' } else { d->quoteText(match, false, false); // quote the whole text } match->prepend(d->m_text_start); } void KShellCompletion::postProcessMatches(QStringList *matches) const { KUrlCompletion::postProcessMatches(matches); for (QStringList::Iterator it = matches->begin(); it != matches->end(); ++it) { if (!(*it).isNull()) { if ((*it).endsWith(QLatin1Char('/'))) { d->quoteText(&(*it), false, true); // don't quote trailing '/' } else { d->quoteText(&(*it), false, false); // quote the whole text } (*it).prepend(d->m_text_start); } } } void KShellCompletion::postProcessMatches(KCompletionMatches *matches) const { KUrlCompletion::postProcessMatches(matches); for (KCompletionMatches::Iterator it = matches->begin(); it != matches->end(); ++it) { if (!(*it).value().isNull()) { if ((*it).value().endsWith(QLatin1Char('/'))) { d->quoteText(&(*it).value(), false, true); // don't quote trailing '/' } else { d->quoteText(&(*it).value(), false, false); // quote the whole text } (*it).value().prepend(d->m_text_start); } } } /* * splitText * * Split text at the last unquoted space * * text_start = [out] text at the left, including the space * text_compl = [out] text at the right */ void KShellCompletionPrivate::splitText(const QString &text, QString &text_start, QString &text_compl) const { bool in_quote = false; bool escaped = false; QChar p_last_quote_char; int last_unquoted_space = -1; int end_space_len = 0; for (int pos = 0; pos < text.length(); pos++) { end_space_len = 0; if (escaped) { escaped = false; } else if (in_quote && text[pos] == p_last_quote_char) { in_quote = false; } else if (!in_quote && text[pos] == m_quote_char1) { p_last_quote_char = m_quote_char1; in_quote = true; } else if (!in_quote && text[pos] == m_quote_char2) { p_last_quote_char = m_quote_char2; in_quote = true; } else if (text[pos] == m_escape_char) { escaped = true; } else if (!in_quote && text[pos] == m_word_break_char) { end_space_len = 1; while (pos + 1 < text.length() && text[pos + 1] == m_word_break_char) { end_space_len++; pos++; } if (pos + 1 == text.length()) { break; } last_unquoted_space = pos; } } text_start = text.left(last_unquoted_space + 1); // the last part without trailing blanks text_compl = text.mid(last_unquoted_space + 1); } /* * quoteText() * * Add quotations to 'text' if needed or if 'force' = true * Returns true if quotes were added * * skip_last => ignore the last charachter (we add a space or '/' to all filenames) */ bool KShellCompletionPrivate::quoteText(QString *text, bool force, bool skip_last) const { int pos = 0; if (!force) { pos = text->indexOf(m_word_break_char); if (skip_last && (pos == (int)(text->length()) - 1)) { pos = -1; } } if (!force && pos == -1) { pos = text->indexOf(m_quote_char1); if (skip_last && (pos == (int)(text->length()) - 1)) { pos = -1; } } if (!force && pos == -1) { pos = text->indexOf(m_quote_char2); if (skip_last && (pos == (int)(text->length()) - 1)) { pos = -1; } } if (!force && pos == -1) { pos = text->indexOf(m_escape_char); if (skip_last && (pos == (int)(text->length()) - 1)) { pos = -1; } } if (force || (pos >= 0)) { // Escape \ in the string text->replace(m_escape_char, QString(m_escape_char) + m_escape_char); // Escape " in the string text->replace(m_quote_char1, QString(m_escape_char) + m_quote_char1); // " at the beginning text->insert(0, m_quote_char1); // " at the end if (skip_last) { text->insert(text->length() - 1, m_quote_char1); } else { text->insert(text->length(), m_quote_char1); } return true; } return false; } /* * unquote * * Remove quotes and return the result in a new string * */ QString KShellCompletionPrivate::unquote(const QString &text) const { bool in_quote = false; bool escaped = false; QChar p_last_quote_char; QString result; for (int pos = 0; pos < text.length(); pos++) { if (escaped) { escaped = false; result.insert(result.length(), text[pos]); } else if (in_quote && text[pos] == p_last_quote_char) { in_quote = false; } else if (!in_quote && text[pos] == m_quote_char1) { p_last_quote_char = m_quote_char1; in_quote = true; } else if (!in_quote && text[pos] == m_quote_char2) { p_last_quote_char = m_quote_char2; in_quote = true; } else if (text[pos] == m_escape_char) { escaped = true; result.insert(result.length(), text[pos]); } else { result.insert(result.length(), text[pos]); } } return result; } diff --git a/src/widgets/kshellcompletion.h b/src/widgets/kshellcompletion.h index 0c804bf0..5680869a 100644 --- a/src/widgets/kshellcompletion.h +++ b/src/widgets/kshellcompletion.h @@ -1,71 +1,71 @@ /* This file is part of the KDE libraries Copyright (C) 2000 David Smith 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. */ #ifndef KSHELLCOMPLETION_H #define KSHELLCOMPLETION_H -#include -#include +#include +#include #include "kurlcompletion.h" class KShellCompletionPrivate; /** * @class KShellCompletion kshellcompletion.h * * This class does shell-like completion of file names. * A string passed to makeCompletion() will be interpreted as a shell * command line. Completion will be done on the last argument on the line. * Returned matches consist of the first arguments (uncompleted) plus the * completed last argument. * * @short Shell-like completion of file names * @author David Smith */ class KIOWIDGETS_EXPORT KShellCompletion : public KUrlCompletion { Q_OBJECT public: /** * Constructs a KShellCompletion object. */ KShellCompletion(); ~KShellCompletion(); /** * Finds completions to the given text. * The first match is returned and emitted in the signal match(). * @param text the text to complete * @return the first match, or QString() if not found */ QString makeCompletion(const QString &text) Q_DECL_OVERRIDE; protected: // Called by KCompletion void postProcessMatch(QString *match) const Q_DECL_OVERRIDE; void postProcessMatches(QStringList *matches) const Q_DECL_OVERRIDE; void postProcessMatches(KCompletionMatches *matches) const Q_DECL_OVERRIDE; private: KShellCompletionPrivate *const d; }; #endif // KSHELLCOMPLETION_H diff --git a/src/widgets/ksslcertificatebox.cpp b/src/widgets/ksslcertificatebox.cpp index a1cf29b6..16eeed4e 100644 --- a/src/widgets/ksslcertificatebox.cpp +++ b/src/widgets/ksslcertificatebox.cpp @@ -1,79 +1,79 @@ /* This file is part of the KDE project * * Copyright (C) 2007 Andreas Hartmetz * * 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 "ksslcertificatebox.h" #include "ui_certificateparty.h" -#include +#include class KSslCertificateBoxPrivate { public: Ui::CertificateParty ui; }; KSslCertificateBox::KSslCertificateBox(QWidget *parent) : QWidget(parent), d(new KSslCertificateBoxPrivate()) { d->ui.setupUi(this); // No fooling us with html tags Q_FOREACH (QLabel *label, findChildren()) { label->setTextFormat(Qt::PlainText); } } KSslCertificateBox::~KSslCertificateBox() { delete d; } void KSslCertificateBox::setCertificate(const QSslCertificate &cert, CertificateParty party) { if (party == Subject) { d->ui.commonName->setText(cert.subjectInfo(QSslCertificate::CommonName).join(QStringLiteral(", "))); d->ui.organization->setText(cert.subjectInfo(QSslCertificate::Organization).join(QStringLiteral(", "))); d->ui.organizationalUnit ->setText(cert.subjectInfo(QSslCertificate::OrganizationalUnitName).join(QStringLiteral(", "))); d->ui.country->setText(cert.subjectInfo(QSslCertificate::CountryName).join(QStringLiteral(", "))); d->ui.state->setText(cert.subjectInfo(QSslCertificate::StateOrProvinceName).join(QStringLiteral(", "))); d->ui.city->setText(cert.subjectInfo(QSslCertificate::LocalityName).join(QStringLiteral(", "))); } else if (party == Issuer) { d->ui.commonName->setText(cert.issuerInfo(QSslCertificate::CommonName).join(QStringLiteral(", "))); d->ui.organization->setText(cert.issuerInfo(QSslCertificate::Organization).join(QStringLiteral(", "))); d->ui.organizationalUnit ->setText(cert.issuerInfo(QSslCertificate::OrganizationalUnitName).join(QStringLiteral(", "))); d->ui.country->setText(cert.issuerInfo(QSslCertificate::CountryName).join(QStringLiteral(", "))); d->ui.state->setText(cert.issuerInfo(QSslCertificate::StateOrProvinceName).join(QStringLiteral(", "))); d->ui.city->setText(cert.issuerInfo(QSslCertificate::LocalityName).join(QStringLiteral(", "))); } } void KSslCertificateBox::clear() { d->ui.commonName->clear(); d->ui.organization->clear(); d->ui.organizationalUnit->clear(); d->ui.country->clear(); d->ui.state->clear(); d->ui.city->clear(); } diff --git a/src/widgets/ksslinfodialog.cpp b/src/widgets/ksslinfodialog.cpp index 54416f9c..9027573d 100644 --- a/src/widgets/ksslinfodialog.cpp +++ b/src/widgets/ksslinfodialog.cpp @@ -1,242 +1,242 @@ /* This file is part of the KDE project * * Copyright (C) 2000,2001 George Staikos * Copyright (C) 2000 Malte Starostik * * 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 "ksslinfodialog.h" #include "ui_sslinfo.h" #include "ksslcertificatebox.h" #include #include #include #include -#include +#include #include #include // BarIcon #include "ktcpsocket.h" class Q_DECL_HIDDEN KSslInfoDialog::KSslInfoDialogPrivate { public: QList certificateChain; QList > certificateErrors; bool isMainPartEncrypted; bool auxPartsEncrypted; Ui::SslInfo ui; KSslCertificateBox *subject; KSslCertificateBox *issuer; }; KSslInfoDialog::KSslInfoDialog(QWidget *parent) : QDialog(parent), d(new KSslInfoDialogPrivate) { setWindowTitle(i18n("KDE SSL Information")); setAttribute(Qt::WA_DeleteOnClose); QVBoxLayout *layout = new QVBoxLayout; setLayout(layout); QWidget *mainWidget = new QWidget(this); d->ui.setupUi(mainWidget); layout->addWidget(mainWidget); d->subject = new KSslCertificateBox(d->ui.certParties); d->issuer = new KSslCertificateBox(d->ui.certParties); d->ui.certParties->addTab(d->subject, i18nc("The receiver of the SSL certificate", "Subject")); d->ui.certParties->addTab(d->issuer, i18nc("The authority that issued the SSL certificate", "Issuer")); d->isMainPartEncrypted = true; d->auxPartsEncrypted = true; updateWhichPartsEncrypted(); QDialogButtonBox *buttonBox = new QDialogButtonBox(this); buttonBox->setStandardButtons(QDialogButtonBox::Close); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); layout->addWidget(buttonBox); #if 0 if (QSslSocket::supportsSsl()) { if (d->m_secCon) { d->pixmap->setPixmap(BarIcon("security-high")); d->info->setText(i18n("Current connection is secured with SSL.")); } else { d->pixmap->setPixmap(BarIcon("security-low")); d->info->setText(i18n("Current connection is not secured with SSL.")); } } else { d->pixmap->setPixmap(BarIcon("security-low")); d->info->setText(i18n("SSL support is not available in this build of KDE.")); } #endif } KSslInfoDialog::~KSslInfoDialog() { delete d; } void KSslInfoDialog::setMainPartEncrypted(bool mainEncrypted) { d->isMainPartEncrypted = mainEncrypted; updateWhichPartsEncrypted(); } void KSslInfoDialog::setAuxiliaryPartsEncrypted(bool auxEncrypted) { d->auxPartsEncrypted = auxEncrypted; updateWhichPartsEncrypted(); } void KSslInfoDialog::updateWhichPartsEncrypted() { if (d->isMainPartEncrypted) { if (d->auxPartsEncrypted) { d->ui.encryptionIndicator->setPixmap(BarIcon(QStringLiteral("security-high"))); d->ui.explanation->setText(i18n("Current connection is secured with SSL.")); } else { d->ui.encryptionIndicator->setPixmap(BarIcon(QStringLiteral("security-medium"))); d->ui.explanation->setText(i18n("The main part of this document is secured " "with SSL, but some parts are not.")); } } else { if (d->auxPartsEncrypted) { d->ui.encryptionIndicator->setPixmap(BarIcon(QStringLiteral("security-medium"))); d->ui.explanation->setText(i18n("Some of this document is secured with SSL, " "but the main part is not.")); } else { d->ui.encryptionIndicator->setPixmap(BarIcon(QStringLiteral("security-low"))); d->ui.explanation->setText(i18n("Current connection is not secured with SSL.")); } } } void KSslInfoDialog::setSslInfo(const QList &certificateChain, const QString &ip, const QString &host, const QString &sslProtocol, const QString &cipher, int usedBits, int bits, const QList > &validationErrors) { d->certificateChain = certificateChain; d->certificateErrors = validationErrors; d->ui.certSelector->clear(); for (int i = 0; i < certificateChain.size(); i++) { const QSslCertificate &cert = certificateChain[i]; QString name; static const QSslCertificate::SubjectInfo si[] = { QSslCertificate::CommonName, QSslCertificate::Organization, QSslCertificate::OrganizationalUnitName }; for (int j = 0; j < 3 && name.isEmpty(); j++) { name = cert.subjectInfo(si[j]).join(QStringLiteral(", ")); } d->ui.certSelector->addItem(name); } if (certificateChain.size() < 2) { d->ui.certSelector->setEnabled(false); } connect(d->ui.certSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(displayFromChain(int))); if (d->certificateChain.isEmpty()) { d->certificateChain.append(QSslCertificate()); } displayFromChain(0); d->ui.ip->setText(ip); d->ui.address->setText(host); d->ui.sslVersion->setText(sslProtocol); const QStringList cipherInfo = cipher.split('\n', QString::SkipEmptyParts); if (cipherInfo.size() >= 4) { d->ui.encryption->setText(i18nc("%1, using %2 bits of a %3 bit key", "%1, %2 %3", cipherInfo[0], i18ncp("Part of: %1, using %2 bits of a %3 bit key", "using %1 bit", "using %1 bits", usedBits), i18ncp("Part of: %1, using %2 bits of a %3 bit key", "of a %1 bit key", "of a %1 bit key", bits))); d->ui.details->setText(QStringLiteral("Auth = %1, Kx = %2, MAC = %3") .arg(cipherInfo[1], cipherInfo[2], cipherInfo[3])); } else { d->ui.encryption->setText(QLatin1String("")); d->ui.details->setText(QLatin1String("")); } } void KSslInfoDialog::displayFromChain(int i) { const QSslCertificate &cert = d->certificateChain[i]; QString trusted; if (!d->certificateErrors[i].isEmpty()) { trusted = i18nc("The certificate is not trusted", "NO, there were errors:"); foreach (KSslError::Error e, d->certificateErrors[i]) { KSslError classError(e); trusted.append('\n'); trusted.append(classError.errorString()); } } else { trusted = i18nc("The certificate is trusted", "Yes"); } d->ui.trusted->setText(trusted); QString vp = i18nc("%1 is the effective date of the certificate, %2 is the expiry date", "%1 to %2", cert.effectiveDate().toString(), cert.expiryDate().toString()); d->ui.validityPeriod->setText(vp); d->ui.serial->setText(cert.serialNumber()); d->ui.digest->setText(cert.digest().toHex()); d->ui.sha1Digest->setText(cert.digest(QCryptographicHash::Sha1).toHex()); d->subject->setCertificate(cert, KSslCertificateBox::Subject); d->issuer->setCertificate(cert, KSslCertificateBox::Issuer); } //static QList > KSslInfoDialog::errorsFromString(const QString &es) { QStringList sl = es.split('\n', QString::KeepEmptyParts); QList > ret; foreach (const QString &s, sl) { QList certErrors; QStringList sl2 = s.split('\t', QString::SkipEmptyParts); foreach (const QString &s2, sl2) { bool didConvert; KSslError::Error error = static_cast(s2.toInt(&didConvert)); if (didConvert) { certErrors.append(error); } } ret.append(certErrors); } return ret; } diff --git a/src/widgets/kurifilter.cpp b/src/widgets/kurifilter.cpp index 2f4caf0e..eb6b331c 100644 --- a/src/widgets/kurifilter.cpp +++ b/src/widgets/kurifilter.cpp @@ -1,694 +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 +#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() { } 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; } } } } } diff --git a/src/widgets/kurifilter.h b/src/widgets/kurifilter.h index 9f4ef533..ceeaee0c 100644 --- a/src/widgets/kurifilter.h +++ b/src/widgets/kurifilter.h @@ -1,1012 +1,1012 @@ /* * This file is part of the KDE libraries * Copyright (C) 2000-2001,2003,2010 Dawit Alemayehu * * Original author * Copyright (C) 2000 Yves Arrouye * * * 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. **/ #ifndef KURIFILTER_H #define KURIFILTER_H #include "kiowidgets_export.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #ifdef Q_OS_WIN #undef ERROR #endif class KUriFilterPrivate; class KUriFilterDataPrivate; class KCModule; class QHostInfo; /** * @class KUriFilterSearchProvider kurifilter.h * * Class that holds information about a search provider. * * @since 4.6 */ class KIOWIDGETS_EXPORT KUriFilterSearchProvider { public: /** * Default constructor. */ KUriFilterSearchProvider(); /** * Copy constructor. */ KUriFilterSearchProvider(const KUriFilterSearchProvider &); /** * Destructor. */ virtual ~KUriFilterSearchProvider(); /** * Returns the desktop filename of the search provider without any extension. * * For example, if the desktop filename of the search provider was * "foobar.desktop", this function will return "foobar". */ QString desktopEntryName() const; /** * Returns the descriptive name of the search provider, e.g. "Google News". * * This name comes from the "Name=" property entry in the desktop file that * contains the search provider's information. */ QString name() const; /** * Returns the icon name associated with the search provider when available. */ virtual QString iconName() const; /** * Returns all the web shortcut keys associated with this search provider. * * @see defaultKey */ QStringList keys() const; /** * Returns the default web shortcut key for this search provider. * * Right now this is the same as doing keys().first(), it might however * change based on what the backend plugins do. * * @see keys */ QString defaultKey() const; /** * Assignment operator. */ KUriFilterSearchProvider &operator=(const KUriFilterSearchProvider &); protected: void setDesktopEntryName(const QString &); void setIconName(const QString &); void setKeys(const QStringList &); void setName(const QString &); private: friend class KUriFilterPlugin; class KUriFilterSearchProviderPrivate; KUriFilterSearchProviderPrivate *const d; }; /** * @class KUriFilterData kurifilter.h * * This class is a basic messaging class used to exchange filtering information * between the filter plugins and the application requesting the filtering * service. * * Use this object if you require a more detailed information about the URI you * want to filter. Any application can create an instance of this class and send * it to KUriFilter to have the plugins fill out all possible information about * the URI. * * On successful filtering you can use @ref uriType() to determine what type * of resource the request was filtered into. See @ref KUriFilter::UriTypes for * details. If an error is encountered, then @ref KUriFilter::Error is returned. * You can use @ref errorMsg to obtain the error information. * * The functions in this class are not reentrant. * * \b Example * * Here is a basic example of how this class is used with @ref KUriFilter: * \code * KUriFilterData filterData (QLatin1String("kde.org")); * bool filtered = KUriFilter::self()->filterUri(filterData); * \endcode * * If you are only interested in getting the list of preferred search providers, * then you can do the following: * * \code * KUriFilterData data; * data.setData(""); * data.setSearchFilteringOption(KUriFilterData::RetrievePreferredSearchProvidersOnly); * bool filtered = KUriFilter::self()->filterSearchUri(data, KUriFilter::NormalTextFilter); * \endcode * * @short A class for exchanging filtering information. * @author Dawit Alemayehu */ class KIOWIDGETS_EXPORT KUriFilterData { public: /** * Describes the type of the URI that was filtered. * Here is a brief description of the types: * * @li NetProtocol - Any network protocol: http, ftp, nttp, pop3, etc... * @li LocalFile - A local file whose executable flag is not set * @li LocalDir - A local directory * @li Executable - A local file whose executable flag is set * @li Help - A man or info page * @li Shell - A shell executable (ex: echo "Test..." >> ~/testfile) * @li Blocked - A URI that should be blocked/filtered (ex: ad filtering) * @li Error - An incorrect URI (ex: "~johndoe" when user johndoe * does not exist in that system ) * @li Unknown - A URI that is not identified. Default value when * a KUriFilterData is first created. */ enum UriTypes { NetProtocol = 0, LocalFile, LocalDir, Executable, Help, Shell, Blocked, Error, Unknown }; /** * This enum describes the search filtering options to be used. * * @li SearchFilterOptionNone * No search filter options are set and normal filtering is performed * on the input data. * @li RetrieveSearchProvidersOnly * If set, the list of all available search providers are returned without * any input filtering. This flag only applies when used in conjunction * with the @ref KUriFilter::NormalTextFilter flag. * @li RetrievePreferredSearchProvidersOnly * If set, the list of preferred search providers are returned without * any input filtering. This flag only applies when used in conjunction * with the @ref KUriFilter::NormalTextFilter flag. * @li RetrieveAvailableSearchProvidersOnly * Same as doing RetrievePreferredSearchProvidersOnly | RetrieveSearchProvidersOnly, * where all available search providers are returned if no preferred ones * ones are available. No input filtering will be performed. * * @see setSearchFilteringOptions * @see KUriFilter::filterSearchUri * @since 4.6 */ enum SearchFilterOption { SearchFilterOptionNone = 0x0, RetrieveSearchProvidersOnly = 0x01, RetrievePreferredSearchProvidersOnly = 0x02, RetrieveAvailableSearchProvidersOnly = (RetrievePreferredSearchProvidersOnly | RetrieveSearchProvidersOnly) }; Q_DECLARE_FLAGS(SearchFilterOptions, SearchFilterOption) /** * Default constructor. * * Creates a UriFilterData object. */ KUriFilterData(); /** * Creates a KUriFilterData object from the given URL. * * @param url is the URL to be filtered. */ explicit KUriFilterData(const QUrl &url); /** * Creates a KUriFilterData object from the given string. * * @param url is the string to be filtered. */ explicit KUriFilterData(const QString &url); /** * Copy constructor. * * Creates a KUriFilterData object from another KURIFilterData object. * * @param other the uri filter data to be copied. */ KUriFilterData(const KUriFilterData &other); /** * Destructor. */ ~KUriFilterData(); /** * Returns the filtered or the original URL. * * If one of the plugins successfully filtered the original input, this * function returns it. Otherwise, it will return the input itself. * * @return the filtered or original url. */ QUrl uri() const; /** * Returns an error message. * * This functions returns the error message set by the plugin whenever the * uri type is set to KUriFilterData::ERROR. Otherwise, it returns a NULL * string. * * @return the error message or a NULL when there is none. */ QString errorMsg() const; /** * Returns the URI type. * * This method always returns KUriFilterData::UNKNOWN if the given URL was * not filtered. * * @return the type of the URI */ UriTypes uriType() const; /** * Returns the absolute path if one has already been set. * * @return the absolute path, or QString() * * @see hasAbsolutePath() */ QString absolutePath() const; /** * Checks whether the supplied data had an absolute path. * * @return true if the supplied data has an absolute path * * @see absolutePath() */ bool hasAbsolutePath() const; /** * Returns the command line options and arguments for a local resource * when present. * * @return options and arguments when present, otherwise QString() */ QString argsAndOptions() const; /** * Checks whether the current data is a local resource with command line * options and arguments. * * @return true if the current data has command line options and arguments */ bool hasArgsAndOptions() const; /** * @return true if the filters should attempt to check whether the * supplied uri is an executable. False otherwise. */ bool checkForExecutables() const; /** * The string as typed by the user, before any URL processing is done. */ QString typedString() const; /** * Returns the search term portion of the typed string. * * If the @ref typedString was not filtered by a search filter plugin, this * function returns an empty string. * * @see typedString * @since 4.5 */ QString searchTerm() const; /** * Returns the character that is used to separate the search term from the * keyword. * * If @ref typedString was not filtered by a search filter plugin, this * function returns a null character. * * @see typedString * @since 4.5 */ QChar searchTermSeparator() const; /** * Returns the name of the search service provider, e.g. Google. * * If @ref typedString was not filtered by a search filter plugin, this * function returns an empty string. * * @see typedString * @since 4.5 */ QString searchProvider() const; /** * Returns a list of the names of preferred or available search providers. * * This function returns the list of providers marked as preferred whenever * the input data, i.e. @ref typedString, is successfully filtered. * * If no default search provider has been selected prior to a filter request, * this function will return an empty list. To avoid this problem you must * either set an alternate default search provider using @ref setAlternateDefaultSearchProvider * or set one of the @ref SearchFilterOption flags if you are only interested * in getting the list of providers and not filtering the input. * * Additionally, you can also provide alternate search providers in case * there are no preferred ones already selected. * * You can use @ref queryForPreferredServiceProvider to obtain the query * associated with the list of search providers returned by this function. * * @see setAlternateSearchProviders * @see setAlternateDefaultSearchProvider * @see setSearchFilteringOption * @see queryForPreferredServiceProvider * @since 4.5 */ QStringList preferredSearchProviders() const; /** * Returns information about @p provider. * * You can use this function to obtain the more information about the search * providers returned by @ref preferredSearchProviders. * * @see preferredSearchProviders * @see KUriFilterSearchProvider * @since 4.6 */ KUriFilterSearchProvider queryForSearchProvider(const QString &provider) const; /** * Returns the web shortcut url for the given preferred search provider. * * You can use this function to obtain the query for the preferred search * providers returned by @ref preferredSearchProviders. * * The query returned by this function is in web shortcut format, i.e. * "gg:foo bar", and must be re-filtered through KUriFilter to obtain a * valid url. * * @see preferredSearchProviders * @since 4.5 */ QString queryForPreferredSearchProvider(const QString &provider) const; /** * Returns all the query urls for the given search provider. * * Use this function to obtain all the different queries that can be used * for the given provider. For example, if a search engine provider named * "foobar" has web shortcuts named "foobar", "foo" and "bar", then this * function, unlike @ref queryForPreferredSearchProvider, will return a * a query for each and every web shortcut. * * @see queryForPreferredSearchProvider * @since 4.6 */ QStringList allQueriesForSearchProvider(const QString &provider) const; /** * Returns the icon associated with the given preferred search provider. * * You can use this function to obtain the icon names associated with the * preferred search providers returned by @ref preferredSearchProviders. * * @see preferredSearchProviders * @since 4.5 */ QString iconNameForPreferredSearchProvider(const QString &provider) const; /** * Returns the list of alternate search providers. * * This function returns an empty list if @ref setAlternateSearchProviders * was not called to set the alternate search providers to be when no * preferred providers have been chosen by the user through the search * configuration module. * * @see setAlternatteSearchProviders * @see preferredSearchProviders * @since 4.5 */ QStringList alternateSearchProviders() const; /** * Returns the search provider to use when a default provider is not available. * * This function returns an empty string if @ref setAlternateDefaultSearchProvider * was not called to set the default search provider to be used when none has been * chosen by the user through the search configuration module. * * @see setAlternateDefaultSearchProvider * @since 4.5 */ QString alternateDefaultSearchProvider() const; /** * Returns the default protocol to use when filtering potentially valid url inputs. * * By default this function will return an empty string. * * @see setDefaultUrlScheme * @since 4.6 */ QString defaultUrlScheme() const; /** * Returns the specified search filter options. * * By default this function returns @ref SearchFilterOptionNone. * * @see setSearchFilteringOptions * @since 4.6 */ SearchFilterOptions searchFilteringOptions() const; /** * The name of the icon that matches the current filtered URL. * * This function returns a null string by default and when no icon is found * for the filtered URL. */ QString iconName(); /** * Check whether the provided uri is executable or not. * * Setting this to false ensures that typing the name of an executable does * not start that application. This is useful in the location bar of a * browser. The default value is true. */ void setCheckForExecutables(bool check); /** * Same as above except the argument is a URL. * * Use this function to set the string to be filtered when you construct an * empty filter object. * * @param url the URL to be filtered. */ void setData(const QUrl &url); /** * Sets the URL to be filtered. * * Use this function to set the string to be * filtered when you construct an empty filter * object. * * @param url the string to be filtered. */ void setData(const QString &url); /** * Sets the absolute path to be used whenever the supplied data is a * relative local URL. * * NOTE: This function should only be used for local resources, i.e. the * "file:/" protocol. It is useful for specifying the absolute path in * cases where the actual URL might be relative. If deriving the path from * a QUrl, make sure you set the argument for this function to the result * of calling path () instead of url (). * * @param abs_path the abolute path to the local resource. * * @return true if absolute path is successfully set. Otherwise, false. */ bool setAbsolutePath(const QString &abs_path); /** * Sets a list of search providers to use in case no preferred search * providers are available. * * The list of preferred search providers set using this function will only * be used if the default and favorite search providers have not yet been * selected by the user. Otherwise, the providers specified through this * function will be ignored. * * @see alternateSearchProviders * @see preferredSearchProviders * @since 4.5 */ void setAlternateSearchProviders(const QStringList &providers); /** * Sets the search provider to use in case no default provider is available. * * The default search provider set using this function will only be used if * the default and favorite search providers have not yet been selected by * the user. Otherwise, the default provider specified by through function * will be ignored. * * @see alternateDefaultSearchProvider * @see preferredSearchProviders * @since 4.5 */ void setAlternateDefaultSearchProvider(const QString &provider); /** * Sets the default scheme used when filtering potentially valid url inputs. * * Use this function to change the default protocol used when filtering * potentially valid url inputs. The default protocol is http. * * If the scheme is specified without a separator, then "://" will be used * as the separator by default. For example, if the default url scheme was * simply set to "ftp", then a potentially valid url input such as "kde.org" * will be filtered to "ftp://kde.org". * * @see defaultUrlScheme * @since 4.6 */ void setDefaultUrlScheme(const QString &); /** * Sets the options used by search filter plugins to filter requests. * * The default search filter option is @ref SearchFilterOptionNone. See * @ref SearchFilterOption for the description of the other flags. * * It is important to note that the options set through this function can * prevent any filtering from being performed by search filter plugins. * As such, @ref uriTypes can return KUriFilterData::Unknown and @ref uri * can return an invalid url eventhough the filtering request returned * a successful response. * * @see searchFilteringOptions * @since 4.6 */ void setSearchFilteringOptions(SearchFilterOptions options); /** * Overloaded assigenment operator. * * This function allows you to easily assign a QUrl * to a KUriFilterData object. * * @return an instance of a KUriFilterData object. */ KUriFilterData &operator=(const QUrl &url); /** * Overloaded assigenment operator. * * This function allows you to easily assign a QString to a KUriFilterData * object. * * @return an instance of a KUriFilterData object. */ KUriFilterData &operator=(const QString &url); private: friend class KUriFilterPlugin; KUriFilterDataPrivate *const d; }; /** * @class KUriFilterPlugin kurifilter.h * * Base class for URI filter plugins. * * This class applies a single filter to a URI. All plugins designed to provide * URI filtering service should inherit from this abstract class and provide a * concrete implementation. * * All inheriting classes need to implement the pure virtual function * @ref filterUri. * * @short Abstract class for URI filter plugins. */ class KIOWIDGETS_EXPORT KUriFilterPlugin : public QObject { Q_OBJECT public: /** * List for holding the following search provider information: * ([search provider name], [search query, search query icon name]) * * @since 4.5 * @deprecated Use @ref KUriFilterSearchProvider instead. See @ref setSearchProviders; */ #ifndef KIOWIDGETS_NO_DEPRECATED KIOWIDGETS_DEPRECATED typedef QHash > ProviderInfoList; #endif /** * Constructs a filter plugin with a given name * * @param parent the parent object, or 0 for no parent * @param name the name of the plugin, mandatory */ explicit KUriFilterPlugin(const QString &name, QObject *parent = nullptr); // KF6 TODO ~KUriFilterPlugin(); /** * Filters a URI. * * @param data the URI data to be filtered. * @return A boolean indicating whether the URI has been changed. */ virtual bool filterUri(KUriFilterData &data) const = 0; /** * Creates a configuration module for the filter. * * It is the responsibility of the caller to delete the module once it is * not needed anymore. * * @return A configuration module, 0 if the filter isn't configurable. */ virtual KCModule *configModule(QWidget *, const char *) const; /** * Returns the name of the configuration module for the filter. * * @return the name of a configuration module or QString() if none. */ virtual QString configName() const; protected: /** * Sets the URL in @p data to @p uri. */ void setFilteredUri(KUriFilterData &data, const QUrl &uri) const; /** * Sets the error message in @p data to @p errormsg. */ void setErrorMsg(KUriFilterData &data, const QString &errmsg) const; /** * Sets the URI type in @p data to @p type. */ void setUriType(KUriFilterData &data, KUriFilterData::UriTypes type) const; /** * Sets the arguments and options string in @p data to @p args if any were * found during filterting. */ void setArguments(KUriFilterData &data, const QString &args) const; /** * Sets the name of the search provider, the search term and keyword/term * separator in @p data. * * @since 4.5 */ void setSearchProvider(KUriFilterData &data, const QString &provider, const QString &term, const QChar &separator) const; /** * Sets the information about the search @p providers in @p data. * * @since 4.6 */ void setSearchProviders(KUriFilterData &data, const QList &providers) const; /** * Returns the icon name for the given @p url and URI @p type. * * @since 4.5 */ QString iconNameFor(const QUrl &url, KUriFilterData::UriTypes type) const; /** * Performs a DNS lookup for @p hostname and returns the result. * * This function uses the KIO/KHTML DNS cache to speed up the * lookup. It also avoids doing a reverse lookup if the given * host name is already an ip address. * * \note All uri filter plugins that need to perform a hostname * lookup should use this function. * * @param hostname the hostname to lookup. * @param timeout the amount of time in msecs to wait for the lookup. * @return the result of the host name lookup. * * @since 4.7 */ QHostInfo resolveName(const QString &hostname, unsigned long timeout) const; private: class KUriFilterPluginPrivate *const d; }; /** * @class KUriFilter kurifilter.h * * KUriFilter applies a number of filters to a URI and returns a filtered version if any * filter matches. * A simple example is "kde.org" to "http://www.kde.org", which is commonplace in web browsers. * * The filters are implemented as plugins in @ref KUriFilterPlugin subclasses. * * KUriFilter is a singleton object: obtain the instance by calling * @p KUriFilter::self() and use the public member functions to * perform the filtering. * * \b Example * * To simply filter a given string: * * \code * QString url("kde.org"); * bool filtered = KUriFilter::self()->filteredUri( url ); * \endcode * * You can alternatively use a QUrl: * * \code * QUrl url("kde.org"); * bool filtered = KUriFilter::self()->filterUri( url ); * \endcode * * If you have a constant string or a constant URL, simply invoke the * corresponding function to obtain the filtered string or URL instead * of a boolean flag: * * \code * QString filteredText = KUriFilter::self()->filteredUri( "kde.org" ); * \endcode * * All of the above examples should result in "kde.org" being filtered into * "http://kde.org". * * You can also restrict the filters to be used by supplying the name of the * filters you want to use. By defualt all available filters are used. * * To use specific filters, add the names of the filters you want to use to a * QStringList and invoke the appropriate filtering function. * * The examples below show the use of specific filters. KDE ships with the * following filter plugins by default: * * kshorturifilter: * This is used for filtering potentially valid url inputs such as "kde.org" * Additionally it filters shell variables and shortcuts such as $HOME and * ~ as well as man and info page shortcuts, # and ## respectively. * * kuriikwsfilter: * This is used for filtering normal input text into a web search url using the * configured fallback search engine selected by the user. * * kurisearchfilter: * This is used for filtering KDE webshortcuts. For example "gg:KDE" will be * converted to a url for searching the work "KDE" using the Google search * engine. * * localdomainfilter: * This is used for doing a DNS lookup to determine whether the input is a valid * local address. * * fixuphosturifilter: * This is used to append "www." to the host name of a pre filtered http url * if the original url cannot be resolved. * * \code * QString text ("kde.org"); * bool filtered = KUriFilter::self()->filterUri(text, QLatin1String("kshorturifilter")); * \endcode * * The above code should result in "kde.org" being filtered into "http://kde.org". * * \code * QStringList list; * list << QLatin1String("kshorturifilter") << QLatin1String("localdomainfilter"); * bool filtered = KUriFilter::self()->filterUri( text, list ); * \endcode * * Additionally if you only want to do search related filtering, you can use the * search specific function, @ref filterSearchUri, that is available in KDE * 4.5 and higher. For example, to search for a given input on the web you * can do the following: * * KUriFilterData filterData ("foo"); * bool filtered = KUriFilter::self()->filterSearchUri(filterData, KUriFilterData::NormalTextFilter); * * KUriFilter converts all filtering requests to use @ref KUriFilterData * internally. The use of this bi-directional class allows you to send specific * instructions to the filter plugins as well as receive detailed information * about the filtered request from them. See the documentation of KUriFilterData * class for more examples and details. * * All functions in this class are thread safe and reentrant. * * @short Filters the given input into a valid url whenever possible. */ class KIOWIDGETS_EXPORT KUriFilter { public: /** * This enum describes the types of search plugin filters available. * * @li NormalTextFilter The plugin used to filter normal text, e.g. "some term to search". * @li WebShortcutFilter The plugin used to filter web shortcuts, e.g. gg:KDE. */ enum SearchFilterType { NormalTextFilter = 0x01, WebShortcutFilter = 0x02 }; Q_DECLARE_FLAGS(SearchFilterTypes, SearchFilterType) /** * Destructor */ ~KUriFilter(); /** * Returns an instance of KUriFilter. */ static KUriFilter *self(); /** * Filters @p data using the specified @p filters. * * If no named filters are specified, the default, then all the * URI filter plugins found will be used. * * @param data object that contains the URI to be filtered. * @param filters specify the list of filters to be used. * * @return a boolean indicating whether the URI has been changed */ bool filterUri(KUriFilterData &data, const QStringList &filters = QStringList()); /** * Filters the URI given by the URL. * * The given URL is filtered based on the specified list of filters. * If the list is empty all available filters would be used. * * @param uri the URI to filter. * @param filters specify the list of filters to be used. * * @return a boolean indicating whether the URI has been changed */ bool filterUri(QUrl &uri, const QStringList &filters = QStringList()); /** * Filters a string representing a URI. * * The given URL is filtered based on the specified list of filters. * If the list is empty all available filters would be used. * * @param uri The URI to filter. * @param filters specify the list of filters to be used. * * @return a boolean indicating whether the URI has been changed */ bool filterUri(QString &uri, const QStringList &filters = QStringList()); /** * Returns the filtered URI. * * The given URL is filtered based on the specified list of filters. * If the list is empty all available filters would be used. * * @param uri The URI to filter. * @param filters specify the list of filters to be used. * * @return the filtered URI or null if it cannot be filtered */ QUrl filteredUri(const QUrl &uri, const QStringList &filters = QStringList()); /** * Return a filtered string representation of a URI. * * The given URL is filtered based on the specified list of filters. * If the list is empty all available filters would be used. * * @param uri the URI to filter. * @param filters specify the list of filters to be used. * * @return the filtered URI or null if it cannot be filtered */ QString filteredUri(const QString &uri, const QStringList &filters = QStringList()); /** * See @ref filterSearchUri(KUriFilterData&, SearchFilterTypes) * * @since 4.5 * @deprecated Use filterSearchUri(KUriFilterData&, SearchFilterTypes) instead. */ #ifndef KIOWIDGETS_NO_DEPRECATED KIOWIDGETS_DEPRECATED bool filterSearchUri(KUriFilterData &data); #endif /** * Filter @p data using the criteria specified by @p types. * * The search filter type can be individual value of @ref SearchFilterTypes * or a combination of those types using the bitwise OR operator. * * You can also use the flags from @ref KUriFilterData::SearchFilterOption * to alter the filtering mechanisms of the search filter providers. * * @param data object that contains the URI to be filtered. * @param types the search filters used to filter the request. * @return true if the specified @p data was successfully filtered. * * @see KUriFilterData::setSearchFilteringOptions * @since 4.6 */ bool filterSearchUri(KUriFilterData &data, SearchFilterTypes types); /** * Return a list of the names of all loaded plugins. * * @return a QStringList of plugin names */ QStringList pluginNames() const; protected: /** * Constructor. * * Creates a KUriFilter object and calls @ref loadPlugins to load all * available URI filter plugins. */ KUriFilter(); /** * Loads all allowed plugins. * * This function only loads URI filter plugins that have not been disabled. */ void loadPlugins(); private: KUriFilterPrivate *const d; friend class KUriFilterSingleton; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KUriFilterData::SearchFilterOptions) Q_DECLARE_OPERATORS_FOR_FLAGS(KUriFilter::SearchFilterTypes) #endif diff --git a/src/widgets/kurlcombobox.cpp b/src/widgets/kurlcombobox.cpp index a8e152bf..79d08073 100644 --- a/src/widgets/kurlcombobox.cpp +++ b/src/widgets/kurlcombobox.cpp @@ -1,451 +1,451 @@ /* This file is part of the KDE libraries Copyright (C) 2000,2001 Carsten Pfeiffer 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 "kurlcombobox.h" -#include +#include #include #include #include #include #include #include #include #include class KUrlComboBoxPrivate { public: KUrlComboBoxPrivate(KUrlComboBox *parent) : m_parent(parent), dirIcon(QStringLiteral("folder")) {} ~KUrlComboBoxPrivate() { qDeleteAll(itemList); qDeleteAll(defaultList); } struct KUrlComboItem { KUrlComboItem(const QUrl &url, const QIcon &icon, const QString &text = QString()) : url(url), icon(icon), text(text) {} QUrl url; QIcon icon; QString text; // if empty, calculated from the QUrl }; void init(KUrlComboBox::Mode mode); QString textForItem(const KUrlComboItem *item) const; void insertUrlItem(const KUrlComboItem *); QIcon getIcon(const QUrl &url) const; void updateItem(const KUrlComboItem *item, int index, const QIcon &icon); void _k_slotActivated(int); KUrlComboBox *m_parent; QIcon dirIcon; bool urlAdded; int myMaximum; KUrlComboBox::Mode myMode; QPoint m_dragPoint; QList itemList; QList defaultList; QMap itemMapper; QIcon opendirIcon; }; QString KUrlComboBoxPrivate::textForItem(const KUrlComboItem *item) const { if (!item->text.isEmpty()) { return item->text; } QString text; QUrl url = item->url; if (myMode == KUrlComboBox::Directories) { if (!url.path().isEmpty() && !url.path().endsWith(QLatin1Char('/'))) { url.setPath(url.path() + QLatin1Char('/')); } } else { url = url.adjusted(QUrl::StripTrailingSlash); } if (url.isLocalFile()) { return url.toLocalFile(); } else { return url.toDisplayString(); } } KUrlComboBox::KUrlComboBox(Mode mode, QWidget *parent) : KComboBox(parent), d(new KUrlComboBoxPrivate(this)) { d->init(mode); } KUrlComboBox::KUrlComboBox(Mode mode, bool rw, QWidget *parent) : KComboBox(rw, parent), d(new KUrlComboBoxPrivate(this)) { d->init(mode); } KUrlComboBox::~KUrlComboBox() { delete d; } void KUrlComboBoxPrivate::init(KUrlComboBox::Mode mode) { myMode = mode; urlAdded = false; myMaximum = 10; // default m_parent->setInsertPolicy(KUrlComboBox::NoInsert); m_parent->setTrapReturnKey(true); m_parent->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); m_parent->setLayoutDirection(Qt::LeftToRight); if (m_parent->completionObject()) { m_parent->completionObject()->setOrder(KCompletion::Sorted); } opendirIcon = QIcon::fromTheme(QStringLiteral("folder-open")); m_parent->connect(m_parent, SIGNAL(activated(int)), SLOT(_k_slotActivated(int))); } QStringList KUrlComboBox::urls() const { // qDebug() << "::urls()"; QStringList list; QString url; for (int i = d->defaultList.count(); i < count(); i++) { url = itemText(i); if (!url.isEmpty()) { if (QDir::isAbsolutePath(url)) list.append(QUrl::fromLocalFile(url).toString()); else list.append(url); } } return list; } void KUrlComboBox::addDefaultUrl(const QUrl &url, const QString &text) { addDefaultUrl(url, d->getIcon(url), text); } void KUrlComboBox::addDefaultUrl(const QUrl &url, const QIcon &icon, const QString &text) { d->defaultList.append(new KUrlComboBoxPrivate::KUrlComboItem(url, icon, text)); } void KUrlComboBox::setDefaults() { clear(); d->itemMapper.clear(); const KUrlComboBoxPrivate::KUrlComboItem *item; for (int id = 0; id < d->defaultList.count(); id++) { item = d->defaultList.at(id); d->insertUrlItem(item); } } void KUrlComboBox::setUrls(const QStringList &urls) { setUrls(urls, RemoveBottom); } void KUrlComboBox::setUrls(const QStringList &_urls, OverLoadResolving remove) { setDefaults(); qDeleteAll(d->itemList); d->itemList.clear(); d->urlAdded = false; if (_urls.isEmpty()) { return; } QStringList urls; QStringList::ConstIterator it = _urls.constBegin(); // kill duplicates while (it != _urls.constEnd()) { if (!urls.contains(*it)) { urls += *it; } ++it; } // limit to myMaximum items /* Note: overload is an (old) C++ keyword, some compilers (KCC) choke on that, so call it Overload (capital 'O'). (matz) */ int Overload = urls.count() - d->myMaximum + d->defaultList.count(); while (Overload > 0) { if (remove == RemoveBottom) { if (!urls.isEmpty()) { urls.removeLast(); } } else { if (!urls.isEmpty()) { urls.removeFirst(); } } Overload--; } it = urls.constBegin(); KUrlComboBoxPrivate::KUrlComboItem *item = nullptr; while (it != urls.constEnd()) { if ((*it).isEmpty()) { ++it; continue; } QUrl u; if (QDir::isAbsolutePath(*it)) { u = QUrl::fromLocalFile(*it); } else { u.setUrl(*it); } // Don't restore if file doesn't exist anymore if (u.isLocalFile() && !QFile::exists(u.toLocalFile())) { ++it; continue; } item = new KUrlComboBoxPrivate::KUrlComboItem(u, d->getIcon(u)); d->insertUrlItem(item); d->itemList.append(item); ++it; } } void KUrlComboBox::setUrl(const QUrl &url) { if (url.isEmpty()) { return; } bool blocked = blockSignals(true); // check for duplicates QMap::ConstIterator mit = d->itemMapper.constBegin(); QString urlToInsert = url.toString(QUrl::StripTrailingSlash); while (mit != d->itemMapper.constEnd()) { Q_ASSERT(mit.value()); if (urlToInsert == mit.value()->url.toString(QUrl::StripTrailingSlash)) { setCurrentIndex(mit.key()); if (d->myMode == Directories) { d->updateItem(mit.value(), mit.key(), d->opendirIcon); } blockSignals(blocked); return; } ++mit; } // not in the combo yet -> create a new item and insert it // first remove the old item if (d->urlAdded) { Q_ASSERT(!d->itemList.isEmpty()); d->itemList.removeLast(); d->urlAdded = false; } setDefaults(); int offset = qMax(0, d->itemList.count() - d->myMaximum + d->defaultList.count()); for (int i = offset; i < d->itemList.count(); i++) { d->insertUrlItem(d->itemList[i]); } KUrlComboBoxPrivate::KUrlComboItem *item = new KUrlComboBoxPrivate::KUrlComboItem(url, d->getIcon(url)); const int id = count(); const QString text = d->textForItem(item); if (d->myMode == Directories) { KComboBox::insertItem(id, d->opendirIcon, text); } else { KComboBox::insertItem(id, item->icon, text); } d->itemMapper.insert(id, item); d->itemList.append(item); setCurrentIndex(id); Q_ASSERT(!d->itemList.isEmpty()); d->urlAdded = true; blockSignals(blocked); } void KUrlComboBoxPrivate::_k_slotActivated(int index) { const KUrlComboItem *item = itemMapper.value(index); if (item) { m_parent->setUrl(item->url); emit m_parent->urlActivated(item->url); } } void KUrlComboBoxPrivate::insertUrlItem(const KUrlComboBoxPrivate::KUrlComboItem *item) { Q_ASSERT(item); // qDebug() << "insertURLItem " << d->textForItem(item); int id = m_parent->count(); m_parent->KComboBox::insertItem(id, item->icon, textForItem(item)); itemMapper.insert(id, item); } void KUrlComboBox::setMaxItems(int max) { d->myMaximum = max; if (count() > d->myMaximum) { int oldCurrent = currentIndex(); setDefaults(); int offset = qMax(0, d->itemList.count() - d->myMaximum + d->defaultList.count()); for (int i = offset; i < d->itemList.count(); i++) { d->insertUrlItem(d->itemList[i]); } if (count() > 0) { // restore the previous currentItem if (oldCurrent >= count()) { oldCurrent = count() - 1; } setCurrentIndex(oldCurrent); } } } int KUrlComboBox::maxItems() const { return d->myMaximum; } void KUrlComboBox::removeUrl(const QUrl &url, bool checkDefaultURLs) { QMap::ConstIterator mit = d->itemMapper.constBegin(); while (mit != d->itemMapper.constEnd()) { if (url.toString(QUrl::StripTrailingSlash) == mit.value()->url.toString(QUrl::StripTrailingSlash)) { if (!d->itemList.removeAll(mit.value()) && checkDefaultURLs) { d->defaultList.removeAll(mit.value()); } } ++mit; } bool blocked = blockSignals(true); setDefaults(); QListIterator it(d->itemList); while (it.hasNext()) { d->insertUrlItem(it.next()); } blockSignals(blocked); } void KUrlComboBox::setCompletionObject(KCompletion *compObj, bool hsig) { if (compObj) { // on a url combo box we want completion matches to be sorted. This way, if we are given // a suggestion, we match the "best" one. For instance, if we have "foo" and "foobar", // and we write "foo", the match is "foo" and never "foobar". (ereslibre) compObj->setOrder(KCompletion::Sorted); } KComboBox::setCompletionObject(compObj, hsig); } void KUrlComboBox::mousePressEvent(QMouseEvent *event) { QStyleOptionComboBox comboOpt; comboOpt.initFrom(this); const int x0 = QStyle::visualRect(layoutDirection(), rect(), style()->subControlRect(QStyle::CC_ComboBox, &comboOpt, QStyle::SC_ComboBoxEditField, this)).x(); const int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &comboOpt, this); if (event->x() < (x0 + KIconLoader::SizeSmall + frameWidth)) { d->m_dragPoint = event->pos(); } else { d->m_dragPoint = QPoint(); } KComboBox::mousePressEvent(event); } void KUrlComboBox::mouseMoveEvent(QMouseEvent *event) { const int index = currentIndex(); const KUrlComboBoxPrivate::KUrlComboItem *item = d->itemMapper.value(index); if (item && !d->m_dragPoint.isNull() && event->buttons() & Qt::LeftButton && (event->pos() - d->m_dragPoint).manhattanLength() > QApplication::startDragDistance()) { QDrag *drag = new QDrag(this); QMimeData *mime = new QMimeData(); mime->setUrls(QList() << item->url); mime->setText(itemText(index)); if (!itemIcon(index).isNull()) { drag->setPixmap(itemIcon(index).pixmap(KIconLoader::SizeMedium)); } drag->setMimeData(mime); drag->exec(); } KComboBox::mouseMoveEvent(event); } QIcon KUrlComboBoxPrivate::getIcon(const QUrl &url) const { if (myMode == KUrlComboBox::Directories) { return dirIcon; } else { return QIcon::fromTheme(KIO::iconNameForUrl(url)); } } // updates "item" with icon "icon" // kdelibs4 used to also say "and sets the URL instead of text", but this breaks const-ness, // now that it would require clearing the text, and I don't see the point since the URL was already in the text. void KUrlComboBoxPrivate::updateItem(const KUrlComboBoxPrivate::KUrlComboItem *item, int index, const QIcon &icon) { m_parent->setItemIcon(index, icon); #if 0 if (m_parent->isEditable()) { item->text.clear(); // so that it gets recalculated } #endif m_parent->setItemText(index, textForItem(item)); } #include "moc_kurlcombobox.cpp" diff --git a/src/widgets/kurlcombobox.h b/src/widgets/kurlcombobox.h index 759a12a6..26b0db91 100644 --- a/src/widgets/kurlcombobox.h +++ b/src/widgets/kurlcombobox.h @@ -1,207 +1,207 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Carsten Pfeiffer 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. */ #ifndef KURLCOMBOBOX_H #define KURLCOMBOBOX_H #include "kiowidgets_export.h" -#include -#include -#include +#include +#include +#include #include #include class QUrl; class KUrlComboBoxPrivate; /** * @class KUrlComboBox kurlcombobox.h * * This combobox shows a number of recent URLs/directories, as well as some * default directories. * It will manage the default dirs root-directory, home-directory and * Desktop-directory, as well as a number of URLs set via setUrls() * and one additional entry to be set via setUrl(). * * This widget forces the layout direction to be Qt::LeftToRight instead * of inheriting the layout direction like a normal widget. This means * that even in RTL desktops the widget will be displayed in LTR mode, * as generally URLs are LTR by nature. * * @short A combo box showing a number of recent URLs/directories * @author Carsten Pfeiffer */ class KIOWIDGETS_EXPORT KUrlComboBox : public KComboBox { Q_OBJECT Q_PROPERTY(QStringList urls READ urls WRITE setUrls DESIGNABLE true) Q_PROPERTY(int maxItems READ maxItems WRITE setMaxItems DESIGNABLE true) public: /** * This enum describes which kind of items is shown in the combo box. */ enum Mode { Files = -1, Directories = 1, Both = 0 }; /** * This Enumeration is used in setUrl() to determine which items * will be removed when the given list is larger than maxItems(). * * @li RemoveTop means that items will be removed from top * @li RemoveBottom means, that items will be removed from the bottom */ enum OverLoadResolving { RemoveTop, RemoveBottom }; /** * Constructs a KUrlComboBox. * @param mode is either Files, Directories or Both and controls the * following behavior: * @li Files all inserted URLs will be treated as files, therefore the * url shown in the combo will never show a trailing / * the icon will be the one associated with the file's mimetype. * @li Directories all inserted URLs will be treated as directories, will * have a trailing slash in the combobox. The current * directory will show the "open folder" icon, other * directories the "folder" icon. * @li Both Don't mess with anything, just show the url as given. * @param parent The parent object of this widget. */ explicit KUrlComboBox(Mode mode, QWidget *parent = nullptr); KUrlComboBox(Mode mode, bool rw, QWidget *parent = nullptr); /** * Destructs the combo box. */ ~KUrlComboBox(); /** * Sets the current url. This combo handles exactly one url additionally * to the default items and those set via setUrls(). So you can call * setUrl() as often as you want, it will always replace the previous one * set via setUrl(). * If @p url is already in the combo, the last item will stay there * and the existing item becomes the current item. * The current item will always have the open-directory-pixmap as icon. * * Note that you won't receive any signals, e.g. textChanged(), * returnPressed() or activated() upon calling this method. */ void setUrl(const QUrl &url); /** * Inserts @p urls into the combobox below the "default urls" (see * addDefaultUrl). * * If the list of urls contains more items than maxItems, the first items * will be stripped. */ void setUrls(const QStringList &urls); /** * Inserts @p urls into the combobox below the "default urls" (see * addDefaultUrl). * * If the list of urls contains more items than maxItems, the @p remove * parameter determines whether the first or last items will be stripped. */ void setUrls(const QStringList &urls, OverLoadResolving remove); /** * @returns a list of all urls currently handled. The list contains at most * maxItems() items. * Use this to save the list of urls in a config-file and reinsert them * via setUrls() next time. * Note that all default urls set via addDefaultUrl() are not * returned, they will automatically be set via setUrls() or setUrl(). * You will always get fully qualified urls, i.e. with protocol like * file:/ */ QStringList urls() const; /** * Sets how many items should be handled and displayed by the combobox. * @see maxItems */ void setMaxItems(int); /** * @returns the maximum of items the combobox handles. * @see setMaxItems */ int maxItems() const; /** * Adds a url that will always be shown in the combobox, it can't be * "rotated away". Default urls won't be returned in urls() and don't * have to be set via setUrls(). * If you want to specify a special pixmap, use the overloaded method with * the pixmap parameter. * Default URLs will be inserted into the combobox by setDefaults() */ void addDefaultUrl(const QUrl &url, const QString &text = QString()); /** * Adds a url that will always be shown in the combobox, it can't be * "rotated away". Default urls won't be returned in urls() and don't * have to be set via setUrls(). * If you don't need to specify a pixmap, use the overloaded method without * the pixmap parameter. * Default URLs will be inserted into the combobox by setDefaults() */ void addDefaultUrl(const QUrl &url, const QIcon &icon, const QString &text = QString()); /** * Clears all items and inserts the default urls into the combo. Will be * called implicitly upon the first call to setUrls() or setUrl() * @see addDefaultUrl */ void setDefaults(); /** * Removes any occurrence of @p url. If @p checkDefaultUrls is false * default-urls won't be removed. */ void removeUrl(const QUrl &url, bool checkDefaultURLs = true); /** * Reimplemented from KComboBox (from KCompletion) * @internal */ void setCompletionObject(KCompletion *compObj, bool hsig = true) Q_DECL_OVERRIDE; Q_SIGNALS: /** * Emitted when an item was clicked at. * @param url is the url of the now current item. */ void urlActivated(const QUrl &url); protected: void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE; private: friend class KUrlComboBoxPrivate; KUrlComboBoxPrivate *const d; Q_DISABLE_COPY(KUrlComboBox) Q_PRIVATE_SLOT(d, void _k_slotActivated(int)) }; #endif // KURLCOMBOBOX_H diff --git a/src/widgets/kurlcompletion.cpp b/src/widgets/kurlcompletion.cpp index 0369d7bb..797b859f 100644 --- a/src/widgets/kurlcompletion.cpp +++ b/src/widgets/kurlcompletion.cpp @@ -1,1567 +1,1567 @@ /* This file is part of the KDE libraries Copyright (C) 2000 David Smith Copyright (C) 2004 Scott Wheeler This class was inspired by a previous KUrlCompletion by Henner Zeller 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 "kurlcompletion.h" #include "../pathhelpers_p.h" #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include // QT_LSTAT, QT_STAT, QT_STATBUF #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #else #include #endif static bool expandTilde(QString &); static bool expandEnv(QString &); static QString unescape(const QString &text); // Permission mask for files that are executable by // user, group or other #define MODE_EXE (S_IXUSR | S_IXGRP | S_IXOTH) // Constants for types of completion enum ComplType {CTNone = 0, CTEnv, CTUser, CTMan, CTExe, CTFile, CTUrl, CTInfo}; class CompletionThread; // Ensure that we don't end up with "//". static QUrl addPathToUrl(const QUrl &url, const QString &relPath) { QUrl u(url); u.setPath(concatPaths(url.path(), relPath)); return u; } static QBasicAtomicInt s_waitDuration = Q_BASIC_ATOMIC_INITIALIZER(-1); static int initialWaitDuration() { if (s_waitDuration.load() == -1) { const QByteArray envVar = qgetenv("KURLCOMPLETION_WAIT"); if (envVar.isEmpty()) { s_waitDuration = 200; // default: 200 ms } else { s_waitDuration = envVar.toInt(); } } return s_waitDuration; } /////////////////////////////////////////////////////// /////////////////////////////////////////////////////// // KUrlCompletionPrivate // class KUrlCompletionPrivate { public: KUrlCompletionPrivate(KUrlCompletion *parent) : q(parent), url_auto_completion(true), userListThread(nullptr), dirListThread(nullptr) { } ~KUrlCompletionPrivate(); void _k_slotEntries(KIO::Job *, const KIO::UDSEntryList &); void _k_slotIOFinished(KJob *); void slotCompletionThreadDone(QThread *thread, const QStringList &matches); class MyURL; bool userCompletion(const MyURL &url, QString *match); bool envCompletion(const MyURL &url, QString *match); bool exeCompletion(const MyURL &url, QString *match); bool fileCompletion(const MyURL &url, QString *match); bool urlCompletion(const MyURL &url, QString *match); bool isAutoCompletion(); // List the next dir in m_dirs QString listDirectories(const QStringList &, const QString &, bool only_exe = false, bool only_dir = false, bool no_hidden = false, bool stat_files = true); void listUrls(const QList &urls, const QString &filter = QString(), bool only_exe = false, bool no_hidden = false); void addMatches(const QStringList &); QString finished(); void init(); void setListedUrl(ComplType compl_type, const QString &dir = QString(), const QString &filter = QString(), bool no_hidden = false); bool isListedUrl(ComplType compl_type, const QString &dir = QString(), const QString &filter = QString(), bool no_hidden = false); KUrlCompletion *q; QList list_urls; bool onlyLocalProto; // urlCompletion() in Auto/Popup mode? bool url_auto_completion; // Append '/' to directories in Popup mode? // Doing that stat's all files and is slower bool popup_append_slash; // Keep track of currently listed files to avoid reading them again bool last_no_hidden; QString last_path_listed; QString last_file_listed; QString last_prepend; ComplType last_compl_type; QUrl cwd; // "current directory" = base dir for completion KUrlCompletion::Mode mode; // ExeCompletion, FileCompletion, DirCompletion bool replace_env; bool replace_home; bool complete_url; // if true completing a URL (i.e. 'prepend' is a URL), otherwise a path KIO::ListJob *list_job; // kio job to list directories QString prepend; // text to prepend to listed items QString compl_text; // text to pass on to KCompletion // Filters for files read with kio bool list_urls_only_exe; // true = only list executables bool list_urls_no_hidden; QString list_urls_filter; // filter for listed files CompletionThread *userListThread; CompletionThread *dirListThread; QStringList mimeTypeFilters; }; class CompletionThread : public QThread { Q_OBJECT protected: CompletionThread(KUrlCompletionPrivate *receiver) : QThread(), m_prepend(receiver->prepend), m_complete_url(receiver->complete_url), m_terminationRequested(false) {} public: void requestTermination() { if (!isFinished()) { qCDebug(KIO_WIDGETS) << "stopping thread" << this; } m_terminationRequested.store(true); wait(); } QStringList matches() const { QMutexLocker locker(&m_mutex); return m_matches; } Q_SIGNALS: void completionThreadDone(QThread *thread, const QStringList &matches); protected: void addMatch(const QString &match) { QMutexLocker locker(&m_mutex); m_matches.append(match); } bool terminationRequested() const { return m_terminationRequested.load(); } void done() { if (!terminationRequested()) { qCDebug(KIO_WIDGETS) << "done, emitting signal with" << m_matches.count() << "matches"; emit completionThreadDone(this, m_matches); } } const QString m_prepend; const bool m_complete_url; // if true completing a URL (i.e. 'm_prepend' is a URL), otherwise a path private: mutable QMutex m_mutex; // protects m_matches QStringList m_matches; // written by secondary thread, read by the matches() method QAtomicInt m_terminationRequested; // used as a bool }; /** * A simple thread that fetches a list of tilde-completions and returns this * to the caller via the completionThreadDone signal. */ class UserListThread : public CompletionThread { Q_OBJECT public: UserListThread(KUrlCompletionPrivate *receiver) : CompletionThread(receiver) {} protected: void run() Q_DECL_OVERRIDE { static const QChar tilde = '~'; // we don't need to handle prepend here, right? ~user is always at pos 0 assert(m_prepend.isEmpty()); #pragma message("TODO: add KUser::allUserNames() with a std::function shouldTerminate parameter") #ifndef Q_OS_WIN struct passwd *pw; ::setpwent(); while ((pw = ::getpwent()) && !terminationRequested()) { addMatch(tilde + QString::fromLocal8Bit(pw->pw_name)); } ::endpwent(); #else //currently terminationRequested is ignored on Windows QStringList allUsers = KUser::allUserNames(); Q_FOREACH(const QString& s, allUsers) { addMatch(tilde + s); } #endif addMatch(QString(tilde)); done(); } }; class DirectoryListThread : public CompletionThread { Q_OBJECT public: DirectoryListThread(KUrlCompletionPrivate *receiver, const QStringList &dirList, const QString &filter, const QStringList &mimeTypeFilters, bool onlyExe, bool onlyDir, bool noHidden, bool appendSlashToDir) : CompletionThread(receiver), m_dirList(dirList), m_filter(filter), m_mimeTypeFilters(mimeTypeFilters), m_onlyExe(onlyExe), m_onlyDir(onlyDir), m_noHidden(noHidden), m_appendSlashToDir(appendSlashToDir) {} void run() Q_DECL_OVERRIDE; private: QStringList m_dirList; QString m_filter; QStringList m_mimeTypeFilters; bool m_onlyExe; bool m_onlyDir; bool m_noHidden; bool m_appendSlashToDir; }; void DirectoryListThread::run() { //qDebug() << "Entered DirectoryListThread::run(), m_filter=" << m_filter << ", m_onlyExe=" << m_onlyExe << ", m_onlyDir=" << m_onlyDir << ", m_appendSlashToDir=" << m_appendSlashToDir << ", m_dirList.size()=" << m_dirList.size(); QDir::Filters iterator_filter = (m_noHidden ? QDir::Filter(0) : QDir::Hidden) | QDir::Readable | QDir::NoDotAndDotDot; if (m_onlyExe) { iterator_filter |= (QDir::Dirs | QDir::Files | QDir::Executable); } else if (m_onlyDir) { iterator_filter |= QDir::Dirs; } else { iterator_filter |= (QDir::Dirs | QDir::Files); } QMimeDatabase mimeTypes; const QStringList::const_iterator end = m_dirList.constEnd(); for (QStringList::const_iterator it = m_dirList.constBegin(); it != end && !terminationRequested(); ++it) { //qDebug() << "Scanning directory" << *it; QDirIterator current_dir_iterator(*it, iterator_filter); while (current_dir_iterator.hasNext() && !terminationRequested()) { current_dir_iterator.next(); QFileInfo file_info = current_dir_iterator.fileInfo(); const QString file_name = file_info.fileName(); //qDebug() << "Found" << file_name; if (!m_filter.isEmpty() && !file_name.startsWith(m_filter)) { continue; } if (!m_mimeTypeFilters.isEmpty() && !file_info.isDir()) { auto mimeType = mimeTypes.mimeTypeForFile(file_info); if (!m_mimeTypeFilters.contains(mimeType.name())) { continue; } } QString toAppend = file_name; // Add '/' to directories if (m_appendSlashToDir && file_info.isDir()) { toAppend.append(QLatin1Char('/')); } if (m_complete_url) { QUrl info(m_prepend); info = addPathToUrl(info, toAppend); addMatch(info.toDisplayString()); } else { addMatch(m_prepend + toAppend); } } } done(); } KUrlCompletionPrivate::~KUrlCompletionPrivate() { } /////////////////////////////////////////////////////// /////////////////////////////////////////////////////// // MyURL - wrapper for QUrl with some different functionality // class KUrlCompletionPrivate::MyURL { public: MyURL(const QString &url, const QUrl &cwd); MyURL(const MyURL &url); ~MyURL(); QUrl kurl() const { return m_kurl; } bool isLocalFile() const { return m_kurl.isLocalFile(); } QString scheme() const { return m_kurl.scheme(); } // The directory with a trailing '/' QString dir() const { return m_kurl.adjusted(QUrl::RemoveFilename).path(); } QString file() const { return m_kurl.fileName(); } // The initial, unparsed, url, as a string. QString url() const { return m_url; } // Is the initial string a URL, or just a path (whether absolute or relative) bool isURL() const { return m_isURL; } void filter(bool replace_user_dir, bool replace_env); private: void init(const QString &url, const QUrl &cwd); QUrl m_kurl; QString m_url; bool m_isURL; }; KUrlCompletionPrivate::MyURL::MyURL(const QString &_url, const QUrl &cwd) { init(_url, cwd); } KUrlCompletionPrivate::MyURL::MyURL(const MyURL &_url) : m_kurl(_url.m_kurl) { m_url = _url.m_url; m_isURL = _url.m_isURL; } void KUrlCompletionPrivate::MyURL::init(const QString &_url, const QUrl &cwd) { // Save the original text m_url = _url; // Non-const copy QString url_copy = _url; // Special shortcuts for "man:" and "info:" if (url_copy.startsWith(QLatin1Char('#'))) { if (url_copy.length() > 1 && url_copy.at(1) == QLatin1Char('#')) { url_copy.replace(0, 2, QStringLiteral("info:")); } else { url_copy.replace(0, 1, QStringLiteral("man:")); } } // Look for a protocol in 'url' QRegExp protocol_regex = QRegExp(QStringLiteral("^(?![A-Za-z]:)[^/\\s\\\\]*:")); // Assume "file:" or whatever is given by 'cwd' if there is // no protocol. (QUrl does this only for absolute paths) if (protocol_regex.indexIn(url_copy) == 0) { m_kurl = QUrl(url_copy); m_isURL = true; } else { // relative path or ~ or $something m_isURL = false; if (!QDir::isRelativePath(url_copy) || url_copy.startsWith(QLatin1Char('~')) || url_copy.startsWith(QLatin1Char('$'))) { m_kurl = QUrl::fromLocalFile(url_copy); } else { // Relative path if (cwd.isEmpty()) { m_kurl = QUrl(url_copy); } else { m_kurl = cwd; m_kurl.setPath(concatPaths(m_kurl.path(), url_copy)); } } } } KUrlCompletionPrivate::MyURL::~MyURL() { } void KUrlCompletionPrivate::MyURL::filter(bool replace_user_dir, bool replace_env) { QString d = dir() + file(); if (replace_user_dir) { expandTilde(d); } if (replace_env) { expandEnv(d); } m_kurl.setPath(d); } /////////////////////////////////////////////////////// /////////////////////////////////////////////////////// // KUrlCompletion // KUrlCompletion::KUrlCompletion() : KCompletion(), d(new KUrlCompletionPrivate(this)) { d->init(); } KUrlCompletion::KUrlCompletion(Mode _mode) : KCompletion(), d(new KUrlCompletionPrivate(this)) { d->init(); setMode(_mode); } KUrlCompletion::~KUrlCompletion() { stop(); delete d; } void KUrlCompletionPrivate::init() { cwd = QUrl::fromLocalFile(QDir::homePath()); replace_home = true; replace_env = true; last_no_hidden = false; last_compl_type = CTNone; list_job = nullptr; mode = KUrlCompletion::FileCompletion; // Read settings KConfigGroup cg(KSharedConfig::openConfig(), "URLCompletion"); url_auto_completion = cg.readEntry("alwaysAutoComplete", true); popup_append_slash = cg.readEntry("popupAppendSlash", true); onlyLocalProto = cg.readEntry("LocalProtocolsOnly", false); q->setIgnoreCase(true); } void KUrlCompletion::setDir(const QUrl &dir) { d->cwd = dir; } QUrl KUrlCompletion::dir() const { return d->cwd; } KUrlCompletion::Mode KUrlCompletion::mode() const { return d->mode; } void KUrlCompletion::setMode(Mode _mode) { d->mode = _mode; } bool KUrlCompletion::replaceEnv() const { return d->replace_env; } void KUrlCompletion::setReplaceEnv(bool replace) { d->replace_env = replace; } bool KUrlCompletion::replaceHome() const { return d->replace_home; } void KUrlCompletion::setReplaceHome(bool replace) { d->replace_home = replace; } /* * makeCompletion() * * Entry point for file name completion */ QString KUrlCompletion::makeCompletion(const QString &text) { qCDebug(KIO_WIDGETS) << text << "d->cwd=" << d->cwd; KUrlCompletionPrivate::MyURL url(text, d->cwd); d->compl_text = text; // Set d->prepend to the original URL, with the filename [and ref/query] stripped. // This is what gets prepended to the directory-listing matches. if (url.isURL()) { QUrl directoryUrl(url.kurl()); directoryUrl.setQuery(QString()); directoryUrl.setFragment(QString()); directoryUrl.setPath(url.dir()); d->prepend = directoryUrl.toString(); } else { d->prepend = text.left(text.length() - url.file().length()); } d->complete_url = url.isURL(); QString aMatch; // Environment variables // if (d->replace_env && d->envCompletion(url, &aMatch)) { return aMatch; } // User directories // if (d->replace_home && d->userCompletion(url, &aMatch)) { return aMatch; } // Replace user directories and variables url.filter(d->replace_home, d->replace_env); //qDebug() << "Filtered: proto=" << url.scheme() // << ", dir=" << url.dir() // << ", file=" << url.file() // << ", kurl url=" << *url.kurl(); if (d->mode == ExeCompletion) { // Executables // if (d->exeCompletion(url, &aMatch)) { return aMatch; } // KRun can run "man:" and "info:" etc. so why not treat them // as executables... if (d->urlCompletion(url, &aMatch)) { return aMatch; } } else { // Local files, directories // if (d->fileCompletion(url, &aMatch)) { return aMatch; } // All other... // if (d->urlCompletion(url, &aMatch)) { return aMatch; } } d->setListedUrl(CTNone); stop(); return QString(); } /* * finished * * Go on and call KCompletion. * Called when all matches have been added */ QString KUrlCompletionPrivate::finished() { if (last_compl_type == CTInfo) { return q->KCompletion::makeCompletion(compl_text.toLower()); } else { return q->KCompletion::makeCompletion(compl_text); } } /* * isRunning * * Return true if either a KIO job or a thread is running */ bool KUrlCompletion::isRunning() const { return d->list_job || (d->dirListThread && !d->dirListThread->isFinished()) || (d->userListThread && !d->userListThread->isFinished()); } /* * stop * * Stop and delete a running KIO job or the DirLister */ void KUrlCompletion::stop() { if (d->list_job) { d->list_job->kill(); d->list_job = nullptr; } if (d->dirListThread) { d->dirListThread->requestTermination(); delete d->dirListThread; d->dirListThread = nullptr; } if (d->userListThread) { d->userListThread->requestTermination(); delete d->userListThread; d->userListThread = nullptr; } } /* * Keep track of the last listed directory */ void KUrlCompletionPrivate::setListedUrl(ComplType complType, const QString &directory, const QString &filter, bool no_hidden) { last_compl_type = complType; last_path_listed = directory; last_file_listed = filter; last_no_hidden = no_hidden; last_prepend = prepend; } bool KUrlCompletionPrivate::isListedUrl(ComplType complType, const QString &directory, const QString &filter, bool no_hidden) { return last_compl_type == complType && (last_path_listed == directory || (directory.isEmpty() && last_path_listed.isEmpty())) && (filter.startsWith(last_file_listed) || (filter.isEmpty() && last_file_listed.isEmpty())) && last_no_hidden == no_hidden && last_prepend == prepend; // e.g. relative path vs absolute } /* * isAutoCompletion * * Returns true if completion mode is Auto or Popup */ bool KUrlCompletionPrivate::isAutoCompletion() { return q->completionMode() == KCompletion::CompletionAuto || q->completionMode() == KCompletion::CompletionPopup || q->completionMode() == KCompletion::CompletionMan || q->completionMode() == KCompletion::CompletionPopupAuto; } ////////////////////////////////////////////////// ////////////////////////////////////////////////// // User directories // bool KUrlCompletionPrivate::userCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch) { if (url.scheme() != QLatin1String("file") || !url.dir().isEmpty() || !url.file().startsWith(QLatin1Char('~')) || !prepend.isEmpty()) { return false; } if (!isListedUrl(CTUser)) { q->stop(); q->clear(); setListedUrl(CTUser); Q_ASSERT(!userListThread); // caller called stop() userListThread = new UserListThread(this); QObject::connect(userListThread, &CompletionThread::completionThreadDone, q, [this](QThread *thread, const QStringList &matches){ slotCompletionThreadDone(thread, matches); }); userListThread->start(); // If the thread finishes quickly make sure that the results // are added to the first matching case. userListThread->wait(initialWaitDuration()); const QStringList l = userListThread->matches(); addMatches(l); } *pMatch = finished(); return true; } ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// // Environment variables // bool KUrlCompletionPrivate::envCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch) { if (url.file().isEmpty() || url.file().at(0) != QLatin1Char('$')) { return false; } if (!isListedUrl(CTEnv)) { q->stop(); q->clear(); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); QStringList keys = env.keys(); QString dollar = QStringLiteral("$"); QStringList l; Q_FOREACH(const QString &key, keys) { l.append(prepend + dollar + key); } addMatches(l); } setListedUrl(CTEnv); *pMatch = finished(); return true; } ////////////////////////////////////////////////// ////////////////////////////////////////////////// // Executables // bool KUrlCompletionPrivate::exeCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch) { if (!url.isLocalFile()) { return false; } QString directory = unescape(url.dir()); // remove escapes // Find directories to search for completions, either // // 1. complete path given in url // 2. current directory (d->cwd) // 3. $PATH // 4. no directory at all QStringList dirList; if (!url.file().isEmpty()) { // $PATH // ### maybe Qt should have a QDir::pathSeparator() to avoid ifdefs.. #ifdef Q_OS_WIN #define KPATH_SEPARATOR ';' #else #define KPATH_SEPARATOR ':' #endif dirList = QString::fromLocal8Bit(qgetenv("PATH")).split( KPATH_SEPARATOR, QString::SkipEmptyParts); QStringList::Iterator it = dirList.begin(); for (; it != dirList.end(); ++it) { it->append(QLatin1Char('/')); } } else if (!QDir::isRelativePath(directory)) { // complete path in url dirList.append(directory); } else if (!directory.isEmpty() && !cwd.isEmpty()) { // current directory dirList.append(cwd.toLocalFile() + QLatin1Char('/') + directory); } // No hidden files unless the user types "." bool no_hidden_files = url.file().isEmpty() || url.file().at(0) != QLatin1Char('.'); // List files if needed // if (!isListedUrl(CTExe, directory, url.file(), no_hidden_files)) { q->stop(); q->clear(); setListedUrl(CTExe, directory, url.file(), no_hidden_files); *pMatch = listDirectories(dirList, url.file(), true, false, no_hidden_files); } else { *pMatch = finished(); } return true; } ////////////////////////////////////////////////// ////////////////////////////////////////////////// // Local files // bool KUrlCompletionPrivate::fileCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch) { if (!url.isLocalFile()) { return false; } QString directory = unescape(url.dir()); if (url.url() == QLatin1String("..")) { *pMatch = QStringLiteral(".."); return true; } //qDebug() << "fileCompletion" << url << "dir=" << dir; // Find directories to search for completions, either // // 1. complete path given in url // 2. current directory (d->cwd) // 3. no directory at all QStringList dirList; if (!QDir::isRelativePath(directory)) { // complete path in url dirList.append(directory); } else if (!cwd.isEmpty()) { // current directory QString dirToAdd = cwd.toLocalFile(); if (!directory.isEmpty()) { if (!dirToAdd.endsWith('/')) { dirToAdd.append(QLatin1Char('/')); } dirToAdd.append(directory); } dirList.append(dirToAdd); } // No hidden files unless the user types "." bool no_hidden_files = !url.file().startsWith(QLatin1Char('.')); // List files if needed // if (!isListedUrl(CTFile, directory, QString(), no_hidden_files)) { q->stop(); q->clear(); setListedUrl(CTFile, directory, QString(), no_hidden_files); // Append '/' to directories in Popup mode? bool append_slash = (popup_append_slash && (q->completionMode() == KCompletion::CompletionPopup || q->completionMode() == KCompletion::CompletionPopupAuto)); bool only_dir = (mode == KUrlCompletion::DirCompletion); *pMatch = listDirectories(dirList, QString(), false, only_dir, no_hidden_files, append_slash); } else { *pMatch = finished(); } return true; } ////////////////////////////////////////////////// ////////////////////////////////////////////////// // URLs not handled elsewhere... // static bool isLocalProtocol(const QString &protocol) { return (KProtocolInfo::protocolClass(protocol) == QLatin1String(":local")); } bool KUrlCompletionPrivate::urlCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch) { //qDebug() << *url.kurl(); if (onlyLocalProto && isLocalProtocol(url.scheme())) { return false; } // Use d->cwd as base url in case url is not absolute QUrl url_dir = url.kurl(); if (url_dir.isRelative() && !cwd.isEmpty()) { // Create an URL with the directory to be listed url_dir = cwd.resolved(url_dir); } // url is malformed if (!url_dir.isValid() || url.scheme().isEmpty()) { return false; } // non local urls if (!isLocalProtocol(url.scheme())) { // url does not specify host if (url_dir.host().isEmpty()) { return false; } // url does not specify a valid directory if (url_dir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path().isEmpty()) { return false; } // automatic completion is disabled if (isAutoCompletion() && !url_auto_completion) { return false; } } // url handler doesn't support listing if (!KProtocolManager::supportsListing(url_dir)) { return false; } // Remove escapes const QString directory = unescape(url_dir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path()); url_dir.setPath(directory); // List files if needed // if (!isListedUrl(CTUrl, directory, url.file())) { q->stop(); q->clear(); setListedUrl(CTUrl, directory, QString()); QList url_list; url_list.append(url_dir); listUrls(url_list, QString(), false); pMatch->clear(); } else if (!q->isRunning()) { *pMatch = finished(); } else { pMatch->clear(); } return true; } ////////////////////////////////////////////////// ////////////////////////////////////////////////// // Directory and URL listing // /* * addMatches * * Called to add matches to KCompletion */ void KUrlCompletionPrivate::addMatches(const QStringList &matchList) { q->insertItems(matchList); } /* * listDirectories * * List files starting with 'filter' in the given directories, * either using DirLister or listURLs() * * In either case, addMatches() is called with the listed * files, and eventually finished() when the listing is done * * Returns the match if available, or QString() if * DirLister timed out or using kio */ QString KUrlCompletionPrivate::listDirectories( const QStringList &dirList, const QString &filter, bool only_exe, bool only_dir, bool no_hidden, bool append_slash_to_dir) { assert(!q->isRunning()); if (qEnvironmentVariableIsEmpty("KURLCOMPLETION_LOCAL_KIO")) { qCDebug(KIO_WIDGETS) << "Listing directories:" << dirList << "with filter=" << filter << "using thread"; // Don't use KIO QStringList dirs; QStringList::ConstIterator end = dirList.constEnd(); for (QStringList::ConstIterator it = dirList.constBegin(); it != end; ++it) { QUrl url = QUrl::fromLocalFile(*it); if (KUrlAuthorized::authorizeUrlAction(QStringLiteral("list"), QUrl(), url)) { dirs.append(*it); } } Q_ASSERT(!dirListThread); // caller called stop() dirListThread = new DirectoryListThread(this, dirs, filter, mimeTypeFilters, only_exe, only_dir, no_hidden, append_slash_to_dir); QObject::connect(dirListThread, &CompletionThread::completionThreadDone, q, [this](QThread *thread, const QStringList &matches){ slotCompletionThreadDone(thread, matches); }); dirListThread->start(); dirListThread->wait(initialWaitDuration()); qCDebug(KIO_WIDGETS) << "Adding initial matches:" << dirListThread->matches(); addMatches(dirListThread->matches()); return finished(); } // Use KIO //qDebug() << "Listing (listDirectories):" << dirList << "with KIO"; QList url_list; QStringList::ConstIterator it = dirList.constBegin(); QStringList::ConstIterator end = dirList.constEnd(); for (; it != end; ++it) { url_list.append(QUrl(*it)); } listUrls(url_list, filter, only_exe, no_hidden); // Will call addMatches() and finished() return QString(); } /* * listURLs * * Use KIO to list the given urls * * addMatches() is called with the listed files * finished() is called when the listing is done */ void KUrlCompletionPrivate::listUrls( const QList &urls, const QString &filter, bool only_exe, bool no_hidden) { assert(list_urls.isEmpty()); assert(list_job == nullptr); list_urls = urls; list_urls_filter = filter; list_urls_only_exe = only_exe; list_urls_no_hidden = no_hidden; //qDebug() << "Listing URLs:" << *urls[0] << ",..."; // Start it off by calling _k_slotIOFinished // // This will start a new list job as long as there // are urls in d->list_urls // _k_slotIOFinished(nullptr); } /* * _k_slotEntries * * Receive files listed by KIO and call addMatches() */ void KUrlCompletionPrivate::_k_slotEntries(KIO::Job *, const KIO::UDSEntryList &entries) { QStringList matchList; KIO::UDSEntryList::ConstIterator it = entries.constBegin(); const KIO::UDSEntryList::ConstIterator end = entries.constEnd(); QString filter = list_urls_filter; int filter_len = filter.length(); // Iterate over all files // for (; it != end; ++it) { const KIO::UDSEntry &entry = *it; const QString url = entry.stringValue(KIO::UDSEntry::UDS_URL); QString entry_name; if (!url.isEmpty()) { //qDebug() << "url:" << url; entry_name = QUrl(url).fileName(); } else { entry_name = entry.stringValue(KIO::UDSEntry::UDS_NAME); } //qDebug() << "name:" << name; if ((!entry_name.isEmpty() && entry_name.at(0) == QLatin1Char('.')) && (list_urls_no_hidden || entry_name.length() == 1 || (entry_name.length() == 2 && entry_name.at(1) == QLatin1Char('.')))) { continue; } const bool isDir = entry.isDir(); if (mode == KUrlCompletion::DirCompletion && !isDir) { continue; } if (filter_len != 0 && entry_name.left(filter_len) != filter) { continue; } if (!mimeTypeFilters.isEmpty() && !isDir && !mimeTypeFilters.contains(entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE))) { continue; } QString toAppend = entry_name; if (isDir) { toAppend.append(QLatin1Char('/')); } if (!list_urls_only_exe || (entry.numberValue(KIO::UDSEntry::UDS_ACCESS) & MODE_EXE) // true if executable ) { if (complete_url) { QUrl url(prepend); url = addPathToUrl(url, toAppend); matchList.append(url.toDisplayString()); } else { matchList.append(prepend + toAppend); } } } addMatches(matchList); } /* * _k_slotIOFinished * * Called when a KIO job is finished. * * Start a new list job if there are still urls in * list_urls, otherwise call finished() */ void KUrlCompletionPrivate::_k_slotIOFinished(KJob *job) { assert(job == list_job); Q_UNUSED(job) if (list_urls.isEmpty()) { list_job = nullptr; finished(); // will call KCompletion::makeCompletion() } else { QUrl kurl(list_urls.takeFirst()); // list_urls.removeAll( kurl ); //qDebug() << "Start KIO::listDir" << kurl; list_job = KIO::listDir(kurl, KIO::HideProgressInfo); list_job->addMetaData(QStringLiteral("no-auth-prompt"), QStringLiteral("true")); assert(list_job); q->connect(list_job, SIGNAL(result(KJob*)), SLOT(_k_slotIOFinished(KJob*))); q->connect(list_job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), SLOT(_k_slotEntries(KIO::Job*,KIO::UDSEntryList))); } } /////////////////////////////////////////////////// /////////////////////////////////////////////////// /* * postProcessMatch, postProcessMatches * * Called by KCompletion before emitting match() and matches() * * Append '/' to directories for file completion. This is * done here to avoid stat()'ing a lot of files */ void KUrlCompletion::postProcessMatch(QString *pMatch) const { //qDebug() << *pMatch; if (!pMatch->isEmpty() && pMatch->startsWith(QLatin1String("file:"))) { // Add '/' to directories in file completion mode // unless it has already been done if (d->last_compl_type == CTFile && pMatch->at(pMatch->length() - 1) != QLatin1Char('/')) { QString copy = QUrl(*pMatch).toLocalFile(); expandTilde(copy); expandEnv(copy); if (QDir::isRelativePath(copy)) { copy.prepend(d->cwd.toLocalFile() + QLatin1Char('/')); } //qDebug() << "stat'ing" << copy; QByteArray file = QFile::encodeName(copy); QT_STATBUF sbuff; if (QT_STAT(file.constData(), &sbuff) == 0) { if ((sbuff.st_mode & QT_STAT_MASK) == QT_STAT_DIR) { pMatch->append(QLatin1Char('/')); } } else { //qDebug() << "Could not stat file" << copy; } } } } void KUrlCompletion::postProcessMatches(QStringList * /*matches*/) const { // Maybe '/' should be added to directories here as in // postProcessMatch() but it would slow things down // when there are a lot of matches... } void KUrlCompletion::postProcessMatches(KCompletionMatches * /*matches*/) const { // Maybe '/' should be added to directories here as in // postProcessMatch() but it would slow things down // when there are a lot of matches... } // no longer used, KF6 TODO: remove this method void KUrlCompletion::customEvent(QEvent *e) { KCompletion::customEvent(e); } void KUrlCompletionPrivate::slotCompletionThreadDone(QThread *thread, const QStringList &matches) { if (thread != userListThread && thread != dirListThread) { qCDebug(KIO_WIDGETS) << "got" << matches.count() << "outdated matches"; return; } qCDebug(KIO_WIDGETS) << "got" << matches.count() << "matches at end of thread"; q->setItems(matches); if (userListThread == thread) { thread->wait(); delete thread; userListThread = nullptr; } if (dirListThread == thread) { thread->wait(); delete thread; dirListThread = nullptr; } finished(); // will call KCompletion::makeCompletion() } // static QString KUrlCompletion::replacedPath(const QString &text, bool replaceHome, bool replaceEnv) { if (text.isEmpty()) { return text; } KUrlCompletionPrivate::MyURL url(text, QUrl()); // no need to replace something of our current cwd if (!url.kurl().isLocalFile()) { return text; } url.filter(replaceHome, replaceEnv); return url.dir() + url.file(); } QString KUrlCompletion::replacedPath(const QString &text) const { return replacedPath(text, d->replace_home, d->replace_env); } void KUrlCompletion::setMimeTypeFilters(const QStringList &mimeTypeFilters) { d->mimeTypeFilters = mimeTypeFilters; } QStringList KUrlCompletion::mimeTypeFilters() const { return d->mimeTypeFilters; } ///////////////////////////////////////////////////////// ///////////////////////////////////////////////////////// // Static functions /* * expandEnv * * Expand environment variables in text. Escaped '$' are ignored. * Return true if expansion was made. */ static bool expandEnv(QString &text) { // Find all environment variables beginning with '$' // int pos = 0; bool expanded = false; while ((pos = text.indexOf(QLatin1Char('$'), pos)) != -1) { // Skip escaped '$' // if (pos > 0 && text.at(pos - 1) == QLatin1Char('\\')) { pos++; } // Variable found => expand // else { // Find the end of the variable = next '/' or ' ' // int pos2 = text.indexOf(QLatin1Char(' '), pos + 1); int pos_tmp = text.indexOf(QLatin1Char('/'), pos + 1); if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2)) { pos2 = pos_tmp; } if (pos2 == -1) { pos2 = text.length(); } // Replace if the variable is terminated by '/' or ' ' // and defined // if (pos2 >= 0) { int len = pos2 - pos; QString key = text.mid(pos + 1, len - 1); QString value = QString::fromLocal8Bit(qgetenv(key.toLocal8Bit())); if (!value.isEmpty()) { expanded = true; text.replace(pos, len, value); pos = pos + value.length(); } else { pos = pos2; } } } } return expanded; } /* * expandTilde * * Replace "~user" with the users home directory * Return true if expansion was made. */ static bool expandTilde(QString &text) { if (text.isEmpty() || (text.at(0) != QLatin1Char('~'))) { return false; } bool expanded = false; // Find the end of the user name = next '/' or ' ' // int pos2 = text.indexOf(QLatin1Char(' '), 1); int pos_tmp = text.indexOf(QLatin1Char('/'), 1); if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2)) { pos2 = pos_tmp; } if (pos2 == -1) { pos2 = text.length(); } // Replace ~user if the user name is terminated by '/' or ' ' // if (pos2 >= 0) { QString userName = text.mid(1, pos2 - 1); QString dir; // A single ~ is replaced with $HOME // if (userName.isEmpty()) { dir = QDir::homePath(); } // ~user is replaced with the dir from passwd // else { KUser user(userName); dir = user.homeDir(); } if (!dir.isEmpty()) { expanded = true; text.replace(0, pos2, dir); } } return expanded; } /* * unescape * * Remove escapes and return the result in a new string * */ static QString unescape(const QString &text) { QString result; for (int pos = 0; pos < text.length(); pos++) if (text.at(pos) != QLatin1Char('\\')) { result.insert(result.length(), text.at(pos)); } return result; } #include "moc_kurlcompletion.cpp" #include "kurlcompletion.moc" diff --git a/src/widgets/kurlcompletion.h b/src/widgets/kurlcompletion.h index 08e60540..aa1968b1 100644 --- a/src/widgets/kurlcompletion.h +++ b/src/widgets/kurlcompletion.h @@ -1,201 +1,201 @@ /* This file is part of the KDE libraries Copyright (C) 2000 David Smith This class was inspired by a previous KUrlCompletion by Henner Zeller 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. */ #ifndef KURLCOMPLETION_H #define KURLCOMPLETION_H #include "kiowidgets_export.h" #include #include -#include +#include namespace KIO { class Job; } class QStringList; class KUrlCompletionPrivate; /** * @class KUrlCompletion kurlcompletion.h * * This class does completion of URLs including user directories (~user) * and environment variables. Remote URLs are passed to KIO. * * @short Completion of a single URL * @author David Smith */ class KIOWIDGETS_EXPORT KUrlCompletion : public KCompletion { Q_OBJECT public: /** * Determines how completion is done. * @li ExeCompletion - executables in $PATH or with full path. * @li FileCompletion - all files with full path or in dir(), URLs * are listed using KIO. * @li DirCompletion - Same as FileCompletion but only returns directories. */ enum Mode { ExeCompletion = 1, FileCompletion, DirCompletion }; /** * Constructs a KUrlCompletion object in FileCompletion mode. */ KUrlCompletion(); /** * This overloaded constructor allows you to set the Mode to ExeCompletion * or FileCompletion without using setMode. Default is FileCompletion. */ KUrlCompletion(Mode); /** * Destructs the KUrlCompletion object. */ virtual ~KUrlCompletion(); /** * Finds completions to the given text. * * Remote URLs are listed with KIO. For performance reasons, local files * are listed with KIO only if KURLCOMPLETION_LOCAL_KIO is set. * The completion is done asyncronously if KIO is used. * * Returns the first match for user, environment, and local dir completion * and QString() for asynchronous completion (KIO or threaded). * * @param text the text to complete * @return the first match, or QString() if not found */ QString makeCompletion(const QString &text) Q_DECL_OVERRIDE; /** * Sets the current directory (used as base for completion). * Default = $HOME. * @param dir the current directory, as a URL (use QUrl::fromLocalFile for local paths) */ virtual void setDir(const QUrl &dir); /** * Returns the current directory, as it was given in setDir * @return the current directory, as a URL (use QUrl::toLocalFile for local paths) */ virtual QUrl dir() const; /** * Check whether asynchronous completion is in progress. * @return true if asynchronous completion is in progress */ virtual bool isRunning() const; /** * Stops asynchronous completion. */ virtual void stop(); /** * Returns the completion mode: exe or file completion (default FileCompletion). * @return the completion mode */ virtual Mode mode() const; /** * Changes the completion mode: exe or file completion * @param mode the new completion mode */ virtual void setMode(Mode mode); /** * Checks whether environment variables are completed and * whether they are replaced internally while finding completions. * Default is enabled. * @return true if environment vvariables will be replaced */ virtual bool replaceEnv() const; /** * Enables/disables completion and replacement (internally) of * environment variables in URLs. Default is enabled. * @param replace true to replace environment variables */ virtual void setReplaceEnv(bool replace); /** * Returns whether ~username is completed and whether ~username * is replaced internally with the user's home directory while * finding completions. Default is enabled. * @return true to replace tilde with the home directory */ virtual bool replaceHome() const; /** * Enables/disables completion of ~username and replacement * (internally) of ~username with the user's home directory. * Default is enabled. * @param replace true to replace tilde with the home directory */ virtual void setReplaceHome(bool replace); /** * Replaces username and/or environment variables, depending on the * current settings and returns the filtered url. Only works with * local files, i.e. returns back the original string for non-local * urls. * @param text the text to process * @return the path or URL resulting from this operation. If you * want to convert it to a QUrl, use QUrl::fromUserInput. */ QString replacedPath(const QString &text) const; /** * @internal I'll let ossi add a real one to KShell :) */ static QString replacedPath(const QString &text, bool replaceHome, bool replaceEnv = true); /** * Sets the mimetype filters for the file dialog. * @see QFileDialog::setMimeTypeFilters() * @since 5.38 */ void setMimeTypeFilters(const QStringList &mimeTypes); /** * Returns the mimetype filters for the file dialog. * @see QFileDialog::mimeTypeFilters() * @since 5.38 */ QStringList mimeTypeFilters() const; protected: // Called by KCompletion, adds '/' to directories void postProcessMatch(QString *match) const Q_DECL_OVERRIDE; void postProcessMatches(QStringList *matches) const Q_DECL_OVERRIDE; void postProcessMatches(KCompletionMatches *matches) const Q_DECL_OVERRIDE; void customEvent(QEvent *e) Q_DECL_OVERRIDE; // KF6 TODO: remove private: KUrlCompletionPrivate *const d; Q_PRIVATE_SLOT(d, void _k_slotEntries(KIO::Job *, const KIO::UDSEntryList &)) Q_PRIVATE_SLOT(d, void _k_slotIOFinished(KJob *)) }; #endif // KURLCOMPLETION_H diff --git a/src/widgets/paste.h b/src/widgets/paste.h index a3d9f132..2f2cef69 100644 --- a/src/widgets/paste.h +++ b/src/widgets/paste.h @@ -1,112 +1,112 @@ /* This file is part of the KDE libraries Copyright (C) 2000-2005 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. */ #ifndef KIO_PASTE_H #define KIO_PASTE_H #include "kiowidgets_export.h" -#include +#include class QWidget; class QUrl; class QMimeData; class KFileItem; namespace KIO { class Job; class CopyJob; /** * Pastes the content of the clipboard to the given destination URL. * URLs are treated separately (performing a file copy) * from other data (which is saved into a file after asking the user * to choose a filename and the preferred data format) * * @param destURL the URL to receive the data * @param widget parent widget to use for dialogs * @param move true to move the data, false to copy -- now ignored and handled automatically * @return the job that handles the operation * @deprecated since 5.4, use KIO::paste() from (which takes care of undo/redo too) */ KIOWIDGETS_DEPRECATED_EXPORT Job *pasteClipboard(const QUrl &destURL, QWidget *widget, bool move = false); /** * Save the given mime @p data to the given destination URL * after offering the user to choose a data format. * This is the method used when handling drops (of anything else than URLs) * onto dolphin and konqueror. * * @param data the QMimeData, usually from a QDropEvent * @param destUrl the URL of the directory where the data will be pasted. * The filename to use in that directory is prompted by this method. * @param dialogText the text to show in the dialog * @param widget parent widget to use for dialogs * * @see pasteClipboard() * @deprecated since 5.4, use KIO::paste() from (which takes care of undo/redo too) */ KIOWIDGETS_DEPRECATED_EXPORT Job *pasteMimeData(const QMimeData *data, const QUrl &destUrl, const QString &dialogText, QWidget *widget); /** * Returns true if pasteMimeData will find any interesting format in @p data. * You can use this method to enable/disable the paste action appropriately. * @since 5.0 (was called canPasteMimeSource before) */ KIOWIDGETS_EXPORT bool canPasteMimeData(const QMimeData *data); /** * Returns the text to use for the Paste action, when the application supports * pasting files, urls, and clipboard data, using pasteClipboard(). * @return a string suitable for QAction::setText, or an empty string if pasting * isn't possible right now. * @deprecated since 5.4, use pasteActionText(const QMimeData *, bool*, const KFileItem &) */ KIOWIDGETS_DEPRECATED_EXPORT QString pasteActionText(); /** * Returns the text to use for the Paste action, when the application supports * pasting files, urls, and clipboard data, using pasteClipboard(). * @param mimeData the mime data, usually QApplication::clipboard()->mimeData(). * @param enable output parameter, to be passed to QAction::setEnabled. * The pointer must be non-null, and in return the function will always set its value. * @param destItem item representing the directory into which the clipboard data * or items would be pasted. Used to find out about permissions in that directory. * @return a string suitable for QAction::setText * @since 5.4 */ KIOWIDGETS_EXPORT QString pasteActionText(const QMimeData *mimeData, bool *enable, const KFileItem &destItem); /** * Add the information whether the files were cut, into the mimedata. * @param mimeData pointer to the mimeData object to be populated. Must not be null. * @param cut if true, the user selected "cut" (saved as application/x-kde-cutselection in the mimedata). * @since 5.2 */ KIOWIDGETS_EXPORT void setClipboardDataCut(QMimeData* mimeData, bool cut); /** * Returns true if the URLs in @p mimeData were cut by the user. * This should be called when pasting, to choose between moving and copying. * @since 5.2 */ KIOWIDGETS_EXPORT bool isClipboardDataCut(const QMimeData *mimeData); } #endif diff --git a/src/widgets/previewjob.cpp b/src/widgets/previewjob.cpp index 89f4dd03..de7c216d 100644 --- a/src/widgets/previewjob.cpp +++ b/src/widgets/previewjob.cpp @@ -1,797 +1,797 @@ // -*- c++ -*- /* This file is part of the KDE libraries Copyright (C) 2000 David Faure 2000 Carsten Pfeiffer 2001 Malte Starostik 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 "previewjob.h" #ifdef Q_OS_UNIX #include #include #endif #include -#include -#include +#include +#include #include #include -#include -#include +#include +#include #include #include -#include +#include #include #include #include #include -#include +#include #include #include #include #include #include "job_p.h" namespace KIO { struct PreviewItem; } using namespace KIO; struct KIO::PreviewItem { KFileItem item; KService::Ptr plugin; }; class KIO::PreviewJobPrivate: public KIO::JobPrivate { public: PreviewJobPrivate(const KFileItemList &items, const QSize &size) : initialItems(items), width(size.width()), height(size.height()), cacheWidth(width), cacheHeight(height), bScale(true), bSave(true), ignoreMaximumSize(false), sequenceIndex(0), succeeded(false), maximumLocalSize(0), maximumRemoteSize(0), iconSize(0), iconAlpha(70), shmid(-1), shmaddr(nullptr) { // http://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html#DIRECTORY thumbRoot = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/thumbnails/"); } enum { STATE_STATORIG, // if the thumbnail exists STATE_GETORIG, // if we create it STATE_CREATETHUMB // thumbnail:/ slave } state; KFileItemList initialItems; QStringList enabledPlugins; // Some plugins support remote URLs, QHash m_remoteProtocolPlugins; // Our todo list :) // We remove the first item at every step, so use QLinkedList QLinkedList items; // The current item PreviewItem currentItem; // The modification time of that URL QDateTime tOrig; // Path to thumbnail cache for the current size QString thumbPath; // Original URL of current item in TMS format // (file:///path/to/file instead of file:/path/to/file) QString origName; // Thumbnail file name for current item QString thumbName; // Size of thumbnail int width; int height; // Unscaled size of thumbnail (128 or 256 if cache is enabled) int cacheWidth; int cacheHeight; // Whether the thumbnail should be scaled bool bScale; // Whether we should save the thumbnail bool bSave; bool ignoreMaximumSize; int sequenceIndex; bool succeeded; // If the file to create a thumb for was a temp file, this is its name QString tempName; KIO::filesize_t maximumLocalSize; KIO::filesize_t maximumRemoteSize; // the size for the icon overlay int iconSize; // the transparency of the blended mimetype icon int iconAlpha; // Shared memory segment Id. The segment is allocated to a size // of extent x extent x 4 (32 bit image) on first need. int shmid; // And the data area uchar *shmaddr; // Root of thumbnail cache QString thumbRoot; void getOrCreateThumbnail(); bool statResultThumbnail(); void createThumbnail(const QString &); void cleanupTempFile(); void determineNextFile(); void emitPreview(const QImage &thumb); void startPreview(); void slotThumbData(KIO::Job *, const QByteArray &); Q_DECLARE_PUBLIC(PreviewJob) }; #ifndef KIOWIDGETS_NO_DEPRECATED PreviewJob::PreviewJob(const KFileItemList &items, int width, int height, int iconSize, int iconAlpha, bool scale, bool save, const QStringList *enabledPlugins) : KIO::Job(*new PreviewJobPrivate(items, QSize(width, height ? height : width))) { Q_D(PreviewJob); d->enabledPlugins = enabledPlugins ? *enabledPlugins : availablePlugins(); d->iconSize = iconSize; d->iconAlpha = iconAlpha; d->bScale = scale; d->bSave = save && scale; // Return to event loop first, determineNextFile() might delete this; QTimer::singleShot(0, this, SLOT(startPreview())); } #endif PreviewJob::PreviewJob(const KFileItemList &items, const QSize &size, const QStringList *enabledPlugins) : KIO::Job(*new PreviewJobPrivate(items, size)) { Q_D(PreviewJob); if (enabledPlugins) { d->enabledPlugins = *enabledPlugins; } else { const KConfigGroup globalConfig(KSharedConfig::openConfig(), "PreviewSettings"); d->enabledPlugins = globalConfig.readEntry("Plugins", QStringList() << QStringLiteral("directorythumbnail") << QStringLiteral("imagethumbnail") << QStringLiteral("jpegthumbnail")); } // Return to event loop first, determineNextFile() might delete this; QTimer::singleShot(0, this, SLOT(startPreview())); } PreviewJob::~PreviewJob() { #ifdef Q_OS_UNIX Q_D(PreviewJob); if (d->shmaddr) { shmdt((char *)d->shmaddr); shmctl(d->shmid, IPC_RMID, nullptr); } #endif } void PreviewJob::setOverlayIconSize(int size) { Q_D(PreviewJob); d->iconSize = size; } int PreviewJob::overlayIconSize() const { Q_D(const PreviewJob); return d->iconSize; } void PreviewJob::setOverlayIconAlpha(int alpha) { Q_D(PreviewJob); d->iconAlpha = qBound(0, alpha, 255); } int PreviewJob::overlayIconAlpha() const { Q_D(const PreviewJob); return d->iconAlpha; } void PreviewJob::setScaleType(ScaleType type) { Q_D(PreviewJob); switch (type) { case Unscaled: d->bScale = false; d->bSave = false; break; case Scaled: d->bScale = true; d->bSave = false; break; case ScaledAndCached: d->bScale = true; d->bSave = true; break; default: break; } } PreviewJob::ScaleType PreviewJob::scaleType() const { Q_D(const PreviewJob); if (d->bScale) { return d->bSave ? ScaledAndCached : Scaled; } return Unscaled; } void PreviewJobPrivate::startPreview() { Q_Q(PreviewJob); // Load the list of plugins to determine which mimetypes are supported const KService::List plugins = KServiceTypeTrader::self()->query(QStringLiteral("ThumbCreator")); QMap mimeMap; QHash > protocolMap; for (KService::List::ConstIterator it = plugins.constBegin(); it != plugins.constEnd(); ++it) { QStringList protocols = (*it)->property(QStringLiteral("X-KDE-Protocols")).toStringList(); const QString p = (*it)->property(QStringLiteral("X-KDE-Protocol")).toString(); if (!p.isEmpty()) { protocols.append(p); } foreach (const QString &protocol, protocols) { // We cannot use mimeTypes() here, it doesn't support groups such as: text/* const QStringList mtypes = (*it)->serviceTypes(); // Add supported mimetype for this protocol QStringList &_ms = m_remoteProtocolPlugins[protocol]; foreach (const QString &_m, mtypes) { if (_m != QLatin1String("ThumbCreator")) { protocolMap[protocol].insert(_m, *it); if (!_ms.contains(_m)) { _ms.append(_m); } } } } if (enabledPlugins.contains((*it)->desktopEntryName())) { const QStringList mimeTypes = (*it)->serviceTypes(); for (QStringList::ConstIterator mt = mimeTypes.constBegin(); mt != mimeTypes.constEnd(); ++mt) { if (*mt != QLatin1String("ThumbCreator")) { mimeMap.insert(*mt, *it); } } } } // Look for images and store the items in our todo list :) bool bNeedCache = false; KFileItemList::const_iterator kit = initialItems.constBegin(); const KFileItemList::const_iterator kend = initialItems.constEnd(); for (; kit != kend; ++kit) { PreviewItem item; item.item = *kit; const QString mimeType = item.item.mimetype(); KService::Ptr plugin(nullptr); // look for protocol-specific thumbnail plugins first QHash >::const_iterator it = protocolMap.constFind(item.item.url().scheme()); if (it != protocolMap.constEnd()) { plugin = it.value().value(mimeType); } if (!plugin) { QMap::ConstIterator pluginIt = mimeMap.constFind(mimeType); if (pluginIt == mimeMap.constEnd()) { QString groupMimeType = mimeType; groupMimeType.replace(QRegExp(QStringLiteral("/.*")), QStringLiteral("/*")); pluginIt = mimeMap.constFind(groupMimeType); if (pluginIt == mimeMap.constEnd()) { QMimeDatabase db; // check mime type inheritance, resolve aliases const QMimeType mimeInfo = db.mimeTypeForName(mimeType); if (mimeInfo.isValid()) { const QStringList parentMimeTypes = mimeInfo.allAncestors(); Q_FOREACH (const QString &parentMimeType, parentMimeTypes) { pluginIt = mimeMap.constFind(parentMimeType); if (pluginIt != mimeMap.constEnd()) { break; } } } } } if (pluginIt != mimeMap.constEnd()) { plugin = *pluginIt; } } if (plugin) { item.plugin = plugin; items.append(item); if (!bNeedCache && bSave && plugin->property(QStringLiteral("CacheThumbnail")).toBool()) { const QUrl url = (*kit).url(); if (!url.isLocalFile() || !url.adjusted(QUrl::RemoveFilename).toLocalFile().startsWith(thumbRoot)) { bNeedCache = true; } } } else { emit q->failed(*kit); } } KConfigGroup cg(KSharedConfig::openConfig(), "PreviewSettings"); maximumLocalSize = cg.readEntry("MaximumSize", std::numeric_limits::max()); maximumRemoteSize = cg.readEntry("MaximumRemoteSize", 0); if (bNeedCache) { if (width <= 128 && height <= 128) { cacheWidth = cacheHeight = 128; } else { cacheWidth = cacheHeight = 256; } thumbPath = thumbRoot + (cacheWidth == 128 ? "normal/" : "large/"); if (!QDir(thumbPath).exists()) { if (QDir().mkpath(thumbPath)) { // Qt5 TODO: mkpath(dirPath, permissions) QFile f(thumbPath); f.setPermissions(QFile::ReadUser | QFile::WriteUser | QFile::ExeUser); // 0700 } } } else { bSave = false; } initialItems.clear(); determineNextFile(); } void PreviewJob::removeItem(const QUrl &url) { Q_D(PreviewJob); for (QLinkedList::Iterator it = d->items.begin(); it != d->items.end(); ++it) if ((*it).item.url() == url) { d->items.erase(it); break; } if (d->currentItem.item.url() == url) { KJob *job = subjobs().first(); job->kill(); removeSubjob(job); d->determineNextFile(); } } void KIO::PreviewJob::setSequenceIndex(int index) { d_func()->sequenceIndex = index; } int KIO::PreviewJob::sequenceIndex() const { return d_func()->sequenceIndex; } void PreviewJob::setIgnoreMaximumSize(bool ignoreSize) { d_func()->ignoreMaximumSize = ignoreSize; } void PreviewJobPrivate::cleanupTempFile() { if (!tempName.isEmpty()) { Q_ASSERT(!QFileInfo(tempName).isDir()); Q_ASSERT(QFileInfo(tempName).isFile()); QFile::remove(tempName); tempName.clear(); } } void PreviewJobPrivate::determineNextFile() { Q_Q(PreviewJob); if (!currentItem.item.isNull()) { if (!succeeded) { emit q->failed(currentItem.item); } } // No more items ? if (items.isEmpty()) { q->emitResult(); return; } else { // First, stat the orig file state = PreviewJobPrivate::STATE_STATORIG; currentItem = items.first(); succeeded = false; items.removeFirst(); KIO::Job *job = KIO::stat(currentItem.item.url(), KIO::HideProgressInfo); job->addMetaData(QStringLiteral("no-auth-prompt"), QStringLiteral("true")); q->addSubjob(job); } } void PreviewJob::slotResult(KJob *job) { Q_D(PreviewJob); removeSubjob(job); Q_ASSERT(!hasSubjobs()); // We should have only one job at a time ... switch (d->state) { case PreviewJobPrivate::STATE_STATORIG: { if (job->error()) { // that's no good news... // Drop this one and move on to the next one d->determineNextFile(); return; } const KIO::UDSEntry entry = static_cast(job)->statResult(); d->tOrig = QDateTime::fromTime_t(entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, 0)); bool skipCurrentItem = false; const KIO::filesize_t size = (KIO::filesize_t)entry.numberValue(KIO::UDSEntry::UDS_SIZE, 0); const QUrl itemUrl = d->currentItem.item.mostLocalUrl(); if (itemUrl.isLocalFile() || KProtocolInfo::protocolClass(itemUrl.scheme()) == QLatin1String(":local")) { skipCurrentItem = !d->ignoreMaximumSize && size > d->maximumLocalSize && !d->currentItem.plugin->property(QStringLiteral("IgnoreMaximumSize")).toBool(); } else { // For remote items the "IgnoreMaximumSize" plugin property is not respected skipCurrentItem = !d->ignoreMaximumSize && size > d->maximumRemoteSize; // Remote directories are not supported, don't try to do a file_copy on them if (!skipCurrentItem) { // TODO update item.mimeType from the UDS entry, in case it wasn't set initially // But we don't use the mimetype anymore, we just use isDir(). if (d->currentItem.item.isDir()) { skipCurrentItem = true; } } } if (skipCurrentItem) { d->determineNextFile(); return; } bool pluginHandlesSequences = d->currentItem.plugin->property(QStringLiteral("HandleSequences"), QVariant::Bool).toBool(); if (!d->currentItem.plugin->property(QStringLiteral("CacheThumbnail")).toBool() || (d->sequenceIndex && pluginHandlesSequences)) { // This preview will not be cached, no need to look for a saved thumbnail // Just create it, and be done d->getOrCreateThumbnail(); return; } if (d->statResultThumbnail()) { return; } d->getOrCreateThumbnail(); return; } case PreviewJobPrivate::STATE_GETORIG: { if (job->error()) { d->cleanupTempFile(); d->determineNextFile(); return; } d->createThumbnail(static_cast(job)->destUrl().toLocalFile()); return; } case PreviewJobPrivate::STATE_CREATETHUMB: { d->cleanupTempFile(); d->determineNextFile(); return; } } } bool PreviewJobPrivate::statResultThumbnail() { if (thumbPath.isEmpty()) { return false; } QUrl url = currentItem.item.mostLocalUrl(); // Don't include the password if any url.setPassword(QString()); origName = url.toString(); QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(QFile::encodeName(origName)); thumbName = QFile::encodeName(md5.result().toHex()) + ".png"; QImage thumb; if (!thumb.load(thumbPath + thumbName)) { return false; } if (thumb.text(QStringLiteral("Thumb::URI")) != origName || thumb.text(QStringLiteral("Thumb::MTime")).toLongLong() != tOrig.toTime_t()) { return false; } QString thumbnailerVersion = currentItem.plugin->property(QStringLiteral("ThumbnailerVersion"), QVariant::String).toString(); if (!thumbnailerVersion.isEmpty() && thumb.text(QStringLiteral("Software")).startsWith(QStringLiteral("KDE Thumbnail Generator"))) { //Check if the version matches //The software string should read "KDE Thumbnail Generator pluginName (vX)" QString softwareString = thumb.text(QStringLiteral("Software")).remove(QStringLiteral("KDE Thumbnail Generator")).trimmed(); if (softwareString.isEmpty()) { // The thumbnail has been created with an older version, recreating return false; } int versionIndex = softwareString.lastIndexOf(QLatin1String("(v")); if (versionIndex < 0) { return false; } QString cachedVersion = softwareString.remove(0, versionIndex + 2); cachedVersion.chop(1); uint thumbnailerMajor = thumbnailerVersion.toInt(); uint cachedMajor = cachedVersion.toInt(); if (thumbnailerMajor > cachedMajor) { return false; } } // Found it, use it emitPreview(thumb); succeeded = true; determineNextFile(); return true; } void PreviewJobPrivate::getOrCreateThumbnail() { Q_Q(PreviewJob); // We still need to load the orig file ! (This is getting tedious) :) const KFileItem &item = currentItem.item; const QString localPath = item.localPath(); if (!localPath.isEmpty()) { createThumbnail(localPath); } else { const QUrl fileUrl = item.url(); // heuristics for remote URL support bool supportsProtocol = false; if (m_remoteProtocolPlugins.value(fileUrl.scheme()).contains(item.mimetype())) { // There's a plugin supporting this protocol and mimetype supportsProtocol = true; } else if (m_remoteProtocolPlugins.value(QStringLiteral("KIO")).contains(item.mimetype())) { // Assume KIO understands any URL, ThumbCreator slaves who have // X-KDE-Protocols=KIO will get fed the remote URL directly. supportsProtocol = true; } if (supportsProtocol) { createThumbnail(fileUrl.toString()); return; } if (item.isDir()) { // Skip remote dirs (bug 208625) cleanupTempFile(); determineNextFile(); return; } // No plugin support access to this remote content, copy the file // to the local machine, then create the thumbnail state = PreviewJobPrivate::STATE_GETORIG; QTemporaryFile localFile; localFile.setAutoRemove(false); localFile.open(); tempName = localFile.fileName(); const QUrl currentURL = item.mostLocalUrl(); KIO::Job *job = KIO::file_copy(currentURL, QUrl::fromLocalFile(tempName), -1, KIO::Overwrite | KIO::HideProgressInfo /* No GUI */); job->addMetaData(QStringLiteral("thumbnail"), QStringLiteral("1")); q->addSubjob(job); } } void PreviewJobPrivate::createThumbnail(const QString &pixPath) { Q_Q(PreviewJob); state = PreviewJobPrivate::STATE_CREATETHUMB; QUrl thumbURL; thumbURL.setScheme(QStringLiteral("thumbnail")); thumbURL.setPath(pixPath); KIO::TransferJob *job = KIO::get(thumbURL, NoReload, HideProgressInfo); q->addSubjob(job); q->connect(job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotThumbData(KIO::Job*,QByteArray))); bool save = bSave && currentItem.plugin->property(QStringLiteral("CacheThumbnail")).toBool() && !sequenceIndex; job->addMetaData(QStringLiteral("mimeType"), currentItem.item.mimetype()); job->addMetaData(QStringLiteral("width"), QString().setNum(save ? cacheWidth : width)); job->addMetaData(QStringLiteral("height"), QString().setNum(save ? cacheHeight : height)); job->addMetaData(QStringLiteral("iconSize"), QString().setNum(save ? 64 : iconSize)); job->addMetaData(QStringLiteral("iconAlpha"), QString().setNum(iconAlpha)); job->addMetaData(QStringLiteral("plugin"), currentItem.plugin->library()); if (sequenceIndex) { job->addMetaData(QStringLiteral("sequence-index"), QString().setNum(sequenceIndex)); } #ifdef Q_OS_UNIX if (shmid == -1) { if (shmaddr) { shmdt((char *)shmaddr); shmctl(shmid, IPC_RMID, nullptr); } shmid = shmget(IPC_PRIVATE, cacheWidth * cacheHeight * 4, IPC_CREAT | 0600); if (shmid != -1) { shmaddr = (uchar *)(shmat(shmid, nullptr, SHM_RDONLY)); if (shmaddr == (uchar *) - 1) { shmctl(shmid, IPC_RMID, nullptr); shmaddr = nullptr; shmid = -1; } } else { shmaddr = nullptr; } } if (shmid != -1) { job->addMetaData(QStringLiteral("shmid"), QString().setNum(shmid)); } #endif } void PreviewJobPrivate::slotThumbData(KIO::Job *, const QByteArray &data) { bool save = bSave && currentItem.plugin->property(QStringLiteral("CacheThumbnail")).toBool() && (!currentItem.item.url().isLocalFile() || !currentItem.item.url().adjusted(QUrl::RemoveFilename).toLocalFile().startsWith(thumbRoot)) && !sequenceIndex; QImage thumb; #ifdef Q_OS_UNIX if (shmaddr) { // Keep this in sync with kdebase/kioslave/thumbnail.cpp QDataStream str(data); int width, height; quint8 iFormat; str >> width >> height >> iFormat; QImage::Format format = static_cast(iFormat); thumb = QImage(shmaddr, width, height, format).copy(); } else #endif thumb.loadFromData(data); if (thumb.isNull()) { QDataStream s(data); s >> thumb; } if (save) { thumb.setText(QStringLiteral("Thumb::URI"), origName); thumb.setText(QStringLiteral("Thumb::MTime"), QString::number(tOrig.toTime_t())); thumb.setText(QStringLiteral("Thumb::Size"), number(currentItem.item.size())); thumb.setText(QStringLiteral("Thumb::Mimetype"), currentItem.item.mimetype()); QString thumbnailerVersion = currentItem.plugin->property(QStringLiteral("ThumbnailerVersion"), QVariant::String).toString(); QString signature = QString("KDE Thumbnail Generator " + currentItem.plugin->name()); if (!thumbnailerVersion.isEmpty()) { signature.append(" (v" + thumbnailerVersion + ')'); } thumb.setText(QStringLiteral("Software"), signature); QSaveFile saveFile(thumbPath + thumbName); if (saveFile.open(QIODevice::WriteOnly)) { if (thumb.save(&saveFile, "PNG")) { saveFile.commit(); } } } emitPreview(thumb); succeeded = true; } void PreviewJobPrivate::emitPreview(const QImage &thumb) { Q_Q(PreviewJob); QPixmap pix; if (thumb.width() > width || thumb.height() > height) { pix = QPixmap::fromImage(thumb.scaled(QSize(width, height), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } else { pix = QPixmap::fromImage(thumb); } emit q->gotPreview(currentItem.item, pix); } QStringList PreviewJob::availablePlugins() { QStringList result; const KService::List plugins = KServiceTypeTrader::self()->query(QStringLiteral("ThumbCreator")); for (KService::List::ConstIterator it = plugins.begin(); it != plugins.end(); ++it) if (!result.contains((*it)->desktopEntryName())) { result.append((*it)->desktopEntryName()); } return result; } QStringList PreviewJob::defaultPlugins() { QStringList blacklist = QStringList() << QStringLiteral("textthumbnail"); QStringList defaultPlugins = availablePlugins(); foreach (const QString plugin, blacklist) { defaultPlugins.removeAll(plugin); } return defaultPlugins; } QStringList PreviewJob::supportedMimeTypes() { QStringList result; const KService::List plugins = KServiceTypeTrader::self()->query(QStringLiteral("ThumbCreator")); for (KService::List::ConstIterator it = plugins.begin(); it != plugins.end(); ++it) { result += (*it)->mimeTypes(); } return result; } #ifndef KIOWIDGETS_NO_DEPRECATED PreviewJob *KIO::filePreview(const KFileItemList &items, int width, int height, int iconSize, int iconAlpha, bool scale, bool save, const QStringList *enabledPlugins) { return new PreviewJob(items, width, height, iconSize, iconAlpha, scale, save, enabledPlugins); } PreviewJob *KIO::filePreview(const QList &items, int width, int height, int iconSize, int iconAlpha, bool scale, bool save, const QStringList *enabledPlugins) { KFileItemList fileItems; for (QList::const_iterator it = items.begin(); it != items.end(); ++it) { Q_ASSERT((*it).isValid()); // please call us with valid urls only fileItems.append(KFileItem(*it)); } return new PreviewJob(fileItems, width, height, iconSize, iconAlpha, scale, save, enabledPlugins); } #endif PreviewJob *KIO::filePreview(const KFileItemList &items, const QSize &size, const QStringList *enabledPlugins) { return new PreviewJob(items, size, enabledPlugins); } #ifndef KIOWIDGETS_NO_DEPRECATED KIO::filesize_t PreviewJob::maximumFileSize() { KConfigGroup cg(KSharedConfig::openConfig(), "PreviewSettings"); return cg.readEntry("MaximumSize", 5 * 1024 * 1024LL /* 5MB */); } #endif #include "moc_previewjob.cpp" diff --git a/tests/kdirlistertest_gui.cpp b/tests/kdirlistertest_gui.cpp index e118af12..39fe4deb 100644 --- a/tests/kdirlistertest_gui.cpp +++ b/tests/kdirlistertest_gui.cpp @@ -1,165 +1,165 @@ /* This file is part of the KDE desktop environment Copyright (C) 2001, 2002 Michael Brade 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 #include #include -#include +#include #include #include #include "kdirlistertest_gui.h" #include KDirListerTest::KDirListerTest(QWidget *parent) : QWidget(parent) { lister = new KDirLister(this); debug = new PrintSignals; QVBoxLayout *layout = new QVBoxLayout(this); QPushButton *startH = new QPushButton(QStringLiteral("Start listing Home"), this); QPushButton *startR = new QPushButton(QStringLiteral("Start listing Root"), this); QPushButton *test = new QPushButton(QStringLiteral("Many"), this); QPushButton *startT = new QPushButton(QStringLiteral("tarfile"), this); layout->addWidget(startH); layout->addWidget(startR); layout->addWidget(startT); layout->addWidget(test); resize(layout->sizeHint()); connect(startR, SIGNAL(clicked()), SLOT(startRoot())); connect(startH, SIGNAL(clicked()), SLOT(startHome())); connect(startT, SIGNAL(clicked()), SLOT(startTar())); connect(test, SIGNAL(clicked()), SLOT(test())); connect(lister, SIGNAL(started(QUrl)), debug, SLOT(started(QUrl))); connect(lister, SIGNAL(completed()), debug, SLOT(completed())); connect(lister, SIGNAL(completed(QUrl)), debug, SLOT(completed(QUrl))); connect(lister, SIGNAL(canceled()), debug, SLOT(canceled())); connect(lister, SIGNAL(canceled(QUrl)), debug, SLOT(canceled(QUrl))); connect(lister, SIGNAL(redirection(QUrl)), debug, SLOT(redirection(QUrl))); connect(lister, SIGNAL(redirection(QUrl,QUrl)), debug, SLOT(redirection(QUrl,QUrl))); connect(lister, SIGNAL(clear()), debug, SLOT(clear())); connect(lister, SIGNAL(newItems(KFileItemList)), debug, SLOT(newItems(KFileItemList))); connect(lister, SIGNAL(itemsFilteredByMime(KFileItemList)), debug, SLOT(itemsFilteredByMime(KFileItemList))); connect(lister, SIGNAL(itemsDeleted(KFileItemList)), debug, SLOT(itemsDeleted(KFileItemList))); connect(lister, SIGNAL(refreshItems(QList >)), debug, SLOT(refreshItems(QList >))); connect(lister, SIGNAL(infoMessage(QString)), debug, SLOT(infoMessage(QString))); connect(lister, SIGNAL(percent(int)), debug, SLOT(percent(int))); connect(lister, SIGNAL(totalSize(KIO::filesize_t)), debug, SLOT(totalSize(KIO::filesize_t))); connect(lister, SIGNAL(processedSize(KIO::filesize_t)), debug, SLOT(processedSize(KIO::filesize_t))); connect(lister, SIGNAL(speed(int)), debug, SLOT(speed(int))); connect(lister, SIGNAL(completed()), this, SLOT(completed())); } KDirListerTest::~KDirListerTest() { } void KDirListerTest::startHome() { QUrl home = QUrl::fromLocalFile(QDir::homePath()); lister->openUrl(home, KDirLister::NoFlags); // lister->stop(); } void KDirListerTest::startRoot() { QUrl root = QUrl::fromLocalFile(QDir::rootPath()); lister->openUrl(root, KDirLister::Keep | KDirLister::Reload); // lister->stop( root ); } void KDirListerTest::startTar() { QUrl root = QUrl::fromLocalFile(QDir::homePath() + "/aclocal_1.tgz"); lister->openUrl(root, KDirLister::Keep | KDirLister::Reload); // lister->stop( root ); } void KDirListerTest::test() { QUrl home = QUrl::fromLocalFile(QDir::homePath()); QUrl root = QUrl::fromLocalFile(QDir::rootPath()); #ifdef Q_OS_WIN lister->openUrl(home, KDirLister::Keep); lister->openUrl(root, KDirLister::Keep | KDirLister::Reload); #else /* lister->openUrl( home, KDirLister::Keep ); lister->openUrl( root, KDirLister::Keep | KDirLister::Reload ); lister->openUrl( QUrl::fromLocalFile("file:/etc"), KDirLister::Keep | KDirLister::Reload ); lister->openUrl( root, KDirLister::Keep | KDirLister::Reload ); lister->openUrl( QUrl::fromLocalFile("file:/dev"), KDirLister::Keep | KDirLister::Reload ); lister->openUrl( QUrl::fromLocalFile("file:/tmp"), KDirLister::Keep | KDirLister::Reload ); lister->openUrl( QUrl::fromLocalFile("file:/usr/include"), KDirLister::Keep | KDirLister::Reload ); lister->updateDirectory( QUrl::fromLocalFile("file:/usr/include") ); lister->updateDirectory( QUrl::fromLocalFile("file:/usr/include") ); lister->openUrl( QUrl::fromLocalFile("file:/usr/"), KDirLister::Keep | KDirLister::Reload ); */ lister->openUrl(QUrl::fromLocalFile(QStringLiteral("/dev")), KDirLister::Keep | KDirLister::Reload); #endif } void KDirListerTest::completed() { if (lister->url().toLocalFile() == QDir::rootPath()) { const KFileItem item = lister->findByUrl(QUrl::fromLocalFile(QDir::tempPath())); if (!item.isNull()) { qDebug() << "Found " << QDir::tempPath() << ": " << item.name(); } else { qWarning() << QDir::tempPath() << " not found! Bug in findByURL?"; } } } int main(int argc, char *argv[]) { QApplication::setApplicationName(QStringLiteral("kdirlistertest")); QApplication app(argc, argv); KDirListerTest *test = new KDirListerTest(nullptr); test->show(); return app.exec(); } #include "moc_kdirlistertest_gui.cpp" diff --git a/tests/kdirlistertest_gui.h b/tests/kdirlistertest_gui.h index ebb09fae..1b1f9000 100644 --- a/tests/kdirlistertest_gui.h +++ b/tests/kdirlistertest_gui.h @@ -1,142 +1,142 @@ /* This file is part of the KDE desktop environment Copyright (C) 2001, 2002 Michael Brade 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. */ #ifndef _KDIRLISTERTEST_GUI_H_ #define _KDIRLISTERTEST_GUI_H_ #include -#include +#include #include #include #include using namespace std; class PrintSignals : public QObject { Q_OBJECT public: PrintSignals() : QObject() { } public Q_SLOTS: void started(const QUrl &url) { cout << "*** started( " << url.url().toLocal8Bit().data() << " )" << endl; } void canceled() { cout << "canceled()" << endl; } void canceled(const QUrl &url) { cout << "*** canceled( " << url.toDisplayString().toLocal8Bit().data() << " )" << endl; } void completed() { cout << "*** completed()" << endl; } void completed(const QUrl &url) { cout << "*** completed( " << url.toDisplayString().toLocal8Bit().data() << " )" << endl; } void redirection(const QUrl &url) { cout << "*** redirection( " << url.toDisplayString().toLocal8Bit().data() << " )" << endl; } void redirection(const QUrl &src, const QUrl &dest) { cout << "*** redirection( " << src.toDisplayString().toLocal8Bit().data() << ", " << dest.toDisplayString().toLocal8Bit().data() << " )" << endl; } void clear() { cout << "*** clear()" << endl; } void newItems(const KFileItemList &items) { cout << "*** newItems: " << endl; KFileItemList::const_iterator it, itEnd = items.constEnd(); for (it = items.constBegin(); it != itEnd; ++it) { cout << (*it).name().toLocal8Bit().data() << endl; } } void itemsDeleted(const KFileItemList &) { cout << "*** itemsDeleted: " << endl; // TODO } void itemsFilteredByMime(const KFileItemList &) { cout << "*** itemsFilteredByMime: " << endl; // TODO } void refreshItems(const QList > &) { cout << "*** refreshItems: " << endl; // TODO } void infoMessage(const QString &msg) { cout << "*** infoMessage: " << msg.toLocal8Bit().data() << endl; } void percent(int percent) { cout << "*** percent: " << percent << endl; } void totalSize(KIO::filesize_t size) { cout << "*** totalSize: " << (long)size << endl; } void processedSize(KIO::filesize_t size) { cout << "*** processedSize: " << (long)size << endl; } void speed(int bytes_per_second) { cout << "*** speed: " << bytes_per_second << endl; } }; class KDirListerTest : public QWidget { Q_OBJECT public: KDirListerTest(QWidget *parent = nullptr); ~KDirListerTest(); public Q_SLOTS: void startRoot(); void startHome(); void startTar(); void test(); void completed(); private: KDirLister *lister; PrintSignals *debug; }; #endif diff --git a/tests/kdirmodeltest_gui.cpp b/tests/kdirmodeltest_gui.cpp index 27087afc..e1dc37f6 100644 --- a/tests/kdirmodeltest_gui.cpp +++ b/tests/kdirmodeltest_gui.cpp @@ -1,110 +1,110 @@ /* * Copyright (C) 2006 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 #include #include #include #include #include -#include -#include +#include +#include #include // Test controller for making the view open up while expandToUrl lists subdirs class TreeController : public QObject { Q_OBJECT public: explicit TreeController(QTreeView *view, KDirModel *model) : QObject(view), m_treeView(view), m_model(model) { connect(model, SIGNAL(expand(QModelIndex)), this, SLOT(slotExpand(QModelIndex))); } private Q_SLOTS: void slotExpand(const QModelIndex &index) { KFileItem item = m_model->itemForIndex(index); qDebug() << "slotListingCompleted" << item.url(); m_treeView->setExpanded(index, true); // The scrollTo call doesn't seem to work. // We probably need to delay this until everything's listed and layouted... m_treeView->scrollTo(index); } private: QTreeView *m_treeView; KDirModel *m_model; }; int main(int argc, char **argv) { //options.add("+[directory ...]", qi18n("Directory(ies) to model")); QApplication a(argc, argv); KDirModel *dirmodel = new KDirModel(nullptr); dirmodel->dirLister()->setDelayedMimeTypes(true); #if 1 QTreeView *treeView = new QTreeView(nullptr); treeView->setModel(dirmodel); treeView->setUniformRowHeights(true); // makes visualRect() much faster treeView->resize(500, 500); treeView->show(); treeView->setItemDelegate(new KFileItemDelegate(treeView)); #endif #if 0 QListView *listView = new QListView(0); listView->setModel(dirmodel); listView->setUniformItemSizes(true); // true in list mode, not in icon mode. listView->show(); listView->setItemDelegate(new KFileItemDelegate(listView)); #endif #if 1 QListView *iconView = new QListView(nullptr); iconView->setModel(dirmodel); iconView->setSelectionMode(QListView::ExtendedSelection); iconView->setViewMode(QListView::IconMode); iconView->show(); iconView->setItemDelegate(new KFileItemDelegate(iconView)); #endif if (argc <= 1) { dirmodel->dirLister()->openUrl(QUrl::fromLocalFile(QStringLiteral("/"))); const QUrl url = QUrl::fromLocalFile(QStringLiteral("/usr/share/applications/kde")); dirmodel->expandToUrl(url); new TreeController(treeView, dirmodel); } const int count = QCoreApplication::arguments().count() - 1; for (int i = 0; i < count; i++) { QUrl u = QUrl::fromUserInput(QCoreApplication::arguments().at(i + 1)); qDebug() << "Adding: " << u; dirmodel->dirLister()->openUrl(u, KDirLister::Keep); } return a.exec(); } #include "kdirmodeltest_gui.moc" diff --git a/tests/kioslavetest.cpp b/tests/kioslavetest.cpp index f360704c..d4276d53 100644 --- a/tests/kioslavetest.cpp +++ b/tests/kioslavetest.cpp @@ -1,541 +1,541 @@ /* This file is or will be part of KDE desktop environment Copyright 1999 Matt Koss It is licensed under GPL version 2. If it is part of KDE libraries than this file is licensed under LGPL version 2. */ #include #include #include -#include +#include #include #include #include #include #include #include #include #include #include #include #include #include -#include +#include #include #include #include "kioslavetest.h" using namespace KIO; KioslaveTest::KioslaveTest(QString src, QString dest, uint op, uint pr) : KMainWindow(nullptr) { job = nullptr; main_widget = new QWidget(this); QBoxLayout *topLayout = new QVBoxLayout(main_widget); QGridLayout *grid = new QGridLayout(); topLayout->addLayout(grid); grid->setRowStretch(0, 1); grid->setRowStretch(1, 1); grid->setColumnStretch(0, 1); grid->setColumnStretch(1, 100); lb_from = new QLabel(QStringLiteral("From:"), main_widget); grid->addWidget(lb_from, 0, 0); le_source = new QLineEdit(main_widget); grid->addWidget(le_source, 0, 1); le_source->setText(src); lb_to = new QLabel(QStringLiteral("To:"), main_widget); grid->addWidget(lb_to, 1, 0); le_dest = new QLineEdit(main_widget); grid->addWidget(le_dest, 1, 1); le_dest->setText(dest); // Operation groupbox & buttons opButtons = new QButtonGroup(main_widget); QGroupBox *box = new QGroupBox(QStringLiteral("Operation"), main_widget); topLayout->addWidget(box, 10); connect(opButtons, SIGNAL(buttonClicked(QAbstractButton*)), SLOT(changeOperation(QAbstractButton*))); QBoxLayout *hbLayout = new QHBoxLayout(box); rbList = new QRadioButton(QStringLiteral("List"), box); opButtons->addButton(rbList); hbLayout->addWidget(rbList, 5); rbListRecursive = new QRadioButton(QStringLiteral("ListRecursive"), box); opButtons->addButton(rbListRecursive); hbLayout->addWidget(rbListRecursive, 5); rbStat = new QRadioButton(QStringLiteral("Stat"), box); opButtons->addButton(rbStat); hbLayout->addWidget(rbStat, 5); rbGet = new QRadioButton(QStringLiteral("Get"), box); opButtons->addButton(rbGet); hbLayout->addWidget(rbGet, 5); rbPut = new QRadioButton(QStringLiteral("Put"), box); opButtons->addButton(rbPut); hbLayout->addWidget(rbPut, 5); rbCopy = new QRadioButton(QStringLiteral("Copy"), box); opButtons->addButton(rbCopy); hbLayout->addWidget(rbCopy, 5); rbMove = new QRadioButton(QStringLiteral("Move"), box); opButtons->addButton(rbMove); hbLayout->addWidget(rbMove, 5); rbDelete = new QRadioButton(QStringLiteral("Delete"), box); opButtons->addButton(rbDelete); hbLayout->addWidget(rbDelete, 5); rbMkdir = new QRadioButton(QStringLiteral("Mkdir"), box); opButtons->addButton(rbMkdir); hbLayout->addWidget(rbMkdir, 5); rbMimetype = new QRadioButton(QStringLiteral("Mimetype"), box); opButtons->addButton(rbMimetype); hbLayout->addWidget(rbMimetype, 5); QAbstractButton *b = opButtons->buttons()[op]; b->setChecked(true); changeOperation(b); // Progress groupbox & buttons progressButtons = new QButtonGroup(main_widget); box = new QGroupBox(QStringLiteral("Progress dialog mode"), main_widget); topLayout->addWidget(box, 10); connect(progressButtons, SIGNAL(buttonClicked(QAbstractButton*)), SLOT(changeProgressMode(QAbstractButton*))); hbLayout = new QHBoxLayout(box); rbProgressNone = new QRadioButton(QStringLiteral("None"), box); progressButtons->addButton(rbProgressNone); hbLayout->addWidget(rbProgressNone, 5); rbProgressDefault = new QRadioButton(QStringLiteral("Default"), box); progressButtons->addButton(rbProgressDefault); hbLayout->addWidget(rbProgressDefault, 5); rbProgressStatus = new QRadioButton(QStringLiteral("Status"), box); progressButtons->addButton(rbProgressStatus); hbLayout->addWidget(rbProgressStatus, 5); b = progressButtons->buttons()[pr]; b->setChecked(true); changeProgressMode(b); // statusbar progress widget statusTracker = new KStatusBarJobTracker(statusBar()); // run & stop buttons hbLayout = new QHBoxLayout(); topLayout->addLayout(hbLayout); hbLayout->setParent(topLayout); pbStart = new QPushButton(QStringLiteral("&Start"), main_widget); pbStart->setFixedSize(pbStart->sizeHint()); connect(pbStart, SIGNAL(clicked()), SLOT(startJob())); hbLayout->addWidget(pbStart, 5); pbStop = new QPushButton(QStringLiteral("Sto&p"), main_widget); pbStop->setFixedSize(pbStop->sizeHint()); pbStop->setEnabled(false); connect(pbStop, SIGNAL(clicked()), SLOT(stopJob())); hbLayout->addWidget(pbStop, 5); // close button close = new QPushButton(QStringLiteral("&Close"), main_widget); close->setFixedSize(close->sizeHint()); connect(close, SIGNAL(clicked()), this, SLOT(slotQuit())); topLayout->addWidget(close, 5); main_widget->setMinimumSize(main_widget->sizeHint()); setCentralWidget(main_widget); slave = nullptr; // slave = KIO::Scheduler::getConnectedSlave(QUrl("ftp://ftp.kde.org")); KIO::Scheduler::connect(SIGNAL(slaveConnected(KIO::Slave*)), this, SLOT(slotSlaveConnected())); KIO::Scheduler::connect(SIGNAL(slaveError(KIO::Slave*,int,QString)), this, SLOT(slotSlaveError())); } void KioslaveTest::slotQuit() { qApp->quit(); } void KioslaveTest::changeOperation(QAbstractButton *b) { // only two urls for copy and move bool enab = rbCopy->isChecked() || rbMove->isChecked(); le_dest->setEnabled(enab); selectedOperation = opButtons->buttons().indexOf(b); } void KioslaveTest::changeProgressMode(QAbstractButton *b) { progressMode = progressButtons->buttons().indexOf(b); if (progressMode == ProgressStatus) { statusBar()->show(); } else { statusBar()->hide(); } } void KioslaveTest::startJob() { QUrl sCurrent(QUrl::fromLocalFile(QDir::currentPath())); QString sSrc(le_source->text()); QUrl src = QUrl(sCurrent).resolved(QUrl(sSrc)); if (!src.isValid()) { QMessageBox::critical(this, QStringLiteral("Kioslave Error Message"), QStringLiteral("Source URL is malformed")); return; } QString sDest(le_dest->text()); QUrl dest = QUrl(sCurrent).resolved(QUrl(sDest)); if (!dest.isValid() && (selectedOperation == Copy || selectedOperation == Move)) { QMessageBox::critical(this, QStringLiteral("Kioslave Error Message"), QStringLiteral("Destination URL is malformed")); return; } pbStart->setEnabled(false); KIO::JobFlags observe = DefaultFlags; if (progressMode != ProgressDefault) { observe = HideProgressInfo; } SimpleJob *myJob = nullptr; switch (selectedOperation) { case List: myJob = KIO::listDir(src); connect(myJob, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList))); break; case ListRecursive: myJob = KIO::listRecursive(src); connect(myJob, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList))); break; case Stat: myJob = KIO::stat(src, KIO::StatJob::SourceSide, 2); break; case Get: myJob = KIO::get(src, KIO::Reload); connect(myJob, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotData(KIO::Job*,QByteArray))); break; case Put: { putBuffer = 0; KIO::TransferJob *tjob = KIO::put(src, -1, KIO::Overwrite); tjob->setTotalSize(48 * 1024 * 1024); myJob = tjob; connect(tjob, SIGNAL(dataReq(KIO::Job*,QByteArray&)), SLOT(slotDataReq(KIO::Job*,QByteArray&))); break; } case Copy: job = KIO::copy(src, dest, observe); break; case Move: job = KIO::move(src, dest, observe); break; case Delete: job = KIO::del(src, observe); break; case Mkdir: myJob = KIO::mkdir(src); break; case Mimetype: myJob = KIO::mimetype(src); break; } if (myJob) { if (slave) { KIO::Scheduler::assignJobToSlave(slave, myJob); } job = myJob; } statusBar()->addWidget(statusTracker->widget(job), 0); connect(job, SIGNAL(result(KJob*)), SLOT(slotResult(KJob*))); connect(job, SIGNAL(canceled(KJob*)), SLOT(slotResult(KJob*))); if (progressMode == ProgressStatus) { statusTracker->registerJob(job); } pbStop->setEnabled(true); } void KioslaveTest::slotResult(KJob *_job) { if (_job->error()) { _job->uiDelegate()->showErrorMessage(); } else if (selectedOperation == Stat) { UDSEntry entry = ((KIO::StatJob *)_job)->statResult(); printUDSEntry(entry); } else if (selectedOperation == Mimetype) { qDebug() << "mimetype is " << ((KIO::MimetypeJob *)_job)->mimetype(); } if (job == _job) { job = nullptr; } pbStart->setEnabled(true); pbStop->setEnabled(false); //statusBar()->removeWidget( statusTracker->widget(job) ); } void KioslaveTest::slotSlaveConnected() { qDebug() << "Slave connected."; } void KioslaveTest::slotSlaveError() { qDebug() << "Error connected."; slave = nullptr; } void KioslaveTest::printUDSEntry(const KIO::UDSEntry &entry) { // It's rather rare to iterate that way, usually you'd use numberValue/stringValue directly. // This is just to print out all that we got const QVector keys = entry.fields(); QVector::const_iterator it = keys.begin(); for (; it != keys.end(); ++it) { switch (*it) { case KIO::UDSEntry::UDS_FILE_TYPE: { mode_t mode = (mode_t)entry.numberValue(*it); qDebug() << "File Type : " << mode; if ((mode & QT_STAT_MASK) == QT_STAT_DIR) { qDebug() << "is a dir"; } } break; case KIO::UDSEntry::UDS_ACCESS: qDebug() << "Access permissions : " << (mode_t)(entry.numberValue(*it)); break; case KIO::UDSEntry::UDS_USER: qDebug() << "User : " << (entry.stringValue(*it)); break; case KIO::UDSEntry::UDS_GROUP: qDebug() << "Group : " << (entry.stringValue(*it)); break; case KIO::UDSEntry::UDS_NAME: qDebug() << "Name : " << (entry.stringValue(*it)); //m_strText = decodeFileName( it.value().toString() ); break; case KIO::UDSEntry::UDS_URL: qDebug() << "URL : " << (entry.stringValue(*it)); break; case KIO::UDSEntry::UDS_MIME_TYPE: qDebug() << "MimeType : " << (entry.stringValue(*it)); break; case KIO::UDSEntry::UDS_LINK_DEST: qDebug() << "LinkDest : " << (entry.stringValue(*it)); break; case KIO::UDSEntry::UDS_SIZE: qDebug() << "Size: " << KIO::convertSize(entry.numberValue(*it)); break; } } } void KioslaveTest::slotEntries(KIO::Job *job, const KIO::UDSEntryList &list) { QUrl url = static_cast(job)->url(); KProtocolInfo::ExtraFieldList extraFields = KProtocolInfo::extraFields(url); UDSEntryList::ConstIterator it = list.begin(); for (; it != list.end(); ++it) { // For each file... QString name = (*it).stringValue(KIO::UDSEntry::UDS_NAME); qDebug() << name; KProtocolInfo::ExtraFieldList::Iterator extraFieldsIt = extraFields.begin(); const QVector fields = it->fields(); QVector::ConstIterator it2 = fields.begin(); for (; it2 != fields.end(); it2++) { if (*it2 >= UDSEntry::UDS_EXTRA && *it2 <= UDSEntry::UDS_EXTRA_END) { if (extraFieldsIt != extraFields.end()) { QString column = (*extraFieldsIt).name; //QString type = (*extraFieldsIt).type; qDebug() << " Extra data (" << column << ") :" << it->stringValue(*it2); ++extraFieldsIt; } else { qDebug() << " Extra data (UNDEFINED) :" << it->stringValue(*it2); } } } } } void KioslaveTest::slotData(KIO::Job *, const QByteArray &data) { if (data.size() == 0) { qDebug() << "Data: "; } else { qDebug() << "Data: \"" << QString(data) << "\""; } } void KioslaveTest::slotDataReq(KIO::Job *, QByteArray &data) { const char *fileDataArray[] = { "Hello world\n", "This is a test file\n", "You can safely delete it.\n", "BIG\n", "BIG1\n", "BIG2\n", "BIG3\n", "BIG4\n", "BIG5\n", nullptr }; const char *fileData = fileDataArray[putBuffer++]; if (!fileData) { qDebug() << "DataReq: "; return; } if (!strncmp(fileData, "BIG", 3)) { data.fill(0, 8 * 1024 * 1024); } else { data = QByteArray(fileData, strlen(fileData)); } qDebug() << "DataReq: \"" << fileData << "\""; QThread::sleep(1); // want to see progress info... } void KioslaveTest::stopJob() { qDebug() << "KioslaveTest::stopJob()"; job->kill(); job = nullptr; pbStop->setEnabled(false); pbStart->setEnabled(true); } int main(int argc, char **argv) { const char version[] = "v0.0.0 0000"; // :-) QApplication app(argc, argv); app.setApplicationVersion(version); uint op = KioslaveTest::Copy; uint pr = 0; QString src, dest, operation; { QCommandLineParser parser; parser.addVersionOption(); parser.setApplicationDescription(QStringLiteral("Test for kioslaves")); parser.addHelpOption(); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("s") << QStringLiteral("src"), QStringLiteral("Source URL"), QStringLiteral("url"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("d") << QStringLiteral("dest"), QStringLiteral("Destination URL"), QStringLiteral("url"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("o") << QStringLiteral("operation"), QStringLiteral("Operation (list,listrecursive,stat,get,put,copy,move,del,mkdir)"), QStringLiteral("operation"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("p") << QStringLiteral("progress"), QStringLiteral("Progress Type (none,default,status)"), QStringLiteral("progress"), QStringLiteral("default"))); parser.process(app); src = parser.value(QStringLiteral("src")); dest = parser.value(QStringLiteral("dest")); operation = parser.value(QStringLiteral("operation")); if (operation == QLatin1String("list")) { op = KioslaveTest::List; } else if (operation == QLatin1String("listrecursive")) { op = KioslaveTest::ListRecursive; } else if (operation == QLatin1String("stat")) { op = KioslaveTest::Stat; } else if (operation == QLatin1String("get")) { op = KioslaveTest::Get; } else if (operation == QLatin1String("put")) { op = KioslaveTest::Put; } else if (operation == QLatin1String("copy")) { op = KioslaveTest::Copy; } else if (operation == QLatin1String("move")) { op = KioslaveTest::Move; } else if (operation == QLatin1String("del")) { op = KioslaveTest::Delete; } else if (operation == QLatin1String("mkdir")) { op = KioslaveTest::Mkdir; } else if (!operation.isEmpty()) { qWarning("Unknown operation, see --help"); return 1; } QString progress = parser.value(QStringLiteral("progress")); if (progress == QLatin1String("none")) { pr = KioslaveTest::ProgressNone; } else if (progress == QLatin1String("default")) { pr = KioslaveTest::ProgressDefault; } else if (progress == QLatin1String("status")) { pr = KioslaveTest::ProgressStatus; } else { qWarning("Unknown progress mode, see --help"); return 1; } } KioslaveTest *test = new KioslaveTest(src, dest, op, pr); if (!operation.isEmpty()) { QTimer::singleShot(100, test, SLOT(startJob())); } test->show(); test->resize(test->sizeHint()); app.exec(); } #include "moc_kioslavetest.cpp" diff --git a/tests/kopenwithtest.cpp b/tests/kopenwithtest.cpp index c5e542f8..2934fbed 100644 --- a/tests/kopenwithtest.cpp +++ b/tests/kopenwithtest.cpp @@ -1,68 +1,68 @@ /* This file is part of the KDE libraries Copyright (C) 2002 Dirk Mueller Copyright (C) 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 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 #include -#include -#include +#include +#include #include #include #include int main(int argc, char **argv) { QApplication::setApplicationName(QStringLiteral("kopenwithdialogtest")); QApplication app(argc, argv); QList list; list += QUrl(QStringLiteral("file:///tmp/testfile.txt")); // Test with one URL KOpenWithDialog *dlg = new KOpenWithDialog(list, QString(), QString()); if (dlg->exec()) { qDebug() << "Dialog ended successfully\ntext: " << dlg->text(); } else { qDebug() << "Dialog was canceled."; } delete dlg; // Test with two URLs list += QUrl(QStringLiteral("http://www.kde.org/index.html")); dlg = new KOpenWithDialog(list, QString(), QString(), nullptr); if (dlg->exec()) { qDebug() << "Dialog ended successfully\ntext: " << dlg->text(); } else { qDebug() << "Dialog was canceled."; } delete dlg; // Test with a mimetype QString mimetype = QStringLiteral("text/plain"); dlg = new KOpenWithDialog(mimetype, QStringLiteral("kedit"), nullptr); if (dlg->exec()) { qDebug() << "Dialog ended successfully\ntext: " << dlg->text(); } else { qDebug() << "Dialog was canceled."; } delete dlg; return 0; } diff --git a/tests/kruntest.cpp b/tests/kruntest.cpp index 14d90853..827bc2af 100644 --- a/tests/kruntest.cpp +++ b/tests/kruntest.cpp @@ -1,154 +1,154 @@ /* * Copyright (C) 2002 David Faure * Copyright (C) 2003 Waldo Bastian * * 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 "kruntest.h" #include #include #include #include #include #include -#include +#include #include const int MAXKRUNS = 100; testKRun *myArray[MAXKRUNS]; void testKRun::foundMimeType(const QString &_type) { qDebug() << "found mime type" << _type << "for URL=" << url(); setFinished(true); return; } static const char testFile[] = "kruntest.cpp"; static const struct { const char *text; const char *expectedResult; const char *exec; const char *url; } s_tests[] = { { "run(kwrite, no url)", "should work normally", "kwrite", nullptr }, { "run(kwrite, file url)", "should work normally", "kwrite", testFile }, { "run(kwrite, remote url)", "should work normally", "kwrite", "http://www.kde.org" }, { "run(doesnotexit, no url)", "should show error message", "doesnotexist", nullptr }, { "run(doesnotexit, file url)", "should show error message", "doesnotexist", testFile }, { "run(doesnotexit, remote url)", "should use kioexec and show error message", "doesnotexist", "http://www.kde.org" }, { "run(missing lib, no url)", "should show error message (remove libqca.so.2 for this, e.g. by editing LD_LIBRARY_PATH if qca is in its own prefix)", "qcatool", nullptr }, { "run(missing lib, file url)", "should show error message (remove libqca.so.2 for this, e.g. by editing LD_LIBRARY_PATH if qca is in its own prefix)", "qcatool", testFile }, { "run(missing lib, remote url)", "should show error message (remove libqca.so.2 for this, e.g. by editing LD_LIBRARY_PATH if qca is in its own prefix)", "qcatool", "http://www.kde.org" }, { "runCommand(empty)", "should error", "", "" }, // #186036 { "runCommand(full path)", "should work normally", "../../kdecore/tests/kurltest", "" } }; Receiver::Receiver() { QVBoxLayout *lay = new QVBoxLayout(this); QPushButton *h = new QPushButton(QStringLiteral("Press here to terminate"), this); lay->addWidget(h); connect(h, SIGNAL(clicked()), qApp, SLOT(quit())); start = new QPushButton(QStringLiteral("Launch KRuns"), this); lay->addWidget(start); connect(start, SIGNAL(clicked()), this, SLOT(slotStart())); stop = new QPushButton(QStringLiteral("Stop those KRuns"), this); stop->setEnabled(false); lay->addWidget(stop); connect(stop, SIGNAL(clicked()), this, SLOT(slotStop())); QPushButton *launchOne = new QPushButton(QStringLiteral("Launch one http KRun"), this); lay->addWidget(launchOne); connect(launchOne, SIGNAL(clicked()), this, SLOT(slotLaunchOne())); for (uint i = 0; i < sizeof(s_tests) / sizeof(*s_tests); ++i) { QHBoxLayout *hbox = new QHBoxLayout; lay->addLayout(hbox); QPushButton *button = new QPushButton(s_tests[i].text, this); button->setProperty("testNumber", i); hbox->addWidget(button); QLabel *label = new QLabel(s_tests[i].expectedResult, this); hbox->addWidget(label); connect(button, SIGNAL(clicked()), this, SLOT(slotLaunchTest())); hbox->addStretch(); } adjustSize(); show(); } void Receiver::slotLaunchTest() { QPushButton *button = qobject_cast(sender()); Q_ASSERT(button); const int testNumber = button->property("testNumber").toInt(); QList urls; if (QByteArray(s_tests[testNumber].text).startsWith("runCommand")) { KRun::runCommand(s_tests[testNumber].exec, this); } else { if (s_tests[testNumber].url) { QString urlStr(s_tests[testNumber].url); if (urlStr == QLatin1String(testFile)) { urlStr = QFINDTESTDATA(testFile); } urls << QUrl::fromUserInput(urlStr); } KRun::run(s_tests[testNumber].exec, urls, this); } } void Receiver::slotStop() { for (int i = 0; i < MAXKRUNS; i++) { qDebug() << " deleting krun " << i; delete myArray[i]; } start->setEnabled(true); stop->setEnabled(false); } void Receiver::slotStart() { for (int i = 0; i < MAXKRUNS; i++) { qDebug() << "creating testKRun " << i; myArray[i] = new testKRun(QUrl::fromLocalFile(QStringLiteral("file:///tmp")), window()); myArray[i]->setAutoDelete(false); } start->setEnabled(false); stop->setEnabled(true); } void Receiver::slotLaunchOne() { new testKRun(QUrl(QStringLiteral("http://www.kde.org")), window()); } int main(int argc, char **argv) { QApplication::setApplicationName(QStringLiteral("kruntest")); QApplication app(argc, argv); Receiver receiver; return app.exec(); }