File indexing completed on 2024-12-01 03:40:24

0001 /*
0002     SPDX-FileCopyrightText: 2019 Harald Sitter <sitter@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include <kio/copyjob.h>
0008 #include <kio/storedtransferjob.h>
0009 
0010 #include <QBuffer>
0011 #include <QProcess>
0012 #include <QStandardPaths>
0013 #include <QTest>
0014 
0015 class FTPTest : public QObject
0016 {
0017     Q_OBJECT
0018 public:
0019     QUrl url(const QString &path) const
0020     {
0021         Q_ASSERT(path.startsWith(QChar('/')));
0022         QUrl newUrl = m_url;
0023         newUrl.setPath(path);
0024         return newUrl;
0025     }
0026 
0027     QTemporaryDir m_remoteDir;
0028     QProcess m_daemonProc;
0029     QUrl m_url = QUrl("ftp://localhost");
0030 
0031 private Q_SLOTS:
0032     static void runDaemon(QProcess &proc, QUrl &url, const QTemporaryDir &remoteDir)
0033     {
0034         QVERIFY(remoteDir.isValid());
0035         proc.setProgram(RubyExe_EXECUTABLE);
0036         proc.setArguments({QFINDTESTDATA("ftpd"), QStringLiteral("0"), remoteDir.path()});
0037         proc.setProcessChannelMode(QProcess::ForwardedOutputChannel);
0038         qDebug() << proc.arguments();
0039         proc.start();
0040         QVERIFY(proc.waitForStarted());
0041         QCOMPARE(proc.state(), QProcess::Running);
0042         // Wait for the daemon to print its port. That tells us both where it's listening
0043         // and also that it is ready to move ahead with testing.
0044         QVERIFY(QTest::qWaitFor(
0045             [&]() -> bool {
0046                 const QString err = proc.readAllStandardError();
0047                 if (!err.isEmpty()) {
0048                     qDebug() << "STDERR:" << err;
0049                 }
0050                 if (!err.startsWith("port = ")) {
0051                     return false;
0052                 }
0053                 bool ok = false;
0054                 const int port = err.split(" = ").at(1).toInt(&ok);
0055                 url.setPort(port);
0056                 return ok;
0057             },
0058             8000));
0059     }
0060 
0061     void initTestCase()
0062     {
0063         // Force the ftp worker from our bindir as first choice. This specifically
0064         // works around the fact that the kioworker executable would load the worker from the system
0065         // as first choice instead of the one from the build dir.
0066         qputenv("QT_PLUGIN_PATH", QCoreApplication::applicationDirPath().toUtf8());
0067 
0068         // Run ftpd to talk to.
0069         runDaemon(m_daemonProc, m_url, m_remoteDir);
0070         // Once it's started we can simply forward the output. Possibly should do the
0071         // same for stdout so it has a prefix.
0072         connect(&m_daemonProc, &QProcess::readyReadStandardError, this, [this] {
0073             qDebug() << "ftpd STDERR:" << m_daemonProc.readAllStandardError();
0074         });
0075 
0076         QStandardPaths::setTestModeEnabled(true);
0077     }
0078 
0079     void cleanupTestCase()
0080     {
0081         m_daemonProc.terminate();
0082         m_daemonProc.kill();
0083         m_daemonProc.waitForFinished();
0084     }
0085 
0086     void init()
0087     {
0088         QCOMPARE(m_daemonProc.state(), QProcess::Running);
0089     }
0090 
0091     void testGet()
0092     {
0093         const QString path("/testGet");
0094         const auto url = this->url(path);
0095         const QString remotePath = m_remoteDir.path() + path;
0096 
0097         QByteArray data("testBasicGet");
0098         QFile file(remotePath);
0099         QVERIFY(file.open(QFile::WriteOnly));
0100         file.write(data);
0101         file.close();
0102 
0103         auto job = KIO::storedGet(url);
0104         job->setUiDelegate(nullptr);
0105         QVERIFY2(job->exec(), qUtf8Printable(job->errorString()));
0106         QCOMPARE(job->data(), data);
0107     }
0108 
0109     void testCopy()
0110     {
0111         const QString path("/testCopy");
0112         const auto url = this->url(path);
0113         const QString remotePath = m_remoteDir.path() + path;
0114         const QString partPath = remotePath + ".part";
0115 
0116         QFile::remove(remotePath);
0117         QFile::remove(partPath);
0118 
0119         auto job = KIO::copy({QUrl::fromLocalFile(QFINDTESTDATA("ftp/testCopy1"))}, url, KIO::DefaultFlags);
0120         job->setUiDelegate(nullptr);
0121         QVERIFY2(job->exec(), qUtf8Printable(job->errorString()));
0122         QCOMPARE(job->error(), 0);
0123         QFile file(remotePath);
0124         QVERIFY(file.exists());
0125         QVERIFY(file.open(QFile::ReadOnly));
0126         QCOMPARE(file.readAll(), QByteArray("part1\n"));
0127     }
0128 
0129     void testCopyResume()
0130     {
0131         const QString path("/testCopy");
0132         const auto url = this->url(path);
0133         const QString remotePath = m_remoteDir.path() + path;
0134         const QString partPath = remotePath + ".part";
0135 
0136         QFile::remove(remotePath);
0137         QFile::remove(partPath);
0138         QVERIFY(QFile::copy(QFINDTESTDATA("ftp/testCopy1"), partPath));
0139 
0140         auto job = KIO::copy({QUrl::fromLocalFile(QFINDTESTDATA("ftp/testCopy2"))}, url, KIO::Resume);
0141         job->setUiDelegate(nullptr);
0142         QVERIFY2(job->exec(), qUtf8Printable(job->errorString()));
0143         QCOMPARE(job->error(), 0);
0144         QFile file(remotePath);
0145         QVERIFY(file.exists());
0146         QVERIFY(file.open(QFile::ReadOnly));
0147         QCOMPARE(file.readAll(), QByteArray("part1\npart2\n"));
0148     }
0149 
0150     void testCopyInaccessible()
0151     {
0152         const QString inaccessiblePath("/testCopy.__inaccessiblePath__");
0153         auto inaccessibleUrl = this->url(inaccessiblePath);
0154 
0155         auto job = KIO::copy({QUrl::fromLocalFile(QFINDTESTDATA("ftp/testCopy1"))}, inaccessibleUrl, KIO::Resume);
0156         job->setUiDelegate(nullptr);
0157         QVERIFY(!job->exec());
0158         QCOMPARE(job->error(), KIO::ERR_CANNOT_WRITE);
0159         QFile file(inaccessiblePath);
0160         QVERIFY(!file.exists());
0161     }
0162 
0163     void testCopyBadResume()
0164     {
0165         const QString inaccessiblePath("/testCopy.__badResume__");
0166         auto inaccessibleUrl = this->url(inaccessiblePath);
0167         inaccessibleUrl.setUserInfo("user");
0168         inaccessibleUrl.setPassword("password");
0169         const QString remoteInaccessiblePath = m_remoteDir.path() + inaccessiblePath;
0170         QVERIFY(QFile::copy(QFINDTESTDATA("ftp/testCopy1"), remoteInaccessiblePath + ".part"));
0171 
0172         auto job = KIO::copy({QUrl::fromLocalFile(QFINDTESTDATA("ftp/testCopy2"))}, inaccessibleUrl, KIO::Resume);
0173         job->setUiDelegate(nullptr);
0174         QVERIFY(!job->exec());
0175         QCOMPARE(job->error(), KIO::ERR_CANNOT_WRITE);
0176         QFile file(inaccessiblePath);
0177         QVERIFY(!file.exists());
0178     }
0179 
0180     void testOverwriteCopy()
0181     {
0182         const QString path("/testOverwriteCopy");
0183         const auto url = this->url(path);
0184 
0185         qDebug() << (m_remoteDir.path() + path);
0186         QVERIFY(QFile::copy(QFINDTESTDATA("ftp/testOverwriteCopy1"), m_remoteDir.path() + path));
0187 
0188         // File already exists, we expect it to be overwritten.
0189         auto job = KIO::copy({QUrl::fromLocalFile(QFINDTESTDATA("ftp/testOverwriteCopy2"))}, url, KIO::Overwrite);
0190         job->setUiDelegate(nullptr);
0191         QVERIFY2(job->exec(), qUtf8Printable(job->errorString()));
0192         QCOMPARE(job->error(), 0);
0193         QFile file(m_remoteDir.path() + path);
0194         QVERIFY(file.exists());
0195         QVERIFY(file.open(QFile::ReadOnly));
0196         QCOMPARE(file.readAll(), QByteArray("testOverwriteCopy2\n"));
0197     }
0198 
0199     void testOverwriteCopyWithoutFlag()
0200     {
0201         const QString path("/testOverwriteCopyWithoutFlag");
0202         const auto url = this->url(path);
0203 
0204         qDebug() << (m_remoteDir.path() + path);
0205         QVERIFY(QFile::copy(QFINDTESTDATA("ftp/testOverwriteCopy1"), m_remoteDir.path() + path));
0206 
0207         // Without overwrite flag.
0208         // https://bugs.kde.org/show_bug.cgi?id=409954
0209         auto job = KIO::copy({QUrl::fromLocalFile(QFINDTESTDATA("ftp/testOverwriteCopy2"))}, url, KIO::DefaultFlags);
0210         job->setUiDelegate(nullptr);
0211         QVERIFY2(!job->exec(), qUtf8Printable(job->errorString()));
0212         QCOMPARE(job->error(), KIO::ERR_FILE_ALREADY_EXIST);
0213         QFile file(m_remoteDir.path() + path);
0214         QVERIFY(file.exists());
0215         QVERIFY(file.open(QFile::ReadOnly));
0216         QCOMPARE(file.readAll(), QByteArray("testOverwriteCopy1\n")); // not 2!
0217     }
0218 };
0219 
0220 QTEST_MAIN(FTPTest)
0221 #include "ftptest.moc"