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