Compare commits

...

7 Commits

Author SHA1 Message Date
Dmitry Belyaev d9712b37f4 waitForEncrypted fix + ignoreSslErrors
added ignoreSslErrors() method
switching to encrypted mode after "AUTH TLS" fixed
2015-07-08 20:55:37 +03:00
Dmitry Belyaev 0e8721f302 FTPES
simple FTP over TLS support
2015-07-08 11:04:22 +03:00
Dmitry Belyaev 13132f5567 Local8Bit + timeout timer
Local 8 bit encoding support for ftp filenames.
Connection timeout timer.
2015-06-13 23:12:59 +03:00
Joerg Bornemann 75b21b033f fix include warning
Change-Id: I920445947fb7bb3802f692a0b5aeab8d292120ef
Reviewed-by: Christian Kandeler <christian.kandeler@digia.com>
2014-11-07 14:04:20 +01:00
Joerg Bornemann 6142a4947e add missing QT_{BEGIN|END}_NAMESPACE
Change-Id: I4e5b1bd4dc4339c1ba5b2ef9cfb153f48fa84a5c
Reviewed-by: Christian Kandeler <christian.kandeler@digia.com>
2014-11-07 14:04:15 +01:00
Joerg Bornemann 736bf93155 remove QT_{BEGIN|END}HEADER
Change-Id: Id30e6b75e5f682df5837f0e53f62bdb227f4323d
Reviewed-by: Christian Kandeler <christian.kandeler@digia.com>
2014-11-07 14:04:06 +01:00
Marcel Krems 80823b53d2 Add CMake tests.
Task-number: QTBUG-41445
Change-Id: Ia3b903aa41d5b8c5de72e9a31ccd37b222e72b5c
Reviewed-by: Stephen Kelly <steveire@gmail.com>
2014-10-28 17:11:04 +01:00
6 changed files with 214 additions and 34 deletions

View File

