diff --git a/src/core/forwardingslavebase.cpp b/src/core/forwardingslavebase.cpp index 0c439ec3..a14d7b8f 100644 --- a/src/core/forwardingslavebase.cpp +++ b/src/core/forwardingslavebase.cpp @@ -1,511 +1,508 @@ /* This file is part of the KDE project Copyright (c) 2004 Kevin Ottens 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. */ -// TODO: remove me -#undef QT_NO_CAST_FROM_ASCII - #include "forwardingslavebase.h" #include "../pathhelpers_p.h" #include "deletejob.h" #include "mkdirjob.h" #include "job.h" #include #include namespace KIO { class ForwardingSlaveBasePrivate { public: ForwardingSlaveBasePrivate(QObject *eventLoopParent, ForwardingSlaveBase *qq) : q(qq) , eventLoop(eventLoopParent) {} ForwardingSlaveBase * const q; QUrl m_processedURL; QUrl m_requestedURL; QEventLoop eventLoop; bool internalRewriteUrl(const QUrl &url, QUrl &newURL); void connectJob(Job *job); void connectSimpleJob(SimpleJob *job); void connectListJob(ListJob *job); void connectTransferJob(TransferJob *job); void _k_slotResult(KJob *job); void _k_slotWarning(KJob *job, const QString &msg); void _k_slotInfoMessage(KJob *job, const QString &msg); void _k_slotTotalSize(KJob *job, qulonglong size); void _k_slotProcessedSize(KJob *job, qulonglong size); void _k_slotSpeed(KJob *job, unsigned long bytesPerSecond); // KIO::SimpleJob subclasses void _k_slotRedirection(KIO::Job *job, const QUrl &url); // KIO::ListJob void _k_slotEntries(KIO::Job *job, const KIO::UDSEntryList &entries); // KIO::TransferJob void _k_slotData(KIO::Job *job, const QByteArray &data); void _k_slotDataReq(KIO::Job *job, QByteArray &data); void _k_slotMimetype(KIO::Job *job, const QString &type); void _k_slotCanResume(KIO::Job *job, KIO::filesize_t offset); }; ForwardingSlaveBase::ForwardingSlaveBase(const QByteArray &protocol, const QByteArray &poolSocket, const QByteArray &appSocket) : QObject(), SlaveBase(protocol, poolSocket, appSocket), d(new ForwardingSlaveBasePrivate(this, this)) { } ForwardingSlaveBase::~ForwardingSlaveBase() { delete d; } bool ForwardingSlaveBasePrivate::internalRewriteUrl(const QUrl &url, QUrl &newURL) { bool result = true; - if (url.scheme() == q->mProtocol) { + if (url.scheme() == QLatin1String(q->mProtocol)) { result = q->rewriteUrl(url, newURL); } else { newURL = url; } m_processedURL = newURL; m_requestedURL = url; return result; } void ForwardingSlaveBase::prepareUDSEntry(KIO::UDSEntry &entry, bool listing) const { //qDebug() << "listing==" << listing; const QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME); QString mimetype = entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE); QUrl url; const QString urlStr = entry.stringValue(KIO::UDSEntry::UDS_URL); const bool url_found = !urlStr.isEmpty(); if (url_found) { url = QUrl(urlStr); QUrl new_url(d->m_requestedURL); if (listing) { new_url.setPath(concatPaths(new_url.path(), url.fileName())); } // ## Didn't find a way to use an iterator instead of re-doing a key lookup entry.replace(KIO::UDSEntry::UDS_URL, new_url.toString()); //qDebug() << "URL =" << url; //qDebug() << "New URL =" << new_url; } if (mimetype.isEmpty()) { QUrl new_url(d->m_processedURL); if (url_found && listing) { new_url.setPath(concatPaths(new_url.path(), url.fileName())); } else if (listing) { new_url.setPath(concatPaths(new_url.path(), name)); } QMimeDatabase db; mimetype = db.mimeTypeForUrl(new_url).name(); entry.replace(KIO::UDSEntry::UDS_MIME_TYPE, mimetype); //qDebug() << "New Mimetype = " << mimetype; } if (d->m_processedURL.isLocalFile()) { QUrl new_url(d->m_processedURL); if (listing) { new_url.setPath(concatPaths(new_url.path(), name)); } entry.replace(KIO::UDSEntry::UDS_LOCAL_PATH, new_url.toLocalFile()); } } QUrl ForwardingSlaveBase::processedUrl() const { return d->m_processedURL; } QUrl ForwardingSlaveBase::requestedUrl() const { return d->m_requestedURL; } void ForwardingSlaveBase::get(const QUrl &url) { //qDebug() << url; QUrl new_url; if (d->internalRewriteUrl(url, new_url)) { KIO::TransferJob *job = KIO::get(new_url, NoReload, HideProgressInfo); d->connectTransferJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); } } void ForwardingSlaveBase::put(const QUrl &url, int permissions, JobFlags flags) { //qDebug() << url; QUrl new_url; if (d->internalRewriteUrl(url, new_url)) { KIO::TransferJob *job = KIO::put(new_url, permissions, flags | HideProgressInfo); d->connectTransferJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_MALFORMED_URL, url.toDisplayString()); } } void ForwardingSlaveBase::stat(const QUrl &url) { //qDebug() << url; QUrl new_url; if (d->internalRewriteUrl(url, new_url)) { KIO::SimpleJob *job = KIO::stat(new_url, KIO::HideProgressInfo); d->connectSimpleJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); } } void ForwardingSlaveBase::mimetype(const QUrl &url) { //qDebug() << url; QUrl new_url; if (d->internalRewriteUrl(url, new_url)) { KIO::TransferJob *job = KIO::mimetype(new_url, KIO::HideProgressInfo); d->connectTransferJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); } } void ForwardingSlaveBase::listDir(const QUrl &url) { //qDebug() << url; QUrl new_url; if (d->internalRewriteUrl(url, new_url)) { KIO::ListJob *job = KIO::listDir(new_url, KIO::HideProgressInfo); d->connectListJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); } } void ForwardingSlaveBase::mkdir(const QUrl &url, int permissions) { //qDebug() << url; QUrl new_url; if (d->internalRewriteUrl(url, new_url)) { KIO::SimpleJob *job = KIO::mkdir(new_url, permissions); d->connectSimpleJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_MALFORMED_URL, url.toDisplayString()); } } void ForwardingSlaveBase::rename(const QUrl &src, const QUrl &dest, JobFlags flags) { //qDebug() << src << "," << dest; QUrl new_src, new_dest; if (!d->internalRewriteUrl(src, new_src)) { error(KIO::ERR_DOES_NOT_EXIST, src.toDisplayString()); } else if (d->internalRewriteUrl(dest, new_dest)) { KIO::Job *job = KIO::rename(new_src, new_dest, flags); d->connectJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_MALFORMED_URL, dest.toDisplayString()); } } void ForwardingSlaveBase::symlink(const QString &target, const QUrl &dest, JobFlags flags) { //qDebug() << target << ", " << dest; QUrl new_dest; if (d->internalRewriteUrl(dest, new_dest)) { KIO::SimpleJob *job = KIO::symlink(target, new_dest, flags | HideProgressInfo); d->connectSimpleJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_MALFORMED_URL, dest.toDisplayString()); } } void ForwardingSlaveBase::chmod(const QUrl &url, int permissions) { //qDebug() << url; QUrl new_url; if (d->internalRewriteUrl(url, new_url)) { KIO::SimpleJob *job = KIO::chmod(new_url, permissions); d->connectSimpleJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); } } void ForwardingSlaveBase::setModificationTime(const QUrl &url, const QDateTime &mtime) { //qDebug() << url; QUrl new_url; if (d->internalRewriteUrl(url, new_url)) { KIO::SimpleJob *job = KIO::setModificationTime(new_url, mtime); d->connectSimpleJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); } } void ForwardingSlaveBase::copy(const QUrl &src, const QUrl &dest, int permissions, JobFlags flags) { //qDebug() << src << "," << dest; QUrl new_src, new_dest; if (!d->internalRewriteUrl(src, new_src)) { error(KIO::ERR_DOES_NOT_EXIST, src.toDisplayString()); } else if (d->internalRewriteUrl(dest, new_dest)) { KIO::Job *job = KIO::file_copy(new_src, new_dest, permissions, flags | HideProgressInfo); d->connectJob(job); d->eventLoop.exec(); } else { error(KIO::ERR_MALFORMED_URL, dest.toDisplayString()); } } void ForwardingSlaveBase::del(const QUrl &url, bool isfile) { //qDebug() << url; QUrl new_url; if (d->internalRewriteUrl(url, new_url)) { if (isfile) { KIO::DeleteJob *job = KIO::del(new_url, HideProgressInfo); d->connectJob(job); } else { KIO::SimpleJob *job = KIO::rmdir(new_url); d->connectSimpleJob(job); } d->eventLoop.exec(); } else { error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); } } ////////////////////////////////////////////////////////////////////////////// void ForwardingSlaveBasePrivate::connectJob(KIO::Job *job) { // We will forward the warning message, no need to let the job // display it itself job->setUiDelegate(nullptr); // Forward metadata (e.g. modification time for put()) job->setMetaData(q->allMetaData()); #if 0 // debug code //qDebug() << "transferring metadata:"; const MetaData md = allMetaData(); for (MetaData::const_iterator it = md.begin(); it != md.end(); ++it) //qDebug() << it.key() << " = " << it.data(); #endif q->connect(job, SIGNAL(result(KJob*)), SLOT(_k_slotResult(KJob*))); q->connect(job, SIGNAL(warning(KJob*,QString,QString)), SLOT(_k_slotWarning(KJob*,QString))); q->connect(job, SIGNAL(infoMessage(KJob*,QString,QString)), SLOT(_k_slotInfoMessage(KJob*,QString))); q->connect(job, SIGNAL(totalSize(KJob*,qulonglong)), SLOT(_k_slotTotalSize(KJob*,qulonglong))); q->connect(job, SIGNAL(processedSize(KJob*,qulonglong)), SLOT(_k_slotProcessedSize(KJob*,qulonglong))); q->connect(job, SIGNAL(speed(KJob*,ulong)), SLOT(_k_slotSpeed(KJob*,ulong))); } void ForwardingSlaveBasePrivate::connectSimpleJob(KIO::SimpleJob *job) { connectJob(job); if (job->metaObject()->indexOfSignal("redirection(KIO::Job*,QUrl)") > -1) { q->connect(job, SIGNAL(redirection(KIO::Job*,QUrl)), SLOT(_k_slotRedirection(KIO::Job*,QUrl))); } } void ForwardingSlaveBasePrivate::connectListJob(KIO::ListJob *job) { connectSimpleJob(job); q->connect(job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), SLOT(_k_slotEntries(KIO::Job*,KIO::UDSEntryList))); } void ForwardingSlaveBasePrivate::connectTransferJob(KIO::TransferJob *job) { connectSimpleJob(job); q->connect(job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(_k_slotData(KIO::Job*,QByteArray))); q->connect(job, SIGNAL(dataReq(KIO::Job*,QByteArray&)), SLOT(_k_slotDataReq(KIO::Job*,QByteArray&))); q->connect(job, SIGNAL(mimetype(KIO::Job*,QString)), SLOT(_k_slotMimetype(KIO::Job*,QString))); q->connect(job, SIGNAL(canResume(KIO::Job*,KIO::filesize_t)), SLOT(_k_slotCanResume(KIO::Job*,KIO::filesize_t))); } ////////////////////////////////////////////////////////////////////////////// void ForwardingSlaveBasePrivate::_k_slotResult(KJob *job) { if (job->error() != 0) { q->error(job->error(), job->errorText()); } else { KIO::StatJob *stat_job = qobject_cast(job); if (stat_job != nullptr) { KIO::UDSEntry entry = stat_job->statResult(); q->prepareUDSEntry(entry); q->statEntry(entry); } q->finished(); } eventLoop.exit(); } void ForwardingSlaveBasePrivate::_k_slotWarning(KJob * /*job*/, const QString &msg) { q->warning(msg); } void ForwardingSlaveBasePrivate::_k_slotInfoMessage(KJob * /*job*/, const QString &msg) { q->infoMessage(msg); } void ForwardingSlaveBasePrivate::_k_slotTotalSize(KJob * /*job*/, qulonglong size) { q->totalSize(size); } void ForwardingSlaveBasePrivate::_k_slotProcessedSize(KJob * /*job*/, qulonglong size) { q->processedSize(size); } void ForwardingSlaveBasePrivate::_k_slotSpeed(KJob * /*job*/, unsigned long bytesPerSecond) { q->speed(bytesPerSecond); } void ForwardingSlaveBasePrivate::_k_slotRedirection(KIO::Job *job, const QUrl &url) { q->redirection(url); // We've been redirected stop everything. job->kill(KJob::Quietly); q->finished(); eventLoop.exit(); } void ForwardingSlaveBasePrivate::_k_slotEntries(KIO::Job * /*job*/, const KIO::UDSEntryList &entries) { KIO::UDSEntryList final_entries = entries; KIO::UDSEntryList::iterator it = final_entries.begin(); const KIO::UDSEntryList::iterator end = final_entries.end(); for (; it != end; ++it) { q->prepareUDSEntry(*it, true); } q->listEntries(final_entries); } void ForwardingSlaveBasePrivate::_k_slotData(KIO::Job * /*job*/, const QByteArray &_data) { q->data(_data); } void ForwardingSlaveBasePrivate::_k_slotDataReq(KIO::Job * /*job*/, QByteArray &data) { q->dataReq(); q->readData(data); } void ForwardingSlaveBasePrivate::_k_slotMimetype(KIO::Job * /*job*/, const QString &type) { q->mimeType(type); } void ForwardingSlaveBasePrivate::_k_slotCanResume(KIO::Job * /*job*/, KIO::filesize_t offset) { q->canResume(offset); } } #include "moc_forwardingslavebase.cpp" diff --git a/src/core/kremoteencoding.cpp b/src/core/kremoteencoding.cpp index 628636c5..919a2dc3 100644 --- a/src/core/kremoteencoding.cpp +++ b/src/core/kremoteencoding.cpp @@ -1,132 +1,130 @@ /* This file is part of the KDE libraries Copyright (C) 2003 Thiago Macieira 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. */ -// TODO: remove me -#undef QT_NO_CAST_FROM_BYTEARRAY - #include "kremoteencoding.h" #include #include #include class KRemoteEncodingPrivate { public: KRemoteEncodingPrivate() : m_codec(nullptr) { } QTextCodec *m_codec; }; KRemoteEncoding::KRemoteEncoding(const char *name) : d(new KRemoteEncodingPrivate) { setEncoding(name); } KRemoteEncoding::~KRemoteEncoding() { delete d; } QString KRemoteEncoding::decode(const QByteArray &name) const { #ifdef CHECK_UTF8 if (d->m_codec->mibEnum() == 106 && !KStringHandler::isUtf8(name)) { return QLatin1String(name); } #endif QString result = d->m_codec->toUnicode(name); if (d->m_codec->fromUnicode(result) != name) // fallback in case of decoding failure { return QLatin1String(name); } return result; } QByteArray KRemoteEncoding::encode(const QString &name) const { QByteArray result = d->m_codec->fromUnicode(name); if (d->m_codec->toUnicode(result) != name) { return name.toLatin1(); } return result; } QByteArray KRemoteEncoding::encode(const QUrl &url) const { return encode(url.path()); } QByteArray KRemoteEncoding::directory(const QUrl &url, bool ignore_trailing_slash) const { QUrl dirUrl(url); if (ignore_trailing_slash && dirUrl.path().endsWith(QLatin1Char('/'))) { dirUrl = dirUrl.adjusted(QUrl::StripTrailingSlash); } const QString dir = dirUrl.adjusted(QUrl::RemoveFilename).path(); return encode(dir); } QByteArray KRemoteEncoding::fileName(const QUrl &url) const { return encode(url.fileName()); } const char *KRemoteEncoding::encoding() const { - return d->m_codec->name(); + // KF6 TODO: return QByteArray + return d->m_codec->name().constData(); } int KRemoteEncoding::encodingMib() const { return d->m_codec->mibEnum(); } void KRemoteEncoding::setEncoding(const char *name) { // don't delete codecs if (name) { d->m_codec = QTextCodec::codecForName(name); } if (d->m_codec == nullptr) { d->m_codec = QTextCodec::codecForMib(106); // fallback to UTF-8 } if (d->m_codec == nullptr) { d->m_codec = QTextCodec::codecForMib(4 /* latin-1 */); } Q_ASSERT(d->m_codec); /*qDebug() << "setting encoding" << d->m_codec->name() << "for name=" << name;*/ } void KRemoteEncoding::virtual_hook(int, void *) { } diff --git a/src/core/slavebase.cpp b/src/core/slavebase.cpp index 8a14d2c4..bbb01bdf 100644 --- a/src/core/slavebase.cpp +++ b/src/core/slavebase.cpp @@ -1,1564 +1,1561 @@ /* * This file is part of the KDE libraries * Copyright (c) 2000 Waldo Bastian * Copyright (c) 2000 David Faure * Copyright (c) 2000 Stephan Kulow * Copyright (c) 2007 Thiago Macieira * * 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. * **/ -// TODO: remove me -#undef QT_NO_CAST_FROM_ASCII - #include "slavebase.h" #include #include #include #include #ifdef Q_OS_WIN #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "kremoteencoding.h" #include "kioglobal_p.h" #include "connection_p.h" #include "commands_p.h" #include "ioslave_defaults.h" #include "slaveinterface.h" #include "kpasswdserverclient.h" #include "kiocoredebug.h" #ifdef Q_OS_UNIX #include #endif #if KIO_ASSERT_SLAVE_STATES #define KIO_STATE_ASSERT(cond, where, what) Q_ASSERT_X(cond, where, what) #else #define KIO_STATE_ASSERT(cond, where, what) do { if (!(cond)) qCWarning(KIO_CORE) << what; } while (false) #endif extern "C" { static void sigpipe_handler(int sig); } using namespace KIO; typedef QList AuthKeysList; typedef QMap AuthKeysMap; #define KIO_DATA QByteArray data; QDataStream stream( &data, QIODevice::WriteOnly ); stream #define KIO_FILESIZE_T(x) quint64(x) static const int KIO_MAX_ENTRIES_PER_BATCH = 200; static const int KIO_MAX_SEND_BATCH_TIME = 300; namespace KIO { class SlaveBasePrivate { public: SlaveBase * const q; explicit SlaveBasePrivate(SlaveBase *owner) : q(owner) , nextTimeoutMsecs(0) , m_passwdServerClient(nullptr) , m_confirmationAsked(false) , m_privilegeOperationStatus(OperationNotAllowed) { if (!qEnvironmentVariableIsEmpty("KIOSLAVE_ENABLE_TESTMODE")) { QStandardPaths::setTestModeEnabled(true); } pendingListEntries.reserve(KIO_MAX_ENTRIES_PER_BATCH); appConnection.setReadMode(Connection::ReadMode::Polled); } ~SlaveBasePrivate() { delete m_passwdServerClient; } UDSEntryList pendingListEntries; QElapsedTimer m_timeSinceLastBatch; Connection appConnection; QString poolSocket; bool isConnectedToApp; QString slaveid; bool resume: 1; bool needSendCanResume: 1; bool onHold: 1; bool wasKilled: 1; bool inOpenLoop: 1; bool exit_loop: 1; MetaData configData; KConfig *config = nullptr; KConfigGroup *configGroup = nullptr; QMap mapConfig; QUrl onHoldUrl; QElapsedTimer lastTimeout; QElapsedTimer nextTimeout; qint64 nextTimeoutMsecs; KIO::filesize_t totalSize; KRemoteEncoding *remotefile = nullptr; enum { Idle, InsideMethod, FinishedCalled, ErrorCalled } m_state; bool m_finalityCommand = true; // whether finished() or error() may/must be called QByteArray timeoutData; KPasswdServerClient *m_passwdServerClient = nullptr; bool m_rootEntryListed = false; bool m_confirmationAsked; QSet m_tempAuths; QString m_warningCaption; QString m_warningMessage; int m_privilegeOperationStatus; PrivilegeOperationStatus askConfirmation() { int status = q->messageBox(SlaveBase::WarningContinueCancel, m_warningMessage, m_warningCaption, QStringLiteral("Continue"), QStringLiteral("Cancel")); switch (status) { case SlaveBase::Continue: return OperationAllowed; case SlaveBase::Cancel: return OperationCanceled; default: return OperationNotAllowed; } } void updateTempAuthStatus() { #ifdef Q_OS_UNIX QSet::iterator it = m_tempAuths.begin(); while (it != m_tempAuths.end()) { KAuth::Action action(*it); if (action.status() != KAuth::Action::AuthorizedStatus) { it = m_tempAuths.erase(it); } else { ++it; } } #endif } bool hasTempAuth() const { return !m_tempAuths.isEmpty(); } // Reconstructs configGroup from configData and mIncomingMetaData void rebuildConfig() { mapConfig.clear(); // mIncomingMetaData cascades over config, so we write config first, // to let it be overwritten MetaData::ConstIterator end = configData.constEnd(); for (MetaData::ConstIterator it = configData.constBegin(); it != end; ++it) { mapConfig.insert(it.key(), it->toUtf8()); } end = q->mIncomingMetaData.constEnd(); for (MetaData::ConstIterator it = q->mIncomingMetaData.constBegin(); it != end; ++it) { mapConfig.insert(it.key(), it->toUtf8()); } delete configGroup; configGroup = nullptr; delete config; config = nullptr; } bool finalState() const { return ((m_state == FinishedCalled) || (m_state == ErrorCalled)); } void verifyState(const char *cmdName) { KIO_STATE_ASSERT(finalState(), Q_FUNC_INFO, qUtf8Printable(QStringLiteral("%1 did not call finished() or error()! Please fix the %2 KIO slave") - .arg(cmdName) + .arg(QLatin1String(cmdName)) .arg(QCoreApplication::applicationName()))); // Force the command into finished state. We'll not reach this for Debug builds // that fail the assertion. For Release builds we'll have made sure that the // command is actually finished after the verification regardless of what // the slave did. if (!finalState()) { q->finished(); } } void verifyErrorFinishedNotCalled(const char *cmdName) { KIO_STATE_ASSERT(!finalState(), Q_FUNC_INFO, qUtf8Printable(QStringLiteral("%1 called finished() or error(), but it's not supposed to! Please fix the %2 KIO slave") - .arg(cmdName) + .arg(QLatin1String(cmdName)) .arg(QCoreApplication::applicationName()))); } KPasswdServerClient *passwdServerClient() { if (!m_passwdServerClient) { m_passwdServerClient = new KPasswdServerClient; } return m_passwdServerClient; } }; } static SlaveBase *globalSlave; static volatile bool slaveWriteError = false; static const char *s_protocol; #ifdef Q_OS_UNIX extern "C" { static void genericsig_handler(int sigNumber) { ::signal(sigNumber, SIG_IGN); //WABA: Don't do anything that requires malloc, we can deadlock on it since //a SIGTERM signal can come in while we are in malloc/free. //qDebug()<<"kioslave : exiting due to signal "<setKillFlag(); } ::signal(SIGALRM, SIG_DFL); alarm(5); //generate an alarm signal in 5 seconds, in this time the slave has to exit } } #endif ////////////// SlaveBase::SlaveBase(const QByteArray &protocol, const QByteArray &pool_socket, const QByteArray &app_socket) : mProtocol(protocol), d(new SlaveBasePrivate(this)) { Q_ASSERT(!app_socket.isEmpty()); d->poolSocket = QFile::decodeName(pool_socket); s_protocol = protocol.data(); KCrash::initialize(); #ifdef Q_OS_UNIX struct sigaction act; act.sa_handler = sigpipe_handler; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGPIPE, &act, nullptr); ::signal(SIGINT, &genericsig_handler); ::signal(SIGQUIT, &genericsig_handler); ::signal(SIGTERM, &genericsig_handler); #endif globalSlave = this; d->isConnectedToApp = true; // by kahl for netmgr (need a way to identify slaves) d->slaveid = QString::fromUtf8(protocol) + QString::number(getpid()); d->resume = false; d->needSendCanResume = false; d->mapConfig = QMap(); d->onHold = false; d->wasKilled = false; // d->processed_size = 0; d->totalSize = 0; connectSlave(QFile::decodeName(app_socket)); d->remotefile = nullptr; d->inOpenLoop = false; d->exit_loop = false; } SlaveBase::~SlaveBase() { delete d->configGroup; delete d->config; delete d->remotefile; delete d; s_protocol = ""; } void SlaveBase::dispatchLoop() { while (!d->exit_loop) { if (d->nextTimeout.isValid() && (d->nextTimeout.hasExpired(d->nextTimeoutMsecs))) { QByteArray data = d->timeoutData; d->nextTimeout.invalidate(); d->timeoutData = QByteArray(); special(data); } Q_ASSERT(d->appConnection.inited()); int ms = -1; if (d->nextTimeout.isValid()) { ms = qMax(d->nextTimeout.elapsed() - d->nextTimeoutMsecs, 1); } int ret = -1; if (d->appConnection.hasTaskAvailable() || d->appConnection.waitForIncomingTask(ms)) { // dispatch application messages int cmd; QByteArray data; ret = d->appConnection.read(&cmd, data); if (ret != -1) { if (d->inOpenLoop) { dispatchOpenCommand(cmd, data); } else { dispatch(cmd, data); } } } else { ret = d->appConnection.isConnected() ? 0 : -1; } if (ret == -1) { // some error occurred, perhaps no more application // When the app exits, should the slave be put back in the pool ? if (!d->exit_loop && d->isConnectedToApp && !d->poolSocket.isEmpty()) { disconnectSlave(); d->isConnectedToApp = false; closeConnection(); d->updateTempAuthStatus(); connectSlave(d->poolSocket); } else { break; } } //I think we get here when we were killed in dispatch() and not in select() if (wasKilled()) { //qDebug() << "slave was killed, returning"; break; } // execute deferred deletes QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); } // execute deferred deletes QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); } void SlaveBase::connectSlave(const QString &address) { d->appConnection.connectToRemote(QUrl(address)); if (!d->appConnection.inited()) { /*qDebug() << "failed to connect to" << address << endl << "Reason:" << d->appConnection.errorString();*/ exit(); } d->inOpenLoop = false; } void SlaveBase::disconnectSlave() { d->appConnection.close(); } void SlaveBase::setMetaData(const QString &key, const QString &value) { mOutgoingMetaData.insert(key, value); // replaces existing key if already there } QString SlaveBase::metaData(const QString &key) const { auto it = mIncomingMetaData.find(key); if (it != mIncomingMetaData.end()) { return *it; } return d->configData.value(key); } MetaData SlaveBase::allMetaData() const { return mIncomingMetaData; } bool SlaveBase::hasMetaData(const QString &key) const { if (mIncomingMetaData.contains(key)) { return true; } if (d->configData.contains(key)) { return true; } return false; } QMap SlaveBase::mapConfig() const { return d->mapConfig; } bool SlaveBase::configValue(const QString &key, bool defaultValue) const { return d->mapConfig.value(key, defaultValue).toBool(); } int SlaveBase::configValue(const QString &key, int defaultValue) const { return d->mapConfig.value(key, defaultValue).toInt(); } QString SlaveBase::configValue(const QString &key, const QString &defaultValue) const { return d->mapConfig.value(key, defaultValue).toString(); } KConfigGroup *SlaveBase::config() { if (!d->config) { d->config = new KConfig(QString(), KConfig::SimpleConfig); d->configGroup = new KConfigGroup(d->config, QString()); auto end = d->mapConfig.cend(); for (auto it = d->mapConfig.cbegin(); it != end; ++it) { d->configGroup->writeEntry(it.key(), it->toString().toUtf8(), KConfigGroup::WriteConfigFlags()); } } return d->configGroup; } void SlaveBase::sendMetaData() { sendAndKeepMetaData(); mOutgoingMetaData.clear(); } void SlaveBase::sendAndKeepMetaData() { if (!mOutgoingMetaData.isEmpty()) { KIO_DATA << mOutgoingMetaData; send(INF_META_DATA, data); } } KRemoteEncoding *SlaveBase::remoteEncoding() { if (d->remotefile) { return d->remotefile; } const QByteArray charset(metaData(QStringLiteral("Charset")).toLatin1()); return (d->remotefile = new KRemoteEncoding(charset.constData())); } void SlaveBase::data(const QByteArray &data) { sendMetaData(); send(MSG_DATA, data); } void SlaveBase::dataReq() { //sendMetaData(); if (d->needSendCanResume) { canResume(0); } send(MSG_DATA_REQ); } void SlaveBase::opened() { sendMetaData(); send(MSG_OPENED); d->inOpenLoop = true; } void SlaveBase::error(int _errid, const QString &_text) { KIO_STATE_ASSERT(d->m_finalityCommand, Q_FUNC_INFO, qUtf8Printable(QStringLiteral("error() was called, but it's not supposed to! Please fix the %1 KIO slave") .arg(QCoreApplication::applicationName()))); if (d->m_state == d->ErrorCalled) { KIO_STATE_ASSERT(false, Q_FUNC_INFO, qUtf8Printable(QStringLiteral("error() called twice! Please fix the %1 KIO slave") .arg(QCoreApplication::applicationName()))); return; } else if (d->m_state == d->FinishedCalled) { KIO_STATE_ASSERT(false, Q_FUNC_INFO, qUtf8Printable(QStringLiteral("error() called after finished()! Please fix the %1 KIO slave") .arg(QCoreApplication::applicationName()))); return; } d->m_state = d->ErrorCalled; mIncomingMetaData.clear(); // Clear meta data d->rebuildConfig(); mOutgoingMetaData.clear(); KIO_DATA << static_cast(_errid) << _text; send(MSG_ERROR, data); //reset d->totalSize = 0; d->inOpenLoop = false; d->m_confirmationAsked = false; d->m_privilegeOperationStatus = OperationNotAllowed; } void SlaveBase::connected() { send(MSG_CONNECTED); } void SlaveBase::finished() { if (!d->pendingListEntries.isEmpty()) { if (!d->m_rootEntryListed) { qCWarning(KIO_CORE) << "UDSEntry for '.' not found, creating a default one. Please fix the" << QCoreApplication::applicationName() << "KIO slave"; KIO::UDSEntry entry; entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral(".")); entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.fastInsert(KIO::UDSEntry::UDS_SIZE, 0); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH); d->pendingListEntries.append(entry); } listEntries(d->pendingListEntries); d->pendingListEntries.clear(); } KIO_STATE_ASSERT(d->m_finalityCommand, Q_FUNC_INFO, qUtf8Printable(QStringLiteral("finished() was called, but it's not supposed to! Please fix the %2 KIO slave") .arg(QCoreApplication::applicationName()))); if (d->m_state == d->FinishedCalled) { KIO_STATE_ASSERT(false, Q_FUNC_INFO, qUtf8Printable(QStringLiteral("finished() called twice! Please fix the %1 KIO slave") .arg(QCoreApplication::applicationName()))); return; } else if (d->m_state == d->ErrorCalled) { KIO_STATE_ASSERT(false, Q_FUNC_INFO, qUtf8Printable(QStringLiteral("finished() called after error()! Please fix the %1 KIO slave") .arg(QCoreApplication::applicationName()))); return; } d->m_state = d->FinishedCalled; mIncomingMetaData.clear(); // Clear meta data d->rebuildConfig(); sendMetaData(); send(MSG_FINISHED); // reset d->totalSize = 0; d->inOpenLoop = false; d->m_rootEntryListed = false; d->m_confirmationAsked = false; d->m_privilegeOperationStatus = OperationNotAllowed; } void SlaveBase::needSubUrlData() { send(MSG_NEED_SUBURL_DATA); } void SlaveBase::slaveStatus(const QString &host, bool connected) { qint64 pid = getpid(); qint8 b = connected ? 1 : 0; KIO_DATA << pid << mProtocol << host << b << d->onHold << d->onHoldUrl << d->hasTempAuth(); send(MSG_SLAVE_STATUS_V2, data); } void SlaveBase::canResume() { send(MSG_CANRESUME); } void SlaveBase::totalSize(KIO::filesize_t _bytes) { KIO_DATA << KIO_FILESIZE_T(_bytes); send(INF_TOTAL_SIZE, data); //this one is usually called before the first item is listed in listDir() d->totalSize = _bytes; } void SlaveBase::processedSize(KIO::filesize_t _bytes) { bool emitSignal = false; if (_bytes == d->totalSize) { emitSignal = true; } else { if (d->lastTimeout.isValid()) { emitSignal = d->lastTimeout.hasExpired(100); // emit size 10 times a second } else { emitSignal = true; } } if (emitSignal) { KIO_DATA << KIO_FILESIZE_T(_bytes); send(INF_PROCESSED_SIZE, data); d->lastTimeout.start(); } // d->processed_size = _bytes; } void SlaveBase::written(KIO::filesize_t _bytes) { KIO_DATA << KIO_FILESIZE_T(_bytes); send(MSG_WRITTEN, data); } void SlaveBase::position(KIO::filesize_t _pos) { KIO_DATA << KIO_FILESIZE_T(_pos); send(INF_POSITION, data); } void SlaveBase::processedPercent(float /* percent */) { //qDebug() << "STUB"; } void SlaveBase::speed(unsigned long _bytes_per_second) { KIO_DATA << static_cast(_bytes_per_second); send(INF_SPEED, data); } void SlaveBase::redirection(const QUrl &_url) { KIO_DATA << _url; send(INF_REDIRECTION, data); } void SlaveBase::errorPage() { send(INF_ERROR_PAGE); } static bool isSubCommand(int cmd) { return ((cmd == CMD_REPARSECONFIGURATION) || (cmd == CMD_META_DATA) || (cmd == CMD_CONFIG) || (cmd == CMD_SUBURL) || (cmd == CMD_SLAVE_STATUS) || (cmd == CMD_SLAVE_CONNECT) || (cmd == CMD_SLAVE_HOLD) || (cmd == CMD_MULTI_GET)); } void SlaveBase::mimeType(const QString &_type) { //qDebug() << _type; int cmd; do { // Send the meta-data each time we send the mime-type. if (!mOutgoingMetaData.isEmpty()) { //qDebug() << "emitting meta data"; KIO_DATA << mOutgoingMetaData; send(INF_META_DATA, data); } KIO_DATA << _type; send(INF_MIME_TYPE, data); while (true) { cmd = 0; int ret = -1; if (d->appConnection.hasTaskAvailable() || d->appConnection.waitForIncomingTask(-1)) { ret = d->appConnection.read(&cmd, data); } if (ret == -1) { //qDebug() << "read error"; exit(); } //qDebug() << "got" << cmd; if (cmd == CMD_HOST) { // Ignore. continue; } if (!isSubCommand(cmd)) { break; } dispatch(cmd, data); } } while (cmd != CMD_NONE); mOutgoingMetaData.clear(); } void SlaveBase::exit() { d->exit_loop = true; // Using ::exit() here is too much (crashes in qdbus's qglobalstatic object), // so let's cleanly exit dispatchLoop() instead. // Update: we do need to call exit(), otherwise a long download (get()) would // keep going until it ends, even though the application exited. ::exit(255); } void SlaveBase::warning(const QString &_msg) { KIO_DATA << _msg; send(INF_WARNING, data); } void SlaveBase::infoMessage(const QString &_msg) { KIO_DATA << _msg; send(INF_INFOMESSAGE, data); } bool SlaveBase::requestNetwork(const QString &host) { KIO_DATA << host << d->slaveid; send(MSG_NET_REQUEST, data); if (waitForAnswer(INF_NETWORK_STATUS, 0, data) != -1) { bool status; QDataStream stream(data); stream >> status; return status; } else { return false; } } void SlaveBase::dropNetwork(const QString &host) { KIO_DATA << host << d->slaveid; send(MSG_NET_DROP, data); } void SlaveBase::statEntry(const UDSEntry &entry) { KIO_DATA << entry; send(MSG_STAT_ENTRY, data); } void SlaveBase::listEntry(const UDSEntry &entry, bool _ready) { if (_ready) { // #366795: many slaves don't create an entry for ".", so we keep track if they do // and we provide a fallback in finished() otherwise. if (entry.stringValue(KIO::UDSEntry::UDS_NAME) == QLatin1Char('.')) { d->m_rootEntryListed = true; } listEntries(d->pendingListEntries); d->pendingListEntries.clear(); } else { listEntry(entry); } } void SlaveBase::listEntry(const UDSEntry &entry) { // #366795: many slaves don't create an entry for ".", so we keep track if they do // and we provide a fallback in finished() otherwise. if (entry.stringValue(KIO::UDSEntry::UDS_NAME) == QLatin1Char('.')) { d->m_rootEntryListed = true; } // We start measuring the time from the point we start filling the list if (d->pendingListEntries.isEmpty()) { d->m_timeSinceLastBatch.restart(); } d->pendingListEntries.append(entry); // If more then KIO_MAX_SEND_BATCH_TIME time is passed, emit the current batch // Also emit if we have piled up a large number of entries already, to save memory (and time) if (d->m_timeSinceLastBatch.elapsed() > KIO_MAX_SEND_BATCH_TIME || d->pendingListEntries.size() > KIO_MAX_ENTRIES_PER_BATCH) { listEntries(d->pendingListEntries); d->pendingListEntries.clear(); // Restart time d->m_timeSinceLastBatch.restart(); } } void SlaveBase::listEntries(const UDSEntryList &list) { QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); for (const UDSEntry &entry : list) { stream << entry; } send(MSG_LIST_ENTRIES, data); } static void sigpipe_handler(int) { // We ignore a SIGPIPE in slaves. // A SIGPIPE can happen in two cases: // 1) Communication error with application. // 2) Communication error with network. slaveWriteError = true; // Don't add anything else here, especially no debug output } void SlaveBase::setHost(QString const &, quint16, QString const &, QString const &) { } KIOCORE_EXPORT QString KIO::unsupportedActionErrorString(const QString &protocol, int cmd) { switch (cmd) { case CMD_CONNECT: return i18n("Opening connections is not supported with the protocol %1.", protocol); case CMD_DISCONNECT: return i18n("Closing connections is not supported with the protocol %1.", protocol); case CMD_STAT: return i18n("Accessing files is not supported with the protocol %1.", protocol); case CMD_PUT: return i18n("Writing to %1 is not supported.", protocol); case CMD_SPECIAL: return i18n("There are no special actions available for protocol %1.", protocol); case CMD_LISTDIR: return i18n("Listing folders is not supported for protocol %1.", protocol); case CMD_GET: return i18n("Retrieving data from %1 is not supported.", protocol); case CMD_MIMETYPE: return i18n("Retrieving mime type information from %1 is not supported.", protocol); case CMD_RENAME: return i18n("Renaming or moving files within %1 is not supported.", protocol); case CMD_SYMLINK: return i18n("Creating symlinks is not supported with protocol %1.", protocol); case CMD_COPY: return i18n("Copying files within %1 is not supported.", protocol); case CMD_DEL: return i18n("Deleting files from %1 is not supported.", protocol); case CMD_MKDIR: return i18n("Creating folders is not supported with protocol %1.", protocol); case CMD_CHMOD: return i18n("Changing the attributes of files is not supported with protocol %1.", protocol); case CMD_CHOWN: return i18n("Changing the ownership of files is not supported with protocol %1.", protocol); case CMD_SUBURL: return i18n("Using sub-URLs with %1 is not supported.", protocol); case CMD_MULTI_GET: return i18n("Multiple get is not supported with protocol %1.", protocol); case CMD_OPEN: return i18n("Opening files is not supported with protocol %1.", protocol); default: return i18n("Protocol %1 does not support action %2.", protocol, cmd); }/*end switch*/ } void SlaveBase::openConnection() { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_CONNECT)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_CONNECT)); } void SlaveBase::closeConnection() { } // No response! void SlaveBase::stat(QUrl const &) { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_STAT)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_STAT)); } void SlaveBase::put(QUrl const &, int, JobFlags) { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_PUT)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_PUT)); } void SlaveBase::special(const QByteArray &) { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_SPECIAL)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_SPECIAL)); } void SlaveBase::listDir(QUrl const &) { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_LISTDIR)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_LISTDIR)); } void SlaveBase::get(QUrl const &) { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_GET)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_GET)); } void SlaveBase::open(QUrl const &, QIODevice::OpenMode) { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_OPEN)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_OPEN)); } void SlaveBase::read(KIO::filesize_t) { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_READ)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_READ)); } void SlaveBase::write(const QByteArray &) { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_WRITE)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_WRITE)); } void SlaveBase::seek(KIO::filesize_t) { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_SEEK)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_SEEK)); } void SlaveBase::close() { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_CLOSE)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_CLOSE)); } void SlaveBase::mimetype(QUrl const &url) { get(url); } void SlaveBase::rename(QUrl const &, QUrl const &, JobFlags) { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_RENAME)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_RENAME)); } void SlaveBase::symlink(QString const &, QUrl const &, JobFlags) { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_SYMLINK)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_SYMLINK)); } void SlaveBase::copy(QUrl const &, QUrl const &, int, JobFlags) { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_COPY)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_COPY)); } void SlaveBase::del(QUrl const &, bool) { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_DEL)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_DEL)); } void SlaveBase::setLinkDest(const QUrl &, const QString &) { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_SETLINKDEST)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_SETLINKDEST)); } void SlaveBase::mkdir(QUrl const &, int) { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_MKDIR)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_MKDIR)); } void SlaveBase::chmod(QUrl const &, int) { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_CHMOD)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_CHMOD)); } void SlaveBase::setModificationTime(QUrl const &, const QDateTime &) { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_SETMODIFICATIONTIME)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_SETMODIFICATIONTIME)); } void SlaveBase::chown(QUrl const &, const QString &, const QString &) { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_CHOWN)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_CHOWN)); } void SlaveBase::setSubUrl(QUrl const &) { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_SUBURL)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_SUBURL)); } void SlaveBase::multiGet(const QByteArray &) { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_MULTI_GET)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_MULTI_GET)); } void SlaveBase::slave_status() { slaveStatus(QString(), false); } void SlaveBase::reparseConfiguration() { delete d->remotefile; d->remotefile = nullptr; } bool SlaveBase::openPasswordDialog(AuthInfo &info, const QString &errorMsg) { const int errorCode = openPasswordDialogV2(info, errorMsg); return errorCode == KJob::NoError; } int SlaveBase::openPasswordDialogV2(AuthInfo &info, const QString &errorMsg) { const long windowId = metaData(QStringLiteral("window-id")).toLong(); const unsigned long userTimestamp = metaData(QStringLiteral("user-timestamp")).toULong(); QString errorMessage; if (metaData(QStringLiteral("no-auth-prompt")).compare(QLatin1String("true"), Qt::CaseInsensitive) == 0) { errorMessage = QStringLiteral(""); } else { errorMessage = errorMsg; } AuthInfo dlgInfo(info); // Make sure the modified flag is not set. dlgInfo.setModified(false); // Prevent queryAuthInfo from caching the user supplied password since // we need the ioslaves to first authenticate against the server with // it to ensure it is valid. dlgInfo.setExtraField(QStringLiteral("skip-caching-on-query"), true); KPasswdServerClient *passwdServerClient = d->passwdServerClient(); const int errCode = passwdServerClient->queryAuthInfo(&dlgInfo, errorMessage, windowId, userTimestamp); if (errCode == KJob::NoError) { info = dlgInfo; } return errCode; } int SlaveBase::messageBox(MessageBoxType type, const QString &text, const QString &caption, const QString &buttonYes, const QString &buttonNo) { return messageBox(text, type, caption, buttonYes, buttonNo, QString()); } int SlaveBase::messageBox(const QString &text, MessageBoxType type, const QString &caption, const QString &_buttonYes, const QString &_buttonNo, const QString &dontAskAgainName) { QString buttonYes = _buttonYes.isNull() ? i18n("&Yes") : _buttonYes; QString buttonNo = _buttonNo.isNull() ? i18n("&No") : _buttonNo; //qDebug() << "messageBox " << type << " " << text << " - " << caption << buttonYes << buttonNo; KIO_DATA << static_cast(type) << text << caption << buttonYes << buttonNo << dontAskAgainName; send(INF_MESSAGEBOX, data); if (waitForAnswer(CMD_MESSAGEBOXANSWER, 0, data) != -1) { QDataStream stream(data); int answer; stream >> answer; //qDebug() << "got messagebox answer" << answer; return answer; } else { return 0; // communication failure } } bool SlaveBase::canResume(KIO::filesize_t offset) { //qDebug() << "offset=" << KIO::number(offset); d->needSendCanResume = false; KIO_DATA << KIO_FILESIZE_T(offset); send(MSG_RESUME, data); if (offset) { int cmd; if (waitForAnswer(CMD_RESUMEANSWER, CMD_NONE, data, &cmd) != -1) { //qDebug() << "returning" << (cmd == CMD_RESUMEANSWER); return cmd == CMD_RESUMEANSWER; } else { return false; } } else { // No resuming possible -> no answer to wait for return true; } } int SlaveBase::waitForAnswer(int expected1, int expected2, QByteArray &data, int *pCmd) { int cmd = 0; int result = -1; for (;;) { if (d->appConnection.hasTaskAvailable() || d->appConnection.waitForIncomingTask(-1)) { result = d->appConnection.read(&cmd, data); } if (result == -1) { //qDebug() << "read error."; return -1; } if (cmd == expected1 || cmd == expected2) { if (pCmd) { *pCmd = cmd; } return result; } if (isSubCommand(cmd)) { dispatch(cmd, data); } else { qFatal("Fatal Error: Got cmd %d, while waiting for an answer!", cmd); } } } int SlaveBase::readData(QByteArray &buffer) { int result = waitForAnswer(MSG_DATA, 0, buffer); //qDebug() << "readData: length = " << result << " "; return result; } void SlaveBase::setTimeoutSpecialCommand(int timeout, const QByteArray &data) { if (timeout > 0) { d->nextTimeoutMsecs = timeout*1000; // from seconds to milliseconds d->nextTimeout.start(); } else if (timeout == 0) { d->nextTimeoutMsecs = 1000; // Immediate timeout d->nextTimeout.start(); } else { d->nextTimeout.invalidate(); // Canceled } d->timeoutData = data; } void SlaveBase::dispatch(int command, const QByteArray &data) { QDataStream stream(data); QUrl url; int i; d->m_finalityCommand = true; // default switch (command) { case CMD_HOST: { QString passwd; QString host, user; quint16 port; stream >> host >> port >> user >> passwd; d->m_state = d->InsideMethod; d->m_finalityCommand = false; setHost(host, port, user, passwd); d->m_state = d->Idle; } break; case CMD_CONNECT: { openConnection(); } break; case CMD_DISCONNECT: { closeConnection(); } break; case CMD_SLAVE_STATUS: { d->m_state = d->InsideMethod; d->m_finalityCommand = false; slave_status(); // TODO verify that the slave has called slaveStatus()? d->m_state = d->Idle; } break; case CMD_SLAVE_CONNECT: { d->onHold = false; QString app_socket; QDataStream stream(data); stream >> app_socket; d->appConnection.send(MSG_SLAVE_ACK); disconnectSlave(); d->isConnectedToApp = true; connectSlave(app_socket); virtual_hook(AppConnectionMade, nullptr); } break; case CMD_SLAVE_HOLD: { QUrl url; QDataStream stream(data); stream >> url; d->onHoldUrl = url; d->onHold = true; disconnectSlave(); d->isConnectedToApp = false; // Do not close connection! connectSlave(d->poolSocket); } break; case CMD_REPARSECONFIGURATION: { d->m_state = d->InsideMethod; d->m_finalityCommand = false; reparseConfiguration(); d->m_state = d->Idle; } break; case CMD_CONFIG: { stream >> d->configData; d->rebuildConfig(); delete d->remotefile; d->remotefile = nullptr; } break; case CMD_GET: { stream >> url; d->m_state = d->InsideMethod; get(url); d->verifyState("get()"); d->m_state = d->Idle; } break; case CMD_OPEN: { stream >> url >> i; QIODevice::OpenMode mode = QFlag(i); d->m_state = d->InsideMethod; open(url, mode); //krazy:exclude=syscalls d->m_state = d->Idle; } break; case CMD_PUT: { int permissions; qint8 iOverwrite, iResume; stream >> url >> iOverwrite >> iResume >> permissions; JobFlags flags; if (iOverwrite != 0) { flags |= Overwrite; } if (iResume != 0) { flags |= Resume; } // Remember that we need to send canResume(), TransferJob is expecting // it. Well, in theory this shouldn't be done if resume is true. // (the resume bool is currently unused) d->needSendCanResume = true /* !resume */; d->m_state = d->InsideMethod; put(url, permissions, flags); d->verifyState("put()"); d->m_state = d->Idle; } break; case CMD_STAT: { stream >> url; d->m_state = d->InsideMethod; stat(url); //krazy:exclude=syscalls d->verifyState("stat()"); d->m_state = d->Idle; } break; case CMD_MIMETYPE: { stream >> url; d->m_state = d->InsideMethod; mimetype(url); d->verifyState("mimetype()"); d->m_state = d->Idle; } break; case CMD_LISTDIR: { stream >> url; d->m_state = d->InsideMethod; listDir(url); d->verifyState("listDir()"); d->m_state = d->Idle; } break; case CMD_MKDIR: { stream >> url >> i; d->m_state = d->InsideMethod; mkdir(url, i); //krazy:exclude=syscalls d->verifyState("mkdir()"); d->m_state = d->Idle; } break; case CMD_RENAME: { qint8 iOverwrite; QUrl url2; stream >> url >> url2 >> iOverwrite; JobFlags flags; if (iOverwrite != 0) { flags |= Overwrite; } d->m_state = d->InsideMethod; rename(url, url2, flags); //krazy:exclude=syscalls d->verifyState("rename()"); d->m_state = d->Idle; } break; case CMD_SYMLINK: { qint8 iOverwrite; QString target; stream >> target >> url >> iOverwrite; JobFlags flags; if (iOverwrite != 0) { flags |= Overwrite; } d->m_state = d->InsideMethod; symlink(target, url, flags); d->verifyState("symlink()"); d->m_state = d->Idle; } break; case CMD_COPY: { int permissions; qint8 iOverwrite; QUrl url2; stream >> url >> url2 >> permissions >> iOverwrite; JobFlags flags; if (iOverwrite != 0) { flags |= Overwrite; } d->m_state = d->InsideMethod; copy(url, url2, permissions, flags); d->verifyState("copy()"); d->m_state = d->Idle; } break; case CMD_DEL: { qint8 isFile; stream >> url >> isFile; d->m_state = d->InsideMethod; del(url, isFile != 0); d->verifyState("del()"); d->m_state = d->Idle; } break; case CMD_CHMOD: { stream >> url >> i; d->m_state = d->InsideMethod; chmod(url, i); d->verifyState("chmod()"); d->m_state = d->Idle; } break; case CMD_CHOWN: { QString owner, group; stream >> url >> owner >> group; d->m_state = d->InsideMethod; chown(url, owner, group); d->verifyState("chown()"); d->m_state = d->Idle; } break; case CMD_SETMODIFICATIONTIME: { QDateTime dt; stream >> url >> dt; d->m_state = d->InsideMethod; setModificationTime(url, dt); d->verifyState("setModificationTime()"); d->m_state = d->Idle; } break; case CMD_SPECIAL: { d->m_state = d->InsideMethod; special(data); d->verifyState("special()"); d->m_state = d->Idle; } break; case CMD_META_DATA: { //qDebug() << "(" << getpid() << ") Incoming meta-data..."; stream >> mIncomingMetaData; d->rebuildConfig(); } break; case CMD_SUBURL: { stream >> url; d->m_state = d->InsideMethod; setSubUrl(url); d->verifyErrorFinishedNotCalled("setSubUrl()"); d->m_state = d->Idle; } break; case CMD_NONE: { qCWarning(KIO_CORE) << "Got unexpected CMD_NONE!"; } break; case CMD_MULTI_GET: { d->m_state = d->InsideMethod; multiGet(data); d->verifyState("multiGet()"); d->m_state = d->Idle; } break; case CMD_FILESYSTEMFREESPACE: { stream >> url; void *data = static_cast(&url); d->m_state = d->InsideMethod; virtual_hook(GetFileSystemFreeSpace, data); d->verifyState("fileSystemFreeSpace()"); d->m_state = d->Idle; } break; default: { // Some command we don't understand. // Just ignore it, it may come from some future version of KIO. } break; } } bool SlaveBase::checkCachedAuthentication(AuthInfo &info) { KPasswdServerClient *passwdServerClient = d->passwdServerClient(); return (passwdServerClient->checkAuthInfo(&info, metaData(QStringLiteral("window-id")).toLong(), metaData(QStringLiteral("user-timestamp")).toULong())); } void SlaveBase::dispatchOpenCommand(int command, const QByteArray &data) { QDataStream stream(data); switch (command) { case CMD_READ: { KIO::filesize_t bytes; stream >> bytes; read(bytes); break; } case CMD_WRITE: { write(data); break; } case CMD_SEEK: { KIO::filesize_t offset; stream >> offset; seek(offset); break; } case CMD_NONE: break; case CMD_CLOSE: close(); // must call finish(), which will set d->inOpenLoop=false break; default: // Some command we don't understand. // Just ignore it, it may come from some future version of KIO. break; } } bool SlaveBase::cacheAuthentication(const AuthInfo &info) { KPasswdServerClient *passwdServerClient = d->passwdServerClient(); passwdServerClient->addAuthInfo(info, metaData(QStringLiteral("window-id")).toLongLong()); return true; } int SlaveBase::connectTimeout() { bool ok; QString tmp = metaData(QStringLiteral("ConnectTimeout")); int result = tmp.toInt(&ok); if (ok) { return result; } return DEFAULT_CONNECT_TIMEOUT; } int SlaveBase::proxyConnectTimeout() { bool ok; QString tmp = metaData(QStringLiteral("ProxyConnectTimeout")); int result = tmp.toInt(&ok); if (ok) { return result; } return DEFAULT_PROXY_CONNECT_TIMEOUT; } int SlaveBase::responseTimeout() { bool ok; QString tmp = metaData(QStringLiteral("ResponseTimeout")); int result = tmp.toInt(&ok); if (ok) { return result; } return DEFAULT_RESPONSE_TIMEOUT; } int SlaveBase::readTimeout() { bool ok; QString tmp = metaData(QStringLiteral("ReadTimeout")); int result = tmp.toInt(&ok); if (ok) { return result; } return DEFAULT_READ_TIMEOUT; } bool SlaveBase::wasKilled() const { return d->wasKilled; } void SlaveBase::setKillFlag() { d->wasKilled = true; } void SlaveBase::send(int cmd, const QByteArray &arr) { slaveWriteError = false; if (!d->appConnection.send(cmd, arr)) // Note that slaveWriteError can also be set by sigpipe_handler { slaveWriteError = true; } if (slaveWriteError) { exit(); } } void SlaveBase::virtual_hook(int id, void *data) { Q_UNUSED(data); switch(id) { case GetFileSystemFreeSpace: { - error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_FILESYSTEMFREESPACE)); + error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(protocolName(), CMD_FILESYSTEMFREESPACE)); } break; } } void SlaveBase::lookupHost(const QString &host) { KIO_DATA << host; send(MSG_HOST_INFO_REQ, data); } int SlaveBase::waitForHostInfo(QHostInfo &info) { QByteArray data; int result = waitForAnswer(CMD_HOST_INFO, 0, data); if (result == -1) { info.setError(QHostInfo::UnknownError); info.setErrorString(i18n("Unknown Error")); return result; } QDataStream stream(data); QString hostName; QList addresses; int error; QString errorString; stream >> hostName >> addresses >> error >> errorString; info.setHostName(hostName); info.setAddresses(addresses); info.setError(QHostInfo::HostInfoError(error)); info.setErrorString(errorString); return result; } PrivilegeOperationStatus SlaveBase::requestPrivilegeOperation() { if (d->m_privilegeOperationStatus == OperationNotAllowed) { QByteArray buffer; send(MSG_PRIVILEGE_EXEC); waitForAnswer(MSG_PRIVILEGE_EXEC, 0, buffer); QDataStream ds(buffer); ds >> d->m_privilegeOperationStatus >> d->m_warningCaption >> d-> m_warningMessage; } if (metaData(QStringLiteral("UnitTesting")) != QLatin1String("true") && d->m_privilegeOperationStatus == OperationAllowed && !d->m_confirmationAsked) { d->m_privilegeOperationStatus = d->askConfirmation(); d->m_confirmationAsked = true; } return KIO::PrivilegeOperationStatus(d->m_privilegeOperationStatus); } void SlaveBase::addTemporaryAuthorization(const QString &action) { d->m_tempAuths.insert(action); } diff --git a/src/core/slavebase.h b/src/core/slavebase.h index c61fcaeb..efc9f807 100644 --- a/src/core/slavebase.h +++ b/src/core/slavebase.h @@ -1,1049 +1,1055 @@ /* Copyright (C) 2000 David Faure 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. */ #ifndef SLAVEBASE_H #define SLAVEBASE_H #include #include #include #include "job_base.h" // for KIO::JobFlags #include #include class KConfigGroup; class KRemoteEncoding; class QUrl; namespace KIO { class Connection; class SlaveBasePrivate; /** * @class KIO::SlaveBase slavebase.h * * There are two classes that specifies the protocol between application (job) * and kioslave. SlaveInterface is the class to use on the application end, * SlaveBase is the one to use on the slave end. * * Slave implementations should simply inherit SlaveBase * * A call to foo() results in a call to slotFoo() on the other end. * * Note that a kioslave doesn't have a Qt event loop. When idle, it's waiting for a command * on the socket that connects it to the application. So don't expect a kioslave to react * to D-Bus signals for instance. KIOSlaves are short-lived anyway, so any kind of watching * or listening for notifications should be done elsewhere, for instance in a kded module * (see kio_desktop's desktopnotifier.cpp for an example). * * If a kioslave needs a Qt event loop within the implementation of one method, e.g. to * wait for an asynchronous operation to finish, that is possible, using QEventLoop. */ class KIOCORE_EXPORT SlaveBase { public: SlaveBase(const QByteArray &protocol, const QByteArray &pool_socket, const QByteArray &app_socket); virtual ~SlaveBase(); /** * @internal * Terminate the slave by calling the destructor and then ::exit() */ Q_NORETURN void exit(); /** * @internal */ void dispatchLoop(); /////////// // Message Signals to send to the job /////////// /** * Sends data in the slave to the job (i.e. in get). * * To signal end of data, simply send an empty * QByteArray(). * * @param data the data read by the slave */ void data(const QByteArray &data); /** * Asks for data from the job. * @see readData */ void dataReq(); /** * open succeeds * @see open() */ void opened(); /** * Call to signal an error. * This also finishes the job, so you must not call * finished() after calling this. * * If the error code is KIO::ERR_SLAVE_DEFINED then the * _text should contain the complete translated text of * of the error message. * * For all other error codes, _text should match the corresponding * error code. Usually, _text is a file or host name, or the error which * was passed from the server.
* For example, for KIO::ERR_DOES_NOT_EXIST, _text may only * be the file or folder which does not exist, nothing else. Otherwise, * this would break error strings generated by KIO::buildErrorString().
* If you have to add more details than what the standard error codes * provide, you'll need to use KIO::ERR_SLAVE_DEFINED. * For a complete list of what _text should contain for each error code, * look at the source of KIO::buildErrorString(). * * You can add rich text markup to the message, the places where the * error message will be displayed are rich text aware. * * @see KIO::Error * @see KIO::buildErrorString * @param _errid the error code from KIO::Error * @param _text the rich text error message */ void error(int _errid, const QString &_text); /** * Call in openConnection, if you reimplement it, when you're done. */ void connected(); /** * Call to signal successful completion of any command * besides openConnection and closeConnection. Do not * call this after calling error(). */ void finished(); /** * Call to signal that data from the sub-URL is needed */ void needSubUrlData(); /** * Used to report the status of the slave. * @param host the slave is currently connected to. (Should be * empty if not connected) * @param connected Whether an actual network connection exists. **/ void slaveStatus(const QString &host, bool connected); /** * Call this from stat() to express details about an object, the * UDSEntry customarily contains the atoms describing file name, size, * mimetype, etc. * @param _entry The UDSEntry containing all of the object attributes. */ void statEntry(const UDSEntry &_entry); /** * Call this in listDir, each time you have a bunch of entries * to report. * @param _entry The UDSEntry containing all of the object attributes. */ void listEntries(const UDSEntryList &_entry); /** * Call this at the beginning of put(), to give the size of the existing * partial file, if there is one. The @p offset argument notifies the * other job (the one that gets the data) about the offset to use. * In this case, the boolean returns whether we can indeed resume or not * (we can't if the protocol doing the get() doesn't support setting an offset) */ bool canResume(KIO::filesize_t offset); /** * Call this at the beginning of get(), if the "range-start" metadata was set * and returning byte ranges is implemented by this protocol. */ void canResume(); /////////// // Info Signals to send to the job /////////// /** * Call this in get and copy, to give the total size * of the file. */ void totalSize(KIO::filesize_t _bytes); /** * Call this during get and copy, once in a while, * to give some info about the current state. * Don't emit it in listDir, listEntries speaks for itself. */ void processedSize(KIO::filesize_t _bytes); void position(KIO::filesize_t _pos); void written(KIO::filesize_t _bytes); /** * Only use this if you can't know in advance the size of the * copied data. For example, if you're doing variable bitrate * compression of the source. * * STUB ! Currently unimplemented. Here now for binary compatibility. * * Call this during get and copy, once in a while, * to give some info about the current state. * Don't emit it in listDir, listEntries speaks for itself. */ void processedPercent(float percent); /** * Call this in get and copy, to give the current transfer * speed, but only if it can't be calculated out of the size you * passed to processedSize (in most cases you don't want to call it) */ void speed(unsigned long _bytes_per_second); /** * Call this to signal a redirection * The job will take care of going to that url. */ void redirection(const QUrl &_url); /** * Tell that we will only get an error page here. * This means: the data you'll get isn't the data you requested, * but an error page (usually HTML) that describes an error. */ void errorPage(); /** * Call this in mimetype() and in get(), when you know the mimetype. * See mimetype about other ways to implement it. */ void mimeType(const QString &_type); /** * Call to signal a warning, to be displayed in a dialog box. */ void warning(const QString &msg); /** * Call to signal a message, to be displayed if the application wants to, * for instance in a status bar. Usual examples are "connecting to host xyz", etc. */ void infoMessage(const QString &msg); /** * Type of message box. Should be kept in sync with KMessageBox::ButtonCode. */ enum MessageBoxType { QuestionYesNo = 1, WarningYesNo = 2, WarningContinueCancel = 3, WarningYesNoCancel = 4, Information = 5, SSLMessageBox = 6 }; /** * Button codes. Should be kept in sync with KMessageBox::ButtonCode */ enum ButtonCode { Ok = 1, Cancel = 2, Yes = 3, No = 4, Continue = 5 }; /** * Call this to show a message box from the slave * @param type type of message box: QuestionYesNo, WarningYesNo, WarningContinueCancel... * @param text Message string. May contain newlines. * @param caption Message box title. * @param buttonYes The text for the first button. * The default is i18n("&Yes"). * @param buttonNo The text for the second button. * The default is i18n("&No"). * Note: for ContinueCancel, buttonYes is the continue button and buttonNo is unused. * and for Information, none is used. * @return a button code, as defined in ButtonCode, or 0 on communication error. */ int messageBox(MessageBoxType type, const QString &text, const QString &caption = QString(), const QString &buttonYes = QString(), const QString &buttonNo = QString()); /** * Call this to show a message box from the slave * @param text Message string. May contain newlines. * @param type type of message box: QuestionYesNo, WarningYesNo, WarningContinueCancel... * @param caption Message box title. * @param buttonYes The text for the first button. * The default is i18n("&Yes"). * @param buttonNo The text for the second button. * The default is i18n("&No"). * Note: for ContinueCancel, buttonYes is the continue button and buttonNo is unused. * and for Information, none is used. * @param dontAskAgainName the name used to store result from 'Do not ask again' checkbox. * @return a button code, as defined in ButtonCode, or 0 on communication error. */ int messageBox(const QString &text, MessageBoxType type, const QString &caption = QString(), const QString &buttonYes = QString(), const QString &buttonNo = QString(), const QString &dontAskAgainName = QString()); /** * Sets meta-data to be send to the application before the first * data() or finished() signal. */ void setMetaData(const QString &key, const QString &value); /** * Queries for the existence of a certain config/meta-data entry * send by the application to the slave. */ bool hasMetaData(const QString &key) const; /** * Queries for config/meta-data send by the application to the slave. */ QString metaData(const QString &key) const; /** * @internal for ForwardingSlaveBase * Contains all metadata (but no config) sent by the application to the slave. */ MetaData allMetaData() const; /** * Returns a map to query config/meta-data information from. * * The application provides the slave with all configuration information * relevant for the current protocol and host. * * Use configValue() as shortcut. * @since 5.64 */ QMap mapConfig() const; /** * Returns a bool from the config/meta-data information. * @since 5.64 */ bool configValue(const QString &key, bool defaultValue) const; /** * Returns an int from the config/meta-data information. * @since 5.64 */ int configValue(const QString &key, int defaultValue) const; /** * Returns a QString from the config/meta-data information. * @since 5.64 */ QString configValue(const QString &key, const QString &defaultValue = QString()) const; #if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 64) /** * Returns a configuration object to query config/meta-data information * from. * * The application provides the slave with all configuration information * relevant for the current protocol and host. * * @deprecated since 5.64 use mapConfig instead */ KIOCORE_DEPRECATED_VERSION(5, 64, "Use SlaveBase::mapConfig()") KConfigGroup *config(); // KF6: perhaps rename mapConfig() to config() when removing this #endif /** * Returns an object that can translate remote filenames into proper * Unicode forms. This encoding can be set by the user. */ KRemoteEncoding *remoteEncoding(); /////////// // Commands sent by the job, the slave has to // override what it wants to implement /////////// /** * Set the host * * Called directly by createSlave, this is why there is no equivalent in * SlaveInterface, unlike the other methods. * * This method is called whenever a change in host, port or user occurs. */ virtual void setHost(const QString &host, quint16 port, const QString &user, const QString &pass); /** * Prepare slave for streaming operation */ virtual void setSubUrl(const QUrl &url); /** * Opens the connection (forced) * When this function gets called the slave is operating in * connection-oriented mode. * When a connection gets lost while the slave operates in * connection oriented mode, the slave should report * ERR_CONNECTION_BROKEN instead of reconnecting. The user is * expected to disconnect the slave in the error handler. */ virtual void openConnection(); /** * Closes the connection (forced) * Called when the application disconnects the slave to close * any open network connections. * * When the slave was operating in connection-oriented mode, * it should reset itself to connectionless (default) mode. */ virtual void closeConnection(); /** * get, aka read. * @param url the full url for this request. Host, port and user of the URL * can be assumed to be the same as in the last setHost() call. * * The slave should first "emit" the mimetype by calling mimeType(), * and then "emit" the data using the data() method. * * The reason why we need get() to emit the mimetype is: * when pasting a URL in krunner, or konqueror's location bar, * we have to find out what is the mimetype of that URL. * Rather than doing it with a call to mimetype(), then the app or part * would have to do a second request to the same server, this is done * like this: get() is called, and when it emits the mimetype, the job * is put on hold and the right app or part is launched. When that app * or part calls get(), the slave is magically reused, and the download * can now happen. All with a single call to get() in the slave. * This mechanism is also described in KIO::get(). */ virtual void get(const QUrl &url); /** * open. * @param url the full url for this request. Host, port and user of the URL * can be assumed to be the same as in the last setHost() call. * @param mode see \ref QIODevice::OpenMode */ virtual void open(const QUrl &url, QIODevice::OpenMode mode); /** * read. * @param size the requested amount of data to read * @see KIO::FileJob::read() */ virtual void read(KIO::filesize_t size); /** * write. * @param data the data to write * @see KIO::FileJob::write() */ virtual void write(const QByteArray &data); /** * seek. * @param offset the requested amount of data to read * @see KIO::FileJob::read() */ virtual void seek(KIO::filesize_t offset); /** * close. * @see KIO::FileJob::close() */ virtual void close(); /** * put, i.e. write data into a file. * * @param url where to write the file * @param permissions may be -1. In this case no special permission mode is set. * @param flags We support Overwrite here. Hopefully, we're going to * support Resume in the future, too. * If the file indeed already exists, the slave should NOT apply the * permissions change to it. * The support for resuming using .part files is done by calling canResume(). * * IMPORTANT: Use the "modified" metadata in order to set the modification time of the file. * * @see canResume() */ virtual void put(const QUrl &url, int permissions, JobFlags flags); /** * Finds all details for one file or directory. * The information returned is the same as what listDir returns, * but only for one file or directory. * Call statEntry() after creating the appropriate UDSEntry for this * url. * * You can use the "details" metadata to optimize this method to only * do as much work as needed by the application. * By default details is 2 (all details wanted, including modification time, size, etc.), * details==1 is used when deleting: we don't need all the information if it takes * too much time, no need to follow symlinks etc. * details==0 is used for very simple probing: we'll only get the answer * "it's a file or a directory (or a symlink), or it doesn't exist". */ virtual void stat(const QUrl &url); /** * Finds mimetype for one file or directory. * * This method should either emit 'mimeType' or it * should send a block of data big enough to be able * to determine the mimetype. * * If the slave doesn't reimplement it, a get will * be issued, i.e. the whole file will be downloaded before * determining the mimetype on it - this is obviously not a * good thing in most cases. */ virtual void mimetype(const QUrl &url); /** * Lists the contents of @p url. * The slave should emit ERR_CANNOT_ENTER_DIRECTORY if it doesn't exist, * if we don't have enough permissions. * It should also emit totalFiles as soon as it knows how many * files it will list. * You should not list files if the path in @p url is empty, but redirect * to a non-empty path instead. */ virtual void listDir(const QUrl &url); /** * Create a directory * @param url path to the directory to create * @param permissions the permissions to set after creating the directory * (-1 if no permissions to be set) * The slave emits ERR_CANNOT_MKDIR if failure. */ virtual void mkdir(const QUrl &url, int permissions); /** * Rename @p oldname into @p newname. * If the slave returns an error ERR_UNSUPPORTED_ACTION, the job will * ask for copy + del instead. * * Important: the slave must implement the logic "if the destination already * exists, error ERR_DIR_ALREADY_EXIST or ERR_FILE_ALREADY_EXIST". * For performance reasons no stat is done in the destination before hand, * the slave must do it. * * By default, rename() is only called when renaming (moving) from * yourproto://host/path to yourproto://host/otherpath. * * If you set renameFromFile=true then rename() will also be called when * moving a file from file:///path to yourproto://host/otherpath. * Otherwise such a move would have to be done the slow way (copy+delete). * See KProtocolManager::canRenameFromFile() for more details. * * If you set renameToFile=true then rename() will also be called when * moving a file from yourproto: to file:. * See KProtocolManager::canRenameToFile() for more details. * * @param src where to move the file from * @param dest where to move the file to * @param flags We support Overwrite here */ virtual void rename(const QUrl &src, const QUrl &dest, JobFlags flags); /** * Creates a symbolic link named @p dest, pointing to @p target, which * may be a relative or an absolute path. * @param target The string that will become the "target" of the link (can be relative) * @param dest The symlink to create. * @param flags We support Overwrite here */ virtual void symlink(const QString &target, const QUrl &dest, JobFlags flags); /** * Change permissions on @p url * The slave emits ERR_DOES_NOT_EXIST or ERR_CANNOT_CHMOD */ virtual void chmod(const QUrl &url, int permissions); /** * Change ownership of @p url * The slave emits ERR_DOES_NOT_EXIST or ERR_CANNOT_CHOWN */ virtual void chown(const QUrl &url, const QString &owner, const QString &group); /** * Sets the modification time for @url * For instance this is what CopyJob uses to set mtime on dirs at the end of a copy. * It could also be used to set the mtime on any file, in theory. * The usual implementation on unix is to call utime(path, &myutimbuf). * The slave emits ERR_DOES_NOT_EXIST or ERR_CANNOT_SETTIME */ virtual void setModificationTime(const QUrl &url, const QDateTime &mtime); /** * Copy @p src into @p dest. * * By default, copy() is only called when copying a file from * yourproto://host/path to yourproto://host/otherpath. * * If you set copyFromFile=true then copy() will also be called when * moving a file from file:///path to yourproto://host/otherpath. * Otherwise such a copy would have to be done the slow way (get+put). * See also KProtocolManager::canCopyFromFile(). * * If you set copyToFile=true then copy() will also be called when * moving a file from yourproto: to file:. * See also KProtocolManager::canCopyToFile(). * * If the slave returns an error ERR_UNSUPPORTED_ACTION, the job will * ask for get + put instead. * @param src where to copy the file from (decoded) * @param dest where to copy the file to (decoded) * @param permissions may be -1. In this case no special permission mode is set. * @param flags We support Overwrite here * * Don't forget to set the modification time of @p dest to be the modification time of @p src. */ virtual void copy(const QUrl &src, const QUrl &dest, int permissions, JobFlags flags); /** * Delete a file or directory. * @param url file/directory to delete * @param isfile if true, a file should be deleted. * if false, a directory should be deleted. * * By default, del() on a directory should FAIL if the directory is not empty. * However, if metadata("recurse") == "true", then the slave can do a recursive deletion. * This behavior is only invoked if the slave specifies deleteRecursive=true in its protocol file. */ virtual void del(const QUrl &url, bool isfile); /** * Change the destination of a symlink * @param url the url of the symlink to modify * @param target the new destination (target) of the symlink */ virtual void setLinkDest(const QUrl &url, const QString &target); /** * Used for any command that is specific to this slave (protocol) * Examples are : HTTP POST, mount and unmount (kio_file) * * @param data packed data; the meaning is completely dependent on the * slave, but usually starts with an int for the command number. * Document your slave's commands, at least in its header file. */ virtual void special(const QByteArray &data); /** * Used for multiple get. Currently only used for HTTP pipelining * support. * * @param data packed data; Contains number of URLs to fetch, and for * each URL the URL itself and its associated MetaData. */ virtual void multiGet(const QByteArray &data); /** * Called to get the status of the slave. Slave should respond * by calling slaveStatus(...) */ virtual void slave_status(); /** * Called by the scheduler to tell the slave that the configuration * changed (i.e. proxy settings) . */ virtual void reparseConfiguration(); /** * @return timeout value for connecting to remote host. */ int connectTimeout(); /** * @return timeout value for connecting to proxy in secs. */ int proxyConnectTimeout(); /** * @return timeout value for read from first data from * remote host in seconds. */ int responseTimeout(); /** * @return timeout value for read from subsequent data from * remote host in secs. */ int readTimeout(); /** * This function sets a timeout of @p timeout seconds and calls * special(data) when the timeout occurs as if it was called by the * application. * * A timeout can only occur when the slave is waiting for a command * from the application. * * Specifying a negative timeout cancels a pending timeout. * * Only one timeout at a time is supported, setting a timeout * cancels any pending timeout. */ void setTimeoutSpecialCommand(int timeout, const QByteArray &data = QByteArray()); ///////////////// // Dispatching (internal) //////////////// /** * @internal */ virtual void dispatch(int command, const QByteArray &data); /** * @internal */ virtual void dispatchOpenCommand(int command, const QByteArray &data); /** * Read data sent by the job, after a dataReq * * @param buffer buffer where data is stored * @return 0 on end of data, * > 0 bytes read * < 0 error **/ int readData(QByteArray &buffer); #if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) /** * It collects entries and emits them via listEntries * when enough of them are there or a certain time * frame exceeded (to make sure the app gets some * items in time but not too many items one by one * as this will cause a drastic performance penalty). * @param _entry The UDSEntry containing all of the object attributes. * @param ready set to true after emitting all items. @p _entry is not * used in this case * @deprecated since 5.0. the listEntry(entry, true) indicated * that the entry listing was completed. However, each slave should * already call finished() to also tell us that we're done listing. * You should make sure that finished() is called when the entry * listing is completed and simply remove the call to listEntry(entry, true). */ KIOCORE_DEPRECATED_VERSION(5, 0, "See API docs") void listEntry(const UDSEntry &_entry, bool ready); #endif /** * It collects entries and emits them via listEntries * when enough of them are there or a certain time * frame exceeded (to make sure the app gets some * items in time but not too many items one by one * as this will cause a drastic performance penalty). * @param entry The UDSEntry containing all of the object attributes. * @since 5.0 */ void listEntry(const UDSEntry &entry); /** * internal function to connect a slave to/ disconnect from * either the slave pool or the application */ void connectSlave(const QString &path); void disconnectSlave(); /** * Prompt the user for Authorization info (login & password). * * Use this function to request authorization information from * the end user. You can also pass an error message which explains * why a previous authorization attempt failed. Here is a very * simple example: * * \code * KIO::AuthInfo authInfo; * int errorCode = openPasswordDialogV2(authInfo); * if (!errorCode) { * qDebug() << QLatin1String("User: ") << authInfo.username; * qDebug() << QLatin1String("Password: not displayed here!"); * } else { * error(errorCode, QString()); * } * \endcode * * You can also preset some values like the username, caption or * comment as follows: * * \code * KIO::AuthInfo authInfo; * authInfo.caption = i18n("Acme Password Dialog"); * authInfo.username = "Wile E. Coyote"; * QString errorMsg = i18n("You entered an incorrect password."); * int errorCode = openPasswordDialogV2(authInfo, errorMsg); * [...] * \endcode * * \note You should consider using checkCachedAuthentication() to * see if the password is available in kpasswdserver before calling * this function. * * \note A call to this function can fail and return @p false, * if the password server could not be started for whatever reason. * * \note This function does not store the password information * automatically (and has not since kdelibs 4.7). If you want to * store the password information in a persistent storage like * KWallet, then you MUST call @ref cacheAuthentication. * * @see checkCachedAuthentication * @param info See AuthInfo. * @param errorMsg Error message to show * @return a KIO error code: NoError (0), KIO::USER_CANCELED, or other error codes. */ int openPasswordDialogV2(KIO::AuthInfo &info, const QString &errorMsg = QString()); #if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 24) /** * @deprecated since KF 5.24, use openPasswordDialogV2. * The return value works differently: * instead of * if (!openPasswordDialog()) { error(USER_CANCELED); } * store and pass the return value to error(), when NOT zero, * as shown documentation for openPasswordDialogV2(). */ KIOCORE_DEPRECATED_VERSION(5, 24, "Use SlaveBase::openPasswordDialogV2(...)") bool openPasswordDialog(KIO::AuthInfo &info, const QString &errorMsg = QString()); #endif /** * Checks for cached authentication based on parameters * given by @p info. * * Use this function to check if any cached password exists * for the URL given by @p info. If @p AuthInfo::realmValue * and/or @p AuthInfo::verifyPath flag is specified, then * they will also be factored in determining the presence * of a cached password. Note that @p Auth::url is a required * parameter when attempting to check for cached authorization * info. Here is a simple example: * * \code * AuthInfo info; * info.url = QUrl("http://www.foobar.org/foo/bar"); * info.username = "somename"; * info.verifyPath = true; * if ( !checkCachedAuthentication( info ) ) * { * int errorCode = openPasswordDialogV2(info); * .... * } * \endcode * * @param info See AuthInfo. * @return @p true if cached Authorization is found, false otherwise. */ bool checkCachedAuthentication(AuthInfo &info); /** * Caches @p info in a persistent storage like KWallet. * * Note that calling openPasswordDialogV2 does not store passwords * automatically for you (and has not since kdelibs 4.7). * * Here is a simple example of how to use cacheAuthentication: * * \code * AuthInfo info; * info.url = QUrl("http://www.foobar.org/foo/bar"); * info.username = "somename"; * info.verifyPath = true; * if ( !checkCachedAuthentication( info ) ) { * int errorCode = openPasswordDialogV2(info); * if (!errorCode) { * if (info.keepPassword) { // user asked password be save/remembered * cacheAuthentication(info); * } * } * } * \endcode * * @param info See AuthInfo. * @return @p true if @p info was successfully cached. */ bool cacheAuthentication(const AuthInfo &info); #if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) /** * Used by the slave to check if it can connect * to a given host. This should be called where the slave is ready * to do a ::connect() on a socket. For each call to * requestNetwork must exist a matching call to * dropNetwork, or the system will stay online until * KNetMgr gets closed (or the SlaveBase gets destructed)! * * If KNetMgr is not running, then this is a no-op and returns true * * @param host tells the netmgr the host the slave wants to connect * to. As this could also be a proxy, we can't just take * the host currently connected to (but that's the default * value) * * @return true in theory, the host is reachable * false the system is offline and the host is in a remote network. * * @deprecated Since 5.0, for a very very long time, not implemented anymore * Probably dates back to model dialup times. */ KIOCORE_DEPRECATED_VERSION(5, 0, "Not implemented & used") bool requestNetwork(const QString &host = QString()); #endif #if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 0) /** * * Used by the slave to withdraw a connection requested by * requestNetwork. This function cancels the last call to * requestNetwork. If a client uses more than one internet * connection, it must use dropNetwork(host) to * stop each request. * * If KNetMgr is not running, then this is a no-op. * * @param host the host passed to requestNetwork * * A slave should call this function every time it disconnect from a host. * * * @deprecated Since 5.0, for a very very long time, not implemented anymore * Probably dates back to model dialup times. * */ KIOCORE_DEPRECATED_VERSION(5, 0, "Not implemented & used") void dropNetwork(const QString &host = QString()); #endif /** * Wait for an answer to our request, until we get @p expected1 or @p expected2 * @return the result from readData, as well as the cmd in *pCmd if set, and the data in @p data */ int waitForAnswer(int expected1, int expected2, QByteArray &data, int *pCmd = nullptr); /** * Internal function to transmit meta data to the application. * m_outgoingMetaData will be cleared; this means that if the slave is for * example put on hold and picked up by a different KIO::Job later the new * job will not see the metadata sent before. * See kio/DESIGN.krun for an overview of the state * progression of a job/slave. * @warning calling this method may seriously interfere with the operation * of KIO which relies on the presence of some metadata at some points in time. * You should not use it if you are not familiar with KIO and not before * the slave is connected to the last job before returning to idle state. */ void sendMetaData(); /** * Internal function to transmit meta data to the application. * Like sendMetaData() but m_outgoingMetaData will not be cleared. * This method is mainly useful in code that runs before the slave is connected * to its final job. */ void sendAndKeepMetaData(); /** If your ioslave was killed by a signal, wasKilled() returns true. Check it regularly in lengthy functions (e.g. in get();) and return as fast as possible from this function if wasKilled() returns true. This will ensure that your slave destructor will be called correctly. */ bool wasKilled() const; /** Internally used. * @internal */ void setKillFlag(); /** Internally used * @internal */ void lookupHost(const QString &host); /** Internally used * @internal */ int waitForHostInfo(QHostInfo &info); /** * Checks with job if privilege operation is allowed. * @return privilege operation status. * @see PrivilegeOperationStatus * @since 5.43 */ PrivilegeOperationStatus requestPrivilegeOperation(); /** * Adds @p action to the list of PolicyKit actions which the * slave is authorized to perform. * * @param action the PolicyKit action * @since 5.45 */ void addTemporaryAuthorization(const QString &action); protected: /** * Name of the protocol supported by this slave */ QByteArray mProtocol; //Often used by TcpSlaveBase and unlikely to change MetaData mOutgoingMetaData; MetaData mIncomingMetaData; enum VirtualFunctionId { AppConnectionMade = 0, GetFileSystemFreeSpace = 1 // KF6 TODO: Turn into a virtual method }; virtual void virtual_hook(int id, void *data); private: + // Convenience function converting mProtocol to QString as unsupportedActionErrorString(), which + // is used in many places in the code, takes a QString parameter + inline const QString protocolName() const { + return QString::fromLatin1(mProtocol); + } + // This helps catching missing tr()/i18n() calls in error(). void error(int _errid, const QByteArray &_text); void send(int cmd, const QByteArray &arr = QByteArray()); SlaveBasePrivate *const d; friend class SlaveBasePrivate; }; /** * Returns an appropriate error message if the given command @p cmd * is an unsupported action (ERR_UNSUPPORTED_ACTION). * @param protocol name of the protocol * @param cmd given command * @see enum Command */ KIOCORE_EXPORT QString unsupportedActionErrorString(const QString &protocol, int cmd); } #endif diff --git a/src/filewidgets/kencodingfiledialog.cpp b/src/filewidgets/kencodingfiledialog.cpp index a7905afe..508746f9 100644 --- a/src/filewidgets/kencodingfiledialog.cpp +++ b/src/filewidgets/kencodingfiledialog.cpp @@ -1,286 +1,284 @@ // -*- c++ -*- /* This file is part of the KDE libraries Copyright (C) 2003 Joseph Wenninger 2003 Andras Mantia 2013 Teo Mrnjavac 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. */ -// TODO: remove me -#undef QT_NO_CAST_FROM_ASCII - #include "kencodingfiledialog.h" #include "kfilewidget.h" #include #include #include #include #include #include #include #include #include #include #include struct KEncodingFileDialogPrivate { KEncodingFileDialogPrivate() : cfgGroup(KSharedConfig::openConfig(), ConfigGroup) {} KComboBox *encoding; KFileWidget *w; KConfigGroup cfgGroup; }; KEncodingFileDialog::KEncodingFileDialog(const QUrl &startDir, const QString &encoding, const QString &filter, const QString &caption, QFileDialog::AcceptMode type, QWidget *parent) : QDialog(parent, Qt::Dialog) , d(new KEncodingFileDialogPrivate) { d->w = new KFileWidget(startDir, this); d->w->setFilter(filter); if (type == QFileDialog::AcceptOpen) { d->w->setOperationMode(KFileWidget::Opening); } else { d->w->setOperationMode(KFileWidget::Saving); } setWindowTitle(caption); //ops->clearHistory(); KWindowConfig::restoreWindowSize(windowHandle(), d->cfgGroup); QBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(d->w); d->w->okButton()->show(); connect(d->w->okButton(), &QAbstractButton::clicked, this, &KEncodingFileDialog::slotOk); d->w->cancelButton()->show(); connect(d->w->cancelButton(), &QAbstractButton::clicked, this, &KEncodingFileDialog::slotCancel); connect(d->w, &KFileWidget::accepted, this, &KEncodingFileDialog::accept); d->encoding = new KComboBox(this); d->w->setCustomWidget(i18n("Encoding:"), d->encoding); d->encoding->clear(); QString sEncoding = encoding; - QString systemEncoding = QTextCodec::codecForLocale()->name(); + QString systemEncoding = QLatin1String(QTextCodec::codecForLocale()->name()); if (sEncoding.isEmpty() || sEncoding == QLatin1String("System")) { sEncoding = systemEncoding; } const QStringList encodings(KCharsets::charsets()->availableEncodingNames()); int insert = 0, system = 0; bool foundRequested = false; for (const QString &encoding : encodings) { bool found = false; QTextCodec *codecForEnc = KCharsets::charsets()->codecForName(encoding, found); if (found) { d->encoding->addItem(encoding); - if ((codecForEnc->name() == sEncoding) || (encoding == sEncoding)) { + const QString codecName = QLatin1String(codecForEnc->name()); + if ((codecName == sEncoding) || (encoding == sEncoding)) { d->encoding->setCurrentIndex(insert); foundRequested = true; } - if ((codecForEnc->name() == systemEncoding) || (encoding == systemEncoding)) { + if ((codecName == systemEncoding) || (encoding == systemEncoding)) { system = insert; } insert++; } } if (!foundRequested) { d->encoding->setCurrentIndex(system); } } KEncodingFileDialog::~KEncodingFileDialog() { delete d; } QString KEncodingFileDialog::selectedEncoding() const { if (d->encoding) { return d->encoding->currentText(); } else { return QString(); } } KEncodingFileDialog::Result KEncodingFileDialog::getOpenFileNameAndEncoding(const QString &encoding, const QUrl &startDir, const QString &filter, QWidget *parent, const QString &caption) { KEncodingFileDialog dlg(startDir, encoding, filter, caption.isNull() ? i18n("Open") : caption, QFileDialog::AcceptOpen, parent); dlg.d->w->setMode(KFile::File | KFile::LocalOnly); dlg.exec(); Result res; res.fileNames << dlg.d->w->selectedFile(); res.encoding = dlg.selectedEncoding(); return res; } KEncodingFileDialog::Result KEncodingFileDialog::getOpenFileNamesAndEncoding(const QString &encoding, const QUrl &startDir, const QString &filter, QWidget *parent, const QString &caption) { KEncodingFileDialog dlg(startDir, encoding, filter, caption.isNull() ? i18n("Open") : caption, QFileDialog::AcceptOpen, parent); dlg.d->w->setMode(KFile::Files | KFile::LocalOnly); dlg.exec(); Result res; res.fileNames = dlg.d->w->selectedFiles(); res.encoding = dlg.selectedEncoding(); return res; } KEncodingFileDialog::Result KEncodingFileDialog::getOpenUrlAndEncoding(const QString &encoding, const QUrl &startDir, const QString &filter, QWidget *parent, const QString &caption) { KEncodingFileDialog dlg(startDir, encoding, filter, caption.isNull() ? i18n("Open") : caption, QFileDialog::AcceptOpen, parent); dlg.d->w->setMode(KFile::File); dlg.exec(); Result res; res.URLs << dlg.d->w->selectedUrl(); res.encoding = dlg.selectedEncoding(); return res; } KEncodingFileDialog::Result KEncodingFileDialog::getOpenUrlsAndEncoding(const QString &encoding, const QUrl &startDir, const QString &filter, QWidget *parent, const QString &caption) { KEncodingFileDialog dlg(startDir, encoding, filter, caption.isNull() ? i18n("Open") : caption, QFileDialog::AcceptOpen, parent); dlg.d->w->setMode(KFile::Files); dlg.exec(); Result res; res.URLs = dlg.d->w->selectedUrls(); res.encoding = dlg.selectedEncoding(); return res; } KEncodingFileDialog::Result KEncodingFileDialog::getSaveFileNameAndEncoding(const QString &encoding, const QUrl &dir, const QString &filter, QWidget *parent, const QString &caption) { KEncodingFileDialog dlg(dir, encoding, filter, caption.isNull() ? i18n("Save As") : caption, QFileDialog::AcceptSave, parent); dlg.d->w->setMode(KFile::File); dlg.exec(); QString filename = dlg.d->w->selectedFile(); if (!filename.isEmpty()) { KRecentDocument::add(QUrl::fromLocalFile(filename)); } Result res; res.fileNames << filename; res.encoding = dlg.selectedEncoding(); return res; } KEncodingFileDialog::Result KEncodingFileDialog::getSaveUrlAndEncoding(const QString &encoding, const QUrl &dir, const QString &filter, QWidget *parent, const QString &caption) { KEncodingFileDialog dlg(dir, encoding, filter, caption.isNull() ? i18n("Save As") : caption, QFileDialog::AcceptSave, parent); dlg.d->w->setMode(KFile::File); Result res; if (dlg.exec() == QDialog::Accepted) { QUrl url = dlg.d->w->selectedUrl(); if (url.isValid()) { KRecentDocument::add(url); } res.URLs << url; res.encoding = dlg.selectedEncoding(); } return res; } QSize KEncodingFileDialog::sizeHint() const { return d->w->dialogSizeHint(); } void KEncodingFileDialog::hideEvent(QHideEvent *e) { KWindowConfig::saveWindowSize(windowHandle(), d->cfgGroup, KConfigBase::Persistent); QDialog::hideEvent(e); } void KEncodingFileDialog::accept() { d->w->accept(); QDialog::accept(); } void KEncodingFileDialog::slotOk() { d->w->slotOk(); } void KEncodingFileDialog::slotCancel() { d->w->slotCancel(); reject(); } #include "moc_kencodingfiledialog.cpp" diff --git a/src/filewidgets/kfileplacesitem.cpp b/src/filewidgets/kfileplacesitem.cpp index 6071b5c1..610f7009 100644 --- a/src/filewidgets/kfileplacesitem.cpp +++ b/src/filewidgets/kfileplacesitem.cpp @@ -1,436 +1,436 @@ /* This file is part of the KDE project Copyright (C) 2007 Kevin Ottens 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 "kfileplacesitem_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static bool isTrash(const KBookmark &bk) { return bk.url().toString() == QLatin1String("trash:/"); } KFilePlacesItem::KFilePlacesItem(KBookmarkManager *manager, const QString &address, const QString &udi) : m_manager(manager), m_folderIsEmpty(true), m_isCdrom(false), m_isAccessible(false) { updateDeviceInfo(udi); setBookmark(m_manager->findByAddress(address)); if (udi.isEmpty() && m_bookmark.metaDataItem(QStringLiteral("ID")).isEmpty()) { m_bookmark.setMetaDataItem(QStringLiteral("ID"), generateNewId()); } else if (udi.isEmpty()) { if (isTrash(m_bookmark)) { KConfig cfg(QStringLiteral("trashrc"), KConfig::SimpleConfig); const KConfigGroup group = cfg.group("Status"); m_folderIsEmpty = group.readEntry("Empty", true); } } } KFilePlacesItem::~KFilePlacesItem() { } QString KFilePlacesItem::id() const { if (isDevice()) { return bookmark().metaDataItem(QStringLiteral("UDI")); } else { return bookmark().metaDataItem(QStringLiteral("ID")); } } bool KFilePlacesItem::isDevice() const { return !bookmark().metaDataItem(QStringLiteral("UDI")).isEmpty(); } KBookmark KFilePlacesItem::bookmark() const { return m_bookmark; } void KFilePlacesItem::setBookmark(const KBookmark &bookmark) { m_bookmark = bookmark; updateDeviceInfo(m_bookmark.metaDataItem(QStringLiteral("UDI"))); if (bookmark.metaDataItem(QStringLiteral("isSystemItem")) == QLatin1String("true")) { // This context must stay as it is - the translated system bookmark names // are created with 'KFile System Bookmarks' as their context, so this // ensures the right string is picked from the catalog. // (coles, 13th May 2009) m_text = i18nc("KFile System Bookmarks", bookmark.text().toUtf8().data()); } else { m_text = bookmark.text(); } const KFilePlacesModel::GroupType type = groupType(); switch (type) { case KFilePlacesModel::PlacesType: m_groupName = i18nc("@item", "Places"); break; case KFilePlacesModel::RemoteType: m_groupName = i18nc("@item", "Remote"); break; case KFilePlacesModel::RecentlySavedType: m_groupName = i18nc("@item The place group section name for recent dynamic lists", "Recent"); break; case KFilePlacesModel::SearchForType: m_groupName = i18nc("@item", "Search For"); break; case KFilePlacesModel::DevicesType: m_groupName = i18nc("@item", "Devices"); break; case KFilePlacesModel::RemovableDevicesType: m_groupName = i18nc("@item", "Removable Devices"); break; case KFilePlacesModel::TagsType: m_groupName = i18nc("@item", "Tags"); break; default: Q_UNREACHABLE(); break; } } Solid::Device KFilePlacesItem::device() const { return m_device; } QVariant KFilePlacesItem::data(int role) const { if (role == KFilePlacesModel::GroupRole) { return QVariant(m_groupName); } else if (role != KFilePlacesModel::HiddenRole && role != Qt::BackgroundRole && isDevice()) { return deviceData(role); } else { return bookmarkData(role); } } KFilePlacesModel::GroupType KFilePlacesItem::groupType() const { if (!isDevice()) { const QString protocol = bookmark().url().scheme(); if (protocol == QLatin1String("timeline") || protocol == QLatin1String("recentlyused")) { return KFilePlacesModel::RecentlySavedType; } if (protocol.contains(QLatin1String("search"))) { return KFilePlacesModel::SearchForType; } if (protocol == QLatin1String("bluetooth") || protocol == QLatin1String("obexftp") || protocol == QLatin1String("kdeconnect")) { return KFilePlacesModel::DevicesType; } if (protocol == QLatin1String("tags")) { return KFilePlacesModel::TagsType; } if (protocol == QLatin1String("remote") || KProtocolInfo::protocolClass(protocol) != QLatin1String(":local")) { return KFilePlacesModel::RemoteType; } else { return KFilePlacesModel::PlacesType; } } if (m_drive && (m_drive->isHotpluggable() || m_drive->isRemovable())) { return KFilePlacesModel::RemovableDevicesType; } else if (m_networkShare) { return KFilePlacesModel::RemoteType; } else { return KFilePlacesModel::DevicesType; } } bool KFilePlacesItem::isHidden() const { return m_bookmark.metaDataItem(QStringLiteral("IsHidden")) == QLatin1String("true"); } void KFilePlacesItem::setHidden(bool hide) { if (m_bookmark.isNull() || isHidden() == hide) { return; } m_bookmark.setMetaDataItem(QStringLiteral("IsHidden"), hide ? QStringLiteral("true") : QStringLiteral("false")); } QVariant KFilePlacesItem::bookmarkData(int role) const { KBookmark b = bookmark(); if (b.isNull()) { return QVariant(); } switch (role) { case Qt::DisplayRole: return m_text; case Qt::DecorationRole: return QIcon::fromTheme(iconNameForBookmark(b)); case Qt::BackgroundRole: if (isHidden()) { return QColor(Qt::lightGray); } else { return QVariant(); } case KFilePlacesModel::UrlRole: return b.url(); case KFilePlacesModel::SetupNeededRole: return false; case KFilePlacesModel::HiddenRole: return isHidden(); case KFilePlacesModel::IconNameRole: return iconNameForBookmark(b); default: return QVariant(); } } QVariant KFilePlacesItem::deviceData(int role) const { Solid::Device d = device(); if (d.isValid()) { switch (role) { case Qt::DisplayRole: return d.description(); case Qt::DecorationRole: return KDE::icon(m_iconPath, m_emblems); case KFilePlacesModel::UrlRole: if (m_access) { const QString path = m_access->filePath(); return path.isEmpty() ? QUrl() : QUrl::fromLocalFile(path); } else if (m_disc && (m_disc->availableContent() & Solid::OpticalDisc::Audio) != 0) { Solid::Block *block = d.as(); if (block) { QString device = block->device(); return QUrl(QStringLiteral("audiocd:/?device=%1").arg(device)); } // We failed to get the block device. Assume audiocd:/ can // figure it out, but cannot handle multiple disc drives. // See https://bugs.kde.org/show_bug.cgi?id=314544#c40 return QUrl(QStringLiteral("audiocd:/")); } else if (m_mtp) { return QUrl(QStringLiteral("mtp:udi=%1").arg(d.udi())); } else { return QVariant(); } case KFilePlacesModel::SetupNeededRole: if (m_access) { return !m_isAccessible; } else { return QVariant(); } case KFilePlacesModel::FixedDeviceRole: { if (m_drive != nullptr) { return !m_drive->isHotpluggable() && !m_drive->isRemovable(); } return true; } case KFilePlacesModel::CapacityBarRecommendedRole: return m_isAccessible && !m_isCdrom; case KFilePlacesModel::IconNameRole: return m_iconPath; default: return QVariant(); } } else { return QVariant(); } } KBookmark KFilePlacesItem::createBookmark(KBookmarkManager *manager, const QString &label, const QUrl &url, const QString &iconName, KFilePlacesItem *after) { KBookmarkGroup root = manager->root(); if (root.isNull()) { return KBookmark(); } QString empty_icon = iconName; if (url.toString() == QLatin1String("trash:/")) { if (empty_icon.endsWith(QLatin1String("-full"))) { empty_icon.chop(5); } else if (empty_icon.isEmpty()) { empty_icon = QStringLiteral("user-trash"); } } KBookmark bookmark = root.addBookmark(label, url, empty_icon); bookmark.setMetaDataItem(QStringLiteral("ID"), generateNewId()); if (after) { root.moveBookmark(bookmark, after->bookmark()); } return bookmark; } KBookmark KFilePlacesItem::createSystemBookmark(KBookmarkManager *manager, - const QString &translationContext, - const QString &untranslatedLabel, + const char *translationContext, + const QByteArray &untranslatedLabel, const QUrl &url, const QString &iconName) { Q_UNUSED(translationContext); // parameter is only necessary to force the caller // to provide a marked-for-translation string for the label, with context - KBookmark bookmark = createBookmark(manager, untranslatedLabel, url, iconName); + KBookmark bookmark = createBookmark(manager, QString::fromUtf8(untranslatedLabel), url, iconName); if (!bookmark.isNull()) { bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), QStringLiteral("true")); } return bookmark; } KBookmark KFilePlacesItem::createDeviceBookmark(KBookmarkManager *manager, const QString &udi) { KBookmarkGroup root = manager->root(); if (root.isNull()) { return KBookmark(); } KBookmark bookmark = root.createNewSeparator(); bookmark.setMetaDataItem(QStringLiteral("UDI"), udi); bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), QStringLiteral("true")); return bookmark; } KBookmark KFilePlacesItem::createTagBookmark(KBookmarkManager *manager, const QString &tag) { - KBookmark bookmark = createSystemBookmark(manager, tag, tag, QUrl(QLatin1String("tags:/") + tag), QLatin1String("tag")); + KBookmark bookmark = createSystemBookmark(manager, tag.toUtf8().data(), tag.toUtf8(), QUrl(QLatin1String("tags:/") + tag), QLatin1String("tag")); bookmark.setMetaDataItem(QStringLiteral("tag"), tag); bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), QStringLiteral("true")); return bookmark; } QString KFilePlacesItem::generateNewId() { static int count = 0; // return QString::number(count++); return QString::number(QDateTime::currentSecsSinceEpoch()) + QLatin1Char('/') + QString::number(count++); // return QString::number(QDateTime::currentSecsSinceEpoch()) // + '/' + QString::number(qrand()); } bool KFilePlacesItem::updateDeviceInfo(const QString &udi) { if (m_device.udi() == udi) { return false; } if (m_access) { m_access->disconnect(this); } m_device = Solid::Device(udi); if (m_device.isValid()) { m_access = m_device.as(); m_volume = m_device.as(); m_disc = m_device.as(); m_mtp = m_device.as(); m_networkShare = m_device.as(); m_iconPath = m_device.icon(); m_emblems = m_device.emblems(); m_drive = nullptr; Solid::Device parentDevice = m_device; while (parentDevice.isValid() && !m_drive) { m_drive = parentDevice.as(); parentDevice = parentDevice.parent(); } if (m_access) { connect(m_access.data(), &Solid::StorageAccess::accessibilityChanged, this, &KFilePlacesItem::onAccessibilityChanged); onAccessibilityChanged(m_access->isAccessible()); } } else { m_access = nullptr; m_volume = nullptr; m_disc = nullptr; m_mtp = nullptr; m_drive = nullptr; m_networkShare = nullptr; m_iconPath.clear(); m_emblems.clear(); } return true; } void KFilePlacesItem::onAccessibilityChanged(bool isAccessible) { m_isAccessible = isAccessible; m_isCdrom = m_device.is() || m_device.parent().is(); m_emblems = m_device.emblems(); emit itemChanged(id()); } QString KFilePlacesItem::iconNameForBookmark(const KBookmark &bookmark) const { if (!m_folderIsEmpty && isTrash(bookmark)) { return bookmark.icon() + QLatin1String("-full"); } else { return bookmark.icon(); } } #include "moc_kfileplacesitem_p.cpp" diff --git a/src/filewidgets/kfileplacesitem_p.h b/src/filewidgets/kfileplacesitem_p.h index 40cd02ef..8ba222a7 100644 --- a/src/filewidgets/kfileplacesitem_p.h +++ b/src/filewidgets/kfileplacesitem_p.h @@ -1,121 +1,121 @@ /* This file is part of the KDE project Copyright (C) 2007 Kevin Ottens 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. */ #ifndef KFILEPLACESITEM_P_H #define KFILEPLACESITEM_P_H #include #include #include #include #include #include #include "kfileplacesmodel.h" class KDirLister; namespace Solid { class StorageAccess; class StorageVolume; class StorageDrive; class NetworkShare; class OpticalDisc; class PortableMediaPlayer; } class KFilePlacesItem : public QObject { Q_OBJECT public: enum GroupType { PlacesType, RemoteType, RecentlySavedType, SearchForType, DevicesType, RemovableDevicesType, TagsType }; KFilePlacesItem(KBookmarkManager *manager, const QString &address, const QString &udi = QString()); ~KFilePlacesItem(); QString id() const; bool isDevice() const; KBookmark bookmark() const; void setBookmark(const KBookmark &bookmark); Solid::Device device() const; QVariant data(int role) const; KFilePlacesModel::GroupType groupType() const; bool isHidden() const; void setHidden(bool hide); static KBookmark createBookmark(KBookmarkManager *manager, const QString &label, const QUrl &url, const QString &iconName, KFilePlacesItem *after = nullptr); static KBookmark createSystemBookmark(KBookmarkManager *manager, - const QString &translationContext, - const QString &untranslatedLabel, + const char *translationContext, + const QByteArray &untranslatedLabel, const QUrl &url, const QString &iconName); static KBookmark createDeviceBookmark(KBookmarkManager *manager, const QString &udi); static KBookmark createTagBookmark(KBookmarkManager *manager, const QString &tag); Q_SIGNALS: void itemChanged(const QString &id); private Q_SLOTS: void onAccessibilityChanged(bool); private: QVariant bookmarkData(int role) const; QVariant deviceData(int role) const; QString iconNameForBookmark(const KBookmark &bookmark) const; static QString generateNewId(); bool updateDeviceInfo(const QString &udi); KBookmarkManager *m_manager; KBookmark m_bookmark; bool m_folderIsEmpty; bool m_isCdrom; bool m_isAccessible; QString m_text; Solid::Device m_device; QPointer m_access; QPointer m_volume; QPointer m_drive; QPointer m_disc; QPointer m_mtp; QPointer m_networkShare; QString m_iconPath; QStringList m_emblems; QString m_groupName; }; #endif diff --git a/src/filewidgets/kfileplacesmodel.cpp b/src/filewidgets/kfileplacesmodel.cpp index 4d14cbc2..f1c8ae6c 100644 --- a/src/filewidgets/kfileplacesmodel.cpp +++ b/src/filewidgets/kfileplacesmodel.cpp @@ -1,1384 +1,1382 @@ /* This file is part of the KDE project Copyright (C) 2007 Kevin Ottens Copyright (C) 2007 David Faure 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. */ -// TODO: remove me -#undef QT_NO_CAST_FROM_ASCII - #include "kfileplacesmodel.h" #include "kfileplacesitem_p.h" #ifdef _WIN32_WCE #include "Windows.h" #include "WinBase.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { QString stateNameForGroupType(KFilePlacesModel::GroupType type) { switch (type) { case KFilePlacesModel::PlacesType: return QStringLiteral("GroupState-Places-IsHidden"); case KFilePlacesModel::RemoteType: return QStringLiteral("GroupState-Remote-IsHidden"); case KFilePlacesModel::RecentlySavedType: return QStringLiteral("GroupState-RecentlySaved-IsHidden"); case KFilePlacesModel::SearchForType: return QStringLiteral("GroupState-SearchFor-IsHidden"); case KFilePlacesModel::DevicesType: return QStringLiteral("GroupState-Devices-IsHidden"); case KFilePlacesModel::RemovableDevicesType: return QStringLiteral("GroupState-RemovableDevices-IsHidden"); case KFilePlacesModel::TagsType: return QStringLiteral("GroupState-Tags-IsHidden"); default: Q_UNREACHABLE(); } } static bool isFileIndexingEnabled() { KConfig config(QStringLiteral("baloofilerc")); KConfigGroup basicSettings = config.group("Basic Settings"); return basicSettings.readEntry("Indexing-Enabled", true); } static QString timelineDateString(int year, int month, int day = 0) { const QString dateFormat = QStringLiteral("%1-%2"); QString date = dateFormat.arg(year).arg(month, 2, 10, QLatin1Char('0')); if (day > 0) { date += QStringLiteral("-%1").arg(day, 2, 10, QLatin1Char('0')); } return date; } static QUrl createTimelineUrl(const QUrl &url) { // based on dolphin urls const QString timelinePrefix = QLatin1String("timeline:") + QLatin1Char('/'); QUrl timelineUrl; const QString path = url.toDisplayString(QUrl::PreferLocalFile); if (path.endsWith(QLatin1String("/yesterday"))) { const QDate date = QDate::currentDate().addDays(-1); const int year = date.year(); const int month = date.month(); const int day = date.day(); timelineUrl = QUrl(timelinePrefix + timelineDateString(year, month) + QLatin1Char('/') + timelineDateString(year, month, day)); } else if (path.endsWith(QLatin1String("/thismonth"))) { const QDate date = QDate::currentDate(); timelineUrl = QUrl(timelinePrefix + timelineDateString(date.year(), date.month())); } else if (path.endsWith(QLatin1String("/lastmonth"))) { const QDate date = QDate::currentDate().addMonths(-1); timelineUrl = QUrl(timelinePrefix + timelineDateString(date.year(), date.month())); } else { Q_ASSERT(path.endsWith(QLatin1String("/today"))); timelineUrl = url; } return timelineUrl; } static QUrl createSearchUrl(const QUrl &url) { QUrl searchUrl = url; const QString path = url.toDisplayString(QUrl::PreferLocalFile); const QStringList validSearchPaths = { QStringLiteral("/documents"), QStringLiteral("/images"), QStringLiteral("/audio"), QStringLiteral("/videos") }; for (const QString &validPath : validSearchPaths) { if (path.endsWith(validPath)) { searchUrl.setScheme(QStringLiteral("baloosearch")); return searchUrl; } } qWarning() << "Invalid search url:" << url; return searchUrl; } } class Q_DECL_HIDDEN KFilePlacesModel::Private { public: explicit Private(KFilePlacesModel *self) : q(self), bookmarkManager(nullptr), fileIndexingEnabled(isFileIndexingEnabled()), tags(), tagsLister(new KCoreDirLister()) { if (KProtocolInfo::isKnownProtocol(QStringLiteral("tags"))) { connect(tagsLister, &KCoreDirLister::itemsAdded, q, [this](const QUrl&, const KFileItemList& items) { if(tags.isEmpty()) { QList existingBookmarks; KBookmarkGroup root = bookmarkManager->root(); KBookmark bookmark = root.first(); while (!bookmark.isNull()) { existingBookmarks.append(bookmark.url()); bookmark = root.next(bookmark); } if (!existingBookmarks.contains(QUrl(tagsUrlBase))) { - KBookmark alltags = KFilePlacesItem::createSystemBookmark(bookmarkManager, QStringLiteral("All tags"), i18n("All tags"), QUrl(tagsUrlBase), QStringLiteral("tag")); + KBookmark alltags = KFilePlacesItem::createSystemBookmark(bookmarkManager, "All tags" + , i18n("All tags").toUtf8().data() + , QUrl(tagsUrlBase), QStringLiteral("tag")); } } for (const KFileItem &item: items) { const QString name = item.name(); if (!tags.contains(name)) { tags.append(name); } } _k_reloadBookmarks(); }); connect(tagsLister, &KCoreDirLister::itemsDeleted, q, [this](const KFileItemList& items) { for (const KFileItem &item: items) { tags.removeAll(item.name()); } _k_reloadBookmarks(); }); tagsLister->openUrl(QUrl(tagsUrlBase), KCoreDirLister::OpenUrlFlag::Reload); } } ~Private() { qDeleteAll(items); } KFilePlacesModel * const q; QList items; QVector availableDevices; QMap setupInProgress; QStringList supportedSchemes; Solid::Predicate predicate; KBookmarkManager *bookmarkManager; const bool fileIndexingEnabled; QString alternativeApplicationName; void reloadAndSignal(); QList loadBookmarkList(); int findNearestPosition(int source, int target); QVector tags; const QString tagsUrlBase = QStringLiteral("tags:/"); KCoreDirLister* tagsLister; void _k_initDeviceList(); void _k_deviceAdded(const QString &udi); void _k_deviceRemoved(const QString &udi); void _k_itemChanged(const QString &udi); void _k_reloadBookmarks(); void _k_storageSetupDone(Solid::ErrorType error, const QVariant &errorData); void _k_storageTeardownDone(Solid::ErrorType error, const QVariant &errorData); private: bool isBalooUrl(const QUrl &url) const; }; KBookmark KFilePlacesModel::bookmarkForUrl(const QUrl &searchUrl) const { KBookmarkGroup root = d->bookmarkManager->root(); KBookmark current = root.first(); while (!current.isNull()) { if (current.url() == searchUrl) { return current; } current = root.next(current); } return KBookmark(); } KFilePlacesModel::KFilePlacesModel(const QString &alternativeApplicationName, QObject *parent) : QAbstractItemModel(parent), d(new Private(this)) { const QString file = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/user-places.xbel"); d->bookmarkManager = KBookmarkManager::managerForExternalFile(file); d->alternativeApplicationName = alternativeApplicationName; // Let's put some places in there if it's empty. KBookmarkGroup root = d->bookmarkManager->root(); const auto setDefaultMetadataItemForGroup = [&root](KFilePlacesModel::GroupType type) { root.setMetaDataItem(stateNameForGroupType(type), QStringLiteral("false")); }; if (root.first().isNull() || !QFile::exists(file)) { - // NOTE: The context for these I18NC_NOOP calls has to be "KFile System Bookmarks". // The real i18nc call is made later, with this context, so the two must match. // createSystemBookmark actually does nothing with its second argument, the context, KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Home"), QUrl::fromLocalFile(QDir::homePath()), QStringLiteral("user-home")); // Some distros may not create various standard XDG folders by default // so check for their existence before adding bookmarks for them const QString desktopFolder = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); if (QDir(desktopFolder).exists()) { KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Desktop"), QUrl::fromLocalFile(desktopFolder), QStringLiteral("user-desktop")); } const QString documentsFolder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); if (QDir(documentsFolder).exists()) { KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Documents"), QUrl::fromLocalFile(documentsFolder), QStringLiteral("folder-documents")); } const QString downloadFolder = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); if (QDir(downloadFolder).exists()) { KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Downloads"), QUrl::fromLocalFile(downloadFolder), QStringLiteral("folder-downloads")); } KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Network"), QUrl(QStringLiteral("remote:/")), QStringLiteral("folder-network")); KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Trash"), QUrl(QStringLiteral("trash:/")), QStringLiteral("user-trash")); setDefaultMetadataItemForGroup(PlacesType); setDefaultMetadataItemForGroup(RemoteType); setDefaultMetadataItemForGroup(DevicesType); setDefaultMetadataItemForGroup(RemovableDevicesType); setDefaultMetadataItemForGroup(TagsType); // Force bookmarks to be saved. If on open/save dialog and the bookmarks are not saved, QFile::exists // will always return false, which opening/closing all the time the open/save dialog would case the // bookmarks to be added once each time, having lots of times each bookmark. (ereslibre) d->bookmarkManager->saveAs(file); } // Add a Recently Used entry if available (it comes from kio-extras) if (qEnvironmentVariableIsSet("KDE_FULL_SESSION") && KProtocolInfo::isKnownProtocol(QStringLiteral("recentlyused")) && root.metaDataItem(QStringLiteral("withRecentlyUsed")) != QLatin1String("true")) { root.setMetaDataItem(QStringLiteral("withRecentlyUsed"), QStringLiteral("true")); KBookmark recentFilesBookmark = KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Recent Files"), QUrl(QStringLiteral("recentlyused:/files")), QStringLiteral("document-open-recent")); KBookmark recentDirectoriesBookmark = KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Recent Locations"), QUrl(QStringLiteral("recentlyused:/locations")), QStringLiteral("folder-open-recent")); setDefaultMetadataItemForGroup(RecentlySavedType); // Move The recently used bookmarks below the trash, making it the first element in the Recent group - KBookmark trashBookmark = bookmarkForUrl(QUrl("trash:/")); + KBookmark trashBookmark = bookmarkForUrl(QUrl(QStringLiteral("trash:/"))); if (!trashBookmark.isNull()) { root.moveBookmark(recentFilesBookmark, trashBookmark); root.moveBookmark(recentDirectoriesBookmark, recentFilesBookmark); } d->bookmarkManager->save(); } // if baloo is enabled, add new urls even if the bookmark file is not empty if (d->fileIndexingEnabled && root.metaDataItem(QStringLiteral("withBaloo")) != QLatin1String("true")) { root.setMetaDataItem(QStringLiteral("withBaloo"), QStringLiteral("true")); KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Modified Today"), QUrl(QStringLiteral("timeline:/today")), QStringLiteral("go-jump-today")); KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Modified Yesterday"), QUrl(QStringLiteral("timeline:/yesterday")), QStringLiteral("view-calendar-day")); KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Documents"), QUrl(QStringLiteral("search:/documents")), QStringLiteral("folder-text")); KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Images"), QUrl(QStringLiteral("search:/images")), QStringLiteral("folder-images")); KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Audio"), QUrl(QStringLiteral("search:/audio")), QStringLiteral("folder-sound")); KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Videos"), QUrl(QStringLiteral("search:/videos")), QStringLiteral("folder-videos")); setDefaultMetadataItemForGroup(SearchForType); setDefaultMetadataItemForGroup(RecentlySavedType); d->bookmarkManager->save(); } QString predicate(QString::fromLatin1("[[[[ StorageVolume.ignored == false AND [ StorageVolume.usage == 'FileSystem' OR StorageVolume.usage == 'Encrypted' ]]" " OR " "[ IS StorageAccess AND StorageDrive.driveType == 'Floppy' ]]" " OR " "OpticalDisc.availableContent & 'Audio' ]" " OR " "StorageAccess.ignored == false ]")); if (KProtocolInfo::isKnownProtocol(QStringLiteral("mtp"))) { predicate = QLatin1Char('[') + predicate + QLatin1String(" OR PortableMediaPlayer.supportedProtocols == 'mtp']"); } d->predicate = Solid::Predicate::fromString(predicate); Q_ASSERT(d->predicate.isValid()); connect(d->bookmarkManager, SIGNAL(changed(QString,QString)), this, SLOT(_k_reloadBookmarks())); connect(d->bookmarkManager, SIGNAL(bookmarksChanged(QString)), this, SLOT(_k_reloadBookmarks())); d->_k_reloadBookmarks(); QTimer::singleShot(0, this, SLOT(_k_initDeviceList())); } KFilePlacesModel::KFilePlacesModel(QObject *parent) : KFilePlacesModel({}, parent) { } KFilePlacesModel::~KFilePlacesModel() { delete d; } QUrl KFilePlacesModel::url(const QModelIndex &index) const { return data(index, UrlRole).toUrl(); } bool KFilePlacesModel::setupNeeded(const QModelIndex &index) const { return data(index, SetupNeededRole).toBool(); } QIcon KFilePlacesModel::icon(const QModelIndex &index) const { return data(index, Qt::DecorationRole).value(); } QString KFilePlacesModel::text(const QModelIndex &index) const { return data(index, Qt::DisplayRole).toString(); } bool KFilePlacesModel::isHidden(const QModelIndex &index) const { //Note: we do not want to show an index if its parent is hidden return data(index, HiddenRole).toBool() || isGroupHidden(index); } bool KFilePlacesModel::isGroupHidden(const GroupType type) const { const QString hidden = d->bookmarkManager->root().metaDataItem(stateNameForGroupType(type)); return hidden == QLatin1String("true") ? true : false; } bool KFilePlacesModel::isGroupHidden(const QModelIndex &index) const { if (!index.isValid()) { return false; } KFilePlacesItem *item = static_cast(index.internalPointer()); return isGroupHidden(item->groupType()); } bool KFilePlacesModel::isDevice(const QModelIndex &index) const { if (!index.isValid()) { return false; } KFilePlacesItem *item = static_cast(index.internalPointer()); return item->isDevice(); } Solid::Device KFilePlacesModel::deviceForIndex(const QModelIndex &index) const { if (!index.isValid()) { return Solid::Device(); } KFilePlacesItem *item = static_cast(index.internalPointer()); if (item->isDevice()) { return item->device(); } else { return Solid::Device(); } } KBookmark KFilePlacesModel::bookmarkForIndex(const QModelIndex &index) const { if (!index.isValid()) { return KBookmark(); } KFilePlacesItem *item = static_cast(index.internalPointer()); return item->bookmark(); } KFilePlacesModel::GroupType KFilePlacesModel::groupType(const QModelIndex &index) const { if (!index.isValid()) { return UnknownType; } KFilePlacesItem *item = static_cast(index.internalPointer()); return item->groupType(); } QModelIndexList KFilePlacesModel::groupIndexes(const KFilePlacesModel::GroupType type) const { if (type == UnknownType) { return QModelIndexList(); } QModelIndexList indexes; const int rows = rowCount(); for (int row = 0; row < rows ; ++row) { const QModelIndex current = index(row, 0); if (groupType(current) == type) { indexes << current; } } return indexes; } QVariant KFilePlacesModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } KFilePlacesItem *item = static_cast(index.internalPointer()); if (role == KFilePlacesModel::GroupHiddenRole) { return isGroupHidden(item->groupType()); } else { return item->data(role); } } QModelIndex KFilePlacesModel::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || column != 0 || row >= d->items.size()) { return QModelIndex(); } if (parent.isValid()) { return QModelIndex(); } return createIndex(row, column, d->items.at(row)); } QModelIndex KFilePlacesModel::parent(const QModelIndex &child) const { Q_UNUSED(child); return QModelIndex(); } int KFilePlacesModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } else { return d->items.size(); } } int KFilePlacesModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) // We only know 1 piece of information for a particular entry return 1; } QModelIndex KFilePlacesModel::closestItem(const QUrl &url) const { int foundRow = -1; int maxLength = 0; // Search the item which is equal to the URL or at least is a parent URL. // If there are more than one possible item URL candidates, choose the item // which covers the bigger range of the URL. for (int row = 0; row < d->items.size(); ++row) { KFilePlacesItem *item = d->items[row]; const QUrl itemUrl(item->data(UrlRole).toUrl()); if (itemUrl.matches(url, QUrl::StripTrailingSlash) || itemUrl.isParentOf(url)) { const int length = itemUrl.toString().length(); if (length > maxLength) { foundRow = row; maxLength = length; } } } if (foundRow == -1) { return QModelIndex(); } else { return createIndex(foundRow, 0, d->items[foundRow]); } } void KFilePlacesModel::Private::_k_initDeviceList() { Solid::DeviceNotifier *notifier = Solid::DeviceNotifier::instance(); connect(notifier, SIGNAL(deviceAdded(QString)), q, SLOT(_k_deviceAdded(QString))); connect(notifier, SIGNAL(deviceRemoved(QString)), q, SLOT(_k_deviceRemoved(QString))); const QList &deviceList = Solid::Device::listFromQuery(predicate); availableDevices.reserve(deviceList.size()); for (const Solid::Device &device : deviceList) { availableDevices << device.udi(); } _k_reloadBookmarks(); } void KFilePlacesModel::Private::_k_deviceAdded(const QString &udi) { Solid::Device d(udi); if (predicate.matches(d)) { availableDevices << udi; _k_reloadBookmarks(); } } void KFilePlacesModel::Private::_k_deviceRemoved(const QString &udi) { auto it = std::find(availableDevices.begin(), availableDevices.end(), udi); if (it != availableDevices.end()) { availableDevices.erase(it); _k_reloadBookmarks(); } } void KFilePlacesModel::Private::_k_itemChanged(const QString &id) { for (int row = 0; row < items.size(); ++row) { if (items.at(row)->id() == id) { QModelIndex index = q->index(row, 0); emit q->dataChanged(index, index); } } } void KFilePlacesModel::Private::_k_reloadBookmarks() { QList currentItems = loadBookmarkList(); QList::Iterator it_i = items.begin(); QList::Iterator it_c = currentItems.begin(); QList::Iterator end_i = items.end(); QList::Iterator end_c = currentItems.end(); while (it_i != end_i || it_c != end_c) { if (it_i == end_i && it_c != end_c) { int row = items.count(); q->beginInsertRows(QModelIndex(), row, row); it_i = items.insert(it_i, *it_c); ++it_i; it_c = currentItems.erase(it_c); end_i = items.end(); end_c = currentItems.end(); q->endInsertRows(); } else if (it_i != end_i && it_c == end_c) { int row = items.indexOf(*it_i); q->beginRemoveRows(QModelIndex(), row, row); delete *it_i; it_i = items.erase(it_i); end_i = items.end(); end_c = currentItems.end(); q->endRemoveRows(); } else if ((*it_i)->id() == (*it_c)->id()) { bool shouldEmit = !((*it_i)->bookmark() == (*it_c)->bookmark()); (*it_i)->setBookmark((*it_c)->bookmark()); if (shouldEmit) { int row = items.indexOf(*it_i); QModelIndex idx = q->index(row, 0); emit q->dataChanged(idx, idx); } ++it_i; ++it_c; } else if ((*it_i)->id() != (*it_c)->id()) { int row = items.indexOf(*it_i); if (it_i + 1 != end_i && (*(it_i + 1))->id() == (*it_c)->id()) { // if the next one matches, it's a remove q->beginRemoveRows(QModelIndex(), row, row); delete *it_i; it_i = items.erase(it_i); end_i = items.end(); end_c = currentItems.end(); q->endRemoveRows(); } else { q->beginInsertRows(QModelIndex(), row, row); it_i = items.insert(it_i, *it_c); ++it_i; it_c = currentItems.erase(it_c); end_i = items.end(); end_c = currentItems.end(); q->endInsertRows(); } } } qDeleteAll(currentItems); currentItems.clear(); } bool KFilePlacesModel::Private::isBalooUrl(const QUrl &url) const { const QString scheme = url.scheme(); return ((scheme == QLatin1String("timeline")) || (scheme == QLatin1String("search"))); } QList KFilePlacesModel::Private::loadBookmarkList() { QList items; KBookmarkGroup root = bookmarkManager->root(); KBookmark bookmark = root.first(); QVector devices = availableDevices; QVector tagsList = tags; while (!bookmark.isNull()) { const QString udi = bookmark.metaDataItem(QStringLiteral("UDI")); const QUrl url = bookmark.url(); const QString tag = bookmark.metaDataItem(QStringLiteral("tag")); if (!udi.isEmpty() || url.isValid()) { QString appName = bookmark.metaDataItem(QStringLiteral("OnlyInApp")); // If it's not a tag it's a device if (tag.isEmpty()) { auto it = std::find(devices.begin(), devices.end(), udi); bool deviceAvailable = (it != devices.end()); if (deviceAvailable) { devices.erase(it); } bool allowedHere = appName.isEmpty() || ((appName == QCoreApplication::instance()->applicationName()) || (appName == alternativeApplicationName)); bool isSupportedUrl = isBalooUrl(url) ? fileIndexingEnabled : true; bool isSupportedScheme = supportedSchemes.isEmpty() || supportedSchemes.contains(url.scheme()); if (isSupportedScheme && ((isSupportedUrl && udi.isEmpty() && allowedHere) || deviceAvailable)) { KFilePlacesItem *item; if (deviceAvailable) { item = new KFilePlacesItem(bookmarkManager, bookmark.address(), udi); // TODO: Update bookmark internal element } else { item = new KFilePlacesItem(bookmarkManager, bookmark.address()); } connect(item, SIGNAL(itemChanged(QString)), q, SLOT(_k_itemChanged(QString))); items << item; } } else { auto it = std::find(tagsList.begin(), tagsList.end(), tag); if (it != tagsList.end()) { tagsList.removeAll(tag); KFilePlacesItem *item = new KFilePlacesItem(bookmarkManager, bookmark.address()); items << item; connect(item, SIGNAL(itemChanged(QString)), q, SLOT(_k_itemChanged(QString))); } } } bookmark = root.next(bookmark); } // Add bookmarks for the remaining devices, they were previously unknown for (const QString &udi : qAsConst(devices)) { bookmark = KFilePlacesItem::createDeviceBookmark(bookmarkManager, udi); if (!bookmark.isNull()) { KFilePlacesItem *item = new KFilePlacesItem(bookmarkManager, bookmark.address(), udi); connect(item, SIGNAL(itemChanged(QString)), q, SLOT(_k_itemChanged(QString))); // TODO: Update bookmark internal element items << item; } } for (const QString& tag: tagsList) { bookmark = KFilePlacesItem::createTagBookmark(bookmarkManager, tag); if (!bookmark.isNull()) { KFilePlacesItem *item = new KFilePlacesItem(bookmarkManager, bookmark.address(), tag); connect(item, SIGNAL(itemChanged(QString)), q, SLOT(_k_itemChanged(QString))); items << item; } } // return a sorted list based on groups std::stable_sort(items.begin(), items.end(), [](KFilePlacesItem *itemA, KFilePlacesItem *itemB) { return (itemA->groupType() < itemB->groupType()); }); return items; } int KFilePlacesModel::Private::findNearestPosition(int source, int target) { const KFilePlacesItem *item = items.at(source); const KFilePlacesModel::GroupType groupType = item->groupType(); int newTarget = qMin(target, items.count() - 1); // moving inside the same group is ok if ((items.at(newTarget)->groupType() == groupType)) { return target; } if (target > source) { // moving down, move it to the end of the group int groupFooter = source; while (items.at(groupFooter)->groupType() == groupType) { groupFooter++; // end of the list move it there if (groupFooter == items.count()) { break; } } target = groupFooter; } else { // moving up, move it to beginning of the group int groupHead = source; while (items.at(groupHead)->groupType() == groupType) { groupHead--; // beginning of the list move it there if (groupHead == 0) { break; } } target = groupHead; } return target; } void KFilePlacesModel::Private::reloadAndSignal() { bookmarkManager->emitChanged(bookmarkManager->root()); // ... we'll get relisted anyway } Qt::DropActions KFilePlacesModel::supportedDropActions() const { return Qt::ActionMask; } Qt::ItemFlags KFilePlacesModel::flags(const QModelIndex &index) const { Qt::ItemFlags res = Qt::ItemIsSelectable | Qt::ItemIsEnabled; if (index.isValid()) { res |= Qt::ItemIsDragEnabled; } if (!index.isValid()) { res |= Qt::ItemIsDropEnabled; } return res; } static QString _k_internalMimetype(const KFilePlacesModel *const self) { return QStringLiteral("application/x-kfileplacesmodel-") + QString::number(reinterpret_cast(self)); } QStringList KFilePlacesModel::mimeTypes() const { QStringList types; types << _k_internalMimetype(this) << QStringLiteral("text/uri-list"); return types; } QMimeData *KFilePlacesModel::mimeData(const QModelIndexList &indexes) const { QList urls; QByteArray itemData; QDataStream stream(&itemData, QIODevice::WriteOnly); for (const QModelIndex &index : qAsConst(indexes)) { QUrl itemUrl = url(index); if (itemUrl.isValid()) { urls << itemUrl; } stream << index.row(); } QMimeData *mimeData = new QMimeData(); if (!urls.isEmpty()) { mimeData->setUrls(urls); } mimeData->setData(_k_internalMimetype(this), itemData); return mimeData; } bool KFilePlacesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { if (action == Qt::IgnoreAction) { return true; } if (column > 0) { return false; } if (row == -1 && parent.isValid()) { return false; // Don't allow to move an item onto another one, // too easy for the user to mess something up // If we really really want to allow copying files this way, // let's do it in the views to get the good old drop menu } if (data->hasFormat(_k_internalMimetype(this))) { // The operation is an internal move QByteArray itemData = data->data(_k_internalMimetype(this)); QDataStream stream(&itemData, QIODevice::ReadOnly); int itemRow; stream >> itemRow; if (!movePlace(itemRow, row)) { return false; } } else if (data->hasFormat(QStringLiteral("text/uri-list"))) { // The operation is an add QMimeDatabase db; KBookmark afterBookmark; if (row == -1) { // The dropped item is moved or added to the last position KFilePlacesItem *lastItem = d->items.last(); afterBookmark = lastItem->bookmark(); } else { // The dropped item is moved or added before position 'row', ie after position 'row-1' if (row > 0) { KFilePlacesItem *afterItem = d->items[row - 1]; afterBookmark = afterItem->bookmark(); } } const QList urls = KUrlMimeData::urlsFromMimeData(data); KBookmarkGroup group = d->bookmarkManager->root(); for (const QUrl &url : urls) { // TODO: use KIO::stat in order to get the UDS_DISPLAY_NAME too KIO::MimetypeJob *job = KIO::mimetype(url); QString mimeString; if (!job->exec()) { mimeString = QStringLiteral("unknown"); } else { mimeString = job->mimetype(); } QMimeType mimetype = db.mimeTypeForName(mimeString); if (!mimetype.isValid()) { qWarning() << "URL not added to Places as mimetype could not be determined!"; continue; } if (!mimetype.inherits(QStringLiteral("inode/directory"))) { // Only directories are allowed continue; } KFileItem item(url, mimetype.name(), S_IFDIR); KBookmark bookmark = KFilePlacesItem::createBookmark(d->bookmarkManager, url.fileName(), url, item.iconName()); group.moveBookmark(bookmark, afterBookmark); afterBookmark = bookmark; } } else { // Oops, shouldn't happen thanks to mimeTypes() qWarning() << ": received wrong mimedata, " << data->formats(); return false; } refresh(); return true; } void KFilePlacesModel::refresh() const { d->reloadAndSignal(); } QUrl KFilePlacesModel::convertedUrl(const QUrl &url) { QUrl newUrl = url; if (url.scheme() == QLatin1String("timeline")) { newUrl = createTimelineUrl(url); } else if (url.scheme() == QLatin1String("search")) { newUrl = createSearchUrl(url); } return newUrl; } void KFilePlacesModel::addPlace(const QString &text, const QUrl &url, const QString &iconName, const QString &appName) { addPlace(text, url, iconName, appName, QModelIndex()); } void KFilePlacesModel::addPlace(const QString &text, const QUrl &url, const QString &iconName, const QString &appName, const QModelIndex &after) { KBookmark bookmark = KFilePlacesItem::createBookmark(d->bookmarkManager, text, url, iconName); if (!appName.isEmpty()) { bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), appName); } if (after.isValid()) { KFilePlacesItem *item = static_cast(after.internalPointer()); d->bookmarkManager->root().moveBookmark(bookmark, item->bookmark()); } refresh(); } void KFilePlacesModel::editPlace(const QModelIndex &index, const QString &text, const QUrl &url, const QString &iconName, const QString &appName) { if (!index.isValid()) { return; } KFilePlacesItem *item = static_cast(index.internalPointer()); if (item->isDevice()) { return; } KBookmark bookmark = item->bookmark(); if (bookmark.isNull()) { return; } bool changed = false; if (text != bookmark.fullText()) { bookmark.setFullText(text); changed = true; } if (url != bookmark.url()) { bookmark.setUrl(url); changed = true; } if (iconName != bookmark.icon()) { bookmark.setIcon(iconName); changed = true; } const QString onlyInApp = bookmark.metaDataItem(QStringLiteral("OnlyInApp")); if (appName != onlyInApp) { bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), appName); changed = true; } if (changed) { refresh(); emit dataChanged(index, index); } } void KFilePlacesModel::removePlace(const QModelIndex &index) const { if (!index.isValid()) { return; } KFilePlacesItem *item = static_cast(index.internalPointer()); if (item->isDevice()) { return; } KBookmark bookmark = item->bookmark(); if (bookmark.isNull()) { return; } d->bookmarkManager->root().deleteBookmark(bookmark); refresh(); } void KFilePlacesModel::setPlaceHidden(const QModelIndex &index, bool hidden) { if (!index.isValid()) { return; } KFilePlacesItem *item = static_cast(index.internalPointer()); if (item->bookmark().isNull() || item->isHidden() == hidden) { return; } const bool groupHidden = isGroupHidden(item->groupType()); const bool hidingChildOnShownParent = hidden && !groupHidden; const bool showingChildOnShownParent = !hidden && !groupHidden; if (hidingChildOnShownParent || showingChildOnShownParent) { item->setHidden(hidden); d->reloadAndSignal(); emit dataChanged(index, index); } } void KFilePlacesModel::setGroupHidden(const GroupType type, bool hidden) { if (isGroupHidden(type) == hidden) return; d->bookmarkManager->root().setMetaDataItem(stateNameForGroupType(type), (hidden ? QStringLiteral("true") : QStringLiteral("false"))); d->reloadAndSignal(); emit groupHiddenChanged(type, hidden); } bool KFilePlacesModel::movePlace(int itemRow, int row) { KBookmark afterBookmark; if ((itemRow < 0) || (itemRow >= d->items.count())) { return false; } if (row >= d->items.count()) { row = -1; } if (row == -1) { // The dropped item is moved or added to the last position KFilePlacesItem *lastItem = d->items.last(); afterBookmark = lastItem->bookmark(); } else { // The dropped item is moved or added before position 'row', ie after position 'row-1' if (row > 0) { KFilePlacesItem *afterItem = d->items[row - 1]; afterBookmark = afterItem->bookmark(); } } KFilePlacesItem *item = d->items[itemRow]; KBookmark bookmark = item->bookmark(); int destRow = row == -1 ? d->items.count() : row; // avoid move item away from its group destRow = d->findNearestPosition(itemRow, destRow); // The item is not moved when the drop indicator is on either item edge if (itemRow == destRow || itemRow + 1 == destRow) { return false; } beginMoveRows(QModelIndex(), itemRow, itemRow, QModelIndex(), destRow); d->bookmarkManager->root().moveBookmark(bookmark, afterBookmark); // Move item ourselves so that _k_reloadBookmarks() does not consider // the move as a remove + insert. // // 2nd argument of QList::move() expects the final destination index, // but 'row' is the value of the destination index before the moved // item has been removed from its original position. That is why we // adjust if necessary. d->items.move(itemRow, itemRow < destRow ? (destRow - 1) : destRow); endMoveRows(); return true; } int KFilePlacesModel::hiddenCount() const { int rows = rowCount(); int hidden = 0; for (int i = 0; i < rows; ++i) { if (isHidden(index(i, 0))) { hidden++; } } return hidden; } QAction *KFilePlacesModel::teardownActionForIndex(const QModelIndex &index) const { Solid::Device device = deviceForIndex(index); if (device.is() && device.as()->isAccessible()) { Solid::StorageDrive *drive = device.as(); if (drive == nullptr) { drive = device.parent().as(); } bool hotpluggable = false; bool removable = false; if (drive != nullptr) { hotpluggable = drive->isHotpluggable(); removable = drive->isRemovable(); } QString iconName; QString text; QString label = data(index, Qt::DisplayRole).toString().replace(QLatin1Char('&'), QLatin1String("&&")); if (device.is()) { text = i18n("&Release '%1'", label); } else if (removable || hotpluggable) { text = i18n("&Safely Remove '%1'", label); iconName = QStringLiteral("media-eject"); } else { text = i18n("&Unmount '%1'", label); iconName = QStringLiteral("media-eject"); } if (!iconName.isEmpty()) { return new QAction(QIcon::fromTheme(iconName), text, nullptr); } else { return new QAction(text, nullptr); } } return nullptr; } QAction *KFilePlacesModel::ejectActionForIndex(const QModelIndex &index) const { Solid::Device device = deviceForIndex(index); if (device.is()) { QString label = data(index, Qt::DisplayRole).toString().replace(QLatin1Char('&'), QLatin1String("&&")); QString text = i18n("&Eject '%1'", label); return new QAction(QIcon::fromTheme(QStringLiteral("media-eject")), text, nullptr); } return nullptr; } void KFilePlacesModel::requestTeardown(const QModelIndex &index) { Solid::Device device = deviceForIndex(index); Solid::StorageAccess *access = device.as(); if (access != nullptr) { connect(access, SIGNAL(teardownDone(Solid::ErrorType,QVariant,QString)), this, SLOT(_k_storageTeardownDone(Solid::ErrorType,QVariant))); access->teardown(); } } void KFilePlacesModel::requestEject(const QModelIndex &index) { Solid::Device device = deviceForIndex(index); Solid::OpticalDrive *drive = device.parent().as(); if (drive != nullptr) { connect(drive, SIGNAL(ejectDone(Solid::ErrorType,QVariant,QString)), this, SLOT(_k_storageTeardownDone(Solid::ErrorType,QVariant))); drive->eject(); } else { QString label = data(index, Qt::DisplayRole).toString().replace(QLatin1Char('&'), QLatin1String("&&")); QString message = i18n("The device '%1' is not a disk and cannot be ejected.", label); emit errorMessage(message); } } void KFilePlacesModel::requestSetup(const QModelIndex &index) { Solid::Device device = deviceForIndex(index); if (device.is() && !d->setupInProgress.contains(device.as()) && !device.as()->isAccessible()) { Solid::StorageAccess *access = device.as(); d->setupInProgress[access] = index; connect(access, SIGNAL(setupDone(Solid::ErrorType,QVariant,QString)), this, SLOT(_k_storageSetupDone(Solid::ErrorType,QVariant))); access->setup(); } } void KFilePlacesModel::Private::_k_storageSetupDone(Solid::ErrorType error, const QVariant &errorData) { QPersistentModelIndex index = setupInProgress.take(q->sender()); if (!index.isValid()) { return; } if (!error) { emit q->setupDone(index, true); } else { if (errorData.isValid()) { emit q->errorMessage(i18n("An error occurred while accessing '%1', the system responded: %2", q->text(index), errorData.toString())); } else { emit q->errorMessage(i18n("An error occurred while accessing '%1'", q->text(index))); } emit q->setupDone(index, false); } } void KFilePlacesModel::Private::_k_storageTeardownDone(Solid::ErrorType error, const QVariant &errorData) { if (error && errorData.isValid()) { emit q->errorMessage(errorData.toString()); } } void KFilePlacesModel::setSupportedSchemes(const QStringList &schemes) { d->supportedSchemes = schemes; d->_k_reloadBookmarks(); } QStringList KFilePlacesModel::supportedSchemes() const { return d->supportedSchemes; } #include "moc_kfileplacesmodel.cpp" diff --git a/src/ioslaves/ftp/CMakeLists.txt b/src/ioslaves/ftp/CMakeLists.txt index 8743a30f..f9e69a0c 100644 --- a/src/ioslaves/ftp/CMakeLists.txt +++ b/src/ioslaves/ftp/CMakeLists.txt @@ -1,21 +1,19 @@ project(kioslave-ftp) -remove_definitions(-DQT_NO_CAST_FROM_ASCII) # TODO REMOVE - include(ConfigureChecks.cmake) configure_file(config-kioslave-ftp.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kioslave-ftp.h ) ########### next target ############### set(kio_ftp_PART_SRCS ftp.cpp ) add_library(kio_ftp MODULE ${kio_ftp_PART_SRCS}) target_link_libraries(kio_ftp Qt5::Network KF5::KIOCore KF5::I18n) set_target_properties(kio_ftp PROPERTIES OUTPUT_NAME "ftp") set_target_properties(kio_ftp PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kio") install(TARGETS kio_ftp DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kio) diff --git a/src/ioslaves/ftp/ftp.cpp b/src/ioslaves/ftp/ftp.cpp index f3108714..67dc8d3a 100644 --- a/src/ioslaves/ftp/ftp.cpp +++ b/src/ioslaves/ftp/ftp.cpp @@ -1,2742 +1,2742 @@ /* This file is part of the KDE libraries Copyright (C) 2000-2006 David Faure Copyright (C) 2019 Harald Sitter 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. */ /* Recommended reading explaining FTP details and quirks: http://cr.yp.to/ftp.html (by D.J. Bernstein) RFC: RFC 959 "File Transfer Protocol (FTP)" RFC 1635 "How to Use Anonymous FTP" RFC 2428 "FTP Extensions for IPv6 and NATs" (defines EPRT and EPSV) */ #include #define KIO_FTP_PRIVATE_INCLUDE #include "ftp.h" #ifdef Q_OS_WIN #include #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kioglobal_p.h" #include Q_DECLARE_LOGGING_CATEGORY(KIO_FTP) Q_LOGGING_CATEGORY(KIO_FTP, "kf5.kio.kio_ftp", QtWarningMsg) #if HAVE_STRTOLL #define charToLongLong(a) strtoll(a, nullptr, 10) #else #define charToLongLong(a) strtol(a, nullptr, 10) #endif #define FTP_LOGIN "anonymous" #define FTP_PASSWD "anonymous@" #define ENABLE_CAN_RESUME // Pseudo plugin class to embed meta data class KIOPluginForMetaData : public QObject { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kde.kio.slave.ftp" FILE "ftp.json") }; static QString ftpCleanPath(const QString &path) { if (path.endsWith(QLatin1String(";type=A"), Qt::CaseInsensitive) || path.endsWith(QLatin1String(";type=I"), Qt::CaseInsensitive) || path.endsWith(QLatin1String(";type=D"), Qt::CaseInsensitive)) { return path.left((path.length() - qstrlen(";type=X"))); } return path; } static char ftpModeFromPath(const QString &path, char defaultMode = '\0') { const int index = path.lastIndexOf(QLatin1String(";type=")); if (index > -1 && (index + 6) < path.size()) { const QChar mode = path.at(index + 6); // kio_ftp supports only A (ASCII) and I(BINARY) modes. if (mode == QLatin1Char('A') || mode == QLatin1Char('a') || mode == QLatin1Char('I') || mode == QLatin1Char('i')) { return mode.toUpper().toLatin1(); } } return defaultMode; } static bool supportedProxyScheme(const QString &scheme) { return (scheme == QLatin1String("ftp") || scheme == QLatin1String("socks")); } // JPF: somebody should find a better solution for this or move this to KIO namespace KIO { enum buffersizes { /** * largest buffer size that should be used to transfer data between * KIO slaves using the data() function */ maximumIpcSize = 32 * 1024, /** * this is a reasonable value for an initial read() that a KIO slave * can do to obtain data via a slow network connection. */ initialIpcSize = 2 * 1024, /** * recommended size of a data block passed to findBufferFileType() */ minimumMimeSize = 1024 }; // JPF: this helper was derived from write_all in file.cc (FileProtocol). static // JPF: in ftp.cc we make it static /** * This helper handles some special issues (blocking and interrupted * system call) when writing to a file handle. * * @return 0 on success or an error code on failure (ERR_CANNOT_WRITE, * ERR_DISK_FULL, ERR_CONNECTION_BROKEN). */ int WriteToFile(int fd, const char *buf, size_t len) { while (len > 0) { // JPF: shouldn't there be a KDE_write? ssize_t written = write(fd, buf, len); if (written >= 0) { buf += written; len -= written; continue; } switch (errno) { case EINTR: continue; case EPIPE: return ERR_CONNECTION_BROKEN; case ENOSPC: return ERR_DISK_FULL; default: return ERR_CANNOT_WRITE; } } return 0; } } const KIO::filesize_t FtpInternal::UnknownSize = (KIO::filesize_t) - 1; using namespace KIO; extern "C" Q_DECL_EXPORT int kdemain(int argc, char **argv) { QCoreApplication app(argc, argv); app.setApplicationName(QStringLiteral("kio_ftp")); qCDebug(KIO_FTP) << "Starting"; if (argc != 4) { fprintf(stderr, "Usage: kio_ftp protocol domain-socket1 domain-socket2\n"); exit(-1); } Ftp slave(argv[2], argv[3]); slave.dispatchLoop(); qCDebug(KIO_FTP) << "Done"; return 0; } //=============================================================================== // FtpInternal //=============================================================================== /** * This closes a data connection opened by ftpOpenDataConnection(). */ void FtpInternal::ftpCloseDataConnection() { delete m_data; m_data = nullptr; delete m_server; m_server = nullptr; } /** * This closes a control connection opened by ftpOpenControlConnection() and reinits the * related states. This method gets called from the constructor with m_control = nullptr. */ void FtpInternal::ftpCloseControlConnection() { m_extControl = 0; delete m_control; m_control = nullptr; m_cDataMode = 0; m_bLoggedOn = false; // logon needs control connection m_bTextMode = false; m_bBusy = false; } /** * Returns the last response from the server (iOffset >= 0) -or- reads a new response * (iOffset < 0). The result is returned (with iOffset chars skipped for iOffset > 0). */ const char *FtpInternal::ftpResponse(int iOffset) { Q_ASSERT(m_control); // must have control connection socket const char *pTxt = m_lastControlLine.data(); // read the next line ... if (iOffset < 0) { int iMore = 0; m_iRespCode = 0; if (!pTxt) { return nullptr; // avoid using a nullptr when calling atoi. } // If the server sends a multiline response starting with // "nnn-text" we loop here until a final "nnn text" line is // reached. Only data from the final line will be stored. do { while (!m_control->canReadLine() && m_control->waitForReadyRead((q->readTimeout() * 1000))) {} m_lastControlLine = m_control->readLine(); pTxt = m_lastControlLine.data(); int iCode = atoi(pTxt); if (iMore == 0) { // first line qCDebug(KIO_FTP) << " > " << pTxt; if (iCode >= 100) { m_iRespCode = iCode; if (pTxt[3] == '-') { // marker for a multiple line response iMore = iCode; } } else { qCWarning(KIO_FTP) << "Cannot parse valid code from line" << pTxt; } } else { // multi-line qCDebug(KIO_FTP) << " > " << pTxt; if (iCode >= 100 && iCode == iMore && pTxt[3] == ' ') { iMore = 0; } } } while (iMore != 0); qCDebug(KIO_FTP) << "resp> " << pTxt; m_iRespType = (m_iRespCode > 0) ? m_iRespCode / 100 : 0; } // return text with offset ... while (iOffset-- > 0 && pTxt[0]) { pTxt++; } return pTxt; } void FtpInternal::closeConnection() { if (m_control || m_data) qCDebug(KIO_FTP) << "m_bLoggedOn=" << m_bLoggedOn << " m_bBusy=" << m_bBusy; if (m_bBusy) { // ftpCloseCommand not called qCWarning(KIO_FTP) << "Abandoned data stream"; ftpCloseDataConnection(); } if (m_bLoggedOn) { // send quit if (!ftpSendCmd(QByteArrayLiteral("quit"), 0) || (m_iRespType != 2)) { qCWarning(KIO_FTP) << "QUIT returned error: " << m_iRespCode; } } // close the data and control connections ... ftpCloseDataConnection(); ftpCloseControlConnection(); } FtpInternal::FtpInternal(Ftp *qptr) : QObject() , q(qptr) { ftpCloseControlConnection(); } FtpInternal::~FtpInternal() { qCDebug(KIO_FTP); closeConnection(); } void FtpInternal::setHost(const QString &_host, quint16 _port, const QString &_user, const QString &_pass) { qCDebug(KIO_FTP) << _host << "port=" << _port << "user=" << _user; m_proxyURL.clear(); m_proxyUrls = q->mapConfig().value(QStringLiteral("ProxyUrls"), QString()).toString().split(QLatin1Char(','), QString::SkipEmptyParts); qCDebug(KIO_FTP) << "proxy urls:" << m_proxyUrls; if (m_host != _host || m_port != _port || m_user != _user || m_pass != _pass) { closeConnection(); } m_host = _host; m_port = _port; m_user = _user; m_pass = _pass; } Result FtpInternal::openConnection() { return ftpOpenConnection(LoginMode::Explicit); } Result FtpInternal::ftpOpenConnection(LoginMode loginMode) { // check for implicit login if we are already logged on ... if (loginMode == LoginMode::Implicit && m_bLoggedOn) { Q_ASSERT(m_control); // must have control connection socket return Result::pass(); } qCDebug(KIO_FTP) << "host=" << m_host << ", port=" << m_port << ", user=" << m_user << "password= [password hidden]"; q->infoMessage(i18n("Opening connection to host %1", m_host)); if (m_host.isEmpty()) { return Result::fail(ERR_UNKNOWN_HOST); } Q_ASSERT(!m_bLoggedOn); m_initialPath.clear(); m_currentPath.clear(); const Result result = ftpOpenControlConnection(); if (!result.success) { return result; } q->infoMessage(i18n("Connected to host %1", m_host)); bool userNameChanged = false; if (loginMode != LoginMode::Defered) { const Result result = ftpLogin(&userNameChanged); m_bLoggedOn = result.success; if (!m_bLoggedOn) { return result; } } m_bTextMode = q->configValue(QStringLiteral("textmode"), false); q->connected(); // Redirected due to credential change... if (userNameChanged && m_bLoggedOn) { QUrl realURL; realURL.setScheme(QStringLiteral("ftp")); if (m_user != QLatin1String(FTP_LOGIN)) { realURL.setUserName(m_user); } if (m_pass != QLatin1String(FTP_PASSWD)) { realURL.setPassword(m_pass); } realURL.setHost(m_host); if (m_port > 0 && m_port != DEFAULT_FTP_PORT) { realURL.setPort(m_port); } if (m_initialPath.isEmpty()) { m_initialPath = QStringLiteral("/"); } realURL.setPath(m_initialPath); qCDebug(KIO_FTP) << "User name changed! Redirecting to" << realURL; q->redirection(realURL); return Result::fail(); } return Result::pass(); } /** * Called by @ref openConnection. It opens the control connection to the ftp server. * * @return true on success. */ Result FtpInternal::ftpOpenControlConnection() { if (m_proxyUrls.isEmpty()) { return ftpOpenControlConnection(m_host, m_port); } Result result = Result::fail(); for (const QString &proxyUrl : qAsConst(m_proxyUrls)) { const QUrl url(proxyUrl); const QString scheme(url.scheme()); if (!supportedProxyScheme(scheme)) { // TODO: Need a new error code to indicate unsupported URL scheme. result = Result::fail(ERR_CANNOT_CONNECT, url.toString()); continue; } if (!isSocksProxyScheme(scheme)) { const Result result = ftpOpenControlConnection(url.host(), url.port()); if (result.success) { return Result::pass(); } continue; } qCDebug(KIO_FTP) << "Connecting to SOCKS proxy @" << url; m_proxyURL = url; result = ftpOpenControlConnection(m_host, m_port); if (result.success) { return result; } m_proxyURL.clear(); } return result; } Result FtpInternal::ftpOpenControlConnection(const QString &host, int port) { // implicitly close, then try to open a new connection ... closeConnection(); QString sErrorMsg; // now connect to the server and read the login message ... if (port == 0) { port = 21; // default FTP port } const auto connectionResult = synchronousConnectToHost(host, port); m_control = connectionResult.socket; int iErrorCode = m_control->state() == QAbstractSocket::ConnectedState ? 0 : ERR_CANNOT_CONNECT; if (!connectionResult.result.success) { qDebug() << "overriding error code!!1" << connectionResult.result.error; iErrorCode = connectionResult.result.error; sErrorMsg = connectionResult.result.errorString; } // on connect success try to read the server message... if (iErrorCode == 0) { const char *psz = ftpResponse(-1); if (m_iRespType != 2) { // login not successful, do we have an message text? if (psz[0]) { sErrorMsg = i18n("%1 (Error %2)", host, q->remoteEncoding()->decode(psz).trimmed()); } iErrorCode = ERR_CANNOT_CONNECT; } } else { if (m_control->error() == QAbstractSocket::HostNotFoundError) { iErrorCode = ERR_UNKNOWN_HOST; } sErrorMsg = QStringLiteral("%1: %2").arg(host, m_control->errorString()); } // if there was a problem - report it ... if (iErrorCode == 0) { // OK, return success return Result::pass(); } closeConnection(); // clean-up on error return Result::fail(iErrorCode, sErrorMsg); } /** * Called by @ref openConnection. It logs us in. * @ref m_initialPath is set to the current working directory * if logging on was successful. * * @return true on success. */ Result FtpInternal::ftpLogin(bool *userChanged) { q->infoMessage(i18n("Sending login information")); Q_ASSERT(!m_bLoggedOn); QString user(m_user); QString pass(m_pass); if (q->configValue(QStringLiteral("EnableAutoLogin"), false)) { QString au = q->configValue(QStringLiteral("autoLoginUser")); if (!au.isEmpty()) { user = au; pass = q->configValue(QStringLiteral("autoLoginPass")); } } AuthInfo info; info.url.setScheme(QStringLiteral("ftp")); info.url.setHost(m_host); if (m_port > 0 && m_port != DEFAULT_FTP_PORT) { info.url.setPort(m_port); } if (!user.isEmpty()) { info.url.setUserName(user); } // Check for cached authentication first and fallback to // anonymous login when no stored credentials are found. if (!q->configValue(QStringLiteral("TryAnonymousLoginFirst"), false) && pass.isEmpty() && q->checkCachedAuthentication(info)) { user = info.username; pass = info.password; } // Try anonymous login if both username/password // information is blank. if (user.isEmpty() && pass.isEmpty()) { user = QStringLiteral(FTP_LOGIN); pass = QStringLiteral(FTP_PASSWD); } QByteArray tempbuf; QString lastServerResponse; int failedAuth = 0; bool promptForRetry = false; // Give the user the option to login anonymously... info.setExtraField(QStringLiteral("anonymous"), false); do { // Check the cache and/or prompt user for password if 1st // login attempt failed OR the user supplied a login name, // but no password. if (failedAuth > 0 || (!user.isEmpty() && pass.isEmpty())) { QString errorMsg; qCDebug(KIO_FTP) << "Prompting user for login info..."; // Ask user if we should retry after when login fails! if (failedAuth > 0 && promptForRetry) { errorMsg = i18n("Message sent:\nLogin using username=%1 and " "password=[hidden]\n\nServer replied:\n%2\n\n" , user, lastServerResponse); } if (user != QLatin1String(FTP_LOGIN)) { info.username = user; } info.prompt = i18n("You need to supply a username and a password " "to access this site."); info.commentLabel = i18n("Site:"); info.comment = i18n("%1", m_host); info.keepPassword = true; // Prompt the user for persistence as well. info.setModified(false); // Default the modified flag since we reuse authinfo. const bool disablePassDlg = q->configValue(QStringLiteral("DisablePassDlg"), false); if (disablePassDlg) { return Result::fail(ERR_USER_CANCELED, m_host); } const int errorCode = q->openPasswordDialogV2(info, errorMsg); if (errorCode) { return Result::fail(errorCode); } else { // User can decide go anonymous using checkbox if (info.getExtraField(QStringLiteral("anonymous")).toBool()) { user = QStringLiteral(FTP_LOGIN); pass = QStringLiteral(FTP_PASSWD); } else { user = info.username; pass = info.password; } promptForRetry = true; } } tempbuf = "USER " + user.toLatin1(); if (m_proxyURL.isValid()) { tempbuf += '@' + m_host.toLatin1(); if (m_port > 0 && m_port != DEFAULT_FTP_PORT) { tempbuf += ':' + QByteArray::number(m_port); } } qCDebug(KIO_FTP) << "Sending Login name: " << tempbuf; bool loggedIn = (ftpSendCmd(tempbuf) && (m_iRespCode == 230)); bool needPass = (m_iRespCode == 331); // Prompt user for login info if we do not // get back a "230" or "331". if (!loggedIn && !needPass) { lastServerResponse = QString::fromUtf8(ftpResponse(0)); qCDebug(KIO_FTP) << "Login failed: " << lastServerResponse; ++failedAuth; continue; // Well we failed, prompt the user please!! } if (needPass) { tempbuf = "PASS " + pass.toLatin1(); qCDebug(KIO_FTP) << "Sending Login password: " << "[protected]"; loggedIn = (ftpSendCmd(tempbuf) && (m_iRespCode == 230)); } if (loggedIn) { // Make sure the user name changed flag is properly set. if (userChanged) { *userChanged = (!m_user.isEmpty() && (m_user != user)); } // Do not cache the default login!! if (user != QLatin1String(FTP_LOGIN) && pass != QLatin1String(FTP_PASSWD)) { // Update the username in case it was changed during login. if (!m_user.isEmpty()) { info.url.setUserName(user); m_user = user; } // Cache the password if the user requested it. if (info.keepPassword) { q->cacheAuthentication(info); } } failedAuth = -1; } else { // some servers don't let you login anymore // if you fail login once, so restart the connection here lastServerResponse = QString::fromUtf8(ftpResponse(0)); const Result result = ftpOpenControlConnection(); if (!result.success) { return result; } } } while (++failedAuth); qCDebug(KIO_FTP) << "Login OK"; q->infoMessage(i18n("Login OK")); // Okay, we're logged in. If this is IIS 4, switch dir listing style to Unix: // Thanks to jk@soegaard.net (Jens Kristian Sgaard) for this hint if (ftpSendCmd(QByteArrayLiteral("SYST")) && (m_iRespType == 2)) { if (!qstrncmp(ftpResponse(0), "215 Windows_NT", 14)) { // should do for any version (void) ftpSendCmd(QByteArrayLiteral("site dirstyle")); // Check if it was already in Unix style // Patch from Keith Refson if (!qstrncmp(ftpResponse(0), "200 MSDOS-like directory output is on", 37)) //It was in Unix style already! { (void) ftpSendCmd(QByteArrayLiteral("site dirstyle")); } // windows won't support chmod before KDE konquers their desktop... m_extControl |= chmodUnknown; } } else { qCWarning(KIO_FTP) << "SYST failed"; } if (q->configValue(QStringLiteral("EnableAutoLoginMacro"), false)) { ftpAutoLoginMacro(); } // Get the current working directory qCDebug(KIO_FTP) << "Searching for pwd"; if (!ftpSendCmd(QByteArrayLiteral("PWD")) || (m_iRespType != 2)) { qCDebug(KIO_FTP) << "Couldn't issue pwd command"; return Result::fail(ERR_CANNOT_LOGIN, i18n("Could not login to %1.", m_host)); // or anything better ? } QString sTmp = q->remoteEncoding()->decode(ftpResponse(3)); const int iBeg = sTmp.indexOf(QLatin1Char('"')); const int iEnd = sTmp.lastIndexOf(QLatin1Char('"')); if (iBeg > 0 && iBeg < iEnd) { m_initialPath = sTmp.mid(iBeg + 1, iEnd - iBeg - 1); if (m_initialPath[0] != QLatin1Char('/')) { m_initialPath.prepend(QLatin1Char('/')); } qCDebug(KIO_FTP) << "Initial path set to: " << m_initialPath; m_currentPath = m_initialPath; } return Result::pass(); } void FtpInternal::ftpAutoLoginMacro() { QString macro = q->metaData(QStringLiteral("autoLoginMacro")); if (macro.isEmpty()) { return; } const QStringList list = macro.split(QLatin1Char('\n'), QString::SkipEmptyParts); for (QStringList::const_iterator it = list.begin(); it != list.end(); ++it) { if ((*it).startsWith(QLatin1String("init"))) { const QStringList list2 = macro.split(QLatin1Char('\\'), QString::SkipEmptyParts); it = list2.begin(); ++it; // ignore the macro name for (; it != list2.end(); ++it) { // TODO: Add support for arbitrary commands // besides simply changing directory!! if ((*it).startsWith(QLatin1String("cwd"))) { (void) ftpFolder((*it).mid(4)); } } break; } } } /** * ftpSendCmd - send a command (@p cmd) and read response * * @param maxretries number of time it should retry. Since it recursively * calls itself if it can't read the answer (this happens especially after * timeouts), we need to limit the recursiveness ;-) * * return true if any response received, false on error */ bool FtpInternal::ftpSendCmd(const QByteArray &cmd, int maxretries) { Q_ASSERT(m_control); // must have control connection socket if (cmd.indexOf('\r') != -1 || cmd.indexOf('\n') != -1) { qCWarning(KIO_FTP) << "Invalid command received (contains CR or LF):" << cmd.data(); return false; } // Don't print out the password... bool isPassCmd = (cmd.left(4).toLower() == "pass"); #if 0 if (!isPassCmd) { qDebug() << "send> " << cmd.data(); } else { qDebug() << "send> pass [protected]"; } #endif // Send the message... const QByteArray buf = cmd + "\r\n"; // Yes, must use CR/LF - see http://cr.yp.to/ftp/request.html int num = m_control->write(buf); while (m_control->bytesToWrite() && m_control->waitForBytesWritten()) {} // If we were able to successfully send the command, then we will // attempt to read the response. Otherwise, take action to re-attempt // the login based on the maximum number of retries specified... if (num > 0) { ftpResponse(-1); } else { m_iRespType = m_iRespCode = 0; } // If respCh is NULL or the response is 421 (Timed-out), we try to re-send // the command based on the value of maxretries. if ((m_iRespType <= 0) || (m_iRespCode == 421)) { // We have not yet logged on... if (!m_bLoggedOn) { // The command was sent from the ftpLogin function, i.e. we are actually // attempting to login in. NOTE: If we already sent the username, we // return false and let the user decide whether (s)he wants to start from // the beginning... if (maxretries > 0 && !isPassCmd) { closeConnection(); const auto result = ftpOpenConnection(LoginMode::Defered); if (result.success && ftpSendCmd(cmd, maxretries - 1)) { return true; } } return false; } else { if (maxretries < 1) { return false; } else { qCDebug(KIO_FTP) << "Was not able to communicate with " << m_host << "Attempting to re-establish connection."; closeConnection(); // Close the old connection... const Result openResult = openConnection(); // Attempt to re-establish a new connection... if (!openResult.success) { if (m_control) { // if openConnection succeeded ... qCDebug(KIO_FTP) << "Login failure, aborting"; closeConnection(); } return false; } qCDebug(KIO_FTP) << "Logged back in, re-issuing command"; // If we were able to login, resend the command... if (maxretries) { maxretries--; } return ftpSendCmd(cmd, maxretries); } } } return true; } /* * ftpOpenPASVDataConnection - set up data connection, using PASV mode * * return 0 if successful, ERR_INTERNAL otherwise * doesn't set error message, since non-pasv mode will always be tried if * this one fails */ int FtpInternal::ftpOpenPASVDataConnection() { Q_ASSERT(m_control); // must have control connection socket Q_ASSERT(!m_data); // ... but no data connection // Check that we can do PASV QHostAddress address = m_control->peerAddress(); if (address.protocol() != QAbstractSocket::IPv4Protocol && !isSocksProxy()) { return ERR_INTERNAL; // no PASV for non-PF_INET connections } if (m_extControl & pasvUnknown) { return ERR_INTERNAL; // already tried and got "unknown command" } m_bPasv = true; /* Let's PASsiVe*/ if (!ftpSendCmd(QByteArrayLiteral("PASV")) || (m_iRespType != 2)) { qCDebug(KIO_FTP) << "PASV attempt failed"; // unknown command? if (m_iRespType == 5) { qCDebug(KIO_FTP) << "disabling use of PASV"; m_extControl |= pasvUnknown; } return ERR_INTERNAL; } // The usual answer is '227 Entering Passive Mode. (160,39,200,55,6,245)' // but anonftpd gives '227 =160,39,200,55,6,245' int i[6]; const char *start = strchr(ftpResponse(3), '('); if (!start) { start = strchr(ftpResponse(3), '='); } if (!start || (sscanf(start, "(%d,%d,%d,%d,%d,%d)", &i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6 && sscanf(start, "=%d,%d,%d,%d,%d,%d", &i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6)) { qCritical() << "parsing IP and port numbers failed. String parsed: " << start; return ERR_INTERNAL; } // we ignore the host part on purpose for two reasons // a) it might be wrong anyway // b) it would make us being susceptible to a port scanning attack // now connect the data socket ... quint16 port = i[4] << 8 | i[5]; const QString host = (isSocksProxy() ? m_host : address.toString()); const auto connectionResult = synchronousConnectToHost(host, port); m_data = connectionResult.socket; if (!connectionResult.result.success) { return connectionResult.result.error; } return m_data->state() == QAbstractSocket::ConnectedState ? 0 : ERR_INTERNAL; } /* * ftpOpenEPSVDataConnection - opens a data connection via EPSV */ int FtpInternal::ftpOpenEPSVDataConnection() { Q_ASSERT(m_control); // must have control connection socket Q_ASSERT(!m_data); // ... but no data connection QHostAddress address = m_control->peerAddress(); int portnum; if (m_extControl & epsvUnknown) { return ERR_INTERNAL; } m_bPasv = true; if (!ftpSendCmd(QByteArrayLiteral("EPSV")) || (m_iRespType != 2)) { // unknown command? if (m_iRespType == 5) { qCDebug(KIO_FTP) << "disabling use of EPSV"; m_extControl |= epsvUnknown; } return ERR_INTERNAL; } const char *start = strchr(ftpResponse(3), '|'); if (!start || sscanf(start, "|||%d|", &portnum) != 1) { return ERR_INTERNAL; } Q_ASSERT(portnum > 0); const QString host = (isSocksProxy() ? m_host : address.toString()); const auto connectionResult = synchronousConnectToHost(host, static_cast(portnum)); m_data = connectionResult.socket; if (!connectionResult.result.success) { return connectionResult.result.error; } return m_data->state() == QAbstractSocket::ConnectedState ? 0 : ERR_INTERNAL; } /* * ftpOpenDataConnection - set up data connection * * The routine calls several ftpOpenXxxxConnection() helpers to find * the best connection mode. If a helper cannot connect if returns * ERR_INTERNAL - so this is not really an error! All other error * codes are treated as fatal, e.g. they are passed back to the caller * who is responsible for calling error(). ftpOpenPortDataConnection * can be called as last try and it does never return ERR_INTERNAL. * * @return 0 if successful, err code otherwise */ int FtpInternal::ftpOpenDataConnection() { // make sure that we are logged on and have no data connection... Q_ASSERT(m_bLoggedOn); ftpCloseDataConnection(); int iErrCode = 0; int iErrCodePASV = 0; // Remember error code from PASV // First try passive (EPSV & PASV) modes if (!q->configValue(QStringLiteral("DisablePassiveMode"), false)) { iErrCode = ftpOpenPASVDataConnection(); if (iErrCode == 0) { return 0; // success } iErrCodePASV = iErrCode; ftpCloseDataConnection(); if (!q->configValue(QStringLiteral("DisableEPSV"), false)) { iErrCode = ftpOpenEPSVDataConnection(); if (iErrCode == 0) { return 0; // success } ftpCloseDataConnection(); } // if we sent EPSV ALL already and it was accepted, then we can't // use active connections any more if (m_extControl & epsvAllSent) { return iErrCodePASV; } } // fall back to port mode iErrCode = ftpOpenPortDataConnection(); if (iErrCode == 0) { return 0; // success } ftpCloseDataConnection(); // prefer to return the error code from PASV if any, since that's what should have worked in the first place return iErrCodePASV ? iErrCodePASV : iErrCode; } /* * ftpOpenPortDataConnection - set up data connection * * @return 0 if successful, err code otherwise (but never ERR_INTERNAL * because this is the last connection mode that is tried) */ int FtpInternal::ftpOpenPortDataConnection() { Q_ASSERT(m_control); // must have control connection socket Q_ASSERT(!m_data); // ... but no data connection m_bPasv = false; if (m_extControl & eprtUnknown) { return ERR_INTERNAL; } if (!m_server) { m_server = new QTcpServer; m_server->listen(QHostAddress::Any, 0); } if (!m_server->isListening()) { delete m_server; m_server = nullptr; return ERR_CANNOT_LISTEN; } m_server->setMaxPendingConnections(1); QString command; QHostAddress localAddress = m_control->localAddress(); if (localAddress.protocol() == QAbstractSocket::IPv4Protocol) { struct { quint32 ip4; quint16 port; } data; data.ip4 = localAddress.toIPv4Address(); data.port = m_server->serverPort(); unsigned char *pData = reinterpret_cast(&data); 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]); } else if (localAddress.protocol() == QAbstractSocket::IPv6Protocol) { command = QStringLiteral("EPRT |2|%2|%3|").arg(localAddress.toString()).arg(m_server->serverPort()); } if (ftpSendCmd(command.toLatin1()) && (m_iRespType == 2)) { return 0; } delete m_server; m_server = nullptr; return ERR_INTERNAL; } Result FtpInternal::ftpOpenCommand(const char *_command, const QString &_path, char _mode, int errorcode, KIO::fileoffset_t _offset) { int errCode = 0; if (!ftpDataMode(ftpModeFromPath(_path, _mode))) { errCode = ERR_CANNOT_CONNECT; } else { errCode = ftpOpenDataConnection(); } if (errCode != 0) { return Result::fail(errCode, m_host); } if (_offset > 0) { // send rest command if offset > 0, this applies to retr and stor commands char buf[100]; sprintf(buf, "rest %lld", _offset); if (!ftpSendCmd(buf)) { return Result::fail(); } if (m_iRespType != 3) { return Result::fail(ERR_CANNOT_RESUME, _path); // should never happen } } QByteArray tmp = _command; QString errormessage; if (!_path.isEmpty()) { tmp += ' ' + q->remoteEncoding()->encode(ftpCleanPath(_path)); } if (!ftpSendCmd(tmp) || (m_iRespType != 1)) { if (_offset > 0 && qstrcmp(_command, "retr") == 0 && (m_iRespType == 4)) { errorcode = ERR_CANNOT_RESUME; } // The error code here depends on the command errormessage = _path + i18n("\nThe server said: \"%1\"", QString::fromUtf8(ftpResponse(0)).trimmed()); } else { // Only now we know for sure that we can resume if (_offset > 0 && qstrcmp(_command, "retr") == 0) { q->canResume(); } if (m_server && !m_data) { qCDebug(KIO_FTP) << "waiting for connection from remote."; m_server->waitForNewConnection(q->connectTimeout() * 1000); m_data = m_server->nextPendingConnection(); } if (m_data) { qCDebug(KIO_FTP) << "connected with remote."; m_bBusy = true; // cleared in ftpCloseCommand return Result::pass(); } qCDebug(KIO_FTP) << "no connection received from remote."; errorcode = ERR_CANNOT_ACCEPT; errormessage = m_host; } if (errorcode != KJob::NoError) { return Result::fail(errorcode, errormessage); } return Result::fail(); } bool FtpInternal::ftpCloseCommand() { // first close data sockets (if opened), then read response that // we got for whatever was used in ftpOpenCommand ( should be 226 ) ftpCloseDataConnection(); if (!m_bBusy) { return true; } qCDebug(KIO_FTP) << "ftpCloseCommand: reading command result"; m_bBusy = false; if (!ftpResponse(-1) || (m_iRespType != 2)) { qCDebug(KIO_FTP) << "ftpCloseCommand: no transfer complete message"; return false; } return true; } Result FtpInternal::mkdir(const QUrl &url, int permissions) { auto result = ftpOpenConnection(LoginMode::Implicit); if (!result.success) { return result; } const QByteArray encodedPath(q->remoteEncoding()->encode(url)); const QString path = QString::fromLatin1(encodedPath.constData(), encodedPath.size()); if (!ftpSendCmd((QByteArrayLiteral("mkd ") + encodedPath)) || (m_iRespType != 2)) { QString currentPath(m_currentPath); // Check whether or not mkdir failed because // the directory already exists... if (ftpFolder(path)) { const QString &failedPath = path; // Change the directory back to what it was... (void) ftpFolder(currentPath); return Result::fail(ERR_DIR_ALREADY_EXIST, failedPath); } return Result::fail(ERR_CANNOT_MKDIR, path); } if (permissions != -1) { // chmod the dir we just created, ignoring errors. (void) ftpChmod(path, permissions); } return Result::pass(); } Result FtpInternal::rename(const QUrl &src, const QUrl &dst, KIO::JobFlags flags) { const auto result = ftpOpenConnection(LoginMode::Implicit); if (!result.success) { return result; } // The actual functionality is in ftpRename because put needs it return ftpRename(src.path(), dst.path(), flags); } Result FtpInternal::ftpRename(const QString &src, const QString &dst, KIO::JobFlags jobFlags) { Q_ASSERT(m_bLoggedOn); // Must check if dst already exists, RNFR+RNTO overwrites by default (#127793). if (!(jobFlags & KIO::Overwrite)) { if (ftpFileExists(dst)) { return Result::fail(ERR_FILE_ALREADY_EXIST, dst); } } if (ftpFolder(dst)) { return Result::fail(ERR_DIR_ALREADY_EXIST, dst); } // CD into parent folder const int pos = src.lastIndexOf(QLatin1Char('/')); if (pos >= 0) { if (!ftpFolder(src.left(pos + 1))) { return Result::fail(ERR_CANNOT_ENTER_DIRECTORY, src); } } const QByteArray from_cmd = "RNFR " + q->remoteEncoding()->encode(src.mid(pos + 1)); if (!ftpSendCmd(from_cmd) || (m_iRespType != 3)) { return Result::fail(ERR_CANNOT_RENAME, src); } const QByteArray to_cmd = "RNTO " + q->remoteEncoding()->encode(dst); if (!ftpSendCmd(to_cmd) || (m_iRespType != 2)) { return Result::fail(ERR_CANNOT_RENAME, src); } return Result::pass(); } Result FtpInternal::del(const QUrl &url, bool isfile) { auto result = ftpOpenConnection(LoginMode::Implicit); if (!result.success) { return result; } // When deleting a directory, we must exit from it first // The last command probably went into it (to stat it) if (!isfile) { - (void) ftpFolder(q->remoteEncoding()->directory(url)); // ignore errors + (void) ftpFolder(q->remoteEncoding()->decode(q->remoteEncoding()->directory(url))); // ignore errors } const QByteArray cmd = (isfile ? "DELE " : "RMD ") + q->remoteEncoding()->encode(url); if (!ftpSendCmd(cmd) || (m_iRespType != 2)) { return Result::fail(ERR_CANNOT_DELETE, url.path()); } return Result::pass(); } bool FtpInternal::ftpChmod(const QString &path, int permissions) { Q_ASSERT(m_bLoggedOn); if (m_extControl & chmodUnknown) { // previous errors? return false; } // we need to do bit AND 777 to get permissions, in case // we were sent a full mode (unlikely) const QByteArray cmd = "SITE CHMOD " + QByteArray::number(permissions & 0777/*octal*/, 8 /*octal*/) + ' ' + q->remoteEncoding()->encode(path); if (ftpSendCmd(cmd)) { qCDebug(KIO_FTP) << "ftpChmod: Failed to issue chmod"; return false; } if (m_iRespType == 2) { return true; } if (m_iRespCode == 500) { m_extControl |= chmodUnknown; qCDebug(KIO_FTP) << "ftpChmod: CHMOD not supported - disabling"; } return false; } Result FtpInternal::chmod(const QUrl &url, int permissions) { const auto result = ftpOpenConnection(LoginMode::Implicit); if (!result.success) { return result; } if (!ftpChmod(url.path(), permissions)) { return Result::fail(ERR_CANNOT_CHMOD, url.path()); } return Result::pass(); } void FtpInternal::ftpCreateUDSEntry(const QString &filename, const FtpEntry &ftpEnt, UDSEntry &entry, bool isDir) { Q_ASSERT(entry.count() == 0); // by contract :-) entry.fastInsert(KIO::UDSEntry::UDS_NAME, filename); entry.fastInsert(KIO::UDSEntry::UDS_SIZE, ftpEnt.size); entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, ftpEnt.date.toSecsSinceEpoch()); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, ftpEnt.access); entry.fastInsert(KIO::UDSEntry::UDS_USER, ftpEnt.owner); if (!ftpEnt.group.isEmpty()) { entry.fastInsert(KIO::UDSEntry::UDS_GROUP, ftpEnt.group); } if (!ftpEnt.link.isEmpty()) { entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, ftpEnt.link); QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl(QUrl(QLatin1String("ftp://host/") + filename)); // Links on ftp sites are often links to dirs, and we have no way to check // that. Let's do like Netscape : assume dirs generally. // But we do this only when the mimetype can't be known from the filename. // --> we do better than Netscape :-) if (mime.isDefault()) { qCDebug(KIO_FTP) << "Setting guessed mime type to inode/directory for " << filename; entry.fastInsert(KIO::UDSEntry::UDS_GUESSED_MIME_TYPE, QStringLiteral("inode/directory")); isDir = true; } } entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, isDir ? S_IFDIR : ftpEnt.type); // entry.insert KIO::UDSEntry::UDS_ACCESS_TIME,buff.st_atime); // entry.insert KIO::UDSEntry::UDS_CREATION_TIME,buff.st_ctime); } void FtpInternal::ftpShortStatAnswer(const QString &filename, bool isDir) { UDSEntry entry; entry.fastInsert(KIO::UDSEntry::UDS_NAME, filename); entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, isDir ? S_IFDIR : S_IFREG); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); if (isDir) { entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory")); } // No details about size, ownership, group, etc. q->statEntry(entry); } Result FtpInternal::ftpStatAnswerNotFound(const QString &path, const QString &filename) { // Only do the 'hack' below if we want to download an existing file (i.e. when looking at the "source") // When e.g. uploading a file, we still need stat() to return "not found" // when the file doesn't exist. QString statSide = q->metaData(QStringLiteral("statSide")); qCDebug(KIO_FTP) << "statSide=" << statSide; if (statSide == QLatin1String("source")) { qCDebug(KIO_FTP) << "Not found, but assuming found, because some servers don't allow listing"; // MS Server is incapable of handling "list " in a case insensitive way // But "retr " works. So lie in stat(), to get going... // // There's also the case of ftp://ftp2.3ddownloads.com/90380/linuxgames/loki/patches/ut/ut-patch-436.run // where listing permissions are denied, but downloading is still possible. ftpShortStatAnswer(filename, false /*file, not dir*/); return Result::pass(); } return Result::fail(ERR_DOES_NOT_EXIST, path); } Result FtpInternal::stat(const QUrl &url) { qCDebug(KIO_FTP) << "path=" << url.path(); auto result = ftpOpenConnection(LoginMode::Implicit); if (!result.success) { return result; } const QString path = ftpCleanPath(QDir::cleanPath(url.path())); qCDebug(KIO_FTP) << "cleaned path=" << path; // We can't stat root, but we know it's a dir. if (path.isEmpty() || path == QLatin1String("/")) { UDSEntry entry; //entry.insert( KIO::UDSEntry::UDS_NAME, UDSField( QString() ) ); entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral(".")); entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory")); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); entry.fastInsert(KIO::UDSEntry::UDS_USER, QStringLiteral("root")); entry.fastInsert(KIO::UDSEntry::UDS_GROUP, QStringLiteral("root")); // no size q->statEntry(entry); return Result::pass(); } QUrl tempurl(url); tempurl.setPath(path); // take the clean one QString listarg; // = tempurl.directory(QUrl::ObeyTrailingSlash); QString parentDir; const QString filename = tempurl.fileName(); Q_ASSERT(!filename.isEmpty()); // Try cwd into it, if it works it's a dir (and then we'll list the parent directory to get more info) // if it doesn't work, it's a file (and then we'll use dir filename) bool isDir = ftpFolder(path); // if we're only interested in "file or directory", we should stop here QString sDetails = q->metaData(QStringLiteral("details")); int details = sDetails.isEmpty() ? 2 : sDetails.toInt(); qCDebug(KIO_FTP) << "details=" << details; if (details == 0) { if (!isDir && !ftpFileExists(path)) { // ok, not a dir -> is it a file ? // no -> it doesn't exist at all return ftpStatAnswerNotFound(path, filename); } ftpShortStatAnswer(filename, isDir); return Result::pass(); // successfully found a dir or a file -> done } if (!isDir) { // It is a file or it doesn't exist, try going to parent directory parentDir = tempurl.adjusted(QUrl::RemoveFilename).path(); // With files we can do "LIST " to avoid listing the whole dir listarg = filename; } else { // --- New implementation: // Don't list the parent dir. Too slow, might not show it, etc. // Just return that it's a dir. UDSEntry entry; entry.fastInsert(KIO::UDSEntry::UDS_NAME, filename); entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); // No clue about size, ownership, group, etc. q->statEntry(entry); return Result::pass(); } // Now cwd the parent dir, to prepare for listing if (!ftpFolder(parentDir)) { return Result::fail(ERR_CANNOT_ENTER_DIRECTORY, parentDir); } result = ftpOpenCommand("list", listarg, 'I', ERR_DOES_NOT_EXIST); if (!result.success) { qCritical() << "COULD NOT LIST"; return result; } qCDebug(KIO_FTP) << "Starting of list was ok"; Q_ASSERT(!filename.isEmpty() && filename != QLatin1String("/")); bool bFound = false; QUrl linkURL; FtpEntry ftpEnt; QList ftpValidateEntList; while (ftpReadDir(ftpEnt)) { if (!ftpEnt.name.isEmpty() && ftpEnt.name.at(0).isSpace()) { ftpValidateEntList.append(ftpEnt); continue; } // We look for search or filename, since some servers (e.g. ftp.tuwien.ac.at) // return only the filename when doing "dir /full/path/to/file" if (!bFound) { bFound = maybeEmitStatEntry(ftpEnt, filename, isDir); } qCDebug(KIO_FTP) << ftpEnt.name; } for (int i = 0, count = ftpValidateEntList.count(); i < count; ++i) { FtpEntry &ftpEnt = ftpValidateEntList[i]; fixupEntryName(&ftpEnt); if (maybeEmitStatEntry(ftpEnt, filename, isDir)) { break; } } ftpCloseCommand(); // closes the data connection only if (!bFound) { return ftpStatAnswerNotFound(path, filename); } if (!linkURL.isEmpty()) { if (linkURL == url || linkURL == tempurl) { return Result::fail(ERR_CYCLIC_LINK, linkURL.toString()); } return FtpInternal::stat(linkURL); } qCDebug(KIO_FTP) << "stat : finished successfully";; return Result::pass(); } bool FtpInternal::maybeEmitStatEntry(FtpEntry &ftpEnt, const QString &filename, bool isDir) { if (filename == ftpEnt.name && !filename.isEmpty()) { UDSEntry entry; ftpCreateUDSEntry(filename, ftpEnt, entry, isDir); q->statEntry(entry); return true; } return false; } Result FtpInternal::listDir(const QUrl &url) { qCDebug(KIO_FTP) << url; auto result = ftpOpenConnection(LoginMode::Implicit); if (!result.success) { return result; } // No path specified ? QString path = url.path(); if (path.isEmpty()) { QUrl realURL; realURL.setScheme(QStringLiteral("ftp")); realURL.setUserName(m_user); realURL.setPassword(m_pass); realURL.setHost(m_host); if (m_port > 0 && m_port != DEFAULT_FTP_PORT) { realURL.setPort(m_port); } if (m_initialPath.isEmpty()) { m_initialPath = QStringLiteral("/"); } realURL.setPath(m_initialPath); qCDebug(KIO_FTP) << "REDIRECTION to " << realURL; q->redirection(realURL); return Result::pass(); } qCDebug(KIO_FTP) << "hunting for path" << path; result = ftpOpenDir(path); if (!result.success) { if (ftpFileExists(path)) { return Result::fail(ERR_IS_FILE, path); } // not sure which to emit //error( ERR_DOES_NOT_EXIST, path ); return Result::fail(ERR_CANNOT_ENTER_DIRECTORY, path); } UDSEntry entry; FtpEntry ftpEnt; QList ftpValidateEntList; while (ftpReadDir(ftpEnt)) { qCDebug(KIO_FTP) << ftpEnt.name; //Q_ASSERT( !ftpEnt.name.isEmpty() ); if (!ftpEnt.name.isEmpty()) { if (ftpEnt.name.at(0).isSpace()) { ftpValidateEntList.append(ftpEnt); continue; } //if ( S_ISDIR( (mode_t)ftpEnt.type ) ) // qDebug() << "is a dir"; //if ( !ftpEnt.link.isEmpty() ) // qDebug() << "is a link to " << ftpEnt.link; ftpCreateUDSEntry(ftpEnt.name, ftpEnt, entry, false); q->listEntry(entry); entry.clear(); } } for (int i = 0, count = ftpValidateEntList.count(); i < count; ++i) { FtpEntry &ftpEnt = ftpValidateEntList[i]; fixupEntryName(&ftpEnt); ftpCreateUDSEntry(ftpEnt.name, ftpEnt, entry, false); q->listEntry(entry); entry.clear(); } ftpCloseCommand(); // closes the data connection only return Result::pass(); } void FtpInternal::slave_status() { qCDebug(KIO_FTP) << "Got slave_status host = " << (!m_host.toLatin1().isEmpty() ? m_host.toLatin1() : "[None]") << " [" << (m_bLoggedOn ? "Connected" : "Not connected") << "]"; q->slaveStatus(m_host, m_bLoggedOn); } Result FtpInternal::ftpOpenDir(const QString &path) { //QString path( _url.path(QUrl::RemoveTrailingSlash) ); // We try to change to this directory first to see whether it really is a directory. // (And also to follow symlinks) QString tmp = path.isEmpty() ? QStringLiteral("/") : path; // We get '550', whether it's a file or doesn't exist... if (!ftpFolder(tmp)) { return Result::fail(); } // Don't use the path in the list command: // We changed into this directory anyway - so it's enough just to send "list". // We use '-a' because the application MAY be interested in dot files. // The only way to really know would be to have a metadata flag for this... // Since some windows ftp server seems not to support the -a argument, we use a fallback here. // In fact we have to use -la otherwise -a removes the default -l (e.g. ftp.trolltech.com) // Pass KJob::NoError first because we don't want to emit error before we // have tried all commands. auto result = ftpOpenCommand("list -la", QString(), 'I', KJob::NoError); if (!result.success) { result = ftpOpenCommand("list", QString(), 'I', KJob::NoError); } if (!result.success) { // Servers running with Turkish locale having problems converting 'i' letter to upper case. // So we send correct upper case command as last resort. result = ftpOpenCommand("LIST -la", QString(), 'I', ERR_CANNOT_ENTER_DIRECTORY); } if (!result.success) { qCWarning(KIO_FTP) << "Can't open for listing"; return result; } qCDebug(KIO_FTP) << "Starting of list was ok"; return Result::pass(); } bool FtpInternal::ftpReadDir(FtpEntry &de) { Q_ASSERT(m_data); // get a line from the data connection ... while (true) { while (!m_data->canReadLine() && m_data->waitForReadyRead((q->readTimeout() * 1000))) {} QByteArray data = m_data->readLine(); if (data.size() == 0) { break; } const char *buffer = data.data(); qCDebug(KIO_FTP) << "dir > " << buffer; //Normally the listing looks like // -rw-r--r-- 1 dfaure dfaure 102 Nov 9 12:30 log // but on Netware servers like ftp://ci-1.ci.pwr.wroc.pl/ it looks like (#76442) // d [RWCEAFMS] Admin 512 Oct 13 2004 PSI // we should always get the following 5 fields ... const char *p_access, *p_junk, *p_owner, *p_group, *p_size; if ((p_access = strtok((char *)buffer, " ")) == nullptr) { continue; } if ((p_junk = strtok(nullptr, " ")) == nullptr) { continue; } if ((p_owner = strtok(nullptr, " ")) == nullptr) { continue; } if ((p_group = strtok(nullptr, " ")) == nullptr) { continue; } if ((p_size = strtok(nullptr, " ")) == nullptr) { continue; } qCDebug(KIO_FTP) << "p_access=" << p_access << " p_junk=" << p_junk << " p_owner=" << p_owner << " p_group=" << p_group << " p_size=" << p_size; de.access = 0; if (qstrlen(p_access) == 1 && p_junk[0] == '[') { // Netware de.access = S_IRWXU | S_IRWXG | S_IRWXO; // unknown -> give all permissions } const char *p_date_1, *p_date_2, *p_date_3, *p_name; // A special hack for "/dev". A listing may look like this: // crw-rw-rw- 1 root root 1, 5 Jun 29 1997 zero // So we just ignore the number in front of the ",". Ok, it is a hack :-) if (strchr(p_size, ',') != nullptr) { qCDebug(KIO_FTP) << "Size contains a ',' -> reading size again (/dev hack)"; if ((p_size = strtok(nullptr, " ")) == nullptr) { continue; } } // This is needed for ftp servers with a directory listing like this (#375610): // drwxr-xr-x folder 0 Mar 15 15:50 directory_name if (strcmp(p_junk, "folder") == 0) { p_date_1 = p_group; p_date_2 = p_size; p_size = p_owner; p_group = nullptr; p_owner = nullptr; } // Check whether the size we just read was really the size // or a month (this happens when the server lists no group) // Used to be the case on sunsite.uio.no, but not anymore // This is needed for the Netware case, too. else if (!isdigit(*p_size)) { p_date_1 = p_size; p_date_2 = strtok(nullptr, " "); p_size = p_group; p_group = nullptr; qCDebug(KIO_FTP) << "Size didn't have a digit -> size=" << p_size << " date_1=" << p_date_1; } else { p_date_1 = strtok(nullptr, " "); p_date_2 = strtok(nullptr, " "); qCDebug(KIO_FTP) << "Size has a digit -> ok. p_date_1=" << p_date_1; } if (p_date_1 != nullptr && p_date_2 != nullptr && (p_date_3 = strtok(nullptr, " ")) != nullptr && (p_name = strtok(nullptr, "\r\n")) != nullptr) { { QByteArray tmp(p_name); if (p_access[0] == 'l') { int i = tmp.lastIndexOf(" -> "); if (i != -1) { de.link = q->remoteEncoding()->decode(p_name + i + 4); tmp.truncate(i); } else { de.link.clear(); } } else { de.link.clear(); } if (tmp[0] == '/') { // listing on ftp://ftp.gnupg.org/ starts with '/' tmp.remove(0, 1); } if (tmp.indexOf('/') != -1) { continue; // Don't trick us! } de.name = q->remoteEncoding()->decode(tmp); } de.type = S_IFREG; switch (p_access[0]) { case 'd': de.type = S_IFDIR; break; case 's': de.type = S_IFSOCK; break; case 'b': de.type = S_IFBLK; break; case 'c': de.type = S_IFCHR; break; case 'l': de.type = S_IFREG; // we don't set S_IFLNK here. de.link says it. break; default: break; } if (p_access[1] == 'r') { de.access |= S_IRUSR; } if (p_access[2] == 'w') { de.access |= S_IWUSR; } if (p_access[3] == 'x' || p_access[3] == 's') { de.access |= S_IXUSR; } if (p_access[4] == 'r') { de.access |= S_IRGRP; } if (p_access[5] == 'w') { de.access |= S_IWGRP; } if (p_access[6] == 'x' || p_access[6] == 's') { de.access |= S_IXGRP; } if (p_access[7] == 'r') { de.access |= S_IROTH; } if (p_access[8] == 'w') { de.access |= S_IWOTH; } if (p_access[9] == 'x' || p_access[9] == 't') { de.access |= S_IXOTH; } if (p_access[3] == 's' || p_access[3] == 'S') { de.access |= S_ISUID; } if (p_access[6] == 's' || p_access[6] == 'S') { de.access |= S_ISGID; } if (p_access[9] == 't' || p_access[9] == 'T') { de.access |= S_ISVTX; } de.owner = q->remoteEncoding()->decode(p_owner); de.group = q->remoteEncoding()->decode(p_group); de.size = charToLongLong(p_size); // Parsing the date is somewhat tricky // Examples : "Oct 6 22:49", "May 13 1999" // First get current date - we need the current month and year QDate currentDate(QDate::currentDate()); int currentMonth = currentDate.month(); int day = currentDate.day(); int month = currentDate.month(); int year = currentDate.year(); int minute = 0; int hour = 0; // Get day number (always second field) if (p_date_2) { day = atoi(p_date_2); } // Get month from first field // NOTE : no, we don't want to use KLocale here // It seems all FTP servers use the English way qCDebug(KIO_FTP) << "Looking for month " << p_date_1; static const char s_months[][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; for (int c = 0; c < 12; c ++) if (!qstrcmp(p_date_1, s_months[c])) { qCDebug(KIO_FTP) << "Found month " << c << " for " << p_date_1; month = c + 1; break; } // Parse third field if (qstrlen(p_date_3) == 4) { // 4 digits, looks like a year year = atoi(p_date_3); } else { // otherwise, the year is implicit // according to man ls, this happens when it is between than 6 months // old and 1 hour in the future. // So the year is : current year if tm_mon <= currentMonth+1 // otherwise current year minus one // (The +1 is a security for the "+1 hour" at the end of the month issue) if (month > currentMonth + 1) { year--; } // and p_date_3 contains probably a time char *semicolon; if (p_date_3 && (semicolon = (char *)strchr(p_date_3, ':'))) { *semicolon = '\0'; minute = atoi(semicolon + 1); hour = atoi(p_date_3); } else { qCWarning(KIO_FTP) << "Can't parse third field " << p_date_3; } } de.date = QDateTime(QDate(year, month, day), QTime(hour, minute)); qCDebug(KIO_FTP) << de.date; return true; } } // line invalid, loop to get another line return false; } //=============================================================================== // public: get download file from server // helper: ftpGet called from get() and copy() //=============================================================================== Result FtpInternal::get(const QUrl &url) { qCDebug(KIO_FTP) << url; const Result result = ftpGet(-1, QString(), url, 0); ftpCloseCommand(); // must close command! return result; } Result FtpInternal::ftpGet(int iCopyFile, const QString &sCopyFile, const QUrl &url, KIO::fileoffset_t llOffset) { auto result = ftpOpenConnection(LoginMode::Implicit); if (!result.success) { return result; } // Try to find the size of the file (and check that it exists at // the same time). If we get back a 550, "File does not exist" // or "not a plain file", check if it is a directory. If it is a // directory, return an error; otherwise simply try to retrieve // the request... if (!ftpSize(url.path(), '?') && (m_iRespCode == 550) && ftpFolder(url.path())) { // Ok it's a dir in fact qCDebug(KIO_FTP) << "it is a directory in fact"; return Result::fail(ERR_IS_DIRECTORY); } QString resumeOffset = q->metaData(QStringLiteral("range-start")); if (resumeOffset.isEmpty()) { resumeOffset = q->metaData(QStringLiteral("resume")); // old name } if (!resumeOffset.isEmpty()) { llOffset = resumeOffset.toLongLong(); qCDebug(KIO_FTP) << "got offset from metadata : " << llOffset; } result = ftpOpenCommand("retr", url.path(), '?', ERR_CANNOT_OPEN_FOR_READING, llOffset); if (!result.success) { qCWarning(KIO_FTP) << "Can't open for reading"; return result; } // Read the size from the response string if (m_size == UnknownSize) { const char *psz = strrchr(ftpResponse(4), '('); if (psz) { m_size = charToLongLong(psz + 1); } if (!m_size) { m_size = UnknownSize; } } // Send the mime-type... if (iCopyFile == -1) { const auto result = ftpSendMimeType(url); if (!result.success) { return result; } } KIO::filesize_t bytesLeft = 0; if (m_size != UnknownSize) { bytesLeft = m_size - llOffset; q->totalSize(m_size); // emit the total size... } qCDebug(KIO_FTP) << "starting with offset=" << llOffset; KIO::fileoffset_t processed_size = llOffset; QByteArray array; char buffer[maximumIpcSize]; // start with small data chunks in case of a slow data source (modem) // - unfortunately this has a negative impact on performance for large // - files - so we will increase the block size after a while ... int iBlockSize = initialIpcSize; int iBufferCur = 0; while (m_size == UnknownSize || bytesLeft > 0) { // let the buffer size grow if the file is larger 64kByte ... if (processed_size - llOffset > 1024 * 64) { iBlockSize = maximumIpcSize; } // read the data and detect EOF or error ... if (iBlockSize + iBufferCur > (int)sizeof(buffer)) { iBlockSize = sizeof(buffer) - iBufferCur; } if (m_data->bytesAvailable() == 0) { m_data->waitForReadyRead((q->readTimeout() * 1000)); } int n = m_data->read(buffer + iBufferCur, iBlockSize); if (n <= 0) { // this is how we detect EOF in case of unknown size if (m_size == UnknownSize && n == 0) { break; } // unexpected eof. Happens when the daemon gets killed. return Result::fail(ERR_CANNOT_READ); } processed_size += n; // collect very small data chunks in buffer before processing ... if (m_size != UnknownSize) { bytesLeft -= n; iBufferCur += n; if (iBufferCur < minimumMimeSize && bytesLeft > 0) { q->processedSize(processed_size); continue; } n = iBufferCur; iBufferCur = 0; } // write output file or pass to data pump ... int writeError = 0; if (iCopyFile == -1) { array = QByteArray::fromRawData(buffer, n); q->data(array); array.clear(); } else if ((writeError = WriteToFile(iCopyFile, buffer, n)) != 0) { return Result::fail(writeError, sCopyFile); } Q_ASSERT(processed_size >= 0); q->processedSize(static_cast(processed_size)); } qCDebug(KIO_FTP) << "done"; if (iCopyFile == -1) { // must signal EOF to data pump ... q->data(array); // array is empty and must be empty! } q->processedSize(m_size == UnknownSize ? processed_size : m_size); return Result::pass(); } #if 0 void FtpInternal::mimetype(const QUrl &url) { if (!ftpOpenConnection(loginImplicit)) { return; } if (!ftpOpenCommand("retr", url.path(), 'I', ERR_CANNOT_OPEN_FOR_READING, 0)) { qCWarning(KIO_FTP) << "Can't open for reading"; return; } char buffer[ 2048 ]; QByteArray array; // Get one chunk of data only and send it, KIO::Job will determine the // mimetype from it using KMimeMagic int n = m_data->read(buffer, 2048); array.setRawData(buffer, n); data(array); array.resetRawData(buffer, n); qCDebug(KIO_FTP) << "aborting"; ftpAbortTransfer(); qCDebug(KIO_FTP) << "finished"; finished(); qCDebug(KIO_FTP) << "after finished"; } void FtpInternal::ftpAbortTransfer() { // RFC 959, page 34-35 // IAC (interpret as command) = 255 ; IP (interrupt process) = 254 // DM = 242 (data mark) char msg[4]; // 1. User system inserts the Telnet "Interrupt Process" (IP) signal // in the Telnet stream. msg[0] = (char) 255; //IAC msg[1] = (char) 254; //IP (void) send(sControl, msg, 2, 0); // 2. User system sends the Telnet "Sync" signal. msg[0] = (char) 255; //IAC msg[1] = (char) 242; //DM if (send(sControl, msg, 2, MSG_OOB) != 2) ; // error... // Send ABOR qCDebug(KIO_FTP) << "send ABOR"; QCString buf = "ABOR\r\n"; if (KSocks::self()->write(sControl, buf.data(), buf.length()) <= 0) { error(ERR_CANNOT_WRITE, QString()); return; } // qCDebug(KIO_FTP) << "read resp"; if (readresp() != '2') { error(ERR_CANNOT_READ, QString()); return; } qCDebug(KIO_FTP) << "close sockets"; closeSockets(); } #endif //=============================================================================== // public: put upload file to server // helper: ftpPut called from put() and copy() //=============================================================================== Result FtpInternal::put(const QUrl &url, int permissions, KIO::JobFlags flags) { qCDebug(KIO_FTP) << url; const auto result = ftpPut(-1, url, permissions, flags); ftpCloseCommand(); // must close command! return result; } Result FtpInternal::ftpPut(int iCopyFile, const QUrl &dest_url, int permissions, KIO::JobFlags flags) { const auto openResult = ftpOpenConnection(LoginMode::Implicit); if (!openResult.success) { return openResult; } // Don't use mark partial over anonymous FTP. // My incoming dir allows put but not rename... bool bMarkPartial; if (m_user.isEmpty() || m_user == QLatin1String(FTP_LOGIN)) { bMarkPartial = false; } else { bMarkPartial = q->configValue(QStringLiteral("MarkPartial"), true); } QString dest_orig = dest_url.path(); const QString dest_part = dest_orig + QLatin1String(".part"); if (ftpSize(dest_orig, 'I')) { if (m_size == 0) { // delete files with zero size const QByteArray cmd = "DELE " + q->remoteEncoding()->encode(dest_orig); if (!ftpSendCmd(cmd) || (m_iRespType != 2)) { return Result::fail(ERR_CANNOT_DELETE_PARTIAL, QString()); } } else if (!(flags & KIO::Overwrite) && !(flags & KIO::Resume)) { return Result::fail(ERR_FILE_ALREADY_EXIST, QString()); } else if (bMarkPartial) { // when using mark partial, append .part extension const auto result = ftpRename(dest_orig, dest_part, KIO::Overwrite); if (!result.success) { return Result::fail(ERR_CANNOT_RENAME_PARTIAL, QString()); } } // Don't chmod an existing file permissions = -1; } else if (bMarkPartial && ftpSize(dest_part, 'I')) { // file with extension .part exists if (m_size == 0) { // delete files with zero size const QByteArray cmd = "DELE " + q->remoteEncoding()->encode(dest_part); if (!ftpSendCmd(cmd) || (m_iRespType != 2)) { return Result::fail(ERR_CANNOT_DELETE_PARTIAL, QString()); } } else if (!(flags & KIO::Overwrite) && !(flags & KIO::Resume)) { flags |= q->canResume(m_size) ? KIO::Resume : KIO::DefaultFlags; if (!(flags & KIO::Resume)) { return Result::fail(ERR_FILE_ALREADY_EXIST, QString()); } } } else { m_size = 0; } QString dest; // if we are using marking of partial downloads -> add .part extension if (bMarkPartial) { qCDebug(KIO_FTP) << "Adding .part extension to " << dest_orig; dest = dest_part; } else { dest = dest_orig; } KIO::fileoffset_t offset = 0; // set the mode according to offset if ((flags & KIO::Resume) && m_size > 0) { offset = m_size; if (iCopyFile != -1) { if (QT_LSEEK(iCopyFile, offset, SEEK_SET) < 0) { return Result::fail(ERR_CANNOT_RESUME, QString()); } } } const auto storResult = ftpOpenCommand("stor", dest, '?', ERR_CANNOT_WRITE, offset); if (!storResult.success) { return storResult; } qCDebug(KIO_FTP) << "ftpPut: starting with offset=" << offset; KIO::fileoffset_t processed_size = offset; QByteArray buffer; int result; int iBlockSize = initialIpcSize; int writeError = 0; // Loop until we got 'dataEnd' do { if (iCopyFile == -1) { q->dataReq(); // Request for data result = q->readData(buffer); } else { // let the buffer size grow if the file is larger 64kByte ... if (processed_size - offset > 1024 * 64) { iBlockSize = maximumIpcSize; } buffer.resize(iBlockSize); result = QT_READ(iCopyFile, buffer.data(), buffer.size()); if (result < 0) { writeError = ERR_CANNOT_READ; } else { buffer.resize(result); } } if (result > 0) { m_data->write(buffer); while (m_data->bytesToWrite() && m_data->waitForBytesWritten()) {} processed_size += result; q->processedSize(processed_size); } } while (result > 0); if (result != 0) { // error ftpCloseCommand(); // don't care about errors qCDebug(KIO_FTP) << "Error during 'put'. Aborting."; if (bMarkPartial) { // Remove if smaller than minimum size if (ftpSize(dest, 'I') && (processed_size < q->configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE))) { const QByteArray cmd = "DELE " + q->remoteEncoding()->encode(dest); (void) ftpSendCmd(cmd); } } return Result::fail(writeError, dest_url.toString()); } if (!ftpCloseCommand()) { return Result::fail(ERR_CANNOT_WRITE); } // after full download rename the file back to original name if (bMarkPartial) { qCDebug(KIO_FTP) << "renaming dest (" << dest << ") back to dest_orig (" << dest_orig << ")"; const auto result = ftpRename(dest, dest_orig, KIO::Overwrite); if (!result.success) { return Result::fail(ERR_CANNOT_RENAME_PARTIAL); } } // set final permissions if (permissions != -1) { if (m_user == QLatin1String(FTP_LOGIN)) qCDebug(KIO_FTP) << "Trying to chmod over anonymous FTP ???"; // chmod the file we just put if (! ftpChmod(dest_orig, permissions)) { // To be tested //if ( m_user != FTP_LOGIN ) // warning( i18n( "Could not change permissions for\n%1" ).arg( dest_orig ) ); } } return Result::pass(); } /** Use the SIZE command to get the file size. Warning : the size depends on the transfer mode, hence the second arg. */ bool FtpInternal::ftpSize(const QString &path, char mode) { m_size = UnknownSize; if (!ftpDataMode(mode)) { return false; } const QByteArray buf = "SIZE " + q->remoteEncoding()->encode(path); if (!ftpSendCmd(buf) || (m_iRespType != 2)) { return false; } // skip leading "213 " (response code) QByteArray psz(ftpResponse(4)); if (psz.isEmpty()) { return false; } bool ok = false; m_size = psz.trimmed().toLongLong(&ok); if (!ok) { m_size = UnknownSize; } return true; } bool FtpInternal::ftpFileExists(const QString &path) { const QByteArray buf = "SIZE " + q->remoteEncoding()->encode(path); if (!ftpSendCmd(buf) || (m_iRespType != 2)) { return false; } // skip leading "213 " (response code) const char *psz = ftpResponse(4); return psz != nullptr; } // Today the differences between ASCII and BINARY are limited to // CR or CR/LF line terminators. Many servers ignore ASCII (like // win2003 -or- vsftp with default config). In the early days of // computing, when even text-files had structure, this stuff was // more important. // Theoretically "list" could return different results in ASCII // and BINARY mode. But again, most servers ignore ASCII here. bool FtpInternal::ftpDataMode(char cMode) { if (cMode == '?') { cMode = m_bTextMode ? 'A' : 'I'; } else if (cMode == 'a') { cMode = 'A'; } else if (cMode != 'A') { cMode = 'I'; } qCDebug(KIO_FTP) << "want" << cMode << "has" << m_cDataMode; if (m_cDataMode == cMode) { return true; } const QByteArray buf = QByteArrayLiteral("TYPE ") + cMode; if (!ftpSendCmd(buf) || (m_iRespType != 2)) { return false; } m_cDataMode = cMode; return true; } bool FtpInternal::ftpFolder(const QString &path) { QString newPath = path; int iLen = newPath.length(); if (iLen > 1 && newPath[iLen - 1] == QLatin1Char('/')) { newPath.chop(1); } qCDebug(KIO_FTP) << "want" << newPath << "has" << m_currentPath; if (m_currentPath == newPath) { return true; } const QByteArray tmp = "cwd " + q->remoteEncoding()->encode(newPath); if (!ftpSendCmd(tmp)) { return false; // connection failure } if (m_iRespType != 2) { return false; // not a folder } m_currentPath = newPath; return true; } //=============================================================================== // public: copy don't use kio data pump if one side is a local file // helper: ftpCopyPut called from copy() on upload // helper: ftpCopyGet called from copy() on download //=============================================================================== Result FtpInternal::copy(const QUrl &src, const QUrl &dest, int permissions, KIO::JobFlags flags) { int iCopyFile = -1; bool bSrcLocal = src.isLocalFile(); bool bDestLocal = dest.isLocalFile(); QString sCopyFile; Result result = Result::pass(); if (bSrcLocal && !bDestLocal) { // File -> Ftp sCopyFile = src.toLocalFile(); qCDebug(KIO_FTP) << "local file" << sCopyFile << "-> ftp" << dest.path(); result = ftpCopyPut(iCopyFile, sCopyFile, dest, permissions, flags); } else if (!bSrcLocal && bDestLocal) { // Ftp -> File sCopyFile = dest.toLocalFile(); qCDebug(KIO_FTP) << "ftp" << src.path() << "-> local file" << sCopyFile; result = ftpCopyGet(iCopyFile, sCopyFile, src, permissions, flags); } else { return Result::fail(ERR_UNSUPPORTED_ACTION, QString()); } // perform clean-ups and report error (if any) if (iCopyFile != -1) { QT_CLOSE(iCopyFile); } ftpCloseCommand(); // must close command! return result; } bool FtpInternal::isSocksProxyScheme(const QString &scheme) { - return scheme == "socks" || scheme == "socks5"; + return scheme == QLatin1String("socks") || scheme == QLatin1String("socks5"); } bool FtpInternal::isSocksProxy() const { return isSocksProxyScheme(m_proxyURL.scheme()); } Result FtpInternal::ftpCopyPut(int &iCopyFile, const QString &sCopyFile, const QUrl &url, int permissions, KIO::JobFlags flags) { // check if source is ok ... QFileInfo info(sCopyFile); bool bSrcExists = info.exists(); if (bSrcExists) { if (info.isDir()) { return Result::fail(ERR_IS_DIRECTORY); } } else { return Result::fail(ERR_DOES_NOT_EXIST); } iCopyFile = QT_OPEN(QFile::encodeName(sCopyFile).constData(), O_RDONLY); if (iCopyFile == -1) { return Result::fail(ERR_CANNOT_OPEN_FOR_READING); } // delegate the real work (iError gets status) ... q->totalSize(info.size()); #ifdef ENABLE_CAN_RESUME return ftpPut(iCopyFile, url, permissions, flags & ~KIO::Resume); #else return ftpPut(iCopyFile, url, permissions, flags | KIO::Resume); #endif } Result FtpInternal::ftpCopyGet(int &iCopyFile, const QString &sCopyFile, const QUrl &url, int permissions, KIO::JobFlags flags) { // check if destination is ok ... QFileInfo info(sCopyFile); const bool bDestExists = info.exists(); if (bDestExists) { if (info.isDir()) { return Result::fail(ERR_IS_DIRECTORY); } if (!(flags & KIO::Overwrite)) { return Result::fail(ERR_FILE_ALREADY_EXIST); } } // do we have a ".part" file? const QString sPart = sCopyFile + QLatin1String(".part"); bool bResume = false; QFileInfo sPartInfo(sPart); const bool bPartExists = sPartInfo.exists(); const bool bMarkPartial = q->configValue(QStringLiteral("MarkPartial"), true); const QString dest = bMarkPartial ? sPart : sCopyFile; if (bMarkPartial && bPartExists && sPartInfo.size() > 0) { // must not be a folder! please fix a similar bug in kio_file!! if (sPartInfo.isDir()) { return Result::fail(ERR_DIR_ALREADY_EXIST); } //doesn't work for copy? -> design flaw? #ifdef ENABLE_CAN_RESUME bResume = q->canResume(sPartInfo.size()); #else bResume = true; #endif } if (bPartExists && !bResume) { // get rid of an unwanted ".part" file QFile::remove(sPart); } // WABA: Make sure that we keep writing permissions ourselves, // otherwise we can be in for a surprise on NFS. mode_t initialMode; if (permissions >= 0) { initialMode = static_cast(permissions | S_IWUSR); } else { initialMode = 0666; } // open the output file ... KIO::fileoffset_t hCopyOffset = 0; if (bResume) { iCopyFile = QT_OPEN(QFile::encodeName(sPart).constData(), O_RDWR); // append if resuming hCopyOffset = QT_LSEEK(iCopyFile, 0, SEEK_END); if (hCopyOffset < 0) { return Result::fail(ERR_CANNOT_RESUME); } qCDebug(KIO_FTP) << "resuming at " << hCopyOffset; } else { iCopyFile = QT_OPEN(QFile::encodeName(dest).constData(), O_CREAT | O_TRUNC | O_WRONLY, initialMode); } if (iCopyFile == -1) { qCDebug(KIO_FTP) << "### COULD NOT WRITE " << sCopyFile; const int error = (errno == EACCES) ? ERR_WRITE_ACCESS_DENIED : ERR_CANNOT_OPEN_FOR_WRITING; return Result::fail(error); } // delegate the real work (iError gets status) ... auto result = ftpGet(iCopyFile, sCopyFile, url, hCopyOffset); if (QT_CLOSE(iCopyFile) == 0 && !result.success) { // If closing the file failed but there isn't an error yet, switch // into an error! result = Result::fail(ERR_CANNOT_WRITE); } iCopyFile = -1; // handle renaming or deletion of a partial file ... if (bMarkPartial) { if (result.success) { // rename ".part" on success if (!QFile::rename(sPart, sCopyFile)) { // If rename fails, try removing the destination first if it exists. if (!bDestExists || !(QFile::remove(sCopyFile) && QFile::rename(sPart, sCopyFile))) { qCDebug(KIO_FTP) << "cannot rename " << sPart << " to " << sCopyFile; result = Result::fail(ERR_CANNOT_RENAME_PARTIAL); } } } else { sPartInfo.refresh(); if (sPartInfo.exists()) { // should a very small ".part" be deleted? int size = q->configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE); if (sPartInfo.size() < size) { QFile::remove(sPart); } } } } if (result.success) { const QString mtimeStr = q->metaData(QStringLiteral("modified")); if (!mtimeStr.isEmpty()) { QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); if (dt.isValid()) { qCDebug(KIO_FTP) << "Updating modified timestamp to" << mtimeStr; struct utimbuf utbuf; info.refresh(); utbuf.actime = info.lastRead().toSecsSinceEpoch(); // access time, unchanged utbuf.modtime = dt.toSecsSinceEpoch(); // modification time ::utime(QFile::encodeName(sCopyFile).constData(), &utbuf); } } } return result; } Result FtpInternal::ftpSendMimeType(const QUrl &url) { const int totalSize = ((m_size == UnknownSize || m_size > 1024) ? 1024 : static_cast(m_size)); QByteArray buffer(totalSize, '\0'); while (true) { // Wait for content to be available... if (m_data->bytesAvailable() == 0 && !m_data->waitForReadyRead((q->readTimeout() * 1000))) { return Result::fail(ERR_CANNOT_READ, url.toString()); } const qint64 bytesRead = m_data->peek(buffer.data(), totalSize); // If we got a -1, it must be an error so return an error. if (bytesRead == -1) { return Result::fail(ERR_CANNOT_READ, url.toString()); } // If m_size is unknown, peek returns 0 (0 sized file ??), or peek returns size // equal to the size we want, then break. if (bytesRead == 0 || bytesRead == totalSize || m_size == UnknownSize) { break; } } if (!buffer.isEmpty()) { QMimeDatabase db; QMimeType mime = db.mimeTypeForFileNameAndData(url.path(), buffer); qCDebug(KIO_FTP) << "Emitting mimetype" << mime.name(); q->mimeType(mime.name()); // emit the mime type... } return Result::pass(); } void FtpInternal::fixupEntryName(FtpEntry *e) { Q_ASSERT(e); if (e->type == S_IFDIR) { if (!ftpFolder(e->name)) { QString name(e->name.trimmed()); if (ftpFolder(name)) { e->name = name; qCDebug(KIO_FTP) << "fixing up directory name from" << e->name << "to" << name; } else { int index = 0; while (e->name.at(index).isSpace()) { index++; name = e->name.mid(index); if (ftpFolder(name)) { qCDebug(KIO_FTP) << "fixing up directory name from" << e->name << "to" << name; e->name = name; break; } } } } } else { if (!ftpFileExists(e->name)) { QString name(e->name.trimmed()); if (ftpFileExists(name)) { e->name = name; qCDebug(KIO_FTP) << "fixing up filename from" << e->name << "to" << name; } else { int index = 0; while (e->name.at(index).isSpace()) { index++; name = e->name.mid(index); if (ftpFileExists(name)) { qCDebug(KIO_FTP) << "fixing up filename from" << e->name << "to" << name; e->name = name; break; } } } } } } ConnectionResult FtpInternal::synchronousConnectToHost(const QString &host, quint16 port) { const QUrl proxyUrl = m_proxyURL; QNetworkProxy proxy; if (!proxyUrl.isEmpty()) { proxy = QNetworkProxy(QNetworkProxy::Socks5Proxy, proxyUrl.host(), static_cast(proxyUrl.port(0)), proxyUrl.userName(), proxyUrl.password()); } QTcpSocket *socket = new QSslSocket; socket->setProxy(proxy); socket->connectToHost(host, port); socket->waitForConnected(q->connectTimeout() * 1000); if (socket->error() == QAbstractSocket::ProxyAuthenticationRequiredError) { AuthInfo info; info.url = proxyUrl; info.verifyPath = true; //### whatever if (!q->checkCachedAuthentication(info)) { info.prompt = i18n("You need to supply a username and a password for " "the proxy server listed below before you are allowed " "to access any sites."); info.keepPassword = true; info.commentLabel = i18n("Proxy:"); info.comment = i18n("%1", proxy.hostName()); const int errorCode = q->openPasswordDialogV2(info, i18n("Proxy Authentication Failed.")); if (errorCode != KJob::NoError) { qCDebug(KIO_FTP) << "user canceled proxy authentication, or communication error." << errorCode; return ConnectionResult { socket, Result::fail(errorCode, proxyUrl.toString()) }; } } proxy.setUser(info.username); proxy.setPassword(info.password); delete socket; socket = new QSslSocket; socket->setProxy(proxy); socket->connectToHost(host, port); socket->waitForConnected(q->connectTimeout() * 1000); if (socket->state() == QAbstractSocket::ConnectedState) { // reconnect with credentials was successful -> save data q->cacheAuthentication(info); m_proxyURL.setUserName(info.username); m_proxyURL.setPassword(info.password); } } return ConnectionResult { socket, Result::pass() }; } //=============================================================================== // Ftp //=============================================================================== Ftp::Ftp(const QByteArray &pool, const QByteArray &app) : SlaveBase(QByteArrayLiteral("ftp"), pool, app) , d(new FtpInternal(this)) { } Ftp::~Ftp() = default; void Ftp::setHost(const QString &host, quint16 port, const QString &user, const QString &pass) { d->setHost(host, port, user, pass); } void Ftp::openConnection() { const auto result = d->openConnection(); if (!result.success) { error(result.error, result.errorString); return; } opened(); } void Ftp::closeConnection() { d->closeConnection(); } void Ftp::stat(const QUrl &url) { finalize(d->stat(url)); } void Ftp::listDir(const QUrl &url) { finalize(d->listDir(url)); } void Ftp::mkdir(const QUrl &url, int permissions) { finalize(d->mkdir(url, permissions)); } void Ftp::rename(const QUrl &src, const QUrl &dst, JobFlags flags) { finalize(d->rename(src, dst, flags)); } void Ftp::del(const QUrl &url, bool isfile) { finalize(d->del(url, isfile)); } void Ftp::chmod(const QUrl &url, int permissions) { finalize(d->chmod(url, permissions)); } void Ftp::get(const QUrl &url) { finalize(d->get(url)); } void Ftp::put(const QUrl &url, int permissions, JobFlags flags) { finalize(d->put(url, permissions, flags)); } void Ftp::slave_status() { d->slave_status(); } void Ftp::copy(const QUrl &src, const QUrl &dest, int permissions, JobFlags flags) { finalize(d->copy(src, dest, permissions, flags)); } void Ftp::finalize(const Result &result) { if (!result.success) { error(result.error, result.errorString); return; } finished(); } QDebug operator<<(QDebug dbg, const Result &r) { QDebugStateSaver saver(dbg); dbg.nospace() << "Result(" << "success=" << r.success << ", err=" << r.error << ", str=" << r.errorString << ')'; return dbg; } // needed for JSON file embedding #include "ftp.moc" diff --git a/src/kcms/kio/kcookiespolicies.cpp b/src/kcms/kio/kcookiespolicies.cpp index 90ccd9d6..d4530af8 100644 --- a/src/kcms/kio/kcookiespolicies.cpp +++ b/src/kcms/kio/kcookiespolicies.cpp @@ -1,465 +1,463 @@ /** * kcookiespolicies.cpp - Cookies configuration * * Original Authors * Copyright (c) Waldo Bastian * Copyright (c) 1999 David Faure * Copyright (c) 2008 Urs Wolfer * * Re-written by: * Copyright (c) 2000- Dawit Alemayehu * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// TODO: remove me -#undef QT_NO_CAST_FROM_ASCII - // Own #include "kcookiespolicies.h" // Local #include "ksaveioconfig.h" // Qt #include #include #include #include // KDE #include #include #include #include #include #include // QUrl::fromAce/toAce don't accept a domain that starts with a '.', like we do here. // So we use these wrappers. QString tolerantFromAce (const QByteArray& _domain) { QByteArray domain (_domain); const bool hasDot = domain.startsWith ('.'); if (hasDot) domain.remove (0, 1); QString ret = QUrl::fromAce(domain); if (hasDot) { ret.prepend(QLatin1Char('.')); } return ret; } static QByteArray tolerantToAce (const QString& _domain) { QString domain (_domain); const bool hasDot = domain.startsWith(QLatin1Char('.')); if (hasDot) domain.remove (0, 1); QByteArray ret = QUrl::toAce (domain); if (hasDot) { ret.prepend ('.'); } return ret; } KCookiesPolicies::KCookiesPolicies (QWidget* parent) : KCModule (parent), mSelectedItemsCount(0) { mUi.setupUi (this); mUi.kListViewSearchLine->setTreeWidget (mUi.policyTreeWidget); QList columns; columns.append (0); mUi.kListViewSearchLine->setSearchColumns (columns); mUi.pbNew->setIcon (QIcon::fromTheme(QStringLiteral("list-add"))); mUi.pbChange->setIcon (QIcon::fromTheme(QStringLiteral("edit-rename"))); mUi.pbDelete->setIcon (QIcon::fromTheme(QStringLiteral("list-remove"))); mUi.pbDeleteAll->setIcon (QIcon::fromTheme(QStringLiteral("edit-delete"))); // Connect the main swicth :) Enable/disable cookie support connect (mUi.cbEnableCookies, &QAbstractButton::toggled, this, &KCookiesPolicies::cookiesEnabled); connect (mUi.cbEnableCookies, &QAbstractButton::toggled, this, &KCookiesPolicies::configChanged); // Connect the preference check boxes... connect (mUi.cbRejectCrossDomainCookies, &QAbstractButton::toggled, this, &KCookiesPolicies::configChanged); connect (mUi.cbAutoAcceptSessionCookies, &QAbstractButton::toggled, this, &KCookiesPolicies::configChanged); connect (mUi.rbPolicyAsk, &QAbstractButton::toggled, this, &KCookiesPolicies::configChanged); connect (mUi.rbPolicyAccept, &QAbstractButton::toggled, this, &KCookiesPolicies::configChanged); connect (mUi.rbPolicyAcceptForSession, &QAbstractButton::toggled, this, &KCookiesPolicies::configChanged); connect (mUi.rbPolicyReject, &QAbstractButton::toggled, this, &KCookiesPolicies::configChanged); // Connect signals from the domain specific policy listview. connect (mUi.policyTreeWidget, &QTreeWidget::itemSelectionChanged, this, &KCookiesPolicies::selectionChanged); connect (mUi.policyTreeWidget, &QTreeWidget::itemDoubleClicked, this, QOverload<>::of(&KCookiesPolicies::changePressed)); // Connect the buttons... connect(mUi.pbNew, &QAbstractButton::clicked, this, QOverload<>::of(&KCookiesPolicies::addPressed)); connect(mUi.pbChange, &QAbstractButton::clicked, this, QOverload<>::of(&KCookiesPolicies::changePressed)); connect(mUi.pbDelete, &QAbstractButton::clicked, this, &KCookiesPolicies::deletePressed); connect(mUi.pbDeleteAll, &QAbstractButton::clicked, this, &KCookiesPolicies::deleteAllPressed); } KCookiesPolicies::~KCookiesPolicies() { } void KCookiesPolicies::configChanged () { //kDebug() << "KCookiesPolicies::configChanged..."; emit changed (true); } void KCookiesPolicies::cookiesEnabled (bool enable) { mUi.bgDefault->setEnabled (enable); mUi.bgPreferences->setEnabled (enable); mUi.gbDomainSpecific->setEnabled (enable); } void KCookiesPolicies::setPolicy (const QString& domain) { QTreeWidgetItemIterator it (mUi.policyTreeWidget); bool hasExistingPolicy = false; while (*it) { if ((*it)->text(0) == domain) { hasExistingPolicy = true; break; } ++it; } if (hasExistingPolicy) { changePressed((*it), false); } else { addPressed(domain); } } void KCookiesPolicies::changePressed() { changePressed(mUi.policyTreeWidget->currentItem()); } void KCookiesPolicies::addPressed() { addPressed(QString()); } void KCookiesPolicies::changePressed(QTreeWidgetItem* item, bool state) { Q_ASSERT(item); const QString oldDomain(item->text (0)); KCookiesPolicySelectionDlg pdlg (this); pdlg.setWindowTitle (i18nc ("@title:window", "Change Cookie Policy")); - pdlg.setPolicy (KCookieAdvice::strToAdvice (mDomainPolicyMap.value(oldDomain))); + pdlg.setPolicy (KCookieAdvice::strToAdvice(QString::fromLatin1(mDomainPolicyMap.value(oldDomain)))); pdlg.setEnableHostEdit (state, oldDomain); if (pdlg.exec() && !pdlg.domain().isEmpty()) { const QString newDomain = tolerantFromAce (pdlg.domain().toLatin1()); int advice = pdlg.advice(); if (newDomain == oldDomain || !handleDuplicate (newDomain, advice)) { mDomainPolicyMap[newDomain] = KCookieAdvice::adviceToStr(advice); item->setText(0, newDomain); item->setText(1, i18n (mDomainPolicyMap.value(newDomain))); configChanged(); } } } void KCookiesPolicies::addPressed(const QString& domain, bool state) { KCookiesPolicySelectionDlg pdlg (this); pdlg.setWindowTitle (i18nc ("@title:window", "New Cookie Policy")); pdlg.setEnableHostEdit(state, domain); if (mUi.rbPolicyAccept->isChecked()) pdlg.setPolicy (KCookieAdvice::Reject); else pdlg.setPolicy (KCookieAdvice::Accept); if (pdlg.exec() && !pdlg.domain().isEmpty()) { const QString domain = tolerantFromAce (pdlg.domain().toLatin1()); int advice = pdlg.advice(); if (!handleDuplicate (domain, advice)) { const char* strAdvice = KCookieAdvice::adviceToStr (advice); const QStringList items { domain, i18n(strAdvice), }; QTreeWidgetItem* item = new QTreeWidgetItem(mUi.policyTreeWidget, items); mDomainPolicyMap.insert (item->text(0), strAdvice); configChanged(); updateButtons(); } } } bool KCookiesPolicies::handleDuplicate (const QString& domain, int advice) { QTreeWidgetItem* item = mUi.policyTreeWidget->topLevelItem (0); while (item != nullptr) { if (item->text (0) == domain) { const int res = KMessageBox::warningContinueCancel (this, i18n ("A policy already exists for" "
%1
" "Do you want to replace it?
", domain), i18nc ("@title:window", "Duplicate Policy"), KGuiItem (i18n ("Replace"))); if (res == KMessageBox::Continue) { mDomainPolicyMap[domain] = KCookieAdvice::adviceToStr(advice); item->setText (0, domain); item->setText (1, i18n (mDomainPolicyMap.value(domain))); configChanged(); return true; } else return true; // User Cancelled!! } item = mUi.policyTreeWidget->itemBelow (item); } return false; } void KCookiesPolicies::deletePressed() { QTreeWidgetItem* nextItem = nullptr; Q_FOREACH (QTreeWidgetItem * item, mUi.policyTreeWidget->selectedItems()) { nextItem = mUi.policyTreeWidget->itemBelow (item); if (!nextItem) nextItem = mUi.policyTreeWidget->itemAbove (item); mDomainPolicyMap.remove (item->text(0)); delete item; } if (nextItem) nextItem->setSelected (true); updateButtons(); configChanged(); } void KCookiesPolicies::deleteAllPressed() { mDomainPolicyMap.clear(); mUi.policyTreeWidget->clear(); updateButtons(); configChanged(); } void KCookiesPolicies::updateButtons() { bool hasItems = mUi.policyTreeWidget->topLevelItemCount() > 0; mUi.pbChange->setEnabled((hasItems && mSelectedItemsCount == 1)); mUi.pbDelete->setEnabled((hasItems && mSelectedItemsCount > 0)); mUi.pbDeleteAll->setEnabled(hasItems); } void KCookiesPolicies::updateDomainList (const QStringList& domainConfig) { mUi.policyTreeWidget->clear(); QStringList::ConstIterator it = domainConfig.begin(); for (; it != domainConfig.end(); ++it) { QString domain; KCookieAdvice::Value advice = KCookieAdvice::Dunno; splitDomainAdvice (*it, domain, advice); if (!domain.isEmpty()) { const QStringList items { tolerantFromAce(domain.toLatin1()), i18n(KCookieAdvice::adviceToStr(advice)), }; QTreeWidgetItem* item = new QTreeWidgetItem (mUi.policyTreeWidget, items); mDomainPolicyMap[item->text(0)] = KCookieAdvice::adviceToStr(advice); } } mUi.policyTreeWidget->sortItems(0, Qt::AscendingOrder); } void KCookiesPolicies::selectionChanged () { mSelectedItemsCount = mUi.policyTreeWidget->selectedItems().count(); updateButtons (); } void KCookiesPolicies::load() { mSelectedItemsCount = 0; KConfig cfg (QStringLiteral("kcookiejarrc")); KConfigGroup group = cfg.group ("Cookie Policy"); bool enableCookies = group.readEntry ("Cookies", true); mUi.cbEnableCookies->setChecked (enableCookies); cookiesEnabled (enableCookies); // Warning: the default values are duplicated in kcookiejar.cpp KCookieAdvice::Value advice = KCookieAdvice::strToAdvice (group.readEntry ( "CookieGlobalAdvice", "Accept")); switch (advice) { case KCookieAdvice::Accept: mUi.rbPolicyAccept->setChecked (true); break; case KCookieAdvice::AcceptForSession: mUi.rbPolicyAcceptForSession->setChecked (true); break; case KCookieAdvice::Reject: mUi.rbPolicyReject->setChecked (true); break; case KCookieAdvice::Ask: case KCookieAdvice::Dunno: default: mUi.rbPolicyAsk->setChecked (true); } bool enable = group.readEntry ("RejectCrossDomainCookies", true); mUi.cbRejectCrossDomainCookies->setChecked (enable); bool sessionCookies = group.readEntry ("AcceptSessionCookies", true); mUi.cbAutoAcceptSessionCookies->setChecked (sessionCookies); updateDomainList (group.readEntry ("CookieDomainAdvice", QStringList())); if (enableCookies) { updateButtons(); } } void KCookiesPolicies::save() { KConfig cfg (QStringLiteral("kcookiejarrc")); KConfigGroup group = cfg.group ("Cookie Policy"); bool state = mUi.cbEnableCookies->isChecked(); group.writeEntry ("Cookies", state); state = mUi.cbRejectCrossDomainCookies->isChecked(); group.writeEntry ("RejectCrossDomainCookies", state); state = mUi.cbAutoAcceptSessionCookies->isChecked(); group.writeEntry ("AcceptSessionCookies", state); QString advice; - if (mUi.rbPolicyAccept->isChecked()) - advice = KCookieAdvice::adviceToStr (KCookieAdvice::Accept); - else if (mUi.rbPolicyAcceptForSession->isChecked()) - advice = KCookieAdvice::adviceToStr (KCookieAdvice::AcceptForSession); - else if (mUi.rbPolicyReject->isChecked()) - advice = KCookieAdvice::adviceToStr (KCookieAdvice::Reject); - else - advice = KCookieAdvice::adviceToStr (KCookieAdvice::Ask); + if (mUi.rbPolicyAccept->isChecked()) { + advice = QString::fromLatin1(KCookieAdvice::adviceToStr(KCookieAdvice::Accept)); + } else if (mUi.rbPolicyAcceptForSession->isChecked()) { + advice = QString::fromLatin1(KCookieAdvice::adviceToStr(KCookieAdvice::AcceptForSession)); + } else if (mUi.rbPolicyReject->isChecked()) { + advice = QString::fromLatin1(KCookieAdvice::adviceToStr(KCookieAdvice::Reject)); + } else { + advice = QString::fromLatin1(KCookieAdvice::adviceToStr(KCookieAdvice::Ask)); + } group.writeEntry ("CookieGlobalAdvice", advice); QStringList domainConfig; QMapIterator it (mDomainPolicyMap); while (it.hasNext()) { it.next(); - const QString policy = tolerantToAce(it.key()) + QLatin1Char(':') + QLatin1String(it.value()); + const QString policy = QLatin1String(tolerantToAce(it.key())) + QLatin1Char(':') + QLatin1String(it.value()); domainConfig << policy; } group.writeEntry ("CookieDomainAdvice", domainConfig); group.sync(); // Update the cookiejar... if (!mUi.cbEnableCookies->isChecked()) { QDBusInterface kded (QStringLiteral("org.kde.kcookiejar5"), QStringLiteral("/modules/kcookiejar"), QStringLiteral("org.kde.KCookieServer"), QDBusConnection::sessionBus()); kded.call (QStringLiteral("shutdown")); } else { QDBusInterface kded (QStringLiteral("org.kde.kcookiejar5"), QStringLiteral("/modules/kcookiejar"), QStringLiteral("org.kde.KCookieServer"), QDBusConnection::sessionBus()); QDBusReply reply = kded.call (QStringLiteral("reloadPolicy")); if (!reply.isValid()) KMessageBox::sorry (nullptr, i18n ("Unable to communicate with the cookie handler service.\n" "Any changes you made will not take effect until the service " "is restarted.")); } // Force running io-slave to reload configurations... KSaveIOConfig::updateRunningIOSlaves (this); emit changed (false); } void KCookiesPolicies::defaults() { mUi.cbEnableCookies->setChecked (true); mUi.rbPolicyAsk->setChecked (true); mUi.rbPolicyAccept->setChecked (false); mUi.rbPolicyAcceptForSession->setChecked (false); mUi.rbPolicyReject->setChecked (false); mUi.cbRejectCrossDomainCookies->setChecked (true); mUi.cbAutoAcceptSessionCookies->setChecked (false); mUi.policyTreeWidget->clear(); mDomainPolicyMap.clear(); cookiesEnabled (mUi.cbEnableCookies->isChecked()); updateButtons(); } void KCookiesPolicies::splitDomainAdvice (const QString& cfg, QString& domain, KCookieAdvice::Value& advice) { int sepPos = cfg.lastIndexOf(QLatin1Char(':')); // Ignore any policy that does not contain a domain... if (sepPos <= 0) return; domain = cfg.left (sepPos); advice = KCookieAdvice::strToAdvice (cfg.mid (sepPos + 1)); } QString KCookiesPolicies::quickHelp() const { return i18n ("

