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