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