Cookies

Cookies contain information that KDE" " application using the HTTP protocol (like Konqueror) stores" " on your computer from a remote Internet server. This means" " that a web server can store information about you and your" " browsing activities on your machine for later use. You might" " consider this an invasion of privacy.

However, cookies are" " useful in certain situations. For example, they are often used" " by Internet shops, so you can 'put things into a shopping" " basket'. Some sites require you have a browser that supports" " cookies.

Because most people want a compromise between privacy" " and the benefits cookies offer, KDE offers you the ability to" " customize the way it handles cookies. You might, for example" " want to set KDE's default policy to ask you whenever a server" " wants to set a cookie or simply reject or accept everything." " For example, you might choose to accept all cookies from your" " favorite shopping web site. For this all you have to do is" " either browse to that particular site and when you are presented" " with the cookie dialog box, click on This domain under" " the 'apply to' tab and choose accept or simply specify the name" " of the site in the Domain Specific Policy tab and set" " it to accept. This enables you to receive cookies from trusted" " web sites without being asked every time KDE receives a cookie.

" ); } diff --git a/src/kcms/kio/smbrodlg.cpp b/src/kcms/kio/smbrodlg.cpp index 22e20090..987659e4 100644 --- a/src/kcms/kio/smbrodlg.cpp +++ b/src/kcms/kio/smbrodlg.cpp @@ -1,185 +1,182 @@ /* This file is part of the KDE project Copyright (C) 2000 Alexander Neundorf 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. */ -// TODO: remove me -#undef QT_NO_CAST_FROM_ASCII - // Own #include "smbrodlg.h" // Qt #include #include #include // KDE #include #include #include #include #include //#include #include K_PLUGIN_FACTORY_DECLARATION(KioConfigFactory) SMBRoOptions::SMBRoOptions(QWidget *parent, const QVariantList &) : KCModule(parent) { QGridLayout *layout = new QGridLayout(this ); QLabel *label=new QLabel(i18n("These settings apply to network browsing only."),this); layout->addWidget(label,0,0, 1, 2 ); m_userLe=new QLineEdit(this); label=new QLabel(i18n("Default user name:"),this); label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); label->setBuddy( m_userLe ); layout->addWidget(label,1,0); layout->addWidget(m_userLe,1,1); m_passwordLe=new QLineEdit(this); m_passwordLe->setEchoMode(QLineEdit::Password); label=new QLabel(i18n("Default password:"),this); label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); label->setBuddy( m_passwordLe ); layout->addWidget(label,2,0); layout->addWidget(m_passwordLe,2,1); /* m_workgroupLe=new QLineEdit(this); label=new QLabel(m_workgroupLe,i18n("Workgroup:"),this); layout->addWidget(label,3,0); layout->addWidget(m_workgroupLe,3,1); m_showHiddenShares=new QCheckBox(i18n("Show hidden shares"),this); layout->addWidget(m_showHiddenShares,4,0, 1, 2 ); m_encodingList = new KComboBox( false, this ); QStringList _strList = KCharsets::charsets()->availableEncodingNames(); m_encodingList->addItems( _strList ); label = new QLabel( i18n( "MS Windows encoding:" ), this ); label->setBuddy( m_encodingList ); layout->addWidget( label, 3, 0 ); layout->addWidget( m_encodingList, 3, 1 ); */ layout->addWidget(new QWidget(this),4,0); // connect(m_showHiddenShares, SIGNAL(toggled(bool)), this, SLOT(changed())); connect(m_userLe, &QLineEdit::textChanged, this, &SMBRoOptions::changed); connect(m_passwordLe, &QLineEdit::textChanged, this, &SMBRoOptions::changed); // connect(m_workgroupLe, SIGNAL(textChanged(QString)), this, SLOT(changed())); // connect( m_encodingList, SIGNAL(activated(QString)), this , SLOT(changed()) ); layout->setRowStretch(4, 1); } SMBRoOptions::~SMBRoOptions() { } void SMBRoOptions::load() { KConfig *cfg = new KConfig(QStringLiteral("kioslaverc")); KConfigGroup group = cfg->group("Browser Settings/SMBro" ); m_userLe->setText(group.readEntry("User")); // m_workgroupLe->setText(group.readEntry("Workgroup")); // m_showHiddenShares->setChecked(group.readEntry("ShowHiddenShares", QVariant(false)).toBool()); //QStringList _strList = KCharsets::charsets()->availableEncodingNames(); //QString m_encoding = QTextCodec::codecForLocale()->name(); //m_encodingList->setCurrentIndex( _strList.indexOf( group.readEntry( "Encoding", m_encoding.toLower() ) ) ); // unscramble QString scrambled = group.readEntry( "Password" ); QString password; const int passwordLength = scrambled.length() / 3; password.reserve(passwordLength); for (int i=0; isetText(password); delete cfg; } void SMBRoOptions::save() { KConfig *cfg = new KConfig(QStringLiteral("kioslaverc")); KConfigGroup group = cfg->group("Browser Settings/SMBro" ); group.writeEntry( "User", m_userLe->text()); // group.writeEntry( "Workgroup", m_workgroupLe->text()); // group.writeEntry( "ShowHiddenShares", m_showHiddenShares->isChecked()); // group.writeEntry( "Encoding", m_encodingList->currentText() ); //taken from Nicola Brodu's smb ioslave //it's not really secure, but at //least better than storing the plain password QString password(m_passwordLe->text()); QString scrambled; for (const QChar c : qAsConst(password)) { unsigned int num = (c.unicode() ^ 173) + 17; unsigned int a1 = (num & 0xFC00) >> 10; unsigned int a2 = (num & 0x3E0) >> 5; unsigned int a3 = (num & 0x1F); - scrambled += (char)(a1+'0'); - scrambled += (char)(a2+'A'); - scrambled += (char)(a3+'0'); + scrambled += QLatin1Char((char)(a1+'0')); + scrambled += QLatin1Char((char)(a2+'A')); + scrambled += QLatin1Char((char)(a3+'0')); } group.writeEntry( "Password", scrambled); delete cfg; } void SMBRoOptions::defaults() { m_userLe->setText(QString()); m_passwordLe->setText(QString()); // m_workgroupLe->setText(""); // m_showHiddenShares->setChecked(false); } void SMBRoOptions::changed() { emit KCModule::changed(true); } QString SMBRoOptions::quickHelp() const { return i18n("

