/* * Copyright (c) 2024, Dan Klishch * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include namespace HTTP { namespace { StringView method_name(Method method) { switch (method) { #define CASE(x) \ case Method::x: \ return #x##sv; ENUMERATE_METHODS(CASE) #undef CASE default: VERIFY_NOT_REACHED(); } } ByteBuffer format_request(RequestData const& data) { StringBuilder builder; builder.append(method_name(data.method)); builder.append(' '); builder.append(data.url); builder.append(" HTTP/1.1\r\n"sv); for (auto const& [name, value] : data.headers) { builder.append(name); builder.append(": "sv); builder.append(value); builder.append("\r\n"sv); } builder.append("\r\n"sv); return MUST(builder.to_byte_buffer()); } struct StatusCodeAndHeaders { u16 status_code; Vector
headers; }; Coroutine> receive_response_headers(AsyncStream& stream) { auto status_line = CO_TRY(co_await AsyncStreamHelpers::consume_until(stream, "\r\n"sv)); GenericLexer status_lexer { StringView { status_line } }; if (!status_lexer.next_is("HTTP/1.1 ")) { stream.reset(); co_return Error::from_string_literal("HTTP-version must be 'HTTP/1.1'"); } status_lexer.consume(9); auto status_code = status_lexer.consume_decimal_integer(); if (status_code.is_error()) { stream.reset(); co_return Error::from_string_literal("Invalid HTTP status code"); } Vector
headers; while (true) { auto header = StringView { CO_TRY(co_await AsyncStreamHelpers::consume_until(stream, "\r\n"sv)) }; if (header == "\r\n"sv) break; auto colon_position = header.find(':'); if (!colon_position.has_value()) { stream.reset(); co_return Error::from_string_literal("':' must be present in a header line"); } headers.append({ .header = header.substring_view(0, colon_position.value()), .value = header.substring_view(colon_position.value() + 1).trim_whitespace(), }); } co_return StatusCodeAndHeaders { .status_code = status_code.value(), .headers = headers, }; } } Coroutine>> Http11Response::create(Badge, RequestData&& data, AsyncStream& stream) { auto header = format_request(data); if (data.body.has()) { CO_TRY(co_await stream.write({ { header } })); } else if (data.body.has()) { auto& body = data.body.get().data; CO_TRY(co_await stream.write({ { header, body.bytes() } })); } else { VERIFY_NOT_REACHED(); } auto [status_code, headers] = CO_TRY(co_await receive_response_headers(stream)); Optional content_length; for (auto const& header : headers) { if (header.header.equals_ignoring_ascii_case("Content-Length"sv)) { content_length = header.value.to_number(); } } if (!content_length.has_value()) { stream.reset(); co_return Error::from_string_literal("'Content-Length' must be provided"); } auto body = make(stream, content_length.value()); co_return adopt_own(*new (nothrow) Http11Response(move(body), status_code, move(headers))); } }