1 /****************************************************************************
2 **
3 ** Copyright (C) 2018 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtNetwork module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qnetworkreplywasmimpl_p.h"
41 #include "qnetworkrequest.h"
42 
43 #include <QtCore/qtimer.h>
44 #include <QtCore/qdatetime.h>
45 #include <QtCore/qcoreapplication.h>
46 #include <QtCore/qfileinfo.h>
47 #include <QtCore/qthread.h>
48 
49 #include <private/qnetworkaccessmanager_p.h>
50 #include <private/qnetworkfile_p.h>
51 
52 #include <emscripten.h>
53 #include <emscripten/fetch.h>
54 
55 QT_BEGIN_NAMESPACE
56 
QNetworkReplyWasmImplPrivate()57 QNetworkReplyWasmImplPrivate::QNetworkReplyWasmImplPrivate()
58     : QNetworkReplyPrivate()
59     , managerPrivate(0)
60     , downloadBufferReadPosition(0)
61     , downloadBufferCurrentSize(0)
62     , totalDownloadSize(0)
63     , percentFinished(0)
64     , m_fetch(0)
65 {
66 }
67 
~QNetworkReplyWasmImplPrivate()68 QNetworkReplyWasmImplPrivate::~QNetworkReplyWasmImplPrivate()
69 {
70     if (m_fetch) {
71         emscripten_fetch_close(m_fetch);
72         m_fetch = 0;
73     }
74 }
75 
QNetworkReplyWasmImpl(QObject * parent)76 QNetworkReplyWasmImpl::QNetworkReplyWasmImpl(QObject *parent)
77     : QNetworkReply(*new QNetworkReplyWasmImplPrivate(), parent)
78 {
79     Q_D( QNetworkReplyWasmImpl);
80     d->state = QNetworkReplyPrivate::Idle;
81 }
82 
~QNetworkReplyWasmImpl()83 QNetworkReplyWasmImpl::~QNetworkReplyWasmImpl()
84 {
85 }
86 
methodName() const87 QByteArray QNetworkReplyWasmImpl::methodName() const
88 {
89     const Q_D( QNetworkReplyWasmImpl);
90     switch (operation()) {
91     case QNetworkAccessManager::HeadOperation:
92         return "HEAD";
93     case QNetworkAccessManager::GetOperation:
94         return "GET";
95     case QNetworkAccessManager::PutOperation:
96         return "PUT";
97     case QNetworkAccessManager::PostOperation:
98         return "POST";
99     case QNetworkAccessManager::DeleteOperation:
100         return "DELETE";
101     case QNetworkAccessManager::CustomOperation:
102         return d->request.attribute(QNetworkRequest::CustomVerbAttribute).toByteArray();
103     default:
104         break;
105     }
106     return QByteArray();
107 }
108 
close()109 void QNetworkReplyWasmImpl::close()
110 {
111     QNetworkReply::close();
112     setFinished(true);
113     emit finished();
114 }
115 
abort()116 void QNetworkReplyWasmImpl::abort()
117 {
118     Q_D( QNetworkReplyWasmImpl);
119     if (d->state == QNetworkReplyPrivate::Finished || d->state == QNetworkReplyPrivate::Aborted)
120         return;
121 
122     d->state = QNetworkReplyPrivate::Aborted;
123     d->doAbort();
124     close();
125 }
126 
bytesAvailable() const127 qint64 QNetworkReplyWasmImpl::bytesAvailable() const
128 {
129     Q_D(const QNetworkReplyWasmImpl);
130 
131     return QNetworkReply::bytesAvailable() + d->downloadBufferCurrentSize - d->downloadBufferReadPosition;
132 }
133 
isSequential() const134 bool QNetworkReplyWasmImpl::isSequential() const
135 {
136     return true;
137 }
138 
size() const139 qint64 QNetworkReplyWasmImpl::size() const
140 {
141     return QNetworkReply::size();
142 }
143 
144 /*!
145     \internal
146 */
readData(char * data,qint64 maxlen)147 qint64 QNetworkReplyWasmImpl::readData(char *data, qint64 maxlen)
148 {
149     Q_D(QNetworkReplyWasmImpl);
150 
151     qint64 howMuch = qMin(maxlen, (d->downloadBuffer.size() - d->downloadBufferReadPosition));
152     memcpy(data, d->downloadBuffer.constData() + d->downloadBufferReadPosition, howMuch);
153     d->downloadBufferReadPosition += howMuch;
154 
155     return howMuch;
156 }
157 
setup(QNetworkAccessManager::Operation op,const QNetworkRequest & req,QIODevice * data)158 void QNetworkReplyWasmImplPrivate::setup(QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *data)
159 {
160     Q_Q(QNetworkReplyWasmImpl);
161 
162     outgoingData = data;
163     request = req;
164     url = request.url();
165     operation = op;
166 
167     q->QIODevice::open(QIODevice::ReadOnly);
168     if (outgoingData && outgoingData->isSequential()) {
169         bool bufferingDisallowed =
170             request.attribute(QNetworkRequest::DoNotBufferUploadDataAttribute, false).toBool();
171 
172         if (bufferingDisallowed) {
173             // if a valid content-length header for the request was supplied, we can disable buffering
174             // if not, we will buffer anyway
175             if (!request.header(QNetworkRequest::ContentLengthHeader).isValid()) {
176                 state = Buffering;
177                 _q_bufferOutgoingData();
178                 return;
179             }
180         } else {
181             // doSendRequest will be called when the buffering has finished.
182             state = Buffering;
183             _q_bufferOutgoingData();
184             return;
185         }
186     }
187     // No outgoing data (POST, ..)
188     doSendRequest();
189 }
190 
setReplyAttributes(quintptr data,int statusCode,const QString & statusReason)191 void QNetworkReplyWasmImplPrivate::setReplyAttributes(quintptr data, int statusCode, const QString &statusReason)
192 {
193     QNetworkReplyWasmImplPrivate *handler = reinterpret_cast<QNetworkReplyWasmImplPrivate*>(data);
194     Q_ASSERT(handler);
195 
196     handler->q_func()->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
197     if (!statusReason.isEmpty())
198         handler->q_func()->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, statusReason);
199 }
200 
doAbort() const201 void QNetworkReplyWasmImplPrivate::doAbort() const
202 {
203     emscripten_fetch_close(m_fetch);
204 }
205 
getArraySize(int factor)206 constexpr int getArraySize (int factor) {
207     return 2 * factor + 1;
208 }
209 
doSendRequest()210 void QNetworkReplyWasmImplPrivate::doSendRequest()
211 {
212     Q_Q(QNetworkReplyWasmImpl);
213     totalDownloadSize = 0;
214 
215     emscripten_fetch_attr_t attr;
216     emscripten_fetch_attr_init(&attr);
217     strcpy(attr.requestMethod, q->methodName().constData());
218 
219     QList<QByteArray> headersData = request.rawHeaderList();
220     int arrayLength = getArraySize(headersData.count());
221     const char* customHeaders[arrayLength];
222 
223     if (headersData.count() > 0) {
224         int i = 0;
225         for (int j = 0; j < headersData.count(); j++) {
226             customHeaders[i] = headersData[j].constData();
227             i += 1;
228             customHeaders[i] = request.rawHeader(headersData[j]).constData();
229             i += 1;
230         }
231         customHeaders[i] = nullptr;
232         attr.requestHeaders = customHeaders;
233     }
234 
235     if (outgoingData) { // data from post request
236         // handle extra data
237         requestData = outgoingData->readAll(); // is there a size restriction here?
238         if (!requestData.isEmpty()) {
239             attr.requestData = requestData.data();
240             attr.requestDataSize = requestData.size();
241         }
242     }
243 
244     // username & password
245     if (!request.url().userInfo().isEmpty()) {
246         attr.userName = request.url().userName().toUtf8();
247         attr.password = request.url().password().toUtf8();
248     }
249 
250     attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
251 
252     QNetworkRequest::CacheLoadControl CacheLoadControlAttribute =
253         (QNetworkRequest::CacheLoadControl)request.attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt();
254 
255     if (CacheLoadControlAttribute == QNetworkRequest::AlwaysCache) {
256         attr.attributes += EMSCRIPTEN_FETCH_NO_DOWNLOAD;
257     }
258     if (CacheLoadControlAttribute == QNetworkRequest::PreferCache) {
259          attr.attributes += EMSCRIPTEN_FETCH_APPEND;
260     }
261 
262     if (CacheLoadControlAttribute == QNetworkRequest::AlwaysNetwork ||
263             request.attribute(QNetworkRequest::CacheSaveControlAttribute, false).toBool()) {
264         attr.attributes -= EMSCRIPTEN_FETCH_PERSIST_FILE;
265     }
266 
267     attr.onsuccess = QNetworkReplyWasmImplPrivate::downloadSucceeded;
268     attr.onerror = QNetworkReplyWasmImplPrivate::downloadFailed;
269     attr.onprogress = QNetworkReplyWasmImplPrivate::downloadProgress;
270     attr.onreadystatechange = QNetworkReplyWasmImplPrivate::stateChange;
271     attr.timeoutMSecs = request.transferTimeout();
272     attr.userData = reinterpret_cast<void *>(this);
273 
274     QString dPath = QStringLiteral("/home/web_user/") + request.url().fileName();
275     attr.destinationPath = dPath.toUtf8();
276 
277     m_fetch = emscripten_fetch(&attr, request.url().toString().toUtf8());
278 }
279 
emitReplyError(QNetworkReply::NetworkError errorCode,const QString & errorString)280 void QNetworkReplyWasmImplPrivate::emitReplyError(QNetworkReply::NetworkError errorCode, const QString &errorString)
281 {
282     Q_Q(QNetworkReplyWasmImpl);
283 
284     q->setError(errorCode, errorString);
285     emit q->errorOccurred(errorCode);
286     emit q->finished();
287 }
288 
emitDataReadProgress(qint64 bytesReceived,qint64 bytesTotal)289 void QNetworkReplyWasmImplPrivate::emitDataReadProgress(qint64 bytesReceived, qint64 bytesTotal)
290 {
291     Q_Q(QNetworkReplyWasmImpl);
292 
293     totalDownloadSize = bytesTotal;
294 
295     percentFinished = (bytesReceived / bytesTotal) * 100;
296 
297     emit q->downloadProgress(bytesReceived, bytesTotal);
298 }
299 
dataReceived(const QByteArray & buffer,int bufferSize)300 void QNetworkReplyWasmImplPrivate::dataReceived(const QByteArray &buffer, int bufferSize)
301 {
302     Q_Q(QNetworkReplyWasmImpl);
303 
304     if (bufferSize > 0)
305         q->setReadBufferSize(bufferSize);
306 
307     bytesDownloaded = bufferSize;
308 
309     if (percentFinished != 100)
310         downloadBufferCurrentSize += bufferSize;
311     else
312         downloadBufferCurrentSize = bufferSize;
313 
314     totalDownloadSize = downloadBufferCurrentSize;
315 
316     downloadBuffer.append(buffer, bufferSize);
317 
318     emit q->readyRead();
319 }
320 
321 //taken from qnetworkrequest.cpp
parseHeaderName(const QByteArray & headerName)322 static int parseHeaderName(const QByteArray &headerName)
323 {
324     if (headerName.isEmpty())
325         return -1;
326 
327     switch (tolower(headerName.at(0))) {
328     case 'c':
329         if (qstricmp(headerName.constData(), "content-type") == 0)
330             return QNetworkRequest::ContentTypeHeader;
331         else if (qstricmp(headerName.constData(), "content-length") == 0)
332             return QNetworkRequest::ContentLengthHeader;
333         else if (qstricmp(headerName.constData(), "cookie") == 0)
334             return QNetworkRequest::CookieHeader;
335         break;
336 
337     case 'l':
338         if (qstricmp(headerName.constData(), "location") == 0)
339             return QNetworkRequest::LocationHeader;
340         else if (qstricmp(headerName.constData(), "last-modified") == 0)
341             return QNetworkRequest::LastModifiedHeader;
342         break;
343 
344     case 's':
345         if (qstricmp(headerName.constData(), "set-cookie") == 0)
346             return QNetworkRequest::SetCookieHeader;
347         else if (qstricmp(headerName.constData(), "server") == 0)
348             return QNetworkRequest::ServerHeader;
349         break;
350 
351     case 'u':
352         if (qstricmp(headerName.constData(), "user-agent") == 0)
353             return QNetworkRequest::UserAgentHeader;
354         break;
355     }
356 
357     return -1; // nothing found
358 }
359 
360 
headersReceived(const QByteArray & buffer)361 void QNetworkReplyWasmImplPrivate::headersReceived(const QByteArray &buffer)
362 {
363     Q_Q(QNetworkReplyWasmImpl);
364 
365     if (!buffer.isEmpty()) {
366         QList<QByteArray> headers = buffer.split('\n');
367 
368         for (int i = 0; i < headers.size(); i++) {
369             if (headers.at(i).contains(':')) { // headers include final \x00, so skip
370                 QByteArray headerName = headers.at(i).split(':').at(0).trimmed();
371                 QByteArray headersValue = headers.at(i).split(':').at(1).trimmed();
372 
373                 if (headerName.isEmpty() || headersValue.isEmpty())
374                     continue;
375 
376                 int headerIndex = parseHeaderName(headerName);
377 
378                 if (headerIndex == -1)
379                     q->setRawHeader(headerName, headersValue);
380                 else
381                     q->setHeader(static_cast<QNetworkRequest::KnownHeaders>(headerIndex), (QVariant)headersValue);
382             }
383         }
384     }
385     emit q->metaDataChanged();
386 }
387 
_q_bufferOutgoingDataFinished()388 void QNetworkReplyWasmImplPrivate::_q_bufferOutgoingDataFinished()
389 {
390     Q_Q(QNetworkReplyWasmImpl);
391 
392     // make sure this is only called once, ever.
393     //_q_bufferOutgoingData may call it or the readChannelFinished emission
394     if (state != Buffering)
395         return;
396 
397     // disconnect signals
398     QObject::disconnect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData()));
399     QObject::disconnect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished()));
400 
401     // finally, start the request
402     doSendRequest();
403 }
404 
_q_bufferOutgoingData()405 void QNetworkReplyWasmImplPrivate::_q_bufferOutgoingData()
406 {
407     Q_Q(QNetworkReplyWasmImpl);
408 
409     if (!outgoingDataBuffer) {
410         // first call, create our buffer
411         outgoingDataBuffer = QSharedPointer<QRingBuffer>::create();
412 
413         QObject::connect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData()));
414         QObject::connect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished()));
415     }
416 
417     qint64 bytesBuffered = 0;
418     qint64 bytesToBuffer = 0;
419 
420     // read data into our buffer
421     forever {
422         bytesToBuffer = outgoingData->bytesAvailable();
423         // unknown? just try 2 kB, this also ensures we always try to read the EOF
424         if (bytesToBuffer <= 0)
425             bytesToBuffer = 2*1024;
426 
427         char *dst = outgoingDataBuffer->reserve(bytesToBuffer);
428         bytesBuffered = outgoingData->read(dst, bytesToBuffer);
429 
430         if (bytesBuffered == -1) {
431             // EOF has been reached.
432             outgoingDataBuffer->chop(bytesToBuffer);
433 
434             _q_bufferOutgoingDataFinished();
435             break;
436         } else if (bytesBuffered == 0) {
437             // nothing read right now, just wait until we get called again
438             outgoingDataBuffer->chop(bytesToBuffer);
439 
440             break;
441         } else {
442             // don't break, try to read() again
443             outgoingDataBuffer->chop(bytesToBuffer - bytesBuffered);
444         }
445     }
446 }
447 
downloadSucceeded(emscripten_fetch_t * fetch)448 void QNetworkReplyWasmImplPrivate::downloadSucceeded(emscripten_fetch_t *fetch)
449 {
450     QNetworkReplyWasmImplPrivate *reply =
451             reinterpret_cast<QNetworkReplyWasmImplPrivate*>(fetch->userData);
452     if (reply) {
453         QByteArray buffer(fetch->data, fetch->numBytes);
454         reply->dataReceived(buffer, buffer.size());
455 
456         QByteArray statusText(fetch->statusText);
457         reply->setStatusCode(fetch->status, statusText);
458         reply->setReplyFinished();
459     }
460 }
461 
setStatusCode(int status,const QByteArray & statusText)462 void QNetworkReplyWasmImplPrivate::setStatusCode(int status, const QByteArray &statusText)
463 {
464     Q_Q(QNetworkReplyWasmImpl);
465     q->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, status);
466     q->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, statusText);
467 }
468 
setReplyFinished()469 void QNetworkReplyWasmImplPrivate::setReplyFinished()
470 {
471     Q_Q(QNetworkReplyWasmImpl);
472     q->setFinished(true);
473     emit q->readChannelFinished();
474     emit q->finished();
475 }
476 
stateChange(emscripten_fetch_t * fetch)477 void QNetworkReplyWasmImplPrivate::stateChange(emscripten_fetch_t *fetch)
478 {
479     if (fetch->readyState == /*HEADERS_RECEIVED*/ 2) {
480         size_t headerLength = emscripten_fetch_get_response_headers_length(fetch);
481         QByteArray str(headerLength, Qt::Uninitialized);
482         emscripten_fetch_get_response_headers(fetch, str.data(), str.size());
483         QNetworkReplyWasmImplPrivate *reply =
484                 reinterpret_cast<QNetworkReplyWasmImplPrivate*>(fetch->userData);
485         reply->headersReceived(str);
486     }
487 }
488 
downloadProgress(emscripten_fetch_t * fetch)489 void QNetworkReplyWasmImplPrivate::downloadProgress(emscripten_fetch_t *fetch)
490 {
491     QNetworkReplyWasmImplPrivate *reply =
492             reinterpret_cast<QNetworkReplyWasmImplPrivate*>(fetch->userData);
493     Q_ASSERT(reply);
494 
495     if (fetch->status < 400) {
496         uint64_t bytes = fetch->dataOffset + fetch->numBytes;
497         uint64_t tBytes = fetch->totalBytes; // totalBytes can be 0 if server not reporting content length
498         if (tBytes == 0)
499             tBytes = bytes;
500         reply->emitDataReadProgress(bytes, tBytes);
501     }
502 }
503 
downloadFailed(emscripten_fetch_t * fetch)504 void QNetworkReplyWasmImplPrivate::downloadFailed(emscripten_fetch_t *fetch)
505 {
506     QNetworkReplyWasmImplPrivate *reply = reinterpret_cast<QNetworkReplyWasmImplPrivate*>(fetch->userData);
507     if (reply) {
508         QString reasonStr;
509         if (fetch->status > 600 ||  reply->state == QNetworkReplyPrivate::Aborted)
510             reasonStr = QStringLiteral("Operation canceled");
511         else
512             reasonStr = QString::fromUtf8(fetch->statusText);
513 
514         QByteArray statusText(fetch->statusText);
515         reply->setStatusCode(fetch->status, statusText);
516         reply->emitReplyError(reply->statusCodeFromHttp(fetch->status, reply->request.url()), reasonStr);
517     }
518 
519     if (fetch->status >= 400)
520         emscripten_fetch_close(fetch); // Also free data on failure.
521 }
522 
523 //taken from qhttpthreaddelegate.cpp
statusCodeFromHttp(int httpStatusCode,const QUrl & url)524 QNetworkReply::NetworkError QNetworkReplyWasmImplPrivate::statusCodeFromHttp(int httpStatusCode, const QUrl &url)
525 {
526     QNetworkReply::NetworkError code;
527     // we've got an error
528     switch (httpStatusCode) {
529     case 400:               // Bad Request
530         code = QNetworkReply::ProtocolInvalidOperationError;
531         break;
532 
533     case 401:               // Authorization required
534         code = QNetworkReply::AuthenticationRequiredError;
535         break;
536 
537     case 403:               // Access denied
538         code = QNetworkReply::ContentAccessDenied;
539         break;
540 
541     case 404:               // Not Found
542         code = QNetworkReply::ContentNotFoundError;
543         break;
544 
545     case 405:               // Method Not Allowed
546         code = QNetworkReply::ContentOperationNotPermittedError;
547         break;
548 
549     case 407:
550         code = QNetworkReply::ProxyAuthenticationRequiredError;
551         break;
552 
553     case 409:               // Resource Conflict
554         code = QNetworkReply::ContentConflictError;
555         break;
556 
557     case 410:               // Content no longer available
558         code = QNetworkReply::ContentGoneError;
559         break;
560 
561     case 418:               // I'm a teapot
562         code = QNetworkReply::ProtocolInvalidOperationError;
563         break;
564 
565     case 500:               // Internal Server Error
566         code = QNetworkReply::InternalServerError;
567         break;
568 
569     case 501:               // Server does not support this functionality
570         code = QNetworkReply::OperationNotImplementedError;
571         break;
572 
573     case 503:               // Service unavailable
574         code = QNetworkReply::ServiceUnavailableError;
575         break;
576 
577     case 65535: //emscripten reply when aborted
578         code =  QNetworkReply::OperationCanceledError;
579         break;
580     default:
581         if (httpStatusCode > 500) {
582             // some kind of server error
583             code = QNetworkReply::UnknownServerError;
584         } else if (httpStatusCode >= 400) {
585             // content error we did not handle above
586             code = QNetworkReply::UnknownContentError;
587         } else {
588             qWarning("QNetworkAccess: got HTTP status code %d which is not expected from url: \"%s\"",
589                      httpStatusCode, qPrintable(url.toString()));
590             code = QNetworkReply::ProtocolFailure;
591         }
592     };
593 
594     return code;
595 }
596 
597 QT_END_NAMESPACE
598