1 /*
2     This file is part of the KDE libraries
3     SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
4     SPDX-FileCopyrightText: 2000-2009 David Faure <faure@kde.org>
5 
6     SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 
9 #include "listjob.h"
10 #include "../pathhelpers_p.h"
11 #include "job_p.h"
12 #include "scheduler.h"
13 #include "slave.h"
14 #include <QTimer>
15 #include <kurlauthorized.h>
16 
17 #include <QDebug>
18 
19 using namespace KIO;
20 
21 class KIO::ListJobPrivate : public KIO::SimpleJobPrivate
22 {
23 public:
ListJobPrivate(const QUrl & url,bool _recursive,const QString & prefix,const QString & displayPrefix,bool _includeHidden)24     ListJobPrivate(const QUrl &url, bool _recursive, const QString &prefix, const QString &displayPrefix, bool _includeHidden)
25         : SimpleJobPrivate(url, CMD_LISTDIR, QByteArray())
26         , recursive(_recursive)
27         , includeHidden(_includeHidden)
28         , m_prefix(prefix)
29         , m_displayPrefix(displayPrefix)
30         , m_processedEntries(0)
31     {
32     }
33     bool recursive;
34     bool includeHidden;
35     QString m_prefix;
36     QString m_displayPrefix;
37     unsigned long m_processedEntries;
38     QUrl m_redirectionURL;
39 
40     /**
41      * @internal
42      * Called by the scheduler when a @p slave gets to
43      * work on this job.
44      * @param slave the slave that starts working on this job
45      */
46     void start(Slave *slave) override;
47 
48     void slotListEntries(const KIO::UDSEntryList &list);
49     void slotRedirection(const QUrl &url);
50     void gotEntries(KIO::Job *subjob, const KIO::UDSEntryList &list);
51     void slotSubError(ListJob *job, ListJob *subJob);
52 
Q_DECLARE_PUBLIC(ListJob)53     Q_DECLARE_PUBLIC(ListJob)
54 
55     static inline ListJob *
56     newJob(const QUrl &u, bool _recursive, const QString &prefix, const QString &displayPrefix, bool _includeHidden, JobFlags flags = HideProgressInfo)
57     {
58         ListJob *job = new ListJob(*new ListJobPrivate(u, _recursive, prefix, displayPrefix, _includeHidden));
59         job->setUiDelegate(KIO::createDefaultJobUiDelegate());
60         if (!(flags & HideProgressInfo)) {
61             KIO::getJobTracker()->registerJob(job);
62         }
63         return job;
64     }
newJobNoUi(const QUrl & u,bool _recursive,const QString & prefix,const QString & displayPrefix,bool _includeHidden)65     static inline ListJob *newJobNoUi(const QUrl &u, bool _recursive, const QString &prefix, const QString &displayPrefix, bool _includeHidden)
66     {
67         return new ListJob(*new ListJobPrivate(u, _recursive, prefix, displayPrefix, _includeHidden));
68     }
69 };
70 
ListJob(ListJobPrivate & dd)71 ListJob::ListJob(ListJobPrivate &dd)
72     : SimpleJob(dd)
73 {
74     Q_D(ListJob);
75     // We couldn't set the args when calling the parent constructor,
76     // so do it now.
77     QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly);
78     stream << d->m_url;
79 }
80 
~ListJob()81 ListJob::~ListJob()
82 {
83 }
84 
slotListEntries(const KIO::UDSEntryList & list)85 void ListJobPrivate::slotListEntries(const KIO::UDSEntryList &list)
86 {
87     Q_Q(ListJob);
88     // Emit progress info (takes care of emit processedSize and percent)
89     m_processedEntries += list.count();
90     slotProcessedSize(m_processedEntries);
91 
92     if (recursive) {
93         UDSEntryList::ConstIterator it = list.begin();
94         const UDSEntryList::ConstIterator end = list.end();
95 
96         for (; it != end; ++it) {
97             const UDSEntry &entry = *it;
98 
99             QUrl itemURL;
100             const QString udsUrl = entry.stringValue(KIO::UDSEntry::UDS_URL);
101             QString filename;
102             if (!udsUrl.isEmpty()) {
103                 itemURL = QUrl(udsUrl);
104                 filename = itemURL.fileName();
105             } else { // no URL, use the name
106                 itemURL = q->url();
107                 filename = entry.stringValue(KIO::UDSEntry::UDS_NAME);
108                 Q_ASSERT(!filename.isEmpty()); // we'll recurse forever otherwise :)
109                 itemURL.setPath(concatPaths(itemURL.path(), filename));
110             }
111 
112             if (entry.isDir() && !entry.isLink()) {
113                 Q_ASSERT(!filename.isEmpty());
114                 QString displayName = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
115                 if (displayName.isEmpty()) {
116                     displayName = filename;
117                 }
118                 // skip hidden dirs when listing if requested
119                 if (filename != QLatin1String("..") && filename != QLatin1String(".") && (includeHidden || filename[0] != QLatin1Char('.'))) {
120                     ListJob *job = ListJobPrivate::newJobNoUi(itemURL,
121                                                               true /*recursive*/,
122                                                               m_prefix + filename + QLatin1Char('/'),
123                                                               m_displayPrefix + displayName + QLatin1Char('/'),
124                                                               includeHidden);
125                     Scheduler::setJobPriority(job, 1);
126                     QObject::connect(job, &ListJob::entries, q, [this](KIO::Job *job, const KIO::UDSEntryList &list) {
127                         gotEntries(job, list);
128                     });
129                     QObject::connect(job, &ListJob::subError, q, [this](KIO::ListJob *job, KIO::ListJob *ljob) {
130                         slotSubError(job, ljob);
131                     });
132 
133                     q->addSubjob(job);
134                 }
135             }
136         }
137     }
138 
139     // Not recursive, or top-level of recursive listing : return now (send . and .. as well)
140     // exclusion of hidden files also requires the full sweep, but the case for full-listing
141     // a single dir is probably common enough to justify the shortcut
142     if (m_prefix.isNull() && includeHidden) {
143         Q_EMIT q->entries(q, list);
144     } else {
145         UDSEntryList newlist = list;
146 
147         auto removeFunc = [this](const UDSEntry &entry) {
148             const QString filename = entry.stringValue(KIO::UDSEntry::UDS_NAME);
149             // Avoid returning entries like subdir/. and subdir/.., but include . and .. for
150             // the toplevel dir, and skip hidden files/dirs if that was requested
151             const bool shouldEmit = (m_prefix.isNull() || (filename != QLatin1String("..") && filename != QLatin1String(".")))
152                 && (includeHidden || (filename[0] != QLatin1Char('.')));
153             return !shouldEmit;
154         };
155         newlist.erase(std::remove_if(newlist.begin(), newlist.end(), removeFunc), newlist.end());
156 
157         for (UDSEntry &newone : newlist) {
158             // Modify the name in the UDSEntry
159             const QString filename = newone.stringValue(KIO::UDSEntry::UDS_NAME);
160             QString displayName = newone.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
161             if (displayName.isEmpty()) {
162                 displayName = filename;
163             }
164 
165             // ## Didn't find a way to use the iterator instead of re-doing a key lookup
166             newone.replace(KIO::UDSEntry::UDS_NAME, m_prefix + filename);
167             newone.replace(KIO::UDSEntry::UDS_DISPLAY_NAME, m_displayPrefix + displayName);
168         }
169 
170         Q_EMIT q->entries(q, newlist);
171     }
172 }
173 
gotEntries(KIO::Job *,const KIO::UDSEntryList & list)174 void ListJobPrivate::gotEntries(KIO::Job *, const KIO::UDSEntryList &list)
175 {
176     // Forward entries received by subjob - faking we received them ourselves
177     Q_Q(ListJob);
178     Q_EMIT q->entries(q, list);
179 }
180 
slotSubError(KIO::ListJob *,KIO::ListJob * subJob)181 void ListJobPrivate::slotSubError(KIO::ListJob * /*job*/, KIO::ListJob *subJob)
182 {
183     Q_Q(ListJob);
184     Q_EMIT q->subError(q, subJob); // Let the signal of subError go up
185 }
186 
slotResult(KJob * job)187 void ListJob::slotResult(KJob *job)
188 {
189     Q_D(ListJob);
190     if (job->error()) {
191         // If we can't list a subdir, the result is still ok
192         // This is why we override KCompositeJob::slotResult - to not set
193         // an error on parent job.
194         // Let's emit a signal about this though
195         Q_EMIT subError(this, static_cast<KIO::ListJob *>(job));
196     }
197     removeSubjob(job);
198     if (!hasSubjobs() && !d->m_slave) { // if the main directory listing is still running, it will emit result in SimpleJob::slotFinished()
199         emitResult();
200     }
201 }
202 
slotRedirection(const QUrl & url)203 void ListJobPrivate::slotRedirection(const QUrl &url)
204 {
205     Q_Q(ListJob);
206     if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("redirect"), m_url, url)) {
207         qCWarning(KIO_CORE) << "Redirection from" << m_url << "to" << url << "REJECTED!";
208         return;
209     }
210     m_redirectionURL = url; // We'll remember that when the job finishes
211     Q_EMIT q->redirection(q, m_redirectionURL);
212 }
213 
slotFinished()214 void ListJob::slotFinished()
215 {
216     Q_D(ListJob);
217 
218     if (!d->m_redirectionURL.isEmpty() && d->m_redirectionURL.isValid() && !error()) {
219         // qDebug() << "Redirection to " << d->m_redirectionURL;
220         if (queryMetaData(QStringLiteral("permanent-redirect")) == QLatin1String("true")) {
221             Q_EMIT permanentRedirection(this, d->m_url, d->m_redirectionURL);
222         }
223 
224         if (d->m_redirectionHandlingEnabled) {
225             d->m_packedArgs.truncate(0);
226             QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly);
227             stream << d->m_redirectionURL;
228 
229             d->restartAfterRedirection(&d->m_redirectionURL);
230             return;
231         }
232     }
233 
234     // Return slave to the scheduler
235     SimpleJob::slotFinished();
236 }
237 
slotMetaData(const KIO::MetaData & _metaData)238 void ListJob::slotMetaData(const KIO::MetaData &_metaData)
239 {
240     Q_D(ListJob);
241     SimpleJob::slotMetaData(_metaData);
242     storeSSLSessionFromJob(d->m_redirectionURL);
243 }
244 
listDir(const QUrl & url,JobFlags flags,bool includeHidden)245 ListJob *KIO::listDir(const QUrl &url, JobFlags flags, bool includeHidden)
246 {
247     return ListJobPrivate::newJob(url, false, QString(), QString(), includeHidden, flags);
248 }
249 
listRecursive(const QUrl & url,JobFlags flags,bool includeHidden)250 ListJob *KIO::listRecursive(const QUrl &url, JobFlags flags, bool includeHidden)
251 {
252     return ListJobPrivate::newJob(url, true, QString(), QString(), includeHidden, flags);
253 }
254 
setUnrestricted(bool unrestricted)255 void ListJob::setUnrestricted(bool unrestricted)
256 {
257     Q_D(ListJob);
258     if (unrestricted) {
259         d->m_extraFlags |= JobPrivate::EF_ListJobUnrestricted;
260     } else {
261         d->m_extraFlags &= ~JobPrivate::EF_ListJobUnrestricted;
262     }
263 }
264 
start(Slave * slave)265 void ListJobPrivate::start(Slave *slave)
266 {
267     Q_Q(ListJob);
268     if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("list"), m_url, m_url) && !(m_extraFlags & EF_ListJobUnrestricted)) {
269         q->setError(ERR_ACCESS_DENIED);
270         q->setErrorText(m_url.toDisplayString());
271         QTimer::singleShot(0, q, &ListJob::slotFinished);
272         return;
273     }
274     QObject::connect(slave, &Slave::listEntries, q, [this](const KIO::UDSEntryList &list) {
275         slotListEntries(list);
276     });
277 
278     QObject::connect(slave, &Slave::totalSize, q, [this](KIO::filesize_t size) {
279         slotTotalSize(size);
280     });
281 
282     QObject::connect(slave, &Slave::redirection, q, [this](const QUrl &url) {
283         slotRedirection(url);
284     });
285 
286     SimpleJobPrivate::start(slave);
287 }
288 
redirectionUrl() const289 const QUrl &ListJob::redirectionUrl() const
290 {
291     return d_func()->m_redirectionURL;
292 }
293 
294 #include "moc_listjob.cpp"
295