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