From f7f79483e73fed5664763a125b3d27861586b7d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5rten=20Nordheim?= Date: Thu, 20 Aug 2020 12:03:54 +0200 Subject: Remove QFtp sources etc. in favor of alternate distribution While we initially wanted to just disable the functionality the way it is currently built forces people to do a full build of Qt just to enable it. Instead of doing this half-measure let's just remove the code completely from QtBase and rather prepare QtNetwork to handle being a plugin that can be compiled at any time. Task-number: QTBUG-80340 Change-Id: I19155c8c167cf932088f01b2a9706d0e7ab792d1 Reviewed-by: Alex Blasche --- src/network/access/qftp.cpp | 2456 ------------------------------------------- 1 file changed, 2456 deletions(-) delete mode 100644 src/network/access/qftp.cpp (limited to 'src/network/access/qftp.cpp') diff --git a/src/network/access/qftp.cpp b/src/network/access/qftp.cpp deleted file mode 100644 index 878f55f604..0000000000 --- a/src/network/access/qftp.cpp +++ /dev/null @@ -1,2456 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtNetwork module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -//#define QFTPPI_DEBUG -//#define QFTPDTP_DEBUG - -#include "private/qftp_p.h" -#include "qabstractsocket.h" - -#include "qcoreapplication.h" -#include "qtcpsocket.h" -#include "qurlinfo_p.h" -#include "qstringlist.h" -#include "qregularexpression.h" -#include "qtimer.h" -#include "qfileinfo.h" -#include "qtcpserver.h" -#include "qlocale.h" - -QT_BEGIN_NAMESPACE - -class QFtpPI; - -/* - The QFtpDTP (DTP = Data Transfer Process) controls all client side - data transfer between the client and server. -*/ -class QFtpDTP : public QObject -{ - Q_OBJECT - -public: - enum ConnectState { - CsHostFound, - CsConnected, - CsClosed, - CsHostNotFound, - CsConnectionRefused - }; - - QFtpDTP(QFtpPI *p, QObject *parent = nullptr); - - void setData(QByteArray *); - void setDevice(QIODevice *); - void writeData(); - void setBytesTotal(qint64 bytes); - - bool hasError() const; - QString errorMessage() const; - void clearError(); - - void connectToHost(const QString & host, quint16 port); - int setupListener(const QHostAddress &address); - void waitForConnection(); - - QTcpSocket::SocketState state() const; - qint64 bytesAvailable() const; - qint64 read(char *data, qint64 maxlen); - QByteArray readAll(); - - void abortConnection(); - - static bool parseDir(const QByteArray &buffer, const QString &userName, QUrlInfo *info); - -signals: - void listInfo(const QUrlInfo&); - void readyRead(); - void dataTransferProgress(qint64, qint64); - - void connectState(int); - -private slots: - void socketConnected(); - void socketReadyRead(); - void socketError(QAbstractSocket::SocketError); - void socketConnectionClosed(); - void socketBytesWritten(qint64); - void setupSocket(); - - void dataReadyRead(); - -private: - void clearData(); - - QTcpSocket *socket; - QTcpServer listener; - - QFtpPI *pi; - QString err; - qint64 bytesDone; - qint64 bytesTotal; - bool callWriteData; - - // If is_ba is true, ba is used; ba is never 0. - // Otherwise dev is used; dev can be 0 or not. - union { - QByteArray *ba; - QIODevice *dev; - } data; - bool is_ba; - - QByteArray bytesFromSocket; -}; - -/********************************************************************** - * - * QFtpPI - Protocol Interpreter - * - *********************************************************************/ - -class QFtpPI : public QObject -{ - Q_OBJECT - -public: - QFtpPI(QObject *parent = nullptr); - - void connectToHost(const QString &host, quint16 port); - - bool sendCommands(const QStringList &cmds); - bool sendCommand(const QString &cmd) - { return sendCommands(QStringList(cmd)); } - - void clearPendingCommands(); - void abort(); - - QString currentCommand() const - { return currentCmd; } - - bool rawCommand; - bool transferConnectionExtended; - - QFtpDTP dtp; // the PI has a DTP which is not the design of RFC 959, but it - // makes the design simpler this way -signals: - void connectState(int); - void finished(const QString&); - void error(int, const QString&); - void rawFtpReply(int, const QString&); - -private slots: - void hostFound(); - void connected(); - void connectionClosed(); - void delayedCloseFinished(); - void readyRead(); - void error(QAbstractSocket::SocketError); - - void dtpConnectState(int); - -private: - // the states are modelled after the generalized state diagram of RFC 959, - // page 58 - enum State { - Begin, - Idle, - Waiting, - Success, - Failure - }; - - enum AbortState { - None, - AbortStarted, - WaitForAbortToFinish - }; - - bool processReply(); - bool startNextCmd(); - - QTcpSocket commandSocket; - QString replyText; - char replyCode[3]; - State state; - AbortState abortState; - QStringList pendingCommands; - QString currentCmd; - - bool waitForDtpToConnect; - bool waitForDtpToClose; - - QByteArray bytesFromSocket; - - friend class QFtpDTP; -}; - -/********************************************************************** - * - * QFtpCommand implemenatation - * - *********************************************************************/ -class QFtpCommand -{ -public: - QFtpCommand(QFtp::Command cmd, const QStringList &raw, const QByteArray &ba); - QFtpCommand(QFtp::Command cmd, const QStringList &raw, QIODevice *dev = nullptr); - ~QFtpCommand(); - - int id; - QFtp::Command command; - QStringList rawCmds; - - // If is_ba is true, ba is used; ba is never 0. - // Otherwise dev is used; dev can be 0 or not. - union { - QByteArray *ba; - QIODevice *dev; - } data; - bool is_ba; - -}; - -static int nextId() -{ - static QBasicAtomicInt counter = Q_BASIC_ATOMIC_INITIALIZER(0); - return 1 + counter.fetchAndAddRelaxed(1); -} - -QFtpCommand::QFtpCommand(QFtp::Command cmd, const QStringList &raw, const QByteArray &ba) - : command(cmd), rawCmds(raw), is_ba(true) -{ - id = nextId(); - data.ba = new QByteArray(ba); -} - -QFtpCommand::QFtpCommand(QFtp::Command cmd, const QStringList &raw, QIODevice *dev) - : command(cmd), rawCmds(raw), is_ba(false) -{ - id = nextId(); - data.dev = dev; -} - -QFtpCommand::~QFtpCommand() -{ - if (is_ba) - delete data.ba; -} - -/********************************************************************** - * - * QFtpDTP implemenatation - * - *********************************************************************/ -QFtpDTP::QFtpDTP(QFtpPI *p, QObject *parent) : - QObject(parent), - socket(nullptr), - listener(this), - pi(p), - callWriteData(false) -{ - clearData(); - listener.setObjectName(QLatin1String("QFtpDTP active state server")); - connect(&listener, SIGNAL(newConnection()), SLOT(setupSocket())); -} - -void QFtpDTP::setData(QByteArray *ba) -{ - is_ba = true; - data.ba = ba; -} - -void QFtpDTP::setDevice(QIODevice *dev) -{ - is_ba = false; - data.dev = dev; -} - -void QFtpDTP::setBytesTotal(qint64 bytes) -{ - bytesTotal = bytes; - bytesDone = 0; - emit dataTransferProgress(bytesDone, bytesTotal); -} - -void QFtpDTP::connectToHost(const QString & host, quint16 port) -{ - bytesFromSocket.clear(); - - if (socket) { - delete socket; - socket = nullptr; - } - socket = new QTcpSocket(this); - socket->setObjectName(QLatin1String("QFtpDTP Passive state socket")); - connect(socket, SIGNAL(connected()), SLOT(socketConnected())); - connect(socket, SIGNAL(readyRead()), SLOT(socketReadyRead())); - connect(socket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), SLOT(socketError(QAbstractSocket::SocketError))); - connect(socket, SIGNAL(disconnected()), SLOT(socketConnectionClosed())); - connect(socket, SIGNAL(bytesWritten(qint64)), SLOT(socketBytesWritten(qint64))); - - socket->connectToHost(host, port); -} - -int QFtpDTP::setupListener(const QHostAddress &address) -{ - if (!listener.isListening() && !listener.listen(address, 0)) - return -1; - return listener.serverPort(); -} - -void QFtpDTP::waitForConnection() -{ - // This function is only interesting in Active transfer mode; it works - // around a limitation in QFtp's design by blocking, waiting for an - // incoming connection. For the default Passive mode, it does nothing. - if (listener.isListening()) - listener.waitForNewConnection(); -} - -QTcpSocket::SocketState QFtpDTP::state() const -{ - return socket ? socket->state() : QTcpSocket::UnconnectedState; -} - -qint64 QFtpDTP::bytesAvailable() const -{ - if (!socket || socket->state() != QTcpSocket::ConnectedState) - return (qint64) bytesFromSocket.size(); - return socket->bytesAvailable(); -} - -qint64 QFtpDTP::read(char *data, qint64 maxlen) -{ - qint64 read; - if (socket && socket->state() == QTcpSocket::ConnectedState) { - read = socket->read(data, maxlen); - } else { - read = qMin(maxlen, qint64(bytesFromSocket.size())); - memcpy(data, bytesFromSocket.data(), read); - bytesFromSocket.remove(0, read); - } - - bytesDone += read; - return read; -} - -QByteArray QFtpDTP::readAll() -{ - QByteArray tmp; - if (socket && socket->state() == QTcpSocket::ConnectedState) { - tmp = socket->readAll(); - bytesDone += tmp.size(); - } else { - tmp = bytesFromSocket; - bytesFromSocket.clear(); - } - return tmp; -} - -void QFtpDTP::writeData() -{ - if (!socket) - return; - - if (is_ba) { -#if defined(QFTPDTP_DEBUG) - qDebug("QFtpDTP::writeData: write %d bytes", data.ba->size()); -#endif - if (data.ba->size() == 0) - emit dataTransferProgress(0, bytesTotal); - else - socket->write(data.ba->data(), data.ba->size()); - - socket->close(); - - clearData(); - } else if (data.dev) { - callWriteData = false; - const qint64 blockSize = 16*1024; - char buf[16*1024]; - qint64 read = data.dev->read(buf, blockSize); -#if defined(QFTPDTP_DEBUG) - qDebug("QFtpDTP::writeData: write() of size %lli bytes", read); -#endif - if (read > 0) { - socket->write(buf, read); - } else if (read == -1 || (!data.dev->isSequential() && data.dev->atEnd())) { - // error or EOF - if (bytesDone == 0 && socket->bytesToWrite() == 0) - emit dataTransferProgress(0, bytesTotal); - socket->close(); - clearData(); - } - - // do we continue uploading? - callWriteData = data.dev != nullptr; - } -} - -void QFtpDTP::dataReadyRead() -{ - writeData(); -} - -inline bool QFtpDTP::hasError() const -{ - return !err.isNull(); -} - -inline QString QFtpDTP::errorMessage() const -{ - return err; -} - -inline void QFtpDTP::clearError() -{ - err.clear(); -} - -void QFtpDTP::abortConnection() -{ -#if defined(QFTPDTP_DEBUG) - qDebug("QFtpDTP::abortConnection, bytesAvailable == %lli", - socket ? socket->bytesAvailable() : (qint64) 0); -#endif - callWriteData = false; - clearData(); - - if (socket) - socket->abort(); -} - -static void _q_fixupDateTime(QDateTime *dateTime) -{ - // Adjust for future tolerance. - const int futureTolerance = 86400; - if (dateTime->secsTo(QDateTime::currentDateTime()) < -futureTolerance) { - QDate d = dateTime->date(); - d.setDate(d.year() - 1, d.month(), d.day()); - dateTime->setDate(d); - } -} - -static void _q_parseUnixDir(const QStringList &tokens, const QString &userName, QUrlInfo *info) -{ - // Unix style, 7 + 1 entries - // -rw-r--r-- 1 ftp ftp 17358091 Aug 10 2004 qt-x11-free-3.3.3.tar.gz - // drwxr-xr-x 3 ftp ftp 4096 Apr 14 2000 compiled-examples - // lrwxrwxrwx 1 ftp ftp 9 Oct 29 2005 qtscape -> qtmozilla - if (tokens.size() != 8) - return; - - char first = tokens.at(1).at(0).toLatin1(); - if (first == 'd') { - info->setDir(true); - info->setFile(false); - info->setSymLink(false); - } else if (first == '-') { - info->setDir(false); - info->setFile(true); - info->setSymLink(false); - } else if (first == 'l') { - info->setDir(true); - info->setFile(false); - info->setSymLink(true); - } - - // Resolve filename - QString name = tokens.at(7); - if (info->isSymLink()) { - int linkPos = name.indexOf(QLatin1String(" ->")); - if (linkPos != -1) - name.resize(linkPos); - } - info->setName(name); - - // Resolve owner & group - info->setOwner(tokens.at(3)); - info->setGroup(tokens.at(4)); - - // Resolve size - info->setSize(tokens.at(5).toLongLong()); - - QStringList formats; - formats << QLatin1String("MMM dd yyyy") << QLatin1String("MMM dd hh:mm") << QLatin1String("MMM d yyyy") - << QLatin1String("MMM d hh:mm") << QLatin1String("MMM d yyyy") << QLatin1String("MMM dd yyyy"); - - QString dateString = tokens.at(6); - dateString[0] = dateString[0].toUpper(); - - // Resolve the modification date by parsing all possible formats - QDateTime dateTime; - int n = 0; -#if QT_CONFIG(datetimeparser) - do { - dateTime = QLocale::c().toDateTime(dateString, formats.at(n++)); - } while (n < formats.size() && (!dateTime.isValid())); -#endif - - if (n == 2 || n == 4) { - // Guess the year. - dateTime.setDate(QDate(QDate::currentDate().year(), - dateTime.date().month(), - dateTime.date().day())); - _q_fixupDateTime(&dateTime); - } - if (dateTime.isValid()) - info->setLastModified(dateTime); - - // Resolve permissions - int permissions = 0; - const QString &p = tokens.at(2); - permissions |= (p[0] == QLatin1Char('r') ? QUrlInfo::ReadOwner : 0); - permissions |= (p[1] == QLatin1Char('w') ? QUrlInfo::WriteOwner : 0); - permissions |= (p[2] == QLatin1Char('x') ? QUrlInfo::ExeOwner : 0); - permissions |= (p[3] == QLatin1Char('r') ? QUrlInfo::ReadGroup : 0); - permissions |= (p[4] == QLatin1Char('w') ? QUrlInfo::WriteGroup : 0); - permissions |= (p[5] == QLatin1Char('x') ? QUrlInfo::ExeGroup : 0); - permissions |= (p[6] == QLatin1Char('r') ? QUrlInfo::ReadOther : 0); - permissions |= (p[7] == QLatin1Char('w') ? QUrlInfo::WriteOther : 0); - permissions |= (p[8] == QLatin1Char('x') ? QUrlInfo::ExeOther : 0); - info->setPermissions(permissions); - - bool isOwner = info->owner() == userName; - info->setReadable((permissions & QUrlInfo::ReadOther) || ((permissions & QUrlInfo::ReadOwner) && isOwner)); - info->setWritable((permissions & QUrlInfo::WriteOther) || ((permissions & QUrlInfo::WriteOwner) && isOwner)); -} - -static void _q_parseDosDir(const QStringList &tokens, const QString &userName, QUrlInfo *info) -{ - // DOS style, 3 + 1 entries - // 01-16-02 11:14AM epsgroup - // 06-05-03 03:19PM 1973 readme.txt - if (tokens.size() != 4) - return; - - Q_UNUSED(userName); - - QString name = tokens.at(3); - info->setName(name); - info->setSymLink(name.endsWith(QLatin1String(".lnk"), Qt::CaseInsensitive)); - - if (tokens.at(2) == QLatin1String("")) { - info->setFile(false); - info->setDir(true); - } else { - info->setFile(true); - info->setDir(false); - info->setSize(tokens.at(2).toLongLong()); - } - - // Note: We cannot use QFileInfo; permissions are for the server-side - // machine, and QFileInfo's behavior depends on the local platform. - int permissions = QUrlInfo::ReadOwner | QUrlInfo::WriteOwner - | QUrlInfo::ReadGroup | QUrlInfo::WriteGroup - | QUrlInfo::ReadOther | QUrlInfo::WriteOther; - QStringView ext; - int extIndex = name.lastIndexOf(QLatin1Char('.')); - if (extIndex != -1) - ext = QStringView{name}.mid(extIndex + 1); - if (ext == QLatin1String("exe") || ext == QLatin1String("bat") || ext == QLatin1String("com")) - permissions |= QUrlInfo::ExeOwner | QUrlInfo::ExeGroup | QUrlInfo::ExeOther; - info->setPermissions(permissions); - - info->setReadable(true); - info->setWritable(info->isFile()); - - QDateTime dateTime; -#if QT_CONFIG(datetimeparser) - dateTime = QLocale::c().toDateTime(tokens.at(1), QLatin1String("MM-dd-yy hh:mmAP")); - if (dateTime.date().year() < 1971) { - dateTime.setDate(QDate(dateTime.date().year() + 100, - dateTime.date().month(), - dateTime.date().day())); - } -#endif - - info->setLastModified(dateTime); - -} - -bool QFtpDTP::parseDir(const QByteArray &buffer, const QString &userName, QUrlInfo *info) -{ - if (buffer.isEmpty()) - return false; - - QString bufferStr = QString::fromUtf8(buffer).trimmed(); - - // Unix style FTP servers - QRegularExpression unixPattern(QLatin1String("^([\\-dl])([a-zA-Z\\-]{9,9})\\s+\\d+\\s+(\\S*)\\s+" - "(\\S*)\\s+(\\d+)\\s+(\\S+\\s+\\S+\\s+\\S+)\\s+(\\S.*)")); - auto unixPatternMatch = unixPattern.match(bufferStr); - if (unixPatternMatch.hasMatch()) { - _q_parseUnixDir(unixPatternMatch.capturedTexts(), userName, info); - return true; - } - - // DOS style FTP servers - QRegularExpression dosPattern(QLatin1String("^(\\d\\d-\\d\\d-\\d\\d\\ \\ \\d\\d:\\d\\d[AP]M)\\s+" - "(|\\d+)\\s+(\\S.*)$")); - auto dosPatternMatch = dosPattern.match(bufferStr); - if (dosPatternMatch.hasMatch()) { - _q_parseDosDir(dosPatternMatch.capturedTexts(), userName, info); - return true; - } - - // Unsupported - return false; -} - -void QFtpDTP::socketConnected() -{ - bytesDone = 0; -#if defined(QFTPDTP_DEBUG) - qDebug("QFtpDTP::connectState(CsConnected)"); -#endif - emit connectState(QFtpDTP::CsConnected); -} - -void QFtpDTP::socketReadyRead() -{ - if (!socket) - return; - - if (pi->currentCommand().isEmpty()) { - socket->close(); -#if defined(QFTPDTP_DEBUG) - qDebug("QFtpDTP::connectState(CsClosed)"); -#endif - emit connectState(QFtpDTP::CsClosed); - return; - } - - if (pi->abortState != QFtpPI::None) { - // discard data - socket->readAll(); - return; - } - - if (pi->currentCommand().startsWith(QLatin1String("LIST"))) { - while (socket->canReadLine()) { - QUrlInfo i; - QByteArray line = socket->readLine(); -#if defined(QFTPDTP_DEBUG) - qDebug("QFtpDTP read (list): '%s'", line.constData()); -#endif - if (parseDir(line, QLatin1String(""), &i)) { - emit listInfo(i); - } else { - // some FTP servers don't return a 550 if the file or directory - // does not exist, but rather write a text to the data socket - // -- try to catch these cases - if (line.endsWith("No such file or directory\r\n")) - err = QString::fromUtf8(line); - } - } - } else { - if (!is_ba && data.dev) { - do { - QByteArray ba; - ba.resize(socket->bytesAvailable()); - qint64 bytesRead = socket->read(ba.data(), ba.size()); - if (bytesRead < 0) { - // a read following a readyRead() signal will - // never fail. - return; - } - ba.resize(bytesRead); - bytesDone += bytesRead; -#if defined(QFTPDTP_DEBUG) - qDebug("QFtpDTP read: %lli bytes (total %lli bytes)", bytesRead, bytesDone); -#endif - if (data.dev) // make sure it wasn't deleted in the slot - data.dev->write(ba); - emit dataTransferProgress(bytesDone, bytesTotal); - - // Need to loop; dataTransferProgress is often connected to - // slots that update the GUI (e.g., progress bar values), and - // if events are processed, more data may have arrived. - } while (socket->bytesAvailable()); - } else { -#if defined(QFTPDTP_DEBUG) - qDebug("QFtpDTP readyRead: %lli bytes available (total %lli bytes read)", - bytesAvailable(), bytesDone); -#endif - emit dataTransferProgress(bytesDone+socket->bytesAvailable(), bytesTotal); - emit readyRead(); - } - } -} - -void QFtpDTP::socketError(QAbstractSocket::SocketError e) -{ - if (e == QTcpSocket::HostNotFoundError) { -#if defined(QFTPDTP_DEBUG) - qDebug("QFtpDTP::connectState(CsHostNotFound)"); -#endif - emit connectState(QFtpDTP::CsHostNotFound); - } else if (e == QTcpSocket::ConnectionRefusedError) { -#if defined(QFTPDTP_DEBUG) - qDebug("QFtpDTP::connectState(CsConnectionRefused)"); -#endif - emit connectState(QFtpDTP::CsConnectionRefused); - } -} - -void QFtpDTP::socketConnectionClosed() -{ - if (!is_ba && data.dev) { - clearData(); - } - - if (socket->isOpen()) - bytesFromSocket = socket->readAll(); - else - bytesFromSocket.clear(); -#if defined(QFTPDTP_DEBUG) - qDebug("QFtpDTP::connectState(CsClosed)"); -#endif - emit connectState(QFtpDTP::CsClosed); -} - -void QFtpDTP::socketBytesWritten(qint64 bytes) -{ - bytesDone += bytes; -#if defined(QFTPDTP_DEBUG) - qDebug("QFtpDTP::bytesWritten(%lli)", bytesDone); -#endif - emit dataTransferProgress(bytesDone, bytesTotal); - if (callWriteData) - writeData(); -} - -void QFtpDTP::setupSocket() -{ - socket = listener.nextPendingConnection(); - socket->setObjectName(QLatin1String("QFtpDTP Active state socket")); - connect(socket, SIGNAL(connected()), SLOT(socketConnected())); - connect(socket, SIGNAL(readyRead()), SLOT(socketReadyRead())); - connect(socket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), SLOT(socketError(QAbstractSocket::SocketError))); - connect(socket, SIGNAL(disconnected()), SLOT(socketConnectionClosed())); - connect(socket, SIGNAL(bytesWritten(qint64)), SLOT(socketBytesWritten(qint64))); - - listener.close(); -} - -void QFtpDTP::clearData() -{ - is_ba = false; - data.dev = nullptr; -} - -/********************************************************************** - * - * QFtpPI implemenatation - * - *********************************************************************/ -QFtpPI::QFtpPI(QObject *parent) : - QObject(parent), - rawCommand(false), - transferConnectionExtended(true), - dtp(this), - commandSocket(nullptr), - state(Begin), abortState(None), - currentCmd(QString()), - waitForDtpToConnect(false), - waitForDtpToClose(false) -{ - commandSocket.setObjectName(QLatin1String("QFtpPI_socket")); - connect(&commandSocket, SIGNAL(hostFound()), - SLOT(hostFound())); - connect(&commandSocket, SIGNAL(connected()), - SLOT(connected())); - connect(&commandSocket, SIGNAL(disconnected()), - SLOT(connectionClosed())); - connect(&commandSocket, SIGNAL(readyRead()), - SLOT(readyRead())); - connect(&commandSocket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), - SLOT(error(QAbstractSocket::SocketError))); - - connect(&dtp, SIGNAL(connectState(int)), - SLOT(dtpConnectState(int))); -} - -void QFtpPI::connectToHost(const QString &host, quint16 port) -{ - emit connectState(QFtp::HostLookup); - commandSocket.connectToHost(host, port); -} - -/* - \internal - - Sends the sequence of commands \a cmds to the FTP server. When the commands - are all done the finished() signal is emitted. When an error occurs, the - error() signal is emitted. - - If there are pending commands in the queue this functions returns \c false and - the \a cmds are not added to the queue; otherwise it returns \c true. -*/ -bool QFtpPI::sendCommands(const QStringList &cmds) -{ - if (!pendingCommands.isEmpty()) - return false; - - if (commandSocket.state() != QTcpSocket::ConnectedState || state!=Idle) { - emit error(QFtp::NotConnected, QFtp::tr("Not connected")); - return true; // there are no pending commands - } - - pendingCommands = cmds; - startNextCmd(); - return true; -} - -void QFtpPI::clearPendingCommands() -{ - pendingCommands.clear(); - dtp.abortConnection(); - currentCmd.clear(); - state = Idle; -} - -void QFtpPI::abort() -{ - pendingCommands.clear(); - - if (abortState != None) - // ABOR already sent - return; - - if (currentCmd.isEmpty()) - return; //no command in progress - - if (currentCmd.startsWith(QLatin1String("STOR "))) { - abortState = AbortStarted; -#if defined(QFTPPI_DEBUG) - qDebug("QFtpPI send: ABOR"); -#endif - commandSocket.write("ABOR\r\n", 6); - - dtp.abortConnection(); - } else { - //Deviation from RFC 959: - //Most FTP servers do not support ABOR, or require the telnet - //IP & synch sequence (TCP urgent data) which is not supported by QTcpSocket. - //Following what most FTP clients do, just reset the data connection and wait for 426 - abortState = WaitForAbortToFinish; - dtp.abortConnection(); - } -} - -void QFtpPI::hostFound() -{ - emit connectState(QFtp::Connecting); -} - -void QFtpPI::connected() -{ - state = Begin; -#if defined(QFTPPI_DEBUG) -// qDebug("QFtpPI state: %d [connected()]", state); -#endif - // try to improve performance by setting TCP_NODELAY - commandSocket.setSocketOption(QAbstractSocket::LowDelayOption, 1); - - emit connectState(QFtp::Connected); -} - -void QFtpPI::connectionClosed() -{ - commandSocket.close(); - emit connectState(QFtp::Unconnected); -} - -void QFtpPI::delayedCloseFinished() -{ - emit connectState(QFtp::Unconnected); -} - -void QFtpPI::error(QAbstractSocket::SocketError e) -{ - if (e == QTcpSocket::HostNotFoundError) { - emit connectState(QFtp::Unconnected); - emit error(QFtp::HostNotFound, - QFtp::tr("Host %1 not found").arg(commandSocket.peerName())); - } else if (e == QTcpSocket::ConnectionRefusedError) { - emit connectState(QFtp::Unconnected); - emit error(QFtp::ConnectionRefused, - QFtp::tr("Connection refused to host %1").arg(commandSocket.peerName())); - } else if (e == QTcpSocket::SocketTimeoutError) { - emit connectState(QFtp::Unconnected); - emit error(QFtp::ConnectionRefused, - QFtp::tr("Connection timed out to host %1").arg(commandSocket.peerName())); - } -} - -void QFtpPI::readyRead() -{ - if (waitForDtpToClose) - return; - - while (commandSocket.canReadLine()) { - // read line with respect to line continuation - QString line = QString::fromUtf8(commandSocket.readLine()); - if (replyText.isEmpty()) { - if (line.length() < 3) { - // protocol error - return; - } - const int lowerLimit[3] = {1,0,0}; - const int upperLimit[3] = {5,5,9}; - for (int i=0; i<3; i++) { - replyCode[i] = line.at(i).digitValue(); - if (replyCode[i]upperLimit[i]) { - // protocol error - return; - } - } - } - const char count[4] = { char('0' + replyCode[0]), char('0' + replyCode[1]), - char('0' + replyCode[2]), char(' ') }; - QString endOfMultiLine(QLatin1String(count, 4)); - QString lineCont(endOfMultiLine); - lineCont[3] = QLatin1Char('-'); - QStringView lineLeft4 = QStringView{line}.left(4); - - while (lineLeft4 != endOfMultiLine) { - if (lineLeft4 == lineCont) - replyText += QStringView{line}.mid(4); // strip 'xyz-' - else - replyText += line; - if (!commandSocket.canReadLine()) - return; - line = QString::fromUtf8(commandSocket.readLine()); - lineLeft4 = QStringView{line}.left(4); - } - replyText += QStringView{line}.mid(4); // strip reply code 'xyz ' - if (replyText.endsWith(QLatin1String("\r\n"))) - replyText.chop(2); - - if (processReply()) - replyText = QLatin1String(""); - } -} - -/* - \internal - - Process a reply from the FTP server. - - Returns \c true if the reply was processed or false if the reply has to be - processed at a later point. -*/ -bool QFtpPI::processReply() -{ -#if defined(QFTPPI_DEBUG) -// qDebug("QFtpPI state: %d [processReply() begin]", state); - if (replyText.length() < 400) - qDebug("QFtpPI recv: %d %s", 100*replyCode[0]+10*replyCode[1]+replyCode[2], replyText.toLatin1().constData()); - else - qDebug("QFtpPI recv: %d (text skipped)", 100*replyCode[0]+10*replyCode[1]+replyCode[2]); -#endif - - int replyCodeInt = 100*replyCode[0] + 10*replyCode[1] + replyCode[2]; - - // process 226 replies ("Closing Data Connection") only when the data - // connection is really closed to avoid short reads of the DTP - if (replyCodeInt == 226 || (replyCodeInt == 250 && currentCmd.startsWith(QLatin1String("RETR")))) { - if (dtp.state() != QTcpSocket::UnconnectedState) { - waitForDtpToClose = true; - return false; - } - } - - switch (abortState) { - case AbortStarted: - abortState = WaitForAbortToFinish; - break; - case WaitForAbortToFinish: - abortState = None; - return true; - default: - break; - } - - // get new state - static const State table[5] = { - /* 1yz 2yz 3yz 4yz 5yz */ - Waiting, Success, Idle, Failure, Failure - }; - switch (state) { - case Begin: - if (replyCode[0] == 1) { - return true; - } else if (replyCode[0] == 2) { - state = Idle; - emit finished(QFtp::tr("Connected to host %1").arg(commandSocket.peerName())); - break; - } - // reply codes not starting with 1 or 2 are not handled. - return true; - case Waiting: - if (static_cast(replyCode[0]) < 0 || replyCode[0] > 5) - state = Failure; - else - if (replyCodeInt == 202) - state = Failure; - else - state = table[replyCode[0] - 1]; - break; - default: - // ignore unrequested message - return true; - } -#if defined(QFTPPI_DEBUG) -// qDebug("QFtpPI state: %d [processReply() intermediate]", state); -#endif - - // special actions on certain replies - emit rawFtpReply(replyCodeInt, replyText); - if (rawCommand) { - rawCommand = false; - } else if (replyCodeInt == 227) { - // 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2) - // rfc959 does not define this response precisely, and gives - // both examples where the parenthesis are used, and where - // they are missing. We need to scan for the address and host - // info. - QRegularExpression addrPortPattern(QLatin1String("(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+)")); - auto addrPortMatch = addrPortPattern.match(replyText); - if (!addrPortMatch.hasMatch()) { -#if defined(QFTPPI_DEBUG) - qDebug("QFtp: bad 227 response -- address and port information missing"); -#endif - // this error should be reported - } else { - const QStringList lst = addrPortMatch.capturedTexts(); - QString host = lst[1] + QLatin1Char('.') + lst[2] + QLatin1Char('.') + lst[3] + QLatin1Char('.') + lst[4]; - quint16 port = (lst[5].toUInt() << 8) + lst[6].toUInt(); - waitForDtpToConnect = true; - dtp.connectToHost(host, port); - } - } else if (replyCodeInt == 229) { - // 229 Extended Passive mode OK (|||10982|) - int portPos = replyText.indexOf(QLatin1Char('(')); - if (portPos == -1) { -#if defined(QFTPPI_DEBUG) - qDebug("QFtp: bad 229 response -- port information missing"); -#endif - // this error should be reported - } else { - ++portPos; - QChar delimiter = replyText.at(portPos); - const auto epsvParameters = QStringView{replyText}.mid(portPos).split(delimiter); - - waitForDtpToConnect = true; - dtp.connectToHost(commandSocket.peerAddress().toString(), - epsvParameters.at(3).toInt()); - } - - } else if (replyCodeInt == 230) { - if (currentCmd.startsWith(QLatin1String("USER ")) && pendingCommands.count()>0 && - pendingCommands.constFirst().startsWith(QLatin1String("PASS "))) { - // no need to send the PASS -- we are already logged in - pendingCommands.pop_front(); - } - // 230 User logged in, proceed. - emit connectState(QFtp::LoggedIn); - } else if (replyCodeInt == 213) { - // 213 File status. - if (currentCmd.startsWith(QLatin1String("SIZE "))) - dtp.setBytesTotal(replyText.simplified().toLongLong()); - } else if (replyCode[0]==1 && currentCmd.startsWith(QLatin1String("STOR "))) { - dtp.waitForConnection(); - dtp.writeData(); - } - - // react on new state - switch (state) { - case Begin: - // should never happen - break; - case Success: - // success handling - state = Idle; - Q_FALLTHROUGH(); - case Idle: - if (dtp.hasError()) { - emit error(QFtp::UnknownError, dtp.errorMessage()); - dtp.clearError(); - } - startNextCmd(); - break; - case Waiting: - // do nothing - break; - case Failure: - // If the EPSV or EPRT commands fail, replace them with - // the old PASV and PORT instead and try again. - if (currentCmd.startsWith(QLatin1String("EPSV"))) { - transferConnectionExtended = false; - pendingCommands.prepend(QLatin1String("PASV\r\n")); - } else if (currentCmd.startsWith(QLatin1String("EPRT"))) { - transferConnectionExtended = false; - pendingCommands.prepend(QLatin1String("PORT\r\n")); - } else { - emit error(QFtp::UnknownError, replyText); - } - if (state != Waiting) { - state = Idle; - startNextCmd(); - } - break; - } -#if defined(QFTPPI_DEBUG) -// qDebug("QFtpPI state: %d [processReply() end]", state); -#endif - return true; -} - -/* - \internal - - Starts next pending command. Returns \c false if there are no pending commands, - otherwise it returns \c true. -*/ -bool QFtpPI::startNextCmd() -{ - if (waitForDtpToConnect) - // don't process any new commands until we are connected - return true; - -#if defined(QFTPPI_DEBUG) - if (state != Idle) - qDebug("QFtpPI startNextCmd: Internal error! QFtpPI called in non-Idle state %d", state); -#endif - if (pendingCommands.isEmpty()) { - currentCmd.clear(); - emit finished(replyText); - return false; - } - currentCmd = pendingCommands.constFirst(); - - // PORT and PASV are edited in-place, depending on whether we - // should try the extended transfer connection commands EPRT and - // EPSV. The PORT command also triggers setting up a listener, and - // the address/port arguments are edited in. - QHostAddress address = commandSocket.localAddress(); - if (currentCmd.startsWith(QLatin1String("PORT"))) { - if ((address.protocol() == QTcpSocket::IPv6Protocol) && transferConnectionExtended) { - int port = dtp.setupListener(address); - currentCmd = QLatin1String("EPRT |"); - currentCmd += (address.protocol() == QTcpSocket::IPv4Protocol) ? QLatin1Char('1') : QLatin1Char('2'); - currentCmd += QLatin1Char('|') + address.toString() + QLatin1Char('|') + QString::number(port); - currentCmd += QLatin1Char('|'); - } else if (address.protocol() == QTcpSocket::IPv4Protocol) { - int port = dtp.setupListener(address); - QString portArg; - quint32 ip = address.toIPv4Address(); - portArg += QString::number((ip & 0xff000000) >> 24); - portArg += QLatin1Char(',') + QString::number((ip & 0xff0000) >> 16); - portArg += QLatin1Char(',') + QString::number((ip & 0xff00) >> 8); - portArg += QLatin1Char(',') + QString::number(ip & 0xff); - portArg += QLatin1Char(',') + QString::number((port & 0xff00) >> 8); - portArg += QLatin1Char(',') + QString::number(port & 0xff); - - currentCmd = QLatin1String("PORT "); - currentCmd += portArg; - } else { - // No IPv6 connection can be set up with the PORT - // command. - return false; - } - - currentCmd += QLatin1String("\r\n"); - } else if (currentCmd.startsWith(QLatin1String("PASV"))) { - if ((address.protocol() == QTcpSocket::IPv6Protocol) && transferConnectionExtended) - currentCmd = QLatin1String("EPSV\r\n"); - } - - pendingCommands.pop_front(); -#if defined(QFTPPI_DEBUG) - qDebug("QFtpPI send: %s", QStringView{currentCmd}.left(currentCmd.length() - 2).toLatin1().constData()); -#endif - state = Waiting; - commandSocket.write(currentCmd.toUtf8()); - return true; -} - -void QFtpPI::dtpConnectState(int s) -{ - switch (s) { - case QFtpDTP::CsClosed: - if (waitForDtpToClose) { - // there is an unprocessed reply - if (processReply()) - replyText = QLatin1String(""); - else - return; - } - waitForDtpToClose = false; - readyRead(); - return; - case QFtpDTP::CsConnected: - waitForDtpToConnect = false; - startNextCmd(); - return; - case QFtpDTP::CsHostNotFound: - case QFtpDTP::CsConnectionRefused: - emit error(QFtp::ConnectionRefused, - QFtp::tr("Data Connection refused")); - startNextCmd(); - return; - default: - return; - } -} - -/********************************************************************** - * - * QFtpPrivate - * - *********************************************************************/ - -QT_BEGIN_INCLUDE_NAMESPACE -#include -QT_END_INCLUDE_NAMESPACE - -class QFtpPrivate : public QObjectPrivate -{ - Q_DECLARE_PUBLIC(QFtp) -public: - - inline QFtpPrivate() : close_waitForStateChange(false), state(QFtp::Unconnected), - transferMode(QFtp::Passive), error(QFtp::NoError) - { } - - ~QFtpPrivate() { while (!pending.isEmpty()) delete pending.takeFirst(); } - - // private slots - void _q_startNextCommand(); - void _q_piFinished(const QString&); - void _q_piError(int, const QString&); - void _q_piConnectState(int); - void _q_piFtpReply(int, const QString&); - - int addCommand(QFtpCommand *cmd); - - QFtpPI pi; - QList pending; - bool close_waitForStateChange; - QFtp::State state; - QFtp::TransferMode transferMode; - QFtp::Error error; - QString errorString; - - QString host; - quint16 port; - QString proxyHost; - quint16 proxyPort; -}; - -int QFtpPrivate::addCommand(QFtpCommand *cmd) -{ - pending.append(cmd); - - if (pending.count() == 1) { - // don't emit the commandStarted() signal before the ID is returned - QTimer::singleShot(0, q_func(), SLOT(_q_startNextCommand())); - } - return cmd->id; -} - -/********************************************************************** - * - * QFtp implementation - * - *********************************************************************/ -/*! - \internal - \class QFtp - \brief The QFtp class provides an implementation of the client side of FTP protocol. - - \ingroup network - \inmodule QtNetwork - - - This class provides a direct interface to FTP that allows you to - have more control over the requests. However, for new - applications, it is recommended to use QNetworkAccessManager and - QNetworkReply, as those classes possess a simpler, yet more - powerful API. - - The class works asynchronously, so there are no blocking - functions. If an operation cannot be executed immediately, the - function will still return straight away and the operation will be - scheduled for later execution. The results of scheduled operations - are reported via signals. This approach depends on the event loop - being in operation. - - The operations that can be scheduled (they are called "commands" - in the rest of the documentation) are the following: - connectToHost(), login(), close(), list(), cd(), get(), put(), - remove(), mkdir(), rmdir(), rename() and rawCommand(). - - All of these commands return a unique identifier that allows you - to keep track of the command that is currently being executed. - When the execution of a command starts, the commandStarted() - signal with the command's identifier is emitted. When the command - is finished, the commandFinished() signal is emitted with the - command's identifier and a bool that indicates whether the command - finished with an error. - - In some cases, you might want to execute a sequence of commands, - e.g. if you want to connect and login to a FTP server. This is - simply achieved: - - \snippet code/src_network_access_qftp.cpp 0 - - In this case two FTP commands have been scheduled. When the last - scheduled command has finished, a done() signal is emitted with - a bool argument that tells you whether the sequence finished with - an error. - - If an error occurs during the execution of one of the commands in - a sequence of commands, all the pending commands (i.e. scheduled, - but not yet executed commands) are cleared and no signals are - emitted for them. - - Some commands, e.g. list(), emit additional signals to report - their results. - - Example: If you want to download the INSTALL file from the Qt - FTP server, you would write this: - - \snippet code/src_network_access_qftp.cpp 1 - - For this example the following sequence of signals is emitted - (with small variations, depending on network traffic, etc.): - - \snippet code/src_network_access_qftp.cpp 2 - - The dataTransferProgress() signal in the above example is useful - if you want to show a \l{QProgressBar}{progress bar} to - inform the user about the progress of the download. The - readyRead() signal tells you that there is data ready to be read. - The amount of data can be queried then with the bytesAvailable() - function and it can be read with the read() or readAll() - function. - - If the login fails for the above example, the signals would look - like this: - - \snippet code/src_network_access_qftp.cpp 3 - - You can then get details about the error with the error() and - errorString() functions. - - For file transfer, QFtp can use both active or passive mode, and - it uses passive file transfer mode by default; see the - documentation for setTransferMode() for more details about this. - - Call setProxy() to make QFtp connect via an FTP proxy server. - - The functions currentId() and currentCommand() provide more - information about the currently executing command. - - The functions hasPendingCommands() and clearPendingCommands() - allow you to query and clear the list of pending commands. - - If you are an experienced network programmer and want to have - complete control you can use rawCommand() to execute arbitrary FTP - commands. - - \warning The current version of QFtp doesn't fully support - non-Unix FTP servers. - - \sa QNetworkAccessManager, QNetworkRequest, QNetworkReply, - {FTP Example} -*/ - - -/*! - \internal - Constructs a QFtp object with the given \a parent. -*/ -QFtp::QFtp(QObject *parent) - : QObject(*new QFtpPrivate, parent) -{ - Q_D(QFtp); - d->errorString = tr("Unknown error"); - - connect(&d->pi, SIGNAL(connectState(int)), - SLOT(_q_piConnectState(int))); - connect(&d->pi, SIGNAL(finished(QString)), - SLOT(_q_piFinished(QString))); - connect(&d->pi, SIGNAL(error(int,QString)), - SLOT(_q_piError(int,QString))); - connect(&d->pi, SIGNAL(rawFtpReply(int,QString)), - SLOT(_q_piFtpReply(int,QString))); - - connect(&d->pi.dtp, SIGNAL(readyRead()), - SIGNAL(readyRead())); - connect(&d->pi.dtp, SIGNAL(dataTransferProgress(qint64,qint64)), - SIGNAL(dataTransferProgress(qint64,qint64))); - connect(&d->pi.dtp, SIGNAL(listInfo(QUrlInfo)), - SIGNAL(listInfo(QUrlInfo))); -} - -/*! - \internal - \enum QFtp::State - - This enum defines the connection state: - - \value Unconnected There is no connection to the host. - \value HostLookup A host name lookup is in progress. - \value Connecting An attempt to connect to the host is in progress. - \value Connected Connection to the host has been achieved. - \value LoggedIn Connection and user login have been achieved. - \value Closing The connection is closing down, but it is not yet - closed. (The state will be \c Unconnected when the connection is - closed.) - - \sa stateChanged(), state() -*/ -/*! - \internal - \enum QFtp::TransferMode - - FTP works with two socket connections; one for commands and - another for transmitting data. While the command connection is - always initiated by the client, the second connection can be - initiated by either the client or the server. - - This enum defines whether the client (Passive mode) or the server - (Active mode) should set up the data connection. - - \value Passive The client connects to the server to transmit its - data. - - \value Active The server connects to the client to transmit its - data. -*/ -/*! - \internal - \enum QFtp::TransferType - - This enum identifies the data transfer type used with get and - put commands. - - \value Binary The data will be transferred in Binary mode. - - \value Ascii The data will be transferred in Ascii mode and new line - characters will be converted to the local format. -*/ -/*! - \internal - \enum QFtp::Error - - This enum identifies the error that occurred. - - \value NoError No error occurred. - \value HostNotFound The host name lookup failed. - \value ConnectionRefused The server refused the connection. - \value NotConnected Tried to send a command, but there is no connection to - a server. - \value UnknownError An error other than those specified above - occurred. - - \sa error() -*/ - -/*! - \internal - \enum QFtp::Command - - This enum is used as the return value for the currentCommand() function. - This allows you to perform specific actions for particular - commands, e.g. in a FTP client, you might want to clear the - directory view when a list() command is started; in this case you - can simply check in the slot connected to the start() signal if - the currentCommand() is \c List. - - \value None No command is being executed. - \value SetTransferMode set the \l{TransferMode}{transfer} mode. - \value SetProxy switch proxying on or off. - \value ConnectToHost connectToHost() is being executed. - \value Login login() is being executed. - \value Close close() is being executed. - \value List list() is being executed. - \value Cd cd() is being executed. - \value Get get() is being executed. - \value Put put() is being executed. - \value Remove remove() is being executed. - \value Mkdir mkdir() is being executed. - \value Rmdir rmdir() is being executed. - \value Rename rename() is being executed. - \value RawCommand rawCommand() is being executed. - - \sa currentCommand() -*/ - -/*! - \internal - \fn void QFtp::stateChanged(int state) - - This signal is emitted when the state of the connection changes. - The argument \a state is the new state of the connection; it is - one of the \l State values. - - It is usually emitted in response to a connectToHost() or close() - command, but it can also be emitted "spontaneously", e.g. when the - server closes the connection unexpectedly. - - \sa connectToHost(), close(), state(), State -*/ - -/*! - \internal - \fn void QFtp::listInfo(const QUrlInfo &i); - - This signal is emitted for each directory entry the list() command - finds. The details of the entry are stored in \a i. - - \sa list() -*/ - -/*! - \internal - \fn void QFtp::commandStarted(int id) - - This signal is emitted when processing the command identified by - \a id starts. - - \sa commandFinished(), done() -*/ - -/*! - \internal - \fn void QFtp::commandFinished(int id, bool error) - - This signal is emitted when processing the command identified by - \a id has finished. \a error is true if an error occurred during - the processing; otherwise \a error is false. - - \sa commandStarted(), done(), error(), errorString() -*/ - -/*! - \internal - \fn void QFtp::done(bool error) - - This signal is emitted when the last pending command has finished; - (it is emitted after the last command's commandFinished() signal). - \a error is true if an error occurred during the processing; - otherwise \a error is false. - - \sa commandFinished(), error(), errorString() -*/ - -/*! - \internal - \fn void QFtp::readyRead() - - This signal is emitted in response to a get() command when there - is new data to read. - - If you specify a device as the second argument in the get() - command, this signal is \e not emitted; instead the data is - written directly to the device. - - You can read the data with the readAll() or read() functions. - - This signal is useful if you want to process the data in chunks as - soon as it becomes available. If you are only interested in the - complete data, just connect to the commandFinished() signal and - read the data then instead. - - \sa get(), read(), readAll(), bytesAvailable() -*/ - -/*! - \internal - \fn void QFtp::dataTransferProgress(qint64 done, qint64 total) - - This signal is emitted in response to a get() or put() request to - indicate the current progress of the download or upload. - - \a done is the amount of data that has already been transferred - and \a total is the total amount of data to be read or written. It - is possible that the QFtp class is not able to determine the total - amount of data that should be transferred, in which case \a total - is 0. (If you connect this signal to a QProgressBar, the progress - bar shows a busy indicator if the total is 0). - - \warning \a done and \a total are not necessarily the size in - bytes, since for large files these values might need to be - "scaled" to avoid overflow. - - \sa get(), put(), QProgressBar -*/ - -/*! - \internal - \fn void QFtp::rawCommandReply(int replyCode, const QString &detail); - - This signal is emitted in response to the rawCommand() function. - \a replyCode is the 3 digit reply code and \a detail is the text - that follows the reply code. - - \sa rawCommand() -*/ - -/*! - \internal - Connects to the FTP server \a host using port \a port. - - The stateChanged() signal is emitted when the state of the - connecting process changes, e.g. to \c HostLookup, then \c - Connecting, then \c Connected. - - The function does not block and returns immediately. The command - is scheduled, and its execution is performed asynchronously. The - function returns a unique identifier which is passed by - commandStarted() and commandFinished(). - - When the command is started the commandStarted() signal is - emitted. When it is finished the commandFinished() signal is - emitted. - - \sa stateChanged(), commandStarted(), commandFinished() -*/ -int QFtp::connectToHost(const QString &host, quint16 port) -{ - QStringList cmds; - cmds << host; - cmds << QString::number((uint)port); - int id = d_func()->addCommand(new QFtpCommand(ConnectToHost, cmds)); - d_func()->pi.transferConnectionExtended = true; - return id; -} - -/*! - \internal - Logs in to the FTP server with the username \a user and the - password \a password. - - The stateChanged() signal is emitted when the state of the - connecting process changes, e.g. to \c LoggedIn. - - The function does not block and returns immediately. The command - is scheduled, and its execution is performed asynchronously. The - function returns a unique identifier which is passed by - commandStarted() and commandFinished(). - - When the command is started the commandStarted() signal is - emitted. When it is finished the commandFinished() signal is - emitted. - - \sa commandStarted(), commandFinished() -*/ -int QFtp::login(const QString &user, const QString &password) -{ - QStringList cmds; - - if (user.isNull() || user.compare(QLatin1String("anonymous"), Qt::CaseInsensitive) == 0) { - cmds << (QLatin1String("USER ") + (user.isNull() ? QLatin1String("anonymous") : user) + QLatin1String("\r\n")); - cmds << (QLatin1String("PASS ") + (password.isNull() ? QLatin1String("anonymous@") : password) + QLatin1String("\r\n")); - } else { - cmds << (QLatin1String("USER ") + user + QLatin1String("\r\n")); - if (!password.isNull()) - cmds << (QLatin1String("PASS ") + password + QLatin1String("\r\n")); - } - - return d_func()->addCommand(new QFtpCommand(Login, cmds)); -} - -/*! - \internal - Closes the connection to the FTP server. - - The stateChanged() signal is emitted when the state of the - connecting process changes, e.g. to \c Closing, then \c - Unconnected. - - The function does not block and returns immediately. The command - is scheduled, and its execution is performed asynchronously. The - function returns a unique identifier which is passed by - commandStarted() and commandFinished(). - - When the command is started the commandStarted() signal is - emitted. When it is finished the commandFinished() signal is - emitted. - - \sa stateChanged(), commandStarted(), commandFinished() -*/ -int QFtp::close() -{ - return d_func()->addCommand(new QFtpCommand(Close, QStringList(QLatin1String("QUIT\r\n")))); -} - -/*! - \internal - Sets the current FTP transfer mode to \a mode. The default is QFtp::Passive. - - \sa QFtp::TransferMode -*/ -int QFtp::setTransferMode(TransferMode mode) -{ - int id = d_func()->addCommand(new QFtpCommand(SetTransferMode, QStringList())); - d_func()->pi.transferConnectionExtended = true; - d_func()->transferMode = mode; - return id; -} - -/*! - \internal - Enables use of the FTP proxy on host \a host and port \a - port. Calling this function with \a host empty disables proxying. - - QFtp does not support FTP-over-HTTP proxy servers. Use - QNetworkAccessManager for this. -*/ -int QFtp::setProxy(const QString &host, quint16 port) -{ - QStringList args; - args << host << QString::number(port); - return d_func()->addCommand(new QFtpCommand(SetProxy, args)); -} - -/*! - \internal - Lists the contents of directory \a dir on the FTP server. If \a - dir is empty, it lists the contents of the current directory. - - The listInfo() signal is emitted for each directory entry found. - - The function does not block and returns immediately. The command - is scheduled, and its execution is performed asynchronously. The - function returns a unique identifier which is passed by - commandStarted() and commandFinished(). - - When the command is started the commandStarted() signal is - emitted. When it is finished the commandFinished() signal is - emitted. - - \sa listInfo(), commandStarted(), commandFinished() -*/ -int QFtp::list(const QString &dir) -{ - QStringList cmds; - cmds << QLatin1String("TYPE A\r\n"); - cmds << QLatin1String(d_func()->transferMode == Passive ? "PASV\r\n" : "PORT\r\n"); - if (dir.isEmpty()) - cmds << QLatin1String("LIST\r\n"); - else - cmds << (QLatin1String("LIST ") + dir + QLatin1String("\r\n")); - return d_func()->addCommand(new QFtpCommand(List, cmds)); -} - -/*! - \internal - Changes the working directory of the server to \a dir. - - The function does not block and returns immediately. The command - is scheduled, and its execution is performed asynchronously. The - function returns a unique identifier which is passed by - commandStarted() and commandFinished(). - - When the command is started the commandStarted() signal is - emitted. When it is finished the commandFinished() signal is - emitted. - - \sa commandStarted(), commandFinished() -*/ -int QFtp::cd(const QString &dir) -{ - return d_func()->addCommand(new QFtpCommand(Cd, QStringList(QLatin1String("CWD ") + dir + QLatin1String("\r\n")))); -} - -/*! - \internal - Downloads the file \a file from the server. - - If \a dev is \nullptr, then the readyRead() signal is emitted when there - is data available to read. You can then read the data with the - read() or readAll() functions. - - If \a dev is not \nullptr, the data is written directly to the device - \a dev. Make sure that the \a dev pointer is valid for the duration - of the operation (it is safe to delete it when the - commandFinished() signal is emitted). In this case the readyRead() - signal is \e not emitted and you cannot read data with the - read() or readAll() functions. - - If you don't read the data immediately it becomes available, i.e. - when the readyRead() signal is emitted, it is still available - until the next command is started. - - For example, if you want to present the data to the user as soon - as there is something available, connect to the readyRead() signal - and read the data immediately. On the other hand, if you only want - to work with the complete data, you can connect to the - commandFinished() signal and read the data when the get() command - is finished. - - The data is transferred as Binary or Ascii depending on the value - of \a type. - - The function does not block and returns immediately. The command - is scheduled, and its execution is performed asynchronously. The - function returns a unique identifier which is passed by - commandStarted() and commandFinished(). - - When the command is started the commandStarted() signal is - emitted. When it is finished the commandFinished() signal is - emitted. - - \sa readyRead(), dataTransferProgress(), commandStarted(), - commandFinished() -*/ -int QFtp::get(const QString &file, QIODevice *dev, TransferType type) -{ - QStringList cmds; - if (type == Binary) - cmds << QLatin1String("TYPE I\r\n"); - else - cmds << QLatin1String("TYPE A\r\n"); - cmds << QLatin1String("SIZE ") + file + QLatin1String("\r\n"); - cmds << QLatin1String(d_func()->transferMode == Passive ? "PASV\r\n" : "PORT\r\n"); - cmds << QLatin1String("RETR ") + file + QLatin1String("\r\n"); - return d_func()->addCommand(new QFtpCommand(Get, cmds, dev)); -} - -/*! - \internal - \overload - - Writes a copy of the given \a data to the file called \a file on - the server. The progress of the upload is reported by the - dataTransferProgress() signal. - - The data is transferred as Binary or Ascii depending on the value - of \a type. - - The function does not block and returns immediately. The command - is scheduled, and its execution is performed asynchronously. The - function returns a unique identifier which is passed by - commandStarted() and commandFinished(). - - When the command is started the commandStarted() signal is - emitted. When it is finished the commandFinished() signal is - emitted. - - Since this function takes a copy of the \a data, you can discard - your own copy when this function returns. - - \sa dataTransferProgress(), commandStarted(), commandFinished() -*/ -int QFtp::put(const QByteArray &data, const QString &file, TransferType type) -{ - QStringList cmds; - if (type == Binary) - cmds << QLatin1String("TYPE I\r\n"); - else - cmds << QLatin1String("TYPE A\r\n"); - cmds << QLatin1String(d_func()->transferMode == Passive ? "PASV\r\n" : "PORT\r\n"); - cmds << QLatin1String("ALLO ") + QString::number(data.size()) + QLatin1String("\r\n"); - cmds << QLatin1String("STOR ") + file + QLatin1String("\r\n"); - return d_func()->addCommand(new QFtpCommand(Put, cmds, data)); -} - -/*! - \internal - Reads the data from the IO device \a dev, and writes it to the - file called \a file on the server. The data is read in chunks from - the IO device, so this overload allows you to transmit large - amounts of data without the need to read all the data into memory - at once. - - The data is transferred as Binary or Ascii depending on the value - of \a type. - - Make sure that the \a dev pointer is valid for the duration of the - operation (it is safe to delete it when the commandFinished() is - emitted). -*/ -int QFtp::put(QIODevice *dev, const QString &file, TransferType type) -{ - QStringList cmds; - if (type == Binary) - cmds << QLatin1String("TYPE I\r\n"); - else - cmds << QLatin1String("TYPE A\r\n"); - cmds << QLatin1String(d_func()->transferMode == Passive ? "PASV\r\n" : "PORT\r\n"); - if (!dev->isSequential()) - cmds << QLatin1String("ALLO ") + QString::number(dev->size()) + QLatin1String("\r\n"); - cmds << QLatin1String("STOR ") + file + QLatin1String("\r\n"); - return d_func()->addCommand(new QFtpCommand(Put, cmds, dev)); -} - -/*! - \internal - Deletes the file called \a file from the server. - - The function does not block and returns immediately. The command - is scheduled, and its execution is performed asynchronously. The - function returns a unique identifier which is passed by - commandStarted() and commandFinished(). - - When the command is started the commandStarted() signal is - emitted. When it is finished the commandFinished() signal is - emitted. - - \sa commandStarted(), commandFinished() -*/ -int QFtp::remove(const QString &file) -{ - return d_func()->addCommand(new QFtpCommand(Remove, QStringList(QLatin1String("DELE ") + file + QLatin1String("\r\n")))); -} - -/*! - \internal - Creates a directory called \a dir on the server. - - The function does not block and returns immediately. The command - is scheduled, and its execution is performed asynchronously. The - function returns a unique identifier which is passed by - commandStarted() and commandFinished(). - - When the command is started the commandStarted() signal is - emitted. When it is finished the commandFinished() signal is - emitted. - - \sa commandStarted(), commandFinished() -*/ -int QFtp::mkdir(const QString &dir) -{ - return d_func()->addCommand(new QFtpCommand(Mkdir, QStringList(QLatin1String("MKD ") + dir + QLatin1String("\r\n")))); -} - -/*! - \internal - Removes the directory called \a dir from the server. - - The function does not block and returns immediately. The command - is scheduled, and its execution is performed asynchronously. The - function returns a unique identifier which is passed by - commandStarted() and commandFinished(). - - When the command is started the commandStarted() signal is - emitted. When it is finished the commandFinished() signal is - emitted. - - \sa commandStarted(), commandFinished() -*/ -int QFtp::rmdir(const QString &dir) -{ - return d_func()->addCommand(new QFtpCommand(Rmdir, QStringList(QLatin1String("RMD ") + dir + QLatin1String("\r\n")))); -} - -/*! - \internal - Renames the file called \a oldname to \a newname on the server. - - The function does not block and returns immediately. The command - is scheduled, and its execution is performed asynchronously. The - function returns a unique identifier which is passed by - commandStarted() and commandFinished(). - - When the command is started the commandStarted() signal is - emitted. When it is finished the commandFinished() signal is - emitted. - - \sa commandStarted(), commandFinished() -*/ -int QFtp::rename(const QString &oldname, const QString &newname) -{ - QStringList cmds; - cmds << QLatin1String("RNFR ") + oldname + QLatin1String("\r\n"); - cmds << QLatin1String("RNTO ") + newname + QLatin1String("\r\n"); - return d_func()->addCommand(new QFtpCommand(Rename, cmds)); -} - -/*! - \internal - Sends the raw FTP command \a command to the FTP server. This is - useful for low-level FTP access. If the operation you wish to - perform has an equivalent QFtp function, we recommend using the - function instead of raw FTP commands since the functions are - easier and safer. - - The function does not block and returns immediately. The command - is scheduled, and its execution is performed asynchronously. The - function returns a unique identifier which is passed by - commandStarted() and commandFinished(). - - When the command is started the commandStarted() signal is - emitted. When it is finished the commandFinished() signal is - emitted. - - \sa rawCommandReply(), commandStarted(), commandFinished() -*/ -int QFtp::rawCommand(const QString &command) -{ - const QString cmd = QStringView{command}.trimmed() + QLatin1String("\r\n"); - return d_func()->addCommand(new QFtpCommand(RawCommand, QStringList(cmd))); -} - -/*! - \internal - Returns the number of bytes that can be read from the data socket - at the moment. - - \sa get(), readyRead(), read(), readAll() -*/ -qint64 QFtp::bytesAvailable() const -{ - return d_func()->pi.dtp.bytesAvailable(); -} - -/*! - \internal - Reads \a maxlen bytes from the data socket into \a data and - returns the number of bytes read. Returns -1 if an error occurred. - - \sa get(), readyRead(), bytesAvailable(), readAll() -*/ -qint64 QFtp::read(char *data, qint64 maxlen) -{ - return d_func()->pi.dtp.read(data, maxlen); -} - -/*! - \internal - Reads all the bytes available from the data socket and returns - them. - - \sa get(), readyRead(), bytesAvailable(), read() -*/ -QByteArray QFtp::readAll() -{ - return d_func()->pi.dtp.readAll(); -} - -/*! - \internal - Aborts the current command and deletes all scheduled commands. - - If there is an unfinished command (i.e. a command for which the - commandStarted() signal has been emitted, but for which the - commandFinished() signal has not been emitted), this function - sends an \c ABORT command to the server. When the server replies - that the command is aborted, the commandFinished() signal with the - \c error argument set to \c true is emitted for the command. Due - to timing issues, it is possible that the command had already - finished before the abort request reached the server, in which - case, the commandFinished() signal is emitted with the \c error - argument set to \c false. - - For all other commands that are affected by the abort(), no - signals are emitted. - - If you don't start further FTP commands directly after the - abort(), there won't be any scheduled commands and the done() - signal is emitted. - - \warning Some FTP servers, for example the BSD FTP daemon (version - 0.3), wrongly return a positive reply even when an abort has - occurred. For these servers the commandFinished() signal has its - error flag set to \c false, even though the command did not - complete successfully. - - \sa clearPendingCommands() -*/ -void QFtp::abort() -{ - if (d_func()->pending.isEmpty()) - return; - - clearPendingCommands(); - d_func()->pi.abort(); -} - -/*! - \internal - Clears the last error. - - \sa currentCommand() -*/ -void QFtp::clearError() -{ - d_func()->error = NoError; -} - -/*! - \internal - Returns the identifier of the FTP command that is being executed - or 0 if there is no command being executed. - - \sa currentCommand() -*/ -int QFtp::currentId() const -{ - if (d_func()->pending.isEmpty()) - return 0; - return d_func()->pending.first()->id; -} - -/*! - \internal - Returns the command type of the FTP command being executed or \c - None if there is no command being executed. - - \sa currentId() -*/ -QFtp::Command QFtp::currentCommand() const -{ - if (d_func()->pending.isEmpty()) - return None; - return d_func()->pending.first()->command; -} - -/*! - \internal - Returns the QIODevice pointer that is used by the FTP command to read data - from or store data to. If there is no current FTP command being executed or - if the command does not use an IO device, this function returns \nullptr. - - This function can be used to delete the QIODevice in the slot connected to - the commandFinished() signal. - - \sa get(), put() -*/ -QIODevice* QFtp::currentDevice() const -{ - if (d_func()->pending.isEmpty()) - return nullptr; - QFtpCommand *c = d_func()->pending.first(); - if (c->is_ba) - return nullptr; - return c->data.dev; -} - -/*! - \internal - Returns \c true if there are any commands scheduled that have not yet - been executed; otherwise returns \c false. - - The command that is being executed is \e not considered as a - scheduled command. - - \sa clearPendingCommands(), currentId(), currentCommand() -*/ -bool QFtp::hasPendingCommands() const -{ - return d_func()->pending.count() > 1; -} - -/*! - \internal - Deletes all pending commands from the list of scheduled commands. - This does not affect the command that is being executed. If you - want to stop this as well, use abort(). - - \sa hasPendingCommands(), abort() -*/ -void QFtp::clearPendingCommands() -{ - // delete all entires except the first one - while (d_func()->pending.count() > 1) - delete d_func()->pending.takeLast(); -} - -/*! - \internal - Returns the current state of the object. When the state changes, - the stateChanged() signal is emitted. - - \sa State, stateChanged() -*/ -QFtp::State QFtp::state() const -{ - return d_func()->state; -} - -/*! - \internal - Returns the last error that occurred. This is useful to find out - what went wrong when receiving a commandFinished() or a done() - signal with the \c error argument set to \c true. - - If you start a new command, the error status is reset to \c NoError. -*/ -QFtp::Error QFtp::error() const -{ - return d_func()->error; -} - -/*! - \internal - Returns a human-readable description of the last error that - occurred. This is useful for presenting a error message to the - user when receiving a commandFinished() or a done() signal with - the \c error argument set to \c true. - - The error string is often (but not always) the reply from the - server, so it is not always possible to translate the string. If - the message comes from Qt, the string has already passed through - tr(). -*/ -QString QFtp::errorString() const -{ - return d_func()->errorString; -} - -/*! \internal -*/ -void QFtpPrivate::_q_startNextCommand() -{ - Q_Q(QFtp); - if (pending.isEmpty()) - return; - QFtpCommand *c = pending.constFirst(); - - error = QFtp::NoError; - errorString = QT_TRANSLATE_NOOP(QFtp, QLatin1String("Unknown error")); - - if (q->bytesAvailable()) - q->readAll(); // clear the data - emit q->commandStarted(c->id); - - // Proxy support, replace the Login argument in place, then fall - // through. - if (c->command == QFtp::Login && !proxyHost.isEmpty()) { - QString loginString; - loginString += QStringView{c->rawCmds.constFirst()}.trimmed() + QLatin1Char('@') + host; - if (port && port != 21) - loginString += QLatin1Char(':') + QString::number(port); - loginString += QLatin1String("\r\n"); - c->rawCmds[0] = loginString; - } - - if (c->command == QFtp::SetTransferMode) { - _q_piFinished(QLatin1String("Transfer mode set")); - } else if (c->command == QFtp::SetProxy) { - proxyHost = c->rawCmds.at(0); - proxyPort = c->rawCmds.at(1).toUInt(); - c->rawCmds.clear(); - _q_piFinished(QLatin1String("Proxy set to ") + proxyHost + QLatin1Char(':') + QString::number(proxyPort)); - } else if (c->command == QFtp::ConnectToHost) { - if (!proxyHost.isEmpty()) { - host = c->rawCmds.at(0); - port = c->rawCmds.at(1).toUInt(); - pi.connectToHost(proxyHost, proxyPort); - } else { - pi.connectToHost(c->rawCmds.at(0), c->rawCmds.at(1).toUInt()); - } - } else { - if (c->command == QFtp::Put) { - if (c->is_ba) { - pi.dtp.setData(c->data.ba); - pi.dtp.setBytesTotal(c->data.ba->size()); - } else if (c->data.dev && (c->data.dev->isOpen() || c->data.dev->open(QIODevice::ReadOnly))) { - pi.dtp.setDevice(c->data.dev); - if (c->data.dev->isSequential()) { - pi.dtp.setBytesTotal(0); - pi.dtp.connect(c->data.dev, SIGNAL(readyRead()), SLOT(dataReadyRead())); - pi.dtp.connect(c->data.dev, SIGNAL(readChannelFinished()), SLOT(dataReadyRead())); - } else { - pi.dtp.setBytesTotal(c->data.dev->size()); - } - } - } else if (c->command == QFtp::Get) { - if (!c->is_ba && c->data.dev) { - pi.dtp.setDevice(c->data.dev); - } - } else if (c->command == QFtp::Close) { - state = QFtp::Closing; - emit q->stateChanged(state); - } - pi.sendCommands(c->rawCmds); - } -} - -/*! \internal -*/ -void QFtpPrivate::_q_piFinished(const QString&) -{ - if (pending.isEmpty()) - return; - QFtpCommand *c = pending.constFirst(); - - if (c->command == QFtp::Close) { - // The order of in which the slots are called is arbitrary, so - // disconnect the SIGNAL-SIGNAL temporary to make sure that we - // don't get the commandFinished() signal before the stateChanged() - // signal. - if (state != QFtp::Unconnected) { - close_waitForStateChange = true; - return; - } - } - emit q_func()->commandFinished(c->id, false); - pending.removeFirst(); - - delete c; - - if (pending.isEmpty()) { - emit q_func()->done(false); - } else { - _q_startNextCommand(); - } -} - -/*! \internal -*/ -void QFtpPrivate::_q_piError(int errorCode, const QString &text) -{ - Q_Q(QFtp); - - if (pending.isEmpty()) { - qWarning("QFtpPrivate::_q_piError was called without pending command!"); - return; - } - - QFtpCommand *c = pending.constFirst(); - - // non-fatal errors - if (c->command == QFtp::Get && pi.currentCommand().startsWith(QLatin1String("SIZE "))) { - pi.dtp.setBytesTotal(0); - return; - } else if (c->command==QFtp::Put && pi.currentCommand().startsWith(QLatin1String("ALLO "))) { - return; - } - - error = QFtp::Error(errorCode); - switch (q->currentCommand()) { - case QFtp::ConnectToHost: - errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Connecting to host failed:\n%1")) - .arg(text); - break; - case QFtp::Login: - errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Login failed:\n%1")) - .arg(text); - break; - case QFtp::List: - errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Listing directory failed:\n%1")) - .arg(text); - break; - case QFtp::Cd: - errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Changing directory failed:\n%1")) - .arg(text); - break; - case QFtp::Get: - errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Downloading file failed:\n%1")) - .arg(text); - break; - case QFtp::Put: - errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Uploading file failed:\n%1")) - .arg(text); - break; - case QFtp::Remove: - errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Removing file failed:\n%1")) - .arg(text); - break; - case QFtp::Mkdir: - errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Creating directory failed:\n%1")) - .arg(text); - break; - case QFtp::Rmdir: - errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Removing directory failed:\n%1")) - .arg(text); - break; - default: - errorString = text; - break; - } - - pi.clearPendingCommands(); - q->clearPendingCommands(); - emit q->commandFinished(c->id, true); - - pending.removeFirst(); - delete c; - if (pending.isEmpty()) - emit q->done(true); - else - _q_startNextCommand(); -} - -/*! \internal -*/ -void QFtpPrivate::_q_piConnectState(int connectState) -{ - state = QFtp::State(connectState); - emit q_func()->stateChanged(state); - if (close_waitForStateChange) { - close_waitForStateChange = false; - _q_piFinished(QLatin1String(QT_TRANSLATE_NOOP("QFtp", "Connection closed"))); - } -} - -/*! \internal -*/ -void QFtpPrivate::_q_piFtpReply(int code, const QString &text) -{ - if (q_func()->currentCommand() == QFtp::RawCommand) { - pi.rawCommand = true; - emit q_func()->rawCommandReply(code, text); - } -} - -/*! - \internal - Destructor. -*/ -QFtp::~QFtp() -{ - abort(); - close(); -} - -QT_END_NAMESPACE - -#include "qftp.moc" - -#include "moc_qftp_p.cpp" -- cgit v1.2.3