CCF
Loading...
Searching...
No Matches
http_parser.h
Go to the documentation of this file.
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the Apache 2.0 License.
3#pragma once
4
5#include "ccf/ds/hex.h"
9#include "http_builder.h"
10#include "http_proc.h"
11
12#include <algorithm>
13#include <cctype>
14#include <llhttp/llhttp.h>
15#include <map>
16#include <queue>
17#include <regex>
18#include <string>
19#include <string_view>
20
21namespace http
22{
23 inline auto split_url_path(const std::string_view& url)
24 {
25 LOG_TRACE_FMT("Received url to parse: {}", std::string_view(url));
26
27 const auto path_end = url.find('?');
28 const auto query_start =
29 path_end == std::string::npos ? url.size() : path_end + 1;
30
31 const auto query_end = url.find('#', query_start);
32 const auto fragment_start =
33 query_end == std::string::npos ? url.size() : query_end + 1;
34
35 return std::make_tuple(
36 url.substr(0, path_end),
37 url.substr(query_start, query_end - query_start),
38 url.substr(fragment_start));
39 }
40
41 static std::string url_decode(const std::string_view& s_)
42 {
43 std::string s(s_);
44 char const* src = s.c_str();
45 char const* end = s.c_str() + s.size();
46 char* dst = s.data();
47
48 while (src < end)
49 {
50 char const c = *src++;
51 if (
52 c == '%' && (src + 1) < end && (isxdigit(src[0]) != 0) &&
53 (isxdigit(src[1]) != 0))
54 {
55 const auto a = ccf::ds::hex_char_to_int(*src++);
56 const auto b = ccf::ds::hex_char_to_int(*src++);
57 *dst++ = (a << 4) | b;
58 }
59 else if (c == '+')
60 {
61 *dst++ = ' ';
62 }
63 else
64 {
65 *dst++ = c;
66 }
67 }
68
69 s.resize(dst - s.data());
70 return s;
71 }
72
73 inline bool status_success(ccf::http_status status)
74 {
75 return status >= 200 && status < 300;
76 }
77
79 {
80 public:
81 ~SimpleRequestProcessor() override = default;
82
83 struct Request
84 {
85 llhttp_method method;
86 std::string url;
88 std::vector<uint8_t> body;
89 };
90
91 std::queue<Request> received;
92
94 llhttp_method method,
95 const std::string_view& url,
96 ccf::http::HeaderMap&& headers,
97 std::vector<uint8_t>&& body,
98 int32_t /*stream_id*/) override
99 {
100 received.emplace(Request{method, std::string(url), headers, body});
101 }
102 };
103
105 {
106 public:
107 ~SimpleResponseProcessor() override = default;
108
115
116 std::queue<Response> received;
117
119 ccf::http_status status,
120 ccf::http::HeaderMap&& headers,
121 std::vector<uint8_t>&& body) override
122 {
123 received.emplace(Response{status, headers, body});
124 }
125 };
126
127 enum State : uint8_t
128 {
131 };
132
133 static int on_msg_begin(llhttp_t* parser);
134 static int on_url(llhttp_t* parser, const char* at, size_t length);
135 static int on_header_field(llhttp_t* parser, const char* at, size_t length);
136 static int on_header_value(llhttp_t* parser, const char* at, size_t length);
137 static int on_headers_complete(llhttp_t* parser);
138 static int on_body(llhttp_t* parser, const char* at, size_t length);
139 static int on_msg_end(llhttp_t* parser);
140
141 struct URL
142 {
143 std::string scheme;
144 std::string host;
145 std::string port;
146 std::string path;
147 std::string query;
148 std::string fragment;
149 };
150
151 inline URL parse_url_full(const std::string& url)
152 {
153 LOG_TRACE_FMT("Received url to parse: {}", url);
154
155 // From https://tools.ietf.org/html/rfc3986#appendix-B
156 std::regex url_regex(
157 "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?");
158
159 std::smatch match;
160 if (!std::regex_match(url, match, url_regex))
161 {
162 throw std::invalid_argument(fmt::format("Unable to parse url: {}", url));
163 }
164
165 const auto host_port = match[4].str();
166
167 // IPv6 hosts may contain colons, so only search for port after the closing
168 // square bracket
169 const auto closing_bracket = host_port.rfind(']');
170 const auto port_delim_start =
171 closing_bracket == std::string::npos ? 0 : closing_bracket;
172 const auto port_delim = host_port.find(':', port_delim_start);
173
174 URL u;
175 u.scheme = match[2].str();
176 u.host = host_port.substr(0, port_delim);
177 if (port_delim != std::string::npos)
178 {
179 u.port = host_port.substr(port_delim + 1);
180 }
181 u.path = match[5].str();
182 u.query = match[7].str();
183 u.fragment = match[9].str();
184 return u;
185 }
186
187 class Parser
188 {
189 public:
190 virtual ~Parser() = default;
191
192 protected:
193 llhttp_t parser;
194 llhttp_settings_t settings;
197
198 std::vector<uint8_t> body_buf;
200
201 std::pair<std::string, std::string> partial_parsed_header;
202
204 {
206 partial_parsed_header.first.clear();
207 partial_parsed_header.second.clear();
208 }
209
211 llhttp_type_t type,
214 parser{},
215 settings{},
216 configuration(std::move(config))
217 {
218 llhttp_settings_init(&settings);
219
220 settings.on_message_begin = on_msg_begin;
221 settings.on_header_field = on_header_field;
222 settings.on_header_value = on_header_value;
223 settings.on_headers_complete = on_headers_complete;
224 settings.on_body = on_body;
225 settings.on_message_complete = on_msg_end;
226
227 llhttp_init(&parser, type, &settings);
228 parser.data = this;
229 }
230
231 public:
232 void execute(const uint8_t* data, size_t size)
233 {
234 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
235 const auto* const data_char = reinterpret_cast<const char*>(data);
236 auto err_no = llhttp_execute(&parser, data_char, size);
237
238 if (err_no == HPE_PAUSED_UPGRADE)
239 {
240 // Assume Upgrade requests will be handled by caller inspecting headers,
241 // so we can instantly resume the parser.
242 llhttp_resume_after_upgrade(&parser);
243 }
244 else if (err_no != HPE_OK)
245 {
246 throw std::runtime_error(fmt::format(
247 "HTTP parsing failed ({}: {}) around byte {}",
248 llhttp_errno_name(err_no),
249 llhttp_get_error_reason(&parser),
250 llhttp_get_error_pos(&parser) - data_char));
251 }
252 }
253
254 void append_body(const char* at, size_t length)
255 {
256 if (state == IN_MESSAGE)
257 {
258 LOG_TRACE_FMT("Appending chunk [{} bytes]", length);
259 std::copy(at, at + length, std::back_inserter(body_buf));
260
261 auto const& max_body_size = configuration.max_body_size.value_or(
262 ccf::http::default_max_body_size);
263 if (body_buf.size() > max_body_size)
264 {
265 throw RequestPayloadTooLargeException(fmt::format(
266 "HTTP request body is too large (max size allowed: {})",
267 max_body_size));
268 }
269 }
270 else
271 {
272 throw std::runtime_error("Receiving content outside of message");
273 }
274 }
275
276 virtual void new_message()
277 {
278 if (state == DONE)
279 {
280 LOG_TRACE_FMT("Entering new message");
282 body_buf.clear();
283 headers.clear();
284 }
285 else
286 {
287 throw std::runtime_error(
288 "Entering new message when previous message isn't complete");
289 }
290 }
291
292 virtual void handle_completed_message() = 0;
293
295 {
296 if (state == IN_MESSAGE)
297 {
298 LOG_TRACE_FMT("Done with message");
300 state = DONE;
301 }
302 else
303 {
304 throw std::runtime_error("Ending message, but not in a message");
305 }
306 }
307
308 void header_field(const char* at, size_t length)
309 {
310 if (!partial_parsed_header.second.empty())
311 {
313 }
314
315 const auto max_headers_count = configuration.max_headers_count.value_or(
316 ccf::http::default_max_headers_count);
317 if (headers.size() >= max_headers_count)
318 {
319 throw RequestHeaderTooLargeException(fmt::format(
320 "Too many headers (max number allowed: {})", max_headers_count));
321 }
322
323 // HTTP headers are stored lowercase as it is easier to verify HTTP
324 // signatures later on
325 auto f = std::string(at, length);
326 ccf::nonstd::to_lower(f);
327 auto& partial_header_key = partial_parsed_header.first;
328 partial_header_key.append(f);
329
330 auto const& max_header_size = configuration.max_header_size.value_or(
331 ccf::http::default_max_header_size);
332 if (partial_header_key.size() > max_header_size)
333 {
334 throw RequestHeaderTooLargeException(fmt::format(
335 "Header key for '{}' is too large (max size allowed: {})",
337 max_header_size));
338 }
339 }
340
341 void header_value(const char* at, size_t length)
342 {
343 auto& partial_header_value = partial_parsed_header.second;
344 partial_header_value.append(at, length);
345 auto const& max_header_size = configuration.max_header_size.value_or(
346 ccf::http::default_max_header_size);
347 if (partial_header_value.size() > max_header_size)
348 {
349 throw RequestHeaderTooLargeException(fmt::format(
350 "Header value for '{}' is too large (max size allowed: {})",
352 max_header_size));
353 }
354 }
355
357 {
359 }
360 };
361
362 static int on_msg_begin(llhttp_t* parser)
363 {
364 auto* p = reinterpret_cast<Parser*>(parser->data);
365 p->new_message();
366 return HPE_OK;
367 }
368
369 static int on_header_field(llhttp_t* parser, const char* at, size_t length)
370 {
371 auto* p = reinterpret_cast<Parser*>(parser->data);
372 p->header_field(at, length);
373 return HPE_OK;
374 }
375
376 static int on_header_value(llhttp_t* parser, const char* at, size_t length)
377 {
378 auto* p = reinterpret_cast<Parser*>(parser->data);
379 p->header_value(at, length);
380 return HPE_OK;
381 }
382
383 static int on_headers_complete(llhttp_t* parser)
384 {
385 auto* p = reinterpret_cast<Parser*>(parser->data);
386 p->headers_complete();
387 return HPE_OK;
388 }
389
390 static int on_body(llhttp_t* parser, const char* at, size_t length)
391 {
392 auto* p = reinterpret_cast<Parser*>(parser->data);
393 p->append_body(at, length);
394 return HPE_OK;
395 }
396
397 static int on_msg_end(llhttp_t* parser)
398 {
399 auto* p = reinterpret_cast<Parser*>(parser->data);
400 p->end_message();
401 return HPE_OK;
402 }
403
404 // Request-specific
405 class RequestParser : public Parser
406 {
407 private:
408 RequestProcessor& proc;
409
410 std::string url;
411
412 public:
413 ~RequestParser() override = default;
414
416 RequestProcessor& proc_,
417 const ccf::http::ParserConfiguration& config =
419 Parser(HTTP_REQUEST, config),
420 proc(proc_)
421 {
422 settings.on_url = on_url;
423 }
424
425 void append_url(const char* at, size_t length)
426 {
427 url.append(at, length);
428 }
429
430 void new_message() override
431 {
433 url.clear();
434 }
435
437 {
438 if (url.empty())
439 {
440 proc.handle_request(
441 llhttp_method(parser.method),
442 {},
443 std::move(headers),
444 std::move(body_buf));
445 }
446 else
447 {
448 proc.handle_request(
449 llhttp_method(parser.method),
450 url,
451 std::move(headers),
452 std::move(body_buf));
453 }
454 }
455 };
456
457 static int on_url(llhttp_t* parser, const char* at, size_t length)
458 {
459 auto* p = reinterpret_cast<RequestParser*>(parser->data);
460 p->append_url(at, length);
461 return HPE_OK;
462 }
463
464 // Response-specific
465 class ResponseParser : public Parser
466 {
467 private:
468 ResponseProcessor& proc;
469
470 public:
471 ~ResponseParser() override = default;
472
474 Parser(HTTP_RESPONSE, ccf::http::ParserConfiguration{}),
475 proc(proc_)
476 {}
477
479 {
480 proc.handle_response(
481 ccf::http_status(parser.status_code),
482 std::move(headers),
483 std::move(body_buf));
484 }
485 };
486}
Definition http_parser.h:188
State state
Definition http_parser.h:196
virtual void handle_completed_message()=0
void append_body(const char *at, size_t length)
Definition http_parser.h:254
ccf::http::ParserConfiguration configuration
Definition http_parser.h:195
llhttp_t parser
Definition http_parser.h:193
void headers_complete()
Definition http_parser.h:356
virtual void new_message()
Definition http_parser.h:276
llhttp_settings_t settings
Definition http_parser.h:194
void execute(const uint8_t *data, size_t size)
Definition http_parser.h:232
std::vector< uint8_t > body_buf
Definition http_parser.h:198
void header_value(const char *at, size_t length)
Definition http_parser.h:341
Parser(llhttp_type_t type, ccf::http::ParserConfiguration config=ccf::http::ParserConfiguration{})
Definition http_parser.h:210
std::pair< std::string, std::string > partial_parsed_header
Definition http_parser.h:201
virtual ~Parser()=default
void complete_header()
Definition http_parser.h:203
void end_message()
Definition http_parser.h:294
void header_field(const char *at, size_t length)
Definition http_parser.h:308
ccf::http::HeaderMap headers
Definition http_parser.h:199
Definition http_exceptions.h:40
Definition http_parser.h:406
~RequestParser() override=default
RequestParser(RequestProcessor &proc_, const ccf::http::ParserConfiguration &config=ccf::http::ParserConfiguration{})
Definition http_parser.h:415
void handle_completed_message() override
Definition http_parser.h:436
void new_message() override
Definition http_parser.h:430
void append_url(const char *at, size_t length)
Definition http_parser.h:425
Definition http_exceptions.h:31
Definition http_proc.h:20
virtual void handle_request(llhttp_method method, const std::string_view &url, ccf::http::HeaderMap &&headers, std::vector< uint8_t > &&body, int32_t stream_id=http2::DEFAULT_STREAM_ID)=0
Definition http_parser.h:466
~ResponseParser() override=default
ResponseParser(ResponseProcessor &proc_)
Definition http_parser.h:473
void handle_completed_message() override
Definition http_parser.h:478
Definition http_proc.h:33
virtual void handle_response(ccf::http_status status, ccf::http::HeaderMap &&headers, std::vector< uint8_t > &&body)=0
#define LOG_TRACE_FMT
Definition internal_logger.h:13
std::map< std::string, std::string, std::less<> > HeaderMap
Definition http_header_map.h:10
Definition app_interface.h:14
llhttp_status http_status
Definition http_status.h:9
Definition error_reporter.h:6
auto split_url_path(const std::string_view &url)
Definition http_parser.h:23
bool status_success(ccf::http_status status)
Definition http_parser.h:73
State
Definition http_parser.h:128
@ DONE
Definition http_parser.h:129
@ IN_MESSAGE
Definition http_parser.h:130
URL parse_url_full(const std::string &url)
Definition http_parser.h:151
STL namespace.
Definition http_configuration.h:24
std::optional< ccf::ds::SizeString > max_header_size
Definition http_configuration.h:26
std::optional< ccf::ds::SizeString > max_body_size
Definition http_configuration.h:25
std::optional< uint32_t > max_headers_count
Definition http_configuration.h:27
Definition http_parser.h:84
std::vector< uint8_t > body
Definition http_parser.h:88
std::string url
Definition http_parser.h:86
ccf::http::HeaderMap headers
Definition http_parser.h:87
llhttp_method method
Definition http_parser.h:85
Definition http_parser.h:79
~SimpleRequestProcessor() override=default
void handle_request(llhttp_method method, const std::string_view &url, ccf::http::HeaderMap &&headers, std::vector< uint8_t > &&body, int32_t) override
Definition http_parser.h:93
std::queue< Request > received
Definition http_parser.h:91
Definition http_parser.h:110
ccf::http::HeaderMap headers
Definition http_parser.h:112
ccf::http_status status
Definition http_parser.h:111
std::vector< uint8_t > body
Definition http_parser.h:113
Definition http_parser.h:105
void handle_response(ccf::http_status status, ccf::http::HeaderMap &&headers, std::vector< uint8_t > &&body) override
Definition http_parser.h:118
std::queue< Response > received
Definition http_parser.h:116
~SimpleResponseProcessor() override=default
Definition http_parser.h:142
std::string host
Definition http_parser.h:144
std::string query
Definition http_parser.h:147
std::string port
Definition http_parser.h:145
std::string path
Definition http_parser.h:146
std::string fragment
Definition http_parser.h:148
std::string scheme
Definition http_parser.h:143