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