diff --git a/autotests/jobtest.cpp b/autotests/jobtest.cpp index fbf0e030..2c07a632 100644 --- a/autotests/jobtest.cpp +++ b/autotests/jobtest.cpp @@ -1,2179 +1,2074 @@ /* 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 "jobtest.h" #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 } static bool otherTmpDirIsOnSamePartition() // true on CI because it's a LXC container { KMountPoint::Ptr srcMountPoint = KMountPoint::currentMountPoints().findByPath(homeTmpDir()); KMountPoint::Ptr destMountPoint = KMountPoint::currentMountPoints().findByPath(otherTmpDir()); Q_ASSERT(srcMountPoint); Q_ASSERT(destMountPoint); return srcMountPoint->mountedFrom() == destMountPoint->mountedFrom(); } -#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::setTestModeEnabled(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, &JobTest::exitLoop, &eventLoop, &QEventLoop::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, &KJob::result, this, &JobTest::slotGetResult); 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); quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago job->setModificationTime(mtime); job->setUiDelegate(nullptr); connect(job, &KJob::result, this, &JobTest::slotResult); connect(job, &KIO::TransferJob::dataReq, this, &JobTest::slotDataReq); 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()); quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago job->setModificationTime(mtime); job->setUiDelegate(nullptr); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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()); quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago job->setModificationTime(mtime); job->setUiDelegate(nullptr); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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()); quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago 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(); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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()); quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago 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(); }); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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()); quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago 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(); }); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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::asyncStoredPutReadyReadAfterFinish() { const QString filePath = homeTmpDir() + "fileFromHome"; const QUrl u = QUrl::fromLocalFile(filePath); QBuffer putDataBuffer; QVERIFY(putDataBuffer.open(QIODevice::ReadWrite)); KIO::StoredTransferJob *job = KIO::storedPut(&putDataBuffer, u, 0600, KIO::Overwrite | KIO::HideProgressInfo); job->setAsyncDataEnabled(true); bool jobFinished = false; connect(job, &KJob::finished, [&jobFinished, &putDataBuffer] { putDataBuffer.readyRead(); jobFinished = true; }); QTimer::singleShot(200, this, [job]() { job->kill(); }); QTRY_VERIFY(jobFinished); } //// 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); // Do it again, without Overwrite (should fail). job = KIO::copyAs(u, d, KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); QVERIFY(!job->exec()); } #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(QString::number(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 QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); // 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()); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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()); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), 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::moveDirectoryToReadonlyFilesystem_data() { QTest::addColumn>("sources"); QTest::addColumn("expectedErrorCode"); const QString srcFileHomePath = homeTmpDir() + "srcFileHome"; const QUrl srcFileHome = QUrl::fromLocalFile(srcFileHomePath); createTestFile(srcFileHomePath); const QString srcFileOtherPath = otherTmpDir() + "srcFileOther"; const QUrl srcFileOther = QUrl::fromLocalFile(srcFileOtherPath); createTestFile(srcFileOtherPath); const QString srcDirHomePath = homeTmpDir() + "srcDirHome"; const QUrl srcDirHome = QUrl::fromLocalFile(srcDirHomePath); createTestDirectory(srcDirHomePath); const QString srcDirHome2Path = homeTmpDir() + "srcDirHome2"; const QUrl srcDirHome2 = QUrl::fromLocalFile(srcDirHome2Path); createTestDirectory(srcDirHome2Path); const QString srcDirOtherPath = otherTmpDir() + "srcDirOther"; const QUrl srcDirOther = QUrl::fromLocalFile(srcDirOtherPath); createTestDirectory(srcDirOtherPath); QTest::newRow("file_same_partition") << QList{srcFileHome} << int(KIO::ERR_WRITE_ACCESS_DENIED); QTest::newRow("file_other_partition") << QList{srcFileOther} << int(KIO::ERR_WRITE_ACCESS_DENIED); QTest::newRow("one_dir_same_partition") << QList{srcDirHome} << int(KIO::ERR_WRITE_ACCESS_DENIED); QTest::newRow("one_dir_other_partition") << QList{srcDirOther} << int(KIO::ERR_WRITE_ACCESS_DENIED); QTest::newRow("dirs_same_partition") << QList{srcDirHome, srcDirHome2} << int(KIO::ERR_WRITE_ACCESS_DENIED); QTest::newRow("dirs_both_partitions") << QList{srcDirOther, srcDirHome} << int(KIO::ERR_WRITE_ACCESS_DENIED); } void JobTest::moveDirectoryToReadonlyFilesystem() { QFETCH(QList, sources); QFETCH(int, expectedErrorCode); const QString dst_dir = homeTmpDir() + "readonlyDest"; const QUrl dst = QUrl::fromLocalFile(dst_dir); QVERIFY2(QDir().mkdir(dst_dir), qPrintable(dst_dir)); QFile(dst_dir).setPermissions(QFile::Permissions(QFile::ReadOwner | QFile::ExeOwner)); // Make it readonly, moving should throw some errors KIO::CopyJob *job = KIO::move(sources, dst, KIO::HideProgressInfo | KIO::NoPrivilegeExecution); job->setUiDelegate(nullptr); QVERIFY(!job->exec()); QCOMPARE(job->error(), expectedErrorCode); for (const QUrl &srcUrl : qAsConst(sources)) { QVERIFY(QFileInfo::exists(srcUrl.toLocalFile())); // no moving happened } KIO::CopyJob *job2 = KIO::move(sources, dst, KIO::HideProgressInfo); job2->setUiDelegate(nullptr); QVERIFY(!job2->exec()); if (job2->error() != KIO::ERR_CANNOT_MKDIR) { // This can happen when moving between partitions, but on CI it's the same partition so allow both QCOMPARE(job2->error(), expectedErrorCode); } for (const QUrl &srcUrl : qAsConst(sources)) { QVERIFY(QFileInfo::exists(srcUrl.toLocalFile())); // no moving happened } // Cleanup QVERIFY(QFile(dst_dir).setPermissions(QFile::Permissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner))); QVERIFY(QDir(dst_dir).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, &KIO::ListJob::entries, this, &JobTest::slotEntries); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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(QLatin1Char(',')); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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 QVERIFY2(job->totalSize() >= 60, qPrintable(QString("totalSize was %1").arg(job->totalSize()))); // size of subdir entries is filesystem dependent. E.g. this is 16428 with ext4 but only 272 with xfs, and 63 on FreeBSD #endif qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete); } void JobTest::directorySizeError() { KIO::DirectorySizeJob *job = KIO::directorySize(QUrl::fromLocalFile(QStringLiteral("/I/Dont/Exist"))); job->setUiDelegate(nullptr); QVERIFY(!job->exec()); 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))); - QVERIFY2(job->exec(), qPrintable(job->errorString())); - - 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); - QVERIFY2(job->exec(), qPrintable(job->errorString())); - 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) QVERIFY(!job->exec()); // 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); } QElapsedTimer dt; dt.start(); KIO::Job *job = KIO::del(dirs, KIO::HideProgressInfo); job->setUiDelegate(nullptr); QVERIFY2(job->exec(), qPrintable(job->errorString())); for (const QUrl &dir : qAsConst(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() { QElapsedTimer 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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; QElapsedTimer 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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)); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); // 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); // 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)); // differentiate link time and source file time const QUrl url(QUrl::fromLocalFile(symlink)); KIO::StatJob *job = KIO::stat(url, KIO::HideProgressInfo); QVERIFY(job); QVERIFY2(job->exec(), qPrintable(job->errorString())); // 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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))); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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))); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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*))); QVERIFY(!job->exec()); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); QVERIFY(!QFile::exists(dir)); } } void JobTest::moveDestAlreadyExistsAutoRename(const QString &destDir, bool moveDirs) // #256650 { const QString prefix = moveDirs ? QStringLiteral("dir ") : QStringLiteral("file "); const QString file1 = homeTmpDir() + prefix + "(1)"; const QString file2 = homeTmpDir() + prefix + "(2)"; const QString existingDest1 = destDir + prefix + "(1)"; const QString existingDest2 = destDir + prefix + "(2)"; const QStringList sources = QStringList() << file1 << file2 << existingDest1 << existingDest2; for (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(); QVERIFY2(job->exec(), qPrintable(job->errorString())); qDebug() << QDir(destDir).entryList(); 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::copyDirectoryAlreadyExistsSkip() { // when copying a directory (which contains at least one file) to some location, and then // copying the same dir to the same location again, and clicking "Skip" there should be no // segmentation fault, bug 408350 const QString src = homeTmpDir() + "a"; createTestDirectory(src); const QString dest = homeTmpDir() + "dest"; createTestDirectory(dest); QUrl u = QUrl::fromLocalFile(src); QUrl d = QUrl::fromLocalFile(dest); KIO::Job *job = KIO::copy(u, d, KIO::HideProgressInfo); job->setUiDelegate(nullptr); QVERIFY2(job->exec(), qPrintable(job->errorString())); QVERIFY(QFile::exists(dest + QStringLiteral("/a/testfile"))); job = KIO::copy(u, d, KIO::HideProgressInfo); // Simulate the user pressing "Skip" in the dialog. PredefinedAnswerJobUiDelegate extension; extension.m_skipResult = KIO::S_SKIP; job->setUiDelegateExtension(&extension); QVERIFY2(job->exec(), qPrintable(job->errorString())); QVERIFY(QFile::exists(dest + QStringLiteral("/a/testfile"))); QDir(src).removeRecursively(); QDir(dest).removeRecursively(); } void JobTest::safeOverwrite_data() { QTest::addColumn("destFileExists"); QTest::newRow("dest file exists") << true; QTest::newRow("dest file doesn't exist") << false; } void JobTest::safeOverwrite() { #ifdef Q_OS_WIN QSKIP("Test skipped on Windows"); #endif QFETCH(bool, destFileExists); const QString srcDir = homeTmpDir() + "overwrite"; const QString srcFile = srcDir + "/testfile"; const QString destDir = otherTmpDir() + "overwrite_other"; const QString destFile = destDir + "/testfile"; const QString destPartFile = destFile + ".part"; createTestDirectory(srcDir); createTestDirectory(destDir); QVERIFY(QFile::resize(srcFile, 1000000)); //~1MB if (!destFileExists) { QVERIFY(QFile::remove(destFile)); } if (otherTmpDirIsOnSamePartition()) { QSKIP(qPrintable(QStringLiteral("This test requires %1 and %2 to be on different partitions").arg(srcDir, destDir))); } KIO::FileCopyJob *job = KIO::file_move(QUrl::fromLocalFile(srcFile), QUrl::fromLocalFile(destFile), -1, KIO::HideProgressInfo | KIO::Overwrite); job->setUiDelegate(nullptr); QSignalSpy spyTotalSize(job, &KIO::FileCopyJob::totalSize); connect(job, &KIO::FileCopyJob::totalSize, this, [destFileExists, destPartFile](KJob *job, qulonglong totalSize) { Q_UNUSED(job); if (totalSize > 0) { QCOMPARE(destFileExists, QFile::exists(destPartFile)); } }); QVERIFY2(job->exec(), qPrintable(job->errorString())); QVERIFY(QFile::exists(destFile)); QVERIFY(!QFile::exists(srcFile)); QVERIFY(!QFile::exists(destPartFile)); QCOMPARE(spyTotalSize.count(), 1); QDir(srcDir).removeRecursively(); QDir(destDir).removeRecursively(); } 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); QVERIFY(!QFile::exists(sourceFile)); // it was moved // Now same thing when the target is a symlink to another file createTestFile(sourceFile); createTestFile(sourceFile + QLatin1Char('2')); createTestSymlink(existingDest, QFile::encodeName(sourceFile + QLatin1Char('2'))); QVERIFY(QFile::exists(existingDest)); job = KIO::file_move(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite); job->setUiDelegate(nullptr); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY(!job->exec()); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(symlinkJob->exec(), qPrintable(symlinkJob->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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); QVERIFY2(job->exec(), qPrintable(job->errorString())); QVERIFY(QFileInfo(dest).isSymLink()); // Changing the link target, with overwrite, works job = KIO::linkAs(QUrl::fromLocalFile(sourceFile + QLatin1Char('2')), QUrl::fromLocalFile(dest), KIO::Overwrite | KIO::HideProgressInfo); QVERIFY2(job->exec(), qPrintable(job->errorString())); QVERIFY(QFileInfo(dest).isSymLink()); QCOMPARE(QFileInfo(dest).symLinkTarget(), QString(sourceFile + QLatin1Char('2'))); // Changing the link target using KIO::symlink, with overwrite, works KIO::Job *symlinkJob = KIO::symlink(sourceFile + QLatin1Char('3'), QUrl::fromLocalFile(dest), KIO::Overwrite | KIO::HideProgressInfo); QVERIFY2(symlinkJob->exec(), qPrintable(symlinkJob->errorString())); QVERIFY(QFileInfo(dest).isSymLink()); QCOMPARE(QFileInfo(dest).symLinkTarget(), QString(sourceFile + QLatin1Char('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); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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";}); QVERIFY2(job->exec(), qPrintable(job->errorString())); 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("")); } } void JobTest::cancelCopyAndCleanDest_data() { QTest::addColumn("suspend"); QTest::addColumn("overwrite"); QTest::newRow("suspend_no_overwrite") << true << false; QTest::newRow("no_suspend_no_overwrite") << false << false; #ifndef Q_OS_WIN QTest::newRow("suspend_with_overwrite") << true << true; QTest::newRow("no_suspend_with_overwrite") << false << true; #endif } void JobTest::cancelCopyAndCleanDest() { QFETCH(bool, suspend); QFETCH(bool, overwrite); const QString baseDir = homeTmpDir(); const QString srcTemplate = baseDir + QStringLiteral("testfile_XXXXXX"); const QString destFile = baseDir + QStringLiteral("testfile_copy"); QTemporaryFile f(srcTemplate); if (!f.open()) { qFatal("Couldn't open %s", qPrintable(f.fileName())); } f.seek(9999999); f.write("0"); f.close(); QCOMPARE(f.size(), 10000000); //~10MB if (overwrite) { createTestFile(destFile); } KIO::JobFlag m_overwriteFlag = overwrite ? KIO::Overwrite : KIO::DefaultFlags; KIO::FileCopyJob *copyJob = KIO::file_copy(QUrl::fromLocalFile(f.fileName()), QUrl::fromLocalFile(destFile), -1, KIO::HideProgressInfo | m_overwriteFlag); copyJob->setUiDelegate(nullptr); QSignalSpy spyProcessedSize(copyJob, &KIO::Job::processedSize); connect(copyJob, &KIO::Job::processedSize, this, [destFile, suspend, overwrite](KJob *job, qulonglong processedSize) { if (processedSize > 0) { const QString destToCheck = (!overwrite) ? destFile : destFile + QStringLiteral(".part"); QVERIFY2(QFile::exists(destToCheck), qPrintable(destToCheck)); if (suspend) { job->suspend(); } job->kill(); QVERIFY(!QFile::exists(destToCheck)); } }); QVERIFY(!copyJob->exec()); QCOMPARE(spyProcessedSize.count(), 1); // Give time to the kioslave to finish copy() and warn about chown/chmod failing (because FileCopyJob::doKill removed the file) // Less confusing if the warnings show here. QTest::qWait(500); QVERIFY(!QFile::exists(destFile)); } diff --git a/src/core/job_p.h b/src/core/job_p.h index ee616268..d76fc05a 100644 --- a/src/core/job_p.h +++ b/src/core/job_p.h @@ -1,397 +1,388 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 2000-2009 David Faure Waldo Bastian Copyright (C) 2007 Thiago Macieira 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_JOB_P_H #define KIO_JOB_P_H #include "simplejob.h" #include "transferjob.h" #include "commands_p.h" #include "kjobtrackerinterface.h" #include #include #include #include #include #include "kiocoredebug.h" #include "global.h" #define KIO_ARGS QByteArray packedArgs; QDataStream stream( &packedArgs, QIODevice::WriteOnly ); stream namespace KIO { class Slave; // Exported for KIOWidgets jobs class KIOCORE_EXPORT JobPrivate { public: JobPrivate() : m_parentJob(nullptr), m_extraFlags(0), m_uiDelegateExtension(KIO::defaultJobUiDelegateExtension()), m_privilegeExecutionEnabled(false) { } virtual ~JobPrivate(); /** * Some extra storage space for jobs that don't have their own * private d pointer. */ enum { EF_TransferJobAsync = (1 << 0), EF_TransferJobNeedData = (1 << 1), EF_TransferJobDataSent = (1 << 2), EF_ListJobUnrestricted = (1 << 3), EF_KillCalled = (1 << 4) }; enum FileOperationType { ChangeAttr, // chmod(), chown(), setModificationTime() Copy, Delete, MkDir, Move, Rename, Symlink, Transfer, // put() and get() Other // if other file operation set message, caption inside the job. }; // Maybe we could use the QObject parent/child mechanism instead // (requires a new ctor, and moving the ctor code to some init()). Job *m_parentJob; int m_extraFlags; MetaData m_incomingMetaData; MetaData m_internalMetaData; MetaData m_outgoingMetaData; JobUiDelegateExtension *m_uiDelegateExtension; Job *q_ptr; // For privilege operation bool m_privilegeExecutionEnabled; QString m_caption, m_message; FileOperationType m_operationType; QByteArray privilegeOperationData(); void slotSpeed(KJob *job, unsigned long speed); static void emitMoving(KIO::Job *, const QUrl &src, const QUrl &dest); static void emitCopying(KIO::Job *, const QUrl &src, const QUrl &dest); static void emitCreatingDir(KIO::Job *, const QUrl &dir); static void emitDeleting(KIO::Job *, const QUrl &url); static void emitStating(KIO::Job *, const QUrl &url); static void emitTransferring(KIO::Job *, const QUrl &url); static void emitMounting(KIO::Job *, const QString &dev, const QString &point); static void emitUnmounting(KIO::Job *, const QString &point); Q_DECLARE_PUBLIC(Job) }; class SimpleJobPrivate: public JobPrivate { public: /** * Creates a new simple job. * @param url the url of the job * @param command the command of the job * @param packedArgs the arguments */ SimpleJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs) : m_slave(nullptr), m_packedArgs(packedArgs), m_url(url), m_command(command), m_checkOnHold(false), m_schedSerial(0), m_redirectionHandlingEnabled(true) { -#if 0 - if (m_url.hasSubUrl()) { - QList list = KUrl::split(m_url); - list.removeLast(); - m_subUrl = KUrl::join(list); - //qDebug() << "New URL = " << m_url.url(); - //qDebug() << "Sub URL = " << m_subUrl.url(); - } -#endif } Slave *m_slave; QByteArray m_packedArgs; QUrl m_url; QUrl m_subUrl; int m_command; // for use in KIO::Scheduler // // There are two kinds of protocol: // (1) The protocol of the url // (2) The actual protocol that the io-slave uses. // // These two often match, but not necessarily. Most notably, they don't // match when doing ftp via a proxy. // In that case (1) is ftp, but (2) is http. // // JobData::protocol stores (2) while Job::url().protocol() returns (1). // The ProtocolInfoDict is indexed with (2). // // We schedule slaves based on (2) but tell the slave about (1) via // Slave::setProtocol(). QString m_protocol; QStringList m_proxyList; bool m_checkOnHold; int m_schedSerial; bool m_redirectionHandlingEnabled; void simpleJobInit(); /** * Called on a slave's connected signal. * @see connected() */ void slotConnected(); /** * Forward signal from the slave. * @param data_size the processed size in bytes * @see processedSize() */ void slotProcessedSize(KIO::filesize_t data_size); /** * Forward signal from the slave. * @param speed the speed in bytes/s * @see speed() */ void slotSpeed(unsigned long speed); /** * Forward signal from the slave * Can also be called by the parent job, when it knows the size. * @param data_size the total size */ void slotTotalSize(KIO::filesize_t data_size); /** * Called on a slave's info message. * @param s the info message * @see infoMessage() */ void _k_slotSlaveInfoMessage(const QString &s); /** * Called when privilegeOperationRequested() is emitted by slave. */ void slotPrivilegeOperationRequested(); /** * @internal * Called by the scheduler when a slave gets to * work on this job. **/ virtual void start(KIO::Slave *slave); /** * @internal * Called to detach a slave from a job. **/ void slaveDone(); /** * Called by subclasses to restart the job after a redirection was signalled. * The m_redirectionURL data member can appear in several subclasses, so we have it * passed in. The regular URL will be set to the redirection URL which is then cleared. */ void restartAfterRedirection(QUrl *redirectionUrl); /** * Request the ui delegate to show a message box. * @internal */ int requestMessageBox(int 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_DECLARE_PUBLIC(SimpleJob) static inline SimpleJobPrivate *get(KIO::SimpleJob *job) { return job->d_func(); } static inline SimpleJob *newJobNoUi(const QUrl &url, int command, const QByteArray &packedArgs) { SimpleJob *job = new SimpleJob(*new SimpleJobPrivate(url, command, packedArgs)); return job; } static inline SimpleJob *newJob(const QUrl &url, int command, const QByteArray &packedArgs, JobFlags flags = HideProgressInfo) { SimpleJob *job = new SimpleJob(*new SimpleJobPrivate(url, command, packedArgs)); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); if (!(flags & HideProgressInfo)) { KIO::getJobTracker()->registerJob(job); } if (!(flags & NoPrivilegeExecution)) { job->d_func()->m_privilegeExecutionEnabled = true; // Only delete, rename and symlink operation accept JobFlags. FileOperationType opType; switch (command) { case CMD_DEL: opType = Delete; break; case CMD_RENAME: opType = Rename; break; case CMD_SYMLINK: opType = Symlink; break; default: return job; } job->d_func()->m_operationType = opType; } return job; } }; class TransferJobPrivate: public SimpleJobPrivate { public: inline TransferJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs, const QByteArray &_staticData) : SimpleJobPrivate(url, command, packedArgs), m_internalSuspended(false), m_errorPage(false), staticData(_staticData), m_isMimetypeEmitted(false), m_closedBeforeStart(false), m_subJob(nullptr) { } inline TransferJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs, QIODevice *ioDevice) : SimpleJobPrivate(url, command, packedArgs), m_internalSuspended(false), m_errorPage(false), m_isMimetypeEmitted(false), m_closedBeforeStart(false), m_subJob(nullptr), m_outgoingDataSource(QPointer(ioDevice)) { } bool m_internalSuspended; bool m_errorPage; QByteArray staticData; QUrl m_redirectionURL; QList m_redirectionList; QString m_mimetype; bool m_isMimetypeEmitted; bool m_closedBeforeStart; TransferJob *m_subJob; QPointer m_outgoingDataSource; /** * Flow control. Suspend data processing from the slave. */ void internalSuspend(); /** * Flow control. Resume data processing from the slave. */ void internalResume(); /** * @internal * Called by the scheduler when a slave gets to * work on this job. * @param slave the slave that works on the job */ void start(KIO::Slave *slave) override; /** * @internal * Called when the ioslave needs the data to send the server. This slot * is invoked when the data is to be sent is read from a QIODevice rather * instead of a QByteArray buffer. */ virtual void slotDataReqFromDevice(); void slotIODeviceClosed(); void slotIODeviceClosedBeforeStart(); void slotPostRedirection(); void slotNeedSubUrlData(); void slotSubUrlData(KIO::Job *, const QByteArray &); Q_DECLARE_PUBLIC(TransferJob) static inline TransferJob *newJob(const QUrl &url, int command, const QByteArray &packedArgs, const QByteArray &_staticData, JobFlags flags) { TransferJob *job = new TransferJob(*new TransferJobPrivate(url, command, packedArgs, _staticData)); 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 = Transfer; } return job; } static inline TransferJob *newJob(const QUrl &url, int command, const QByteArray &packedArgs, QIODevice *ioDevice, JobFlags flags) { TransferJob *job = new TransferJob(*new TransferJobPrivate(url, command, packedArgs, ioDevice)); 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 = Transfer; } return job; } }; class DirectCopyJobPrivate; /** * @internal * Used for direct copy from or to the local filesystem (i.e. SlaveBase::copy()) */ class DirectCopyJob : public SimpleJob { Q_OBJECT public: DirectCopyJob(const QUrl &url, const QByteArray &packedArgs); ~DirectCopyJob(); public Q_SLOTS: void slotCanResume(KIO::filesize_t offset); Q_SIGNALS: /** * @internal * Emitted if the job found an existing partial file * and supports resuming. Used by FileCopyJob. */ void canResume(KIO::Job *job, KIO::filesize_t offset); private: Q_DECLARE_PRIVATE(DirectCopyJob) }; } #endif diff --git a/src/core/kprotocolmanager.cpp b/src/core/kprotocolmanager.cpp index cfc81e0e..e4c9f72b 100644 --- a/src/core/kprotocolmanager.cpp +++ b/src/core/kprotocolmanager.cpp @@ -1,1326 +1,1319 @@ /* This file is part of the KDE libraries Copyright (C) 1999 Torben Weis Copyright (C) 2000- Waldo Bastain Copyright (C) 2000- Dawit Alemayehu Copyright (C) 2008 Jarosław Staniek 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 "kprotocolmanager.h" #include "kprotocolinfo_p.h" #include "hostinfo.h" #include #include #include #ifdef Q_OS_WIN #include #undef interface //windows.h defines this, breaks QtDBus since it has parameters named interface #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #if !defined(QT_NO_NETWORKPROXY) && (defined (Q_OS_WIN32) || defined(Q_OS_MAC)) #include #include #endif #include #include #include #include #include #include "slaveconfig.h" #include "ioslave_defaults.h" #include "http_slave_defaults.h" #define QL1S(x) QLatin1String(x) #define QL1C(x) QLatin1Char(x) typedef QPair SubnetPair; /* Domain suffix match. E.g. return true if host is "cuzco.inka.de" and nplist is "inka.de,hadiko.de" or if host is "localhost" and nplist is "localhost". */ static bool revmatch(const char *host, const char *nplist) { if (host == nullptr) { return false; } const char *hptr = host + strlen(host) - 1; const char *nptr = nplist + strlen(nplist) - 1; const char *shptr = hptr; while (nptr >= nplist) { if (*hptr != *nptr) { hptr = shptr; // Try to find another domain or host in the list while (--nptr >= nplist && *nptr != ',' && *nptr != ' '); // Strip out multiple spaces and commas while (--nptr >= nplist && (*nptr == ',' || *nptr == ' ')); } else { if (nptr == nplist || nptr[-1] == ',' || nptr[-1] == ' ') { return true; } if (nptr[-1] == '/' && hptr == host) { // "bugs.kde.org" vs "http://bugs.kde.org", the config UI says URLs are ok return true; } if (hptr == host) { // e.g. revmatch("bugs.kde.org","mybugs.kde.org") return false; } hptr--; nptr--; } } return false; } class KProxyData : public QObject { Q_OBJECT public: KProxyData(const QString &slaveProtocol, const QStringList &proxyAddresses) : protocol(slaveProtocol) , proxyList(proxyAddresses) { } void removeAddress(const QString &address) { proxyList.removeAll(address); } QString protocol; QStringList proxyList; }; class KProtocolManagerPrivate { public: KProtocolManagerPrivate(); ~KProtocolManagerPrivate(); bool shouldIgnoreProxyFor(const QUrl &url); void sync(); KProtocolManager::ProxyType proxyType(); bool useReverseProxy(); QString readNoProxyFor(); QString proxyFor(const QString &protocol); QStringList getSystemProxyFor(const QUrl &url); QMutex mutex; // protects all member vars KSharedConfig::Ptr configPtr; KSharedConfig::Ptr http_config; QString modifiers; QString useragent; QString noProxyFor; QList noProxySubnets; QCache cachedProxyData; QMap protocolForArchiveMimetypes; }; Q_GLOBAL_STATIC(KProtocolManagerPrivate, kProtocolManagerPrivate) static void syncOnExit() { if (kProtocolManagerPrivate.exists()) kProtocolManagerPrivate()->sync(); } KProtocolManagerPrivate::KProtocolManagerPrivate() { // post routine since KConfig::sync() breaks if called too late qAddPostRoutine(syncOnExit); cachedProxyData.setMaxCost(200); // double the max cost. } KProtocolManagerPrivate::~KProtocolManagerPrivate() { } /* * Returns true if url is in the no proxy list. */ bool KProtocolManagerPrivate::shouldIgnoreProxyFor(const QUrl &url) { bool isMatch = false; const KProtocolManager::ProxyType type = proxyType(); const bool useRevProxy = ((type == KProtocolManager::ManualProxy) && useReverseProxy()); const bool useNoProxyList = (type == KProtocolManager::ManualProxy || type == KProtocolManager::EnvVarProxy); // No proxy only applies to ManualProxy and EnvVarProxy types... if (useNoProxyList && noProxyFor.isEmpty()) { QStringList noProxyForList(readNoProxyFor().split(QL1C(','))); QMutableStringListIterator it(noProxyForList); while (it.hasNext()) { SubnetPair subnet = QHostAddress::parseSubnet(it.next()); if (!subnet.first.isNull()) { noProxySubnets << subnet; it.remove(); } } noProxyFor = noProxyForList.join(QLatin1Char(',')); } if (!noProxyFor.isEmpty()) { QString qhost = url.host().toLower(); QByteArray host = qhost.toLatin1(); const QString qno_proxy = noProxyFor.trimmed().toLower(); const QByteArray no_proxy = qno_proxy.toLatin1(); isMatch = revmatch(host.constData(), no_proxy.constData()); // If no match is found and the request url has a port // number, try the combination of "host:port". This allows // users to enter host:port in the No-proxy-For list. if (!isMatch && url.port() > 0) { qhost += QL1C(':') + QString::number(url.port()); host = qhost.toLatin1(); isMatch = revmatch(host.constData(), no_proxy.constData()); } // If the hostname does not contain a dot, check if // is part of noProxy. if (!isMatch && !host.isEmpty() && (strchr(host.constData(), '.') == nullptr)) { isMatch = revmatch("", no_proxy.constData()); } } const QString host(url.host()); if (!noProxySubnets.isEmpty() && !host.isEmpty()) { QHostAddress address(host); // If request url is not IP address, do a DNS lookup of the hostname. // TODO: Perhaps we should make configurable ? if (address.isNull()) { //qDebug() << "Performing DNS lookup for" << host; QHostInfo info = KIO::HostInfo::lookupHost(host, 2000); const QList addresses = info.addresses(); if (!addresses.isEmpty()) { address = addresses.first(); } } if (!address.isNull()) { for (const SubnetPair &subnet : qAsConst(noProxySubnets)) { if (address.isInSubnet(subnet)) { isMatch = true; break; } } } } return (useRevProxy != isMatch); } void KProtocolManagerPrivate::sync() { QMutexLocker lock(&mutex); if (http_config) { http_config->sync(); } if (configPtr) { configPtr->sync(); } } #define PRIVATE_DATA \ KProtocolManagerPrivate *d = kProtocolManagerPrivate() void KProtocolManager::reparseConfiguration() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); if (d->http_config) { d->http_config->reparseConfiguration(); } if (d->configPtr) { d->configPtr->reparseConfiguration(); } d->cachedProxyData.clear(); d->noProxyFor.clear(); d->modifiers.clear(); d->useragent.clear(); lock.unlock(); // Force the slave config to re-read its config... KIO::SlaveConfig::self()->reset(); } static KSharedConfig::Ptr config() { PRIVATE_DATA; Q_ASSERT(!d->mutex.tryLock()); // the caller must have locked the mutex if (!d->configPtr) { d->configPtr = KSharedConfig::openConfig(QStringLiteral("kioslaverc"), KConfig::NoGlobals); } return d->configPtr; } KProtocolManager::ProxyType KProtocolManagerPrivate::proxyType() { KConfigGroup cg(config(), "Proxy Settings"); return static_cast(cg.readEntry("ProxyType", 0)); } bool KProtocolManagerPrivate::useReverseProxy() { KConfigGroup cg(config(), "Proxy Settings"); return cg.readEntry("ReversedException", false); } QString KProtocolManagerPrivate::readNoProxyFor() { QString noProxy = config()->group("Proxy Settings").readEntry("NoProxyFor"); if (proxyType() == KProtocolManager::EnvVarProxy) { noProxy = QString::fromLocal8Bit(qgetenv(noProxy.toLocal8Bit().constData())); } return noProxy; } QMap KProtocolManager::entryMap(const QString &group) { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return config()->entryMap(group); } static KConfigGroup http_config() { PRIVATE_DATA; Q_ASSERT(!d->mutex.tryLock()); // the caller must have locked the mutex if (!d->http_config) { d->http_config = KSharedConfig::openConfig(QStringLiteral("kio_httprc"), KConfig::NoGlobals); } return KConfigGroup(d->http_config, QString()); } /*=============================== TIMEOUT SETTINGS ==========================*/ int KProtocolManager::readTimeout() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); KConfigGroup cg(config(), QString()); int val = cg.readEntry("ReadTimeout", DEFAULT_READ_TIMEOUT); return qMax(MIN_TIMEOUT_VALUE, val); } int KProtocolManager::connectTimeout() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); KConfigGroup cg(config(), QString()); int val = cg.readEntry("ConnectTimeout", DEFAULT_CONNECT_TIMEOUT); return qMax(MIN_TIMEOUT_VALUE, val); } int KProtocolManager::proxyConnectTimeout() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); KConfigGroup cg(config(), QString()); int val = cg.readEntry("ProxyConnectTimeout", DEFAULT_PROXY_CONNECT_TIMEOUT); return qMax(MIN_TIMEOUT_VALUE, val); } int KProtocolManager::responseTimeout() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); KConfigGroup cg(config(), QString()); int val = cg.readEntry("ResponseTimeout", DEFAULT_RESPONSE_TIMEOUT); return qMax(MIN_TIMEOUT_VALUE, val); } /*========================== PROXY SETTINGS =================================*/ bool KProtocolManager::useProxy() { return proxyType() != NoProxy; } bool KProtocolManager::useReverseProxy() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return d->useReverseProxy(); } KProtocolManager::ProxyType KProtocolManager::proxyType() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return d->proxyType(); } KProtocolManager::ProxyAuthMode KProtocolManager::proxyAuthMode() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); KConfigGroup cg(config(), "Proxy Settings"); return static_cast(cg.readEntry("AuthMode", 0)); } /*========================== CACHING =====================================*/ bool KProtocolManager::useCache() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return http_config().readEntry("UseCache", true); } KIO::CacheControl KProtocolManager::cacheControl() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); QString tmp = http_config().readEntry("cache"); if (tmp.isEmpty()) { return DEFAULT_CACHE_CONTROL; } return KIO::parseCacheControl(tmp); } QString KProtocolManager::cacheDir() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return http_config().readPathEntry("CacheDir", QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/kio_http")); } int KProtocolManager::maxCacheAge() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return http_config().readEntry("MaxCacheAge", DEFAULT_MAX_CACHE_AGE); } int KProtocolManager::maxCacheSize() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return http_config().readEntry("MaxCacheSize", DEFAULT_MAX_CACHE_SIZE); } QString KProtocolManager::noProxyFor() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return d->readNoProxyFor(); } static QString adjustProtocol(const QString &scheme) { if (scheme.compare(QL1S("webdav"), Qt::CaseInsensitive) == 0) { return QStringLiteral("http"); } if (scheme.compare(QL1S("webdavs"), Qt::CaseInsensitive) == 0) { return QStringLiteral("https"); } return scheme.toLower(); } QString KProtocolManager::proxyFor(const QString &protocol) { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return d->proxyFor(protocol); } QString KProtocolManagerPrivate::proxyFor(const QString &protocol) { const QString key = adjustProtocol(protocol) + QL1S("Proxy"); QString proxyStr(config()->group("Proxy Settings").readEntry(key)); const int index = proxyStr.lastIndexOf(QL1C(' ')); if (index > -1) { bool ok = false; const QStringRef portStr(proxyStr.rightRef(proxyStr.length() - index - 1)); portStr.toInt(&ok); if (ok) { proxyStr = proxyStr.leftRef(index) + QL1C(':') + portStr; } else { proxyStr.clear(); } } return proxyStr; } QString KProtocolManager::proxyForUrl(const QUrl &url) { const QStringList proxies = proxiesForUrl(url); if (proxies.isEmpty()) { return QString(); } return proxies.first(); } QStringList KProtocolManagerPrivate::getSystemProxyFor(const QUrl &url) { QStringList proxies; #if !defined(QT_NO_NETWORKPROXY) && (defined(Q_OS_WIN32) || defined(Q_OS_MAC)) QNetworkProxyQuery query(url); const QList proxyList = QNetworkProxyFactory::systemProxyForQuery(query); proxies.reserve(proxyList.size()); for (const QNetworkProxy &proxy : proxyList) { QUrl url; const QNetworkProxy::ProxyType type = proxy.type(); if (type == QNetworkProxy::NoProxy || type == QNetworkProxy::DefaultProxy) { proxies << QL1S("DIRECT"); continue; } if (type == QNetworkProxy::HttpProxy || type == QNetworkProxy::HttpCachingProxy) { url.setScheme(QL1S("http")); } else if (type == QNetworkProxy::Socks5Proxy) { url.setScheme(QL1S("socks")); } else if (type == QNetworkProxy::FtpCachingProxy) { url.setScheme(QL1S("ftp")); } url.setHost(proxy.hostName()); url.setPort(proxy.port()); url.setUserName(proxy.user()); proxies << url.url(); } #else // On Unix/Linux use system environment variables if any are set. QString proxyVar(proxyFor(url.scheme())); // Check for SOCKS proxy, if not proxy is found for given url. if (!proxyVar.isEmpty()) { const QString proxy(QString::fromLocal8Bit(qgetenv(proxyVar.toLocal8Bit().constData())).trimmed()); if (!proxy.isEmpty()) { proxies << proxy; } } // Add the socks proxy as an alternate proxy if it exists, proxyVar = proxyFor(QStringLiteral("socks")); if (!proxyVar.isEmpty()) { QString proxy = QString::fromLocal8Bit(qgetenv(proxyVar.toLocal8Bit().constData())).trimmed(); // Make sure the scheme of SOCKS proxy is always set to "socks://". const int index = proxy.indexOf(QL1S("://")); const int offset = (index == -1) ? 0 : (index + 3); proxy = QL1S("socks://") + proxy.midRef(offset); if (!proxy.isEmpty()) { proxies << proxy; } } #endif return proxies; } QStringList KProtocolManager::proxiesForUrl(const QUrl &url) { QStringList proxyList; PRIVATE_DATA; QMutexLocker lock(&d->mutex); if (!d->shouldIgnoreProxyFor(url)) { switch (d->proxyType()) { case PACProxy: case WPADProxy: { QUrl u(url); const QString protocol = adjustProtocol(u.scheme()); u.setScheme(protocol); if (protocol.startsWith(QLatin1String("http")) || protocol.startsWith(QLatin1String("ftp"))) { QDBusReply reply = QDBusInterface(QStringLiteral("org.kde.kded5"), QStringLiteral("/modules/proxyscout"), QStringLiteral("org.kde.KPAC.ProxyScout")) .call(QStringLiteral("proxiesForUrl"), u.toString()); proxyList = reply; } break; } case EnvVarProxy: proxyList = d->getSystemProxyFor(url); break; case ManualProxy: { QString proxy(d->proxyFor(url.scheme())); if (!proxy.isEmpty()) { proxyList << proxy; } // Add the socks proxy as an alternate proxy if it exists, proxy = d->proxyFor(QStringLiteral("socks")); if (!proxy.isEmpty()) { // Make sure the scheme of SOCKS proxy is always set to "socks://". const int index = proxy.indexOf(QL1S("://")); const int offset = (index == -1) ? 0 : (index + 3); proxy = QL1S("socks://") + proxy.midRef(offset); proxyList << proxy; } } break; case NoProxy: break; } } if (proxyList.isEmpty()) { proxyList << QStringLiteral("DIRECT"); } return proxyList; } void KProtocolManager::badProxy(const QString &proxy) { QDBusInterface(QStringLiteral("org.kde.kded5"), QStringLiteral("/modules/proxyscout")) .asyncCall(QStringLiteral("blackListProxy"), proxy); PRIVATE_DATA; QMutexLocker lock(&d->mutex); const QStringList keys(d->cachedProxyData.keys()); for (const QString &key : keys) { d->cachedProxyData[key]->removeAddress(proxy); } } QString KProtocolManager::slaveProtocol(const QUrl &url, QString &proxy) { QStringList proxyList; const QString protocol = KProtocolManager::slaveProtocol(url, proxyList); if (!proxyList.isEmpty()) { proxy = proxyList.first(); } return protocol; } // Generates proxy cache key from request given url. static void extractProxyCacheKeyFromUrl(const QUrl &u, QString *key) { if (!key) { return; } *key = u.scheme(); *key += u.host(); if (u.port() > 0) { *key += QString::number(u.port()); } } QString KProtocolManager::slaveProtocol(const QUrl &url, QStringList &proxyList) { -#if 0 - if (url.hasSubUrl()) { // We don't want the suburl's protocol - const QUrl::List list = QUrl::split(url); - return slaveProtocol(list.last(), proxyList); - } -#endif - proxyList.clear(); // Do not perform a proxy lookup for any url classified as a ":local" url or // one that does not have a host component or if proxy is disabled. QString protocol(url.scheme()); if (url.host().isEmpty() || KProtocolInfo::protocolClass(protocol) == QL1S(":local") || KProtocolManager::proxyType() == KProtocolManager::NoProxy) { return protocol; } QString proxyCacheKey; extractProxyCacheKeyFromUrl(url, &proxyCacheKey); PRIVATE_DATA; QMutexLocker lock(&d->mutex); // Look for cached proxy information to avoid more work. if (d->cachedProxyData.contains(proxyCacheKey)) { KProxyData *data = d->cachedProxyData.object(proxyCacheKey); proxyList = data->proxyList; return data->protocol; } lock.unlock(); const QStringList proxies = proxiesForUrl(url); const int count = proxies.count(); if (count > 0 && !(count == 1 && proxies.first() == QL1S("DIRECT"))) { for (const QString &proxy : proxies) { if (proxy == QL1S("DIRECT")) { proxyList << proxy; } else { QUrl u(proxy); if (!u.isEmpty() && u.isValid() && !u.scheme().isEmpty()) { proxyList << proxy; } } } } // The idea behind slave protocols is not applicable to http // and webdav protocols as well as protocols unknown to KDE. if (!proxyList.isEmpty() && !protocol.startsWith(QLatin1String("http")) && !protocol.startsWith(QLatin1String("webdav")) && KProtocolInfo::isKnownProtocol(protocol)) { for (const QString &proxy : qAsConst(proxyList)) { QUrl u(proxy); if (u.isValid() && KProtocolInfo::isKnownProtocol(u.scheme())) { protocol = u.scheme(); break; } } } lock.relock(); // cache the proxy information... d->cachedProxyData.insert(proxyCacheKey, new KProxyData(protocol, proxyList)); return protocol; } /*================================= USER-AGENT SETTINGS =====================*/ QString KProtocolManager::userAgentForHost(const QString &hostname) { const QString sendUserAgent = KIO::SlaveConfig::self()->configData(QStringLiteral("http"), hostname.toLower(), QStringLiteral("SendUserAgent")).toLower(); if (sendUserAgent == QL1S("false")) { return QString(); } const QString useragent = KIO::SlaveConfig::self()->configData(QStringLiteral("http"), hostname.toLower(), QStringLiteral("UserAgent")); // Return the default user-agent if none is specified // for the requested host. if (useragent.isEmpty()) { return defaultUserAgent(); } return useragent; } QString KProtocolManager::defaultUserAgent() { const QString modifiers = KIO::SlaveConfig::self()->configData(QStringLiteral("http"), QString(), QStringLiteral("UserAgentKeys")); return defaultUserAgent(modifiers); } static QString defaultUserAgentFromPreferredService() { QString agentStr; // Check if the default COMPONENT contains a custom default UA string... KService::Ptr service = KMimeTypeTrader::self()->preferredService(QStringLiteral("text/html"), QStringLiteral("KParts/ReadOnlyPart")); if (service && service->showInCurrentDesktop()) agentStr = service->property(QStringLiteral("X-KDE-Default-UserAgent"), QVariant::String).toString(); return agentStr; } // This is not the OS, but the windowing system, e.g. X11 on Unix/Linux. static QString platform() { #if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) return QStringLiteral("X11"); #elif defined(Q_OS_MAC) return QStringLiteral("Macintosh"); #elif defined(Q_OS_WIN) return QStringLiteral("Windows"); #else return QStringLiteral("Unknown"); #endif } QString KProtocolManager::defaultUserAgent(const QString &_modifiers) { PRIVATE_DATA; QMutexLocker lock(&d->mutex); QString modifiers = _modifiers.toLower(); if (modifiers.isEmpty()) { modifiers = QStringLiteral(DEFAULT_USER_AGENT_KEYS); } if (d->modifiers == modifiers && !d->useragent.isEmpty()) { return d->useragent; } d->modifiers = modifiers; /* The following code attempts to determine the default user agent string from the 'X-KDE-UA-DEFAULT-STRING' property of the desktop file for the preferred service that was configured to handle the 'text/html' mime type. If the preferred service's desktop file does not specify this property, the long standing default user agent string will be used. The following keyword placeholders are automatically converted when the user agent string is read from the property: %SECURITY% Expands to"N" when SSL is not supported, otherwise it is ignored. %OSNAME% Expands to operating system name, e.g. Linux. %OSVERSION% Expands to operating system version, e.g. 2.6.32 %SYSTYPE% Expands to machine or system type, e.g. i386 %PLATFORM% Expands to windowing system, e.g. X11 on Unix/Linux. %LANGUAGE% Expands to default language in use, e.g. en-US. %APPVERSION% Expands to QCoreApplication applicationName()/applicationVerison(), e.g. Konqueror/4.5.0. If application name and/or application version number are not set, then "KDE" and the runtime KDE version numbers are used respectively. All of the keywords are handled case-insensitively. */ QString systemName, systemVersion, machine, supp; const bool sysInfoFound = getSystemNameVersionAndMachine(systemName, systemVersion, machine); QString agentStr = defaultUserAgentFromPreferredService(); if (agentStr.isEmpty()) { supp += platform(); if (sysInfoFound) { if (modifiers.contains(QL1C('o'))) { supp += QL1S("; ") + systemName; if (modifiers.contains(QL1C('v'))) { supp += QL1C(' ') + systemVersion; } if (modifiers.contains(QL1C('m'))) { supp += QL1C(' ') + machine; } } if (modifiers.contains(QL1C('l'))) { supp += QL1S("; ") + QLocale::languageToString(QLocale().language()); } } // Full format: Mozilla/5.0 (Linux d->useragent = QL1S("Mozilla/5.0 (") + supp + QL1S(") KHTML/") + QString::number(KIO_VERSION_MAJOR) + QL1C('.') + QString::number(KIO_VERSION_MINOR) + QL1C('.') + QString::number(KIO_VERSION_PATCH) + QL1S(" (like Gecko) Konqueror/") + QString::number(KIO_VERSION_MAJOR) + QL1S(" KIO/") + QString::number(KIO_VERSION_MAJOR) + QL1C('.') + QString::number(KIO_VERSION_MINOR); } else { QString appName = QCoreApplication::applicationName(); if (appName.isEmpty() || appName.startsWith(QLatin1String("kcmshell"), Qt::CaseInsensitive)) { appName = QStringLiteral("KDE"); } QString appVersion = QCoreApplication::applicationVersion(); if (appVersion.isEmpty()) { appVersion += QString::number(KIO_VERSION_MAJOR) + QL1C('.') + QString::number(KIO_VERSION_MINOR) + QL1C('.') + QString::number(KIO_VERSION_PATCH); } appName += QL1C('/') + appVersion; agentStr.replace(QL1S("%appversion%"), appName, Qt::CaseInsensitive); if (!QSslSocket::supportsSsl()) { agentStr.replace(QLatin1String("%security%"), QL1S("N"), Qt::CaseInsensitive); } else { agentStr.remove(QStringLiteral("%security%"), Qt::CaseInsensitive); } if (sysInfoFound) { // Platform (e.g. X11). It is no longer configurable from UI. agentStr.replace(QL1S("%platform%"), platform(), Qt::CaseInsensitive); // Operating system (e.g. Linux) if (modifiers.contains(QL1C('o'))) { agentStr.replace(QL1S("%osname%"), systemName, Qt::CaseInsensitive); // OS version (e.g. 2.6.36) if (modifiers.contains(QL1C('v'))) { agentStr.replace(QL1S("%osversion%"), systemVersion, Qt::CaseInsensitive); } else { agentStr.remove(QStringLiteral("%osversion%"), Qt::CaseInsensitive); } // Machine type (i686, x86-64, etc.) if (modifiers.contains(QL1C('m'))) { agentStr.replace(QL1S("%systype%"), machine, Qt::CaseInsensitive); } else { agentStr.remove(QStringLiteral("%systype%"), Qt::CaseInsensitive); } } else { agentStr.remove(QStringLiteral("%osname%"), Qt::CaseInsensitive); agentStr.remove(QStringLiteral("%osversion%"), Qt::CaseInsensitive); agentStr.remove(QStringLiteral("%systype%"), Qt::CaseInsensitive); } // Language (e.g. en_US) if (modifiers.contains(QL1C('l'))) { agentStr.replace(QL1S("%language%"), QLocale::languageToString(QLocale().language()), Qt::CaseInsensitive); } else { agentStr.remove(QStringLiteral("%language%"), Qt::CaseInsensitive); } // Clean up unnecessary separators that could be left over from the // possible keyword removal above... agentStr.replace(QRegExp(QL1S("[(]\\s*[;]\\s*")), QStringLiteral("(")); agentStr.replace(QRegExp(QL1S("[;]\\s*[;]\\s*")), QStringLiteral("; ")); agentStr.replace(QRegExp(QL1S("\\s*[;]\\s*[)]")), QStringLiteral(")")); } else { agentStr.remove(QStringLiteral("%osname%")); agentStr.remove(QStringLiteral("%osversion%")); agentStr.remove(QStringLiteral("%platform%")); agentStr.remove(QStringLiteral("%systype%")); agentStr.remove(QStringLiteral("%language%")); } d->useragent = agentStr.simplified(); } //qDebug() << "USERAGENT STRING:" << d->useragent; return d->useragent; } QString KProtocolManager::userAgentForApplication(const QString &appName, const QString &appVersion, const QStringList &extraInfo) { QString systemName, systemVersion, machine, info; if (getSystemNameVersionAndMachine(systemName, systemVersion, machine)) { info += systemName + QL1C('/') + systemVersion + QL1S("; "); } info += QL1S("KDE/") + QString::number(KIO_VERSION_MAJOR) + QL1C('.') + QString::number(KIO_VERSION_MINOR) + QL1C('.') + QString::number(KIO_VERSION_PATCH); if (!machine.isEmpty()) { info += QL1S("; ") + machine; } info += QL1S("; ") + extraInfo.join(QLatin1String("; ")); return (appName + QL1C('/') + appVersion + QStringLiteral(" (") + info + QL1C(')')); } bool KProtocolManager::getSystemNameVersionAndMachine( QString &systemName, QString &systemVersion, QString &machine) { #if defined(Q_OS_WIN) && !defined(_WIN32_WCE) // we do not use unameBuf.sysname information constructed in kdewin32 // because we want to get separate name and version systemName = QStringLiteral("Windows"); OSVERSIONINFOEX versioninfo; ZeroMemory(&versioninfo, sizeof(OSVERSIONINFOEX)); // try calling GetVersionEx using the OSVERSIONINFOEX, if that fails, try using the OSVERSIONINFO versioninfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); bool ok = GetVersionEx((OSVERSIONINFO *) &versioninfo); if (!ok) { versioninfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); ok = GetVersionEx((OSVERSIONINFO *) &versioninfo); } if (ok) { systemVersion = QString::number(versioninfo.dwMajorVersion); systemVersion += QL1C('.'); systemVersion += QString::number(versioninfo.dwMinorVersion); } #else struct utsname unameBuf; if (0 != uname(&unameBuf)) { return false; } systemName = QString::fromUtf8(unameBuf.sysname); systemVersion = QString::fromUtf8(unameBuf.release); machine = QString::fromUtf8(unameBuf.machine); #endif return true; } QString KProtocolManager::acceptLanguagesHeader() { const QLatin1String english("en"); // User's desktop language preference. QStringList languageList = QLocale().uiLanguages(); // Replace possible "C" in the language list with "en", unless "en" is // already pressent. This is to keep user's priorities in order. // If afterwards "en" is still not present, append it. int idx = languageList.indexOf(QLatin1String("C")); if (idx != -1) { if (languageList.contains(english)) { languageList.removeAt(idx); } else { languageList[idx] = english; } } if (!languageList.contains(english)) { languageList += english; } // Some languages may have web codes different from locale codes, // read them from the config and insert in proper order. KConfig acclangConf(QStringLiteral("accept-languages.codes"), KConfig::NoGlobals); KConfigGroup replacementCodes(&acclangConf, "ReplacementCodes"); QStringList languageListFinal; for (const QString &lang : qAsConst(languageList)) { const QStringList langs = replacementCodes.readEntry(lang, QStringList()); if (langs.isEmpty()) { languageListFinal += lang; } else { languageListFinal += langs; } } // The header is composed of comma separated languages, with an optional // associated priority estimate (q=1..0) defaulting to 1. // As our language tags are already sorted by priority, we'll just decrease // the value evenly int prio = 10; QString header; for (const QString &lang : qAsConst(languageListFinal)) { header += lang; if (prio < 10) { header += QL1S(";q=0.") + QString::number(prio); } // do not add cosmetic whitespace in here : it is less compatible (#220677) header += QL1C(','); if (prio > 1) { --prio; } } header.chop(1); // Some of the languages may have country specifier delimited by // underscore, or modifier delimited by at-sign. // The header should use dashes instead. header.replace(QL1C('_'), QL1C('-')); header.replace(QL1C('@'), QL1C('-')); return header; } /*==================================== OTHERS ===============================*/ bool KProtocolManager::markPartial() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return config()->group(QByteArray()).readEntry("MarkPartial", true); } int KProtocolManager::minimumKeepSize() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return config()->group(QByteArray()).readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); // 5000 byte } bool KProtocolManager::autoResume() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return config()->group(QByteArray()).readEntry("AutoResume", false); } bool KProtocolManager::persistentConnections() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return config()->group(QByteArray()).readEntry("PersistentConnections", true); } bool KProtocolManager::persistentProxyConnection() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return config()->group(QByteArray()).readEntry("PersistentProxyConnection", false); } QString KProtocolManager::proxyConfigScript() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return config()->group("Proxy Settings").readEntry("Proxy Config Script"); } /* =========================== PROTOCOL CAPABILITIES ============== */ static KProtocolInfoPrivate *findProtocol(const QUrl &url) { if (!url.isValid()) { return nullptr; } QString protocol = url.scheme(); if (!KProtocolInfo::proxiedBy(protocol).isEmpty()) { QString dummy; protocol = KProtocolManager::slaveProtocol(url, dummy); } return KProtocolInfoFactory::self()->findProtocol(protocol); } KProtocolInfo::Type KProtocolManager::inputType(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return KProtocolInfo::T_NONE; } return prot->m_inputType; } KProtocolInfo::Type KProtocolManager::outputType(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return KProtocolInfo::T_NONE; } return prot->m_outputType; } bool KProtocolManager::isSourceProtocol(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_isSourceProtocol; } bool KProtocolManager::supportsListing(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_supportsListing; } QStringList KProtocolManager::listing(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return QStringList(); } return prot->m_listing; } bool KProtocolManager::supportsReading(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_supportsReading; } bool KProtocolManager::supportsWriting(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_supportsWriting; } bool KProtocolManager::supportsMakeDir(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_supportsMakeDir; } bool KProtocolManager::supportsDeleting(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_supportsDeleting; } bool KProtocolManager::supportsLinking(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_supportsLinking; } bool KProtocolManager::supportsMoving(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_supportsMoving; } bool KProtocolManager::supportsOpening(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_supportsOpening; } bool KProtocolManager::canCopyFromFile(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_canCopyFromFile; } bool KProtocolManager::canCopyToFile(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_canCopyToFile; } bool KProtocolManager::canRenameFromFile(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_canRenameFromFile; } bool KProtocolManager::canRenameToFile(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_canRenameToFile; } bool KProtocolManager::canDeleteRecursive(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_canDeleteRecursive; } KProtocolInfo::FileNameUsedForCopying KProtocolManager::fileNameUsedForCopying(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return KProtocolInfo::FromUrl; } return prot->m_fileNameUsedForCopying; } QString KProtocolManager::defaultMimetype(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return QString(); } return prot->m_defaultMimetype; } QString KProtocolManager::protocolForArchiveMimetype(const QString &mimeType) { PRIVATE_DATA; QMutexLocker lock(&d->mutex); if (d->protocolForArchiveMimetypes.isEmpty()) { const QList allProtocols = KProtocolInfoFactory::self()->allProtocols(); for (KProtocolInfoPrivate *allProtocol : allProtocols) { const QStringList archiveMimetypes = allProtocol->m_archiveMimeTypes; for (const QString &mime : archiveMimetypes) { d->protocolForArchiveMimetypes.insert(mime, allProtocol->m_name); } } } return d->protocolForArchiveMimetypes.value(mimeType); } QString KProtocolManager::charsetFor(const QUrl &url) { return KIO::SlaveConfig::self()->configData(url.scheme(), url.host(), QStringLiteral("Charset")); } #undef PRIVATE_DATA #include "kprotocolmanager.moc"