File indexing completed on 2024-10-13 03:38:11

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
0004     SPDX-FileCopyrightText: 2000-2009 David Faure <faure@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "listjob.h"
0010 #include "../utils_p.h"
0011 #include "job_p.h"
0012 #include "worker_p.h"
0013 #include <QTimer>
0014 #include <kurlauthorized.h>
0015 
0016 #include <QDebug>
0017 
0018 using namespace KIO;
0019 
0020 class KIO::ListJobPrivate : public KIO::SimpleJobPrivate
0021 {
0022 public:
0023     ListJobPrivate(const QUrl &url, bool _recursive, const QString &prefix, const QString &displayPrefix, ListJob::ListFlags listFlags)
0024         : SimpleJobPrivate(url, CMD_LISTDIR, QByteArray())
0025         , recursive(_recursive)
0026         , listFlags(listFlags)
0027         , m_prefix(prefix)
0028         , m_displayPrefix(displayPrefix)
0029         , m_processedEntries(0)
0030     {
0031     }
0032     bool recursive;
0033     ListJob::ListFlags listFlags;
0034     QString m_prefix;
0035     QString m_displayPrefix;
0036     unsigned long m_processedEntries;
0037     QUrl m_redirectionURL;
0038 
0039     /**
0040      * @internal
0041      * Called by the scheduler when a @p worker gets to
0042      * work on this job.
0043      * @param worker the worker that starts working on this job
0044      */
0045     void start(Worker *worker) override;
0046 
0047     void slotListEntries(const KIO::UDSEntryList &list);
0048     void slotRedirection(const QUrl &url);
0049     void gotEntries(KIO::Job *subjob, const KIO::UDSEntryList &list);
0050     void slotSubError(ListJob *job, ListJob *subJob);
0051 
0052     Q_DECLARE_PUBLIC(ListJob)
0053 
0054     static inline ListJob *
0055     newJob(const QUrl &u, bool _recursive, const QString &prefix, const QString &displayPrefix, ListJob::ListFlags listFlags, JobFlags flags = HideProgressInfo)
0056     {
0057         ListJob *job = new ListJob(*new ListJobPrivate(u, _recursive, prefix, displayPrefix, listFlags));
0058         job->setUiDelegate(KIO::createDefaultJobUiDelegate());
0059         if (!(flags & HideProgressInfo)) {
0060             KIO::getJobTracker()->registerJob(job);
0061         }
0062         return job;
0063     }
0064     static inline ListJob *newJobNoUi(const QUrl &u, bool _recursive, const QString &prefix, const QString &displayPrefix, ListJob::ListFlags listFlags)
0065     {
0066         return new ListJob(*new ListJobPrivate(u, _recursive, prefix, displayPrefix, listFlags));
0067     }
0068 };
0069 
0070 ListJob::ListJob(ListJobPrivate &dd)
0071     : SimpleJob(dd)
0072 {
0073     Q_D(ListJob);
0074     // We couldn't set the args when calling the parent constructor,
0075     // so do it now.
0076     QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly);
0077     stream << d->m_url;
0078 }
0079 
0080 ListJob::~ListJob()
0081 {
0082 }
0083 
0084 void ListJobPrivate::slotListEntries(const KIO::UDSEntryList &list)
0085 {
0086     Q_Q(ListJob);
0087 
0088     const bool includeHidden = listFlags.testFlag(ListJob::ListFlag::IncludeHidden);
0089 
0090     // Emit progress info (takes care of emit processedSize and percent)
0091     m_processedEntries += list.count();
0092     slotProcessedSize(m_processedEntries);
0093 
0094     if (recursive) {
0095         UDSEntryList::ConstIterator it = list.begin();
0096         const UDSEntryList::ConstIterator end = list.end();
0097 
0098         for (; it != end; ++it) {
0099             const UDSEntry &entry = *it;
0100 
0101             QUrl itemURL;
0102             const QString udsUrl = entry.stringValue(KIO::UDSEntry::UDS_URL);
0103             QString filename;
0104             if (!udsUrl.isEmpty()) {
0105                 itemURL = QUrl(udsUrl);
0106                 filename = itemURL.fileName();
0107             } else { // no URL, use the name
0108                 itemURL = q->url();
0109                 filename = entry.stringValue(KIO::UDSEntry::UDS_NAME);
0110                 Q_ASSERT(!filename.isEmpty()); // we'll recurse forever otherwise :)
0111                 itemURL.setPath(Utils::concatPaths(itemURL.path(), filename));
0112             }
0113 
0114             if (entry.isDir() && !entry.isLink()) {
0115                 Q_ASSERT(!filename.isEmpty());
0116                 QString displayName = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
0117                 if (displayName.isEmpty()) {
0118                     displayName = filename;
0119                 }
0120                 // skip hidden dirs when listing if requested
0121                 if (filename != QLatin1String("..") && filename != QLatin1String(".") && (includeHidden || filename[0] != QLatin1Char('.'))) {
0122                     ListJob *job = ListJobPrivate::newJobNoUi(itemURL,
0123                                                               true /*recursive*/,
0124                                                               m_prefix + filename + QLatin1Char('/'),
0125                                                               m_displayPrefix + displayName + QLatin1Char('/'),
0126                                                               listFlags);
0127                     QObject::connect(job, &ListJob::entries, q, [this](KIO::Job *job, const KIO::UDSEntryList &list) {
0128                         gotEntries(job, list);
0129                     });
0130                     QObject::connect(job, &ListJob::subError, q, [this](KIO::ListJob *job, KIO::ListJob *ljob) {
0131                         slotSubError(job, ljob);
0132                     });
0133 
0134                     q->addSubjob(job);
0135                 }
0136             }
0137         }
0138     }
0139 
0140     // Not recursive, or top-level of recursive listing : return now (send . and .. as well)
0141     // exclusion of hidden files also requires the full sweep, but the case for full-listing
0142     // a single dir is probably common enough to justify the shortcut
0143     if (m_prefix.isNull() && includeHidden) {
0144         Q_EMIT q->entries(q, list);
0145     } else {
0146         UDSEntryList newlist = list;
0147 
0148         auto removeFunc = [this, includeHidden](const UDSEntry &entry) {
0149             const QString filename = entry.stringValue(KIO::UDSEntry::UDS_NAME);
0150             // Avoid returning entries like subdir/. and subdir/.., but include . and .. for
0151             // the toplevel dir, and skip hidden files/dirs if that was requested
0152             const bool shouldEmit = (m_prefix.isNull() || (filename != QLatin1String("..") && filename != QLatin1String(".")))
0153                 && (includeHidden || (filename[0] != QLatin1Char('.')));
0154             return !shouldEmit;
0155         };
0156         newlist.erase(std::remove_if(newlist.begin(), newlist.end(), removeFunc), newlist.end());
0157 
0158         for (UDSEntry &newone : newlist) {
0159             // Modify the name in the UDSEntry
0160             const QString filename = newone.stringValue(KIO::UDSEntry::UDS_NAME);
0161             QString displayName = newone.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
0162             if (displayName.isEmpty()) {
0163                 displayName = filename;
0164             }
0165 
0166             // ## Didn't find a way to use the iterator instead of re-doing a key lookup
0167             newone.replace(KIO::UDSEntry::UDS_NAME, m_prefix + filename);
0168             newone.replace(KIO::UDSEntry::UDS_DISPLAY_NAME, m_displayPrefix + displayName);
0169         }
0170 
0171         Q_EMIT q->entries(q, newlist);
0172     }
0173 }
0174 
0175 void ListJobPrivate::gotEntries(KIO::Job *, const KIO::UDSEntryList &list)
0176 {
0177     // Forward entries received by subjob - faking we received them ourselves
0178     Q_Q(ListJob);
0179     Q_EMIT q->entries(q, list);
0180 }
0181 
0182 void ListJobPrivate::slotSubError(KIO::ListJob * /*job*/, KIO::ListJob *subJob)
0183 {
0184     Q_Q(ListJob);
0185     Q_EMIT q->subError(q, subJob); // Let the signal of subError go up
0186 }
0187 
0188 void ListJob::slotResult(KJob *job)
0189 {
0190     Q_D(ListJob);
0191     if (job->error()) {
0192         // If we can't list a subdir, the result is still ok
0193         // This is why we override KCompositeJob::slotResult - to not set
0194         // an error on parent job.
0195         // Let's emit a signal about this though
0196         Q_EMIT subError(this, static_cast<KIO::ListJob *>(job));
0197     }
0198     removeSubjob(job);
0199     if (!hasSubjobs() && !d->m_worker) { // if the main directory listing is still running, it will emit result in SimpleJob::slotFinished()
0200         emitResult();
0201     }
0202 }
0203 
0204 void ListJobPrivate::slotRedirection(const QUrl &url)
0205 {
0206     Q_Q(ListJob);
0207     if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("redirect"), m_url, url)) {
0208         qCWarning(KIO_CORE) << "Redirection from" << m_url << "to" << url << "REJECTED!";
0209         return;
0210     }
0211     m_redirectionURL = url; // We'll remember that when the job finishes
0212     Q_EMIT q->redirection(q, m_redirectionURL);
0213 }
0214 
0215 void ListJob::slotFinished()
0216 {
0217     Q_D(ListJob);
0218 
0219     if (!d->m_redirectionURL.isEmpty() && d->m_redirectionURL.isValid() && !error()) {
0220         // qDebug() << "Redirection to " << d->m_redirectionURL;
0221         if (queryMetaData(QStringLiteral("permanent-redirect")) == QLatin1String("true")) {
0222             Q_EMIT permanentRedirection(this, d->m_url, d->m_redirectionURL);
0223         }
0224 
0225         if (d->m_redirectionHandlingEnabled) {
0226             d->m_packedArgs.truncate(0);
0227             QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly);
0228             stream << d->m_redirectionURL;
0229 
0230             d->restartAfterRedirection(&d->m_redirectionURL);
0231             return;
0232         }
0233     }
0234 
0235     // Return worker to the scheduler
0236     SimpleJob::slotFinished();
0237 }
0238 
0239 ListJob *KIO::listDir(const QUrl &url, JobFlags flags, ListJob::ListFlags listFlags)
0240 {
0241     return ListJobPrivate::newJob(url, false, QString(), QString(), listFlags, flags);
0242 }
0243 
0244 ListJob *KIO::listRecursive(const QUrl &url, JobFlags flags, ListJob::ListFlags listFlags)
0245 {
0246     return ListJobPrivate::newJob(url, true, QString(), QString(), listFlags, flags);
0247 }
0248 
0249 void ListJob::setUnrestricted(bool unrestricted)
0250 {
0251     Q_D(ListJob);
0252     if (unrestricted) {
0253         d->m_extraFlags |= JobPrivate::EF_ListJobUnrestricted;
0254     } else {
0255         d->m_extraFlags &= ~JobPrivate::EF_ListJobUnrestricted;
0256     }
0257 }
0258 
0259 void ListJobPrivate::start(Worker *worker)
0260 {
0261     Q_Q(ListJob);
0262     if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("list"), m_url, m_url) && !(m_extraFlags & EF_ListJobUnrestricted)) {
0263         q->setError(ERR_ACCESS_DENIED);
0264         q->setErrorText(m_url.toDisplayString());
0265         QTimer::singleShot(0, q, &ListJob::slotFinished);
0266         return;
0267     }
0268     QObject::connect(worker, &Worker::listEntries, q, [this](const KIO::UDSEntryList &list) {
0269         slotListEntries(list);
0270     });
0271 
0272     QObject::connect(worker, &Worker::totalSize, q, [this](KIO::filesize_t size) {
0273         slotTotalSize(size);
0274     });
0275 
0276     QObject::connect(worker, &Worker::redirection, q, [this](const QUrl &url) {
0277         slotRedirection(url);
0278     });
0279 
0280     SimpleJobPrivate::start(worker);
0281 }
0282 
0283 const QUrl &ListJob::redirectionUrl() const
0284 {
0285     return d_func()->m_redirectionURL;
0286 }
0287 
0288 #include "moc_listjob.cpp"