Windows Shares

Applications using the " "SMB kioslave (like Konqueror) are able to access shared Microsoft " "Windows file systems, if properly configured.

You can specify " "here the credentials used to access the shared resources. " "Passwords will be stored locally, and scrambled so as to render them " "unreadable to the human eye. For security reasons, you may not want to " "do that, as entries with passwords are clearly indicated as such.

"); } diff --git a/src/kioexec/main.cpp b/src/kioexec/main.cpp index 548c1908..4d98e1e5 100644 --- a/src/kioexec/main.cpp +++ b/src/kioexec/main.cpp @@ -1,318 +1,315 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2005 David Faure Copyright (C) 2001 Waldo Bastian This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// TODO: remove me -#undef QT_NO_CAST_FROM_ASCII - #include "main.h" #include "kio_version.h" #include "kioexecdinterface.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_X11 #include #endif static const char description[] = I18N_NOOP("KIO Exec - Opens remote files, watches modifications, asks for upload"); KIOExec::KIOExec(const QStringList &args, bool tempFiles, const QString &suggestedFileName) : mExited(false) , mTempFiles(tempFiles) , mUseDaemon(false) , mSuggestedFileName(suggestedFileName) , expectedCounter(0) , command(args.first()) , jobCounter(0) { qDebug() << "command=" << command << "args=" << args; for (int i = 1; i < args.count(); i++) { const QUrl urlArg = QUrl::fromUserInput(args.value(i)); if (!urlArg.isValid()) { KMessageBox::error(nullptr, i18n("Invalid URL: %1", args.value(i))); exit(1); } KIO::StatJob* mostlocal = KIO::mostLocalUrl(urlArg); bool b = mostlocal->exec(); if (!b) { KMessageBox::error(nullptr, i18n("File not found: %1", urlArg.toDisplayString())); exit(1); } Q_ASSERT(b); const QUrl url = mostlocal->mostLocalUrl(); //kDebug() << "url=" << url.url() << " filename=" << url.fileName(); // A local file, not an URL ? // => It is not encoded and not shell escaped, too. if (url.isLocalFile()) { FileInfo file; file.path = url.toLocalFile(); file.url = url; fileList.append(file); } else { // It is an URL if (!url.isValid()) { KMessageBox::error(nullptr, i18n("The URL %1\nis malformed" , url.url())); } else if (mTempFiles) { KMessageBox::error(nullptr, i18n("Remote URL %1\nnot allowed with --tempfiles switch" , url.toDisplayString())); } else { // We must fetch the file QString fileName = KIO::encodeFileName(url.fileName()); if (!suggestedFileName.isEmpty()) fileName = suggestedFileName; if (fileName.isEmpty()) fileName = QStringLiteral("unnamed"); // Build the destination filename, in ~/.cache/kioexec/krun/ // Unlike KDE-1.1, we put the filename at the end so that the extension is kept // (Some programs rely on it) QString krun_writable = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QStringLiteral("/krun/%1_%2/").arg(QCoreApplication::applicationPid()).arg(jobCounter++); QDir().mkpath(krun_writable); // error handling will be done by the job QString tmp = krun_writable + fileName; FileInfo file; file.path = tmp; file.url = url; fileList.append(file); expectedCounter++; const QUrl dest = QUrl::fromLocalFile(tmp); qDebug() << "Copying" << url << " to" << dest; KIO::Job *job = KIO::file_copy(url, dest); jobList.append(job); connect(job, &KJob::result, this, &KIOExec::slotResult); } } } if (mTempFiles) { slotRunApp(); return; } counter = 0; if (counter == expectedCounter) { slotResult(nullptr); } } void KIOExec::slotResult(KJob *job) { if (job) { KIO::FileCopyJob *copyJob = static_cast(job); const QString path = copyJob->destUrl().path(); if (job->error()) { // That error dialog would be queued, i.e. not immediate... //job->showErrorDialog(); if ((job->error() != KIO::ERR_USER_CANCELED)) KMessageBox::error(nullptr, job->errorString()); auto it = std::find_if(fileList.begin(), fileList.end(), [&path](const FileInfo &i) { return (i.path == path); }); if (it != fileList.end()) { fileList.erase(it); } else { qDebug() << path << " not found in list"; } } else { // Tell kioexecd to watch the file for changes. const QString dest = copyJob->srcUrl().toString(); qDebug() << "Telling kioexecd to watch path" << path << "dest" << dest; OrgKdeKIOExecdInterface kioexecd(QStringLiteral("org.kde.kioexecd"), QStringLiteral("/modules/kioexecd"), QDBusConnection::sessionBus()); kioexecd.watch(path, dest); mUseDaemon = !kioexecd.lastError().isValid(); if (!mUseDaemon) { qDebug() << "Not using kioexecd"; } } } counter++; if (counter < expectedCounter) { return; } qDebug() << "All files downloaded, will call slotRunApp shortly"; // We know we can run the app now - but let's finish the job properly first. QTimer::singleShot(0, this, &KIOExec::slotRunApp); jobList.clear(); } void KIOExec::slotRunApp() { if (fileList.isEmpty()) { qDebug() << "No files downloaded -> exiting"; mExited = true; QApplication::exit(1); return; } KService service(QStringLiteral("dummy"), command, QString()); QList list; list.reserve(fileList.size()); // Store modification times QList::Iterator it = fileList.begin(); for (; it != fileList.end() ; ++it) { - QFileInfo info(QFile::encodeName(it->path)); + QFileInfo info(it->path); it->time = info.lastModified(); QUrl url = QUrl::fromLocalFile(it->path); list << url; } KIO::DesktopExecParser execParser(service, list); QStringList params = execParser.resultingArguments(); qDebug() << "EXEC" << params.join(QLatin1Char(' ')); // propagate the startup identification to the started process KStartupInfoId id; QByteArray startupId; #if HAVE_X11 if (QX11Info::isPlatformX11()) { startupId = QX11Info::nextStartupId(); } #endif id.initId(startupId); id.setupStartupEnv(); QString exe(params.takeFirst()); const int exit_code = QProcess::execute(exe, params); KStartupInfo::resetStartupEnv(); qDebug() << "EXEC done"; // Test whether one of the files changed for (it = fileList.begin(); it != fileList.end(); ++it) { QString src = it->path; const QUrl dest = it->url; QFileInfo info(src); const bool uploadChanges = !mUseDaemon && !dest.isLocalFile(); if (info.exists() && (it->time != info.lastModified())) { if (mTempFiles) { if (KMessageBox::questionYesNo(nullptr, i18n("The supposedly temporary file\n%1\nhas been modified.\nDo you still want to delete it?", dest.toDisplayString(QUrl::PreferLocalFile)), i18n("File Changed"), KStandardGuiItem::del(), KGuiItem(i18n("Do Not Delete"))) != KMessageBox::Yes) continue; // don't delete the temp file } else if (uploadChanges) { // no upload when it's already a local file or kioexecd already did it. if (KMessageBox::questionYesNo(nullptr, i18n("The file\n%1\nhas been modified.\nDo you want to upload the changes?" , dest.toDisplayString()), i18n("File Changed"), KGuiItem(i18n("Upload")), KGuiItem(i18n("Do Not Upload"))) == KMessageBox::Yes) { qDebug() << "src='" << src << "' dest='" << dest << "'"; // Do it the synchronous way. KIO::CopyJob* job = KIO::copy(QUrl::fromLocalFile(src), dest); if (!job->exec()) { KMessageBox::error(nullptr, job->errorText()); continue; // don't delete the temp file } } } } if ((uploadChanges || mTempFiles) && exit_code == 0) { // Wait for a reasonable time so that even if the application forks on startup (like OOo or amarok) // it will have time to start up and read the file before it gets deleted. #130709. const int sleepSecs = 180; qDebug() << "sleeping for" << sleepSecs << "seconds before deleting file..."; QThread::currentThread()->sleep(sleepSecs); const QString parentDir = info.path(); qDebug() << sleepSecs << "seconds have passed, deleting" << info.filePath(); - QFile(QFile::encodeName(src)).remove(); + QFile(src).remove(); // NOTE: this is not necessarily a temporary directory. if (QDir().rmdir(parentDir)) { qDebug() << "Removed empty parent directory" << parentDir; } } } mExited = true; QApplication::exit(exit_code); } int main(int argc, char **argv) { QApplication app(argc, argv); KAboutData aboutData(QStringLiteral("kioexec"), i18n("KIOExec"), QStringLiteral(KIO_VERSION_STRING), i18n(description), KAboutLicense::GPL, i18n("(c) 1998-2000,2003 The KFM/Konqueror Developers")); aboutData.addAuthor(i18n("David Faure"), QString(), QStringLiteral("faure@kde.org")); aboutData.addAuthor(i18n("Stephan Kulow"), QString(), QStringLiteral("coolo@kde.org")); aboutData.addAuthor(i18n("Bernhard Rosenkraenzer"), QString(), QStringLiteral("bero@arklinux.org")); aboutData.addAuthor(i18n("Waldo Bastian"), QString(), QStringLiteral("bastian@kde.org")); aboutData.addAuthor(i18n("Oswald Buddenhagen"), QString(), QStringLiteral("ossi@kde.org")); KAboutData::setApplicationData(aboutData); KDBusService service(KDBusService::Multiple); QCommandLineParser parser; parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("tempfiles") , i18n("Treat URLs as local files and delete them afterwards"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("suggestedfilename"), i18n("Suggested file name for the downloaded file"), QStringLiteral("filename"))); parser.addPositionalArgument(QStringLiteral("command"), i18n("Command to execute")); parser.addPositionalArgument(QStringLiteral("urls"), i18n("URL(s) or local file(s) used for 'command'")); app.setQuitOnLastWindowClosed(false); aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); if (parser.positionalArguments().count() < 1) { parser.showHelp(-1); return -1; } const bool tempfiles = parser.isSet(QStringLiteral("tempfiles")); const QString suggestedfilename = parser.value(QStringLiteral("suggestedfilename")); KIOExec exec(parser.positionalArguments(), tempfiles, suggestedfilename); // Don't go into the event loop if we already want to exit (#172197) if (exec.exited()) { return 0; } return app.exec(); } diff --git a/src/kpasswdserver/autotests/CMakeLists.txt b/src/kpasswdserver/autotests/CMakeLists.txt index db5438a2..3c7e6374 100644 --- a/src/kpasswdserver/autotests/CMakeLists.txt +++ b/src/kpasswdserver/autotests/CMakeLists.txt @@ -1,16 +1,13 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_BINARY_DIR}/.. ) -remove_definitions(-DQT_NO_CAST_FROM_ASCII) -remove_definitions(-DQT_NO_CAST_FROM_BYTEARRAY) - set (kpasswdserver_test_SRCS kpasswdservertest.cpp ../kpasswdserver.cpp ) qt5_add_dbus_adaptor(kpasswdserver_test_SRCS ../../core/org.kde.KPasswdServer.xml kpasswdserver.h KPasswdServer) include(ECMAddTests) ecm_add_test(${kpasswdserver_test_SRCS} TEST_NAME kpasswdservertest LINK_LIBRARIES KF5::DBusAddons KF5::KIOCore KF5::WidgetsAddons KF5::WindowSystem KF5::I18n Qt5::Core Qt5::Test ${WALLET_LIB}) diff --git a/src/kpasswdserver/autotests/kpasswdservertest.cpp b/src/kpasswdserver/autotests/kpasswdservertest.cpp index 32a0df2d..fcca89a4 100644 --- a/src/kpasswdserver/autotests/kpasswdservertest.cpp +++ b/src/kpasswdserver/autotests/kpasswdservertest.cpp @@ -1,566 +1,566 @@ /* This file is part of the KDE project Copyright 2010 David Faure Copyright 2012 Dawit Alemayehu This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Boston, MA 02110-1301, USA. */ #include #include #include #include #include static const char* sigQueryAuthInfoResult = SIGNAL(queryAuthInfoAsyncResult(qlonglong,qlonglong,KIO::AuthInfo)); static const char* sigCheckAuthInfoResult = SIGNAL(checkAuthInfoAsyncResult(qlonglong,qlonglong,KIO::AuthInfo)); // For the retry dialog (and only that one) static QDialogButtonBox::StandardButton s_buttonYes = QDialogButtonBox::Yes; static QDialogButtonBox::StandardButton s_buttonCancel = QDialogButtonBox::Cancel; Q_DECLARE_METATYPE(QDialogButtonBox::StandardButton) Q_DECLARE_METATYPE(QDialog::DialogCode) static QString getUserNameFrom(const KIO::AuthInfo& auth) { if (auth.username.isEmpty() && !auth.url.userName().isEmpty()) { return auth.url.userName(); } return auth.username; } class KPasswdServerTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase() { qRegisterMetaType(); qRegisterMetaType(); } void simpleTest() { KPasswdServer server(this); server.setWalletDisabled(true); // Check that processRequest doesn't crash when it has nothing to do server.processRequest(); KIO::AuthInfo info; info.url = QUrl(QStringLiteral("http://www.example.com")); // Make a check for that host, should say "not found" QVERIFY(noCheckAuth(server, info)); // Now add auth to the cache const qlonglong windowId = 42; KIO::AuthInfo realInfo = info; realInfo.username = QStringLiteral("toto"); // you can see I'm french realInfo.password = QStringLiteral("foobar"); server.addAuthInfo(realInfo, windowId); // seqnr=2 // queryAuth without the ability to prompt, will just return info unmodified KIO::AuthInfo resultInfo; queryAuth(server, info, resultInfo); QCOMPARE(resultInfo.url, info.url); QCOMPARE(resultInfo.username, QString()); QCOMPARE(resultInfo.password, QString()); QCOMPARE(resultInfo.isModified(), false); // Check that checkAuth finds it QVERIFY(successCheckAuth(server, info, realInfo)); // Now remove auth server.removeAuthInfo(info.url.host(), info.url.scheme(), info.username); // Check we can't find that auth anymore QVERIFY(noCheckAuth(server, info)); } void testCheckDuringQuery() { KPasswdServer server(this); server.setWalletDisabled(true); KIO::AuthInfo info; info.url = QUrl(QStringLiteral("http://www.kde.org")); // Start a query QSignalSpy spyQuery(&server, sigQueryAuthInfoResult); const qlonglong windowId = 42; const qlonglong seqNr = 2; const qlonglong id = server.queryAuthInfoAsync( info, QStringLiteral(""), // magic string to avoid a dialog windowId, seqNr, 16 /*usertime*/); // Before it is processed, do a check, it will reply delayed. QSignalSpy spyCheck(&server, sigCheckAuthInfoResult); const qlonglong idCheck = server.checkAuthInfoAsync(info, windowId, 17 /*usertime*/); QCOMPARE(idCheck, 0LL); // always QCOMPARE(spyCheck.count(), 0); // no reply yet // Wait for the query to be processed QVERIFY(QSignalSpy(&server, sigQueryAuthInfoResult).wait(1000)); QCOMPARE(spyQuery.count(), 1); QCOMPARE(spyQuery[0][0].toLongLong(), id); KIO::AuthInfo result = spyQuery[0][2].value(); // Now the check will have replied QCOMPARE(spyCheck.count(), 1); QCOMPARE(spyCheck[0][0].toLongLong(), id+1); // it was the next request after the query KIO::AuthInfo resultCheck = spyCheck[0][2].value(); QCOMPARE(result.username, resultCheck.username); QCOMPARE(result.password, resultCheck.password); } void testExpiry() { KPasswdServer server(this); server.setWalletDisabled(true); KIO::AuthInfo info; info.url = QUrl(QStringLiteral("http://www.example.com")); // Add auth to the cache const qlonglong windowId = 42; KIO::AuthInfo realInfo = info; realInfo.username = QStringLiteral("toto"); realInfo.password = QStringLiteral("foobar"); server.addAuthInfo(realInfo, windowId); QVERIFY(successCheckAuth(server, info, realInfo)); // Close another window, shouldn't hurt server.removeAuthForWindowId(windowId + 1); QVERIFY(successCheckAuth(server, info, realInfo)); // Close window server.removeAuthForWindowId(windowId); // Check we can't find that auth anymore QVERIFY(noCheckAuth(server, info)); } void testFillDialog() { KPasswdServer server(this); server.setWalletDisabled(true); // What the app would ask KIO::AuthInfo info; info.url = QUrl(QStringLiteral("http://www.example.com")); // What the user would type KIO::AuthInfo filledInfo(info); filledInfo.username = QStringLiteral("dfaure"); filledInfo.password = QStringLiteral("toto"); KIO::AuthInfo result; queryAuthWithDialog(server, info, filledInfo, result); } void testRejectRetryDialog() { KPasswdServer server(this); server.setWalletDisabled(true); // What the app would ask KIO::AuthInfo info; info.url = QUrl(QStringLiteral("http://www.example.com")); // What the user would type KIO::AuthInfo filledInfo(info); filledInfo.username = QStringLiteral("username"); filledInfo.password = QStringLiteral("password"); KIO::AuthInfo result; queryAuthWithDialog(server, info, filledInfo, result); // Pretend that the returned credentials failed and initiate a retry, // but cancel the retry dialog. info.password.clear(); result = KIO::AuthInfo(); queryAuthWithDialog(server, info, filledInfo, result, s_buttonCancel, QDialog::Accepted /*unused*/, QStringLiteral("Invalid username or password")); } void testAcceptRetryDialog() { KPasswdServer server(this); server.setWalletDisabled(true); // What the app would ask KIO::AuthInfo info; info.url = QUrl(QStringLiteral("http://www.example.com")); // What the user would type KIO::AuthInfo filledInfo(info); filledInfo.username = QStringLiteral("username"); filledInfo.password = QStringLiteral("password"); KIO::AuthInfo result; queryAuthWithDialog(server, info, filledInfo, result); // Pretend that the returned credentials failed and initiate a retry, // but this time continue the retry. info.password.clear(); result = KIO::AuthInfo(); queryAuthWithDialog(server, info, filledInfo, result, s_buttonYes, QDialog::Accepted, QStringLiteral("Invalid username or password")); } void testUsernameMistmatch() { KPasswdServer server(this); server.setWalletDisabled(true); // What the app would ask. Note the username in the URL. KIO::AuthInfo info; info.url = QUrl(QStringLiteral("http://foo@www.example.com")); // What the user would type KIO::AuthInfo filledInfo(info); filledInfo.username = QStringLiteral("bar"); filledInfo.password = QStringLiteral("blah"); KIO::AuthInfo result; queryAuthWithDialog(server, info, filledInfo, result); // Check the returned url does not match the request url because of the // username mismatch between the request URL and the filled in one. QVERIFY(result.url != filledInfo.url); // Verify there is NO cached auth data if the request URL contains the // original user name (foo). QVERIFY(noCheckAuth(server, info)); // Verify there is a cached auth data if the request URL contains the // new user name (bar). filledInfo.url = QUrl(QStringLiteral("http://bar@www.example.com")); QVERIFY(successCheckAuth(server, filledInfo, result)); // Now the URL check should be valid too. QCOMPARE(result.url, filledInfo.url); } void testCancelPasswordDialog() { KPasswdServer server(this); server.setWalletDisabled(true); // What the app would ask. KIO::AuthInfo info; info.url = QUrl(QStringLiteral("http://www.example.com")); info.username = info.url.userName(); KIO::AuthInfo result; queryAuthWithDialog(server, info, KIO::AuthInfo(), result, QDialogButtonBox::NoButton, QDialog::Rejected); } void testVerifyPath() { KPasswdServer server(this); server.setWalletDisabled(true); // Add auth to the cache const qlonglong windowId = 42; KIO::AuthInfo authInfo; authInfo.url = QUrl(QStringLiteral("http://www.example.com/test/test.html")); authInfo.username = QStringLiteral("toto"); authInfo.password = QStringLiteral("foobar"); server.addAuthInfo(authInfo, windowId); KIO::AuthInfo queryAuthInfo; queryAuthInfo.url = QUrl(QStringLiteral("http://www.example.com/test/test2/test.html")); queryAuthInfo.verifyPath = true; KIO::AuthInfo expectedAuthInfo; expectedAuthInfo.username = QStringLiteral("toto"); expectedAuthInfo.password = QStringLiteral("foobar"); QVERIFY(successCheckAuth(server, queryAuthInfo, expectedAuthInfo)); } void testConcurrentQueryAuth() { KPasswdServer server(this); server.setWalletDisabled(true); QList authInfos; for (int i=0; i < 10; ++i) { KIO::AuthInfo info; - info.url = QUrl("http://www.example.com/test" + QString::number(i) + ".html"); + info.url = QUrl(QLatin1String("http://www.example.com/test") + QString::number(i) + QLatin1String(".html")); authInfos << info; } // What the user would type KIO::AuthInfo filledInfo; filledInfo.username = QStringLiteral("bar"); filledInfo.password = QStringLiteral("blah"); QList results; concurrentQueryAuthWithDialog(server, authInfos, filledInfo, results); } void testConcurrentCheckAuth() { KPasswdServer server(this); server.setWalletDisabled(true); QList authInfos; for (int i=0; i < 10; ++i) { KIO::AuthInfo info; - info.url = QUrl("http://www.example.com/test" + QString::number(i) + ".html"); + info.url = QUrl(QLatin1String("http://www.example.com/test") + QString::number(i) + QStringLiteral(".html")); authInfos << info; } // What the user would type KIO::AuthInfo filledInfo; filledInfo.username = QStringLiteral("bar"); filledInfo.password = QStringLiteral("blah"); QList results; concurrentQueryAuthWithDialog(server, authInfos, filledInfo, results); } private: // Checks that no auth is available for @p info bool noCheckAuth(KPasswdServer& server, const KIO::AuthInfo& info) { KIO::AuthInfo result; checkAuth(server, info, result); return (result.username == info.username) && (result.password == info.password) && !result.isModified(); } // Check that the auth is available and equal to @expectedInfo bool successCheckAuth(KPasswdServer& server, const KIO::AuthInfo& info, const KIO::AuthInfo& expectedInfo) { KIO::AuthInfo result; checkAuth(server, info, result); return (result.username == expectedInfo.username) && (result.password == expectedInfo.password) && result.isModified(); } void checkAuth(KPasswdServer& server, const KIO::AuthInfo& info, KIO::AuthInfo& result) { QSignalSpy spy(&server, sigCheckAuthInfoResult); const qlonglong windowId = 42; const qlonglong id = server.checkAuthInfoAsync(info, windowId, 17 /*usertime*/); QCOMPARE(id, 0LL); // always if (spy.isEmpty()) { QVERIFY(QSignalSpy(&server, sigCheckAuthInfoResult).wait(1000)); } QCOMPARE(spy.count(), 1); // kpasswdserver emits a requestId via dbus, we can't get that id here QVERIFY(spy[0][0].toLongLong() >= 0); //QCOMPARE(spy[0][1].toLongLong(), 3LL); // seqNr result = spy[0][2].value(); } void queryAuth(KPasswdServer& server, const KIO::AuthInfo& info, KIO::AuthInfo& result) { QSignalSpy spy(&server, sigQueryAuthInfoResult); const qlonglong windowId = 42; const qlonglong seqNr = 2; const qlonglong id = server.queryAuthInfoAsync( info, QStringLiteral(""), // magic string to avoid a dialog windowId, seqNr, 16 /*usertime*/); QVERIFY(id >= 0); // requestId, ever increasing if (spy.isEmpty()) QVERIFY(QSignalSpy(&server, sigQueryAuthInfoResult).wait(1000)); QCOMPARE(spy.count(), 1); QCOMPARE(spy[0][0].toLongLong(), id); //QCOMPARE(spy[0][1].toLongLong(), 3LL); // seqNr result = spy[0][2].value(); } void queryAuthWithDialog(KPasswdServer& server, const KIO::AuthInfo& info, const KIO::AuthInfo& filledInfo, KIO::AuthInfo& result, QDialogButtonBox::StandardButton retryButton = s_buttonYes, QDialog::DialogCode code = QDialog::Accepted, const QString& errMsg = QString()) { QSignalSpy spy(&server, sigQueryAuthInfoResult); const qlonglong windowId = 42; const qlonglong seqNr = 2; const qlonglong id = server.queryAuthInfoAsync( info, errMsg, windowId, seqNr, 16 /*usertime*/); QVERIFY(id >= 0); // requestId, ever increasing QVERIFY(spy.isEmpty()); const bool hasErrorMessage = (!errMsg.isEmpty()); const bool isCancelRetryDialogTest = (hasErrorMessage && retryButton == s_buttonCancel); if (hasErrorMessage) { // Retry dialog only knows Yes/No QMetaObject::invokeMethod(this, "checkRetryDialog", Qt::QueuedConnection, Q_ARG(QDialogButtonBox::StandardButton, retryButton)); } if (!isCancelRetryDialogTest) { QMetaObject::invokeMethod(this, "checkAndFillDialog", Qt::QueuedConnection, Q_ARG(KIO::AuthInfo, info), Q_ARG(KIO::AuthInfo, filledInfo), Q_ARG(QDialog::DialogCode, code )); } // Force KPasswdServer to process the request now, otherwise the checkAndFillDialog needs a timer too... server.processRequest(); if (spy.isEmpty()) QVERIFY(QSignalSpy(&server, sigQueryAuthInfoResult).wait(1000)); QCOMPARE(spy.count(), 1); QCOMPARE(spy[0][0].toLongLong(), id); //QCOMPARE(spy[0][1].toLongLong(), 3LL); // seqNr result = spy[0][2].value(); const QString username = (isCancelRetryDialogTest ? QString() : filledInfo.username); const QString password = (isCancelRetryDialogTest ? QString() : filledInfo.password); QCOMPARE(result.username, username); QCOMPARE(result.password, password); QCOMPARE(result.isModified(), retryButton == s_buttonYes && code == QDialog::Accepted); } void concurrentQueryAuthWithDialog(KPasswdServer& server, const QList& infos, const KIO::AuthInfo& filledInfo, QList& results, QDialog::DialogCode code = QDialog::Accepted) { QSignalSpy spy(&server, sigQueryAuthInfoResult); const qlonglong windowId = 42; qlonglong seqNr = 0; QList idList; for (const KIO::AuthInfo& info : infos) { const qlonglong id = server.queryAuthInfoAsync( info, QString(), windowId, seqNr, 16 /*usertime*/); QVERIFY(id >= 0); // requestId, ever increasing idList << id; } QVERIFY(spy.isEmpty()); QMetaObject::invokeMethod(this, "checkAndFillDialog", Qt::QueuedConnection, Q_ARG(KIO::AuthInfo, infos.first()), Q_ARG(KIO::AuthInfo,filledInfo), Q_ARG(QDialog::DialogCode, code)); // Force KPasswdServer to process the request now, otherwise the checkAndFillDialog needs a timer too... server.processRequest(); while (spy.count() < infos.count()) QVERIFY(QSignalSpy(&server, sigQueryAuthInfoResult).wait(1000)); QCOMPARE(spy.count(), infos.count()); for(int i = 0, count = spy.count(); i < count; ++i) { QCOMPARE(spy[i][0].toLongLong(), idList.at(i)); //QCOMPARE(spy[0][1].toLongLong(), 3LL); // seqNr KIO::AuthInfo result = spy[i][2].value(); QCOMPARE(result.username, filledInfo.username); QCOMPARE(result.password, filledInfo.password); QCOMPARE(result.isModified(), code == QDialog::Accepted); results << result; } } void concurrentCheckAuthWithDialog(KPasswdServer& server, const QList& infos, const KIO::AuthInfo& filledInfo, QList& results, QDialog::DialogCode code = QDialog::Accepted) { QSignalSpy spy(&server, sigQueryAuthInfoResult); const qlonglong windowId = 42; qlonglong seqNr = 0; QList idList; QListIterator it (infos); if (it.hasNext()) { const qlonglong id = server.queryAuthInfoAsync( it.next(), QString(), windowId, seqNr, 16 /*usertime*/); QVERIFY(id >= 0); // requestId, ever increasing idList << id; } while (it.hasNext()) { const qlonglong id = server.checkAuthInfoAsync(it.next(), windowId,16 /*usertime*/); QVERIFY(id >= 0); // requestId, ever increasing idList << id; } QVERIFY(spy.isEmpty()); QMetaObject::invokeMethod(this, "checkAndFillDialog", Qt::QueuedConnection, Q_ARG(KIO::AuthInfo, infos.first()), Q_ARG(KIO::AuthInfo,filledInfo), Q_ARG(QDialog::DialogCode, code)); // Force KPasswdServer to process the request now, otherwise the checkAndFillDialog needs a timer too... server.processRequest(); if (spy.isEmpty()) { QVERIFY(QSignalSpy(&server, sigQueryAuthInfoResult).wait(1000)); } while ((spy.count()-1) < infos.count()) { QVERIFY(QSignalSpy(&server, sigCheckAuthInfoResult).wait(1000)); } for(int i = 0, count = spy.count(); i < count; ++i) { QCOMPARE(spy[i][0].toLongLong(), idList.at(i)); //QCOMPARE(spy[0][1].toLongLong(), 3LL); // seqNr KIO::AuthInfo result = spy[i][2].value(); QCOMPARE(result.username, filledInfo.username); QCOMPARE(result.password, filledInfo.password); QCOMPARE(result.isModified(), code == QDialog::Accepted); results << result; } } protected Q_SLOTS: void checkAndFillDialog(const KIO::AuthInfo& info, const KIO::AuthInfo& filledInfo, QDialog::DialogCode code) { Q_FOREACH(QWidget *widget, QApplication::topLevelWidgets()) { if (KPasswordDialog* dialog = qobject_cast(widget)) { if (code == QDialog::Accepted) { QCOMPARE(dialog->username(), getUserNameFrom(info)); QCOMPARE(dialog->password(), info.password); dialog->setUsername(filledInfo.username); dialog->setPassword(filledInfo.password); } dialog->done(code); return; } } qWarning() << "No KPasswordDialog found!"; } void checkRetryDialog(QDialogButtonBox::StandardButton code = s_buttonYes) { Q_FOREACH(QWidget *widget, QApplication::topLevelWidgets()) { QDialog* dialog = qobject_cast(widget); if (dialog && !dialog->inherits("KPasswordDialog")) { dialog->done(code); return; } } qWarning() << "No retry dialog found"; } }; QTEST_MAIN( KPasswdServerTest ) #include "kpasswdservertest.moc"