123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533 |
- /*************************************************************************
- *
- * Copyright 2022 Realm Inc.
- *
- * 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.
- *
- **************************************************************************/
- #pragma once
- #include <cstdint>
- #include <type_traits>
- #include <map>
- #include <system_error>
- #include <iosfwd>
- #include <locale>
- #include <realm/util/optional.hpp>
- #include <realm/util/basic_system_errors.hpp>
- #include <realm/util/logger.hpp>
- #include <realm/string_data.hpp>
- namespace realm::sync {
- enum class HTTPParserError {
- None = 0,
- ContentTooLong,
- HeaderLineTooLong,
- MalformedResponse,
- MalformedRequest,
- BadRequest,
- };
- std::error_code make_error_code(HTTPParserError);
- } // namespace realm::sync
- namespace std {
- template <>
- struct is_error_code_enum<realm::sync::HTTPParserError> : std::true_type {
- };
- } // namespace std
- namespace realm::sync {
- /// See: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
- ///
- /// It is guaranteed that the backing integer value of this enum corresponds
- /// to the numerical code representing the status.
- enum class HTTPStatus {
- Unknown = 0,
- Continue = 100,
- SwitchingProtocols = 101,
- Ok = 200,
- Created = 201,
- Accepted = 202,
- NonAuthoritative = 203,
- NoContent = 204,
- ResetContent = 205,
- PartialContent = 206,
- MultipleChoices = 300,
- MovedPermanently = 301,
- Found = 302,
- SeeOther = 303,
- NotModified = 304,
- UseProxy = 305,
- SwitchProxy = 306,
- TemporaryRedirect = 307,
- PermanentRedirect = 308,
- BadRequest = 400,
- Unauthorized = 401,
- PaymentRequired = 402,
- Forbidden = 403,
- NotFound = 404,
- MethodNotAllowed = 405,
- NotAcceptable = 406,
- ProxyAuthenticationRequired = 407,
- RequestTimeout = 408,
- Conflict = 409,
- Gone = 410,
- LengthRequired = 411,
- PreconditionFailed = 412,
- PayloadTooLarge = 413,
- UriTooLong = 414,
- UnsupportedMediaType = 415,
- RangeNotSatisfiable = 416,
- ExpectationFailed = 417,
- ImATeapot = 418,
- MisdirectedRequest = 421,
- UpgradeRequired = 426,
- PreconditionRequired = 428,
- TooManyRequests = 429,
- RequestHeaderFieldsTooLarge = 431,
- UnavailableForLegalReasons = 451,
- InternalServerError = 500,
- NotImplemented = 501,
- BadGateway = 502,
- ServiceUnavailable = 503,
- GatewayTimeout = 504,
- HttpVersionNotSupported = 505,
- VariantAlsoNegotiates = 506,
- NotExtended = 510,
- NetworkAuthenticationRequired = 511,
- };
- bool valid_http_status_code(unsigned int code);
- /// See: https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
- enum class HTTPMethod {
- Options,
- Get,
- Head,
- Post,
- Put,
- Delete,
- Trace,
- Connect,
- };
- struct HTTPAuthorization {
- std::string scheme;
- std::map<std::string, std::string> values;
- };
- HTTPAuthorization parse_authorization(const std::string&);
- class HeterogeneousCaseInsensitiveCompare {
- public:
- using is_transparent = std::true_type;
- template <class A, class B>
- bool operator()(const A& a, const B& b) const noexcept
- {
- return comp(std::string_view(a), std::string_view(b));
- }
- private:
- bool comp(std::string_view a, std::string_view b) const noexcept
- {
- auto cmp = [](char lhs, char rhs) {
- return std::tolower(lhs, std::locale::classic()) < std::tolower(rhs, std::locale::classic());
- };
- return std::lexicographical_compare(begin(a), end(a), begin(b), end(b), cmp);
- }
- };
- /// Case-insensitive map suitable for storing HTTP headers.
- using HTTPHeaders = std::map<std::string, std::string, HeterogeneousCaseInsensitiveCompare>;
- struct HTTPRequest {
- HTTPMethod method = HTTPMethod::Get;
- HTTPHeaders headers;
- std::string path;
- /// If the request object has a body, the Content-Length header MUST be
- /// set to a string representation of the number of bytes in the body.
- /// FIXME: Relax this restriction, and also support Transfer-Encoding
- /// and other HTTP/1.1 features.
- util::Optional<std::string> body;
- };
- struct HTTPResponse {
- HTTPStatus status = HTTPStatus::Unknown;
- std::string reason;
- HTTPHeaders headers;
- // A body is only read from the response stream if the server sent the
- // Content-Length header.
- // FIXME: Support other transfer methods, including Transfer-Encoding and
- // HTTP/1.1 features.
- util::Optional<std::string> body;
- };
- /// Serialize HTTP request to output stream.
- std::ostream& operator<<(std::ostream&, const HTTPRequest&);
- /// Serialize HTTP response to output stream.
- std::ostream& operator<<(std::ostream&, const HTTPResponse&);
- /// Serialize HTTP method to output stream ("GET", "POST", etc.).
- std::ostream& operator<<(std::ostream&, HTTPMethod);
- /// Serialize HTTP status to output stream, include reason string ("200 OK" etc.)
- std::ostream& operator<<(std::ostream&, HTTPStatus);
- struct HTTPParserBase {
- const std::shared_ptr<util::Logger> logger_ptr;
- util::Logger& logger;
- // FIXME: Generally useful?
- struct CallocDeleter {
- void operator()(void* ptr)
- {
- std::free(ptr);
- }
- };
- HTTPParserBase(const std::shared_ptr<util::Logger>& logger_ptr)
- : logger_ptr{logger_ptr}
- , logger{*logger_ptr}
- {
- // Allocating read buffer with calloc to avoid accidentally spilling
- // data from other sessions in case of a buffer overflow exploit.
- m_read_buffer.reset(static_cast<char*>(std::calloc(read_buffer_size, 1)));
- }
- virtual ~HTTPParserBase() {}
- std::string m_write_buffer;
- std::unique_ptr<char[], CallocDeleter> m_read_buffer;
- util::Optional<size_t> m_found_content_length;
- static const size_t read_buffer_size = 8192;
- static const size_t max_header_line_length = read_buffer_size;
- /// Parses the contents of m_read_buffer as a HTTP header line,
- /// and calls on_header() as appropriate. on_header() will be called at
- /// most once per invocation.
- /// Returns false if the contents of m_read_buffer is not a valid HTTP
- /// header line.
- bool parse_header_line(size_t len);
- virtual std::error_code on_first_line(StringData line) = 0;
- virtual void on_header(StringData key, StringData value) = 0;
- virtual void on_body(StringData body) = 0;
- virtual void on_complete(std::error_code = std::error_code{}) = 0;
- /// If the input matches a known HTTP method string, return the appropriate
- /// HTTPMethod enum value. Otherwise, returns none.
- static util::Optional<HTTPMethod> parse_method_string(StringData method);
- /// Interpret line as the first line of an HTTP request. If the return value
- /// is true, out_method and out_uri have been assigned the appropriate
- /// values found in the request line.
- static bool parse_first_line_of_request(StringData line, HTTPMethod& out_method, StringData& out_uri);
- /// Interpret line as the first line of an HTTP response. If the return
- /// value is true, out_status and out_reason have been assigned the
- /// appropriate values found in the response line.
- static bool parse_first_line_of_response(StringData line, HTTPStatus& out_status, StringData& out_reason,
- util::Logger& logger);
- void set_write_buffer(const HTTPRequest&);
- void set_write_buffer(const HTTPResponse&);
- };
- template <class Socket>
- struct HTTPParser : protected HTTPParserBase {
- explicit HTTPParser(Socket& socket, const std::shared_ptr<util::Logger>& logger_ptr)
- : HTTPParserBase(logger_ptr)
- , m_socket(socket)
- {
- }
- void read_first_line()
- {
- auto handler = [this](std::error_code ec, size_t n) {
- if (ec == util::error::operation_aborted) {
- return;
- }
- if (ec) {
- on_complete(ec);
- return;
- }
- ec = on_first_line(StringData(m_read_buffer.get(), n));
- if (ec) {
- on_complete(ec);
- return;
- }
- read_headers();
- };
- m_socket.async_read_until(m_read_buffer.get(), max_header_line_length, '\n', std::move(handler));
- }
- void read_headers()
- {
- auto handler = [this](std::error_code ec, size_t n) {
- if (ec == util::error::operation_aborted) {
- return;
- }
- if (ec) {
- on_complete(ec);
- return;
- }
- if (n <= 2) {
- read_body();
- return;
- }
- if (!parse_header_line(n)) {
- on_complete(HTTPParserError::BadRequest);
- return;
- }
- // FIXME: Limit the total size of headers. Apache uses 8K.
- read_headers();
- };
- m_socket.async_read_until(m_read_buffer.get(), max_header_line_length, '\n', std::move(handler));
- }
- void read_body()
- {
- if (m_found_content_length) {
- // FIXME: Support longer bodies.
- // FIXME: Support multipart and other body types (no body shaming).
- if (*m_found_content_length > read_buffer_size) {
- on_complete(HTTPParserError::ContentTooLong);
- return;
- }
- auto handler = [this](std::error_code ec, size_t n) {
- if (ec == util::error::operation_aborted) {
- return;
- }
- if (!ec) {
- on_body(StringData(m_read_buffer.get(), n));
- }
- on_complete(ec);
- };
- m_socket.async_read(m_read_buffer.get(), *m_found_content_length, std::move(handler));
- }
- else {
- // No body, just finish.
- on_complete();
- }
- }
- void write_buffer(util::UniqueFunction<void(std::error_code, size_t)> handler)
- {
- m_socket.async_write(m_write_buffer.data(), m_write_buffer.size(), std::move(handler));
- }
- Socket& m_socket;
- };
- template <class Socket>
- struct HTTPClient : protected HTTPParser<Socket> {
- using Handler = void(HTTPResponse, std::error_code);
- explicit HTTPClient(Socket& socket, const std::shared_ptr<util::Logger>& logger_ptr)
- : HTTPParser<Socket>(socket, logger_ptr)
- {
- }
- /// Serialize and send \a request over the connected socket asynchronously.
- ///
- /// When the response has been received, or an error occurs, \a handler will
- /// be invoked with the appropriate parameters. The HTTPResponse object
- /// passed to \a handler will only be complete in non-error conditions, but
- /// may be partially populated.
- ///
- /// It is an error to start a request before the \a handler of a previous
- /// request has been invoked. It is permitted to call async_request() from
- /// the handler, unless an error has been reported representing a condition
- /// where the underlying socket is no longer able to communicate (for
- /// example, if it has been closed).
- ///
- /// If a request is already in progress, an exception will be thrown.
- ///
- /// This method is *NOT* thread-safe.
- void async_request(const HTTPRequest& request, util::UniqueFunction<Handler> handler)
- {
- if (REALM_UNLIKELY(m_handler)) {
- throw LogicError(ErrorCodes::LogicError, "Request already in progress.");
- }
- this->set_write_buffer(request);
- m_handler = std::move(handler);
- this->write_buffer([this](std::error_code ec, size_t bytes_written) {
- static_cast<void>(bytes_written);
- if (ec == util::error::operation_aborted) {
- return;
- }
- if (ec) {
- this->on_complete(ec);
- return;
- }
- this->read_first_line();
- });
- }
- private:
- util::UniqueFunction<Handler> m_handler;
- HTTPResponse m_response;
- std::error_code on_first_line(StringData line) override final
- {
- HTTPStatus status;
- StringData reason;
- if (this->parse_first_line_of_response(line, status, reason, this->logger)) {
- m_response.status = status;
- m_response.reason = reason;
- return std::error_code{};
- }
- return HTTPParserError::MalformedResponse;
- }
- void on_header(StringData key, StringData value) override final
- {
- // FIXME: Multiple headers with the same key should show up as a
- // comma-separated list of their values, rather than overwriting.
- m_response.headers[std::string(key)] = std::string(value);
- }
- void on_body(StringData body) override final
- {
- m_response.body = std::string(body);
- }
- void on_complete(std::error_code ec) override final
- {
- auto handler = std::move(m_handler);
- m_handler = nullptr;
- handler(std::move(m_response), ec);
- }
- };
- template <class Socket>
- struct HTTPServer : protected HTTPParser<Socket> {
- using RequestHandler = void(HTTPRequest, std::error_code);
- using RespondHandler = void(std::error_code);
- explicit HTTPServer(Socket& socket, const std::shared_ptr<util::Logger>& logger_ptr)
- : HTTPParser<Socket>(socket, logger_ptr)
- {
- }
- /// Receive a request on the underlying socket asynchronously.
- ///
- /// This function starts an asynchronous read operation and keeps reading
- /// until an HTTP request has been received. \a handler is invoked when a
- /// request has been received, or an error occurs.
- ///
- /// After a request is received, callers MUST invoke async_send_response()
- /// to provide the client with a valid HTTP response, unless the error
- /// passed to the handler represents a condition where the underlying socket
- /// is no longer able to communicate (for example, if it has been closed).
- ///
- /// It is an error to attempt to receive a request before any previous
- /// requests have been fully responded to, i.e. the \a handler argument of
- /// async_send_response() must have been invoked before attempting to
- /// receive the next request.
- ///
- /// This function is *NOT* thread-safe.
- void async_receive_request(util::UniqueFunction<RequestHandler> handler)
- {
- if (REALM_UNLIKELY(m_request_handler)) {
- throw LogicError(ErrorCodes::LogicError, "Request already in progress");
- }
- m_request_handler = std::move(handler);
- this->read_first_line();
- }
- /// Send an HTTP response to a client asynchronously.
- ///
- /// This function starts an asynchronous write operation on the underlying
- /// socket. \a handler is invoked when the response has been written to the
- /// socket, or an error occurs.
- ///
- /// It is an error to call async_receive_request() again before \a handler
- /// has been invoked, and it is an error to call async_send_response()
- /// before the \a handler of a previous invocation has been invoked.
- ///
- /// This function is *NOT* thread-safe.
- void async_send_response(const HTTPResponse& response, util::UniqueFunction<RespondHandler> handler)
- {
- if (REALM_UNLIKELY(!m_request_handler)) {
- throw LogicError(ErrorCodes::LogicError, "No request in progress");
- }
- if (m_respond_handler) {
- // FIXME: Proper exception type.
- throw LogicError(ErrorCodes::LogicError, "Already responding to request");
- }
- m_respond_handler = std::move(handler);
- this->set_write_buffer(response);
- this->write_buffer([this](std::error_code ec, size_t) {
- if (ec == util::error::operation_aborted) {
- return;
- }
- m_request_handler = nullptr;
- auto handler = std::move(m_respond_handler);
- handler(ec);
- });
- ;
- }
- private:
- util::UniqueFunction<RequestHandler> m_request_handler;
- util::UniqueFunction<RespondHandler> m_respond_handler;
- HTTPRequest m_request;
- std::error_code on_first_line(StringData line) override final
- {
- HTTPMethod method;
- StringData uri;
- if (this->parse_first_line_of_request(line, method, uri)) {
- m_request.method = method;
- m_request.path = uri;
- return std::error_code{};
- }
- return HTTPParserError::MalformedRequest;
- }
- void on_header(StringData key, StringData value) override final
- {
- // FIXME: Multiple headers with the same key should show up as a
- // comma-separated list of their values, rather than overwriting.
- m_request.headers[std::string(key)] = std::string(value);
- }
- void on_body(StringData body) override final
- {
- m_request.body = std::string(body);
- }
- void on_complete(std::error_code ec) override final
- {
- // Deliberately not nullifying m_request_handler so that we can
- // check for invariants in async_send_response.
- m_request_handler(std::move(m_request), ec);
- }
- };
- } // namespace realm::sync
|