1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
4 SPDX-FileCopyrightText: 2019-2021 Harald Sitter <sitter@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#ifndef KDELIBS_FTP_H
10#define KDELIBS_FTP_H
11
12#include <qplatformdefs.h>
13
14#include <QDateTime>
15#include <QUrl>
16
17#include <workerbase.h>
18
19class QTcpServer;
20class QTcpSocket;
21class QNetworkProxy;
22class QAuthenticator;
23
24struct FtpEntry {
25 QString name;
26 QString owner;
27 QString group;
28 QString link;
29
30 KIO::filesize_t size;
31 mode_t type;
32 mode_t access;
33 QDateTime date;
34};
35
36class FtpInternal;
37
38/**
39 * Login Mode for ftpOpenConnection
40 */
41enum class LoginMode {
42 Deferred,
43 Explicit,
44 Implicit,
45};
46
47using Result = KIO::WorkerResult;
48
49/**
50 * Special Result composite for errors during connection.
51 */
52struct ConnectionResult {
53 QTcpSocket *socket;
54 Result result;
55};
56
57QDebug operator<<(QDebug dbg, const Result &r);
58
59//===============================================================================
60// Ftp
61// The API class. This class should not contain *any* FTP logic. It acts
62// as a container for FtpInternal to prevent the latter from directly doing
63// state manipulation via error/finished/opened etc.
64//===============================================================================
65class Ftp : public KIO::WorkerBase
66{
67public:
68 Ftp(const QByteArray &pool, const QByteArray &app);
69 ~Ftp() override;
70
71 void setHost(const QString &host, quint16 port, const QString &user, const QString &pass) override;
72
73 /**
74 * Connects to a ftp server and logs us in
75 * m_bLoggedOn is set to true if logging on was successful.
76 * It is set to false if the connection becomes closed.
77 *
78 */
79 KIO::WorkerResult openConnection() override;
80
81 /**
82 * Closes the connection
83 */
84 void closeConnection() override;
85
86 KIO::WorkerResult stat(const QUrl &url) override;
87
88 KIO::WorkerResult listDir(const QUrl &url) override;
89 KIO::WorkerResult mkdir(const QUrl &url, int permissions) override;
90 KIO::WorkerResult rename(const QUrl &src, const QUrl &dst, KIO::JobFlags flags) override;
91 KIO::WorkerResult del(const QUrl &url, bool isfile) override;
92 KIO::WorkerResult chmod(const QUrl &url, int permissions) override;
93
94 KIO::WorkerResult get(const QUrl &url) override;
95 KIO::WorkerResult put(const QUrl &url, int permissions, KIO::JobFlags flags) override;
96
97 void worker_status() override;
98
99 /**
100 * Handles the case that one side of the job is a local file
101 */
102 KIO::WorkerResult copy(const QUrl &src, const QUrl &dest, int permissions, KIO::JobFlags flags) override;
103
104 std::unique_ptr<FtpInternal> d;
105};
106
107/**
108 * Internal logic class.
109 *
110 * This class implements strict separation between the API (Ftp) and
111 * the logic behind the API (FtpInternal). This class' functions
112 * are meant to return Result objects up the call stack to Ftp where
113 * they will be turned into command results (e.g. error(),
114 * finished(), etc.). This class cannot and must not call these signals
115 * directly as it leads to unclear states.
116 */
117class FtpInternal : public QObject
118{
119 Q_OBJECT
120public:
121 explicit FtpInternal(Ftp *qptr);
122 ~FtpInternal() override;
123
124 // ---------------------------------------- API
125
126 void setHost(const QString &host, quint16 port, const QString &user, const QString &pass);
127
128 /**
129 * Connects to a ftp server and logs us in
130 * m_bLoggedOn is set to true if logging on was successful.
131 * It is set to false if the connection becomes closed.
132 *
133 */
134 Q_REQUIRED_RESULT Result openConnection();
135
136 /**
137 * Closes the connection
138 */
139 void closeConnection();
140
141 Q_REQUIRED_RESULT Result stat(const QUrl &url);
142
143 Result listDir(const QUrl &url);
144 Q_REQUIRED_RESULT Result mkdir(const QUrl &url, int permissions);
145 Q_REQUIRED_RESULT Result rename(const QUrl &src, const QUrl &dst, KIO::JobFlags flags);
146 Q_REQUIRED_RESULT Result del(const QUrl &url, bool isfile);
147 Q_REQUIRED_RESULT Result chmod(const QUrl &url, int permissions);
148
149 Q_REQUIRED_RESULT Result get(const QUrl &url);
150 Q_REQUIRED_RESULT Result put(const QUrl &url, int permissions, KIO::JobFlags flags);
151 // virtual void mimetype( const QUrl& url );
152
153 void worker_status();
154
155 /**
156 * Handles the case that one side of the job is a local file
157 */
158 Q_REQUIRED_RESULT Result copy(const QUrl &src, const QUrl &dest, int permissions, KIO::JobFlags flags);
159
160 // ---------------------------------------- END API
161
162 static bool isSocksProxyScheme(const QString &scheme);
163 bool isSocksProxy() const;
164
165 /**
166 * Connect and login to the FTP server.
167 *
168 * @param loginMode controls if login info should be sent<br>
169 * loginDeferred - must not be logged on, no login info is sent<br>
170 * loginExplicit - must not be logged on, login info is sent<br>
171 * loginImplicit - login info is sent if not logged on
172 *
173 * @return true on success (a login failure would return false).
174 */
175 Q_REQUIRED_RESULT Result ftpOpenConnection(LoginMode loginMode);
176
177 /**
178 * Called by openConnection. It logs us in.
179 * m_initialPath is set to the current working directory
180 * if logging on was successful.
181 *
182 * @param userChanged if not nullptr, will be set to true if the user name
183 * was changed during login.
184 * @return true on success.
185 */
186 Q_REQUIRED_RESULT Result ftpLogin(bool *userChanged = nullptr);
187
188 /**
189 * ftpSendCmd - send a command (@p cmd) and read response
190 *
191 * @param maxretries number of time it should retry. Since it recursively
192 * calls itself if it can't read the answer (this happens especially after
193 * timeouts), we need to limit the recursiveness ;-)
194 *
195 * return true if any response received, false on error
196 */
197 Q_REQUIRED_RESULT bool ftpSendCmd(const QByteArray &cmd, int maxretries = 1);
198
199 /**
200 * Use the SIZE command to get the file size.
201 * @param mode the size depends on the transfer mode, hence this arg.
202 * @return true on success
203 * Gets the size into m_size.
204 */
205 bool ftpSize(const QString &path, char mode);
206
207 /**
208 * Returns true if the file exists.
209 * Implemented using the SIZE command.
210 */
211 bool ftpFileExists(const QString &path);
212
213 /**
214 * Set the current working directory, but only if not yet current
215 */
216 Q_REQUIRED_RESULT bool ftpFolder(const QString &path);
217
218 /**
219 * Runs a command on the ftp server like "list" or "retr". In contrast to
220 * ftpSendCmd a data connection is opened. The corresponding socket
221 * sData is available for reading/writing on success.
222 * The connection must be closed afterwards with ftpCloseCommand.
223 *
224 * @param mode is 'A' or 'I'. 'A' means ASCII transfer, 'I' means binary transfer.
225 * @param errorcode the command-dependent error code to emit on error
226 *
227 * @return true if the command was accepted by the server.
228 */
229 Q_REQUIRED_RESULT Result ftpOpenCommand(const char *command, const QString &path, char mode, int errorcode, KIO::fileoffset_t offset = 0);
230
231 /**
232 * The counterpart to openCommand.
233 * Closes data sockets and then reads line sent by server at
234 * end of command.
235 * @return false on error (line doesn't start with '2')
236 */
237 bool ftpCloseCommand();
238
239 /**
240 * Send "TYPE I" or "TYPE A" only if required, see m_cDataMode.
241 *
242 * Use 'A' to select ASCII and 'I' to select BINARY mode. If
243 * cMode is '?' the m_bTextMode flag is used to choose a mode.
244 */
245 bool ftpDataMode(char cMode);
246
247 // void ftpAbortTransfer();
248
249 /**
250 * Used by ftpOpenCommand, return 0 on success or an error code
251 */
252 int ftpOpenDataConnection();
253
254 /**
255 * closes a data connection, see ftpOpenDataConnection()
256 */
257 void ftpCloseDataConnection();
258
259 /**
260 * Helper for ftpOpenDataConnection
261 */
262 int ftpOpenPASVDataConnection();
263 /**
264 * Helper for ftpOpenDataConnection
265 */
266 int ftpOpenEPSVDataConnection();
267 /**
268 * Helper for ftpOpenDataConnection
269 */
270 int ftpOpenPortDataConnection();
271
272 bool ftpChmod(const QString &path, int permissions);
273
274 // used by listDir
275 Q_REQUIRED_RESULT Result ftpOpenDir(const QString &path);
276 /**
277 * Called to parse directory listings, call this until it returns false
278 */
279 bool ftpReadDir(FtpEntry &ftpEnt);
280
281 /**
282 * Helper to fill an UDSEntry
283 */
284 void ftpCreateUDSEntry(const QString &filename, const FtpEntry &ftpEnt, KIO::UDSEntry &entry, bool isDir);
285
286 void ftpShortStatAnswer(const QString &filename, bool isDir);
287
288 Q_REQUIRED_RESULT Result ftpStatAnswerNotFound(const QString &path, const QString &filename);
289
290 /**
291 * This is the internal implementation of rename() - set put().
292 *
293 * @return true on success.
294 */
295 Q_REQUIRED_RESULT Result ftpRename(const QString &src, const QString &dst, KIO::JobFlags flags);
296
297 /**
298 * Called by openConnection. It opens the control connection to the ftp server.
299 *
300 * @return true on success.
301 */
302 Q_REQUIRED_RESULT Result ftpOpenControlConnection();
303 Q_REQUIRED_RESULT Result ftpOpenControlConnection(const QString &host, int port);
304
305 /**
306 * closes the socket holding the control connection (see ftpOpenControlConnection)
307 */
308 void ftpCloseControlConnection();
309
310 /**
311 * read a response from the server (a trailing CR gets stripped)
312 * @param iOffset -1 to read a new line from the server<br>
313 * 0 to return the whole response string
314 * >0 to return the response with iOffset chars skipped
315 * @return the response message with iOffset chars skipped (or "" if iOffset points
316 * behind the available data)
317 */
318 const char *ftpResponse(int iOffset);
319
320 /**
321 * This is the internal implementation of get() - see copy().
322 *
323 * IMPORTANT: the caller should call ftpCloseCommand() on return.
324 * The function does not call error(), the caller should do this.
325 *
326 * @param iError set to an ERR_xxxx code on error
327 * @param iCopyFile -1 -or- handle of a local destination file
328 * @param hCopyOffset local file only: non-zero for resume
329 * @return 0 for success, -1 for server error, -2 for client error
330 */
331 Q_REQUIRED_RESULT Result ftpGet(int iCopyFile, const QString &sCopyFile, const QUrl &url, KIO::fileoffset_t hCopyOffset);
332
333 /**
334 * This is the internal implementation of put() - see copy().
335 *
336 * IMPORTANT: the caller should call ftpCloseCommand() on return.
337 * The function does not call error(), the caller should do this.
338 *
339 * @param iError set to an ERR_xxxx code on error
340 * @param iCopyFile -1 -or- handle of a local source file
341 * @return 0 for success, -1 for server error, -2 for client error
342 */
343 Q_REQUIRED_RESULT Result ftpPut(int iCopyFile, const QUrl &url, int permissions, KIO::JobFlags flags);
344
345 /**
346 * helper called from copy() to implement FILE -> FTP transfers
347 *
348 * @param iError set to an ERR_xxxx code on error
349 * @param iCopyFile [out] handle of a local source file
350 * @param sCopyFile path of the local source file
351 * @return 0 for success, -1 for server error, -2 for client error
352 */
353 Q_REQUIRED_RESULT Result ftpCopyPut(int &iCopyFile, const QString &sCopyFile, const QUrl &url, int permissions, KIO::JobFlags flags);
354
355 /**
356 * helper called from copy() to implement FTP -> FILE transfers
357 *
358 * @param iError set to an ERR_xxxx code on error
359 * @param iCopyFile [out] handle of a local source file
360 * @param sCopyFile path of the local destination file
361 * @return 0 for success, -1 for server error, -2 for client error
362 */
363 Q_REQUIRED_RESULT Result ftpCopyGet(int &iCopyFile, const QString &sCopyFile, const QUrl &url, int permissions, KIO::JobFlags flags);
364
365 /**
366 * Sends the MIME type of the content to retrieved.
367 *
368 * @param iError set to an ERR_xxxx code on error
369 * @return 0 for success, -1 for server error, -2 for client error
370 */
371 Q_REQUIRED_RESULT Result ftpSendMimeType(const QUrl &url);
372
373 /**
374 * Fixes up an entry name so that extraneous whitespaces do not cause
375 * problems. See bug# 88575 and bug# 300988.
376 */
377 void fixupEntryName(FtpEntry *ftpEnt);
378
379 /**
380 * Calls @ref statEntry.
381 */
382 bool maybeEmitStatEntry(FtpEntry &ftpEnt, const QString &filename, bool isDir);
383
384 /**
385 * Setup the connection to the server.
386 */
387 Q_REQUIRED_RESULT ConnectionResult synchronousConnectToHost(const QString &host, quint16 port);
388
389private: // data members
390 Ftp *const q;
391
392 QString m_host;
393 int m_port = 0;
394 QString m_user;
395 QString m_pass;
396 /**
397 * Where we end up after connecting
398 */
399 QString m_initialPath;
400 QUrl m_proxyURL;
401 QStringList m_proxyUrls;
402
403 /**
404 * the current working directory - see ftpFolder
405 */
406 QString m_currentPath;
407
408 /**
409 * the status returned by the FTP protocol, set in ftpResponse()
410 */
411 int m_iRespCode = 0;
412
413 /**
414 * the status/100 returned by the FTP protocol, set in ftpResponse()
415 */
416 int m_iRespType = 0;
417
418 /**
419 * This flag is maintained by ftpDataMode() and contains I or A after
420 * ftpDataMode() has successfully set the mode.
421 */
422 char m_cDataMode;
423
424 /**
425 * true if logged on (m_control should also be non-nullptr)
426 */
427 bool m_bLoggedOn;
428
429 /**
430 * true if a "textmode" metadata key was found by ftpLogin(). This
431 * switches the ftp data transfer mode from binary to ASCII.
432 */
433 bool m_bTextMode;
434
435 /**
436 * true if a data stream is open, used in closeConnection().
437 *
438 * When the user cancels a get or put command the Ftp dtor will be called,
439 * which in turn calls closeConnection(). The later would try to send QUIT
440 * which won't work until timeout. ftpOpenCommand sets the m_bBusy flag so
441 * that the sockets will be closed immediately - the server should be
442 * capable of handling this and return an error code on thru the control
443 * connection. The m_bBusy gets cleared by the ftpCloseCommand() routine.
444 */
445 bool m_bBusy;
446
447 bool m_bPasv;
448
449 KIO::filesize_t m_size;
450 static const KIO::filesize_t UnknownSize;
451
452 enum {
453 epsvUnknown = 0x01,
454 epsvAllUnknown = 0x02,
455 eprtUnknown = 0x04,
456 epsvAllSent = 0x10,
457 pasvUnknown = 0x20,
458 chmodUnknown = 0x100,
459 };
460 int m_extControl;
461
462 /**
463 * control connection socket, only set if openControl() succeeded
464 */
465 QTcpSocket *m_control = nullptr;
466 QByteArray m_lastControlLine;
467
468 /**
469 * data connection socket
470 */
471 QTcpSocket *m_data = nullptr;
472
473 /**
474 * active mode server socket
475 */
476 QTcpServer *m_server = nullptr;
477};
478
479#endif // KDELIBS_FTP_H
480

source code of kio/src/kioworkers/ftp/ftp.h