1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtNetwork module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 //#define QFTPPI_DEBUG
41 //#define QFTPDTP_DEBUG
42 
43 #include "private/qftp_p.h"
44 #include "qabstractsocket.h"
45 
46 #include "qcoreapplication.h"
47 #include "qtcpsocket.h"
48 #include "qurlinfo_p.h"
49 #include "qstringlist.h"
50 #include "qregexp.h"
51 #include "qtimer.h"
52 #include "qfileinfo.h"
53 #include "qtcpserver.h"
54 #include "qlocale.h"
55 
56 QT_BEGIN_NAMESPACE
57 
58 class QFtpPI;
59 
60 /*
61     The QFtpDTP (DTP = Data Transfer Process) controls all client side
62     data transfer between the client and server.
63 */
64 class QFtpDTP : public QObject
65 {
66     Q_OBJECT
67 
68 public:
69     enum ConnectState {
70         CsHostFound,
71         CsConnected,
72         CsClosed,
73         CsHostNotFound,
74         CsConnectionRefused
75     };
76 
77     QFtpDTP(QFtpPI *p, QObject *parent = nullptr);
78 
79     void setData(QByteArray *);
80     void setDevice(QIODevice *);
81     void writeData();
82     void setBytesTotal(qint64 bytes);
83 
84     bool hasError() const;
85     QString errorMessage() const;
86     void clearError();
87 
88     void connectToHost(const QString & host, quint16 port);
89     int setupListener(const QHostAddress &address);
90     void waitForConnection();
91 
92     QTcpSocket::SocketState state() const;
93     qint64 bytesAvailable() const;
94     qint64 read(char *data, qint64 maxlen);
95     QByteArray readAll();
96 
97     void abortConnection();
98 
99     static bool parseDir(const QByteArray &buffer, const QString &userName, QUrlInfo *info);
100 
101 signals:
102     void listInfo(const QUrlInfo&);
103     void readyRead();
104     void dataTransferProgress(qint64, qint64);
105 
106     void connectState(int);
107 
108 private slots:
109     void socketConnected();
110     void socketReadyRead();
111     void socketError(QAbstractSocket::SocketError);
112     void socketConnectionClosed();
113     void socketBytesWritten(qint64);
114     void setupSocket();
115 
116     void dataReadyRead();
117 
118 private:
119     void clearData();
120 
121     QTcpSocket *socket;
122     QTcpServer listener;
123 
124     QFtpPI *pi;
125     QString err;
126     qint64 bytesDone;
127     qint64 bytesTotal;
128     bool callWriteData;
129 
130     // If is_ba is true, ba is used; ba is never 0.
131     // Otherwise dev is used; dev can be 0 or not.
132     union {
133         QByteArray *ba;
134         QIODevice *dev;
135     } data;
136     bool is_ba;
137 
138     QByteArray bytesFromSocket;
139 };
140 
141 /**********************************************************************
142  *
143  * QFtpPI - Protocol Interpreter
144  *
145  *********************************************************************/
146 
147 class QFtpPI : public QObject
148 {
149     Q_OBJECT
150 
151 public:
152     QFtpPI(QObject *parent = nullptr);
153 
154     void connectToHost(const QString &host, quint16 port);
155 
156     bool sendCommands(const QStringList &cmds);
sendCommand(const QString & cmd)157     bool sendCommand(const QString &cmd)
158         { return sendCommands(QStringList(cmd)); }
159 
160     void clearPendingCommands();
161     void abort();
162 
currentCommand() const163     QString currentCommand() const
164         { return currentCmd; }
165 
166     bool rawCommand;
167     bool transferConnectionExtended;
168 
169     QFtpDTP dtp; // the PI has a DTP which is not the design of RFC 959, but it
170                  // makes the design simpler this way
171 signals:
172     void connectState(int);
173     void finished(const QString&);
174     void error(int, const QString&);
175     void rawFtpReply(int, const QString&);
176 
177 private slots:
178     void hostFound();
179     void connected();
180     void connectionClosed();
181     void delayedCloseFinished();
182     void readyRead();
183     void error(QAbstractSocket::SocketError);
184 
185     void dtpConnectState(int);
186 
187 private:
188     // the states are modelled after the generalized state diagram of RFC 959,
189     // page 58
190     enum State {
191         Begin,
192         Idle,
193         Waiting,
194         Success,
195         Failure
196     };
197 
198     enum AbortState {
199         None,
200         AbortStarted,
201         WaitForAbortToFinish
202     };
203 
204     bool processReply();
205     bool startNextCmd();
206 
207     QTcpSocket commandSocket;
208     QString replyText;
209     char replyCode[3];
210     State state;
211     AbortState abortState;
212     QStringList pendingCommands;
213     QString currentCmd;
214 
215     bool waitForDtpToConnect;
216     bool waitForDtpToClose;
217 
218     QByteArray bytesFromSocket;
219 
220     friend class QFtpDTP;
221 };
222 
223 /**********************************************************************
224  *
225  * QFtpCommand implemenatation
226  *
227  *********************************************************************/
228 class QFtpCommand
229 {
230 public:
231     QFtpCommand(QFtp::Command cmd, const QStringList &raw, const QByteArray &ba);
232     QFtpCommand(QFtp::Command cmd, const QStringList &raw, QIODevice *dev = nullptr);
233     ~QFtpCommand();
234 
235     int id;
236     QFtp::Command command;
237     QStringList rawCmds;
238 
239     // If is_ba is true, ba is used; ba is never 0.
240     // Otherwise dev is used; dev can be 0 or not.
241     union {
242         QByteArray *ba;
243         QIODevice *dev;
244     } data;
245     bool is_ba;
246 
247 };
248 
nextId()249 static int nextId()
250 {
251     static QBasicAtomicInt counter = Q_BASIC_ATOMIC_INITIALIZER(0);
252     return 1 + counter.fetchAndAddRelaxed(1);
253 }
254 
QFtpCommand(QFtp::Command cmd,const QStringList & raw,const QByteArray & ba)255 QFtpCommand::QFtpCommand(QFtp::Command cmd, const QStringList &raw, const QByteArray &ba)
256     : command(cmd), rawCmds(raw), is_ba(true)
257 {
258     id = nextId();
259     data.ba = new QByteArray(ba);
260 }
261 
QFtpCommand(QFtp::Command cmd,const QStringList & raw,QIODevice * dev)262 QFtpCommand::QFtpCommand(QFtp::Command cmd, const QStringList &raw, QIODevice *dev)
263     : command(cmd), rawCmds(raw), is_ba(false)
264 {
265     id = nextId();
266     data.dev = dev;
267 }
268 
~QFtpCommand()269 QFtpCommand::~QFtpCommand()
270 {
271     if (is_ba)
272         delete data.ba;
273 }
274 
275 /**********************************************************************
276  *
277  * QFtpDTP implemenatation
278  *
279  *********************************************************************/
QFtpDTP(QFtpPI * p,QObject * parent)280 QFtpDTP::QFtpDTP(QFtpPI *p, QObject *parent) :
281     QObject(parent),
282     socket(nullptr),
283     listener(this),
284     pi(p),
285     callWriteData(false)
286 {
287     clearData();
288     listener.setObjectName(QLatin1String("QFtpDTP active state server"));
289     connect(&listener, SIGNAL(newConnection()), SLOT(setupSocket()));
290 }
291 
setData(QByteArray * ba)292 void QFtpDTP::setData(QByteArray *ba)
293 {
294     is_ba = true;
295     data.ba = ba;
296 }
297 
setDevice(QIODevice * dev)298 void QFtpDTP::setDevice(QIODevice *dev)
299 {
300     is_ba = false;
301     data.dev = dev;
302 }
303 
setBytesTotal(qint64 bytes)304 void QFtpDTP::setBytesTotal(qint64 bytes)
305 {
306     bytesTotal = bytes;
307     bytesDone = 0;
308     emit dataTransferProgress(bytesDone, bytesTotal);
309 }
310 
connectToHost(const QString & host,quint16 port)311 void QFtpDTP::connectToHost(const QString & host, quint16 port)
312 {
313     bytesFromSocket.clear();
314 
315     if (socket) {
316         delete socket;
317         socket = nullptr;
318     }
319     socket = new QTcpSocket(this);
320 #ifndef QT_NO_BEARERMANAGEMENT // ### Qt6: Remove section
321     //copy network session down to the socket
322     socket->setProperty("_q_networksession", property("_q_networksession"));
323 #endif
324     socket->setObjectName(QLatin1String("QFtpDTP Passive state socket"));
325     connect(socket, SIGNAL(connected()), SLOT(socketConnected()));
326     connect(socket, SIGNAL(readyRead()), SLOT(socketReadyRead()));
327     connect(socket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), SLOT(socketError(QAbstractSocket::SocketError)));
328     connect(socket, SIGNAL(disconnected()), SLOT(socketConnectionClosed()));
329     connect(socket, SIGNAL(bytesWritten(qint64)), SLOT(socketBytesWritten(qint64)));
330 
331     socket->connectToHost(host, port);
332 }
333 
setupListener(const QHostAddress & address)334 int QFtpDTP::setupListener(const QHostAddress &address)
335 {
336 #ifndef QT_NO_BEARERMANAGEMENT // ### Qt6: Remove section
337     //copy network session down to the socket
338     listener.setProperty("_q_networksession", property("_q_networksession"));
339 #endif
340     if (!listener.isListening() && !listener.listen(address, 0))
341         return -1;
342     return listener.serverPort();
343 }
344 
waitForConnection()345 void QFtpDTP::waitForConnection()
346 {
347     // This function is only interesting in Active transfer mode; it works
348     // around a limitation in QFtp's design by blocking, waiting for an
349     // incoming connection. For the default Passive mode, it does nothing.
350     if (listener.isListening())
351         listener.waitForNewConnection();
352 }
353 
state() const354 QTcpSocket::SocketState QFtpDTP::state() const
355 {
356     return socket ? socket->state() : QTcpSocket::UnconnectedState;
357 }
358 
bytesAvailable() const359 qint64 QFtpDTP::bytesAvailable() const
360 {
361     if (!socket || socket->state() != QTcpSocket::ConnectedState)
362         return (qint64) bytesFromSocket.size();
363     return socket->bytesAvailable();
364 }
365 
read(char * data,qint64 maxlen)366 qint64 QFtpDTP::read(char *data, qint64 maxlen)
367 {
368     qint64 read;
369     if (socket && socket->state() == QTcpSocket::ConnectedState) {
370         read = socket->read(data, maxlen);
371     } else {
372         read = qMin(maxlen, qint64(bytesFromSocket.size()));
373         memcpy(data, bytesFromSocket.data(), read);
374         bytesFromSocket.remove(0, read);
375     }
376 
377     bytesDone += read;
378     return read;
379 }
380 
readAll()381 QByteArray QFtpDTP::readAll()
382 {
383     QByteArray tmp;
384     if (socket && socket->state() == QTcpSocket::ConnectedState) {
385         tmp = socket->readAll();
386         bytesDone += tmp.size();
387     } else {
388         tmp = bytesFromSocket;
389         bytesFromSocket.clear();
390     }
391     return tmp;
392 }
393 
writeData()394 void QFtpDTP::writeData()
395 {
396     if (!socket)
397         return;
398 
399     if (is_ba) {
400 #if defined(QFTPDTP_DEBUG)
401         qDebug("QFtpDTP::writeData: write %d bytes", data.ba->size());
402 #endif
403         if (data.ba->size() == 0)
404             emit dataTransferProgress(0, bytesTotal);
405         else
406             socket->write(data.ba->data(), data.ba->size());
407 
408         socket->close();
409 
410         clearData();
411     } else if (data.dev) {
412         callWriteData = false;
413         const qint64 blockSize = 16*1024;
414         char buf[16*1024];
415         qint64 read = data.dev->read(buf, blockSize);
416 #if defined(QFTPDTP_DEBUG)
417         qDebug("QFtpDTP::writeData: write() of size %lli bytes", read);
418 #endif
419         if (read > 0) {
420             socket->write(buf, read);
421         } else if (read == -1 || (!data.dev->isSequential() && data.dev->atEnd())) {
422             // error or EOF
423             if (bytesDone == 0 && socket->bytesToWrite() == 0)
424                 emit dataTransferProgress(0, bytesTotal);
425             socket->close();
426             clearData();
427         }
428 
429         // do we continue uploading?
430         callWriteData = data.dev != nullptr;
431     }
432 }
433 
dataReadyRead()434 void QFtpDTP::dataReadyRead()
435 {
436     writeData();
437 }
438 
hasError() const439 inline bool QFtpDTP::hasError() const
440 {
441     return !err.isNull();
442 }
443 
errorMessage() const444 inline QString QFtpDTP::errorMessage() const
445 {
446     return err;
447 }
448 
clearError()449 inline void QFtpDTP::clearError()
450 {
451     err.clear();
452 }
453 
abortConnection()454 void QFtpDTP::abortConnection()
455 {
456 #if defined(QFTPDTP_DEBUG)
457     qDebug("QFtpDTP::abortConnection, bytesAvailable == %lli",
458            socket ? socket->bytesAvailable() : (qint64) 0);
459 #endif
460     callWriteData = false;
461     clearData();
462 
463     if (socket)
464         socket->abort();
465 }
466 
_q_fixupDateTime(QDateTime * dateTime)467 static void _q_fixupDateTime(QDateTime *dateTime)
468 {
469     // Adjust for future tolerance.
470     const int futureTolerance = 86400;
471     if (dateTime->secsTo(QDateTime::currentDateTime()) < -futureTolerance) {
472         QDate d = dateTime->date();
473         d.setDate(d.year() - 1, d.month(), d.day());
474         dateTime->setDate(d);
475     }
476 }
477 
_q_parseUnixDir(const QStringList & tokens,const QString & userName,QUrlInfo * info)478 static void _q_parseUnixDir(const QStringList &tokens, const QString &userName, QUrlInfo *info)
479 {
480     // Unix style, 7 + 1 entries
481     // -rw-r--r--    1 ftp      ftp      17358091 Aug 10  2004 qt-x11-free-3.3.3.tar.gz
482     // drwxr-xr-x    3 ftp      ftp          4096 Apr 14  2000 compiled-examples
483     // lrwxrwxrwx    1 ftp      ftp             9 Oct 29  2005 qtscape -> qtmozilla
484     if (tokens.size() != 8)
485         return;
486 
487     char first = tokens.at(1).at(0).toLatin1();
488     if (first == 'd') {
489         info->setDir(true);
490         info->setFile(false);
491         info->setSymLink(false);
492     } else if (first == '-') {
493         info->setDir(false);
494         info->setFile(true);
495         info->setSymLink(false);
496     } else if (first == 'l') {
497         info->setDir(true);
498         info->setFile(false);
499         info->setSymLink(true);
500     }
501 
502     // Resolve filename
503     QString name = tokens.at(7);
504     if (info->isSymLink()) {
505         int linkPos = name.indexOf(QLatin1String(" ->"));
506         if (linkPos != -1)
507             name.resize(linkPos);
508     }
509     info->setName(name);
510 
511     // Resolve owner & group
512     info->setOwner(tokens.at(3));
513     info->setGroup(tokens.at(4));
514 
515     // Resolve size
516     info->setSize(tokens.at(5).toLongLong());
517 
518     QStringList formats;
519     formats << QLatin1String("MMM dd  yyyy") << QLatin1String("MMM dd hh:mm") << QLatin1String("MMM  d  yyyy")
520             << QLatin1String("MMM  d hh:mm") << QLatin1String("MMM  d yyyy") << QLatin1String("MMM dd yyyy");
521 
522     QString dateString = tokens.at(6);
523     dateString[0] = dateString[0].toUpper();
524 
525     // Resolve the modification date by parsing all possible formats
526     QDateTime dateTime;
527     int n = 0;
528 #if QT_CONFIG(datestring)
529     do {
530         dateTime = QLocale::c().toDateTime(dateString, formats.at(n++));
531     }  while (n < formats.size() && (!dateTime.isValid()));
532 #endif
533 
534     if (n == 2 || n == 4) {
535         // Guess the year.
536         dateTime.setDate(QDate(QDate::currentDate().year(),
537                                dateTime.date().month(),
538                                dateTime.date().day()));
539         _q_fixupDateTime(&dateTime);
540     }
541     if (dateTime.isValid())
542         info->setLastModified(dateTime);
543 
544     // Resolve permissions
545     int permissions = 0;
546     const QString &p = tokens.at(2);
547     permissions |= (p[0] == QLatin1Char('r') ? QUrlInfo::ReadOwner : 0);
548     permissions |= (p[1] == QLatin1Char('w') ? QUrlInfo::WriteOwner : 0);
549     permissions |= (p[2] == QLatin1Char('x') ? QUrlInfo::ExeOwner : 0);
550     permissions |= (p[3] == QLatin1Char('r') ? QUrlInfo::ReadGroup : 0);
551     permissions |= (p[4] == QLatin1Char('w') ? QUrlInfo::WriteGroup : 0);
552     permissions |= (p[5] == QLatin1Char('x') ? QUrlInfo::ExeGroup : 0);
553     permissions |= (p[6] == QLatin1Char('r') ? QUrlInfo::ReadOther : 0);
554     permissions |= (p[7] == QLatin1Char('w') ? QUrlInfo::WriteOther : 0);
555     permissions |= (p[8] == QLatin1Char('x') ? QUrlInfo::ExeOther : 0);
556     info->setPermissions(permissions);
557 
558     bool isOwner = info->owner() == userName;
559     info->setReadable((permissions & QUrlInfo::ReadOther) || ((permissions & QUrlInfo::ReadOwner) && isOwner));
560     info->setWritable((permissions & QUrlInfo::WriteOther) || ((permissions & QUrlInfo::WriteOwner) && isOwner));
561 }
562 
_q_parseDosDir(const QStringList & tokens,const QString & userName,QUrlInfo * info)563 static void _q_parseDosDir(const QStringList &tokens, const QString &userName, QUrlInfo *info)
564 {
565     // DOS style, 3 + 1 entries
566     // 01-16-02  11:14AM       <DIR>          epsgroup
567     // 06-05-03  03:19PM                 1973 readme.txt
568     if (tokens.size() != 4)
569         return;
570 
571     Q_UNUSED(userName);
572 
573     QString name = tokens.at(3);
574     info->setName(name);
575     info->setSymLink(name.endsWith(QLatin1String(".lnk"), Qt::CaseInsensitive));
576 
577     if (tokens.at(2) == QLatin1String("<DIR>")) {
578         info->setFile(false);
579         info->setDir(true);
580     } else {
581         info->setFile(true);
582         info->setDir(false);
583         info->setSize(tokens.at(2).toLongLong());
584     }
585 
586     // Note: We cannot use QFileInfo; permissions are for the server-side
587     // machine, and QFileInfo's behavior depends on the local platform.
588     int permissions = QUrlInfo::ReadOwner | QUrlInfo::WriteOwner
589                       | QUrlInfo::ReadGroup | QUrlInfo::WriteGroup
590                       | QUrlInfo::ReadOther | QUrlInfo::WriteOther;
591     QStringRef ext;
592     int extIndex = name.lastIndexOf(QLatin1Char('.'));
593     if (extIndex != -1)
594         ext = name.midRef(extIndex + 1);
595     if (ext == QLatin1String("exe") || ext == QLatin1String("bat") || ext == QLatin1String("com"))
596         permissions |= QUrlInfo::ExeOwner | QUrlInfo::ExeGroup | QUrlInfo::ExeOther;
597     info->setPermissions(permissions);
598 
599     info->setReadable(true);
600     info->setWritable(info->isFile());
601 
602     QDateTime dateTime;
603 #if QT_CONFIG(datestring)
604     dateTime = QLocale::c().toDateTime(tokens.at(1), QLatin1String("MM-dd-yy  hh:mmAP"));
605     if (dateTime.date().year() < 1971) {
606         dateTime.setDate(QDate(dateTime.date().year() + 100,
607                                dateTime.date().month(),
608                                dateTime.date().day()));
609     }
610 #endif
611 
612     info->setLastModified(dateTime);
613 
614 }
615 
parseDir(const QByteArray & buffer,const QString & userName,QUrlInfo * info)616 bool QFtpDTP::parseDir(const QByteArray &buffer, const QString &userName, QUrlInfo *info)
617 {
618     if (buffer.isEmpty())
619         return false;
620 
621     QString bufferStr = QString::fromUtf8(buffer).trimmed();
622 
623     // Unix style FTP servers
624     QRegExp unixPattern(QLatin1String("^([\\-dl])([a-zA-Z\\-]{9,9})\\s+\\d+\\s+(\\S*)\\s+"
625                                       "(\\S*)\\s+(\\d+)\\s+(\\S+\\s+\\S+\\s+\\S+)\\s+(\\S.*)"));
626     if (unixPattern.indexIn(bufferStr) == 0) {
627         _q_parseUnixDir(unixPattern.capturedTexts(), userName, info);
628         return true;
629     }
630 
631     // DOS style FTP servers
632     QRegExp dosPattern(QLatin1String("^(\\d\\d-\\d\\d-\\d\\d\\ \\ \\d\\d:\\d\\d[AP]M)\\s+"
633                                      "(<DIR>|\\d+)\\s+(\\S.*)$"));
634     if (dosPattern.indexIn(bufferStr) == 0) {
635         _q_parseDosDir(dosPattern.capturedTexts(), userName, info);
636         return true;
637     }
638 
639     // Unsupported
640     return false;
641 }
642 
socketConnected()643 void QFtpDTP::socketConnected()
644 {
645     bytesDone = 0;
646 #if defined(QFTPDTP_DEBUG)
647     qDebug("QFtpDTP::connectState(CsConnected)");
648 #endif
649     emit connectState(QFtpDTP::CsConnected);
650 }
651 
socketReadyRead()652 void QFtpDTP::socketReadyRead()
653 {
654     if (!socket)
655         return;
656 
657     if (pi->currentCommand().isEmpty()) {
658         socket->close();
659 #if defined(QFTPDTP_DEBUG)
660         qDebug("QFtpDTP::connectState(CsClosed)");
661 #endif
662         emit connectState(QFtpDTP::CsClosed);
663         return;
664     }
665 
666     if (pi->abortState != QFtpPI::None) {
667         // discard data
668         socket->readAll();
669         return;
670     }
671 
672     if (pi->currentCommand().startsWith(QLatin1String("LIST"))) {
673         while (socket->canReadLine()) {
674             QUrlInfo i;
675             QByteArray line = socket->readLine();
676 #if defined(QFTPDTP_DEBUG)
677             qDebug("QFtpDTP read (list): '%s'", line.constData());
678 #endif
679             if (parseDir(line, QLatin1String(""), &i)) {
680                 emit listInfo(i);
681             } else {
682                 // some FTP servers don't return a 550 if the file or directory
683                 // does not exist, but rather write a text to the data socket
684                 // -- try to catch these cases
685                 if (line.endsWith("No such file or directory\r\n"))
686                     err = QString::fromUtf8(line);
687             }
688         }
689     } else {
690         if (!is_ba && data.dev) {
691             do {
692                 QByteArray ba;
693                 ba.resize(socket->bytesAvailable());
694                 qint64 bytesRead = socket->read(ba.data(), ba.size());
695                 if (bytesRead < 0) {
696                     // a read following a readyRead() signal will
697                     // never fail.
698                     return;
699                 }
700                 ba.resize(bytesRead);
701                 bytesDone += bytesRead;
702 #if defined(QFTPDTP_DEBUG)
703                 qDebug("QFtpDTP read: %lli bytes (total %lli bytes)", bytesRead, bytesDone);
704 #endif
705                 if (data.dev)       // make sure it wasn't deleted in the slot
706                     data.dev->write(ba);
707                 emit dataTransferProgress(bytesDone, bytesTotal);
708 
709                 // Need to loop; dataTransferProgress is often connected to
710                 // slots that update the GUI (e.g., progress bar values), and
711                 // if events are processed, more data may have arrived.
712             } while (socket->bytesAvailable());
713         } else {
714 #if defined(QFTPDTP_DEBUG)
715             qDebug("QFtpDTP readyRead: %lli bytes available (total %lli bytes read)",
716                    bytesAvailable(), bytesDone);
717 #endif
718             emit dataTransferProgress(bytesDone+socket->bytesAvailable(), bytesTotal);
719             emit readyRead();
720         }
721     }
722 }
723 
socketError(QAbstractSocket::SocketError e)724 void QFtpDTP::socketError(QAbstractSocket::SocketError e)
725 {
726     if (e == QTcpSocket::HostNotFoundError) {
727 #if defined(QFTPDTP_DEBUG)
728         qDebug("QFtpDTP::connectState(CsHostNotFound)");
729 #endif
730         emit connectState(QFtpDTP::CsHostNotFound);
731     } else if (e == QTcpSocket::ConnectionRefusedError) {
732 #if defined(QFTPDTP_DEBUG)
733         qDebug("QFtpDTP::connectState(CsConnectionRefused)");
734 #endif
735         emit connectState(QFtpDTP::CsConnectionRefused);
736     }
737 }
738 
socketConnectionClosed()739 void QFtpDTP::socketConnectionClosed()
740 {
741     if (!is_ba && data.dev) {
742         clearData();
743     }
744 
745     if (socket->isOpen())
746         bytesFromSocket = socket->readAll();
747     else
748         bytesFromSocket.clear();
749 #if defined(QFTPDTP_DEBUG)
750     qDebug("QFtpDTP::connectState(CsClosed)");
751 #endif
752     emit connectState(QFtpDTP::CsClosed);
753 }
754 
socketBytesWritten(qint64 bytes)755 void QFtpDTP::socketBytesWritten(qint64 bytes)
756 {
757     bytesDone += bytes;
758 #if defined(QFTPDTP_DEBUG)
759     qDebug("QFtpDTP::bytesWritten(%lli)", bytesDone);
760 #endif
761     emit dataTransferProgress(bytesDone, bytesTotal);
762     if (callWriteData)
763         writeData();
764 }
765 
setupSocket()766 void QFtpDTP::setupSocket()
767 {
768     socket = listener.nextPendingConnection();
769     socket->setObjectName(QLatin1String("QFtpDTP Active state socket"));
770     connect(socket, SIGNAL(connected()), SLOT(socketConnected()));
771     connect(socket, SIGNAL(readyRead()), SLOT(socketReadyRead()));
772     connect(socket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), SLOT(socketError(QAbstractSocket::SocketError)));
773     connect(socket, SIGNAL(disconnected()), SLOT(socketConnectionClosed()));
774     connect(socket, SIGNAL(bytesWritten(qint64)), SLOT(socketBytesWritten(qint64)));
775 
776     listener.close();
777 }
778 
clearData()779 void QFtpDTP::clearData()
780 {
781     is_ba = false;
782     data.dev = nullptr;
783 }
784 
785 /**********************************************************************
786  *
787  * QFtpPI implemenatation
788  *
789  *********************************************************************/
QFtpPI(QObject * parent)790 QFtpPI::QFtpPI(QObject *parent) :
791     QObject(parent),
792     rawCommand(false),
793     transferConnectionExtended(true),
794     dtp(this),
795     commandSocket(nullptr),
796     state(Begin), abortState(None),
797     currentCmd(QString()),
798     waitForDtpToConnect(false),
799     waitForDtpToClose(false)
800 {
801     commandSocket.setObjectName(QLatin1String("QFtpPI_socket"));
802     connect(&commandSocket, SIGNAL(hostFound()),
803             SLOT(hostFound()));
804     connect(&commandSocket, SIGNAL(connected()),
805             SLOT(connected()));
806     connect(&commandSocket, SIGNAL(disconnected()),
807             SLOT(connectionClosed()));
808     connect(&commandSocket, SIGNAL(readyRead()),
809             SLOT(readyRead()));
810     connect(&commandSocket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)),
811             SLOT(error(QAbstractSocket::SocketError)));
812 
813     connect(&dtp, SIGNAL(connectState(int)),
814              SLOT(dtpConnectState(int)));
815 }
816 
connectToHost(const QString & host,quint16 port)817 void QFtpPI::connectToHost(const QString &host, quint16 port)
818 {
819     emit connectState(QFtp::HostLookup);
820 #ifndef QT_NO_BEARERMANAGEMENT // ### Qt6: Remove section
821     //copy network session down to the socket & DTP
822     commandSocket.setProperty("_q_networksession", property("_q_networksession"));
823     dtp.setProperty("_q_networksession", property("_q_networksession"));
824 #endif
825     commandSocket.connectToHost(host, port);
826 }
827 
828 /*
829   \internal
830 
831   Sends the sequence of commands \a cmds to the FTP server. When the commands
832   are all done the finished() signal is emitted. When an error occurs, the
833   error() signal is emitted.
834 
835   If there are pending commands in the queue this functions returns \c false and
836   the \a cmds are not added to the queue; otherwise it returns \c true.
837 */
sendCommands(const QStringList & cmds)838 bool QFtpPI::sendCommands(const QStringList &cmds)
839 {
840     if (!pendingCommands.isEmpty())
841         return false;
842 
843     if (commandSocket.state() != QTcpSocket::ConnectedState || state!=Idle) {
844         emit error(QFtp::NotConnected, QFtp::tr("Not connected"));
845         return true; // there are no pending commands
846     }
847 
848     pendingCommands = cmds;
849     startNextCmd();
850     return true;
851 }
852 
clearPendingCommands()853 void QFtpPI::clearPendingCommands()
854 {
855     pendingCommands.clear();
856     dtp.abortConnection();
857     currentCmd.clear();
858     state = Idle;
859 }
860 
abort()861 void QFtpPI::abort()
862 {
863     pendingCommands.clear();
864 
865     if (abortState != None)
866         // ABOR already sent
867         return;
868 
869     if (currentCmd.isEmpty())
870         return; //no command in progress
871 
872     if (currentCmd.startsWith(QLatin1String("STOR "))) {
873         abortState = AbortStarted;
874 #if defined(QFTPPI_DEBUG)
875         qDebug("QFtpPI send: ABOR");
876 #endif
877         commandSocket.write("ABOR\r\n", 6);
878 
879         dtp.abortConnection();
880     } else {
881         //Deviation from RFC 959:
882         //Most FTP servers do not support ABOR, or require the telnet
883         //IP & synch sequence (TCP urgent data) which is not supported by QTcpSocket.
884         //Following what most FTP clients do, just reset the data connection and wait for 426
885         abortState = WaitForAbortToFinish;
886         dtp.abortConnection();
887     }
888 }
889 
hostFound()890 void QFtpPI::hostFound()
891 {
892     emit connectState(QFtp::Connecting);
893 }
894 
connected()895 void QFtpPI::connected()
896 {
897     state = Begin;
898 #if defined(QFTPPI_DEBUG)
899 //    qDebug("QFtpPI state: %d [connected()]", state);
900 #endif
901     // try to improve performance by setting TCP_NODELAY
902     commandSocket.setSocketOption(QAbstractSocket::LowDelayOption, 1);
903 
904     emit connectState(QFtp::Connected);
905 }
906 
connectionClosed()907 void QFtpPI::connectionClosed()
908 {
909     commandSocket.close();
910     emit connectState(QFtp::Unconnected);
911 }
912 
delayedCloseFinished()913 void QFtpPI::delayedCloseFinished()
914 {
915     emit connectState(QFtp::Unconnected);
916 }
917 
error(QAbstractSocket::SocketError e)918 void QFtpPI::error(QAbstractSocket::SocketError e)
919 {
920     if (e == QTcpSocket::HostNotFoundError) {
921         emit connectState(QFtp::Unconnected);
922         emit error(QFtp::HostNotFound,
923                     QFtp::tr("Host %1 not found").arg(commandSocket.peerName()));
924     } else if (e == QTcpSocket::ConnectionRefusedError) {
925         emit connectState(QFtp::Unconnected);
926         emit error(QFtp::ConnectionRefused,
927                     QFtp::tr("Connection refused to host %1").arg(commandSocket.peerName()));
928     } else if (e == QTcpSocket::SocketTimeoutError) {
929         emit connectState(QFtp::Unconnected);
930         emit error(QFtp::ConnectionRefused,
931                    QFtp::tr("Connection timed out to host %1").arg(commandSocket.peerName()));
932     }
933 }
934 
readyRead()935 void QFtpPI::readyRead()
936 {
937     if (waitForDtpToClose)
938         return;
939 
940     while (commandSocket.canReadLine()) {
941         // read line with respect to line continuation
942         QString line = QString::fromUtf8(commandSocket.readLine());
943         if (replyText.isEmpty()) {
944             if (line.length() < 3) {
945                 // protocol error
946                 return;
947             }
948             const int lowerLimit[3] = {1,0,0};
949             const int upperLimit[3] = {5,5,9};
950             for (int i=0; i<3; i++) {
951                 replyCode[i] = line.at(i).digitValue();
952                 if (replyCode[i]<lowerLimit[i] || replyCode[i]>upperLimit[i]) {
953                     // protocol error
954                     return;
955                 }
956             }
957         }
958         const char count[4] = { char('0' + replyCode[0]), char('0' + replyCode[1]),
959                                 char('0' + replyCode[2]), char(' ') };
960         QString endOfMultiLine(QLatin1String(count, 4));
961         QString lineCont(endOfMultiLine);
962         lineCont[3] = QLatin1Char('-');
963         QStringRef lineLeft4 = line.leftRef(4);
964 
965         while (lineLeft4 != endOfMultiLine) {
966             if (lineLeft4 == lineCont)
967                 replyText += line.midRef(4); // strip 'xyz-'
968             else
969                 replyText += line;
970             if (!commandSocket.canReadLine())
971                 return;
972             line = QString::fromUtf8(commandSocket.readLine());
973             lineLeft4 = line.leftRef(4);
974         }
975         replyText += line.midRef(4); // strip reply code 'xyz '
976         if (replyText.endsWith(QLatin1String("\r\n")))
977             replyText.chop(2);
978 
979         if (processReply())
980             replyText = QLatin1String("");
981     }
982 }
983 
984 /*
985   \internal
986 
987   Process a reply from the FTP server.
988 
989   Returns \c true if the reply was processed or false if the reply has to be
990   processed at a later point.
991 */
processReply()992 bool QFtpPI::processReply()
993 {
994 #if defined(QFTPPI_DEBUG)
995 //    qDebug("QFtpPI state: %d [processReply() begin]", state);
996     if (replyText.length() < 400)
997         qDebug("QFtpPI recv: %d %s", 100*replyCode[0]+10*replyCode[1]+replyCode[2], replyText.toLatin1().constData());
998     else
999         qDebug("QFtpPI recv: %d (text skipped)", 100*replyCode[0]+10*replyCode[1]+replyCode[2]);
1000 #endif
1001 
1002     int replyCodeInt = 100*replyCode[0] + 10*replyCode[1] + replyCode[2];
1003 
1004     // process 226 replies ("Closing Data Connection") only when the data
1005     // connection is really closed to avoid short reads of the DTP
1006     if (replyCodeInt == 226 || (replyCodeInt == 250 && currentCmd.startsWith(QLatin1String("RETR")))) {
1007         if (dtp.state() != QTcpSocket::UnconnectedState) {
1008             waitForDtpToClose = true;
1009             return false;
1010         }
1011     }
1012 
1013     switch (abortState) {
1014         case AbortStarted:
1015             abortState = WaitForAbortToFinish;
1016             break;
1017         case WaitForAbortToFinish:
1018             abortState = None;
1019             return true;
1020         default:
1021             break;
1022     }
1023 
1024     // get new state
1025     static const State table[5] = {
1026         /* 1yz   2yz      3yz   4yz      5yz */
1027         Waiting, Success, Idle, Failure, Failure
1028     };
1029     switch (state) {
1030         case Begin:
1031             if (replyCode[0] == 1) {
1032                 return true;
1033             } else if (replyCode[0] == 2) {
1034                 state = Idle;
1035                 emit finished(QFtp::tr("Connected to host %1").arg(commandSocket.peerName()));
1036                 break;
1037             }
1038             // reply codes not starting with 1 or 2 are not handled.
1039             return true;
1040         case Waiting:
1041             if (static_cast<signed char>(replyCode[0]) < 0 || replyCode[0] > 5)
1042                 state = Failure;
1043             else
1044             if (replyCodeInt == 202)
1045                 state = Failure;
1046             else
1047                 state = table[replyCode[0] - 1];
1048             break;
1049         default:
1050             // ignore unrequested message
1051             return true;
1052     }
1053 #if defined(QFTPPI_DEBUG)
1054 //    qDebug("QFtpPI state: %d [processReply() intermediate]", state);
1055 #endif
1056 
1057     // special actions on certain replies
1058     emit rawFtpReply(replyCodeInt, replyText);
1059     if (rawCommand) {
1060         rawCommand = false;
1061     } else if (replyCodeInt == 227) {
1062         // 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2)
1063         // rfc959 does not define this response precisely, and gives
1064         // both examples where the parenthesis are used, and where
1065         // they are missing. We need to scan for the address and host
1066         // info.
1067         QRegExp addrPortPattern(QLatin1String("(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+)"));
1068         if (addrPortPattern.indexIn(replyText) == -1) {
1069 #if defined(QFTPPI_DEBUG)
1070             qDebug("QFtp: bad 227 response -- address and port information missing");
1071 #endif
1072             // this error should be reported
1073         } else {
1074             const QStringList lst = addrPortPattern.capturedTexts();
1075             QString host = lst[1] + QLatin1Char('.') + lst[2] + QLatin1Char('.') + lst[3] + QLatin1Char('.') + lst[4];
1076             quint16 port = (lst[5].toUInt() << 8) + lst[6].toUInt();
1077             waitForDtpToConnect = true;
1078             dtp.connectToHost(host, port);
1079         }
1080     } else if (replyCodeInt == 229) {
1081         // 229 Extended Passive mode OK (|||10982|)
1082         int portPos = replyText.indexOf(QLatin1Char('('));
1083         if (portPos == -1) {
1084 #if defined(QFTPPI_DEBUG)
1085             qDebug("QFtp: bad 229 response -- port information missing");
1086 #endif
1087             // this error should be reported
1088         } else {
1089             ++portPos;
1090             QChar delimiter = replyText.at(portPos);
1091             const auto epsvParameters = replyText.midRef(portPos).split(delimiter);
1092 
1093             waitForDtpToConnect = true;
1094             dtp.connectToHost(commandSocket.peerAddress().toString(),
1095                               epsvParameters.at(3).toInt());
1096         }
1097 
1098     } else if (replyCodeInt == 230) {
1099         if (currentCmd.startsWith(QLatin1String("USER ")) && pendingCommands.count()>0 &&
1100             pendingCommands.constFirst().startsWith(QLatin1String("PASS "))) {
1101             // no need to send the PASS -- we are already logged in
1102             pendingCommands.pop_front();
1103         }
1104         // 230 User logged in, proceed.
1105         emit connectState(QFtp::LoggedIn);
1106     } else if (replyCodeInt == 213) {
1107         // 213 File status.
1108         if (currentCmd.startsWith(QLatin1String("SIZE ")))
1109             dtp.setBytesTotal(replyText.simplified().toLongLong());
1110     } else if (replyCode[0]==1 && currentCmd.startsWith(QLatin1String("STOR "))) {
1111         dtp.waitForConnection();
1112         dtp.writeData();
1113     }
1114 
1115     // react on new state
1116     switch (state) {
1117         case Begin:
1118             // should never happen
1119             break;
1120         case Success:
1121             // success handling
1122             state = Idle;
1123             Q_FALLTHROUGH();
1124         case Idle:
1125             if (dtp.hasError()) {
1126                 emit error(QFtp::UnknownError, dtp.errorMessage());
1127                 dtp.clearError();
1128             }
1129             startNextCmd();
1130             break;
1131         case Waiting:
1132             // do nothing
1133             break;
1134         case Failure:
1135             // If the EPSV or EPRT commands fail, replace them with
1136             // the old PASV and PORT instead and try again.
1137             if (currentCmd.startsWith(QLatin1String("EPSV"))) {
1138                 transferConnectionExtended = false;
1139                 pendingCommands.prepend(QLatin1String("PASV\r\n"));
1140             } else if (currentCmd.startsWith(QLatin1String("EPRT"))) {
1141                 transferConnectionExtended = false;
1142                 pendingCommands.prepend(QLatin1String("PORT\r\n"));
1143             } else {
1144                 emit error(QFtp::UnknownError, replyText);
1145             }
1146             if (state != Waiting) {
1147                 state = Idle;
1148                 startNextCmd();
1149             }
1150             break;
1151     }
1152 #if defined(QFTPPI_DEBUG)
1153 //    qDebug("QFtpPI state: %d [processReply() end]", state);
1154 #endif
1155     return true;
1156 }
1157 
1158 /*
1159   \internal
1160 
1161   Starts next pending command. Returns \c false if there are no pending commands,
1162   otherwise it returns \c true.
1163 */
startNextCmd()1164 bool QFtpPI::startNextCmd()
1165 {
1166     if (waitForDtpToConnect)
1167         // don't process any new commands until we are connected
1168         return true;
1169 
1170 #if defined(QFTPPI_DEBUG)
1171     if (state != Idle)
1172         qDebug("QFtpPI startNextCmd: Internal error! QFtpPI called in non-Idle state %d", state);
1173 #endif
1174     if (pendingCommands.isEmpty()) {
1175         currentCmd.clear();
1176         emit finished(replyText);
1177         return false;
1178     }
1179     currentCmd = pendingCommands.constFirst();
1180 
1181     // PORT and PASV are edited in-place, depending on whether we
1182     // should try the extended transfer connection commands EPRT and
1183     // EPSV. The PORT command also triggers setting up a listener, and
1184     // the address/port arguments are edited in.
1185     QHostAddress address = commandSocket.localAddress();
1186     if (currentCmd.startsWith(QLatin1String("PORT"))) {
1187         if ((address.protocol() == QTcpSocket::IPv6Protocol) && transferConnectionExtended) {
1188             int port = dtp.setupListener(address);
1189             currentCmd = QLatin1String("EPRT |");
1190             currentCmd += (address.protocol() == QTcpSocket::IPv4Protocol) ? QLatin1Char('1') : QLatin1Char('2');
1191             currentCmd += QLatin1Char('|') + address.toString() + QLatin1Char('|') + QString::number(port);
1192             currentCmd += QLatin1Char('|');
1193         } else if (address.protocol() == QTcpSocket::IPv4Protocol) {
1194             int port = dtp.setupListener(address);
1195             QString portArg;
1196             quint32 ip = address.toIPv4Address();
1197             portArg += QString::number((ip & 0xff000000) >> 24);
1198             portArg += QLatin1Char(',') + QString::number((ip & 0xff0000) >> 16);
1199             portArg += QLatin1Char(',') + QString::number((ip & 0xff00) >> 8);
1200             portArg += QLatin1Char(',') + QString::number(ip & 0xff);
1201             portArg += QLatin1Char(',') + QString::number((port & 0xff00) >> 8);
1202             portArg += QLatin1Char(',') + QString::number(port & 0xff);
1203 
1204             currentCmd = QLatin1String("PORT ");
1205             currentCmd += portArg;
1206         } else {
1207             // No IPv6 connection can be set up with the PORT
1208             // command.
1209             return false;
1210         }
1211 
1212         currentCmd += QLatin1String("\r\n");
1213     } else if (currentCmd.startsWith(QLatin1String("PASV"))) {
1214         if ((address.protocol() == QTcpSocket::IPv6Protocol) && transferConnectionExtended)
1215             currentCmd = QLatin1String("EPSV\r\n");
1216     }
1217 
1218     pendingCommands.pop_front();
1219 #if defined(QFTPPI_DEBUG)
1220     qDebug("QFtpPI send: %s", currentCmd.leftRef(currentCmd.length() - 2).toLatin1().constData());
1221 #endif
1222     state = Waiting;
1223     commandSocket.write(currentCmd.toUtf8());
1224     return true;
1225 }
1226 
dtpConnectState(int s)1227 void QFtpPI::dtpConnectState(int s)
1228 {
1229     switch (s) {
1230         case QFtpDTP::CsClosed:
1231             if (waitForDtpToClose) {
1232                 // there is an unprocessed reply
1233                 if (processReply())
1234                     replyText = QLatin1String("");
1235                 else
1236                     return;
1237             }
1238             waitForDtpToClose = false;
1239             readyRead();
1240             return;
1241         case QFtpDTP::CsConnected:
1242             waitForDtpToConnect = false;
1243             startNextCmd();
1244             return;
1245         case QFtpDTP::CsHostNotFound:
1246         case QFtpDTP::CsConnectionRefused:
1247             emit error(QFtp::ConnectionRefused,
1248                         QFtp::tr("Data Connection refused"));
1249             startNextCmd();
1250             return;
1251         default:
1252             return;
1253     }
1254 }
1255 
1256 /**********************************************************************
1257  *
1258  * QFtpPrivate
1259  *
1260  *********************************************************************/
1261 
1262 QT_BEGIN_INCLUDE_NAMESPACE
1263 #include <private/qobject_p.h>
1264 QT_END_INCLUDE_NAMESPACE
1265 
1266 class QFtpPrivate : public QObjectPrivate
1267 {
1268     Q_DECLARE_PUBLIC(QFtp)
1269 public:
1270 
QFtpPrivate()1271     inline QFtpPrivate() : close_waitForStateChange(false), state(QFtp::Unconnected),
1272                            transferMode(QFtp::Passive), error(QFtp::NoError)
1273     { }
1274 
~QFtpPrivate()1275     ~QFtpPrivate() { while (!pending.isEmpty()) delete pending.takeFirst(); }
1276 
1277     // private slots
1278     void _q_startNextCommand();
1279     void _q_piFinished(const QString&);
1280     void _q_piError(int, const QString&);
1281     void _q_piConnectState(int);
1282     void _q_piFtpReply(int, const QString&);
1283 
1284     int addCommand(QFtpCommand *cmd);
1285 
1286     QFtpPI pi;
1287     QList<QFtpCommand *> pending;
1288     bool close_waitForStateChange;
1289     QFtp::State state;
1290     QFtp::TransferMode transferMode;
1291     QFtp::Error error;
1292     QString errorString;
1293 
1294     QString host;
1295     quint16 port;
1296     QString proxyHost;
1297     quint16 proxyPort;
1298 };
1299 
addCommand(QFtpCommand * cmd)1300 int QFtpPrivate::addCommand(QFtpCommand *cmd)
1301 {
1302     pending.append(cmd);
1303 
1304     if (pending.count() == 1) {
1305         // don't emit the commandStarted() signal before the ID is returned
1306         QTimer::singleShot(0, q_func(), SLOT(_q_startNextCommand()));
1307     }
1308     return cmd->id;
1309 }
1310 
1311 /**********************************************************************
1312  *
1313  * QFtp implementation
1314  *
1315  *********************************************************************/
1316 /*!
1317     \internal
1318     \class QFtp
1319     \brief The QFtp class provides an implementation of the client side of FTP protocol.
1320 
1321     \ingroup network
1322     \inmodule QtNetwork
1323 
1324 
1325     This class provides a direct interface to FTP that allows you to
1326     have more control over the requests. However, for new
1327     applications, it is recommended to use QNetworkAccessManager and
1328     QNetworkReply, as those classes possess a simpler, yet more
1329     powerful API.
1330 
1331     The class works asynchronously, so there are no blocking
1332     functions. If an operation cannot be executed immediately, the
1333     function will still return straight away and the operation will be
1334     scheduled for later execution. The results of scheduled operations
1335     are reported via signals. This approach depends on the event loop
1336     being in operation.
1337 
1338     The operations that can be scheduled (they are called "commands"
1339     in the rest of the documentation) are the following:
1340     connectToHost(), login(), close(), list(), cd(), get(), put(),
1341     remove(), mkdir(), rmdir(), rename() and rawCommand().
1342 
1343     All of these commands return a unique identifier that allows you
1344     to keep track of the command that is currently being executed.
1345     When the execution of a command starts, the commandStarted()
1346     signal with the command's identifier is emitted. When the command
1347     is finished, the commandFinished() signal is emitted with the
1348     command's identifier and a bool that indicates whether the command
1349     finished with an error.
1350 
1351     In some cases, you might want to execute a sequence of commands,
1352     e.g. if you want to connect and login to a FTP server. This is
1353     simply achieved:
1354 
1355     \snippet code/src_network_access_qftp.cpp 0
1356 
1357     In this case two FTP commands have been scheduled. When the last
1358     scheduled command has finished, a done() signal is emitted with
1359     a bool argument that tells you whether the sequence finished with
1360     an error.
1361 
1362     If an error occurs during the execution of one of the commands in
1363     a sequence of commands, all the pending commands (i.e. scheduled,
1364     but not yet executed commands) are cleared and no signals are
1365     emitted for them.
1366 
1367     Some commands, e.g. list(), emit additional signals to report
1368     their results.
1369 
1370     Example: If you want to download the INSTALL file from the Qt
1371     FTP server, you would write this:
1372 
1373     \snippet code/src_network_access_qftp.cpp 1
1374 
1375     For this example the following sequence of signals is emitted
1376     (with small variations, depending on network traffic, etc.):
1377 
1378     \snippet code/src_network_access_qftp.cpp 2
1379 
1380     The dataTransferProgress() signal in the above example is useful
1381     if you want to show a \l{QProgressBar}{progress bar} to
1382     inform the user about the progress of the download. The
1383     readyRead() signal tells you that there is data ready to be read.
1384     The amount of data can be queried then with the bytesAvailable()
1385     function and it can be read with the read() or readAll()
1386     function.
1387 
1388     If the login fails for the above example, the signals would look
1389     like this:
1390 
1391     \snippet code/src_network_access_qftp.cpp 3
1392 
1393     You can then get details about the error with the error() and
1394     errorString() functions.
1395 
1396     For file transfer, QFtp can use both active or passive mode, and
1397     it uses passive file transfer mode by default; see the
1398     documentation for setTransferMode() for more details about this.
1399 
1400     Call setProxy() to make QFtp connect via an FTP proxy server.
1401 
1402     The functions currentId() and currentCommand() provide more
1403     information about the currently executing command.
1404 
1405     The functions hasPendingCommands() and clearPendingCommands()
1406     allow you to query and clear the list of pending commands.
1407 
1408     If you are an experienced network programmer and want to have
1409     complete control you can use rawCommand() to execute arbitrary FTP
1410     commands.
1411 
1412     \warning The current version of QFtp doesn't fully support
1413     non-Unix FTP servers.
1414 
1415     \sa QNetworkAccessManager, QNetworkRequest, QNetworkReply,
1416         {FTP Example}
1417 */
1418 
1419 
1420 /*!
1421     \internal
1422     Constructs a QFtp object with the given \a parent.
1423 */
QFtp(QObject * parent)1424 QFtp::QFtp(QObject *parent)
1425     : QObject(*new QFtpPrivate, parent)
1426 {
1427     Q_D(QFtp);
1428     d->errorString = tr("Unknown error");
1429 
1430     connect(&d->pi, SIGNAL(connectState(int)),
1431             SLOT(_q_piConnectState(int)));
1432     connect(&d->pi, SIGNAL(finished(QString)),
1433             SLOT(_q_piFinished(QString)));
1434     connect(&d->pi, SIGNAL(error(int,QString)),
1435             SLOT(_q_piError(int,QString)));
1436     connect(&d->pi, SIGNAL(rawFtpReply(int,QString)),
1437             SLOT(_q_piFtpReply(int,QString)));
1438 
1439     connect(&d->pi.dtp, SIGNAL(readyRead()),
1440             SIGNAL(readyRead()));
1441     connect(&d->pi.dtp, SIGNAL(dataTransferProgress(qint64,qint64)),
1442             SIGNAL(dataTransferProgress(qint64,qint64)));
1443     connect(&d->pi.dtp, SIGNAL(listInfo(QUrlInfo)),
1444             SIGNAL(listInfo(QUrlInfo)));
1445 }
1446 
1447 /*!
1448     \internal
1449     \enum QFtp::State
1450 
1451     This enum defines the connection state:
1452 
1453     \value Unconnected There is no connection to the host.
1454     \value HostLookup A host name lookup is in progress.
1455     \value Connecting An attempt to connect to the host is in progress.
1456     \value Connected Connection to the host has been achieved.
1457     \value LoggedIn Connection and user login have been achieved.
1458     \value Closing The connection is closing down, but it is not yet
1459     closed. (The state will be \c Unconnected when the connection is
1460     closed.)
1461 
1462     \sa stateChanged(), state()
1463 */
1464 /*!
1465     \internal
1466     \enum QFtp::TransferMode
1467 
1468     FTP works with two socket connections; one for commands and
1469     another for transmitting data. While the command connection is
1470     always initiated by the client, the second connection can be
1471     initiated by either the client or the server.
1472 
1473     This enum defines whether the client (Passive mode) or the server
1474     (Active mode) should set up the data connection.
1475 
1476     \value Passive The client connects to the server to transmit its
1477     data.
1478 
1479     \value Active The server connects to the client to transmit its
1480     data.
1481 */
1482 /*!
1483     \internal
1484     \enum QFtp::TransferType
1485 
1486     This enum identifies the data transfer type used with get and
1487     put commands.
1488 
1489     \value Binary The data will be transferred in Binary mode.
1490 
1491     \value Ascii The data will be transferred in Ascii mode and new line
1492     characters will be converted to the local format.
1493 */
1494 /*!
1495     \internal
1496     \enum QFtp::Error
1497 
1498     This enum identifies the error that occurred.
1499 
1500     \value NoError No error occurred.
1501     \value HostNotFound The host name lookup failed.
1502     \value ConnectionRefused The server refused the connection.
1503     \value NotConnected Tried to send a command, but there is no connection to
1504     a server.
1505     \value UnknownError An error other than those specified above
1506     occurred.
1507 
1508     \sa error()
1509 */
1510 
1511 /*!
1512     \internal
1513     \enum QFtp::Command
1514 
1515     This enum is used as the return value for the currentCommand() function.
1516     This allows you to perform specific actions for particular
1517     commands, e.g. in a FTP client, you might want to clear the
1518     directory view when a list() command is started; in this case you
1519     can simply check in the slot connected to the start() signal if
1520     the currentCommand() is \c List.
1521 
1522     \value None No command is being executed.
1523     \value SetTransferMode set the \l{TransferMode}{transfer} mode.
1524     \value SetProxy switch proxying on or off.
1525     \value ConnectToHost connectToHost() is being executed.
1526     \value Login login() is being executed.
1527     \value Close close() is being executed.
1528     \value List list() is being executed.
1529     \value Cd cd() is being executed.
1530     \value Get get() is being executed.
1531     \value Put put() is being executed.
1532     \value Remove remove() is being executed.
1533     \value Mkdir mkdir() is being executed.
1534     \value Rmdir rmdir() is being executed.
1535     \value Rename rename() is being executed.
1536     \value RawCommand rawCommand() is being executed.
1537 
1538     \sa currentCommand()
1539 */
1540 
1541 /*!
1542     \internal
1543     \fn void QFtp::stateChanged(int state)
1544 
1545     This signal is emitted when the state of the connection changes.
1546     The argument \a state is the new state of the connection; it is
1547     one of the \l State values.
1548 
1549     It is usually emitted in response to a connectToHost() or close()
1550     command, but it can also be emitted "spontaneously", e.g. when the
1551     server closes the connection unexpectedly.
1552 
1553     \sa connectToHost(), close(), state(), State
1554 */
1555 
1556 /*!
1557     \internal
1558     \fn void QFtp::listInfo(const QUrlInfo &i);
1559 
1560     This signal is emitted for each directory entry the list() command
1561     finds. The details of the entry are stored in \a i.
1562 
1563     \sa list()
1564 */
1565 
1566 /*!
1567     \internal
1568     \fn void QFtp::commandStarted(int id)
1569 
1570     This signal is emitted when processing the command identified by
1571     \a id starts.
1572 
1573     \sa commandFinished(), done()
1574 */
1575 
1576 /*!
1577     \internal
1578     \fn void QFtp::commandFinished(int id, bool error)
1579 
1580     This signal is emitted when processing the command identified by
1581     \a id has finished. \a error is true if an error occurred during
1582     the processing; otherwise \a error is false.
1583 
1584     \sa commandStarted(), done(), error(), errorString()
1585 */
1586 
1587 /*!
1588     \internal
1589     \fn void QFtp::done(bool error)
1590 
1591     This signal is emitted when the last pending command has finished;
1592     (it is emitted after the last command's commandFinished() signal).
1593     \a error is true if an error occurred during the processing;
1594     otherwise \a error is false.
1595 
1596     \sa commandFinished(), error(), errorString()
1597 */
1598 
1599 /*!
1600     \internal
1601     \fn void QFtp::readyRead()
1602 
1603     This signal is emitted in response to a get() command when there
1604     is new data to read.
1605 
1606     If you specify a device as the second argument in the get()
1607     command, this signal is \e not emitted; instead the data is
1608     written directly to the device.
1609 
1610     You can read the data with the readAll() or read() functions.
1611 
1612     This signal is useful if you want to process the data in chunks as
1613     soon as it becomes available. If you are only interested in the
1614     complete data, just connect to the commandFinished() signal and
1615     read the data then instead.
1616 
1617     \sa get(), read(), readAll(), bytesAvailable()
1618 */
1619 
1620 /*!
1621     \internal
1622     \fn void QFtp::dataTransferProgress(qint64 done, qint64 total)
1623 
1624     This signal is emitted in response to a get() or put() request to
1625     indicate the current progress of the download or upload.
1626 
1627     \a done is the amount of data that has already been transferred
1628     and \a total is the total amount of data to be read or written. It
1629     is possible that the QFtp class is not able to determine the total
1630     amount of data that should be transferred, in which case \a total
1631     is 0. (If you connect this signal to a QProgressBar, the progress
1632     bar shows a busy indicator if the total is 0).
1633 
1634     \warning \a done and \a total are not necessarily the size in
1635     bytes, since for large files these values might need to be
1636     "scaled" to avoid overflow.
1637 
1638     \sa get(), put(), QProgressBar
1639 */
1640 
1641 /*!
1642     \internal
1643     \fn void QFtp::rawCommandReply(int replyCode, const QString &detail);
1644 
1645     This signal is emitted in response to the rawCommand() function.
1646     \a replyCode is the 3 digit reply code and \a detail is the text
1647     that follows the reply code.
1648 
1649     \sa rawCommand()
1650 */
1651 
1652 /*!
1653     \internal
1654     Connects to the FTP server \a host using port \a port.
1655 
1656     The stateChanged() signal is emitted when the state of the
1657     connecting process changes, e.g. to \c HostLookup, then \c
1658     Connecting, then \c Connected.
1659 
1660     The function does not block and returns immediately. The command
1661     is scheduled, and its execution is performed asynchronously. The
1662     function returns a unique identifier which is passed by
1663     commandStarted() and commandFinished().
1664 
1665     When the command is started the commandStarted() signal is
1666     emitted. When it is finished the commandFinished() signal is
1667     emitted.
1668 
1669     \sa stateChanged(), commandStarted(), commandFinished()
1670 */
connectToHost(const QString & host,quint16 port)1671 int QFtp::connectToHost(const QString &host, quint16 port)
1672 {
1673     QStringList cmds;
1674     cmds << host;
1675     cmds << QString::number((uint)port);
1676     int id = d_func()->addCommand(new QFtpCommand(ConnectToHost, cmds));
1677     d_func()->pi.transferConnectionExtended = true;
1678     return id;
1679 }
1680 
1681 /*!
1682     \internal
1683     Logs in to the FTP server with the username \a user and the
1684     password \a password.
1685 
1686     The stateChanged() signal is emitted when the state of the
1687     connecting process changes, e.g. to \c LoggedIn.
1688 
1689     The function does not block and returns immediately. The command
1690     is scheduled, and its execution is performed asynchronously. The
1691     function returns a unique identifier which is passed by
1692     commandStarted() and commandFinished().
1693 
1694     When the command is started the commandStarted() signal is
1695     emitted. When it is finished the commandFinished() signal is
1696     emitted.
1697 
1698     \sa commandStarted(), commandFinished()
1699 */
login(const QString & user,const QString & password)1700 int QFtp::login(const QString &user, const QString &password)
1701 {
1702     QStringList cmds;
1703 
1704     if (user.isNull() || user.compare(QLatin1String("anonymous"), Qt::CaseInsensitive) == 0) {
1705         cmds << (QLatin1String("USER ") + (user.isNull() ? QLatin1String("anonymous") : user) + QLatin1String("\r\n"));
1706         cmds << (QLatin1String("PASS ") + (password.isNull() ? QLatin1String("anonymous@") : password) + QLatin1String("\r\n"));
1707     } else {
1708         cmds << (QLatin1String("USER ") + user + QLatin1String("\r\n"));
1709         if (!password.isNull())
1710             cmds << (QLatin1String("PASS ") + password + QLatin1String("\r\n"));
1711     }
1712 
1713     return d_func()->addCommand(new QFtpCommand(Login, cmds));
1714 }
1715 
1716 /*!
1717     \internal
1718     Closes the connection to the FTP server.
1719 
1720     The stateChanged() signal is emitted when the state of the
1721     connecting process changes, e.g. to \c Closing, then \c
1722     Unconnected.
1723 
1724     The function does not block and returns immediately. The command
1725     is scheduled, and its execution is performed asynchronously. The
1726     function returns a unique identifier which is passed by
1727     commandStarted() and commandFinished().
1728 
1729     When the command is started the commandStarted() signal is
1730     emitted. When it is finished the commandFinished() signal is
1731     emitted.
1732 
1733     \sa stateChanged(), commandStarted(), commandFinished()
1734 */
close()1735 int QFtp::close()
1736 {
1737     return d_func()->addCommand(new QFtpCommand(Close, QStringList(QLatin1String("QUIT\r\n"))));
1738 }
1739 
1740 /*!
1741     \internal
1742     Sets the current FTP transfer mode to \a mode. The default is QFtp::Passive.
1743 
1744     \sa QFtp::TransferMode
1745 */
setTransferMode(TransferMode mode)1746 int QFtp::setTransferMode(TransferMode mode)
1747 {
1748     int id = d_func()->addCommand(new QFtpCommand(SetTransferMode, QStringList()));
1749     d_func()->pi.transferConnectionExtended = true;
1750     d_func()->transferMode = mode;
1751     return id;
1752 }
1753 
1754 /*!
1755     \internal
1756     Enables use of the FTP proxy on host \a host and port \a
1757     port. Calling this function with \a host empty disables proxying.
1758 
1759     QFtp does not support FTP-over-HTTP proxy servers. Use
1760     QNetworkAccessManager for this.
1761 */
setProxy(const QString & host,quint16 port)1762 int QFtp::setProxy(const QString &host, quint16 port)
1763 {
1764     QStringList args;
1765     args << host << QString::number(port);
1766     return d_func()->addCommand(new QFtpCommand(SetProxy, args));
1767 }
1768 
1769 /*!
1770     \internal
1771     Lists the contents of directory \a dir on the FTP server. If \a
1772     dir is empty, it lists the contents of the current directory.
1773 
1774     The listInfo() signal is emitted for each directory entry found.
1775 
1776     The function does not block and returns immediately. The command
1777     is scheduled, and its execution is performed asynchronously. The
1778     function returns a unique identifier which is passed by
1779     commandStarted() and commandFinished().
1780 
1781     When the command is started the commandStarted() signal is
1782     emitted. When it is finished the commandFinished() signal is
1783     emitted.
1784 
1785     \sa listInfo(), commandStarted(), commandFinished()
1786 */
list(const QString & dir)1787 int QFtp::list(const QString &dir)
1788 {
1789     QStringList cmds;
1790     cmds << QLatin1String("TYPE A\r\n");
1791     cmds << QLatin1String(d_func()->transferMode == Passive ? "PASV\r\n" : "PORT\r\n");
1792     if (dir.isEmpty())
1793         cmds << QLatin1String("LIST\r\n");
1794     else
1795         cmds << (QLatin1String("LIST ") + dir + QLatin1String("\r\n"));
1796     return d_func()->addCommand(new QFtpCommand(List, cmds));
1797 }
1798 
1799 /*!
1800     \internal
1801     Changes the working directory of the server to \a dir.
1802 
1803     The function does not block and returns immediately. The command
1804     is scheduled, and its execution is performed asynchronously. The
1805     function returns a unique identifier which is passed by
1806     commandStarted() and commandFinished().
1807 
1808     When the command is started the commandStarted() signal is
1809     emitted. When it is finished the commandFinished() signal is
1810     emitted.
1811 
1812     \sa commandStarted(), commandFinished()
1813 */
cd(const QString & dir)1814 int QFtp::cd(const QString &dir)
1815 {
1816     return d_func()->addCommand(new QFtpCommand(Cd, QStringList(QLatin1String("CWD ") + dir + QLatin1String("\r\n"))));
1817 }
1818 
1819 /*!
1820     \internal
1821     Downloads the file \a file from the server.
1822 
1823     If \a dev is \nullptr, then the readyRead() signal is emitted when there
1824     is data available to read. You can then read the data with the
1825     read() or readAll() functions.
1826 
1827     If \a dev is not \nullptr, the data is written directly to the device
1828     \a dev. Make sure that the \a dev pointer is valid for the duration
1829     of the operation (it is safe to delete it when the
1830     commandFinished() signal is emitted). In this case the readyRead()
1831     signal is \e not emitted and you cannot read data with the
1832     read() or readAll() functions.
1833 
1834     If you don't read the data immediately it becomes available, i.e.
1835     when the readyRead() signal is emitted, it is still available
1836     until the next command is started.
1837 
1838     For example, if you want to present the data to the user as soon
1839     as there is something available, connect to the readyRead() signal
1840     and read the data immediately. On the other hand, if you only want
1841     to work with the complete data, you can connect to the
1842     commandFinished() signal and read the data when the get() command
1843     is finished.
1844 
1845     The data is transferred as Binary or Ascii depending on the value
1846     of \a type.
1847 
1848     The function does not block and returns immediately. The command
1849     is scheduled, and its execution is performed asynchronously. The
1850     function returns a unique identifier which is passed by
1851     commandStarted() and commandFinished().
1852 
1853     When the command is started the commandStarted() signal is
1854     emitted. When it is finished the commandFinished() signal is
1855     emitted.
1856 
1857     \sa readyRead(), dataTransferProgress(), commandStarted(),
1858     commandFinished()
1859 */
get(const QString & file,QIODevice * dev,TransferType type)1860 int QFtp::get(const QString &file, QIODevice *dev, TransferType type)
1861 {
1862     QStringList cmds;
1863     if (type == Binary)
1864         cmds << QLatin1String("TYPE I\r\n");
1865     else
1866         cmds << QLatin1String("TYPE A\r\n");
1867     cmds << QLatin1String("SIZE ") + file + QLatin1String("\r\n");
1868     cmds << QLatin1String(d_func()->transferMode == Passive ? "PASV\r\n" : "PORT\r\n");
1869     cmds << QLatin1String("RETR ") + file + QLatin1String("\r\n");
1870     return d_func()->addCommand(new QFtpCommand(Get, cmds, dev));
1871 }
1872 
1873 /*!
1874     \internal
1875     \overload
1876 
1877     Writes a copy of the given \a data to the file called \a file on
1878     the server. The progress of the upload is reported by the
1879     dataTransferProgress() signal.
1880 
1881     The data is transferred as Binary or Ascii depending on the value
1882     of \a type.
1883 
1884     The function does not block and returns immediately. The command
1885     is scheduled, and its execution is performed asynchronously. The
1886     function returns a unique identifier which is passed by
1887     commandStarted() and commandFinished().
1888 
1889     When the command is started the commandStarted() signal is
1890     emitted. When it is finished the commandFinished() signal is
1891     emitted.
1892 
1893     Since this function takes a copy of the \a data, you can discard
1894     your own copy when this function returns.
1895 
1896     \sa dataTransferProgress(), commandStarted(), commandFinished()
1897 */
put(const QByteArray & data,const QString & file,TransferType type)1898 int QFtp::put(const QByteArray &data, const QString &file, TransferType type)
1899 {
1900     QStringList cmds;
1901     if (type == Binary)
1902         cmds << QLatin1String("TYPE I\r\n");
1903     else
1904         cmds << QLatin1String("TYPE A\r\n");
1905     cmds << QLatin1String(d_func()->transferMode == Passive ? "PASV\r\n" : "PORT\r\n");
1906     cmds << QLatin1String("ALLO ") + QString::number(data.size()) + QLatin1String("\r\n");
1907     cmds << QLatin1String("STOR ") + file + QLatin1String("\r\n");
1908     return d_func()->addCommand(new QFtpCommand(Put, cmds, data));
1909 }
1910 
1911 /*!
1912     \internal
1913     Reads the data from the IO device \a dev, and writes it to the
1914     file called \a file on the server. The data is read in chunks from
1915     the IO device, so this overload allows you to transmit large
1916     amounts of data without the need to read all the data into memory
1917     at once.
1918 
1919     The data is transferred as Binary or Ascii depending on the value
1920     of \a type.
1921 
1922     Make sure that the \a dev pointer is valid for the duration of the
1923     operation (it is safe to delete it when the commandFinished() is
1924     emitted).
1925 */
put(QIODevice * dev,const QString & file,TransferType type)1926 int QFtp::put(QIODevice *dev, const QString &file, TransferType type)
1927 {
1928     QStringList cmds;
1929     if (type == Binary)
1930         cmds << QLatin1String("TYPE I\r\n");
1931     else
1932         cmds << QLatin1String("TYPE A\r\n");
1933     cmds << QLatin1String(d_func()->transferMode == Passive ? "PASV\r\n" : "PORT\r\n");
1934     if (!dev->isSequential())
1935         cmds << QLatin1String("ALLO ") + QString::number(dev->size()) + QLatin1String("\r\n");
1936     cmds << QLatin1String("STOR ") + file + QLatin1String("\r\n");
1937     return d_func()->addCommand(new QFtpCommand(Put, cmds, dev));
1938 }
1939 
1940 /*!
1941     \internal
1942     Deletes the file called \a file from the server.
1943 
1944     The function does not block and returns immediately. The command
1945     is scheduled, and its execution is performed asynchronously. The
1946     function returns a unique identifier which is passed by
1947     commandStarted() and commandFinished().
1948 
1949     When the command is started the commandStarted() signal is
1950     emitted. When it is finished the commandFinished() signal is
1951     emitted.
1952 
1953     \sa commandStarted(), commandFinished()
1954 */
remove(const QString & file)1955 int QFtp::remove(const QString &file)
1956 {
1957     return d_func()->addCommand(new QFtpCommand(Remove, QStringList(QLatin1String("DELE ") + file + QLatin1String("\r\n"))));
1958 }
1959 
1960 /*!
1961     \internal
1962     Creates a directory called \a dir on the server.
1963 
1964     The function does not block and returns immediately. The command
1965     is scheduled, and its execution is performed asynchronously. The
1966     function returns a unique identifier which is passed by
1967     commandStarted() and commandFinished().
1968 
1969     When the command is started the commandStarted() signal is
1970     emitted. When it is finished the commandFinished() signal is
1971     emitted.
1972 
1973     \sa commandStarted(), commandFinished()
1974 */
mkdir(const QString & dir)1975 int QFtp::mkdir(const QString &dir)
1976 {
1977     return d_func()->addCommand(new QFtpCommand(Mkdir, QStringList(QLatin1String("MKD ") + dir + QLatin1String("\r\n"))));
1978 }
1979 
1980 /*!
1981     \internal
1982     Removes the directory called \a dir from the server.
1983 
1984     The function does not block and returns immediately. The command
1985     is scheduled, and its execution is performed asynchronously. The
1986     function returns a unique identifier which is passed by
1987     commandStarted() and commandFinished().
1988 
1989     When the command is started the commandStarted() signal is
1990     emitted. When it is finished the commandFinished() signal is
1991     emitted.
1992 
1993     \sa commandStarted(), commandFinished()
1994 */
rmdir(const QString & dir)1995 int QFtp::rmdir(const QString &dir)
1996 {
1997     return d_func()->addCommand(new QFtpCommand(Rmdir, QStringList(QLatin1String("RMD ") + dir + QLatin1String("\r\n"))));
1998 }
1999 
2000 /*!
2001     \internal
2002     Renames the file called \a oldname to \a newname on the server.
2003 
2004     The function does not block and returns immediately. The command
2005     is scheduled, and its execution is performed asynchronously. The
2006     function returns a unique identifier which is passed by
2007     commandStarted() and commandFinished().
2008 
2009     When the command is started the commandStarted() signal is
2010     emitted. When it is finished the commandFinished() signal is
2011     emitted.
2012 
2013     \sa commandStarted(), commandFinished()
2014 */
rename(const QString & oldname,const QString & newname)2015 int QFtp::rename(const QString &oldname, const QString &newname)
2016 {
2017     QStringList cmds;
2018     cmds << QLatin1String("RNFR ") + oldname + QLatin1String("\r\n");
2019     cmds << QLatin1String("RNTO ") + newname + QLatin1String("\r\n");
2020     return d_func()->addCommand(new QFtpCommand(Rename, cmds));
2021 }
2022 
2023 /*!
2024     \internal
2025     Sends the raw FTP command \a command to the FTP server. This is
2026     useful for low-level FTP access. If the operation you wish to
2027     perform has an equivalent QFtp function, we recommend using the
2028     function instead of raw FTP commands since the functions are
2029     easier and safer.
2030 
2031     The function does not block and returns immediately. The command
2032     is scheduled, and its execution is performed asynchronously. The
2033     function returns a unique identifier which is passed by
2034     commandStarted() and commandFinished().
2035 
2036     When the command is started the commandStarted() signal is
2037     emitted. When it is finished the commandFinished() signal is
2038     emitted.
2039 
2040     \sa rawCommandReply(), commandStarted(), commandFinished()
2041 */
rawCommand(const QString & command)2042 int QFtp::rawCommand(const QString &command)
2043 {
2044     const QString cmd = QStringRef(&command).trimmed() + QLatin1String("\r\n");
2045     return d_func()->addCommand(new QFtpCommand(RawCommand, QStringList(cmd)));
2046 }
2047 
2048 /*!
2049     \internal
2050     Returns the number of bytes that can be read from the data socket
2051     at the moment.
2052 
2053     \sa get(), readyRead(), read(), readAll()
2054 */
bytesAvailable() const2055 qint64 QFtp::bytesAvailable() const
2056 {
2057     return d_func()->pi.dtp.bytesAvailable();
2058 }
2059 
2060 /*!
2061     \internal
2062     Reads \a maxlen bytes from the data socket into \a data and
2063     returns the number of bytes read. Returns -1 if an error occurred.
2064 
2065     \sa get(), readyRead(), bytesAvailable(), readAll()
2066 */
read(char * data,qint64 maxlen)2067 qint64 QFtp::read(char *data, qint64 maxlen)
2068 {
2069     return d_func()->pi.dtp.read(data, maxlen);
2070 }
2071 
2072 /*!
2073     \internal
2074     Reads all the bytes available from the data socket and returns
2075     them.
2076 
2077     \sa get(), readyRead(), bytesAvailable(), read()
2078 */
readAll()2079 QByteArray QFtp::readAll()
2080 {
2081     return d_func()->pi.dtp.readAll();
2082 }
2083 
2084 /*!
2085     \internal
2086     Aborts the current command and deletes all scheduled commands.
2087 
2088     If there is an unfinished command (i.e. a command for which the
2089     commandStarted() signal has been emitted, but for which the
2090     commandFinished() signal has not been emitted), this function
2091     sends an \c ABORT command to the server. When the server replies
2092     that the command is aborted, the commandFinished() signal with the
2093     \c error argument set to \c true is emitted for the command. Due
2094     to timing issues, it is possible that the command had already
2095     finished before the abort request reached the server, in which
2096     case, the commandFinished() signal is emitted with the \c error
2097     argument set to \c false.
2098 
2099     For all other commands that are affected by the abort(), no
2100     signals are emitted.
2101 
2102     If you don't start further FTP commands directly after the
2103     abort(), there won't be any scheduled commands and the done()
2104     signal is emitted.
2105 
2106     \warning Some FTP servers, for example the BSD FTP daemon (version
2107     0.3), wrongly return a positive reply even when an abort has
2108     occurred. For these servers the commandFinished() signal has its
2109     error flag set to \c false, even though the command did not
2110     complete successfully.
2111 
2112     \sa clearPendingCommands()
2113 */
abort()2114 void QFtp::abort()
2115 {
2116     if (d_func()->pending.isEmpty())
2117         return;
2118 
2119     clearPendingCommands();
2120     d_func()->pi.abort();
2121 }
2122 
2123 /*!
2124     \internal
2125     Clears the last error.
2126 
2127     \sa currentCommand()
2128 */
clearError()2129 void QFtp::clearError()
2130 {
2131     d_func()->error = NoError;
2132 }
2133 
2134 /*!
2135     \internal
2136     Returns the identifier of the FTP command that is being executed
2137     or 0 if there is no command being executed.
2138 
2139     \sa currentCommand()
2140 */
currentId() const2141 int QFtp::currentId() const
2142 {
2143     if (d_func()->pending.isEmpty())
2144         return 0;
2145     return d_func()->pending.first()->id;
2146 }
2147 
2148 /*!
2149     \internal
2150     Returns the command type of the FTP command being executed or \c
2151     None if there is no command being executed.
2152 
2153     \sa currentId()
2154 */
currentCommand() const2155 QFtp::Command QFtp::currentCommand() const
2156 {
2157     if (d_func()->pending.isEmpty())
2158         return None;
2159     return d_func()->pending.first()->command;
2160 }
2161 
2162 /*!
2163     \internal
2164     Returns the QIODevice pointer that is used by the FTP command to read data
2165     from or store data to. If there is no current FTP command being executed or
2166     if the command does not use an IO device, this function returns \nullptr.
2167 
2168     This function can be used to delete the QIODevice in the slot connected to
2169     the commandFinished() signal.
2170 
2171     \sa get(), put()
2172 */
currentDevice() const2173 QIODevice* QFtp::currentDevice() const
2174 {
2175     if (d_func()->pending.isEmpty())
2176         return nullptr;
2177     QFtpCommand *c = d_func()->pending.first();
2178     if (c->is_ba)
2179         return nullptr;
2180     return c->data.dev;
2181 }
2182 
2183 /*!
2184     \internal
2185     Returns \c true if there are any commands scheduled that have not yet
2186     been executed; otherwise returns \c false.
2187 
2188     The command that is being executed is \e not considered as a
2189     scheduled command.
2190 
2191     \sa clearPendingCommands(), currentId(), currentCommand()
2192 */
hasPendingCommands() const2193 bool QFtp::hasPendingCommands() const
2194 {
2195     return d_func()->pending.count() > 1;
2196 }
2197 
2198 /*!
2199     \internal
2200     Deletes all pending commands from the list of scheduled commands.
2201     This does not affect the command that is being executed. If you
2202     want to stop this as well, use abort().
2203 
2204     \sa hasPendingCommands(), abort()
2205 */
clearPendingCommands()2206 void QFtp::clearPendingCommands()
2207 {
2208     // delete all entires except the first one
2209     while (d_func()->pending.count() > 1)
2210         delete d_func()->pending.takeLast();
2211 }
2212 
2213 /*!
2214     \internal
2215     Returns the current state of the object. When the state changes,
2216     the stateChanged() signal is emitted.
2217 
2218     \sa State, stateChanged()
2219 */
state() const2220 QFtp::State QFtp::state() const
2221 {
2222     return d_func()->state;
2223 }
2224 
2225 /*!
2226     \internal
2227     Returns the last error that occurred. This is useful to find out
2228     what went wrong when receiving a commandFinished() or a done()
2229     signal with the \c error argument set to \c true.
2230 
2231     If you start a new command, the error status is reset to \c NoError.
2232 */
error() const2233 QFtp::Error QFtp::error() const
2234 {
2235     return d_func()->error;
2236 }
2237 
2238 /*!
2239     \internal
2240     Returns a human-readable description of the last error that
2241     occurred. This is useful for presenting a error message to the
2242     user when receiving a commandFinished() or a done() signal with
2243     the \c error argument set to \c true.
2244 
2245     The error string is often (but not always) the reply from the
2246     server, so it is not always possible to translate the string. If
2247     the message comes from Qt, the string has already passed through
2248     tr().
2249 */
errorString() const2250 QString QFtp::errorString() const
2251 {
2252     return d_func()->errorString;
2253 }
2254 
2255 /*! \internal
2256 */
_q_startNextCommand()2257 void QFtpPrivate::_q_startNextCommand()
2258 {
2259     Q_Q(QFtp);
2260     if (pending.isEmpty())
2261         return;
2262     QFtpCommand *c = pending.constFirst();
2263 
2264     error = QFtp::NoError;
2265     errorString = QT_TRANSLATE_NOOP(QFtp, QLatin1String("Unknown error"));
2266 
2267     if (q->bytesAvailable())
2268         q->readAll(); // clear the data
2269     emit q->commandStarted(c->id);
2270 
2271     // Proxy support, replace the Login argument in place, then fall
2272     // through.
2273     if (c->command == QFtp::Login && !proxyHost.isEmpty()) {
2274         QString loginString;
2275         loginString += QStringRef(&c->rawCmds.constFirst()).trimmed() + QLatin1Char('@') + host;
2276         if (port && port != 21)
2277             loginString += QLatin1Char(':') + QString::number(port);
2278         loginString += QLatin1String("\r\n");
2279         c->rawCmds[0] = loginString;
2280     }
2281 
2282     if (c->command == QFtp::SetTransferMode) {
2283         _q_piFinished(QLatin1String("Transfer mode set"));
2284     } else if (c->command == QFtp::SetProxy) {
2285         proxyHost = c->rawCmds.at(0);
2286         proxyPort = c->rawCmds.at(1).toUInt();
2287         c->rawCmds.clear();
2288         _q_piFinished(QLatin1String("Proxy set to ") + proxyHost + QLatin1Char(':') + QString::number(proxyPort));
2289     } else if (c->command == QFtp::ConnectToHost) {
2290 #ifndef QT_NO_BEARERMANAGEMENT // ### Qt6: Remove section
2291         //copy network session down to the PI
2292         pi.setProperty("_q_networksession", q->property("_q_networksession"));
2293 #endif
2294         if (!proxyHost.isEmpty()) {
2295             host = c->rawCmds.at(0);
2296             port = c->rawCmds.at(1).toUInt();
2297             pi.connectToHost(proxyHost, proxyPort);
2298         } else {
2299             pi.connectToHost(c->rawCmds.at(0), c->rawCmds.at(1).toUInt());
2300         }
2301     } else {
2302         if (c->command == QFtp::Put) {
2303             if (c->is_ba) {
2304                 pi.dtp.setData(c->data.ba);
2305                 pi.dtp.setBytesTotal(c->data.ba->size());
2306             } else if (c->data.dev && (c->data.dev->isOpen() || c->data.dev->open(QIODevice::ReadOnly))) {
2307                 pi.dtp.setDevice(c->data.dev);
2308                 if (c->data.dev->isSequential()) {
2309                     pi.dtp.setBytesTotal(0);
2310                     pi.dtp.connect(c->data.dev, SIGNAL(readyRead()), SLOT(dataReadyRead()));
2311                     pi.dtp.connect(c->data.dev, SIGNAL(readChannelFinished()), SLOT(dataReadyRead()));
2312                 } else {
2313                     pi.dtp.setBytesTotal(c->data.dev->size());
2314                 }
2315             }
2316         } else if (c->command == QFtp::Get) {
2317             if (!c->is_ba && c->data.dev) {
2318                 pi.dtp.setDevice(c->data.dev);
2319             }
2320         } else if (c->command == QFtp::Close) {
2321             state = QFtp::Closing;
2322             emit q->stateChanged(state);
2323         }
2324         pi.sendCommands(c->rawCmds);
2325     }
2326 }
2327 
2328 /*! \internal
2329 */
_q_piFinished(const QString &)2330 void QFtpPrivate::_q_piFinished(const QString&)
2331 {
2332     if (pending.isEmpty())
2333         return;
2334     QFtpCommand *c = pending.constFirst();
2335 
2336     if (c->command == QFtp::Close) {
2337         // The order of in which the slots are called is arbitrary, so
2338         // disconnect the SIGNAL-SIGNAL temporary to make sure that we
2339         // don't get the commandFinished() signal before the stateChanged()
2340         // signal.
2341         if (state != QFtp::Unconnected) {
2342             close_waitForStateChange = true;
2343             return;
2344         }
2345     }
2346     emit q_func()->commandFinished(c->id, false);
2347     pending.removeFirst();
2348 
2349     delete c;
2350 
2351     if (pending.isEmpty()) {
2352         emit q_func()->done(false);
2353     } else {
2354         _q_startNextCommand();
2355     }
2356 }
2357 
2358 /*! \internal
2359 */
_q_piError(int errorCode,const QString & text)2360 void QFtpPrivate::_q_piError(int errorCode, const QString &text)
2361 {
2362     Q_Q(QFtp);
2363 
2364     if (pending.isEmpty()) {
2365         qWarning("QFtpPrivate::_q_piError was called without pending command!");
2366         return;
2367     }
2368 
2369     QFtpCommand *c = pending.constFirst();
2370 
2371     // non-fatal errors
2372     if (c->command == QFtp::Get && pi.currentCommand().startsWith(QLatin1String("SIZE "))) {
2373         pi.dtp.setBytesTotal(0);
2374         return;
2375     } else if (c->command==QFtp::Put && pi.currentCommand().startsWith(QLatin1String("ALLO "))) {
2376         return;
2377     }
2378 
2379     error = QFtp::Error(errorCode);
2380     switch (q->currentCommand()) {
2381         case QFtp::ConnectToHost:
2382             errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Connecting to host failed:\n%1"))
2383                           .arg(text);
2384             break;
2385         case QFtp::Login:
2386             errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Login failed:\n%1"))
2387                           .arg(text);
2388             break;
2389         case QFtp::List:
2390             errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Listing directory failed:\n%1"))
2391                           .arg(text);
2392             break;
2393         case QFtp::Cd:
2394             errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Changing directory failed:\n%1"))
2395                           .arg(text);
2396             break;
2397         case QFtp::Get:
2398             errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Downloading file failed:\n%1"))
2399                           .arg(text);
2400             break;
2401         case QFtp::Put:
2402             errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Uploading file failed:\n%1"))
2403                           .arg(text);
2404             break;
2405         case QFtp::Remove:
2406             errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Removing file failed:\n%1"))
2407                           .arg(text);
2408             break;
2409         case QFtp::Mkdir:
2410             errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Creating directory failed:\n%1"))
2411                           .arg(text);
2412             break;
2413         case QFtp::Rmdir:
2414             errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Removing directory failed:\n%1"))
2415                           .arg(text);
2416             break;
2417         default:
2418             errorString = text;
2419             break;
2420     }
2421 
2422     pi.clearPendingCommands();
2423     q->clearPendingCommands();
2424     emit q->commandFinished(c->id, true);
2425 
2426     pending.removeFirst();
2427     delete c;
2428     if (pending.isEmpty())
2429         emit q->done(true);
2430     else
2431         _q_startNextCommand();
2432 }
2433 
2434 /*! \internal
2435 */
_q_piConnectState(int connectState)2436 void QFtpPrivate::_q_piConnectState(int connectState)
2437 {
2438     state = QFtp::State(connectState);
2439     emit q_func()->stateChanged(state);
2440     if (close_waitForStateChange) {
2441         close_waitForStateChange = false;
2442         _q_piFinished(QLatin1String(QT_TRANSLATE_NOOP("QFtp", "Connection closed")));
2443     }
2444 }
2445 
2446 /*! \internal
2447 */
_q_piFtpReply(int code,const QString & text)2448 void QFtpPrivate::_q_piFtpReply(int code, const QString &text)
2449 {
2450     if (q_func()->currentCommand() == QFtp::RawCommand) {
2451         pi.rawCommand = true;
2452         emit q_func()->rawCommandReply(code, text);
2453     }
2454 }
2455 
2456 /*!
2457     \internal
2458     Destructor.
2459 */
~QFtp()2460 QFtp::~QFtp()
2461 {
2462     abort();
2463     close();
2464 }
2465 
2466 QT_END_NAMESPACE
2467 
2468 #include "qftp.moc"
2469 
2470 #include "moc_qftp_p.cpp"
2471