@ -39,8 +39,10 @@
**
****************************************************************************/
//#define QFTPPI_DEBUG
//#define QFTPDTP_DEBUG
#ifdef DEBUG
#define QFTPPI_DEBUG
#define QFTPDTP_DEBUG
#endif
#include "qftp.h"
#include "qabstractsocket.h"
@ -57,6 +59,9 @@
#include "qhash.h"
#include "qtcpserver.h"
#include "qlocale.h"
#include <QTimer>
#include <QSslSocket>
#include <QSslConfiguration>
QT_BEGIN_NAMESPACE
@ -112,8 +117,10 @@ signals:
private slots:
void socketConnected();
void socketEncrypted();
void socketReadyRead();
void socketError(QAbstractSocket::SocketError);
void sslErrors (const QList<QSslError> &) ;
void socketConnectionClosed();
void socketBytesWritten(qint64);
void setupSocket();
@ -164,6 +171,16 @@ public:
void clearPendingCommands();
void abort();
bool isTls() {return tls;}
void setTls(bool _tls) {tls=_tls;}
void addCaCertificates(QList<QSslCertificate> certs)
{
commandSocket.addCaCertificates(certs);
}
void ignoreSslErrors(const bool ignore)
{
_ignoreSslErrors = ignore;
}
QString currentCommand() const
{ return currentCmd; }
@ -178,14 +195,18 @@ signals:
void finished(const QString&);
void error(int, const QString&);
void rawFtpReply(int, const QString&);
void encrypted();
private slots:
void hostFound();
void connected();
void connectionClosed();
void connectionEncrypted();
void delayedCloseFinished();
void readyRead();
void error(QAbstractSocket::SocketError);
void check_connectToHost_timeout();
void sslErrors ( const QList<QSslError> & errors ) ;
void dtpConnectState(int);
@ -209,7 +230,7 @@ private:
bool processReply();
bool startNextCmd();
QTcpSocket commandSocket;
QSslSocket commandSocket;
QString replyText;
char replyCode[3];
State state;
@ -219,8 +240,12 @@ private:
bool waitForDtpToConnect;
bool waitForDtpToClose;
bool tls;
bool _ignoreSslErrors;
QByteArray bytesFromSocket;
QTimer timer;
QSslConfiguration ssl_config;
friend class QFtpDTP;
};
@ -318,7 +343,12 @@ void QFtpDTP::connectToHost(const QString & host, quint16 port)
delete socket;
socket = 0;
}
socket = new QTcpSocket(this);
if (pi->isTls()) {
socket = new QSslSocket(this);
} else {
socket = new QTcpSocket(this);
}
#ifndef QT_NO_BEARERMANAGEMENT
//copy network session down to the socket
socket->setProperty("_q_networksession", property("_q_networksession"));
@ -330,7 +360,19 @@ void QFtpDTP::connectToHost(const QString & host, quint16 port)
connect(socket, SIGNAL(disconnected()), SLOT(socketConnectionClosed()));
connect(socket, SIGNAL(bytesWritten(qint64)), SLOT(socketBytesWritten(qint64)));
socket->connectToHost(host, port);
QSslSocket* ssl_socket = qobject_cast<QSslSocket*>(socket);
if (ssl_socket) // here we need to setup QSslSocket (0 if QTcpSocket)
{
connect(ssl_socket, SIGNAL(encrypted()), SLOT(socketEncrypted()));
connect(ssl_socket, SIGNAL(sslErrors ( const QList<QSslError> & ) ),
SLOT(sslErrors ( const QList<QSslError> & ) ));
//TODO: implement TLS session resumption (err 450)
ssl_socket->setSslConfiguration(pi->ssl_config);
ssl_socket->connectToHostEncrypted(host, port);
} else {
socket->connectToHost(host, port);
}
}
int QFtpDTP::setupListener(const QHostAddress &address)
@ -645,12 +687,21 @@ bool QFtpDTP::parseDir(const QByteArray &buffer, const QString &userName, QUrlIn
void QFtpDTP::socketConnected()
{
bytesDone = 0;
QSslSocket* ssl_socket = qobject_cast<QSslSocket*>(socket);
if (ssl_socket){ // 0 if socket is QTcpSocket
return; // wait for encrypted
}
#if defined(QFTPDTP_DEBUG)
qDebug("QFtpDTP::connectState(CsConnected)");
#endif
emit connectState(QFtpDTP::CsConnected);
}
void QFtpDTP::socketEncrypted()
{
emit connectState(QFtpDTP::CsConnected);
}
void QFtpDTP::socketReadyRead()
{
if (!socket)
@ -738,6 +789,19 @@ void QFtpDTP::socketError(QAbstractSocket::SocketError e)
}
}
void QFtpDTP::sslErrors(const QList<QSslError> &)
{
if (pi->_ignoreSslErrors) {
QSslSocket *ssl_socket = qobject_cast<QSslSocket*>(socket);
if (ssl_socket){
ssl_socket->ignoreSslErrors();
}
return;
}
emit connectState(QFtpDTP::CsConnectionRefused); //TODO: add another connect state
}
void QFtpDTP::socketConnectionClosed()
{
if (!is_ba && data.dev) {
@ -790,12 +854,14 @@ QFtpPI::QFtpPI(QObject *parent) :
QObject(parent),
rawCommand(false),
transferConnectionExtended(true),
dtp(this),
dtp(this, this),
commandSocket(0),
state(Begin), abortState(None),
currentCmd(QString()),
waitForDtpToConnect(false),
waitForDtpToClose(false)
waitForDtpToClose(false),
tls(false),
ssl_config(QSslConfiguration::defaultConfiguration())
{
commandSocket.setObjectName(QLatin1String("QFtpPI_socket"));
connect(&commandSocket, SIGNAL(hostFound()),
@ -811,6 +877,34 @@ QFtpPI::QFtpPI(QObject *parent) :
connect(&dtp, SIGNAL(connectState(int)),
SLOT(dtpConnectState(int)));
connect(&commandSocket, SIGNAL(encrypted()),
SIGNAL(encrypted()));
connect(&commandSocket, SIGNAL(encrypted()),
SLOT(connectionEncrypted()));
connect(&commandSocket, SIGNAL(sslErrors ( const QList<QSslError> & ) ),
SLOT(sslErrors ( const QList<QSslError> & ) ));
// additional ssl settings
ssl_config.setProtocol(QSsl::TlsV1_2);
ssl_config.setPeerVerifyMode(QSslSocket::VerifyPeer);
commandSocket.setSslConfiguration(ssl_config);
}
void QFtpPI::sslErrors ( const QList<QSslError> & errors )
{
if (_ignoreSslErrors) {
commandSocket.ignoreSslErrors();
return;
}
QString e;
for(int i=0; i< errors.size(); ++i)
{
e.append((errors[i].errorString())+".\n");
}
emit error((int)QFtp::SslError, e);
}
void QFtpPI::connectToHost(const QString &host, quint16 port)
@ -822,6 +916,9 @@ void QFtpPI::connectToHost(const QString &host, quint16 port)
dtp.setProperty("_q_networksession", property("_q_networksession"));
#endif
commandSocket.connectToHost(host, port);
// connection timeout
connect(&timer, SIGNAL(timeout()), this, SLOT(check_connectToHost_timeout()));
timer.start(55000);
}
/*
@ -880,6 +977,8 @@ void QFtpPI::hostFound()
void QFtpPI::connected()
{
timer.stop();
state = Begin;
#if defined(QFTPPI_DEBUG)
// qDebug("QFtpPI state: %d [connected()]", state);
@ -896,6 +995,12 @@ void QFtpPI::connectionClosed()
emit connectState(QFtp::Unconnected);
}
void QFtpPI::connectionEncrypted()
{
waitForDtpToConnect = false;
startNextCmd();
}
void QFtpPI::delayedCloseFinished()
{
emit connectState(QFtp::Unconnected);
@ -918,6 +1023,15 @@ void QFtpPI::error(QAbstractSocket::SocketError e)
}
}
void QFtpPI::check_connectToHost_timeout()
{
if (!commandSocket.waitForConnected(1)){
emit connectState(QFtp::Unconnected);
emit error(QFtp::ConnectionRefused,
QFtp::tr("Connection timed out to host %1").arg(commandSocket.peerName()));
}
}
void QFtpPI::readyRead()
{
if (waitForDtpToClose)
@ -1069,6 +1183,10 @@ bool QFtpPI::processReply()
QString host = lst[1] + QLatin1Char('.') + lst[2] + QLatin1Char('.') + lst[3] + QLatin1Char('.') + lst[4];
quint16 port = (lst[5].toUInt() << 8) + lst[6].toUInt();
waitForDtpToConnect = true;
#ifndef QT_NO_BEARERMANAGEMENT
//copy network session down to the socket
dtp.setProperty("_q_networksession", commandSocket.property("_q_networksession"));
#endif
dtp.connectToHost(host, port);
}
} else if (replyCodeInt == 229) {
@ -1102,8 +1220,16 @@ bool QFtpPI::processReply()
if (currentCmd.startsWith(QLatin1String("SIZE ")))
dtp.setBytesTotal(replyText.simplified().toLongLong());
} else if (replyCode[0]==1 && currentCmd.startsWith(QLatin1String("STOR "))) {
dtp.waitForConnection();
if (!tls) dtp.waitForConnection();
dtp.writeData();
} else if (replyCodeInt == 234 && tls) //TLS OK
{
commandSocket.startClientEncryption();
waitForDtpToConnect = true; // TODO: use other variable or rename
}
else if (replyCodeInt == 235 && tls) //TLS security data needed
{
state = Failure;
}
// react on new state
@ -1176,7 +1302,10 @@ bool QFtpPI::startNextCmd()
// the address/port arguments are edited in.
QHostAddress address = commandSocket.localAddress();
if (currentCmd.startsWith(QLatin1String("PORT"))) {
if ((address.protocol() == QTcpSocket::IPv6Protocol) && transferConnectionExtended) {
if (tls) {
qDebug() << "PORT command ignored in tls mode"; //TODO
return false;
} else if ((address.protocol() == QTcpSocket::IPv6Protocol) || transferConnectionExtended) {
int port = dtp.setupListener(address);
currentCmd = QLatin1String("EPRT |");
currentCmd += (address.protocol() == QTcpSocket::IPv4Protocol) ? QLatin1Char('1') : QLatin1Char('2');
@ -1203,7 +1332,7 @@ bool QFtpPI::startNextCmd()
currentCmd += QLatin1String("\r\n");
} else if (currentCmd.startsWith(QLatin1String("PASV"))) {
if ((address.protocol() == QTcpSocket::IPv6Protocol) && transferConnectionExtended)
if ((address.protocol() == QTcpSocket::IPv6Protocol) || transferConnectionExtended)
currentCmd = QLatin1String("EPSV\r\n");
}
@ -1212,7 +1341,7 @@ bool QFtpPI::startNextCmd()
qDebug("QFtpPI send: %s", currentCmd.left(currentCmd.length()-2).toLatin1().constData());
#endif
state = Waiting;
commandSocket.write(currentCmd.toLatin1());
commandSocket.write(currentCmd.toLocal8Bit());
return true;
}
@ -1650,6 +1779,21 @@ int QFtp::connectToHost(const QString &host, quint16 port)
return id;
}
void QFtp::addCaCertificates(QList<QSslCertificate> certs)
{
d->pi.addCaCertificates(certs);
}
void QFtp::ignoreSslErrors(const bool ignore)
{
d->pi.ignoreSslErrors(ignore);
}
void QFtp::setTls(bool tls)
{
return d->pi.setTls(tls);
}
/*!
Logs in to the FTP server with the username \a user and the
password \a password.
@ -1671,8 +1815,21 @@ int QFtp::connectToHost(const QString &host, quint16 port)
int QFtp::login(const QString &user, const QString &password)
{
QStringList cmds;
if(d->pi.isTls()){
cmds << (QLatin1String("AUTH TLS\r\n"));
}
cmds << (QLatin1String("USER ") + (user.isNull() ? QLatin1String("anonymous") : user) + QLatin1String("\r\n"));
cmds << (QLatin1String("PASS ") + (password.isNull() ? QLatin1String("anonymous@") : password) + QLatin1String("\r\n"));
//Switch to TLS on the data-channel
if(d->pi.isTls())
{
cmds << (QLatin1String("PBSZ 0\r\n"));
cmds << (QLatin1String("PROT P\r\n"));
}
return d->addCommand(new QFtpCommand(Login, cmds));
}
@ -1747,7 +1904,7 @@ int QFtp::list(const QString &dir)
{
QStringList cmds;
cmds << QLatin1String("TYPE A\r\n");
cmds << QLatin1String(d->transferMode == Passive ? "PASV\r\n" : "PORT\r\n");
cmds << QLatin1String(d->transferMode == Passive ? "EPSV\r\n" : "PORT\r\n");
if (dir.isEmpty())
cmds << QLatin1String("LIST\r\n");
else
@ -1822,7 +1979,7 @@ int QFtp::get(const QString &file, QIODevice *dev, TransferType type)
else
cmds << QLatin1String("TYPE A\r\n");
cmds << QLatin1String("SIZE ") + file + QLatin1String("\r\n");
cmds << QLatin1String(d->transferMode == Passive ? "PASV\r\n" : "PORT\r\n");
cmds << QLatin1String(d->transferMode == Passive ? "EPSV\r\n" : "PORT\r\n");
cmds << QLatin1String("RETR ") + file + QLatin1String("\r\n");
return d->addCommand(new QFtpCommand(Get, cmds, dev));
}
@ -1858,7 +2015,7 @@ int QFtp::put(const QByteArray &data, const QString &file, TransferType type)
cmds << QLatin1String("TYPE I\r\n");
else
cmds << QLatin1String("TYPE A\r\n");
cmds << QLatin1String(d->transferMode == Passive ? "PASV\r\n" : "PORT\r\n");
cmds << QLatin1String(d->transferMode == Passive ? "EPSV\r\n" : "PORT\r\n");
cmds << QLatin1String("ALLO ") + QString::number(data.size()) + QLatin1String("\r\n");
cmds << QLatin1String("STOR ") + file + QLatin1String("\r\n");
return d->addCommand(new QFtpCommand(Put, cmds, data));
@ -1885,7 +2042,7 @@ int QFtp::put(QIODevice *dev, const QString &file, TransferType type)
cmds << QLatin1String("TYPE I\r\n");
else
cmds << QLatin1String("TYPE A\r\n");
cmds << QLatin1String(d->transferMode == Passive ? "PASV\r\n" : "PORT\r\n");
cmds << QLatin1String(d->transferMode == Passive ? "EPSV\r\n" : "PORT\r\n");
if (!dev->isSequential())
cmds << QLatin1String("ALLO ") + QString::number(dev->size()) + QLatin1String("\r\n");
cmds << QLatin1String("STOR ") + file + QLatin1String("\r\n");
@ -2311,39 +2468,39 @@ void QFtpPrivate::_q_piError(int errorCode, const QString &text)
error = QFtp::Error(errorCode);
switch (q->currentCommand()) {
case QFtp::ConnectToHost:
errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Connecting to host failed:\n%1"))
errorString = QFtp::tr("Connecting to host failed:\n%1")
.arg(text);
break;
case QFtp::Login:
errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Login failed:\n%1"))
errorString = QFtp::tr("Login failed:\n%1")
.arg(text);
break;
case QFtp::List:
errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Listing directory failed:\n%1"))
errorString = QFtp::tr("Listing directory failed:\n%1")
.arg(text);
break;
case QFtp::Cd:
errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Changing directory failed:\n%1"))
errorString = QFtp::tr("Changing directory failed:\n%1")
.arg(text);
break;
case QFtp::Get:
errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Downloading file failed:\n%1"))
errorString = QFtp::tr("Downloading file failed:\n%1")
.arg(text);
break;
case QFtp::Put:
errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Uploading file failed:\n%1"))
errorString = QFtp::tr("Uploading file failed:\n%1")
.arg(text);
break;
case QFtp::Remove:
errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Removing file failed:\n%1"))
errorString = QFtp::tr("Removing file failed:\n%1")
.arg(text);
break;
case QFtp::Mkdir:
errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Creating directory failed:\n%1"))
errorString = QFtp::tr("Creating directory failed:\n%1")
.arg(text);
break;
case QFtp::Rmdir:
errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Removing directory failed:\n%1"))
errorString = QFtp::tr("Removing directory failed:\n%1")
.arg(text);
break;
default:
@ -2371,7 +2528,7 @@ void QFtpPrivate::_q_piConnectState(int connectState)
emit q_func()->stateChanged(state);
if (close_waitForStateChange) {
close_waitForStateChange = false;
_q_piFinished(QLatin1String(QT_TRANSLATE_NOOP("QFtp", "Connection closed")));
_q_piFinished(QFtp::tr("Connection closed"));
}
}

View File

@ -43,10 +43,12 @@
#define QFTP_H
#include <QtCore/qstring.h>
#include "qurlinfo.h"
#include <QtCore/qobject.h>
#include <QtFtp/qurlinfo.h>
#include <QList>
#include <QSslCertificate>
QT_BEGIN_HEADER
QT_BEGIN_NAMESPACE
class QFtpPrivate;
@ -71,7 +73,8 @@ public:
UnknownError,
HostNotFound,
ConnectionRefused,
NotConnected
NotConnected,
SslError
};
enum Command {
None,
@ -101,6 +104,9 @@ public:
int setProxy(const QString &host, quint16 port);
int connectToHost(const QString &host, quint16 port=21);
void addCaCertificates(QList<QSslCertificate> certs);
void ignoreSslErrors(const bool ignore);
void setTls(bool tls);
int login(const QString &user = QString(), const QString &password = QString());
int close();
int setTransferMode(TransferMode mode);
@ -156,6 +162,6 @@ private:
Q_PRIVATE_SLOT(d, void _q_piFtpReply(int, const QString&))
};
QT_END_HEADER
QT_END_NAMESPACE
#endif // QFTP_H

View File

@ -46,11 +46,8 @@
#include <QtCore/qstring.h>
#include <QtCore/qiodevice.h>
QT_BEGIN_HEADER
QT_BEGIN_NAMESPACE
class QUrl;
class QUrlInfoPrivate;
@ -121,6 +118,4 @@ private:
QT_END_NAMESPACE
QT_END_HEADER
#endif // QURLINFO_H

View File

@ -2,3 +2,4 @@ TEMPLATE = subdirs
SUBDIRS += headersclean
SUBDIRS += qftp
SUBDIRS += cmake

View File

@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 2.8)
project(qmake_cmake_files)
enable_testing()
find_package(Qt5Core REQUIRED)
include("${_Qt5CTestMacros}")
test_module_includes(
Ftp QFtp
)

View File

@ -0,0 +1,7 @@
# Cause make to do nothing.
TEMPLATE = subdirs
CMAKE_QT_MODULES_UNDER_TEST = ftp
CONFIG += ctest_testcase