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