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