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

source code of qtbase/src/network/access/qftp.cpp