diff --git a/src/core/ktcpsocket.cpp b/src/core/ktcpsocket.cpp index b511f2e2..80af185b 100644 --- a/src/core/ktcpsocket.cpp +++ b/src/core/ktcpsocket.cpp @@ -1,1056 +1,1064 @@ /* This file is part of the KDE libraries Copyright (C) 2007, 2008 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ktcpsocket.h" #include "ksslerror_p.h" #include "kiocoredebug.h" #include #include #include #include #include #include #include #include static KTcpSocket::SslVersion kSslVersionFromQ(QSsl::SslProtocol protocol) { switch (protocol) { case QSsl::SslV2: return KTcpSocket::SslV2; case QSsl::SslV3: return KTcpSocket::SslV3; case QSsl::TlsV1_0: return KTcpSocket::TlsV1; case QSsl::TlsV1_1: return KTcpSocket::TlsV1_1; case QSsl::TlsV1_2: return KTcpSocket::TlsV1_2; #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) case QSsl::TlsV1_3: return KTcpSocket::TlsV1_3; #endif case QSsl::AnyProtocol: return KTcpSocket::AnySslVersion; case QSsl::TlsV1SslV3: return KTcpSocket::TlsV1SslV3; case QSsl::SecureProtocols: return KTcpSocket::SecureProtocols; default: return KTcpSocket::UnknownSslVersion; } } static QSsl::SslProtocol qSslProtocolFromK(KTcpSocket::SslVersion sslVersion) { //### this lowlevel bit-banging is a little dangerous and a likely source of bugs if (sslVersion == KTcpSocket::AnySslVersion) { return QSsl::AnyProtocol; } //does it contain any valid protocol? KTcpSocket::SslVersions validVersions(KTcpSocket::SslV2 | KTcpSocket::SslV3 | KTcpSocket::TlsV1); validVersions |= KTcpSocket::TlsV1_1; validVersions |= KTcpSocket::TlsV1_2; #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) validVersions |= KTcpSocket::TlsV1_3; #endif validVersions |= KTcpSocket::TlsV1SslV3; validVersions |= KTcpSocket::SecureProtocols; if (!(sslVersion & validVersions)) { return QSsl::UnknownProtocol; } switch (sslVersion) { case KTcpSocket::SslV2: return QSsl::SslV2; case KTcpSocket::SslV3: return QSsl::SslV3; case KTcpSocket::TlsV1_0: return QSsl::TlsV1_0; case KTcpSocket::TlsV1_1: return QSsl::TlsV1_1; case KTcpSocket::TlsV1_2: return QSsl::TlsV1_2; #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) case KTcpSocket::TlsV1_3: return QSsl::TlsV1_3; #endif case KTcpSocket::TlsV1SslV3: return QSsl::TlsV1SslV3; case KTcpSocket::SecureProtocols: return QSsl::SecureProtocols; default: //QSslSocket doesn't really take arbitrary combinations. It's one or all. return QSsl::AnyProtocol; } } static QString protocolString(QSsl::SslProtocol protocol) { switch (protocol) { case QSsl::SslV2: return QStringLiteral("SSLv2"); case QSsl::SslV3: return QStringLiteral("SSLv3"); case QSsl::TlsV1_0: return QStringLiteral("TLSv1.0"); case QSsl::TlsV1_1: return QStringLiteral("TLSv1.1"); case QSsl::TlsV1_2: return QStringLiteral("TLSv1.2"); #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) case QSsl::TlsV1_3: return QStringLiteral("TLSv1.3"); #endif default: return QStringLiteral("Unknown");; } } //cipher class converter KSslCipher -> QSslCipher class CipherCc { public: CipherCc() { const QList list = QSslConfiguration::supportedCiphers(); for (const QSslCipher &c : list) { allCiphers.insert(c.name(), c); } } QSslCipher converted(const KSslCipher &ksc) { return allCiphers.value(ksc.name()); } private: QHash allCiphers; }; KSslError::Error KSslErrorPrivate::errorFromQSslError(QSslError::SslError e) { switch (e) { case QSslError::NoError: return KSslError::NoError; case QSslError::UnableToGetLocalIssuerCertificate: case QSslError::InvalidCaCertificate: return KSslError::InvalidCertificateAuthorityCertificate; case QSslError::InvalidNotBeforeField: case QSslError::InvalidNotAfterField: case QSslError::CertificateNotYetValid: case QSslError::CertificateExpired: return KSslError::ExpiredCertificate; case QSslError::UnableToDecodeIssuerPublicKey: case QSslError::SubjectIssuerMismatch: case QSslError::AuthorityIssuerSerialNumberMismatch: return KSslError::InvalidCertificate; case QSslError::SelfSignedCertificate: case QSslError::SelfSignedCertificateInChain: return KSslError::SelfSignedCertificate; case QSslError::CertificateRevoked: return KSslError::RevokedCertificate; case QSslError::InvalidPurpose: return KSslError::InvalidCertificatePurpose; case QSslError::CertificateUntrusted: return KSslError::UntrustedCertificate; case QSslError::CertificateRejected: return KSslError::RejectedCertificate; case QSslError::NoPeerCertificate: return KSslError::NoPeerCertificate; case QSslError::HostNameMismatch: return KSslError::HostNameMismatch; case QSslError::UnableToVerifyFirstCertificate: case QSslError::UnableToDecryptCertificateSignature: case QSslError::UnableToGetIssuerCertificate: case QSslError::CertificateSignatureFailed: return KSslError::CertificateSignatureFailed; case QSslError::PathLengthExceeded: return KSslError::PathLengthExceeded; case QSslError::UnspecifiedError: case QSslError::NoSslSupport: default: return KSslError::UnknownError; } } QSslError::SslError KSslErrorPrivate::errorFromKSslError(KSslError::Error e) { switch (e) { case KSslError::NoError: return QSslError::NoError; case KSslError::InvalidCertificateAuthorityCertificate: return QSslError::InvalidCaCertificate; case KSslError::InvalidCertificate: return QSslError::UnableToDecodeIssuerPublicKey; case KSslError::CertificateSignatureFailed: return QSslError::CertificateSignatureFailed; case KSslError::SelfSignedCertificate: return QSslError::SelfSignedCertificate; case KSslError::ExpiredCertificate: return QSslError::CertificateExpired; case KSslError::RevokedCertificate: return QSslError::CertificateRevoked; case KSslError::InvalidCertificatePurpose: return QSslError::InvalidPurpose; case KSslError::RejectedCertificate: return QSslError::CertificateRejected; case KSslError::UntrustedCertificate: return QSslError::CertificateUntrusted; case KSslError::NoPeerCertificate: return QSslError::NoPeerCertificate; case KSslError::HostNameMismatch: return QSslError::HostNameMismatch; case KSslError::PathLengthExceeded: return QSslError::PathLengthExceeded; case KSslError::UnknownError: default: return QSslError::UnspecifiedError; } } KSslError::KSslError(Error errorCode, const QSslCertificate &certificate) : d(new KSslErrorPrivate()) { d->error = QSslError(d->errorFromKSslError(errorCode), certificate); } KSslError::KSslError(const QSslError &other) : d(new KSslErrorPrivate()) { d->error = other; } KSslError::KSslError(const KSslError &other) : d(new KSslErrorPrivate()) { *d = *other.d; } KSslError::~KSslError() { delete d; } KSslError &KSslError::operator=(const KSslError &other) { *d = *other.d; return *this; } KSslError::Error KSslError::error() const { return KSslErrorPrivate::errorFromQSslError(d->error.error()); } QString KSslError::errorString() const { return d->error.errorString(); } QSslCertificate KSslError::certificate() const { return d->error.certificate(); } QSslError KSslError::sslError() const { return d->error; } class KTcpSocketPrivate { public: explicit KTcpSocketPrivate(KTcpSocket *qq) : q(qq), certificatesLoaded(false), emittedReadyRead(false) { // create the instance, which sets Qt's static internal cert set to empty. KSslCertificateManager::self(); } KTcpSocket::State state(QAbstractSocket::SocketState s) { switch (s) { case QAbstractSocket::UnconnectedState: return KTcpSocket::UnconnectedState; case QAbstractSocket::HostLookupState: return KTcpSocket::HostLookupState; case QAbstractSocket::ConnectingState: return KTcpSocket::ConnectingState; case QAbstractSocket::ConnectedState: return KTcpSocket::ConnectedState; case QAbstractSocket::ClosingState: return KTcpSocket::ClosingState; case QAbstractSocket::BoundState: case QAbstractSocket::ListeningState: //### these two are not relevant as long as this can't be a server socket default: return KTcpSocket::UnconnectedState; //the closest to "error" } } KTcpSocket::EncryptionMode encryptionMode(QSslSocket::SslMode mode) { switch (mode) { case QSslSocket::SslClientMode: return KTcpSocket::SslClientMode; case QSslSocket::SslServerMode: return KTcpSocket::SslServerMode; default: return KTcpSocket::UnencryptedMode; } } KTcpSocket::Error errorFromAbsSocket(QAbstractSocket::SocketError e) { switch (e) { case QAbstractSocket::ConnectionRefusedError: return KTcpSocket::ConnectionRefusedError; case QAbstractSocket::RemoteHostClosedError: return KTcpSocket::RemoteHostClosedError; case QAbstractSocket::HostNotFoundError: return KTcpSocket::HostNotFoundError; case QAbstractSocket::SocketAccessError: return KTcpSocket::SocketAccessError; case QAbstractSocket::SocketResourceError: return KTcpSocket::SocketResourceError; case QAbstractSocket::SocketTimeoutError: return KTcpSocket::SocketTimeoutError; case QAbstractSocket::NetworkError: return KTcpSocket::NetworkError; case QAbstractSocket::UnsupportedSocketOperationError: return KTcpSocket::UnsupportedSocketOperationError; case QAbstractSocket::SslHandshakeFailedError: return KTcpSocket::SslHandshakeFailedError; case QAbstractSocket::DatagramTooLargeError: //we don't do UDP case QAbstractSocket::AddressInUseError: case QAbstractSocket::SocketAddressNotAvailableError: //### own values if/when we ever get server socket support case QAbstractSocket::ProxyAuthenticationRequiredError: //### maybe we need an enum value for this case QAbstractSocket::UnknownSocketError: default: return KTcpSocket::UnknownError; } } //private slots void reemitSocketError(QAbstractSocket::SocketError e) { q->setErrorString(sock.errorString()); emit q->error(errorFromAbsSocket(e)); } void reemitSslErrors(const QList &errors) { q->setErrorString(sock.errorString()); q->showSslErrors(); //H4X QList kErrors; kErrors.reserve(errors.size()); for (const QSslError &e : errors) { kErrors.append(KSslError(e)); } emit q->sslErrors(kErrors); } void reemitStateChanged(QAbstractSocket::SocketState s) { emit q->stateChanged(state(s)); } void reemitModeChanged(QSslSocket::SslMode m) { emit q->encryptionModeChanged(encryptionMode(m)); } // This method is needed because we might emit readyRead() due to this QIODevice // having some data buffered, so we need to care about blocking, too. //### useless ATM as readyRead() now just calls d->sock.readyRead(). void reemitReadyRead() { if (!emittedReadyRead) { emittedReadyRead = true; emit q->readyRead(); emittedReadyRead = false; } } void maybeLoadCertificates() { if (!certificatesLoaded) { q->setCaCertificates(KSslCertificateManager::self()->caCertificates()); } } KTcpSocket *const q; bool certificatesLoaded; bool emittedReadyRead; QSslSocket sock; QList ciphers; KTcpSocket::SslVersion advertisedSslVersion; CipherCc ccc; }; KTcpSocket::KTcpSocket(QObject *parent) : QIODevice(parent), d(new KTcpSocketPrivate(this)) { d->advertisedSslVersion = SslV3; connect(&d->sock, &QIODevice::aboutToClose, this, &QIODevice::aboutToClose); connect(&d->sock, &QIODevice::bytesWritten, this, &QIODevice::bytesWritten); connect(&d->sock, &QSslSocket::encryptedBytesWritten, this, &KTcpSocket::encryptedBytesWritten); connect(&d->sock, SIGNAL(readyRead()), this, SLOT(reemitReadyRead())); connect(&d->sock, &QAbstractSocket::connected, this, &KTcpSocket::connected); connect(&d->sock, &QSslSocket::encrypted, this, &KTcpSocket::encrypted); connect(&d->sock, &QAbstractSocket::disconnected, this, &KTcpSocket::disconnected); #ifndef QT_NO_NETWORKPROXY connect(&d->sock, &QAbstractSocket::proxyAuthenticationRequired, this, &KTcpSocket::proxyAuthenticationRequired); #endif connect(&d->sock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(reemitSocketError(QAbstractSocket::SocketError))); connect(&d->sock, SIGNAL(sslErrors(QList)), this, SLOT(reemitSslErrors(QList))); connect(&d->sock, &QAbstractSocket::hostFound, this, &KTcpSocket::hostFound); connect(&d->sock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(reemitStateChanged(QAbstractSocket::SocketState))); connect(&d->sock, SIGNAL(modeChanged(QSslSocket::SslMode)), this, SLOT(reemitModeChanged(QSslSocket::SslMode))); } KTcpSocket::~KTcpSocket() { delete d; } ////////////////////////////// (mostly) virtuals from QIODevice bool KTcpSocket::atEnd() const { return d->sock.atEnd() && QIODevice::atEnd(); } qint64 KTcpSocket::bytesAvailable() const { return d->sock.bytesAvailable() + QIODevice::bytesAvailable(); } qint64 KTcpSocket::bytesToWrite() const { return d->sock.bytesToWrite(); } bool KTcpSocket::canReadLine() const { return d->sock.canReadLine() || QIODevice::canReadLine(); } void KTcpSocket::close() { d->sock.close(); QIODevice::close(); } bool KTcpSocket::isSequential() const { return true; } bool KTcpSocket::open(QIODevice::OpenMode open) { bool ret = d->sock.open(open); setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); return ret; } bool KTcpSocket::waitForBytesWritten(int msecs) { return d->sock.waitForBytesWritten(msecs); } bool KTcpSocket::waitForReadyRead(int msecs) { return d->sock.waitForReadyRead(msecs); } qint64 KTcpSocket::readData(char *data, qint64 maxSize) { return d->sock.read(data, maxSize); } qint64 KTcpSocket::writeData(const char *data, qint64 maxSize) { return d->sock.write(data, maxSize); } ////////////////////////////// public methods from QAbstractSocket void KTcpSocket::abort() { d->sock.abort(); } void KTcpSocket::connectToHost(const QString &hostName, quint16 port, ProxyPolicy policy) { if (policy == AutoProxy) { //### } d->sock.connectToHost(hostName, port); // there are enough layers of buffers between us and the network, and there is a quirk // in QIODevice that can make it try to readData() twice per read() call if buffered and // reaData() does not deliver enough data the first time. like when the other side is // simply not sending any more data... // this can *apparently* lead to long delays sometimes which stalls applications. // do not want. setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); } void KTcpSocket::connectToHost(const QHostAddress &hostAddress, quint16 port, ProxyPolicy policy) { if (policy == AutoProxy) { //### } d->sock.connectToHost(hostAddress, port); setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); } void KTcpSocket::connectToHost(const QUrl &url, ProxyPolicy policy) { if (policy == AutoProxy) { //### } d->sock.connectToHost(url.host(), url.port()); setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); } void KTcpSocket::disconnectFromHost() { d->sock.disconnectFromHost(); setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); } KTcpSocket::Error KTcpSocket::error() const { return d->errorFromAbsSocket(d->sock.error()); } QList KTcpSocket::sslErrors() const { //### pretty slow; also consider throwing out duplicate error codes. We may get // duplicates even though there were none in the original list because KSslError // has a smallest common denominator range of SSL error codes. const auto qsslErrors = d->sock.sslErrors(); QList ret; ret.reserve(qsslErrors.size()); for (const QSslError &e : qsslErrors) { ret.append(KSslError(e)); } return ret; } bool KTcpSocket::flush() { return d->sock.flush(); } bool KTcpSocket::isValid() const { return d->sock.isValid(); } QHostAddress KTcpSocket::localAddress() const { return d->sock.localAddress(); } QHostAddress KTcpSocket::peerAddress() const { return d->sock.peerAddress(); } QString KTcpSocket::peerName() const { return d->sock.peerName(); } quint16 KTcpSocket::peerPort() const { return d->sock.peerPort(); } #ifndef QT_NO_NETWORKPROXY QNetworkProxy KTcpSocket::proxy() const { return d->sock.proxy(); } #endif qint64 KTcpSocket::readBufferSize() const { return d->sock.readBufferSize(); } #ifndef QT_NO_NETWORKPROXY void KTcpSocket::setProxy(const QNetworkProxy &proxy) { d->sock.setProxy(proxy); } #endif void KTcpSocket::setReadBufferSize(qint64 size) { d->sock.setReadBufferSize(size); } KTcpSocket::State KTcpSocket::state() const { return d->state(d->sock.state()); } bool KTcpSocket::waitForConnected(int msecs) { bool ret = d->sock.waitForConnected(msecs); if (!ret) { setErrorString(d->sock.errorString()); } setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); return ret; } bool KTcpSocket::waitForDisconnected(int msecs) { bool ret = d->sock.waitForDisconnected(msecs); if (!ret) { setErrorString(d->sock.errorString()); } setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); return ret; } ////////////////////////////// public methods from QSslSocket void KTcpSocket::addCaCertificate(const QSslCertificate &certificate) { d->maybeLoadCertificates(); +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) d->sock.addCaCertificate(certificate); +#else + d->sock.sslConfiguration().addCaCertificate(certificate); +#endif } /* bool KTcpSocket::addCaCertificates(const QString &path, QSsl::EncodingFormat format, QRegExp::PatternSyntax syntax) { d->maybeLoadCertificates(); return d->sock.addCaCertificates(path, format, syntax); } */ void KTcpSocket::addCaCertificates(const QList &certificates) { d->maybeLoadCertificates(); +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) d->sock.addCaCertificates(certificates); +#else + d->sock.sslConfiguration().addCaCertificates(certificates); +#endif } QList KTcpSocket::caCertificates() const { d->maybeLoadCertificates(); return d->sock.sslConfiguration().caCertificates(); } QList KTcpSocket::ciphers() const { return d->ciphers; } void KTcpSocket::connectToHostEncrypted(const QString &hostName, quint16 port, OpenMode openMode) { d->maybeLoadCertificates(); d->sock.setProtocol(qSslProtocolFromK(d->advertisedSslVersion)); d->sock.connectToHostEncrypted(hostName, port, openMode); setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); } QSslCertificate KTcpSocket::localCertificate() const { return d->sock.localCertificate(); } QList KTcpSocket::peerCertificateChain() const { return d->sock.peerCertificateChain(); } KSslKey KTcpSocket::privateKey() const { return KSslKey(d->sock.privateKey()); } KSslCipher KTcpSocket::sessionCipher() const { return KSslCipher(d->sock.sessionCipher()); } void KTcpSocket::setCaCertificates(const QList &certificates) { QSslConfiguration configuration = d->sock.sslConfiguration(); configuration.setCaCertificates(certificates); d->sock.setSslConfiguration(configuration); d->certificatesLoaded = true; } void KTcpSocket::setCiphers(const QList &ciphers) { d->ciphers = ciphers; QList cl; cl.reserve(d->ciphers.size()); for (const KSslCipher &c : ciphers) { cl.append(d->ccc.converted(c)); } QSslConfiguration configuration = d->sock.sslConfiguration(); configuration.setCiphers(cl); d->sock.setSslConfiguration(configuration); } void KTcpSocket::setLocalCertificate(const QSslCertificate &certificate) { d->sock.setLocalCertificate(certificate); } void KTcpSocket::setLocalCertificate(const QString &fileName, QSsl::EncodingFormat format) { d->sock.setLocalCertificate(fileName, format); } void KTcpSocket::setVerificationPeerName(const QString &hostName) { d->sock.setPeerVerifyName(hostName); } void KTcpSocket::setPrivateKey(const KSslKey &key) { // We cannot map KSslKey::Algorithm:Dh to anything in QSsl::KeyAlgorithm. if (key.algorithm() == KSslKey::Dh) { return; } QSslKey _key(key.toDer(), (key.algorithm() == KSslKey::Rsa) ? QSsl::Rsa : QSsl::Dsa, QSsl::Der, (key.secrecy() == KSslKey::PrivateKey) ? QSsl::PrivateKey : QSsl::PublicKey); d->sock.setPrivateKey(_key); } void KTcpSocket::setPrivateKey(const QString &fileName, KSslKey::Algorithm algorithm, QSsl::EncodingFormat format, const QByteArray &passPhrase) { // We cannot map KSslKey::Algorithm:Dh to anything in QSsl::KeyAlgorithm. if (algorithm == KSslKey::Dh) { return; } d->sock.setPrivateKey(fileName, (algorithm == KSslKey::Rsa) ? QSsl::Rsa : QSsl::Dsa, format, passPhrase); } bool KTcpSocket::waitForEncrypted(int msecs) { return d->sock.waitForEncrypted(msecs); } KTcpSocket::EncryptionMode KTcpSocket::encryptionMode() const { return d->encryptionMode(d->sock.mode()); } QVariant KTcpSocket::socketOption(QAbstractSocket::SocketOption options) const { return d->sock.socketOption(options); } void KTcpSocket::setSocketOption(QAbstractSocket::SocketOption options, const QVariant &value) { d->sock.setSocketOption(options, value); } QSslConfiguration KTcpSocket::sslConfiguration() const { return d->sock.sslConfiguration(); } void KTcpSocket::setSslConfiguration(const QSslConfiguration &configuration) { d->sock.setSslConfiguration(configuration); } //slot void KTcpSocket::ignoreSslErrors() { d->sock.ignoreSslErrors(); } //slot void KTcpSocket::startClientEncryption() { d->maybeLoadCertificates(); d->sock.setProtocol(qSslProtocolFromK(d->advertisedSslVersion)); d->sock.startClientEncryption(); } //debugging H4X void KTcpSocket::showSslErrors() { const QList list = d->sock.sslErrors(); for (const QSslError &e : list) { qCDebug(KIO_CORE) << e.errorString(); } } void KTcpSocket::setAdvertisedSslVersion(KTcpSocket::SslVersion version) { d->advertisedSslVersion = version; } KTcpSocket::SslVersion KTcpSocket::advertisedSslVersion() const { return d->advertisedSslVersion; } KTcpSocket::SslVersion KTcpSocket::negotiatedSslVersion() const { if (!d->sock.isEncrypted()) { return UnknownSslVersion; } return kSslVersionFromQ(d->sock.sessionProtocol()); } QString KTcpSocket::negotiatedSslVersionName() const { if (!d->sock.isEncrypted()) { return QString(); } return protocolString(d->sock.sessionProtocol()); } ////////////////////////////// KSslKey class KSslKeyPrivate { public: KSslKey::Algorithm convertAlgorithm(QSsl::KeyAlgorithm a) { switch (a) { case QSsl::Dsa: return KSslKey::Dsa; default: return KSslKey::Rsa; } } KSslKey::Algorithm algorithm; KSslKey::KeySecrecy secrecy; bool isExportable; QByteArray der; }; KSslKey::KSslKey() : d(new KSslKeyPrivate) { d->algorithm = Rsa; d->secrecy = PublicKey; d->isExportable = true; } KSslKey::KSslKey(const KSslKey &other) : d(new KSslKeyPrivate) { *d = *other.d; } KSslKey::KSslKey(const QSslKey &qsk) : d(new KSslKeyPrivate) { d->algorithm = d->convertAlgorithm(qsk.algorithm()); d->secrecy = (qsk.type() == QSsl::PrivateKey) ? PrivateKey : PublicKey; d->isExportable = true; d->der = qsk.toDer(); } KSslKey::~KSslKey() { delete d; } KSslKey &KSslKey::operator=(const KSslKey &other) { *d = *other.d; return *this; } KSslKey::Algorithm KSslKey::algorithm() const { return d->algorithm; } bool KSslKey::isExportable() const { return d->isExportable; } KSslKey::KeySecrecy KSslKey::secrecy() const { return d->secrecy; } QByteArray KSslKey::toDer() const { return d->der; } ////////////////////////////// KSslCipher //nice-to-have: make implicitly shared class KSslCipherPrivate { public: QString authenticationMethod; QString encryptionMethod; QString keyExchangeMethod; QString name; bool isNull; int supportedBits; int usedBits; }; KSslCipher::KSslCipher() : d(new KSslCipherPrivate) { d->isNull = true; d->supportedBits = 0; d->usedBits = 0; } KSslCipher::KSslCipher(const KSslCipher &other) : d(new KSslCipherPrivate) { *d = *other.d; } KSslCipher::KSslCipher(const QSslCipher &qsc) : d(new KSslCipherPrivate) { d->authenticationMethod = qsc.authenticationMethod(); d->encryptionMethod = qsc.encryptionMethod(); //Qt likes to append the number of bits (usedBits?) to the algorithm, //for example "AES(256)". We only want the pure algorithm name, though. int parenIdx = d->encryptionMethod.indexOf(QLatin1Char('(')); if (parenIdx > 0) { d->encryptionMethod.truncate(parenIdx); } d->keyExchangeMethod = qsc.keyExchangeMethod(); d->name = qsc.name(); d->isNull = qsc.isNull(); d->supportedBits = qsc.supportedBits(); d->usedBits = qsc.usedBits(); } KSslCipher::~KSslCipher() { delete d; } KSslCipher &KSslCipher::operator=(const KSslCipher &other) { *d = *other.d; return *this; } bool KSslCipher::isNull() const { return d->isNull; } QString KSslCipher::authenticationMethod() const { return d->authenticationMethod; } QString KSslCipher::encryptionMethod() const { return d->encryptionMethod; } QString KSslCipher::keyExchangeMethod() const { return d->keyExchangeMethod; } QString KSslCipher::digestMethod() const { //### This is not really backend neutral. It works for OpenSSL and // for RFC compliant names, though. if (d->name.endsWith(QLatin1String("SHA"))) { return QStringLiteral("SHA-1"); } else if (d->name.endsWith(QLatin1String("MD5"))) { return QStringLiteral("MD5"); } else { return QString(); } } QString KSslCipher::name() const { return d->name; } int KSslCipher::supportedBits() const { return d->supportedBits; } int KSslCipher::usedBits() const { return d->usedBits; } //static QList KSslCipher::supportedCiphers() { QList ret; const QList candidates = QSslConfiguration::supportedCiphers(); ret.reserve(candidates.size()); for (const QSslCipher &c : candidates) { ret.append(KSslCipher(c)); } return ret; } #include "moc_ktcpsocket.cpp" diff --git a/src/core/slave.cpp b/src/core/slave.cpp index 4002b65c..0e3ef8b0 100644 --- a/src/core/slave.cpp +++ b/src/core/slave.cpp @@ -1,594 +1,602 @@ /* * This file is part of the KDE libraries * Copyright (c) 2000 Waldo Bastian * 2000 Stephan Kulow * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. **/ #include "slave.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "dataprotocol_p.h" #include "connection_p.h" #include "commands_p.h" #include "connectionserver.h" #include "kioglobal_p.h" #include #include // CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 #include "slaveinterface_p.h" #include "kiocoredebug.h" using namespace KIO; #define SLAVE_CONNECTION_TIMEOUT_MIN 2 // Without debug info we consider it an error if the slave doesn't connect // within 10 seconds. // With debug info we give the slave an hour so that developers have a chance // to debug their slave. #ifdef NDEBUG #define SLAVE_CONNECTION_TIMEOUT_MAX 10 #else #define SLAVE_CONNECTION_TIMEOUT_MAX 3600 #endif static QThreadStorage s_kslaveLauncher; static org::kde::KSlaveLauncher *klauncher() { KDEInitInterface::ensureKdeinitRunning(); if (!s_kslaveLauncher.hasLocalData()) { org::kde::KSlaveLauncher *launcher = new org::kde::KSlaveLauncher(QStringLiteral("org.kde.klauncher5"), QStringLiteral("/KLauncher"), QDBusConnection::sessionBus()); s_kslaveLauncher.setLocalData(launcher); return launcher; } return s_kslaveLauncher.localData(); } // In such case we start the slave via QProcess. // It's possible to force this by setting the env. variable // KDE_FORK_SLAVES, Clearcase seems to require this. static QBasicAtomicInt bForkSlaves = #if KIO_FORK_SLAVES Q_BASIC_ATOMIC_INITIALIZER(1); #else Q_BASIC_ATOMIC_INITIALIZER(-1); #endif static bool forkSlaves() { // In such case we start the slave via QProcess. // It's possible to force this by setting the env. variable // KDE_FORK_SLAVES, Clearcase seems to require this. +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) if (bForkSlaves.load() == -1) { +#else + if (bForkSlaves.loadRelaxed() == -1) { +#endif bool fork = qEnvironmentVariableIsSet("KDE_FORK_SLAVES"); // no dbus? => fork slaves as we can't talk to klauncher if (!fork) { fork = !QDBusConnection::sessionBus().interface(); } #ifdef Q_OS_UNIX if (!fork) { // check the UID of klauncher QDBusReply reply = QDBusConnection::sessionBus().interface()->serviceUid(klauncher()->service()); // if reply is not valid, fork, most likely klauncher can not be run or is not installed // fallback: if there's an klauncher process owned by a different user: still fork if (!reply.isValid() || getuid() != reply) { fork = true; } } #endif bForkSlaves.testAndSetRelaxed(-1, fork ? 1 : 0); } +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) return bForkSlaves.load() == 1; +#else + return bForkSlaves.loadRelaxed() == 1; +#endif } namespace KIO { /** * @internal */ class SlavePrivate: public SlaveInterfacePrivate { public: explicit SlavePrivate(const QString &protocol) : m_protocol(protocol), m_slaveProtocol(protocol), slaveconnserver(new KIO::ConnectionServer), m_job(nullptr), m_pid(0), m_port(0), contacted(false), dead(false), m_refCount(1) { contact_started.start(); slaveconnserver->listenForRemote(); if (!slaveconnserver->isListening()) { qCWarning(KIO_CORE) << "KIO Connection server not listening, could not connect"; } } ~SlavePrivate() override { delete slaveconnserver; } QString m_protocol; QString m_slaveProtocol; QString m_host; QString m_user; QString m_passwd; KIO::ConnectionServer *slaveconnserver; KIO::SimpleJob *m_job; qint64 m_pid; quint16 m_port; bool contacted; bool dead; QElapsedTimer contact_started; QElapsedTimer m_idleSince; int m_refCount; }; } void Slave::accept() { Q_D(Slave); d->slaveconnserver->setNextPendingConnection(d->connection); d->slaveconnserver->deleteLater(); d->slaveconnserver = nullptr; connect(d->connection, &Connection::readyRead, this, &Slave::gotInput); } void Slave::timeout() { Q_D(Slave); if (d->dead) { //already dead? then slaveDied was emitted and we are done return; } if (d->connection->isConnected()) { return; } /*qDebug() << "slave failed to connect to application pid=" << d->m_pid << " protocol=" << d->m_protocol;*/ if (d->m_pid && KIOPrivate::isProcessAlive(d->m_pid)) { int delta_t = d->contact_started.elapsed() / 1000; //qDebug() << "slave is slow... pid=" << d->m_pid << " t=" << delta_t; if (delta_t < SLAVE_CONNECTION_TIMEOUT_MAX) { QTimer::singleShot(1000 * SLAVE_CONNECTION_TIMEOUT_MIN, this, &Slave::timeout); return; } } //qDebug() << "Houston, we lost our slave, pid=" << d->m_pid; d->connection->close(); d->dead = true; QString arg = d->m_protocol; if (!d->m_host.isEmpty()) { arg += QLatin1String("://") + d->m_host; } //qDebug() << "slave died pid = " << d->m_pid; ref(); // Tell the job about the problem. emit error(ERR_SLAVE_DIED, arg); // Tell the scheduler about the problem. emit slaveDied(this); // After the above signal we're dead!! deref(); } Slave::Slave(const QString &protocol, QObject *parent) : SlaveInterface(*new SlavePrivate(protocol), parent) { Q_D(Slave); d->slaveconnserver->setParent(this); d->connection = new Connection(this); connect(d->slaveconnserver, &ConnectionServer::newConnection, this, &Slave::accept); } Slave::~Slave() { //qDebug() << "destructing slave object pid = " << d->m_pid; //delete d; } QString Slave::protocol() { Q_D(Slave); return d->m_protocol; } void Slave::setProtocol(const QString &protocol) { Q_D(Slave); d->m_protocol = protocol; } QString Slave::slaveProtocol() { Q_D(Slave); return d->m_slaveProtocol; } QString Slave::host() { Q_D(Slave); return d->m_host; } quint16 Slave::port() { Q_D(Slave); return d->m_port; } QString Slave::user() { Q_D(Slave); return d->m_user; } QString Slave::passwd() { Q_D(Slave); return d->m_passwd; } void Slave::setIdle() { Q_D(Slave); d->m_idleSince.start(); } bool Slave::isConnected() { Q_D(Slave); return d->contacted; } void Slave::setConnected(bool c) { Q_D(Slave); d->contacted = c; } void Slave::ref() { Q_D(Slave); d->m_refCount++; } void Slave::deref() { Q_D(Slave); d->m_refCount--; if (!d->m_refCount) { aboutToDelete(); delete this; // yes it reads funny, but it's too late for a deleteLater() here, no event loop anymore } } void Slave::aboutToDelete() { Q_D(Slave); d->connection->disconnect(this); this->disconnect(); } int Slave::idleTime() { Q_D(Slave); if (!d->m_idleSince.isValid()) { return 0; } return d->m_idleSince.elapsed() / 1000; } void Slave::setPID(qint64 pid) { Q_D(Slave); d->m_pid = pid; } qint64 Slave::slave_pid() { Q_D(Slave); return d->m_pid; } void Slave::setJob(KIO::SimpleJob *job) { Q_D(Slave); if (!d->sslMetaData.isEmpty()) { emit metaData(d->sslMetaData); } d->m_job = job; } KIO::SimpleJob *Slave::job() const { Q_D(const Slave); return d->m_job; } bool Slave::isAlive() { Q_D(Slave); return !d->dead; } void Slave::hold(const QUrl &url) { Q_D(Slave); ref(); { QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); stream << url; d->connection->send(CMD_SLAVE_HOLD, data); d->connection->close(); d->dead = true; emit slaveDied(this); } deref(); // Call KSlaveLauncher::waitForSlave(pid); { klauncher()->waitForSlave(d->m_pid); } } void Slave::suspend() { Q_D(Slave); d->connection->suspend(); } void Slave::resume() { Q_D(Slave); d->connection->resume(); } bool Slave::suspended() { Q_D(Slave); return d->connection->suspended(); } void Slave::send(int cmd, const QByteArray &arr) { Q_D(Slave); d->connection->send(cmd, arr); } void Slave::gotInput() { Q_D(Slave); if (d->dead) { //already dead? then slaveDied was emitted and we are done return; } ref(); if (!dispatch()) { d->connection->close(); d->dead = true; QString arg = d->m_protocol; if (!d->m_host.isEmpty()) { arg += QLatin1String("://") + d->m_host; } //qDebug() << "slave died pid = " << d->m_pid; // Tell the job about the problem. emit error(ERR_SLAVE_DIED, arg); // Tell the scheduler about the problem. emit slaveDied(this); } deref(); // Here we might be dead!! } void Slave::kill() { Q_D(Slave); d->dead = true; // OO can be such simple. //qDebug() << "killing slave pid" << d->m_pid // << "(" << d->m_protocol + QLatin1String("://") + d->m_host << ")"; if (d->m_pid) { KIOPrivate::sendTerminateSignal(d->m_pid); d->m_pid = 0; } } void Slave::setHost(const QString &host, quint16 port, const QString &user, const QString &passwd) { Q_D(Slave); d->m_host = host; d->m_port = port; d->m_user = user; d->m_passwd = passwd; d->sslMetaData.clear(); QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); stream << d->m_host << d->m_port << d->m_user << d->m_passwd; d->connection->send(CMD_HOST, data); } void Slave::resetHost() { Q_D(Slave); d->sslMetaData.clear(); d->m_host = QStringLiteral(""); } void Slave::setConfig(const MetaData &config) { Q_D(Slave); QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); stream << config; d->connection->send(CMD_CONFIG, data); } Slave *Slave::createSlave(const QString &protocol, const QUrl &url, int &error, QString &error_text) { //qDebug() << "createSlave" << protocol << "for" << url; // Firstly take into account all special slaves if (protocol == QLatin1String("data")) { return new DataProtocol(); } Slave *slave = new Slave(protocol); QUrl slaveAddress = slave->d_func()->slaveconnserver->address(); if (slaveAddress.isEmpty()) { error_text = i18n("Can not create socket for launching io-slave for protocol '%1'.", protocol); error = KIO::ERR_CANNOT_CREATE_SLAVE; delete slave; return nullptr; } if (forkSlaves() == 1) { QString _name = KProtocolInfo::exec(protocol); if (_name.isEmpty()) { error_text = i18n("Unknown protocol '%1'.", protocol); error = KIO::ERR_CANNOT_CREATE_SLAVE; delete slave; return nullptr; } // find the kioslave using KPluginLoader; kioslave would do this // anyway, but if it doesn't exist, we want to be able to return // a useful error message immediately QString lib_path = KPluginLoader::findPlugin(_name); if (lib_path.isEmpty()) { error_text = i18n("Can not find io-slave for protocol '%1'.", protocol); error = KIO::ERR_CANNOT_CREATE_SLAVE; delete slave; return nullptr; } const QStringList args = QStringList() << lib_path << protocol << QLatin1String("") << slaveAddress.toString(); //qDebug() << "kioslave" << ", " << lib_path << ", " << protocol << ", " << QString() << ", " << slaveAddress; // look where libexec path is (can be set in qt.conf) const QString qlibexec = QLibraryInfo::location(QLibraryInfo::LibraryExecutablesPath); // on !win32 we use a kf5 suffix const QString qlibexecKF5 = QDir(qlibexec).filePath(QStringLiteral("kf5")); // search paths const QStringList searchPaths = QStringList() << QCoreApplication::applicationDirPath() // then look where our application binary is located << qlibexec << qlibexecKF5 << QFile::decodeName(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5); // look at our installation location QString kioslaveExecutable = QStandardPaths::findExecutable(QStringLiteral("kioslave5"), searchPaths); if (kioslaveExecutable.isEmpty()) { // Fallback to PATH. On win32 we install to bin/ which tests outside // KIO cannot not find at the time ctest is run because it // isn't the same as applicationDirPath(). kioslaveExecutable = QStandardPaths::findExecutable(QStringLiteral("kioslave5")); } if (kioslaveExecutable.isEmpty()) { error_text = i18n("Can not find 'kioslave5' executable at '%1'", searchPaths.join(QLatin1String(", "))); error = KIO::ERR_CANNOT_CREATE_SLAVE; delete slave; return nullptr; } qint64 pid = 0; QProcess::startDetached(kioslaveExecutable, args, QString(), &pid); slave->setPID(pid); return slave; } QString errorStr; QDBusReply reply = klauncher()->requestSlave(protocol, url.host(), slaveAddress.toString(), errorStr); if (!reply.isValid()) { error_text = i18n("Cannot talk to klauncher: %1", klauncher()->lastError().message()); error = KIO::ERR_CANNOT_CREATE_SLAVE; delete slave; return nullptr; } qint64 pid = reply; if (!pid) { error_text = i18n("klauncher said: %1", errorStr); error = KIO::ERR_CANNOT_CREATE_SLAVE; delete slave; return nullptr; } slave->setPID(pid); QTimer::singleShot(1000 * SLAVE_CONNECTION_TIMEOUT_MIN, slave, &Slave::timeout); return slave; } Slave *Slave::holdSlave(const QString &protocol, const QUrl &url) { //qDebug() << "holdSlave" << protocol << "for" << url; // Firstly take into account all special slaves if (protocol == QLatin1String("data")) { return nullptr; } if (forkSlaves()) { return nullptr; } Slave *slave = new Slave(protocol); QUrl slaveAddress = slave->d_func()->slaveconnserver->address(); QDBusReply reply = klauncher()->requestHoldSlave(url.toString(), slaveAddress.toString()); if (!reply.isValid()) { delete slave; return nullptr; } qint64 pid = reply; if (!pid) { delete slave; return nullptr; } slave->setPID(pid); QTimer::singleShot(1000 * SLAVE_CONNECTION_TIMEOUT_MIN, slave, &Slave::timeout); return slave; } bool Slave::checkForHeldSlave(const QUrl &url) { if (forkSlaves()) { return false; } return klauncher()->checkForHeldSlave(url.toString()); } diff --git a/src/widgets/kurlcompletion.cpp b/src/widgets/kurlcompletion.cpp index 97eaa267..5410508d 100644 --- a/src/widgets/kurlcompletion.cpp +++ b/src/widgets/kurlcompletion.cpp @@ -1,1560 +1,1572 @@ /* This file is part of the KDE libraries Copyright (C) 2000 David Smith Copyright (C) 2004 Scott Wheeler This class was inspired by a previous KUrlCompletion by Henner Zeller This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kurlcompletion.h" #include "../pathhelpers_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // QT_LSTAT, QT_STAT, QT_STATBUF #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #else #include #include #endif static bool expandTilde(QString &); static bool expandEnv(QString &); static QString unescape(const QString &text); // Permission mask for files that are executable by // user, group or other #define MODE_EXE (S_IXUSR | S_IXGRP | S_IXOTH) // Constants for types of completion enum ComplType {CTNone = 0, CTEnv, CTUser, CTMan, CTExe, CTFile, CTUrl, CTInfo}; class CompletionThread; // Ensure that we don't end up with "//". static QUrl addPathToUrl(const QUrl &url, const QString &relPath) { QUrl u(url); u.setPath(concatPaths(url.path(), relPath)); return u; } static QBasicAtomicInt s_waitDuration = Q_BASIC_ATOMIC_INITIALIZER(-1); static int initialWaitDuration() { +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) if (s_waitDuration.load() == -1) { +#else + if (s_waitDuration.loadRelaxed() == -1) { +#endif const QByteArray envVar = qgetenv("KURLCOMPLETION_WAIT"); if (envVar.isEmpty()) { s_waitDuration = 200; // default: 200 ms } else { s_waitDuration = envVar.toInt(); } } return s_waitDuration; } /////////////////////////////////////////////////////// /////////////////////////////////////////////////////// // KUrlCompletionPrivate // class KUrlCompletionPrivate { public: explicit KUrlCompletionPrivate(KUrlCompletion *parent) : q(parent), url_auto_completion(true), userListThread(nullptr), dirListThread(nullptr) { } ~KUrlCompletionPrivate(); void _k_slotEntries(KIO::Job *, const KIO::UDSEntryList &); void _k_slotIOFinished(KJob *); void slotCompletionThreadDone(QThread *thread, const QStringList &matches); class MyURL; bool userCompletion(const MyURL &url, QString *match); bool envCompletion(const MyURL &url, QString *match); bool exeCompletion(const MyURL &url, QString *match); bool fileCompletion(const MyURL &url, QString *match); bool urlCompletion(const MyURL &url, QString *match); bool isAutoCompletion(); // List the next dir in m_dirs QString listDirectories(const QStringList &, const QString &, bool only_exe = false, bool only_dir = false, bool no_hidden = false, bool stat_files = true); void listUrls(const QList &urls, const QString &filter = QString(), bool only_exe = false, bool no_hidden = false); void addMatches(const QStringList &); QString finished(); void init(); void setListedUrl(ComplType compl_type, const QString &dir = QString(), const QString &filter = QString(), bool no_hidden = false); bool isListedUrl(ComplType compl_type, const QString &dir = QString(), const QString &filter = QString(), bool no_hidden = false); KUrlCompletion * const q; QList list_urls; bool onlyLocalProto; // urlCompletion() in Auto/Popup mode? bool url_auto_completion; // Append '/' to directories in Popup mode? // Doing that stat's all files and is slower bool popup_append_slash; // Keep track of currently listed files to avoid reading them again bool last_no_hidden; QString last_path_listed; QString last_file_listed; QString last_prepend; ComplType last_compl_type; QUrl cwd; // "current directory" = base dir for completion KUrlCompletion::Mode mode; // ExeCompletion, FileCompletion, DirCompletion bool replace_env; bool replace_home; bool complete_url; // if true completing a URL (i.e. 'prepend' is a URL), otherwise a path KIO::ListJob *list_job; // kio job to list directories QString prepend; // text to prepend to listed items QString compl_text; // text to pass on to KCompletion // Filters for files read with kio bool list_urls_only_exe; // true = only list executables bool list_urls_no_hidden; QString list_urls_filter; // filter for listed files CompletionThread *userListThread; CompletionThread *dirListThread; QStringList mimeTypeFilters; }; class CompletionThread : public QThread { Q_OBJECT protected: CompletionThread(KUrlCompletionPrivate *receiver) : QThread(), m_prepend(receiver->prepend), m_complete_url(receiver->complete_url), m_terminationRequested(false) {} public: void requestTermination() { if (!isFinished()) { qCDebug(KIO_WIDGETS) << "stopping thread" << this; } +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) m_terminationRequested.store(true); +#else + m_terminationRequested.storeRelaxed(true); +#endif wait(); } QStringList matches() const { QMutexLocker locker(&m_mutex); return m_matches; } Q_SIGNALS: void completionThreadDone(QThread *thread, const QStringList &matches); protected: void addMatch(const QString &match) { QMutexLocker locker(&m_mutex); m_matches.append(match); } bool terminationRequested() const { +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) return m_terminationRequested.load(); +#else + return m_terminationRequested.loadRelaxed(); +#endif } void done() { if (!terminationRequested()) { qCDebug(KIO_WIDGETS) << "done, emitting signal with" << m_matches.count() << "matches"; emit completionThreadDone(this, m_matches); } } const QString m_prepend; const bool m_complete_url; // if true completing a URL (i.e. 'm_prepend' is a URL), otherwise a path private: mutable QMutex m_mutex; // protects m_matches QStringList m_matches; // written by secondary thread, read by the matches() method QAtomicInt m_terminationRequested; // used as a bool }; /** * A simple thread that fetches a list of tilde-completions and returns this * to the caller via the completionThreadDone signal. */ class UserListThread : public CompletionThread { Q_OBJECT public: UserListThread(KUrlCompletionPrivate *receiver) : CompletionThread(receiver) {} protected: void run() override { #ifndef Q_OS_ANDROID const QChar tilde = QLatin1Char('~'); // we don't need to handle prepend here, right? ~user is always at pos 0 assert(m_prepend.isEmpty()); #pragma message("TODO: add KUser::allUserNames() with a std::function shouldTerminate parameter") #ifndef Q_OS_WIN struct passwd *pw; ::setpwent(); while ((pw = ::getpwent()) && !terminationRequested()) { addMatch(tilde + QString::fromLocal8Bit(pw->pw_name)); } ::endpwent(); #else //currently terminationRequested is ignored on Windows const QStringList allUsers = KUser::allUserNames(); for (const QString& s : allUsers) { addMatch(tilde + s); } #endif addMatch(QString(tilde)); #endif done(); } }; class DirectoryListThread : public CompletionThread { Q_OBJECT public: DirectoryListThread(KUrlCompletionPrivate *receiver, const QStringList &dirList, const QString &filter, const QStringList &mimeTypeFilters, bool onlyExe, bool onlyDir, bool noHidden, bool appendSlashToDir) : CompletionThread(receiver), m_dirList(dirList), m_filter(filter), m_mimeTypeFilters(mimeTypeFilters), m_onlyExe(onlyExe), m_onlyDir(onlyDir), m_noHidden(noHidden), m_appendSlashToDir(appendSlashToDir) {} void run() override; private: QStringList m_dirList; QString m_filter; QStringList m_mimeTypeFilters; bool m_onlyExe; bool m_onlyDir; bool m_noHidden; bool m_appendSlashToDir; }; void DirectoryListThread::run() { //qDebug() << "Entered DirectoryListThread::run(), m_filter=" << m_filter << ", m_onlyExe=" << m_onlyExe << ", m_onlyDir=" << m_onlyDir << ", m_appendSlashToDir=" << m_appendSlashToDir << ", m_dirList.size()=" << m_dirList.size(); QDir::Filters iterator_filter = (m_noHidden ? QDir::Filter(0) : QDir::Hidden) | QDir::Readable | QDir::NoDotAndDotDot; if (m_onlyExe) { iterator_filter |= (QDir::Dirs | QDir::Files | QDir::Executable); } else if (m_onlyDir) { iterator_filter |= QDir::Dirs; } else { iterator_filter |= (QDir::Dirs | QDir::Files); } QMimeDatabase mimeTypes; const QStringList::const_iterator end = m_dirList.constEnd(); for (QStringList::const_iterator it = m_dirList.constBegin(); it != end && !terminationRequested(); ++it) { //qDebug() << "Scanning directory" << *it; QDirIterator current_dir_iterator(*it, iterator_filter); while (current_dir_iterator.hasNext() && !terminationRequested()) { current_dir_iterator.next(); QFileInfo file_info = current_dir_iterator.fileInfo(); const QString file_name = file_info.fileName(); //qDebug() << "Found" << file_name; if (!m_filter.isEmpty() && !file_name.startsWith(m_filter)) { continue; } if (!m_mimeTypeFilters.isEmpty() && !file_info.isDir()) { auto mimeType = mimeTypes.mimeTypeForFile(file_info); if (!m_mimeTypeFilters.contains(mimeType.name())) { continue; } } QString toAppend = file_name; // Add '/' to directories if (m_appendSlashToDir && file_info.isDir()) { toAppend.append(QLatin1Char('/')); } if (m_complete_url) { QUrl info(m_prepend); info = addPathToUrl(info, toAppend); addMatch(info.toDisplayString()); } else { addMatch(m_prepend + toAppend); } } } done(); } KUrlCompletionPrivate::~KUrlCompletionPrivate() { } /////////////////////////////////////////////////////// /////////////////////////////////////////////////////// // MyURL - wrapper for QUrl with some different functionality // class KUrlCompletionPrivate::MyURL { public: MyURL(const QString &url, const QUrl &cwd); MyURL(const MyURL &url); ~MyURL(); QUrl kurl() const { return m_kurl; } bool isLocalFile() const { return m_kurl.isLocalFile(); } QString scheme() const { return m_kurl.scheme(); } // The directory with a trailing '/' QString dir() const { return m_kurl.adjusted(QUrl::RemoveFilename).path(); } QString file() const { return m_kurl.fileName(); } // The initial, unparsed, url, as a string. QString url() const { return m_url; } // Is the initial string a URL, or just a path (whether absolute or relative) bool isURL() const { return m_isURL; } void filter(bool replace_user_dir, bool replace_env); private: void init(const QString &url, const QUrl &cwd); QUrl m_kurl; QString m_url; bool m_isURL; }; KUrlCompletionPrivate::MyURL::MyURL(const QString &_url, const QUrl &cwd) { init(_url, cwd); } KUrlCompletionPrivate::MyURL::MyURL(const MyURL &_url) : m_kurl(_url.m_kurl) { m_url = _url.m_url; m_isURL = _url.m_isURL; } void KUrlCompletionPrivate::MyURL::init(const QString &_url, const QUrl &cwd) { // Save the original text m_url = _url; // Non-const copy QString url_copy = _url; // Special shortcuts for "man:" and "info:" if (url_copy.startsWith(QLatin1Char('#'))) { if (url_copy.length() > 1 && url_copy.at(1) == QLatin1Char('#')) { url_copy.replace(0, 2, QStringLiteral("info:")); } else { url_copy.replace(0, 1, QStringLiteral("man:")); } } // Look for a protocol in 'url' QRegExp protocol_regex = QRegExp(QStringLiteral("^(?![A-Za-z]:)[^/\\s\\\\]*:")); // Assume "file:" or whatever is given by 'cwd' if there is // no protocol. (QUrl does this only for absolute paths) if (protocol_regex.indexIn(url_copy) == 0) { m_kurl = QUrl(url_copy); m_isURL = true; } else { // relative path or ~ or $something m_isURL = false; if (!QDir::isRelativePath(url_copy) || url_copy.startsWith(QLatin1Char('~')) || url_copy.startsWith(QLatin1Char('$'))) { m_kurl = QUrl::fromLocalFile(url_copy); } else { // Relative path if (cwd.isEmpty()) { m_kurl = QUrl(url_copy); } else { m_kurl = cwd; m_kurl.setPath(concatPaths(m_kurl.path(), url_copy)); } } } } KUrlCompletionPrivate::MyURL::~MyURL() { } void KUrlCompletionPrivate::MyURL::filter(bool replace_user_dir, bool replace_env) { QString d = dir() + file(); if (replace_user_dir) { expandTilde(d); } if (replace_env) { expandEnv(d); } m_kurl.setPath(d); } /////////////////////////////////////////////////////// /////////////////////////////////////////////////////// // KUrlCompletion // KUrlCompletion::KUrlCompletion() : KCompletion(), d(new KUrlCompletionPrivate(this)) { d->init(); } KUrlCompletion::KUrlCompletion(Mode _mode) : KCompletion(), d(new KUrlCompletionPrivate(this)) { d->init(); setMode(_mode); } KUrlCompletion::~KUrlCompletion() { stop(); delete d; } void KUrlCompletionPrivate::init() { cwd = QUrl::fromLocalFile(QDir::homePath()); replace_home = true; replace_env = true; last_no_hidden = false; last_compl_type = CTNone; list_job = nullptr; mode = KUrlCompletion::FileCompletion; // Read settings KConfigGroup cg(KSharedConfig::openConfig(), "URLCompletion"); url_auto_completion = cg.readEntry("alwaysAutoComplete", true); popup_append_slash = cg.readEntry("popupAppendSlash", true); onlyLocalProto = cg.readEntry("LocalProtocolsOnly", false); q->setIgnoreCase(true); } void KUrlCompletion::setDir(const QUrl &dir) { d->cwd = dir; } QUrl KUrlCompletion::dir() const { return d->cwd; } KUrlCompletion::Mode KUrlCompletion::mode() const { return d->mode; } void KUrlCompletion::setMode(Mode _mode) { d->mode = _mode; } bool KUrlCompletion::replaceEnv() const { return d->replace_env; } void KUrlCompletion::setReplaceEnv(bool replace) { d->replace_env = replace; } bool KUrlCompletion::replaceHome() const { return d->replace_home; } void KUrlCompletion::setReplaceHome(bool replace) { d->replace_home = replace; } /* * makeCompletion() * * Entry point for file name completion */ QString KUrlCompletion::makeCompletion(const QString &text) { qCDebug(KIO_WIDGETS) << text << "d->cwd=" << d->cwd; KUrlCompletionPrivate::MyURL url(text, d->cwd); d->compl_text = text; // Set d->prepend to the original URL, with the filename [and ref/query] stripped. // This is what gets prepended to the directory-listing matches. if (url.isURL()) { QUrl directoryUrl(url.kurl()); directoryUrl.setQuery(QString()); directoryUrl.setFragment(QString()); directoryUrl.setPath(url.dir()); d->prepend = directoryUrl.toString(); } else { d->prepend = text.left(text.length() - url.file().length()); } d->complete_url = url.isURL(); QString aMatch; // Environment variables // if (d->replace_env && d->envCompletion(url, &aMatch)) { return aMatch; } // User directories // if (d->replace_home && d->userCompletion(url, &aMatch)) { return aMatch; } // Replace user directories and variables url.filter(d->replace_home, d->replace_env); //qDebug() << "Filtered: proto=" << url.scheme() // << ", dir=" << url.dir() // << ", file=" << url.file() // << ", kurl url=" << *url.kurl(); if (d->mode == ExeCompletion) { // Executables // if (d->exeCompletion(url, &aMatch)) { return aMatch; } // KRun can run "man:" and "info:" etc. so why not treat them // as executables... if (d->urlCompletion(url, &aMatch)) { return aMatch; } } else { // Local files, directories // if (d->fileCompletion(url, &aMatch)) { return aMatch; } // All other... // if (d->urlCompletion(url, &aMatch)) { return aMatch; } } d->setListedUrl(CTNone); stop(); return QString(); } /* * finished * * Go on and call KCompletion. * Called when all matches have been added */ QString KUrlCompletionPrivate::finished() { if (last_compl_type == CTInfo) { return q->KCompletion::makeCompletion(compl_text.toLower()); } else { return q->KCompletion::makeCompletion(compl_text); } } /* * isRunning * * Return true if either a KIO job or a thread is running */ bool KUrlCompletion::isRunning() const { return d->list_job || (d->dirListThread && !d->dirListThread->isFinished()) || (d->userListThread && !d->userListThread->isFinished()); } /* * stop * * Stop and delete a running KIO job or the DirLister */ void KUrlCompletion::stop() { if (d->list_job) { d->list_job->kill(); d->list_job = nullptr; } if (d->dirListThread) { d->dirListThread->requestTermination(); delete d->dirListThread; d->dirListThread = nullptr; } if (d->userListThread) { d->userListThread->requestTermination(); delete d->userListThread; d->userListThread = nullptr; } } /* * Keep track of the last listed directory */ void KUrlCompletionPrivate::setListedUrl(ComplType complType, const QString &directory, const QString &filter, bool no_hidden) { last_compl_type = complType; last_path_listed = directory; last_file_listed = filter; last_no_hidden = no_hidden; last_prepend = prepend; } bool KUrlCompletionPrivate::isListedUrl(ComplType complType, const QString &directory, const QString &filter, bool no_hidden) { return last_compl_type == complType && (last_path_listed == directory || (directory.isEmpty() && last_path_listed.isEmpty())) && (filter.startsWith(last_file_listed) || (filter.isEmpty() && last_file_listed.isEmpty())) && last_no_hidden == no_hidden && last_prepend == prepend; // e.g. relative path vs absolute } /* * isAutoCompletion * * Returns true if completion mode is Auto or Popup */ bool KUrlCompletionPrivate::isAutoCompletion() { return q->completionMode() == KCompletion::CompletionAuto || q->completionMode() == KCompletion::CompletionPopup || q->completionMode() == KCompletion::CompletionMan || q->completionMode() == KCompletion::CompletionPopupAuto; } ////////////////////////////////////////////////// ////////////////////////////////////////////////// // User directories // bool KUrlCompletionPrivate::userCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch) { if (url.scheme() != QLatin1String("file") || !url.dir().isEmpty() || !url.file().startsWith(QLatin1Char('~')) || !prepend.isEmpty()) { return false; } if (!isListedUrl(CTUser)) { q->stop(); q->clear(); setListedUrl(CTUser); Q_ASSERT(!userListThread); // caller called stop() userListThread = new UserListThread(this); QObject::connect(userListThread, &CompletionThread::completionThreadDone, q, [this](QThread *thread, const QStringList &matches){ slotCompletionThreadDone(thread, matches); }); userListThread->start(); // If the thread finishes quickly make sure that the results // are added to the first matching case. userListThread->wait(initialWaitDuration()); const QStringList l = userListThread->matches(); addMatches(l); } *pMatch = finished(); return true; } ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// // Environment variables // bool KUrlCompletionPrivate::envCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch) { if (url.file().isEmpty() || url.file().at(0) != QLatin1Char('$')) { return false; } if (!isListedUrl(CTEnv)) { q->stop(); q->clear(); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); const QStringList keys = env.keys(); QStringList l; l.reserve(keys.size()); for (const QString &key : keys) { l.append(prepend + QLatin1Char('$') + key); } addMatches(l); } setListedUrl(CTEnv); *pMatch = finished(); return true; } ////////////////////////////////////////////////// ////////////////////////////////////////////////// // Executables // bool KUrlCompletionPrivate::exeCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch) { if (!url.isLocalFile()) { return false; } QString directory = unescape(url.dir()); // remove escapes // Find directories to search for completions, either // // 1. complete path given in url // 2. current directory (d->cwd) // 3. $PATH // 4. no directory at all QStringList dirList; if (!url.file().isEmpty()) { // $PATH dirList = QString::fromLocal8Bit(qgetenv("PATH")).split( QDir::listSeparator(), QString::SkipEmptyParts); QStringList::Iterator it = dirList.begin(); for (; it != dirList.end(); ++it) { it->append(QLatin1Char('/')); } } else if (!QDir::isRelativePath(directory)) { // complete path in url dirList.append(directory); } else if (!directory.isEmpty() && !cwd.isEmpty()) { // current directory dirList.append(cwd.toLocalFile() + QLatin1Char('/') + directory); } // No hidden files unless the user types "." bool no_hidden_files = url.file().isEmpty() || url.file().at(0) != QLatin1Char('.'); // List files if needed // if (!isListedUrl(CTExe, directory, url.file(), no_hidden_files)) { q->stop(); q->clear(); setListedUrl(CTExe, directory, url.file(), no_hidden_files); *pMatch = listDirectories(dirList, url.file(), true, false, no_hidden_files); } else { *pMatch = finished(); } return true; } ////////////////////////////////////////////////// ////////////////////////////////////////////////// // Local files // bool KUrlCompletionPrivate::fileCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch) { if (!url.isLocalFile()) { return false; } QString directory = unescape(url.dir()); if (url.url() == QLatin1String("..")) { *pMatch = QStringLiteral(".."); return true; } //qDebug() << "fileCompletion" << url << "dir=" << dir; // Find directories to search for completions, either // // 1. complete path given in url // 2. current directory (d->cwd) // 3. no directory at all QStringList dirList; if (!QDir::isRelativePath(directory)) { // complete path in url dirList.append(directory); } else if (!cwd.isEmpty()) { // current directory QString dirToAdd = cwd.toLocalFile(); if (!directory.isEmpty()) { if (!dirToAdd.endsWith(QLatin1Char('/'))) { dirToAdd.append(QLatin1Char('/')); } dirToAdd.append(directory); } dirList.append(dirToAdd); } // No hidden files unless the user types "." bool no_hidden_files = !url.file().startsWith(QLatin1Char('.')); // List files if needed // if (!isListedUrl(CTFile, directory, QString(), no_hidden_files)) { q->stop(); q->clear(); setListedUrl(CTFile, directory, QString(), no_hidden_files); // Append '/' to directories in Popup mode? bool append_slash = (popup_append_slash && (q->completionMode() == KCompletion::CompletionPopup || q->completionMode() == KCompletion::CompletionPopupAuto)); bool only_dir = (mode == KUrlCompletion::DirCompletion); *pMatch = listDirectories(dirList, QString(), false, only_dir, no_hidden_files, append_slash); } else { *pMatch = finished(); } return true; } ////////////////////////////////////////////////// ////////////////////////////////////////////////// // URLs not handled elsewhere... // static bool isLocalProtocol(const QString &protocol) { return (KProtocolInfo::protocolClass(protocol) == QLatin1String(":local")); } bool KUrlCompletionPrivate::urlCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch) { //qDebug() << *url.kurl(); if (onlyLocalProto && isLocalProtocol(url.scheme())) { return false; } // Use d->cwd as base url in case url is not absolute QUrl url_dir = url.kurl(); if (url_dir.isRelative() && !cwd.isEmpty()) { // Create an URL with the directory to be listed url_dir = cwd.resolved(url_dir); } // url is malformed if (!url_dir.isValid() || url.scheme().isEmpty()) { return false; } // non local urls if (!isLocalProtocol(url.scheme())) { // url does not specify host if (url_dir.host().isEmpty()) { return false; } // url does not specify a valid directory if (url_dir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path().isEmpty()) { return false; } // automatic completion is disabled if (isAutoCompletion() && !url_auto_completion) { return false; } } // url handler doesn't support listing if (!KProtocolManager::supportsListing(url_dir)) { return false; } // Remove escapes const QString directory = unescape(url_dir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path()); url_dir.setPath(directory); // List files if needed // if (!isListedUrl(CTUrl, directory, url.file())) { q->stop(); q->clear(); setListedUrl(CTUrl, directory, QString()); QList url_list; url_list.append(url_dir); listUrls(url_list, QString(), false); pMatch->clear(); } else if (!q->isRunning()) { *pMatch = finished(); } else { pMatch->clear(); } return true; } ////////////////////////////////////////////////// ////////////////////////////////////////////////// // Directory and URL listing // /* * addMatches * * Called to add matches to KCompletion */ void KUrlCompletionPrivate::addMatches(const QStringList &matchList) { q->insertItems(matchList); } /* * listDirectories * * List files starting with 'filter' in the given directories, * either using DirLister or listURLs() * * In either case, addMatches() is called with the listed * files, and eventually finished() when the listing is done * * Returns the match if available, or QString() if * DirLister timed out or using kio */ QString KUrlCompletionPrivate::listDirectories( const QStringList &dirList, const QString &filter, bool only_exe, bool only_dir, bool no_hidden, bool append_slash_to_dir) { assert(!q->isRunning()); if (qEnvironmentVariableIsEmpty("KURLCOMPLETION_LOCAL_KIO")) { qCDebug(KIO_WIDGETS) << "Listing directories:" << dirList << "with filter=" << filter << "using thread"; // Don't use KIO QStringList dirs; QStringList::ConstIterator end = dirList.constEnd(); for (QStringList::ConstIterator it = dirList.constBegin(); it != end; ++it) { QUrl url = QUrl::fromLocalFile(*it); if (KUrlAuthorized::authorizeUrlAction(QStringLiteral("list"), QUrl(), url)) { dirs.append(*it); } } Q_ASSERT(!dirListThread); // caller called stop() dirListThread = new DirectoryListThread(this, dirs, filter, mimeTypeFilters, only_exe, only_dir, no_hidden, append_slash_to_dir); QObject::connect(dirListThread, &CompletionThread::completionThreadDone, q, [this](QThread *thread, const QStringList &matches){ slotCompletionThreadDone(thread, matches); }); dirListThread->start(); dirListThread->wait(initialWaitDuration()); qCDebug(KIO_WIDGETS) << "Adding initial matches:" << dirListThread->matches(); addMatches(dirListThread->matches()); return finished(); } // Use KIO //qDebug() << "Listing (listDirectories):" << dirList << "with KIO"; QList url_list; QStringList::ConstIterator it = dirList.constBegin(); QStringList::ConstIterator end = dirList.constEnd(); url_list.reserve(dirList.size()); for (; it != end; ++it) { url_list.append(QUrl(*it)); } listUrls(url_list, filter, only_exe, no_hidden); // Will call addMatches() and finished() return QString(); } /* * listURLs * * Use KIO to list the given urls * * addMatches() is called with the listed files * finished() is called when the listing is done */ void KUrlCompletionPrivate::listUrls( const QList &urls, const QString &filter, bool only_exe, bool no_hidden) { assert(list_urls.isEmpty()); assert(list_job == nullptr); list_urls = urls; list_urls_filter = filter; list_urls_only_exe = only_exe; list_urls_no_hidden = no_hidden; //qDebug() << "Listing URLs:" << *urls[0] << ",..."; // Start it off by calling _k_slotIOFinished // // This will start a new list job as long as there // are urls in d->list_urls // _k_slotIOFinished(nullptr); } /* * _k_slotEntries * * Receive files listed by KIO and call addMatches() */ void KUrlCompletionPrivate::_k_slotEntries(KIO::Job *, const KIO::UDSEntryList &entries) { QStringList matchList; KIO::UDSEntryList::ConstIterator it = entries.constBegin(); const KIO::UDSEntryList::ConstIterator end = entries.constEnd(); QString filter = list_urls_filter; int filter_len = filter.length(); // Iterate over all files // for (; it != end; ++it) { const KIO::UDSEntry &entry = *it; const QString url = entry.stringValue(KIO::UDSEntry::UDS_URL); QString entry_name; if (!url.isEmpty()) { //qDebug() << "url:" << url; entry_name = QUrl(url).fileName(); } else { entry_name = entry.stringValue(KIO::UDSEntry::UDS_NAME); } //qDebug() << "name:" << name; if ((!entry_name.isEmpty() && entry_name.at(0) == QLatin1Char('.')) && (list_urls_no_hidden || entry_name.length() == 1 || (entry_name.length() == 2 && entry_name.at(1) == QLatin1Char('.')))) { continue; } const bool isDir = entry.isDir(); if (mode == KUrlCompletion::DirCompletion && !isDir) { continue; } if (filter_len != 0 && entry_name.leftRef(filter_len) != filter) { continue; } if (!mimeTypeFilters.isEmpty() && !isDir && !mimeTypeFilters.contains(entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE))) { continue; } QString toAppend = entry_name; if (isDir) { toAppend.append(QLatin1Char('/')); } if (!list_urls_only_exe || (entry.numberValue(KIO::UDSEntry::UDS_ACCESS) & MODE_EXE) // true if executable ) { if (complete_url) { QUrl url(prepend); url = addPathToUrl(url, toAppend); matchList.append(url.toDisplayString()); } else { matchList.append(prepend + toAppend); } } } addMatches(matchList); } /* * _k_slotIOFinished * * Called when a KIO job is finished. * * Start a new list job if there are still urls in * list_urls, otherwise call finished() */ void KUrlCompletionPrivate::_k_slotIOFinished(KJob *job) { assert(job == list_job); Q_UNUSED(job) if (list_urls.isEmpty()) { list_job = nullptr; finished(); // will call KCompletion::makeCompletion() } else { QUrl kurl(list_urls.takeFirst()); // list_urls.removeAll( kurl ); //qDebug() << "Start KIO::listDir" << kurl; list_job = KIO::listDir(kurl, KIO::HideProgressInfo); list_job->addMetaData(QStringLiteral("no-auth-prompt"), QStringLiteral("true")); assert(list_job); q->connect(list_job, SIGNAL(result(KJob*)), SLOT(_k_slotIOFinished(KJob*))); q->connect(list_job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), SLOT(_k_slotEntries(KIO::Job*,KIO::UDSEntryList))); } } /////////////////////////////////////////////////// /////////////////////////////////////////////////// /* * postProcessMatch, postProcessMatches * * Called by KCompletion before emitting match() and matches() * * Append '/' to directories for file completion. This is * done here to avoid stat()'ing a lot of files */ void KUrlCompletion::postProcessMatch(QString *pMatch) const { //qDebug() << *pMatch; if (!pMatch->isEmpty() && pMatch->startsWith(QLatin1String("file:"))) { // Add '/' to directories in file completion mode // unless it has already been done if (d->last_compl_type == CTFile && pMatch->at(pMatch->length() - 1) != QLatin1Char('/')) { QString copy = QUrl(*pMatch).toLocalFile(); expandTilde(copy); expandEnv(copy); if (QDir::isRelativePath(copy)) { copy.prepend(d->cwd.toLocalFile() + QLatin1Char('/')); } //qDebug() << "stat'ing" << copy; QByteArray file = QFile::encodeName(copy); QT_STATBUF sbuff; if (QT_STAT(file.constData(), &sbuff) == 0) { if ((sbuff.st_mode & QT_STAT_MASK) == QT_STAT_DIR) { pMatch->append(QLatin1Char('/')); } } else { //qDebug() << "Could not stat file" << copy; } } } } void KUrlCompletion::postProcessMatches(QStringList * /*matches*/) const { // Maybe '/' should be added to directories here as in // postProcessMatch() but it would slow things down // when there are a lot of matches... } void KUrlCompletion::postProcessMatches(KCompletionMatches * /*matches*/) const { // Maybe '/' should be added to directories here as in // postProcessMatch() but it would slow things down // when there are a lot of matches... } // no longer used, KF6 TODO: remove this method void KUrlCompletion::customEvent(QEvent *e) { KCompletion::customEvent(e); } void KUrlCompletionPrivate::slotCompletionThreadDone(QThread *thread, const QStringList &matches) { if (thread != userListThread && thread != dirListThread) { qCDebug(KIO_WIDGETS) << "got" << matches.count() << "outdated matches"; return; } qCDebug(KIO_WIDGETS) << "got" << matches.count() << "matches at end of thread"; q->setItems(matches); if (userListThread == thread) { thread->wait(); delete thread; userListThread = nullptr; } if (dirListThread == thread) { thread->wait(); delete thread; dirListThread = nullptr; } finished(); // will call KCompletion::makeCompletion() } // static QString KUrlCompletion::replacedPath(const QString &text, bool replaceHome, bool replaceEnv) { if (text.isEmpty()) { return text; } KUrlCompletionPrivate::MyURL url(text, QUrl()); // no need to replace something of our current cwd if (!url.kurl().isLocalFile()) { return text; } url.filter(replaceHome, replaceEnv); return url.dir() + url.file(); } QString KUrlCompletion::replacedPath(const QString &text) const { return replacedPath(text, d->replace_home, d->replace_env); } void KUrlCompletion::setMimeTypeFilters(const QStringList &mimeTypeFilters) { d->mimeTypeFilters = mimeTypeFilters; } QStringList KUrlCompletion::mimeTypeFilters() const { return d->mimeTypeFilters; } ///////////////////////////////////////////////////////// ///////////////////////////////////////////////////////// // Static functions /* * expandEnv * * Expand environment variables in text. Escaped '$' are ignored. * Return true if expansion was made. */ static bool expandEnv(QString &text) { // Find all environment variables beginning with '$' // int pos = 0; bool expanded = false; while ((pos = text.indexOf(QLatin1Char('$'), pos)) != -1) { // Skip escaped '$' // if (pos > 0 && text.at(pos - 1) == QLatin1Char('\\')) { pos++; } // Variable found => expand // else { // Find the end of the variable = next '/' or ' ' // int pos2 = text.indexOf(QLatin1Char(' '), pos + 1); int pos_tmp = text.indexOf(QLatin1Char('/'), pos + 1); if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2)) { pos2 = pos_tmp; } if (pos2 == -1) { pos2 = text.length(); } // Replace if the variable is terminated by '/' or ' ' // and defined // if (pos2 >= 0) { int len = pos2 - pos; const QStringRef key = text.midRef(pos + 1, len - 1); QString value = QString::fromLocal8Bit(qgetenv(key.toLocal8Bit().constData())); if (!value.isEmpty()) { expanded = true; text.replace(pos, len, value); pos = pos + value.length(); } else { pos = pos2; } } } } return expanded; } /* * expandTilde * * Replace "~user" with the users home directory * Return true if expansion was made. */ static bool expandTilde(QString &text) { if (text.isEmpty() || (text.at(0) != QLatin1Char('~'))) { return false; } bool expanded = false; // Find the end of the user name = next '/' or ' ' // int pos2 = text.indexOf(QLatin1Char(' '), 1); int pos_tmp = text.indexOf(QLatin1Char('/'), 1); if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2)) { pos2 = pos_tmp; } if (pos2 == -1) { pos2 = text.length(); } // Replace ~user if the user name is terminated by '/' or ' ' // if (pos2 >= 0) { QString userName = text.mid(1, pos2 - 1); QString dir; // A single ~ is replaced with $HOME // if (userName.isEmpty()) { dir = QDir::homePath(); } // ~user is replaced with the dir from passwd // else { KUser user(userName); dir = user.homeDir(); } if (!dir.isEmpty()) { expanded = true; text.replace(0, pos2, dir); } } return expanded; } /* * unescape * * Remove escapes and return the result in a new string * */ static QString unescape(const QString &text) { QString result; result.reserve(text.size()); for (const QChar ch : text) { if (ch != QLatin1Char('\\')) { result.append(ch); } } return result; } #include "moc_kurlcompletion.cpp" #include "kurlcompletion.moc"