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"