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