blob: 31da6fd61608fa2ae6c979e754be920ae498cf1f [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 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 "http2protocol_p.h"
#include "http2frames_p.h"
#include "private/qhttpnetworkrequest_p.h"
#include "private/qhttpnetworkreply_p.h"
#include <access/qhttp2configuration.h>
#include <QtCore/qbytearray.h>
#include <QtCore/qstring.h>
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(QT_HTTP2, "qt.network.http2")
namespace Http2
{
// 3.5 HTTP/2 Connection Preface:
// "That is, the connection preface starts with the string
// PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n)."
const char Http2clientPreface[clientPrefaceLength] =
{0x50, 0x52, 0x49, 0x20, 0x2a, 0x20,
0x48, 0x54, 0x54, 0x50, 0x2f, 0x32,
0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a,
0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a};
Frame configurationToSettingsFrame(const QHttp2Configuration &config)
{
// 6.5 SETTINGS
FrameWriter builder(FrameType::SETTINGS, FrameFlag::EMPTY, connectionStreamID);
// Server push:
builder.append(Settings::ENABLE_PUSH_ID);
builder.append(int(config.serverPushEnabled()));
// Stream receive window size:
builder.append(Settings::INITIAL_WINDOW_SIZE_ID);
builder.append(config.streamReceiveWindowSize());
if (config.maxFrameSize() != minPayloadLimit) {
builder.append(Settings::MAX_FRAME_SIZE_ID);
builder.append(config.maxFrameSize());
}
// TODO: In future, if the need is proven, we can
// also send decoding table size and header list size.
// For now, defaults suffice.
return builder.outboundFrame();
}
QByteArray settingsFrameToBase64(const Frame &frame)
{
// SETTINGS frame's payload consists of pairs:
// 2-byte-identifier | 4-byte-value == multiple of 6.
Q_ASSERT(frame.payloadSize() && !(frame.payloadSize() % 6));
const char *src = reinterpret_cast<const char *>(frame.dataBegin());
const QByteArray wrapper(QByteArray::fromRawData(src, int(frame.dataSize())));
// 3.2.1
// The content of the HTTP2-Settings header field is the payload
// of a SETTINGS frame (Section 6.5), encoded as a base64url string
// (that is, the URL- and filename-safe Base64 encoding described in
// Section 5 of [RFC4648], with any trailing '=' characters omitted).
return wrapper.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
}
void appendProtocolUpgradeHeaders(const QHttp2Configuration &config, QHttpNetworkRequest *request)
{
Q_ASSERT(request);
// RFC 2616, 14.10
// RFC 7540, 3.2
QByteArray value(request->headerField("Connection"));
// We _append_ 'Upgrade':
if (value.size())
value += ", ";
value += "Upgrade, HTTP2-Settings";
request->setHeaderField("Connection", value);
// This we just (re)write.
request->setHeaderField("Upgrade", "h2c");
const Frame frame(configurationToSettingsFrame(config));
// This we just (re)write.
request->setHeaderField("HTTP2-Settings", settingsFrameToBase64(frame));
}
void qt_error(quint32 errorCode, QNetworkReply::NetworkError &error,
QString &errorMessage)
{
if (errorCode > quint32(HTTP_1_1_REQUIRED)) {
error = QNetworkReply::ProtocolFailure;
errorMessage = QLatin1String("RST_STREAM with unknown error code (%1)");
errorMessage = errorMessage.arg(errorCode);
return;
}
const Http2Error http2Error = Http2Error(errorCode);
switch (http2Error) {
case HTTP2_NO_ERROR:
error = QNetworkReply::NoError;
errorMessage.clear();
break;
case PROTOCOL_ERROR:
error = QNetworkReply::ProtocolFailure;
errorMessage = QLatin1String("HTTP/2 protocol error");
break;
case INTERNAL_ERROR:
error = QNetworkReply::InternalServerError;
errorMessage = QLatin1String("Internal server error");
break;
case FLOW_CONTROL_ERROR:
error = QNetworkReply::ProtocolFailure;
errorMessage = QLatin1String("Flow control error");
break;
case SETTINGS_TIMEOUT:
error = QNetworkReply::TimeoutError;
errorMessage = QLatin1String("SETTINGS ACK timeout error");
break;
case STREAM_CLOSED:
error = QNetworkReply::ProtocolFailure;
errorMessage = QLatin1String("Server received frame(s) on a half-closed stream");
break;
case FRAME_SIZE_ERROR:
error = QNetworkReply::ProtocolFailure;
errorMessage = QLatin1String("Server received a frame with an invalid size");
break;
case REFUSE_STREAM:
error = QNetworkReply::ProtocolFailure;
errorMessage = QLatin1String("Server refused a stream");
break;
case CANCEL:
error = QNetworkReply::ProtocolFailure;
errorMessage = QLatin1String("Stream is no longer needed");
break;
case COMPRESSION_ERROR:
error = QNetworkReply::ProtocolFailure;
errorMessage = QLatin1String("Server is unable to maintain the "
"header compression context for the connection");
break;
case CONNECT_ERROR:
// TODO: in Qt6 we'll have to add more error codes in QNetworkReply.
error = QNetworkReply::UnknownNetworkError;
errorMessage = QLatin1String("The connection established in response "
"to a CONNECT request was reset or abnormally closed");
break;
case ENHANCE_YOUR_CALM:
error = QNetworkReply::UnknownServerError;
errorMessage = QLatin1String("Server dislikes our behavior, excessive load detected.");
break;
case INADEQUATE_SECURITY:
error = QNetworkReply::ContentAccessDenied;
errorMessage = QLatin1String("The underlying transport has properties "
"that do not meet minimum security "
"requirements");
break;
case HTTP_1_1_REQUIRED:
error = QNetworkReply::ProtocolFailure;
errorMessage = QLatin1String("Server requires that HTTP/1.1 "
"be used instead of HTTP/2.");
}
}
QString qt_error_string(quint32 errorCode)
{
QNetworkReply::NetworkError error = QNetworkReply::NoError;
QString message;
qt_error(errorCode, error, message);
return message;
}
QNetworkReply::NetworkError qt_error(quint32 errorCode)
{
QNetworkReply::NetworkError error = QNetworkReply::NoError;
QString message;
qt_error(errorCode, error, message);
return error;
}
bool is_protocol_upgraded(const QHttpNetworkReply &reply)
{
if (reply.statusCode() == 101) {
// Do some minimal checks here - we expect 'Upgrade: h2c' to be found.
const auto &header = reply.header();
for (const QPair<QByteArray, QByteArray> &field : header) {
if (field.first.compare("upgrade", Qt::CaseInsensitive) == 0 &&
field.second.compare("h2c", Qt::CaseInsensitive) == 0)
return true;
}
}
return false;
}
} // namespace Http2
QT_END_NAMESPACE