diff --git a/src/core/connectionbackend.cpp b/src/core/connectionbackend.cpp index ba7c1225..d3f7d877 100644 --- a/src/core/connectionbackend.cpp +++ b/src/core/connectionbackend.cpp @@ -1,340 +1,340 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow David Faure 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 as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "connectionbackend_p.h" #include #include #include #include "klocalsocket.h" #include #include #include #include #include #include #include #include "kiocoredebug.h" using namespace KIO; ConnectionBackend::ConnectionBackend(Mode m, QObject *parent) : QObject(parent), state(Idle), socket(nullptr), len(-1), cmd(0), signalEmitted(false), mode(m) { localServer = nullptr; } ConnectionBackend::~ConnectionBackend() { if (mode == LocalSocketMode && localServer && localServer->localSocketType() == KLocalSocket::UnixSocket) { QFile::remove(localServer->localPath()); } } void ConnectionBackend::setSuspended(bool enable) { if (state != Connected) { return; } Q_ASSERT(socket); Q_ASSERT(!localServer); // !tcpServer as well if (enable) { //qCDebug(KIO_CORE) << socket << "suspending"; socket->setReadBufferSize(1); } else { //qCDebug(KIO_CORE) << socket << "resuming"; // Calling setReadBufferSize from a readyRead slot leads to a bug in Qt, fixed in 13c246ee119 socket->setReadBufferSize(StandardBufferSize); if (socket->bytesAvailable() >= HeaderSize) { // there are bytes available QMetaObject::invokeMethod(this, "socketReadyRead", Qt::QueuedConnection); } // We read all bytes here, but we don't use readAll() because we need // to read at least one byte (even if there isn't any) so that the // socket notifier is reenabled QByteArray data = socket->read(socket->bytesAvailable() + 1); for (int i = data.size(); --i >= 0;) { socket->ungetChar(data[i]); } // Workaround Qt5 bug, readyRead isn't always emitted here... QMetaObject::invokeMethod(this, "socketReadyRead", Qt::QueuedConnection); } } bool ConnectionBackend::connectToRemote(const QUrl &url) { Q_ASSERT(state == Idle); Q_ASSERT(!socket); Q_ASSERT(!localServer); // !tcpServer as well if (mode == LocalSocketMode) { KLocalSocket *sock = new KLocalSocket(this); QString path = url.path(); #if 0 // TODO: Activate once abstract socket support is implemented in Qt. KLocalSocket::LocalSocketType type = KLocalSocket::UnixSocket; if (url.queryItem(QLatin1String("abstract")) == QLatin1String("1")) { type = KLocalSocket::AbstractUnixSocket; } #endif sock->connectToPath(path); socket = sock; } else { socket = new QTcpSocket(this); socket->connectToHost(url.host(), url.port()); if (!socket->waitForConnected(1000)) { state = Idle; qCDebug(KIO_CORE) << "could not connect to" << url; return false; } } connect(socket, SIGNAL(readyRead()), SLOT(socketReadyRead())); connect(socket, SIGNAL(disconnected()), SLOT(socketDisconnected())); state = Connected; return true; } void ConnectionBackend::socketDisconnected() { state = Idle; emit disconnected(); } bool ConnectionBackend::listenForRemote() { Q_ASSERT(state == Idle); Q_ASSERT(!socket); Q_ASSERT(!localServer); // !tcpServer as well if (mode == LocalSocketMode) { const QString prefix = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); static QBasicAtomicInt s_socketCounter = Q_BASIC_ATOMIC_INITIALIZER(1); QString appName = QCoreApplication::instance()->applicationName(); - appName.replace('/', '_'); // #357499 + appName.replace(QLatin1Char('/'), QLatin1Char('_')); // #357499 QTemporaryFile socketfile(prefix + QLatin1Char('/') + appName + QStringLiteral("XXXXXX.%1.slave-socket").arg(s_socketCounter.fetchAndAddAcquire(1))); if (!socketfile.open()) { - errorString = i18n("Unable to create io-slave: %1", strerror(errno)); + errorString = i18n("Unable to create io-slave: %1", QString::fromUtf8(strerror(errno))); return false; } QString sockname = socketfile.fileName(); address.clear(); address.setScheme(QStringLiteral("local")); address.setPath(sockname); socketfile.setAutoRemove(false); socketfile.remove(); // can't bind if there is such a file localServer = new KLocalSocketServer(this); if (!localServer->listen(sockname, KLocalSocket::UnixSocket)) { errorString = localServer->errorString(); delete localServer; localServer = nullptr; return false; } connect(localServer, SIGNAL(newConnection()), SIGNAL(newConnection())); } else { tcpServer = new QTcpServer(this); tcpServer->listen(QHostAddress::LocalHost); if (!tcpServer->isListening()) { errorString = tcpServer->errorString(); delete tcpServer; tcpServer = nullptr; return false; } - address = QUrl("tcp://127.0.0.1:" + QString::number(tcpServer->serverPort())); + address = QUrl(QLatin1String("tcp://127.0.0.1:") + QString::number(tcpServer->serverPort())); connect(tcpServer, SIGNAL(newConnection()), SIGNAL(newConnection())); } state = Listening; return true; } bool ConnectionBackend::waitForIncomingTask(int ms) { Q_ASSERT(state == Connected); Q_ASSERT(socket); if (socket->state() != QAbstractSocket::ConnectedState) { state = Idle; return false; // socket has probably closed, what do we do? } signalEmitted = false; if (socket->bytesAvailable()) { socketReadyRead(); } if (signalEmitted) { return true; // there was enough data in the socket } // not enough data in the socket, so wait for more QElapsedTimer timer; timer.start(); while (socket->state() == QAbstractSocket::ConnectedState && !signalEmitted && (ms == -1 || timer.elapsed() < ms)) if (!socket->waitForReadyRead(ms == -1 ? -1 : ms - timer.elapsed())) { break; } if (signalEmitted) { return true; } if (socket->state() != QAbstractSocket::ConnectedState) { state = Idle; } return false; } bool ConnectionBackend::sendCommand(int cmd, const QByteArray &data) const { Q_ASSERT(state == Connected); Q_ASSERT(socket); char buffer[HeaderSize + 2]; sprintf(buffer, "%6x_%2x_", data.size(), cmd); socket->write(buffer, HeaderSize); socket->write(data); //qCDebug(KIO_CORE) << this << "Sending command" << hex << cmd << "of" // << data.size() << "bytes (" << socket->bytesToWrite() // << "bytes left to write )"; // blocking mode: while (socket->bytesToWrite() > 0 && socket->state() == QAbstractSocket::ConnectedState) { socket->waitForBytesWritten(-1); } return socket->state() == QAbstractSocket::ConnectedState; } ConnectionBackend *ConnectionBackend::nextPendingConnection() { Q_ASSERT(state == Listening); Q_ASSERT(localServer || tcpServer); Q_ASSERT(!socket); //qCDebug(KIO_CORE) << "Got a new connection"; QTcpSocket *newSocket; if (mode == LocalSocketMode) { newSocket = localServer->nextPendingConnection(); } else { newSocket = tcpServer->nextPendingConnection(); } if (!newSocket) { return nullptr; // there was no connection... } ConnectionBackend *result = new ConnectionBackend(Mode(mode)); result->state = Connected; result->socket = newSocket; newSocket->setParent(result); connect(newSocket, SIGNAL(readyRead()), result, SLOT(socketReadyRead())); connect(newSocket, SIGNAL(disconnected()), result, SLOT(socketDisconnected())); return result; } void ConnectionBackend::socketReadyRead() { bool shouldReadAnother; do { if (!socket) // might happen if the invokeMethods were delivered after we disconnected { return; } //qCDebug(KIO_CORE) << this << "Got" << socket->bytesAvailable() << "bytes"; if (len == -1) { // We have to read the header char buffer[HeaderSize]; if (socket->bytesAvailable() < HeaderSize) { return; // wait for more data } socket->read(buffer, sizeof buffer); buffer[6] = 0; buffer[9] = 0; char *p = buffer; while (*p == ' ') { p++; } len = strtol(p, nullptr, 16); p = buffer + 7; while (*p == ' ') { p++; } cmd = strtol(p, nullptr, 16); //qCDebug(KIO_CORE) << this << "Beginning of command" << hex << cmd << "of size" << len; } QPointer that = this; //qCDebug(KIO_CORE) << socket << "Want to read" << len << "bytes"; if (socket->bytesAvailable() >= len) { Task task; task.cmd = cmd; if (len) { task.data = socket->read(len); } len = -1; signalEmitted = true; emit commandReceived(task); } else if (len > StandardBufferSize) { qCDebug(KIO_CORE) << socket << "Jumbo packet of" << len << "bytes"; // Calling setReadBufferSize from a readyRead slot leads to a bug in Qt, fixed in 13c246ee119 socket->setReadBufferSize(len + 1); } // If we're dead, better don't try anything. if (that.isNull()) { return; } // Do we have enough for an another read? if (len == -1) { shouldReadAnother = socket->bytesAvailable() >= HeaderSize; } else { shouldReadAnother = socket->bytesAvailable() >= len; } } while (shouldReadAnother); } diff --git a/src/core/copyjob.cpp b/src/core/copyjob.cpp index 6fd16ffa..13888299 100644 --- a/src/core/copyjob.cpp +++ b/src/core/copyjob.cpp @@ -1,2278 +1,2278 @@ /* This file is part of the KDE libraries Copyright 2000 Stephan Kulow Copyright 2000-2006 David Faure Copyright 2000 Waldo Bastian This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "copyjob.h" #include "kiocoredebug.h" #include #include "kcoredirlister.h" #include "kfileitem.h" #include "job.h" // buildErrorString #include "mkdirjob.h" #include "listjob.h" #include "statjob.h" #include "deletejob.h" #include "filecopyjob.h" #include "../pathhelpers_p.h" #include #include #include #include "slave.h" #include "scheduler.h" #include "kdirwatch.h" #include "kprotocolmanager.h" #include #include #include #ifdef Q_OS_UNIX #include #endif #include #include #include #include #include #include // mode_t #include #include "job_p.h" #include #include #include Q_DECLARE_LOGGING_CATEGORY(KIO_COPYJOB_DEBUG) Q_LOGGING_CATEGORY(KIO_COPYJOB_DEBUG, "kf5.kio.core.copyjob", QtWarningMsg) using namespace KIO; //this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX #define REPORT_TIMEOUT 200 enum DestinationState { DEST_NOT_STATED, DEST_IS_DIR, DEST_IS_FILE, DEST_DOESNT_EXIST }; /** * States: * STATE_INITIAL the constructor was called * STATE_STATING for the dest * statCurrentSrc then does, for each src url: * STATE_RENAMING if direct rename looks possible * (on already exists, and user chooses rename, TODO: go to STATE_RENAMING again) * STATE_STATING * and then, if dir -> STATE_LISTING (filling 'd->dirs' and 'd->files') * STATE_CREATING_DIRS (createNextDir, iterating over 'd->dirs') * if conflict: STATE_CONFLICT_CREATING_DIRS * STATE_COPYING_FILES (copyNextFile, iterating over 'd->files') * if conflict: STATE_CONFLICT_COPYING_FILES * STATE_DELETING_DIRS (deleteNextDir) (if moving) * STATE_SETTING_DIR_ATTRIBUTES (setNextDirAttribute, iterating over d->m_directoriesCopied) * done. */ enum CopyJobState { STATE_INITIAL, STATE_STATING, STATE_RENAMING, STATE_LISTING, STATE_CREATING_DIRS, STATE_CONFLICT_CREATING_DIRS, STATE_COPYING_FILES, STATE_CONFLICT_COPYING_FILES, STATE_DELETING_DIRS, STATE_SETTING_DIR_ATTRIBUTES }; static QUrl addPathToUrl(const QUrl &url, const QString &relPath) { QUrl u(url); u.setPath(concatPaths(url.path(), relPath)); return u; } /** @internal */ class KIO::CopyJobPrivate: public KIO::JobPrivate { public: CopyJobPrivate(const QList &src, const QUrl &dest, CopyJob::CopyMode mode, bool asMethod) : m_globalDest(dest) , m_globalDestinationState(DEST_NOT_STATED) , m_defaultPermissions(false) , m_bURLDirty(false) , m_mode(mode) , m_asMethod(asMethod) , destinationState(DEST_NOT_STATED) , state(STATE_INITIAL) , m_freeSpace(-1) , m_totalSize(0) , m_processedSize(0) , m_fileProcessedSize(0) , m_processedFiles(0) , m_processedDirs(0) , m_srcList(src) , m_currentStatSrc(m_srcList.constBegin()) , m_bCurrentOperationIsLink(false) , m_bSingleFileCopy(false) , m_bOnlyRenames(mode == CopyJob::Move) , m_dest(dest) , m_bAutoRenameFiles(false) , m_bAutoRenameDirs(false) , m_bAutoSkipFiles(false) , m_bAutoSkipDirs(false) , m_bOverwriteAllFiles(false) , m_bOverwriteAllDirs(false) , m_conflictError(0) , m_reportTimer(nullptr) { } // This is the dest URL that was initially given to CopyJob // It is copied into m_dest, which can be changed for a given src URL // (when using the RENAME dialog in slotResult), // and which will be reset for the next src URL. QUrl m_globalDest; // The state info about that global dest DestinationState m_globalDestinationState; // See setDefaultPermissions bool m_defaultPermissions; // Whether URLs changed (and need to be emitted by the next slotReport call) bool m_bURLDirty; // Used after copying all the files into the dirs, to set mtime (TODO: and permissions?) // after the copy is done QLinkedList m_directoriesCopied; QLinkedList::const_iterator m_directoriesCopiedIterator; CopyJob::CopyMode m_mode; bool m_asMethod; DestinationState destinationState; CopyJobState state; KIO::filesize_t m_freeSpace; KIO::filesize_t m_totalSize; KIO::filesize_t m_processedSize; KIO::filesize_t m_fileProcessedSize; int m_processedFiles; int m_processedDirs; QList files; QList dirs; QList dirsToRemove; QList m_srcList; QList m_successSrcList; // Entries in m_srcList that have successfully been moved QList::const_iterator m_currentStatSrc; bool m_bCurrentSrcIsDir; bool m_bCurrentOperationIsLink; bool m_bSingleFileCopy; bool m_bOnlyRenames; QUrl m_dest; QUrl m_currentDest; // set during listing, used by slotEntries // QStringList m_skipList; QSet m_overwriteList; bool m_bAutoRenameFiles; bool m_bAutoRenameDirs; bool m_bAutoSkipFiles; bool m_bAutoSkipDirs; bool m_bOverwriteAllFiles; bool m_bOverwriteAllDirs; int m_conflictError; QTimer *m_reportTimer; // The current src url being stat'ed or copied // During the stat phase, this is initially equal to *m_currentStatSrc but it can be resolved to a local file equivalent (#188903). QUrl m_currentSrcURL; QUrl m_currentDestURL; QSet m_parentDirs; void statCurrentSrc(); void statNextSrc(); // Those aren't slots but submethods for slotResult. void slotResultStating(KJob *job); void startListing(const QUrl &src); void slotResultCreatingDirs(KJob *job); void slotResultConflictCreatingDirs(KJob *job); void createNextDir(); void slotResultCopyingFiles(KJob *job); void slotResultErrorCopyingFiles(KJob *job); // KIO::Job* linkNextFile( const QUrl& uSource, const QUrl& uDest, bool overwrite ); KIO::Job *linkNextFile(const QUrl &uSource, const QUrl &uDest, JobFlags flags); void copyNextFile(); void slotResultDeletingDirs(KJob *job); void deleteNextDir(); void sourceStated(const UDSEntry &entry, const QUrl &sourceUrl); void skip(const QUrl &sourceURL, bool isDir); void slotResultRenaming(KJob *job); void slotResultSettingDirAttributes(KJob *job); void setNextDirAttribute(); void startRenameJob(const QUrl &slave_url); bool shouldOverwriteDir(const QString &path) const; bool shouldOverwriteFile(const QString &path) const; bool shouldSkip(const QString &path) const; void skipSrc(bool isDir); void renameDirectory(const QList::iterator &it, const QUrl &newUrl); QUrl finalDestUrl(const QUrl &src, const QUrl &dest) const; void slotStart(); void slotEntries(KIO::Job *, const KIO::UDSEntryList &list); void slotSubError(KIO::ListJob *job, KIO::ListJob *subJob); void addCopyInfoFromUDSEntry(const UDSEntry &entry, const QUrl &srcUrl, bool srcIsDir, const QUrl ¤tDest); /** * Forward signal from subjob */ void slotProcessedSize(KJob *, qulonglong data_size); /** * Forward signal from subjob * @param size the total size */ void slotTotalSize(KJob *, qulonglong size); void slotReport(); Q_DECLARE_PUBLIC(CopyJob) static inline CopyJob *newJob(const QList &src, const QUrl &dest, CopyJob::CopyMode mode, bool asMethod, JobFlags flags) { CopyJob *job = new CopyJob(*new CopyJobPrivate(src, dest, mode, asMethod)); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); if (!(flags & HideProgressInfo)) { KIO::getJobTracker()->registerJob(job); } if (flags & KIO::Overwrite) { job->d_func()->m_bOverwriteAllDirs = true; job->d_func()->m_bOverwriteAllFiles = true; } if (!(flags & KIO::NoPrivilegeExecution)) { job->d_func()->m_privilegeExecutionEnabled = true; FileOperationType copyType; switch (mode) { case CopyJob::Copy: copyType = Copy; break; case CopyJob::Move: copyType = Move; break; case CopyJob::Link: copyType = Symlink; break; } job->d_func()->m_operationType = copyType; } return job; } }; CopyJob::CopyJob(CopyJobPrivate &dd) : Job(dd) { setProperty("destUrl", d_func()->m_dest.toString()); QTimer::singleShot(0, this, SLOT(slotStart())); qRegisterMetaType(); } CopyJob::~CopyJob() { } QList CopyJob::srcUrls() const { return d_func()->m_srcList; } QUrl CopyJob::destUrl() const { return d_func()->m_dest; } void CopyJobPrivate::slotStart() { Q_Q(CopyJob); if (q->isSuspended()) { return; } if (m_mode == CopyJob::CopyMode::Move) { Q_FOREACH (const QUrl &url, m_srcList) { if (m_dest.scheme() == url.scheme() && m_dest.host() == url.host()) { QString srcPath = url.path(); if (!srcPath.endsWith(QLatin1Char('/'))) srcPath += QLatin1Char('/'); if (m_dest.path().startsWith(srcPath)) { q->setError(KIO::ERR_CANNOT_MOVE_INTO_ITSELF); q->emitResult(); return; } } } } /** We call the functions directly instead of using signals. Calling a function via a signal takes approx. 65 times the time compared to calling it directly (at least on my machine). aleXXX */ m_reportTimer = new QTimer(q); q->connect(m_reportTimer, SIGNAL(timeout()), q, SLOT(slotReport())); m_reportTimer->start(REPORT_TIMEOUT); // Stat the dest state = STATE_STATING; const QUrl dest = m_asMethod ? m_dest.adjusted(QUrl::RemoveFilename) : m_dest; KIO::Job *job = KIO::stat(dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo); qCDebug(KIO_COPYJOB_DEBUG) << "CopyJob: stating the dest" << m_dest; q->addSubjob(job); } // For unit test purposes KIOCORE_EXPORT bool kio_resolve_local_urls = true; void CopyJobPrivate::slotResultStating(KJob *job) { Q_Q(CopyJob); qCDebug(KIO_COPYJOB_DEBUG); // Was there an error while stating the src ? if (job->error() && destinationState != DEST_NOT_STATED) { const QUrl srcurl = static_cast(job)->url(); if (!srcurl.isLocalFile()) { // Probably : src doesn't exist. Well, over some protocols (e.g. FTP) // this info isn't really reliable (thanks to MS FTP servers). // We'll assume a file, and try to download anyway. qCDebug(KIO_COPYJOB_DEBUG) << "Error while stating source. Activating hack"; q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); // We should have only one job at a time ... struct CopyInfo info; info.permissions = (mode_t) - 1; info.size = (KIO::filesize_t) - 1; info.uSource = srcurl; info.uDest = m_dest; // Append filename or dirname to destination URL, if allowed if (destinationState == DEST_IS_DIR && !m_asMethod) { const QString fileName = srcurl.scheme() == QLatin1String("data") ? QStringLiteral("data") : srcurl.fileName(); // #379093 info.uDest = addPathToUrl(info.uDest, fileName); } files.append(info); statNextSrc(); return; } // Local file. If stat fails, the file definitely doesn't exist. // yes, q->Job::, because we don't want to call our override q->Job::slotResult(job); // will set the error and emit result(this) return; } // Keep copy of the stat result const UDSEntry entry = static_cast(job)->statResult(); if (destinationState == DEST_NOT_STATED) { if (m_dest.isLocalFile()) { //works for dirs as well QString path(m_dest.toLocalFile()); QFileInfo fileInfo(path); if (m_asMethod || !fileInfo.exists()) { // In copy-as mode, we want to check the directory to which we're // copying. The target file or directory does not exist yet, which // might confuse KDiskFreeSpaceInfo. path = fileInfo.absolutePath(); } KDiskFreeSpaceInfo freeSpaceInfo = KDiskFreeSpaceInfo::freeSpaceInfo(path); if (freeSpaceInfo.isValid()) { m_freeSpace = freeSpaceInfo.available(); } else { qCDebug(KIO_COPYJOB_DEBUG) << "Couldn't determine free space information for" << path; } //TODO actually preliminary check is even more valuable for slow NFS/SMB mounts, //but we need to find a way to report connection errors to user } const bool isGlobalDest = m_dest == m_globalDest; const bool isDir = entry.isDir(); // we were stating the dest if (job->error()) { destinationState = DEST_DOESNT_EXIST; qCDebug(KIO_COPYJOB_DEBUG) << "dest does not exist"; } else { // Treat symlinks to dirs as dirs here, so no test on isLink destinationState = isDir ? DEST_IS_DIR : DEST_IS_FILE; qCDebug(KIO_COPYJOB_DEBUG) << "dest is dir:" << isDir; const QString sLocalPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); if (!sLocalPath.isEmpty() && kio_resolve_local_urls && destinationState != DEST_DOESNT_EXIST) { const QString fileName = m_dest.fileName(); m_dest = QUrl::fromLocalFile(sLocalPath); if (m_asMethod) { m_dest = addPathToUrl(m_dest, fileName); } qCDebug(KIO_COPYJOB_DEBUG) << "Setting m_dest to the local path:" << sLocalPath; if (isGlobalDest) { m_globalDest = m_dest; } } } if (isGlobalDest) { m_globalDestinationState = destinationState; } q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); // After knowing what the dest is, we can start stat'ing the first src. statCurrentSrc(); } else { sourceStated(entry, static_cast(job)->url()); q->removeSubjob(job); } } void CopyJobPrivate::sourceStated(const UDSEntry &entry, const QUrl &sourceUrl) { const QString sLocalPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); const bool isDir = entry.isDir(); // We were stating the current source URL // Is it a file or a dir ? // There 6 cases, and all end up calling addCopyInfoFromUDSEntry first : // 1 - src is a dir, destination is a directory, // slotEntries will append the source-dir-name to the destination // 2 - src is a dir, destination is a file -- will offer to overwrite, later on. // 3 - src is a dir, destination doesn't exist, then it's the destination dirname, // so slotEntries will use it as destination. // 4 - src is a file, destination is a directory, // slotEntries will append the filename to the destination. // 5 - src is a file, destination is a file, m_dest is the exact destination name // 6 - src is a file, destination doesn't exist, m_dest is the exact destination name QUrl srcurl; if (!sLocalPath.isEmpty() && destinationState != DEST_DOESNT_EXIST) { qCDebug(KIO_COPYJOB_DEBUG) << "Using sLocalPath. destinationState=" << destinationState; // Prefer the local path -- but only if we were able to stat() the dest. // Otherwise, renaming a desktop:/ url would copy from src=file to dest=desktop (#218719) srcurl = QUrl::fromLocalFile(sLocalPath); } else { srcurl = sourceUrl; } addCopyInfoFromUDSEntry(entry, srcurl, false, m_dest); m_currentDest = m_dest; m_bCurrentSrcIsDir = false; if (isDir // treat symlinks as files (no recursion) && !entry.isLink() && m_mode != CopyJob::Link) { // No recursion in Link mode either. qCDebug(KIO_COPYJOB_DEBUG) << "Source is a directory"; if (srcurl.isLocalFile()) { const QString parentDir = srcurl.adjusted(QUrl::StripTrailingSlash).toLocalFile(); m_parentDirs.insert(parentDir); } m_bCurrentSrcIsDir = true; // used by slotEntries if (destinationState == DEST_IS_DIR) { // (case 1) if (!m_asMethod) { // Use / as destination, from now on QString directory = srcurl.fileName(); const QString sName = entry.stringValue(KIO::UDSEntry::UDS_NAME); KProtocolInfo::FileNameUsedForCopying fnu = KProtocolManager::fileNameUsedForCopying(srcurl); if (fnu == KProtocolInfo::Name) { if (!sName.isEmpty()) { directory = sName; } } else if (fnu == KProtocolInfo::DisplayName) { const QString dispName = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME); if (!dispName.isEmpty()) { directory = dispName; } else if (!sName.isEmpty()) { directory = sName; } } m_currentDest = addPathToUrl(m_currentDest, directory); } } else { // (case 3) // otherwise dest is new name for toplevel dir // so the destination exists, in fact, from now on. // (This even works with other src urls in the list, since the // dir has effectively been created) destinationState = DEST_IS_DIR; if (m_dest == m_globalDest) { m_globalDestinationState = destinationState; } } startListing(srcurl); } else { qCDebug(KIO_COPYJOB_DEBUG) << "Source is a file (or a symlink), or we are linking -> no recursive listing"; if (srcurl.isLocalFile()) { const QString parentDir = srcurl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path(); m_parentDirs.insert(parentDir); } statNextSrc(); } } bool CopyJob::doSuspend() { Q_D(CopyJob); d->slotReport(); return Job::doSuspend(); } bool CopyJob::doResume() { Q_D(CopyJob); switch (d->state) { case STATE_INITIAL: QTimer::singleShot(0, this, SLOT(slotStart())); break; default: // not implemented break; } return Job::doResume(); } void CopyJobPrivate::slotReport() { Q_Q(CopyJob); if (q->isSuspended()) { return; } // If showProgressInfo was set, progressId() is > 0. switch (state) { case STATE_RENAMING: q->setTotalAmount(KJob::Files, m_srcList.count()); // fall-through intended Q_FALLTHROUGH(); case STATE_COPYING_FILES: q->setProcessedAmount(KJob::Files, m_processedFiles); if (m_bURLDirty) { // Only emit urls when they changed. This saves time, and fixes #66281 m_bURLDirty = false; if (m_mode == CopyJob::Move) { emitMoving(q, m_currentSrcURL, m_currentDestURL); emit q->moving(q, m_currentSrcURL, m_currentDestURL); } else if (m_mode == CopyJob::Link) { emitCopying(q, m_currentSrcURL, m_currentDestURL); // we don't have a delegate->linking emit q->linking(q, m_currentSrcURL.path(), m_currentDestURL); } else { emitCopying(q, m_currentSrcURL, m_currentDestURL); emit q->copying(q, m_currentSrcURL, m_currentDestURL); } } break; case STATE_CREATING_DIRS: q->setProcessedAmount(KJob::Directories, m_processedDirs); if (m_bURLDirty) { m_bURLDirty = false; emit q->creatingDir(q, m_currentDestURL); emitCreatingDir(q, m_currentDestURL); } break; case STATE_STATING: case STATE_LISTING: if (m_bURLDirty) { m_bURLDirty = false; if (m_mode == CopyJob::Move) { emitMoving(q, m_currentSrcURL, m_currentDestURL); } else { emitCopying(q, m_currentSrcURL, m_currentDestURL); } } q->setTotalAmount(KJob::Bytes, m_totalSize); q->setTotalAmount(KJob::Files, files.count()); q->setTotalAmount(KJob::Directories, dirs.count()); break; default: break; } } void CopyJobPrivate::slotEntries(KIO::Job *job, const UDSEntryList &list) { //Q_Q(CopyJob); UDSEntryList::ConstIterator it = list.constBegin(); UDSEntryList::ConstIterator end = list.constEnd(); for (; it != end; ++it) { const UDSEntry &entry = *it; addCopyInfoFromUDSEntry(entry, static_cast(job)->url(), m_bCurrentSrcIsDir, m_currentDest); } } void CopyJobPrivate::slotSubError(ListJob *job, ListJob *subJob) { const QUrl url = subJob->url(); qCWarning(KIO_CORE) << url << subJob->errorString(); Q_Q(CopyJob); emit q->warning(job, subJob->errorString(), QString()); skip(url, true); } void CopyJobPrivate::addCopyInfoFromUDSEntry(const UDSEntry &entry, const QUrl &srcUrl, bool srcIsDir, const QUrl ¤tDest) { struct CopyInfo info; info.permissions = entry.numberValue(KIO::UDSEntry::UDS_ACCESS, -1); info.mtime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1)); info.ctime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1)); info.size = static_cast(entry.numberValue(KIO::UDSEntry::UDS_SIZE, -1)); if (info.size != (KIO::filesize_t) - 1) { m_totalSize += info.size; } // recursive listing, displayName can be a/b/c/d const QString fileName = entry.stringValue(KIO::UDSEntry::UDS_NAME); const QString urlStr = entry.stringValue(KIO::UDSEntry::UDS_URL); QUrl url; if (!urlStr.isEmpty()) { url = QUrl(urlStr); } QString localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); const bool isDir = entry.isDir(); info.linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST); if (fileName != QLatin1String("..") && fileName != QLatin1String(".")) { const bool hasCustomURL = !url.isEmpty() || !localPath.isEmpty(); if (!hasCustomURL) { // Make URL from displayName url = srcUrl; if (srcIsDir) { // Only if src is a directory. Otherwise uSource is fine as is qCDebug(KIO_COPYJOB_DEBUG) << "adding path" << fileName; url = addPathToUrl(url, fileName); } } qCDebug(KIO_COPYJOB_DEBUG) << "fileName=" << fileName << "url=" << url; if (!localPath.isEmpty() && kio_resolve_local_urls && destinationState != DEST_DOESNT_EXIST) { url = QUrl::fromLocalFile(localPath); } info.uSource = url; info.uDest = currentDest; qCDebug(KIO_COPYJOB_DEBUG) << "uSource=" << info.uSource << "uDest(1)=" << info.uDest; // Append filename or dirname to destination URL, if allowed if (destinationState == DEST_IS_DIR && // "copy/move as " means 'foo' is the dest for the base srcurl // (passed here during stating) but not its children (during listing) (!(m_asMethod && state == STATE_STATING))) { QString destFileName; KProtocolInfo::FileNameUsedForCopying fnu = KProtocolManager::fileNameUsedForCopying(url); if (hasCustomURL && fnu == KProtocolInfo::FromUrl) { //destFileName = url.fileName(); // Doesn't work for recursive listing // Count the number of prefixes used by the recursive listjob - int numberOfSlashes = fileName.count('/'); // don't make this a find()! + int numberOfSlashes = fileName.count(QLatin1Char('/')); // don't make this a find()! QString path = url.path(); int pos = 0; for (int n = 0; n < numberOfSlashes + 1; ++n) { - pos = path.lastIndexOf('/', pos - 1); + pos = path.lastIndexOf(QLatin1Char('/'), pos - 1); if (pos == -1) { // error qCWarning(KIO_CORE) << "kioslave bug: not enough slashes in UDS_URL" << path << "- looking for" << numberOfSlashes << "slashes"; break; } } if (pos >= 0) { destFileName = path.mid(pos + 1); } } else if (fnu == KProtocolInfo::Name) { // destination filename taken from UDS_NAME destFileName = fileName; } else { // from display name (with fallback to name) const QString displayName = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME); destFileName = displayName.isEmpty() ? fileName : displayName; } // Here we _really_ have to add some filename to the dest. // Otherwise, we end up with e.g. dest=..../Desktop/ itself. // (This can happen when dropping a link to a webpage with no path) if (destFileName.isEmpty()) { destFileName = KIO::encodeFileName(info.uSource.toDisplayString()); } qCDebug(KIO_COPYJOB_DEBUG) << " adding destFileName=" << destFileName; info.uDest = addPathToUrl(info.uDest, destFileName); } qCDebug(KIO_COPYJOB_DEBUG) << " uDest(2)=" << info.uDest; qCDebug(KIO_COPYJOB_DEBUG) << " " << info.uSource << "->" << info.uDest; if (info.linkDest.isEmpty() && isDir && m_mode != CopyJob::Link) { // Dir dirs.append(info); // Directories if (m_mode == CopyJob::Move) { dirsToRemove.append(info.uSource); } } else { files.append(info); // Files and any symlinks } } } // Adjust for kio_trash choosing its own dest url... QUrl CopyJobPrivate::finalDestUrl(const QUrl& src, const QUrl &dest) const { Q_Q(const CopyJob); if (dest.scheme() == QLatin1String("trash")) { const QMap& metaData = q->metaData(); - QMap::ConstIterator it = metaData.find("trashURL-" + src.path()); + QMap::ConstIterator it = metaData.find(QLatin1String("trashURL-") + src.path()); if (it != metaData.constEnd()) { qCDebug(KIO_COPYJOB_DEBUG) << "finalDestUrl=" << it.value(); return QUrl(it.value()); } } return dest; } void CopyJobPrivate::skipSrc(bool isDir) { m_dest = m_globalDest; destinationState = m_globalDestinationState; skip(*m_currentStatSrc, isDir); ++m_currentStatSrc; statCurrentSrc(); } void CopyJobPrivate::statNextSrc() { /* Revert to the global destination, the one that applies to all source urls. * Imagine you copy the items a b and c into /d, but /d/b exists so the user uses "Rename" to put it in /foo/b instead. * d->m_dest is /foo/b for b, but we have to revert to /d for item c and following. */ m_dest = m_globalDest; qCDebug(KIO_COPYJOB_DEBUG) << "Setting m_dest to" << m_dest; destinationState = m_globalDestinationState; ++m_currentStatSrc; statCurrentSrc(); } void CopyJobPrivate::statCurrentSrc() { Q_Q(CopyJob); if (m_currentStatSrc != m_srcList.constEnd()) { m_currentSrcURL = (*m_currentStatSrc); m_bURLDirty = true; if (m_mode == CopyJob::Link) { // Skip the "stating the source" stage, we don't need it for linking m_currentDest = m_dest; struct CopyInfo info; info.permissions = -1; info.size = (KIO::filesize_t) - 1; info.uSource = m_currentSrcURL; info.uDest = m_currentDest; // Append filename or dirname to destination URL, if allowed if (destinationState == DEST_IS_DIR && !m_asMethod) { if ( (m_currentSrcURL.scheme() == info.uDest.scheme()) && (m_currentSrcURL.host() == info.uDest.host()) && (m_currentSrcURL.port() == info.uDest.port()) && (m_currentSrcURL.userName() == info.uDest.userName()) && (m_currentSrcURL.password() == info.uDest.password())) { // This is the case of creating a real symlink info.uDest = addPathToUrl(info.uDest, m_currentSrcURL.fileName()); } else { // Different protocols, we'll create a .desktop file // We have to change the extension anyway, so while we're at it, // name the file like the URL - info.uDest = addPathToUrl(info.uDest, KIO::encodeFileName(m_currentSrcURL.toDisplayString()) + ".desktop"); + info.uDest = addPathToUrl(info.uDest, KIO::encodeFileName(m_currentSrcURL.toDisplayString()) + QLatin1String(".desktop")); } } files.append(info); // Files and any symlinks statNextSrc(); // we could use a loop instead of a recursive call :) return; } // Let's see if we can skip stat'ing, for the case where a directory view has the info already KIO::UDSEntry entry; const KFileItem cachedItem = KCoreDirLister::cachedItemForUrl(m_currentSrcURL); if (!cachedItem.isNull()) { entry = cachedItem.entry(); if (destinationState != DEST_DOESNT_EXIST) { // only resolve src if we could resolve dest (#218719) bool dummyIsLocal; m_currentSrcURL = cachedItem.mostLocalUrl(dummyIsLocal); // #183585 } } if (m_mode == CopyJob::Move && ( // Don't go renaming right away if we need a stat() to find out the destination filename KProtocolManager::fileNameUsedForCopying(m_currentSrcURL) == KProtocolInfo::FromUrl || destinationState != DEST_IS_DIR || m_asMethod) ) { // If moving, before going for the full stat+[list+]copy+del thing, try to rename // The logic is pretty similar to FileCopyJobPrivate::slotStart() if ((m_currentSrcURL.scheme() == m_dest.scheme()) && (m_currentSrcURL.host() == m_dest.host()) && (m_currentSrcURL.port() == m_dest.port()) && (m_currentSrcURL.userName() == m_dest.userName()) && (m_currentSrcURL.password() == m_dest.password())) { startRenameJob(m_currentSrcURL); return; } else if (m_currentSrcURL.isLocalFile() && KProtocolManager::canRenameFromFile(m_dest)) { startRenameJob(m_dest); return; } else if (m_dest.isLocalFile() && KProtocolManager::canRenameToFile(m_currentSrcURL)) { startRenameJob(m_currentSrcURL); return; } } // if the file system doesn't support deleting, we do not even stat if (m_mode == CopyJob::Move && !KProtocolManager::supportsDeleting(m_currentSrcURL)) { QPointer that = q; emit q->warning(q, buildErrorString(ERR_CANNOT_DELETE, m_currentSrcURL.toDisplayString())); if (that) { statNextSrc(); // we could use a loop instead of a recursive call :) } return; } m_bOnlyRenames = false; // Testing for entry.count()>0 here is not good enough; KFileItem inserts // entries for UDS_USER and UDS_GROUP even on initially empty UDSEntries (#192185) if (entry.contains(KIO::UDSEntry::UDS_NAME)) { qCDebug(KIO_COPYJOB_DEBUG) << "fast path! found info about" << m_currentSrcURL << "in KCoreDirLister"; // sourceStated(entry, m_currentSrcURL); // don't recurse, see #319747, use queued invokeMethod instead QMetaObject::invokeMethod(q, "sourceStated", Qt::QueuedConnection, Q_ARG(KIO::UDSEntry, entry), Q_ARG(QUrl, m_currentSrcURL)); return; } // Stat the next src url Job *job = KIO::stat(m_currentSrcURL, StatJob::SourceSide, 2, KIO::HideProgressInfo); qCDebug(KIO_COPYJOB_DEBUG) << "KIO::stat on" << m_currentSrcURL; state = STATE_STATING; q->addSubjob(job); m_currentDestURL = m_dest; m_bURLDirty = true; } else { // Finished the stat'ing phase // First make sure that the totals were correctly emitted state = STATE_STATING; m_bURLDirty = true; slotReport(); qCDebug(KIO_COPYJOB_DEBUG)<<"Stating finished. To copy:"< m_freeSpace && m_freeSpace != static_cast(-1)) { q->setError(ERR_DISK_FULL); q->setErrorText(m_currentSrcURL.toDisplayString()); q->emitResult(); return; } if (!dirs.isEmpty()) { emit q->aboutToCreate(q, dirs); } if (!files.isEmpty()) { emit q->aboutToCreate(q, files); } // Check if we are copying a single file m_bSingleFileCopy = (files.count() == 1 && dirs.isEmpty()); // Then start copying things state = STATE_CREATING_DIRS; createNextDir(); } } void CopyJobPrivate::startRenameJob(const QUrl &slave_url) { Q_Q(CopyJob); // Silence KDirWatch notifications, otherwise performance is horrible if (m_currentSrcURL.isLocalFile()) { const QString parentDir = m_currentSrcURL.adjusted(QUrl::RemoveFilename).path(); if (!m_parentDirs.contains(parentDir)) { KDirWatch::self()->stopDirScan(parentDir); m_parentDirs.insert(parentDir); } } QUrl dest = m_dest; // Append filename or dirname to destination URL, if allowed if (destinationState == DEST_IS_DIR && !m_asMethod) { dest = addPathToUrl(dest, m_currentSrcURL.fileName()); } m_currentDestURL = dest; qCDebug(KIO_COPYJOB_DEBUG) << m_currentSrcURL << "->" << dest << "trying direct rename first"; state = STATE_RENAMING; struct CopyInfo info; info.permissions = -1; info.size = (KIO::filesize_t) - 1; info.uSource = m_currentSrcURL; info.uDest = dest; QList files; files.append(info); emit q->aboutToCreate(q, files); KIO_ARGS << m_currentSrcURL << dest << (qint8) false /*no overwrite*/; SimpleJob *newJob = SimpleJobPrivate::newJobNoUi(slave_url, CMD_RENAME, packedArgs); newJob->setParentJob(q); Scheduler::setJobPriority(newJob, 1); q->addSubjob(newJob); if (m_currentSrcURL.adjusted(QUrl::RemoveFilename) != dest.adjusted(QUrl::RemoveFilename)) { // For the user, moving isn't renaming. Only renaming is. m_bOnlyRenames = false; } } void CopyJobPrivate::startListing(const QUrl &src) { Q_Q(CopyJob); state = STATE_LISTING; m_bURLDirty = true; ListJob *newjob = listRecursive(src, KIO::HideProgressInfo); newjob->setUnrestricted(true); q->connect(newjob, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList))); q->connect(newjob, SIGNAL(subError(KIO::ListJob*,KIO::ListJob*)), SLOT(slotSubError(KIO::ListJob*,KIO::ListJob*))); q->addSubjob(newjob); } void CopyJobPrivate::skip(const QUrl &sourceUrl, bool isDir) { QUrl dir(sourceUrl); if (!isDir) { // Skipping a file: make sure not to delete the parent dir (#208418) dir = dir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); } while (dirsToRemove.removeAll(dir) > 0) { // Do not rely on rmdir() on the parent directories aborting. // Exclude the parent dirs explicitly. dir = dir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); } } bool CopyJobPrivate::shouldOverwriteDir(const QString &path) const { if (m_bOverwriteAllDirs) { return true; } return m_overwriteList.contains(path); } bool CopyJobPrivate::shouldOverwriteFile(const QString &path) const { if (m_bOverwriteAllFiles) { return true; } return m_overwriteList.contains(path); } bool CopyJobPrivate::shouldSkip(const QString &path) const { Q_FOREACH (const QString &skipPath, m_skipList) { if (path.startsWith(skipPath)) { return true; } } return false; } void CopyJobPrivate::renameDirectory(const QList::iterator &it, const QUrl &newUrl) { Q_Q(CopyJob); emit q->renamed(q, (*it).uDest, newUrl); // for e.g. KPropertiesDialog QString oldPath = (*it).uDest.path(); - if (!oldPath.endsWith('/')) { - oldPath += '/'; + if (!oldPath.endsWith(QLatin1Char('/'))) { + oldPath += QLatin1Char('/'); } // Change the current one and strip the trailing '/' (*it).uDest = newUrl.adjusted(QUrl::StripTrailingSlash); QString newPath = newUrl.path(); // With trailing slash - if (!newPath.endsWith('/')) { - newPath += '/'; + if (!newPath.endsWith(QLatin1Char('/'))) { + newPath += QLatin1Char('/'); } QList::Iterator renamedirit = it; ++renamedirit; // Change the name of subdirectories inside the directory for (; renamedirit != dirs.end(); ++renamedirit) { QString path = (*renamedirit).uDest.path(); if (path.startsWith(oldPath)) { QString n = path; n.replace(0, oldPath.length(), newPath); /*qDebug() << "dirs list:" << (*renamedirit).uSource.path() << "was going to be" << path << ", changed into" << n;*/ (*renamedirit).uDest.setPath(n, QUrl::DecodedMode); } } // Change filenames inside the directory QList::Iterator renamefileit = files.begin(); for (; renamefileit != files.end(); ++renamefileit) { QString path = (*renamefileit).uDest.path(QUrl::FullyDecoded); if (path.startsWith(oldPath)) { QString n = path; n.replace(0, oldPath.length(), newPath); /*qDebug() << "files list:" << (*renamefileit).uSource.path() << "was going to be" << path << ", changed into" << n;*/ (*renamefileit).uDest.setPath(n, QUrl::DecodedMode); } } if (!dirs.isEmpty()) { emit q->aboutToCreate(q, dirs); } if (!files.isEmpty()) { emit q->aboutToCreate(q, files); } } void CopyJobPrivate::slotResultCreatingDirs(KJob *job) { Q_Q(CopyJob); // The dir we are trying to create: QList::Iterator it = dirs.begin(); // Was there an error creating a dir ? if (job->error()) { m_conflictError = job->error(); if ((m_conflictError == ERR_DIR_ALREADY_EXIST) || (m_conflictError == ERR_FILE_ALREADY_EXIST)) { // can't happen? QUrl oldURL = ((SimpleJob *)job)->url(); // Should we skip automatically ? if (m_bAutoSkipDirs) { // We don't want to copy files in this directory, so we put it on the skip list QString path = oldURL.path(); - if (!path.endsWith('/')) { - path += '/'; + if (!path.endsWith(QLatin1Char('/'))) { + path += QLatin1Char('/'); } m_skipList.append(path); skip(oldURL, true); dirs.erase(it); // Move on to next dir } else { // Did the user choose to overwrite already? const QString destDir = (*it).uDest.path(); if (shouldOverwriteDir(destDir)) { // overwrite => just skip emit q->copyingDone(q, (*it).uSource, finalDestUrl((*it).uSource, (*it).uDest), (*it).mtime, true /* directory */, false /* renamed */); dirs.erase(it); // Move on to next dir } else { if (m_bAutoRenameDirs) { const QUrl destDirectory = (*it).uDest.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); const QString newName = KIO::suggestName(destDirectory, (*it).uDest.fileName()); QUrl newUrl(destDirectory); newUrl.setPath(concatPaths(newUrl.path(), newName)); renameDirectory(it, newUrl); } else { if (!q->uiDelegateExtension()) { q->Job::slotResult(job); // will set the error and emit result(this) return; } Q_ASSERT(((SimpleJob *)job)->url() == (*it).uDest); q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); // We should have only one job at a time ... // We need to stat the existing dir, to get its last-modification time QUrl existingDest((*it).uDest); SimpleJob *newJob = KIO::stat(existingDest, StatJob::DestinationSide, 2, KIO::HideProgressInfo); Scheduler::setJobPriority(newJob, 1); qCDebug(KIO_COPYJOB_DEBUG) << "KIO::stat for resolving conflict on" << existingDest; state = STATE_CONFLICT_CREATING_DIRS; q->addSubjob(newJob); return; // Don't move to next dir yet ! } } } } else { // Severe error, abort q->Job::slotResult(job); // will set the error and emit result(this) return; } } else { // no error : remove from list, to move on to next dir //this is required for the undo feature emit q->copyingDone(q, (*it).uSource, finalDestUrl((*it).uSource, (*it).uDest), (*it).mtime, true, false); m_directoriesCopied.append(*it); dirs.erase(it); } m_processedDirs++; //emit processedAmount( this, KJob::Directories, m_processedDirs ); q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); // We should have only one job at a time ... createNextDir(); } void CopyJobPrivate::slotResultConflictCreatingDirs(KJob *job) { Q_Q(CopyJob); // We come here after a conflict has been detected and we've stated the existing dir // The dir we were trying to create: QList::Iterator it = dirs.begin(); const UDSEntry entry = ((KIO::StatJob *)job)->statResult(); // Its modification time: const QDateTime destmtime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1)); const QDateTime destctime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1)); const KIO::filesize_t destsize = entry.numberValue(KIO::UDSEntry::UDS_SIZE); const QString linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST); q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); // We should have only one job at a time ... // Always multi and skip (since there are files after that) RenameDialog_Options options(RenameDialog_MultipleItems | RenameDialog_Skip | RenameDialog_IsDirectory); // Overwrite only if the existing thing is a dir (no chance with a file) if (m_conflictError == ERR_DIR_ALREADY_EXIST) { if ((*it).uSource == (*it).uDest || ((*it).uSource.scheme() == (*it).uDest.scheme() && (*it).uSource.adjusted(QUrl::StripTrailingSlash).path() == linkDest)) { options |= RenameDialog_OverwriteItself; } else { options |= RenameDialog_Overwrite; } } const QString existingDest = (*it).uDest.path(); QString newPath; if (m_reportTimer) { m_reportTimer->stop(); } RenameDialog_Result r = q->uiDelegateExtension()->askFileRename(q, i18n("Folder Already Exists"), (*it).uSource, (*it).uDest, options, newPath, (*it).size, destsize, (*it).ctime, destctime, (*it).mtime, destmtime); if (m_reportTimer) { m_reportTimer->start(REPORT_TIMEOUT); } switch (r) { case Result_Cancel: q->setError(ERR_USER_CANCELED); q->emitResult(); return; case Result_AutoRename: m_bAutoRenameDirs = true; // fall through case Result_Rename: { QUrl newUrl((*it).uDest); newUrl.setPath(newPath, QUrl::DecodedMode); renameDirectory(it, newUrl); } break; case Result_AutoSkip: m_bAutoSkipDirs = true; // fall through case Result_Skip: m_skipList.append(existingDest); skip((*it).uSource, true); // Move on to next dir dirs.erase(it); m_processedDirs++; break; case Result_Overwrite: m_overwriteList.insert(existingDest); emit q->copyingDone(q, (*it).uSource, finalDestUrl((*it).uSource, (*it).uDest), (*it).mtime, true /* directory */, false /* renamed */); // Move on to next dir dirs.erase(it); m_processedDirs++; break; case Result_OverwriteAll: m_bOverwriteAllDirs = true; emit q->copyingDone(q, (*it).uSource, finalDestUrl((*it).uSource, (*it).uDest), (*it).mtime, true /* directory */, false /* renamed */); // Move on to next dir dirs.erase(it); m_processedDirs++; break; default: Q_ASSERT(0); } state = STATE_CREATING_DIRS; //emit processedAmount( this, KJob::Directories, m_processedDirs ); createNextDir(); } void CopyJobPrivate::createNextDir() { Q_Q(CopyJob); QUrl udir; if (!dirs.isEmpty()) { // Take first dir to create out of list QList::Iterator it = dirs.begin(); // Is this URL on the skip list or the overwrite list ? while (it != dirs.end() && udir.isEmpty()) { const QString dir = (*it).uDest.path(); if (shouldSkip(dir)) { it = dirs.erase(it); } else { udir = (*it).uDest; } } } if (!udir.isEmpty()) { // any dir to create, finally ? // Create the directory - with default permissions so that we can put files into it // TODO : change permissions once all is finished; but for stuff coming from CDROM it sucks... KIO::SimpleJob *newjob = KIO::mkdir(udir, -1); newjob->setParentJob(q); Scheduler::setJobPriority(newjob, 1); if (shouldOverwriteFile(udir.path())) { // if we are overwriting an existing file or symlink newjob->addMetaData(QStringLiteral("overwrite"), QStringLiteral("true")); } m_currentDestURL = udir; m_bURLDirty = true; q->addSubjob(newjob); return; } else { // we have finished creating dirs q->setProcessedAmount(KJob::Directories, m_processedDirs); // make sure final number appears if (m_mode == CopyJob::Move) { // Now we know which dirs hold the files we're going to delete. // To speed things up and prevent double-notification, we disable KDirWatch // on those dirs temporarily (using KDirWatch::self, that's the instanced // used by e.g. kdirlister). for (QSet::const_iterator it = m_parentDirs.constBegin(); it != m_parentDirs.constEnd(); ++it) { KDirWatch::self()->stopDirScan(*it); } } state = STATE_COPYING_FILES; m_processedFiles++; // Ralf wants it to start at 1, not 0 copyNextFile(); } } void CopyJobPrivate::slotResultCopyingFiles(KJob *job) { Q_Q(CopyJob); // The file we were trying to copy: QList::Iterator it = files.begin(); if (job->error()) { // Should we skip automatically ? if (m_bAutoSkipFiles) { skip((*it).uSource, false); m_fileProcessedSize = (*it).size; files.erase(it); // Move on to next file } else { m_conflictError = job->error(); // save for later // Existing dest ? if ((m_conflictError == ERR_FILE_ALREADY_EXIST) || (m_conflictError == ERR_DIR_ALREADY_EXIST) || (m_conflictError == ERR_IDENTICAL_FILES)) { if (m_bAutoRenameFiles) { QUrl destDirectory = (*it).uDest.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); const QString newName = KIO::suggestName(destDirectory, (*it).uDest.fileName()); QUrl newDest(destDirectory); newDest.setPath(concatPaths(newDest.path(), newName)); emit q->renamed(q, (*it).uDest, newDest); // for e.g. kpropsdlg (*it).uDest = newDest; QList files; files.append(*it); emit q->aboutToCreate(q, files); } else { if (!q->uiDelegateExtension()) { q->Job::slotResult(job); // will set the error and emit result(this) return; } q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); // We need to stat the existing file, to get its last-modification time QUrl existingFile((*it).uDest); SimpleJob *newJob = KIO::stat(existingFile, StatJob::DestinationSide, 2, KIO::HideProgressInfo); Scheduler::setJobPriority(newJob, 1); qCDebug(KIO_COPYJOB_DEBUG) << "KIO::stat for resolving conflict on" << existingFile; state = STATE_CONFLICT_COPYING_FILES; q->addSubjob(newJob); return; // Don't move to next file yet ! } } else { if (m_bCurrentOperationIsLink && qobject_cast(job)) { // Very special case, see a few lines below // We are deleting the source of a symlink we successfully moved... ignore error m_fileProcessedSize = (*it).size; files.erase(it); } else { if (!q->uiDelegateExtension()) { q->Job::slotResult(job); // will set the error and emit result(this) return; } // Go directly to the conflict resolution, there is nothing to stat slotResultErrorCopyingFiles(job); return; } } } } else { // no error // Special case for moving links. That operation needs two jobs, unlike others. if (m_bCurrentOperationIsLink && m_mode == CopyJob::Move && !qobject_cast(job) // Deleting source not already done ) { q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); // The only problem with this trick is that the error handling for this del operation // is not going to be right... see 'Very special case' above. KIO::Job *newjob = KIO::del((*it).uSource, HideProgressInfo); newjob->setParentJob(q); q->addSubjob(newjob); return; // Don't move to next file yet ! } const QUrl finalUrl = finalDestUrl((*it).uSource, (*it).uDest); if (m_bCurrentOperationIsLink) { QString target = (m_mode == CopyJob::Link ? (*it).uSource.path() : (*it).linkDest); //required for the undo feature emit q->copyingLinkDone(q, (*it).uSource, target, finalUrl); } else { //required for the undo feature emit q->copyingDone(q, (*it).uSource, finalUrl, (*it).mtime, false, false); if (m_mode == CopyJob::Move) { org::kde::KDirNotify::emitFileMoved((*it).uSource, finalUrl); } m_successSrcList.append((*it).uSource); if (m_freeSpace != (KIO::filesize_t) - 1 && (*it).size != (KIO::filesize_t) - 1) { m_freeSpace -= (*it).size; } } // remove from list, to move on to next file files.erase(it); } m_processedFiles++; // clear processed size for last file and add it to overall processed size m_processedSize += m_fileProcessedSize; m_fileProcessedSize = 0; qCDebug(KIO_COPYJOB_DEBUG) << files.count() << "files remaining"; // Merge metadata from subjob KIO::Job *kiojob = dynamic_cast(job); Q_ASSERT(kiojob); m_incomingMetaData += kiojob->metaData(); q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); // We should have only one job at a time ... copyNextFile(); } void CopyJobPrivate::slotResultErrorCopyingFiles(KJob *job) { Q_Q(CopyJob); // We come here after a conflict has been detected and we've stated the existing file // The file we were trying to create: QList::Iterator it = files.begin(); RenameDialog_Result res; QString newPath; if (m_reportTimer) { m_reportTimer->stop(); } if ((m_conflictError == ERR_FILE_ALREADY_EXIST) || (m_conflictError == ERR_DIR_ALREADY_EXIST) || (m_conflictError == ERR_IDENTICAL_FILES)) { // Its modification time: const UDSEntry entry = static_cast(job)->statResult(); const QDateTime destmtime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1)); const QDateTime destctime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1)); const KIO::filesize_t destsize = entry.numberValue(KIO::UDSEntry::UDS_SIZE); const QString linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST); // Offer overwrite only if the existing thing is a file // If src==dest, use "overwrite-itself" RenameDialog_Options options; bool isDir = true; if (m_conflictError == ERR_DIR_ALREADY_EXIST) { options = RenameDialog_IsDirectory; } else { if ((*it).uSource == (*it).uDest || ((*it).uSource.scheme() == (*it).uDest.scheme() && (*it).uSource.adjusted(QUrl::StripTrailingSlash).path() == linkDest)) { options = RenameDialog_OverwriteItself; } else { options = RenameDialog_Overwrite; } isDir = false; } if (!m_bSingleFileCopy) { options = RenameDialog_Options(options | RenameDialog_MultipleItems | RenameDialog_Skip); } res = q->uiDelegateExtension()->askFileRename(q, !isDir ? i18n("File Already Exists") : i18n("Already Exists as Folder"), (*it).uSource, (*it).uDest, options, newPath, (*it).size, destsize, (*it).ctime, destctime, (*it).mtime, destmtime); } else { if (job->error() == ERR_USER_CANCELED) { res = Result_Cancel; } else if (!q->uiDelegateExtension()) { q->Job::slotResult(job); // will set the error and emit result(this) return; } else { SkipDialog_Options options; if (files.count() > 1) { options |= SkipDialog_MultipleItems; } res = q->uiDelegateExtension()->askSkip(q, options, job->errorString()); } } if (m_reportTimer) { m_reportTimer->start(REPORT_TIMEOUT); } q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); switch (res) { case Result_Cancel: q->setError(ERR_USER_CANCELED); q->emitResult(); return; case Result_AutoRename: m_bAutoRenameFiles = true; // fall through Q_FALLTHROUGH(); case Result_Rename: { QUrl newUrl((*it).uDest); newUrl.setPath(newPath); emit q->renamed(q, (*it).uDest, newUrl); // for e.g. kpropsdlg (*it).uDest = newUrl; QList files; files.append(*it); emit q->aboutToCreate(q, files); } break; case Result_AutoSkip: m_bAutoSkipFiles = true; // fall through Q_FALLTHROUGH(); case Result_Skip: // Move on to next file skip((*it).uSource, false); m_processedSize += (*it).size; files.erase(it); m_processedFiles++; break; case Result_OverwriteAll: m_bOverwriteAllFiles = true; break; case Result_Overwrite: // Add to overwrite list, so that copyNextFile knows to overwrite m_overwriteList.insert((*it).uDest.path()); break; case Result_Retry: // Do nothing, copy file again break; default: Q_ASSERT(0); } state = STATE_COPYING_FILES; copyNextFile(); } KIO::Job *CopyJobPrivate::linkNextFile(const QUrl &uSource, const QUrl &uDest, JobFlags flags) { qCDebug(KIO_COPYJOB_DEBUG) << "Linking"; if ( (uSource.scheme() == uDest.scheme()) && (uSource.host() == uDest.host()) && (uSource.port() == uDest.port()) && (uSource.userName() == uDest.userName()) && (uSource.password() == uDest.password())) { // This is the case of creating a real symlink KIO::SimpleJob *newJob = KIO::symlink(uSource.path(), uDest, flags | HideProgressInfo /*no GUI*/); newJob->setParentJob(q_func()); Scheduler::setJobPriority(newJob, 1); qCDebug(KIO_COPYJOB_DEBUG) << "Linking target=" << uSource.path() << "link=" << uDest; //emit linking( this, uSource.path(), uDest ); m_bCurrentOperationIsLink = true; m_currentSrcURL = uSource; m_currentDestURL = uDest; m_bURLDirty = true; //Observer::self()->slotCopying( this, uSource, uDest ); // should be slotLinking perhaps return newJob; } else { Q_Q(CopyJob); qCDebug(KIO_COPYJOB_DEBUG) << "Linking URL=" << uSource << "link=" << uDest; if (uDest.isLocalFile()) { // if the source is a devices url, handle it a littlebit special QString path = uDest.toLocalFile(); qCDebug(KIO_COPYJOB_DEBUG) << "path=" << path; QFile f(path); if (f.open(QIODevice::ReadWrite)) { f.close(); KDesktopFile desktopFile(path); KConfigGroup config = desktopFile.desktopGroup(); QUrl url = uSource; url.setPassword(QString()); config.writePathEntry("URL", url.toString()); config.writeEntry("Name", url.toString()); config.writeEntry("Type", QStringLiteral("Link")); QString protocol = uSource.scheme(); if (protocol == QLatin1String("ftp")) { config.writeEntry("Icon", QStringLiteral("folder-remote")); } else if (protocol == QLatin1String("http")) { config.writeEntry("Icon", QStringLiteral("text-html")); } else if (protocol == QLatin1String("info")) { config.writeEntry("Icon", QStringLiteral("text-x-texinfo")); } else if (protocol == QLatin1String("mailto")) { // sven: config.writeEntry("Icon", QStringLiteral("internet-mail")); // added mailto: support } else if (protocol == QLatin1String("trash") && url.path().length() <= 1) { // trash:/ link config.writeEntry("Name", i18n("Trash")); config.writeEntry("Icon", QStringLiteral("user-trash-full")); config.writeEntry("EmptyIcon", QStringLiteral("user-trash")); } else { config.writeEntry("Icon", QStringLiteral("unknown")); } config.sync(); files.erase(files.begin()); // done with this one, move on m_processedFiles++; //emit processedAmount( this, KJob::Files, m_processedFiles ); copyNextFile(); return nullptr; } else { qCDebug(KIO_COPYJOB_DEBUG) << "ERR_CANNOT_OPEN_FOR_WRITING"; q->setError(ERR_CANNOT_OPEN_FOR_WRITING); q->setErrorText(uDest.toLocalFile()); q->emitResult(); return nullptr; } } else { // Todo: not show "link" on remote dirs if the src urls are not from the same protocol+host+... q->setError(ERR_CANNOT_SYMLINK); q->setErrorText(uDest.toDisplayString()); q->emitResult(); return nullptr; } } } void CopyJobPrivate::copyNextFile() { Q_Q(CopyJob); bool bCopyFile = false; qCDebug(KIO_COPYJOB_DEBUG); // Take the first file in the list QList::Iterator it = files.begin(); // Is this URL on the skip list ? while (it != files.end() && !bCopyFile) { const QString destFile = (*it).uDest.path(); bCopyFile = !shouldSkip(destFile); if (!bCopyFile) { it = files.erase(it); } } if (bCopyFile) { // any file to create, finally ? qCDebug(KIO_COPYJOB_DEBUG)<<"preparing to copy"<<(*it).uSource<<(*it).size<setError(ERR_DISK_FULL); q->emitResult(); return; } //TODO check if dst mount is msdos and (*it).size exceeds it's limits } const QUrl &uSource = (*it).uSource; const QUrl &uDest = (*it).uDest; // Do we set overwrite ? bool bOverwrite; const QString destFile = uDest.path(); qCDebug(KIO_COPYJOB_DEBUG) << "copying" << destFile; if (uDest == uSource) { bOverwrite = false; } else { bOverwrite = shouldOverwriteFile(destFile); } // If source isn't local and target is local, we ignore the original permissions // Otherwise, files downloaded from HTTP end up with -r--r--r-- const bool remoteSource = !KProtocolManager::supportsListing(uSource) || uSource.scheme() == QLatin1String("trash"); int permissions = (*it).permissions; if (m_defaultPermissions || (remoteSource && uDest.isLocalFile())) { permissions = -1; } const JobFlags flags = bOverwrite ? Overwrite : DefaultFlags; m_bCurrentOperationIsLink = false; KIO::Job *newjob = nullptr; if (m_mode == CopyJob::Link) { // User requested that a symlink be made newjob = linkNextFile(uSource, uDest, flags); if (!newjob) { return; } } else if (!(*it).linkDest.isEmpty() && (uSource.scheme() == uDest.scheme()) && (uSource.host() == uDest.host()) && (uSource.port() == uDest.port()) && (uSource.userName() == uDest.userName()) && (uSource.password() == uDest.password())) // Copying a symlink - only on the same protocol/host/etc. (#5601, downloading an FTP file through its link), { KIO::SimpleJob *newJob = KIO::symlink((*it).linkDest, uDest, flags | HideProgressInfo /*no GUI*/); newJob->setParentJob(q); Scheduler::setJobPriority(newJob, 1); newjob = newJob; qCDebug(KIO_COPYJOB_DEBUG) << "Linking target=" << (*it).linkDest << "link=" << uDest; m_currentSrcURL = QUrl::fromUserInput((*it).linkDest); m_currentDestURL = uDest; m_bURLDirty = true; //emit linking( this, (*it).linkDest, uDest ); //Observer::self()->slotCopying( this, m_currentSrcURL, uDest ); // should be slotLinking perhaps m_bCurrentOperationIsLink = true; // NOTE: if we are moving stuff, the deletion of the source will be done in slotResultCopyingFiles } else if (m_mode == CopyJob::Move) { // Moving a file KIO::FileCopyJob *moveJob = KIO::file_move(uSource, uDest, permissions, flags | HideProgressInfo/*no GUI*/); moveJob->setParentJob(q); moveJob->setSourceSize((*it).size); moveJob->setModificationTime((*it).mtime); // #55804 newjob = moveJob; qCDebug(KIO_COPYJOB_DEBUG) << "Moving" << uSource << "to" << uDest; //emit moving( this, uSource, uDest ); m_currentSrcURL = uSource; m_currentDestURL = uDest; m_bURLDirty = true; //Observer::self()->slotMoving( this, uSource, uDest ); } else { // Copying a file KIO::FileCopyJob *copyJob = KIO::file_copy(uSource, uDest, permissions, flags | HideProgressInfo/*no GUI*/); copyJob->setParentJob(q); // in case of rename dialog copyJob->setSourceSize((*it).size); copyJob->setModificationTime((*it).mtime); newjob = copyJob; qCDebug(KIO_COPYJOB_DEBUG) << "Copying" << uSource << "to" << uDest; m_currentSrcURL = uSource; m_currentDestURL = uDest; m_bURLDirty = true; } q->addSubjob(newjob); q->connect(newjob, SIGNAL(processedSize(KJob*,qulonglong)), SLOT(slotProcessedSize(KJob*,qulonglong))); q->connect(newjob, SIGNAL(totalSize(KJob*,qulonglong)), SLOT(slotTotalSize(KJob*,qulonglong))); } else { // We're done qCDebug(KIO_COPYJOB_DEBUG) << "copyNextFile finished"; deleteNextDir(); } } void CopyJobPrivate::deleteNextDir() { Q_Q(CopyJob); if (m_mode == CopyJob::Move && !dirsToRemove.isEmpty()) { // some dirs to delete ? state = STATE_DELETING_DIRS; m_bURLDirty = true; // Take first dir to delete out of list - last ones first ! QList::Iterator it = --dirsToRemove.end(); SimpleJob *job = KIO::rmdir(*it); job->setParentJob(q); Scheduler::setJobPriority(job, 1); dirsToRemove.erase(it); q->addSubjob(job); } else { // This step is done, move on state = STATE_SETTING_DIR_ATTRIBUTES; m_directoriesCopiedIterator = m_directoriesCopied.constBegin(); setNextDirAttribute(); } } void CopyJobPrivate::setNextDirAttribute() { Q_Q(CopyJob); while (m_directoriesCopiedIterator != m_directoriesCopied.constEnd() && !(*m_directoriesCopiedIterator).mtime.isValid()) { ++m_directoriesCopiedIterator; } if (m_directoriesCopiedIterator != m_directoriesCopied.constEnd()) { const QUrl url = (*m_directoriesCopiedIterator).uDest; const QDateTime dt = (*m_directoriesCopiedIterator).mtime; ++m_directoriesCopiedIterator; KIO::SimpleJob *job = KIO::setModificationTime(url, dt); job->setParentJob(q); Scheduler::setJobPriority(job, 1); q->addSubjob(job); #if 0 // ifdef Q_OS_UNIX // TODO: can be removed now. Or reintroduced as a fast path for local files // if launching even more jobs as done above is a performance problem. // QLinkedList::const_iterator it = m_directoriesCopied.constBegin(); for (; it != m_directoriesCopied.constEnd(); ++it) { const QUrl &url = (*it).uDest; if (url.isLocalFile() && (*it).mtime != (time_t) - 1) { QT_STATBUF statbuf; if (QT_LSTAT(url.path(), &statbuf) == 0) { struct utimbuf utbuf; utbuf.actime = statbuf.st_atime; // access time, unchanged utbuf.modtime = (*it).mtime; // modification time utime(path, &utbuf); } } } m_directoriesCopied.clear(); // but then we need to jump to the else part below. Maybe with a recursive call? #endif } else { if (m_reportTimer) { m_reportTimer->stop(); } --m_processedFiles; // undo the "start at 1" hack slotReport(); // display final numbers, important if progress dialog stays up q->emitResult(); } } void CopyJob::emitResult() { Q_D(CopyJob); // Before we go, tell the world about the changes that were made. // Even if some error made us abort midway, we might still have done // part of the job so we better update the views! (#118583) if (!d->m_bOnlyRenames) { // If only renaming happened, KDirNotify::FileRenamed was emitted by the rename jobs QUrl url(d->m_globalDest); if (d->m_globalDestinationState != DEST_IS_DIR || d->m_asMethod) { url = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); } qCDebug(KIO_COPYJOB_DEBUG) << "KDirNotify'ing FilesAdded" << url; org::kde::KDirNotify::emitFilesAdded(url); if (d->m_mode == CopyJob::Move && !d->m_successSrcList.isEmpty()) { qCDebug(KIO_COPYJOB_DEBUG) << "KDirNotify'ing FilesRemoved" << d->m_successSrcList; org::kde::KDirNotify::emitFilesRemoved(d->m_successSrcList); } } // Re-enable watching on the dirs that held the deleted/moved files if (d->m_mode == CopyJob::Move) { for (QSet::const_iterator it = d->m_parentDirs.constBegin(); it != d->m_parentDirs.constEnd(); ++it) { KDirWatch::self()->restartDirScan(*it); } } Job::emitResult(); } void CopyJobPrivate::slotProcessedSize(KJob *, qulonglong data_size) { Q_Q(CopyJob); qCDebug(KIO_COPYJOB_DEBUG) << data_size; m_fileProcessedSize = data_size; q->setProcessedAmount(KJob::Bytes, m_processedSize + m_fileProcessedSize); if (m_processedSize + m_fileProcessedSize > m_totalSize) { // Example: download any attachment from bugs.kde.org m_totalSize = m_processedSize + m_fileProcessedSize; qCDebug(KIO_COPYJOB_DEBUG) << "Adjusting m_totalSize to" << m_totalSize; q->setTotalAmount(KJob::Bytes, m_totalSize); // safety } qCDebug(KIO_COPYJOB_DEBUG) << "emit processedSize" << (unsigned long) (m_processedSize + m_fileProcessedSize); q->setProcessedAmount(KJob::Bytes, m_processedSize + m_fileProcessedSize); } void CopyJobPrivate::slotTotalSize(KJob *, qulonglong size) { Q_Q(CopyJob); qCDebug(KIO_COPYJOB_DEBUG) << size; // Special case for copying a single file // This is because some protocols don't implement stat properly // (e.g. HTTP), and don't give us a size in some cases (redirection) // so we'd rather rely on the size given for the transfer if (m_bSingleFileCopy && size != m_totalSize) { qCDebug(KIO_COPYJOB_DEBUG) << "slotTotalSize: updating totalsize to" << size; m_totalSize = size; q->setTotalAmount(KJob::Bytes, size); } } void CopyJobPrivate::slotResultDeletingDirs(KJob *job) { Q_Q(CopyJob); if (job->error()) { // Couldn't remove directory. Well, perhaps it's not empty // because the user pressed Skip for a given file in it. // Let's not display "Could not remove dir ..." for each of those dir ! } else { m_successSrcList.append(static_cast(job)->url()); } q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); deleteNextDir(); } void CopyJobPrivate::slotResultSettingDirAttributes(KJob *job) { Q_Q(CopyJob); if (job->error()) { // Couldn't set directory attributes. Ignore the error, it can happen // with inferior file systems like VFAT. // Let's not display warnings for each dir like "cp -a" does. } q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); setNextDirAttribute(); } // We were trying to do a direct renaming, before even stat'ing void CopyJobPrivate::slotResultRenaming(KJob *job) { Q_Q(CopyJob); int err = job->error(); const QString errText = job->errorText(); // Merge metadata from subjob KIO::Job *kiojob = dynamic_cast(job); Q_ASSERT(kiojob); m_incomingMetaData += kiojob->metaData(); q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); // Determine dest again QUrl dest = m_dest; if (destinationState == DEST_IS_DIR && !m_asMethod) { dest = addPathToUrl(dest, m_currentSrcURL.fileName()); } if (err) { // Direct renaming didn't work. Try renaming to a temp name, // this can help e.g. when renaming 'a' to 'A' on a VFAT partition. // In that case it's the _same_ dir, we don't want to copy+del (data loss!) // TODO: replace all this code with QFile::rename once // https://codereview.qt-project.org/44823 is in if ((err == ERR_FILE_ALREADY_EXIST || err == ERR_DIR_ALREADY_EXIST || err == ERR_IDENTICAL_FILES) && m_currentSrcURL.isLocalFile() && dest.isLocalFile()) { const QString _src(m_currentSrcURL.adjusted(QUrl::StripTrailingSlash).toLocalFile()); const QString _dest(dest.adjusted(QUrl::StripTrailingSlash).toLocalFile()); if (_src != _dest && QString::compare(_src, _dest, Qt::CaseInsensitive) == 0) { qCDebug(KIO_COPYJOB_DEBUG) << "Couldn't rename directly, dest already exists. Detected special case of lower/uppercase renaming in same dir, try with 2 rename calls"; const QString srcDir = QFileInfo(_src).absolutePath(); - QTemporaryFile tmpFile(srcDir + "kio_XXXXXX"); + QTemporaryFile tmpFile(srcDir + QLatin1String("kio_XXXXXX")); const bool openOk = tmpFile.open(); if (!openOk) { qCWarning(KIO_CORE) << "Couldn't open temp file in" << srcDir; } else { const QString _tmp(tmpFile.fileName()); tmpFile.close(); tmpFile.remove(); qCDebug(KIO_COPYJOB_DEBUG) << "QTemporaryFile using" << _tmp << "as intermediary"; if (QFile::rename(_src, _tmp)) { qCDebug(KIO_COPYJOB_DEBUG) << "Renaming" << _src << "to" << _tmp << "succeeded"; if (!QFile::exists(_dest) && QFile::rename(_tmp, _dest)) { err = 0; org::kde::KDirNotify::emitFileRenamed(m_currentSrcURL, dest); } else { qCDebug(KIO_COPYJOB_DEBUG) << "Didn't manage to rename" << _tmp << "to" << _dest << ", reverting"; // Revert back to original name! if (!QFile::rename(_tmp, _src)) { qCWarning(KIO_CORE) << "Couldn't rename" << _tmp << "back to" << _src << '!'; // Severe error, abort q->Job::slotResult(job); // will set the error and emit result(this) return; } } } else { qCDebug(KIO_COPYJOB_DEBUG) << "mv" << _src << _tmp << "failed:" << strerror(errno); } } } } } if (err) { // This code is similar to CopyJobPrivate::slotResultErrorCopyingFiles // but here it's about the base src url being moved/renamed // (m_currentSrcURL) and its dest (m_dest), not about a single file. // It also means we already stated the dest, here. // On the other hand we haven't stated the src yet (we skipped doing it // to save time, since it's not necessary to rename directly!)... // Existing dest? if (err == ERR_DIR_ALREADY_EXIST || err == ERR_FILE_ALREADY_EXIST || err == ERR_IDENTICAL_FILES) { // Should we skip automatically ? bool isDir = (err == ERR_DIR_ALREADY_EXIST); // ## technically, isDir means "source is dir", not "dest is dir" ####### if ((isDir && m_bAutoSkipDirs) || (!isDir && m_bAutoSkipFiles)) { // Move on to next source url skipSrc(isDir); return; } else if ((isDir && m_bOverwriteAllDirs) || (!isDir && m_bOverwriteAllFiles)) { ; // nothing to do, stat+copy+del will overwrite } else if ((isDir && m_bAutoRenameDirs) || (!isDir && m_bAutoRenameFiles)) { QUrl destDirectory = m_currentDestURL.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); // m_currendDestURL includes filename const QString newName = KIO::suggestName(destDirectory, m_currentDestURL.fileName()); m_dest = destDirectory; m_dest.setPath(concatPaths(m_dest.path(), newName)); KIO::Job *job = KIO::stat(m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo); state = STATE_STATING; destinationState = DEST_NOT_STATED; q->addSubjob(job); return; } else if (q->uiDelegateExtension()) { QString newPath; // we lack mtime info for both the src (not stated) // and the dest (stated but this info wasn't stored) // Let's do it for local files, at least KIO::filesize_t sizeSrc = (KIO::filesize_t) - 1; KIO::filesize_t sizeDest = (KIO::filesize_t) - 1; QDateTime ctimeSrc; QDateTime ctimeDest; QDateTime mtimeSrc; QDateTime mtimeDest; bool destIsDir = err == ERR_DIR_ALREADY_EXIST; // ## TODO we need to stat the source using KIO::stat // so that this code is properly network-transparent. if (m_currentSrcURL.isLocalFile()) { QFileInfo info(m_currentSrcURL.toLocalFile()); if (info.exists()) { sizeSrc = info.size(); ctimeSrc = info.created(); mtimeSrc = info.lastModified(); isDir = info.isDir(); } } if (dest.isLocalFile()) { QFileInfo destInfo(dest.toLocalFile()); if (destInfo.exists()) { sizeDest = destInfo.size(); ctimeDest = destInfo.created(); mtimeDest = destInfo.lastModified(); destIsDir = destInfo.isDir(); } } // If src==dest, use "overwrite-itself" RenameDialog_Options options = (m_currentSrcURL == dest) ? RenameDialog_OverwriteItself : RenameDialog_Overwrite; if (!isDir && destIsDir) { // We can't overwrite a dir with a file. options = RenameDialog_Options(); } if (m_srcList.count() > 1) { options |= RenameDialog_Options(RenameDialog_MultipleItems | RenameDialog_Skip); } if (destIsDir) { options |= RenameDialog_IsDirectory; } if (m_reportTimer) { m_reportTimer->stop(); } RenameDialog_Result r = q->uiDelegateExtension()->askFileRename( q, err != ERR_DIR_ALREADY_EXIST ? i18n("File Already Exists") : i18n("Already Exists as Folder"), m_currentSrcURL, dest, options, newPath, sizeSrc, sizeDest, ctimeSrc, ctimeDest, mtimeSrc, mtimeDest); if (m_reportTimer) { m_reportTimer->start(REPORT_TIMEOUT); } switch (r) { case Result_Cancel: { q->setError(ERR_USER_CANCELED); q->emitResult(); return; } case Result_AutoRename: if (isDir) { m_bAutoRenameDirs = true; } else { m_bAutoRenameFiles = true; } // fall through Q_FALLTHROUGH(); case Result_Rename: { // Set m_dest to the chosen destination // This is only for this src url; the next one will revert to m_globalDest m_dest.setPath(newPath); KIO::Job *job = KIO::stat(m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo); state = STATE_STATING; destinationState = DEST_NOT_STATED; q->addSubjob(job); return; } case Result_AutoSkip: if (isDir) { m_bAutoSkipDirs = true; } else { m_bAutoSkipFiles = true; } // fall through Q_FALLTHROUGH(); case Result_Skip: // Move on to next url skipSrc(isDir); return; case Result_OverwriteAll: if (destIsDir) { m_bOverwriteAllDirs = true; } else { m_bOverwriteAllFiles = true; } break; case Result_Overwrite: // Add to overwrite list // Note that we add dest, not m_dest. // This ensures that when moving several urls into a dir (m_dest), // we only overwrite for the current one, not for all. // When renaming a single file (m_asMethod), it makes no difference. qCDebug(KIO_COPYJOB_DEBUG) << "adding to overwrite list: " << dest.path(); m_overwriteList.insert(dest.path()); break; default: //Q_ASSERT( 0 ); break; } } else if (err != KIO::ERR_UNSUPPORTED_ACTION) { // Dest already exists, and job is not interactive -> abort with error q->setError(err); q->setErrorText(errText); q->emitResult(); return; } } else if (err != KIO::ERR_UNSUPPORTED_ACTION) { qCDebug(KIO_COPYJOB_DEBUG) << "Couldn't rename" << m_currentSrcURL << "to" << dest << ", aborting"; q->setError(err); q->setErrorText(errText); q->emitResult(); return; } qCDebug(KIO_COPYJOB_DEBUG) << "Couldn't rename" << m_currentSrcURL << "to" << dest << ", reverting to normal way, starting with stat"; qCDebug(KIO_COPYJOB_DEBUG) << "KIO::stat on" << m_currentSrcURL; KIO::Job *job = KIO::stat(m_currentSrcURL, StatJob::SourceSide, 2, KIO::HideProgressInfo); state = STATE_STATING; q->addSubjob(job); m_bOnlyRenames = false; } else { qCDebug(KIO_COPYJOB_DEBUG) << "Renaming succeeded, move on"; ++m_processedFiles; emit q->copyingDone(q, *m_currentStatSrc, finalDestUrl(*m_currentStatSrc, dest), QDateTime() /*mtime unknown, and not needed*/, true, true); m_successSrcList.append(*m_currentStatSrc); statNextSrc(); } } void CopyJob::slotResult(KJob *job) { Q_D(CopyJob); qCDebug(KIO_COPYJOB_DEBUG) << "d->state=" << (int) d->state; // In each case, what we have to do is : // 1 - check for errors and treat them // 2 - removeSubjob(job); // 3 - decide what to do next switch (d->state) { case STATE_STATING: // We were trying to stat a src url or the dest d->slotResultStating(job); break; case STATE_RENAMING: { // We were trying to do a direct renaming, before even stat'ing d->slotResultRenaming(job); break; } case STATE_LISTING: // recursive listing finished qCDebug(KIO_COPYJOB_DEBUG) << "totalSize:" << (unsigned int) d->m_totalSize << "files:" << d->files.count() << "d->dirs:" << d->dirs.count(); // Was there an error ? if (job->error()) { Job::slotResult(job); // will set the error and emit result(this) return; } removeSubjob(job); Q_ASSERT(!hasSubjobs()); d->statNextSrc(); break; case STATE_CREATING_DIRS: d->slotResultCreatingDirs(job); break; case STATE_CONFLICT_CREATING_DIRS: d->slotResultConflictCreatingDirs(job); break; case STATE_COPYING_FILES: d->slotResultCopyingFiles(job); break; case STATE_CONFLICT_COPYING_FILES: d->slotResultErrorCopyingFiles(job); break; case STATE_DELETING_DIRS: d->slotResultDeletingDirs(job); break; case STATE_SETTING_DIR_ATTRIBUTES: d->slotResultSettingDirAttributes(job); break; default: Q_ASSERT(0); } } void KIO::CopyJob::setDefaultPermissions(bool b) { d_func()->m_defaultPermissions = b; } KIO::CopyJob::CopyMode KIO::CopyJob::operationMode() const { return d_func()->m_mode; } void KIO::CopyJob::setAutoSkip(bool autoSkip) { d_func()->m_bAutoSkipFiles = autoSkip; d_func()->m_bAutoSkipDirs = autoSkip; } void KIO::CopyJob::setAutoRename(bool autoRename) { d_func()->m_bAutoRenameFiles = autoRename; d_func()->m_bAutoRenameDirs = autoRename; } void KIO::CopyJob::setWriteIntoExistingDirectories(bool overwriteAll) // #65926 { d_func()->m_bOverwriteAllDirs = overwriteAll; } CopyJob *KIO::copy(const QUrl &src, const QUrl &dest, JobFlags flags) { qCDebug(KIO_COPYJOB_DEBUG) << "src=" << src << "dest=" << dest; QList srcList; srcList.append(src); return CopyJobPrivate::newJob(srcList, dest, CopyJob::Copy, false, flags); } CopyJob *KIO::copyAs(const QUrl &src, const QUrl &dest, JobFlags flags) { qCDebug(KIO_COPYJOB_DEBUG) << "src=" << src << "dest=" << dest; QList srcList; srcList.append(src); return CopyJobPrivate::newJob(srcList, dest, CopyJob::Copy, true, flags); } CopyJob *KIO::copy(const QList &src, const QUrl &dest, JobFlags flags) { qCDebug(KIO_COPYJOB_DEBUG) << src << dest; return CopyJobPrivate::newJob(src, dest, CopyJob::Copy, false, flags); } CopyJob *KIO::move(const QUrl &src, const QUrl &dest, JobFlags flags) { qCDebug(KIO_COPYJOB_DEBUG) << src << dest; QList srcList; srcList.append(src); CopyJob *job = CopyJobPrivate::newJob(srcList, dest, CopyJob::Move, false, flags); if (job->uiDelegateExtension()) { job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::UpdateContent); } return job; } CopyJob *KIO::moveAs(const QUrl &src, const QUrl &dest, JobFlags flags) { qCDebug(KIO_COPYJOB_DEBUG) << src << dest; QList srcList; srcList.append(src); CopyJob *job = CopyJobPrivate::newJob(srcList, dest, CopyJob::Move, true, flags); if (job->uiDelegateExtension()) { job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::UpdateContent); } return job; } CopyJob *KIO::move(const QList &src, const QUrl &dest, JobFlags flags) { qCDebug(KIO_COPYJOB_DEBUG) << src << dest; CopyJob *job = CopyJobPrivate::newJob(src, dest, CopyJob::Move, false, flags); if (job->uiDelegateExtension()) { job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::UpdateContent); } return job; } CopyJob *KIO::link(const QUrl &src, const QUrl &destDir, JobFlags flags) { QList srcList; srcList.append(src); return CopyJobPrivate::newJob(srcList, destDir, CopyJob::Link, false, flags); } CopyJob *KIO::link(const QList &srcList, const QUrl &destDir, JobFlags flags) { return CopyJobPrivate::newJob(srcList, destDir, CopyJob::Link, false, flags); } CopyJob *KIO::linkAs(const QUrl &src, const QUrl &destDir, JobFlags flags) { QList srcList; srcList.append(src); return CopyJobPrivate::newJob(srcList, destDir, CopyJob::Link, true, flags); } CopyJob *KIO::trash(const QUrl &src, JobFlags flags) { QList srcList; srcList.append(src); return CopyJobPrivate::newJob(srcList, QUrl(QStringLiteral("trash:/")), CopyJob::Move, false, flags); } CopyJob *KIO::trash(const QList &srcList, JobFlags flags) { return CopyJobPrivate::newJob(srcList, QUrl(QStringLiteral("trash:/")), CopyJob::Move, false, flags); } #include "moc_copyjob.cpp" diff --git a/src/core/davjob.cpp b/src/core/davjob.cpp index c4cee0b0..dabf941d 100644 --- a/src/core/davjob.cpp +++ b/src/core/davjob.cpp @@ -1,164 +1,164 @@ // -*- c++ -*- /* This file is part of the KDE libraries Copyright (C) 2002 Jan-Pascal van Best This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "davjob.h" #include #include #include "httpmethod_p.h" #include "jobclasses.h" #include "global.h" #include "job.h" #include "job_p.h" using namespace KIO; /** @internal */ class KIO::DavJobPrivate: public KIO::TransferJobPrivate { public: DavJobPrivate(const QUrl &url) : TransferJobPrivate(url, KIO::CMD_SPECIAL, QByteArray(), QByteArray()) {} QByteArray savedStaticData; QByteArray str_response; QDomDocument m_response; //TransferJob *m_subJob; //bool m_suspended; Q_DECLARE_PUBLIC(DavJob) static inline DavJob *newJob(const QUrl &url, int method, const QString &request, JobFlags flags) { DavJob *job = new DavJob(*new DavJobPrivate(url), method, request); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); if (!(flags & HideProgressInfo)) { KIO::getJobTracker()->registerJob(job); } return job; } }; DavJob::DavJob(DavJobPrivate &dd, int method, const QString &request) : TransferJob(dd) { // We couldn't set the args when calling the parent constructor, // so do it now. Q_D(DavJob); QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly); stream << (int) 7 << d->m_url << method; // Same for static data if (! request.isEmpty()) { d->staticData = "\r\n" + request.toUtf8(); d->staticData.truncate(d->staticData.size() - 1); d->savedStaticData = d->staticData; stream << static_cast(d->staticData.size()); } else { stream << static_cast(-1); } } QDomDocument &DavJob::response() { return d_func()->m_response; } void DavJob::slotData(const QByteArray &data) { Q_D(DavJob); if (d->m_redirectionURL.isEmpty() || !d->m_redirectionURL.isValid() || error()) { unsigned int oldSize = d->str_response.size(); d->str_response.resize(oldSize + data.size()); memcpy(d->str_response.data() + oldSize, data.data(), data.size()); } } void DavJob::slotFinished() { Q_D(DavJob); //qDebug() << d->str_response; if (!d->m_redirectionURL.isEmpty() && d->m_redirectionURL.isValid() && (d->m_command == CMD_SPECIAL)) { QDataStream istream(d->m_packedArgs); int s_cmd, s_method; qint64 s_size; QUrl s_url; istream >> s_cmd; istream >> s_url; istream >> s_method; istream >> s_size; // PROPFIND if ((s_cmd == 7) && (s_method == (int)KIO::DAV_PROPFIND)) { d->m_packedArgs.truncate(0); QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly); stream << (int)7 << d->m_redirectionURL << (int)KIO::DAV_PROPFIND << s_size; } } else if (! d->m_response.setContent(d->str_response, true)) { // An error occurred parsing the XML response QDomElement root = d->m_response.createElementNS(QStringLiteral("DAV:"), QStringLiteral("error-report")); d->m_response.appendChild(root); QDomElement el = d->m_response.createElementNS(QStringLiteral("DAV:"), QStringLiteral("offending-response")); - QDomText textnode = d->m_response.createTextNode(d->str_response); + QDomText textnode = d->m_response.createTextNode(QString::fromUtf8(d->str_response)); el.appendChild(textnode); root.appendChild(el); } //qDebug() << d->m_response.toString(); TransferJob::slotFinished(); d->staticData = d->savedStaticData; // Need to send DAV request to this host too } /* Convenience methods */ DavJob *KIO::davPropFind(const QUrl &url, const QDomDocument &properties, const QString &depth, JobFlags flags) { DavJob *job = DavJobPrivate::newJob(url, (int) KIO::DAV_PROPFIND, properties.toString(), flags); job->addMetaData(QStringLiteral("davDepth"), depth); return job; } DavJob *KIO::davPropPatch(const QUrl &url, const QDomDocument &properties, JobFlags flags) { return DavJobPrivate::newJob(url, (int) KIO::DAV_PROPPATCH, properties.toString(), flags); } DavJob *KIO::davSearch(const QUrl &url, const QString &nsURI, const QString &qName, const QString &query, JobFlags flags) { QDomDocument doc; QDomElement searchrequest = doc.createElementNS(QStringLiteral("DAV:"), QStringLiteral("searchrequest")); QDomElement searchelement = doc.createElementNS(nsURI, qName); QDomText text = doc.createTextNode(query); searchelement.appendChild(text); searchrequest.appendChild(searchelement); doc.appendChild(searchrequest); return DavJobPrivate::newJob(url, KIO::DAV_SEARCH, doc.toString(), flags); } DavJob *KIO::davReport(const QUrl &url, const QString &report, const QString &depth, JobFlags flags) { DavJob *job = DavJobPrivate::newJob(url, (int) KIO::DAV_REPORT, report, flags); job->addMetaData(QStringLiteral("davDepth"), depth); return job; } diff --git a/src/core/desktopexecparser.cpp b/src/core/desktopexecparser.cpp index 3686e305..571cb44c 100644 --- a/src/core/desktopexecparser.cpp +++ b/src/core/desktopexecparser.cpp @@ -1,453 +1,460 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Torben Weis Copyright (C) 2006-2013 David Faure Copyright (C) 2009 Michael Pyne This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "desktopexecparser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 #include "kiocoredebug.h" class KRunMX1 : public KMacroExpanderBase { public: - KRunMX1(const KService &_service) : - KMacroExpanderBase('%'), hasUrls(false), hasSpec(false), service(_service) {} + explicit KRunMX1(const KService &_service) + : KMacroExpanderBase(QLatin1Char('%')) + , hasUrls(false) + , hasSpec(false) + , service(_service) + {} bool hasUrls; bool hasSpec; protected: int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override; private: const KService &service; }; int KRunMX1::expandEscapedMacro(const QString &str, int pos, QStringList &ret) { uint option = str[pos + 1].unicode(); switch (option) { case 'c': - ret << service.name().replace('%', QLatin1String("%%")); + ret << service.name().replace(QLatin1Char('%'), QLatin1String("%%")); break; case 'k': - ret << service.entryPath().replace('%', QLatin1String("%%")); + ret << service.entryPath().replace(QLatin1Char('%'), QLatin1String("%%")); break; case 'i': - ret << QStringLiteral("--icon") << service.icon().replace('%', QLatin1String("%%")); + ret << QStringLiteral("--icon") << service.icon().replace(QLatin1Char('%'), QLatin1String("%%")); break; case 'm': // ret << "-miniicon" << service.icon().replace( '%', "%%" ); qCWarning(KIO_CORE) << "-miniicon isn't supported anymore (service" << service.name() << ')'; break; case 'u': case 'U': hasUrls = true; Q_FALLTHROUGH(); /* fallthrough */ case 'f': case 'F': case 'n': case 'N': case 'd': case 'D': case 'v': hasSpec = true; Q_FALLTHROUGH(); /* fallthrough */ default: return -2; // subst with same and skip } return 2; } class KRunMX2 : public KMacroExpanderBase { public: - KRunMX2(const QList &_urls) : - KMacroExpanderBase('%'), ignFile(false), urls(_urls) {} + explicit KRunMX2(const QList &_urls) + : KMacroExpanderBase(QLatin1Char('%')) + , ignFile(false), + urls(_urls) + {} bool ignFile; protected: int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override; private: void subst(int option, const QUrl &url, QStringList &ret); const QList &urls; }; void KRunMX2::subst(int option, const QUrl &url, QStringList &ret) { switch (option) { case 'u': ret << ((url.isLocalFile() && url.fragment().isNull() && url.query().isNull()) ? QDir::toNativeSeparators(url.toLocalFile()) : url.toString()); break; case 'd': ret << url.adjusted(QUrl::RemoveFilename).path(); break; case 'f': ret << QDir::toNativeSeparators(url.toLocalFile()); break; case 'n': ret << url.fileName(); break; case 'v': if (url.isLocalFile() && QFile::exists(url.toLocalFile())) { ret << KDesktopFile(url.toLocalFile()).desktopGroup().readEntry("Dev"); } break; } return; } int KRunMX2::expandEscapedMacro(const QString &str, int pos, QStringList &ret) { uint option = str[pos + 1].unicode(); switch (option) { case 'f': case 'u': case 'n': case 'd': case 'v': if (urls.isEmpty()) { if (!ignFile) { //qCDebug(KIO_CORE) << "No URLs supplied to single-URL service" << str; } } else if (urls.count() > 1) { qCWarning(KIO_CORE) << urls.count() << "URLs supplied to single-URL service" << str; } else { subst(option, urls.first(), ret); } break; case 'F': case 'U': case 'N': case 'D': option += 'a' - 'A'; for (QList::ConstIterator it = urls.begin(); it != urls.end(); ++it) { subst(option, *it, ret); } break; case '%': ret = QStringList(QStringLiteral("%")); break; default: return -2; // subst with same and skip } return 2; } QStringList KIO::DesktopExecParser::supportedProtocols(const KService &service) { QStringList supportedProtocols = service.property(QStringLiteral("X-KDE-Protocols")).toStringList(); KRunMX1 mx1(service); QString exec = service.exec(); if (mx1.expandMacrosShellQuote(exec) && !mx1.hasUrls) { if (!supportedProtocols.isEmpty()) { qCWarning(KIO_CORE) << service.entryPath() << "contains a X-KDE-Protocols line but doesn't use %u or %U in its Exec line! This is inconsistent."; } return QStringList(); } else { if (supportedProtocols.isEmpty()) { // compat mode: assume KIO if not set and it's a KDE app (or a KDE service) const QStringList categories = service.property(QStringLiteral("Categories")).toStringList(); if (categories.contains(QStringLiteral("KDE")) || !service.isApplication() || service.entryPath().isEmpty() /*temp service*/) { supportedProtocols.append(QStringLiteral("KIO")); } else { // if no KDE app, be a bit over-generic supportedProtocols.append(QStringLiteral("http")); supportedProtocols.append(QStringLiteral("https")); // #253294 supportedProtocols.append(QStringLiteral("ftp")); } } } //qCDebug(KIO_CORE) << "supportedProtocols:" << supportedProtocols; return supportedProtocols; } bool KIO::DesktopExecParser::isProtocolInSupportedList(const QUrl &url, const QStringList &supportedProtocols) { if (supportedProtocols.contains(QStringLiteral("KIO"))) { return true; } return url.isLocalFile() || supportedProtocols.contains(url.scheme().toLower()); } bool KIO::DesktopExecParser::hasSchemeHandler(const QUrl &url) { if (KProtocolInfo::isHelperProtocol(url)) { return true; } if (KProtocolInfo::isKnownProtocol(url)) { return false; // see schemeHandler()... this is case B, we prefer kioslaves over the competition } const KService::Ptr service = KMimeTypeTrader::self()->preferredService(QLatin1String("x-scheme-handler/") + url.scheme()); if (service) { - qCDebug(KIO_CORE) << "preferred service for x-scheme-handler/" + url.scheme() << service->desktopEntryName(); + qCDebug(KIO_CORE) << QLatin1String("preferred service for x-scheme-handler/") + url.scheme() << service->desktopEntryName(); } return service; } class KIO::DesktopExecParserPrivate { public: DesktopExecParserPrivate(const KService &_service, const QList &_urls) : service(_service), urls(_urls), tempFiles(false) {} const KService &service; QList urls; bool tempFiles; QString suggestedFileName; }; KIO::DesktopExecParser::DesktopExecParser(const KService &service, const QList &urls) : d(new DesktopExecParserPrivate(service, urls)) { } KIO::DesktopExecParser::~DesktopExecParser() { } void KIO::DesktopExecParser::setUrlsAreTempFiles(bool tempFiles) { d->tempFiles = tempFiles; } void KIO::DesktopExecParser::setSuggestedFileName(const QString &suggestedFileName) { d->suggestedFileName = suggestedFileName; } static const QString kioexecPath() { - QString kioexec = QCoreApplication::applicationDirPath() + "/kioexec"; + QString kioexec = QCoreApplication::applicationDirPath() + QLatin1String("/kioexec"); if (!QFileInfo::exists(kioexec)) - kioexec = CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/kioexec"; + kioexec = QStringLiteral(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/kioexec"); Q_ASSERT(QFileInfo::exists(kioexec)); return kioexec; } QStringList KIO::DesktopExecParser::resultingArguments() const { QString exec = d->service.exec(); if (exec.isEmpty()) { qCWarning(KIO_CORE) << "KRun: no Exec field in `" << d->service.entryPath() << "' !"; return QStringList(); } QStringList result; bool appHasTempFileOption; KRunMX1 mx1(d->service); KRunMX2 mx2(d->urls); if (!mx1.expandMacrosShellQuote(exec)) { // Error in shell syntax qCWarning(KIO_CORE) << "KRun: syntax error in command" << d->service.exec() << ", service" << d->service.name(); return QStringList(); } // FIXME: the current way of invoking kioexec disables term and su use // Check if we need "tempexec" (kioexec in fact) appHasTempFileOption = d->tempFiles && d->service.property(QStringLiteral("X-KDE-HasTempFileOption")).toBool(); if (d->tempFiles && !appHasTempFileOption && d->urls.size()) { result << kioexecPath() << QStringLiteral("--tempfiles") << exec; if (!d->suggestedFileName.isEmpty()) { result << QStringLiteral("--suggestedfilename"); result << d->suggestedFileName; } result += QUrl::toStringList(d->urls); return result; } // Check if we need kioexec bool useKioexec = false; if (!mx1.hasUrls) { Q_FOREACH (const QUrl &url, d->urls) if (!url.isLocalFile() && !hasSchemeHandler(url)) { useKioexec = true; //qCDebug(KIO_CORE) << "non-local files, application does not support urls, using kioexec"; break; } } else { // app claims to support %u/%U, check which protocols QStringList appSupportedProtocols = supportedProtocols(d->service); Q_FOREACH (const QUrl &url, d->urls) { if (!isProtocolInSupportedList(url, appSupportedProtocols) && !hasSchemeHandler(url)) { useKioexec = true; //qCDebug(KIO_CORE) << "application does not support url, using kioexec:" << url; break; } } } if (useKioexec) { // We need to run the app through kioexec result << kioexecPath(); if (d->tempFiles) { result << QStringLiteral("--tempfiles"); } if (!d->suggestedFileName.isEmpty()) { result << QStringLiteral("--suggestedfilename"); result << d->suggestedFileName; } result << exec; result += QUrl::toStringList(d->urls); return result; } if (appHasTempFileOption) { exec += QLatin1String(" --tempfile"); } // Did the user forget to append something like '%f'? // If so, then assume that '%f' is the right choice => the application // accepts only local files. if (!mx1.hasSpec) { exec += QLatin1String(" %f"); mx2.ignFile = true; } mx2.expandMacrosShellQuote(exec); // syntax was already checked, so don't check return value /* 1 = need_shell, 2 = terminal, 4 = su 0 << split(cmd) 1 << "sh" << "-c" << cmd 2 << split(term) << "-e" << split(cmd) 3 << split(term) << "-e" << "sh" << "-c" << cmd 4 << "kdesu" << "-u" << user << "-c" << cmd 5 << "kdesu" << "-u" << user << "-c" << ("sh -c " + quote(cmd)) 6 << split(term) << "-e" << "su" << user << "-c" << cmd 7 << split(term) << "-e" << "su" << user << "-c" << ("sh -c " + quote(cmd)) "sh -c" is needed in the "su" case, too, as su uses the user's login shell, not sh. this could be optimized with the -s switch of some su versions (e.g., debian linux). */ if (d->service.terminal()) { KConfigGroup cg(KSharedConfig::openConfig(), "General"); QString terminal = cg.readPathEntry("TerminalApplication", QStringLiteral("konsole")); if (terminal == QLatin1String("konsole")) { if (!d->service.path().isEmpty()) { - terminal += " --workdir " + KShell::quoteArg(d->service.path()); + terminal += QLatin1String(" --workdir ") + KShell::quoteArg(d->service.path()); } terminal += QLatin1String(" -qwindowtitle '%c' %i"); } - terminal += ' '; + terminal += QLatin1Char(' '); terminal += d->service.terminalOptions(); if (!mx1.expandMacrosShellQuote(terminal)) { qCWarning(KIO_CORE) << "KRun: syntax error in command" << terminal << ", service" << d->service.name(); return QStringList(); } mx2.expandMacrosShellQuote(terminal); result = KShell::splitArgs(terminal); // assuming that the term spec never needs a shell! result << QStringLiteral("-e"); } KShell::Errors err; QStringList execlist = KShell::splitArgs(exec, KShell::AbortOnMeta | KShell::TildeExpand, &err); if (err == KShell::NoError && !execlist.isEmpty()) { // mx1 checked for syntax errors already // Resolve the executable to ensure that helpers in libexec are found. // Too bad for commands that need a shell - they must reside in $PATH. QString exePath = QStandardPaths::findExecutable(execlist.first()); if (exePath.isEmpty()) { exePath = QFile::decodeName(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/") + execlist.first(); } if (QFile::exists(exePath)) { execlist[0] = exePath; } } if (d->service.substituteUid()) { if (d->service.terminal()) { result << QStringLiteral("su"); } else { QString kdesu = QFile::decodeName(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/kdesu"); if (!QFile::exists(kdesu)) { kdesu = QStandardPaths::findExecutable(QStringLiteral("kdesu")); } if (!QFile::exists(kdesu)) { // Insert kdesu as string so we show a nice warning: 'Could not launch kdesu' result << QStringLiteral("kdesu"); return result; } else { result << kdesu << QStringLiteral("-u"); } } result << d->service.username() << QStringLiteral("-c"); if (err == KShell::FoundMeta) { - exec = "/bin/sh -c " + KShell::quoteArg(exec); + exec = QLatin1String("/bin/sh -c ") + KShell::quoteArg(exec); } else { exec = KShell::joinArgs(execlist); } result << exec; } else { if (err == KShell::FoundMeta) { result << QStringLiteral("/bin/sh") << QStringLiteral("-c") << exec; } else { result += execlist; } } return result; } //static QString KIO::DesktopExecParser::executableName(const QString &execLine) { const QString bin = executablePath(execLine); - return bin.mid(bin.lastIndexOf('/') + 1); + return bin.mid(bin.lastIndexOf(QLatin1Char('/')) + 1); } //static QString KIO::DesktopExecParser::executablePath(const QString &execLine) { // Remove parameters and/or trailing spaces. const QStringList args = KShell::splitArgs(execLine); for (QStringList::ConstIterator it = args.begin(); it != args.end(); ++it) { - if (!(*it).contains('=')) { + if (!(*it).contains(QLatin1Char('='))) { return *it; } } return QString(); } diff --git a/src/core/global.cpp b/src/core/global.cpp index 4495d8a9..c1763158 100644 --- a/src/core/global.cpp +++ b/src/core/global.cpp @@ -1,339 +1,339 @@ /* This file is part of the KDE libraries 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 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 "global.h" #include "kioglobal_p.h" #include "faviconscache_p.h" #include #include #include #include #include #include #include #include #include #include #include "kiocoredebug.h" KFormat::BinaryUnitDialect _k_loadBinaryDialect(); Q_GLOBAL_STATIC_WITH_ARGS(KFormat::BinaryUnitDialect, _k_defaultBinaryDialect, (_k_loadBinaryDialect())) KFormat::BinaryUnitDialect _k_loadBinaryDialect() { KConfigGroup mainGroup(KSharedConfig::openConfig(), "Locale"); KFormat::BinaryUnitDialect dialect(KFormat::BinaryUnitDialect(mainGroup.readEntry("BinaryUnitDialect", int(KFormat::DefaultBinaryDialect)))); dialect = static_cast(mainGroup.readEntry("BinaryUnitDialect", int(dialect))); // Error checking if (dialect <= KFormat::DefaultBinaryDialect || dialect > KFormat::LastBinaryDialect) { dialect = KFormat::IECBinaryDialect; } return dialect; } KIOCORE_EXPORT QString KIO::convertSize(KIO::filesize_t fileSize) { const KFormat::BinaryUnitDialect dialect = *_k_defaultBinaryDialect(); return KFormat().formatByteSize(fileSize, 1, dialect); } KIOCORE_EXPORT QString KIO::convertSizeFromKiB(KIO::filesize_t kibSize) { return convertSize(kibSize * 1024); } KIOCORE_EXPORT QString KIO::number(KIO::filesize_t size) { char charbuf[256]; sprintf(charbuf, "%lld", size); return QLatin1String(charbuf); } KIOCORE_EXPORT unsigned int KIO::calculateRemainingSeconds(KIO::filesize_t totalSize, KIO::filesize_t processedSize, KIO::filesize_t speed) { if ((speed != 0) && (totalSize != 0)) { return (totalSize - processedSize) / speed; } else { return 0; } } KIOCORE_EXPORT QString KIO::convertSeconds(unsigned int seconds) { unsigned int days = seconds / 86400; unsigned int hours = (seconds - (days * 86400)) / 3600; unsigned int mins = (seconds - (days * 86400) - (hours * 3600)) / 60; seconds = (seconds - (days * 86400) - (hours * 3600) - (mins * 60)); const QTime time(hours, mins, seconds); const QString timeStr(time.toString(QStringLiteral("hh:mm:ss"))); if (days > 0) { return i18np("1 day %2", "%1 days %2", days, timeStr); } else { return timeStr; } } #ifndef KIOCORE_NO_DEPRECATED KIOCORE_EXPORT QTime KIO::calculateRemaining(KIO::filesize_t totalSize, KIO::filesize_t processedSize, KIO::filesize_t speed) { QTime remainingTime; if (speed != 0) { KIO::filesize_t secs; if (totalSize == 0) { secs = 0; } else { secs = (totalSize - processedSize) / speed; } if (secs >= (24 * 60 * 60)) { // Limit to 23:59:59 secs = (24 * 60 * 60) - 1; } int hr = secs / (60 * 60); int mn = (secs - hr * 60 * 60) / 60; int sc = (secs - hr * 60 * 60 - mn * 60); remainingTime.setHMS(hr, mn, sc); } return remainingTime; } #endif KIOCORE_EXPORT QString KIO::itemsSummaryString(uint items, uint files, uint dirs, KIO::filesize_t size, bool showSize) { if (files == 0 && dirs == 0 && items == 0) { return i18np("%1 Item", "%1 Items", 0); } QString summary; const QString foldersText = i18np("1 Folder", "%1 Folders", dirs); const QString filesText = i18np("1 File", "%1 Files", files); if (files > 0 && dirs > 0) { summary = showSize ? i18nc("folders, files (size)", "%1, %2 (%3)", foldersText, filesText, KIO::convertSize(size)) : i18nc("folders, files", "%1, %2", foldersText, filesText); } else if (files > 0) { summary = showSize ? i18nc("files (size)", "%1 (%2)", filesText, KIO::convertSize(size)) : filesText; } else if (dirs > 0) { summary = foldersText; } if (items > dirs + files) { const QString itemsText = i18np("%1 Item", "%1 Items", items); summary = summary.isEmpty() ? itemsText : i18nc("items: folders, files (size)", "%1: %2", itemsText, summary); } return summary; } KIOCORE_EXPORT QString KIO::encodeFileName(const QString &_str) { QString str(_str); - str.replace('/', QChar(0x2044)); // "Fraction slash" + str.replace(QLatin1Char('/'), QChar(0x2044)); // "Fraction slash" return str; } KIOCORE_EXPORT QString KIO::decodeFileName(const QString &_str) { // Nothing to decode. "Fraction slash" is fine in filenames. return _str; } /*************************************************************** * * Utility functions * ***************************************************************/ KIO::CacheControl KIO::parseCacheControl(const QString &cacheControl) { QString tmp = cacheControl.toLower(); if (tmp == QLatin1String("cacheonly")) { return KIO::CC_CacheOnly; } if (tmp == QLatin1String("cache")) { return KIO::CC_Cache; } if (tmp == QLatin1String("verify")) { return KIO::CC_Verify; } if (tmp == QLatin1String("refresh")) { return KIO::CC_Refresh; } if (tmp == QLatin1String("reload")) { return KIO::CC_Reload; } qCDebug(KIO_CORE) << "unrecognized Cache control option:" << cacheControl; return KIO::CC_Verify; } QString KIO::getCacheControlString(KIO::CacheControl cacheControl) { if (cacheControl == KIO::CC_CacheOnly) { return QStringLiteral("CacheOnly"); } if (cacheControl == KIO::CC_Cache) { return QStringLiteral("Cache"); } if (cacheControl == KIO::CC_Verify) { return QStringLiteral("Verify"); } if (cacheControl == KIO::CC_Refresh) { return QStringLiteral("Refresh"); } if (cacheControl == KIO::CC_Reload) { return QStringLiteral("Reload"); } qCDebug(KIO_CORE) << "unrecognized Cache control enum value:" << cacheControl; return QString(); } QString KIO::favIconForUrl(const QUrl &url) { if (url.isLocalFile() || !url.scheme().startsWith(QLatin1String("http"))) { return QString(); } return FavIconsCache::instance()->iconForUrl(url); } QString KIO::iconNameForUrl(const QUrl &url) { const QLatin1String unknown("unknown"); if (url.scheme().isEmpty()) { // empty URL or relative URL (e.g. '~') return unknown; } QMimeDatabase db; const QMimeType mt = db.mimeTypeForUrl(url); const QString mimeTypeIcon = mt.iconName(); QString i = mimeTypeIcon; // check whether it's a xdg location (e.g. Pictures folder) if (url.isLocalFile() && mt.inherits(QStringLiteral("inode/directory"))) { i = KIOPrivate::iconForStandardPath(url.toLocalFile()); } // if we don't find an icon, maybe we can use the one for the protocol if (i == unknown || i.isEmpty() || mt.isDefault() // and for the root of the protocol (e.g. trash:/) the protocol icon has priority over the mimetype icon || url.path().length() <= 1) { i = favIconForUrl(url); // maybe there is a favicon? // reflect actual fill state of trash can if (url.scheme() == QLatin1String("trash") && url.path().length() <= 1) { KConfig trashConfig(QStringLiteral("trashrc"), KConfig::SimpleConfig); if (trashConfig.group("Status").readEntry("Empty", true)) { i = QStringLiteral("user-trash"); } else { i = QStringLiteral("user-trash-full"); } } if (i.isEmpty()) { i = KProtocolInfo::icon(url.scheme()); } // root of protocol: if we found nothing, revert to mimeTypeIcon (which is usually "folder") if (url.path().length() <= 1 && (i == unknown || i.isEmpty())) { i = mimeTypeIcon; } } return !i.isEmpty() ? i : unknown; } QUrl KIO::upUrl(const QUrl &url) { if (!url.isValid() || url.isRelative()) { return QUrl(); } QUrl u(url); if (url.hasQuery()) { u.setQuery(QString()); return u; } if (url.hasFragment()) { u.setFragment(QString()); } u = u.adjusted(QUrl::StripTrailingSlash); /// don't combine with the line below return u.adjusted(QUrl::RemoveFilename); } QString KIO::suggestName(const QUrl &baseURL, const QString &oldName) { QString basename; // Extract the original file extension from the filename QMimeDatabase db; QString nameSuffix = db.suffixForFileName(oldName); - if (oldName.lastIndexOf('.') == 0) { + if (oldName.lastIndexOf(QLatin1Char('.')) == 0) { basename = QStringLiteral("."); nameSuffix = oldName; } else if (nameSuffix.isEmpty()) { - const int lastDot = oldName.lastIndexOf('.'); + const int lastDot = oldName.lastIndexOf(QLatin1Char('.')); if (lastDot == -1) { basename = oldName; } else { basename = oldName.left(lastDot); nameSuffix = oldName.mid(lastDot); } } else { - nameSuffix.prepend('.'); + nameSuffix.prepend(QLatin1Char('.')); basename = oldName.left(oldName.length() - nameSuffix.length()); } // check if (number) exists from the end of the oldName and increment that number QRegExp numSearch(QStringLiteral("\\(\\d+\\)")); int start = numSearch.lastIndexIn(oldName); if (start != -1) { QString numAsStr = numSearch.cap(0); QString number = QString::number(numAsStr.midRef(1, numAsStr.size() - 2).toInt() + 1); - basename = basename.left(start) + '(' + number + ')'; + basename = basename.left(start) + QLatin1Char('(') + number + QLatin1Char(')'); } else { // number does not exist, so just append " (1)" to filename basename += QLatin1String(" (1)"); } const QString suggestedName = basename + nameSuffix; // Check if suggested name already exists bool exists = false; // TODO: network transparency. However, using NetAccess from a modal dialog // could be a problem, no? (given that it uses a modal widget itself....) if (baseURL.isLocalFile()) { exists = QFileInfo::exists(baseURL.toLocalFile() + QLatin1Char('/') + suggestedName); } if (!exists) { return suggestedName; } else { // already exists -> recurse return suggestName(baseURL, suggestedName); } } diff --git a/src/core/job_error.cpp b/src/core/job_error.cpp index 0c2f565e..fa69ec9b 100644 --- a/src/core/job_error.cpp +++ b/src/core/job_error.cpp @@ -1,1121 +1,1121 @@ /* This file is part of the KDE libraries 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 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 "job.h" #include "kioglobal_p.h" #include #include #include #include #include #include // S_IRUSR etc QString KIO::Job::errorString() const { return KIO::buildErrorString(error(), errorText()); } KIOCORE_EXPORT QString KIO::buildErrorString(int errorCode, const QString &errorText) { QString result; switch (errorCode) { case KIO::ERR_CANNOT_OPEN_FOR_READING: result = i18n("Could not read %1.", errorText); break; case KIO::ERR_CANNOT_OPEN_FOR_WRITING: result = i18n("Could not write to %1.", errorText); break; case KIO::ERR_CANNOT_LAUNCH_PROCESS: result = i18n("Could not start process %1.", errorText); break; case KIO::ERR_INTERNAL: result = i18n("Internal Error\nPlease send a full bug report at http://bugs.kde.org\n%1", errorText); break; case KIO::ERR_MALFORMED_URL: result = i18n("Malformed URL %1.", errorText); break; case KIO::ERR_UNSUPPORTED_PROTOCOL: result = i18n("The protocol %1 is not supported.", errorText); break; case KIO::ERR_NO_SOURCE_PROTOCOL: result = i18n("The protocol %1 is only a filter protocol.", errorText); break; case KIO::ERR_UNSUPPORTED_ACTION: result = errorText; // result = i18n( "Unsupported action %1" ).arg( errorText ); break; case KIO::ERR_IS_DIRECTORY: result = i18n("%1 is a folder, but a file was expected.", errorText); break; case KIO::ERR_IS_FILE: result = i18n("%1 is a file, but a folder was expected.", errorText); break; case KIO::ERR_DOES_NOT_EXIST: result = i18n("The file or folder %1 does not exist.", errorText); break; case KIO::ERR_FILE_ALREADY_EXIST: result = i18n("A file named %1 already exists.", errorText); break; case KIO::ERR_DIR_ALREADY_EXIST: result = i18n("A folder named %1 already exists.", errorText); break; case KIO::ERR_UNKNOWN_HOST: result = errorText.isEmpty() ? i18n("No hostname specified.") : i18n("Unknown host %1", errorText); break; case KIO::ERR_ACCESS_DENIED: result = i18n("Access denied to %1.", errorText); break; case KIO::ERR_WRITE_ACCESS_DENIED: result = i18n("Access denied.\nCould not write to %1.", errorText); break; case KIO::ERR_CANNOT_ENTER_DIRECTORY: result = i18n("Could not enter folder %1.", errorText); break; case KIO::ERR_PROTOCOL_IS_NOT_A_FILESYSTEM: result = i18n("The protocol %1 does not implement a folder service.", errorText); break; case KIO::ERR_CYCLIC_LINK: result = i18n("Found a cyclic link in %1.", errorText); break; case KIO::ERR_USER_CANCELED: // Do nothing in this case. The user doesn't need to be told what he just did. break; case KIO::ERR_CYCLIC_COPY: result = i18n("Found a cyclic link while copying %1.", errorText); break; case KIO::ERR_CANNOT_CREATE_SOCKET: result = i18n("Could not create socket for accessing %1.", errorText); break; case KIO::ERR_CANNOT_CONNECT: result = i18n("Could not connect to host %1.", errorText.isEmpty() ? QStringLiteral("localhost") : errorText); break; case KIO::ERR_CONNECTION_BROKEN: result = i18n("Connection to host %1 is broken.", errorText); break; case KIO::ERR_NOT_FILTER_PROTOCOL: result = i18n("The protocol %1 is not a filter protocol.", errorText); break; case KIO::ERR_CANNOT_MOUNT: result = i18n("Could not mount device.\nThe reported error was:\n%1", errorText); break; case KIO::ERR_CANNOT_UNMOUNT: result = i18n("Could not unmount device.\nThe reported error was:\n%1", errorText); break; case KIO::ERR_CANNOT_READ: result = i18n("Could not read file %1.", errorText); break; case KIO::ERR_CANNOT_WRITE: result = i18n("Could not write to file %1.", errorText); break; case KIO::ERR_CANNOT_BIND: result = i18n("Could not bind %1.", errorText); break; case KIO::ERR_CANNOT_LISTEN: result = i18n("Could not listen %1.", errorText); break; case KIO::ERR_CANNOT_ACCEPT: result = i18n("Could not accept %1.", errorText); break; case KIO::ERR_CANNOT_LOGIN: result = errorText; break; case KIO::ERR_CANNOT_STAT: result = i18n("Could not access %1.", errorText); break; case KIO::ERR_CANNOT_CLOSEDIR: result = i18n("Could not terminate listing %1.", errorText); break; case KIO::ERR_CANNOT_MKDIR: result = i18n("Could not make folder %1.", errorText); break; case KIO::ERR_CANNOT_RMDIR: result = i18n("Could not remove folder %1.", errorText); break; case KIO::ERR_CANNOT_RESUME: result = i18n("Could not resume file %1.", errorText); break; case KIO::ERR_CANNOT_RENAME: result = i18n("Could not rename file %1.", errorText); break; case KIO::ERR_CANNOT_CHMOD: result = i18n("Could not change permissions for %1.", errorText); break; case KIO::ERR_CANNOT_CHOWN: result = i18n("Could not change ownership for %1.", errorText); break; case KIO::ERR_CANNOT_DELETE: result = i18n("Could not delete file %1.", errorText); break; case KIO::ERR_SLAVE_DIED: result = i18n("The process for the %1 protocol died unexpectedly.", errorText); break; case KIO::ERR_OUT_OF_MEMORY: result = i18n("Error. Out of memory.\n%1", errorText); break; case KIO::ERR_UNKNOWN_PROXY_HOST: result = i18n("Unknown proxy host\n%1", errorText); break; case KIO::ERR_CANNOT_AUTHENTICATE: result = i18n("Authorization failed, %1 authentication not supported", errorText); break; case KIO::ERR_ABORTED: result = i18n("User canceled action\n%1", errorText); break; case KIO::ERR_INTERNAL_SERVER: result = i18n("Internal error in server\n%1", errorText); break; case KIO::ERR_SERVER_TIMEOUT: result = i18n("Timeout on server\n%1", errorText); break; case KIO::ERR_UNKNOWN: result = i18n("Unknown error\n%1", errorText); break; case KIO::ERR_UNKNOWN_INTERRUPT: result = i18n("Unknown interrupt\n%1", errorText); break; /* case KIO::ERR_CHECKSUM_MISMATCH: if (errorText) result = i18n( "Warning: MD5 Checksum for %1 does not match checksum returned from server" ).arg(errorText); else result = i18n( "Warning: MD5 Checksum for %1 does not match checksum returned from server" ).arg("document"); break; */ case KIO::ERR_CANNOT_DELETE_ORIGINAL: result = i18n("Could not delete original file %1.\nPlease check permissions.", errorText); break; case KIO::ERR_CANNOT_DELETE_PARTIAL: result = i18n("Could not delete partial file %1.\nPlease check permissions.", errorText); break; case KIO::ERR_CANNOT_RENAME_ORIGINAL: result = i18n("Could not rename original file %1.\nPlease check permissions.", errorText); break; case KIO::ERR_CANNOT_RENAME_PARTIAL: result = i18n("Could not rename partial file %1.\nPlease check permissions.", errorText); break; case KIO::ERR_CANNOT_SYMLINK: result = i18n("Could not create symlink %1.\nPlease check permissions.", errorText); break; case KIO::ERR_NO_CONTENT: result = errorText; break; case KIO::ERR_DISK_FULL: result = i18n("There is not enough space on the disk to write %1.", errorText); break; case KIO::ERR_IDENTICAL_FILES: result = i18n("The source and destination are the same file.\n%1", errorText); break; case KIO::ERR_SLAVE_DEFINED: result = errorText; break; case KIO::ERR_UPGRADE_REQUIRED: result = i18n("%1 is required by the server, but is not available.", errorText); break; case KIO::ERR_POST_DENIED: result = i18n("Access to restricted port in POST denied."); break; case KIO::ERR_POST_NO_SIZE: result = i18n("The required content size information was not provided for a POST operation."); break; case KIO::ERR_DROP_ON_ITSELF: result = i18n("A file or folder cannot be dropped onto itself"); break; case KIO::ERR_CANNOT_MOVE_INTO_ITSELF: result = i18n("A folder cannot be moved into itself"); break; case KIO::ERR_PASSWD_SERVER: result = i18n("Communication with the local password server failed"); break; case KIO::ERR_CANNOT_CREATE_SLAVE: result = i18n("Unable to create io-slave. %1", errorText); break; default: result = i18n("Unknown error code %1\n%2\nPlease send a full bug report at https://bugs.kde.org.", errorCode, errorText); break; } return result; } QStringList KIO::Job::detailedErrorStrings(const QUrl *reqUrl /*= 0*/, int method /*= -1*/) const { QString errorName, techName, description, ret2; QStringList causes, solutions, ret; QByteArray raw = rawErrorDetail(error(), errorText(), reqUrl, method); QDataStream stream(raw); stream >> errorName >> techName >> description >> causes >> solutions; QString url, protocol, datetime; if (reqUrl) { QString prettyUrl; prettyUrl = reqUrl->toDisplayString(); url = prettyUrl.toHtmlEscaped(); protocol = reqUrl->scheme(); } else { url = i18nc("@info url", "(unknown)"); } datetime = QDateTime::currentDateTime().toString(Qt::DefaultLocaleLongDate); ret << errorName; ret << i18nc("@info %1 error name, %2 description", "

%1

%2

", errorName, description); ret2 = QStringLiteral(""); if (!techName.isEmpty()) ret2 += QLatin1String("

") + i18n("Technical reason: ") + techName + QLatin1String("

"); ret2 += QLatin1String("

") + i18n("Details of the request:") + QLatin1String("

    ") + i18n("
  • URL: %1
  • ", url); if (!protocol.isEmpty()) { ret2 += i18n("
  • Protocol: %1
  • ", protocol); } ret2 += i18n("
  • Date and time: %1
  • ", datetime) + i18n("
  • Additional information: %1
  • ", errorText()) + QLatin1String("
"); if (!causes.isEmpty()) { ret2 += QLatin1String("

") + i18n("Possible causes:") + QLatin1String("

  • ") + causes.join(QStringLiteral("
  • ")) + QLatin1String("
"); } if (!solutions.isEmpty()) { ret2 += QLatin1String("

") + i18n("Possible solutions:") + QLatin1String("

  • ") + solutions.join(QStringLiteral("
  • ")) + QLatin1String("
"); } ret2 += QLatin1String("
"); ret << ret2; return ret; } KIOCORE_EXPORT QByteArray KIO::rawErrorDetail(int errorCode, const QString &errorText, const QUrl *reqUrl /*= 0*/, int /*method = -1*/) { QString url, host, protocol, datetime, domain, path, filename; bool isSlaveNetwork = false; if (reqUrl) { url = reqUrl->toDisplayString(); host = reqUrl->host(); protocol = reqUrl->scheme(); if (host.startsWith(QLatin1String("www."))) { domain = host.mid(4); } else { domain = host; } filename = reqUrl->fileName(); path = reqUrl->path(); // detect if protocol is a network protocol... isSlaveNetwork = KProtocolInfo::protocolClass(protocol) == QLatin1String(":internet"); } else { // assume that the errorText has the location we are interested in url = host = domain = path = filename = errorText; protocol = i18nc("@info protocol", "(unknown)"); } datetime = QDateTime::currentDateTime().toString(Qt::DefaultLocaleLongDate); QString errorName, techName, description; QStringList causes, solutions; // c == cause, s == solution QString sSysadmin = i18n("Contact your appropriate computer support system, " "whether the system administrator, or technical support group for further " "assistance."); QString sServeradmin = i18n("Contact the administrator of the server " "for further assistance."); // FIXME active link to permissions dialog QString sAccess = i18n("Check your access permissions on this resource."); QString cAccess = i18n("Your access permissions may be inadequate to " "perform the requested operation on this resource."); QString cLocked = i18n("The file may be in use (and thus locked) by " "another user or application."); QString sQuerylock = i18n("Check to make sure that no other " "application or user is using the file or has locked the file."); QString cHardware = i18n("Although unlikely, a hardware error may have " "occurred."); QString cBug = i18n("You may have encountered a bug in the program."); QString cBuglikely = i18n("This is most likely to be caused by a bug in the " "program. Please consider submitting a full bug report as detailed below."); QString sUpdate = i18n("Update your software to the latest version. " "Your distribution should provide tools to update your software."); QString sBugreport = i18n("When all else fails, please consider helping the " "KDE team or the third party maintainer of this software by submitting a " "high quality bug report. If the software is provided by a third party, " "please contact them directly. Otherwise, first look to see if " "the same bug has been submitted by someone else by searching at the " "KDE bug reporting website. If not, take " "note of the details given above, and include them in your bug report, along " "with as many other details as you think might help."); QString cNetwork = i18n("There may have been a problem with your network " "connection."); // FIXME netconf kcontrol link QString cNetconf = i18n("There may have been a problem with your network " "configuration. If you have been accessing the Internet with no problems " "recently, this is unlikely."); QString cNetpath = i18n("There may have been a problem at some point along " "the network path between the server and this computer."); QString sTryagain = i18n("Try again, either now or at a later time."); QString cProtocol = i18n("A protocol error or incompatibility may have occurred."); QString sExists = i18n("Ensure that the resource exists, and try again."); QString cExists = i18n("The specified resource may not exist."); QString sTypo = i18n("Double-check that you have entered the correct location " "and try again."); QString sNetwork = i18n("Check your network connection status."); switch (errorCode) { case KIO::ERR_CANNOT_OPEN_FOR_READING: errorName = i18n("Cannot Open Resource For Reading"); description = i18n("This means that the contents of the requested file " "or folder %1 could not be retrieved, as read " "access could not be obtained.", path); causes << i18n("You may not have permissions to read the file or open " "the folder.") << cLocked << cHardware; solutions << sAccess << sQuerylock << sSysadmin; break; case KIO::ERR_CANNOT_OPEN_FOR_WRITING: errorName = i18n("Cannot Open Resource For Writing"); description = i18n("This means that the file, %1, could " "not be written to as requested, because access with permission to " "write could not be obtained.", filename); causes << cAccess << cLocked << cHardware; solutions << sAccess << sQuerylock << sSysadmin; break; case KIO::ERR_CANNOT_LAUNCH_PROCESS: errorName = i18n("Cannot Launch Process required by the %1 Protocol", protocol); techName = i18n("Unable to Launch Process"); description = i18n("The program on your computer which provides access " "to the %1 protocol could not be found or started. This is " "usually due to technical reasons.", protocol); causes << i18n("The program which provides compatibility with this " "protocol may not have been updated with your last update of KDE. " "This can cause the program to be incompatible with the current version " "and thus not start.") << cBug; solutions << sUpdate << sSysadmin; break; case KIO::ERR_INTERNAL: errorName = i18n("Internal Error"); description = i18n("The program on your computer which provides access " "to the %1 protocol has reported an internal error.", protocol); causes << cBuglikely; solutions << sUpdate << sBugreport; break; case KIO::ERR_MALFORMED_URL: errorName = i18n("Improperly Formatted URL"); description = i18n("The Uniform Resource " "Locator (URL) that you entered was not properly " "formatted. The format of a URL is generally as follows:" "
protocol://user:password@www.example.org:port/folder/" "filename.extension?query=value
"); solutions << sTypo; break; case KIO::ERR_UNSUPPORTED_PROTOCOL: errorName = i18n("Unsupported Protocol %1", protocol); description = i18n("The protocol %1 is not supported " "by the KDE programs currently installed on this computer.", protocol); causes << i18n("The requested protocol may not be supported.") << i18n("The versions of the %1 protocol supported by this computer and " "the server may be incompatible.", protocol); solutions << i18n("You may perform a search on the Internet for a KDE " "program (called a kioslave or ioslave) which supports this protocol. " "Places to search include " "http://kde-apps.org/ and " "http://freshmeat.net/.") << sUpdate << sSysadmin; break; case KIO::ERR_NO_SOURCE_PROTOCOL: errorName = i18n("URL Does Not Refer to a Resource."); techName = i18n("Protocol is a Filter Protocol"); description = i18n("The Uniform Resource " "Locator (URL) that you entered did not refer to a " "specific resource."); causes << i18n("KDE is able to communicate through a protocol within a " "protocol; the protocol specified is only for use in such situations, " "however this is not one of these situations. This is a rare event, and " "is likely to indicate a programming error."); solutions << sTypo; break; case KIO::ERR_UNSUPPORTED_ACTION: errorName = i18n("Unsupported Action: %1", errorText); description = i18n("The requested action is not supported by the KDE " "program which is implementing the %1 protocol.", protocol); causes << i18n("This error is very much dependent on the KDE program. The " "additional information should give you more information than is available " "to the KDE input/output architecture."); solutions << i18n("Attempt to find another way to accomplish the same " "outcome."); break; case KIO::ERR_IS_DIRECTORY: errorName = i18n("File Expected"); description = i18n("The request expected a file, however the " "folder %1 was found instead.", path); causes << i18n("This may be an error on the server side.") << cBug; solutions << sUpdate << sSysadmin; break; case KIO::ERR_IS_FILE: errorName = i18n("Folder Expected"); description = i18n("The request expected a folder, however " "the file %1 was found instead.", filename); causes << cBug; solutions << sUpdate << sSysadmin; break; case KIO::ERR_DOES_NOT_EXIST: errorName = i18n("File or Folder Does Not Exist"); description = i18n("The specified file or folder %1 " "does not exist.", path); causes << cExists; solutions << sExists; break; case KIO::ERR_FILE_ALREADY_EXIST: errorName = i18n("File Already Exists"); description = i18n("The requested file could not be created because a " "file with the same name already exists."); solutions << i18n("Try moving the current file out of the way first, " "and then try again.") << i18n("Delete the current file and try again.") << i18n("Choose an alternate filename for the new file."); break; case KIO::ERR_DIR_ALREADY_EXIST: errorName = i18n("Folder Already Exists"); description = i18n("The requested folder could not be created because " "a folder with the same name already exists."); solutions << i18n("Try moving the current folder out of the way first, " "and then try again.") << i18n("Delete the current folder and try again.") << i18n("Choose an alternate name for the new folder."); break; case KIO::ERR_UNKNOWN_HOST: errorName = i18n("Unknown Host"); description = i18n("An unknown host error indicates that the server with " "the requested name, %1, could not be " "located on the Internet.", host); causes << i18n("The name that you typed, %1, may not exist: it may be " "incorrectly typed.", host) << cNetwork << cNetconf; solutions << sNetwork << sSysadmin; break; case KIO::ERR_ACCESS_DENIED: errorName = i18n("Access Denied"); description = i18n("Access was denied to the specified resource, " "%1.", url); causes << i18n("You may have supplied incorrect authentication details or " "none at all.") << i18n("Your account may not have permission to access the " "specified resource."); solutions << i18n("Retry the request and ensure your authentication details " "are entered correctly.") << sSysadmin; if (!isSlaveNetwork) { solutions << sServeradmin; } break; case KIO::ERR_WRITE_ACCESS_DENIED: errorName = i18n("Write Access Denied"); description = i18n("This means that an attempt to write to the file " "%1 was rejected.", filename); causes << cAccess << cLocked << cHardware; solutions << sAccess << sQuerylock << sSysadmin; break; case KIO::ERR_CANNOT_ENTER_DIRECTORY: errorName = i18n("Unable to Enter Folder"); description = i18n("This means that an attempt to enter (in other words, " "to open) the requested folder %1 was rejected.", path); causes << cAccess << cLocked; solutions << sAccess << sQuerylock << sSysadmin; break; case KIO::ERR_PROTOCOL_IS_NOT_A_FILESYSTEM: errorName = i18n("Folder Listing Unavailable"); techName = i18n("Protocol %1 is not a Filesystem", protocol); description = i18n("This means that a request was made which requires " "determining the contents of the folder, and the KDE program supporting " "this protocol is unable to do so."); causes << cBug; solutions << sUpdate << sBugreport; break; case KIO::ERR_CYCLIC_LINK: errorName = i18n("Cyclic Link Detected"); description = i18n("UNIX environments are commonly able to link a file or " "folder to a separate name and/or location. KDE detected a link or " "series of links that results in an infinite loop - i.e. the file was " "(perhaps in a roundabout way) linked to itself."); solutions << i18n("Delete one part of the loop in order that it does not " "cause an infinite loop, and try again.") << sSysadmin; break; case KIO::ERR_USER_CANCELED: // Do nothing in this case. The user doesn't need to be told what he just did. // rodda: However, if we have been called, an application is about to display // this information anyway. If we don't return sensible information, the // user sees a blank dialog (I have seen this myself) errorName = i18n("Request Aborted By User"); description = i18n("The request was not completed because it was " "aborted."); solutions << i18n("Retry the request."); break; case KIO::ERR_CYCLIC_COPY: errorName = i18n("Cyclic Link Detected During Copy"); description = i18n("UNIX environments are commonly able to link a file or " "folder to a separate name and/or location. During the requested copy " "operation, KDE detected a link or series of links that results in an " "infinite loop - i.e. the file was (perhaps in a roundabout way) linked " "to itself."); solutions << i18n("Delete one part of the loop in order that it does not " "cause an infinite loop, and try again.") << sSysadmin; break; case KIO::ERR_CANNOT_CREATE_SOCKET: errorName = i18n("Could Not Create Network Connection"); techName = i18n("Could Not Create Socket"); description = i18n("This is a fairly technical error in which a required " "device for network communications (a socket) could not be created."); causes << i18n("The network connection may be incorrectly configured, or " "the network interface may not be enabled."); solutions << sNetwork << sSysadmin; break; case KIO::ERR_CANNOT_CONNECT: errorName = i18n("Connection to Server Refused"); description = i18n("The server %1 refused to allow this " "computer to make a connection.", host); causes << i18n("The server, while currently connected to the Internet, " "may not be configured to allow requests.") << i18n("The server, while currently connected to the Internet, " "may not be running the requested service (%1).", protocol) << i18n("A network firewall (a device which restricts Internet " "requests), either protecting your network or the network of the server, " "may have intervened, preventing this request."); solutions << sTryagain << sServeradmin << sSysadmin; break; case KIO::ERR_CONNECTION_BROKEN: errorName = i18n("Connection to Server Closed Unexpectedly"); description = i18n("Although a connection was established to " "%1, the connection was closed at an unexpected point " "in the communication.", host); causes << cNetwork << cNetpath << i18n("A protocol error may have occurred, " "causing the server to close the connection as a response to the error."); solutions << sTryagain << sServeradmin << sSysadmin; break; case KIO::ERR_NOT_FILTER_PROTOCOL: errorName = i18n("URL Resource Invalid"); techName = i18n("Protocol %1 is not a Filter Protocol", protocol); description = i18n("The Uniform Resource " "Locator (URL) that you entered did not refer to " "a valid mechanism of accessing the specific resource, " "%1%2.", - !host.isNull() ? host + '/' : QString(), path); + !host.isNull() ? host + QLatin1Char('/') : QString(), path); causes << i18n("KDE is able to communicate through a protocol within a " "protocol. This request specified a protocol be used as such, however " "this protocol is not capable of such an action. This is a rare event, " "and is likely to indicate a programming error."); solutions << sTypo << sSysadmin; break; case KIO::ERR_CANNOT_MOUNT: errorName = i18n("Unable to Initialize Input/Output Device"); techName = i18n("Could Not Mount Device"); description = i18n("The requested device could not be initialized " "(\"mounted\"). The reported error was: %1", errorText); causes << i18n("The device may not be ready, for example there may be " "no media in a removable media device (i.e. no CD-ROM in a CD drive), " "or in the case of a peripheral/portable device, the device may not " "be correctly connected.") << i18n("You may not have permissions to initialize (\"mount\") the " "device. On UNIX systems, often system administrator privileges are " "required to initialize a device.") << cHardware; solutions << i18n("Check that the device is ready; removable drives " "must contain media, and portable devices must be connected and powered " "on.; and try again.") << sAccess << sSysadmin; break; case KIO::ERR_CANNOT_UNMOUNT: errorName = i18n("Unable to Uninitialize Input/Output Device"); techName = i18n("Could Not Unmount Device"); description = i18n("The requested device could not be uninitialized " "(\"unmounted\"). The reported error was: %1", errorText); causes << i18n("The device may be busy, that is, still in use by " "another application or user. Even such things as having an open " "browser window on a location on this device may cause the device to " "remain in use.") << i18n("You may not have permissions to uninitialize (\"unmount\") " "the device. On UNIX systems, system administrator privileges are " "often required to uninitialize a device.") << cHardware; solutions << i18n("Check that no applications are accessing the device, " "and try again.") << sAccess << sSysadmin; break; case KIO::ERR_CANNOT_READ: errorName = i18n("Cannot Read From Resource"); description = i18n("This means that although the resource, " "%1, was able to be opened, an error occurred while " "reading the contents of the resource.", url); causes << i18n("You may not have permissions to read from the resource."); if (!isSlaveNetwork) { causes << cNetwork; } causes << cHardware; solutions << sAccess; if (!isSlaveNetwork) { solutions << sNetwork; } solutions << sSysadmin; break; case KIO::ERR_CANNOT_WRITE: errorName = i18n("Cannot Write to Resource"); description = i18n("This means that although the resource, %1" ", was able to be opened, an error occurred while writing to the resource.", url); causes << i18n("You may not have permissions to write to the resource."); if (!isSlaveNetwork) { causes << cNetwork; } causes << cHardware; solutions << sAccess; if (!isSlaveNetwork) { solutions << sNetwork; } solutions << sSysadmin; break; case KIO::ERR_CANNOT_BIND: errorName = i18n("Could Not Listen for Network Connections"); techName = i18n("Could Not Bind"); description = i18n("This is a fairly technical error in which a required " "device for network communications (a socket) could not be established " "to listen for incoming network connections."); causes << i18n("The network connection may be incorrectly configured, or " "the network interface may not be enabled."); solutions << sNetwork << sSysadmin; break; case KIO::ERR_CANNOT_LISTEN: errorName = i18n("Could Not Listen for Network Connections"); techName = i18n("Could Not Listen"); description = i18n("This is a fairly technical error in which a required " "device for network communications (a socket) could not be established " "to listen for incoming network connections."); causes << i18n("The network connection may be incorrectly configured, or " "the network interface may not be enabled."); solutions << sNetwork << sSysadmin; break; case KIO::ERR_CANNOT_ACCEPT: errorName = i18n("Could Not Accept Network Connection"); description = i18n("This is a fairly technical error in which an error " "occurred while attempting to accept an incoming network connection."); causes << i18n("The network connection may be incorrectly configured, or " "the network interface may not be enabled.") << i18n("You may not have permissions to accept the connection."); solutions << sNetwork << sSysadmin; break; case KIO::ERR_CANNOT_LOGIN: errorName = i18n("Could Not Login: %1", errorText); description = i18n("An attempt to login to perform the requested " "operation was unsuccessful."); causes << i18n("You may have supplied incorrect authentication details or " "none at all.") << i18n("Your account may not have permission to access the " "specified resource.") << cProtocol; solutions << i18n("Retry the request and ensure your authentication details " "are entered correctly.") << sServeradmin << sSysadmin; break; case KIO::ERR_CANNOT_STAT: errorName = i18n("Could Not Determine Resource Status"); techName = i18n("Could Not Stat Resource"); description = i18n("An attempt to determine information about the status " "of the resource %1, such as the resource name, type, " "size, etc., was unsuccessful.", url); causes << i18n("The specified resource may not have existed or may " "not be accessible.") << cProtocol << cHardware; solutions << i18n("Retry the request and ensure your authentication details " "are entered correctly.") << sSysadmin; break; case KIO::ERR_CANNOT_CLOSEDIR: //result = i18n( "Could not terminate listing %1" ).arg( errorText ); errorName = i18n("Could Not Cancel Listing"); techName = i18n("FIXME: Document this"); break; case KIO::ERR_CANNOT_MKDIR: errorName = i18n("Could Not Create Folder"); description = i18n("An attempt to create the requested folder failed."); causes << cAccess << i18n("The location where the folder was to be created " "may not exist."); if (!isSlaveNetwork) { causes << cProtocol; } solutions << i18n("Retry the request.") << sAccess; break; case KIO::ERR_CANNOT_RMDIR: errorName = i18n("Could Not Remove Folder"); description = i18n("An attempt to remove the specified folder, " "%1, failed.", path); causes << i18n("The specified folder may not exist.") << i18n("The specified folder may not be empty.") << cAccess; if (!isSlaveNetwork) { causes << cProtocol; } solutions << i18n("Ensure that the folder exists and is empty, and try " "again.") << sAccess; break; case KIO::ERR_CANNOT_RESUME: errorName = i18n("Could Not Resume File Transfer"); description = i18n("The specified request asked that the transfer of " "file %1 be resumed at a certain point of the " "transfer. This was not possible.", filename); causes << i18n("The protocol, or the server, may not support file " "resuming."); solutions << i18n("Retry the request without attempting to resume " "transfer."); break; case KIO::ERR_CANNOT_RENAME: errorName = i18n("Could Not Rename Resource"); description = i18n("An attempt to rename the specified resource " "%1 failed.", url); causes << cAccess << cExists; if (!isSlaveNetwork) { causes << cProtocol; } solutions << sAccess << sExists; break; case KIO::ERR_CANNOT_CHMOD: errorName = i18n("Could Not Alter Permissions of Resource"); description = i18n("An attempt to alter the permissions on the specified " "resource %1 failed.", url); causes << cAccess << cExists; solutions << sAccess << sExists; break; case KIO::ERR_CANNOT_CHOWN: errorName = i18n("Could Not Change Ownership of Resource"); description = i18n("An attempt to change the ownership of the specified " "resource %1 failed.", url); causes << cAccess << cExists; solutions << sAccess << sExists; break; case KIO::ERR_CANNOT_DELETE: errorName = i18n("Could Not Delete Resource"); description = i18n("An attempt to delete the specified resource " "%1 failed.", url); causes << cAccess << cExists; solutions << sAccess << sExists; break; case KIO::ERR_SLAVE_DIED: errorName = i18n("Unexpected Program Termination"); description = i18n("The program on your computer which provides access " "to the %1 protocol has unexpectedly terminated.", url); causes << cBuglikely; solutions << sUpdate << sBugreport; break; case KIO::ERR_OUT_OF_MEMORY: errorName = i18n("Out of Memory"); description = i18n("The program on your computer which provides access " "to the %1 protocol could not obtain the memory " "required to continue.", protocol); causes << cBuglikely; solutions << sUpdate << sBugreport; break; case KIO::ERR_UNKNOWN_PROXY_HOST: errorName = i18n("Unknown Proxy Host"); description = i18n("While retrieving information about the specified " "proxy host, %1, an Unknown Host error was encountered. " "An unknown host error indicates that the requested name could not be " "located on the Internet.", errorText); causes << i18n("There may have been a problem with your network " "configuration, specifically your proxy's hostname. If you have been " "accessing the Internet with no problems recently, this is unlikely.") << cNetwork; solutions << i18n("Double-check your proxy settings and try again.") << sSysadmin; break; case KIO::ERR_CANNOT_AUTHENTICATE: errorName = i18n("Authentication Failed: Method %1 Not Supported", errorText); description = i18n("Although you may have supplied the correct " "authentication details, the authentication failed because the " "method that the server is using is not supported by the KDE " "program implementing the protocol %1.", protocol); solutions << i18n("Please file a bug at " "http://bugs.kde.org/ to inform the KDE team of the unsupported " "authentication method.") << sSysadmin; break; case KIO::ERR_ABORTED: errorName = i18n("Request Aborted"); description = i18n("The request was not completed because it was " "aborted."); solutions << i18n("Retry the request."); break; case KIO::ERR_INTERNAL_SERVER: errorName = i18n("Internal Error in Server"); description = i18n("The program on the server which provides access " "to the %1 protocol has reported an internal error: " "%2.", protocol, errorText); causes << i18n("This is most likely to be caused by a bug in the " "server program. Please consider submitting a full bug report as " "detailed below."); solutions << i18n("Contact the administrator of the server " "to advise them of the problem.") << i18n("If you know who the authors of the server software are, " "submit the bug report directly to them."); break; case KIO::ERR_SERVER_TIMEOUT: errorName = i18n("Timeout Error"); description = i18n("Although contact was made with the server, a " "response was not received within the amount of time allocated for " "the request as follows:
    " "
  • Timeout for establishing a connection: %1 seconds
  • " "
  • Timeout for receiving a response: %2 seconds
  • " "
  • Timeout for accessing proxy servers: %3 seconds
" "Please note that you can alter these timeout settings in the KDE " "System Settings, by selecting Network Settings -> Connection Preferences.", KProtocolManager::connectTimeout(), KProtocolManager::responseTimeout(), KProtocolManager::proxyConnectTimeout()); causes << cNetpath << i18n("The server was too busy responding to other " "requests to respond."); solutions << sTryagain << sServeradmin; break; case KIO::ERR_UNKNOWN: errorName = i18n("Unknown Error"); description = i18n("The program on your computer which provides access " "to the %1 protocol has reported an unknown error: " "%2.", protocol, errorText); causes << cBug; solutions << sUpdate << sBugreport; break; case KIO::ERR_UNKNOWN_INTERRUPT: errorName = i18n("Unknown Interruption"); description = i18n("The program on your computer which provides access " "to the %1 protocol has reported an interruption of " "an unknown type: %2.", protocol, errorText); causes << cBug; solutions << sUpdate << sBugreport; break; case KIO::ERR_CANNOT_DELETE_ORIGINAL: errorName = i18n("Could Not Delete Original File"); description = i18n("The requested operation required the deleting of " "the original file, most likely at the end of a file move operation. " "The original file %1 could not be deleted.", errorText); causes << cAccess; solutions << sAccess; break; case KIO::ERR_CANNOT_DELETE_PARTIAL: errorName = i18n("Could Not Delete Temporary File"); description = i18n("The requested operation required the creation of " "a temporary file in which to save the new file while being " "downloaded. This temporary file %1 could not be " "deleted.", errorText); causes << cAccess; solutions << sAccess; break; case KIO::ERR_CANNOT_RENAME_ORIGINAL: errorName = i18n("Could Not Rename Original File"); description = i18n("The requested operation required the renaming of " "the original file %1, however it could not be " "renamed.", errorText); causes << cAccess; solutions << sAccess; break; case KIO::ERR_CANNOT_RENAME_PARTIAL: errorName = i18n("Could Not Rename Temporary File"); description = i18n("The requested operation required the creation of " "a temporary file %1, however it could not be " "created.", errorText); causes << cAccess; solutions << sAccess; break; case KIO::ERR_CANNOT_SYMLINK: errorName = i18n("Could Not Create Link"); techName = i18n("Could Not Create Symbolic Link"); description = i18n("The requested symbolic link %1 could not be created.", errorText); causes << cAccess; solutions << sAccess; break; case KIO::ERR_NO_CONTENT: errorName = i18n("No Content"); description = errorText; break; case KIO::ERR_DISK_FULL: errorName = i18n("Disk Full"); description = i18n("The requested file %1 could not be " "written to as there is inadequate disk space.", errorText); solutions << i18n("Free up enough disk space by 1) deleting unwanted and " "temporary files; 2) archiving files to removable media storage such as " "CD-Recordable discs; or 3) obtain more storage capacity.") << sSysadmin; break; case KIO::ERR_IDENTICAL_FILES: errorName = i18n("Source and Destination Files Identical"); description = i18n("The operation could not be completed because the " "source and destination files are the same file."); solutions << i18n("Choose a different filename for the destination file."); break; case KIO::ERR_DROP_ON_ITSELF: errorName = i18n("File or Folder dropped onto itself"); description = i18n("The operation could not be completed because the " "source and destination file or folder are the same."); solutions << i18n("Drop the item into a different file or folder."); break; // We assume that the slave has all the details case KIO::ERR_SLAVE_DEFINED: errorName.clear(); description = errorText; break; case KIO::ERR_CANNOT_MOVE_INTO_ITSELF: errorName = i18n("Folder moved into itself"); description = i18n("The operation could not be completed because the " "source can not be moved into itself."); solutions << i18n("Move the item into a different folder."); break; case KIO::ERR_PASSWD_SERVER: errorName = i18n("Could not communicate with password server"); description = i18n("The operation could not be completed because the " "service for requesting passwords (kpasswdserver) couldn't be contacted"); solutions << i18n("Try restarting your session, or look in the logs for errors from kiod."); break; case KIO::ERR_CANNOT_CREATE_SLAVE: errorName = i18n("Cannot Initiate the %1 Protocol", protocol); techName = i18n("Unable to Create io-slave"); description = i18n("The io-slave which provides access " "to the %1 protocol could not be started. This is " "usually due to technical reasons.", protocol); causes << i18n("klauncher could not find or start the plugin which provides the protocol." "This means you may have an outdated version of the plugin."); solutions << sUpdate << sSysadmin; break; default: // fall back to the plain error... errorName = i18n("Undocumented Error"); description = buildErrorString(errorCode, errorText); } QByteArray ret; QDataStream stream(&ret, QIODevice::WriteOnly); stream << errorName << techName << description << causes << solutions; return ret; } QFile::Permissions KIO::convertPermissions(int permissions) { QFile::Permissions qPermissions; if (permissions > 0) { if (permissions & S_IRUSR) { qPermissions |= QFile::ReadOwner; } if (permissions & S_IWUSR) { qPermissions |= QFile::WriteOwner; } if (permissions & S_IXUSR) { qPermissions |= QFile::ExeOwner; } if (permissions & S_IRGRP) { qPermissions |= QFile::ReadGroup; } if (permissions & S_IWGRP) { qPermissions |= QFile::WriteGroup; } if (permissions & S_IXGRP) { qPermissions |= QFile::ExeGroup; } if (permissions & S_IROTH) { qPermissions |= QFile::ReadOther; } if (permissions & S_IWOTH) { qPermissions |= QFile::WriteOther; } if (permissions & S_IXOTH) { qPermissions |= QFile::ExeOther; } } return qPermissions; } diff --git a/src/core/kacl.cpp b/src/core/kacl.cpp index 38e3aff9..20578366 100644 --- a/src/core/kacl.cpp +++ b/src/core/kacl.cpp @@ -1,722 +1,722 @@ /* This file is part of the KDE project Copyright (C) 2005 - 2007 Till Adam 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. */ // $Id: kacl.cpp 424977 2005-06-13 15:13:22Z tilladam $ #include "kacl.h" #include #include #if HAVE_POSIX_ACL #include #include #endif #include #include #include #include #include #include #include "kiocoredebug.h" class Q_DECL_HIDDEN KACL::KACLPrivate { public: KACLPrivate() #if HAVE_POSIX_ACL : m_acl(nullptr) #endif {} #if HAVE_POSIX_ACL KACLPrivate(acl_t acl) : m_acl(acl) {} #endif #if HAVE_POSIX_ACL ~KACLPrivate() { if (m_acl) { acl_free(m_acl); } } #endif // helpers #if HAVE_POSIX_ACL bool setMaskPermissions(unsigned short v); QString getUserName(uid_t uid) const; QString getGroupName(gid_t gid) const; bool setAllUsersOrGroups(const QList< QPair > &list, acl_tag_t type); bool setNamedUserOrGroupPermissions(const QString &name, unsigned short permissions, acl_tag_t type); acl_t m_acl; mutable QHash m_usercache; mutable QHash m_groupcache; #endif }; KACL::KACL(const QString &aclString) : d(new KACLPrivate) { setACL(aclString); } KACL::KACL(mode_t basePermissions) #if HAVE_POSIX_ACL : d(new KACLPrivate(acl_from_mode(basePermissions))) #else : d(new KACLPrivate) #endif { #if !HAVE_POSIX_ACL Q_UNUSED(basePermissions); #endif } KACL::KACL() : d(new KACLPrivate) { } KACL::KACL(const KACL &rhs) : d(new KACLPrivate) { setACL(rhs.asString()); } KACL::~KACL() { delete d; } KACL &KACL::operator=(const KACL &rhs) { if (this != &rhs) { setACL(rhs.asString()); } return *this; } bool KACL::operator==(const KACL &rhs) const { #if HAVE_POSIX_ACL return (acl_cmp(d->m_acl, rhs.d->m_acl) == 0); #else Q_UNUSED(rhs); return true; #endif } bool KACL::operator!=(const KACL &rhs) const { return !operator==(rhs); } bool KACL::isValid() const { bool valid = false; #if HAVE_POSIX_ACL if (d->m_acl) { valid = (acl_valid(d->m_acl) == 0); } #endif return valid; } bool KACL::isExtended() const { #if HAVE_POSIX_ACL return (acl_equiv_mode(d->m_acl, nullptr) != 0); #else return false; #endif } #if HAVE_POSIX_ACL static acl_entry_t entryForTag(acl_t acl, acl_tag_t tag) { acl_entry_t entry; int ret = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); while (ret == 1) { acl_tag_t currentTag; acl_get_tag_type(entry, ¤tTag); if (currentTag == tag) { return entry; } ret = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry); } return nullptr; } static unsigned short entryToPermissions(acl_entry_t entry) { if (entry == nullptr) { return 0; } acl_permset_t permset; if (acl_get_permset(entry, &permset) != 0) { return 0; } return (acl_get_perm(permset, ACL_READ) << 2 | acl_get_perm(permset, ACL_WRITE) << 1 | acl_get_perm(permset, ACL_EXECUTE)); } static void permissionsToEntry(acl_entry_t entry, unsigned short v) { if (entry == nullptr) { return; } acl_permset_t permset; if (acl_get_permset(entry, &permset) != 0) { return; } acl_clear_perms(permset); if (v & 4) { acl_add_perm(permset, ACL_READ); } if (v & 2) { acl_add_perm(permset, ACL_WRITE); } if (v & 1) { acl_add_perm(permset, ACL_EXECUTE); } } #if HAVE_POSIX_ACL #if 0 static void printACL(acl_t acl, const QString &comment) { const char *txt = acl_to_text(acl); qCDebug(KIO_CORE) << comment << txt; acl_free(txt); } #endif #endif static int getUidForName(const QString &name) { - struct passwd *user = getpwnam(name.toLocal8Bit()); + struct passwd *user = getpwnam(name.toLocal8Bit().constData()); if (user) { return user->pw_uid; } else { return -1; } } static int getGidForName(const QString &name) { - struct group *group = getgrnam(name.toLocal8Bit()); + struct group *group = getgrnam(name.toLocal8Bit().constData()); if (group) { return group->gr_gid; } else { return -1; } } #endif // ------------------ begin API implementation ------------ unsigned short KACL::ownerPermissions() const { #if HAVE_POSIX_ACL return entryToPermissions(entryForTag(d->m_acl, ACL_USER_OBJ)); #else return 0; #endif } bool KACL::setOwnerPermissions(unsigned short v) { #if HAVE_POSIX_ACL permissionsToEntry(entryForTag(d->m_acl, ACL_USER_OBJ), v); #else Q_UNUSED(v); #endif return true; } unsigned short KACL::owningGroupPermissions() const { #if HAVE_POSIX_ACL return entryToPermissions(entryForTag(d->m_acl, ACL_GROUP_OBJ)); #else return 0; #endif } bool KACL::setOwningGroupPermissions(unsigned short v) { #if HAVE_POSIX_ACL permissionsToEntry(entryForTag(d->m_acl, ACL_GROUP_OBJ), v); #else Q_UNUSED(v); #endif return true; } unsigned short KACL::othersPermissions() const { #if HAVE_POSIX_ACL return entryToPermissions(entryForTag(d->m_acl, ACL_OTHER)); #else return 0; #endif } bool KACL::setOthersPermissions(unsigned short v) { #if HAVE_POSIX_ACL permissionsToEntry(entryForTag(d->m_acl, ACL_OTHER), v); #else Q_UNUSED(v); #endif return true; } mode_t KACL::basePermissions() const { mode_t perms(0); #if HAVE_POSIX_ACL if (ownerPermissions() & ACL_READ) { perms |= S_IRUSR; } if (ownerPermissions() & ACL_WRITE) { perms |= S_IWUSR; } if (ownerPermissions() & ACL_EXECUTE) { perms |= S_IXUSR; } if (owningGroupPermissions() & ACL_READ) { perms |= S_IRGRP; } if (owningGroupPermissions() & ACL_WRITE) { perms |= S_IWGRP; } if (owningGroupPermissions() & ACL_EXECUTE) { perms |= S_IXGRP; } if (othersPermissions() & ACL_READ) { perms |= S_IROTH; } if (othersPermissions() & ACL_WRITE) { perms |= S_IWOTH; } if (othersPermissions() & ACL_EXECUTE) { perms |= S_IXOTH; } #endif return perms; } unsigned short KACL::maskPermissions(bool &exists) const { exists = true; #if HAVE_POSIX_ACL acl_entry_t entry = entryForTag(d->m_acl, ACL_MASK); if (entry == nullptr) { exists = false; return 0; } return entryToPermissions(entry); #else return 0; #endif } #if HAVE_POSIX_ACL bool KACL::KACLPrivate::setMaskPermissions(unsigned short v) { acl_entry_t entry = entryForTag(m_acl, ACL_MASK); if (entry == nullptr) { acl_create_entry(&m_acl, &entry); acl_set_tag_type(entry, ACL_MASK); } permissionsToEntry(entry, v); return true; } #endif bool KACL::setMaskPermissions(unsigned short v) { #if HAVE_POSIX_ACL return d->setMaskPermissions(v); #else Q_UNUSED(v); return true; #endif } #if HAVE_POSIX_ACL using unique_ptr_acl_free = std::unique_ptr; #endif /************************** * Deal with named users * **************************/ unsigned short KACL::namedUserPermissions(const QString &name, bool *exists) const { #if HAVE_POSIX_ACL acl_entry_t entry; *exists = false; int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry); while (ret == 1) { acl_tag_t currentTag; acl_get_tag_type(entry, ¤tTag); if (currentTag == ACL_USER) { const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free); const uid_t id = *(static_cast(idptr.get())); if (d->getUserName(id) == name) { *exists = true; return entryToPermissions(entry); } } ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry); } #else Q_UNUSED(name); Q_UNUSED(exists); #endif return 0; } #if HAVE_POSIX_ACL bool KACL::KACLPrivate::setNamedUserOrGroupPermissions(const QString &name, unsigned short permissions, acl_tag_t type) { bool allIsWell = true; acl_t newACL = acl_dup(m_acl); acl_entry_t entry; bool createdNewEntry = false; bool found = false; int ret = acl_get_entry(newACL, ACL_FIRST_ENTRY, &entry); while (ret == 1) { acl_tag_t currentTag; acl_get_tag_type(entry, ¤tTag); if (currentTag == type) { const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free); const int id = * (static_cast(idptr.get())); // We assume that sizeof(uid_t) == sizeof(gid_t) const QString entryName = type == ACL_USER ? getUserName(id) : getGroupName(id); if (entryName == name) { // found him, update permissionsToEntry(entry, permissions); found = true; break; } } ret = acl_get_entry(newACL, ACL_NEXT_ENTRY, &entry); } if (!found) { acl_create_entry(&newACL, &entry); acl_set_tag_type(entry, type); int id = type == ACL_USER ? getUidForName(name) : getGidForName(name); if (id == -1 || acl_set_qualifier(entry, &id) != 0) { acl_delete_entry(newACL, entry); allIsWell = false; } else { permissionsToEntry(entry, permissions); createdNewEntry = true; } } if (allIsWell && createdNewEntry) { // 23.1.1 of 1003.1e states that as soon as there is a named user or // named group entry, there needs to be a mask entry as well, so add // one, if the user hasn't explicitly set one. if (entryForTag(newACL, ACL_MASK) == nullptr) { acl_calc_mask(&newACL); } } if (!allIsWell || acl_valid(newACL) != 0) { acl_free(newACL); allIsWell = false; } else { acl_free(m_acl); m_acl = newACL; } return allIsWell; } #endif bool KACL::setNamedUserPermissions(const QString &name, unsigned short permissions) { #if HAVE_POSIX_ACL return d->setNamedUserOrGroupPermissions(name, permissions, ACL_USER); #else Q_UNUSED(name); Q_UNUSED(permissions); return true; #endif } ACLUserPermissionsList KACL::allUserPermissions() const { ACLUserPermissionsList list; #if HAVE_POSIX_ACL acl_entry_t entry; int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry); while (ret == 1) { acl_tag_t currentTag; acl_get_tag_type(entry, ¤tTag); if (currentTag == ACL_USER) { const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free); const uid_t id = *(static_cast(idptr.get())); QString name = d->getUserName(id); unsigned short permissions = entryToPermissions(entry); ACLUserPermissions pair = qMakePair(name, permissions); list.append(pair); } ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry); } #endif return list; } #if HAVE_POSIX_ACL bool KACL::KACLPrivate::setAllUsersOrGroups(const QList< QPair > &list, acl_tag_t type) { bool allIsWell = true; bool atLeastOneUserOrGroup = false; // make working copy, in case something goes wrong acl_t newACL = acl_dup(m_acl); acl_entry_t entry; //printACL( newACL, "Before cleaning: " ); // clear user entries int ret = acl_get_entry(newACL, ACL_FIRST_ENTRY, &entry); while (ret == 1) { acl_tag_t currentTag; acl_get_tag_type(entry, ¤tTag); if (currentTag == type) { acl_delete_entry(newACL, entry); // we have to start from the beginning, the iterator is // invalidated, on deletion ret = acl_get_entry(newACL, ACL_FIRST_ENTRY, &entry); } else { ret = acl_get_entry(newACL, ACL_NEXT_ENTRY, &entry); } } //printACL( newACL, "After cleaning out entries: " ); // now add the entries from the list QList< QPair >::const_iterator it = list.constBegin(); while (it != list.constEnd()) { acl_create_entry(&newACL, &entry); acl_set_tag_type(entry, type); int id = type == ACL_USER ? getUidForName((*it).first) : getGidForName((*it).first); if (id == -1 || acl_set_qualifier(entry, &id) != 0) { // user or group doesn't exist => error acl_delete_entry(newACL, entry); allIsWell = false; break; } else { permissionsToEntry(entry, (*it).second); atLeastOneUserOrGroup = true; } ++it; } //printACL( newACL, "After adding entries: " ); if (allIsWell && atLeastOneUserOrGroup) { // 23.1.1 of 1003.1e states that as soon as there is a named user or // named group entry, there needs to be a mask entry as well, so add // one, if the user hasn't explicitly set one. if (entryForTag(newACL, ACL_MASK) == nullptr) { acl_calc_mask(&newACL); } } if (allIsWell && (acl_valid(newACL) == 0)) { acl_free(m_acl); m_acl = newACL; } else { acl_free(newACL); } return allIsWell; } #endif bool KACL::setAllUserPermissions(const ACLUserPermissionsList &users) { #if HAVE_POSIX_ACL return d->setAllUsersOrGroups(users, ACL_USER); #else Q_UNUSED(users); return true; #endif } /************************** * Deal with named groups * **************************/ unsigned short KACL::namedGroupPermissions(const QString &name, bool *exists) const { *exists = false; #if HAVE_POSIX_ACL acl_entry_t entry; int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry); while (ret == 1) { acl_tag_t currentTag; acl_get_tag_type(entry, ¤tTag); if (currentTag == ACL_GROUP) { const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free); const gid_t id = *(static_cast(idptr.get())); if (d->getGroupName(id) == name) { *exists = true; return entryToPermissions(entry); } } ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry); } #else Q_UNUSED(name); #endif return 0; } bool KACL::setNamedGroupPermissions(const QString &name, unsigned short permissions) { #if HAVE_POSIX_ACL return d->setNamedUserOrGroupPermissions(name, permissions, ACL_GROUP); #else Q_UNUSED(name); Q_UNUSED(permissions); return true; #endif } ACLGroupPermissionsList KACL::allGroupPermissions() const { ACLGroupPermissionsList list; #if HAVE_POSIX_ACL acl_entry_t entry; int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry); while (ret == 1) { acl_tag_t currentTag; acl_get_tag_type(entry, ¤tTag); if (currentTag == ACL_GROUP) { const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free); const gid_t id = *(static_cast(idptr.get())); QString name = d->getGroupName(id); unsigned short permissions = entryToPermissions(entry); ACLGroupPermissions pair = qMakePair(name, permissions); list.append(pair); } ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry); } #endif return list; } bool KACL::setAllGroupPermissions(const ACLGroupPermissionsList &groups) { #if HAVE_POSIX_ACL return d->setAllUsersOrGroups(groups, ACL_GROUP); #else Q_UNUSED(groups); return true; #endif } /************************** * from and to string * **************************/ bool KACL::setACL(const QString &aclStr) { bool ret = false; #if HAVE_POSIX_ACL - acl_t temp = acl_from_text(aclStr.toLatin1()); + acl_t temp = acl_from_text(aclStr.toLatin1().constData()); if (acl_valid(temp) != 0) { // TODO errno is set, what to do with it here? acl_free(temp); } else { if (d->m_acl) { acl_free(d->m_acl); } d->m_acl = temp; ret = true; } #else Q_UNUSED(aclStr); #endif return ret; } QString KACL::asString() const { #if HAVE_POSIX_ACL ssize_t size = 0; char *txt = acl_to_text(d->m_acl, &size); const QString ret = QString::fromLatin1(txt, size); acl_free(txt); return ret; #else return QString(); #endif } // helpers #if HAVE_POSIX_ACL QString KACL::KACLPrivate::getUserName(uid_t uid) const { if (!m_usercache.contains(uid)) { struct passwd *user = getpwuid(uid); if (user) { m_usercache.insert(uid, QString::fromLatin1(user->pw_name)); } else { return QString::number(uid); } } return m_usercache[uid]; } QString KACL::KACLPrivate::getGroupName(gid_t gid) const { if (!m_groupcache.contains(gid)) { struct group *grp = getgrgid(gid); if (grp) { m_groupcache.insert(gid, QString::fromLatin1(grp->gr_name)); } else { return QString::number(gid); } } return m_groupcache[gid]; } #endif void KACL::virtual_hook(int, void *) { /*BASE::virtual_hook( id, data );*/ } QDataStream &operator<< (QDataStream &s, const KACL &a) { s << a.asString(); return s; } QDataStream &operator>> (QDataStream &s, KACL &a) { QString str; s >> str; a.setACL(str); return s; } diff --git a/src/core/kcoredirlister.cpp b/src/core/kcoredirlister.cpp index 5c3652ab..e3974009 100644 --- a/src/core/kcoredirlister.cpp +++ b/src/core/kcoredirlister.cpp @@ -1,2809 +1,2809 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis 2000 Carsten Pfeiffer 2003-2005 David Faure 2001-2006 Michael Brade This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kcoredirlister.h" #include "kcoredirlister_p.h" #include #include #include "kprotocolmanager.h" #include "kmountpoint.h" #include "kiocoredebug.h" #include "../pathhelpers_p.h" #include #include #include #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(KIO_CORE_DIRLISTER) Q_LOGGING_CATEGORY(KIO_CORE_DIRLISTER, "kf5.kio.core.dirlister", QtWarningMsg) // Enable this to get printDebug() called often, to see the contents of the cache //#define DEBUG_CACHE // Make really sure it doesn't get activated in the final build #ifdef NDEBUG #undef DEBUG_CACHE #endif Q_GLOBAL_STATIC(KCoreDirListerCache, kDirListerCache) KCoreDirListerCache::KCoreDirListerCache() : itemsCached(10), // keep the last 10 directories around m_cacheHiddenFiles(10) // keep the last 10 ".hidden" files around { qCDebug(KIO_CORE_DIRLISTER); connect(&pendingUpdateTimer, &QTimer::timeout, this, &KCoreDirListerCache::processPendingUpdates); pendingUpdateTimer.setSingleShot(true); connect(KDirWatch::self(), &KDirWatch::dirty, this, &KCoreDirListerCache::slotFileDirty); connect(KDirWatch::self(), &KDirWatch::created, this, &KCoreDirListerCache::slotFileCreated); connect(KDirWatch::self(), &KDirWatch::deleted, this, &KCoreDirListerCache::slotFileDeleted); kdirnotify = new org::kde::KDirNotify(QString(), QString(), QDBusConnection::sessionBus(), this); connect(kdirnotify, &org::kde::KDirNotify::FileRenamedWithLocalPath, this, &KCoreDirListerCache::slotFileRenamed); connect(kdirnotify, &org::kde::KDirNotify::FilesAdded , this, &KCoreDirListerCache::slotFilesAdded); connect(kdirnotify, &org::kde::KDirNotify::FilesChanged, this, &KCoreDirListerCache::slotFilesChanged); connect(kdirnotify, &org::kde::KDirNotify::FilesRemoved, this, QOverload::of(&KCoreDirListerCache::slotFilesRemoved)); // Probably not needed in KF5 anymore: // The use of KUrl::url() in ~DirItem (sendSignal) crashes if the static for QRegExpEngine got deleted already, // so we need to destroy the KCoreDirListerCache before that. //qAddPostRoutine(kDirListerCache.destroy); } KCoreDirListerCache::~KCoreDirListerCache() { qCDebug(KIO_CORE_DIRLISTER); qDeleteAll(itemsInUse); itemsInUse.clear(); itemsCached.clear(); directoryData.clear(); m_cacheHiddenFiles.clear(); if (KDirWatch::exists()) { KDirWatch::self()->disconnect(this); } } // setting _reload to true will emit the old files and // call updateDirectory bool KCoreDirListerCache::listDir(KCoreDirLister *lister, const QUrl &_u, bool _keep, bool _reload) { QUrl _url(_u); _url.setPath(QDir::cleanPath(_url.path())); // kill consecutive slashes if (!_url.host().isEmpty() && KProtocolInfo::protocolClass(_url.scheme()) == QLatin1String(":local") && _url.scheme() != QLatin1String("file")) { // ":local" protocols ignore the hostname, so strip it out preventively - #160057 // kio_file is special cased since it does honor the hostname (by redirecting to e.g. smb) _url.setHost(QString()); if (_keep == false) { emit lister->redirection(_url); } } // like this we don't have to worry about trailing slashes any further _url = _url.adjusted(QUrl::StripTrailingSlash); QString resolved; if (_url.isLocalFile()) { // Resolve symlinks (#213799) const QString local = _url.toLocalFile(); resolved = QFileInfo(local).canonicalFilePath(); if (local != resolved) { canonicalUrls[QUrl::fromLocalFile(resolved)].append(_url); } // TODO: remove entry from canonicalUrls again in forgetDirs // Note: this is why we use a QStringList value in there rather than a QSet: // we can just remove one entry and not have to worry about other dirlisters // (the non-unicity of the stringlist gives us the refcounting, basically). } if (!validUrl(lister, _url)) { qCDebug(KIO_CORE_DIRLISTER) << lister << "url=" << _url << "not a valid url"; return false; } qCDebug(KIO_CORE_DIRLISTER) << lister << "url=" << _url << "keep=" << _keep << "reload=" << _reload; #ifdef DEBUG_CACHE printDebug(); #endif if (!_keep) { // stop any running jobs for lister stop(lister, true /*silent*/); // clear our internal list for lister forgetDirs(lister); lister->d->rootFileItem = KFileItem(); } else if (lister->d->lstDirs.contains(_url)) { // stop the job listing _url for this lister stopListingUrl(lister, _url, true /*silent*/); // remove the _url as well, it will be added in a couple of lines again! // forgetDirs with three args does not do this // TODO: think about moving this into forgetDirs lister->d->lstDirs.removeAll(_url); // clear _url for lister forgetDirs(lister, _url, true); if (lister->d->url == _url) { lister->d->rootFileItem = KFileItem(); } } lister->d->complete = false; lister->d->lstDirs.append(_url); if (lister->d->url.isEmpty() || !_keep) { // set toplevel URL only if not set yet lister->d->url = _url; } DirItem *itemU = itemsInUse.value(_url); KCoreDirListerCacheDirectoryData &dirData = directoryData[_url]; // find or insert if (dirData.listersCurrentlyListing.isEmpty()) { // if there is an update running for _url already we get into // the following case - it will just be restarted by updateDirectory(). dirData.listersCurrentlyListing.append(lister); DirItem *itemFromCache = nullptr; if (itemU || (!_reload && (itemFromCache = itemsCached.take(_url)))) { if (itemU) { qCDebug(KIO_CORE_DIRLISTER) << "Entry already in use:" << _url; // if _reload is set, then we'll emit cached items and then updateDirectory. } else { qCDebug(KIO_CORE_DIRLISTER) << "Entry in cache:" << _url; itemsInUse.insert(_url, itemFromCache); itemU = itemFromCache; } if (lister->d->autoUpdate) { itemU->incAutoUpdate(); } if (itemFromCache && itemFromCache->watchedWhileInCache) { itemFromCache->watchedWhileInCache = false;; itemFromCache->decAutoUpdate(); } emit lister->started(_url); // List items from the cache in a delayed manner, just like things would happen // if we were not using the cache. new KCoreDirLister::Private::CachedItemsJob(lister, _url, _reload); } else { // dir not in cache or _reload is true if (_reload) { qCDebug(KIO_CORE_DIRLISTER) << "Reloading directory:" << _url; itemsCached.remove(_url); } else { qCDebug(KIO_CORE_DIRLISTER) << "Listing directory:" << _url; } itemU = new DirItem(_url, resolved); itemsInUse.insert(_url, itemU); if (lister->d->autoUpdate) { itemU->incAutoUpdate(); } // // we have a limit of MAX_JOBS_PER_LISTER concurrently running jobs // if ( lister->d->numJobs() >= MAX_JOBS_PER_LISTER ) // { // pendingUpdates.insert( _url ); // } // else { KIO::ListJob *job = KIO::listDir(_url, KIO::HideProgressInfo); runningListJobs.insert(job, KIO::UDSEntryList()); lister->jobStarted(job); lister->d->connectJob(job); connect(job, &KIO::ListJob::entries, this, &KCoreDirListerCache::slotEntries); connect(job, &KJob::result, this, &KCoreDirListerCache::slotResult); connect(job, &KIO::ListJob::redirection, this, &KCoreDirListerCache::slotRedirection); emit lister->started(_url); } qCDebug(KIO_CORE_DIRLISTER) << "Entry now being listed by" << dirData.listersCurrentlyListing; } } else { qCDebug(KIO_CORE_DIRLISTER) << "Entry currently being listed:" << _url << "by" << dirData.listersCurrentlyListing; #ifdef DEBUG_CACHE printDebug(); #endif emit lister->started(_url); // Maybe listersCurrentlyListing/listersCurrentlyHolding should be QSets? Q_ASSERT(!dirData.listersCurrentlyListing.contains(lister)); dirData.listersCurrentlyListing.append(lister); KIO::ListJob *job = jobForUrl(_url); // job will be 0 if we were listing from cache rather than listing from a kio job. if (job) { lister->jobStarted(job); lister->d->connectJob(job); } Q_ASSERT(itemU); // List existing items in a delayed manner, just like things would happen // if we were not using the cache. qCDebug(KIO_CORE_DIRLISTER) << "Listing" << itemU->lstItems.count() << "cached items soon"; KCoreDirLister::Private::CachedItemsJob* cachedItemsJob = new KCoreDirLister::Private::CachedItemsJob(lister, _url, _reload); if (job) { // The ListJob will take care of emitting completed. // ### If it finishes before the CachedItemsJob, then we'll emit cached items after completed(), not sure how bad this is. cachedItemsJob->setEmitCompleted(false); } #ifdef DEBUG_CACHE printDebug(); #endif } return true; } KCoreDirLister::Private::CachedItemsJob *KCoreDirLister::Private::cachedItemsJobForUrl(const QUrl &url) const { Q_FOREACH (CachedItemsJob *job, m_cachedItemsJobs) { if (job->url() == url) { return job; } } return nullptr; } KCoreDirLister::Private::CachedItemsJob::CachedItemsJob(KCoreDirLister *lister, const QUrl &url, bool reload) : KJob(lister), m_lister(lister), m_url(url), m_reload(reload), m_emitCompleted(true) { qCDebug(KIO_CORE_DIRLISTER) << "Creating CachedItemsJob" << this << "for lister" << lister << url; if (lister->d->cachedItemsJobForUrl(url)) { qCWarning(KIO_CORE) << "Lister" << lister << "has a cached items job already for" << url; } lister->d->m_cachedItemsJobs.append(this); setAutoDelete(true); start(); } // Called by start() via QueuedConnection void KCoreDirLister::Private::CachedItemsJob::done() { if (!m_lister) { // job was already killed, but waiting deletion due to deleteLater return; } kDirListerCache()->emitItemsFromCache(this, m_lister, m_url, m_reload, m_emitCompleted); emitResult(); } bool KCoreDirLister::Private::CachedItemsJob::doKill() { qCDebug(KIO_CORE_DIRLISTER) << this; kDirListerCache()->forgetCachedItemsJob(this, m_lister, m_url); if (!property("_kdlc_silent").toBool()) { emit m_lister->canceled(m_url); emit m_lister->canceled(); } m_lister = nullptr; return true; } void KCoreDirListerCache::emitItemsFromCache(KCoreDirLister::Private::CachedItemsJob *cachedItemsJob, KCoreDirLister *lister, const QUrl &_url, bool _reload, bool _emitCompleted) { KCoreDirLister::Private *kdl = lister->d; kdl->complete = false; DirItem *itemU = kDirListerCache()->itemsInUse.value(_url); if (!itemU) { qCWarning(KIO_CORE) << "Can't find item for directory" << _url << "anymore"; } else { const QList items = itemU->lstItems; const KFileItem rootItem = itemU->rootItem; _reload = _reload || !itemU->complete; if (kdl->rootFileItem.isNull() && !rootItem.isNull() && kdl->url == _url) { kdl->rootFileItem = rootItem; } if (!items.isEmpty()) { qCDebug(KIO_CORE_DIRLISTER) << "emitting" << items.count() << "for lister" << lister; kdl->addNewItems(_url, items); kdl->emitItems(); } } forgetCachedItemsJob(cachedItemsJob, lister, _url); // Emit completed, unless we were told not to, // or if listDir() was called while another directory listing for this dir was happening, // so we "joined" it. We detect that using jobForUrl to ensure it's a real ListJob, // not just a lister-specific CachedItemsJob (which wouldn't emit completed for us). if (_emitCompleted) { kdl->complete = true; emit lister->completed(_url); emit lister->completed(); if (_reload) { updateDirectory(_url); } } } void KCoreDirListerCache::forgetCachedItemsJob(KCoreDirLister::Private::CachedItemsJob *cachedItemsJob, KCoreDirLister *lister, const QUrl &_url) { // Modifications to data structures only below this point; // so that addNewItems is called with a consistent state lister->d->m_cachedItemsJobs.removeAll(cachedItemsJob); KCoreDirListerCacheDirectoryData &dirData = directoryData[_url]; Q_ASSERT(dirData.listersCurrentlyListing.contains(lister)); KIO::ListJob *listJob = jobForUrl(_url); if (!listJob) { Q_ASSERT(!dirData.listersCurrentlyHolding.contains(lister)); qCDebug(KIO_CORE_DIRLISTER) << "Moving from listing to holding, because no more job" << lister << _url; dirData.listersCurrentlyHolding.append(lister); dirData.listersCurrentlyListing.removeAll(lister); } else { qCDebug(KIO_CORE_DIRLISTER) << "Still having a listjob" << listJob << ", so not moving to currently-holding."; } } bool KCoreDirListerCache::validUrl(KCoreDirLister *lister, const QUrl &url) const { if (!url.isValid()) { qCWarning(KIO_CORE) << url.errorString(); lister->handleErrorMessage(i18n("Malformed URL\n%1", url.errorString())); return false; } if (!KProtocolManager::supportsListing(url)) { lister->handleErrorMessage(i18n("URL cannot be listed\n%1", url.toString())); return false; } return true; } void KCoreDirListerCache::stop(KCoreDirLister *lister, bool silent) { #ifdef DEBUG_CACHE //printDebug(); #endif qCDebug(KIO_CORE_DIRLISTER) << "lister:" << lister << "silent=" << silent; const QList urls = lister->d->lstDirs; Q_FOREACH (const QUrl &url, urls) { stopListingUrl(lister, url, silent); } #if 0 // test code QHash::iterator dirit = directoryData.begin(); const QHash::iterator dirend = directoryData.end(); for (; dirit != dirend; ++dirit) { KCoreDirListerCacheDirectoryData &dirData = dirit.value(); if (dirData.listersCurrentlyListing.contains(lister)) { qCDebug(KIO_CORE_DIRLISTER) << "ERROR: found lister" << lister << "in list - for" << dirit.key(); Q_ASSERT(false); } } #endif } void KCoreDirListerCache::stopListingUrl(KCoreDirLister *lister, const QUrl &_u, bool silent) { QUrl url(_u); url = url.adjusted(QUrl::StripTrailingSlash); KCoreDirLister::Private::CachedItemsJob *cachedItemsJob = lister->d->cachedItemsJobForUrl(url); if (cachedItemsJob) { if (silent) { cachedItemsJob->setProperty("_kdlc_silent", true); } cachedItemsJob->kill(); // removes job from list, too } // TODO: consider to stop all the "child jobs" of url as well qCDebug(KIO_CORE_DIRLISTER) << lister << " url=" << url; const auto dirit = directoryData.find(url); if (dirit == directoryData.end()) { return; } KCoreDirListerCacheDirectoryData &dirData = dirit.value(); if (dirData.listersCurrentlyListing.contains(lister)) { qCDebug(KIO_CORE_DIRLISTER) << " found lister" << lister << "in list - for" << url; if (dirData.listersCurrentlyListing.count() == 1) { // This was the only dirlister interested in the list job -> kill the job stopListJob(url, silent); } else { // Leave the job running for the other dirlisters, just unsubscribe us. dirData.listersCurrentlyListing.removeAll(lister); if (!silent) { emit lister->canceled(); emit lister->canceled(url); } } } } // Helper for stop() and stopListingUrl() void KCoreDirListerCache::stopListJob(const QUrl &url, bool silent) { // Old idea: if it's an update job, let's just leave the job running. // After all, update jobs do run for "listersCurrentlyHolding", // so there's no reason to kill them just because @p lister is now a holder. // However it could be a long-running non-local job (e.g. filenamesearch), which // the user wants to abort, and which will never be used for updating... // And in any case slotEntries/slotResult is not meant to be called by update jobs. // So, change of plan, let's kill it after all, in a way that triggers slotResult/slotUpdateResult. KIO::ListJob *job = jobForUrl(url); if (job) { qCDebug(KIO_CORE_DIRLISTER) << "Killing list job" << job << "for" << url; if (silent) { job->setProperty("_kdlc_silent", true); } job->kill(KJob::EmitResult); } } void KCoreDirListerCache::setAutoUpdate(KCoreDirLister *lister, bool enable) { // IMPORTANT: this method does not check for the current autoUpdate state! for (auto it = lister->d->lstDirs.constBegin(), cend = lister->d->lstDirs.constEnd(); it != cend; ++it) { DirItem *dirItem = itemsInUse.value(*it); Q_ASSERT(dirItem); if (enable) { dirItem->incAutoUpdate(); } else { dirItem->decAutoUpdate(); } } } void KCoreDirListerCache::forgetDirs(KCoreDirLister *lister) { qCDebug(KIO_CORE_DIRLISTER) << lister; emit lister->clear(); // clear lister->d->lstDirs before calling forgetDirs(), so that // it doesn't contain things that itemsInUse doesn't. When emitting // the canceled signals, lstDirs must not contain anything that // itemsInUse does not contain. (otherwise it might crash in findByName()). const QList lstDirsCopy = lister->d->lstDirs; lister->d->lstDirs.clear(); qCDebug(KIO_CORE_DIRLISTER) << "Iterating over dirs" << lstDirsCopy; for (QList::const_iterator it = lstDirsCopy.begin(); it != lstDirsCopy.end(); ++it) { forgetDirs(lister, *it, false); } } static bool manually_mounted(const QString &path, const KMountPoint::List &possibleMountPoints) { KMountPoint::Ptr mp = possibleMountPoints.findByPath(path); if (!mp) { // not listed in fstab -> yes, manually mounted if (possibleMountPoints.isEmpty()) { // no fstab at all -> don't assume anything return false; } return true; } // noauto -> manually mounted. Otherwise, mounted at boot time, won't be unmounted any time soon hopefully. return mp->mountOptions().contains(QStringLiteral("noauto")); } void KCoreDirListerCache::forgetDirs(KCoreDirLister *lister, const QUrl &_url, bool notify) { qCDebug(KIO_CORE_DIRLISTER) << lister << " _url: " << _url; const QUrl url = _url.adjusted(QUrl::StripTrailingSlash); DirectoryDataHash::iterator dit = directoryData.find(url); if (dit == directoryData.end()) { return; } KCoreDirListerCacheDirectoryData &dirData = *dit; dirData.listersCurrentlyHolding.removeAll(lister); // This lister doesn't care for updates running in anymore KIO::ListJob *job = jobForUrl(url); if (job) { lister->d->jobDone(job); } DirItem *item = itemsInUse.value(url); Q_ASSERT(item); bool insertIntoCache = false; if (dirData.listersCurrentlyHolding.isEmpty() && dirData.listersCurrentlyListing.isEmpty()) { // item not in use anymore -> move into cache if complete directoryData.erase(dit); itemsInUse.remove(url); // this job is a running update which nobody cares about anymore if (job) { killJob(job); qCDebug(KIO_CORE_DIRLISTER) << "Killing update job for " << url; // Well, the user of KCoreDirLister doesn't really care that we're stopping // a background-running job from a previous URL (in listDir) -> commented out. // stop() already emitted canceled. //emit lister->canceled( url ); if (lister->d->numJobs() == 0) { lister->d->complete = true; //emit lister->canceled(); } } if (notify) { lister->d->lstDirs.removeAll(url); emit lister->clear(url); } insertIntoCache = item->complete; if (insertIntoCache) { // TODO(afiestas): remove use of KMountPoint+manually_mounted and port to Solid: // 1) find Volume for the local path "item->url.toLocalFile()" (which could be anywhere // under the mount point) -- probably needs a new operator in libsolid query parser // 2) [**] becomes: if (Drive is hotpluggable or Volume is removable) "set to dirty" else "keep watch" const KMountPoint::List possibleMountPoints = KMountPoint::possibleMountPoints(KMountPoint::NeedMountOptions); // Should we forget the dir for good, or keep a watch on it? // Generally keep a watch, except when it would prevent // unmounting a removable device (#37780) const bool isLocal = item->url.isLocalFile(); bool isManuallyMounted = false; bool containsManuallyMounted = false; if (isLocal) { isManuallyMounted = manually_mounted(item->url.toLocalFile(), possibleMountPoints); if (!isManuallyMounted) { // Look for a manually-mounted directory inside // If there's one, we can't keep a watch either, FAM would prevent unmounting the CDROM // I hope this isn't too slow auto kit = item->lstItems.constBegin(); const auto kend = item->lstItems.constEnd(); for (; kit != kend && !containsManuallyMounted; ++kit) if ((*kit).isDir() && manually_mounted((*kit).url().toLocalFile(), possibleMountPoints)) { containsManuallyMounted = true; } } } if (isManuallyMounted || containsManuallyMounted) { // [**] qCDebug(KIO_CORE_DIRLISTER) << "Not adding a watch on " << item->url << " because it " << ( isManuallyMounted ? "is manually mounted" : "contains a manually mounted subdir" ); item->complete = false; // set to "dirty" } else { item->incAutoUpdate(); // keep watch item->watchedWhileInCache = true; } } else { delete item; item = nullptr; } } if (item && lister->d->autoUpdate) { item->decAutoUpdate(); } // Inserting into QCache must be done last, since it might delete the item if (item && insertIntoCache) { qCDebug(KIO_CORE_DIRLISTER) << lister << "item moved into cache:" << url; itemsCached.insert(url, item); } } void KCoreDirListerCache::updateDirectory(const QUrl &_dir) { qCDebug(KIO_CORE_DIRLISTER) << _dir; const QUrl dir = _dir.adjusted(QUrl::StripTrailingSlash); if (!checkUpdate(dir)) { if (dir.isLocalFile() && !(findByUrl(nullptr, dir).isNull())) { pendingUpdates.insert(dir.toLocalFile()); if (!pendingUpdateTimer.isActive()) { pendingUpdateTimer.start(500); } } return; } // A job can be running to // - only list a new directory: the listers are in listersCurrentlyListing // - only update a directory: the listers are in listersCurrentlyHolding // - update a currently running listing: the listers are in both KCoreDirListerCacheDirectoryData &dirData = directoryData[dir]; QList listers = dirData.listersCurrentlyListing; QList holders = dirData.listersCurrentlyHolding; qCDebug(KIO_CORE_DIRLISTER) << dir << "listers=" << listers << "holders=" << holders; bool killed = false; KIO::ListJob *job = jobForUrl(dir); if (job) { // the job is running already, tell it to do another update at the end // (don't kill it, we would keep doing that during a long download to a slow sshfs mount) job->setProperty("need_another_update", true); return; } else { // Emit any cached items. // updateDirectory() is about the diff compared to the cached items... Q_FOREACH (KCoreDirLister *kdl, listers) { KCoreDirLister::Private::CachedItemsJob *cachedItemsJob = kdl->d->cachedItemsJobForUrl(dir); if (cachedItemsJob) { cachedItemsJob->setEmitCompleted(false); cachedItemsJob->done(); // removes from cachedItemsJobs list delete cachedItemsJob; killed = true; } } } qCDebug(KIO_CORE_DIRLISTER) << "Killed=" << killed; // we don't need to emit canceled signals since we only replaced the job, // the listing is continuing. if (!(listers.isEmpty() || killed)) { qCWarning(KIO_CORE) << "The unexpected happened."; qCWarning(KIO_CORE) << "listers for" << dir << "=" << listers; qCWarning(KIO_CORE) << "job=" << job; Q_FOREACH(KCoreDirLister *kdl, listers) { qCDebug(KIO_CORE_DIRLISTER) << "lister" << kdl << "m_cachedItemsJobs=" << kdl->d->m_cachedItemsJobs; } #ifndef NDEBUG printDebug(); #endif } Q_ASSERT(listers.isEmpty() || killed); job = KIO::listDir(dir, KIO::HideProgressInfo); runningListJobs.insert(job, KIO::UDSEntryList()); connect(job, &KIO::ListJob::entries, this, &KCoreDirListerCache::slotUpdateEntries); connect(job, &KJob::result, this, &KCoreDirListerCache::slotUpdateResult); qCDebug(KIO_CORE_DIRLISTER) << "update started in" << dir; foreach (KCoreDirLister *kdl, listers) { kdl->jobStarted(job); } if (!holders.isEmpty()) { if (!killed) { foreach (KCoreDirLister *kdl, holders) { kdl->jobStarted(job); emit kdl->started(dir); } } else { foreach (KCoreDirLister *kdl, holders) { kdl->jobStarted(job); } } } } bool KCoreDirListerCache::checkUpdate(const QUrl &_dir) { if (!itemsInUse.contains(_dir)) { DirItem *item = itemsCached[_dir]; if (item && item->complete) { item->complete = false; item->watchedWhileInCache = false; item->decAutoUpdate(); // Hmm, this debug output might include login/password from the _dir URL. qCDebug(KIO_CORE_DIRLISTER) << "directory " << _dir << " not in use, marked dirty."; } //else qCDebug(KIO_CORE_DIRLISTER) << "aborted, directory " << _dir << " not in cache."; return false; } else { return true; } } KFileItem KCoreDirListerCache::itemForUrl(const QUrl &url) const { return findByUrl(nullptr, url); } KCoreDirListerCache::DirItem *KCoreDirListerCache::dirItemForUrl(const QUrl &dir) const { const QUrl url = dir.adjusted(QUrl::StripTrailingSlash); DirItem *item = itemsInUse.value(url); if (!item) { item = itemsCached[url]; } return item; } QList *KCoreDirListerCache::itemsForDir(const QUrl &dir) const { DirItem *item = dirItemForUrl(dir); return item ? &item->lstItems : nullptr; } KFileItem KCoreDirListerCache::findByName(const KCoreDirLister *lister, const QString &_name) const { Q_ASSERT(lister); for (QList::const_iterator it = lister->d->lstDirs.constBegin(); it != lister->d->lstDirs.constEnd(); ++it) { DirItem *dirItem = itemsInUse.value(*it); Q_ASSERT(dirItem); auto lit = dirItem->lstItems.constBegin(); const auto litend = dirItem->lstItems.constEnd(); for (; lit != litend; ++lit) { if ((*lit).name() == _name) { return *lit; } } } return KFileItem(); } KFileItem KCoreDirListerCache::findByUrl(const KCoreDirLister *lister, const QUrl &_u) const { QUrl url(_u); url = url.adjusted(QUrl::StripTrailingSlash); const QUrl parentDir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); DirItem *dirItem = dirItemForUrl(parentDir); if (dirItem) { // If lister is set, check that it contains this dir if (!lister || lister->d->lstDirs.contains(parentDir)) { // Binary search auto it = std::lower_bound(dirItem->lstItems.begin(), dirItem->lstItems.end(), url); if (it != dirItem->lstItems.end() && it->url() == url) { return *it; } } } // Maybe _u is a directory itself? (see KDirModelTest::testChmodDirectory) // We check this last, though, we prefer returning a kfileitem with an actual // name if possible (and we make it '.' for root items later). dirItem = dirItemForUrl(url); if (dirItem && !dirItem->rootItem.isNull() && dirItem->rootItem.url() == url) { // If lister is set, check that it contains this dir if (!lister || lister->d->lstDirs.contains(url)) { return dirItem->rootItem; } } return KFileItem(); } void KCoreDirListerCache::slotFilesAdded(const QString &dir /*url*/) // from KDirNotify signals { QUrl urlDir(dir); itemsAddedInDirectory(urlDir); } void KCoreDirListerCache::itemsAddedInDirectory(const QUrl &urlDir) { qCDebug(KIO_CORE_DIRLISTER) << urlDir; // output urls, not qstrings, since they might contain a password Q_FOREACH (const QUrl &u, directoriesForCanonicalPath(urlDir)) { updateDirectory(u); } } void KCoreDirListerCache::slotFilesRemoved(const QStringList &fileList) // from KDirNotify signals { // TODO: handling of symlinks-to-directories isn't done here, // because I'm not sure how to do it and keep the performance ok... slotFilesRemoved(QUrl::fromStringList(fileList)); } void KCoreDirListerCache::slotFilesRemoved(const QList &fileList) { qCDebug(KIO_CORE_DIRLISTER) << fileList.count(); // Group notifications by parent dirs (usually there would be only one parent dir) QMap removedItemsByDir; QList deletedSubdirs; for (auto it = fileList.cbegin(), cend = fileList.end(); it != cend; ++it) { QUrl url(*it); DirItem *dirItem = dirItemForUrl(url); // is it a listed directory? if (dirItem) { deletedSubdirs.append(url); if (!dirItem->rootItem.isNull()) { removedItemsByDir[url].append(dirItem->rootItem); } } const QUrl parentDir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); dirItem = dirItemForUrl(parentDir); if (!dirItem) { continue; } for (auto fit = dirItem->lstItems.begin(), fend = dirItem->lstItems.end(); fit != fend; ++fit) { if ((*fit).url() == url) { const KFileItem fileitem = *fit; removedItemsByDir[parentDir].append(fileitem); // If we found a fileitem, we can test if it's a dir. If not, we'll go to deleteDir just in case. if (fileitem.isNull() || fileitem.isDir()) { deletedSubdirs.append(url); } dirItem->lstItems.erase(fit); // remove fileitem from list break; } } } for (auto rit = removedItemsByDir.constBegin(), cend = removedItemsByDir.constEnd(); rit != cend; ++rit) { // Tell the views about it before calling deleteDir. // They might need the subdirs' file items (see the dirtree). auto dit = directoryData.constFind(rit.key()); if (dit != directoryData.constEnd()) { itemsDeleted((*dit).listersCurrentlyHolding, rit.value()); } } Q_FOREACH (const QUrl &url, deletedSubdirs) { // in case of a dir, check if we have any known children, there's much to do in that case // (stopping jobs, removing dirs from cache etc.) deleteDir(url); } } void KCoreDirListerCache::slotFilesChanged(const QStringList &fileList) // from KDirNotify signals { qCDebug(KIO_CORE_DIRLISTER) << fileList; QList dirsToUpdate; QStringList::const_iterator it = fileList.begin(); for (; it != fileList.end(); ++it) { QUrl url(*it); const KFileItem &fileitem = findByUrl(nullptr, url); if (fileitem.isNull()) { qCDebug(KIO_CORE_DIRLISTER) << "item not found for" << url; continue; } if (url.isLocalFile()) { pendingUpdates.insert(url.toLocalFile()); // delegate the work to processPendingUpdates } else { pendingRemoteUpdates.insert(fileitem); // For remote files, we won't be able to figure out the new information, // we have to do a update (directory listing) const QUrl dir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); if (!dirsToUpdate.contains(dir)) { dirsToUpdate.prepend(dir); } } } QList::const_iterator itdir = dirsToUpdate.constBegin(); for (; itdir != dirsToUpdate.constEnd(); ++itdir) { updateDirectory(*itdir); } // ## TODO problems with current jobs listing/updating that dir // ( see kde-2.2.2's kdirlister ) processPendingUpdates(); } void KCoreDirListerCache::slotFileRenamed(const QString &_src, const QString &_dst, const QString &dstPath) // from KDirNotify signals { QUrl src(_src); QUrl dst(_dst); qCDebug(KIO_CORE_DIRLISTER) << src << "->" << dst; #ifdef DEBUG_CACHE printDebug(); #endif QUrl oldurl = src.adjusted(QUrl::StripTrailingSlash); KFileItem fileitem = findByUrl(nullptr, oldurl); if (fileitem.isNull()) { qCDebug(KIO_CORE_DIRLISTER) << "Item not found:" << oldurl; return; } const KFileItem oldItem = fileitem; // Dest already exists? Was overwritten then (testcase: #151851) // We better emit it as deleted -before- doing the renaming, otherwise // the "update" mechanism will emit the old one as deleted and // kdirmodel will delete the new (renamed) one! const KFileItem &existingDestItem = findByUrl(nullptr, dst); if (!existingDestItem.isNull()) { qCDebug(KIO_CORE_DIRLISTER) << dst << "already existed, let's delete it"; slotFilesRemoved(QList() << dst); } // If the item had a UDS_URL as well as UDS_NAME set, the user probably wants // to be updating the name only (since they can't see the URL). // Check to see if a URL exists, and if so, if only the file part has changed, // only update the name and not the underlying URL. bool nameOnly = !fileitem.entry().stringValue(KIO::UDSEntry::UDS_URL).isEmpty(); nameOnly = nameOnly && src.adjusted(QUrl::RemoveFilename) == dst.adjusted(QUrl::RemoveFilename); if (!nameOnly && fileitem.isDir()) { renameDir(oldurl, dst); // #172945 - if the fileitem was the root item of a DirItem that was just removed from the cache, // then it's a dangling pointer now... fileitem = findByUrl(nullptr, oldurl); if (fileitem.isNull()) { //deleted from cache altogether, #188807 return; } } // Now update the KFileItem representing that file or dir (not exclusive with the above!) if (!oldItem.isLocalFile() && !oldItem.localPath().isEmpty() && dstPath.isEmpty()) { // it uses UDS_LOCAL_PATH and we don't know the new path? needs an update then slotFilesChanged(QStringList() << src.toString()); } else { const QUrl &itemOldUrl = fileitem.url(); if (nameOnly) { fileitem.setName(dst.fileName()); } else { fileitem.setUrl(dst); } if (!dstPath.isEmpty()) { fileitem.setLocalPath(dstPath); } fileitem.refreshMimeType(); fileitem.determineMimeType(); reinsert(fileitem, itemOldUrl); QSet listers = emitRefreshItem(oldItem, fileitem); Q_FOREACH (KCoreDirLister *kdl, listers) { kdl->d->emitItems(); } } #ifdef DEBUG_CACHE printDebug(); #endif } QSet KCoreDirListerCache::emitRefreshItem(const KFileItem &oldItem, const KFileItem &fileitem) { qCDebug(KIO_CORE_DIRLISTER) << "old:" << oldItem.name() << oldItem.url() << "new:" << fileitem.name() << fileitem.url(); // Look whether this item was shown in any view, i.e. held by any dirlister const QUrl parentDir = oldItem.url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); DirectoryDataHash::iterator dit = directoryData.find(parentDir); QList listers; // Also look in listersCurrentlyListing, in case the user manages to rename during a listing if (dit != directoryData.end()) { listers += (*dit).listersCurrentlyHolding + (*dit).listersCurrentlyListing; } if (oldItem.isDir()) { // For a directory, look for dirlisters where it's the root item. dit = directoryData.find(oldItem.url()); if (dit != directoryData.end()) { listers += (*dit).listersCurrentlyHolding + (*dit).listersCurrentlyListing; } } QSet listersToRefresh; Q_FOREACH (KCoreDirLister *kdl, listers) { // For a directory, look for dirlisters where it's the root item. QUrl directoryUrl(oldItem.url()); if (oldItem.isDir() && kdl->d->rootFileItem == oldItem) { const KFileItem oldRootItem = kdl->d->rootFileItem; kdl->d->rootFileItem = fileitem; kdl->d->addRefreshItem(directoryUrl, oldRootItem, fileitem); } else { directoryUrl = directoryUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); kdl->d->addRefreshItem(directoryUrl, oldItem, fileitem); } listersToRefresh.insert(kdl); } return listersToRefresh; } QList KCoreDirListerCache::directoriesForCanonicalPath(const QUrl &dir) const { QList dirs; dirs << dir; dirs << canonicalUrls.value(dir).toSet().toList(); /* make unique; there are faster ways, but this is really small anyway */ if (dirs.count() > 1) { qCDebug(KIO_CORE_DIRLISTER) << dir << "known as" << dirs; } return dirs; } // private slots // Called by KDirWatch - usually when a dir we're watching has been modified, // but it can also be called for a file. void KCoreDirListerCache::slotFileDirty(const QString &path) { qCDebug(KIO_CORE_DIRLISTER) << path; QUrl url = QUrl::fromLocalFile(path).adjusted(QUrl::StripTrailingSlash); // File or dir? bool isDir; const KFileItem item = itemForUrl(url); if (!item.isNull()) { isDir = item.isDir(); } else { QFileInfo info(path); if (!info.exists()) { return; // error } isDir = info.isDir(); } if (isDir) { Q_FOREACH (const QUrl &dir, directoriesForCanonicalPath(url)) { handleFileDirty(dir); // e.g. for permission changes handleDirDirty(dir); } } else { Q_FOREACH (const QUrl &dir, directoriesForCanonicalPath(url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash))) { QUrl aliasUrl(dir); aliasUrl.setPath(concatPaths(aliasUrl.path(), url.fileName())); handleFileDirty(aliasUrl); } } } // Called by slotFileDirty void KCoreDirListerCache::handleDirDirty(const QUrl &url) { // A dir: launch an update job if anyone cares about it // This also means we can forget about pending updates to individual files in that dir const QString dir = url.toLocalFile(); QString dirPath = dir; - if (!dirPath.endsWith('/')) { - dirPath += '/'; + if (!dirPath.endsWith(QLatin1Char('/'))) { + dirPath += QLatin1Char('/'); } QMutableSetIterator pendingIt(pendingUpdates); while (pendingIt.hasNext()) { const QString updPath = pendingIt.next(); qCDebug(KIO_CORE_DIRLISTER) << "had pending update" << updPath; if (updPath.startsWith(dirPath) && - updPath.indexOf('/', dirPath.length()) == -1) { // direct child item + updPath.indexOf(QLatin1Char('/'), dirPath.length()) == -1) { // direct child item qCDebug(KIO_CORE_DIRLISTER) << "forgetting about individual update to" << updPath; pendingIt.remove(); } } if (checkUpdate(url) && !pendingDirectoryUpdates.contains(dir)) { pendingDirectoryUpdates.insert(dir); if (!pendingUpdateTimer.isActive()) { pendingUpdateTimer.start(200); } } } // Called by slotFileDirty void KCoreDirListerCache::handleFileDirty(const QUrl &url) { // A file: do we know about it already? const KFileItem &existingItem = findByUrl(nullptr, url); const QUrl dir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); QString filePath = url.toLocalFile(); if (existingItem.isNull()) { // No - update the parent dir then handleDirDirty(dir); } // Delay updating the file, FAM is flooding us with events if (checkUpdate(dir) && !pendingUpdates.contains(filePath)) { pendingUpdates.insert(filePath); if (!pendingUpdateTimer.isActive()) { pendingUpdateTimer.start(200); } } } void KCoreDirListerCache::slotFileCreated(const QString &path) // from KDirWatch { qCDebug(KIO_CORE_DIRLISTER) << path; // XXX: how to avoid a complete rescan here? // We'd need to stat that one file separately and refresh the item(s) for it. QUrl fileUrl(QUrl::fromLocalFile(path)); itemsAddedInDirectory(fileUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash)); } void KCoreDirListerCache::slotFileDeleted(const QString &path) // from KDirWatch { qCDebug(KIO_CORE_DIRLISTER) << path; const QString fileName = QFileInfo(path).fileName(); QUrl dirUrl(QUrl::fromLocalFile(path)); QStringList fileUrls; Q_FOREACH (const QUrl &url, directoriesForCanonicalPath(dirUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash))) { QUrl urlInfo(url); urlInfo.setPath(concatPaths(urlInfo.path(), fileName)); fileUrls << urlInfo.toString(); } slotFilesRemoved(fileUrls); } void KCoreDirListerCache::slotEntries(KIO::Job *job, const KIO::UDSEntryList &entries) { QUrl url(joburl(static_cast(job))); url = url.adjusted(QUrl::StripTrailingSlash); qCDebug(KIO_CORE_DIRLISTER) << "new entries for " << url; DirItem *dir = itemsInUse.value(url); if (!dir) { qCWarning(KIO_CORE) << "Internal error: job is listing" << url << "but itemsInUse only knows about" << itemsInUse.keys(); Q_ASSERT(dir); return; } DirectoryDataHash::iterator dit = directoryData.find(url); if (dit == directoryData.end()) { qCWarning(KIO_CORE) << "Internal error: job is listing" << url << "but directoryData doesn't know about that url, only about:" << directoryData.keys(); Q_ASSERT(dit != directoryData.end()); return; } KCoreDirListerCacheDirectoryData &dirData = *dit; if (dirData.listersCurrentlyListing.isEmpty()) { qCWarning(KIO_CORE) << "Internal error: job is listing" << url << "but directoryData says no listers are currently listing " << url; #ifndef NDEBUG printDebug(); #endif Q_ASSERT(!dirData.listersCurrentlyListing.isEmpty()); return; } // check if anyone wants the mimetypes immediately bool delayedMimeTypes = true; foreach (KCoreDirLister *kdl, dirData.listersCurrentlyListing) { delayedMimeTypes &= kdl->d->delayedMimeTypes; } QSet filesToHide; bool dotHiddenChecked = false; KIO::UDSEntryList::const_iterator it = entries.begin(); const KIO::UDSEntryList::const_iterator end = entries.end(); for (; it != end; ++it) { const QString name = (*it).stringValue(KIO::UDSEntry::UDS_NAME); Q_ASSERT(!name.isEmpty()); if (name.isEmpty()) { continue; } if (name == QLatin1String(".")) { Q_ASSERT(dir->rootItem.isNull()); // Try to reuse an existing KFileItem (if we listed the parent dir) // rather than creating a new one. There are many reasons: // 1) renames and permission changes to the item would have to emit the signals // twice, otherwise, so that both views manage to recognize the item. // 2) with kio_ftp we can only know that something is a symlink when // listing the parent, so prefer that item, which has more info. // Note that it gives a funky name() to the root item, rather than "." ;) dir->rootItem = itemForUrl(url); if (dir->rootItem.isNull()) { dir->rootItem = KFileItem(*it, url, delayedMimeTypes, true); } foreach (KCoreDirLister *kdl, dirData.listersCurrentlyListing) if (kdl->d->rootFileItem.isNull() && kdl->d->url == url) { kdl->d->rootFileItem = dir->rootItem; } } else if (name != QLatin1String("..")) { KFileItem item(*it, url, delayedMimeTypes, true); // get the names of the files listed in ".hidden", if it exists and is a local file if (!dotHiddenChecked) { const QString localPath = item.localPath(); if (!localPath.isEmpty()) { const QString rootItemPath = QFileInfo(localPath).absolutePath(); filesToHide = filesInDotHiddenForDir(rootItemPath); } dotHiddenChecked = true; } // hide file if listed in ".hidden" if (filesToHide.contains(name)) { item.setHidden(); } qCDebug(KIO_CORE_DIRLISTER)<< "Adding item: " << item.url(); // Add the items sorted by url, needed by findByUrl dir->insert(item); foreach (KCoreDirLister *kdl, dirData.listersCurrentlyListing) { kdl->d->addNewItem(url, item); } } } foreach (KCoreDirLister *kdl, dirData.listersCurrentlyListing) { kdl->d->emitItems(); } } void KCoreDirListerCache::slotResult(KJob *j) { #ifdef DEBUG_CACHE //printDebug(); #endif Q_ASSERT(j); KIO::ListJob *job = static_cast(j); runningListJobs.remove(job); QUrl jobUrl(joburl(job)); jobUrl = jobUrl.adjusted(QUrl::StripTrailingSlash); // need remove trailing slashes again, in case of redirections qCDebug(KIO_CORE_DIRLISTER) << "finished listing" << jobUrl; const auto dit = directoryData.find(jobUrl); if (dit == directoryData.end()) { qCWarning(KIO_CORE) << "Nothing found in directoryData for URL" << jobUrl; #ifndef NDEBUG printDebug(); #endif Q_ASSERT(dit != directoryData.end()); return; } KCoreDirListerCacheDirectoryData &dirData = *dit; if (dirData.listersCurrentlyListing.isEmpty()) { qCWarning(KIO_CORE) << "OOOOPS, nothing in directoryData.listersCurrentlyListing for" << jobUrl; // We're about to assert; dump the current state... #ifndef NDEBUG printDebug(); #endif Q_ASSERT(!dirData.listersCurrentlyListing.isEmpty()); } QList listers = dirData.listersCurrentlyListing; // move all listers to the holding list, do it before emitting // the signals to make sure it exists in KCoreDirListerCache in case someone // calls listDir during the signal emission Q_ASSERT(dirData.listersCurrentlyHolding.isEmpty()); dirData.moveListersWithoutCachedItemsJob(jobUrl); if (job->error()) { foreach (KCoreDirLister *kdl, listers) { kdl->d->jobDone(job); if (job->error() != KJob::KilledJobError) { kdl->handleError(job); } const bool silent = job->property("_kdlc_silent").toBool(); if (!silent) { emit kdl->canceled(jobUrl); } if (kdl->d->numJobs() == 0) { kdl->d->complete = true; if (!silent) { emit kdl->canceled(); } } } } else { DirItem *dir = itemsInUse.value(jobUrl); Q_ASSERT(dir); dir->complete = true; foreach (KCoreDirLister *kdl, listers) { kdl->d->jobDone(job); emit kdl->completed(jobUrl); if (kdl->d->numJobs() == 0) { kdl->d->complete = true; emit kdl->completed(); } } } // TODO: hmm, if there was an error and job is a parent of one or more // of the pending urls we should cancel it/them as well processPendingUpdates(); if (job->property("need_another_update").toBool()) { updateDirectory(jobUrl); } #ifdef DEBUG_CACHE printDebug(); #endif } void KCoreDirListerCache::slotRedirection(KIO::Job *j, const QUrl &url) { Q_ASSERT(j); KIO::ListJob *job = static_cast(j); QUrl oldUrl(job->url()); // here we really need the old url! QUrl newUrl(url); // strip trailing slashes oldUrl = oldUrl.adjusted(QUrl::StripTrailingSlash); newUrl = newUrl.adjusted(QUrl::StripTrailingSlash); if (oldUrl == newUrl) { qCDebug(KIO_CORE_DIRLISTER) << "New redirection url same as old, giving up."; return; } else if (newUrl.isEmpty()) { qCDebug(KIO_CORE_DIRLISTER) << "New redirection url is empty, giving up."; return; } qCDebug(KIO_CORE_DIRLISTER) << oldUrl << "->" << newUrl; #ifdef DEBUG_CACHE // Can't do that here. KCoreDirListerCache::joburl() will use the new url already, // while our data structures haven't been updated yet -> assert fail. //printDebug(); #endif // I don't think there can be dirItems that are children of oldUrl. // Am I wrong here? And even if so, we don't need to delete them, right? // DF: redirection happens before listDir emits any item. Makes little sense otherwise. // oldUrl cannot be in itemsCached because only completed items are moved there DirItem *dir = itemsInUse.take(oldUrl); Q_ASSERT(dir); DirectoryDataHash::iterator dit = directoryData.find(oldUrl); Q_ASSERT(dit != directoryData.end()); KCoreDirListerCacheDirectoryData oldDirData = *dit; directoryData.erase(dit); Q_ASSERT(!oldDirData.listersCurrentlyListing.isEmpty()); const QList listers = oldDirData.listersCurrentlyListing; Q_ASSERT(!listers.isEmpty()); foreach (KCoreDirLister *kdl, listers) { kdl->d->redirect(oldUrl, newUrl, false /*clear items*/); } // when a lister was stopped before the job emits the redirection signal, the old url will // also be in listersCurrentlyHolding const QList holders = oldDirData.listersCurrentlyHolding; foreach (KCoreDirLister *kdl, holders) { kdl->jobStarted(job); // do it like when starting a new list-job that will redirect later // TODO: maybe don't emit started if there's an update running for newUrl already? emit kdl->started(oldUrl); kdl->d->redirect(oldUrl, newUrl, false /*clear items*/); } DirItem *newDir = itemsInUse.value(newUrl); if (newDir) { qCDebug(KIO_CORE_DIRLISTER) << newUrl << "already in use"; // only in this case there can newUrl already be in listersCurrentlyListing or listersCurrentlyHolding delete dir; // get the job if one's running for newUrl already (can be a list-job or an update-job), but // do not return this 'job', which would happen because of the use of redirectionURL() KIO::ListJob *oldJob = jobForUrl(newUrl, job); // listers of newUrl with oldJob: forget about the oldJob and use the already running one // which will be converted to an updateJob KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrl]; QList &curListers = newDirData.listersCurrentlyListing; if (!curListers.isEmpty()) { qCDebug(KIO_CORE_DIRLISTER) << "and it is currently listed"; Q_ASSERT(oldJob); // ?! foreach (KCoreDirLister *kdl, curListers) { // listers of newUrl kdl->d->jobDone(oldJob); kdl->jobStarted(job); kdl->d->connectJob(job); } // append listers of oldUrl with newJob to listers of newUrl with oldJob foreach (KCoreDirLister *kdl, listers) { curListers.append(kdl); } } else { curListers = listers; } if (oldJob) { // kill the old job, be it a list-job or an update-job killJob(oldJob); } // holders of newUrl: use the already running job which will be converted to an updateJob QList &curHolders = newDirData.listersCurrentlyHolding; if (!curHolders.isEmpty()) { qCDebug(KIO_CORE_DIRLISTER) << "and it is currently held."; foreach (KCoreDirLister *kdl, curHolders) { // holders of newUrl kdl->jobStarted(job); emit kdl->started(newUrl); } // append holders of oldUrl to holders of newUrl foreach (KCoreDirLister *kdl, holders) { curHolders.append(kdl); } } else { curHolders = holders; } // emit old items: listers, holders. NOT: newUrlListers/newUrlHolders, they already have them listed // TODO: make this a separate method? foreach (KCoreDirLister *kdl, listers + holders) { if (kdl->d->rootFileItem.isNull() && kdl->d->url == newUrl) { kdl->d->rootFileItem = newDir->rootItem; } kdl->d->addNewItems(newUrl, newDir->lstItems); kdl->d->emitItems(); } } else if ((newDir = itemsCached.take(newUrl))) { qCDebug(KIO_CORE_DIRLISTER) << newUrl << "is unused, but already in the cache."; delete dir; itemsInUse.insert(newUrl, newDir); KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrl]; newDirData.listersCurrentlyListing = listers; newDirData.listersCurrentlyHolding = holders; // emit old items: listers, holders foreach (KCoreDirLister *kdl, listers + holders) { if (kdl->d->rootFileItem.isNull() && kdl->d->url == newUrl) { kdl->d->rootFileItem = newDir->rootItem; } kdl->d->addNewItems(newUrl, newDir->lstItems); kdl->d->emitItems(); } } else { qCDebug(KIO_CORE_DIRLISTER) << newUrl << "has not been listed yet."; dir->rootItem = KFileItem(); dir->lstItems.clear(); dir->redirect(newUrl); itemsInUse.insert(newUrl, dir); KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrl]; newDirData.listersCurrentlyListing = listers; newDirData.listersCurrentlyHolding = holders; if (holders.isEmpty()) { #ifdef DEBUG_CACHE printDebug(); #endif return; // only in this case the job doesn't need to be converted, } } // make the job an update job job->disconnect(this); connect(job, &KIO::ListJob::entries, this, &KCoreDirListerCache::slotUpdateEntries); connect(job, &KJob::result, this, &KCoreDirListerCache::slotUpdateResult); // FIXME: autoUpdate-Counts!! #ifdef DEBUG_CACHE printDebug(); #endif } struct KCoreDirListerCache::ItemInUseChange { ItemInUseChange(const QUrl &old, const QUrl &newU, DirItem *di) : oldUrl(old), newUrl(newU), dirItem(di) {} QUrl oldUrl; QUrl newUrl; DirItem *dirItem; }; void KCoreDirListerCache::renameDir(const QUrl &oldUrl, const QUrl &newUrl) { qCDebug(KIO_CORE_DIRLISTER) << oldUrl << "->" << newUrl; //const QString oldUrlStr = oldUrl.url(KUrl::RemoveTrailingSlash); //const QString newUrlStr = newUrl.url(KUrl::RemoveTrailingSlash); // Not enough. Also need to look at any child dir, even sub-sub-sub-dir. //DirItem *dir = itemsInUse.take( oldUrlStr ); //emitRedirections( oldUrl, url ); QLinkedList itemsToChange; QSet listers; // Look at all dirs being listed/shown for (auto itu = itemsInUse.begin(), ituend = itemsInUse.end(); itu != ituend; ++itu) { DirItem *dir = itu.value(); QUrl oldDirUrl(itu.key()); qCDebug(KIO_CORE_DIRLISTER) << "itemInUse:" << oldDirUrl; // Check if this dir is oldUrl, or a subfolder of it if (oldDirUrl == oldUrl || oldUrl.isParentOf(oldDirUrl)) { // TODO should use KUrl::cleanpath like isParentOf does QString relPath = oldDirUrl.path().mid(oldUrl.path().length()+1); QUrl newDirUrl(newUrl); // take new base if (!relPath.isEmpty()) { newDirUrl.setPath(concatPaths(newDirUrl.path(), relPath)); // add unchanged relative path } qCDebug(KIO_CORE_DIRLISTER) << "new url=" << newDirUrl; // Update URL in dir item and in itemsInUse dir->redirect(newDirUrl); itemsToChange.append(ItemInUseChange(oldDirUrl.adjusted(QUrl::StripTrailingSlash), newDirUrl.adjusted(QUrl::StripTrailingSlash), dir)); // Rename all items under that dir for (auto kit = dir->lstItems.begin(), kend = dir->lstItems.end(); kit != kend; ++kit) { const KFileItem oldItem = *kit; KFileItem newItem = oldItem; const QUrl &oldItemUrl = oldItem.url(); QUrl newItemUrl(oldItemUrl); newItemUrl.setPath(concatPaths(newDirUrl.path(), oldItemUrl.fileName())); qCDebug(KIO_CORE_DIRLISTER) << "renaming" << oldItemUrl << "to" << newItemUrl; newItem.setUrl(newItemUrl); reinsert(newItem, oldItemUrl); listers |= emitRefreshItem(oldItem, newItem); } } } Q_FOREACH (KCoreDirLister *kdl, listers) { kdl->d->emitItems(); } // Do the changes to itemsInUse out of the loop to avoid messing up iterators, // and so that emitRefreshItem can find the stuff in the hash. foreach (const ItemInUseChange &i, itemsToChange) { itemsInUse.remove(i.oldUrl); itemsInUse.insert(i.newUrl, i.dirItem); } //Now that all the caches are updated and consistent, emit the redirection. foreach(const ItemInUseChange& i, itemsToChange) { emitRedirections(QUrl(i.oldUrl), QUrl(i.newUrl)); } // Is oldUrl a directory in the cache? // Remove any child of oldUrl from the cache - even if the renamed dir itself isn't in it! removeDirFromCache(oldUrl); // TODO rename, instead. } // helper for renameDir, not used for redirections from KIO::listDir(). void KCoreDirListerCache::emitRedirections(const QUrl &_oldUrl, const QUrl &_newUrl) { qCDebug(KIO_CORE_DIRLISTER) << _oldUrl << "->" << _newUrl; const QUrl oldUrl = _oldUrl.adjusted(QUrl::StripTrailingSlash); const QUrl newUrl = _newUrl.adjusted(QUrl::StripTrailingSlash); KIO::ListJob *job = jobForUrl(oldUrl); if (job) { killJob(job); } // Check if we were listing this dir. Need to abort and restart with new name in that case. DirectoryDataHash::iterator dit = directoryData.find(oldUrl); if (dit == directoryData.end()) { return; } const QList listers = (*dit).listersCurrentlyListing; const QList holders = (*dit).listersCurrentlyHolding; KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrl]; // Tell the world that the job listing the old url is dead. foreach (KCoreDirLister *kdl, listers) { if (job) { kdl->d->jobDone(job); } emit kdl->canceled(oldUrl); } newDirData.listersCurrentlyListing += listers; // Check if we are currently displaying this directory (odds opposite wrt above) foreach (KCoreDirLister *kdl, holders) { if (job) { kdl->d->jobDone(job); } } newDirData.listersCurrentlyHolding += holders; directoryData.erase(dit); if (!listers.isEmpty()) { updateDirectory(newUrl); // Tell the world about the new url foreach (KCoreDirLister *kdl, listers) { emit kdl->started(newUrl); } } // And notify the dirlisters of the redirection foreach (KCoreDirLister *kdl, holders) { kdl->d->redirect(oldUrl, newUrl, true /*keep items*/); } } void KCoreDirListerCache::removeDirFromCache(const QUrl &dir) { qCDebug(KIO_CORE_DIRLISTER) << dir; const QList cachedDirs = itemsCached.keys(); // seems slow, but there's no qcache iterator... foreach (const QUrl &cachedDir, cachedDirs) { if (dir == cachedDir || dir.isParentOf(cachedDir)) { itemsCached.remove(cachedDir); } } } void KCoreDirListerCache::slotUpdateEntries(KIO::Job *job, const KIO::UDSEntryList &list) { runningListJobs[static_cast(job)] += list; } void KCoreDirListerCache::slotUpdateResult(KJob *j) { Q_ASSERT(j); KIO::ListJob *job = static_cast(j); QUrl jobUrl(joburl(job)); jobUrl = jobUrl.adjusted(QUrl::StripTrailingSlash); // need remove trailing slashes again, in case of redirections qCDebug(KIO_CORE_DIRLISTER) << "finished update" << jobUrl; KCoreDirListerCacheDirectoryData &dirData = directoryData[jobUrl]; // Collect the dirlisters which were listing the URL using that ListJob // plus those that were already holding that URL - they all get updated. dirData.moveListersWithoutCachedItemsJob(jobUrl); QList listers = dirData.listersCurrentlyHolding; listers += dirData.listersCurrentlyListing; // once we are updating dirs that are only in the cache this will fail! Q_ASSERT(!listers.isEmpty()); if (job->error()) { foreach (KCoreDirLister *kdl, listers) { kdl->d->jobDone(job); //don't bother the user //kdl->handleError( job ); const bool silent = job->property("_kdlc_silent").toBool(); if (!silent) { emit kdl->canceled(jobUrl); } if (kdl->d->numJobs() == 0) { kdl->d->complete = true; if (!silent) { emit kdl->canceled(); } } } runningListJobs.remove(job); // TODO: if job is a parent of one or more // of the pending urls we should cancel them processPendingUpdates(); return; } DirItem *dir = itemsInUse.value(jobUrl, nullptr); if (!dir) { qCWarning(KIO_CORE) << "Internal error: itemsInUse did not contain" << jobUrl; #ifndef NDEBUG printDebug(); #endif Q_ASSERT(dir); } else { dir->complete = true; } // check if anyone wants the mimetypes immediately bool delayedMimeTypes = true; foreach (KCoreDirLister *kdl, listers) { delayedMimeTypes &= kdl->d->delayedMimeTypes; } typedef QHash FileItemHash; // fileName -> KFileItem FileItemHash fileItems; // Fill the hash from the old list of items. We'll remove entries as we see them // in the new listing, and the resulting hash entries will be the deleted items. for (auto kit = dir->lstItems.begin(), kend = dir->lstItems.end(); kit != kend; ++kit) { fileItems.insert((*kit).name(), *kit); } QSet filesToHide; bool dotHiddenChecked = false; const KIO::UDSEntryList &buf = runningListJobs.value(job); KIO::UDSEntryList::const_iterator it = buf.constBegin(); const KIO::UDSEntryList::const_iterator end = buf.constEnd(); for (; it != end; ++it) { // Form the complete url KFileItem item(*it, jobUrl, delayedMimeTypes, true); const QString name = item.name(); Q_ASSERT(!name.isEmpty()); // A kioslave setting an empty UDS_NAME is utterly broken, fix the kioslave! // we duplicate the check for dotdot here, to avoid iterating over // all items again and checking in matchesFilter() that way. if (name.isEmpty() || name == QLatin1String("..")) { continue; } if (name == QLatin1String(".")) { // if the update was started before finishing the original listing // there is no root item yet if (dir->rootItem.isNull()) { dir->rootItem = item; foreach (KCoreDirLister *kdl, listers) if (kdl->d->rootFileItem.isNull() && kdl->d->url == jobUrl) { kdl->d->rootFileItem = dir->rootItem; } } continue; } else { // get the names of the files listed in ".hidden", if it exists and is a local file if (!dotHiddenChecked) { const QString localPath = item.localPath(); if (!localPath.isEmpty()) { const QString rootItemPath = QFileInfo(localPath).absolutePath(); filesToHide = filesInDotHiddenForDir(rootItemPath); } dotHiddenChecked = true; } } // hide file if listed in ".hidden" if (filesToHide.contains(name)) { item.setHidden(); } // Find this item FileItemHash::iterator fiit = fileItems.find(item.name()); if (fiit != fileItems.end()) { const KFileItem tmp = fiit.value(); QSet::iterator pru_it = pendingRemoteUpdates.find(tmp); const bool inPendingRemoteUpdates = (pru_it != pendingRemoteUpdates.end()); // check if something changed for this file, using KFileItem::cmp() if (!tmp.cmp(item) || inPendingRemoteUpdates) { if (inPendingRemoteUpdates) { pendingRemoteUpdates.erase(pru_it); } qCDebug(KIO_CORE_DIRLISTER) << "file changed:" << tmp.name(); reinsert(item, tmp.url()); foreach (KCoreDirLister *kdl, listers) { kdl->d->addRefreshItem(jobUrl, tmp, item); } } // Seen, remove fileItems.erase(fiit); } else { // this is a new file qCDebug(KIO_CORE_DIRLISTER) << "new file:" << name; dir->insert(item); foreach (KCoreDirLister *kdl, listers) { kdl->d->addNewItem(jobUrl, item); } } } runningListJobs.remove(job); if (!fileItems.isEmpty()) { deleteUnmarkedItems(listers, dir->lstItems, fileItems); } foreach (KCoreDirLister *kdl, listers) { kdl->d->emitItems(); kdl->d->jobDone(job); emit kdl->completed(jobUrl); if (kdl->d->numJobs() == 0) { kdl->d->complete = true; emit kdl->completed(); } } // TODO: hmm, if there was an error and job is a parent of one or more // of the pending urls we should cancel it/them as well processPendingUpdates(); if (job->property("need_another_update").toBool()) { updateDirectory(jobUrl); } } // private KIO::ListJob *KCoreDirListerCache::jobForUrl(const QUrl &url, KIO::ListJob *not_job) { auto it = runningListJobs.constBegin(); while (it != runningListJobs.constEnd()) { KIO::ListJob *job = it.key(); const QUrl jobUrl = joburl(job).adjusted(QUrl::StripTrailingSlash); if (jobUrl == url && job != not_job) { return job; } ++it; } return nullptr; } const QUrl &KCoreDirListerCache::joburl(KIO::ListJob *job) { if (job->redirectionUrl().isValid()) { return job->redirectionUrl(); } else { return job->url(); } } void KCoreDirListerCache::killJob(KIO::ListJob *job) { runningListJobs.remove(job); job->disconnect(this); job->kill(); } void KCoreDirListerCache::deleteUnmarkedItems(const QList &listers, QList &lstItems, const QHash &itemsToDelete) { // Make list of deleted items (for emitting) KFileItemList deletedItems; QHashIterator kit(itemsToDelete); while (kit.hasNext()) { const KFileItem item = kit.next().value(); deletedItems.append(item); qCDebug(KIO_CORE_DIRLISTER) << "deleted:" << item.name() << item; } // Delete all remaining items QMutableListIterator it(lstItems); while (it.hasNext()) { if (itemsToDelete.contains(it.next().name())) { it.remove(); } } itemsDeleted(listers, deletedItems); } void KCoreDirListerCache::itemsDeleted(const QList &listers, const KFileItemList &deletedItems) { Q_FOREACH (KCoreDirLister *kdl, listers) { kdl->d->emitItemsDeleted(deletedItems); } Q_FOREACH (const KFileItem &item, deletedItems) { if (item.isDir()) { deleteDir(item.url()); } } } void KCoreDirListerCache::deleteDir(const QUrl &_dirUrl) { qCDebug(KIO_CORE_DIRLISTER) << _dirUrl; // unregister and remove the children of the deleted item. // Idea: tell all the KCoreDirListers that they should forget the dir // and then remove it from the cache. QUrl dirUrl(_dirUrl.adjusted(QUrl::StripTrailingSlash)); // Separate itemsInUse iteration and calls to forgetDirs (which modify itemsInUse) QList affectedItems; auto itu = itemsInUse.begin(); const auto ituend = itemsInUse.end(); for (; itu != ituend; ++itu) { const QUrl deletedUrl(itu.key()); if (dirUrl == deletedUrl || dirUrl.isParentOf(deletedUrl)) { affectedItems.append(deletedUrl); } } foreach (const QUrl &deletedUrl, affectedItems) { // stop all jobs for deletedUrlStr DirectoryDataHash::iterator dit = directoryData.find(deletedUrl); if (dit != directoryData.end()) { // we need a copy because stop modifies the list QList listers = (*dit).listersCurrentlyListing; foreach (KCoreDirLister *kdl, listers) { stopListingUrl(kdl, deletedUrl); } // tell listers holding deletedUrl to forget about it // this will stop running updates for deletedUrl as well // we need a copy because forgetDirs modifies the list QList holders = (*dit).listersCurrentlyHolding; foreach (KCoreDirLister *kdl, holders) { // lister's root is the deleted item if (kdl->d->url == deletedUrl) { // tell the view first. It might need the subdirs' items (which forgetDirs will delete) if (!kdl->d->rootFileItem.isNull()) { emit kdl->itemsDeleted(KFileItemList() << kdl->d->rootFileItem); } forgetDirs(kdl); kdl->d->rootFileItem = KFileItem(); } else { const bool treeview = kdl->d->lstDirs.count() > 1; if (!treeview) { emit kdl->clear(); kdl->d->lstDirs.clear(); } else { kdl->d->lstDirs.removeAll(deletedUrl); } forgetDirs(kdl, deletedUrl, treeview); } } } // delete the entry for deletedUrl - should not be needed, it's in // items cached now int count = itemsInUse.remove(deletedUrl); Q_ASSERT(count == 0); Q_UNUSED(count); //keep gcc "unused variable" complaining quiet when in release mode } // remove the children from the cache removeDirFromCache(dirUrl); } // delayed updating of files, FAM is flooding us with events void KCoreDirListerCache::processPendingUpdates() { QSet listers; foreach (const QString &file, pendingUpdates) { // always a local path qCDebug(KIO_CORE_DIRLISTER) << file; QUrl u = QUrl::fromLocalFile(file); KFileItem item = findByUrl(nullptr, u); // search all items if (!item.isNull()) { // we need to refresh the item, because e.g. the permissions can have changed. KFileItem oldItem = item; item.refresh(); if (!oldItem.cmp(item)) { reinsert(item, oldItem.url()); listers |= emitRefreshItem(oldItem, item); } } } pendingUpdates.clear(); Q_FOREACH (KCoreDirLister *kdl, listers) { kdl->d->emitItems(); } // Directories in need of updating foreach (const QString &dir, pendingDirectoryUpdates) { updateDirectory(QUrl::fromLocalFile(dir)); } pendingDirectoryUpdates.clear(); } #ifndef NDEBUG void KCoreDirListerCache::printDebug() { qCDebug(KIO_CORE_DIRLISTER) << "Items in use:"; auto itu = itemsInUse.constBegin(); const auto ituend = itemsInUse.constEnd(); for (; itu != ituend; ++itu) { qCDebug(KIO_CORE_DIRLISTER) << " " << itu.key() << "URL:" << itu.value()->url << "rootItem:" << (!itu.value()->rootItem.isNull() ? itu.value()->rootItem.url() : QUrl()) << "autoUpdates refcount:" << itu.value()->autoUpdates << "complete:" << itu.value()->complete << QStringLiteral("with %1 items.").arg(itu.value()->lstItems.count()); } QList listersWithoutJob; qCDebug(KIO_CORE_DIRLISTER) << "Directory data:"; DirectoryDataHash::const_iterator dit = directoryData.constBegin(); for (; dit != directoryData.constEnd(); ++dit) { QString list; foreach (KCoreDirLister *listit, (*dit).listersCurrentlyListing) { - list += " 0x" + QString::number(reinterpret_cast(listit), 16); + list += QLatin1String(" 0x") + QString::number(reinterpret_cast(listit), 16); } qCDebug(KIO_CORE_DIRLISTER) << " " << dit.key() << (*dit).listersCurrentlyListing.count() << "listers:" << list; foreach (KCoreDirLister *listit, (*dit).listersCurrentlyListing) { if (!listit->d->m_cachedItemsJobs.isEmpty()) { qCDebug(KIO_CORE_DIRLISTER) << " Lister" << listit << "has CachedItemsJobs" << listit->d->m_cachedItemsJobs; } else if (KIO::ListJob *listJob = jobForUrl(dit.key())) { qCDebug(KIO_CORE_DIRLISTER) << " Lister" << listit << "has ListJob" << listJob; } else { listersWithoutJob.append(listit); } } list.clear(); foreach (KCoreDirLister *listit, (*dit).listersCurrentlyHolding) { - list += " 0x" + QString::number(reinterpret_cast(listit), 16); + list += QLatin1String(" 0x") + QString::number(reinterpret_cast(listit), 16); } qCDebug(KIO_CORE_DIRLISTER) << " " << dit.key() << (*dit).listersCurrentlyHolding.count() << "holders:" << list; } QMap< KIO::ListJob *, KIO::UDSEntryList >::Iterator jit = runningListJobs.begin(); qCDebug(KIO_CORE_DIRLISTER) << "Jobs:"; for (; jit != runningListJobs.end(); ++jit) { qCDebug(KIO_CORE_DIRLISTER) << " " << jit.key() << "listing" << joburl(jit.key()) << ":" << (*jit).count() << "entries."; } qCDebug(KIO_CORE_DIRLISTER) << "Items in cache:"; const QList cachedDirs = itemsCached.keys(); foreach (const QUrl &cachedDir, cachedDirs) { DirItem *dirItem = itemsCached.object(cachedDir); qCDebug(KIO_CORE_DIRLISTER) << " " << cachedDir << "rootItem:" << (!dirItem->rootItem.isNull() ? dirItem->rootItem.url().toString() : QStringLiteral("NULL")) << "with" << dirItem->lstItems.count() << "items."; } // Abort on listers without jobs -after- showing the full dump. Easier debugging. Q_FOREACH (KCoreDirLister *listit, listersWithoutJob) { qCWarning(KIO_CORE) << "Fatal Error: HUH? Lister" << listit << "is supposed to be listing, but has no job!"; abort(); } } #endif KCoreDirLister::KCoreDirLister(QObject *parent) : QObject(parent), d(new Private(this)) { qCDebug(KIO_CORE_DIRLISTER) << "+KCoreDirLister"; d->complete = true; setAutoUpdate(true); setDirOnlyMode(false); setShowingDotFiles(false); } KCoreDirLister::~KCoreDirLister() { qCDebug(KIO_CORE_DIRLISTER) << "~KCoreDirLister" << this; // Stop all running jobs, remove lister from lists if (!kDirListerCache.isDestroyed()) { stop(); kDirListerCache()->forgetDirs(this); } delete d; } bool KCoreDirLister::openUrl(const QUrl &_url, OpenUrlFlags _flags) { // emit the current changes made to avoid an inconsistent treeview if (d->hasPendingChanges && (_flags & Keep)) { emitChanges(); } d->hasPendingChanges = false; return kDirListerCache()->listDir(this, _url, _flags & Keep, _flags & Reload); } void KCoreDirLister::stop() { kDirListerCache()->stop(this); } void KCoreDirLister::stop(const QUrl &_url) { kDirListerCache()->stopListingUrl(this, _url); } bool KCoreDirLister::autoUpdate() const { return d->autoUpdate; } void KCoreDirLister::setAutoUpdate(bool _enable) { if (d->autoUpdate == _enable) { return; } d->autoUpdate = _enable; kDirListerCache()->setAutoUpdate(this, _enable); } bool KCoreDirLister::showingDotFiles() const { return d->settings.isShowingDotFiles; } void KCoreDirLister::setShowingDotFiles(bool _showDotFiles) { if (d->settings.isShowingDotFiles == _showDotFiles) { return; } d->prepareForSettingsChange(); d->settings.isShowingDotFiles = _showDotFiles; } bool KCoreDirLister::dirOnlyMode() const { return d->settings.dirOnlyMode; } void KCoreDirLister::setDirOnlyMode(bool _dirsOnly) { if (d->settings.dirOnlyMode == _dirsOnly) { return; } d->prepareForSettingsChange(); d->settings.dirOnlyMode = _dirsOnly; } QUrl KCoreDirLister::url() const { return d->url; } QList KCoreDirLister::directories() const { return d->lstDirs; } void KCoreDirLister::emitChanges() { d->emitChanges(); } void KCoreDirLister::Private::emitChanges() { if (!hasPendingChanges) { return; } // reset 'hasPendingChanges' now, in case of recursion // (testcase: enabling recursive scan in ktorrent, #174920) hasPendingChanges = false; const Private::FilterSettings newSettings = settings; settings = oldSettings; // temporarily // Fill hash with all items that are currently visible QSet oldVisibleItems; Q_FOREACH (const QUrl &dir, lstDirs) { QList *itemList = kDirListerCache()->itemsForDir(dir); if (!itemList) { continue; } foreach (const KFileItem &item, *itemList) { if (isItemVisible(item) && m_parent->matchesMimeFilter(item)) { oldVisibleItems.insert(item.name()); } } } settings = newSettings; Q_FOREACH (const QUrl &dir, lstDirs) { KFileItemList deletedItems; QList *itemList = kDirListerCache()->itemsForDir(dir); if (!itemList) { continue; } auto kit = itemList->begin(); const auto kend = itemList->end(); for (; kit != kend; ++kit) { KFileItem &item = *kit; const QString text = item.text(); if (text == QLatin1String(".") || text == QLatin1String("..")) { continue; } const bool wasVisible = oldVisibleItems.contains(item.name()); const bool nowVisible = isItemVisible(item) && m_parent->matchesMimeFilter(item); if (nowVisible && !wasVisible) { addNewItem(dir, item); // takes care of emitting newItem or itemsFilteredByMime } else if (!nowVisible && wasVisible) { deletedItems.append(*kit); } } if (!deletedItems.isEmpty()) { emit m_parent->itemsDeleted(deletedItems); } emitItems(); } oldSettings = settings; } void KCoreDirLister::updateDirectory(const QUrl &_u) { kDirListerCache()->updateDirectory(_u); } bool KCoreDirLister::isFinished() const { return d->complete; } KFileItem KCoreDirLister::rootItem() const { return d->rootFileItem; } KFileItem KCoreDirLister::findByUrl(const QUrl &_url) const { return kDirListerCache()->findByUrl(this, _url); } KFileItem KCoreDirLister::findByName(const QString &_name) const { return kDirListerCache()->findByName(this, _name); } // ================ public filter methods ================ // void KCoreDirLister::setNameFilter(const QString &nameFilter) { if (d->nameFilter == nameFilter) { return; } d->prepareForSettingsChange(); d->settings.lstFilters.clear(); d->nameFilter = nameFilter; // Split on white space - const QStringList list = nameFilter.split(' ', QString::SkipEmptyParts); + const QStringList list = nameFilter.split(QLatin1Char(' '), QString::SkipEmptyParts); for (QStringList::const_iterator it = list.begin(); it != list.end(); ++it) { d->settings.lstFilters.append(QRegExp(*it, Qt::CaseInsensitive, QRegExp::Wildcard)); } } QString KCoreDirLister::nameFilter() const { return d->nameFilter; } void KCoreDirLister::setMimeFilter(const QStringList &mimeFilter) { if (d->settings.mimeFilter == mimeFilter) { return; } d->prepareForSettingsChange(); if (mimeFilter.contains(QStringLiteral("application/octet-stream")) || mimeFilter.contains(QStringLiteral("all/allfiles"))) { // all files d->settings.mimeFilter.clear(); } else { d->settings.mimeFilter = mimeFilter; } } void KCoreDirLister::setMimeExcludeFilter(const QStringList &mimeExcludeFilter) { if (d->settings.mimeExcludeFilter == mimeExcludeFilter) { return; } d->prepareForSettingsChange(); d->settings.mimeExcludeFilter = mimeExcludeFilter; } void KCoreDirLister::clearMimeFilter() { d->prepareForSettingsChange(); d->settings.mimeFilter.clear(); d->settings.mimeExcludeFilter.clear(); } QStringList KCoreDirLister::mimeFilters() const { return d->settings.mimeFilter; } bool KCoreDirLister::matchesFilter(const QString &name) const { return doNameFilter(name, d->settings.lstFilters); } bool KCoreDirLister::matchesMimeFilter(const QString &mime) const { return doMimeFilter(mime, d->settings.mimeFilter) && d->doMimeExcludeFilter(mime, d->settings.mimeExcludeFilter); } // ================ protected methods ================ // bool KCoreDirLister::matchesFilter(const KFileItem &item) const { Q_ASSERT(!item.isNull()); if (item.text() == QLatin1String("..")) { return false; } if (!d->settings.isShowingDotFiles && item.isHidden()) { return false; } if (item.isDir() || d->settings.lstFilters.isEmpty()) { return true; } return matchesFilter(item.text()); } bool KCoreDirLister::matchesMimeFilter(const KFileItem &item) const { Q_ASSERT(!item.isNull()); // Don't lose time determining the mimetype if there is no filter if (d->settings.mimeFilter.isEmpty() && d->settings.mimeExcludeFilter.isEmpty()) { return true; } return matchesMimeFilter(item.mimetype()); } bool KCoreDirLister::doNameFilter(const QString &name, const QList &filters) const { for (QList::const_iterator it = filters.begin(); it != filters.end(); ++it) if ((*it).exactMatch(name)) { return true; } return false; } bool KCoreDirLister::doMimeFilter(const QString &mime, const QStringList &filters) const { if (filters.isEmpty()) { return true; } QMimeDatabase db; const QMimeType mimeptr = db.mimeTypeForName(mime); if (!mimeptr.isValid()) { return false; } qCDebug(KIO_CORE_DIRLISTER) << "doMimeFilter: investigating:" << mimeptr.name(); QStringList::const_iterator it = filters.begin(); for (; it != filters.end(); ++it) if (mimeptr.inherits(*it)) { return true; } //else qCDebug(KIO_CORE_DIRLISTER) << "doMimeFilter: compared without result to "<<*it; return false; } bool KCoreDirLister::Private::doMimeExcludeFilter(const QString &mime, const QStringList &filters) const { if (filters.isEmpty()) { return true; } QStringList::const_iterator it = filters.begin(); for (; it != filters.end(); ++it) if ((*it) == mime) { return false; } return true; } void KCoreDirLister::handleError(KIO::Job *job) { qCWarning(KIO_CORE) << job->errorString(); } void KCoreDirLister::handleErrorMessage(const QString &message) { qCWarning(KIO_CORE) << message; } // ================= private methods ================= // void KCoreDirLister::Private::addNewItem(const QUrl &directoryUrl, const KFileItem &item) { if (!isItemVisible(item)) { return; // No reason to continue... bailing out here prevents a mimetype scan. } qCDebug(KIO_CORE_DIRLISTER) << "in" << directoryUrl << "item:" << item.url(); if (m_parent->matchesMimeFilter(item)) { Q_ASSERT(!item.isNull()); lstNewItems[directoryUrl].append(item); // items not filtered } else { Q_ASSERT(!item.isNull()); lstMimeFilteredItems.append(item); // only filtered by mime } } void KCoreDirLister::Private::addNewItems(const QUrl &directoryUrl, const QList &items) { // TODO: make this faster - test if we have a filter at all first // DF: was this profiled? The matchesFoo() functions should be fast, w/o filters... // Of course if there is no filter and we can do a range-insertion instead of a loop, that might be good. auto kit = items.cbegin(); const auto kend = items.cend(); for (; kit != kend; ++kit) { addNewItem(directoryUrl, *kit); } } void KCoreDirLister::Private::addRefreshItem(const QUrl &directoryUrl, const KFileItem &oldItem, const KFileItem &item) { const bool refreshItemWasFiltered = !isItemVisible(oldItem) || !m_parent->matchesMimeFilter(oldItem); if (isItemVisible(item) && m_parent->matchesMimeFilter(item)) { if (refreshItemWasFiltered) { Q_ASSERT(!item.isNull()); lstNewItems[directoryUrl].append(item); } else { Q_ASSERT(!item.isNull()); lstRefreshItems.append(qMakePair(oldItem, item)); } } else if (!refreshItemWasFiltered) { // notify the user that the mimetype of a file changed that doesn't match // a filter or does match an exclude filter // This also happens when renaming foo to .foo and dot files are hidden (#174721) Q_ASSERT(!oldItem.isNull()); lstRemoveItems.append(oldItem); } } void KCoreDirLister::Private::emitItems() { if (!lstNewItems.empty()) { QHashIterator it(lstNewItems); while (it.hasNext()) { it.next(); emit m_parent->itemsAdded(it.key(), it.value()); emit m_parent->newItems(it.value()); // compat } lstNewItems.clear(); } if (!lstMimeFilteredItems.empty()) { emit m_parent->itemsFilteredByMime(lstMimeFilteredItems); lstMimeFilteredItems.clear(); } if (!lstRefreshItems.empty()) { emit m_parent->refreshItems(lstRefreshItems); lstRefreshItems.clear(); } if (!lstRemoveItems.empty()) { emit m_parent->itemsDeleted(lstRemoveItems); lstRemoveItems.clear(); } } bool KCoreDirLister::Private::isItemVisible(const KFileItem &item) const { // Note that this doesn't include mime filters, because // of the itemsFilteredByMime signal. Filtered-by-mime items are // considered "visible", they are just visible via a different signal... return (!settings.dirOnlyMode || item.isDir()) && m_parent->matchesFilter(item); } void KCoreDirLister::Private::emitItemsDeleted(const KFileItemList &_items) { KFileItemList items = _items; QMutableListIterator it(items); while (it.hasNext()) { const KFileItem &item = it.next(); if (!isItemVisible(item) || !m_parent->matchesMimeFilter(item)) { it.remove(); } } if (!items.isEmpty()) { emit m_parent->itemsDeleted(items); } } // ================ private slots ================ // void KCoreDirLister::Private::_k_slotInfoMessage(KJob *, const QString &message) { emit m_parent->infoMessage(message); } void KCoreDirLister::Private::_k_slotPercent(KJob *job, unsigned long pcnt) { jobData[static_cast(job)].percent = pcnt; int result = 0; KIO::filesize_t size = 0; QMap< KIO::ListJob *, Private::JobData >::Iterator dataIt = jobData.begin(); while (dataIt != jobData.end()) { result += (*dataIt).percent * (*dataIt).totalSize; size += (*dataIt).totalSize; ++dataIt; } if (size != 0) { result /= size; } else { result = 100; } emit m_parent->percent(result); } void KCoreDirLister::Private::_k_slotTotalSize(KJob *job, qulonglong size) { jobData[static_cast(job)].totalSize = size; KIO::filesize_t result = 0; QMap< KIO::ListJob *, Private::JobData >::Iterator dataIt = jobData.begin(); while (dataIt != jobData.end()) { result += (*dataIt).totalSize; ++dataIt; } emit m_parent->totalSize(result); } void KCoreDirLister::Private::_k_slotProcessedSize(KJob *job, qulonglong size) { jobData[static_cast(job)].processedSize = size; KIO::filesize_t result = 0; QMap< KIO::ListJob *, Private::JobData >::Iterator dataIt = jobData.begin(); while (dataIt != jobData.end()) { result += (*dataIt).processedSize; ++dataIt; } emit m_parent->processedSize(result); } void KCoreDirLister::Private::_k_slotSpeed(KJob *job, unsigned long spd) { jobData[static_cast(job)].speed = spd; int result = 0; QMap< KIO::ListJob *, Private::JobData >::Iterator dataIt = jobData.begin(); while (dataIt != jobData.end()) { result += (*dataIt).speed; ++dataIt; } emit m_parent->speed(result); } uint KCoreDirLister::Private::numJobs() { #ifdef DEBUG_CACHE // This code helps detecting stale entries in the jobData map. qCDebug(KIO_CORE_DIRLISTER) << m_parent << "numJobs:" << jobData.count(); QMapIterator it(jobData); while (it.hasNext()) { it.next(); qCDebug(KIO_CORE_DIRLISTER) << (void*)it.key(); qCDebug(KIO_CORE_DIRLISTER) << it.key(); } #endif return jobData.count(); } void KCoreDirLister::Private::jobDone(KIO::ListJob *job) { jobData.remove(job); } void KCoreDirLister::jobStarted(KIO::ListJob *job) { Private::JobData data; data.speed = 0; data.percent = 0; data.processedSize = 0; data.totalSize = 0; d->jobData.insert(job, data); d->complete = false; } void KCoreDirLister::Private::connectJob(KIO::ListJob *job) { m_parent->connect(job, &KJob::infoMessage, m_parent, [this](KJob *job, const QString &plain){ _k_slotInfoMessage(job, plain); }); m_parent->connect(job, QOverload::of(&KJob::percent), m_parent, [this](KJob *job, ulong _percent){ _k_slotPercent(job, _percent); }); m_parent->connect(job, &KJob::totalSize, m_parent, [this](KJob *job, qulonglong _size){ _k_slotTotalSize(job, _size); }); m_parent->connect(job, &KJob::processedSize, m_parent, [this](KJob *job, qulonglong _psize){ _k_slotProcessedSize(job, _psize); }); m_parent->connect(job, &KJob::speed, m_parent, [this](KJob *job, qulonglong _speed){ _k_slotSpeed(job, _speed); }); } KFileItemList KCoreDirLister::items(WhichItems which) const { return itemsForDir(url(), which); } KFileItemList KCoreDirLister::itemsForDir(const QUrl &dir, WhichItems which) const { QList *allItems = kDirListerCache()->itemsForDir(dir); KFileItemList result; if (!allItems) { return result; } if (which == AllItems) { return KFileItemList(*allItems); } else { // only items passing the filters auto kit = allItems->constBegin(); const auto kend = allItems->constEnd(); for (; kit != kend; ++kit) { const KFileItem &item = *kit; if (d->isItemVisible(item) && matchesMimeFilter(item)) { result.append(item); } } } return result; } bool KCoreDirLister::delayedMimeTypes() const { return d->delayedMimeTypes; } void KCoreDirLister::setDelayedMimeTypes(bool delayedMimeTypes) { d->delayedMimeTypes = delayedMimeTypes; } // called by KCoreDirListerCache::slotRedirection void KCoreDirLister::Private::redirect(const QUrl &oldUrl, const QUrl &newUrl, bool keepItems) { if (url.matches(oldUrl, QUrl::StripTrailingSlash)) { if (!keepItems) { rootFileItem = KFileItem(); } else { rootFileItem.setUrl(newUrl); } url = newUrl; } const int idx = lstDirs.indexOf(oldUrl); if (idx == -1) { qCWarning(KIO_CORE) << "Unexpected redirection from" << oldUrl << "to" << newUrl << "but this dirlister is currently listing/holding" << lstDirs; } else { lstDirs[ idx ] = newUrl; } if (lstDirs.count() == 1) { if (!keepItems) { emit m_parent->clear(); } emit m_parent->redirection(newUrl); } else { if (!keepItems) { emit m_parent->clear(oldUrl); } } emit m_parent->redirection(oldUrl, newUrl); } void KCoreDirListerCacheDirectoryData::moveListersWithoutCachedItemsJob(const QUrl &url) { // Move dirlisters from listersCurrentlyListing to listersCurrentlyHolding, // but not those that are still waiting on a CachedItemsJob... // Unit-testing note: // Run kdirmodeltest in valgrind to hit the case where an update // is triggered while a lister has a CachedItemsJob (different timing...) QMutableListIterator lister_it(listersCurrentlyListing); while (lister_it.hasNext()) { KCoreDirLister *kdl = lister_it.next(); if (!kdl->d->cachedItemsJobForUrl(url)) { // OK, move this lister from "currently listing" to "currently holding". // Huh? The KCoreDirLister was present twice in listersCurrentlyListing, or was in both lists? Q_ASSERT(!listersCurrentlyHolding.contains(kdl)); if (!listersCurrentlyHolding.contains(kdl)) { listersCurrentlyHolding.append(kdl); } lister_it.remove(); } else { qCDebug(KIO_CORE_DIRLISTER) << "Not moving" << kdl << "to listersCurrentlyHolding because it still has job" << kdl->d->m_cachedItemsJobs; } } } KFileItem KCoreDirLister::cachedItemForUrl(const QUrl &url) { if (kDirListerCache.exists()) { return kDirListerCache()->itemForUrl(url); } else { return {}; } } QSet KCoreDirListerCache::filesInDotHiddenForDir(const QString& dir) { - const QString path = dir + "/.hidden"; + const QString path = dir + QLatin1String("/.hidden"); QFile dotHiddenFile(path); if (dotHiddenFile.exists()) { const QDateTime mtime = QFileInfo(dotHiddenFile).lastModified(); const CacheHiddenFile *cachedDotHiddenFile = m_cacheHiddenFiles.object(path); if (cachedDotHiddenFile && mtime <= cachedDotHiddenFile->mtime) { // ".hidden" is in cache and still valid (the file was not modified since then), // so return it return cachedDotHiddenFile->listedFiles; } else { // read the ".hidden" file, then cache it and return it if (dotHiddenFile.open(QIODevice::ReadOnly | QIODevice::Text)) { QSet filesToHide; QTextStream stream(&dotHiddenFile); while (!stream.atEnd()) { QString name = stream.readLine(); if (!name.isEmpty()) { filesToHide.insert(name); } } m_cacheHiddenFiles.insert(path, new CacheHiddenFile(mtime, filesToHide)); return filesToHide; } } } return QSet(); } #include "moc_kcoredirlister.cpp" #include "moc_kcoredirlister_p.cpp" diff --git a/src/core/kfileitem.cpp b/src/core/kfileitem.cpp index 5244d472..f4f679ac 100644 --- a/src/core/kfileitem.cpp +++ b/src/core/kfileitem.cpp @@ -1,1602 +1,1602 @@ /* This file is part of the KDE project Copyright (C) 1999-2011 David Faure 2001 Carsten Pfeiffer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kfileitem.h" #include "kioglobal_p.h" #include "kiocoredebug.h" #include "../pathhelpers_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef Q_OS_WIN #include #include #endif #include class KFileItemPrivate : public QSharedData { public: KFileItemPrivate(const KIO::UDSEntry &entry, mode_t mode, mode_t permissions, const QUrl &itemOrDirUrl, bool urlIsDirectory, bool delayedMimeTypes) : m_entry(entry), m_url(itemOrDirUrl), m_strName(), m_strText(), m_iconName(), m_strLowerCaseName(), m_mimeType(), m_fileMode(mode), m_permissions(permissions), m_bLink(false), m_bIsLocalUrl(itemOrDirUrl.isLocalFile()), m_bMimeTypeKnown(false), m_delayedMimeTypes(delayedMimeTypes), m_useIconNameCache(false), m_hidden(Auto), m_slow(SlowUnknown) { if (entry.count() != 0) { readUDSEntry(urlIsDirectory); } else { Q_ASSERT(!urlIsDirectory); m_strName = itemOrDirUrl.fileName(); m_strText = KIO::decodeFileName(m_strName); } init(); } /** * Computes the text and mode from the UDSEntry * Called by constructor, but can be called again later * Nothing does that anymore though (I guess some old KonqFileItem did) * so it's not a protected method of the public class anymore. */ void init(); QString localPath() const; KIO::filesize_t size() const; QDateTime time(KFileItem::FileTimes which) const; void setTime(KFileItem::FileTimes which, uint time_t_val) const; void setTime(KFileItem::FileTimes which, const QDateTime &val) const; bool cmp(const KFileItemPrivate &item) const; bool isSlow() const; /** * Extracts the data from the UDSEntry member and updates the KFileItem * accordingly. */ void readUDSEntry(bool _urlIsDirectory); /** * Parses the given permission set and provides it for access() */ QString parsePermissions(mode_t perm) const; /** * The UDSEntry that contains the data for this fileitem, if it came from a directory listing. */ mutable KIO::UDSEntry m_entry; /** * The url of the file */ QUrl m_url; /** * The text for this item, i.e. the file name without path, */ QString m_strName; /** * The text for this item, i.e. the file name without path, decoded * ('%%' becomes '%', '%2F' becomes '/') */ QString m_strText; /** * The icon name for this item. */ mutable QString m_iconName; /** * The filename in lower case (to speed up sorting) */ mutable QString m_strLowerCaseName; /** * The mimetype of the file */ mutable QMimeType m_mimeType; /** * The file mode */ mode_t m_fileMode; /** * The permissions */ mode_t m_permissions; /** * Whether the file is a link */ bool m_bLink: 1; /** * True if local file */ bool m_bIsLocalUrl: 1; mutable bool m_bMimeTypeKnown: 1; mutable bool m_delayedMimeTypes: 1; /** True if m_iconName should be used as cache. */ mutable bool m_useIconNameCache: 1; // Auto: check leading dot. enum { Auto, Hidden, Shown } m_hidden: 3; // Slow? (nfs/smb/ssh) mutable enum { SlowUnknown, Fast, Slow } m_slow: 3; // For special case like link to dirs over FTP QString m_guessedMimeType; mutable QString m_access; }; void KFileItemPrivate::init() { m_access.clear(); // metaInfo = KFileMetaInfo(); // stat() local files if needed // TODO: delay this until requested if (m_fileMode == KFileItem::Unknown || m_permissions == KFileItem::Unknown || m_entry.count() == 0) { if (m_url.isLocalFile()) { /* directories may not have a slash at the end if * we want to stat() them; it requires that we * change into it .. which may not be allowed * stat("/is/unaccessible") -> rwx------ * stat("/is/unaccessible/") -> EPERM H.Z. * This is the reason for the StripTrailingSlash */ QT_STATBUF buf; const QString path = m_url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QByteArray pathBA = QFile::encodeName(path); if (QT_LSTAT(pathBA.constData(), &buf) == 0) { m_entry.reserve(9); m_entry.replace(KIO::UDSEntry::UDS_DEVICE_ID, buf.st_dev); m_entry.replace(KIO::UDSEntry::UDS_INODE, buf.st_ino); mode_t mode = buf.st_mode; if ((buf.st_mode & QT_STAT_MASK) == QT_STAT_LNK) { m_bLink = true; - if (QT_STAT(pathBA, &buf) == 0) { + if (QT_STAT(pathBA.constData(), &buf) == 0) { mode = buf.st_mode; } else {// link pointing to nowhere (see FileProtocol::createUDSEntry() in ioslaves/file/file.cpp) mode = (QT_STAT_MASK - 1) | S_IRWXU | S_IRWXG | S_IRWXO; } } m_entry.replace(KIO::UDSEntry::UDS_SIZE, buf.st_size); m_entry.replace(KIO::UDSEntry::UDS_FILE_TYPE, buf.st_mode & QT_STAT_MASK); // extract file type m_entry.replace(KIO::UDSEntry::UDS_ACCESS, buf.st_mode & 07777); // extract permissions m_entry.replace(KIO::UDSEntry::UDS_MODIFICATION_TIME, buf.st_mtime); // TODO: we could use msecs too... m_entry.replace(KIO::UDSEntry::UDS_ACCESS_TIME, buf.st_atime); #ifndef Q_OS_WIN m_entry.replace(KIO::UDSEntry::UDS_USER, KUser(buf.st_uid).loginName()); m_entry.replace(KIO::UDSEntry::UDS_GROUP, KUserGroup(buf.st_gid).name()); #endif // TODO: these can be removed, we can use UDS_FILE_TYPE and UDS_ACCESS everywhere if (m_fileMode == KFileItem::Unknown) { m_fileMode = mode & QT_STAT_MASK; // extract file type } if (m_permissions == KFileItem::Unknown) { m_permissions = mode & 07777; // extract permissions } } } } } void KFileItemPrivate::readUDSEntry(bool _urlIsDirectory) { // extract fields from the KIO::UDS Entry m_fileMode = m_entry.numberValue(KIO::UDSEntry::UDS_FILE_TYPE, KFileItem::Unknown); m_permissions = m_entry.numberValue(KIO::UDSEntry::UDS_ACCESS, KFileItem::Unknown); m_strName = m_entry.stringValue(KIO::UDSEntry::UDS_NAME); const QString displayName = m_entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME); if (!displayName.isEmpty()) { m_strText = displayName; } else { m_strText = KIO::decodeFileName(m_strName); } const QString urlStr = m_entry.stringValue(KIO::UDSEntry::UDS_URL); const bool UDS_URL_seen = !urlStr.isEmpty(); if (UDS_URL_seen) { m_url = QUrl(urlStr); if (m_url.isLocalFile()) { m_bIsLocalUrl = true; } } QMimeDatabase db; const QString mimeTypeStr = m_entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE); m_bMimeTypeKnown = !mimeTypeStr.isEmpty(); if (m_bMimeTypeKnown) { m_mimeType = db.mimeTypeForName(mimeTypeStr); } m_guessedMimeType = m_entry.stringValue(KIO::UDSEntry::UDS_GUESSED_MIME_TYPE); m_bLink = !m_entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST).isEmpty(); // we don't store the link dest const int hiddenVal = m_entry.numberValue(KIO::UDSEntry::UDS_HIDDEN, -1); m_hidden = hiddenVal == 1 ? Hidden : (hiddenVal == 0 ? Shown : Auto); if (_urlIsDirectory && !UDS_URL_seen && !m_strName.isEmpty() && m_strName != QLatin1String(".")) { m_url.setPath(concatPaths(m_url.path(), m_strName)); } m_iconName.clear(); } inline //because it is used only in one place KIO::filesize_t KFileItemPrivate::size() const { // Extract it from the KIO::UDSEntry long long fieldVal = m_entry.numberValue(KIO::UDSEntry::UDS_SIZE, -1); if (fieldVal != -1) { return fieldVal; } // If not in the KIO::UDSEntry, or if UDSEntry empty, use stat() [if local URL] if (m_bIsLocalUrl) { return QFileInfo(m_url.toLocalFile()).size(); } return 0; } static uint udsFieldForTime(KFileItem::FileTimes mappedWhich) { switch (mappedWhich) { case KFileItem::ModificationTime: return KIO::UDSEntry::UDS_MODIFICATION_TIME; case KFileItem::AccessTime: return KIO::UDSEntry::UDS_ACCESS_TIME; case KFileItem::CreationTime: return KIO::UDSEntry::UDS_CREATION_TIME; } return 0; } void KFileItemPrivate::setTime(KFileItem::FileTimes mappedWhich, uint time_t_val) const { m_entry.replace(udsFieldForTime(mappedWhich), time_t_val); } void KFileItemPrivate::setTime(KFileItem::FileTimes mappedWhich, const QDateTime &val) const { const QDateTime dt = val.toLocalTime(); // #160979 setTime(mappedWhich, dt.toTime_t()); } QDateTime KFileItemPrivate::time(KFileItem::FileTimes mappedWhich) const { // Extract it from the KIO::UDSEntry const uint uds = udsFieldForTime(mappedWhich); if (uds > 0) { const long long fieldVal = m_entry.numberValue(uds, -1); if (fieldVal != -1) { return QDateTime::fromMSecsSinceEpoch(1000 * fieldVal); } } return QDateTime(); } inline //because it is used only in one place bool KFileItemPrivate::cmp(const KFileItemPrivate &item) const { #if 0 //qDebug() << "Comparing" << m_url << "and" << item.m_url; //qDebug() << " name" << (m_strName == item.m_strName); //qDebug() << " local" << (m_bIsLocalUrl == item.m_bIsLocalUrl); //qDebug() << " mode" << (m_fileMode == item.m_fileMode); //qDebug() << " perm" << (m_permissions == item.m_permissions); //qDebug() << " UDS_EXTENDED_ACL" << (m_entry.stringValue( KIO::UDSEntry::UDS_EXTENDED_ACL ) == item.m_entry.stringValue( KIO::UDSEntry::UDS_EXTENDED_ACL )); //qDebug() << " UDS_ACL_STRING" << (m_entry.stringValue( KIO::UDSEntry::UDS_ACL_STRING ) == item.m_entry.stringValue( KIO::UDSEntry::UDS_ACL_STRING )); //qDebug() << " UDS_DEFAULT_ACL_STRING" << (m_entry.stringValue( KIO::UDSEntry::UDS_DEFAULT_ACL_STRING ) == item.m_entry.stringValue( KIO::UDSEntry::UDS_DEFAULT_ACL_STRING )); //qDebug() << " m_bLink" << (m_bLink == item.m_bLink); //qDebug() << " m_hidden" << (m_hidden == item.m_hidden); //qDebug() << " size" << (size() == item.size()); //qDebug() << " ModificationTime" << m_entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME) << item.m_entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME); //qDebug() << " UDS_ICON_NAME" << (m_entry.stringValue( KIO::UDSEntry::UDS_ICON_NAME ) == item.m_entry.stringValue( KIO::UDSEntry::UDS_ICON_NAME )); #endif return (m_strName == item.m_strName && m_bIsLocalUrl == item.m_bIsLocalUrl && m_fileMode == item.m_fileMode && m_permissions == item.m_permissions && m_entry.stringValue(KIO::UDSEntry::UDS_EXTENDED_ACL) == item.m_entry.stringValue(KIO::UDSEntry::UDS_EXTENDED_ACL) && m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING) == item.m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING) && m_entry.stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING) == item.m_entry.stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING) && m_bLink == item.m_bLink && m_hidden == item.m_hidden && size() == item.size() && m_entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME) == item.m_entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME) && m_entry.stringValue(KIO::UDSEntry::UDS_ICON_NAME) == item.m_entry.stringValue(KIO::UDSEntry::UDS_ICON_NAME) && m_entry.stringValue(KIO::UDSEntry::UDS_TARGET_URL) == item.m_entry.stringValue(KIO::UDSEntry::UDS_TARGET_URL) && m_entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH) == item.m_entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH) ); // Don't compare the mimetypes here. They might not be known, and we don't want to // do the slow operation of determining them here. } inline //because it is used only in one place QString KFileItemPrivate::parsePermissions(mode_t perm) const { static char buffer[ 12 ]; char uxbit, gxbit, oxbit; if ((perm & (S_IXUSR | S_ISUID)) == (S_IXUSR | S_ISUID)) { uxbit = 's'; } else if ((perm & (S_IXUSR | S_ISUID)) == S_ISUID) { uxbit = 'S'; } else if ((perm & (S_IXUSR | S_ISUID)) == S_IXUSR) { uxbit = 'x'; } else { uxbit = '-'; } if ((perm & (S_IXGRP | S_ISGID)) == (S_IXGRP | S_ISGID)) { gxbit = 's'; } else if ((perm & (S_IXGRP | S_ISGID)) == S_ISGID) { gxbit = 'S'; } else if ((perm & (S_IXGRP | S_ISGID)) == S_IXGRP) { gxbit = 'x'; } else { gxbit = '-'; } if ((perm & (S_IXOTH | S_ISVTX)) == (S_IXOTH | S_ISVTX)) { oxbit = 't'; } else if ((perm & (S_IXOTH | S_ISVTX)) == S_ISVTX) { oxbit = 'T'; } else if ((perm & (S_IXOTH | S_ISVTX)) == S_IXOTH) { oxbit = 'x'; } else { oxbit = '-'; } // Include the type in the first char like ls does; people are more used to seeing it, // even though it's not really part of the permissions per se. if (m_bLink) { buffer[0] = 'l'; } else if (m_fileMode != KFileItem::Unknown) { if ((m_fileMode & QT_STAT_MASK) == QT_STAT_DIR) { buffer[0] = 'd'; } #ifdef Q_OS_UNIX else if (S_ISSOCK(m_fileMode)) { buffer[0] = 's'; } else if (S_ISCHR(m_fileMode)) { buffer[0] = 'c'; } else if (S_ISBLK(m_fileMode)) { buffer[0] = 'b'; } else if (S_ISFIFO(m_fileMode)) { buffer[0] = 'p'; } #endif // Q_OS_UNIX else { buffer[0] = '-'; } } else { buffer[0] = '-'; } buffer[1] = (((perm & S_IRUSR) == S_IRUSR) ? 'r' : '-'); buffer[2] = (((perm & S_IWUSR) == S_IWUSR) ? 'w' : '-'); buffer[3] = uxbit; buffer[4] = (((perm & S_IRGRP) == S_IRGRP) ? 'r' : '-'); buffer[5] = (((perm & S_IWGRP) == S_IWGRP) ? 'w' : '-'); buffer[6] = gxbit; buffer[7] = (((perm & S_IROTH) == S_IROTH) ? 'r' : '-'); buffer[8] = (((perm & S_IWOTH) == S_IWOTH) ? 'w' : '-'); buffer[9] = oxbit; // if (hasExtendedACL()) if (m_entry.contains(KIO::UDSEntry::UDS_EXTENDED_ACL)) { buffer[10] = '+'; buffer[11] = 0; } else { buffer[10] = 0; } return QString::fromLatin1(buffer); } /////// KFileItem::KFileItem() : d(nullptr) { } KFileItem::KFileItem(const KIO::UDSEntry &entry, const QUrl &itemOrDirUrl, bool delayedMimeTypes, bool urlIsDirectory) : d(new KFileItemPrivate(entry, KFileItem::Unknown, KFileItem::Unknown, itemOrDirUrl, urlIsDirectory, delayedMimeTypes)) { } KFileItem::KFileItem(mode_t mode, mode_t permissions, const QUrl &url, bool delayedMimeTypes) : d(new KFileItemPrivate(KIO::UDSEntry(), mode, permissions, url, false, delayedMimeTypes)) { } KFileItem::KFileItem(const QUrl &url, const QString &mimeType, mode_t mode) : d(new KFileItemPrivate(KIO::UDSEntry(), mode, KFileItem::Unknown, url, false, false)) { d->m_bMimeTypeKnown = !mimeType.isEmpty(); if (d->m_bMimeTypeKnown) { QMimeDatabase db; d->m_mimeType = db.mimeTypeForName(mimeType); } } // Default implementations for: // - Copy constructor // - Move constructor // - Copy assignment // - Move assignment // - Destructor // The compiler will now generate the content of those. KFileItem::KFileItem(const KFileItem&) = default; KFileItem::~KFileItem() = default; KFileItem::KFileItem(KFileItem&&) = default; KFileItem& KFileItem::operator=(const KFileItem&) = default; KFileItem& KFileItem::operator=(KFileItem&&) = default; void KFileItem::refresh() { if (!d) { qCWarning(KIO_CORE) << "null item"; return; } d->m_fileMode = KFileItem::Unknown; d->m_permissions = KFileItem::Unknown; d->m_hidden = KFileItemPrivate::Auto; refreshMimeType(); // Basically, we can't trust any information we got while listing. // Everything could have changed... // Clearing m_entry makes it possible to detect changes in the size of the file, // the time information, etc. d->m_entry.clear(); d->init(); // re-populates d->m_entry } void KFileItem::refreshMimeType() { if (!d) { return; } d->m_mimeType = QMimeType(); d->m_bMimeTypeKnown = false; d->m_iconName.clear(); } void KFileItem::setDelayedMimeTypes(bool b) { if (!d) { return; } d->m_delayedMimeTypes = b; } void KFileItem::setUrl(const QUrl &url) { if (!d) { qCWarning(KIO_CORE) << "null item"; return; } d->m_url = url; setName(url.fileName()); } void KFileItem::setLocalPath(const QString &path) { if (!d) { qCWarning(KIO_CORE) << "null item"; return; } d->m_entry.replace(KIO::UDSEntry::UDS_LOCAL_PATH, path); } void KFileItem::setName(const QString &name) { if (!d) { qCWarning(KIO_CORE) << "null item"; return; } d->m_strName = name; if (!d->m_strName.isEmpty()) { d->m_strText = KIO::decodeFileName(d->m_strName); } if (d->m_entry.contains(KIO::UDSEntry::UDS_NAME)) { d->m_entry.replace(KIO::UDSEntry::UDS_NAME, d->m_strName); // #195385 } } QString KFileItem::linkDest() const { if (!d) { return QString(); } // Extract it from the KIO::UDSEntry const QString linkStr = d->m_entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST); if (!linkStr.isEmpty()) { return linkStr; } // If not in the KIO::UDSEntry, or if UDSEntry empty, use readlink() [if local URL] if (d->m_bIsLocalUrl) { return QFile::symLinkTarget(d->m_url.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } return QString(); } QString KFileItemPrivate::localPath() const { if (m_bIsLocalUrl) { return m_url.toLocalFile(); } // Extract the local path from the KIO::UDSEntry return m_entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); } QString KFileItem::localPath() const { if (!d) { return QString(); } return d->localPath(); } KIO::filesize_t KFileItem::size() const { if (!d) { return 0; } return d->size(); } bool KFileItem::hasExtendedACL() const { if (!d) { return false; } // Check if the field exists; its value doesn't matter return d->m_entry.contains(KIO::UDSEntry::UDS_EXTENDED_ACL); } KACL KFileItem::ACL() const { if (!d) { return KACL(); } if (hasExtendedACL()) { // Extract it from the KIO::UDSEntry const QString fieldVal = d->m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING); if (!fieldVal.isEmpty()) { return KACL(fieldVal); } } // create one from the basic permissions return KACL(d->m_permissions); } KACL KFileItem::defaultACL() const { if (!d) { return KACL(); } // Extract it from the KIO::UDSEntry const QString fieldVal = d->m_entry.stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING); if (!fieldVal.isEmpty()) { return KACL(fieldVal); } else { return KACL(); } } QDateTime KFileItem::time(FileTimes which) const { if (!d) { return QDateTime(); } return d->time(which); } QString KFileItem::user() const { if (!d) { return QString(); } return d->m_entry.stringValue(KIO::UDSEntry::UDS_USER); } QString KFileItem::group() const { if (!d) { return QString(); } return d->m_entry.stringValue(KIO::UDSEntry::UDS_GROUP); } bool KFileItemPrivate::isSlow() const { if (m_slow == SlowUnknown) { const QString path = localPath(); if (!path.isEmpty()) { const KFileSystemType::Type fsType = KFileSystemType::fileSystemType(path); m_slow = (fsType == KFileSystemType::Nfs || fsType == KFileSystemType::Smb) ? Slow : Fast; } else { m_slow = Slow; } } return m_slow == Slow; } bool KFileItem::isSlow() const { if (!d) { return false; } return d->isSlow(); } QString KFileItem::mimetype() const { if (!d) { return QString(); } KFileItem *that = const_cast(this); return that->determineMimeType().name(); } QMimeType KFileItem::determineMimeType() const { if (!d) { return QMimeType(); } if (!d->m_mimeType.isValid() || !d->m_bMimeTypeKnown) { QMimeDatabase db; if (isDir()) { d->m_mimeType = db.mimeTypeForName(QStringLiteral("inode/directory")); } else { bool isLocalUrl; const QUrl url = mostLocalUrl(&isLocalUrl); d->m_mimeType = db.mimeTypeForUrl(url); // was: d->m_mimeType = KMimeType::findByUrl( url, d->m_fileMode, isLocalUrl ); // => we are no longer using d->m_fileMode for remote URLs. Q_ASSERT(d->m_mimeType.isValid()); //qDebug() << d << "finding final mimetype for" << url << ":" << d->m_mimeType.name(); } d->m_bMimeTypeKnown = true; } if (d->m_delayedMimeTypes) { // if we delayed getting the iconName up till now, this is the right point in time to do so d->m_delayedMimeTypes = false; d->m_useIconNameCache = false; (void)iconName(); } return d->m_mimeType; } bool KFileItem::isMimeTypeKnown() const { if (!d) { return false; } // The mimetype isn't known if determineMimeType was never called (on-demand determination) // or if this fileitem has a guessed mimetype (e.g. ftp symlink) - in which case // it always remains "not fully determined" return d->m_bMimeTypeKnown && d->m_guessedMimeType.isEmpty(); } static bool isDirectoryMounted(const QUrl &url) { // Stating .directory files can cause long freezes when e.g. /home // uses autofs for every user's home directory, i.e. opening /home // in a file dialog will mount every single home directory. // These non-mounted directories can be identified by having 0 size. // There are also other directories with 0 size, such as /proc, that may // be mounted, but those are unlikely to contain .directory (and checking // this would require checking with KMountPoint). // TODO: maybe this could be checked with KFileSystemType instead? QFileInfo info(url.toLocalFile()); if (info.isDir() && info.size() == 0) { return false; } return true; } bool KFileItem::isFinalIconKnown() const { if (!d) { return false; } return d->m_bMimeTypeKnown && (!d->m_delayedMimeTypes); } // KDE5 TODO: merge with comment()? Need to see what lxr says about the usage of both. QString KFileItem::mimeComment() const { if (!d) { return QString(); } const QString displayType = d->m_entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_TYPE); if (!displayType.isEmpty()) { return displayType; } bool isLocalUrl; QUrl url = mostLocalUrl(&isLocalUrl); QMimeType mime = currentMimeType(); // This cannot move to kio_file (with UDS_DISPLAY_TYPE) because it needs // the mimetype to be determined, which is done here, and possibly delayed... if (isLocalUrl && !d->isSlow() && mime.inherits(QStringLiteral("application/x-desktop"))) { KDesktopFile cfg(url.toLocalFile()); QString comment = cfg.desktopGroup().readEntry("Comment"); if (!comment.isEmpty()) { return comment; } } // Support for .directory file in directories if (isLocalUrl && isDir() && !d->isSlow() && isDirectoryMounted(url)) { QUrl u(url); u.setPath(concatPaths(u.path(), QStringLiteral(".directory"))); const KDesktopFile cfg(u.toLocalFile()); const QString comment = cfg.readComment(); if (!comment.isEmpty()) { return comment; } } const QString comment = mime.comment(); //qDebug() << "finding comment for " << url.url() << " : " << d->m_mimeType->name(); if (!comment.isEmpty()) { return comment; } else { return mime.name(); } } static QString iconFromDirectoryFile(const QString &path) { const QString filePath = path + QLatin1String("/.directory"); if (!QFileInfo(filePath).isFile()) { // exists -and- is a file return QString(); } KDesktopFile cfg(filePath); QString icon = cfg.readIcon(); const KConfigGroup group = cfg.desktopGroup(); const QString emptyIcon = group.readEntry("EmptyIcon"); if (!emptyIcon.isEmpty()) { bool isDirEmpty = true; QDirIterator dirIt(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); while (dirIt.hasNext()) { dirIt.next(); if (dirIt.fileName() != QLatin1String(".directory")) { isDirEmpty = false; break; } } if (isDirEmpty) { icon = emptyIcon; } } if (icon.startsWith(QLatin1String("./"))) { // path is relative with respect to the location // of the .directory file (#73463) return path + icon.mid(1); } return icon; } static QString iconFromDesktopFile(const QString &path) { KDesktopFile cfg(path); const QString icon = cfg.readIcon(); if (cfg.hasLinkType()) { const KConfigGroup group = cfg.desktopGroup(); const QString emptyIcon = group.readEntry("EmptyIcon"); if (!emptyIcon.isEmpty()) { const QString u = cfg.readUrl(); const QUrl url(u); if (url.scheme() == QLatin1String("trash")) { // We need to find if the trash is empty, preferably without using a KIO job. // So instead kio_trash leaves an entry in its config file for us. KConfig trashConfig(QStringLiteral("trashrc"), KConfig::SimpleConfig); if (trashConfig.group("Status").readEntry("Empty", true)) { return emptyIcon; } } } } return icon; } QString KFileItem::iconName() const { if (!d) { return QString(); } if (d->m_useIconNameCache && !d->m_iconName.isEmpty()) { return d->m_iconName; } d->m_iconName = d->m_entry.stringValue(KIO::UDSEntry::UDS_ICON_NAME); if (!d->m_iconName.isEmpty()) { d->m_useIconNameCache = d->m_bMimeTypeKnown; return d->m_iconName; } bool isLocalUrl; QUrl url = mostLocalUrl(&isLocalUrl); QMimeDatabase db; QMimeType mime; // Use guessed mimetype for the icon if (!d->m_guessedMimeType.isEmpty()) { mime = db.mimeTypeForName(d->m_guessedMimeType); } else { mime = currentMimeType(); } const bool delaySlowOperations = d->m_delayedMimeTypes; if (isLocalUrl && !delaySlowOperations && mime.inherits(QStringLiteral("application/x-desktop"))) { d->m_iconName = iconFromDesktopFile(url.toLocalFile()); if (!d->m_iconName.isEmpty()) { d->m_useIconNameCache = d->m_bMimeTypeKnown; return d->m_iconName; } } if (isLocalUrl && !delaySlowOperations && isDir()) { if (isDirectoryMounted(url)) { d->m_iconName = iconFromDirectoryFile(url.toLocalFile()); if (!d->m_iconName.isEmpty()) { d->m_useIconNameCache = d->m_bMimeTypeKnown; return d->m_iconName; } } d->m_iconName = KIOPrivate::iconForStandardPath(url.toLocalFile()); if (!d->m_iconName.isEmpty()) { d->m_useIconNameCache = d->m_bMimeTypeKnown; return d->m_iconName; } } d->m_iconName = mime.iconName(); d->m_useIconNameCache = d->m_bMimeTypeKnown; return d->m_iconName; } /** * Returns true if this is a desktop file. * Mimetype determination is optional. */ static bool checkDesktopFile(const KFileItem &item, bool _determineMimeType) { // only local files bool isLocal; const QUrl url = item.mostLocalUrl(&isLocal); if (!isLocal) { return false; } // only regular files if (!item.isRegularFile()) { return false; } // only if readable if (!item.isReadable()) { return false; } // return true if desktop file QMimeType mime = _determineMimeType ? item.determineMimeType() : item.currentMimeType(); return mime.inherits(QStringLiteral("application/x-desktop")); } QStringList KFileItem::overlays() const { if (!d) { return QStringList(); } - QStringList names = d->m_entry.stringValue(KIO::UDSEntry::UDS_ICON_OVERLAY_NAMES).split(',',QString::SkipEmptyParts); + QStringList names = d->m_entry.stringValue(KIO::UDSEntry::UDS_ICON_OVERLAY_NAMES).split(QLatin1Char(','), QString::SkipEmptyParts); if (d->m_bLink) { names.append(QStringLiteral("emblem-symbolic-link")); } if (!isReadable()) { names.append(QStringLiteral("emblem-locked")); } if (checkDesktopFile(*this, false)) { KDesktopFile cfg(localPath()); const KConfigGroup group = cfg.desktopGroup(); // Add a warning emblem if this is an executable desktop file // which is untrusted. if (group.hasKey("Exec") && !KDesktopFile::isAuthorizedDesktopFile(localPath())) { names.append(QStringLiteral("emblem-important")); } if (cfg.hasDeviceType()) { const QString dev = cfg.readDevice(); if (!dev.isEmpty()) { KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByDevice(dev); if (mountPoint) { // mounted? names.append(QStringLiteral("emblem-mounted")); } } } } if (isHidden()) { names.append(QStringLiteral("hidden")); } #ifndef Q_OS_WIN if (isDir()) { bool isLocalUrl; const QUrl url = mostLocalUrl(&isLocalUrl); if (isLocalUrl) { const QString path = url.toLocalFile(); if (KSambaShare::instance()->isDirectoryShared(path) || KNFSShare::instance()->isDirectoryShared(path)) { names.append(QStringLiteral("emblem-shared")); } } } #endif // Q_OS_WIN return names; } QString KFileItem::comment() const { if (!d) { return QString(); } return d->m_entry.stringValue(KIO::UDSEntry::UDS_COMMENT); } bool KFileItem::isReadable() const { if (!d) { return false; } /* struct passwd * user = getpwuid( geteuid() ); bool isMyFile = (QString::fromLocal8Bit(user->pw_name) == d->m_user); // This gets ugly for the group.... // Maybe we want a static QString for the user and a static QStringList // for the groups... then we need to handle the deletion properly... */ if (d->m_permissions != KFileItem::Unknown) { const mode_t readMask = S_IRUSR | S_IRGRP | S_IROTH; // No read permission at all if ((d->m_permissions & readMask) == 0) { return false; } // Read permissions for all: save a stat call if ((d->m_permissions & readMask) == readMask) { return true; } } // Or if we can't read it - not network transparent if (d->m_bIsLocalUrl && !QFileInfo(d->m_url.toLocalFile()).isReadable()) { return false; } return true; } bool KFileItem::isWritable() const { if (!d) { return false; } /* struct passwd * user = getpwuid( geteuid() ); bool isMyFile = (QString::fromLocal8Bit(user->pw_name) == d->m_user); // This gets ugly for the group.... // Maybe we want a static QString for the user and a static QStringList // for the groups... then we need to handle the deletion properly... */ if (d->m_permissions != KFileItem::Unknown) { // No write permission at all if (!(S_IWUSR & d->m_permissions) && !(S_IWGRP & d->m_permissions) && !(S_IWOTH & d->m_permissions)) { return false; } } // Or if we can't write it - not network transparent if (d->m_bIsLocalUrl && !QFileInfo(d->m_url.toLocalFile()).isWritable()) { return false; } return true; } bool KFileItem::isHidden() const { if (!d) { return false; } // The kioslave can specify explicitly that a file is hidden or shown if (d->m_hidden != KFileItemPrivate::Auto) { return d->m_hidden == KFileItemPrivate::Hidden; } // Prefer the filename that is part of the URL, in case the display name is different. QString fileName = d->m_url.fileName(); if (fileName.isEmpty()) { // e.g. "trash:/" fileName = d->m_strName; } - return fileName.length() > 1 && fileName[0] == '.'; // Just "." is current directory, not hidden. + return fileName.length() > 1 && fileName[0] == QLatin1Char('.'); // Just "." is current directory, not hidden. } void KFileItem::setHidden() { if (d) { d->m_hidden = KFileItemPrivate::Hidden; } } bool KFileItem::isDir() const { if (!d) { return false; } if (d->m_fileMode == KFileItem::Unknown) { // Probably the file was deleted already, and KDirLister hasn't told the world yet. //qDebug() << d << url() << "can't say -> false"; return false; // can't say for sure, so no } return (d->m_fileMode & QT_STAT_MASK) == QT_STAT_DIR; } bool KFileItem::isFile() const { if (!d) { return false; } return !isDir(); } #ifndef KIOCORE_NO_DEPRECATED bool KFileItem::acceptsDrops() const { // A directory ? if (isDir()) { return isWritable(); } // But only local .desktop files and executables if (!d->m_bIsLocalUrl) { return false; } if (mimetype() == QLatin1String("application/x-desktop")) { return true; } // Executable, shell script ... ? if (QFileInfo(d->m_url.toLocalFile()).isExecutable()) { return true; } return false; } #endif QString KFileItem::getStatusBarInfo() const { if (!d) { return QString(); } QString text = d->m_strText; const QString comment = mimeComment(); if (d->m_bLink) { - text += ' '; + text += QLatin1Char(' '); if (comment.isEmpty()) { text += i18n("(Symbolic Link to %1)", linkDest()); } else { text += i18n("(%1, Link to %2)", comment, linkDest()); } } else if (targetUrl() != url()) { text += i18n(" (Points to %1)", targetUrl().toDisplayString()); } else if ((d->m_fileMode & QT_STAT_MASK) == QT_STAT_REG) { text += QStringLiteral(" (%1, %2)").arg(comment, KIO::convertSize(size())); } else { text += QStringLiteral(" (%1)").arg(comment); } return text; } bool KFileItem::cmp(const KFileItem &item) const { if (!d && !item.d) { return true; } if (!d || !item.d) { return false; } return d->cmp(*item.d); } bool KFileItem::operator==(const KFileItem &other) const { if (!d && !other.d) { return true; } if (!d || !other.d) { return false; } return d->m_url == other.d->m_url; } bool KFileItem::operator!=(const KFileItem &other) const { return !operator==(other); } bool KFileItem::operator<(const KFileItem &other) const { if (!other.d) { return false; } if (!d) { return other.d->m_url.isValid(); } return d->m_url < other.d->m_url; } bool KFileItem::operator<(const QUrl &other) const { if (!d) { return other.isValid(); } return d->m_url < other; } KFileItem::operator QVariant() const { return qVariantFromValue(*this); } QString KFileItem::permissionsString() const { if (!d) { return QString(); } if (d->m_access.isNull() && d->m_permissions != KFileItem::Unknown) { d->m_access = d->parsePermissions(d->m_permissions); } return d->m_access; } // check if we need to cache this QString KFileItem::timeString(FileTimes which) const { if (!d) { return QString(); } return d->time(which).toString(); } #ifndef KIOCORE_NO_DEPRECATED QString KFileItem::timeString(unsigned int which) const { if (!d) { return QString(); } switch (which) { case KIO::UDSEntry::UDS_ACCESS_TIME: return timeString(AccessTime); case KIO::UDSEntry::UDS_CREATION_TIME: return timeString(CreationTime); case KIO::UDSEntry::UDS_MODIFICATION_TIME: default: return timeString(ModificationTime); } } #endif #ifndef KIOCORE_NO_DEPRECATED void KFileItem::assign(const KFileItem &item) { *this = item; } #endif QUrl KFileItem::mostLocalUrl(bool *local) const { if (!d) { return QUrl(); } const QString local_path = localPath(); if (!local_path.isEmpty()) { if (local) { *local = true; } return QUrl::fromLocalFile(local_path); } else { if (local) { *local = d->m_bIsLocalUrl; } return d->m_url; } } QDataStream &operator<< (QDataStream &s, const KFileItem &a) { if (a.d) { // We don't need to save/restore anything that refresh() invalidates, // since that means we can re-determine those by ourselves. s << a.d->m_url; s << a.d->m_strName; s << a.d->m_strText; } else { s << QUrl(); s << QString(); s << QString(); } return s; } QDataStream &operator>> (QDataStream &s, KFileItem &a) { QUrl url; QString strName, strText; s >> url; s >> strName; s >> strText; if (!a.d) { qCWarning(KIO_CORE) << "null item"; return s; } if (url.isEmpty()) { a.d = nullptr; return s; } a.d->m_url = url; a.d->m_strName = strName; a.d->m_strText = strText; a.d->m_bIsLocalUrl = a.d->m_url.isLocalFile(); a.d->m_bMimeTypeKnown = false; a.refresh(); return s; } QUrl KFileItem::url() const { if (!d) { return QUrl(); } return d->m_url; } mode_t KFileItem::permissions() const { if (!d) { return 0; } return d->m_permissions; } mode_t KFileItem::mode() const { if (!d) { return 0; } return d->m_fileMode; } bool KFileItem::isLink() const { if (!d) { return false; } return d->m_bLink; } bool KFileItem::isLocalFile() const { if (!d) { return false; } return d->m_bIsLocalUrl; } QString KFileItem::text() const { if (!d) { return QString(); } return d->m_strText; } QString KFileItem::name(bool lowerCase) const { if (!d) { return QString(); } if (!lowerCase) { return d->m_strName; } else if (d->m_strLowerCaseName.isNull()) { d->m_strLowerCaseName = d->m_strName.toLower(); } return d->m_strLowerCaseName; } QUrl KFileItem::targetUrl() const { if (!d) { return QUrl(); } const QString targetUrlStr = d->m_entry.stringValue(KIO::UDSEntry::UDS_TARGET_URL); if (!targetUrlStr.isEmpty()) { return QUrl(targetUrlStr); } else { return url(); } } /* * Mimetype handling. * * Initial state: m_mimeType = QMimeType(). * When currentMimeType() is called first: fast mimetype determination, * might either find an accurate mimetype (-> Final state), otherwise we * set m_mimeType but not m_bMimeTypeKnown (-> Intermediate state) * Intermediate state: determineMimeType() does the real determination -> Final state. * * If delayedMimeTypes isn't set, then we always go to the Final state directly. */ QMimeType KFileItem::currentMimeType() const { if (!d) { return QMimeType(); } if (!d->m_mimeType.isValid()) { // On-demand fast (but not always accurate) mimetype determination Q_ASSERT(!d->m_url.isEmpty()); QMimeDatabase db; if (isDir()) { d->m_mimeType = db.mimeTypeForName(QStringLiteral("inode/directory")); return d->m_mimeType; } const QUrl url = mostLocalUrl(); if (d->m_delayedMimeTypes) { const QList mimeTypes = db.mimeTypesForFileName(url.path()); if (mimeTypes.isEmpty()) { d->m_mimeType = db.mimeTypeForName(QStringLiteral("application/octet-stream")); d->m_bMimeTypeKnown = false; } else { d->m_mimeType = mimeTypes.first(); // If there were conflicting globs. determineMimeType will be able to do better. d->m_bMimeTypeKnown = (mimeTypes.count() == 1); } } else { // ## d->m_fileMode isn't used anymore (for remote urls) d->m_mimeType = db.mimeTypeForUrl(url); d->m_bMimeTypeKnown = true; } } return d->m_mimeType; } KIO::UDSEntry KFileItem::entry() const { if (!d) { return KIO::UDSEntry(); } return d->m_entry; } bool KFileItem::isNull() const { return d == nullptr; } KFileItemList::KFileItemList() { } KFileItemList::KFileItemList(const QList &items) : QList(items) { } KFileItem KFileItemList::findByName(const QString &fileName) const { const_iterator it = begin(); const const_iterator itend = end(); for (; it != itend; ++it) { if ((*it).name() == fileName) { return *it; } } return KFileItem(); } KFileItem KFileItemList::findByUrl(const QUrl &url) const { const_iterator it = begin(); const const_iterator itend = end(); for (; it != itend; ++it) { if ((*it).url() == url) { return *it; } } return KFileItem(); } QList KFileItemList::urlList() const { QList lst; const_iterator it = begin(); const const_iterator itend = end(); for (; it != itend; ++it) { lst.append((*it).url()); } return lst; } QList KFileItemList::targetUrlList() const { QList lst; const_iterator it = begin(); const const_iterator itend = end(); for (; it != itend; ++it) { lst.append((*it).targetUrl()); } return lst; } bool KFileItem::isDesktopFile() const { return checkDesktopFile(*this, true); } bool KFileItem::isRegularFile() const { if (!d) { return false; } return (d->m_fileMode & QT_STAT_MASK) == QT_STAT_REG; } QDebug operator<<(QDebug stream, const KFileItem &item) { QDebugStateSaver saver(stream); stream.nospace(); if (item.isNull()) { stream << "[null KFileItem]"; } else { stream << "[KFileItem for " << item.url() << "]"; } return stream; } diff --git a/src/core/kfileitemlistproperties.cpp b/src/core/kfileitemlistproperties.cpp index 3adceaaa..2e5fd625 100644 --- a/src/core/kfileitemlistproperties.cpp +++ b/src/core/kfileitemlistproperties.cpp @@ -1,218 +1,218 @@ /* This file is part of the KDE project Copyright (C) 2008 by Peter Penz Copyright (C) 2008 by George Goldberg Copyright 2009 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 ) 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 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 "kfileitemlistproperties.h" #include #include #include class KFileItemListPropertiesPrivate : public QSharedData { public: KFileItemListPropertiesPrivate() : m_isDirectory(false), m_isFile(false), m_supportsReading(false), m_supportsDeleting(false), m_supportsWriting(false), m_supportsMoving(false), m_isLocal(true) { } void setItems(const KFileItemList &items); void determineMimeTypeAndGroup() const; KFileItemList m_items; mutable QString m_mimeType; mutable QString m_mimeGroup; bool m_isDirectory : 1; bool m_isFile : 1; bool m_supportsReading : 1; bool m_supportsDeleting : 1; bool m_supportsWriting : 1; bool m_supportsMoving : 1; bool m_isLocal : 1; }; KFileItemListProperties::KFileItemListProperties() : d(new KFileItemListPropertiesPrivate) { } KFileItemListProperties::KFileItemListProperties(const KFileItemList &items) : d(new KFileItemListPropertiesPrivate) { setItems(items); } void KFileItemListProperties::setItems(const KFileItemList &items) { d->setItems(items); } void KFileItemListPropertiesPrivate::setItems(const KFileItemList &items) { const bool initialValue = !items.isEmpty(); m_items = items; m_supportsReading = initialValue; m_supportsDeleting = initialValue; m_supportsWriting = initialValue; m_supportsMoving = initialValue; m_isDirectory = initialValue; m_isFile = initialValue; m_isLocal = true; m_mimeType.clear(); m_mimeGroup.clear(); QFileInfo parentDirInfo; foreach (const KFileItem &item, items) { bool isLocal = false; const QUrl url = item.mostLocalUrl(&isLocal); m_isLocal = m_isLocal && isLocal; m_supportsReading = m_supportsReading && KProtocolManager::supportsReading(url); m_supportsDeleting = m_supportsDeleting && KProtocolManager::supportsDeleting(url); m_supportsWriting = m_supportsWriting && KProtocolManager::supportsWriting(url) && item.isWritable(); m_supportsMoving = m_supportsMoving && KProtocolManager::supportsMoving(url); // For local files we can do better: check if we have write permission in parent directory // TODO: if we knew about the parent KFileItem, we could even do that for remote protocols too #ifndef Q_OS_WIN if (m_isLocal && (m_supportsDeleting || m_supportsMoving)) { const QString directory = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile(); if (parentDirInfo.filePath() != directory) { parentDirInfo.setFile(directory); } if (!parentDirInfo.isWritable()) { m_supportsDeleting = false; m_supportsMoving = false; } } #else if (m_isLocal && m_supportsDeleting) { if (!QFileInfo(url.toLocalFile()).isWritable()) m_supportsDeleting = false; } #endif if (m_isDirectory && !item.isDir()) { m_isDirectory = false; } if (m_isFile && !item.isFile()) { m_isFile = false; } } } KFileItemListProperties::KFileItemListProperties(const KFileItemListProperties &other) : d(other.d) { } KFileItemListProperties &KFileItemListProperties::operator=(const KFileItemListProperties &other) { d = other.d; return *this; } KFileItemListProperties::~KFileItemListProperties() { } bool KFileItemListProperties::supportsReading() const { return d->m_supportsReading; } bool KFileItemListProperties::supportsDeleting() const { return d->m_supportsDeleting; } bool KFileItemListProperties::supportsWriting() const { return d->m_supportsWriting; } bool KFileItemListProperties::supportsMoving() const { return d->m_supportsMoving && d->m_supportsDeleting; } bool KFileItemListProperties::isLocal() const { return d->m_isLocal; } KFileItemList KFileItemListProperties::items() const { return d->m_items; } QList KFileItemListProperties::urlList() const { return d->m_items.targetUrlList(); } bool KFileItemListProperties::isDirectory() const { return d->m_isDirectory; } bool KFileItemListProperties::isFile() const { return d->m_isFile; } QString KFileItemListProperties::mimeType() const { if (d->m_mimeType.isEmpty()) { d->determineMimeTypeAndGroup(); } return d->m_mimeType; } QString KFileItemListProperties::mimeGroup() const { if (d->m_mimeType.isEmpty()) { d->determineMimeTypeAndGroup(); } return d->m_mimeGroup; } void KFileItemListPropertiesPrivate::determineMimeTypeAndGroup() const { if (!m_items.isEmpty()) { m_mimeType = m_items.first().mimetype(); - m_mimeGroup = m_mimeType.left(m_mimeType.indexOf('/')); + m_mimeGroup = m_mimeType.left(m_mimeType.indexOf(QLatin1Char('/'))); } foreach (const KFileItem &item, m_items) { const QString itemMimeType = item.mimetype(); // Determine if common mimetype among all items if (m_mimeType != itemMimeType) { m_mimeType.clear(); - if (m_mimeGroup != itemMimeType.left(itemMimeType.indexOf('/'))) { + if (m_mimeGroup != itemMimeType.left(itemMimeType.indexOf(QLatin1Char('/')))) { m_mimeGroup.clear(); // mimetype groups are different as well! } } } } diff --git a/src/core/kioglobal_p_unix.cpp b/src/core/kioglobal_p_unix.cpp index 84542938..273e93cf 100644 --- a/src/core/kioglobal_p_unix.cpp +++ b/src/core/kioglobal_p_unix.cpp @@ -1,43 +1,43 @@ /* This file is part of the KDE libraries Copyright (C) 2014 Alex Richardson 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 "kioglobal_p.h" #include #include #include KIOCORE_EXPORT bool KIOPrivate::isProcessAlive(qint64 pid) { return ::kill(pid, 0) == 0; } KIOCORE_EXPORT void KIOPrivate::sendTerminateSignal(qint64 pid) { ::kill(pid, SIGTERM); } KIOCORE_EXPORT bool KIOPrivate::createSymlink(const QString &source, const QString &destination, SymlinkType type) { Q_UNUSED(type) - return ::symlink(QFile::encodeName(source), QFile::encodeName(destination)) == 0; + return ::symlink(QFile::encodeName(source).constData(), QFile::encodeName(destination).constData()) == 0; } KIOCORE_EXPORT bool KIOPrivate::changeOwnership(const QString& file, KUserId newOwner, KGroupId newGroup) { - return chown(QFile::encodeName(file), newOwner.nativeId(), newGroup.nativeId()) == 0; + return ::chown(QFile::encodeName(file).constData(), newOwner.nativeId(), newGroup.nativeId()) == 0; } diff --git a/src/core/knfsshare.cpp b/src/core/knfsshare.cpp index 4df0615c..6f34efc0 100644 --- a/src/core/knfsshare.cpp +++ b/src/core/knfsshare.cpp @@ -1,233 +1,233 @@ /* This file is part of the KDE project Copyright (c) 2004 Jan Schaefer 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 "knfsshare.h" #include #include #include #include #include #include #include #include #include #include "kiocoredebug.h" class Q_DECL_HIDDEN KNFSShare::KNFSSharePrivate { public: KNFSSharePrivate(KNFSShare *parent); void _k_slotFileChange(const QString &); bool readExportsFile(); bool findExportsFile(); KNFSShare *q; QSet sharedPaths; QString exportsFile; }; KNFSShare::KNFSSharePrivate::KNFSSharePrivate(KNFSShare *parent) : q(parent) { if (findExportsFile()) { readExportsFile(); } } /** * Try to find the nfs config file path * First tries the kconfig, then checks * several well-known paths * @return whether an 'exports' file was found. **/ bool KNFSShare::KNFSSharePrivate::findExportsFile() { KConfig knfsshare(QStringLiteral("knfsshare")); KConfigGroup config(&knfsshare, "General"); exportsFile = config.readPathEntry("exportsFile", QString()); if (!exportsFile.isEmpty() && QFileInfo::exists(exportsFile)) { return true; } if (QFile::exists(QStringLiteral("/etc/exports"))) { exportsFile = QStringLiteral("/etc/exports"); } else { //qDebug() << "Could not find exports file! /etc/exports doesn't exist. Configure it in share/config/knfsshare, [General], exportsFile=...."; return false; } config.writeEntry("exportsFile", exportsFile); return true; } /** * Reads all paths from the exports file * and fills the sharedPaths dict with the values */ bool KNFSShare::KNFSSharePrivate::readExportsFile() { QFile f(exportsFile); //qDebug() << exportsFile; if (!f.open(QIODevice::ReadOnly)) { qCWarning(KIO_CORE) << "KNFSShare: Could not open" << exportsFile; return false; } sharedPaths.clear(); QTextStream s(&f); bool continuedLine = false; // is true if the line before ended with a backslash QString completeLine; while (!s.atEnd()) { QString currentLine = s.readLine().trimmed(); if (continuedLine) { completeLine += currentLine; continuedLine = false; } else { completeLine = currentLine; } // is the line continued in the next line ? if (completeLine.endsWith(QLatin1Char('\\'))) { continuedLine = true; // remove the ending backslash completeLine.chop(1); continue; } // comments or empty lines if (completeLine.startsWith(QLatin1Char('#')) || completeLine.isEmpty()) { continue; } QString path; // Handle quotation marks if (completeLine[0] == QLatin1Char('\"')) { int i = completeLine.indexOf(QLatin1Char('"'), 1); if (i == -1) { qCWarning(KIO_CORE) << "KNFSShare: Parse error: Missing quotation mark:" << completeLine; continue; } path = completeLine.mid(1, i - 1); } else { // no quotation marks int i = completeLine.indexOf(QLatin1Char(' ')); if (i == -1) { i = completeLine.indexOf(QLatin1Char('\t')); } if (i == -1) { path = completeLine; } else { path = completeLine.left(i); } } //qDebug() << "KNFSShare: Found path: " << path; if (!path.isEmpty()) { // normalize path if (!path.endsWith(QLatin1Char('/'))) { path += QLatin1Char('/'); } sharedPaths.insert(path); } } return true; } KNFSShare::KNFSShare() : d(new KNFSSharePrivate(this)) { if (!d->exportsFile.isEmpty() && QFileInfo::exists(d->exportsFile)) { KDirWatch::self()->addFile(d->exportsFile); connect(KDirWatch::self(), SIGNAL(dirty(QString)), this, SLOT(_k_slotFileChange(QString))); } } KNFSShare::~KNFSShare() { // This is not needed, we're exiting the process anyway, and KDirWatch is already deleted. //if (QFile::exists(d->exportsFile)) { // KDirWatch::self()->removeFile(d->exportsFile); //} delete d; } bool KNFSShare::isDirectoryShared(const QString &path) const { if (path.isEmpty()) { return false; } QString fixedPath = path; - if (path[path.length() - 1] != '/') { - fixedPath += '/'; + if (!fixedPath.endsWith(QLatin1Char('/'))) { + fixedPath += QLatin1Char('/'); } return d->sharedPaths.contains(fixedPath); } QStringList KNFSShare::sharedDirectories() const { return d->sharedPaths.values(); } QString KNFSShare::exportsPath() const { return d->exportsFile; } void KNFSShare::KNFSSharePrivate::_k_slotFileChange(const QString &path) { if (path == exportsFile) { readExportsFile(); } emit q->changed(); } class KNFSShareSingleton { public: KNFSShare instance; }; Q_GLOBAL_STATIC(KNFSShareSingleton, _instance) KNFSShare *KNFSShare::instance() { return &_instance()->instance; } #include "moc_knfsshare.cpp" diff --git a/src/core/kprotocolinfo.cpp b/src/core/kprotocolinfo.cpp index 0290c63b..e2ae03e5 100644 --- a/src/core/kprotocolinfo.cpp +++ b/src/core/kprotocolinfo.cpp @@ -1,418 +1,418 @@ /* This file is part of the KDE libraries Copyright (C) 1999 Torben Weis Copyright (C) 2003 Waldo Bastian Copyright 2012 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. */ #include "kprotocolinfo.h" #include "kprotocolinfo_p.h" #include "kprotocolinfofactory_p.h" #include #include #include #include #include // // Internal functions: // KProtocolInfoPrivate::KProtocolInfoPrivate(const QString &path) { KConfig sconfig(path, KConfig::SimpleConfig); KConfigGroup config(&sconfig, "Protocol"); m_name = config.readEntry("protocol"); m_exec = config.readPathEntry("exec", QString()); m_isSourceProtocol = config.readEntry("source", true); m_isHelperProtocol = config.readEntry("helper", false); m_supportsReading = config.readEntry("reading", false); m_supportsWriting = config.readEntry("writing", false); m_supportsMakeDir = config.readEntry("makedir", false); m_supportsDeleting = config.readEntry("deleting", false); m_supportsLinking = config.readEntry("linking", false); m_supportsMoving = config.readEntry("moving", false); m_supportsOpening = config.readEntry("opening", false); m_canCopyFromFile = config.readEntry("copyFromFile", false); m_canCopyToFile = config.readEntry("copyToFile", false); m_canRenameFromFile = config.readEntry("renameFromFile", false); m_canRenameToFile = config.readEntry("renameToFile", false); m_canDeleteRecursive = config.readEntry("deleteRecursive", false); const QString fnu = config.readEntry("fileNameUsedForCopying", "FromURL"); m_fileNameUsedForCopying = KProtocolInfo::FromUrl; if (fnu == QLatin1String("Name")) { m_fileNameUsedForCopying = KProtocolInfo::Name; } else if (fnu == QLatin1String("DisplayName")) { m_fileNameUsedForCopying = KProtocolInfo::DisplayName; } m_listing = config.readEntry("listing", QStringList()); // Many .protocol files say "Listing=false" when they really mean "Listing=" (i.e. unsupported) if (m_listing.count() == 1 && m_listing.first() == QLatin1String("false")) { m_listing.clear(); } m_supportsListing = (m_listing.count() > 0); m_defaultMimetype = config.readEntry("defaultMimetype"); m_determineMimetypeFromExtension = config.readEntry("determineMimetypeFromExtension", true); m_archiveMimeTypes = config.readEntry("archiveMimetype", QStringList()); m_icon = config.readEntry("Icon"); m_config = config.readEntry("config", m_name); m_maxSlaves = config.readEntry("maxInstances", 1); m_maxSlavesPerHost = config.readEntry("maxInstancesPerHost", 0); QString tmp = config.readEntry("input"); if (tmp == QLatin1String("filesystem")) { m_inputType = KProtocolInfo::T_FILESYSTEM; } else if (tmp == QLatin1String("stream")) { m_inputType = KProtocolInfo::T_STREAM; } else { m_inputType = KProtocolInfo::T_NONE; } tmp = config.readEntry("output"); if (tmp == QLatin1String("filesystem")) { m_outputType = KProtocolInfo::T_FILESYSTEM; } else if (tmp == QLatin1String("stream")) { m_outputType = KProtocolInfo::T_STREAM; } else { m_outputType = KProtocolInfo::T_NONE; } m_docPath = config.readPathEntry("X-DocPath", QString()); if (m_docPath.isEmpty()) { m_docPath = config.readPathEntry("DocPath", QString()); } m_protClass = config.readEntry("Class").toLower(); if (m_protClass[0] != QLatin1Char(':')) { m_protClass.prepend(QLatin1Char(':')); } const QStringList extraNames = config.readEntry("ExtraNames", QStringList()); const QStringList extraTypes = config.readEntry("ExtraTypes", QStringList()); QStringList::const_iterator it = extraNames.begin(); QStringList::const_iterator typeit = extraTypes.begin(); for (; it != extraNames.end() && typeit != extraTypes.end(); ++it, ++typeit) { - QVariant::Type type = QVariant::nameToType((*typeit).toLatin1()); + QVariant::Type type = QVariant::nameToType((*typeit).toLatin1().constData()); // currently QVariant::Type and ExtraField::Type use the same subset of values, so we can just cast. m_extraFields.append(KProtocolInfo::ExtraField(*it, static_cast(type))); } m_showPreviews = config.readEntry("ShowPreviews", m_protClass == QLatin1String(":local")); m_capabilities = config.readEntry("Capabilities", QStringList()); m_slaveHandlesNotify = config.readEntry("slaveHandlesNotify", QStringList()); m_proxyProtocol = config.readEntry("ProxiedBy"); } KProtocolInfoPrivate::KProtocolInfoPrivate(const QString &name, const QString &exec, const QJsonObject &json) : m_name(name) , m_exec(exec) { // source has fallback true if not set m_isSourceProtocol = json.value(QStringLiteral("source")).toBool(true); // other bools are fine with default false by toBool m_isHelperProtocol = json.value(QStringLiteral("helper")).toBool(); m_supportsReading = json.value(QStringLiteral("reading")).toBool(); m_supportsWriting = json.value(QStringLiteral("writing")).toBool(); m_supportsMakeDir = json.value(QStringLiteral("makedir")).toBool(); m_supportsDeleting = json.value(QStringLiteral("deleting")).toBool(); m_supportsLinking = json.value(QStringLiteral("linking")).toBool(); m_supportsMoving = json.value(QStringLiteral("moving")).toBool(); m_supportsOpening = json.value(QStringLiteral("opening")).toBool(); m_canCopyFromFile = json.value(QStringLiteral("copyFromFile")).toBool(); m_canCopyToFile = json.value(QStringLiteral("copyToFile")).toBool(); m_canRenameFromFile = json.value(QStringLiteral("renameFromFile")).toBool(); m_canRenameToFile = json.value(QStringLiteral("renameToFile")).toBool(); m_canDeleteRecursive = json.value(QStringLiteral("deleteRecursive")).toBool(); // default is "FromURL" const QString fnu = json.value(QStringLiteral("fileNameUsedForCopying")).toString(); m_fileNameUsedForCopying = KProtocolInfo::FromUrl; if (fnu == QLatin1String("Name")) { m_fileNameUsedForCopying = KProtocolInfo::Name; } else if (fnu == QLatin1String("DisplayName")) { m_fileNameUsedForCopying = KProtocolInfo::DisplayName; } m_listing = json.value(QStringLiteral("listing")).toVariant().toStringList(); // Many .protocol files say "Listing=false" when they really mean "Listing=" (i.e. unsupported) if (m_listing.count() == 1 && m_listing.first() == QLatin1String("false")) { m_listing.clear(); } m_supportsListing = (m_listing.count() > 0); m_defaultMimetype = json.value(QStringLiteral("defaultMimetype")).toString(); // determineMimetypeFromExtension has fallback true if not set m_determineMimetypeFromExtension = json.value(QStringLiteral("determineMimetypeFromExtension")).toBool(true); m_archiveMimeTypes = json.value(QStringLiteral("archiveMimetype")).toVariant().toStringList(); m_icon = json.value(QStringLiteral("Icon")).toString(); // config has fallback to name if not set m_config = json.value(QStringLiteral("config")).toString(m_name); // max slaves has fallback to 1 if not set m_maxSlaves = json.value(QStringLiteral("maxInstances")).toInt(1); m_maxSlavesPerHost = json.value(QStringLiteral("maxInstancesPerHost")).toInt(); QString tmp = json.value(QStringLiteral("input")).toString(); if (tmp == QLatin1String("filesystem")) { m_inputType = KProtocolInfo::T_FILESYSTEM; } else if (tmp == QLatin1String("stream")) { m_inputType = KProtocolInfo::T_STREAM; } else { m_inputType = KProtocolInfo::T_NONE; } tmp = json.value(QStringLiteral("output")).toString(); if (tmp == QLatin1String("filesystem")) { m_outputType = KProtocolInfo::T_FILESYSTEM; } else if (tmp == QLatin1String("stream")) { m_outputType = KProtocolInfo::T_STREAM; } else { m_outputType = KProtocolInfo::T_NONE; } m_docPath = json.value(QStringLiteral("X-DocPath")).toString(); if (m_docPath.isEmpty()) { m_docPath = json.value(QStringLiteral("DocPath")).toString(); } m_protClass = json.value(QStringLiteral("Class")).toString().toLower(); if (m_protClass[0] != QLatin1Char(':')) { m_protClass.prepend(QLatin1Char(':')); } // ExtraNames is a translated value, use the KCoreAddons helper to read it const QStringList extraNames = KPluginMetaData::readTranslatedValue(json, QStringLiteral("ExtraNames")).toVariant().toStringList(); const QStringList extraTypes = json.value(QStringLiteral("ExtraTypes")).toVariant().toStringList(); QStringList::const_iterator it = extraNames.begin(); QStringList::const_iterator typeit = extraTypes.begin(); for (; it != extraNames.end() && typeit != extraTypes.end(); ++it, ++typeit) { - QVariant::Type type = QVariant::nameToType((*typeit).toLatin1()); + QVariant::Type type = QVariant::nameToType((*typeit).toLatin1().constData()); // currently QVariant::Type and ExtraField::Type use the same subset of values, so we can just cast. m_extraFields.append(KProtocolInfo::ExtraField(*it, static_cast(type))); } // fallback based on class m_showPreviews = json.value(QStringLiteral("ShowPreviews")).toBool(m_protClass == QLatin1String(":local")); m_capabilities = json.value(QStringLiteral("Capabilities")).toVariant().toStringList(); m_slaveHandlesNotify = json.value(QStringLiteral("slaveHandlesNotify")).toVariant().toStringList(); m_proxyProtocol = json.value(QStringLiteral("ProxiedBy")).toString(); } // // Static functions: // QStringList KProtocolInfo::protocols() { return KProtocolInfoFactory::self()->protocols(); } bool KProtocolInfo::isFilterProtocol(const QString &_protocol) { // We call the findProtocol directly (not via KProtocolManager) to bypass any proxy settings. KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol); if (!prot) { return false; } return !prot->m_isSourceProtocol; } QString KProtocolInfo::icon(const QString &_protocol) { // We call the findProtocol directly (not via KProtocolManager) to bypass any proxy settings. KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol); if (!prot) { return QString(); } return prot->m_icon; } QString KProtocolInfo::config(const QString &_protocol) { // We call the findProtocol directly (not via KProtocolManager) to bypass any proxy settings. KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol); if (!prot) { return QString(); } return QStringLiteral("kio_%1rc").arg(prot->m_config); } int KProtocolInfo::maxSlaves(const QString &_protocol) { KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol); if (!prot) { return 1; } return prot->m_maxSlaves; } int KProtocolInfo::maxSlavesPerHost(const QString &_protocol) { KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol); if (!prot) { return 0; } return prot->m_maxSlavesPerHost; } bool KProtocolInfo::determineMimetypeFromExtension(const QString &_protocol) { KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol); if (!prot) { return true; } return prot->m_determineMimetypeFromExtension; } QString KProtocolInfo::exec(const QString &protocol) { KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(protocol); if (!prot) { return QString(); } return prot->m_exec; } KProtocolInfo::ExtraFieldList KProtocolInfo::extraFields(const QUrl &url) { KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(url.scheme()); if (!prot) { return ExtraFieldList(); } return prot->m_extraFields; } QString KProtocolInfo::docPath(const QString &_protocol) { KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol); if (!prot) { return QString(); } return prot->m_docPath; } QString KProtocolInfo::protocolClass(const QString &_protocol) { KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol); if (!prot) { return QString(); } return prot->m_protClass; } bool KProtocolInfo::showFilePreview(const QString &_protocol) { KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol); const bool defaultSetting = prot ? prot->m_showPreviews : false; KConfigGroup group(KSharedConfig::openConfig(), "PreviewSettings"); return group.readEntry(_protocol, defaultSetting); } QStringList KProtocolInfo::capabilities(const QString &_protocol) { KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol); if (!prot) { return QStringList(); } return prot->m_capabilities; } QStringList KProtocolInfo::archiveMimetypes(const QString &protocol) { KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(protocol); if (!prot) { return QStringList(); } return prot->m_archiveMimeTypes; } QStringList KProtocolInfo::slaveHandlesNotify(const QString &_protocol) { KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol); if (!prot) { return QStringList(); } return prot->m_slaveHandlesNotify; } QString KProtocolInfo::proxiedBy(const QString &_protocol) { KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol); if (!prot) { return QString(); } return prot->m_proxyProtocol; } bool KProtocolInfo::isFilterProtocol(const QUrl &url) { return isFilterProtocol(url.scheme()); } bool KProtocolInfo::isHelperProtocol(const QUrl &url) { return isHelperProtocol(url.scheme()); } bool KProtocolInfo::isHelperProtocol(const QString &protocol) { // We call the findProtocol directly (not via KProtocolManager) to bypass any proxy settings. KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(protocol); if (prot) { return prot->m_isHelperProtocol; } return false; } bool KProtocolInfo::isKnownProtocol(const QUrl &url) { return isKnownProtocol(url.scheme()); } bool KProtocolInfo::isKnownProtocol(const QString &protocol) { // We call the findProtocol (const QString&) to bypass any proxy settings. KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(protocol); return prot; } diff --git a/src/core/kprotocolinfofactory.cpp b/src/core/kprotocolinfofactory.cpp index f80ec3ee..5e818c93 100644 --- a/src/core/kprotocolinfofactory.cpp +++ b/src/core/kprotocolinfofactory.cpp @@ -1,144 +1,144 @@ /* This file is part of the KDE libraries Copyright (C) 1999 Torben Weis Copyright (C) 2003 Waldo Bastian Copyright 2012 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. */ #include "kprotocolinfofactory_p.h" #include "kprotocolinfo_p.h" #include #include #include #include #include "kiocoredebug.h" Q_GLOBAL_STATIC(KProtocolInfoFactory, kProtocolInfoFactoryInstance) KProtocolInfoFactory *KProtocolInfoFactory::self() { return kProtocolInfoFactoryInstance(); } KProtocolInfoFactory::KProtocolInfoFactory() : m_cacheDirty(true) { } KProtocolInfoFactory::~KProtocolInfoFactory() { QMutexLocker locker(&m_mutex); qDeleteAll(m_cache); m_cache.clear(); m_cacheDirty = true; } QStringList KProtocolInfoFactory::protocols() { QMutexLocker locker(&m_mutex); // fill cache, if not already done and use it fillCache(); return m_cache.keys(); } QList KProtocolInfoFactory::allProtocols() { QMutexLocker locker(&m_mutex); // fill cache, if not already done and use it fillCache(); return m_cache.values(); } KProtocolInfoPrivate *KProtocolInfoFactory::findProtocol(const QString &protocol) { Q_ASSERT(!protocol.isEmpty()); - Q_ASSERT(!protocol.contains(':')); + Q_ASSERT(!protocol.contains(QLatin1Char(':'))); QMutexLocker locker(&m_mutex); const bool filled = fillCache(); KProtocolInfoPrivate *info = m_cache.value(protocol); if (!info && !filled) { // Unknown protocol! Maybe it just got installed and our cache is out of date? qCDebug(KIO_CORE) << "Refilling KProtocolInfoFactory cache in the hope to find" << protocol; m_cacheDirty = true; fillCache(); info = m_cache.value(protocol); } return info; } bool KProtocolInfoFactory::fillCache() { // mutex MUST be locked from the outside! Q_ASSERT(!m_mutex.tryLock()); // no work if filled if (!m_cacheDirty) { return false; } qDeleteAll(m_cache); m_cache.clear(); // first: search for meta data protocol info, that might be bundled with applications // we search in all library paths inside kf5/kio Q_FOREACH (const KPluginMetaData &md, KPluginLoader::findPlugins(QStringLiteral("kf5/kio"))) { // get slave name & protocols it supports, if any const QString slavePath = md.fileName(); const QJsonObject protocols(md.rawData().value(QStringLiteral("KDE-KIO-Protocols")).toObject()); qCDebug(KIO_CORE) << slavePath << "supports protocols" << protocols.keys(); // add all protocols, does nothing if object invalid for (auto it = protocols.begin(); it != protocols.end(); ++it) { // skip empty objects const QJsonObject protocol(it.value().toObject()); if (protocol.isEmpty()) { continue; } // add to cache, skip double entries if (!m_cache.contains(it.key())) { m_cache.insert(it.key(), new KProtocolInfoPrivate(it.key(), slavePath, protocol)); } } } // second: fallback to .protocol files Q_FOREACH (const QString &serviceDir, QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kservices5"), QStandardPaths::LocateDirectory)) { QDirIterator it(serviceDir); while (it.hasNext()) { const QString file = it.next(); if (file.endsWith(QLatin1String(".protocol"))) { const QString prot = it.fileInfo().baseName(); // add to cache, skip double entries if (!m_cache.contains(prot)) { m_cache.insert(prot, new KProtocolInfoPrivate(file)); } } } } // all done, don't do it again m_cacheDirty = false; return true; } diff --git a/src/core/kprotocolmanager.cpp b/src/core/kprotocolmanager.cpp index 1db64d44..fa175e1f 100644 --- a/src/core/kprotocolmanager.cpp +++ b/src/core/kprotocolmanager.cpp @@ -1,1334 +1,1334 @@ /* This file is part of the KDE libraries Copyright (C) 1999 Torben Weis Copyright (C) 2000- Waldo Bastain Copyright (C) 2000- Dawit Alemayehu Copyright (C) 2008 JarosÅ‚aw Staniek 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 "kprotocolmanager.h" #include "kprotocolinfo_p.h" #include "hostinfo.h" #include #include #include #ifdef Q_OS_WIN #include #undef interface //windows.h defines this, breaks QtDBus since it has parameters named interface #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #if !defined(QT_NO_NETWORKPROXY) && (defined (Q_OS_WIN32) || defined(Q_OS_MAC)) #include #include #endif #include #include #include #include #include #include "slaveconfig.h" #include "ioslave_defaults.h" #include "http_slave_defaults.h" #define QL1S(x) QLatin1String(x) #define QL1C(x) QLatin1Char(x) typedef QPair SubnetPair; /* Domain suffix match. E.g. return true if host is "cuzco.inka.de" and nplist is "inka.de,hadiko.de" or if host is "localhost" and nplist is "localhost". */ static bool revmatch(const char *host, const char *nplist) { if (host == nullptr) { return false; } const char *hptr = host + strlen(host) - 1; const char *nptr = nplist + strlen(nplist) - 1; const char *shptr = hptr; while (nptr >= nplist) { if (*hptr != *nptr) { hptr = shptr; // Try to find another domain or host in the list while (--nptr >= nplist && *nptr != ',' && *nptr != ' '); // Strip out multiple spaces and commas while (--nptr >= nplist && (*nptr == ',' || *nptr == ' ')); } else { if (nptr == nplist || nptr[-1] == ',' || nptr[-1] == ' ') { return true; } if (nptr[-1] == '/' && hptr == host) { // "bugs.kde.org" vs "http://bugs.kde.org", the config UI says URLs are ok return true; } if (hptr == host) { // e.g. revmatch("bugs.kde.org","mybugs.kde.org") return false; } hptr--; nptr--; } } return false; } class KProxyData : public QObject { Q_OBJECT public: KProxyData(const QString &slaveProtocol, const QStringList &proxyAddresses) : protocol(slaveProtocol) , proxyList(proxyAddresses) { } void removeAddress(const QString &address) { proxyList.removeAll(address); } QString protocol; QStringList proxyList; }; class KProtocolManagerPrivate { public: KProtocolManagerPrivate(); ~KProtocolManagerPrivate(); bool shouldIgnoreProxyFor(const QUrl &url); void sync(); KProtocolManager::ProxyType proxyType(); bool useReverseProxy(); QString readNoProxyFor(); QString proxyFor(const QString &protocol); QStringList getSystemProxyFor(const QUrl &url); QMutex mutex; // protects all member vars KSharedConfig::Ptr configPtr; KSharedConfig::Ptr http_config; QString modifiers; QString useragent; QString noProxyFor; QList noProxySubnets; QCache cachedProxyData; QMap protocolForArchiveMimetypes; }; Q_GLOBAL_STATIC(KProtocolManagerPrivate, kProtocolManagerPrivate) static void syncOnExit() { if (kProtocolManagerPrivate.exists()) kProtocolManagerPrivate()->sync(); } KProtocolManagerPrivate::KProtocolManagerPrivate() { // post routine since KConfig::sync() breaks if called too late qAddPostRoutine(syncOnExit); cachedProxyData.setMaxCost(200); // double the max cost. } KProtocolManagerPrivate::~KProtocolManagerPrivate() { } /* * Returns true if url is in the no proxy list. */ bool KProtocolManagerPrivate::shouldIgnoreProxyFor(const QUrl &url) { bool isMatch = false; const KProtocolManager::ProxyType type = proxyType(); const bool useRevProxy = ((type == KProtocolManager::ManualProxy) && useReverseProxy()); const bool useNoProxyList = (type == KProtocolManager::ManualProxy || type == KProtocolManager::EnvVarProxy); // No proxy only applies to ManualProxy and EnvVarProxy types... if (useNoProxyList && noProxyFor.isEmpty()) { QStringList noProxyForList(readNoProxyFor().split(QL1C(','))); QMutableStringListIterator it(noProxyForList); while (it.hasNext()) { SubnetPair subnet = QHostAddress::parseSubnet(it.next()); if (!subnet.first.isNull()) { noProxySubnets << subnet; it.remove(); } } noProxyFor = noProxyForList.join(QLatin1Char(',')); } if (!noProxyFor.isEmpty()) { QString qhost = url.host().toLower(); QByteArray host = qhost.toLatin1(); const QString qno_proxy = noProxyFor.trimmed().toLower(); const QByteArray no_proxy = qno_proxy.toLatin1(); - isMatch = revmatch(host, no_proxy); + isMatch = revmatch(host.constData(), no_proxy.constData()); // If no match is found and the request url has a port // number, try the combination of "host:port". This allows // users to enter host:port in the No-proxy-For list. if (!isMatch && url.port() > 0) { qhost += QL1C(':'); qhost += QString::number(url.port()); host = qhost.toLatin1(); - isMatch = revmatch(host, no_proxy); + isMatch = revmatch(host.constData(), no_proxy.constData()); } // If the hostname does not contain a dot, check if // is part of noProxy. - if (!isMatch && !host.isEmpty() && (strchr(host, '.') == nullptr)) { - isMatch = revmatch("", no_proxy); + if (!isMatch && !host.isEmpty() && (strchr(host.constData(), '.') == nullptr)) { + isMatch = revmatch("", no_proxy.constData()); } } const QString host(url.host()); if (!noProxySubnets.isEmpty() && !host.isEmpty()) { QHostAddress address(host); // If request url is not IP address, do a DNS lookup of the hostname. // TODO: Perhaps we should make configurable ? if (address.isNull()) { //qDebug() << "Performing DNS lookup for" << host; QHostInfo info = KIO::HostInfo::lookupHost(host, 2000); const QList addresses = info.addresses(); if (!addresses.isEmpty()) { address = addresses.first(); } } if (!address.isNull()) { Q_FOREACH (const SubnetPair &subnet, noProxySubnets) { if (address.isInSubnet(subnet)) { isMatch = true; break; } } } } return (useRevProxy != isMatch); } void KProtocolManagerPrivate::sync() { QMutexLocker lock(&mutex); if (http_config) { http_config->sync(); } if (configPtr) { configPtr->sync(); } } #define PRIVATE_DATA \ KProtocolManagerPrivate *d = kProtocolManagerPrivate() void KProtocolManager::reparseConfiguration() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); if (d->http_config) { d->http_config->reparseConfiguration(); } if (d->configPtr) { d->configPtr->reparseConfiguration(); } d->cachedProxyData.clear(); d->noProxyFor.clear(); d->modifiers.clear(); d->useragent.clear(); lock.unlock(); // Force the slave config to re-read its config... KIO::SlaveConfig::self()->reset(); } static KSharedConfig::Ptr config() { PRIVATE_DATA; Q_ASSERT(!d->mutex.tryLock()); // the caller must have locked the mutex if (!d->configPtr) { d->configPtr = KSharedConfig::openConfig(QStringLiteral("kioslaverc"), KConfig::NoGlobals); } return d->configPtr; } KProtocolManager::ProxyType KProtocolManagerPrivate::proxyType() { KConfigGroup cg(config(), "Proxy Settings"); return static_cast(cg.readEntry("ProxyType", 0)); } bool KProtocolManagerPrivate::useReverseProxy() { KConfigGroup cg(config(), "Proxy Settings"); return cg.readEntry("ReversedException", false); } QString KProtocolManagerPrivate::readNoProxyFor() { QString noProxy = config()->group("Proxy Settings").readEntry("NoProxyFor"); if (proxyType() == KProtocolManager::EnvVarProxy) { - noProxy = QString::fromLocal8Bit(qgetenv(noProxy.toLocal8Bit())); + noProxy = QString::fromLocal8Bit(qgetenv(noProxy.toLocal8Bit().constData())); } return noProxy; } QMap KProtocolManager::entryMap(const QString &group) { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return config()->entryMap(group); } static KConfigGroup http_config() { PRIVATE_DATA; Q_ASSERT(!d->mutex.tryLock()); // the caller must have locked the mutex if (!d->http_config) { d->http_config = KSharedConfig::openConfig(QStringLiteral("kio_httprc"), KConfig::NoGlobals); } return KConfigGroup(d->http_config, QString()); } /*=============================== TIMEOUT SETTINGS ==========================*/ int KProtocolManager::readTimeout() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); KConfigGroup cg(config(), QString()); int val = cg.readEntry("ReadTimeout", DEFAULT_READ_TIMEOUT); return qMax(MIN_TIMEOUT_VALUE, val); } int KProtocolManager::connectTimeout() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); KConfigGroup cg(config(), QString()); int val = cg.readEntry("ConnectTimeout", DEFAULT_CONNECT_TIMEOUT); return qMax(MIN_TIMEOUT_VALUE, val); } int KProtocolManager::proxyConnectTimeout() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); KConfigGroup cg(config(), QString()); int val = cg.readEntry("ProxyConnectTimeout", DEFAULT_PROXY_CONNECT_TIMEOUT); return qMax(MIN_TIMEOUT_VALUE, val); } int KProtocolManager::responseTimeout() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); KConfigGroup cg(config(), QString()); int val = cg.readEntry("ResponseTimeout", DEFAULT_RESPONSE_TIMEOUT); return qMax(MIN_TIMEOUT_VALUE, val); } /*========================== PROXY SETTINGS =================================*/ bool KProtocolManager::useProxy() { return proxyType() != NoProxy; } bool KProtocolManager::useReverseProxy() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return d->useReverseProxy(); } KProtocolManager::ProxyType KProtocolManager::proxyType() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return d->proxyType(); } KProtocolManager::ProxyAuthMode KProtocolManager::proxyAuthMode() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); KConfigGroup cg(config(), "Proxy Settings"); return static_cast(cg.readEntry("AuthMode", 0)); } /*========================== CACHING =====================================*/ bool KProtocolManager::useCache() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return http_config().readEntry("UseCache", true); } KIO::CacheControl KProtocolManager::cacheControl() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); QString tmp = http_config().readEntry("cache"); if (tmp.isEmpty()) { return DEFAULT_CACHE_CONTROL; } return KIO::parseCacheControl(tmp); } QString KProtocolManager::cacheDir() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); - return http_config().readPathEntry("CacheDir", QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/kio_http"); + return http_config().readPathEntry("CacheDir", QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/kio_http")); } int KProtocolManager::maxCacheAge() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return http_config().readEntry("MaxCacheAge", DEFAULT_MAX_CACHE_AGE); } int KProtocolManager::maxCacheSize() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return http_config().readEntry("MaxCacheSize", DEFAULT_MAX_CACHE_SIZE); } QString KProtocolManager::noProxyFor() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return d->readNoProxyFor(); } static QString adjustProtocol(const QString &scheme) { if (scheme.compare(QL1S("webdav"), Qt::CaseInsensitive) == 0) { return QStringLiteral("http"); } if (scheme.compare(QL1S("webdavs"), Qt::CaseInsensitive) == 0) { return QStringLiteral("https"); } return scheme.toLower(); } QString KProtocolManager::proxyFor(const QString &protocol) { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return d->proxyFor(protocol); } QString KProtocolManagerPrivate::proxyFor(const QString &protocol) { const QString key = adjustProtocol(protocol) + QL1S("Proxy"); QString proxyStr(config()->group("Proxy Settings").readEntry(key)); const int index = proxyStr.lastIndexOf(QL1C(' ')); if (index > -1) { bool ok = false; const QString portStr(proxyStr.right(proxyStr.length() - index - 1)); portStr.toInt(&ok); if (ok) { proxyStr = proxyStr.left(index) + QL1C(':') + portStr; } else { proxyStr.clear(); } } return proxyStr; } QString KProtocolManager::proxyForUrl(const QUrl &url) { const QStringList proxies = proxiesForUrl(url); if (proxies.isEmpty()) { return QString(); } return proxies.first(); } QStringList KProtocolManagerPrivate::getSystemProxyFor(const QUrl &url) { QStringList proxies; #if !defined(QT_NO_NETWORKPROXY) && (defined(Q_OS_WIN32) || defined(Q_OS_MAC)) QNetworkProxyQuery query(url); const QList proxyList = QNetworkProxyFactory::systemProxyForQuery(query); Q_FOREACH (const QNetworkProxy &proxy, proxyList) { QUrl url; const QNetworkProxy::ProxyType type = proxy.type(); if (type == QNetworkProxy::NoProxy || type == QNetworkProxy::DefaultProxy) { proxies << QL1S("DIRECT"); continue; } if (type == QNetworkProxy::HttpProxy || type == QNetworkProxy::HttpCachingProxy) { url.setScheme(QL1S("http")); } else if (type == QNetworkProxy::Socks5Proxy) { url.setScheme(QL1S("socks")); } else if (type == QNetworkProxy::FtpCachingProxy) { url.setScheme(QL1S("ftp")); } url.setHost(proxy.hostName()); url.setPort(proxy.port()); url.setUserName(proxy.user()); proxies << url.url(); } #else // On Unix/Linux use system environment variables if any are set. QString proxyVar(proxyFor(url.scheme())); // Check for SOCKS proxy, if not proxy is found for given url. if (!proxyVar.isEmpty()) { - const QString proxy(QString::fromLocal8Bit(qgetenv(proxyVar.toLocal8Bit())).trimmed()); + const QString proxy(QString::fromLocal8Bit(qgetenv(proxyVar.toLocal8Bit().constData())).trimmed()); if (!proxy.isEmpty()) { proxies << proxy; } } // Add the socks proxy as an alternate proxy if it exists, proxyVar = proxyFor(QStringLiteral("socks")); if (!proxyVar.isEmpty()) { - QString proxy = QString::fromLocal8Bit(qgetenv(proxyVar.toLocal8Bit())).trimmed(); + QString proxy = QString::fromLocal8Bit(qgetenv(proxyVar.toLocal8Bit().constData())).trimmed(); // Make sure the scheme of SOCKS proxy is always set to "socks://". const int index = proxy.indexOf(QL1S("://")); proxy = QL1S("socks://") + (index == -1 ? proxy : proxy.mid(index + 3)); if (!proxy.isEmpty()) { proxies << proxy; } } #endif return proxies; } QStringList KProtocolManager::proxiesForUrl(const QUrl &url) { QStringList proxyList; PRIVATE_DATA; QMutexLocker lock(&d->mutex); if (!d->shouldIgnoreProxyFor(url)) { switch (d->proxyType()) { case PACProxy: case WPADProxy: { QUrl u(url); const QString protocol = adjustProtocol(u.scheme()); u.setScheme(protocol); if (protocol.startsWith(QLatin1String("http")) || protocol.startsWith(QLatin1String("ftp"))) { QDBusReply reply = QDBusInterface(QStringLiteral("org.kde.kded5"), QStringLiteral("/modules/proxyscout"), QStringLiteral("org.kde.KPAC.ProxyScout")) .call(QStringLiteral("proxiesForUrl"), u.toString()); proxyList = reply; } break; } case EnvVarProxy: proxyList = d->getSystemProxyFor(url); break; case ManualProxy: { QString proxy(d->proxyFor(url.scheme())); if (!proxy.isEmpty()) { proxyList << proxy; } // Add the socks proxy as an alternate proxy if it exists, proxy = d->proxyFor(QStringLiteral("socks")); if (!proxy.isEmpty()) { // Make sure the scheme of SOCKS proxy is always set to "socks://". const int index = proxy.indexOf(QL1S("://")); proxy = QStringLiteral("socks://") + (index == -1 ? proxy : proxy.mid(index + 3)); proxyList << proxy; } } break; case NoProxy: break; } } if (proxyList.isEmpty()) { proxyList << QStringLiteral("DIRECT"); } return proxyList; } void KProtocolManager::badProxy(const QString &proxy) { QDBusInterface(QStringLiteral("org.kde.kded5"), QStringLiteral("/modules/proxyscout")) .asyncCall(QStringLiteral("blackListProxy"), proxy); PRIVATE_DATA; QMutexLocker lock(&d->mutex); const QStringList keys(d->cachedProxyData.keys()); Q_FOREACH (const QString &key, keys) { d->cachedProxyData[key]->removeAddress(proxy); } } QString KProtocolManager::slaveProtocol(const QUrl &url, QString &proxy) { QStringList proxyList; const QString protocol = KProtocolManager::slaveProtocol(url, proxyList); if (!proxyList.isEmpty()) { proxy = proxyList.first(); } return protocol; } // Generates proxy cache key from request given url. static void extractProxyCacheKeyFromUrl(const QUrl &u, QString *key) { if (!key) { return; } *key = u.scheme(); *key += u.host(); if (u.port() > 0) { *key += QString::number(u.port()); } } QString KProtocolManager::slaveProtocol(const QUrl &url, QStringList &proxyList) { #if 0 if (url.hasSubUrl()) { // We don't want the suburl's protocol const QUrl::List list = QUrl::split(url); return slaveProtocol(list.last(), proxyList); } #endif proxyList.clear(); // Do not perform a proxy lookup for any url classified as a ":local" url or // one that does not have a host component or if proxy is disabled. QString protocol(url.scheme()); if (url.host().isEmpty() || KProtocolInfo::protocolClass(protocol) == QL1S(":local") || KProtocolManager::proxyType() == KProtocolManager::NoProxy) { return protocol; } QString proxyCacheKey; extractProxyCacheKeyFromUrl(url, &proxyCacheKey); PRIVATE_DATA; QMutexLocker lock(&d->mutex); // Look for cached proxy information to avoid more work. if (d->cachedProxyData.contains(proxyCacheKey)) { KProxyData *data = d->cachedProxyData.object(proxyCacheKey); proxyList = data->proxyList; return data->protocol; } lock.unlock(); const QStringList proxies = proxiesForUrl(url); const int count = proxies.count(); if (count > 0 && !(count == 1 && proxies.first() == QL1S("DIRECT"))) { Q_FOREACH (const QString &proxy, proxies) { if (proxy == QL1S("DIRECT")) { proxyList << proxy; } else { QUrl u(proxy); if (!u.isEmpty() && u.isValid() && !u.scheme().isEmpty()) { proxyList << proxy; } } } } // The idea behind slave protocols is not applicable to http // and webdav protocols as well as protocols unknown to KDE. if (!proxyList.isEmpty() && !protocol.startsWith(QLatin1String("http")) && !protocol.startsWith(QLatin1String("webdav")) && KProtocolInfo::isKnownProtocol(protocol)) { Q_FOREACH (const QString &proxy, proxyList) { QUrl u(proxy); if (u.isValid() && KProtocolInfo::isKnownProtocol(u.scheme())) { protocol = u.scheme(); break; } } } lock.relock(); // cache the proxy information... d->cachedProxyData.insert(proxyCacheKey, new KProxyData(protocol, proxyList)); return protocol; } /*================================= USER-AGENT SETTINGS =====================*/ QString KProtocolManager::userAgentForHost(const QString &hostname) { const QString sendUserAgent = KIO::SlaveConfig::self()->configData(QStringLiteral("http"), hostname.toLower(), QStringLiteral("SendUserAgent")).toLower(); if (sendUserAgent == QL1S("false")) { return QString(); } const QString useragent = KIO::SlaveConfig::self()->configData(QStringLiteral("http"), hostname.toLower(), QStringLiteral("UserAgent")); // Return the default user-agent if none is specified // for the requested host. if (useragent.isEmpty()) { return defaultUserAgent(); } return useragent; } QString KProtocolManager::defaultUserAgent() { const QString modifiers = KIO::SlaveConfig::self()->configData(QStringLiteral("http"), QString(), QStringLiteral("UserAgentKeys")); return defaultUserAgent(modifiers); } static QString defaultUserAgentFromPreferredService() { QString agentStr; // Check if the default COMPONENT contains a custom default UA string... KService::Ptr service = KMimeTypeTrader::self()->preferredService(QStringLiteral("text/html"), QStringLiteral("KParts/ReadOnlyPart")); if (service && service->showInCurrentDesktop()) agentStr = service->property(QStringLiteral("X-KDE-Default-UserAgent"), QVariant::String).toString(); return agentStr; } // This is not the OS, but the windowing system, e.g. X11 on Unix/Linux. static QString platform() { #if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) return QStringLiteral("X11"); #elif defined(Q_OS_MAC) return QStringLiteral("Macintosh"); #elif defined(Q_OS_WIN) return QStringLiteral("Windows"); #else return QStringLiteral("Unknown"); #endif } QString KProtocolManager::defaultUserAgent(const QString &_modifiers) { PRIVATE_DATA; QMutexLocker lock(&d->mutex); QString modifiers = _modifiers.toLower(); if (modifiers.isEmpty()) { modifiers = QStringLiteral(DEFAULT_USER_AGENT_KEYS); } if (d->modifiers == modifiers && !d->useragent.isEmpty()) { return d->useragent; } d->modifiers = modifiers; /* The following code attempts to determine the default user agent string from the 'X-KDE-UA-DEFAULT-STRING' property of the desktop file for the preferred service that was configured to handle the 'text/html' mime type. If the preferred service's desktop file does not specify this property, the long standing default user agent string will be used. The following keyword placeholders are automatically converted when the user agent string is read from the property: %SECURITY% Expands to"N" when SSL is not supported, otherwise it is ignored. %OSNAME% Expands to operating system name, e.g. Linux. %OSVERSION% Expands to operating system version, e.g. 2.6.32 %SYSTYPE% Expands to machine or system type, e.g. i386 %PLATFORM% Expands to windowing system, e.g. X11 on Unix/Linux. %LANGUAGE% Expands to default language in use, e.g. en-US. %APPVERSION% Expands to QCoreApplication applicationName()/applicationVerison(), e.g. Konqueror/4.5.0. If application name and/or application version number are not set, then "KDE" and the runtime KDE version numbers are used respectively. All of the keywords are handled case-insensitively. */ QString systemName, systemVersion, machine, supp; const bool sysInfoFound = getSystemNameVersionAndMachine(systemName, systemVersion, machine); QString agentStr = defaultUserAgentFromPreferredService(); if (agentStr.isEmpty()) { supp += platform(); if (sysInfoFound) { - if (modifiers.contains('o')) { + if (modifiers.contains(QL1C('o'))) { supp += QL1S("; "); supp += systemName; - if (modifiers.contains('v')) { + if (modifiers.contains(QL1C('v'))) { supp += QL1C(' '); supp += systemVersion; } - if (modifiers.contains('m')) { + if (modifiers.contains(QL1C('m'))) { supp += QL1C(' '); supp += machine; } } - if (modifiers.contains('l')) { + if (modifiers.contains(QL1C('l'))) { supp += QL1S("; "); supp += QLocale::languageToString(QLocale().language()); } } // Full format: Mozilla/5.0 (Linux d->useragent = QStringLiteral("Mozilla/5.0 ("); d->useragent += supp; d->useragent += QStringLiteral(") KHTML/"); d->useragent += QString::number(KIO_VERSION_MAJOR); d->useragent += QL1C('.'); d->useragent += QString::number(KIO_VERSION_MINOR); d->useragent += QL1C('.'); d->useragent += QString::number(KIO_VERSION_PATCH); d->useragent += QStringLiteral(" (like Gecko) Konqueror/"); d->useragent += QString::number(KIO_VERSION_MAJOR); d->useragent += QStringLiteral(" KIO/"); d->useragent += QString::number(KIO_VERSION_MAJOR); d->useragent += QL1C('.'); d->useragent += QString::number(KIO_VERSION_MINOR); } else { QString appName = QCoreApplication::applicationName(); if (appName.isEmpty() || appName.startsWith(QLatin1String("kcmshell"), Qt::CaseInsensitive)) { appName = QStringLiteral("KDE"); } QString appVersion = QCoreApplication::applicationVersion(); if (appVersion.isEmpty()) { appVersion += QString::number(KIO_VERSION_MAJOR); appVersion += QL1C('.'); appVersion += QString::number(KIO_VERSION_MINOR); appVersion += QL1C('.'); appVersion += QString::number(KIO_VERSION_PATCH); } appName += QL1C('/'); appName += appVersion; agentStr.replace(QL1S("%appversion%"), appName, Qt::CaseInsensitive); if (!QSslSocket::supportsSsl()) { agentStr.replace(QLatin1String("%security%"), QL1S("N"), Qt::CaseInsensitive); } else { agentStr.remove(QStringLiteral("%security%"), Qt::CaseInsensitive); } if (sysInfoFound) { // Platform (e.g. X11). It is no longer configurable from UI. agentStr.replace(QL1S("%platform%"), platform(), Qt::CaseInsensitive); // Operating system (e.g. Linux) - if (modifiers.contains('o')) { + if (modifiers.contains(QL1C('o'))) { agentStr.replace(QL1S("%osname%"), systemName, Qt::CaseInsensitive); // OS version (e.g. 2.6.36) - if (modifiers.contains('v')) { + if (modifiers.contains(QL1C('v'))) { agentStr.replace(QL1S("%osversion%"), systemVersion, Qt::CaseInsensitive); } else { agentStr.remove(QStringLiteral("%osversion%"), Qt::CaseInsensitive); } // Machine type (i686, x86-64, etc.) - if (modifiers.contains('m')) { + if (modifiers.contains(QL1C('m'))) { agentStr.replace(QL1S("%systype%"), machine, Qt::CaseInsensitive); } else { agentStr.remove(QStringLiteral("%systype%"), Qt::CaseInsensitive); } } else { agentStr.remove(QStringLiteral("%osname%"), Qt::CaseInsensitive); agentStr.remove(QStringLiteral("%osversion%"), Qt::CaseInsensitive); agentStr.remove(QStringLiteral("%systype%"), Qt::CaseInsensitive); } // Language (e.g. en_US) - if (modifiers.contains('l')) { + if (modifiers.contains(QL1C('l'))) { agentStr.replace(QL1S("%language%"), QLocale::languageToString(QLocale().language()), Qt::CaseInsensitive); } else { agentStr.remove(QStringLiteral("%language%"), Qt::CaseInsensitive); } // Clean up unnecessary separators that could be left over from the // possible keyword removal above... agentStr.replace(QRegExp(QL1S("[(]\\s*[;]\\s*")), QStringLiteral("(")); agentStr.replace(QRegExp(QL1S("[;]\\s*[;]\\s*")), QStringLiteral("; ")); agentStr.replace(QRegExp(QL1S("\\s*[;]\\s*[)]")), QStringLiteral(")")); } else { agentStr.remove(QStringLiteral("%osname%")); agentStr.remove(QStringLiteral("%osversion%")); agentStr.remove(QStringLiteral("%platform%")); agentStr.remove(QStringLiteral("%systype%")); agentStr.remove(QStringLiteral("%language%")); } d->useragent = agentStr.simplified(); } //qDebug() << "USERAGENT STRING:" << d->useragent; return d->useragent; } QString KProtocolManager::userAgentForApplication(const QString &appName, const QString &appVersion, const QStringList &extraInfo) { QString systemName, systemVersion, machine, info; if (getSystemNameVersionAndMachine(systemName, systemVersion, machine)) { info += systemName; info += QL1C('/'); info += systemVersion; info += QStringLiteral("; "); } info += QStringLiteral("KDE/"); info += QString::number(KIO_VERSION_MAJOR); info += QL1C('.'); info += QString::number(KIO_VERSION_MINOR); info += QL1C('.'); info += QString::number(KIO_VERSION_PATCH); if (!machine.isEmpty()) { info += QStringLiteral("; "); info += machine; } info += QStringLiteral("; "); info += extraInfo.join(QStringLiteral("; ")); return (appName + QL1C('/') + appVersion + QStringLiteral(" (") + info + QL1C(')')); } bool KProtocolManager::getSystemNameVersionAndMachine( QString &systemName, QString &systemVersion, QString &machine) { #if defined(Q_OS_WIN) && !defined(_WIN32_WCE) // we do not use unameBuf.sysname information constructed in kdewin32 // because we want to get separate name and version systemName = QStringLiteral("Windows"); OSVERSIONINFOEX versioninfo; ZeroMemory(&versioninfo, sizeof(OSVERSIONINFOEX)); // try calling GetVersionEx using the OSVERSIONINFOEX, if that fails, try using the OSVERSIONINFO versioninfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); bool ok = GetVersionEx((OSVERSIONINFO *) &versioninfo); if (!ok) { versioninfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); ok = GetVersionEx((OSVERSIONINFO *) &versioninfo); } if (ok) { systemVersion = QString::number(versioninfo.dwMajorVersion); systemVersion += QL1C('.'); systemVersion += QString::number(versioninfo.dwMinorVersion); } #else struct utsname unameBuf; if (0 != uname(&unameBuf)) { return false; } - systemName = unameBuf.sysname; - systemVersion = unameBuf.release; - machine = unameBuf.machine; + systemName = QString::fromUtf8(unameBuf.sysname); + systemVersion = QString::fromUtf8(unameBuf.release); + machine = QString::fromUtf8(unameBuf.machine); #endif return true; } QString KProtocolManager::acceptLanguagesHeader() { const QLatin1String english("en"); // User's desktop language preference. QStringList languageList = QLocale().uiLanguages(); // Replace possible "C" in the language list with "en", unless "en" is // already pressent. This is to keep user's priorities in order. // If afterwards "en" is still not present, append it. int idx = languageList.indexOf(QStringLiteral("C")); if (idx != -1) { if (languageList.contains(english)) { languageList.removeAt(idx); } else { languageList[idx] = english; } } if (!languageList.contains(english)) { languageList += english; } // Some languages may have web codes different from locale codes, // read them from the config and insert in proper order. KConfig acclangConf(QStringLiteral("accept-languages.codes"), KConfig::NoGlobals); KConfigGroup replacementCodes(&acclangConf, "ReplacementCodes"); QStringList languageListFinal; Q_FOREACH (const QString &lang, languageList) { const QStringList langs = replacementCodes.readEntry(lang, QStringList()); if (langs.isEmpty()) { languageListFinal += lang; } else { languageListFinal += langs; } } // The header is composed of comma separated languages, with an optional // associated priority estimate (q=1..0) defaulting to 1. // As our language tags are already sorted by priority, we'll just decrease // the value evenly int prio = 10; QString header; Q_FOREACH (const QString &lang, languageListFinal) { header += lang; if (prio < 10) { header += QL1S(";q=0."); header += QString::number(prio); } // do not add cosmetic whitespace in here : it is less compatible (#220677) header += QL1S(","); if (prio > 1) { --prio; } } header.chop(1); // Some of the languages may have country specifier delimited by // underscore, or modifier delimited by at-sign. // The header should use dashes instead. - header.replace('_', '-'); - header.replace('@', '-'); + header.replace(QL1C('_'), QL1C('-')); + header.replace(QL1C('@'), QL1C('-')); return header; } /*==================================== OTHERS ===============================*/ bool KProtocolManager::markPartial() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return config()->group(QByteArray()).readEntry("MarkPartial", true); } int KProtocolManager::minimumKeepSize() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return config()->group(QByteArray()).readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); // 5000 byte } bool KProtocolManager::autoResume() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return config()->group(QByteArray()).readEntry("AutoResume", false); } bool KProtocolManager::persistentConnections() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return config()->group(QByteArray()).readEntry("PersistentConnections", true); } bool KProtocolManager::persistentProxyConnection() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return config()->group(QByteArray()).readEntry("PersistentProxyConnection", false); } QString KProtocolManager::proxyConfigScript() { PRIVATE_DATA; QMutexLocker lock(&d->mutex); return config()->group("Proxy Settings").readEntry("Proxy Config Script"); } /* =========================== PROTOCOL CAPABILITIES ============== */ static KProtocolInfoPrivate *findProtocol(const QUrl &url) { if (!url.isValid()) { return nullptr; } QString protocol = url.scheme(); if (!KProtocolInfo::proxiedBy(protocol).isEmpty()) { QString dummy; protocol = KProtocolManager::slaveProtocol(url, dummy); } return KProtocolInfoFactory::self()->findProtocol(protocol); } KProtocolInfo::Type KProtocolManager::inputType(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return KProtocolInfo::T_NONE; } return prot->m_inputType; } KProtocolInfo::Type KProtocolManager::outputType(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return KProtocolInfo::T_NONE; } return prot->m_outputType; } bool KProtocolManager::isSourceProtocol(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_isSourceProtocol; } bool KProtocolManager::supportsListing(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_supportsListing; } QStringList KProtocolManager::listing(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return QStringList(); } return prot->m_listing; } bool KProtocolManager::supportsReading(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_supportsReading; } bool KProtocolManager::supportsWriting(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_supportsWriting; } bool KProtocolManager::supportsMakeDir(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_supportsMakeDir; } bool KProtocolManager::supportsDeleting(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_supportsDeleting; } bool KProtocolManager::supportsLinking(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_supportsLinking; } bool KProtocolManager::supportsMoving(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_supportsMoving; } bool KProtocolManager::supportsOpening(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_supportsOpening; } bool KProtocolManager::canCopyFromFile(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_canCopyFromFile; } bool KProtocolManager::canCopyToFile(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_canCopyToFile; } bool KProtocolManager::canRenameFromFile(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_canRenameFromFile; } bool KProtocolManager::canRenameToFile(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_canRenameToFile; } bool KProtocolManager::canDeleteRecursive(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return false; } return prot->m_canDeleteRecursive; } KProtocolInfo::FileNameUsedForCopying KProtocolManager::fileNameUsedForCopying(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return KProtocolInfo::FromUrl; } return prot->m_fileNameUsedForCopying; } QString KProtocolManager::defaultMimetype(const QUrl &url) { KProtocolInfoPrivate *prot = findProtocol(url); if (!prot) { return QString(); } return prot->m_defaultMimetype; } QString KProtocolManager::protocolForArchiveMimetype(const QString &mimeType) { PRIVATE_DATA; QMutexLocker lock(&d->mutex); if (d->protocolForArchiveMimetypes.isEmpty()) { const QList allProtocols = KProtocolInfoFactory::self()->allProtocols(); for (QList::const_iterator it = allProtocols.begin(); it != allProtocols.end(); ++it) { const QStringList archiveMimetypes = (*it)->m_archiveMimeTypes; Q_FOREACH (const QString &mime, archiveMimetypes) { d->protocolForArchiveMimetypes.insert(mime, (*it)->m_name); } } } return d->protocolForArchiveMimetypes.value(mimeType); } QString KProtocolManager::charsetFor(const QUrl &url) { return KIO::SlaveConfig::self()->configData(url.scheme(), url.host(), QStringLiteral("Charset")); } #undef PRIVATE_DATA #include "kprotocolmanager.moc" diff --git a/src/core/ksambashare.cpp b/src/core/ksambashare.cpp index 79220e4a..a9ce09e7 100644 --- a/src/core/ksambashare.cpp +++ b/src/core/ksambashare.cpp @@ -1,515 +1,515 @@ /* This file is part of the KDE project Copyright (c) 2004 Jan Schaefer Copyright 2010 Rodrigo Belem 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 "ksambashare.h" #include "ksambashare_p.h" #include "ksambasharedata.h" #include "ksambasharedata_p.h" #include "kiocoredebug.h" #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(KIO_CORE_SAMBASHARE) Q_LOGGING_CATEGORY(KIO_CORE_SAMBASHARE, "kf5.kio.core.sambashare", QtWarningMsg) // Default smb.conf locations // sorted by priority, most priority first static const char *const DefaultSambaConfigFilePathList[] = { "/etc/samba/smb.conf", "/etc/smb.conf", "/usr/local/etc/smb.conf", "/usr/local/samba/lib/smb.conf", "/usr/samba/lib/smb.conf", "/usr/lib/smb.conf", "/usr/local/lib/smb.conf" }; static const int DefaultSambaConfigFilePathListSize = sizeof(DefaultSambaConfigFilePathList) / sizeof(char *); KSambaSharePrivate::KSambaSharePrivate(KSambaShare *parent) : q_ptr(parent) , data() , smbConf() , userSharePath() , skipUserShare(false) { setUserSharePath(); findSmbConf(); data = parse(getNetUserShareInfo()); } KSambaSharePrivate::~KSambaSharePrivate() { } bool KSambaSharePrivate::isSambaInstalled() { if (QFile::exists(QStringLiteral("/usr/sbin/smbd")) || QFile::exists(QStringLiteral("/usr/local/sbin/smbd"))) { return true; } //qDebug() << "Samba is not installed!"; return false; } // Try to find the samba config file path // in several well-known paths bool KSambaSharePrivate::findSmbConf() { for (int i = 0; i < DefaultSambaConfigFilePathListSize; ++i) { - const QString filePath(DefaultSambaConfigFilePathList[i]); + const QString filePath = QString::fromLatin1(DefaultSambaConfigFilePathList[i]); if (QFile::exists(filePath)) { smbConf = filePath; return true; } } qCDebug(KIO_CORE_SAMBASHARE) << "KSambaShare: Could not find smb.conf!"; return false; } void KSambaSharePrivate::setUserSharePath() { const QString rawString = testparmParamValue(QStringLiteral("usershare path")); const QFileInfo fileInfo(rawString); if (fileInfo.isDir()) { userSharePath = rawString; } } int KSambaSharePrivate::runProcess(const QString &progName, const QStringList &args, QByteArray &stdOut, QByteArray &stdErr) { QProcess process; process.setProcessChannelMode(QProcess::SeparateChannels); process.start(progName, args); //TODO: make it async in future process.waitForFinished(); stdOut = process.readAllStandardOutput(); stdErr = process.readAllStandardError(); return process.exitCode(); } QString KSambaSharePrivate::testparmParamValue(const QString ¶meterName) { if (!isSambaInstalled()) { return QString(); } QStringList args; QByteArray stdErr; QByteArray stdOut; args << QStringLiteral("-d0") << QStringLiteral("-s") << QStringLiteral("--parameter-name") << parameterName; runProcess(QStringLiteral("testparm"), args, stdOut, stdErr); //TODO: parse and process error messages. // create a parser for the error output and // send error message somewhere if (!stdErr.isEmpty()) { QList err; err << stdErr.trimmed().split('\n'); if ((err.count() == 2) && err.at(0).startsWith("Load smb config files from") && err.at(1).startsWith("Loaded services file OK.")) { //qDebug() << "Running testparm" << args; } else { qCWarning(KIO_CORE) << "We got some errors while running testparm" << stdErr; } } if (!stdOut.isEmpty()) { return QString::fromLocal8Bit(stdOut.trimmed()); } return QString(); } QByteArray KSambaSharePrivate::getNetUserShareInfo() { if (skipUserShare || !isSambaInstalled()) { return QByteArray(); } QStringList args; QByteArray stdOut; QByteArray stdErr; args << QStringLiteral("usershare") << QStringLiteral("info"); runProcess(QStringLiteral("net"), args, stdOut, stdErr); if (!stdErr.isEmpty()) { if (stdErr.contains("You do not have permission to create a usershare")) { skipUserShare = true; } else if (stdErr.contains("usershares are currently disabled")) { skipUserShare = true; } else { //TODO: parse and process other error messages. // create a parser for the error output and // send error message somewhere qCWarning(KIO_CORE) << "We got some errors while running 'net usershare info'"; qCWarning(KIO_CORE) << stdErr; } } return stdOut; } QStringList KSambaSharePrivate::shareNames() const { return data.keys(); } QStringList KSambaSharePrivate::sharedDirs() const { QStringList dirs; QMap::ConstIterator i; for (i = data.constBegin(); i != data.constEnd(); ++i) { if (!dirs.contains(i.value().path())) { dirs << i.value().path(); } } return dirs; } KSambaShareData KSambaSharePrivate::getShareByName(const QString &shareName) const { return data.value(shareName); } QList KSambaSharePrivate::getSharesByPath(const QString &path) const { QList shares; QMap::ConstIterator i; for (i = data.constBegin(); i != data.constEnd(); ++i) { if (i.value().path() == path) { shares << i.value(); } } return shares; } bool KSambaSharePrivate::isShareNameValid(const QString &name) const { // Samba forbidden chars const QRegExp notToMatchRx(QStringLiteral("[%<>*\?|/\\+=;:\",]")); return (notToMatchRx.indexIn(name) == -1); } bool KSambaSharePrivate::isDirectoryShared(const QString &path) const { QMap::ConstIterator i; for (i = data.constBegin(); i != data.constEnd(); ++i) { if (i.value().path() == path) { return true; } } return false; } bool KSambaSharePrivate::isShareNameAvailable(const QString &name) const { // Samba does not allow to name a share with a user name registered in the system return (!KUser::allUserNames().contains(name) || !data.contains(name)); } KSambaShareData::UserShareError KSambaSharePrivate::isPathValid(const QString &path) const { QFileInfo pathInfo = path; if (!pathInfo.exists()) { return KSambaShareData::UserSharePathNotExists; } if (!pathInfo.isDir()) { return KSambaShareData::UserSharePathNotDirectory; } if (pathInfo.isRelative()) { if (pathInfo.makeAbsolute()) { return KSambaShareData::UserSharePathNotAbsolute; } } // TODO: check if the user is root if (KSambaSharePrivate::testparmParamValue(QStringLiteral("usershare owner only")) == QLatin1String("Yes")) { if (!pathInfo.permission(QFile::ReadUser | QFile::WriteUser)) { return KSambaShareData::UserSharePathNotAllowed; } } return KSambaShareData::UserSharePathOk; } KSambaShareData::UserShareError KSambaSharePrivate::isAclValid(const QString &acl) const { const QRegExp aclRx(QStringLiteral("(?:(?:(\\w(\\w|\\s)*)\\\\|)(\\w+\\s*):([fFrRd]{1})(?:,|))*")); // TODO: check if user is a valid smb user return aclRx.exactMatch(acl) ? KSambaShareData::UserShareAclOk : KSambaShareData::UserShareAclInvalid; } KSambaShareData::UserShareError KSambaSharePrivate::guestsAllowed(const KSambaShareData::GuestPermission &guestok) const { if (guestok == KSambaShareData::GuestsAllowed) { if (KSambaSharePrivate::testparmParamValue(QStringLiteral("usershare allow guests")) == QLatin1String("No")) { return KSambaShareData::UserShareGuestsNotAllowed; } } return KSambaShareData::UserShareGuestsOk; } KSambaShareData::UserShareError KSambaSharePrivate::add(const KSambaShareData &shareData) { // TODO: // * check for usershare max shares if (!isSambaInstalled()) { return KSambaShareData::UserShareSystemError; } QStringList args; QByteArray stdOut; QByteArray stdErr; if (data.contains(shareData.name())) { if (data.value(shareData.name()).path() != shareData.path()) { return KSambaShareData::UserShareNameInUse; } } else { // It needs to be added here, otherwise another instance of KSambaShareDataPrivate // will be created and added to data. data.insert(shareData.name(), shareData); } QString guestok = QStringLiteral("guest_ok=%1").arg( (shareData.guestPermission() == KSambaShareData::GuestsNotAllowed) ? QStringLiteral("n") : QStringLiteral("y")); args << QStringLiteral("usershare") << QStringLiteral("add") << shareData.name() << shareData.path() << shareData.comment() << shareData.acl() << guestok; int ret = runProcess(QStringLiteral("net"), args, stdOut, stdErr); //TODO: parse and process error messages. if (!stdErr.isEmpty()) { // create a parser for the error output and // send error message somewhere qCWarning(KIO_CORE) << "We got some errors while running 'net usershare add'" << args; qCWarning(KIO_CORE) << stdErr; } return (ret == 0) ? KSambaShareData::UserShareOk : KSambaShareData::UserShareSystemError; } KSambaShareData::UserShareError KSambaSharePrivate::remove(const KSambaShareData &shareData) const { if (!isSambaInstalled()) { return KSambaShareData::UserShareSystemError; } QStringList args; if (!data.contains(shareData.name())) { return KSambaShareData::UserShareNameInvalid; } args << QStringLiteral("usershare") << QStringLiteral("delete") << shareData.name(); int result = QProcess::execute(QStringLiteral("net"), args); return (result == 0) ? KSambaShareData::UserShareOk : KSambaShareData::UserShareSystemError; } QMap KSambaSharePrivate::parse(const QByteArray &usershareData) { const QRegExp headerRx(QString::fromLatin1("^\\s*\\[" "([^%<>*\?|/\\+=;:\",]+)" "\\]")); const QRegExp OptValRx(QString::fromLatin1("^\\s*([\\w\\d\\s]+)" "=" "(.*)$")); QTextStream stream(usershareData); QString currentShare; QMap shares; while (!stream.atEnd()) { const QString line = stream.readLine().trimmed(); if (headerRx.exactMatch(line)) { currentShare = headerRx.cap(1).trimmed(); if (!shares.contains(currentShare)) { KSambaShareData shareData; shareData.dd->name = currentShare; shares.insert(currentShare, shareData); } } else if (OptValRx.exactMatch(line)) { const QString key = OptValRx.cap(1).trimmed(); const QString value = OptValRx.cap(2).trimmed(); KSambaShareData shareData = shares[currentShare]; if (key == QLatin1String("path")) { // Samba accepts paths with and w/o trailing slash, we // use and expect path without slash if (value.endsWith(QLatin1Char('/'))) { shareData.dd->path = value.left(value.size() - 1); } else { shareData.dd->path = value; } } else if (key == QLatin1String("comment")) { shareData.dd->comment = value; } else if (key == QLatin1String("usershare_acl")) { shareData.dd->acl = value; } else if (key == QLatin1String("guest_ok")) { shareData.dd->guestPermission = value; } else { qCWarning(KIO_CORE) << "Something nasty happen while parsing 'net usershare info'" << "share:" << currentShare << "key:" << key; } } else if (line.trimmed().isEmpty()) { continue; } else { return shares; } } return shares; } void KSambaSharePrivate::_k_slotFileChange(const QString &path) { if (path != userSharePath) { return; } data = parse(getNetUserShareInfo()); //qDebug() << "path changed:" << path; Q_Q(KSambaShare); emit q->changed(); } KSambaShare::KSambaShare() : QObject(nullptr) , d_ptr(new KSambaSharePrivate(this)) { Q_D(const KSambaShare); if (!d->userSharePath.isEmpty() && QFileInfo::exists(d->userSharePath)) { KDirWatch::self()->addDir(d->userSharePath, KDirWatch::WatchFiles); connect(KDirWatch::self(), SIGNAL(dirty(QString)), this, SLOT(_k_slotFileChange(QString))); } } KSambaShare::~KSambaShare() { Q_D(const KSambaShare); if (KDirWatch::exists() && KDirWatch::self()->contains(d->userSharePath)) { KDirWatch::self()->removeDir(d->userSharePath); } delete d_ptr; } #ifndef KIOCORE_NO_DEPRECATED QString KSambaShare::smbConfPath() const { Q_D(const KSambaShare); return d->smbConf; } #endif bool KSambaShare::isDirectoryShared(const QString &path) const { Q_D(const KSambaShare); return d->isDirectoryShared(path); } bool KSambaShare::isShareNameAvailable(const QString &name) const { Q_D(const KSambaShare); return d->isShareNameValid(name) && d->isShareNameAvailable(name); } QStringList KSambaShare::shareNames() const { Q_D(const KSambaShare); return d->shareNames(); } QStringList KSambaShare::sharedDirectories() const { Q_D(const KSambaShare); return d->sharedDirs(); } KSambaShareData KSambaShare::getShareByName(const QString &name) const { Q_D(const KSambaShare); return d->getShareByName(name); } QList KSambaShare::getSharesByPath(const QString &path) const { Q_D(const KSambaShare); return d->getSharesByPath(path); } class KSambaShareSingleton { public: KSambaShare instance; }; Q_GLOBAL_STATIC(KSambaShareSingleton, _instance) KSambaShare *KSambaShare::instance() { return &_instance()->instance; } #include "moc_ksambashare.cpp" diff --git a/src/core/kssl/ksslsettings.cpp b/src/core/kssl/ksslsettings.cpp index 6adb8e48..f5ad0605 100644 --- a/src/core/kssl/ksslsettings.cpp +++ b/src/core/kssl/ksslsettings.cpp @@ -1,244 +1,244 @@ /* This file is part of the KDE project * * Copyright (C) 2000 George Staikos * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ksslsettings.h" #include #include #include class CipherNode { public: CipherNode(const char *_name, int _keylen) : - name(_name), keylen(_keylen) {} + name(QString::fromUtf8(_name)), keylen(_keylen) {} QString name; int keylen; inline int operator==(CipherNode &x) { return ((x.keylen == keylen) && (x.name == name)); } inline int operator< (CipherNode &x) { return keylen < x.keylen; } inline int operator<=(CipherNode &x) { return keylen <= x.keylen; } inline int operator> (CipherNode &x) { return keylen > x.keylen; } inline int operator>=(CipherNode &x) { return keylen >= x.keylen; } }; class KSSLSettingsPrivate { public: KSSLSettingsPrivate() { } ~KSSLSettingsPrivate() { } bool m_bUseEGD; bool m_bUseEFile; QString m_EGDPath; bool m_bSendX509; bool m_bPromptX509; KConfig *m_cfg; bool m_bWarnOnEnter, m_bWarnOnUnencrypted, m_bWarnOnLeave, m_bWarnOnMixed; bool m_bWarnSelfSigned, m_bWarnRevoked, m_bWarnExpired; QStringList m_v3ciphers; QStringList m_v3selectedciphers; QList m_v3bits; }; // // FIXME // Implementation note: for now, we only read cipher settings from disk, // and do not store them in memory. This should change. // KSSLSettings::KSSLSettings(bool readConfig) : d(new KSSLSettingsPrivate) { d->m_cfg = new KConfig(QStringLiteral("cryptodefaults"), KConfig::NoGlobals); if (readConfig) { load(); } } // we don't save settings incase it was a temporary object KSSLSettings::~KSSLSettings() { delete d->m_cfg; delete d; } QString KSSLSettings::getCipherList() { QString clist; // TODO fill in list here (or just remove this method!) return clist; } // FIXME - sync these up so that we can use them with the control module!! void KSSLSettings::load() { d->m_cfg->reparseConfiguration(); KConfigGroup cfg(d->m_cfg, "Warnings"); d->m_bWarnOnEnter = cfg.readEntry("OnEnter", false); d->m_bWarnOnLeave = cfg.readEntry("OnLeave", true); d->m_bWarnOnUnencrypted = cfg.readEntry("OnUnencrypted", false); d->m_bWarnOnMixed = cfg.readEntry("OnMixed", true); cfg = KConfigGroup(d->m_cfg, "Validation"); d->m_bWarnSelfSigned = cfg.readEntry("WarnSelfSigned", true); d->m_bWarnExpired = cfg.readEntry("WarnExpired", true); d->m_bWarnRevoked = cfg.readEntry("WarnRevoked", true); cfg = KConfigGroup(d->m_cfg, "EGD"); d->m_bUseEGD = cfg.readEntry("UseEGD", false); d->m_bUseEFile = cfg.readEntry("UseEFile", false); d->m_EGDPath = cfg.readPathEntry("EGDPath", QString()); cfg = KConfigGroup(d->m_cfg, "Auth"); - d->m_bSendX509 = ("send" == cfg.readEntry("AuthMethod", "")); - d->m_bPromptX509 = ("prompt" == cfg.readEntry("AuthMethod", "")); + d->m_bSendX509 = (QLatin1String("send") == cfg.readEntry("AuthMethod", "")); + d->m_bPromptX509 = (QLatin1String("prompt") == cfg.readEntry("AuthMethod", "")); } void KSSLSettings::defaults() { d->m_bWarnOnEnter = false; d->m_bWarnOnLeave = true; d->m_bWarnOnUnencrypted = true; d->m_bWarnOnMixed = true; d->m_bWarnSelfSigned = true; d->m_bWarnExpired = true; d->m_bWarnRevoked = true; d->m_bUseEGD = false; d->m_bUseEFile = false; d->m_EGDPath = QLatin1String(""); } void KSSLSettings::save() { KConfigGroup cfg(d->m_cfg, "Warnings"); cfg.writeEntry("OnEnter", d->m_bWarnOnEnter); cfg.writeEntry("OnLeave", d->m_bWarnOnLeave); cfg.writeEntry("OnUnencrypted", d->m_bWarnOnUnencrypted); cfg.writeEntry("OnMixed", d->m_bWarnOnMixed); cfg = KConfigGroup(d->m_cfg, "Validation"); cfg.writeEntry("WarnSelfSigned", d->m_bWarnSelfSigned); cfg.writeEntry("WarnExpired", d->m_bWarnExpired); cfg.writeEntry("WarnRevoked", d->m_bWarnRevoked); cfg = KConfigGroup(d->m_cfg, "EGD"); cfg.writeEntry("UseEGD", d->m_bUseEGD); cfg.writeEntry("UseEFile", d->m_bUseEFile); cfg.writePathEntry("EGDPath", d->m_EGDPath); d->m_cfg->sync(); // FIXME - ciphers #if 0 #if KSSL_HAVE_SSL cfg.setGroup("SSLv3"); for (unsigned int i = 0; i < v3ciphers.count(); i++) { QString ciphername; ciphername.sprintf("cipher_%s", v3ciphers[i].ascii()); if (v3selectedciphers.contains(v3ciphers[i])) { cfg.writeEntry(ciphername, true); } else { cfg.writeEntry(ciphername, false); } } d->m_cfg->sync(); #endif // insure proper permissions -- contains sensitive data QString cfgName(QStandardPaths::locate(QStandardPaths::GenericConfigLocation, "cryptodefaults")); if (!cfgName.isEmpty()) { KDE::chmod(cfgName, 0600); } #endif } bool KSSLSettings::warnOnEnter() const { return d->m_bWarnOnEnter; } void KSSLSettings::setWarnOnEnter(bool x) { d->m_bWarnOnEnter = x; } bool KSSLSettings::warnOnUnencrypted() const { return d->m_bWarnOnUnencrypted; } void KSSLSettings::setWarnOnUnencrypted(bool x) { d->m_bWarnOnUnencrypted = x; } bool KSSLSettings::warnOnLeave() const { return d->m_bWarnOnLeave; } void KSSLSettings::setWarnOnLeave(bool x) { d->m_bWarnOnLeave = x; } bool KSSLSettings::warnOnMixed() const { return d->m_bWarnOnMixed; } bool KSSLSettings::useEGD() const { return d->m_bUseEGD; } bool KSSLSettings::useEFile() const { return d->m_bUseEFile; } bool KSSLSettings::autoSendX509() const { return d->m_bSendX509; } bool KSSLSettings::promptSendX509() const { return d->m_bPromptX509; } QString &KSSLSettings::getEGDPath() { return d->m_EGDPath; } diff --git a/src/core/ksslcertificatemanager.cpp b/src/core/ksslcertificatemanager.cpp index d374b41c..ee2cafb8 100644 --- a/src/core/ksslcertificatemanager.cpp +++ b/src/core/ksslcertificatemanager.cpp @@ -1,498 +1,498 @@ /* This file is part of the KDE project * * Copyright (C) 2007, 2008, 2010 Andreas Hartmetz * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ksslcertificatemanager.h" #include "ksslcertificatemanager_p.h" #include "ktcpsocket.h" #include "ktcpsocket_p.h" #include #include #include #include #include #include #include #include #include #include #include "kssld_interface.h" /* Config file format: [] = #for example #mail.kdab.net = ExpireUTC 2008-08-20T18:22:14, SelfSigned, Expired #very.old.com = ExpireUTC 2008-08-20T18:22:14, TooWeakEncryption <- not actually planned to implement #clueless.admin.com = ExpireUTC 2008-08-20T18:22:14, HostNameMismatch # #Wildcard syntax #* = ExpireUTC 2008-08-20T18:22:14, SelfSigned #*.kdab.net = ExpireUTC 2008-08-20T18:22:14, SelfSigned #mail.kdab.net = ExpireUTC 2008-08-20T18:22:14, All <- not implemented #* = ExpireUTC 9999-12-31T23:59:59, Reject #we know that something is wrong with that certificate CertificatePEM = #host entries are all lowercase, thus no clashes */ // TODO GUI for managing exception rules class KSslCertificateRulePrivate { public: QSslCertificate certificate; QString hostName; bool isRejected; QDateTime expiryDateTime; QList ignoredErrors; }; KSslCertificateRule::KSslCertificateRule(const QSslCertificate &cert, const QString &hostName) : d(new KSslCertificateRulePrivate()) { d->certificate = cert; d->hostName = hostName; d->isRejected = false; } KSslCertificateRule::KSslCertificateRule(const KSslCertificateRule &other) : d(new KSslCertificateRulePrivate()) { *d = *other.d; } KSslCertificateRule::~KSslCertificateRule() { delete d; } KSslCertificateRule &KSslCertificateRule::operator=(const KSslCertificateRule &other) { *d = *other.d; return *this; } QSslCertificate KSslCertificateRule::certificate() const { return d->certificate; } QString KSslCertificateRule::hostName() const { return d->hostName; } void KSslCertificateRule::setExpiryDateTime(const QDateTime &dateTime) { d->expiryDateTime = dateTime; } QDateTime KSslCertificateRule::expiryDateTime() const { return d->expiryDateTime; } void KSslCertificateRule::setRejected(bool rejected) { d->isRejected = rejected; } bool KSslCertificateRule::isRejected() const { return d->isRejected; } bool KSslCertificateRule::isErrorIgnored(KSslError::Error error) const { foreach (KSslError::Error ignoredError, d->ignoredErrors) if (error == ignoredError) { return true; } return false; } void KSslCertificateRule::setIgnoredErrors(const QList &errors) { d->ignoredErrors.clear(); //### Quadratic runtime, woohoo! Use a QSet if that should ever be an issue. foreach (KSslError::Error e, errors) if (!isErrorIgnored(e)) { d->ignoredErrors.append(e); } } void KSslCertificateRule::setIgnoredErrors(const QList &errors) { QList el; foreach (const KSslError &e, errors) { el.append(e.error()); } setIgnoredErrors(el); } QList KSslCertificateRule::ignoredErrors() const { return d->ignoredErrors; } QList KSslCertificateRule::filterErrors(const QList &errors) const { QList ret; foreach (KSslError::Error error, errors) { if (!isErrorIgnored(error)) { ret.append(error); } } return ret; } QList KSslCertificateRule::filterErrors(const QList &errors) const { QList ret; foreach (const KSslError &error, errors) { if (!isErrorIgnored(error.error())) { ret.append(error); } } return ret; } //////////////////////////////////////////////////////////////////// static QList deduplicate(const QList &certs) { QSet digests; QList ret; foreach (const QSslCertificate &cert, certs) { QByteArray digest = cert.digest(); if (!digests.contains(digest)) { digests.insert(digest); ret.append(cert); } } return ret; } KSslCertificateManagerPrivate::KSslCertificateManagerPrivate() : config(QStringLiteral("ksslcertificatemanager"), KConfig::SimpleConfig), iface(new org::kde::KSSLDInterface(QStringLiteral("org.kde.kssld5"), QStringLiteral("/modules/kssld"), QDBusConnection::sessionBus())), isCertListLoaded(false), userCertDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kssl/userCaCertificates/")) { } KSslCertificateManagerPrivate::~KSslCertificateManagerPrivate() { delete iface; iface = nullptr; } void KSslCertificateManagerPrivate::loadDefaultCaCertificates() { defaultCaCertificates.clear(); QList certs = deduplicate(QSslSocket::systemCaCertificates()); KConfig config(QStringLiteral("ksslcablacklist"), KConfig::SimpleConfig); KConfigGroup group = config.group("Blacklist of CA Certificates"); certs.append(QSslCertificate::fromPath(userCertDir + QStringLiteral("*"), QSsl::Pem, QRegExp::Wildcard)); foreach (const QSslCertificate &cert, certs) { const QByteArray digest = cert.digest().toHex(); if (!group.hasKey(digest.constData())) { defaultCaCertificates += cert; } } isCertListLoaded = true; } bool KSslCertificateManagerPrivate::addCertificate(const KSslCaCertificate &in) { //qDebug() << Q_FUNC_INFO; // cannot add a certificate to the system store if (in.store == KSslCaCertificate::SystemStore) { Q_ASSERT(false); return false; } if (knownCerts.contains(in.certHash)) { Q_ASSERT(false); return false; } QString certFilename = userCertDir + QString::fromLatin1(in.certHash); QFile certFile(certFilename); if (!QDir().mkpath(userCertDir) || certFile.open(QIODevice::ReadOnly)) { return false; } if (!certFile.open(QIODevice::WriteOnly)) { return false; } if (certFile.write(in.cert.toPem()) < 1) { return false; } knownCerts.insert(in.certHash); updateCertificateBlacklisted(in); return true; } bool KSslCertificateManagerPrivate::removeCertificate(const KSslCaCertificate &old) { //qDebug() << Q_FUNC_INFO; // cannot remove a certificate from the system store if (old.store == KSslCaCertificate::SystemStore) { Q_ASSERT(false); return false; } if (!QFile::remove(userCertDir + QString::fromLatin1(old.certHash))) { // suppose somebody copied a certificate file into userCertDir without changing the // filename to the digest. // the rest of the code will work fine because it loads all certificate files from // userCertDir without asking for the name, we just can't remove the certificate using // its digest as filename - so search the whole directory. // if the certificate was added with the digest as name *and* with a different name, we // still fail to remove it completely at first try - BAD USER! BAD! bool removed = false; QDir dir(userCertDir); foreach (const QString &certFilename, dir.entryList(QDir::Files)) { const QString certPath = userCertDir + certFilename; QList certs = QSslCertificate::fromPath(certPath); if (!certs.isEmpty() && certs.at(0).digest().toHex() == old.certHash) { if (QFile::remove(certPath)) { removed = true; } else { // maybe the file is readable but not writable return false; } } } if (!removed) { // looks like the file is not there return false; } } // note that knownCerts *should* need no updating due to the way setAllCertificates() works - // it should never call addCertificate and removeCertificate for the same cert in one run // clean up the blacklist setCertificateBlacklisted(old.certHash, false); return true; } static bool certLessThan(const KSslCaCertificate &cacert1, const KSslCaCertificate &cacert2) { if (cacert1.store != cacert2.store) { // SystemStore is numerically smaller so the system certs come first; this is important // so that system certificates come first in case the user added an already-present // certificate as a user certificate. return cacert1.store < cacert2.store; } return cacert1.certHash < cacert2.certHash; } void KSslCertificateManagerPrivate::setAllCertificates(const QList &certsIn) { Q_ASSERT(knownCerts.isEmpty()); QList in = certsIn; QList old = allCertificates(); std::sort(in.begin(), in.end(), certLessThan); std::sort(old.begin(), old.end(), certLessThan); for (int ii = 0, oi = 0; ii < in.size() || oi < old.size(); ++ii, ++oi) { // look at all elements in both lists, even if we reach the end of one early. if (ii >= in.size()) { removeCertificate(old.at(oi)); continue; } else if (oi >= old.size()) { addCertificate(in.at(ii)); continue; } if (certLessThan(old.at(oi), in.at(ii))) { // the certificate in "old" is not in "in". only advance the index of "old". removeCertificate(old.at(oi)); ii--; } else if (certLessThan(in.at(ii), old.at(oi))) { // the certificate in "in" is not in "old". only advance the index of "in". addCertificate(in.at(ii)); oi--; } else { // in.at(ii) "==" old.at(oi) if (in.at(ii).cert != old.at(oi).cert) { // hash collision, be prudent(?) and don't do anything. } else { knownCerts.insert(old.at(oi).certHash); if (in.at(ii).isBlacklisted != old.at(oi).isBlacklisted) { updateCertificateBlacklisted(in.at(ii)); } } } } knownCerts.clear(); QMutexLocker certListLocker(&certListMutex); isCertListLoaded = false; loadDefaultCaCertificates(); } QList KSslCertificateManagerPrivate::allCertificates() const { //qDebug() << Q_FUNC_INFO; QList ret; foreach (const QSslCertificate &cert, deduplicate(QSslSocket::systemCaCertificates())) { ret += KSslCaCertificate(cert, KSslCaCertificate::SystemStore, false); } - foreach (const QSslCertificate &cert, QSslCertificate::fromPath(userCertDir + "*", + foreach (const QSslCertificate &cert, QSslCertificate::fromPath(userCertDir + QLatin1Char('*'), QSsl::Pem, QRegExp::Wildcard)) { ret += KSslCaCertificate(cert, KSslCaCertificate::UserStore, false); } KConfig config(QStringLiteral("ksslcablacklist"), KConfig::SimpleConfig); KConfigGroup group = config.group("Blacklist of CA Certificates"); for (int i = 0; i < ret.size(); i++) { if (group.hasKey(ret[i].certHash.constData())) { ret[i].isBlacklisted = true; //qDebug() << "is blacklisted"; } } return ret; } bool KSslCertificateManagerPrivate::updateCertificateBlacklisted(const KSslCaCertificate &cert) { return setCertificateBlacklisted(cert.certHash, cert.isBlacklisted); } bool KSslCertificateManagerPrivate::setCertificateBlacklisted(const QByteArray &certHash, bool isBlacklisted) { //qDebug() << Q_FUNC_INFO << isBlacklisted; KConfig config(QStringLiteral("ksslcablacklist"), KConfig::SimpleConfig); KConfigGroup group = config.group("Blacklist of CA Certificates"); if (isBlacklisted) { // TODO check against certificate list ? group.writeEntry(certHash.constData(), QString()); } else { if (!group.hasKey(certHash.constData())) { return false; } group.deleteEntry(certHash.constData()); } return true; } class KSslCertificateManagerContainer { public: KSslCertificateManager sslCertificateManager; }; Q_GLOBAL_STATIC(KSslCertificateManagerContainer, g_instance) KSslCertificateManager::KSslCertificateManager() : d(new KSslCertificateManagerPrivate()) { } KSslCertificateManager::~KSslCertificateManager() { delete d; } //static KSslCertificateManager *KSslCertificateManager::self() { return &g_instance()->sslCertificateManager; } void KSslCertificateManager::setRule(const KSslCertificateRule &rule) { d->iface->setRule(rule); } void KSslCertificateManager::clearRule(const KSslCertificateRule &rule) { d->iface->clearRule(rule); } void KSslCertificateManager::clearRule(const QSslCertificate &cert, const QString &hostName) { d->iface->clearRule(cert, hostName); } KSslCertificateRule KSslCertificateManager::rule(const QSslCertificate &cert, const QString &hostName) const { return d->iface->rule(cert, hostName); } QList KSslCertificateManager::caCertificates() const { QMutexLocker certLocker(&d->certListMutex); if (!d->isCertListLoaded) { d->loadDefaultCaCertificates(); } return d->defaultCaCertificates; } //static QList KSslCertificateManager::nonIgnorableErrors(const QList &/*e*/) { QList ret; // ### add filtering here... return ret; } //static QList KSslCertificateManager::nonIgnorableErrors(const QList &/*e*/) { QList ret; // ### add filtering here... return ret; } QList _allKsslCaCertificates(KSslCertificateManager *cm) { return KSslCertificateManagerPrivate::get(cm)->allCertificates(); } void _setAllKsslCaCertificates(KSslCertificateManager *cm, const QList &certsIn) { KSslCertificateManagerPrivate::get(cm)->setAllCertificates(certsIn); } #include "moc_kssld_interface.cpp" diff --git a/src/core/listjob.cpp b/src/core/listjob.cpp index e738b99b..346c081c 100644 --- a/src/core/listjob.cpp +++ b/src/core/listjob.cpp @@ -1,304 +1,304 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 2000-2009 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. */ #include "listjob.h" #include "job_p.h" #include "scheduler.h" #include #include "slave.h" #include "../pathhelpers_p.h" #include using namespace KIO; class KIO::ListJobPrivate: public KIO::SimpleJobPrivate { public: ListJobPrivate(const QUrl &url, bool _recursive, const QString &prefix, const QString &displayPrefix, bool _includeHidden) : SimpleJobPrivate(url, CMD_LISTDIR, QByteArray()), recursive(_recursive), includeHidden(_includeHidden), m_prefix(prefix), m_displayPrefix(displayPrefix), m_processedEntries(0) {} bool recursive; bool includeHidden; QString m_prefix; QString m_displayPrefix; unsigned long m_processedEntries; QUrl m_redirectionURL; /** * @internal * Called by the scheduler when a @p slave gets to * work on this job. * @param slave the slave that starts working on this job */ void start(Slave *slave) override; void slotListEntries(const KIO::UDSEntryList &list); void slotRedirection(const QUrl &url); void gotEntries(KIO::Job *subjob, const KIO::UDSEntryList &list); void slotSubError(ListJob* job, ListJob* subJob); Q_DECLARE_PUBLIC(ListJob) static inline ListJob *newJob(const QUrl &u, bool _recursive, const QString &prefix, const QString &displayPrefix, bool _includeHidden, JobFlags flags = HideProgressInfo) { ListJob *job = new ListJob(*new ListJobPrivate(u, _recursive, prefix, displayPrefix, _includeHidden)); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); if (!(flags & HideProgressInfo)) { KIO::getJobTracker()->registerJob(job); } return job; } static inline ListJob *newJobNoUi(const QUrl &u, bool _recursive, const QString &prefix, const QString &displayPrefix, bool _includeHidden) { return new ListJob(*new ListJobPrivate(u, _recursive, prefix, displayPrefix, _includeHidden)); } }; ListJob::ListJob(ListJobPrivate &dd) : SimpleJob(dd) { Q_D(ListJob); // We couldn't set the args when calling the parent constructor, // so do it now. QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly); stream << d->m_url; } ListJob::~ListJob() { } void ListJobPrivate::slotListEntries(const KIO::UDSEntryList &list) { Q_Q(ListJob); // Emit progress info (takes care of emit processedSize and percent) m_processedEntries += list.count(); slotProcessedSize(m_processedEntries); if (recursive) { UDSEntryList::ConstIterator it = list.begin(); const UDSEntryList::ConstIterator end = list.end(); for (; it != end; ++it) { const UDSEntry &entry = *it; QUrl itemURL; const QString udsUrl = entry.stringValue(KIO::UDSEntry::UDS_URL); QString filename; if (!udsUrl.isEmpty()) { itemURL = QUrl(udsUrl); filename = itemURL.fileName(); } else { // no URL, use the name itemURL = q->url(); filename = entry.stringValue(KIO::UDSEntry::UDS_NAME); Q_ASSERT(!filename.isEmpty()); // we'll recurse forever otherwise :) itemURL.setPath(concatPaths(itemURL.path(), filename)); } if (entry.isDir() && !entry.isLink()) { Q_ASSERT(!filename.isEmpty()); QString displayName = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME); if (displayName.isEmpty()) { displayName = filename; } // skip hidden dirs when listing if requested - if (filename != QLatin1String("..") && filename != QLatin1String(".") && (includeHidden || filename[0] != '.')) { + if (filename != QLatin1String("..") && filename != QLatin1String(".") && (includeHidden || filename[0] != QLatin1Char('.'))) { ListJob *job = ListJobPrivate::newJobNoUi(itemURL, true /*recursive*/, - m_prefix + filename + '/', - m_displayPrefix + displayName + '/', + m_prefix + filename + QLatin1Char('/'), + m_displayPrefix + displayName + QLatin1Char('/'), includeHidden); Scheduler::setJobPriority(job, 1); QObject::connect(job, &ListJob::entries, q, [this](KIO::Job *job, const KIO::UDSEntryList &list) {gotEntries(job, list);} ); QObject::connect(job, &ListJob::subError, q, [this](KIO::ListJob *job, KIO::ListJob *ljob) {slotSubError(job, ljob);} ); q->addSubjob(job); } } } } // Not recursive, or top-level of recursive listing : return now (send . and .. as well) // exclusion of hidden files also requires the full sweep, but the case for full-listing // a single dir is probably common enough to justify the shortcut if (m_prefix.isNull() && includeHidden) { emit q->entries(q, list); } else { // cull the unwanted hidden dirs and/or parent dir references from the listing, then emit that UDSEntryList newlist; UDSEntryList::const_iterator it = list.begin(); const UDSEntryList::const_iterator end = list.end(); for (; it != end; ++it) { // Modify the name in the UDSEntry UDSEntry newone = *it; const QString filename = newone.stringValue(KIO::UDSEntry::UDS_NAME); QString displayName = newone.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME); if (displayName.isEmpty()) { displayName = filename; } // Avoid returning entries like subdir/. and subdir/.., but include . and .. for // the toplevel dir, and skip hidden files/dirs if that was requested if ((m_prefix.isNull() || (filename != QLatin1String("..") && filename != QLatin1String("."))) - && (includeHidden || (filename[0] != '.'))) { + && (includeHidden || (filename[0] != QLatin1Char('.')))) { // ## Didn't find a way to use the iterator instead of re-doing a key lookup newone.replace(KIO::UDSEntry::UDS_NAME, m_prefix + filename); newone.replace(KIO::UDSEntry::UDS_DISPLAY_NAME, m_displayPrefix + displayName); newlist.append(newone); } } emit q->entries(q, newlist); } } void ListJobPrivate::gotEntries(KIO::Job *, const KIO::UDSEntryList &list) { // Forward entries received by subjob - faking we received them ourselves Q_Q(ListJob); emit q->entries(q, list); } void ListJobPrivate::slotSubError(KIO::ListJob* /*job*/, KIO::ListJob* subJob) { Q_Q(ListJob); emit q->subError(q, subJob); // Let the signal of subError go up } void ListJob::slotResult(KJob *job) { Q_D(ListJob); if (job->error()) { // If we can't list a subdir, the result is still ok // This is why we override KCompositeJob::slotResult - to not set // an error on parent job. // Let's emit a signal about this though emit subError(this, static_cast(job)); } removeSubjob(job); if (!hasSubjobs() && !d->m_slave) { // if the main directory listing is still running, it will emit result in SimpleJob::slotFinished() emitResult(); } } void ListJobPrivate::slotRedirection(const QUrl &url) { Q_Q(ListJob); if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("redirect"), m_url, url)) { qCWarning(KIO_CORE) << "Redirection from" << m_url << "to" << url << "REJECTED!"; return; } m_redirectionURL = url; // We'll remember that when the job finishes emit q->redirection(q, m_redirectionURL); } void ListJob::slotFinished() { Q_D(ListJob); if (!d->m_redirectionURL.isEmpty() && d->m_redirectionURL.isValid() && !error()) { //qDebug() << "Redirection to " << d->m_redirectionURL; if (queryMetaData(QStringLiteral("permanent-redirect")) == QLatin1String("true")) { emit permanentRedirection(this, d->m_url, d->m_redirectionURL); } if (d->m_redirectionHandlingEnabled) { d->m_packedArgs.truncate(0); QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly); stream << d->m_redirectionURL; d->restartAfterRedirection(&d->m_redirectionURL); return; } } // Return slave to the scheduler SimpleJob::slotFinished(); } void ListJob::slotMetaData(const KIO::MetaData &_metaData) { Q_D(ListJob); SimpleJob::slotMetaData(_metaData); storeSSLSessionFromJob(d->m_redirectionURL); } ListJob *KIO::listDir(const QUrl &url, JobFlags flags, bool includeHidden) { return ListJobPrivate::newJob(url, false, QString(), QString(), includeHidden, flags); } ListJob *KIO::listRecursive(const QUrl &url, JobFlags flags, bool includeHidden) { return ListJobPrivate::newJob(url, true, QString(), QString(), includeHidden, flags); } void ListJob::setUnrestricted(bool unrestricted) { Q_D(ListJob); if (unrestricted) { d->m_extraFlags |= JobPrivate::EF_ListJobUnrestricted; } else { d->m_extraFlags &= ~JobPrivate::EF_ListJobUnrestricted; } } void ListJobPrivate::start(Slave *slave) { Q_Q(ListJob); if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("list"), m_url, m_url) && !(m_extraFlags & EF_ListJobUnrestricted)) { q->setError(ERR_ACCESS_DENIED); q->setErrorText(m_url.toDisplayString()); QTimer::singleShot(0, q, SLOT(slotFinished())); return; } QObject::connect(slave, &Slave::listEntries, q, [this](const KIO::UDSEntryList &list){ slotListEntries(list);} ); QObject::connect(slave, &Slave::totalSize, q, [this](KIO::filesize_t size){ slotTotalSize(size);} ); QObject::connect(slave, &Slave::redirection, q, [this](const QUrl &url){ slotRedirection(url);} ); SimpleJobPrivate::start(slave); } const QUrl &ListJob::redirectionUrl() const { return d_func()->m_redirectionURL; } #include "moc_listjob.cpp" diff --git a/src/core/mkpathjob.cpp b/src/core/mkpathjob.cpp index c710694a..500915d6 100644 --- a/src/core/mkpathjob.cpp +++ b/src/core/mkpathjob.cpp @@ -1,159 +1,159 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 2000-2009 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. */ #include "mkpathjob.h" #include "job_p.h" #include "mkdirjob.h" #include "../pathhelpers_p.h" #include #include #include #include using namespace KIO; class KIO::MkpathJobPrivate : public KIO::JobPrivate { public: MkpathJobPrivate(const QUrl &url, const QUrl &baseUrl, JobFlags flags) : JobPrivate(), m_url(url), m_pathComponents(url.path().split(QLatin1Char('/'), QString::SkipEmptyParts)), m_pathIterator(), m_flags(flags) { - const QStringList basePathComponents = baseUrl.path().split('/', QString::SkipEmptyParts); + const QStringList basePathComponents = baseUrl.path().split(QLatin1Char('/'), QString::SkipEmptyParts); m_url.setPath(QStringLiteral("/")); int i = 0; for (; i < basePathComponents.count() && i < m_pathComponents.count(); ++i) { const QString pathComponent = m_pathComponents.at(i); if (pathComponent == basePathComponents.at(i)) { m_url.setPath(concatPaths(m_url.path(), pathComponent)); } else { break; } } if (i > 0) { m_pathComponents.erase(m_pathComponents.begin(), m_pathComponents.begin() + i); } // fast path for local files using QFileInfo::isDir if (m_url.isLocalFile()) { i = 0; for (; i < m_pathComponents.count(); ++i) { const QString localFile = m_url.toLocalFile(); QString testDir; if (localFile == QLatin1String("/")) { testDir = localFile + m_pathComponents.at(i); } else { - testDir = localFile + '/' + m_pathComponents.at(i); + testDir = localFile + QLatin1Char('/') + m_pathComponents.at(i); } if (QFileInfo(testDir).isDir()) { m_url.setPath(testDir); } else { break; } } if (i > 0) { m_pathComponents.erase(m_pathComponents.begin(), m_pathComponents.begin() + i); } } m_pathIterator = m_pathComponents.constBegin(); } QUrl m_url; QUrl m_baseUrl; QStringList m_pathComponents; QStringList::const_iterator m_pathIterator; const JobFlags m_flags; Q_DECLARE_PUBLIC(MkpathJob) void slotStart(); static inline MkpathJob *newJob(const QUrl &url, const QUrl &baseUrl, JobFlags flags) { MkpathJob *job = new MkpathJob(*new MkpathJobPrivate(url, baseUrl, flags)); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); if (!(flags & HideProgressInfo)) { KIO::getJobTracker()->registerJob(job); } if (!(flags & NoPrivilegeExecution)) { job->d_func()->m_privilegeExecutionEnabled = true; job->d_func()->m_operationType = MkDir; } return job; } }; MkpathJob::MkpathJob(MkpathJobPrivate &dd) : Job(dd) { QTimer::singleShot(0, this, SLOT(slotStart())); } MkpathJob::~MkpathJob() { } void MkpathJobPrivate::slotStart() { Q_Q(MkpathJob); if (m_pathIterator == m_pathComponents.constBegin()) { // first time: emit total q->setTotalAmount(KJob::Directories, m_pathComponents.count()); } if (m_pathIterator != m_pathComponents.constEnd()) { m_url.setPath(concatPaths(m_url.path(), *m_pathIterator)); KIO::Job* job = KIO::mkdir(m_url); job->setParentJob(q); q->addSubjob(job); q->setProcessedAmount(KJob::Directories, q->processedAmount(KJob::Directories) + 1); } else { q->emitResult(); } } void MkpathJob::slotResult(KJob *job) { Q_D(MkpathJob); if (job->error() && job->error() != KIO::ERR_DIR_ALREADY_EXIST) { KIO::Job::slotResult(job); // will set the error and emit result(this) return; } removeSubjob(job); emit directoryCreated(d->m_url); // Move on to next one ++d->m_pathIterator; emitPercent(d->m_pathIterator - d->m_pathComponents.constBegin(), d->m_pathComponents.count()); d->slotStart(); } MkpathJob * KIO::mkpath(const QUrl &url, const QUrl &baseUrl, KIO::JobFlags flags) { return MkpathJobPrivate::newJob(url, baseUrl, flags); } #include "moc_mkpathjob.cpp" diff --git a/src/core/scheduler.cpp b/src/core/scheduler.cpp index d0053028..2265ca84 100644 --- a/src/core/scheduler.cpp +++ b/src/core/scheduler.cpp @@ -1,1318 +1,1318 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow Waldo Bastian Copyright (C) 2009, 2010 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License 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 "scheduler.h" #include "scheduler_p.h" #include "sessiondata_p.h" #include "slaveconfig.h" #include "slave.h" #include "connection_p.h" #include "job_p.h" #include #include //#include #include #include #include #include #include #include // Slaves may be idle for a certain time (3 minutes) before they are killed. static const int s_idleSlaveLifetime = 3 * 60; using namespace KIO; static inline Slave *jobSlave(SimpleJob *job) { return SimpleJobPrivate::get(job)->m_slave; } static inline int jobCommand(SimpleJob *job) { return SimpleJobPrivate::get(job)->m_command; } static inline void startJob(SimpleJob *job, Slave *slave) { SimpleJobPrivate::get(job)->start(slave); } // here be uglies // forward declaration to break cross-dependency of SlaveKeeper and SchedulerPrivate static void setupSlave(KIO::Slave *slave, const QUrl &url, const QString &protocol, const QStringList &proxyList, bool newSlave, const KIO::MetaData *config = nullptr); // same reason as above static Scheduler *scheduler(); static Slave *heldSlaveForJob(SimpleJob *job); int SerialPicker::changedPrioritySerial(int oldSerial, int newPriority) const { Q_ASSERT(newPriority >= -10 && newPriority <= 10); newPriority = qBound(-10, newPriority, 10); int unbiasedSerial = oldSerial % m_jobsPerPriority; return unbiasedSerial + newPriority * m_jobsPerPriority; } SlaveKeeper::SlaveKeeper() { m_grimTimer.setSingleShot(true); connect(&m_grimTimer, SIGNAL(timeout()), SLOT(grimReaper())); } SlaveKeeper::~SlaveKeeper() { grimReaper(); } void SlaveKeeper::returnSlave(Slave *slave) { Q_ASSERT(slave); slave->setIdle(); m_idleSlaves.insert(slave->host(), slave); scheduleGrimReaper(); } Slave *SlaveKeeper::takeSlaveForJob(SimpleJob *job) { Slave *slave = heldSlaveForJob(job); if (slave) { return slave; } QUrl url = SimpleJobPrivate::get(job)->m_url; // TODO take port, username and password into account QMultiHash::Iterator it = m_idleSlaves.find(url.host()); if (it == m_idleSlaves.end()) { it = m_idleSlaves.begin(); } if (it == m_idleSlaves.end()) { return nullptr; } slave = it.value(); m_idleSlaves.erase(it); return slave; } bool SlaveKeeper::removeSlave(Slave *slave) { // ### performance not so great QMultiHash::Iterator it = m_idleSlaves.begin(); for (; it != m_idleSlaves.end(); ++it) { if (it.value() == slave) { m_idleSlaves.erase(it); return true; } } return false; } void SlaveKeeper::clear() { m_idleSlaves.clear(); } QList SlaveKeeper::allSlaves() const { return m_idleSlaves.values(); } void SlaveKeeper::scheduleGrimReaper() { if (!m_grimTimer.isActive()) { m_grimTimer.start((s_idleSlaveLifetime / 2) * 1000); } } //private slot void SlaveKeeper::grimReaper() { QMultiHash::Iterator it = m_idleSlaves.begin(); while (it != m_idleSlaves.end()) { Slave *slave = it.value(); if (slave->idleTime() >= s_idleSlaveLifetime) { it = m_idleSlaves.erase(it); if (slave->job()) { //qDebug() << "Idle slave" << slave << "still has job" << slave->job(); } slave->kill(); // avoid invoking slotSlaveDied() because its cleanup services are not needed slave->deref(); } else { ++it; } } if (!m_idleSlaves.isEmpty()) { scheduleGrimReaper(); } } int HostQueue::lowestSerial() const { QMap::ConstIterator first = m_queuedJobs.constBegin(); if (first != m_queuedJobs.constEnd()) { return first.key(); } return SerialPicker::maxSerial; } void HostQueue::queueJob(SimpleJob *job) { const int serial = SimpleJobPrivate::get(job)->m_schedSerial; Q_ASSERT(serial != 0); Q_ASSERT(!m_queuedJobs.contains(serial)); Q_ASSERT(!m_runningJobs.contains(job)); m_queuedJobs.insert(serial, job); } SimpleJob *HostQueue::takeFirstInQueue() { Q_ASSERT(!m_queuedJobs.isEmpty()); QMap::iterator first = m_queuedJobs.begin(); SimpleJob *job = first.value(); m_queuedJobs.erase(first); m_runningJobs.insert(job); return job; } bool HostQueue::removeJob(SimpleJob *job) { const int serial = SimpleJobPrivate::get(job)->m_schedSerial; if (m_runningJobs.remove(job)) { Q_ASSERT(!m_queuedJobs.contains(serial)); return true; } if (m_queuedJobs.remove(serial)) { return true; } return false; } QList HostQueue::allSlaves() const { QList ret; Q_FOREACH (SimpleJob *job, m_runningJobs) { Slave *slave = jobSlave(job); Q_ASSERT(slave); ret.append(slave); } return ret; } ConnectedSlaveQueue::ConnectedSlaveQueue() { m_startJobsTimer.setSingleShot(true); connect(&m_startJobsTimer, SIGNAL(timeout()), SLOT(startRunnableJobs())); } bool ConnectedSlaveQueue::queueJob(SimpleJob *job, Slave *slave) { QHash::Iterator it = m_connectedSlaves.find(slave); if (it == m_connectedSlaves.end()) { return false; } SimpleJobPrivate::get(job)->m_slave = slave; PerSlaveQueue &jobs = it.value(); jobs.waitingList.append(job); if (!jobs.runningJob) { // idle slave now has a job to run m_runnableSlaves.insert(slave); m_startJobsTimer.start(); } return true; } bool ConnectedSlaveQueue::removeJob(SimpleJob *job) { Slave *slave = jobSlave(job); Q_ASSERT(slave); QHash::Iterator it = m_connectedSlaves.find(slave); if (it == m_connectedSlaves.end()) { return false; } PerSlaveQueue &jobs = it.value(); if (jobs.runningJob || jobs.waitingList.isEmpty()) { // a slave that was busy running a job was not runnable. // a slave that has no waiting job(s) was not runnable either. Q_ASSERT(!m_runnableSlaves.contains(slave)); } const bool removedRunning = jobs.runningJob == job; const bool removedWaiting = jobs.waitingList.removeAll(job) != 0; if (removedRunning) { jobs.runningJob = nullptr; Q_ASSERT(!removedWaiting); } const bool removedTheJob = removedRunning || removedWaiting; if (!slave->isAlive()) { removeSlave(slave); return removedTheJob; } if (removedRunning && jobs.waitingList.count()) { m_runnableSlaves.insert(slave); m_startJobsTimer.start(); } if (removedWaiting && jobs.waitingList.isEmpty()) { m_runnableSlaves.remove(slave); } return removedTheJob; } void ConnectedSlaveQueue::addSlave(Slave *slave) { Q_ASSERT(slave); if (!m_connectedSlaves.contains(slave)) { m_connectedSlaves.insert(slave, PerSlaveQueue()); } } bool ConnectedSlaveQueue::removeSlave(Slave *slave) { QHash::Iterator it = m_connectedSlaves.find(slave); if (it == m_connectedSlaves.end()) { return false; } PerSlaveQueue &jobs = it.value(); Q_FOREACH (SimpleJob *job, jobs.waitingList) { // ### for compatibility with the old scheduler we don't touch the running job, if any. // make sure that the job doesn't call back into Scheduler::cancelJob(); this would // a) crash and b) be unnecessary because we clean up just fine. SimpleJobPrivate::get(job)->m_schedSerial = 0; job->kill(); } m_connectedSlaves.erase(it); m_runnableSlaves.remove(slave); slave->kill(); return true; } // KDE5: only one caller, for doubtful reasons. remove this if possible. bool ConnectedSlaveQueue::isIdle(Slave *slave) { QHash::Iterator it = m_connectedSlaves.find(slave); if (it == m_connectedSlaves.end()) { return false; } return it.value().runningJob == nullptr; } //private slot void ConnectedSlaveQueue::startRunnableJobs() { QSet::Iterator it = m_runnableSlaves.begin(); while (it != m_runnableSlaves.end()) { Slave *slave = *it; if (!slave->isConnected()) { // this polling is somewhat inefficient... m_startJobsTimer.start(); ++it; continue; } it = m_runnableSlaves.erase(it); PerSlaveQueue &jobs = m_connectedSlaves[slave]; SimpleJob *job = jobs.waitingList.takeFirst(); Q_ASSERT(!jobs.runningJob); jobs.runningJob = job; const QUrl url = job->url(); // no port is -1 in QUrl, but in kde3 we used 0 and the kioslaves assume that. const int port = url.port() == -1 ? 0 : url.port(); if (slave->host() == QLatin1String("")) { MetaData configData = SlaveConfig::self()->configData(url.scheme(), url.host()); slave->setConfig(configData); slave->setProtocol(url.scheme()); slave->setHost(url.host(), port, url.userName(), url.password()); } Q_ASSERT(slave->protocol() == url.scheme()); Q_ASSERT(slave->host() == url.host()); Q_ASSERT(slave->port() == port); startJob(job, slave); } } static void ensureNoDuplicates(QMap *queuesBySerial) { Q_UNUSED(queuesBySerial); #ifdef SCHEDULER_DEBUG // a host queue may *never* be in queuesBySerial twice. QSet seen; Q_FOREACH (HostQueue *hq, *queuesBySerial) { Q_ASSERT(!seen.contains(hq)); seen.insert(hq); } #endif } static void verifyRunningJobsCount(QHash *queues, int runningJobsCount) { Q_UNUSED(queues); Q_UNUSED(runningJobsCount); #ifdef SCHEDULER_DEBUG int realRunningJobsCount = 0; Q_FOREACH (const HostQueue &hq, *queues) { realRunningJobsCount += hq.runningJobsCount(); } Q_ASSERT(realRunningJobsCount == runningJobsCount); // ...and of course we may never run the same job twice! QSet seenJobs; Q_FOREACH (const HostQueue &hq, *queues) { Q_FOREACH (SimpleJob *job, hq.runningJobs()) { Q_ASSERT(!seenJobs.contains(job)); seenJobs.insert(job); } } #endif } ProtoQueue::ProtoQueue(int maxSlaves, int maxSlavesPerHost) : m_maxConnectionsPerHost(maxSlavesPerHost ? maxSlavesPerHost : maxSlaves), m_maxConnectionsTotal(qMax(maxSlaves, maxSlavesPerHost)), m_runningJobsCount(0) { /*qDebug() << "m_maxConnectionsTotal:" << m_maxConnectionsTotal << "m_maxConnectionsPerHost:" << m_maxConnectionsPerHost;*/ Q_ASSERT(m_maxConnectionsPerHost >= 1); Q_ASSERT(maxSlaves >= maxSlavesPerHost); m_startJobTimer.setSingleShot(true); connect(&m_startJobTimer, SIGNAL(timeout()), SLOT(startAJob())); } ProtoQueue::~ProtoQueue() { // Gather list of all slaves first const QList slaves = allSlaves(); // Clear the idle slaves in the keeper to avoid dangling pointers m_slaveKeeper.clear(); for (Slave *slave : slaves) { // kill the slave process, then remove the interface in our process slave->kill(); slave->deref(); } } void ProtoQueue::queueJob(SimpleJob *job) { QString hostname = SimpleJobPrivate::get(job)->m_url.host(); HostQueue &hq = m_queuesByHostname[hostname]; const int prevLowestSerial = hq.lowestSerial(); Q_ASSERT(hq.runningJobsCount() <= m_maxConnectionsPerHost); // nevert insert a job twice Q_ASSERT(SimpleJobPrivate::get(job)->m_schedSerial == 0); SimpleJobPrivate::get(job)->m_schedSerial = m_serialPicker.next(); const bool wasQueueEmpty = hq.isQueueEmpty(); hq.queueJob(job); // note that HostQueue::queueJob() into an empty queue changes its lowestSerial() too... // the queue's lowest serial job may have changed, so update the ordered list of queues. // however, we ignore all jobs that would cause more connections to a host than allowed. if (prevLowestSerial != hq.lowestSerial()) { if (hq.runningJobsCount() < m_maxConnectionsPerHost) { // if the connection limit didn't keep the HQ unscheduled it must have been lack of jobs if (m_queuesBySerial.remove(prevLowestSerial) == 0) { Q_UNUSED(wasQueueEmpty); Q_ASSERT(wasQueueEmpty); } m_queuesBySerial.insert(hq.lowestSerial(), &hq); } else { #ifdef SCHEDULER_DEBUG // ### this assertion may fail if the limits were modified at runtime! // if the per-host connection limit is already reached the host queue's lowest serial // should not be queued. Q_ASSERT(!m_queuesBySerial.contains(prevLowestSerial)); #endif } } // just in case; startAJob() will refuse to start a job if it shouldn't. m_startJobTimer.start(); ensureNoDuplicates(&m_queuesBySerial); } void ProtoQueue::changeJobPriority(SimpleJob *job, int newPrio) { SimpleJobPrivate *jobPriv = SimpleJobPrivate::get(job); QHash::Iterator it = m_queuesByHostname.find(jobPriv->m_url.host()); if (it == m_queuesByHostname.end()) { return; } HostQueue &hq = it.value(); const int prevLowestSerial = hq.lowestSerial(); if (hq.isJobRunning(job) || !hq.removeJob(job)) { return; } jobPriv->m_schedSerial = m_serialPicker.changedPrioritySerial(jobPriv->m_schedSerial, newPrio); hq.queueJob(job); const bool needReinsert = hq.lowestSerial() != prevLowestSerial; // the host queue might be absent from m_queuesBySerial because the connections per host limit // for that host has been reached. if (needReinsert && m_queuesBySerial.remove(prevLowestSerial)) { m_queuesBySerial.insert(hq.lowestSerial(), &hq); } ensureNoDuplicates(&m_queuesBySerial); } void ProtoQueue::removeJob(SimpleJob *job) { SimpleJobPrivate *jobPriv = SimpleJobPrivate::get(job); HostQueue &hq = m_queuesByHostname[jobPriv->m_url.host()]; const int prevLowestSerial = hq.lowestSerial(); const int prevRunningJobs = hq.runningJobsCount(); Q_ASSERT(hq.runningJobsCount() <= m_maxConnectionsPerHost); if (hq.removeJob(job)) { if (hq.lowestSerial() != prevLowestSerial) { // we have dequeued the not yet running job with the lowest serial Q_ASSERT(!jobPriv->m_slave); Q_ASSERT(prevRunningJobs == hq.runningJobsCount()); if (m_queuesBySerial.remove(prevLowestSerial) == 0) { // make sure that the queue was not scheduled for a good reason Q_ASSERT(hq.runningJobsCount() == m_maxConnectionsPerHost); } } else { if (prevRunningJobs != hq.runningJobsCount()) { // we have dequeued a previously running job Q_ASSERT(prevRunningJobs - 1 == hq.runningJobsCount()); m_runningJobsCount--; Q_ASSERT(m_runningJobsCount >= 0); } } if (!hq.isQueueEmpty() && hq.runningJobsCount() < m_maxConnectionsPerHost) { // this may be a no-op, but it's faster than first checking if it's already in. m_queuesBySerial.insert(hq.lowestSerial(), &hq); } if (hq.isEmpty()) { // no queued jobs, no running jobs. this destroys hq from above. m_queuesByHostname.remove(jobPriv->m_url.host()); } if (jobPriv->m_slave && jobPriv->m_slave->isAlive()) { m_slaveKeeper.returnSlave(jobPriv->m_slave); } // just in case; startAJob() will refuse to start a job if it shouldn't. m_startJobTimer.start(); } else { // should be a connected slave // if the assertion fails the job has probably changed the host part of its URL while // running, so we can't find it by hostname. don't do this. const bool removed = m_connectedSlaveQueue.removeJob(job); Q_UNUSED(removed); Q_ASSERT(removed); } ensureNoDuplicates(&m_queuesBySerial); } Slave *ProtoQueue::createSlave(const QString &protocol, SimpleJob *job, const QUrl &url) { int error; QString errortext; Slave *slave = Slave::createSlave(protocol, url, error, errortext); if (slave) { scheduler()->connect(slave, SIGNAL(slaveDied(KIO::Slave*)), SLOT(slotSlaveDied(KIO::Slave*))); scheduler()->connect(slave, SIGNAL(slaveStatus(qint64,QByteArray,QString,bool)), SLOT(slotSlaveStatus(qint64,QByteArray,QString,bool))); } else { qCWarning(KIO_CORE) << "couldn't create slave:" << errortext; if (job) { job->slotError(error, errortext); } } return slave; } bool ProtoQueue::removeSlave(KIO::Slave *slave) { const bool removedConnected = m_connectedSlaveQueue.removeSlave(slave); const bool removedUnconnected = m_slaveKeeper.removeSlave(slave); Q_ASSERT(!(removedConnected && removedUnconnected)); return removedConnected || removedUnconnected; } QList ProtoQueue::allSlaves() const { QList ret(m_slaveKeeper.allSlaves()); Q_FOREACH (const HostQueue &hq, m_queuesByHostname) { ret.append(hq.allSlaves()); } ret.append(m_connectedSlaveQueue.allSlaves()); return ret; } //private slot void ProtoQueue::startAJob() { ensureNoDuplicates(&m_queuesBySerial); verifyRunningJobsCount(&m_queuesByHostname, m_runningJobsCount); #ifdef SCHEDULER_DEBUG //qDebug() << "m_runningJobsCount:" << m_runningJobsCount; Q_FOREACH (const HostQueue &hq, m_queuesByHostname) { Q_FOREACH (SimpleJob *job, hq.runningJobs()) { //qDebug() << SimpleJobPrivate::get(job)->m_url; } } #endif if (m_runningJobsCount >= m_maxConnectionsTotal) { #ifdef SCHEDULER_DEBUG //qDebug() << "not starting any jobs because maxConnectionsTotal has been reached."; #endif return; } QMap::iterator first = m_queuesBySerial.begin(); if (first != m_queuesBySerial.end()) { // pick a job and maintain the queue invariant: lower serials first HostQueue *hq = first.value(); const int prevLowestSerial = first.key(); Q_UNUSED(prevLowestSerial); Q_ASSERT(hq->lowestSerial() == prevLowestSerial); // the following assertions should hold due to queueJob(), takeFirstInQueue() and // removeJob() being correct Q_ASSERT(hq->runningJobsCount() < m_maxConnectionsPerHost); SimpleJob *startingJob = hq->takeFirstInQueue(); Q_ASSERT(hq->runningJobsCount() <= m_maxConnectionsPerHost); Q_ASSERT(hq->lowestSerial() != prevLowestSerial); m_queuesBySerial.erase(first); // we've increased hq's runningJobsCount() by calling nexStartingJob() // so we need to check again. if (!hq->isQueueEmpty() && hq->runningJobsCount() < m_maxConnectionsPerHost) { m_queuesBySerial.insert(hq->lowestSerial(), hq); } // always increase m_runningJobsCount because it's correct if there is a slave and if there // is no slave, removeJob() will balance the number again. removeJob() would decrease the // number too much otherwise. // Note that createSlave() can call slotError() on a job which in turn calls removeJob(), // so increase the count here already. m_runningJobsCount++; bool isNewSlave = false; Slave *slave = m_slaveKeeper.takeSlaveForJob(startingJob); SimpleJobPrivate *jobPriv = SimpleJobPrivate::get(startingJob); if (!slave) { isNewSlave = true; slave = createSlave(jobPriv->m_protocol, startingJob, jobPriv->m_url); } if (slave) { jobPriv->m_slave = slave; setupSlave(slave, jobPriv->m_url, jobPriv->m_protocol, jobPriv->m_proxyList, isNewSlave); startJob(startingJob, slave); } else { // dispose of our records about the job and mark the job as unknown // (to prevent crashes later) // note that the job's slotError() can have called removeJob() first, so check that // it's not a ghost job with null serial already. if (jobPriv->m_schedSerial) { removeJob(startingJob); jobPriv->m_schedSerial = 0; } } } else { #ifdef SCHEDULER_DEBUG //qDebug() << "not starting any jobs because there is no queued job."; #endif } if (!m_queuesBySerial.isEmpty()) { m_startJobTimer.start(); } } class KIO::SchedulerPrivate { public: SchedulerPrivate() : q(new Scheduler()), m_slaveOnHold(nullptr), m_checkOnHold(true), // !! Always check with KLauncher for the first request m_ignoreConfigReparse(false) { } ~SchedulerPrivate() { delete q; q = nullptr; Q_FOREACH (ProtoQueue *p, m_protocols) { Q_FOREACH (Slave *slave, p->allSlaves()) { slave->kill(); } } qDeleteAll(m_protocols); } Scheduler *q; Slave *m_slaveOnHold; QUrl m_urlOnHold; bool m_checkOnHold; bool m_ignoreConfigReparse; SessionData sessionData; void doJob(SimpleJob *job); #ifndef KIOCORE_NO_DEPRECATED void scheduleJob(SimpleJob *job); #endif void setJobPriority(SimpleJob *job, int priority); void cancelJob(SimpleJob *job); void jobFinished(KIO::SimpleJob *job, KIO::Slave *slave); void putSlaveOnHold(KIO::SimpleJob *job, const QUrl &url); void removeSlaveOnHold(); Slave *getConnectedSlave(const QUrl &url, const KIO::MetaData &metaData); bool assignJobToSlave(KIO::Slave *slave, KIO::SimpleJob *job); bool disconnectSlave(KIO::Slave *slave); void checkSlaveOnHold(bool b); void publishSlaveOnHold(); Slave *heldSlaveForJob(KIO::SimpleJob *job); bool isSlaveOnHoldFor(const QUrl &url); void updateInternalMetaData(SimpleJob *job); MetaData metaDataFor(const QString &protocol, const QStringList &proxyList, const QUrl &url); void setupSlave(KIO::Slave *slave, const QUrl &url, const QString &protocol, const QStringList &proxyList, bool newSlave, const KIO::MetaData *config = nullptr); void slotSlaveDied(KIO::Slave *slave); void slotSlaveStatus(qint64 pid, const QByteArray &protocol, const QString &host, bool connected); void slotReparseSlaveConfiguration(const QString &, const QDBusMessage &); void slotSlaveOnHoldListChanged(); void slotSlaveConnected(); void slotSlaveError(int error, const QString &errorMsg); ProtoQueue *protoQ(const QString &protocol, const QString &host) { ProtoQueue *pq = m_protocols.value(protocol, nullptr); if (!pq) { //qDebug() << "creating ProtoQueue instance for" << protocol; const int maxSlaves = KProtocolInfo::maxSlaves(protocol); int maxSlavesPerHost = -1; if (!host.isEmpty()) { bool ok = false; const int value = SlaveConfig::self()->configData(protocol, host, QStringLiteral("MaxConnections")).toInt(&ok); if (ok) { maxSlavesPerHost = value; } } if (maxSlavesPerHost == -1) { maxSlavesPerHost = KProtocolInfo::maxSlavesPerHost(protocol); } // Never allow maxSlavesPerHost to exceed maxSlaves. pq = new ProtoQueue(maxSlaves, qMin(maxSlaves, maxSlavesPerHost)); m_protocols.insert(protocol, pq); } return pq; } private: QHash m_protocols; }; static QThreadStorage s_storage; static SchedulerPrivate *schedulerPrivate() { if (!s_storage.hasLocalData()) { s_storage.setLocalData(new SchedulerPrivate); } return s_storage.localData(); } Scheduler *Scheduler::self() { return schedulerPrivate()->q; } SchedulerPrivate *Scheduler::d_func() { return schedulerPrivate(); } //static Scheduler *scheduler() { return schedulerPrivate()->q; } //static Slave *heldSlaveForJob(SimpleJob *job) { return schedulerPrivate()->heldSlaveForJob(job); } Scheduler::Scheduler() { setObjectName(QStringLiteral("scheduler")); const QString dbusPath = QStringLiteral("/KIO/Scheduler"); const QString dbusInterface = QStringLiteral("org.kde.KIO.Scheduler"); QDBusConnection dbus = QDBusConnection::sessionBus(); // Not needed, right? We just want to emit two signals. //dbus.registerObject("/KIO/Scheduler", this, QDBusConnection::ExportScriptableSlots | // QDBusConnection::ExportScriptableSignals); dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("reparseSlaveConfiguration"), this, SLOT(slotReparseSlaveConfiguration(QString,QDBusMessage))); dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("slaveOnHoldListChanged"), this, SLOT(slotSlaveOnHoldListChanged())); } Scheduler::~Scheduler() { } void Scheduler::doJob(SimpleJob *job) { schedulerPrivate()->doJob(job); } #ifndef KIOCORE_NO_DEPRECATED void Scheduler::scheduleJob(SimpleJob *job) { schedulerPrivate()->scheduleJob(job); } #endif void Scheduler::setJobPriority(SimpleJob *job, int priority) { schedulerPrivate()->setJobPriority(job, priority); } void Scheduler::cancelJob(SimpleJob *job) { schedulerPrivate()->cancelJob(job); } void Scheduler::jobFinished(KIO::SimpleJob *job, KIO::Slave *slave) { schedulerPrivate()->jobFinished(job, slave); } void Scheduler::putSlaveOnHold(KIO::SimpleJob *job, const QUrl &url) { schedulerPrivate()->putSlaveOnHold(job, url); } void Scheduler::removeSlaveOnHold() { schedulerPrivate()->removeSlaveOnHold(); } void Scheduler::publishSlaveOnHold() { schedulerPrivate()->publishSlaveOnHold(); } bool Scheduler::isSlaveOnHoldFor(const QUrl &url) { return schedulerPrivate()->isSlaveOnHoldFor(url); } void Scheduler::updateInternalMetaData(SimpleJob *job) { schedulerPrivate()->updateInternalMetaData(job); } KIO::Slave *Scheduler::getConnectedSlave(const QUrl &url, const KIO::MetaData &config) { return schedulerPrivate()->getConnectedSlave(url, config); } bool Scheduler::assignJobToSlave(KIO::Slave *slave, KIO::SimpleJob *job) { return schedulerPrivate()->assignJobToSlave(slave, job); } bool Scheduler::disconnectSlave(KIO::Slave *slave) { return schedulerPrivate()->disconnectSlave(slave); } bool Scheduler::connect(const char *signal, const QObject *receiver, const char *member) { return QObject::connect(self(), signal, receiver, member); } bool Scheduler::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member) { return QObject::connect(sender, signal, receiver, member); } bool Scheduler::disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *member) { return QObject::disconnect(sender, signal, receiver, member); } bool Scheduler::connect(const QObject *sender, const char *signal, const char *member) { return QObject::connect(sender, signal, member); } void Scheduler::checkSlaveOnHold(bool b) { schedulerPrivate()->checkSlaveOnHold(b); } void Scheduler::emitReparseSlaveConfiguration() { // Do it immediately in this process, otherwise we might send a request before reparsing // (e.g. when changing useragent in the plugin) schedulerPrivate()->slotReparseSlaveConfiguration(QString(), QDBusMessage()); schedulerPrivate()->m_ignoreConfigReparse = true; emit self()->reparseSlaveConfiguration(QString()); } void SchedulerPrivate::slotReparseSlaveConfiguration(const QString &proto, const QDBusMessage &) { if (m_ignoreConfigReparse) { //qDebug() << "Ignoring signal sent by myself"; m_ignoreConfigReparse = false; return; } //qDebug() << "proto=" << proto; KProtocolManager::reparseConfiguration(); SlaveConfig::self()->reset(); sessionData.reset(); NetRC::self()->reload(); QHash::ConstIterator it = proto.isEmpty() ? m_protocols.constBegin() : m_protocols.constFind(proto); // not found? if (it == m_protocols.constEnd()) { return; } QHash::ConstIterator endIt = proto.isEmpty() ? m_protocols.constEnd() : it + 1; for (; it != endIt; ++it) { Q_FOREACH (Slave *slave, (*it)->allSlaves()) { slave->send(CMD_REPARSECONFIGURATION); slave->resetHost(); } } } void SchedulerPrivate::slotSlaveOnHoldListChanged() { m_checkOnHold = true; } static bool mayReturnContent(int cmd, const QString &protocol) { if (cmd == CMD_GET) { return true; } if (cmd == CMD_MULTI_GET) { return true; } if (cmd == CMD_SPECIAL && protocol.startsWith(QLatin1String("http"), Qt::CaseInsensitive)) { return true; } return false; } void SchedulerPrivate::doJob(SimpleJob *job) { //qDebug() << job; KIO::SimpleJobPrivate *const jobPriv = SimpleJobPrivate::get(job); jobPriv->m_proxyList.clear(); jobPriv->m_protocol = KProtocolManager::slaveProtocol(job->url(), jobPriv->m_proxyList); if (mayReturnContent(jobCommand(job), jobPriv->m_protocol)) { jobPriv->m_checkOnHold = m_checkOnHold; m_checkOnHold = false; } ProtoQueue *proto = protoQ(jobPriv->m_protocol, job->url().host()); proto->queueJob(job); } #ifndef KIOCORE_NO_DEPRECATED void SchedulerPrivate::scheduleJob(SimpleJob *job) { //qDebug() << job; setJobPriority(job, 1); } #endif void SchedulerPrivate::setJobPriority(SimpleJob *job, int priority) { //qDebug() << job << priority; const QString protocol = SimpleJobPrivate::get(job)->m_protocol; if (!protocol.isEmpty()) { ProtoQueue *proto = protoQ(SimpleJobPrivate::get(job)->m_protocol, job->url().host()); proto->changeJobPriority(job, priority); } } void SchedulerPrivate::cancelJob(SimpleJob *job) { // this method is called all over the place in job.cpp, so just do this check here to avoid // much boilerplate in job code. if (SimpleJobPrivate::get(job)->m_schedSerial == 0) { //qDebug() << "Doing nothing because I don't know job" << job; return; } Slave *slave = jobSlave(job); //qDebug() << job << slave; if (slave) { //qDebug() << "Scheduler: killing slave " << slave->slave_pid(); slave->kill(); } jobFinished(job, slave); } void SchedulerPrivate::jobFinished(SimpleJob *job, Slave *slave) { //qDebug() << job << slave; KIO::SimpleJobPrivate *const jobPriv = SimpleJobPrivate::get(job); // make sure that we knew about the job! Q_ASSERT(jobPriv->m_schedSerial); ProtoQueue *pq = m_protocols.value(jobPriv->m_protocol); if (pq) { pq->removeJob(job); } if (slave) { // If we have internal meta-data, tell existing ioslaves to reload // their configuration. if (jobPriv->m_internalMetaData.count()) { //qDebug() << "Updating ioslaves with new internal metadata information"; ProtoQueue *queue = m_protocols.value(slave->protocol()); if (queue) { QListIterator it(queue->allSlaves()); while (it.hasNext()) { Slave *runningSlave = it.next(); if (slave->host() == runningSlave->host()) { slave->setConfig(metaDataFor(slave->protocol(), jobPriv->m_proxyList, job->url())); /*qDebug() << "Updated configuration of" << slave->protocol() << "ioslave, pid=" << slave->slave_pid();*/ } } } } slave->setJob(nullptr); slave->disconnect(job); } jobPriv->m_schedSerial = 0; // this marks the job as unscheduled again jobPriv->m_slave = nullptr; // Clear the values in the internal metadata container since they have // already been taken care of above... jobPriv->m_internalMetaData.clear(); } // static void setupSlave(KIO::Slave *slave, const QUrl &url, const QString &protocol, const QStringList &proxyList, bool newSlave, const KIO::MetaData *config) { schedulerPrivate()->setupSlave(slave, url, protocol, proxyList, newSlave, config); } MetaData SchedulerPrivate::metaDataFor(const QString &protocol, const QStringList &proxyList, const QUrl &url) { const QString host = url.host(); MetaData configData = SlaveConfig::self()->configData(protocol, host); sessionData.configDataFor(configData, protocol, host); if (proxyList.isEmpty()) { configData.remove(QStringLiteral("UseProxy")); configData.remove(QStringLiteral("ProxyUrls")); } else { configData[QStringLiteral("UseProxy")] = proxyList.first(); configData[QStringLiteral("ProxyUrls")] = proxyList.join(QStringLiteral(",")); } if (configData.contains(QStringLiteral("EnableAutoLogin")) && configData.value(QStringLiteral("EnableAutoLogin")).compare(QLatin1String("true"), Qt::CaseInsensitive) == 0) { NetRC::AutoLogin l; l.login = url.userName(); bool usern = (protocol == QLatin1String("ftp")); if (NetRC::self()->lookup(url, l, usern)) { configData[QStringLiteral("autoLoginUser")] = l.login; configData[QStringLiteral("autoLoginPass")] = l.password; if (usern) { QString macdef; QMap::ConstIterator it = l.macdef.constBegin(); for (; it != l.macdef.constEnd(); ++it) { - macdef += it.key() + '\\' + it.value().join(QStringLiteral("\\")) + '\n'; + macdef += it.key() + QLatin1Char('\\') + it.value().join(QStringLiteral("\\")) + QLatin1Char('\n'); } configData[QStringLiteral("autoLoginMacro")] = macdef; } } } return configData; } void SchedulerPrivate::setupSlave(KIO::Slave *slave, const QUrl &url, const QString &protocol, const QStringList &proxyList, bool newSlave, const KIO::MetaData *config) { int port = url.port(); if (port == -1) { // no port is -1 in QUrl, but in kde3 we used 0 and the kioslaves assume that. port = 0; } const QString host = url.host(); const QString user = url.userName(); const QString passwd = url.password(); if (newSlave || slave->host() != host || slave->port() != port || slave->user() != user || slave->passwd() != passwd) { MetaData configData = metaDataFor(protocol, proxyList, url); if (config) { configData += *config; } slave->setConfig(configData); slave->setProtocol(url.scheme()); slave->setHost(host, port, user, passwd); } } void SchedulerPrivate::slotSlaveStatus(qint64, const QByteArray &, const QString &, bool) { } void SchedulerPrivate::slotSlaveDied(KIO::Slave *slave) { //qDebug() << slave; Q_ASSERT(slave); Q_ASSERT(!slave->isAlive()); ProtoQueue *pq = m_protocols.value(slave->protocol()); if (pq) { if (slave->job()) { pq->removeJob(slave->job()); } // in case this was a connected slave... pq->removeSlave(slave); } if (slave == m_slaveOnHold) { m_slaveOnHold = nullptr; m_urlOnHold.clear(); } // can't use slave->deref() here because we need to use deleteLater slave->aboutToDelete(); slave->deleteLater(); } void SchedulerPrivate::putSlaveOnHold(KIO::SimpleJob *job, const QUrl &url) { Slave *slave = jobSlave(job); //qDebug() << job << url << slave; slave->disconnect(job); // prevent the fake death of the slave from trying to kill the job again; // cf. Slave::hold(const QUrl &url) called in SchedulerPrivate::publishSlaveOnHold(). slave->setJob(nullptr); SimpleJobPrivate::get(job)->m_slave = nullptr; if (m_slaveOnHold) { m_slaveOnHold->kill(); } m_slaveOnHold = slave; m_urlOnHold = url; m_slaveOnHold->suspend(); } void SchedulerPrivate::publishSlaveOnHold() { //qDebug() << m_slaveOnHold; if (!m_slaveOnHold) { return; } m_slaveOnHold->hold(m_urlOnHold); emit q->slaveOnHoldListChanged(); } bool SchedulerPrivate::isSlaveOnHoldFor(const QUrl &url) { if (url.isValid() && m_urlOnHold.isValid() && url == m_urlOnHold) { return true; } return Slave::checkForHeldSlave(url); } Slave *SchedulerPrivate::heldSlaveForJob(SimpleJob *job) { Slave *slave = nullptr; KIO::SimpleJobPrivate *const jobPriv = SimpleJobPrivate::get(job); if (jobPriv->m_checkOnHold) { slave = Slave::holdSlave(jobPriv->m_protocol, job->url()); } if (!slave && m_slaveOnHold) { // Make sure that the job wants to do a GET or a POST, and with no offset const int cmd = jobPriv->m_command; bool canJobReuse = (cmd == CMD_GET || cmd == CMD_MULTI_GET); if (KIO::TransferJob *tJob = qobject_cast(job)) { canJobReuse = (canJobReuse || cmd == CMD_SPECIAL); if (canJobReuse) { KIO::MetaData outgoing = tJob->outgoingMetaData(); const QString resume = outgoing.value(QStringLiteral("resume")); const QString rangeStart = outgoing.value(QStringLiteral("range-start")); //qDebug() << "Resume metadata is" << resume; canJobReuse = (resume.isEmpty() || resume == QLatin1String("0")) && (rangeStart.isEmpty() || rangeStart == QLatin1String("0")); } } if (job->url() == m_urlOnHold) { if (canJobReuse) { //qDebug() << "HOLD: Reusing held slave (" << m_slaveOnHold << ")"; slave = m_slaveOnHold; } else { //qDebug() << "HOLD: Discarding held slave (" << m_slaveOnHold << ")"; m_slaveOnHold->kill(); } m_slaveOnHold = nullptr; m_urlOnHold.clear(); } } else if (slave) { //qDebug() << "HOLD: Reusing klauncher held slave (" << slave << ")"; } return slave; } void SchedulerPrivate::removeSlaveOnHold() { //qDebug() << m_slaveOnHold; if (m_slaveOnHold) { m_slaveOnHold->kill(); } m_slaveOnHold = nullptr; m_urlOnHold.clear(); } Slave *SchedulerPrivate::getConnectedSlave(const QUrl &url, const KIO::MetaData &config) { QStringList proxyList; const QString protocol = KProtocolManager::slaveProtocol(url, proxyList); ProtoQueue *pq = protoQ(protocol, url.host()); Slave *slave = pq->createSlave(protocol, /* job */nullptr, url); if (slave) { setupSlave(slave, url, protocol, proxyList, true, &config); pq->m_connectedSlaveQueue.addSlave(slave); slave->send(CMD_CONNECT); q->connect(slave, SIGNAL(connected()), SLOT(slotSlaveConnected())); q->connect(slave, SIGNAL(error(int,QString)), SLOT(slotSlaveError(int,QString))); } //qDebug() << url << slave; return slave; } void SchedulerPrivate::slotSlaveConnected() { //qDebug(); Slave *slave = static_cast(q->sender()); slave->setConnected(true); q->disconnect(slave, SIGNAL(connected()), q, SLOT(slotSlaveConnected())); emit q->slaveConnected(slave); } void SchedulerPrivate::slotSlaveError(int errorNr, const QString &errorMsg) { Slave *slave = static_cast(q->sender()); //qDebug() << slave << errorNr << errorMsg; ProtoQueue *pq = protoQ(slave->protocol(), slave->host()); if (!slave->isConnected() || pq->m_connectedSlaveQueue.isIdle(slave)) { // Only forward to application if slave is idle or still connecting. // ### KDE5: can we remove this apparently arbitrary behavior and just always emit SlaveError? emit q->slaveError(slave, errorNr, errorMsg); } } bool SchedulerPrivate::assignJobToSlave(KIO::Slave *slave, SimpleJob *job) { //qDebug() << slave << job; // KDE5: queueing of jobs can probably be removed, it provides very little benefit ProtoQueue *pq = m_protocols.value(slave->protocol()); if (pq) { pq->removeJob(job); return pq->m_connectedSlaveQueue.queueJob(job, slave); } return false; } bool SchedulerPrivate::disconnectSlave(KIO::Slave *slave) { //qDebug() << slave; ProtoQueue *pq = m_protocols.value(slave->protocol()); return (pq ? pq->m_connectedSlaveQueue.removeSlave(slave) : false); } void SchedulerPrivate::checkSlaveOnHold(bool b) { //qDebug() << b; m_checkOnHold = b; } void SchedulerPrivate::updateInternalMetaData(SimpleJob *job) { KIO::SimpleJobPrivate *const jobPriv = SimpleJobPrivate::get(job); // Preserve all internal meta-data so they can be sent back to the // ioslaves as needed... const QUrl jobUrl = job->url(); //qDebug() << job << jobPriv->m_internalMetaData; QMapIterator it(jobPriv->m_internalMetaData); while (it.hasNext()) { it.next(); if (it.key().startsWith(QLatin1String("{internal~currenthost}"), Qt::CaseInsensitive)) { SlaveConfig::self()->setConfigData(jobUrl.scheme(), jobUrl.host(), it.key().mid(22), it.value()); } else if (it.key().startsWith(QLatin1String("{internal~allhosts}"), Qt::CaseInsensitive)) { SlaveConfig::self()->setConfigData(jobUrl.scheme(), QString(), it.key().mid(19), it.value()); } } } #include "moc_scheduler.cpp" #include "moc_scheduler_p.cpp" diff --git a/src/core/sessiondata.cpp b/src/core/sessiondata.cpp index ee990450..7ec1f58a 100644 --- a/src/core/sessiondata.cpp +++ b/src/core/sessiondata.cpp @@ -1,142 +1,142 @@ /* This file is part of the KDE project Copyright (C) 2000 Dawit Alemayehu This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License (LGPL) 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 Lesser 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 "sessiondata_p.h" #include #include #include #include #include #include #include #include #include #include "http_slave_defaults.h" namespace KIO { /***************************** SessionData::AuthData ************************/ #if 0 struct SessionData::AuthData { public: AuthData() {} AuthData(const QByteArray &k, const QByteArray &g, bool p) { key = k; group = g; persist = p; } bool isKeyMatch(const QByteArray &val) const { return (val == key); } bool isGroupMatch(const QByteArray &val) const { return (val == group); } QByteArray key; QByteArray group; bool persist; }; #endif /********************************* SessionData ****************************/ class SessionData::SessionDataPrivate { public: SessionDataPrivate() { useCookie = true; initDone = false; } bool initDone; bool useCookie; QString charsets; QString language; }; SessionData::SessionData() : d(new SessionDataPrivate) { // authData = 0; } SessionData::~SessionData() { delete d; } void SessionData::configDataFor(MetaData &configData, const QString &proto, const QString &) { if ((proto.startsWith(QLatin1String("http"), Qt::CaseInsensitive)) || (proto.startsWith(QLatin1String("webdav"), Qt::CaseInsensitive))) { if (!d->initDone) { reset(); } // These might have already been set so check first // to make sure that we do not trumpt settings sent // by apps or end-user. if (configData[QStringLiteral("Cookies")].isEmpty()) { configData[QStringLiteral("Cookies")] = d->useCookie ? QStringLiteral("true") : QStringLiteral("false"); } if (configData[QStringLiteral("Languages")].isEmpty()) { configData[QStringLiteral("Languages")] = d->language; } if (configData[QStringLiteral("Charsets")].isEmpty()) { configData[QStringLiteral("Charsets")] = d->charsets; } if (configData[QStringLiteral("CacheDir")].isEmpty()) { - const QString httpCacheDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/kio_http"; + const QString httpCacheDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/kio_http"); QDir().mkpath(httpCacheDir); configData[QStringLiteral("CacheDir")] = httpCacheDir; } if (configData[QStringLiteral("UserAgent")].isEmpty()) { configData[QStringLiteral("UserAgent")] = KProtocolManager::defaultUserAgent(); } } } void SessionData::reset() { d->initDone = true; // Get Cookie settings... d->useCookie = KSharedConfig::openConfig(QStringLiteral("kcookiejarrc"), KConfig::NoGlobals)-> group("Cookie Policy"). readEntry("Cookies", true); d->language = KProtocolManager::acceptLanguagesHeader(); d->charsets = QString::fromLatin1(QTextCodec::codecForLocale()->name()).toLower(); KProtocolManager::reparseConfiguration(); } } diff --git a/src/core/slave.cpp b/src/core/slave.cpp index 7823d732..60005d37 100644 --- a/src/core/slave.cpp +++ b/src/core/slave.cpp @@ -1,580 +1,580 @@ /* * This file is part of the KDE libraries * Copyright (c) 2000 Waldo Bastian * 2000 Stephan Kulow * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. **/ #include "slave.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "dataprotocol_p.h" #include "connection_p.h" #include "commands_p.h" #include "connectionserver.h" #include "kioglobal_p.h" #include #include // CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 #include "slaveinterface_p.h" #include "kiocoredebug.h" using namespace KIO; #define SLAVE_CONNECTION_TIMEOUT_MIN 2 // Without debug info we consider it an error if the slave doesn't connect // within 10 seconds. // With debug info we give the slave an hour so that developers have a chance // to debug their slave. #ifdef NDEBUG #define SLAVE_CONNECTION_TIMEOUT_MAX 10 #else #define SLAVE_CONNECTION_TIMEOUT_MAX 3600 #endif static QThreadStorage s_kslaveLauncher; static org::kde::KSlaveLauncher *klauncher() { KDEInitInterface::ensureKdeinitRunning(); if (!s_kslaveLauncher.hasLocalData()) { org::kde::KSlaveLauncher *launcher = new org::kde::KSlaveLauncher(QStringLiteral("org.kde.klauncher5"), QStringLiteral("/KLauncher"), QDBusConnection::sessionBus()); s_kslaveLauncher.setLocalData(launcher); return launcher; } return s_kslaveLauncher.localData(); } // In such case we start the slave via QProcess. // It's possible to force this by setting the env. variable // KDE_FORK_SLAVES, Clearcase seems to require this. static QBasicAtomicInt bForkSlaves = #if KIO_FORK_SLAVES Q_BASIC_ATOMIC_INITIALIZER(1); #else Q_BASIC_ATOMIC_INITIALIZER(-1); #endif static bool forkSlaves() { // In such case we start the slave via QProcess. // It's possible to force this by setting the env. variable // KDE_FORK_SLAVES, Clearcase seems to require this. if (bForkSlaves.load() == -1) { bool fork = qEnvironmentVariableIsSet("KDE_FORK_SLAVES"); // no dbus? => fork slaves as we can't talk to klauncher if (!fork) { fork = !QDBusConnection::sessionBus().interface(); } #ifdef Q_OS_UNIX if (!fork) { // check the UID of klauncher QDBusReply reply = QDBusConnection::sessionBus().interface()->serviceUid(klauncher()->service()); // if reply is not valid, fork, most likely klauncher can not be run or is not installed // fallback: if there's an klauncher process owned by a different user: still fork if (!reply.isValid() || getuid() != reply) { fork = true; } } #endif bForkSlaves.testAndSetRelaxed(-1, fork ? 1 : 0); } return bForkSlaves.load() == 1; } namespace KIO { /** * @internal */ class SlavePrivate: public SlaveInterfacePrivate { public: SlavePrivate(const QString &protocol) : m_protocol(protocol), m_slaveProtocol(protocol), slaveconnserver(new KIO::ConnectionServer), m_job(nullptr), m_pid(0), m_port(0), contacted(false), dead(false), m_refCount(1) { contact_started.start(); slaveconnserver->listenForRemote(); if (!slaveconnserver->isListening()) { qCWarning(KIO_CORE) << "KIO Connection server not listening, could not connect"; } } ~SlavePrivate() { delete slaveconnserver; } QString m_protocol; QString m_slaveProtocol; QString m_host; QString m_user; QString m_passwd; KIO::ConnectionServer *slaveconnserver; KIO::SimpleJob *m_job; qint64 m_pid; quint16 m_port; bool contacted; bool dead; QElapsedTimer contact_started; QElapsedTimer m_idleSince; int m_refCount; }; } void Slave::accept() { Q_D(Slave); d->slaveconnserver->setNextPendingConnection(d->connection); d->slaveconnserver->deleteLater(); d->slaveconnserver = nullptr; connect(d->connection, &Connection::readyRead, this, &Slave::gotInput); } void Slave::timeout() { Q_D(Slave); if (d->dead) { //already dead? then slaveDied was emitted and we are done return; } if (d->connection->isConnected()) { return; } /*qDebug() << "slave failed to connect to application pid=" << d->m_pid << " protocol=" << d->m_protocol;*/ if (d->m_pid && KIOPrivate::isProcessAlive(d->m_pid)) { int delta_t = d->contact_started.elapsed() / 1000; //qDebug() << "slave is slow... pid=" << d->m_pid << " t=" << delta_t; if (delta_t < SLAVE_CONNECTION_TIMEOUT_MAX) { QTimer::singleShot(1000 * SLAVE_CONNECTION_TIMEOUT_MIN, this, SLOT(timeout())); return; } } //qDebug() << "Houston, we lost our slave, pid=" << d->m_pid; d->connection->close(); d->dead = true; QString arg = d->m_protocol; if (!d->m_host.isEmpty()) { - arg += "://" + d->m_host; + arg += QLatin1String("://") + d->m_host; } //qDebug() << "slave died pid = " << d->m_pid; ref(); // Tell the job about the problem. emit error(ERR_SLAVE_DIED, arg); // Tell the scheduler about the problem. emit slaveDied(this); // After the above signal we're dead!! deref(); } Slave::Slave(const QString &protocol, QObject *parent) : SlaveInterface(*new SlavePrivate(protocol), parent) { Q_D(Slave); d->slaveconnserver->setParent(this); d->connection = new Connection(this); connect(d->slaveconnserver, &ConnectionServer::newConnection, this, &Slave::accept); } Slave::~Slave() { //qDebug() << "destructing slave object pid = " << d->m_pid; //delete d; } QString Slave::protocol() { Q_D(Slave); return d->m_protocol; } void Slave::setProtocol(const QString &protocol) { Q_D(Slave); d->m_protocol = protocol; } QString Slave::slaveProtocol() { Q_D(Slave); return d->m_slaveProtocol; } QString Slave::host() { Q_D(Slave); return d->m_host; } quint16 Slave::port() { Q_D(Slave); return d->m_port; } QString Slave::user() { Q_D(Slave); return d->m_user; } QString Slave::passwd() { Q_D(Slave); return d->m_passwd; } void Slave::setIdle() { Q_D(Slave); d->m_idleSince.start(); } bool Slave::isConnected() { Q_D(Slave); return d->contacted; } void Slave::setConnected(bool c) { Q_D(Slave); d->contacted = c; } void Slave::ref() { Q_D(Slave); d->m_refCount++; } void Slave::deref() { Q_D(Slave); d->m_refCount--; if (!d->m_refCount) { aboutToDelete(); delete this; // yes it reads funny, but it's too late for a deleteLater() here, no event loop anymore } } void Slave::aboutToDelete() { Q_D(Slave); d->connection->disconnect(this); this->disconnect(); } int Slave::idleTime() { Q_D(Slave); if (!d->m_idleSince.isValid()) { return 0; } return d->m_idleSince.elapsed() / 1000; } void Slave::setPID(qint64 pid) { Q_D(Slave); d->m_pid = pid; } qint64 Slave::slave_pid() { Q_D(Slave); return d->m_pid; } void Slave::setJob(KIO::SimpleJob *job) { Q_D(Slave); if (!d->sslMetaData.isEmpty()) { emit metaData(d->sslMetaData); } d->m_job = job; } KIO::SimpleJob *Slave::job() const { Q_D(const Slave); return d->m_job; } bool Slave::isAlive() { Q_D(Slave); return !d->dead; } void Slave::hold(const QUrl &url) { Q_D(Slave); ref(); { QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); stream << url; d->connection->send(CMD_SLAVE_HOLD, data); d->connection->close(); d->dead = true; emit slaveDied(this); } deref(); // Call KSlaveLauncher::waitForSlave(pid); { klauncher()->waitForSlave(d->m_pid); } } void Slave::suspend() { Q_D(Slave); d->connection->suspend(); } void Slave::resume() { Q_D(Slave); d->connection->resume(); } bool Slave::suspended() { Q_D(Slave); return d->connection->suspended(); } void Slave::send(int cmd, const QByteArray &arr) { Q_D(Slave); d->connection->send(cmd, arr); } void Slave::gotInput() { Q_D(Slave); if (d->dead) { //already dead? then slaveDied was emitted and we are done return; } ref(); if (!dispatch()) { d->connection->close(); d->dead = true; QString arg = d->m_protocol; if (!d->m_host.isEmpty()) { - arg += "://" + d->m_host; + arg += QLatin1String("://") + d->m_host; } //qDebug() << "slave died pid = " << d->m_pid; // Tell the job about the problem. emit error(ERR_SLAVE_DIED, arg); // Tell the scheduler about the problem. emit slaveDied(this); } deref(); // Here we might be dead!! } void Slave::kill() { Q_D(Slave); d->dead = true; // OO can be such simple. /*qDebug() << "killing slave pid" << d->m_pid << "(" << QString(d->m_protocol) + "://" + d->m_host << ")";*/ if (d->m_pid) { KIOPrivate::sendTerminateSignal(d->m_pid); d->m_pid = 0; } } void Slave::setHost(const QString &host, quint16 port, const QString &user, const QString &passwd) { Q_D(Slave); d->m_host = host; d->m_port = port; d->m_user = user; d->m_passwd = passwd; d->sslMetaData.clear(); QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); stream << d->m_host << d->m_port << d->m_user << d->m_passwd; d->connection->send(CMD_HOST, data); } void Slave::resetHost() { Q_D(Slave); d->sslMetaData.clear(); d->m_host = QStringLiteral(""); } void Slave::setConfig(const MetaData &config) { Q_D(Slave); QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); stream << config; d->connection->send(CMD_CONFIG, data); } Slave *Slave::createSlave(const QString &protocol, const QUrl &url, int &error, QString &error_text) { //qDebug() << "createSlave" << protocol << "for" << url; // Firstly take into account all special slaves if (protocol == QLatin1String("data")) { return new DataProtocol(); } Slave *slave = new Slave(protocol); QUrl slaveAddress = slave->d_func()->slaveconnserver->address(); if (slaveAddress.isEmpty()) { error_text = i18n("Can not create socket for launching io-slave for protocol '%1'.", protocol); error = KIO::ERR_CANNOT_CREATE_SLAVE; delete slave; return nullptr; } if (forkSlaves() == 1) { QString _name = KProtocolInfo::exec(protocol); if (_name.isEmpty()) { error_text = i18n("Unknown protocol '%1'.", protocol); error = KIO::ERR_CANNOT_CREATE_SLAVE; delete slave; return nullptr; } // find the kioslave using KPluginLoader; kioslave would do this // anyway, but if it doesn't exist, we want to be able to return // a useful error message immediately QString lib_path = KPluginLoader::findPlugin(_name); if (lib_path.isEmpty()) { error_text = i18n("Can not find io-slave for protocol '%1'.", protocol); error = KIO::ERR_CANNOT_CREATE_SLAVE; delete slave; return nullptr; } const QStringList args = QStringList() << lib_path << protocol << QLatin1String("") << slaveAddress.toString(); //qDebug() << "kioslave" << ", " << lib_path << ", " << protocol << ", " << QString() << ", " << slaveAddress; // search paths const QStringList searchPaths = QStringList() << QCoreApplication::applicationDirPath() // then look where our application binary is located << QLibraryInfo::location(QLibraryInfo::LibraryExecutablesPath) // look where libexec path is (can be set in qt.conf) << QFile::decodeName(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5); // look at our installation location const QString kioslaveExecutable = QStandardPaths::findExecutable(QStringLiteral("kioslave"), searchPaths); if (kioslaveExecutable.isEmpty()) { error_text = i18n("Can not find 'kioslave' executable at '%1'", searchPaths.join(QStringLiteral(", "))); error = KIO::ERR_CANNOT_CREATE_SLAVE; delete slave; return nullptr; } QProcess::startDetached(kioslaveExecutable, args); return slave; } QString errorStr; QDBusReply reply = klauncher()->requestSlave(protocol, url.host(), slaveAddress.toString(), errorStr); if (!reply.isValid()) { error_text = i18n("Cannot talk to klauncher: %1", klauncher()->lastError().message()); error = KIO::ERR_CANNOT_CREATE_SLAVE; delete slave; return nullptr; } qint64 pid = reply; if (!pid) { error_text = i18n("klauncher said: %1", errorStr); error = KIO::ERR_CANNOT_CREATE_SLAVE; delete slave; return nullptr; } slave->setPID(pid); QTimer::singleShot(1000 * SLAVE_CONNECTION_TIMEOUT_MIN, slave, SLOT(timeout())); return slave; } Slave *Slave::holdSlave(const QString &protocol, const QUrl &url) { //qDebug() << "holdSlave" << protocol << "for" << url; // Firstly take into account all special slaves if (protocol == QLatin1String("data")) { return nullptr; } if (forkSlaves()) { return nullptr; } Slave *slave = new Slave(protocol); QUrl slaveAddress = slave->d_func()->slaveconnserver->address(); QDBusReply reply = klauncher()->requestHoldSlave(url.toString(), slaveAddress.toString()); if (!reply.isValid()) { delete slave; return nullptr; } qint64 pid = reply; if (!pid) { delete slave; return nullptr; } slave->setPID(pid); QTimer::singleShot(1000 * SLAVE_CONNECTION_TIMEOUT_MIN, slave, SLOT(timeout())); return slave; } bool Slave::checkForHeldSlave(const QUrl &url) { if (forkSlaves()) { return false; } return klauncher()->checkForHeldSlave(url.toString()); } diff --git a/src/core/slavebase.cpp b/src/core/slavebase.cpp index 532181be..c1d8900d 100644 --- a/src/core/slavebase.cpp +++ b/src/core/slavebase.cpp @@ -1,1476 +1,1475 @@ /* * 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. * **/ #include "slavebase.h" #include #include #include #include #ifdef Q_OS_WIN #include #endif #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 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 *q; 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); } ~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; KConfigGroup *configGroup; QUrl onHoldUrl; QElapsedTimer lastTimeout; QElapsedTimer nextTimeout; qint64 nextTimeoutMsecs; KIO::filesize_t totalSize; KRemoteEncoding *remotefile; enum { Idle, InsideMethod, FinishedCalled, ErrorCalled } m_state; QByteArray timeoutData; KPasswdServerClient *m_passwdServerClient; 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() { configGroup->deleteGroup(KConfigGroup::WriteConfigFlags()); // 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) { configGroup->writeEntry(it.key(), it->toUtf8(), KConfigGroup::WriteConfigFlags()); } end = q->mIncomingMetaData.constEnd(); for (MetaData::ConstIterator it = q->mIncomingMetaData.constBegin(); it != end; ++it) { configGroup->writeEntry(it.key(), it->toUtf8(), KConfigGroup::WriteConfigFlags()); } } void verifyState(const char *cmdName) { if ((m_state != FinishedCalled) && (m_state != ErrorCalled)) { qCWarning(KIO_CORE) << cmdName << "did not call finished() or error()! Please fix the" << QCoreApplication::applicationName() << "KIO slave"; } } void verifyErrorFinishedNotCalled(const char *cmdName) { if (m_state == FinishedCalled || m_state == ErrorCalled) { qCWarning(KIO_CORE) << cmdName << "called finished() or error(), but it's not supposed to! Please fix the" << QCoreApplication::applicationName() << "KIO slave"; } } 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 = protocol; - d->slaveid += QString::number(getpid()); + d->slaveid = QString::fromUtf8(protocol) + QString::number(getpid()); d->resume = false; d->needSendCanResume = false; d->config = new KConfig(QString(), KConfig::SimpleConfig); // The KConfigGroup needs the KConfig to exist during its whole lifetime. d->configGroup = new KConfigGroup(d->config, QString()); 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; } KConfigGroup *SlaveBase::config() { 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)); + 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) { if (d->m_state == d->ErrorCalled) { qCWarning(KIO_CORE) << "error() called twice! Please fix the" << QCoreApplication::applicationName() << "KIO slave"; return; } else if (d->m_state == d->FinishedCalled) { qCWarning(KIO_CORE) << "error() called after finished()! Please fix the" << QCoreApplication::applicationName() << "KIO slave"; 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(); } if (d->m_state == d->FinishedCalled) { qCWarning(KIO_CORE) << "finished() called twice! Please fix the" << QCoreApplication::applicationName() << "KIO slave"; return; } else if (d->m_state == d->ErrorCalled) { qCWarning(KIO_CORE) << "finished() called after error()! Please fix the" << QCoreApplication::applicationName() << "KIO slave"; 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); } #ifndef KIOCORE_NO_DEPRECATED 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); } #endif void SlaveBase::statEntry(const UDSEntry &entry) { KIO_DATA << entry; send(MSG_STAT_ENTRY, data); } #ifndef KIOCORE_NO_DEPRECATED 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) == QLatin1String(".")) { d->m_rootEntryListed = true; } listEntries(d->pendingListEntries); d->pendingListEntries.clear(); } else { listEntry(entry); } } #endif 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) == QLatin1String(".")) { 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); foreach (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(void) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_CONNECT)); } void SlaveBase::closeConnection(void) { } // No response! void SlaveBase::stat(QUrl const &) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_STAT)); } void SlaveBase::put(QUrl const &, int, JobFlags) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_PUT)); } void SlaveBase::special(const QByteArray &) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_SPECIAL)); } void SlaveBase::listDir(QUrl const &) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_LISTDIR)); } void SlaveBase::get(QUrl const &) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_GET)); } void SlaveBase::open(QUrl const &, QIODevice::OpenMode) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_OPEN)); } void SlaveBase::read(KIO::filesize_t) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_READ)); } void SlaveBase::write(const QByteArray &) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_WRITE)); } void SlaveBase::seek(KIO::filesize_t) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_SEEK)); } void SlaveBase::close() { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, 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)); } void SlaveBase::symlink(QString const &, QUrl const &, JobFlags) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_SYMLINK)); } void SlaveBase::copy(QUrl const &, QUrl const &, int, JobFlags) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_COPY)); } void SlaveBase::del(QUrl const &, bool) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_DEL)); } void SlaveBase::setLinkDest(const QUrl &, const QString &) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_SETLINKDEST)); } void SlaveBase::mkdir(QUrl const &, int) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_MKDIR)); } void SlaveBase::chmod(QUrl const &, int) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_CHMOD)); } void SlaveBase::setModificationTime(QUrl const &, const QDateTime &) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_SETMODIFICATIONTIME)); } void SlaveBase::chown(QUrl const &, const QString &, const QString &) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_CHOWN)); } void SlaveBase::setSubUrl(QUrl const &) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_SUBURL)); } void SlaveBase::multiGet(const QByteArray &) { error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, 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 miliseconds 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; switch (command) { case CMD_HOST: { QString passwd; QString host, user; quint16 port; stream >> host >> port >> user >> passwd; d->m_state = d->InsideMethod; setHost(host, port, user, passwd); d->verifyErrorFinishedNotCalled("setHost()"); 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; slave_status(); // TODO verify that the slave has called slaveStatus()? d->verifyErrorFinishedNotCalled("slave_status()"); 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; reparseConfiguration(); d->verifyErrorFinishedNotCalled("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)); } 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/slaveconfig.cpp b/src/core/slaveconfig.cpp index 2423543f..e5664d2b 100644 --- a/src/core/slaveconfig.cpp +++ b/src/core/slaveconfig.cpp @@ -1,231 +1,231 @@ // -*- c++ -*- /* * This file is part of the KDE libraries * Copyright (c) 2001 Waldo Bastian * * 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 "slaveconfig.h" #include #include #include #include #include #include using namespace KIO; namespace KIO { class SlaveConfigProtocol { public: SlaveConfigProtocol() {} ~SlaveConfigProtocol() { delete configFile; } public: MetaData global; QHash host; KConfig *configFile; }; static void readConfig(KConfig *config, const QString &group, MetaData *metaData) { *metaData += config->entryMap(group); } class SlaveConfigPrivate { public: void readGlobalConfig(); SlaveConfigProtocol *readProtocolConfig(const QString &_protocol); SlaveConfigProtocol *findProtocolConfig(const QString &_protocol); void readConfigProtocolHost(const QString &_protocol, SlaveConfigProtocol *scp, const QString &host); public: MetaData global; QHash protocol; }; void SlaveConfigPrivate::readGlobalConfig() { global.clear(); // Read stuff... readConfig(KSharedConfig::openConfig().data(), QStringLiteral("Socks"), &global); // Socks settings. global += KProtocolManager::entryMap(QStringLiteral("")); } SlaveConfigProtocol *SlaveConfigPrivate::readProtocolConfig(const QString &_protocol) { SlaveConfigProtocol *scp = protocol.value(_protocol, nullptr); if (!scp) { QString filename = KProtocolInfo::config(_protocol); scp = new SlaveConfigProtocol; scp->configFile = new KConfig(filename, KConfig::NoGlobals); protocol.insert(_protocol, scp); } // Read global stuff... readConfig(scp->configFile, QStringLiteral(""), &(scp->global)); return scp; } SlaveConfigProtocol *SlaveConfigPrivate::findProtocolConfig(const QString &_protocol) { SlaveConfigProtocol *scp = protocol.value(_protocol, nullptr); if (!scp) { scp = readProtocolConfig(_protocol); } return scp; } void SlaveConfigPrivate::readConfigProtocolHost(const QString &, SlaveConfigProtocol *scp, const QString &host) { MetaData metaData; scp->host.insert(host, metaData); // Read stuff // Break host into domains QString domain = host; - if (!domain.contains('.')) { + if (!domain.contains(QLatin1Char('.'))) { // Host without domain. if (scp->configFile->hasGroup("")) { readConfig(scp->configFile, QStringLiteral(""), &metaData); scp->host.insert(host, metaData); } } int pos = 0; do { - pos = host.lastIndexOf('.', pos - 1); + pos = host.lastIndexOf(QLatin1Char('.'), pos - 1); if (pos < 0) { domain = host; } else { domain = host.mid(pos + 1); } if (scp->configFile->hasGroup(domain)) { readConfig(scp->configFile, domain.toLower(), &metaData); scp->host.insert(host, metaData); } } while (pos > 0); } class SlaveConfigSingleton { public: SlaveConfig instance; }; template T * perThreadGlobalStatic() { static QThreadStorage s_storage; if (!s_storage.hasLocalData()) { s_storage.setLocalData(new T); } return s_storage.localData(); } //Q_GLOBAL_STATIC(SlaveConfigSingleton, _self) SlaveConfigSingleton *_self() { return perThreadGlobalStatic(); } SlaveConfig *SlaveConfig::self() { return &_self()->instance; } SlaveConfig::SlaveConfig() : d(new SlaveConfigPrivate) { d->readGlobalConfig(); } SlaveConfig::~SlaveConfig() { qDeleteAll(d->protocol); delete d; } void SlaveConfig::setConfigData(const QString &protocol, const QString &host, const QString &key, const QString &value) { MetaData config; config.insert(key, value); setConfigData(protocol, host, config); } void SlaveConfig::setConfigData(const QString &protocol, const QString &host, const MetaData &config) { if (protocol.isEmpty()) { d->global += config; } else { SlaveConfigProtocol *scp = d->findProtocolConfig(protocol); if (host.isEmpty()) { scp->global += config; } else { if (!scp->host.contains(host)) { d->readConfigProtocolHost(protocol, scp, host); } MetaData hostConfig = scp->host.value(host); hostConfig += config; scp->host.insert(host, hostConfig); } } } MetaData SlaveConfig::configData(const QString &protocol, const QString &host) { MetaData config = d->global; SlaveConfigProtocol *scp = d->findProtocolConfig(protocol); config += scp->global; if (host.isEmpty()) { return config; } if (!scp->host.contains(host)) { d->readConfigProtocolHost(protocol, scp, host); emit configNeeded(protocol, host); } MetaData hostConfig = scp->host.value(host); config += hostConfig; return config; } QString SlaveConfig::configData(const QString &protocol, const QString &host, const QString &key) { return configData(protocol, host)[key]; } void SlaveConfig::reset() { qDeleteAll(d->protocol); d->protocol.clear(); d->readGlobalConfig(); } } diff --git a/src/core/tcpslavebase.cpp b/src/core/tcpslavebase.cpp index 76084b87..faaac8fd 100644 --- a/src/core/tcpslavebase.cpp +++ b/src/core/tcpslavebase.cpp @@ -1,941 +1,942 @@ /* * Copyright (C) 2000 Alex Zepeda * Copyright (C) 2001-2003 George Staikos * Copyright (C) 2001 Dawit Alemayehu * Copyright (C) 2007,2008 Andreas Hartmetz * Copyright (C) 2008 Roland Harnau * Copyright (C) 2010 Richard Moore * * This file is part of the KDE project * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "tcpslavebase.h" #include "kiocoredebug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KIO; //using namespace KNetwork; namespace KIO { Q_DECLARE_OPERATORS_FOR_FLAGS(TCPSlaveBase::SslResult) } //TODO Proxy support whichever way works; KPAC reportedly does *not* work. //NOTE kded_proxyscout may or may not be interesting //TODO resurrect SSL session recycling; this means save the session on disconnect and look //for a reusable session on connect. Consider how HTTP persistent connections interact with that. //TODO in case we support SSL-lessness we need static KTcpSocket::sslAvailable() and check it //in most places we ATM check for d->isSSL. //TODO check if d->isBlocking is honored everywhere it makes sense //TODO fold KSSLSetting and KSSLCertificateHome into KSslSettings and use that everywhere. //TODO recognize partially encrypted websites as "somewhat safe" /* List of dialogs/messageboxes we need to use (current code location in parentheses) - Can the "dontAskAgainName" thing be improved? - "SSLCertDialog" [select client cert] (SlaveInterface) - Enter password for client certificate (inline) - Password for client cert was wrong. Please reenter. (inline) - Setting client cert failed. [doesn't give reason] (inline) - "SSLInfoDialog" [mostly server cert info] (SlaveInterface) - You are about to enter secure mode. Security information/Display SSL information/Connect (inline) - You are about to leave secure mode. Security information/Continue loading/Abort (inline) - Hostname mismatch: Continue/Details/Cancel (inline) - IP address mismatch: Continue/Details/Cancel (inline) - Certificate failed authenticity check: Continue/Details/Cancel (inline) - Would you like to accept this certificate forever: Yes/No/Current sessions only (inline) */ /** @internal */ class Q_DECL_HIDDEN TCPSlaveBase::TcpSlaveBasePrivate { public: TcpSlaveBasePrivate(TCPSlaveBase *qq) : q(qq) {} void setSslMetaData() { sslMetaData.insert(QStringLiteral("ssl_in_use"), QStringLiteral("TRUE")); KSslCipher cipher = socket.sessionCipher(); sslMetaData.insert(QStringLiteral("ssl_protocol_version"), socket.negotiatedSslVersionName()); - QString sslCipher = cipher.encryptionMethod() + '\n'; - sslCipher += cipher.authenticationMethod() + '\n'; - sslCipher += cipher.keyExchangeMethod() + '\n'; - sslCipher += cipher.digestMethod(); + const QString sslCipher = + cipher.encryptionMethod() + QLatin1Char('\n') + + cipher.authenticationMethod() + QLatin1Char('\n') + + cipher.keyExchangeMethod() + QLatin1Char('\n') + + cipher.digestMethod(); sslMetaData.insert(QStringLiteral("ssl_cipher"), sslCipher); sslMetaData.insert(QStringLiteral("ssl_cipher_name"), cipher.name()); sslMetaData.insert(QStringLiteral("ssl_cipher_used_bits"), QString::number(cipher.usedBits())); sslMetaData.insert(QStringLiteral("ssl_cipher_bits"), QString::number(cipher.supportedBits())); sslMetaData.insert(QStringLiteral("ssl_peer_ip"), ip); // try to fill in the blanks, i.e. missing certificates, and just assume that // those belong to the peer (==website or similar) certificate. for (int i = 0; i < sslErrors.count(); i++) { if (sslErrors[i].certificate().isNull()) { const QList peerCertificateChain = socket.peerCertificateChain(); sslErrors[i] = KSslError(sslErrors[i].error(), peerCertificateChain[0]); } } QString errorStr; // encode the two-dimensional numeric error list using '\n' and '\t' as outer and inner separators Q_FOREACH (const QSslCertificate &cert, socket.peerCertificateChain()) { Q_FOREACH (const KSslError &error, sslErrors) { if (error.certificate() == cert) { - errorStr += QString::number(static_cast(error.error())) + '\t'; + errorStr += QString::number(static_cast(error.error())) + QLatin1Char('\t'); } } - if (errorStr.endsWith('\t')) { + if (errorStr.endsWith(QLatin1Char('\t'))) { errorStr.chop(1); } - errorStr += '\n'; + errorStr += QLatin1Char('\n'); } errorStr.chop(1); sslMetaData.insert(QStringLiteral("ssl_cert_errors"), errorStr); QString peerCertChain; Q_FOREACH (const QSslCertificate &cert, socket.peerCertificateChain()) { - peerCertChain.append(cert.toPem()); - peerCertChain.append('\x01'); + peerCertChain.append(QString::fromUtf8(cert.toPem())); + peerCertChain.append(QLatin1Char('\x01')); } peerCertChain.chop(1); sslMetaData.insert(QStringLiteral("ssl_peer_chain"), peerCertChain); sendSslMetaData(); } void clearSslMetaData() { sslMetaData.clear(); sslMetaData.insert(QStringLiteral("ssl_in_use"), QStringLiteral("FALSE")); sendSslMetaData(); } void sendSslMetaData() { MetaData::ConstIterator it = sslMetaData.constBegin(); for (; it != sslMetaData.constEnd(); ++it) { q->setMetaData(it.key(), it.value()); } } SslResult startTLSInternal(KTcpSocket::SslVersion sslVersion, int waitForEncryptedTimeout = -1); TCPSlaveBase *q; bool isBlocking; KTcpSocket socket; QString host; QString ip; quint16 port; QByteArray serviceName; KSSLSettings sslSettings; bool usingSSL; bool autoSSL; bool sslNoUi; // If true, we just drop the connection silently // if SSL certificate check fails in some way. QList sslErrors; MetaData sslMetaData; }; //### uh, is this a good idea?? QIODevice *TCPSlaveBase::socket() const { return &d->socket; } TCPSlaveBase::TCPSlaveBase(const QByteArray &protocol, const QByteArray &poolSocket, const QByteArray &appSocket, bool autoSSL) : SlaveBase(protocol, poolSocket, appSocket), d(new TcpSlaveBasePrivate(this)) { d->isBlocking = true; d->port = 0; d->serviceName = protocol; d->usingSSL = false; d->autoSSL = autoSSL; d->sslNoUi = false; // Limit the read buffer size to 14 MB (14*1024*1024) (based on the upload limit // in TransferJob::slotDataReq). See the docs for QAbstractSocket::setReadBufferSize // and the BR# 187876 to understand why setting this limit is necessary. d->socket.setReadBufferSize(14680064); } TCPSlaveBase::~TCPSlaveBase() { delete d; } ssize_t TCPSlaveBase::write(const char *data, ssize_t len) { ssize_t written = d->socket.write(data, len); if (written == -1) { /*qDebug() << "d->socket.write() returned -1! Socket error is" << d->socket.error() << ", Socket state is" << d->socket.state();*/ } bool success = false; if (d->isBlocking) { // Drain the tx buffer success = d->socket.waitForBytesWritten(-1); } else { // ### I don't know how to make sure that all data does get written at some point // without doing it now. There is no event loop to do it behind the scenes. // Polling in the dispatch() loop? Something timeout based? success = d->socket.waitForBytesWritten(0); } d->socket.flush(); //this is supposed to get the data on the wire faster if (d->socket.state() != KTcpSocket::ConnectedState || !success) { /*qDebug() << "Write failed, will return -1! Socket error is" << d->socket.error() << ", Socket state is" << d->socket.state() << "Return value of waitForBytesWritten() is" << success;*/ return -1; } return written; } ssize_t TCPSlaveBase::read(char *data, ssize_t len) { if (d->usingSSL && (d->socket.encryptionMode() != KTcpSocket::SslClientMode)) { d->clearSslMetaData(); //qDebug() << "lost SSL connection."; return -1; } if (!d->socket.bytesAvailable()) { const int timeout = d->isBlocking ? -1 : (readTimeout() * 1000); d->socket.waitForReadyRead(timeout); } #if 0 // Do not do this because its only benefit is to cause a nasty side effect // upstream in Qt. See BR# 260769. else if (d->socket.encryptionMode() != KTcpSocket::SslClientMode || QNetworkProxy::applicationProxy().type() == QNetworkProxy::NoProxy) { // we only do this when it doesn't trigger Qt socket bugs. When it doesn't break anything // it seems to help performance. d->socket.waitForReadyRead(0); } #endif return d->socket.read(data, len); } ssize_t TCPSlaveBase::readLine(char *data, ssize_t len) { if (d->usingSSL && (d->socket.encryptionMode() != KTcpSocket::SslClientMode)) { d->clearSslMetaData(); //qDebug() << "lost SSL connection."; return -1; } const int timeout = (d->isBlocking ? -1 : (readTimeout() * 1000)); ssize_t readTotal = 0; do { if (!d->socket.bytesAvailable()) { d->socket.waitForReadyRead(timeout); } ssize_t readStep = d->socket.readLine(&data[readTotal], len - readTotal); if (readStep == -1 || (readStep == 0 && d->socket.state() != KTcpSocket::ConnectedState)) { return -1; } readTotal += readStep; } while (readTotal == 0 || data[readTotal - 1] != '\n'); return readTotal; } bool TCPSlaveBase::connectToHost(const QString &/*protocol*/, const QString &host, quint16 port) { QString errorString; const int errCode = connectToHost(host, port, &errorString); if (errCode == 0) { return true; } error(errCode, errorString); return false; } int TCPSlaveBase::connectToHost(const QString &host, quint16 port, QString *errorString) { d->clearSslMetaData(); //We have separate connection and SSL setup phases if (errorString) { errorString->clear(); // clear prior error messages. } d->socket.setVerificationPeerName(host); // Used for ssl certificate verification (SNI) // - leaving SSL - warn before we even connect //### see if it makes sense to move this into the HTTP ioslave which is the only // user. if (metaData(QStringLiteral("main_frame_request")) == QLatin1String("TRUE") //### this looks *really* unreliable && metaData(QStringLiteral("ssl_activate_warnings")) == QLatin1String("TRUE") && metaData(QStringLiteral("ssl_was_in_use")) == QLatin1String("TRUE") && !d->autoSSL) { if (d->sslSettings.warnOnLeave()) { int result = messageBox(i18n("You are about to leave secure " "mode. Transmissions will no " "longer be encrypted.\nThis " "means that a third party could " "observe your data in transit."), WarningContinueCancel, i18n("Security Information"), i18n("C&ontinue Loading"), QString(), QStringLiteral("WarnOnLeaveSSLMode")); if (result == SlaveBase::Cancel) { if (errorString) { *errorString = host; } return ERR_USER_CANCELED; } } } const int timeout = (connectTimeout() * 1000); // 20 sec timeout value disconnectFromHost(); //Reset some state, even if we are already disconnected d->host = host; d->socket.connectToHost(host, port); /*const bool connectOk = */d->socket.waitForConnected(timeout > -1 ? timeout : -1); /*qDebug() << "Socket: state=" << d->socket.state() << ", error=" << d->socket.error() << ", connected?" << connectOk;*/ if (d->socket.state() != KTcpSocket::ConnectedState) { if (errorString) { *errorString = host + QLatin1String(": ") + d->socket.errorString(); } switch (d->socket.error()) { case KTcpSocket::UnsupportedSocketOperationError: return ERR_UNSUPPORTED_ACTION; case KTcpSocket::RemoteHostClosedError: return ERR_CONNECTION_BROKEN; case KTcpSocket::SocketTimeoutError: return ERR_SERVER_TIMEOUT; case KTcpSocket::HostNotFoundError: return ERR_UNKNOWN_HOST; default: return ERR_CANNOT_CONNECT; } } //### check for proxyAuthenticationRequiredError d->ip = d->socket.peerAddress().toString(); d->port = d->socket.peerPort(); if (d->autoSSL) { const SslResult res = d->startTLSInternal(KTcpSocket::SecureProtocols, timeout); if (res & ResultFailed) { if (errorString) { *errorString = i18nc("%1 is a host name", "%1: SSL negotiation failed", host); } return ERR_CANNOT_CONNECT; } } return 0; } void TCPSlaveBase::disconnectFromHost() { //qDebug(); d->host.clear(); d->ip.clear(); d->usingSSL = false; if (d->socket.state() == KTcpSocket::UnconnectedState) { // discard incoming data - the remote host might have disconnected us in the meantime // but the visible effect of disconnectFromHost() should stay the same. d->socket.close(); return; } //### maybe save a session for reuse on SSL shutdown if and when QSslSocket // does that. QCA::TLS can do it apparently but that is not enough if // we want to present that as KDE API. Not a big loss in any case. d->socket.disconnectFromHost(); if (d->socket.state() != KTcpSocket::UnconnectedState) { d->socket.waitForDisconnected(-1); // wait for unsent data to be sent } d->socket.close(); //whatever that means on a socket } bool TCPSlaveBase::isAutoSsl() const { return d->autoSSL; } bool TCPSlaveBase::isUsingSsl() const { return d->usingSSL; } quint16 TCPSlaveBase::port() const { return d->port; } bool TCPSlaveBase::atEnd() const { return d->socket.atEnd(); } bool TCPSlaveBase::startSsl() { if (d->usingSSL) { return false; } return d->startTLSInternal(KTcpSocket::SecureProtocols) & ResultOk; } TCPSlaveBase::SslResult TCPSlaveBase::TcpSlaveBasePrivate::startTLSInternal(KTcpSocket::SslVersion version, int waitForEncryptedTimeout) { q->selectClientCertificate(); //setMetaData("ssl_session_id", d->kssl->session()->toString()); //### we don't support session reuse for now... usingSSL = true; // Set the SSL version to use... socket.setAdvertisedSslVersion(version); /* Usually ignoreSslErrors() would be called in the slot invoked by the sslErrors() signal but that would mess up the flow of control. We will check for errors anyway to decide if we want to continue connecting. Otherwise ignoreSslErrors() before connecting would be very insecure. */ socket.ignoreSslErrors(); socket.startClientEncryption(); const bool encryptionStarted = socket.waitForEncrypted(waitForEncryptedTimeout); //Set metadata, among other things for the "SSL Details" dialog KSslCipher cipher = socket.sessionCipher(); if (!encryptionStarted || socket.encryptionMode() != KTcpSocket::SslClientMode || cipher.isNull() || cipher.usedBits() == 0 || socket.peerCertificateChain().isEmpty()) { usingSSL = false; clearSslMetaData(); /*qDebug() << "Initial SSL handshake failed. encryptionStarted is" << encryptionStarted << ", cipher.isNull() is" << cipher.isNull() << ", cipher.usedBits() is" << cipher.usedBits() << ", length of certificate chain is" << socket.peerCertificateChain().count() << ", the socket says:" << socket.errorString() << "and the list of SSL errors contains" << socket.sslErrors().count() << "items.";*/ /*Q_FOREACH(const KSslError& sslError, socket.sslErrors()) { qDebug() << "SSL ERROR: (" << sslError.error() << ")" << sslError.errorString(); }*/ return ResultFailed | ResultFailedEarly; } /*qDebug() << "Cipher info - " << " advertised SSL protocol version" << socket.advertisedSslVersion() << " negotiated SSL protocol version" << socket.negotiatedSslVersion() << " authenticationMethod:" << cipher.authenticationMethod() << " encryptionMethod:" << cipher.encryptionMethod() << " keyExchangeMethod:" << cipher.keyExchangeMethod() << " name:" << cipher.name() << " supportedBits:" << cipher.supportedBits() << " usedBits:" << cipher.usedBits();*/ sslErrors = socket.sslErrors(); // TODO: review / rewrite / remove the comment // The app side needs the metadata now for the SSL error dialog (if any) but // the same metadata will be needed later, too. When "later" arrives the slave // may actually be connected to a different application that doesn't know // the metadata the slave sent to the previous application. // The quite important SSL indicator icon in Konqi's URL bar relies on metadata // from here, for example. And Konqi will be the second application to connect // to the slave. // Therefore we choose to have our metadata and send it, too :) setSslMetaData(); q->sendAndKeepMetaData(); SslResult rc = q->verifyServerCertificate(); if (rc & ResultFailed) { usingSSL = false; clearSslMetaData(); //qDebug() << "server certificate verification failed."; socket.disconnectFromHost(); //Make the connection fail (cf. ignoreSslErrors()) return ResultFailed; } else if (rc & ResultOverridden) { //qDebug() << "server certificate verification failed but continuing at user's request."; } //"warn" when starting SSL/TLS if (q->metaData(QStringLiteral("ssl_activate_warnings")) == QLatin1String("TRUE") && q->metaData(QStringLiteral("ssl_was_in_use")) == QLatin1String("FALSE") && sslSettings.warnOnEnter()) { int msgResult = q->messageBox(i18n("You are about to enter secure mode. " "All transmissions will be encrypted " "unless otherwise noted.\nThis means " "that no third party will be able to " "easily observe your data in transit."), WarningYesNo, i18n("Security Information"), i18n("Display SSL &Information"), i18n("C&onnect"), QStringLiteral("WarnOnEnterSSLMode")); if (msgResult == SlaveBase::Yes) { q->messageBox(SSLMessageBox /*==the SSL info dialog*/, host); } } return rc; } void TCPSlaveBase::selectClientCertificate() { #if 0 //hehe QString certname; // the cert to use this session bool send = false, prompt = false, save = false, forcePrompt = false; KSSLCertificateHome::KSSLAuthAction aa; setMetaData("ssl_using_client_cert", "FALSE"); // we change this if needed if (metaData("ssl_no_client_cert") == "TRUE") { return; } forcePrompt = (metaData("ssl_force_cert_prompt") == "TRUE"); // Delete the old cert since we're certainly done with it now if (d->pkcs) { delete d->pkcs; d->pkcs = NULL; } if (!d->kssl) { return; } // Look for a general certificate if (!forcePrompt) { certname = KSSLCertificateHome::getDefaultCertificateName(&aa); switch (aa) { case KSSLCertificateHome::AuthSend: send = true; prompt = false; break; case KSSLCertificateHome::AuthDont: send = false; prompt = false; certname.clear(); break; case KSSLCertificateHome::AuthPrompt: send = false; prompt = true; break; default: break; } } // Look for a certificate on a per-host basis as an override QString tmpcn = KSSLCertificateHome::getDefaultCertificateName(d->host, &aa); if (aa != KSSLCertificateHome::AuthNone) { // we must override switch (aa) { case KSSLCertificateHome::AuthSend: send = true; prompt = false; certname = tmpcn; break; case KSSLCertificateHome::AuthDont: send = false; prompt = false; certname.clear(); break; case KSSLCertificateHome::AuthPrompt: send = false; prompt = true; certname = tmpcn; break; default: break; } } // Finally, we allow the application to override anything. if (hasMetaData("ssl_demand_certificate")) { certname = metaData("ssl_demand_certificate"); if (!certname.isEmpty()) { forcePrompt = false; prompt = false; send = true; } } if (certname.isEmpty() && !prompt && !forcePrompt) { return; } // Ok, we're supposed to prompt the user.... if (prompt || forcePrompt) { QStringList certs = KSSLCertificateHome::getCertificateList(); QStringList::const_iterator it = certs.begin(); while (it != certs.end()) { KSSLPKCS12 *pkcs = KSSLCertificateHome::getCertificateByName(*it); if (pkcs && (!pkcs->getCertificate() || !pkcs->getCertificate()->x509V3Extensions().certTypeSSLClient())) { it = certs.erase(it); } else { ++it; } delete pkcs; } if (certs.isEmpty()) { return; // we had nothing else, and prompt failed } QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface(); if (!bus->isServiceRegistered("org.kde.kio.uiserver")) { bus->startService("org.kde.kuiserver"); } QDBusInterface uis("org.kde.kio.uiserver", "/UIServer", "org.kde.KIO.UIServer"); QDBusMessage retVal = uis.call("showSSLCertDialog", d->host, certs, metaData("window-id").toLongLong()); if (retVal.type() == QDBusMessage::ReplyMessage) { if (retVal.arguments().at(0).toBool()) { send = retVal.arguments().at(1).toBool(); save = retVal.arguments().at(2).toBool(); certname = retVal.arguments().at(3).toString(); } } } // The user may have said to not send the certificate, // but to save the choice if (!send) { if (save) { KSSLCertificateHome::setDefaultCertificate(certname, d->host, false, false); } return; } // We're almost committed. If we can read the cert, we'll send it now. KSSLPKCS12 *pkcs = KSSLCertificateHome::getCertificateByName(certname); if (!pkcs && KSSLCertificateHome::hasCertificateByName(certname)) { // We need the password KIO::AuthInfo ai; bool first = true; do { ai.prompt = i18n("Enter the certificate password:"); ai.caption = i18n("SSL Certificate Password"); ai.url.setScheme("kssl"); ai.url.setHost(certname); ai.username = certname; ai.keepPassword = true; bool showprompt; if (first) { showprompt = !checkCachedAuthentication(ai); } else { showprompt = true; } if (showprompt) { if (!openPasswordDialog(ai, first ? QString() : i18n("Unable to open the certificate. Try a new password?"))) { break; } } first = false; pkcs = KSSLCertificateHome::getCertificateByName(certname, ai.password); } while (!pkcs); } // If we could open the certificate, let's send it if (pkcs) { if (!d->kssl->setClientCertificate(pkcs)) { messageBox(Information, i18n("The procedure to set the " "client certificate for the session " "failed."), i18n("SSL")); delete pkcs; // we don't need this anymore pkcs = 0L; } else { //qDebug() << "Client SSL certificate is being used."; setMetaData("ssl_using_client_cert", "TRUE"); if (save) { KSSLCertificateHome::setDefaultCertificate(certname, d->host, true, false); } } d->pkcs = pkcs; } #endif } TCPSlaveBase::SslResult TCPSlaveBase::verifyServerCertificate() { d->sslNoUi = hasMetaData(QStringLiteral("ssl_no_ui")) && (metaData(QStringLiteral("ssl_no_ui")) != QLatin1String("FALSE")); if (d->sslErrors.isEmpty()) { return ResultOk; } else if (d->sslNoUi) { return ResultFailed; } QList fatalErrors = KSslCertificateManager::nonIgnorableErrors(d->sslErrors); if (!fatalErrors.isEmpty()) { //TODO message "sorry, fatal error, you can't override it" return ResultFailed; } QList peerCertificationChain = d->socket.peerCertificateChain(); KSslCertificateManager *const cm = KSslCertificateManager::self(); KSslCertificateRule rule = cm->rule(peerCertificationChain.first(), d->host); // remove previously seen and acknowledged errors QList remainingErrors = rule.filterErrors(d->sslErrors); if (remainingErrors.isEmpty()) { //qDebug() << "Error list empty after removing errors to be ignored. Continuing."; return ResultOk | ResultOverridden; } //### We don't ask to permanently reject the certificate QString message = i18n("The server failed the authenticity check (%1).\n\n", d->host); Q_FOREACH (const KSslError &err, d->sslErrors) { message.append(err.errorString()); - message.append('\n'); + message.append(QLatin1Char('\n')); } message = message.trimmed(); int msgResult; QDateTime ruleExpiry = QDateTime::currentDateTime(); do { msgResult = messageBox(WarningYesNoCancel, message, i18n("Server Authentication"), i18n("&Details"), i18n("Co&ntinue")); switch (msgResult) { case SlaveBase::Yes: //Details was chosen- show the certificate and error details messageBox(SSLMessageBox /*the SSL info dialog*/, d->host); break; case SlaveBase::No: { //fall through on SlaveBase::No const int result = messageBox(WarningYesNoCancel, i18n("Would you like to accept this " "certificate forever without " "being prompted?"), i18n("Server Authentication"), i18n("&Forever"), i18n("&Current Session only")); if (result == SlaveBase::Yes) { //accept forever ("for a very long time") ruleExpiry = ruleExpiry.addYears(1000); } else if (result == SlaveBase::No) { //accept "for a short time", half an hour. ruleExpiry = ruleExpiry.addSecs(30*60); } else { msgResult = SlaveBase::Yes; } break; } case SlaveBase::Cancel: return ResultFailed; default: qCWarning(KIO_CORE) << "Unexpected MessageBox response received:" << msgResult; return ResultFailed; } } while (msgResult == SlaveBase::Yes); //TODO special cases for wildcard domain name in the certificate! //rule = KSslCertificateRule(d->socket.peerCertificateChain().first(), whatever); rule.setExpiryDateTime(ruleExpiry); rule.setIgnoredErrors(d->sslErrors); cm->setRule(rule); return ResultOk | ResultOverridden; #if 0 //### need to do something like the old code about the main and subframe stuff //qDebug() << "SSL HTTP frame the parent? " << metaData("main_frame_request"); if (!hasMetaData("main_frame_request") || metaData("main_frame_request") == "TRUE") { // Since we're the parent, we need to teach the child. setMetaData("ssl_parent_ip", d->ip); setMetaData("ssl_parent_cert", pc.toString()); // - Read from cache and see if there is a policy for this KSSLCertificateCache::KSSLCertificatePolicy cp = d->certCache->getPolicyByCertificate(pc); // - validation code if (ksv != KSSLCertificate::Ok) { if (d->sslNoUi) { return -1; } if (cp == KSSLCertificateCache::Unknown || cp == KSSLCertificateCache::Ambiguous) { cp = KSSLCertificateCache::Prompt; } else { // A policy was already set so let's honor that. permacache = d->certCache->isPermanent(pc); } if (!_IPmatchesCN && cp == KSSLCertificateCache::Accept) { cp = KSSLCertificateCache::Prompt; // ksv = KSSLCertificate::Ok; } ////// SNIP SNIP ////////// // - cache the results d->certCache->addCertificate(pc, cp, permacache); if (doAddHost) { d->certCache->addHost(pc, d->host); } } else { // Child frame // - Read from cache and see if there is a policy for this KSSLCertificateCache::KSSLCertificatePolicy cp = d->certCache->getPolicyByCertificate(pc); isChild = true; // Check the cert and IP to make sure they're the same // as the parent frame bool certAndIPTheSame = (d->ip == metaData("ssl_parent_ip") && pc.toString() == metaData("ssl_parent_cert")); if (ksv == KSSLCertificate::Ok) { if (certAndIPTheSame) { // success rc = 1; setMetaData("ssl_action", "accept"); } else { /* if (d->sslNoUi) { return -1; } result = messageBox(WarningYesNo, i18n("The certificate is valid but does not appear to have been assigned to this server. Do you wish to continue loading?"), i18n("Server Authentication")); if (result == SlaveBase::Yes) { // success rc = 1; setMetaData("ssl_action", "accept"); } else { // fail rc = -1; setMetaData("ssl_action", "reject"); } */ setMetaData("ssl_action", "accept"); rc = 1; // Let's accept this now. It's bad, but at least the user // will see potential attacks in KDE3 with the pseudo-lock // icon on the toolbar, and can investigate with the RMB } } else { if (d->sslNoUi) { return -1; } if (cp == KSSLCertificateCache::Accept) { if (certAndIPTheSame) { // success rc = 1; setMetaData("ssl_action", "accept"); } else { // fail result = messageBox(WarningYesNo, i18n("You have indicated that you wish to accept this certificate, but it is not issued to the server who is presenting it. Do you wish to continue loading?"), i18n("Server Authentication")); if (result == SlaveBase::Yes) { rc = 1; setMetaData("ssl_action", "accept"); d->certCache->addHost(pc, d->host); } else { rc = -1; setMetaData("ssl_action", "reject"); } } } else if (cp == KSSLCertificateCache::Reject) { // fail messageBox(Information, i18n("SSL certificate is being rejected as requested. You can disable this in the KDE System Settings."), i18n("Server Authentication")); rc = -1; setMetaData("ssl_action", "reject"); } else { //////// SNIP SNIP ////////// return rc; } } } } #endif //#if 0 } bool TCPSlaveBase::isConnected() const { //QSslSocket::isValid() and therefore KTcpSocket::isValid() are shady... return d->socket.state() == KTcpSocket::ConnectedState; } bool TCPSlaveBase::waitForResponse(int t) { if (d->socket.bytesAvailable()) { return true; } return d->socket.waitForReadyRead(t * 1000); } void TCPSlaveBase::setBlocking(bool b) { if (!b) { qCWarning(KIO_CORE) << "Caller requested non-blocking mode, but that doesn't work"; return; } d->isBlocking = b; } void TCPSlaveBase::virtual_hook(int id, void *data) { if (id == SlaveBase::AppConnectionMade) { d->sendSslMetaData(); } else { SlaveBase::virtual_hook(id, data); } }