/**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtNetwork module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qnetworkreplywasmimpl_p.h" #include "qnetworkrequest.h" #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE using namespace emscripten; static void q_requestErrorCallback(val event) { if (event.isNull() || event.isUndefined()) return; val xhr = event["target"]; if (xhr.isNull() || xhr.isUndefined()) return; quintptr func = xhr["data-handler"].as(); QNetworkReplyWasmImplPrivate *reply = reinterpret_cast(func); Q_ASSERT(reply); int statusCode = xhr["status"].as(); QString reasonStr = QString::fromStdString(xhr["statusText"].as()); reply->setReplyAttributes(func, statusCode, reasonStr); if (statusCode >= 400 && !reasonStr.isEmpty()) reply->emitReplyError(reply->statusCodeFromHttp(statusCode, reply->request.url()), reasonStr); } static void q_progressCallback(val event) { if (event.isNull() || event.isUndefined()) return; val xhr = event["target"]; if (xhr.isNull() || xhr.isUndefined()) return; QNetworkReplyWasmImplPrivate *reply = reinterpret_cast(xhr["data-handler"].as()); Q_ASSERT(reply); if (xhr["status"].as() < 400) reply->emitDataReadProgress(event["loaded"].as(), event["total"].as()); } static void q_loadCallback(val event) { if (event.isNull() || event.isUndefined()) return; val xhr = event["target"]; if (xhr.isNull() || xhr.isUndefined()) return; QNetworkReplyWasmImplPrivate *reply = reinterpret_cast(xhr["data-handler"].as()); Q_ASSERT(reply); int status = xhr["status"].as(); if (status >= 300) { q_requestErrorCallback(event); return; } QString statusText = QString::fromStdString(xhr["statusText"].as()); int readyState = xhr["readyState"].as(); if (status == 200 || status == 203) { QString responseString; const std::string responseType = xhr["responseType"].as(); if (responseType.length() == 0 || responseType == "document" || responseType == "text") { responseString = QString::fromStdWString(xhr["responseText"].as()); } else if (responseType == "json") { responseString = QString::fromStdWString(val::global("JSON").call("stringify", xhr["response"])); } else if (responseType == "arraybuffer" || responseType == "blob") { // handle this data in the FileReader, triggered by the call to readAsArrayBuffer val blob = xhr["response"]; if (blob.isNull() || blob.isUndefined()) return; val reader = val::global("FileReader").new_(); if (reader.isNull() || reader.isUndefined()) return; reader.set("onload", val::module_property("qt_QNetworkReplyWasmImplPrivate_readBinary")); reader.set("data-handler", xhr["data-handler"]); reader.call("readAsArrayBuffer", blob); val::global("Module").delete_(reader); } if (readyState == 4) { // done reply->setReplyAttributes(xhr["data-handler"].as(), status, statusText); if (!responseString.isEmpty()) { QByteArray responseStringArray = responseString.toUtf8(); reply->dataReceived(responseStringArray, responseStringArray.size()); } } } if (status >= 400 && !statusText.isEmpty()) reply->emitReplyError(reply->statusCodeFromHttp(status, reply->request.url()), statusText); } static void q_responseHeadersCallback(val event) { if (event.isNull() || event.isUndefined()) return; val xhr = event["target"]; if (xhr.isNull() || xhr.isUndefined()) return; if (xhr["readyState"].as() == 2) { // HEADERS_RECEIVED std::string responseHeaders = xhr.call("getAllResponseHeaders"); if (!responseHeaders.empty()) { QNetworkReplyWasmImplPrivate *reply = reinterpret_cast(xhr["data-handler"].as()); Q_ASSERT(reply); reply->headersReceived(QString::fromStdString(responseHeaders)); } } } static void q_readBinary(val event) { if (event.isNull() || event.isUndefined()) return; val fileReader = event["target"]; if (fileReader.isNull() || fileReader.isUndefined()) return; QNetworkReplyWasmImplPrivate *reply = reinterpret_cast(fileReader["data-handler"].as()); Q_ASSERT(reply); if (reply->state == QNetworkReplyPrivate::Finished || reply->state == QNetworkReplyPrivate::Aborted) return; // Set up source typed array val result = fileReader["result"]; // ArrayBuffer if (result.isNull() || result.isUndefined()) return; val Uint8Array = val::global("Uint8Array"); val sourceTypedArray = Uint8Array.new_(result); // Allocate and set up destination typed array const quintptr size = result["byteLength"].as(); QByteArray buffer(size, Qt::Uninitialized); val destinationTypedArray = Uint8Array.new_(val::module_property("HEAPU8")["buffer"], reinterpret_cast(buffer.data()), size); destinationTypedArray.call("set", sourceTypedArray); reply->dataReceived(buffer, buffer.size()); event.delete_(fileReader); Uint8Array.delete_(sourceTypedArray); QCoreApplication::processEvents(); } EMSCRIPTEN_BINDINGS(qtNetworkModule) { function("qt_QNetworkReplyWasmImplPrivate_requestErrorCallback", q_requestErrorCallback); function("qt_QNetworkReplyWasmImplPrivate_progressCallback", q_progressCallback); function("qt_QNetworkReplyWasmImplPrivate_loadCallback", q_loadCallback); function("qt_QNetworkReplyWasmImplPrivate_responseHeadersCallback", q_responseHeadersCallback); function("qt_QNetworkReplyWasmImplPrivate_readBinary", q_readBinary); } QNetworkReplyWasmImplPrivate::QNetworkReplyWasmImplPrivate() : QNetworkReplyPrivate() , managerPrivate(0) , downloadBufferReadPosition(0) , downloadBufferCurrentSize(0) , totalDownloadSize(0) , percentFinished(0) { } QNetworkReplyWasmImplPrivate::~QNetworkReplyWasmImplPrivate() { m_xhr.set("onerror", val::null()); m_xhr.set("onload", val::null()); m_xhr.set("onprogress", val::null()); m_xhr.set("onreadystatechange", val::null()); m_xhr.set("data-handler", val::null()); } QNetworkReplyWasmImpl::QNetworkReplyWasmImpl(QObject *parent) : QNetworkReply(*new QNetworkReplyWasmImplPrivate(), parent) { Q_D( QNetworkReplyWasmImpl); d->state = QNetworkReplyPrivate::Idle; } QNetworkReplyWasmImpl::~QNetworkReplyWasmImpl() { } QByteArray QNetworkReplyWasmImpl::methodName() const { const Q_D( QNetworkReplyWasmImpl); switch (operation()) { case QNetworkAccessManager::HeadOperation: return "HEAD"; case QNetworkAccessManager::GetOperation: return "GET"; case QNetworkAccessManager::PutOperation: return "PUT"; case QNetworkAccessManager::PostOperation: return "POST"; case QNetworkAccessManager::DeleteOperation: return "DELETE"; case QNetworkAccessManager::CustomOperation: return d->request.attribute(QNetworkRequest::CustomVerbAttribute).toByteArray(); default: break; } return QByteArray(); } void QNetworkReplyWasmImpl::close() { QNetworkReply::close(); setFinished(true); emit finished(); } void QNetworkReplyWasmImpl::abort() { Q_D( QNetworkReplyWasmImpl); if (d->state == QNetworkReplyPrivate::Finished || d->state == QNetworkReplyPrivate::Aborted) return; setError(QNetworkReply::OperationCanceledError, QStringLiteral("Operation canceled")); d->doAbort(); close(); d->state = QNetworkReplyPrivate::Aborted; } qint64 QNetworkReplyWasmImpl::bytesAvailable() const { Q_D(const QNetworkReplyWasmImpl); return QNetworkReply::bytesAvailable() + d->downloadBufferCurrentSize - d->downloadBufferReadPosition; } bool QNetworkReplyWasmImpl::isSequential() const { return true; } qint64 QNetworkReplyWasmImpl::size() const { return QNetworkReply::size(); } /*! \internal */ qint64 QNetworkReplyWasmImpl::readData(char *data, qint64 maxlen) { Q_D(QNetworkReplyWasmImpl); qint64 howMuch = qMin(maxlen, (d->downloadBuffer.size() - d->downloadBufferReadPosition)); memcpy(data, d->downloadBuffer.constData() + d->downloadBufferReadPosition, howMuch); d->downloadBufferReadPosition += howMuch; return howMuch; } void QNetworkReplyWasmImplPrivate::setup(QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *data) { Q_Q(QNetworkReplyWasmImpl); outgoingData = data; request = req; url = request.url(); operation = op; q->QIODevice::open(QIODevice::ReadOnly); if (outgoingData && outgoingData->isSequential()) { bool bufferingDisallowed = request.attribute(QNetworkRequest::DoNotBufferUploadDataAttribute, false).toBool(); if (bufferingDisallowed) { // if a valid content-length header for the request was supplied, we can disable buffering // if not, we will buffer anyway if (!request.header(QNetworkRequest::ContentLengthHeader).isValid()) { state = Buffering; _q_bufferOutgoingData(); return; } } else { // doSendRequest will be called when the buffering has finished. state = Buffering; _q_bufferOutgoingData(); return; } } // No outgoing data (POST, ..) doSendRequest(); } void QNetworkReplyWasmImplPrivate::setReplyAttributes(quintptr data, int statusCode, const QString &statusReason) { QNetworkReplyWasmImplPrivate *handler = reinterpret_cast(data); Q_ASSERT(handler); handler->q_func()->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusCode); if (!statusReason.isEmpty()) handler->q_func()->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, statusReason); } void QNetworkReplyWasmImplPrivate::doAbort() const { m_xhr.call("abort"); } void QNetworkReplyWasmImplPrivate::doSendRequest() { Q_Q(QNetworkReplyWasmImpl); totalDownloadSize = 0; m_xhr = val::global("XMLHttpRequest").new_(); std::string verb = q->methodName().toStdString(); m_xhr.call("open", verb, request.url().toString().toStdString()); m_xhr.set("onerror", val::module_property("qt_QNetworkReplyWasmImplPrivate_requestErrorCallback")); m_xhr.set("onload", val::module_property("qt_QNetworkReplyWasmImplPrivate_loadCallback")); m_xhr.set("onprogress", val::module_property("qt_QNetworkReplyWasmImplPrivate_progressCallback")); m_xhr.set("onreadystatechange", val::module_property("qt_QNetworkReplyWasmImplPrivate_responseHeadersCallback")); m_xhr.set("data-handler", val(quintptr(reinterpret_cast(this)))); QByteArray contentType = request.rawHeader("Content-Type"); // handle extra data val dataToSend = val::null(); QByteArray extraData; if (outgoingData) // data from post request extraData = outgoingData->readAll(); if (!extraData.isEmpty()) { dataToSend = val(typed_memory_view(extraData.size(), reinterpret_cast (extraData.constData()))); } m_xhr.set("responseType", val("blob")); // set request headers for (auto header : request.rawHeaderList()) { m_xhr.call("setRequestHeader", header.toStdString(), request.rawHeader(header).toStdString()); } m_xhr.call("send", dataToSend); } void QNetworkReplyWasmImplPrivate::emitReplyError(QNetworkReply::NetworkError errorCode, const QString &errorString) { Q_UNUSED(errorCode) Q_Q(QNetworkReplyWasmImpl); q->setError(errorCode, errorString); emit q->error(errorCode); q->setFinished(true); emit q->finished(); } void QNetworkReplyWasmImplPrivate::emitDataReadProgress(qint64 bytesReceived, qint64 bytesTotal) { Q_Q(QNetworkReplyWasmImpl); totalDownloadSize = bytesTotal; percentFinished = (bytesReceived / bytesTotal) * 100; emit q->downloadProgress(bytesReceived, bytesTotal); } void QNetworkReplyWasmImplPrivate::dataReceived(const QByteArray &buffer, int bufferSize) { Q_Q(QNetworkReplyWasmImpl); if (bufferSize > 0) q->setReadBufferSize(bufferSize); bytesDownloaded = bufferSize; if (percentFinished != 100) downloadBufferCurrentSize += bufferSize; else downloadBufferCurrentSize = bufferSize; totalDownloadSize = downloadBufferCurrentSize; downloadBuffer.append(buffer, bufferSize); emit q->readyRead(); if (downloadBufferCurrentSize == totalDownloadSize) { q->setFinished(true); emit q->readChannelFinished(); emit q->finished(); } } //taken from qnetworkrequest.cpp static int parseHeaderName(const QByteArray &headerName) { if (headerName.isEmpty()) return -1; switch (tolower(headerName.at(0))) { case 'c': if (qstricmp(headerName.constData(), "content-type") == 0) return QNetworkRequest::ContentTypeHeader; else if (qstricmp(headerName.constData(), "content-length") == 0) return QNetworkRequest::ContentLengthHeader; else if (qstricmp(headerName.constData(), "cookie") == 0) return QNetworkRequest::CookieHeader; break; case 'l': if (qstricmp(headerName.constData(), "location") == 0) return QNetworkRequest::LocationHeader; else if (qstricmp(headerName.constData(), "last-modified") == 0) return QNetworkRequest::LastModifiedHeader; break; case 's': if (qstricmp(headerName.constData(), "set-cookie") == 0) return QNetworkRequest::SetCookieHeader; else if (qstricmp(headerName.constData(), "server") == 0) return QNetworkRequest::ServerHeader; break; case 'u': if (qstricmp(headerName.constData(), "user-agent") == 0) return QNetworkRequest::UserAgentHeader; break; } return -1; // nothing found } void QNetworkReplyWasmImplPrivate::headersReceived(const QString &bufferString) { Q_Q(QNetworkReplyWasmImpl); if (!bufferString.isEmpty()) { QStringList headers = bufferString.split(QString::fromUtf8("\r\n"), QString::SkipEmptyParts); for (int i = 0; i < headers.size(); i++) { QString headerName = headers.at(i).split(QString::fromUtf8(": ")).at(0); QString headersValue = headers.at(i).split(QString::fromUtf8(": ")).at(1); if (headerName.isEmpty() || headersValue.isEmpty()) continue; int headerIndex = parseHeaderName(headerName.toLocal8Bit()); if (headerIndex == -1) q->setRawHeader(headerName.toLocal8Bit(), headersValue.toLocal8Bit()); else q->setHeader(static_cast(headerIndex), (QVariant)headersValue); } } emit q->metaDataChanged(); } void QNetworkReplyWasmImplPrivate::_q_bufferOutgoingDataFinished() { Q_Q(QNetworkReplyWasmImpl); // make sure this is only called once, ever. //_q_bufferOutgoingData may call it or the readChannelFinished emission if (state != Buffering) return; // disconnect signals QObject::disconnect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData())); QObject::disconnect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished())); // finally, start the request doSendRequest(); } void QNetworkReplyWasmImplPrivate::_q_bufferOutgoingData() { Q_Q(QNetworkReplyWasmImpl); if (!outgoingDataBuffer) { // first call, create our buffer outgoingDataBuffer = QSharedPointer::create(); QObject::connect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData())); QObject::connect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished())); } qint64 bytesBuffered = 0; qint64 bytesToBuffer = 0; // read data into our buffer forever { bytesToBuffer = outgoingData->bytesAvailable(); // unknown? just try 2 kB, this also ensures we always try to read the EOF if (bytesToBuffer <= 0) bytesToBuffer = 2*1024; char *dst = outgoingDataBuffer->reserve(bytesToBuffer); bytesBuffered = outgoingData->read(dst, bytesToBuffer); if (bytesBuffered == -1) { // EOF has been reached. outgoingDataBuffer->chop(bytesToBuffer); _q_bufferOutgoingDataFinished(); break; } else if (bytesBuffered == 0) { // nothing read right now, just wait until we get called again outgoingDataBuffer->chop(bytesToBuffer); break; } else { // don't break, try to read() again outgoingDataBuffer->chop(bytesToBuffer - bytesBuffered); } } } //taken from qhttpthreaddelegate.cpp QNetworkReply::NetworkError QNetworkReplyWasmImplPrivate::statusCodeFromHttp(int httpStatusCode, const QUrl &url) { QNetworkReply::NetworkError code; // we've got an error switch (httpStatusCode) { case 400: // Bad Request code = QNetworkReply::ProtocolInvalidOperationError; break; case 401: // Authorization required code = QNetworkReply::AuthenticationRequiredError; break; case 403: // Access denied code = QNetworkReply::ContentAccessDenied; break; case 404: // Not Found code = QNetworkReply::ContentNotFoundError; break; case 405: // Method Not Allowed code = QNetworkReply::ContentOperationNotPermittedError; break; case 407: code = QNetworkReply::ProxyAuthenticationRequiredError; break; case 409: // Resource Conflict code = QNetworkReply::ContentConflictError; break; case 410: // Content no longer available code = QNetworkReply::ContentGoneError; break; case 418: // I'm a teapot code = QNetworkReply::ProtocolInvalidOperationError; break; case 500: // Internal Server Error code = QNetworkReply::InternalServerError; break; case 501: // Server does not support this functionality code = QNetworkReply::OperationNotImplementedError; break; case 503: // Service unavailable code = QNetworkReply::ServiceUnavailableError; break; default: if (httpStatusCode > 500) { // some kind of server error code = QNetworkReply::UnknownServerError; } else if (httpStatusCode >= 400) { // content error we did not handle above code = QNetworkReply::UnknownContentError; } else { qWarning("QNetworkAccess: got HTTP status code %d which is not expected from url: \"%s\"", httpStatusCode, qPrintable(url.toString())); code = QNetworkReply::ProtocolFailure; } }; return code; } QT_END_NAMESPACE