File indexing completed on 2024-05-12 03:56:45
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2000-2006 David Faure <faure@kde.org> 0004 SPDX-FileCopyrightText: 2019-2021 Harald Sitter <sitter@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 /* 0010 Recommended reading explaining FTP details and quirks: 0011 https://cr.yp.to/ftp.html (by D.J. Bernstein) 0012 0013 RFC: 0014 RFC 959 "File Transfer Protocol (FTP)" 0015 RFC 1635 "How to Use Anonymous FTP" 0016 RFC 2428 "FTP Extensions for IPv6 and NATs" (defines EPRT and EPSV) 0017 */ 0018 0019 #include <config-kioworker-ftp.h> 0020 0021 #include "ftp.h" 0022 0023 #ifdef Q_OS_WIN 0024 #include <sys/utime.h> 0025 #else 0026 #include <utime.h> 0027 #endif 0028 0029 #include <cctype> 0030 #include <cerrno> 0031 #include <cstdlib> 0032 #include <cstring> 0033 0034 #include <QAuthenticator> 0035 #include <QCoreApplication> 0036 #include <QDir> 0037 #include <QHostAddress> 0038 #include <QMimeDatabase> 0039 #include <QNetworkProxy> 0040 #include <QSslSocket> 0041 #include <QTcpServer> 0042 #include <QTcpSocket> 0043 0044 #include <KConfigGroup> 0045 #include <KLocalizedString> 0046 #include <QDebug> 0047 #include <authinfo.h> 0048 #include <ioworker_defaults.h> 0049 #include <kremoteencoding.h> 0050 0051 #include "kioglobal_p.h" 0052 0053 #include <QLoggingCategory> 0054 Q_DECLARE_LOGGING_CATEGORY(KIO_FTP) 0055 Q_LOGGING_CATEGORY(KIO_FTP, "kf.kio.workers.ftp", QtWarningMsg) 0056 0057 #if HAVE_STRTOLL 0058 #define charToLongLong(a) strtoll(a, nullptr, 10) 0059 #else 0060 #define charToLongLong(a) strtol(a, nullptr, 10) 0061 #endif 0062 0063 static constexpr char s_ftpLogin[] = "anonymous"; 0064 static constexpr char s_ftpPasswd[] = "anonymous@"; 0065 0066 static constexpr bool s_enableCanResume = true; 0067 0068 // Pseudo plugin class to embed meta data 0069 class KIOPluginForMetaData : public QObject 0070 { 0071 Q_OBJECT 0072 Q_PLUGIN_METADATA(IID "org.kde.kio.worker.ftp" FILE "ftp.json") 0073 }; 0074 0075 static QString ftpCleanPath(const QString &path) 0076 { 0077 if (path.endsWith(QLatin1String(";type=A"), Qt::CaseInsensitive) || path.endsWith(QLatin1String(";type=I"), Qt::CaseInsensitive) 0078 || path.endsWith(QLatin1String(";type=D"), Qt::CaseInsensitive)) { 0079 return path.left((path.length() - qstrlen(";type=X"))); 0080 } 0081 0082 return path; 0083 } 0084 0085 static char ftpModeFromPath(const QString &path, char defaultMode = '\0') 0086 { 0087 const int index = path.lastIndexOf(QLatin1String(";type=")); 0088 0089 if (index > -1 && (index + 6) < path.size()) { 0090 const QChar mode = path.at(index + 6); 0091 // kio_ftp supports only A (ASCII) and I(BINARY) modes. 0092 if (mode == QLatin1Char('A') || mode == QLatin1Char('a') || mode == QLatin1Char('I') || mode == QLatin1Char('i')) { 0093 return mode.toUpper().toLatin1(); 0094 } 0095 } 0096 0097 return defaultMode; 0098 } 0099 0100 static bool supportedProxyScheme(const QString &scheme) 0101 { 0102 return (scheme == QLatin1String("ftp") || scheme == QLatin1String("socks")); 0103 } 0104 0105 // JPF: somebody should find a better solution for this or move this to KIO 0106 namespace KIO 0107 { 0108 enum buffersizes { 0109 /** 0110 * largest buffer size that should be used to transfer data between 0111 * KIO workers using the data() function 0112 */ 0113 maximumIpcSize = 32 * 1024, 0114 /** 0115 * this is a reasonable value for an initial read() that a KIO worker 0116 * can do to obtain data via a slow network connection. 0117 */ 0118 initialIpcSize = 2 * 1024, 0119 /** 0120 * recommended size of a data block passed to findBufferFileType() 0121 */ 0122 minimumMimeSize = 1024, 0123 }; 0124 0125 // JPF: this helper was derived from write_all in file.cc (FileProtocol). 0126 static // JPF: in ftp.cc we make it static 0127 /** 0128 * This helper handles some special issues (blocking and interrupted 0129 * system call) when writing to a file handle. 0130 * 0131 * @return 0 on success or an error code on failure (ERR_CANNOT_WRITE, 0132 * ERR_DISK_FULL, ERR_CONNECTION_BROKEN). 0133 */ 0134 int 0135 WriteToFile(int fd, const char *buf, size_t len) 0136 { 0137 while (len > 0) { 0138 // JPF: shouldn't there be a KDE_write? 0139 ssize_t written = write(fd, buf, len); 0140 if (written >= 0) { 0141 buf += written; 0142 len -= written; 0143 continue; 0144 } 0145 switch (errno) { 0146 case EINTR: 0147 continue; 0148 case EPIPE: 0149 return ERR_CONNECTION_BROKEN; 0150 case ENOSPC: 0151 return ERR_DISK_FULL; 0152 default: 0153 return ERR_CANNOT_WRITE; 0154 } 0155 } 0156 return 0; 0157 } 0158 } 0159 0160 const KIO::filesize_t FtpInternal::UnknownSize = (KIO::filesize_t)-1; 0161 0162 using namespace KIO; 0163 0164 extern "C" Q_DECL_EXPORT int kdemain(int argc, char **argv) 0165 { 0166 QCoreApplication app(argc, argv); 0167 app.setApplicationName(QStringLiteral("kio_ftp")); 0168 0169 qCDebug(KIO_FTP) << "Starting"; 0170 0171 if (argc != 4) { 0172 fprintf(stderr, "Usage: kio_ftp protocol domain-socket1 domain-socket2\n"); 0173 exit(-1); 0174 } 0175 0176 Ftp worker(argv[2], argv[3]); 0177 worker.dispatchLoop(); 0178 0179 qCDebug(KIO_FTP) << "Done"; 0180 return 0; 0181 } 0182 0183 //=============================================================================== 0184 // FtpInternal 0185 //=============================================================================== 0186 0187 /** 0188 * This closes a data connection opened by ftpOpenDataConnection(). 0189 */ 0190 void FtpInternal::ftpCloseDataConnection() 0191 { 0192 delete m_data; 0193 m_data = nullptr; 0194 delete m_server; 0195 m_server = nullptr; 0196 } 0197 0198 /** 0199 * This closes a control connection opened by ftpOpenControlConnection() and reinits the 0200 * related states. This method gets called from the constructor with m_control = nullptr. 0201 */ 0202 void FtpInternal::ftpCloseControlConnection() 0203 { 0204 m_extControl = 0; 0205 delete m_control; 0206 m_control = nullptr; 0207 m_cDataMode = 0; 0208 m_bLoggedOn = false; // logon needs control connection 0209 m_bTextMode = false; 0210 m_bBusy = false; 0211 } 0212 0213 /** 0214 * Returns the last response from the server (iOffset >= 0) -or- reads a new response 0215 * (iOffset < 0). The result is returned (with iOffset chars skipped for iOffset > 0). 0216 */ 0217 const char *FtpInternal::ftpResponse(int iOffset) 0218 { 0219 Q_ASSERT(m_control); // must have control connection socket 0220 const char *pTxt = m_lastControlLine.data(); 0221 0222 // read the next line ... 0223 if (iOffset < 0) { 0224 int iMore = 0; 0225 m_iRespCode = 0; 0226 0227 if (!pTxt) { 0228 return nullptr; // avoid using a nullptr when calling atoi. 0229 } 0230 0231 // If the server sends a multiline response starting with 0232 // "nnn-text" we loop here until a final "nnn text" line is 0233 // reached. Only data from the final line will be stored. 0234 do { 0235 while (!m_control->canReadLine() && m_control->waitForReadyRead((q->readTimeout() * 1000))) { } 0236 m_lastControlLine = m_control->readLine(); 0237 pTxt = m_lastControlLine.data(); 0238 int iCode = atoi(pTxt); 0239 if (iMore == 0) { 0240 // first line 0241 qCDebug(KIO_FTP) << " > " << pTxt; 0242 if (iCode >= 100) { 0243 m_iRespCode = iCode; 0244 if (pTxt[3] == '-') { 0245 // marker for a multiple line response 0246 iMore = iCode; 0247 } 0248 } else { 0249 qCWarning(KIO_FTP) << "Cannot parse valid code from line" << pTxt; 0250 } 0251 } else { 0252 // multi-line 0253 qCDebug(KIO_FTP) << " > " << pTxt; 0254 if (iCode >= 100 && iCode == iMore && pTxt[3] == ' ') { 0255 iMore = 0; 0256 } 0257 } 0258 } while (iMore != 0); 0259 qCDebug(KIO_FTP) << "resp> " << pTxt; 0260 0261 m_iRespType = (m_iRespCode > 0) ? m_iRespCode / 100 : 0; 0262 } 0263 0264 // return text with offset ... 0265 while (iOffset-- > 0 && pTxt[0]) { 0266 pTxt++; 0267 } 0268 return pTxt; 0269 } 0270 0271 void FtpInternal::closeConnection() 0272 { 0273 if (m_control || m_data) { 0274 qCDebug(KIO_FTP) << "m_bLoggedOn=" << m_bLoggedOn << " m_bBusy=" << m_bBusy; 0275 } 0276 0277 if (m_bBusy) { // ftpCloseCommand not called 0278 qCWarning(KIO_FTP) << "Abandoned data stream"; 0279 ftpCloseDataConnection(); 0280 } 0281 0282 if (m_bLoggedOn) { // send quit 0283 if (!ftpSendCmd(QByteArrayLiteral("quit"), 0) || (m_iRespType != 2)) { 0284 qCWarning(KIO_FTP) << "QUIT returned error: " << m_iRespCode; 0285 } 0286 } 0287 0288 // close the data and control connections ... 0289 ftpCloseDataConnection(); 0290 ftpCloseControlConnection(); 0291 } 0292 0293 FtpInternal::FtpInternal(Ftp *qptr) 0294 : QObject() 0295 , q(qptr) 0296 { 0297 ftpCloseControlConnection(); 0298 } 0299 0300 FtpInternal::~FtpInternal() 0301 { 0302 qCDebug(KIO_FTP); 0303 closeConnection(); 0304 } 0305 0306 void FtpInternal::setHost(const QString &_host, quint16 _port, const QString &_user, const QString &_pass) 0307 { 0308 qCDebug(KIO_FTP) << _host << "port=" << _port << "user=" << _user; 0309 0310 m_proxyURL.clear(); 0311 m_proxyUrls.clear(); 0312 const auto proxies = QNetworkProxyFactory::proxyForQuery(QNetworkProxyQuery(_host, _port, QStringLiteral("ftp"), QNetworkProxyQuery::UrlRequest)); 0313 0314 for (const QNetworkProxy &proxy : proxies) { 0315 if (proxy.type() != QNetworkProxy::NoProxy) { 0316 QUrl proxyUrl; 0317 proxyUrl.setScheme(QStringLiteral("ftp")); 0318 proxyUrl.setUserName(proxy.user()); 0319 proxyUrl.setPassword(proxy.password()); 0320 proxyUrl.setHost(proxy.hostName()); 0321 proxyUrl.setPort(proxy.port()); 0322 0323 m_proxyUrls << proxyUrl.toString(); 0324 } 0325 } 0326 0327 qCDebug(KIO_FTP) << "proxy urls:" << m_proxyUrls; 0328 0329 if (m_host != _host || m_port != _port || m_user != _user || m_pass != _pass) { 0330 closeConnection(); 0331 } 0332 0333 m_host = _host; 0334 m_port = _port; 0335 m_user = _user; 0336 m_pass = _pass; 0337 } 0338 0339 Result FtpInternal::openConnection() 0340 { 0341 return ftpOpenConnection(LoginMode::Explicit); 0342 } 0343 0344 Result FtpInternal::ftpOpenConnection(LoginMode loginMode) 0345 { 0346 // check for implicit login if we are already logged on ... 0347 if (loginMode == LoginMode::Implicit && m_bLoggedOn) { 0348 Q_ASSERT(m_control); // must have control connection socket 0349 return Result::pass(); 0350 } 0351 0352 qCDebug(KIO_FTP) << "host=" << m_host << ", port=" << m_port << ", user=" << m_user << "password= [password hidden]"; 0353 0354 q->infoMessage(i18n("Opening connection to host %1", m_host)); 0355 0356 if (m_host.isEmpty()) { 0357 return Result::fail(ERR_UNKNOWN_HOST); 0358 } 0359 0360 Q_ASSERT(!m_bLoggedOn); 0361 0362 m_initialPath.clear(); 0363 m_currentPath.clear(); 0364 0365 const Result result = ftpOpenControlConnection(); 0366 if (!result.success()) { 0367 return result; 0368 } 0369 q->infoMessage(i18n("Connected to host %1", m_host)); 0370 0371 bool userNameChanged = false; 0372 if (loginMode != LoginMode::Deferred) { 0373 const Result result = ftpLogin(&userNameChanged); 0374 m_bLoggedOn = result.success(); 0375 if (!m_bLoggedOn) { 0376 return result; 0377 } 0378 } 0379 0380 m_bTextMode = q->configValue(QStringLiteral("textmode"), false); 0381 0382 // Redirected due to credential change... 0383 if (userNameChanged && m_bLoggedOn) { 0384 QUrl realURL; 0385 realURL.setScheme(QStringLiteral("ftp")); 0386 if (m_user != QLatin1String(s_ftpLogin)) { 0387 realURL.setUserName(m_user); 0388 } 0389 if (m_pass != QLatin1String(s_ftpPasswd)) { 0390 realURL.setPassword(m_pass); 0391 } 0392 realURL.setHost(m_host); 0393 if (m_port > 0 && m_port != DEFAULT_FTP_PORT) { 0394 realURL.setPort(m_port); 0395 } 0396 if (m_initialPath.isEmpty()) { 0397 m_initialPath = QStringLiteral("/"); 0398 } 0399 realURL.setPath(m_initialPath); 0400 qCDebug(KIO_FTP) << "User name changed! Redirecting to" << realURL; 0401 q->redirection(realURL); 0402 return Result::fail(); 0403 } 0404 0405 return Result::pass(); 0406 } 0407 0408 /** 0409 * Called by @ref openConnection. It opens the control connection to the ftp server. 0410 * 0411 * @return true on success. 0412 */ 0413 Result FtpInternal::ftpOpenControlConnection() 0414 { 0415 if (m_proxyUrls.isEmpty()) { 0416 return ftpOpenControlConnection(m_host, m_port); 0417 } 0418 0419 Result result = Result::fail(); 0420 0421 for (const QString &proxyUrl : std::as_const(m_proxyUrls)) { 0422 const QUrl url(proxyUrl); 0423 const QString scheme(url.scheme()); 0424 0425 if (!supportedProxyScheme(scheme)) { 0426 // TODO: Need a new error code to indicate unsupported URL scheme. 0427 result = Result::fail(ERR_CANNOT_CONNECT, url.toString()); 0428 continue; 0429 } 0430 0431 if (!isSocksProxyScheme(scheme)) { 0432 const Result result = ftpOpenControlConnection(url.host(), url.port()); 0433 if (result.success()) { 0434 return Result::pass(); 0435 } 0436 continue; 0437 } 0438 0439 qCDebug(KIO_FTP) << "Connecting to SOCKS proxy @" << url; 0440 m_proxyURL = url; 0441 result = ftpOpenControlConnection(m_host, m_port); 0442 if (result.success()) { 0443 return result; 0444 } 0445 m_proxyURL.clear(); 0446 } 0447 0448 return result; 0449 } 0450 0451 Result FtpInternal::ftpOpenControlConnection(const QString &host, int port) 0452 { 0453 // implicitly close, then try to open a new connection ... 0454 closeConnection(); 0455 QString sErrorMsg; 0456 0457 // now connect to the server and read the login message ... 0458 if (port == 0) { 0459 port = 21; // default FTP port 0460 } 0461 const auto connectionResult = synchronousConnectToHost(host, port); 0462 m_control = connectionResult.socket; 0463 0464 int iErrorCode = m_control->state() == QAbstractSocket::ConnectedState ? 0 : ERR_CANNOT_CONNECT; 0465 if (!connectionResult.result.success()) { 0466 qDebug() << "overriding error code!!1" << connectionResult.result.error(); 0467 iErrorCode = connectionResult.result.error(); 0468 sErrorMsg = connectionResult.result.errorString(); 0469 } 0470 0471 // on connect success try to read the server message... 0472 if (iErrorCode == 0) { 0473 const char *psz = ftpResponse(-1); 0474 if (m_iRespType != 2) { 0475 // login not successful, do we have an message text? 0476 if (psz[0]) { 0477 sErrorMsg = i18n("%1 (Error %2)", host, q->remoteEncoding()->decode(psz).trimmed()); 0478 } 0479 iErrorCode = ERR_CANNOT_CONNECT; 0480 } 0481 } else { 0482 const auto socketError = m_control->error(); 0483 if (socketError == QAbstractSocket::HostNotFoundError) { 0484 iErrorCode = ERR_UNKNOWN_HOST; 0485 } 0486 0487 sErrorMsg = QStringLiteral("%1: %2").arg(host, m_control->errorString()); 0488 } 0489 0490 // if there was a problem - report it ... 0491 if (iErrorCode == 0) { // OK, return success 0492 return Result::pass(); 0493 } 0494 closeConnection(); // clean-up on error 0495 return Result::fail(iErrorCode, sErrorMsg); 0496 } 0497 0498 /** 0499 * Called by @ref openConnection. It logs us in. 0500 * @ref m_initialPath is set to the current working directory 0501 * if logging on was successful. 0502 * 0503 * @return true on success. 0504 */ 0505 Result FtpInternal::ftpLogin(bool *userChanged) 0506 { 0507 q->infoMessage(i18n("Sending login information")); 0508 0509 Q_ASSERT(!m_bLoggedOn); 0510 0511 QString user(m_user); 0512 QString pass(m_pass); 0513 0514 AuthInfo info; 0515 info.url.setScheme(QStringLiteral("ftp")); 0516 info.url.setHost(m_host); 0517 if (m_port > 0 && m_port != DEFAULT_FTP_PORT) { 0518 info.url.setPort(m_port); 0519 } 0520 if (!user.isEmpty()) { 0521 info.url.setUserName(user); 0522 } 0523 0524 // Check for cached authentication first and fallback to 0525 // anonymous login when no stored credentials are found. 0526 if (!q->configValue(QStringLiteral("TryAnonymousLoginFirst"), false) && pass.isEmpty() && q->checkCachedAuthentication(info)) { 0527 user = info.username; 0528 pass = info.password; 0529 } 0530 0531 // Try anonymous login if both username/password 0532 // information is blank. 0533 if (user.isEmpty() && pass.isEmpty()) { 0534 user = QString::fromLatin1(s_ftpLogin); 0535 pass = QString::fromLatin1(s_ftpPasswd); 0536 } 0537 0538 QByteArray tempbuf; 0539 QString lastServerResponse; 0540 int failedAuth = 0; 0541 bool promptForRetry = false; 0542 0543 // Give the user the option to login anonymously... 0544 info.setExtraField(QStringLiteral("anonymous"), false); 0545 0546 do { 0547 // Check the cache and/or prompt user for password if 1st 0548 // login attempt failed OR the user supplied a login name, 0549 // but no password. 0550 if (failedAuth > 0 || (!user.isEmpty() && pass.isEmpty())) { 0551 QString errorMsg; 0552 qCDebug(KIO_FTP) << "Prompting user for login info..."; 0553 0554 // Ask user if we should retry after when login fails! 0555 if (failedAuth > 0 && promptForRetry) { 0556 errorMsg = i18n( 0557 "Message sent:\nLogin using username=%1 and " 0558 "password=[hidden]\n\nServer replied:\n%2\n\n", 0559 user, 0560 lastServerResponse); 0561 } 0562 0563 if (user != QLatin1String(s_ftpLogin)) { 0564 info.username = user; 0565 } 0566 0567 info.prompt = i18n( 0568 "You need to supply a username and a password " 0569 "to access this site."); 0570 info.commentLabel = i18n("Site:"); 0571 info.comment = i18n("<b>%1</b>", m_host); 0572 info.keepPassword = true; // Prompt the user for persistence as well. 0573 info.setModified(false); // Default the modified flag since we reuse authinfo. 0574 0575 const bool disablePassDlg = q->configValue(QStringLiteral("DisablePassDlg"), false); 0576 if (disablePassDlg) { 0577 return Result::fail(ERR_USER_CANCELED, m_host); 0578 } 0579 const int errorCode = q->openPasswordDialog(info, errorMsg); 0580 if (errorCode) { 0581 return Result::fail(errorCode); 0582 } else { 0583 // User can decide go anonymous using checkbox 0584 if (info.getExtraField(QStringLiteral("anonymous")).toBool()) { 0585 user = QString::fromLatin1(s_ftpLogin); 0586 pass = QString::fromLatin1(s_ftpPasswd); 0587 } else { 0588 user = info.username; 0589 pass = info.password; 0590 } 0591 promptForRetry = true; 0592 } 0593 } 0594 0595 tempbuf = "USER " + user.toLatin1(); 0596 if (m_proxyURL.isValid()) { 0597 tempbuf += '@' + m_host.toLatin1(); 0598 if (m_port > 0 && m_port != DEFAULT_FTP_PORT) { 0599 tempbuf += ':' + QByteArray::number(m_port); 0600 } 0601 } 0602 0603 qCDebug(KIO_FTP) << "Sending Login name: " << tempbuf; 0604 0605 bool loggedIn = (ftpSendCmd(tempbuf) && (m_iRespCode == 230)); 0606 bool needPass = (m_iRespCode == 331); 0607 // Prompt user for login info if we do not 0608 // get back a "230" or "331". 0609 if (!loggedIn && !needPass) { 0610 lastServerResponse = QString::fromUtf8(ftpResponse(0)); 0611 qCDebug(KIO_FTP) << "Login failed: " << lastServerResponse; 0612 ++failedAuth; 0613 continue; // Well we failed, prompt the user please!! 0614 } 0615 0616 if (needPass) { 0617 tempbuf = "PASS " + pass.toLatin1(); 0618 qCDebug(KIO_FTP) << "Sending Login password: " 0619 << "[protected]"; 0620 loggedIn = (ftpSendCmd(tempbuf) && (m_iRespCode == 230)); 0621 } 0622 0623 if (loggedIn) { 0624 // Make sure the user name changed flag is properly set. 0625 if (userChanged) { 0626 *userChanged = (!m_user.isEmpty() && (m_user != user)); 0627 } 0628 0629 // Do not cache the default login!! 0630 if (user != QLatin1String(s_ftpLogin) && pass != QLatin1String(s_ftpPasswd)) { 0631 // Update the username in case it was changed during login. 0632 if (!m_user.isEmpty()) { 0633 info.url.setUserName(user); 0634 m_user = user; 0635 } 0636 0637 // Cache the password if the user requested it. 0638 if (info.keepPassword) { 0639 q->cacheAuthentication(info); 0640 } 0641 } 0642 failedAuth = -1; 0643 } else { 0644 // some servers don't let you login anymore 0645 // if you fail login once, so restart the connection here 0646 lastServerResponse = QString::fromUtf8(ftpResponse(0)); 0647 const Result result = ftpOpenControlConnection(); 0648 if (!result.success()) { 0649 return result; 0650 } 0651 } 0652 } while (++failedAuth); 0653 0654 qCDebug(KIO_FTP) << "Login OK"; 0655 q->infoMessage(i18n("Login OK")); 0656 0657 // Okay, we're logged in. If this is IIS 4, switch dir listing style to Unix: 0658 // Thanks to jk@soegaard.net (Jens Kristian Sgaard) for this hint 0659 if (ftpSendCmd(QByteArrayLiteral("SYST")) && (m_iRespType == 2)) { 0660 if (!qstrncmp(ftpResponse(0), "215 Windows_NT", 14)) { // should do for any version 0661 (void)ftpSendCmd(QByteArrayLiteral("site dirstyle")); 0662 // Check if it was already in Unix style 0663 // Patch from Keith Refson <Keith.Refson@earth.ox.ac.uk> 0664 if (!qstrncmp(ftpResponse(0), "200 MSDOS-like directory output is on", 37)) 0665 // It was in Unix style already! 0666 { 0667 (void)ftpSendCmd(QByteArrayLiteral("site dirstyle")); 0668 } 0669 // windows won't support chmod before KDE konquers their desktop... 0670 m_extControl |= chmodUnknown; 0671 } 0672 } else { 0673 qCWarning(KIO_FTP) << "SYST failed"; 0674 } 0675 0676 // Get the current working directory 0677 qCDebug(KIO_FTP) << "Searching for pwd"; 0678 if (!ftpSendCmd(QByteArrayLiteral("PWD")) || (m_iRespType != 2)) { 0679 qCDebug(KIO_FTP) << "Couldn't issue pwd command"; 0680 return Result::fail(ERR_CANNOT_LOGIN, i18n("Could not login to %1.", m_host)); // or anything better ? 0681 } 0682 0683 QString sTmp = q->remoteEncoding()->decode(ftpResponse(3)); 0684 const int iBeg = sTmp.indexOf(QLatin1Char('"')); 0685 const int iEnd = sTmp.lastIndexOf(QLatin1Char('"')); 0686 if (iBeg > 0 && iBeg < iEnd) { 0687 m_initialPath = sTmp.mid(iBeg + 1, iEnd - iBeg - 1); 0688 if (!m_initialPath.startsWith(QLatin1Char('/'))) { 0689 m_initialPath.prepend(QLatin1Char('/')); 0690 } 0691 qCDebug(KIO_FTP) << "Initial path set to: " << m_initialPath; 0692 m_currentPath = m_initialPath; 0693 } 0694 0695 return Result::pass(); 0696 } 0697 0698 /** 0699 * ftpSendCmd - send a command (@p cmd) and read response 0700 * 0701 * @param maxretries number of time it should retry. Since it recursively 0702 * calls itself if it can't read the answer (this happens especially after 0703 * timeouts), we need to limit the recursiveness ;-) 0704 * 0705 * return true if any response received, false on error 0706 */ 0707 bool FtpInternal::ftpSendCmd(const QByteArray &cmd, int maxretries) 0708 { 0709 Q_ASSERT(m_control); // must have control connection socket 0710 0711 if (cmd.indexOf('\r') != -1 || cmd.indexOf('\n') != -1) { 0712 qCWarning(KIO_FTP) << "Invalid command received (contains CR or LF):" << cmd.data(); 0713 return false; 0714 } 0715 0716 // Don't print out the password... 0717 bool isPassCmd = (cmd.left(4).toLower() == "pass"); 0718 0719 // Send the message... 0720 const QByteArray buf = cmd + "\r\n"; // Yes, must use CR/LF - see https://cr.yp.to/ftp/request.html 0721 int num = m_control->write(buf); 0722 while (m_control->bytesToWrite() && m_control->waitForBytesWritten()) { } 0723 0724 // If we were able to successfully send the command, then we will 0725 // attempt to read the response. Otherwise, take action to re-attempt 0726 // the login based on the maximum number of retries specified... 0727 if (num > 0) { 0728 ftpResponse(-1); 0729 } else { 0730 m_iRespType = m_iRespCode = 0; 0731 } 0732 0733 // If respCh is NULL or the response is 421 (Timed-out), we try to re-send 0734 // the command based on the value of maxretries. 0735 if ((m_iRespType <= 0) || (m_iRespCode == 421)) { 0736 // We have not yet logged on... 0737 if (!m_bLoggedOn) { 0738 // The command was sent from the ftpLogin function, i.e. we are actually 0739 // attempting to login in. NOTE: If we already sent the username, we 0740 // return false and let the user decide whether (s)he wants to start from 0741 // the beginning... 0742 if (maxretries > 0 && !isPassCmd) { 0743 closeConnection(); 0744 const auto result = ftpOpenConnection(LoginMode::Deferred); 0745 if (result.success() && ftpSendCmd(cmd, maxretries - 1)) { 0746 return true; 0747 } 0748 } 0749 0750 return false; 0751 } else { 0752 if (maxretries < 1) { 0753 return false; 0754 } else { 0755 qCDebug(KIO_FTP) << "Was not able to communicate with " << m_host << "Attempting to re-establish connection."; 0756 0757 closeConnection(); // Close the old connection... 0758 const Result openResult = openConnection(); // Attempt to re-establish a new connection... 0759 0760 if (!openResult.success()) { 0761 if (m_control) { // if openConnection succeeded ... 0762 qCDebug(KIO_FTP) << "Login failure, aborting"; 0763 closeConnection(); 0764 } 0765 return false; 0766 } 0767 0768 qCDebug(KIO_FTP) << "Logged back in, re-issuing command"; 0769 0770 // If we were able to login, resend the command... 0771 if (maxretries) { 0772 maxretries--; 0773 } 0774 0775 return ftpSendCmd(cmd, maxretries); 0776 } 0777 } 0778 } 0779 0780 return true; 0781 } 0782 0783 /* 0784 * ftpOpenPASVDataConnection - set up data connection, using PASV mode 0785 * 0786 * return 0 if successful, ERR_INTERNAL otherwise 0787 * doesn't set error message, since non-pasv mode will always be tried if 0788 * this one fails 0789 */ 0790 int FtpInternal::ftpOpenPASVDataConnection() 0791 { 0792 Q_ASSERT(m_control); // must have control connection socket 0793 Q_ASSERT(!m_data); // ... but no data connection 0794 0795 // Check that we can do PASV 0796 QHostAddress address = m_control->peerAddress(); 0797 if (address.protocol() != QAbstractSocket::IPv4Protocol && !isSocksProxy()) { 0798 return ERR_INTERNAL; // no PASV for non-PF_INET connections 0799 } 0800 0801 if (m_extControl & pasvUnknown) { 0802 return ERR_INTERNAL; // already tried and got "unknown command" 0803 } 0804 0805 m_bPasv = true; 0806 0807 /* Let's PASsiVe*/ 0808 if (!ftpSendCmd(QByteArrayLiteral("PASV")) || (m_iRespType != 2)) { 0809 qCDebug(KIO_FTP) << "PASV attempt failed"; 0810 // unknown command? 0811 if (m_iRespType == 5) { 0812 qCDebug(KIO_FTP) << "disabling use of PASV"; 0813 m_extControl |= pasvUnknown; 0814 } 0815 return ERR_INTERNAL; 0816 } 0817 0818 // The usual answer is '227 Entering Passive Mode. (160,39,200,55,6,245)' 0819 // but anonftpd gives '227 =160,39,200,55,6,245' 0820 int i[6]; 0821 const char *start = strchr(ftpResponse(3), '('); 0822 if (!start) { 0823 start = strchr(ftpResponse(3), '='); 0824 } 0825 if (!start 0826 || (sscanf(start, "(%d,%d,%d,%d,%d,%d)", &i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6 0827 && sscanf(start, "=%d,%d,%d,%d,%d,%d", &i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6)) { 0828 qCritical() << "parsing IP and port numbers failed. String parsed: " << start; 0829 return ERR_INTERNAL; 0830 } 0831 0832 // we ignore the host part on purpose for two reasons 0833 // a) it might be wrong anyway 0834 // b) it would make us being susceptible to a port scanning attack 0835 0836 // now connect the data socket ... 0837 quint16 port = i[4] << 8 | i[5]; 0838 const QString host = (isSocksProxy() ? m_host : address.toString()); 0839 const auto connectionResult = synchronousConnectToHost(host, port); 0840 m_data = connectionResult.socket; 0841 if (!connectionResult.result.success()) { 0842 return connectionResult.result.error(); 0843 } 0844 0845 return m_data->state() == QAbstractSocket::ConnectedState ? 0 : ERR_INTERNAL; 0846 } 0847 0848 /* 0849 * ftpOpenEPSVDataConnection - opens a data connection via EPSV 0850 */ 0851 int FtpInternal::ftpOpenEPSVDataConnection() 0852 { 0853 Q_ASSERT(m_control); // must have control connection socket 0854 Q_ASSERT(!m_data); // ... but no data connection 0855 0856 QHostAddress address = m_control->peerAddress(); 0857 int portnum; 0858 0859 if (m_extControl & epsvUnknown) { 0860 return ERR_INTERNAL; 0861 } 0862 0863 m_bPasv = true; 0864 if (!ftpSendCmd(QByteArrayLiteral("EPSV")) || (m_iRespType != 2)) { 0865 // unknown command? 0866 if (m_iRespType == 5) { 0867 qCDebug(KIO_FTP) << "disabling use of EPSV"; 0868 m_extControl |= epsvUnknown; 0869 } 0870 return ERR_INTERNAL; 0871 } 0872 0873 const char *start = strchr(ftpResponse(3), '|'); 0874 if (!start || sscanf(start, "|||%d|", &portnum) != 1) { 0875 return ERR_INTERNAL; 0876 } 0877 Q_ASSERT(portnum > 0); 0878 0879 const QString host = (isSocksProxy() ? m_host : address.toString()); 0880 const auto connectionResult = synchronousConnectToHost(host, static_cast<quint16>(portnum)); 0881 m_data = connectionResult.socket; 0882 if (!connectionResult.result.success()) { 0883 return connectionResult.result.error(); 0884 } 0885 return m_data->state() == QAbstractSocket::ConnectedState ? 0 : ERR_INTERNAL; 0886 } 0887 0888 /* 0889 * ftpOpenDataConnection - set up data connection 0890 * 0891 * The routine calls several ftpOpenXxxxConnection() helpers to find 0892 * the best connection mode. If a helper cannot connect if returns 0893 * ERR_INTERNAL - so this is not really an error! All other error 0894 * codes are treated as fatal, e.g. they are passed back to the caller 0895 * who is responsible for calling error(). ftpOpenPortDataConnection 0896 * can be called as last try and it does never return ERR_INTERNAL. 0897 * 0898 * @return 0 if successful, err code otherwise 0899 */ 0900 int FtpInternal::ftpOpenDataConnection() 0901 { 0902 // make sure that we are logged on and have no data connection... 0903 Q_ASSERT(m_bLoggedOn); 0904 ftpCloseDataConnection(); 0905 0906 int iErrCode = 0; 0907 int iErrCodePASV = 0; // Remember error code from PASV 0908 0909 // First try passive (EPSV & PASV) modes 0910 if (!q->configValue(QStringLiteral("DisablePassiveMode"), false)) { 0911 iErrCode = ftpOpenPASVDataConnection(); 0912 if (iErrCode == 0) { 0913 return 0; // success 0914 } 0915 iErrCodePASV = iErrCode; 0916 ftpCloseDataConnection(); 0917 0918 if (!q->configValue(QStringLiteral("DisableEPSV"), false)) { 0919 iErrCode = ftpOpenEPSVDataConnection(); 0920 if (iErrCode == 0) { 0921 return 0; // success 0922 } 0923 ftpCloseDataConnection(); 0924 } 0925 0926 // if we sent EPSV ALL already and it was accepted, then we can't 0927 // use active connections any more 0928 if (m_extControl & epsvAllSent) { 0929 return iErrCodePASV; 0930 } 0931 } 0932 0933 // fall back to port mode 0934 iErrCode = ftpOpenPortDataConnection(); 0935 if (iErrCode == 0) { 0936 return 0; // success 0937 } 0938 0939 ftpCloseDataConnection(); 0940 // prefer to return the error code from PASV if any, since that's what should have worked in the first place 0941 return iErrCodePASV ? iErrCodePASV : iErrCode; 0942 } 0943 0944 /* 0945 * ftpOpenPortDataConnection - set up data connection 0946 * 0947 * @return 0 if successful, err code otherwise (but never ERR_INTERNAL 0948 * because this is the last connection mode that is tried) 0949 */ 0950 int FtpInternal::ftpOpenPortDataConnection() 0951 { 0952 Q_ASSERT(m_control); // must have control connection socket 0953 Q_ASSERT(!m_data); // ... but no data connection 0954 0955 m_bPasv = false; 0956 if (m_extControl & eprtUnknown) { 0957 return ERR_INTERNAL; 0958 } 0959 0960 if (!m_server) { 0961 m_server = new QTcpServer; 0962 m_server->listen(QHostAddress::Any, 0); 0963 } 0964 0965 if (!m_server->isListening()) { 0966 delete m_server; 0967 m_server = nullptr; 0968 return ERR_CANNOT_LISTEN; 0969 } 0970 0971 m_server->setMaxPendingConnections(1); 0972 0973 QString command; 0974 QHostAddress localAddress = m_control->localAddress(); 0975 if (localAddress.protocol() == QAbstractSocket::IPv4Protocol) { 0976 struct { 0977 quint32 ip4; 0978 quint16 port; 0979 } data; 0980 data.ip4 = localAddress.toIPv4Address(); 0981 data.port = m_server->serverPort(); 0982 0983 unsigned char *pData = reinterpret_cast<unsigned char *>(&data); 0984 command = QStringLiteral("PORT %1,%2,%3,%4,%5,%6").arg(pData[3]).arg(pData[2]).arg(pData[1]).arg(pData[0]).arg(pData[5]).arg(pData[4]); 0985 } else if (localAddress.protocol() == QAbstractSocket::IPv6Protocol) { 0986 command = QStringLiteral("EPRT |2|%2|%3|").arg(localAddress.toString()).arg(m_server->serverPort()); 0987 } 0988 0989 if (ftpSendCmd(command.toLatin1()) && (m_iRespType == 2)) { 0990 return 0; 0991 } 0992 0993 delete m_server; 0994 m_server = nullptr; 0995 return ERR_INTERNAL; 0996 } 0997 0998 Result FtpInternal::ftpOpenCommand(const char *_command, const QString &_path, char _mode, int errorcode, KIO::fileoffset_t _offset) 0999 { 1000 if (!ftpDataMode(ftpModeFromPath(_path, _mode))) { 1001 return Result::fail(ERR_CANNOT_CONNECT, m_host); 1002 } 1003 1004 if (int error = ftpOpenDataConnection()) { 1005 return Result::fail(error, m_host); 1006 } 1007 1008 if (_offset > 0) { 1009 // send rest command if offset > 0, this applies to retr and stor commands 1010 char buf[100]; 1011 sprintf(buf, "rest %lld", _offset); 1012 if (!ftpSendCmd(buf)) { 1013 return Result::fail(); 1014 } 1015 if (m_iRespType != 3) { 1016 return Result::fail(ERR_CANNOT_RESUME, _path); // should never happen 1017 } 1018 } 1019 1020 QByteArray tmp = _command; 1021 QString errormessage; 1022 1023 if (!_path.isEmpty()) { 1024 tmp += ' ' + q->remoteEncoding()->encode(ftpCleanPath(_path)); 1025 } 1026 1027 if (!ftpSendCmd(tmp) || (m_iRespType != 1)) { 1028 if (_offset > 0 && qstrcmp(_command, "retr") == 0 && (m_iRespType == 4)) { 1029 errorcode = ERR_CANNOT_RESUME; 1030 } 1031 // The error code here depends on the command 1032 errormessage = _path + i18n("\nThe server said: \"%1\"", QString::fromUtf8(ftpResponse(0)).trimmed()); 1033 } 1034 1035 else { 1036 // Only now we know for sure that we can resume 1037 if (_offset > 0 && qstrcmp(_command, "retr") == 0) { 1038 q->canResume(); 1039 } 1040 1041 if (m_server && !m_data) { 1042 qCDebug(KIO_FTP) << "waiting for connection from remote."; 1043 m_server->waitForNewConnection(q->connectTimeout() * 1000); 1044 m_data = m_server->nextPendingConnection(); 1045 } 1046 1047 if (m_data) { 1048 qCDebug(KIO_FTP) << "connected with remote."; 1049 m_bBusy = true; // cleared in ftpCloseCommand 1050 return Result::pass(); 1051 } 1052 1053 qCDebug(KIO_FTP) << "no connection received from remote."; 1054 errorcode = ERR_CANNOT_ACCEPT; 1055 errormessage = m_host; 1056 } 1057 1058 if (errorcode != KJob::NoError) { 1059 return Result::fail(errorcode, errormessage); 1060 } 1061 return Result::fail(); 1062 } 1063 1064 bool FtpInternal::ftpCloseCommand() 1065 { 1066 // first close data sockets (if opened), then read response that 1067 // we got for whatever was used in ftpOpenCommand ( should be 226 ) 1068 ftpCloseDataConnection(); 1069 1070 if (!m_bBusy) { 1071 return true; 1072 } 1073 1074 qCDebug(KIO_FTP) << "ftpCloseCommand: reading command result"; 1075 m_bBusy = false; 1076 1077 if (!ftpResponse(-1) || (m_iRespType != 2)) { 1078 qCDebug(KIO_FTP) << "ftpCloseCommand: no transfer complete message"; 1079 return false; 1080 } 1081 return true; 1082 } 1083 1084 Result FtpInternal::mkdir(const QUrl &url, int permissions) 1085 { 1086 auto result = ftpOpenConnection(LoginMode::Implicit); 1087 if (!result.success()) { 1088 return result; 1089 } 1090 1091 const QByteArray encodedPath(q->remoteEncoding()->encode(url)); 1092 const QString path = QString::fromLatin1(encodedPath.constData(), encodedPath.size()); 1093 1094 if (!ftpSendCmd((QByteArrayLiteral("mkd ") + encodedPath)) || (m_iRespType != 2)) { 1095 QString currentPath(m_currentPath); 1096 1097 // Check whether or not mkdir failed because 1098 // the directory already exists... 1099 if (ftpFolder(path)) { 1100 const QString &failedPath = path; 1101 // Change the directory back to what it was... 1102 (void)ftpFolder(currentPath); 1103 return Result::fail(ERR_DIR_ALREADY_EXIST, failedPath); 1104 } 1105 1106 return Result::fail(ERR_CANNOT_MKDIR, path); 1107 } 1108 1109 if (permissions != -1) { 1110 // chmod the dir we just created, ignoring errors. 1111 (void)ftpChmod(path, permissions); 1112 } 1113 1114 return Result::pass(); 1115 } 1116 1117 Result FtpInternal::rename(const QUrl &src, const QUrl &dst, KIO::JobFlags flags) 1118 { 1119 const auto result = ftpOpenConnection(LoginMode::Implicit); 1120 if (!result.success()) { 1121 return result; 1122 } 1123 1124 // The actual functionality is in ftpRename because put needs it 1125 return ftpRename(src.path(), dst.path(), flags); 1126 } 1127 1128 Result FtpInternal::ftpRename(const QString &src, const QString &dst, KIO::JobFlags jobFlags) 1129 { 1130 Q_ASSERT(m_bLoggedOn); 1131 1132 // Must check if dst already exists, RNFR+RNTO overwrites by default (#127793). 1133 if (!(jobFlags & KIO::Overwrite)) { 1134 if (ftpFileExists(dst)) { 1135 return Result::fail(ERR_FILE_ALREADY_EXIST, dst); 1136 } 1137 } 1138 1139 if (ftpFolder(dst)) { 1140 return Result::fail(ERR_DIR_ALREADY_EXIST, dst); 1141 } 1142 1143 // CD into parent folder 1144 const int pos = src.lastIndexOf(QLatin1Char('/')); 1145 if (pos >= 0) { 1146 if (!ftpFolder(src.left(pos + 1))) { 1147 return Result::fail(ERR_CANNOT_ENTER_DIRECTORY, src); 1148 } 1149 } 1150 1151 const QByteArray from_cmd = "RNFR " + q->remoteEncoding()->encode(src.mid(pos + 1)); 1152 if (!ftpSendCmd(from_cmd) || (m_iRespType != 3)) { 1153 return Result::fail(ERR_CANNOT_RENAME, src); 1154 } 1155 1156 const QByteArray to_cmd = "RNTO " + q->remoteEncoding()->encode(dst); 1157 if (!ftpSendCmd(to_cmd) || (m_iRespType != 2)) { 1158 return Result::fail(ERR_CANNOT_RENAME, src); 1159 } 1160 1161 return Result::pass(); 1162 } 1163 1164 Result FtpInternal::del(const QUrl &url, bool isfile) 1165 { 1166 auto result = ftpOpenConnection(LoginMode::Implicit); 1167 if (!result.success()) { 1168 return result; 1169 } 1170 1171 // When deleting a directory, we must exit from it first 1172 // The last command probably went into it (to stat it) 1173 if (!isfile) { 1174 (void)ftpFolder(q->remoteEncoding()->decode(q->remoteEncoding()->directory(url))); // ignore errors 1175 } 1176 1177 const QByteArray cmd = (isfile ? "DELE " : "RMD ") + q->remoteEncoding()->encode(url); 1178 1179 if (!ftpSendCmd(cmd) || (m_iRespType != 2)) { 1180 return Result::fail(ERR_CANNOT_DELETE, url.path()); 1181 } 1182 1183 return Result::pass(); 1184 } 1185 1186 bool FtpInternal::ftpChmod(const QString &path, int permissions) 1187 { 1188 Q_ASSERT(m_bLoggedOn); 1189 1190 if (m_extControl & chmodUnknown) { // previous errors? 1191 return false; 1192 } 1193 1194 // we need to do bit AND 777 to get permissions, in case 1195 // we were sent a full mode (unlikely) 1196 const QByteArray cmd = "SITE CHMOD " + QByteArray::number(permissions & 0777 /*octal*/, 8 /*octal*/) + ' ' + q->remoteEncoding()->encode(path); 1197 1198 if (ftpSendCmd(cmd)) { 1199 qCDebug(KIO_FTP) << "ftpChmod: Failed to issue chmod"; 1200 return false; 1201 } 1202 1203 if (m_iRespType == 2) { 1204 return true; 1205 } 1206 1207 if (m_iRespCode == 500) { 1208 m_extControl |= chmodUnknown; 1209 qCDebug(KIO_FTP) << "ftpChmod: CHMOD not supported - disabling"; 1210 } 1211 return false; 1212 } 1213 1214 Result FtpInternal::chmod(const QUrl &url, int permissions) 1215 { 1216 const auto result = ftpOpenConnection(LoginMode::Implicit); 1217 if (!result.success()) { 1218 return result; 1219 } 1220 1221 if (!ftpChmod(url.path(), permissions)) { 1222 return Result::fail(ERR_CANNOT_CHMOD, url.path()); 1223 } 1224 1225 return Result::pass(); 1226 } 1227 1228 void FtpInternal::ftpCreateUDSEntry(const QString &filename, const FtpEntry &ftpEnt, UDSEntry &entry, bool isDir) 1229 { 1230 Q_ASSERT(entry.count() == 0); // by contract :-) 1231 1232 entry.reserve(9); 1233 entry.fastInsert(KIO::UDSEntry::UDS_NAME, filename); 1234 entry.fastInsert(KIO::UDSEntry::UDS_SIZE, ftpEnt.size); 1235 entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, ftpEnt.date.toSecsSinceEpoch()); 1236 entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, ftpEnt.access); 1237 entry.fastInsert(KIO::UDSEntry::UDS_USER, ftpEnt.owner); 1238 if (!ftpEnt.group.isEmpty()) { 1239 entry.fastInsert(KIO::UDSEntry::UDS_GROUP, ftpEnt.group); 1240 } 1241 1242 if (!ftpEnt.link.isEmpty()) { 1243 entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, ftpEnt.link); 1244 1245 QMimeDatabase db; 1246 QMimeType mime = db.mimeTypeForUrl(QUrl(QLatin1String("ftp://host/") + filename)); 1247 // Links on ftp sites are often links to dirs, and we have no way to check 1248 // that. Let's do like Netscape : assume dirs generally. 1249 // But we do this only when the MIME type can't be known from the filename. 1250 // --> we do better than Netscape :-) 1251 if (mime.isDefault()) { 1252 qCDebug(KIO_FTP) << "Setting guessed MIME type to inode/directory for " << filename; 1253 entry.fastInsert(KIO::UDSEntry::UDS_GUESSED_MIME_TYPE, QStringLiteral("inode/directory")); 1254 isDir = true; 1255 } 1256 } 1257 1258 entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, isDir ? S_IFDIR : ftpEnt.type); 1259 // entry.insert KIO::UDSEntry::UDS_ACCESS_TIME,buff.st_atime); 1260 // entry.insert KIO::UDSEntry::UDS_CREATION_TIME,buff.st_ctime); 1261 } 1262 1263 void FtpInternal::ftpShortStatAnswer(const QString &filename, bool isDir) 1264 { 1265 UDSEntry entry; 1266 1267 entry.reserve(4); 1268 entry.fastInsert(KIO::UDSEntry::UDS_NAME, filename); 1269 entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, isDir ? S_IFDIR : S_IFREG); 1270 entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); 1271 if (isDir) { 1272 entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory")); 1273 } 1274 // No details about size, ownership, group, etc. 1275 1276 q->statEntry(entry); 1277 } 1278 1279 Result FtpInternal::ftpStatAnswerNotFound(const QString &path, const QString &filename) 1280 { 1281 // Only do the 'hack' below if we want to download an existing file (i.e. when looking at the "source") 1282 // When e.g. uploading a file, we still need stat() to return "not found" 1283 // when the file doesn't exist. 1284 QString statSide = q->metaData(QStringLiteral("statSide")); 1285 qCDebug(KIO_FTP) << "statSide=" << statSide; 1286 if (statSide == QLatin1String("source")) { 1287 qCDebug(KIO_FTP) << "Not found, but assuming found, because some servers don't allow listing"; 1288 // MS Server is incapable of handling "list <blah>" in a case insensitive way 1289 // But "retr <blah>" works. So lie in stat(), to get going... 1290 // 1291 // There's also the case of ftp://ftp2.3ddownloads.com/90380/linuxgames/loki/patches/ut/ut-patch-436.run 1292 // where listing permissions are denied, but downloading is still possible. 1293 ftpShortStatAnswer(filename, false /*file, not dir*/); 1294 1295 return Result::pass(); 1296 } 1297 1298 return Result::fail(ERR_DOES_NOT_EXIST, path); 1299 } 1300 1301 Result FtpInternal::stat(const QUrl &url) 1302 { 1303 qCDebug(KIO_FTP) << "path=" << url.path(); 1304 auto result = ftpOpenConnection(LoginMode::Implicit); 1305 if (!result.success()) { 1306 return result; 1307 } 1308 1309 const QString path = ftpCleanPath(QDir::cleanPath(url.path())); 1310 qCDebug(KIO_FTP) << "cleaned path=" << path; 1311 1312 // We can't stat root, but we know it's a dir. 1313 if (path.isEmpty() || path == QLatin1String("/")) { 1314 UDSEntry entry; 1315 entry.reserve(6); 1316 // entry.insert( KIO::UDSEntry::UDS_NAME, UDSField( QString() ) ); 1317 entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral(".")); 1318 entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); 1319 entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory")); 1320 entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); 1321 entry.fastInsert(KIO::UDSEntry::UDS_USER, QStringLiteral("root")); 1322 entry.fastInsert(KIO::UDSEntry::UDS_GROUP, QStringLiteral("root")); 1323 // no size 1324 1325 q->statEntry(entry); 1326 return Result::pass(); 1327 } 1328 1329 QUrl tempurl(url); 1330 tempurl.setPath(path); // take the clean one 1331 QString listarg; // = tempurl.directory(QUrl::ObeyTrailingSlash); 1332 QString parentDir; 1333 const QString filename = tempurl.fileName(); 1334 Q_ASSERT(!filename.isEmpty()); 1335 1336 // Try cwd into it, if it works it's a dir (and then we'll list the parent directory to get more info) 1337 // if it doesn't work, it's a file (and then we'll use dir filename) 1338 bool isDir = ftpFolder(path); 1339 1340 // if we're only interested in "file or directory", we should stop here 1341 QString sDetails = q->metaData(QStringLiteral("details")); 1342 int details = sDetails.isEmpty() ? 2 : sDetails.toInt(); 1343 qCDebug(KIO_FTP) << "details=" << details; 1344 if (details == 0) { 1345 if (!isDir && !ftpFileExists(path)) { // ok, not a dir -> is it a file ? 1346 // no -> it doesn't exist at all 1347 return ftpStatAnswerNotFound(path, filename); 1348 } 1349 ftpShortStatAnswer(filename, isDir); 1350 return Result::pass(); // successfully found a dir or a file -> done 1351 } 1352 1353 if (!isDir) { 1354 // It is a file or it doesn't exist, try going to parent directory 1355 parentDir = tempurl.adjusted(QUrl::RemoveFilename).path(); 1356 // With files we can do "LIST <filename>" to avoid listing the whole dir 1357 listarg = filename; 1358 } else { 1359 // --- New implementation: 1360 // Don't list the parent dir. Too slow, might not show it, etc. 1361 // Just return that it's a dir. 1362 UDSEntry entry; 1363 entry.fastInsert(KIO::UDSEntry::UDS_NAME, filename); 1364 entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); 1365 entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); 1366 // No clue about size, ownership, group, etc. 1367 1368 q->statEntry(entry); 1369 return Result::pass(); 1370 } 1371 1372 // Now cwd the parent dir, to prepare for listing 1373 if (!ftpFolder(parentDir)) { 1374 return Result::fail(ERR_CANNOT_ENTER_DIRECTORY, parentDir); 1375 } 1376 1377 result = ftpOpenCommand("list", listarg, 'I', ERR_DOES_NOT_EXIST); 1378 if (!result.success()) { 1379 qCritical() << "COULD NOT LIST"; 1380 return result; 1381 } 1382 qCDebug(KIO_FTP) << "Starting of list was ok"; 1383 1384 Q_ASSERT(!filename.isEmpty() && filename != QLatin1String("/")); 1385 1386 bool bFound = false; 1387 QUrl linkURL; 1388 FtpEntry ftpEnt; 1389 QList<FtpEntry> ftpValidateEntList; 1390 while (ftpReadDir(ftpEnt)) { 1391 if (!ftpEnt.name.isEmpty() && ftpEnt.name.at(0).isSpace()) { 1392 ftpValidateEntList.append(ftpEnt); 1393 continue; 1394 } 1395 1396 // We look for search or filename, since some servers (e.g. ftp.tuwien.ac.at) 1397 // return only the filename when doing "dir /full/path/to/file" 1398 if (!bFound) { 1399 bFound = maybeEmitStatEntry(ftpEnt, filename, isDir); 1400 } 1401 qCDebug(KIO_FTP) << ftpEnt.name; 1402 } 1403 1404 for (int i = 0, count = ftpValidateEntList.count(); i < count; ++i) { 1405 FtpEntry &ftpEnt = ftpValidateEntList[i]; 1406 fixupEntryName(&ftpEnt); 1407 if (maybeEmitStatEntry(ftpEnt, filename, isDir)) { 1408 break; 1409 } 1410 } 1411 1412 ftpCloseCommand(); // closes the data connection only 1413 1414 if (!bFound) { 1415 return ftpStatAnswerNotFound(path, filename); 1416 } 1417 1418 if (!linkURL.isEmpty()) { 1419 if (linkURL == url || linkURL == tempurl) { 1420 return Result::fail(ERR_CYCLIC_LINK, linkURL.toString()); 1421 } 1422 return FtpInternal::stat(linkURL); 1423 } 1424 1425 qCDebug(KIO_FTP) << "stat : finished successfully"; 1426 ; 1427 return Result::pass(); 1428 } 1429 1430 bool FtpInternal::maybeEmitStatEntry(FtpEntry &ftpEnt, const QString &filename, bool isDir) 1431 { 1432 if (filename == ftpEnt.name && !filename.isEmpty()) { 1433 UDSEntry entry; 1434 ftpCreateUDSEntry(filename, ftpEnt, entry, isDir); 1435 q->statEntry(entry); 1436 return true; 1437 } 1438 1439 return false; 1440 } 1441 1442 Result FtpInternal::listDir(const QUrl &url) 1443 { 1444 qCDebug(KIO_FTP) << url; 1445 auto result = ftpOpenConnection(LoginMode::Implicit); 1446 if (!result.success()) { 1447 return result; 1448 } 1449 1450 // No path specified ? 1451 QString path = url.path(); 1452 if (path.isEmpty()) { 1453 QUrl realURL; 1454 realURL.setScheme(QStringLiteral("ftp")); 1455 realURL.setUserName(m_user); 1456 realURL.setPassword(m_pass); 1457 realURL.setHost(m_host); 1458 if (m_port > 0 && m_port != DEFAULT_FTP_PORT) { 1459 realURL.setPort(m_port); 1460 } 1461 if (m_initialPath.isEmpty()) { 1462 m_initialPath = QStringLiteral("/"); 1463 } 1464 realURL.setPath(m_initialPath); 1465 qCDebug(KIO_FTP) << "REDIRECTION to " << realURL; 1466 q->redirection(realURL); 1467 return Result::pass(); 1468 } 1469 1470 qCDebug(KIO_FTP) << "hunting for path" << path; 1471 1472 result = ftpOpenDir(path); 1473 if (!result.success()) { 1474 if (ftpFileExists(path)) { 1475 return Result::fail(ERR_IS_FILE, path); 1476 } 1477 // not sure which to emit 1478 // error( ERR_DOES_NOT_EXIST, path ); 1479 return Result::fail(ERR_CANNOT_ENTER_DIRECTORY, path); 1480 } 1481 1482 UDSEntry entry; 1483 FtpEntry ftpEnt; 1484 QList<FtpEntry> ftpValidateEntList; 1485 while (ftpReadDir(ftpEnt)) { 1486 qCDebug(KIO_FTP) << ftpEnt.name; 1487 // Q_ASSERT( !ftpEnt.name.isEmpty() ); 1488 if (!ftpEnt.name.isEmpty()) { 1489 if (ftpEnt.name.at(0).isSpace()) { 1490 ftpValidateEntList.append(ftpEnt); 1491 continue; 1492 } 1493 1494 // if ( S_ISDIR( (mode_t)ftpEnt.type ) ) 1495 // qDebug() << "is a dir"; 1496 // if ( !ftpEnt.link.isEmpty() ) 1497 // qDebug() << "is a link to " << ftpEnt.link; 1498 ftpCreateUDSEntry(ftpEnt.name, ftpEnt, entry, false); 1499 q->listEntry(entry); 1500 entry.clear(); 1501 } 1502 } 1503 1504 for (int i = 0, count = ftpValidateEntList.count(); i < count; ++i) { 1505 FtpEntry &ftpEnt = ftpValidateEntList[i]; 1506 fixupEntryName(&ftpEnt); 1507 ftpCreateUDSEntry(ftpEnt.name, ftpEnt, entry, false); 1508 q->listEntry(entry); 1509 entry.clear(); 1510 } 1511 1512 ftpCloseCommand(); // closes the data connection only 1513 return Result::pass(); 1514 } 1515 1516 void FtpInternal::worker_status() 1517 { 1518 qCDebug(KIO_FTP) << "Got worker_status host = " << (!m_host.toLatin1().isEmpty() ? m_host.toLatin1() : "[None]") << " [" 1519 << (m_bLoggedOn ? "Connected" : "Not connected") << "]"; 1520 q->workerStatus(m_host, m_bLoggedOn); 1521 } 1522 1523 Result FtpInternal::ftpOpenDir(const QString &path) 1524 { 1525 // QString path( _url.path(QUrl::RemoveTrailingSlash) ); 1526 1527 // We try to change to this directory first to see whether it really is a directory. 1528 // (And also to follow symlinks) 1529 QString tmp = path.isEmpty() ? QStringLiteral("/") : path; 1530 1531 // We get '550', whether it's a file or doesn't exist... 1532 if (!ftpFolder(tmp)) { 1533 return Result::fail(); 1534 } 1535 1536 // Don't use the path in the list command: 1537 // We changed into this directory anyway - so it's enough just to send "list". 1538 // We use '-a' because the application MAY be interested in dot files. 1539 // The only way to really know would be to have a metadata flag for this... 1540 // Since some windows ftp server seems not to support the -a argument, we use a fallback here. 1541 // In fact we have to use -la otherwise -a removes the default -l (e.g. ftp.trolltech.com) 1542 // Pass KJob::NoError first because we don't want to emit error before we 1543 // have tried all commands. 1544 auto result = ftpOpenCommand("list -la", QString(), 'I', KJob::NoError); 1545 if (!result.success()) { 1546 result = ftpOpenCommand("list", QString(), 'I', KJob::NoError); 1547 } 1548 if (!result.success()) { 1549 // Servers running with Turkish locale having problems converting 'i' letter to upper case. 1550 // So we send correct upper case command as last resort. 1551 result = ftpOpenCommand("LIST -la", QString(), 'I', ERR_CANNOT_ENTER_DIRECTORY); 1552 } 1553 1554 if (!result.success()) { 1555 qCWarning(KIO_FTP) << "Can't open for listing"; 1556 return result; 1557 } 1558 1559 qCDebug(KIO_FTP) << "Starting of list was ok"; 1560 return Result::pass(); 1561 } 1562 1563 bool FtpInternal::ftpReadDir(FtpEntry &de) 1564 { 1565 Q_ASSERT(m_data); 1566 1567 // get a line from the data connection ... 1568 while (true) { 1569 while (!m_data->canReadLine() && m_data->waitForReadyRead((q->readTimeout() * 1000))) { } 1570 QByteArray data = m_data->readLine(); 1571 if (data.size() == 0) { 1572 break; 1573 } 1574 1575 const char *buffer = data.data(); 1576 qCDebug(KIO_FTP) << "dir > " << buffer; 1577 1578 // Normally the listing looks like 1579 // -rw-r--r-- 1 dfaure dfaure 102 Nov 9 12:30 log 1580 // but on Netware servers like ftp://ci-1.ci.pwr.wroc.pl/ it looks like (#76442) 1581 // d [RWCEAFMS] Admin 512 Oct 13 2004 PSI 1582 1583 // we should always get the following 5 fields ... 1584 const char *p_access; 1585 const char *p_junk; 1586 const char *p_owner; 1587 const char *p_group; 1588 const char *p_size; 1589 if ((p_access = strtok((char *)buffer, " ")) == nullptr) { 1590 continue; 1591 } 1592 if ((p_junk = strtok(nullptr, " ")) == nullptr) { 1593 continue; 1594 } 1595 if ((p_owner = strtok(nullptr, " ")) == nullptr) { 1596 continue; 1597 } 1598 if ((p_group = strtok(nullptr, " ")) == nullptr) { 1599 continue; 1600 } 1601 if ((p_size = strtok(nullptr, " ")) == nullptr) { 1602 continue; 1603 } 1604 1605 qCDebug(KIO_FTP) << "p_access=" << p_access << " p_junk=" << p_junk << " p_owner=" << p_owner << " p_group=" << p_group << " p_size=" << p_size; 1606 1607 de.access = 0; 1608 if (qstrlen(p_access) == 1 && p_junk[0] == '[') { // Netware 1609 de.access = S_IRWXU | S_IRWXG | S_IRWXO; // unknown -> give all permissions 1610 } 1611 1612 const char *p_date_1; 1613 const char *p_date_2; 1614 const char *p_date_3; 1615 const char *p_name; 1616 1617 // A special hack for "/dev". A listing may look like this: 1618 // crw-rw-rw- 1 root root 1, 5 Jun 29 1997 zero 1619 // So we just ignore the number in front of the ",". Ok, it is a hack :-) 1620 if (strchr(p_size, ',') != nullptr) { 1621 qCDebug(KIO_FTP) << "Size contains a ',' -> reading size again (/dev hack)"; 1622 if ((p_size = strtok(nullptr, " ")) == nullptr) { 1623 continue; 1624 } 1625 } 1626 1627 // This is needed for ftp servers with a directory listing like this (#375610): 1628 // drwxr-xr-x folder 0 Mar 15 15:50 directory_name 1629 if (strcmp(p_junk, "folder") == 0) { 1630 p_date_1 = p_group; 1631 p_date_2 = p_size; 1632 p_size = p_owner; 1633 p_group = nullptr; 1634 p_owner = nullptr; 1635 } 1636 // Check whether the size we just read was really the size 1637 // or a month (this happens when the server lists no group) 1638 // Used to be the case on sunsite.uio.no, but not anymore 1639 // This is needed for the Netware case, too. 1640 else if (!isdigit(*p_size)) { 1641 p_date_1 = p_size; 1642 p_date_2 = strtok(nullptr, " "); 1643 p_size = p_group; 1644 p_group = nullptr; 1645 qCDebug(KIO_FTP) << "Size didn't have a digit -> size=" << p_size << " date_1=" << p_date_1; 1646 } else { 1647 p_date_1 = strtok(nullptr, " "); 1648 p_date_2 = strtok(nullptr, " "); 1649 qCDebug(KIO_FTP) << "Size has a digit -> ok. p_date_1=" << p_date_1; 1650 } 1651 1652 if (p_date_1 != nullptr && p_date_2 != nullptr && (p_date_3 = strtok(nullptr, " ")) != nullptr && (p_name = strtok(nullptr, "\r\n")) != nullptr) { 1653 { 1654 QByteArray tmp(p_name); 1655 if (p_access[0] == 'l') { 1656 int i = tmp.lastIndexOf(" -> "); 1657 if (i != -1) { 1658 de.link = q->remoteEncoding()->decode(p_name + i + 4); 1659 tmp.truncate(i); 1660 } else { 1661 de.link.clear(); 1662 } 1663 } else { 1664 de.link.clear(); 1665 } 1666 1667 if (tmp.startsWith('/')) { // listing on ftp://ftp.gnupg.org/ starts with '/' 1668 tmp.remove(0, 1); 1669 } 1670 1671 if (tmp.indexOf('/') != -1) { 1672 continue; // Don't trick us! 1673 } 1674 1675 de.name = q->remoteEncoding()->decode(tmp); 1676 } 1677 1678 de.type = S_IFREG; 1679 switch (p_access[0]) { 1680 case 'd': 1681 de.type = S_IFDIR; 1682 break; 1683 case 's': 1684 de.type = S_IFSOCK; 1685 break; 1686 case 'b': 1687 de.type = S_IFBLK; 1688 break; 1689 case 'c': 1690 de.type = S_IFCHR; 1691 break; 1692 case 'l': 1693 de.type = S_IFREG; 1694 // we don't set S_IFLNK here. de.link says it. 1695 break; 1696 default: 1697 break; 1698 } 1699 1700 if (p_access[1] == 'r') { 1701 de.access |= S_IRUSR; 1702 } 1703 if (p_access[2] == 'w') { 1704 de.access |= S_IWUSR; 1705 } 1706 if (p_access[3] == 'x' || p_access[3] == 's') { 1707 de.access |= S_IXUSR; 1708 } 1709 if (p_access[4] == 'r') { 1710 de.access |= S_IRGRP; 1711 } 1712 if (p_access[5] == 'w') { 1713 de.access |= S_IWGRP; 1714 } 1715 if (p_access[6] == 'x' || p_access[6] == 's') { 1716 de.access |= S_IXGRP; 1717 } 1718 if (p_access[7] == 'r') { 1719 de.access |= S_IROTH; 1720 } 1721 if (p_access[8] == 'w') { 1722 de.access |= S_IWOTH; 1723 } 1724 if (p_access[9] == 'x' || p_access[9] == 't') { 1725 de.access |= S_IXOTH; 1726 } 1727 if (p_access[3] == 's' || p_access[3] == 'S') { 1728 de.access |= S_ISUID; 1729 } 1730 if (p_access[6] == 's' || p_access[6] == 'S') { 1731 de.access |= S_ISGID; 1732 } 1733 if (p_access[9] == 't' || p_access[9] == 'T') { 1734 de.access |= S_ISVTX; 1735 } 1736 1737 de.owner = q->remoteEncoding()->decode(p_owner); 1738 de.group = q->remoteEncoding()->decode(p_group); 1739 de.size = charToLongLong(p_size); 1740 1741 // Parsing the date is somewhat tricky 1742 // Examples : "Oct 6 22:49", "May 13 1999" 1743 1744 // First get current date - we need the current month and year 1745 QDate currentDate(QDate::currentDate()); 1746 int currentMonth = currentDate.month(); 1747 int day = currentDate.day(); 1748 int month = currentDate.month(); 1749 int year = currentDate.year(); 1750 int minute = 0; 1751 int hour = 0; 1752 // Get day number (always second field) 1753 if (p_date_2) { 1754 day = atoi(p_date_2); 1755 } 1756 // Get month from first field 1757 // NOTE : no, we don't want to use KLocale here 1758 // It seems all FTP servers use the English way 1759 qCDebug(KIO_FTP) << "Looking for month " << p_date_1; 1760 static const char s_months[][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; 1761 for (int c = 0; c < 12; c++) { 1762 if (!qstrcmp(p_date_1, s_months[c])) { 1763 qCDebug(KIO_FTP) << "Found month " << c << " for " << p_date_1; 1764 month = c + 1; 1765 break; 1766 } 1767 } 1768 1769 // Parse third field 1770 if (p_date_3 && !strchr(p_date_3, ':')) { // No colon, looks like a year 1771 year = atoi(p_date_3); 1772 } else { 1773 // otherwise, the year is implicit 1774 // according to man ls, this happens when it is between than 6 months 1775 // old and 1 hour in the future. 1776 // So the year is : current year if tm_mon <= currentMonth+1 1777 // otherwise current year minus one 1778 // (The +1 is a security for the "+1 hour" at the end of the month issue) 1779 if (month > currentMonth + 1) { 1780 year--; 1781 } 1782 1783 // and p_date_3 contains probably a time 1784 char *semicolon; 1785 if (p_date_3 && (semicolon = (char *)strchr(p_date_3, ':'))) { 1786 *semicolon = '\0'; 1787 minute = atoi(semicolon + 1); 1788 hour = atoi(p_date_3); 1789 } else { 1790 qCWarning(KIO_FTP) << "Can't parse third field " << p_date_3; 1791 } 1792 } 1793 1794 de.date = QDateTime(QDate(year, month, day), QTime(hour, minute)); 1795 qCDebug(KIO_FTP) << de.date; 1796 return true; 1797 } 1798 } // line invalid, loop to get another line 1799 return false; 1800 } 1801 1802 //=============================================================================== 1803 // public: get download file from server 1804 // helper: ftpGet called from get() and copy() 1805 //=============================================================================== 1806 Result FtpInternal::get(const QUrl &url) 1807 { 1808 qCDebug(KIO_FTP) << url; 1809 const Result result = ftpGet(-1, QString(), url, 0); 1810 ftpCloseCommand(); // must close command! 1811 return result; 1812 } 1813 1814 Result FtpInternal::ftpGet(int iCopyFile, const QString &sCopyFile, const QUrl &url, KIO::fileoffset_t llOffset) 1815 { 1816 auto result = ftpOpenConnection(LoginMode::Implicit); 1817 if (!result.success()) { 1818 return result; 1819 } 1820 1821 // Try to find the size of the file (and check that it exists at 1822 // the same time). If we get back a 550, "File does not exist" 1823 // or "not a plain file", check if it is a directory. If it is a 1824 // directory, return an error; otherwise simply try to retrieve 1825 // the request... 1826 if (!ftpSize(url.path(), '?') && (m_iRespCode == 550) && ftpFolder(url.path())) { 1827 // Ok it's a dir in fact 1828 qCDebug(KIO_FTP) << "it is a directory in fact"; 1829 return Result::fail(ERR_IS_DIRECTORY); 1830 } 1831 1832 QString resumeOffset = q->metaData(QStringLiteral("range-start")); 1833 if (resumeOffset.isEmpty()) { 1834 resumeOffset = q->metaData(QStringLiteral("resume")); // old name 1835 } 1836 if (!resumeOffset.isEmpty()) { 1837 llOffset = resumeOffset.toLongLong(); 1838 qCDebug(KIO_FTP) << "got offset from metadata : " << llOffset; 1839 } 1840 1841 result = ftpOpenCommand("retr", url.path(), '?', ERR_CANNOT_OPEN_FOR_READING, llOffset); 1842 if (!result.success()) { 1843 qCWarning(KIO_FTP) << "Can't open for reading"; 1844 return result; 1845 } 1846 1847 // Read the size from the response string 1848 if (m_size == UnknownSize) { 1849 const char *psz = strrchr(ftpResponse(4), '('); 1850 if (psz) { 1851 m_size = charToLongLong(psz + 1); 1852 } 1853 if (!m_size) { 1854 m_size = UnknownSize; 1855 } 1856 } 1857 1858 // Send the MIME type... 1859 if (iCopyFile == -1) { 1860 const auto result = ftpSendMimeType(url); 1861 if (!result.success()) { 1862 return result; 1863 } 1864 } 1865 1866 KIO::filesize_t bytesLeft = 0; 1867 if (m_size != UnknownSize) { 1868 bytesLeft = m_size - llOffset; 1869 q->totalSize(m_size); // emit the total size... 1870 } 1871 1872 qCDebug(KIO_FTP) << "starting with offset=" << llOffset; 1873 KIO::fileoffset_t processed_size = llOffset; 1874 1875 QByteArray array; 1876 char buffer[maximumIpcSize]; 1877 // start with small data chunks in case of a slow data source (modem) 1878 // - unfortunately this has a negative impact on performance for large 1879 // - files - so we will increase the block size after a while ... 1880 int iBlockSize = initialIpcSize; 1881 int iBufferCur = 0; 1882 1883 while (m_size == UnknownSize || bytesLeft > 0) { 1884 // let the buffer size grow if the file is larger 64kByte ... 1885 if (processed_size - llOffset > 1024 * 64) { 1886 iBlockSize = maximumIpcSize; 1887 } 1888 1889 // read the data and detect EOF or error ... 1890 if (iBlockSize + iBufferCur > (int)sizeof(buffer)) { 1891 iBlockSize = sizeof(buffer) - iBufferCur; 1892 } 1893 if (m_data->bytesAvailable() == 0) { 1894 m_data->waitForReadyRead((q->readTimeout() * 1000)); 1895 } 1896 int n = m_data->read(buffer + iBufferCur, iBlockSize); 1897 if (n <= 0) { 1898 // this is how we detect EOF in case of unknown size 1899 if (m_size == UnknownSize && n == 0) { 1900 break; 1901 } 1902 // unexpected eof. Happens when the daemon gets killed. 1903 return Result::fail(ERR_CANNOT_READ); 1904 } 1905 processed_size += n; 1906 1907 // collect very small data chunks in buffer before processing ... 1908 if (m_size != UnknownSize) { 1909 bytesLeft -= n; 1910 iBufferCur += n; 1911 if (iBufferCur < minimumMimeSize && bytesLeft > 0) { 1912 q->processedSize(processed_size); 1913 continue; 1914 } 1915 n = iBufferCur; 1916 iBufferCur = 0; 1917 } 1918 1919 // write output file or pass to data pump ... 1920 int writeError = 0; 1921 if (iCopyFile == -1) { 1922 array = QByteArray::fromRawData(buffer, n); 1923 q->data(array); 1924 array.clear(); 1925 } else if ((writeError = WriteToFile(iCopyFile, buffer, n)) != 0) { 1926 return Result::fail(writeError, sCopyFile); 1927 } 1928 1929 Q_ASSERT(processed_size >= 0); 1930 q->processedSize(static_cast<KIO::filesize_t>(processed_size)); 1931 } 1932 1933 qCDebug(KIO_FTP) << "done"; 1934 if (iCopyFile == -1) { // must signal EOF to data pump ... 1935 q->data(array); // array is empty and must be empty! 1936 } 1937 1938 q->processedSize(m_size == UnknownSize ? processed_size : m_size); 1939 return Result::pass(); 1940 } 1941 1942 //=============================================================================== 1943 // public: put upload file to server 1944 // helper: ftpPut called from put() and copy() 1945 //=============================================================================== 1946 Result FtpInternal::put(const QUrl &url, int permissions, KIO::JobFlags flags) 1947 { 1948 qCDebug(KIO_FTP) << url; 1949 const auto result = ftpPut(-1, url, permissions, flags); 1950 ftpCloseCommand(); // must close command! 1951 return result; 1952 } 1953 1954 Result FtpInternal::ftpPut(int iCopyFile, const QUrl &dest_url, int permissions, KIO::JobFlags flags) 1955 { 1956 const auto openResult = ftpOpenConnection(LoginMode::Implicit); 1957 if (!openResult.success()) { 1958 return openResult; 1959 } 1960 1961 // Don't use mark partial over anonymous FTP. 1962 // My incoming dir allows put but not rename... 1963 bool bMarkPartial; 1964 if (m_user.isEmpty() || m_user == QLatin1String(s_ftpLogin)) { 1965 bMarkPartial = false; 1966 } else { 1967 bMarkPartial = q->configValue(QStringLiteral("MarkPartial"), true); 1968 } 1969 1970 QString dest_orig = dest_url.path(); 1971 const QString dest_part = dest_orig + QLatin1String(".part"); 1972 1973 if (ftpSize(dest_orig, 'I')) { 1974 if (m_size == 0) { 1975 // delete files with zero size 1976 const QByteArray cmd = "DELE " + q->remoteEncoding()->encode(dest_orig); 1977 if (!ftpSendCmd(cmd) || (m_iRespType != 2)) { 1978 return Result::fail(ERR_CANNOT_DELETE_PARTIAL, QString()); 1979 } 1980 } else if (!(flags & KIO::Overwrite) && !(flags & KIO::Resume)) { 1981 return Result::fail(ERR_FILE_ALREADY_EXIST, QString()); 1982 } else if (bMarkPartial) { 1983 // when using mark partial, append .part extension 1984 const auto result = ftpRename(dest_orig, dest_part, KIO::Overwrite); 1985 if (!result.success()) { 1986 return Result::fail(ERR_CANNOT_RENAME_PARTIAL, QString()); 1987 } 1988 } 1989 // Don't chmod an existing file 1990 permissions = -1; 1991 } else if (bMarkPartial && ftpSize(dest_part, 'I')) { 1992 // file with extension .part exists 1993 if (m_size == 0) { 1994 // delete files with zero size 1995 const QByteArray cmd = "DELE " + q->remoteEncoding()->encode(dest_part); 1996 if (!ftpSendCmd(cmd) || (m_iRespType != 2)) { 1997 return Result::fail(ERR_CANNOT_DELETE_PARTIAL, QString()); 1998 } 1999 } else if (!(flags & KIO::Overwrite) && !(flags & KIO::Resume)) { 2000 flags |= q->canResume(m_size) ? KIO::Resume : KIO::DefaultFlags; 2001 if (!(flags & KIO::Resume)) { 2002 return Result::fail(ERR_FILE_ALREADY_EXIST, QString()); 2003 } 2004 } 2005 } else { 2006 m_size = 0; 2007 } 2008 2009 QString dest; 2010 2011 // if we are using marking of partial downloads -> add .part extension 2012 if (bMarkPartial) { 2013 qCDebug(KIO_FTP) << "Adding .part extension to " << dest_orig; 2014 dest = dest_part; 2015 } else { 2016 dest = dest_orig; 2017 } 2018 2019 KIO::fileoffset_t offset = 0; 2020 2021 // set the mode according to offset 2022 if ((flags & KIO::Resume) && m_size > 0) { 2023 offset = m_size; 2024 if (iCopyFile != -1) { 2025 if (QT_LSEEK(iCopyFile, offset, SEEK_SET) < 0) { 2026 return Result::fail(ERR_CANNOT_RESUME, QString()); 2027 } 2028 } 2029 } 2030 2031 const auto storResult = ftpOpenCommand("stor", dest, '?', ERR_CANNOT_WRITE, offset); 2032 if (!storResult.success()) { 2033 return storResult; 2034 } 2035 2036 qCDebug(KIO_FTP) << "ftpPut: starting with offset=" << offset; 2037 KIO::fileoffset_t processed_size = offset; 2038 2039 QByteArray buffer; 2040 int result; 2041 int iBlockSize = initialIpcSize; 2042 int writeError = 0; 2043 // Loop until we got 'dataEnd' 2044 do { 2045 if (iCopyFile == -1) { 2046 q->dataReq(); // Request for data 2047 result = q->readData(buffer); 2048 } else { 2049 // let the buffer size grow if the file is larger 64kByte ... 2050 if (processed_size - offset > 1024 * 64) { 2051 iBlockSize = maximumIpcSize; 2052 } 2053 buffer.resize(iBlockSize); 2054 result = QT_READ(iCopyFile, buffer.data(), buffer.size()); 2055 if (result < 0) { 2056 writeError = ERR_CANNOT_READ; 2057 } else { 2058 buffer.resize(result); 2059 } 2060 } 2061 2062 if (result > 0) { 2063 m_data->write(buffer); 2064 while (m_data->bytesToWrite() && m_data->waitForBytesWritten()) { } 2065 processed_size += result; 2066 q->processedSize(processed_size); 2067 } 2068 } while (result > 0); 2069 2070 if (result != 0) { // error 2071 ftpCloseCommand(); // don't care about errors 2072 qCDebug(KIO_FTP) << "Error during 'put'. Aborting."; 2073 if (bMarkPartial) { 2074 // Remove if smaller than minimum size 2075 if (ftpSize(dest, 'I') && (processed_size < q->configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE))) { 2076 const QByteArray cmd = "DELE " + q->remoteEncoding()->encode(dest); 2077 (void)ftpSendCmd(cmd); 2078 } 2079 } 2080 return Result::fail(writeError, dest_url.toString()); 2081 } 2082 2083 if (!ftpCloseCommand()) { 2084 return Result::fail(ERR_CANNOT_WRITE); 2085 } 2086 2087 // after full download rename the file back to original name 2088 if (bMarkPartial) { 2089 qCDebug(KIO_FTP) << "renaming dest (" << dest << ") back to dest_orig (" << dest_orig << ")"; 2090 const auto result = ftpRename(dest, dest_orig, KIO::Overwrite); 2091 if (!result.success()) { 2092 return Result::fail(ERR_CANNOT_RENAME_PARTIAL); 2093 } 2094 } 2095 2096 // set final permissions 2097 if (permissions != -1) { 2098 if (m_user == QLatin1String(s_ftpLogin)) { 2099 qCDebug(KIO_FTP) << "Trying to chmod over anonymous FTP ???"; 2100 } 2101 // chmod the file we just put 2102 if (!ftpChmod(dest_orig, permissions)) { 2103 // To be tested 2104 // if ( m_user != s_ftpLogin ) 2105 // warning( i18n( "Could not change permissions for\n%1" ).arg( dest_orig ) ); 2106 } 2107 } 2108 2109 return Result::pass(); 2110 } 2111 2112 /** Use the SIZE command to get the file size. 2113 Warning : the size depends on the transfer mode, hence the second arg. */ 2114 bool FtpInternal::ftpSize(const QString &path, char mode) 2115 { 2116 m_size = UnknownSize; 2117 if (!ftpDataMode(mode)) { 2118 return false; 2119 } 2120 2121 const QByteArray buf = "SIZE " + q->remoteEncoding()->encode(path); 2122 if (!ftpSendCmd(buf) || (m_iRespType != 2)) { 2123 return false; 2124 } 2125 2126 // skip leading "213 " (response code) 2127 QByteArray psz(ftpResponse(4)); 2128 if (psz.isEmpty()) { 2129 return false; 2130 } 2131 bool ok = false; 2132 m_size = psz.trimmed().toLongLong(&ok); 2133 if (!ok) { 2134 m_size = UnknownSize; 2135 } 2136 return true; 2137 } 2138 2139 bool FtpInternal::ftpFileExists(const QString &path) 2140 { 2141 const QByteArray buf = "SIZE " + q->remoteEncoding()->encode(path); 2142 if (!ftpSendCmd(buf) || (m_iRespType != 2)) { 2143 return false; 2144 } 2145 2146 // skip leading "213 " (response code) 2147 const char *psz = ftpResponse(4); 2148 return psz != nullptr; 2149 } 2150 2151 // Today the differences between ASCII and BINARY are limited to 2152 // CR or CR/LF line terminators. Many servers ignore ASCII (like 2153 // win2003 -or- vsftp with default config). In the early days of 2154 // computing, when even text-files had structure, this stuff was 2155 // more important. 2156 // Theoretically "list" could return different results in ASCII 2157 // and BINARY mode. But again, most servers ignore ASCII here. 2158 bool FtpInternal::ftpDataMode(char cMode) 2159 { 2160 if (cMode == '?') { 2161 cMode = m_bTextMode ? 'A' : 'I'; 2162 } else if (cMode == 'a') { 2163 cMode = 'A'; 2164 } else if (cMode != 'A') { 2165 cMode = 'I'; 2166 } 2167 2168 qCDebug(KIO_FTP) << "want" << cMode << "has" << m_cDataMode; 2169 if (m_cDataMode == cMode) { 2170 return true; 2171 } 2172 2173 const QByteArray buf = QByteArrayLiteral("TYPE ") + cMode; 2174 if (!ftpSendCmd(buf) || (m_iRespType != 2)) { 2175 return false; 2176 } 2177 m_cDataMode = cMode; 2178 return true; 2179 } 2180 2181 bool FtpInternal::ftpFolder(const QString &path) 2182 { 2183 QString newPath = path; 2184 int iLen = newPath.length(); 2185 if (iLen > 1 && newPath[iLen - 1] == QLatin1Char('/')) { 2186 newPath.chop(1); 2187 } 2188 2189 qCDebug(KIO_FTP) << "want" << newPath << "has" << m_currentPath; 2190 if (m_currentPath == newPath) { 2191 return true; 2192 } 2193 2194 const QByteArray tmp = "cwd " + q->remoteEncoding()->encode(newPath); 2195 if (!ftpSendCmd(tmp)) { 2196 return false; // connection failure 2197 } 2198 if (m_iRespType != 2) { 2199 return false; // not a folder 2200 } 2201 m_currentPath = newPath; 2202 return true; 2203 } 2204 2205 //=============================================================================== 2206 // public: copy don't use kio data pump if one side is a local file 2207 // helper: ftpCopyPut called from copy() on upload 2208 // helper: ftpCopyGet called from copy() on download 2209 //=============================================================================== 2210 Result FtpInternal::copy(const QUrl &src, const QUrl &dest, int permissions, KIO::JobFlags flags) 2211 { 2212 int iCopyFile = -1; 2213 bool bSrcLocal = src.isLocalFile(); 2214 bool bDestLocal = dest.isLocalFile(); 2215 QString sCopyFile; 2216 2217 Result result = Result::pass(); 2218 if (bSrcLocal && !bDestLocal) { // File -> Ftp 2219 sCopyFile = src.toLocalFile(); 2220 qCDebug(KIO_FTP) << "local file" << sCopyFile << "-> ftp" << dest.path(); 2221 result = ftpCopyPut(iCopyFile, sCopyFile, dest, permissions, flags); 2222 } else if (!bSrcLocal && bDestLocal) { // Ftp -> File 2223 sCopyFile = dest.toLocalFile(); 2224 qCDebug(KIO_FTP) << "ftp" << src.path() << "-> local file" << sCopyFile; 2225 result = ftpCopyGet(iCopyFile, sCopyFile, src, permissions, flags); 2226 } else { 2227 return Result::fail(ERR_UNSUPPORTED_ACTION, QString()); 2228 } 2229 2230 // perform clean-ups and report error (if any) 2231 if (iCopyFile != -1) { 2232 QT_CLOSE(iCopyFile); 2233 } 2234 ftpCloseCommand(); // must close command! 2235 2236 return result; 2237 } 2238 2239 bool FtpInternal::isSocksProxyScheme(const QString &scheme) 2240 { 2241 return scheme == QLatin1String("socks") || scheme == QLatin1String("socks5"); 2242 } 2243 2244 bool FtpInternal::isSocksProxy() const 2245 { 2246 return isSocksProxyScheme(m_proxyURL.scheme()); 2247 } 2248 2249 Result FtpInternal::ftpCopyPut(int &iCopyFile, const QString &sCopyFile, const QUrl &url, int permissions, KIO::JobFlags flags) 2250 { 2251 // check if source is ok ... 2252 QFileInfo info(sCopyFile); 2253 bool bSrcExists = info.exists(); 2254 if (bSrcExists) { 2255 if (info.isDir()) { 2256 return Result::fail(ERR_IS_DIRECTORY); 2257 } 2258 } else { 2259 return Result::fail(ERR_DOES_NOT_EXIST); 2260 } 2261 2262 iCopyFile = QT_OPEN(QFile::encodeName(sCopyFile).constData(), O_RDONLY); 2263 if (iCopyFile == -1) { 2264 return Result::fail(ERR_CANNOT_OPEN_FOR_READING); 2265 } 2266 2267 // delegate the real work (iError gets status) ... 2268 q->totalSize(info.size()); 2269 if (s_enableCanResume) { 2270 return ftpPut(iCopyFile, url, permissions, flags & ~KIO::Resume); 2271 } else { 2272 return ftpPut(iCopyFile, url, permissions, flags | KIO::Resume); 2273 } 2274 } 2275 2276 Result FtpInternal::ftpCopyGet(int &iCopyFile, const QString &sCopyFile, const QUrl &url, int permissions, KIO::JobFlags flags) 2277 { 2278 // check if destination is ok ... 2279 QFileInfo info(sCopyFile); 2280 const bool bDestExists = info.exists(); 2281 if (bDestExists) { 2282 if (info.isDir()) { 2283 return Result::fail(ERR_IS_DIRECTORY); 2284 } 2285 if (!(flags & KIO::Overwrite)) { 2286 return Result::fail(ERR_FILE_ALREADY_EXIST); 2287 } 2288 } 2289 2290 // do we have a ".part" file? 2291 const QString sPart = sCopyFile + QLatin1String(".part"); 2292 bool bResume = false; 2293 QFileInfo sPartInfo(sPart); 2294 const bool bPartExists = sPartInfo.exists(); 2295 const bool bMarkPartial = q->configValue(QStringLiteral("MarkPartial"), true); 2296 const QString dest = bMarkPartial ? sPart : sCopyFile; 2297 if (bMarkPartial && bPartExists && sPartInfo.size() > 0) { 2298 // must not be a folder! please fix a similar bug in kio_file!! 2299 if (sPartInfo.isDir()) { 2300 return Result::fail(ERR_DIR_ALREADY_EXIST); 2301 } 2302 // doesn't work for copy? -> design flaw? 2303 bResume = s_enableCanResume ? q->canResume(sPartInfo.size()) : true; 2304 } 2305 2306 if (bPartExists && !bResume) { // get rid of an unwanted ".part" file 2307 QFile::remove(sPart); 2308 } 2309 2310 // WABA: Make sure that we keep writing permissions ourselves, 2311 // otherwise we can be in for a surprise on NFS. 2312 mode_t initialMode; 2313 if (permissions >= 0) { 2314 initialMode = static_cast<mode_t>(permissions | S_IWUSR); 2315 } else { 2316 initialMode = 0666; 2317 } 2318 2319 // open the output file ... 2320 KIO::fileoffset_t hCopyOffset = 0; 2321 if (bResume) { 2322 iCopyFile = QT_OPEN(QFile::encodeName(sPart).constData(), O_RDWR); // append if resuming 2323 hCopyOffset = QT_LSEEK(iCopyFile, 0, SEEK_END); 2324 if (hCopyOffset < 0) { 2325 return Result::fail(ERR_CANNOT_RESUME); 2326 } 2327 qCDebug(KIO_FTP) << "resuming at " << hCopyOffset; 2328 } else { 2329 iCopyFile = QT_OPEN(QFile::encodeName(dest).constData(), O_CREAT | O_TRUNC | O_WRONLY, initialMode); 2330 } 2331 2332 if (iCopyFile == -1) { 2333 qCDebug(KIO_FTP) << "### COULD NOT WRITE " << sCopyFile; 2334 const int error = (errno == EACCES) ? ERR_WRITE_ACCESS_DENIED : ERR_CANNOT_OPEN_FOR_WRITING; 2335 return Result::fail(error); 2336 } 2337 2338 // delegate the real work (iError gets status) ... 2339 auto result = ftpGet(iCopyFile, sCopyFile, url, hCopyOffset); 2340 2341 if (QT_CLOSE(iCopyFile) == 0 && !result.success()) { 2342 // If closing the file failed but there isn't an error yet, switch 2343 // into an error! 2344 result = Result::fail(ERR_CANNOT_WRITE); 2345 } 2346 iCopyFile = -1; 2347 2348 // handle renaming or deletion of a partial file ... 2349 if (bMarkPartial) { 2350 if (result.success()) { 2351 // rename ".part" on success 2352 if (!QFile::rename(sPart, sCopyFile)) { 2353 // If rename fails, try removing the destination first if it exists. 2354 if (!bDestExists || !(QFile::remove(sCopyFile) && QFile::rename(sPart, sCopyFile))) { 2355 qCDebug(KIO_FTP) << "cannot rename " << sPart << " to " << sCopyFile; 2356 result = Result::fail(ERR_CANNOT_RENAME_PARTIAL); 2357 } 2358 } 2359 } else { 2360 sPartInfo.refresh(); 2361 if (sPartInfo.exists()) { // should a very small ".part" be deleted? 2362 int size = q->configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE); 2363 if (sPartInfo.size() < size) { 2364 QFile::remove(sPart); 2365 } 2366 } 2367 } 2368 } 2369 2370 if (result.success()) { 2371 const QString mtimeStr = q->metaData(QStringLiteral("modified")); 2372 if (!mtimeStr.isEmpty()) { 2373 QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); 2374 if (dt.isValid()) { 2375 qCDebug(KIO_FTP) << "Updating modified timestamp to" << mtimeStr; 2376 struct utimbuf utbuf; 2377 info.refresh(); 2378 utbuf.actime = info.lastRead().toSecsSinceEpoch(); // access time, unchanged 2379 utbuf.modtime = dt.toSecsSinceEpoch(); // modification time 2380 ::utime(QFile::encodeName(sCopyFile).constData(), &utbuf); 2381 } 2382 } 2383 } 2384 2385 return result; 2386 } 2387 2388 Result FtpInternal::ftpSendMimeType(const QUrl &url) 2389 { 2390 const int totalSize = ((m_size == UnknownSize || m_size > 1024) ? 1024 : static_cast<int>(m_size)); 2391 QByteArray buffer(totalSize, '\0'); 2392 2393 while (true) { 2394 // Wait for content to be available... 2395 if (m_data->bytesAvailable() == 0 && !m_data->waitForReadyRead((q->readTimeout() * 1000))) { 2396 return Result::fail(ERR_CANNOT_READ, url.toString()); 2397 } 2398 2399 const qint64 bytesRead = m_data->peek(buffer.data(), totalSize); 2400 2401 // If we got a -1, it must be an error so return an error. 2402 if (bytesRead == -1) { 2403 return Result::fail(ERR_CANNOT_READ, url.toString()); 2404 } 2405 2406 // If m_size is unknown, peek returns 0 (0 sized file ??), or peek returns size 2407 // equal to the size we want, then break. 2408 if (bytesRead == 0 || bytesRead == totalSize || m_size == UnknownSize) { 2409 break; 2410 } 2411 } 2412 2413 if (!buffer.isEmpty()) { 2414 QMimeDatabase db; 2415 QMimeType mime = db.mimeTypeForFileNameAndData(url.path(), buffer); 2416 qCDebug(KIO_FTP) << "Emitting MIME type" << mime.name(); 2417 q->mimeType(mime.name()); // emit the MIME type... 2418 } 2419 2420 return Result::pass(); 2421 } 2422 2423 void FtpInternal::fixupEntryName(FtpEntry *e) 2424 { 2425 Q_ASSERT(e); 2426 if (e->type == S_IFDIR) { 2427 if (!ftpFolder(e->name)) { 2428 QString name(e->name.trimmed()); 2429 if (ftpFolder(name)) { 2430 e->name = name; 2431 qCDebug(KIO_FTP) << "fixing up directory name from" << e->name << "to" << name; 2432 } else { 2433 int index = 0; 2434 while (e->name.at(index).isSpace()) { 2435 index++; 2436 name = e->name.mid(index); 2437 if (ftpFolder(name)) { 2438 qCDebug(KIO_FTP) << "fixing up directory name from" << e->name << "to" << name; 2439 e->name = name; 2440 break; 2441 } 2442 } 2443 } 2444 } 2445 } else { 2446 if (!ftpFileExists(e->name)) { 2447 QString name(e->name.trimmed()); 2448 if (ftpFileExists(name)) { 2449 e->name = name; 2450 qCDebug(KIO_FTP) << "fixing up filename from" << e->name << "to" << name; 2451 } else { 2452 int index = 0; 2453 while (e->name.at(index).isSpace()) { 2454 index++; 2455 name = e->name.mid(index); 2456 if (ftpFileExists(name)) { 2457 qCDebug(KIO_FTP) << "fixing up filename from" << e->name << "to" << name; 2458 e->name = name; 2459 break; 2460 } 2461 } 2462 } 2463 } 2464 } 2465 } 2466 2467 ConnectionResult FtpInternal::synchronousConnectToHost(const QString &host, quint16 port) 2468 { 2469 const QUrl proxyUrl = m_proxyURL; 2470 QNetworkProxy proxy; 2471 if (!proxyUrl.isEmpty()) { 2472 proxy = QNetworkProxy(QNetworkProxy::Socks5Proxy, proxyUrl.host(), static_cast<quint16>(proxyUrl.port(0)), proxyUrl.userName(), proxyUrl.password()); 2473 } 2474 2475 QTcpSocket *socket = new QSslSocket; 2476 socket->setProxy(proxy); 2477 socket->connectToHost(host, port); 2478 socket->waitForConnected(q->connectTimeout() * 1000); 2479 const auto socketError = socket->error(); 2480 if (socketError == QAbstractSocket::ProxyAuthenticationRequiredError) { 2481 AuthInfo info; 2482 info.url = proxyUrl; 2483 info.verifyPath = true; // ### whatever 2484 2485 if (!q->checkCachedAuthentication(info)) { 2486 info.prompt = i18n( 2487 "You need to supply a username and a password for " 2488 "the proxy server listed below before you are allowed " 2489 "to access any sites."); 2490 info.keepPassword = true; 2491 info.commentLabel = i18n("Proxy:"); 2492 info.comment = i18n("<b>%1</b>", proxy.hostName()); 2493 2494 const int errorCode = q->openPasswordDialog(info, i18n("Proxy Authentication Failed.")); 2495 if (errorCode != KJob::NoError) { 2496 qCDebug(KIO_FTP) << "user canceled proxy authentication, or communication error." << errorCode; 2497 return ConnectionResult{socket, Result::fail(errorCode, proxyUrl.toString())}; 2498 } 2499 } 2500 2501 proxy.setUser(info.username); 2502 proxy.setPassword(info.password); 2503 2504 delete socket; 2505 socket = new QSslSocket; 2506 socket->setProxy(proxy); 2507 socket->connectToHost(host, port); 2508 socket->waitForConnected(q->connectTimeout() * 1000); 2509 2510 if (socket->state() == QAbstractSocket::ConnectedState) { 2511 // reconnect with credentials was successful -> save data 2512 q->cacheAuthentication(info); 2513 2514 m_proxyURL.setUserName(info.username); 2515 m_proxyURL.setPassword(info.password); 2516 } 2517 } 2518 2519 return ConnectionResult{socket, Result::pass()}; 2520 } 2521 2522 //=============================================================================== 2523 // Ftp 2524 //=============================================================================== 2525 2526 Ftp::Ftp(const QByteArray &pool, const QByteArray &app) 2527 : WorkerBase(QByteArrayLiteral("ftp"), pool, app) 2528 , d(new FtpInternal(this)) 2529 { 2530 } 2531 2532 Ftp::~Ftp() = default; 2533 2534 void Ftp::setHost(const QString &host, quint16 port, const QString &user, const QString &pass) 2535 { 2536 d->setHost(host, port, user, pass); 2537 } 2538 2539 KIO::WorkerResult Ftp::openConnection() 2540 { 2541 return d->openConnection(); 2542 } 2543 2544 void Ftp::closeConnection() 2545 { 2546 d->closeConnection(); 2547 } 2548 2549 KIO::WorkerResult Ftp::stat(const QUrl &url) 2550 { 2551 return d->stat(url); 2552 } 2553 2554 KIO::WorkerResult Ftp::listDir(const QUrl &url) 2555 { 2556 return d->listDir(url); 2557 } 2558 2559 KIO::WorkerResult Ftp::mkdir(const QUrl &url, int permissions) 2560 { 2561 return d->mkdir(url, permissions); 2562 } 2563 2564 KIO::WorkerResult Ftp::rename(const QUrl &src, const QUrl &dst, JobFlags flags) 2565 { 2566 return d->rename(src, dst, flags); 2567 } 2568 2569 KIO::WorkerResult Ftp::del(const QUrl &url, bool isfile) 2570 { 2571 return d->del(url, isfile); 2572 } 2573 2574 KIO::WorkerResult Ftp::chmod(const QUrl &url, int permissions) 2575 { 2576 return d->chmod(url, permissions); 2577 } 2578 2579 KIO::WorkerResult Ftp::get(const QUrl &url) 2580 { 2581 return d->get(url); 2582 } 2583 2584 KIO::WorkerResult Ftp::put(const QUrl &url, int permissions, JobFlags flags) 2585 { 2586 return d->put(url, permissions, flags); 2587 } 2588 2589 void Ftp::worker_status() 2590 { 2591 d->worker_status(); 2592 } 2593 2594 KIO::WorkerResult Ftp::copy(const QUrl &src, const QUrl &dest, int permissions, JobFlags flags) 2595 { 2596 return d->copy(src, dest, permissions, flags); 2597 } 2598 2599 QDebug operator<<(QDebug dbg, const Result &r) 2600 2601 { 2602 QDebugStateSaver saver(dbg); 2603 dbg.nospace() << "Result(" 2604 << "success=" << r.success() << ", err=" << r.error() << ", str=" << r.errorString() << ')'; 2605 return dbg; 2606 } 2607 2608 // needed for JSON file embedding 2609 #include "ftp.moc" 2610 2611 #include "moc_ftp.cpp"