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 | |
19 | class QTcpServer; |
20 | class QTcpSocket; |
21 | class QNetworkProxy; |
22 | class QAuthenticator; |
23 | |
24 | struct 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 | |
36 | class FtpInternal; |
37 | |
38 | /** |
39 | * Login Mode for ftpOpenConnection |
40 | */ |
41 | enum class LoginMode { |
42 | Deferred, |
43 | Explicit, |
44 | Implicit, |
45 | }; |
46 | |
47 | using Result = KIO::WorkerResult; |
48 | |
49 | /** |
50 | * Special Result composite for errors during connection. |
51 | */ |
52 | struct ConnectionResult { |
53 | QTcpSocket *socket; |
54 | Result result; |
55 | }; |
56 | |
57 | QDebug 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 | //=============================================================================== |
65 | class Ftp : public KIO::WorkerBase |
66 | { |
67 | public: |
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 | */ |
117 | class FtpInternal : public QObject |
118 | { |
119 | Q_OBJECT |
120 | public: |
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 | |
389 | private: // 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 | |