1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtNetwork module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | //#define QFTPPI_DEBUG |
41 | //#define QFTPDTP_DEBUG |
42 | |
43 | #include "private/qftp_p.h" |
44 | #include "qabstractsocket.h" |
45 | |
46 | #include "qcoreapplication.h" |
47 | #include "qtcpsocket.h" |
48 | #include "qurlinfo_p.h" |
49 | #include "qstringlist.h" |
50 | #include "qregexp.h" |
51 | #include "qtimer.h" |
52 | #include "qfileinfo.h" |
53 | #include "qtcpserver.h" |
54 | #include "qlocale.h" |
55 | |
56 | QT_BEGIN_NAMESPACE |
57 | |
58 | class QFtpPI; |
59 | |
60 | /* |
61 | The QFtpDTP (DTP = Data Transfer Process) controls all client side |
62 | data transfer between the client and server. |
63 | */ |
64 | class QFtpDTP : public QObject |
65 | { |
66 | Q_OBJECT |
67 | |
68 | public: |
69 | enum ConnectState { |
70 | CsHostFound, |
71 | CsConnected, |
72 | CsClosed, |
73 | CsHostNotFound, |
74 | CsConnectionRefused |
75 | }; |
76 | |
77 | QFtpDTP(QFtpPI *p, QObject *parent = nullptr); |
78 | |
79 | void setData(QByteArray *); |
80 | void setDevice(QIODevice *); |
81 | void writeData(); |
82 | void setBytesTotal(qint64 bytes); |
83 | |
84 | bool hasError() const; |
85 | QString errorMessage() const; |
86 | void clearError(); |
87 | |
88 | void connectToHost(const QString & host, quint16 port); |
89 | int setupListener(const QHostAddress &address); |
90 | void waitForConnection(); |
91 | |
92 | QTcpSocket::SocketState state() const; |
93 | qint64 bytesAvailable() const; |
94 | qint64 read(char *data, qint64 maxlen); |
95 | QByteArray readAll(); |
96 | |
97 | void abortConnection(); |
98 | |
99 | static bool parseDir(const QByteArray &buffer, const QString &userName, QUrlInfo *info); |
100 | |
101 | signals: |
102 | void listInfo(const QUrlInfo&); |
103 | void readyRead(); |
104 | void dataTransferProgress(qint64, qint64); |
105 | |
106 | void connectState(int); |
107 | |
108 | private slots: |
109 | void socketConnected(); |
110 | void socketReadyRead(); |
111 | void socketError(QAbstractSocket::SocketError); |
112 | void socketConnectionClosed(); |
113 | void socketBytesWritten(qint64); |
114 | void setupSocket(); |
115 | |
116 | void dataReadyRead(); |
117 | |
118 | private: |
119 | void clearData(); |
120 | |
121 | QTcpSocket *socket; |
122 | QTcpServer listener; |
123 | |
124 | QFtpPI *pi; |
125 | QString err; |
126 | qint64 bytesDone; |
127 | qint64 bytesTotal; |
128 | bool callWriteData; |
129 | |
130 | // If is_ba is true, ba is used; ba is never 0. |
131 | // Otherwise dev is used; dev can be 0 or not. |
132 | union { |
133 | QByteArray *ba; |
134 | QIODevice *dev; |
135 | } data; |
136 | bool is_ba; |
137 | |
138 | QByteArray bytesFromSocket; |
139 | }; |
140 | |
141 | /********************************************************************** |
142 | * |
143 | * QFtpPI - Protocol Interpreter |
144 | * |
145 | *********************************************************************/ |
146 | |
147 | class QFtpPI : public QObject |
148 | { |
149 | Q_OBJECT |
150 | |
151 | public: |
152 | QFtpPI(QObject *parent = nullptr); |
153 | |
154 | void connectToHost(const QString &host, quint16 port); |
155 | |
156 | bool sendCommands(const QStringList &cmds); |
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 |
171 | signals: |
172 | void connectState(int); |
173 | void finished(const QString&); |
174 | void error(int, const QString&); |
175 | void rawFtpReply(int, const QString&); |
176 | |
177 | private slots: |
178 | void hostFound(); |
179 | void connected(); |
180 | void connectionClosed(); |
181 | void delayedCloseFinished(); |
182 | void readyRead(); |
183 | void error(QAbstractSocket::SocketError); |
184 | |
185 | void dtpConnectState(int); |
186 | |
187 | private: |
188 | // the states are modelled after the generalized state diagram of RFC 959, |
189 | // page 58 |
190 | enum State { |
191 | Begin, |
192 | Idle, |
193 | Waiting, |
194 | Success, |
195 | Failure |
196 | }; |
197 | |
198 | enum AbortState { |
199 | None, |
200 | AbortStarted, |
201 | WaitForAbortToFinish |
202 | }; |
203 | |
204 | bool processReply(); |
205 | bool startNextCmd(); |
206 | |
207 | QTcpSocket commandSocket; |
208 | QString replyText; |
209 | char replyCode[3]; |
210 | State state; |
211 | AbortState abortState; |
212 | QStringList pendingCommands; |
213 | QString currentCmd; |
214 | |
215 | bool waitForDtpToConnect; |
216 | bool waitForDtpToClose; |
217 | |
218 | QByteArray bytesFromSocket; |
219 | |
220 | friend class QFtpDTP; |
221 | }; |
222 | |
223 | /********************************************************************** |
224 | * |
225 | * QFtpCommand implemenatation |
226 | * |
227 | *********************************************************************/ |
228 | class QFtpCommand |
229 | { |
230 | public: |
231 | QFtpCommand(QFtp::Command cmd, const QStringList &raw, const QByteArray &ba); |
232 | QFtpCommand(QFtp::Command cmd, const QStringList &raw, QIODevice *dev = nullptr); |
233 | ~QFtpCommand(); |
234 | |
235 | int id; |
236 | QFtp::Command command; |
237 | QStringList rawCmds; |
238 | |
239 | // If is_ba is true, ba is used; ba is never 0. |
240 | // Otherwise dev is used; dev can be 0 or not. |
241 | union { |
242 | QByteArray *ba; |
243 | QIODevice *dev; |
244 | } data; |
245 | bool is_ba; |
246 | |
247 | }; |
248 | |
249 | static int nextId() |
250 | { |
251 | static QBasicAtomicInt counter = Q_BASIC_ATOMIC_INITIALIZER(0); |
252 | return 1 + counter.fetchAndAddRelaxed(valueToAdd: 1); |
253 | } |
254 | |
255 | QFtpCommand::QFtpCommand(QFtp::Command cmd, const QStringList &raw, const QByteArray &ba) |
256 | : command(cmd), rawCmds(raw), is_ba(true) |
257 | { |
258 | id = nextId(); |
259 | data.ba = new QByteArray(ba); |
260 | } |
261 | |
262 | QFtpCommand::QFtpCommand(QFtp::Command cmd, const QStringList &raw, QIODevice *dev) |
263 | : command(cmd), rawCmds(raw), is_ba(false) |
264 | { |
265 | id = nextId(); |
266 | data.dev = dev; |
267 | } |
268 | |
269 | QFtpCommand::~QFtpCommand() |
270 | { |
271 | if (is_ba) |
272 | delete data.ba; |
273 | } |
274 | |
275 | /********************************************************************** |
276 | * |
277 | * QFtpDTP implemenatation |
278 | * |
279 | *********************************************************************/ |
280 | QFtpDTP::QFtpDTP(QFtpPI *p, QObject *parent) : |
281 | QObject(parent), |
282 | socket(nullptr), |
283 | listener(this), |
284 | pi(p), |
285 | callWriteData(false) |
286 | { |
287 | clearData(); |
288 | listener.setObjectName(QLatin1String("QFtpDTP active state server" )); |
289 | connect(asender: &listener, SIGNAL(newConnection()), SLOT(setupSocket())); |
290 | } |
291 | |
292 | void QFtpDTP::setData(QByteArray *ba) |
293 | { |
294 | is_ba = true; |
295 | data.ba = ba; |
296 | } |
297 | |
298 | void QFtpDTP::setDevice(QIODevice *dev) |
299 | { |
300 | is_ba = false; |
301 | data.dev = dev; |
302 | } |
303 | |
304 | void QFtpDTP::setBytesTotal(qint64 bytes) |
305 | { |
306 | bytesTotal = bytes; |
307 | bytesDone = 0; |
308 | emit dataTransferProgress(bytesDone, bytesTotal); |
309 | } |
310 | |
311 | void QFtpDTP::connectToHost(const QString & host, quint16 port) |
312 | { |
313 | bytesFromSocket.clear(); |
314 | |
315 | if (socket) { |
316 | delete socket; |
317 | socket = nullptr; |
318 | } |
319 | socket = new QTcpSocket(this); |
320 | #ifndef QT_NO_BEARERMANAGEMENT // ### Qt6: Remove section |
321 | //copy network session down to the socket |
322 | socket->setProperty(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 | |
334 | int QFtpDTP::setupListener(const QHostAddress &address) |
335 | { |
336 | #ifndef QT_NO_BEARERMANAGEMENT // ### Qt6: Remove section |
337 | //copy network session down to the socket |
338 | listener.setProperty(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 | |
345 | void QFtpDTP::waitForConnection() |
346 | { |
347 | // This function is only interesting in Active transfer mode; it works |
348 | // around a limitation in QFtp's design by blocking, waiting for an |
349 | // incoming connection. For the default Passive mode, it does nothing. |
350 | if (listener.isListening()) |
351 | listener.waitForNewConnection(); |
352 | } |
353 | |
354 | QTcpSocket::SocketState QFtpDTP::state() const |
355 | { |
356 | return socket ? socket->state() : QTcpSocket::UnconnectedState; |
357 | } |
358 | |
359 | qint64 QFtpDTP::bytesAvailable() const |
360 | { |
361 | if (!socket || socket->state() != QTcpSocket::ConnectedState) |
362 | return (qint64) bytesFromSocket.size(); |
363 | return socket->bytesAvailable(); |
364 | } |
365 | |
366 | qint64 QFtpDTP::read(char *data, qint64 maxlen) |
367 | { |
368 | qint64 read; |
369 | if (socket && socket->state() == QTcpSocket::ConnectedState) { |
370 | read = socket->read(data, maxlen); |
371 | } else { |
372 | read = qMin(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 | |
381 | QByteArray QFtpDTP::readAll() |
382 | { |
383 | QByteArray tmp; |
384 | if (socket && socket->state() == QTcpSocket::ConnectedState) { |
385 | tmp = socket->readAll(); |
386 | bytesDone += tmp.size(); |
387 | } else { |
388 | tmp = bytesFromSocket; |
389 | bytesFromSocket.clear(); |
390 | } |
391 | return tmp; |
392 | } |
393 | |
394 | void QFtpDTP::writeData() |
395 | { |
396 | if (!socket) |
397 | return; |
398 | |
399 | if (is_ba) { |
400 | #if defined(QFTPDTP_DEBUG) |
401 | qDebug("QFtpDTP::writeData: write %d bytes" , data.ba->size()); |
402 | #endif |
403 | if (data.ba->size() == 0) |
404 | emit dataTransferProgress(0, bytesTotal); |
405 | else |
406 | socket->write(data: 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 | |
434 | void QFtpDTP::dataReadyRead() |
435 | { |
436 | writeData(); |
437 | } |
438 | |
439 | inline bool QFtpDTP::hasError() const |
440 | { |
441 | return !err.isNull(); |
442 | } |
443 | |
444 | inline QString QFtpDTP::errorMessage() const |
445 | { |
446 | return err; |
447 | } |
448 | |
449 | inline void QFtpDTP::clearError() |
450 | { |
451 | err.clear(); |
452 | } |
453 | |
454 | void QFtpDTP::abortConnection() |
455 | { |
456 | #if defined(QFTPDTP_DEBUG) |
457 | qDebug("QFtpDTP::abortConnection, bytesAvailable == %lli" , |
458 | socket ? socket->bytesAvailable() : (qint64) 0); |
459 | #endif |
460 | callWriteData = false; |
461 | clearData(); |
462 | |
463 | if (socket) |
464 | socket->abort(); |
465 | } |
466 | |
467 | static void _q_fixupDateTime(QDateTime *dateTime) |
468 | { |
469 | // Adjust for future tolerance. |
470 | const int futureTolerance = 86400; |
471 | if (dateTime->secsTo(QDateTime::currentDateTime()) < -futureTolerance) { |
472 | QDate d = dateTime->date(); |
473 | d.setDate(year: d.year() - 1, month: d.month(), day: d.day()); |
474 | dateTime->setDate(d); |
475 | } |
476 | } |
477 | |
478 | static void _q_parseUnixDir(const QStringList &tokens, const QString &userName, QUrlInfo *info) |
479 | { |
480 | // Unix style, 7 + 1 entries |
481 | // -rw-r--r-- 1 ftp ftp 17358091 Aug 10 2004 qt-x11-free-3.3.3.tar.gz |
482 | // drwxr-xr-x 3 ftp ftp 4096 Apr 14 2000 compiled-examples |
483 | // lrwxrwxrwx 1 ftp ftp 9 Oct 29 2005 qtscape -> qtmozilla |
484 | if (tokens.size() != 8) |
485 | return; |
486 | |
487 | char first = tokens.at(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 | |
563 | static void _q_parseDosDir(const QStringList &tokens, const QString &userName, QUrlInfo *info) |
564 | { |
565 | // DOS style, 3 + 1 entries |
566 | // 01-16-02 11:14AM <DIR> epsgroup |
567 | // 06-05-03 03:19PM 1973 readme.txt |
568 | if (tokens.size() != 4) |
569 | return; |
570 | |
571 | Q_UNUSED(userName); |
572 | |
573 | QString name = tokens.at(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 | |
616 | bool QFtpDTP::parseDir(const QByteArray &buffer, const QString &userName, QUrlInfo *info) |
617 | { |
618 | if (buffer.isEmpty()) |
619 | return false; |
620 | |
621 | QString bufferStr = QString::fromUtf8(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 | |
643 | void QFtpDTP::socketConnected() |
644 | { |
645 | bytesDone = 0; |
646 | #if defined(QFTPDTP_DEBUG) |
647 | qDebug("QFtpDTP::connectState(CsConnected)" ); |
648 | #endif |
649 | emit connectState(QFtpDTP::CsConnected); |
650 | } |
651 | |
652 | void QFtpDTP::socketReadyRead() |
653 | { |
654 | if (!socket) |
655 | return; |
656 | |
657 | if (pi->currentCommand().isEmpty()) { |
658 | socket->close(); |
659 | #if defined(QFTPDTP_DEBUG) |
660 | qDebug("QFtpDTP::connectState(CsClosed)" ); |
661 | #endif |
662 | emit connectState(QFtpDTP::CsClosed); |
663 | return; |
664 | } |
665 | |
666 | if (pi->abortState != QFtpPI::None) { |
667 | // discard data |
668 | socket->readAll(); |
669 | return; |
670 | } |
671 | |
672 | if (pi->currentCommand().startsWith(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 | |
724 | void QFtpDTP::socketError(QAbstractSocket::SocketError e) |
725 | { |
726 | if (e == QTcpSocket::HostNotFoundError) { |
727 | #if defined(QFTPDTP_DEBUG) |
728 | qDebug("QFtpDTP::connectState(CsHostNotFound)" ); |
729 | #endif |
730 | emit connectState(QFtpDTP::CsHostNotFound); |
731 | } else if (e == QTcpSocket::ConnectionRefusedError) { |
732 | #if defined(QFTPDTP_DEBUG) |
733 | qDebug("QFtpDTP::connectState(CsConnectionRefused)" ); |
734 | #endif |
735 | emit connectState(QFtpDTP::CsConnectionRefused); |
736 | } |
737 | } |
738 | |
739 | void QFtpDTP::socketConnectionClosed() |
740 | { |
741 | if (!is_ba && data.dev) { |
742 | clearData(); |
743 | } |
744 | |
745 | if (socket->isOpen()) |
746 | bytesFromSocket = socket->readAll(); |
747 | else |
748 | bytesFromSocket.clear(); |
749 | #if defined(QFTPDTP_DEBUG) |
750 | qDebug("QFtpDTP::connectState(CsClosed)" ); |
751 | #endif |
752 | emit connectState(QFtpDTP::CsClosed); |
753 | } |
754 | |
755 | void QFtpDTP::socketBytesWritten(qint64 bytes) |
756 | { |
757 | bytesDone += bytes; |
758 | #if defined(QFTPDTP_DEBUG) |
759 | qDebug("QFtpDTP::bytesWritten(%lli)" , bytesDone); |
760 | #endif |
761 | emit dataTransferProgress(bytesDone, bytesTotal); |
762 | if (callWriteData) |
763 | writeData(); |
764 | } |
765 | |
766 | void 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 | |
779 | void QFtpDTP::clearData() |
780 | { |
781 | is_ba = false; |
782 | data.dev = nullptr; |
783 | } |
784 | |
785 | /********************************************************************** |
786 | * |
787 | * QFtpPI implemenatation |
788 | * |
789 | *********************************************************************/ |
790 | QFtpPI::QFtpPI(QObject *parent) : |
791 | QObject(parent), |
792 | rawCommand(false), |
793 | transferConnectionExtended(true), |
794 | dtp(this), |
795 | commandSocket(nullptr), |
796 | state(Begin), abortState(None), |
797 | currentCmd(QString()), |
798 | waitForDtpToConnect(false), |
799 | waitForDtpToClose(false) |
800 | { |
801 | commandSocket.setObjectName(QLatin1String("QFtpPI_socket" )); |
802 | connect(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 | |
817 | void QFtpPI::connectToHost(const QString &host, quint16 port) |
818 | { |
819 | emit connectState(QFtp::HostLookup); |
820 | #ifndef QT_NO_BEARERMANAGEMENT // ### Qt6: Remove section |
821 | //copy network session down to the socket & DTP |
822 | commandSocket.setProperty(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 | */ |
838 | bool QFtpPI::sendCommands(const QStringList &cmds) |
839 | { |
840 | if (!pendingCommands.isEmpty()) |
841 | return false; |
842 | |
843 | if (commandSocket.state() != QTcpSocket::ConnectedState || state!=Idle) { |
844 | emit error(QFtp::NotConnected, QFtp::tr(s: "Not connected" )); |
845 | return true; // there are no pending commands |
846 | } |
847 | |
848 | pendingCommands = cmds; |
849 | startNextCmd(); |
850 | return true; |
851 | } |
852 | |
853 | void QFtpPI::clearPendingCommands() |
854 | { |
855 | pendingCommands.clear(); |
856 | dtp.abortConnection(); |
857 | currentCmd.clear(); |
858 | state = Idle; |
859 | } |
860 | |
861 | void QFtpPI::abort() |
862 | { |
863 | pendingCommands.clear(); |
864 | |
865 | if (abortState != None) |
866 | // ABOR already sent |
867 | return; |
868 | |
869 | if (currentCmd.isEmpty()) |
870 | return; //no command in progress |
871 | |
872 | if (currentCmd.startsWith(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 | |
890 | void QFtpPI::hostFound() |
891 | { |
892 | emit connectState(QFtp::Connecting); |
893 | } |
894 | |
895 | void QFtpPI::connected() |
896 | { |
897 | state = Begin; |
898 | #if defined(QFTPPI_DEBUG) |
899 | // qDebug("QFtpPI state: %d [connected()]", state); |
900 | #endif |
901 | // try to improve performance by setting TCP_NODELAY |
902 | commandSocket.setSocketOption(option: QAbstractSocket::LowDelayOption, value: 1); |
903 | |
904 | emit connectState(QFtp::Connected); |
905 | } |
906 | |
907 | void QFtpPI::connectionClosed() |
908 | { |
909 | commandSocket.close(); |
910 | emit connectState(QFtp::Unconnected); |
911 | } |
912 | |
913 | void QFtpPI::delayedCloseFinished() |
914 | { |
915 | emit connectState(QFtp::Unconnected); |
916 | } |
917 | |
918 | void QFtpPI::error(QAbstractSocket::SocketError e) |
919 | { |
920 | if (e == QTcpSocket::HostNotFoundError) { |
921 | emit connectState(QFtp::Unconnected); |
922 | emit error(QFtp::HostNotFound, |
923 | QFtp::tr(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 | |
935 | void QFtpPI::readyRead() |
936 | { |
937 | if (waitForDtpToClose) |
938 | return; |
939 | |
940 | while (commandSocket.canReadLine()) { |
941 | // read line with respect to line continuation |
942 | QString line = QString::fromUtf8(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 | */ |
992 | bool QFtpPI::processReply() |
993 | { |
994 | #if defined(QFTPPI_DEBUG) |
995 | // qDebug("QFtpPI state: %d [processReply() begin]", state); |
996 | if (replyText.length() < 400) |
997 | qDebug("QFtpPI recv: %d %s" , 100*replyCode[0]+10*replyCode[1]+replyCode[2], replyText.toLatin1().constData()); |
998 | else |
999 | qDebug("QFtpPI recv: %d (text skipped)" , 100*replyCode[0]+10*replyCode[1]+replyCode[2]); |
1000 | #endif |
1001 | |
1002 | int replyCodeInt = 100*replyCode[0] + 10*replyCode[1] + replyCode[2]; |
1003 | |
1004 | // process 226 replies ("Closing Data Connection") only when the data |
1005 | // connection is really closed to avoid short reads of the DTP |
1006 | if (replyCodeInt == 226 || (replyCodeInt == 250 && currentCmd.startsWith(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 | */ |
1164 | bool QFtpPI::startNextCmd() |
1165 | { |
1166 | if (waitForDtpToConnect) |
1167 | // don't process any new commands until we are connected |
1168 | return true; |
1169 | |
1170 | #if defined(QFTPPI_DEBUG) |
1171 | if (state != Idle) |
1172 | qDebug("QFtpPI startNextCmd: Internal error! QFtpPI called in non-Idle state %d" , state); |
1173 | #endif |
1174 | if (pendingCommands.isEmpty()) { |
1175 | currentCmd.clear(); |
1176 | emit finished(replyText); |
1177 | return false; |
1178 | } |
1179 | currentCmd = pendingCommands.constFirst(); |
1180 | |
1181 | // PORT and PASV are edited in-place, depending on whether we |
1182 | // should try the extended transfer connection commands EPRT and |
1183 | // EPSV. The PORT command also triggers setting up a listener, and |
1184 | // the address/port arguments are edited in. |
1185 | QHostAddress address = commandSocket.localAddress(); |
1186 | if (currentCmd.startsWith(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 | |
1227 | void QFtpPI::dtpConnectState(int s) |
1228 | { |
1229 | switch (s) { |
1230 | case QFtpDTP::CsClosed: |
1231 | if (waitForDtpToClose) { |
1232 | // there is an unprocessed reply |
1233 | if (processReply()) |
1234 | replyText = QLatin1String("" ); |
1235 | else |
1236 | return; |
1237 | } |
1238 | waitForDtpToClose = false; |
1239 | readyRead(); |
1240 | return; |
1241 | case QFtpDTP::CsConnected: |
1242 | waitForDtpToConnect = false; |
1243 | startNextCmd(); |
1244 | return; |
1245 | case QFtpDTP::CsHostNotFound: |
1246 | case QFtpDTP::CsConnectionRefused: |
1247 | emit error(QFtp::ConnectionRefused, |
1248 | QFtp::tr(s: "Data Connection refused" )); |
1249 | startNextCmd(); |
1250 | return; |
1251 | default: |
1252 | return; |
1253 | } |
1254 | } |
1255 | |
1256 | /********************************************************************** |
1257 | * |
1258 | * QFtpPrivate |
1259 | * |
1260 | *********************************************************************/ |
1261 | |
1262 | QT_BEGIN_INCLUDE_NAMESPACE |
1263 | #include <private/qobject_p.h> |
1264 | QT_END_INCLUDE_NAMESPACE |
1265 | |
1266 | class QFtpPrivate : public QObjectPrivate |
1267 | { |
1268 | Q_DECLARE_PUBLIC(QFtp) |
1269 | public: |
1270 | |
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 | |
1300 | int 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 | */ |
1424 | QFtp::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 | */ |
1671 | int QFtp::connectToHost(const QString &host, quint16 port) |
1672 | { |
1673 | QStringList cmds; |
1674 | cmds << host; |
1675 | cmds << QString::number((uint)port); |
1676 | int id = d_func()->addCommand(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 | */ |
1700 | int 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 | */ |
1735 | int 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 | */ |
1746 | int 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 | */ |
1762 | int 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 | */ |
1787 | int QFtp::list(const QString &dir) |
1788 | { |
1789 | QStringList cmds; |
1790 | cmds << QLatin1String("TYPE A\r\n" ); |
1791 | cmds << QLatin1String(d_func()->transferMode == Passive ? "PASV\r\n" : "PORT\r\n" ); |
1792 | if (dir.isEmpty()) |
1793 | cmds << QLatin1String("LIST\r\n" ); |
1794 | else |
1795 | cmds << (QLatin1String("LIST " ) + dir + QLatin1String("\r\n" )); |
1796 | return d_func()->addCommand(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 | */ |
1814 | int 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 | */ |
1860 | int QFtp::get(const QString &file, QIODevice *dev, TransferType type) |
1861 | { |
1862 | QStringList cmds; |
1863 | if (type == Binary) |
1864 | cmds << QLatin1String("TYPE I\r\n" ); |
1865 | else |
1866 | cmds << QLatin1String("TYPE A\r\n" ); |
1867 | cmds << QLatin1String("SIZE " ) + file + QLatin1String("\r\n" ); |
1868 | cmds << QLatin1String(d_func()->transferMode == Passive ? "PASV\r\n" : "PORT\r\n" ); |
1869 | cmds << QLatin1String("RETR " ) + file + QLatin1String("\r\n" ); |
1870 | return d_func()->addCommand(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 | */ |
1898 | int QFtp::put(const QByteArray &data, const QString &file, TransferType type) |
1899 | { |
1900 | QStringList cmds; |
1901 | if (type == Binary) |
1902 | cmds << QLatin1String("TYPE I\r\n" ); |
1903 | else |
1904 | cmds << QLatin1String("TYPE A\r\n" ); |
1905 | cmds << QLatin1String(d_func()->transferMode == Passive ? "PASV\r\n" : "PORT\r\n" ); |
1906 | cmds << QLatin1String("ALLO " ) + QString::number(data.size()) + QLatin1String("\r\n" ); |
1907 | cmds << QLatin1String("STOR " ) + file + QLatin1String("\r\n" ); |
1908 | return d_func()->addCommand(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 | */ |
1926 | int QFtp::put(QIODevice *dev, const QString &file, TransferType type) |
1927 | { |
1928 | QStringList cmds; |
1929 | if (type == Binary) |
1930 | cmds << QLatin1String("TYPE I\r\n" ); |
1931 | else |
1932 | cmds << QLatin1String("TYPE A\r\n" ); |
1933 | cmds << QLatin1String(d_func()->transferMode == Passive ? "PASV\r\n" : "PORT\r\n" ); |
1934 | if (!dev->isSequential()) |
1935 | cmds << QLatin1String("ALLO " ) + QString::number(dev->size()) + QLatin1String("\r\n" ); |
1936 | cmds << QLatin1String("STOR " ) + file + QLatin1String("\r\n" ); |
1937 | return d_func()->addCommand(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 | */ |
1955 | int 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 | */ |
1975 | int 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 | */ |
1995 | int 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 | */ |
2015 | int QFtp::rename(const QString &oldname, const QString &newname) |
2016 | { |
2017 | QStringList cmds; |
2018 | cmds << QLatin1String("RNFR " ) + oldname + QLatin1String("\r\n" ); |
2019 | cmds << QLatin1String("RNTO " ) + newname + QLatin1String("\r\n" ); |
2020 | return d_func()->addCommand(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 | */ |
2042 | int 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 | */ |
2055 | qint64 QFtp::bytesAvailable() const |
2056 | { |
2057 | return d_func()->pi.dtp.bytesAvailable(); |
2058 | } |
2059 | |
2060 | /*! |
2061 | \internal |
2062 | Reads \a maxlen bytes from the data socket into \a data and |
2063 | returns the number of bytes read. Returns -1 if an error occurred. |
2064 | |
2065 | \sa get(), readyRead(), bytesAvailable(), readAll() |
2066 | */ |
2067 | qint64 QFtp::read(char *data, qint64 maxlen) |
2068 | { |
2069 | return d_func()->pi.dtp.read(data, maxlen); |
2070 | } |
2071 | |
2072 | /*! |
2073 | \internal |
2074 | Reads all the bytes available from the data socket and returns |
2075 | them. |
2076 | |
2077 | \sa get(), readyRead(), bytesAvailable(), read() |
2078 | */ |
2079 | QByteArray QFtp::readAll() |
2080 | { |
2081 | return d_func()->pi.dtp.readAll(); |
2082 | } |
2083 | |
2084 | /*! |
2085 | \internal |
2086 | Aborts the current command and deletes all scheduled commands. |
2087 | |
2088 | If there is an unfinished command (i.e. a command for which the |
2089 | commandStarted() signal has been emitted, but for which the |
2090 | commandFinished() signal has not been emitted), this function |
2091 | sends an \c ABORT command to the server. When the server replies |
2092 | that the command is aborted, the commandFinished() signal with the |
2093 | \c error argument set to \c true is emitted for the command. Due |
2094 | to timing issues, it is possible that the command had already |
2095 | finished before the abort request reached the server, in which |
2096 | case, the commandFinished() signal is emitted with the \c error |
2097 | argument set to \c false. |
2098 | |
2099 | For all other commands that are affected by the abort(), no |
2100 | signals are emitted. |
2101 | |
2102 | If you don't start further FTP commands directly after the |
2103 | abort(), there won't be any scheduled commands and the done() |
2104 | signal is emitted. |
2105 | |
2106 | \warning Some FTP servers, for example the BSD FTP daemon (version |
2107 | 0.3), wrongly return a positive reply even when an abort has |
2108 | occurred. For these servers the commandFinished() signal has its |
2109 | error flag set to \c false, even though the command did not |
2110 | complete successfully. |
2111 | |
2112 | \sa clearPendingCommands() |
2113 | */ |
2114 | void QFtp::abort() |
2115 | { |
2116 | if (d_func()->pending.isEmpty()) |
2117 | return; |
2118 | |
2119 | clearPendingCommands(); |
2120 | d_func()->pi.abort(); |
2121 | } |
2122 | |
2123 | /*! |
2124 | \internal |
2125 | Clears the last error. |
2126 | |
2127 | \sa currentCommand() |
2128 | */ |
2129 | void QFtp::clearError() |
2130 | { |
2131 | d_func()->error = NoError; |
2132 | } |
2133 | |
2134 | /*! |
2135 | \internal |
2136 | Returns the identifier of the FTP command that is being executed |
2137 | or 0 if there is no command being executed. |
2138 | |
2139 | \sa currentCommand() |
2140 | */ |
2141 | int QFtp::currentId() const |
2142 | { |
2143 | if (d_func()->pending.isEmpty()) |
2144 | return 0; |
2145 | return d_func()->pending.first()->id; |
2146 | } |
2147 | |
2148 | /*! |
2149 | \internal |
2150 | Returns the command type of the FTP command being executed or \c |
2151 | None if there is no command being executed. |
2152 | |
2153 | \sa currentId() |
2154 | */ |
2155 | QFtp::Command QFtp::currentCommand() const |
2156 | { |
2157 | if (d_func()->pending.isEmpty()) |
2158 | return None; |
2159 | return d_func()->pending.first()->command; |
2160 | } |
2161 | |
2162 | /*! |
2163 | \internal |
2164 | Returns the QIODevice pointer that is used by the FTP command to read data |
2165 | from or store data to. If there is no current FTP command being executed or |
2166 | if the command does not use an IO device, this function returns \nullptr. |
2167 | |
2168 | This function can be used to delete the QIODevice in the slot connected to |
2169 | the commandFinished() signal. |
2170 | |
2171 | \sa get(), put() |
2172 | */ |
2173 | QIODevice* QFtp::currentDevice() const |
2174 | { |
2175 | if (d_func()->pending.isEmpty()) |
2176 | return nullptr; |
2177 | QFtpCommand *c = d_func()->pending.first(); |
2178 | if (c->is_ba) |
2179 | return nullptr; |
2180 | return c->data.dev; |
2181 | } |
2182 | |
2183 | /*! |
2184 | \internal |
2185 | Returns \c true if there are any commands scheduled that have not yet |
2186 | been executed; otherwise returns \c false. |
2187 | |
2188 | The command that is being executed is \e not considered as a |
2189 | scheduled command. |
2190 | |
2191 | \sa clearPendingCommands(), currentId(), currentCommand() |
2192 | */ |
2193 | bool QFtp::hasPendingCommands() const |
2194 | { |
2195 | return d_func()->pending.count() > 1; |
2196 | } |
2197 | |
2198 | /*! |
2199 | \internal |
2200 | Deletes all pending commands from the list of scheduled commands. |
2201 | This does not affect the command that is being executed. If you |
2202 | want to stop this as well, use abort(). |
2203 | |
2204 | \sa hasPendingCommands(), abort() |
2205 | */ |
2206 | void QFtp::clearPendingCommands() |
2207 | { |
2208 | // delete all entires except the first one |
2209 | while (d_func()->pending.count() > 1) |
2210 | delete d_func()->pending.takeLast(); |
2211 | } |
2212 | |
2213 | /*! |
2214 | \internal |
2215 | Returns the current state of the object. When the state changes, |
2216 | the stateChanged() signal is emitted. |
2217 | |
2218 | \sa State, stateChanged() |
2219 | */ |
2220 | QFtp::State QFtp::state() const |
2221 | { |
2222 | return d_func()->state; |
2223 | } |
2224 | |
2225 | /*! |
2226 | \internal |
2227 | Returns the last error that occurred. This is useful to find out |
2228 | what went wrong when receiving a commandFinished() or a done() |
2229 | signal with the \c error argument set to \c true. |
2230 | |
2231 | If you start a new command, the error status is reset to \c NoError. |
2232 | */ |
2233 | QFtp::Error QFtp::error() const |
2234 | { |
2235 | return d_func()->error; |
2236 | } |
2237 | |
2238 | /*! |
2239 | \internal |
2240 | Returns a human-readable description of the last error that |
2241 | occurred. This is useful for presenting a error message to the |
2242 | user when receiving a commandFinished() or a done() signal with |
2243 | the \c error argument set to \c true. |
2244 | |
2245 | The error string is often (but not always) the reply from the |
2246 | server, so it is not always possible to translate the string. If |
2247 | the message comes from Qt, the string has already passed through |
2248 | tr(). |
2249 | */ |
2250 | QString QFtp::errorString() const |
2251 | { |
2252 | return d_func()->errorString; |
2253 | } |
2254 | |
2255 | /*! \internal |
2256 | */ |
2257 | void QFtpPrivate::_q_startNextCommand() |
2258 | { |
2259 | Q_Q(QFtp); |
2260 | if (pending.isEmpty()) |
2261 | return; |
2262 | QFtpCommand *c = pending.constFirst(); |
2263 | |
2264 | error = QFtp::NoError; |
2265 | errorString = QT_TRANSLATE_NOOP(QFtp, QLatin1String("Unknown error" )); |
2266 | |
2267 | if (q->bytesAvailable()) |
2268 | q->readAll(); // clear the data |
2269 | emit q->commandStarted(c->id); |
2270 | |
2271 | // Proxy support, replace the Login argument in place, then fall |
2272 | // through. |
2273 | if (c->command == QFtp::Login && !proxyHost.isEmpty()) { |
2274 | QString loginString; |
2275 | loginString += QStringRef(&c->rawCmds.constFirst()).trimmed() + QLatin1Char('@') + host; |
2276 | if (port && port != 21) |
2277 | loginString += QLatin1Char(':') + QString::number(port); |
2278 | loginString += QLatin1String("\r\n" ); |
2279 | c->rawCmds[0] = loginString; |
2280 | } |
2281 | |
2282 | if (c->command == QFtp::SetTransferMode) { |
2283 | _q_piFinished(QLatin1String("Transfer mode set" )); |
2284 | } else if (c->command == QFtp::SetProxy) { |
2285 | proxyHost = c->rawCmds.at(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 | */ |
2330 | void QFtpPrivate::_q_piFinished(const QString&) |
2331 | { |
2332 | if (pending.isEmpty()) |
2333 | return; |
2334 | QFtpCommand *c = pending.constFirst(); |
2335 | |
2336 | if (c->command == QFtp::Close) { |
2337 | // The order of in which the slots are called is arbitrary, so |
2338 | // disconnect the SIGNAL-SIGNAL temporary to make sure that we |
2339 | // don't get the commandFinished() signal before the stateChanged() |
2340 | // signal. |
2341 | if (state != QFtp::Unconnected) { |
2342 | close_waitForStateChange = true; |
2343 | return; |
2344 | } |
2345 | } |
2346 | emit q_func()->commandFinished(c->id, false); |
2347 | pending.removeFirst(); |
2348 | |
2349 | delete c; |
2350 | |
2351 | if (pending.isEmpty()) { |
2352 | emit q_func()->done(false); |
2353 | } else { |
2354 | _q_startNextCommand(); |
2355 | } |
2356 | } |
2357 | |
2358 | /*! \internal |
2359 | */ |
2360 | void 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 | */ |
2436 | void QFtpPrivate::_q_piConnectState(int connectState) |
2437 | { |
2438 | state = QFtp::State(connectState); |
2439 | emit q_func()->stateChanged(state); |
2440 | if (close_waitForStateChange) { |
2441 | close_waitForStateChange = false; |
2442 | _q_piFinished(QLatin1String(QT_TRANSLATE_NOOP("QFtp" , "Connection closed" ))); |
2443 | } |
2444 | } |
2445 | |
2446 | /*! \internal |
2447 | */ |
2448 | void QFtpPrivate::_q_piFtpReply(int code, const QString &text) |
2449 | { |
2450 | if (q_func()->currentCommand() == QFtp::RawCommand) { |
2451 | pi.rawCommand = true; |
2452 | emit q_func()->rawCommandReply(code, text); |
2453 | } |
2454 | } |
2455 | |
2456 | /*! |
2457 | \internal |
2458 | Destructor. |
2459 | */ |
2460 | QFtp::~QFtp() |
2461 | { |
2462 | abort(); |
2463 | close(); |
2464 | } |
2465 | |
2466 | QT_END_NAMESPACE |
2467 | |
2468 | #include "qftp.moc" |
2469 | |
2470 | #include "moc_qftp_p.cpp" |
2471 | |