blob: 2efe9f7583fa9142fbe571a098249e1709731459 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "perfetto/ext/base/http/http_server.h"
#include <cinttypes>
#include <vector>
#include "perfetto/ext/base/base64.h"
#include "perfetto/ext/base/endian.h"
#include "perfetto/ext/base/http/sha1.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/string_view.h"
namespace perfetto {
namespace base {
namespace {
constexpr size_t kMaxPayloadSize = 64 * 1024 * 1024;
constexpr size_t kMaxRequestSize = kMaxPayloadSize + 4096;
enum WebsocketOpcode : uint8_t {
kOpcodeContinuation = 0x0,
kOpcodeText = 0x1,
kOpcodeBinary = 0x2,
kOpcodeDataUnused = 0x3,
kOpcodeClose = 0x8,
kOpcodePing = 0x9,
kOpcodePong = 0xA,
kOpcodeControlUnused = 0xB,
};
// From https://datatracker.ietf.org/doc/html/rfc6455#section-1.3.
constexpr char kWebsocketGuid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
} // namespace
HttpServer::HttpServer(TaskRunner* task_runner, HttpRequestHandler* req_handler)
: task_runner_(task_runner), req_handler_(req_handler) {}
HttpServer::~HttpServer() = default;
void HttpServer::Start(int port) {
std::string ipv4_addr = "127.0.0.1:" + std::to_string(port);
std::string ipv6_addr = "[::1]:" + std::to_string(port);
sock4_ = UnixSocket::Listen(ipv4_addr, this, task_runner_, SockFamily::kInet,
SockType::kStream);
bool ipv4_listening = sock4_ && sock4_->is_listening();
if (!ipv4_listening) {
PERFETTO_PLOG("Failed to listen on IPv4 socket");
sock4_.reset();
}
sock6_ = UnixSocket::Listen(ipv6_addr, this, task_runner_, SockFamily::kInet6,
SockType::kStream);
bool ipv6_listening = sock6_ && sock6_->is_listening();
if (!ipv6_listening) {
PERFETTO_PLOG("Failed to listen on IPv6 socket");
sock6_.reset();
}
}
void HttpServer::AddAllowedOrigin(const std::string& origin) {
allowed_origins_.emplace_back(origin);
}
void HttpServer::OnNewIncomingConnection(
UnixSocket*, // The listening socket, irrelevant here.
std::unique_ptr<UnixSocket> sock) {
PERFETTO_LOG("[HTTP] New connection");
clients_.emplace_back(std::move(sock));
}
void HttpServer::OnConnect(UnixSocket*, bool) {}
void HttpServer::OnDisconnect(UnixSocket* sock) {
PERFETTO_LOG("[HTTP] Client disconnected");
for (auto it = clients_.begin(); it != clients_.end(); ++it) {
if (it->sock.get() == sock) {
req_handler_->OnHttpConnectionClosed(&*it);
clients_.erase(it);
return;
}
}
PERFETTO_DFATAL("[HTTP] Untracked client in OnDisconnect()");
}
void HttpServer::OnDataAvailable(UnixSocket* sock) {
HttpServerConnection* conn = nullptr;
for (auto it = clients_.begin(); it != clients_.end() && !conn; ++it)
conn = (it->sock.get() == sock) ? &*it : nullptr;
PERFETTO_CHECK(conn);
char* rxbuf = reinterpret_cast<char*>(conn->rxbuf.Get());
for (;;) {
size_t avail = conn->rxbuf_avail();
PERFETTO_CHECK(avail <= kMaxRequestSize);
if (avail == 0) {
conn->SendResponseAndClose("413 Payload Too Large");
return;
}
size_t rsize = sock->Receive(&rxbuf[conn->rxbuf_used], avail);
conn->rxbuf_used += rsize;
if (rsize == 0 || conn->rxbuf_avail() == 0)
break;
}
// At this point |rxbuf| can contain a partial HTTP request, a full one or
// more (in case of HTTP Keepalive pipelining).
for (;;) {
size_t bytes_consumed;
if (conn->is_websocket()) {
bytes_consumed = ParseOneWebsocketFrame(conn);
} else {
bytes_consumed = ParseOneHttpRequest(conn);
}
if (bytes_consumed == 0)
break;
memmove(rxbuf, &rxbuf[bytes_consumed], conn->rxbuf_used - bytes_consumed);
conn->rxbuf_used -= bytes_consumed;
}
}
// Parses the HTTP request and invokes HandleRequest(). It returns the size of
// the HTTP header + body that has been processed or 0 if there isn't enough
// data for a full HTTP request in the buffer.
size_t HttpServer::ParseOneHttpRequest(HttpServerConnection* conn) {
auto* rxbuf = reinterpret_cast<char*>(conn->rxbuf.Get());
StringView buf_view(rxbuf, conn->rxbuf_used);
bool has_parsed_first_line = false;
bool all_headers_received = false;
HttpRequest http_req(conn);
size_t body_size = 0;
// This loop parses the HTTP request headers and sets the |body_offset|.
while (!buf_view.empty()) {
size_t next = buf_view.find('\n');
if (next == StringView::npos)
break;
StringView line = buf_view.substr(0, next);
buf_view = buf_view.substr(next + 1); // Eat the current line.
while (!line.empty() && (line.at(line.size() - 1) == '\r' ||
line.at(line.size() - 1) == '\n')) {
line = line.substr(0, line.size() - 1);
}
if (!has_parsed_first_line) {
// Parse the "GET /xxx HTTP/1.1" line.
has_parsed_first_line = true;
size_t space = line.find(' ');
if (space == std::string::npos || space + 2 >= line.size()) {
conn->SendResponseAndClose("400 Bad Request");
return 0;
}
http_req.method = line.substr(0, space);
size_t uri_size = line.find(' ', space + 1) - (space + 1);
http_req.uri = line.substr(space + 1, uri_size);
} else if (line.empty()) {
all_headers_received = true;
// The CR-LF marker that separates headers from body.
break;
} else {
// Parse HTTP headers, e.g. "Content-Length: 1234".
size_t col = line.find(':');
if (col == StringView::npos) {
PERFETTO_DLOG("[HTTP] Malformed HTTP header: \"%s\"",
line.ToStdString().c_str());
conn->SendResponseAndClose("400 Bad Request", {}, "Bad HTTP header");
return 0;
}
auto hdr_name = line.substr(0, col);
auto hdr_value = line.substr(col + 2);
if (http_req.num_headers < http_req.headers.size()) {
http_req.headers[http_req.num_headers++] = {hdr_name, hdr_value};
} else {
conn->SendResponseAndClose("400 Bad Request", {},
"Too many HTTP headers");
}
if (hdr_name.CaseInsensitiveEq("content-length")) {
body_size = static_cast<size_t>(atoi(hdr_value.ToStdString().c_str()));
} else if (hdr_name.CaseInsensitiveEq("origin")) {
http_req.origin = hdr_value;
if (IsOriginAllowed(hdr_value))
conn->origin_allowed_ = hdr_value.ToStdString();
} else if (hdr_name.CaseInsensitiveEq("connection")) {
conn->keepalive_ = hdr_value.CaseInsensitiveEq("keep-alive");
http_req.is_websocket_handshake =
hdr_value.CaseInsensitiveEq("upgrade");
}
}
}
// At this point |buf_view| has been stripped of the header and contains the
// request body. We don't know yet if we have all the bytes for it or not.
PERFETTO_CHECK(buf_view.size() <= conn->rxbuf_used);
const size_t headers_size = conn->rxbuf_used - buf_view.size();
if (body_size + headers_size >= kMaxRequestSize ||
body_size > kMaxPayloadSize) {
conn->SendResponseAndClose("413 Payload Too Large");
return 0;
}
// If we can't read the full request return and try again next time with more
// data.
if (!all_headers_received || buf_view.size() < body_size)
return 0;
http_req.body = buf_view.substr(0, body_size);
PERFETTO_LOG("[HTTP] %.*s %.*s [body=%zuB, origin=\"%.*s\"]",
static_cast<int>(http_req.method.size()), http_req.method.data(),
static_cast<int>(http_req.uri.size()), http_req.uri.data(),
http_req.body.size(), static_cast<int>(http_req.origin.size()),
http_req.origin.data());
if (http_req.method == "OPTIONS") {
HandleCorsPreflightRequest(http_req);
} else {
// Let the HttpHandler handle the request.
req_handler_->OnHttpRequest(http_req);
}
// The handler is expected to send a response. If not, bail with a HTTP 500.
if (!conn->headers_sent_)
conn->SendResponseAndClose("500 Internal Server Error");
// Allow chaining multiple responses in the same HTTP-Keepalive connection.
conn->headers_sent_ = false;
return headers_size + body_size;
}
void HttpServer::HandleCorsPreflightRequest(const HttpRequest& req) {
req.conn->SendResponseAndClose(
"204 No Content",
{
"Access-Control-Allow-Methods: POST, GET, OPTIONS", //
"Access-Control-Allow-Headers: *", //
"Access-Control-Max-Age: 86400", //
"Access-Control-Allow-Private-Network: true", //
});
}
bool HttpServer::IsOriginAllowed(StringView origin) {
for (const std::string& allowed_origin : allowed_origins_) {
if (origin.CaseInsensitiveEq(StringView(allowed_origin))) {
return true;
}
}
if (!origin_error_logged_ && !origin.empty()) {
origin_error_logged_ = true;
PERFETTO_ELOG(
"[HTTP] The origin \"%.*s\" is not allowed, Access-Control-Allow-Origin"
" won't be emitted. If this request comes from a browser it will fail.",
static_cast<int>(origin.size()), origin.data());
}
return false;
}
void HttpServerConnection::UpgradeToWebsocket(const HttpRequest& req) {
PERFETTO_CHECK(req.is_websocket_handshake);
// |origin_allowed_| is set to the req.origin only if it's in the allowlist.
if (origin_allowed_.empty())
return SendResponseAndClose("403 Forbidden", {}, "Origin not allowed");
auto ws_ver = req.GetHeader("sec-webSocket-version").value_or(StringView());
auto ws_key = req.GetHeader("sec-webSocket-key").value_or(StringView());
if (!ws_ver.CaseInsensitiveEq("13"))
return SendResponseAndClose("505 HTTP Version Not Supported", {});
if (ws_key.size() != 24) {
// The nonce must be a base64 encoded 16 bytes value (24 after base64).
return SendResponseAndClose("400 Bad Request", {});
}
// From https://datatracker.ietf.org/doc/html/rfc6455#section-1.3 :
// For this header field, the server has to take the value (as present
// in the header field, e.g., the base64-encoded [RFC4648] version minus
// any leading and trailing whitespace) and concatenate this with the
// Globally Unique Identifier (GUID, [RFC4122]) "258EAFA5-E914-47DA-
// 95CA-C5AB0DC85B11" in string form, which is unlikely to be used by
// network endpoints that do not understand the WebSocket Protocol. A
// SHA-1 hash (160 bits) [FIPS.180-3], base64-encoded (see Section 4 of
// [RFC4648]), of this concatenation is then returned in the server's
// handshake.
StackString<128> signed_nonce("%.*s%s", static_cast<int>(ws_key.size()),
ws_key.data(), kWebsocketGuid);
auto digest = SHA1Hash(signed_nonce.c_str(), signed_nonce.len());
std::string digest_b64 = Base64Encode(digest.data(), digest.size());
StackString<128> accept_hdr("Sec-WebSocket-Accept: %s", digest_b64.c_str());
std::initializer_list<const char*> headers = {
"Upgrade: websocket", //
"Connection: Upgrade", //
accept_hdr.c_str(), //
};
PERFETTO_DLOG("[HTTP] Handshaking WebSocket for %.*s",
static_cast<int>(req.uri.size()), req.uri.data());
for (const char* hdr : headers)
PERFETTO_DLOG("> %s", hdr);
SendResponseHeaders("101 Switching Protocols", headers,
HttpServerConnection::kOmitContentLength);
is_websocket_ = true;
}
size_t HttpServer::ParseOneWebsocketFrame(HttpServerConnection* conn) {
auto* rxbuf = reinterpret_cast<uint8_t*>(conn->rxbuf.Get());
const size_t frame_size = conn->rxbuf_used;
uint8_t* rd = rxbuf;
uint8_t* const end = rxbuf + frame_size;
auto avail = [&] {
PERFETTO_CHECK(rd <= end);
return static_cast<size_t>(end - rd);
};
// From https://datatracker.ietf.org/doc/html/rfc6455#section-5.2 :
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-------+-+-------------+-------------------------------+
// |F|R|R|R| opcode|M| Payload len | Extended payload length |
// |I|S|S|S| (4) |A| (7) | (16/64) |
// |N|V|V|V| |S| | (if payload len==126/127) |
// | |1|2|3| |K| | |
// +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
// | Extended payload length continued, if payload len == 127 |
// + - - - - - - - - - - - - - - - +-------------------------------+
// | |Masking-key, if MASK set to 1 |
// +-------------------------------+-------------------------------+
// | Masking-key (continued) | Payload Data |
// +-------------------------------- - - - - - - - - - - - - - - - +
// : Payload Data continued ... :
// + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
// | Payload Data continued ... |
// +---------------------------------------------------------------+
if (avail() < 2)
return 0; // Can't even decode the frame header. Wait for more data.
uint8_t h0 = *(rd++);
uint8_t h1 = *(rd++);
const bool fin = !!(h0 & 0x80); // This bit is set if this frame is the last
// data to complete this message.
const uint8_t opcode = h0 & 0x0F;
const bool has_mask = !!(h1 & 0x80);
uint64_t payload_len_u64 = (h1 & 0x7F);
uint8_t extended_payload_size = 0;
if (payload_len_u64 == 126) {
extended_payload_size = 2;
} else if (payload_len_u64 == 127) {
extended_payload_size = 8;
}
if (extended_payload_size > 0) {
if (avail() < extended_payload_size)
return 0; // Not enough data to read the extended header.
payload_len_u64 = 0;
for (uint8_t i = 0; i < extended_payload_size; ++i) {
payload_len_u64 <<= 8;
payload_len_u64 |= *(rd++);
}
}
if (payload_len_u64 >= kMaxPayloadSize) {
PERFETTO_ELOG("[HTTP] Websocket payload too big (%" PRIu64 " > %zu)",
payload_len_u64, kMaxPayloadSize);
conn->Close();
return 0;
}
const size_t payload_len = static_cast<size_t>(payload_len_u64);
if (!has_mask) {
// https://datatracker.ietf.org/doc/html/rfc6455#section-5.1
// The server MUST close the connection upon receiving a frame that is
// not masked.
PERFETTO_ELOG("[HTTP] Websocket inbound frames must be masked");
conn->Close();
return 0;
}
uint8_t mask[4];
if (avail() < sizeof(mask))
return 0; // Not enough data to read the masking key.
memcpy(mask, rd, sizeof(mask));
rd += sizeof(mask);
PERFETTO_DLOG(
"[HTTP] Websocket fin=%d opcode=%u, payload_len=%zu (avail=%zu), "
"mask=%02x%02x%02x%02x",
fin, opcode, payload_len, avail(), mask[0], mask[1], mask[2], mask[3]);
if (avail() < payload_len)
return 0; // Not enouh data to read the payload.
uint8_t* const payload_start = rd;
// Unmask the payload.
for (uint32_t i = 0; i < payload_len; ++i)
payload_start[i] ^= mask[i % sizeof(mask)];
if (opcode == kOpcodePing) {
PERFETTO_DLOG("[HTTP] Websocket PING");
conn->SendWebsocketFrame(kOpcodePong, payload_start, payload_len);
} else if (opcode == kOpcodeBinary || opcode == kOpcodeText ||
opcode == kOpcodeContinuation) {
// We do NOT handle fragmentation. We propagate all fragments as individual
// messages, breaking the message-oriented nature of websockets. We do this
// because in all our use cases we need only a byte stream without caring
// about message boundaries.
// If we wanted to support fragmentation, we'd have to stash
// kOpcodeContinuation messages in a buffer, until we FIN bit is set.
// When loading traces with trace processor, the messages can be up to
// 32MB big (SLICE_SIZE in trace_stream.ts). The double-buffering would
// slow down significantly trace loading with no benefits.
WebsocketMessage msg(conn);
msg.data =
StringView(reinterpret_cast<const char*>(payload_start), payload_len);
msg.is_text = opcode == kOpcodeText;
req_handler_->OnWebsocketMessage(msg);
} else if (opcode == kOpcodeClose) {
conn->Close();
} else {
PERFETTO_LOG("Unsupported WebSocket opcode: %d", opcode);
}
return static_cast<size_t>(rd - rxbuf) + payload_len;
}
void HttpServerConnection::SendResponseHeaders(
const char* http_code,
std::initializer_list<const char*> headers,
size_t content_length) {
PERFETTO_CHECK(!headers_sent_);
PERFETTO_CHECK(!is_websocket_);
headers_sent_ = true;
std::vector<char> resp_hdr;
resp_hdr.reserve(512);
bool has_connection_header = false;
auto append = [&resp_hdr](const char* str) {
resp_hdr.insert(resp_hdr.end(), str, str + strlen(str));
};
append("HTTP/1.1 ");
append(http_code);
append("\r\n");
for (const char* hdr_cstr : headers) {
StringView hdr = (hdr_cstr);
if (hdr.empty())
continue;
has_connection_header |= hdr.substr(0, 11).CaseInsensitiveEq("connection:");
append(hdr_cstr);
append("\r\n");
}
content_len_actual_ = 0;
content_len_headers_ = content_length;
if (content_length != kOmitContentLength) {
append("Content-Length: ");
append(std::to_string(content_length).c_str());
append("\r\n");
}
if (!has_connection_header) {
// Various clients (e.g., python's http.client) assume that a HTTP
// connection is keep-alive if the server says nothing, even when they do
// NOT ask for it. Hence we must be explicit. If we are about to close the
// connection, we must say so.
append(keepalive_ ? "Connection: keep-alive\r\n" : "Connection: close\r\n");
}
if (!origin_allowed_.empty()) {
append("Access-Control-Allow-Origin: ");
append(origin_allowed_.c_str());
append("\r\n");
append("Vary: Origin\r\n");
}
append("\r\n"); // End-of-headers marker.
sock->Send(resp_hdr.data(),
resp_hdr.size()); // Send response headers.
}
void HttpServerConnection::SendResponseBody(const void* data, size_t len) {
PERFETTO_CHECK(!is_websocket_);
if (data == nullptr) {
PERFETTO_DCHECK(len == 0);
return;
}
content_len_actual_ += len;
PERFETTO_CHECK(content_len_actual_ <= content_len_headers_ ||
content_len_headers_ == kOmitContentLength);
sock->Send(data, len);
}
void HttpServerConnection::Close() {
sock->Shutdown(/*notify=*/true);
}
void HttpServerConnection::SendResponse(
const char* http_code,
std::initializer_list<const char*> headers,
StringView content,
bool force_close) {
if (force_close)
keepalive_ = false;
SendResponseHeaders(http_code, headers, content.size());
SendResponseBody(content.data(), content.size());
if (!keepalive_)
Close();
}
void HttpServerConnection::SendWebsocketMessage(const void* data, size_t len) {
SendWebsocketFrame(kOpcodeBinary, data, len);
}
void HttpServerConnection::SendWebsocketFrame(uint8_t opcode,
const void* payload,
size_t payload_len) {
PERFETTO_CHECK(is_websocket_);
uint8_t hdr[10]{};
uint32_t hdr_len = 0;
hdr[0] = opcode | 0x80 /* FIN=1, no fragmentation */;
if (payload_len < 126) {
hdr_len = 2;
hdr[1] = static_cast<uint8_t>(payload_len);
} else if (payload_len < 0xffff) {
hdr_len = 4;
hdr[1] = 126; // Special value: Header extends for 2 bytes.
uint16_t len_be = HostToBE16(static_cast<uint16_t>(payload_len));
memcpy(&hdr[2], &len_be, sizeof(len_be));
} else {
hdr_len = 10;
hdr[1] = 127; // Special value: Header extends for 4 bytes.
uint64_t len_be = HostToBE64(payload_len);
memcpy(&hdr[2], &len_be, sizeof(len_be));
}
sock->Send(hdr, hdr_len);
if (payload && payload_len > 0)
sock->Send(payload, payload_len);
}
HttpServerConnection::HttpServerConnection(std::unique_ptr<UnixSocket> s)
: sock(std::move(s)), rxbuf(PagedMemory::Allocate(kMaxRequestSize)) {}
HttpServerConnection::~HttpServerConnection() = default;
std::optional<StringView> HttpRequest::GetHeader(StringView name) const {
for (size_t i = 0; i < num_headers; i++) {
if (headers[i].name.CaseInsensitiveEq(name))
return headers[i].value;
}
return std::nullopt;
}
HttpRequestHandler::~HttpRequestHandler() = default;
void HttpRequestHandler::OnWebsocketMessage(const WebsocketMessage&) {}
void HttpRequestHandler::OnHttpConnectionClosed(HttpServerConnection*) {}
} // namespace base
} // namespace perfetto