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