diff options
Diffstat (limited to 'src/network/access/qhttp2protocolhandler.cpp')
-rw-r--r-- | src/network/access/qhttp2protocolhandler.cpp | 139 |
1 files changed, 108 insertions, 31 deletions
diff --git a/src/network/access/qhttp2protocolhandler.cpp b/src/network/access/qhttp2protocolhandler.cpp index 4c9e722166..39dd460881 100644 --- a/src/network/access/qhttp2protocolhandler.cpp +++ b/src/network/access/qhttp2protocolhandler.cpp @@ -264,7 +264,8 @@ void QHttp2ProtocolHandler::_q_uploadDataDestroyed(QObject *uploadData) void QHttp2ProtocolHandler::_q_readyRead() { - _q_receiveReply(); + if (!goingAway || activeStreams.size()) + _q_receiveReply(); } void QHttp2ProtocolHandler::_q_receiveReply() @@ -272,6 +273,11 @@ void QHttp2ProtocolHandler::_q_receiveReply() Q_ASSERT(m_socket); Q_ASSERT(m_channel); + if (goingAway && activeStreams.isEmpty()) { + m_channel->close(); + return; + } + while (!goingAway || activeStreams.size()) { const auto result = frameReader.read(*m_socket); switch (result) { @@ -387,7 +393,8 @@ bool QHttp2ProtocolHandler::sendRequest() initReplyFromPushPromise(message, key); } - const auto streamsToUse = std::min<quint32>(maxConcurrentStreams - activeStreams.size(), + const auto streamsToUse = std::min<quint32>(maxConcurrentStreams > quint32(activeStreams.size()) + ? maxConcurrentStreams - quint32(activeStreams.size()) : 0, requests.size()); auto it = requests.begin(); for (quint32 i = 0; i < streamsToUse; ++i) { @@ -489,6 +496,10 @@ bool QHttp2ProtocolHandler::sendHEADERS(Stream &stream) #ifndef QT_NO_NETWORKPROXY useProxy = m_connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy; #endif + if (stream.request().withCredentials()) { + m_connection->d_func()->createAuthorization(m_socket, stream.request()); + stream.request().d->needResendWithCredentials = false; + } const auto headers = build_headers(stream.request(), maxHeaderListSize, useProxy); if (!headers.size()) // nothing fits into maxHeaderListSize return false; @@ -514,7 +525,7 @@ bool QHttp2ProtocolHandler::sendDATA(Stream &stream) Q_ASSERT(replyPrivate); auto slot = std::min<qint32>(sessionSendWindowSize, stream.sendWindow); - while (!stream.data()->atEnd() && slot) { + while (replyPrivate->totallyUploadedData < request.contentLength() && slot) { qint64 chunkSize = 0; const uchar *src = reinterpret_cast<const uchar *>(stream.data()->readPointer(slot, chunkSize)); @@ -1013,8 +1024,10 @@ void QHttp2ProtocolHandler::handleContinuedHEADERS() if (activeStreams.contains(streamID)) { Stream &stream = activeStreams[streamID]; updateStream(stream, decoder.decodedHeader()); - // No DATA frames. - if (continuedFrames[0].flags() & FrameFlag::END_STREAM) { + // Needs to resend the request; we should finish and delete the current stream + const bool needResend = stream.request().d->needResendWithCredentials; + // No DATA frames. Or needs to resend. + if (continuedFrames[0].flags() & FrameFlag::END_STREAM || needResend) { finishStream(stream); deleteActiveStream(stream.streamID); } @@ -1072,17 +1085,12 @@ bool QHttp2ProtocolHandler::acceptSetting(Http2::Settings identifier, quint32 ne QMetaObject::invokeMethod(this, "resumeSuspendedStreams", Qt::QueuedConnection); } - if (identifier == Settings::MAX_CONCURRENT_STREAMS_ID) { - if (newValue > maxPeerConcurrentStreams) { - connectionError(PROTOCOL_ERROR, "SETTINGS invalid number of concurrent streams"); - return false; - } + if (identifier == Settings::MAX_CONCURRENT_STREAMS_ID) maxConcurrentStreams = newValue; - } if (identifier == Settings::MAX_FRAME_SIZE_ID) { if (newValue < Http2::minPayloadLimit || newValue > Http2::maxPayloadSize) { - connectionError(PROTOCOL_ERROR, "SETTGINGS max frame size is out of range"); + connectionError(PROTOCOL_ERROR, "SETTINGS max frame size is out of range"); return false; } maxFrameSize = newValue; @@ -1102,7 +1110,7 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader Qt::ConnectionType connectionType) { const auto httpReply = stream.reply(); - const auto &httpRequest = stream.request(); + auto &httpRequest = stream.request(); Q_ASSERT(httpReply || stream.state == Stream::remoteReserved); if (!httpReply) { @@ -1127,8 +1135,6 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader // moment and we are probably not done yet. So we extract url and set it // here, if needed. int statusCode = 0; - QUrl redirectUrl; - for (const auto &pair : headers) { const auto &name = pair.name; auto value = pair.value; @@ -1140,6 +1146,7 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader if (name == ":status") { statusCode = value.left(3).toInt(); httpReply->setStatusCode(statusCode); + m_channel->lastStatus = statusCode; // Mostly useless for http/2, needed for auth httpReplyPrivate->reasonPhrase = QString::fromLatin1(value.mid(4)); } else if (name == ":version") { httpReplyPrivate->majorVersion = value.at(5) - '0'; @@ -1150,8 +1157,6 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader if (ok) httpReply->setContentLength(length); } else { - if (name == "location") - redirectUrl = QUrl::fromEncoded(value); QByteArray binder(", "); if (name == "set-cookie") binder = "\n"; @@ -1159,21 +1164,91 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader } } - if (QHttpNetworkReply::isHttpRedirect(statusCode) && redirectUrl.isValid()) - httpReply->setRedirectUrl(redirectUrl); + const auto handleAuth = [&, this](const QByteArray &authField, bool isProxy) -> bool { + Q_ASSERT(httpReply); + const auto auth = authField.trimmed(); + if (auth.startsWith("Negotiate") || auth.startsWith("NTLM")) { + // @todo: We're supposed to fall back to http/1.1: + // https://docs.microsoft.com/en-us/iis/get-started/whats-new-in-iis-10/http2-on-iis#when-is-http2-not-supported + // "Windows authentication (NTLM/Kerberos/Negotiate) is not supported with HTTP/2. + // In this case IIS will fall back to HTTP/1.1." + // Though it might be OK to ignore this. The server shouldn't let us connect with + // HTTP/2 if it doesn't support us using it. + } else if (!auth.isEmpty()) { + // Somewhat mimics parts of QHttpNetworkConnectionChannel::handleStatus + bool resend = false; + const bool authenticateHandled = m_connection->d_func()->handleAuthenticateChallenge( + m_socket, httpReply, isProxy, resend); + if (authenticateHandled && resend) { + httpReply->d_func()->eraseData(); + // Add the request back in queue, we'll retry later now that + // we've gotten some username/password set on it: + httpRequest.d->needResendWithCredentials = true; + m_channel->spdyRequestsToSend.insert(httpRequest.priority(), stream.httpPair); + httpReply->d_func()->clearHeaders(); + // If we have data we were uploading we need to reset it: + if (stream.data()) { + stream.data()->reset(); + httpReplyPrivate->totallyUploadedData = 0; + } + return true; + } // else: Authentication failed or was cancelled + } + return false; + }; + + if (httpReply) { + // See Note further down. These statuses would in HTTP/1.1 be handled + // by QHttpNetworkConnectionChannel::handleStatus. But because h2 has + // multiple streams/requests in a single channel this structure does not + // map properly to that function. + if (httpReply->statusCode() == 401) { + const auto wwwAuth = httpReply->headerField("www-authenticate"); + if (handleAuth(wwwAuth, false)) { + sendRST_STREAM(stream.streamID, CANCEL); + markAsReset(stream.streamID); + // The stream is finalized and deleted after returning + return; + } // else: errors handled later + } else if (httpReply->statusCode() == 407) { + const auto proxyAuth = httpReply->headerField("proxy-authenticate"); + if (handleAuth(proxyAuth, true)) { + sendRST_STREAM(stream.streamID, CANCEL); + markAsReset(stream.streamID); + // The stream is finalized and deleted after returning + return; + } // else: errors handled later + } + } + + if (QHttpNetworkReply::isHttpRedirect(statusCode) && httpRequest.isFollowRedirects()) { + QHttpNetworkConnectionPrivate::ParseRedirectResult result = + m_connection->d_func()->parseRedirectResponse(httpReply); + if (result.errorCode != QNetworkReply::NoError) { + auto errorString = m_connection->d_func()->errorDetail(result.errorCode, m_socket); + finishStreamWithError(stream, result.errorCode, errorString); + sendRST_STREAM(stream.streamID, INTERNAL_ERROR); + markAsReset(stream.streamID); + return; + } + + if (result.redirectUrl.isValid()) + httpReply->setRedirectUrl(result.redirectUrl); + } if (httpReplyPrivate->isCompressed() && httpRequest.d->autoDecompress) httpReplyPrivate->removeAutoDecompressHeader(); - if (QHttpNetworkReply::isHttpRedirect(statusCode) - || statusCode == 401 || statusCode == 407) { - // These are the status codes that can trigger uploadByteDevice->reset() - // in QHttpNetworkConnectionChannel::handleStatus. Alas, we have no - // single request/reply, we multiplex several requests and thus we never - // simply call 'handleStatus'. If we have byte-device - we try to reset - // it here, we don't (and can't) handle any error during reset operation. - if (stream.data()) + if (QHttpNetworkReply::isHttpRedirect(statusCode)) { + // Note: This status code can trigger uploadByteDevice->reset() in + // QHttpNetworkConnectionChannel::handleStatus. Alas, we have no single + // request/reply, we multiplex several requests and thus we never simply + // call 'handleStatus'. If we have a byte-device - we try to reset it + // here, we don't (and can't) handle any error during reset operation. + if (stream.data()) { stream.data()->reset(); + httpReplyPrivate->totallyUploadedData = 0; + } } if (connectionType == Qt::DirectConnection) @@ -1242,10 +1317,12 @@ void QHttp2ProtocolHandler::finishStream(Stream &stream, Qt::ConnectionType conn if (stream.data()) stream.data()->disconnect(this); - if (connectionType == Qt::DirectConnection) - emit httpReply->finished(); - else - QMetaObject::invokeMethod(httpReply, "finished", connectionType); + if (!stream.request().d->needResendWithCredentials) { + if (connectionType == Qt::DirectConnection) + emit httpReply->finished(); + else + QMetaObject::invokeMethod(httpReply, "finished", connectionType); + } } qCDebug(QT_HTTP2) << "stream" << stream.streamID << "closed"; |