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