File indexing completed on 2024-04-28 15:26:29

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 "slave.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, bool _includeHidden)
0024         : SimpleJobPrivate(url, CMD_LISTDIR, QByteArray())
0025         , recursive(_recursive)
0026         , includeHidden(_includeHidden)
0027         , m_prefix(prefix)
0028         , m_displayPrefix(displayPrefix)
0029         , m_processedEntries(0)
0030     {
0031     }
0032     bool recursive;
0033     bool includeHidden;
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 slave gets to
0042      * work on this job.
0043      * @param slave the slave that starts working on this job
0044      */
0045     void start(Slave *slave) 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, bool _includeHidden, JobFlags flags = HideProgressInfo)
0056     {
0057         ListJob *job = new ListJob(*new ListJobPrivate(u, _recursive, prefix, displayPrefix, _includeHidden));
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, bool _includeHidden)
0065     {
0066         return new ListJob(*new ListJobPrivate(u, _recursive, prefix, displayPrefix, _includeHidden));
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     // Emit progress info (takes care of emit processedSize and percent)
0088     m_processedEntries += list.count();
0089     slotProcessedSize(m_processedEntries);
0090 
0091     if (recursive) {
0092         UDSEntryList::ConstIterator it = list.begin();
0093         const UDSEntryList::ConstIterator end = list.end();
0094 
0095         for (; it != end; ++it) {
0096             const UDSEntry &entry = *it;
0097 
0098             QUrl itemURL;
0099             const QString udsUrl = entry.stringValue(KIO::UDSEntry::UDS_URL);
0100             QString filename;
0101             if (!udsUrl.isEmpty()) {
0102                 itemURL = QUrl(udsUrl);
0103                 filename = itemURL.fileName();
0104             } else { // no URL, use the name
0105                 itemURL = q->url();
0106                 filename = entry.stringValue(KIO::UDSEntry::UDS_NAME);
0107                 Q_ASSERT(!filename.isEmpty()); // we'll recurse forever otherwise :)
0108                 itemURL.setPath(Utils::concatPaths(itemURL.path(), filename));
0109             }
0110 
0111             if (entry.isDir() && !entry.isLink()) {
0112                 Q_ASSERT(!filename.isEmpty());
0113                 QString displayName = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
0114                 if (displayName.isEmpty()) {
0115                     displayName = filename;
0116                 }
0117                 // skip hidden dirs when listing if requested
0118                 if (filename != QLatin1String("..") && filename != QLatin1String(".") && (includeHidden || filename[0] != QLatin1Char('.'))) {
0119                     ListJob *job = ListJobPrivate::newJobNoUi(itemURL,
0120                                                               true /*recursive*/,
0121                                                               m_prefix + filename + QLatin1Char('/'),
0122                                                               m_displayPrefix + displayName + QLatin1Char('/'),
0123                                                               includeHidden);
0124                     QObject::connect(job, &ListJob::entries, q, [this](KIO::Job *job, const KIO::UDSEntryList &list) {
0125                         gotEntries(job, list);
0126                     });
0127                     QObject::connect(job, &ListJob::subError, q, [this](KIO::ListJob *job, KIO::ListJob *ljob) {
0128                         slotSubError(job, ljob);
0129                     });
0130 
0131                     q->addSubjob(job);
0132                 }
0133             }
0134         }
0135     }
0136 
0137     // Not recursive, or top-level of recursive listing : return now (send . and .. as well)
0138     // exclusion of hidden files also requires the full sweep, but the case for full-listing
0139     // a single dir is probably common enough to justify the shortcut
0140     if (m_prefix.isNull() && includeHidden) {
0141         Q_EMIT q->entries(q, list);
0142     } else {
0143         UDSEntryList newlist = list;
0144 
0145         auto removeFunc = [this](const UDSEntry &entry) {
0146             const QString filename = entry.stringValue(KIO::UDSEntry::UDS_NAME);
0147             // Avoid returning entries like subdir/. and subdir/.., but include . and .. for
0148             // the toplevel dir, and skip hidden files/dirs if that was requested
0149             const bool shouldEmit = (m_prefix.isNull() || (filename != QLatin1String("..") && filename != QLatin1String(".")))
0150                 && (includeHidden || (filename[0] != QLatin1Char('.')));
0151             return !shouldEmit;
0152         };
0153         newlist.erase(std::remove_if(newlist.begin(), newlist.end(), removeFunc), newlist.end());
0154 
0155         for (UDSEntry &newone : newlist) {
0156             // Modify the name in the UDSEntry
0157             const QString filename = newone.stringValue(KIO::UDSEntry::UDS_NAME);
0158             QString displayName = newone.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
0159             if (displayName.isEmpty()) {
0160                 displayName = filename;
0161             }
0162 
0163             // ## Didn't find a way to use the iterator instead of re-doing a key lookup
0164             newone.replace(KIO::UDSEntry::UDS_NAME, m_prefix + filename);
0165             newone.replace(KIO::UDSEntry::UDS_DISPLAY_NAME, m_displayPrefix + displayName);
0166         }
0167 
0168         Q_EMIT q->entries(q, newlist);
0169     }
0170 }
0171 
0172 void ListJobPrivate::gotEntries(KIO::Job *, const KIO::UDSEntryList &list)
0173 {
0174     // Forward entries received by subjob - faking we received them ourselves
0175     Q_Q(ListJob);
0176     Q_EMIT q->entries(q, list);
0177 }
0178 
0179 void ListJobPrivate::slotSubError(KIO::ListJob * /*job*/, KIO::ListJob *subJob)
0180 {
0181     Q_Q(ListJob);
0182     Q_EMIT q->subError(q, subJob); // Let the signal of subError go up
0183 }
0184 
0185 void ListJob::slotResult(KJob *job)
0186 {
0187     Q_D(ListJob);
0188     if (job->error()) {
0189         // If we can't list a subdir, the result is still ok
0190         // This is why we override KCompositeJob::slotResult - to not set
0191         // an error on parent job.
0192         // Let's emit a signal about this though
0193         Q_EMIT subError(this, static_cast<KIO::ListJob *>(job));
0194     }
0195     removeSubjob(job);
0196     if (!hasSubjobs() && !d->m_slave) { // if the main directory listing is still running, it will emit result in SimpleJob::slotFinished()
0197         emitResult();
0198     }
0199 }
0200 
0201 void ListJobPrivate::slotRedirection(const QUrl &url)
0202 {
0203     Q_Q(ListJob);
0204     if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("redirect"), m_url, url)) {
0205         qCWarning(KIO_CORE) << "Redirection from" << m_url << "to" << url << "REJECTED!";
0206         return;
0207     }
0208     m_redirectionURL = url; // We'll remember that when the job finishes
0209     Q_EMIT q->redirection(q, m_redirectionURL);
0210 }
0211 
0212 void ListJob::slotFinished()
0213 {
0214     Q_D(ListJob);
0215 
0216     if (!d->m_redirectionURL.isEmpty() && d->m_redirectionURL.isValid() && !error()) {
0217         // qDebug() << "Redirection to " << d->m_redirectionURL;
0218         if (queryMetaData(QStringLiteral("permanent-redirect")) == QLatin1String("true")) {
0219             Q_EMIT permanentRedirection(this, d->m_url, d->m_redirectionURL);
0220         }
0221 
0222         if (d->m_redirectionHandlingEnabled) {
0223             d->m_packedArgs.truncate(0);
0224             QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly);
0225             stream << d->m_redirectionURL;
0226 
0227             d->restartAfterRedirection(&d->m_redirectionURL);
0228             return;
0229         }
0230     }
0231 
0232     // Return slave to the scheduler
0233     SimpleJob::slotFinished();
0234 }
0235 
0236 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 101)
0237 void ListJob::slotMetaData(const KIO::MetaData &_metaData)
0238 {
0239     SimpleJob::slotMetaData(_metaData);
0240 }
0241 #endif
0242 
0243 ListJob *KIO::listDir(const QUrl &url, JobFlags flags, bool includeHidden)
0244 {
0245     return ListJobPrivate::newJob(url, false, QString(), QString(), includeHidden, flags);
0246 }
0247 
0248 ListJob *KIO::listRecursive(const QUrl &url, JobFlags flags, bool includeHidden)
0249 {
0250     return ListJobPrivate::newJob(url, true, QString(), QString(), includeHidden, flags);
0251 }
0252 
0253 void ListJob::setUnrestricted(bool unrestricted)
0254 {
0255     Q_D(ListJob);
0256     if (unrestricted) {
0257         d->m_extraFlags |= JobPrivate::EF_ListJobUnrestricted;
0258     } else {
0259         d->m_extraFlags &= ~JobPrivate::EF_ListJobUnrestricted;
0260     }
0261 }
0262 
0263 void ListJobPrivate::start(Slave *slave)
0264 {
0265     Q_Q(ListJob);
0266     if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("list"), m_url, m_url) && !(m_extraFlags & EF_ListJobUnrestricted)) {
0267         q->setError(ERR_ACCESS_DENIED);
0268         q->setErrorText(m_url.toDisplayString());
0269         QTimer::singleShot(0, q, &ListJob::slotFinished);
0270         return;
0271     }
0272     QObject::connect(slave, &Slave::listEntries, q, [this](const KIO::UDSEntryList &list) {
0273         slotListEntries(list);
0274     });
0275 
0276     QObject::connect(slave, &Slave::totalSize, q, [this](KIO::filesize_t size) {
0277         slotTotalSize(size);
0278     });
0279 
0280     QObject::connect(slave, &Slave::redirection, q, [this](const QUrl &url) {
0281         slotRedirection(url);
0282     });
0283 
0284     SimpleJobPrivate::start(slave);
0285 }
0286 
0287 const QUrl &ListJob::redirectionUrl() const
0288 {
0289     return d_func()->m_redirectionURL;
0290 }
0291 
0292 #include "moc_listjob.cpp"