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