1 /*************************************************************************
6 * [2011] - [2016] Realm Inc
9 * NOTICE: All information contained herein is, and remains
10 * the property of Realm Incorporated and its suppliers,
11 * if any. The intellectual and technical concepts contained
12 * herein are proprietary to Realm Incorporated
13 * and its suppliers and may be covered by U.S. and Foreign Patents,
14 * patents in process, and are protected by trade secret or copyright law.
15 * Dissemination of this information or reproduction of this material
16 * is strictly forbidden unless prior written permission is obtained
17 * from Realm Incorporated.
19 **************************************************************************/
21 #ifndef REALM_UTIL_HTTP_HPP
22 #define REALM_UTIL_HTTP_HPP
25 #include <system_error>
29 #include <realm/util/optional.hpp>
30 #include <realm/util/network.hpp>
31 #include <realm/string_data.hpp>
35 enum class HTTPParserError {
42 std::error_code make_error_code(HTTPParserError);
48 struct is_error_code_enum<realm::util::HTTPParserError>: std::true_type {};
54 /// See: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
56 /// It is guaranteed that the backing integer value of this enum corresponds
57 /// to the numerical code representing the status.
58 enum class HTTPStatus {
62 SwitchingProtocols = 101,
67 NonAuthoritative = 203,
72 MultipleChoices = 300,
73 MovedPermanently = 301,
79 TemporaryRedirect = 307,
80 PermanentRedirect = 308,
84 PaymentRequired = 402,
87 MethodNotAllowed = 405,
89 ProxyAuthenticationRequired = 407,
94 PreconditionFailed = 412,
95 PayloadTooLarge = 413,
97 UnsupportedMediaType = 415,
98 RangeNotSatisfiable = 416,
99 ExpectationFailed = 417,
101 MisdirectedRequest = 421,
102 UpgradeRequired = 426,
103 PreconditionRequired = 428,
104 TooManyRequests = 429,
105 RequestHeaderFieldsTooLarge = 431,
106 UnavailableForLegalReasons = 451,
108 InternalServerError = 500,
109 NotImplemented = 501,
111 ServiceUnavailable = 503,
112 GatewayTimeout = 504,
113 HttpVersionNotSupported = 505,
114 VariantAlsoNegotiates = 506,
116 NetworkAuthenticationRequired = 511,
119 /// See: https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
120 enum class HTTPMethod {
131 struct HTTPAuthorization {
133 std::map<std::string, std::string> values;
136 HTTPAuthorization parse_authorization(const std::string&);
138 struct CaseInsensitiveCompare {
139 bool operator()(const std::string& a, const std::string& b) const
141 auto cmp = [](char lhs, char rhs) {
142 return std::tolower(lhs, std::locale::classic()) <
143 std::tolower(rhs, std::locale::classic());
145 return std::lexicographical_compare(begin(a), end(a), begin(b), end(b), cmp);
149 /// Case-insensitive map suitable for storing HTTP headers.
150 using HTTPHeaders = std::map<std::string, std::string, CaseInsensitiveCompare>;
153 HTTPMethod method = HTTPMethod::Get;
157 /// If the request object has a body, the Content-Length header MUST be
158 /// set to a string representation of the number of bytes in the body.
159 /// FIXME: Relax this restriction, and also support Transfer-Encoding
160 /// and other HTTP/1.1 features.
161 Optional<std::string> body;
164 struct HTTPResponse {
165 HTTPStatus status = HTTPStatus::Unknown;
168 // A body is only read from the response stream if the server sent the
169 // Content-Length header.
170 // FIXME: Support other transfer methods, including Transfer-Encoding and
171 // HTTP/1.1 features.
172 Optional<std::string> body;
176 /// Serialize HTTP request to output stream.
177 std::ostream& operator<<(std::ostream&, const HTTPRequest&);
178 /// Serialize HTTP response to output stream.
179 std::ostream& operator<<(std::ostream&, const HTTPResponse&);
180 /// Serialize HTTP method to output stream ("GET", "POST", etc.).
181 std::ostream& operator<<(std::ostream&, HTTPMethod);
182 /// Serialize HTTP status to output stream, include reason string ("200 OK" etc.)
183 std::ostream& operator<<(std::ostream&, HTTPStatus);
185 struct HTTPParserBase {
186 // FIXME: Generally useful?
187 struct CallocDeleter {
188 void operator()(void* ptr)
196 // Allocating read buffer with calloc to avoid accidentally spilling
197 // data from other sessions in case of a buffer overflow exploit.
198 m_read_buffer.reset(static_cast<char*>(std::calloc(read_buffer_size, 1)));
200 virtual ~HTTPParserBase() {}
202 std::string m_write_buffer;
203 std::unique_ptr<char[], CallocDeleter> m_read_buffer;
204 Optional<size_t> m_found_content_length;
205 static const size_t read_buffer_size = 8192;
206 static const size_t max_header_line_length = read_buffer_size;
208 /// Parses the contents of m_read_buffer as a HTTP header line,
209 /// and calls on_header() as appropriate. on_header() will be called at
210 /// most once per invocation.
211 /// Returns false if the contents of m_read_buffer is not a valid HTTP
213 bool parse_header_line(size_t len);
215 virtual std::error_code on_first_line(StringData line) = 0;
216 virtual void on_header(StringData key, StringData value) = 0;
217 virtual void on_body(StringData body) = 0;
218 virtual void on_complete(std::error_code = std::error_code{}) = 0;
220 /// If the input matches a known HTTP method string, return the appropriate
221 /// HTTPMethod enum value. Otherwise, returns none.
222 static Optional<HTTPMethod> parse_method_string(StringData method);
224 /// Interpret line as the first line of an HTTP request. If the return value
225 /// is true, out_method and out_uri have been assigned the appropriate
226 /// values found in the request line.
227 static bool parse_first_line_of_request(StringData line, HTTPMethod& out_method,
228 StringData& out_uri);
230 /// Interpret line as the first line of an HTTP response. If the return
231 /// value is true, out_status and out_reason have been assigned the
232 /// appropriate values found in the response line.
233 static bool parse_first_line_of_response(StringData line, HTTPStatus& out_status,
234 StringData& out_reason);
236 void set_write_buffer(const HTTPRequest&);
237 void set_write_buffer(const HTTPResponse&);
240 template<class Socket>
241 struct HTTPParser: protected HTTPParserBase {
242 explicit HTTPParser(Socket& socket) : m_socket(socket)
245 void read_first_line()
247 auto handler = [this](std::error_code ec, size_t n) {
248 if (ec == error::operation_aborted) {
255 ec = on_first_line(StringData(m_read_buffer.get(), n));
262 m_socket.async_read_until(m_read_buffer.get(), max_header_line_length, '\n',
268 auto handler = [this](std::error_code ec, size_t n) {
269 if (ec == error::operation_aborted) {
280 parse_header_line(n);
281 // FIXME: Limit the total size of headers. Apache uses 8K.
284 m_socket.async_read_until(m_read_buffer.get(), max_header_line_length, '\n',
290 if (m_found_content_length) {
291 // FIXME: Support longer bodies.
292 // FIXME: Support multipart and other body types (no body shaming).
293 if (*m_found_content_length > read_buffer_size) {
294 on_complete(HTTPParserError::ContentTooLong);
298 auto handler = [this](std::error_code ec, size_t n) {
299 if (ec == error::operation_aborted) {
303 on_body(StringData(m_read_buffer.get(), n));
307 m_socket.async_read(m_read_buffer.get(), *m_found_content_length,
311 // No body, just finish.
316 void write_buffer(std::function<void(std::error_code, size_t)> handler)
318 m_socket.async_write(m_write_buffer.data(), m_write_buffer.size(),
325 template<class Socket>
326 struct HTTPClient: protected HTTPParser<Socket> {
327 using Handler = void(HTTPResponse, std::error_code);
329 explicit HTTPClient(Socket& socket) : HTTPParser<Socket>(socket) {}
331 /// Serialize and send \a request over the connected socket asynchronously.
333 /// When the response has been received, or an error occurs, \a handler will
334 /// be invoked with the appropriate parameters. The HTTPResponse object
335 /// passed to \a handler will only be complete in non-error conditions, but
336 /// may be partially populated.
338 /// It is an error to start a request before the \a handler of a previous
339 /// request has been invoked. It is permitted to call async_request() from
340 /// the handler, unless an error has been reported representing a condition
341 /// where the underlying socket is no longer able to communicate (for
342 /// example, if it has been closed).
344 /// If a request is already in progress, an exception will be thrown.
346 /// This method is *NOT* thread-safe.
347 void async_request(const HTTPRequest& request, std::function<Handler> handler)
349 if (REALM_UNLIKELY(m_handler)) {
350 throw std::runtime_error("Request already in progress.");
352 this->set_write_buffer(request);
353 m_handler = std::move(handler);
354 this->write_buffer([this](std::error_code ec, size_t bytes_written) {
355 static_cast<void>(bytes_written);
356 if (ec == error::operation_aborted) {
360 this->on_complete(ec);
363 this->read_first_line();
368 std::function<Handler> m_handler;
369 HTTPResponse m_response;
371 std::error_code on_first_line(StringData line) override final
375 if (this->parse_first_line_of_response(line, status, reason)) {
376 m_response.status = status;
377 static_cast<void>(reason); // Ignore for now.
378 return std::error_code{};
380 return HTTPParserError::MalformedResponse;
383 void on_header(StringData key, StringData value) override final
385 // FIXME: Multiple headers with the same key should show up as a
386 // comma-separated list of their values, rather than overwriting.
387 m_response.headers[std::string(key)] = std::string(value);
390 void on_body(StringData body) override final
392 m_response.body = std::string(body);
395 void on_complete(std::error_code ec) override final
397 auto handler = std::move(m_handler); // Nullifies m_handler
398 handler(std::move(m_response), ec);
402 template<class Socket>
403 struct HTTPServer: protected HTTPParser<Socket> {
404 using RequestHandler = void(HTTPRequest, std::error_code);
405 using RespondHandler = void(std::error_code);
407 explicit HTTPServer(Socket& socket): HTTPParser<Socket>(socket)
410 /// Receive a request on the underlying socket asynchronously.
412 /// This function starts an asynchronous read operation and keeps reading
413 /// until an HTTP request has been received. \a handler is invoked when a
414 /// request has been received, or an error occurs.
416 /// After a request is received, callers MUST invoke async_send_response()
417 /// to provide the client with a valid HTTP response, unless the error
418 /// passed to the handler represents a condition where the underlying socket
419 /// is no longer able to communicate (for example, if it has been closed).
421 /// It is an error to attempt to receive a request before any previous
422 /// requests have been fully responded to, i.e. the \a handler argument of
423 /// async_send_response() must have been invoked before attempting to
424 /// receive the next request.
426 /// This function is *NOT* thread-safe.
427 void async_receive_request(std::function<RequestHandler> handler)
429 if (REALM_UNLIKELY(m_request_handler)) {
430 throw std::runtime_error("Response already in progress.");
432 m_request_handler = std::move(handler);
433 this->read_first_line();
436 /// Send an HTTP response to a client asynchronously.
438 /// This function starts an asynchronous write operation on the underlying
439 /// socket. \a handler is invoked when the response has been written to the
440 /// socket, or an error occurs.
442 /// It is an error to call async_receive_request() again before \a handler
443 /// has been invoked, and it is an error to call async_send_response()
444 /// before the \a handler of a previous invocation has been invoked.
446 /// This function is *NOT* thread-safe.
447 void async_send_response(const HTTPResponse& response,
448 std::function<RespondHandler> handler)
450 if (REALM_UNLIKELY(!m_request_handler)) {
451 throw std::runtime_error("No request in progress.");
453 if (m_respond_handler) {
454 // FIXME: Proper exception type.
455 throw std::runtime_error("Already responding to request");
457 m_respond_handler = std::move(handler);
458 this->set_write_buffer(response);
459 this->write_buffer([this](std::error_code ec, size_t) {
460 if (ec == error::operation_aborted) {
463 m_request_handler = nullptr;
464 auto handler = std::move(m_respond_handler);
470 std::function<RequestHandler> m_request_handler;
471 std::function<RespondHandler> m_respond_handler;
472 HTTPRequest m_request;
474 std::error_code on_first_line(StringData line) override final
478 if (this->parse_first_line_of_request(line, method, uri)) {
479 m_request.method = method;
480 m_request.path = uri;
481 return std::error_code{};
483 return HTTPParserError::MalformedRequest;
486 void on_header(StringData key, StringData value) override final
488 // FIXME: Multiple headers with the same key should show up as a
489 // comma-separated list of their values, rather than overwriting.
490 m_request.headers[std::string(key)] = std::string(value);
493 void on_body(StringData body) override final
495 m_request.body = std::string(body);
498 void on_complete(std::error_code ec) override final
500 // Deliberately not nullifying m_request_handler so that we can
501 // check for invariants in async_send_response.
502 m_request_handler(std::move(m_request), ec);
510 #endif // REALM_UTIL_HTTP_HPP