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 "storedtransferjob.h" |
10 | #include "job_p.h" |
11 | #include <KConfig> |
12 | #include <KConfigGroup> |
13 | #include <QTimer> |
14 | #include <kurlauthorized.h> |
15 | |
16 | using namespace KIO; |
17 | |
18 | class KIO::StoredTransferJobPrivate : public TransferJobPrivate |
19 | { |
20 | public: |
21 | StoredTransferJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs, const QByteArray &_staticData) |
22 | : TransferJobPrivate(url, command, packedArgs, _staticData) |
23 | , m_uploadOffset(0) |
24 | { |
25 | } |
26 | StoredTransferJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs, QIODevice *ioDevice) |
27 | : TransferJobPrivate(url, command, packedArgs, ioDevice) |
28 | , m_uploadOffset(0) |
29 | { |
30 | } |
31 | |
32 | QByteArray m_data; |
33 | int m_uploadOffset; |
34 | |
35 | void slotStoredData(KIO::Job *job, const QByteArray &data); |
36 | void slotStoredDataReq(KIO::Job *job, QByteArray &data); |
37 | |
38 | Q_DECLARE_PUBLIC(StoredTransferJob) |
39 | |
40 | static inline StoredTransferJob *newJob(const QUrl &url, int command, const QByteArray &packedArgs, const QByteArray &staticData, JobFlags flags) |
41 | { |
42 | StoredTransferJob *job = new StoredTransferJob(*new StoredTransferJobPrivate(url, command, packedArgs, staticData)); |
43 | job->setUiDelegate(KIO::createDefaultJobUiDelegate()); |
44 | if (!(flags & HideProgressInfo)) { |
45 | job->setFinishedNotificationHidden(); |
46 | KIO::getJobTracker()->registerJob(job); |
47 | } |
48 | if (!(flags & NoPrivilegeExecution)) { |
49 | job->d_func()->m_privilegeExecutionEnabled = true; |
50 | job->d_func()->m_operationType = Transfer; |
51 | } |
52 | return job; |
53 | } |
54 | |
55 | static inline StoredTransferJob *newJob(const QUrl &url, int command, const QByteArray &packedArgs, QIODevice *ioDevice, JobFlags flags) |
56 | { |
57 | StoredTransferJob *job = new StoredTransferJob(*new StoredTransferJobPrivate(url, command, packedArgs, ioDevice)); |
58 | job->setUiDelegate(KIO::createDefaultJobUiDelegate()); |
59 | if (!(flags & HideProgressInfo)) { |
60 | job->setFinishedNotificationHidden(); |
61 | KIO::getJobTracker()->registerJob(job); |
62 | } |
63 | if (!(flags & NoPrivilegeExecution)) { |
64 | job->d_func()->m_privilegeExecutionEnabled = true; |
65 | job->d_func()->m_operationType = Transfer; |
66 | } |
67 | return job; |
68 | } |
69 | }; |
70 | |
71 | StoredTransferJob::StoredTransferJob(StoredTransferJobPrivate &dd) |
72 | : TransferJob(dd) |
73 | { |
74 | connect(sender: this, signal: &TransferJob::data, context: this, slot: [this](KIO::Job *job, const QByteArray &data) { |
75 | d_func()->slotStoredData(job, data); |
76 | }); |
77 | connect(sender: this, signal: &TransferJob::dataReq, context: this, slot: [this](KIO::Job *job, QByteArray &data) { |
78 | d_func()->slotStoredDataReq(job, data); |
79 | }); |
80 | } |
81 | |
82 | StoredTransferJob::~StoredTransferJob() |
83 | { |
84 | } |
85 | |
86 | void StoredTransferJob::setData(const QByteArray &arr) |
87 | { |
88 | Q_D(StoredTransferJob); |
89 | Q_ASSERT(d->m_data.isNull()); // check that we're only called once |
90 | Q_ASSERT(d->m_uploadOffset == 0); // no upload started yet |
91 | d->m_data = arr; |
92 | setTotalSize(d->m_data.size()); |
93 | } |
94 | |
95 | QByteArray StoredTransferJob::data() const |
96 | { |
97 | return d_func()->m_data; |
98 | } |
99 | |
100 | void StoredTransferJobPrivate::slotStoredData(KIO::Job *, const QByteArray &data) |
101 | { |
102 | // check for end-of-data marker: |
103 | if (data.size() == 0) { |
104 | return; |
105 | } |
106 | unsigned int oldSize = m_data.size(); |
107 | m_data.resize(size: oldSize + data.size()); |
108 | memcpy(dest: m_data.data() + oldSize, src: data.data(), n: data.size()); |
109 | } |
110 | |
111 | void StoredTransferJobPrivate::slotStoredDataReq(KIO::Job *, QByteArray &data) |
112 | { |
113 | // Inspired from kmail's KMKernel::byteArrayToRemoteFile |
114 | // send the data in 64 KB chunks |
115 | const int MAX_CHUNK_SIZE = 64 * 1024; |
116 | int remainingBytes = m_data.size() - m_uploadOffset; |
117 | if (remainingBytes > MAX_CHUNK_SIZE) { |
118 | // send MAX_CHUNK_SIZE bytes to the receiver (deep copy) |
119 | data = QByteArray(m_data.data() + m_uploadOffset, MAX_CHUNK_SIZE); |
120 | m_uploadOffset += MAX_CHUNK_SIZE; |
121 | // qDebug() << "Sending " << MAX_CHUNK_SIZE << " bytes (" |
122 | // << remainingBytes - MAX_CHUNK_SIZE << " bytes remain)\n"; |
123 | } else { |
124 | // send the remaining bytes to the receiver (deep copy) |
125 | data = QByteArray(m_data.data() + m_uploadOffset, remainingBytes); |
126 | m_data = QByteArray(); |
127 | m_uploadOffset = 0; |
128 | // qDebug() << "Sending " << remainingBytes << " bytes\n"; |
129 | } |
130 | } |
131 | |
132 | StoredTransferJob *KIO::storedGet(const QUrl &url, LoadType reload, JobFlags flags) |
133 | { |
134 | // Send decoded path and encoded query |
135 | KIO_ARGS << url; |
136 | StoredTransferJob *job = StoredTransferJobPrivate::newJob(url, command: CMD_GET, packedArgs, staticData: QByteArray(), flags); |
137 | if (reload == Reload) { |
138 | job->addMetaData(QStringLiteral("cache" ), QStringLiteral("reload" )); |
139 | } |
140 | return job; |
141 | } |
142 | |
143 | StoredTransferJob *KIO::storedPut(const QByteArray &arr, const QUrl &url, int permissions, JobFlags flags) |
144 | { |
145 | KIO_ARGS << url << qint8((flags & Overwrite) ? 1 : 0) << qint8((flags & Resume) ? 1 : 0) << permissions; |
146 | StoredTransferJob *job = StoredTransferJobPrivate::newJob(url, command: CMD_PUT, packedArgs, staticData: QByteArray(), flags); |
147 | job->setData(arr); |
148 | return job; |
149 | } |
150 | |
151 | StoredTransferJob *KIO::storedPut(QIODevice *input, const QUrl &url, int permissions, JobFlags flags) |
152 | { |
153 | Q_ASSERT(input && input->isReadable()); |
154 | KIO_ARGS << url << qint8((flags & Overwrite) ? 1 : 0) << qint8((flags & Resume) ? 1 : 0) << permissions; |
155 | StoredTransferJob *job = StoredTransferJobPrivate::newJob(url, command: CMD_PUT, packedArgs, ioDevice: input, flags); |
156 | if (!input->isSequential()) { |
157 | job->setTotalSize(input->size()); |
158 | } |
159 | return job; |
160 | } |
161 | |
162 | namespace KIO |
163 | { |
164 | class PostErrorJob : public StoredTransferJob |
165 | { |
166 | Q_OBJECT |
167 | public: |
168 | PostErrorJob(int _error, const QString &url, const QByteArray &packedArgs, const QByteArray &postData) |
169 | : StoredTransferJob(*new StoredTransferJobPrivate(QUrl(), CMD_SPECIAL, packedArgs, postData)) |
170 | { |
171 | setError(_error); |
172 | setErrorText(url); |
173 | } |
174 | |
175 | PostErrorJob(int _error, const QString &url, const QByteArray &packedArgs, QIODevice *ioDevice) |
176 | : StoredTransferJob(*new StoredTransferJobPrivate(QUrl(), CMD_SPECIAL, packedArgs, ioDevice)) |
177 | { |
178 | setError(_error); |
179 | setErrorText(url); |
180 | } |
181 | }; |
182 | } |
183 | |
184 | static int isUrlPortBad(const QUrl &url) |
185 | { |
186 | int _error = 0; |
187 | |
188 | // filter out some malicious ports |
189 | static const int bad_ports[] = {1, // tcpmux |
190 | 7, // echo |
191 | 9, // discard |
192 | 11, // systat |
193 | 13, // daytime |
194 | 15, // netstat |
195 | 17, // qotd |
196 | 19, // chargen |
197 | 20, // ftp-data |
198 | 21, // ftp-cntl |
199 | 22, // ssh |
200 | 23, // telnet |
201 | 25, // smtp |
202 | 37, // time |
203 | 42, // name |
204 | 43, // nicname |
205 | 53, // domain |
206 | 77, // priv-rjs |
207 | 79, // finger |
208 | 87, // ttylink |
209 | 95, // supdup |
210 | 101, // hostriame |
211 | 102, // iso-tsap |
212 | 103, // gppitnp |
213 | 104, // acr-nema |
214 | 109, // pop2 |
215 | 110, // pop3 |
216 | 111, // sunrpc |
217 | 113, // auth |
218 | 115, // sftp |
219 | 117, // uucp-path |
220 | 119, // nntp |
221 | 123, // NTP |
222 | 135, // loc-srv / epmap |
223 | 139, // netbios |
224 | 143, // imap2 |
225 | 179, // BGP |
226 | 389, // ldap |
227 | 512, // print / exec |
228 | 513, // login |
229 | 514, // shell |
230 | 515, // printer |
231 | 526, // tempo |
232 | 530, // courier |
233 | 531, // Chat |
234 | 532, // netnews |
235 | 540, // uucp |
236 | 556, // remotefs |
237 | 587, // sendmail |
238 | 601, // |
239 | 989, // ftps data |
240 | 990, // ftps |
241 | 992, // telnets |
242 | 993, // imap/SSL |
243 | 995, // pop3/SSL |
244 | 1080, // SOCKS |
245 | 2049, // nfs |
246 | 4045, // lockd |
247 | 6000, // x11 |
248 | 6667, // irc |
249 | 0}; |
250 | if (url.port() != 80) { |
251 | const int port = url.port(); |
252 | for (int cnt = 0; bad_ports[cnt] && bad_ports[cnt] <= port; ++cnt) { |
253 | if (port == bad_ports[cnt]) { |
254 | _error = KIO::ERR_POST_DENIED; |
255 | break; |
256 | } |
257 | } |
258 | } |
259 | |
260 | if (_error) { |
261 | static bool override_loaded = false; |
262 | static QList<int> *overriden_ports = nullptr; |
263 | if (!override_loaded) { |
264 | KConfig cfg(QStringLiteral("kio_httprc" )); |
265 | overriden_ports = new QList<int>; |
266 | *overriden_ports = cfg.group(group: QString()).readEntry(key: "OverriddenPorts" , defaultValue: QList<int>()); |
267 | override_loaded = true; |
268 | } |
269 | for (QList<int>::ConstIterator it = overriden_ports->constBegin(); it != overriden_ports->constEnd(); ++it) { |
270 | if (overriden_ports->contains(t: url.port())) { |
271 | _error = 0; |
272 | } |
273 | } |
274 | } |
275 | |
276 | // filter out non https? protocols |
277 | if ((url.scheme() != QLatin1String("http" )) && (url.scheme() != QLatin1String("https" ))) { |
278 | _error = KIO::ERR_POST_DENIED; |
279 | } |
280 | |
281 | if (!_error && !KUrlAuthorized::authorizeUrlAction(QStringLiteral("open" ), baseUrl: QUrl(), destUrl: url)) { |
282 | _error = KIO::ERR_ACCESS_DENIED; |
283 | } |
284 | |
285 | return _error; |
286 | } |
287 | |
288 | static KIO::PostErrorJob *precheckHttpPost(const QUrl &url, QIODevice *ioDevice, JobFlags flags) |
289 | { |
290 | // if request is not valid, return an invalid transfer job |
291 | const int _error = isUrlPortBad(url); |
292 | |
293 | if (_error) { |
294 | KIO_ARGS << (int)1 << url; |
295 | PostErrorJob *job = new PostErrorJob(_error, url.toString(), packedArgs, ioDevice); |
296 | job->setUiDelegate(KIO::createDefaultJobUiDelegate()); |
297 | if (!(flags & HideProgressInfo)) { |
298 | KIO::getJobTracker()->registerJob(job); |
299 | } |
300 | return job; |
301 | } |
302 | |
303 | // all is ok, return 0 |
304 | return nullptr; |
305 | } |
306 | |
307 | static KIO::PostErrorJob *precheckHttpPost(const QUrl &url, const QByteArray &postData, JobFlags flags) |
308 | { |
309 | // if request is not valid, return an invalid transfer job |
310 | const int _error = isUrlPortBad(url); |
311 | |
312 | if (_error) { |
313 | KIO_ARGS << (int)1 << url; |
314 | PostErrorJob *job = new PostErrorJob(_error, url.toString(), packedArgs, postData); |
315 | job->setUiDelegate(KIO::createDefaultJobUiDelegate()); |
316 | if (!(flags & HideProgressInfo)) { |
317 | KIO::getJobTracker()->registerJob(job); |
318 | } |
319 | return job; |
320 | } |
321 | |
322 | // all is ok, return 0 |
323 | return nullptr; |
324 | } |
325 | |
326 | TransferJob *KIO::http_post(const QUrl &url, const QByteArray &postData, JobFlags flags) |
327 | { |
328 | bool redirection = false; |
329 | QUrl _url(url); |
330 | if (_url.path().isEmpty()) { |
331 | redirection = true; |
332 | _url.setPath(QStringLiteral("/" )); |
333 | } |
334 | |
335 | TransferJob *job = precheckHttpPost(url: _url, postData, flags); |
336 | if (job) { |
337 | return job; |
338 | } |
339 | |
340 | // Send http post command (1), decoded path and encoded query |
341 | KIO_ARGS << (int)1 << _url << static_cast<qint64>(postData.size()); |
342 | job = TransferJobPrivate::newJob(url: _url, command: CMD_SPECIAL, packedArgs, staticData: postData, flags); |
343 | |
344 | if (redirection) { |
345 | QTimer::singleShot(interval: 0, receiver: job, slot: [job]() { |
346 | Q_EMIT job->redirection(job, url: job->url()); |
347 | }); |
348 | } |
349 | |
350 | return job; |
351 | } |
352 | |
353 | TransferJob *KIO::http_post(const QUrl &url, QIODevice *ioDevice, qint64 size, JobFlags flags) |
354 | { |
355 | bool redirection = false; |
356 | QUrl _url(url); |
357 | if (_url.path().isEmpty()) { |
358 | redirection = true; |
359 | _url.setPath(QStringLiteral("/" )); |
360 | } |
361 | |
362 | TransferJob *job = precheckHttpPost(url: _url, ioDevice, flags); |
363 | if (job) { |
364 | return job; |
365 | } |
366 | |
367 | // If no size is specified and the QIODevice is not a sequential one, |
368 | // attempt to obtain the size information from it. |
369 | Q_ASSERT(ioDevice); |
370 | if (size < 0) { |
371 | size = ((ioDevice && !ioDevice->isSequential()) ? ioDevice->size() : -1); |
372 | } |
373 | |
374 | // Send http post command (1), decoded path and encoded query |
375 | KIO_ARGS << (int)1 << _url << size; |
376 | job = TransferJobPrivate::newJob(url: _url, command: CMD_SPECIAL, packedArgs, ioDevice, flags); |
377 | |
378 | if (redirection) { |
379 | QTimer::singleShot(interval: 0, receiver: job, slot: [job]() { |
380 | Q_EMIT job->redirection(job, url: job->url()); |
381 | }); |
382 | } |
383 | |
384 | return job; |
385 | } |
386 | |
387 | TransferJob *KIO::http_delete(const QUrl &url, JobFlags flags) |
388 | { |
389 | // Send decoded path and encoded query |
390 | KIO_ARGS << url; |
391 | TransferJob *job = TransferJobPrivate::newJob(url, command: CMD_DEL, packedArgs, staticData: QByteArray(), flags); |
392 | return job; |
393 | } |
394 | |
395 | StoredTransferJob *KIO::storedHttpPost(const QByteArray &postData, const QUrl &url, JobFlags flags) |
396 | { |
397 | QUrl _url(url); |
398 | if (_url.path().isEmpty()) { |
399 | _url.setPath(QStringLiteral("/" )); |
400 | } |
401 | |
402 | StoredTransferJob *job = precheckHttpPost(url: _url, postData, flags); |
403 | if (job) { |
404 | return job; |
405 | } |
406 | |
407 | // Send http post command (1), decoded path and encoded query |
408 | KIO_ARGS << (int)1 << _url << static_cast<qint64>(postData.size()); |
409 | job = StoredTransferJobPrivate::newJob(url: _url, command: CMD_SPECIAL, packedArgs, staticData: postData, flags); |
410 | return job; |
411 | } |
412 | |
413 | StoredTransferJob *KIO::storedHttpPost(QIODevice *ioDevice, const QUrl &url, qint64 size, JobFlags flags) |
414 | { |
415 | QUrl _url(url); |
416 | if (_url.path().isEmpty()) { |
417 | _url.setPath(QStringLiteral("/" )); |
418 | } |
419 | |
420 | StoredTransferJob *job = precheckHttpPost(url: _url, ioDevice, flags); |
421 | if (job) { |
422 | return job; |
423 | } |
424 | |
425 | // If no size is specified and the QIODevice is not a sequential one, |
426 | // attempt to obtain the size information from it. |
427 | Q_ASSERT(ioDevice); |
428 | if (size < 0) { |
429 | size = ((ioDevice && !ioDevice->isSequential()) ? ioDevice->size() : -1); |
430 | } |
431 | |
432 | // Send http post command (1), decoded path and encoded query |
433 | KIO_ARGS << (int)1 << _url << size; |
434 | job = StoredTransferJobPrivate::newJob(url: _url, command: CMD_SPECIAL, packedArgs, ioDevice, flags); |
435 | return job; |
436 | } |
437 | |
438 | // http post got redirected from http://host to http://host/ by TransferJob |
439 | // We must do this redirection ourselves because redirections by the |
440 | // worker change post jobs into get jobs. |
441 | void TransferJobPrivate::slotPostRedirection() |
442 | { |
443 | Q_Q(TransferJob); |
444 | // qDebug() << m_url; |
445 | // Tell the user about the new url. |
446 | Q_EMIT q->redirection(job: q, url: m_url); |
447 | } |
448 | |
449 | TransferJob *KIO::put(const QUrl &url, int permissions, JobFlags flags) |
450 | { |
451 | KIO_ARGS << url << qint8((flags & Overwrite) ? 1 : 0) << qint8((flags & Resume) ? 1 : 0) << permissions; |
452 | return TransferJobPrivate::newJob(url, command: CMD_PUT, packedArgs, staticData: QByteArray(), flags); |
453 | } |
454 | |
455 | #include "moc_storedtransferjob.cpp" |
456 | #include "storedtransferjob.moc" |
457 | |