Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qnetworkreplywasmimpl.cpp
Go to the documentation of this file.
1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
7
8#include <QtCore/qdatetime.h>
9#include <QtCore/qcoreapplication.h>
10#include <QtCore/qfileinfo.h>
11#include <QtCore/qthread.h>
12#include <QtCore/private/qwasmglobal_p.h>
13#include <QtCore/private/qoffsetstringarray_p.h>
14#include <QtCore/private/qtools_p.h>
15
16#include <private/qnetworkaccessmanager_p.h>
17#include <private/qnetworkfile_p.h>
18
19#include <emscripten.h>
20#include <emscripten/fetch.h>
21
23
24using namespace Qt::StringLiterals;
25
26namespace {
27
28static constexpr auto BannedHeaders = qOffsetStringArray(
29 "accept-charset",
30 "accept-encoding",
31 "access-control-request-headers",
32 "access-control-request-method",
33 "connection",
34 "content-length",
35 "cookie",
36 "cookie2",
37 "date",
38 "dnt",
39 "expect",
40 "host",
41 "keep-alive",
42 "origin",
43 "referer",
44 "te",
45 "trailer",
46 "transfer-encoding",
47 "upgrade",
48 "via"
49);
50
51bool isUnsafeHeader(QLatin1StringView header) noexcept
52{
53 return header.startsWith("proxy-"_L1, Qt::CaseInsensitive)
54 || header.startsWith("sec-"_L1, Qt::CaseInsensitive)
55 || BannedHeaders.contains(header, Qt::CaseInsensitive);
56}
57} // namespace
58
70
90
91QNetworkReplyWasmImpl::QNetworkReplyWasmImpl(QObject *parent)
92 : QNetworkReply(*new QNetworkReplyWasmImplPrivate(), parent)
93{
94 Q_D( QNetworkReplyWasmImpl);
95 d->state = QNetworkReplyPrivate::Idle;
96}
97
99{
100 if (isRunning())
101 abort();
102 close();
103}
104
105QByteArray QNetworkReplyWasmImpl::methodName() const
106{
107 const Q_D( QNetworkReplyWasmImpl);
108 switch (operation()) {
109 case QNetworkAccessManager::HeadOperation:
110 return "HEAD";
111 case QNetworkAccessManager::GetOperation:
112 return "GET";
113 case QNetworkAccessManager::PutOperation:
114 return "PUT";
115 case QNetworkAccessManager::PostOperation:
116 return "POST";
117 case QNetworkAccessManager::DeleteOperation:
118 return "DELETE";
119 case QNetworkAccessManager::CustomOperation:
120 return d->request.attribute(QNetworkRequest::CustomVerbAttribute).toByteArray();
121 default:
122 break;
123 }
124 return QByteArray();
125}
126
128{
129 Q_D(QNetworkReplyWasmImpl);
130
131 if (!d->m_fetch)
132 return;
133 emscripten_fetch_close(d->m_fetch);
134 if (d->state != QNetworkReplyPrivate::Aborted &&
135 d->state != QNetworkReplyPrivate::Finished &&
136 d->state != QNetworkReplyPrivate::Idle) {
137 d->state = QNetworkReplyPrivate::Finished;
138 d->setCanceled();
139 }
140 QNetworkReply::close();
141 d->m_fetch = nullptr;
142}
143
145{
146 Q_D(QNetworkReplyWasmImpl);
147
148 if (d->state == QNetworkReplyPrivate::Finished || d->state == QNetworkReplyPrivate::Aborted)
149 return;
150
151 d->state = QNetworkReplyPrivate::Aborted;
152 d->setCanceled();
153}
154
156{
157 Q_Q(QNetworkReplyWasmImpl);
158 {
159 if (m_fetchContext) {
160 std::scoped_lock lock{ m_fetchContext->mutex };
164 }
165 }
166
167 emitReplyError(QNetworkReply::OperationCanceledError, QStringLiteral("Operation canceled"));
168 q->setFinished(true);
169 emit q->finished();
170}
171
173{
174 Q_D(const QNetworkReplyWasmImpl);
175
176 return QNetworkReply::bytesAvailable() + d->downloadBufferCurrentSize - d->downloadBufferReadPosition;
177}
178
180{
181 return true;
182}
183
185{
186 return QNetworkReply::size();
187}
188
189/*!
190 \internal
191*/
192qint64 QNetworkReplyWasmImpl::readData(char *data, qint64 maxlen)
193{
194 Q_D(QNetworkReplyWasmImpl);
195
196 qint64 howMuch = qMin(maxlen, (d->downloadBuffer.size() - d->downloadBufferReadPosition));
197 memcpy(data, d->downloadBuffer.constData() + d->downloadBufferReadPosition, howMuch);
198 d->downloadBufferReadPosition += howMuch;
199
200 return howMuch;
201}
202
203void QNetworkReplyWasmImplPrivate::setup(QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *data)
204{
205 Q_Q(QNetworkReplyWasmImpl);
206
207 outgoingData = data;
208 request = req;
209 url = request.url();
210 operation = op;
211
212 q->QIODevice::open(QIODevice::ReadOnly);
213 if (outgoingData && outgoingData->isSequential()) {
214 bool bufferingDisallowed =
215 request.attribute(QNetworkRequest::DoNotBufferUploadDataAttribute, false).toBool();
216
217 if (bufferingDisallowed) {
218 // if a valid content-length header for the request was supplied, we can disable buffering
219 // if not, we will buffer anyway
220 if (!request.header(QNetworkRequest::ContentLengthHeader).isValid()) {
223 return;
224 }
225 } else {
226 // doSendRequest will be called when the buffering has finished.
229 return;
230 }
231 }
232 // No outgoing data (POST, ..)
234}
235
236void QNetworkReplyWasmImplPrivate::setReplyAttributes(quintptr data, int statusCode, const QString &statusReason)
237{
238 QNetworkReplyWasmImplPrivate *handler = reinterpret_cast<QNetworkReplyWasmImplPrivate*>(data);
239 Q_ASSERT(handler);
240
241 handler->q_func()->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
242 if (!statusReason.isEmpty())
243 handler->q_func()->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, statusReason);
244}
245
246constexpr int getArraySize (int factor) {
247 return 2 * factor + 1;
248}
249
251{
252 Q_Q(QNetworkReplyWasmImpl);
253 totalDownloadSize = 0;
254
255 emscripten_fetch_attr_t attr;
256 emscripten_fetch_attr_init(&attr);
257 qstrncpy(attr.requestMethod, q->methodName().constData(), 32); // requestMethod is char[32] in emscripten
258
259 attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
260
261 QNetworkRequest::CacheLoadControl CacheLoadControlAttribute =
262 (QNetworkRequest::CacheLoadControl)request.attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt();
263
264 if (CacheLoadControlAttribute == QNetworkRequest::AlwaysCache) {
265 attr.attributes += EMSCRIPTEN_FETCH_NO_DOWNLOAD;
266 }
267 if (CacheLoadControlAttribute == QNetworkRequest::PreferCache) {
268 attr.attributes += EMSCRIPTEN_FETCH_APPEND;
269 }
270
271 attr.withCredentials = request.attribute(QNetworkRequest::UseCredentialsAttribute, false).toBool();
272 attr.onsuccess = QNetworkReplyWasmImplPrivate::downloadSucceeded;
273 attr.onerror = QNetworkReplyWasmImplPrivate::downloadFailed;
274 attr.onprogress = QNetworkReplyWasmImplPrivate::downloadProgress;
275 attr.onreadystatechange = QNetworkReplyWasmImplPrivate::stateChange;
276 attr.timeoutMSecs = request.transferTimeout();
277
279 attr.userData = static_cast<void *>(m_fetchContext);
280 if (outgoingData) { // data from post request
281 m_fetchContext->requestData = outgoingData->readAll(); // is there a size restriction here?
282 if (!m_fetchContext->requestData.isEmpty()) {
283 attr.requestData = m_fetchContext->requestData.data();
284 attr.requestDataSize = m_fetchContext->requestData.size();
285 }
286 }
287
288 qwasmglobal::runOnMainThread([attr, fetchContext = m_fetchContext]() mutable {
289 std::unique_lock lock{ fetchContext->mutex };
290 if (fetchContext->state == FetchContext::State::CANCELED) {
291 fetchContext->state = FetchContext::State::FINISHED;
292 return;
293 } else if (fetchContext->state == FetchContext::State::TO_BE_DESTROYED) {
294 lock.unlock();
295 delete fetchContext;
296 return;
297 }
298 const auto reply = fetchContext->reply;
299 const auto &request = reply->request;
300
301 QByteArray userName, password;
302 if (!request.url().userInfo().isEmpty()) {
303 userName = request.url().userName().toUtf8();
304 password = request.url().password().toUtf8();
305 attr.userName = userName.constData();
306 attr.password = password.constData();
307 }
308
309 QList<QByteArray> headersData = request.rawHeaderList();
310 int arrayLength = getArraySize(headersData.count());
311 const char *customHeaders[arrayLength];
312 QStringList trimmedHeaders;
313 if (headersData.count() > 0) {
314 int i = 0;
315 for (const auto &headerName : headersData) {
316 if (isUnsafeHeader(QLatin1StringView(headerName.constData()))) {
317 trimmedHeaders.push_back(QString::fromLatin1(headerName));
318 } else {
319 customHeaders[i++] = headerName.constData();
320 customHeaders[i++] = request.rawHeader(headerName).constData();
321 }
322 }
323 if (!trimmedHeaders.isEmpty()) {
324 qWarning() << "Qt has trimmed the following forbidden headers from the request:"
325 << trimmedHeaders.join(QLatin1StringView(", "));
326 }
327 customHeaders[i] = nullptr;
328 attr.requestHeaders = customHeaders;
329 }
330
331 auto url = request.url().toString().toUtf8();
332 QString dPath = "/home/web_user/"_L1 + request.url().fileName();
333 QByteArray destinationPath = dPath.toUtf8();
334 attr.destinationPath = destinationPath.constData();
335 reply->m_fetch = emscripten_fetch(&attr, url.constData());
336 fetchContext->state = FetchContext::State::SENT;
337 });
338 state = Working;
339}
340
341void QNetworkReplyWasmImplPrivate::emitReplyError(QNetworkReply::NetworkError errorCode, const QString &errorString)
342{
343 Q_Q(QNetworkReplyWasmImpl);
344
345 q->setError(errorCode, errorString);
346 emit q->errorOccurred(errorCode);
347}
348
349void QNetworkReplyWasmImplPrivate::emitDataReadProgress(qint64 bytesReceived, qint64 bytesTotal)
350{
351 Q_Q(QNetworkReplyWasmImpl);
352
353 totalDownloadSize = bytesTotal;
354
355 percentFinished = bytesTotal ? (bytesReceived / bytesTotal) * 100 : 100;
356
357 emit q->downloadProgress(bytesReceived, bytesTotal);
358}
359
361{
362 Q_Q(QNetworkReplyWasmImpl);
363
364 const qsizetype bufferSize = buffer.size();
365 if (bufferSize > 0)
366 q->setReadBufferSize(bufferSize);
367
368 bytesDownloaded = bufferSize;
369
370 if (percentFinished != 100)
371 downloadBufferCurrentSize += bufferSize;
372 else
373 downloadBufferCurrentSize = bufferSize;
374
375 totalDownloadSize = downloadBufferCurrentSize;
376
377 downloadBuffer.append(buffer);
378
379 emit q->readyRead();
380}
381
382//taken from qnetworkrequest.cpp
383static int parseHeaderName(const QByteArray &headerName)
384{
385 if (headerName.isEmpty())
386 return -1;
387
388 auto is = [&](const char *what) {
389 return qstrnicmp(headerName.data(), headerName.size(), what) == 0;
390 };
391
392 switch (QtMiscUtils::toAsciiLower(headerName.front())) {
393 case 'c':
394 if (is("content-type"))
395 return QNetworkRequest::ContentTypeHeader;
396 else if (is("content-length"))
397 return QNetworkRequest::ContentLengthHeader;
398 else if (is("cookie"))
399 return QNetworkRequest::CookieHeader;
400 break;
401
402 case 'l':
403 if (is("location"))
404 return QNetworkRequest::LocationHeader;
405 else if (is("last-modified"))
406 return QNetworkRequest::LastModifiedHeader;
407 break;
408
409 case 's':
410 if (is("set-cookie"))
411 return QNetworkRequest::SetCookieHeader;
412 else if (is("server"))
413 return QNetworkRequest::ServerHeader;
414 break;
415
416 case 'u':
417 if (is("user-agent"))
418 return QNetworkRequest::UserAgentHeader;
419 break;
420 }
421
422 return -1; // nothing found
423}
424
425
427{
428 Q_Q(QNetworkReplyWasmImpl);
429
430 if (!buffer.isEmpty()) {
431 QList<QByteArray> headers = buffer.split('\n');
432
433 for (auto &&header : headers) {
434 if (auto splitPos = header.indexOf(':');
435 splitPos != -1) { // headers include final \x00, so skip
436 auto headerName = header.first(splitPos).trimmed();
437 auto headerValue = header.sliced(splitPos + 1).trimmed();
438 headerValue = headerValue.replace('\x00', "").trimmed();
439
440 if (headerName.isEmpty() || headerValue.isEmpty())
441 continue;
442
443 int headerIndex = parseHeaderName(headerName);
444
445 if (headerIndex == -1)
446 q->setRawHeader(headerName, headerValue);
447 else
448 q->setHeader(static_cast<QNetworkRequest::KnownHeaders>(headerIndex),
449 (QVariant)headerValue);
450 }
451 }
452 }
453 emit q->metaDataChanged();
454}
455
457{
458 Q_Q(QNetworkReplyWasmImpl);
459
460 // make sure this is only called once, ever.
461 //_q_bufferOutgoingData may call it or the readChannelFinished emission
462 if (state != Buffering)
463 return;
464
465 // disconnect signals
466 QObject::disconnect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData()));
467 QObject::disconnect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished()));
468
469 // finally, start the request
471}
472
474{
475 Q_Q(QNetworkReplyWasmImpl);
476
477 if (!outgoingDataBuffer) {
478 // first call, create our buffer
479 outgoingDataBuffer = std::make_shared<QRingBuffer>();
480
481 QObject::connect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData()));
482 QObject::connect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished()));
483 }
484
485 qint64 bytesBuffered = 0;
486 qint64 bytesToBuffer = 0;
487
488 // read data into our buffer
489 forever {
490 bytesToBuffer = outgoingData->bytesAvailable();
491 // unknown? just try 2 kB, this also ensures we always try to read the EOF
492 if (bytesToBuffer <= 0)
493 bytesToBuffer = 2*1024;
494
495 char *dst = outgoingDataBuffer->reserve(bytesToBuffer);
496 bytesBuffered = outgoingData->read(dst, bytesToBuffer);
497
498 if (bytesBuffered == -1) {
499 // EOF has been reached.
500 outgoingDataBuffer->chop(bytesToBuffer);
501
502 _q_bufferOutgoingDataFinished();
503 break;
504 } else if (bytesBuffered == 0) {
505 // nothing read right now, just wait until we get called again
506 outgoingDataBuffer->chop(bytesToBuffer);
507
508 break;
509 } else {
510 // don't break, try to read() again
511 outgoingDataBuffer->chop(bytesToBuffer - bytesBuffered);
512 }
513 }
514}
515
516void QNetworkReplyWasmImplPrivate::downloadSucceeded(emscripten_fetch_t *fetch)
517{
518 auto fetchContext = static_cast<FetchContext *>(fetch->userData);
519 std::unique_lock lock{ fetchContext->mutex };
520
521 if (fetchContext->state == FetchContext::State::TO_BE_DESTROYED) {
522 lock.unlock();
523 delete fetchContext;
524 return;
525 } else if (fetchContext->state == FetchContext::State::CANCELED) {
526 fetchContext->state = FetchContext::State::FINISHED;
527 return;
528 } else if (fetchContext->state == FetchContext::State::SENT) {
529 const auto reply = fetchContext->reply;
530 if (reply->state != QNetworkReplyPrivate::Aborted) {
531 QByteArray statusText(fetch->statusText);
532 reply->setStatusCode(fetch->status, statusText);
533 QByteArray buffer(fetch->data, fetch->numBytes);
534 reply->dataReceived(buffer);
535 reply->setReplyFinished();
536 }
537 reply->m_fetch = nullptr;
538 fetchContext->state = FetchContext::State::FINISHED;
539 }
540}
541
543{
544 Q_Q(QNetworkReplyWasmImpl);
546 q->setFinished(true);
547 emit q->readChannelFinished();
548 emit q->finished();
549}
550
551void QNetworkReplyWasmImplPrivate::setStatusCode(int status, const QByteArray &statusText)
552{
553 Q_Q(QNetworkReplyWasmImpl);
554 q->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, status);
555 q->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, statusText);
556}
557
558void QNetworkReplyWasmImplPrivate::stateChange(emscripten_fetch_t *fetch)
559{
560 const auto fetchContext = static_cast<FetchContext*>(fetch->userData);
561 const auto reply = fetchContext->reply;
562 if (reply && reply->state != QNetworkReplyPrivate::Aborted) {
563 if (fetch->readyState == /*HEADERS_RECEIVED*/ 2) {
564 size_t headerLength = emscripten_fetch_get_response_headers_length(fetch);
565 QByteArray str(headerLength, Qt::Uninitialized);
566 emscripten_fetch_get_response_headers(fetch, str.data(), str.size());
567 reply->headersReceived(str);
568 }
569 }
570}
571
572void QNetworkReplyWasmImplPrivate::downloadProgress(emscripten_fetch_t *fetch)
573{
574 const auto fetchContext = static_cast<FetchContext*>(fetch->userData);
575 const auto reply = fetchContext->reply;
576 if (reply && reply->state != QNetworkReplyPrivate::Aborted) {
577 if (fetch->status < 400) {
578 uint64_t bytes = fetch->dataOffset + fetch->numBytes;
579 uint64_t tBytes = fetch->totalBytes; // totalBytes can be 0 if server not reporting content length
580 if (tBytes == 0)
581 tBytes = bytes;
582 reply->emitDataReadProgress(bytes, tBytes);
583 }
584 }
585}
586
587void QNetworkReplyWasmImplPrivate::downloadFailed(emscripten_fetch_t *fetch)
588{
589 const auto fetchContext = static_cast<FetchContext*>(fetch->userData);
590 std::unique_lock lock{ fetchContext->mutex };
591
592 if (fetchContext->state == FetchContext::State::TO_BE_DESTROYED) {
593 lock.unlock();
594 delete fetchContext;
595 return;
596 } else if (fetchContext->state == FetchContext::State::CANCELED) {
597 fetchContext->state = FetchContext::State::FINISHED;
598 return;
599 } else if (fetchContext->state == FetchContext::State::SENT) {
600 const auto reply = fetchContext->reply;
601 if (reply->state != QNetworkReplyPrivate::Aborted) {
602 QString reasonStr;
603 if (fetch->status > 600)
604 reasonStr = QStringLiteral("Operation canceled");
605 else
606 reasonStr = QString::fromUtf8(fetch->statusText);
607 QByteArray statusText(fetch->statusText);
608 reply->setStatusCode(fetch->status, statusText);
609 QByteArray buffer(fetch->data, fetch->numBytes);
610 reply->dataReceived(buffer);
611 reply->emitReplyError(reply->statusCodeFromHttp(fetch->status, reply->request.url()),
612 reasonStr);
613 reply->setReplyFinished();
614 }
615 reply->m_fetch = nullptr;
616 fetchContext->state = FetchContext::State::FINISHED;
617 }
618}
619
620//taken from qhttpthreaddelegate.cpp
621QNetworkReply::NetworkError QNetworkReplyWasmImplPrivate::statusCodeFromHttp(int httpStatusCode, const QUrl &url)
622{
623 QNetworkReply::NetworkError code;
624 // we've got an error
625 switch (httpStatusCode) {
626 case 400: // Bad Request
627 code = QNetworkReply::ProtocolInvalidOperationError;
628 break;
629
630 case 401: // Authorization required
631 code = QNetworkReply::AuthenticationRequiredError;
632 break;
633
634 case 403: // Access denied
635 code = QNetworkReply::ContentAccessDenied;
636 break;
637
638 case 404: // Not Found
639 code = QNetworkReply::ContentNotFoundError;
640 break;
641
642 case 405: // Method Not Allowed
643 code = QNetworkReply::ContentOperationNotPermittedError;
644 break;
645
646 case 407:
647 code = QNetworkReply::ProxyAuthenticationRequiredError;
648 break;
649
650 case 409: // Resource Conflict
651 code = QNetworkReply::ContentConflictError;
652 break;
653
654 case 410: // Content no longer available
655 code = QNetworkReply::ContentGoneError;
656 break;
657
658 case 418: // I'm a teapot
659 code = QNetworkReply::ProtocolInvalidOperationError;
660 break;
661
662 case 500: // Internal Server Error
663 code = QNetworkReply::InternalServerError;
664 break;
665
666 case 501: // Server does not support this functionality
667 code = QNetworkReply::OperationNotImplementedError;
668 break;
669
670 case 503: // Service unavailable
671 code = QNetworkReply::ServiceUnavailableError;
672 break;
673
674 case 65535: //emscripten reply when aborted
675 code = QNetworkReply::OperationCanceledError;
676 break;
677 default:
678 if (httpStatusCode > 500) {
679 // some kind of server error
680 code = QNetworkReply::UnknownServerError;
681 } else if (httpStatusCode >= 400) {
682 // content error we did not handle above
683 code = QNetworkReply::UnknownContentError;
684 } else {
685 qWarning("QNetworkAccess: got HTTP status code %d which is not expected from url: \"%s\"",
686 httpStatusCode, qPrintable(url.toString()));
687 code = QNetworkReply::ProtocolFailure;
688 }
689 };
690
691 return code;
692}
693
694QT_END_NAMESPACE
695
696#include "moc_qnetworkreplywasmimpl_p.cpp"
void setup(QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *outgoingData)
void emitReplyError(QNetworkReply::NetworkError errorCode, const QString &)
void setStatusCode(int status, const QByteArray &statusText)
void headersReceived(const QByteArray &buffer)
QNetworkAccessManagerPrivate * managerPrivate
void emitDataReadProgress(qint64 done, qint64 total)
void dataReceived(const QByteArray &buffer)
qint64 size() const override
For open random-access devices, this function returns the size of the device.
virtual qint64 bytesAvailable() const override
Returns the number of bytes that are available for reading.
virtual qint64 readData(char *data, qint64 maxlen) override
virtual void abort() override
Aborts the operation immediately and closes any network connections still open.
virtual bool isSequential() const override
\reimp
virtual void close() override
Closes this device for reading.
Combined button and popup list for selecting options.
constexpr int getArraySize(int factor)
static int parseHeaderName(const QByteArray &headerName)
The FetchContext class ensures the requestData object remains valid while a fetch operation is pendin...
FetchContext(QNetworkReplyWasmImplPrivate *networkReply)
QNetworkReplyWasmImplPrivate * reply