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
16using namespace KIO;
17
18class KIO::StoredTransferJobPrivate : public TransferJobPrivate
19{
20public:
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
71StoredTransferJob::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
82StoredTransferJob::~StoredTransferJob()
83{
84}
85
86void 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
95QByteArray StoredTransferJob::data() const
96{
97 return d_func()->m_data;
98}
99
100void 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
111void 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
132StoredTransferJob *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
143StoredTransferJob *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
151StoredTransferJob *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
162namespace KIO
163{
164class PostErrorJob : public StoredTransferJob
165{
166 Q_OBJECT
167public:
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
184static 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
288static 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
307static 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
326TransferJob *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
353TransferJob *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
387TransferJob *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
395StoredTransferJob *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
413StoredTransferJob *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.
441void 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
449TransferJob *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

source code of kio/src/core/storedtransferjob.cpp