include/boost/burl/parser.hpp
100.0% Lines (5/5)
100.0% List of functions (5/5)
-% Branches (0/0)
Functions (5)
Function
Calls
Lines
Blocks
boost::burl::parser::decoder::~decoder()
:131
56x
100.0%
100.0%
boost::burl::parser::parser()
:519
3x
100.0%
100.0%
boost::burl::parser::parser(boost::burl::parser&&)
:526
335x
100.0%
100.0%
boost::burl::parser::operator=(boost::burl::parser&&)
:529
3x
100.0%
100.0%
boost::burl::parser::~parser()
:536
635x
100.0%
100.0%
| Line | TLA | Hits | Source Code |
|---|---|---|---|
| 1 | // | ||
| 2 | // Copyright (c) 2026 Mohammad Nejati | ||
| 3 | // | ||
| 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | ||
| 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | ||
| 6 | // | ||
| 7 | // Official repository: https://github.com/cppalliance/burl | ||
| 8 | // | ||
| 9 | |||
| 10 | #ifndef BOOST_BURL_PARSER_HPP | ||
| 11 | #define BOOST_BURL_PARSER_HPP | ||
| 12 | |||
| 13 | #include <boost/burl/detail/circular_buffer.hpp> | ||
| 14 | #include <boost/burl/detail/config.hpp> | ||
| 15 | #include <boost/burl/head_parser.hpp> | ||
| 16 | #include <boost/burl/request_head_base.hpp> | ||
| 17 | #include <boost/burl/response_head_base.hpp> | ||
| 18 | |||
| 19 | #include <boost/capy/buffers.hpp> | ||
| 20 | #include <boost/http/metadata.hpp> | ||
| 21 | #include <boost/system/error_code.hpp> | ||
| 22 | |||
| 23 | #include <array> | ||
| 24 | #include <cstddef> | ||
| 25 | #include <cstdint> | ||
| 26 | #include <memory> | ||
| 27 | #include <span> | ||
| 28 | #include <string_view> | ||
| 29 | #include <system_error> | ||
| 30 | |||
| 31 | namespace boost | ||
| 32 | { | ||
| 33 | namespace burl | ||
| 34 | { | ||
| 35 | |||
| 36 | /** A parser for HTTP/1 messages. | ||
| 37 | |||
| 38 | The parser performs no I/O. Received bytes are | ||
| 39 | handed to it through @ref prepare and @ref | ||
| 40 | commit, and each parsing operation reports @ref | ||
| 41 | http::error::need_data when it requires more. | ||
| 42 | Driving the parser over a stream is the job of | ||
| 43 | @ref message_reader. | ||
| 44 | |||
| 45 | The parser uses a single block of memory | ||
| 46 | allocated during construction and never exceeds | ||
| 47 | it. The space is reused across messages, one at | ||
| 48 | a time, and holds: | ||
| 49 | |||
| 50 | @li raw octets received from the stream, | ||
| 51 | @li the message header, with O(1) access to the | ||
| 52 | start line, | ||
| 53 | @li all or part of the message body, and | ||
| 54 | @li decoded output when a @ref decoder is | ||
| 55 | installed. | ||
| 56 | |||
| 57 | @par Operations | ||
| 58 | |||
| 59 | The body can be retrieved three ways, which | ||
| 60 | differ in where the octets end up: | ||
| 61 | |||
| 62 | @li @ref flatten_body returns the whole body in | ||
| 63 | place, without copying, | ||
| 64 | @li @ref read_some copies into caller-supplied | ||
| 65 | memory, or lets an installed decoder write | ||
| 66 | into it directly, and | ||
| 67 | @li @ref pull borrows the parser's own buffers, | ||
| 68 | which @ref consume then releases. | ||
| 69 | |||
| 70 | Each parses the header first when it has not | ||
| 71 | been parsed already, so a caller with no | ||
| 72 | interest in the header never has to call @ref | ||
| 73 | parse_header. Installing a decoder does require | ||
| 74 | it, because @ref set_decoder must run after the | ||
| 75 | header and before any body octet. | ||
| 76 | |||
| 77 | @par Errors | ||
| 78 | |||
| 79 | Every parsing operation reports through an | ||
| 80 | `error_code` out parameter: | ||
| 81 | |||
| 82 | @li @ref http::error::need_data — fill @ref | ||
| 83 | prepare, call @ref commit, and try again. | ||
| 84 | Reported only while @ref prepare has room. | ||
| 85 | @li @ref http::error::in_place_overflow — more | ||
| 86 | input is required but no writable space | ||
| 87 | remains. | ||
| 88 | @li @ref http::error::incomplete — more input is | ||
| 89 | required but @ref commit_eof was called. | ||
| 90 | @li @ref http::error::end_of_stream — the stream | ||
| 91 | closed cleanly before the message began. | ||
| 92 | |||
| 93 | An operation reports either transferred octets | ||
| 94 | or an error, never both. | ||
| 95 | |||
| 96 | @see | ||
| 97 | @ref message_reader, | ||
| 98 | @ref request_parser, | ||
| 99 | @ref response_parser. | ||
| 100 | */ | ||
| 101 | class parser | ||
| 102 | { | ||
| 103 | public: | ||
| 104 | /** A content decoder. | ||
| 105 | |||
| 106 | Installed with @ref set_decoder before body | ||
| 107 | parsing begins, a decoder transforms the | ||
| 108 | payload octets as they arrive. | ||
| 109 | */ | ||
| 110 | struct decoder | ||
| 111 | { | ||
| 112 | /// The outcome of a call to @ref process. | ||
| 113 | struct result | ||
| 114 | { | ||
| 115 | /// The number of input octets consumed. | ||
| 116 | std::size_t consumed; | ||
| 117 | |||
| 118 | /// The number of output octets produced. | ||
| 119 | std::size_t produced; | ||
| 120 | |||
| 121 | /** The error, if any. | ||
| 122 | |||
| 123 | Set to `capy::error::eof` once the | ||
| 124 | decoder has produced the complete | ||
| 125 | output. | ||
| 126 | */ | ||
| 127 | std::error_code ec; | ||
| 128 | }; | ||
| 129 | |||
| 130 | /// Destructor. | ||
| 131 | 56x | virtual ~decoder() = default; | |
| 132 | |||
| 133 | /** Transform payload octets. | ||
| 134 | |||
| 135 | @param out The destination for decoded | ||
| 136 | output. | ||
| 137 | |||
| 138 | @param in The octets to decode. | ||
| 139 | |||
| 140 | @param eof True when `in` ends the | ||
| 141 | payload. | ||
| 142 | |||
| 143 | @return The octets consumed and | ||
| 144 | produced, and the error if any. | ||
| 145 | */ | ||
| 146 | virtual result | ||
| 147 | process( | ||
| 148 | capy::mutable_buffer out, | ||
| 149 | capy::const_buffer in, | ||
| 150 | bool eof) = 0; | ||
| 151 | }; | ||
| 152 | |||
| 153 | /// Settings which apply for the life of the parser. | ||
| 154 | struct config | ||
| 155 | { | ||
| 156 | /// The limits enforced while parsing a header. | ||
| 157 | header_limits hdr_limits; | ||
| 158 | |||
| 159 | /// The space reserved for buffering received octets. | ||
| 160 | std::size_t in_buffer = 64 * 1024; | ||
| 161 | |||
| 162 | /// The space reserved for decoded output. | ||
| 163 | std::size_t dec_buffer = 8 * 1024; | ||
| 164 | |||
| 165 | /// The default maximum body size. | ||
| 166 | std::uint64_t body_limit = std::uint64_t(-1); | ||
| 167 | }; | ||
| 168 | |||
| 169 | //-------------------------------------------- | ||
| 170 | // | ||
| 171 | // Observers | ||
| 172 | // | ||
| 173 | //-------------------------------------------- | ||
| 174 | |||
| 175 | /** Return true if the header has been parsed. | ||
| 176 | */ | ||
| 177 | BOOST_BURL_DECL | ||
| 178 | bool | ||
| 179 | got_header() const noexcept; | ||
| 180 | |||
| 181 | /** Return true if the entire message has arrived. | ||
| 182 | */ | ||
| 183 | BOOST_BURL_DECL | ||
| 184 | bool | ||
| 185 | got_body() const noexcept; | ||
| 186 | |||
| 187 | /** Return true if octets are buffered past the message. | ||
| 188 | |||
| 189 | Returns true when the buffer holds octets | ||
| 190 | which lie beyond the current message, such | ||
| 191 | as the start of a pipelined message. Returns | ||
| 192 | false before the message is complete, and | ||
| 193 | false for a payload which is delimited by | ||
| 194 | the end of the stream. | ||
| 195 | |||
| 196 | @see @ref buffered_data. | ||
| 197 | */ | ||
| 198 | BOOST_BURL_DECL | ||
| 199 | bool | ||
| 200 | has_buffered_data() const noexcept; | ||
| 201 | |||
| 202 | /** Return the unconsumed octets in the buffer. | ||
| 203 | |||
| 204 | The returned octets are raw: no message | ||
| 205 | framing is applied. After a message whose | ||
| 206 | body has been read to completion they are | ||
| 207 | the octets which follow it, which is how the | ||
| 208 | remainder of a tunnel is recovered following | ||
| 209 | a CONNECT request. | ||
| 210 | |||
| 211 | Note that the framing of a response to | ||
| 212 | CONNECT cannot be determined from the | ||
| 213 | response alone, so the parser reports a | ||
| 214 | payload which continues to the end of the | ||
| 215 | stream and @ref has_buffered_data returns | ||
| 216 | false. A caller which knows it issued a | ||
| 217 | CONNECT should use this function directly. | ||
| 218 | |||
| 219 | If the body of a sized payload has only | ||
| 220 | partially been read, the unread remainder is | ||
| 221 | included. | ||
| 222 | |||
| 223 | @par Preconditions | ||
| 224 | `this->got_header() == true` | ||
| 225 | |||
| 226 | @see @ref has_buffered_data. | ||
| 227 | */ | ||
| 228 | BOOST_BURL_DECL | ||
| 229 | std::array<capy::const_buffer, 2> | ||
| 230 | buffered_data() const noexcept; | ||
| 231 | |||
| 232 | //-------------------------------------------- | ||
| 233 | // | ||
| 234 | // Modifiers | ||
| 235 | // | ||
| 236 | //-------------------------------------------- | ||
| 237 | |||
| 238 | /** Prepare for a new stream. | ||
| 239 | |||
| 240 | Discards all parsing state and any buffered | ||
| 241 | octets. | ||
| 242 | */ | ||
| 243 | BOOST_BURL_DECL | ||
| 244 | void | ||
| 245 | reset() noexcept; | ||
| 246 | |||
| 247 | /** Install a content decoder. | ||
| 248 | |||
| 249 | The decoder must remain valid until the | ||
| 250 | message has been parsed. Passing `nullptr` | ||
| 251 | removes a previously installed decoder. | ||
| 252 | |||
| 253 | @par Preconditions | ||
| 254 | `this->got_header() == true` and no body | ||
| 255 | octet has been parsed. | ||
| 256 | |||
| 257 | @param dec The decoder to install. | ||
| 258 | */ | ||
| 259 | BOOST_BURL_DECL | ||
| 260 | void | ||
| 261 | set_decoder(decoder* dec) noexcept; | ||
| 262 | |||
| 263 | /** Set the maximum body size. | ||
| 264 | |||
| 265 | Overrides @ref config::body_limit. The limit | ||
| 266 | is sticky: it applies to every subsequent | ||
| 267 | message until changed, and is not restored | ||
| 268 | by @ref start or @ref reset. | ||
| 269 | |||
| 270 | @param n The body size limit in octets. | ||
| 271 | */ | ||
| 272 | BOOST_BURL_DECL | ||
| 273 | void | ||
| 274 | set_body_limit(std::uint64_t n) noexcept; | ||
| 275 | |||
| 276 | /** Return the buffer region for receiving octets. | ||
| 277 | |||
| 278 | The second region is empty unless the buffer | ||
| 279 | has wrapped. Report octets written into it | ||
| 280 | with @ref commit. | ||
| 281 | |||
| 282 | The region may be empty; in that case an | ||
| 283 | operation which requires more input fails | ||
| 284 | with @ref http::error::in_place_overflow | ||
| 285 | rather than asking for it. | ||
| 286 | |||
| 287 | @see @ref commit, @ref commit_eof. | ||
| 288 | */ | ||
| 289 | BOOST_BURL_DECL | ||
| 290 | std::array<capy::mutable_buffer, 2> | ||
| 291 | prepare() noexcept; | ||
| 292 | |||
| 293 | /** Report octets received into the buffer. | ||
| 294 | |||
| 295 | @par Preconditions | ||
| 296 | `n <= capy::buffer_size( this->prepare() )` | ||
| 297 | |||
| 298 | @par Postconditions | ||
| 299 | Regions returned by @ref prepare are | ||
| 300 | invalidated. | ||
| 301 | |||
| 302 | @param n The number of octets received. | ||
| 303 | |||
| 304 | @see @ref prepare. | ||
| 305 | */ | ||
| 306 | BOOST_BURL_DECL | ||
| 307 | void | ||
| 308 | commit(std::size_t n) noexcept; | ||
| 309 | |||
| 310 | /** Report the end of the stream. | ||
| 311 | |||
| 312 | Call this when the stream has closed and no | ||
| 313 | further octets will arrive. | ||
| 314 | |||
| 315 | @par Postconditions | ||
| 316 | Regions returned by @ref prepare are | ||
| 317 | invalidated. | ||
| 318 | |||
| 319 | @see @ref prepare. | ||
| 320 | */ | ||
| 321 | BOOST_BURL_DECL | ||
| 322 | void | ||
| 323 | commit_eof() noexcept; | ||
| 324 | |||
| 325 | /** Return the octets which may be received directly. | ||
| 326 | |||
| 327 | Body octets may be read from the stream | ||
| 328 | straight into caller-supplied memory, | ||
| 329 | bypassing the parser's buffer, when every | ||
| 330 | one of these holds: | ||
| 331 | |||
| 332 | @li the header has been parsed, | ||
| 333 | |||
| 334 | @li the payload has a known size or is | ||
| 335 | delimited by the end of the stream, | ||
| 336 | |||
| 337 | @li no @ref decoder is installed, | ||
| 338 | |||
| 339 | @li the buffer holds no payload octets, | ||
| 340 | |||
| 341 | @li @ref commit_eof has not been called, and | ||
| 342 | |||
| 343 | @li the body limit permits more octets: a | ||
| 344 | known size must fit within what remains of | ||
| 345 | the limit, and a payload delimited by the | ||
| 346 | end of the stream must not have reached it. | ||
| 347 | |||
| 348 | Otherwise body octets must be received | ||
| 349 | through @ref prepare and @ref commit. | ||
| 350 | |||
| 351 | @return The number of octets which may be | ||
| 352 | received directly, or zero when that is not | ||
| 353 | permitted. For a payload of known size this | ||
| 354 | is what remains of the payload; for one | ||
| 355 | delimited by the end of the stream it is | ||
| 356 | what remains of the body limit. | ||
| 357 | |||
| 358 | @see @ref commit_direct. | ||
| 359 | */ | ||
| 360 | BOOST_BURL_DECL | ||
| 361 | std::size_t | ||
| 362 | direct_capacity() const noexcept; | ||
| 363 | |||
| 364 | /** Report octets received into caller memory. | ||
| 365 | |||
| 366 | @par Preconditions | ||
| 367 | `n <= this->direct_capacity()` | ||
| 368 | |||
| 369 | @param n The number of octets received. | ||
| 370 | |||
| 371 | @see @ref direct_capacity. | ||
| 372 | */ | ||
| 373 | BOOST_BURL_DECL | ||
| 374 | void | ||
| 375 | commit_direct(std::size_t n) noexcept; | ||
| 376 | |||
| 377 | //-------------------------------------------- | ||
| 378 | // | ||
| 379 | // Parsing | ||
| 380 | // | ||
| 381 | //-------------------------------------------- | ||
| 382 | |||
| 383 | /** Parse the message header. | ||
| 384 | |||
| 385 | Returns as soon as the header is complete, | ||
| 386 | so that @ref set_decoder and @ref | ||
| 387 | set_body_limit can be called before any body | ||
| 388 | octet is parsed. Has no effect once @ref | ||
| 389 | got_header returns true. | ||
| 390 | |||
| 391 | @par Preconditions | ||
| 392 | @ref start has been called. | ||
| 393 | |||
| 394 | @param ec Set to the error, if any occurred. | ||
| 395 | */ | ||
| 396 | BOOST_BURL_DECL | ||
| 397 | void | ||
| 398 | parse_header(system::error_code& ec); | ||
| 399 | |||
| 400 | /** Flatten the body in place and return it. | ||
| 401 | |||
| 402 | Coalesces the buffered body octets into a | ||
| 403 | contiguous range in the parser's own buffer | ||
| 404 | and returns a view of them, without copying. | ||
| 405 | A chunked payload is de-chunked in place. | ||
| 406 | Parses the header first if @ref got_header | ||
| 407 | returns false. | ||
| 408 | |||
| 409 | @par Preconditions | ||
| 410 | @ref start has been called. | ||
| 411 | |||
| 412 | @param ec Set to the error, if any occurred. | ||
| 413 | Set to @ref http::error::need_data until the | ||
| 414 | complete body is buffered, or to | ||
| 415 | @ref http::error::in_place_overflow if the | ||
| 416 | body does not fit in the buffer. | ||
| 417 | |||
| 418 | @return A view of the body octets flattened | ||
| 419 | so far, valid until the parser is modified. | ||
| 420 | The body is complete when no error is | ||
| 421 | reported. | ||
| 422 | */ | ||
| 423 | BOOST_BURL_DECL | ||
| 424 | std::string_view | ||
| 425 | flatten_body(system::error_code& ec); | ||
| 426 | |||
| 427 | /** Copy body octets into caller-supplied memory. | ||
| 428 | |||
| 429 | When a decoder is installed, it writes its | ||
| 430 | output into `buffers` directly. Parses the | ||
| 431 | header first if @ref got_header returns | ||
| 432 | false. | ||
| 433 | |||
| 434 | @par Preconditions | ||
| 435 | @ref start has been called. | ||
| 436 | |||
| 437 | @param buffers The destination. | ||
| 438 | |||
| 439 | @param ec Set to the error, if any occurred. | ||
| 440 | Set to `capy::error::eof` once the body is | ||
| 441 | complete. | ||
| 442 | |||
| 443 | @return The number of octets written. | ||
| 444 | */ | ||
| 445 | BOOST_BURL_DECL | ||
| 446 | std::size_t | ||
| 447 | read_some( | ||
| 448 | std::span<capy::mutable_buffer const> buffers, | ||
| 449 | system::error_code& ec); | ||
| 450 | |||
| 451 | /** Return available body octets in place. | ||
| 452 | |||
| 453 | Fills `dest` with descriptors referring to | ||
| 454 | the parser's own buffers. Release them with | ||
| 455 | @ref consume. Parses the header first if | ||
| 456 | @ref got_header returns false. | ||
| 457 | |||
| 458 | @par Preconditions | ||
| 459 | @ref start has been called. | ||
| 460 | |||
| 461 | @param dest The descriptors to fill. | ||
| 462 | |||
| 463 | @param ec Set to the error, if any occurred. | ||
| 464 | Set to `capy::error::eof` once the body is | ||
| 465 | complete. | ||
| 466 | |||
| 467 | @return The filled prefix of `dest`, valid | ||
| 468 | until the parser is modified. | ||
| 469 | |||
| 470 | @see @ref consume. | ||
| 471 | */ | ||
| 472 | BOOST_BURL_DECL | ||
| 473 | std::span<capy::const_buffer> | ||
| 474 | pull( | ||
| 475 | std::span<capy::const_buffer> dest, | ||
| 476 | system::error_code& ec); | ||
| 477 | |||
| 478 | /** Release body octets returned by @ref pull. | ||
| 479 | |||
| 480 | @par Preconditions | ||
| 481 | `n` does not exceed the octets returned by | ||
| 482 | the last call to @ref pull. | ||
| 483 | |||
| 484 | @param n The number of octets to release. | ||
| 485 | |||
| 486 | @see @ref pull. | ||
| 487 | */ | ||
| 488 | BOOST_BURL_DECL | ||
| 489 | void | ||
| 490 | consume(std::size_t n) noexcept; | ||
| 491 | |||
| 492 | /** Copy the trailer fields into a container. | ||
| 493 | |||
| 494 | Appends each field in the trailer section | ||
| 495 | of a chunked payload to `f`, in the order | ||
| 496 | received. | ||
| 497 | |||
| 498 | @par Preconditions | ||
| 499 | `this->got_header() == true` | ||
| 500 | |||
| 501 | @par Exception Safety | ||
| 502 | Basic guarantee. An exception from the | ||
| 503 | container leaves the parser unchanged; | ||
| 504 | fields already appended remain, and the | ||
| 505 | call may be retried. | ||
| 506 | |||
| 507 | @param f The container to append to. | ||
| 508 | |||
| 509 | @param ec Set to the error, if any | ||
| 510 | occurred. | ||
| 511 | */ | ||
| 512 | BOOST_BURL_DECL | ||
| 513 | void | ||
| 514 | parse_trailer( | ||
| 515 | fields_base& f, | ||
| 516 | system::error_code& ec); | ||
| 517 | |||
| 518 | protected: | ||
| 519 | 3x | parser() = default; | |
| 520 | |||
| 521 | BOOST_BURL_DECL | ||
| 522 | parser( | ||
| 523 | config const& cfg, | ||
| 524 | bool is_req); | ||
| 525 | |||
| 526 | 335x | parser(parser&& other) noexcept = default; | |
| 527 | |||
| 528 | parser& | ||
| 529 | 3x | operator=(parser&& other) noexcept = default; | |
| 530 | |||
| 531 | parser(const parser&) = delete; | ||
| 532 | |||
| 533 | parser& | ||
| 534 | operator=(const parser&) = delete; | ||
| 535 | |||
| 536 | 635x | ~parser() = default; | |
| 537 | |||
| 538 | BOOST_BURL_DECL | ||
| 539 | void | ||
| 540 | start(bool head); | ||
| 541 | |||
| 542 | BOOST_BURL_DECL | ||
| 543 | burl::response_head_base const& | ||
| 544 | get_response() const; | ||
| 545 | |||
| 546 | BOOST_BURL_DECL | ||
| 547 | burl::request_head_base const& | ||
| 548 | get_request() const; | ||
| 549 | |||
| 550 | private: | ||
| 551 | struct chunk_fn; | ||
| 552 | |||
| 553 | std::error_code | ||
| 554 | need_more() const noexcept; | ||
| 555 | |||
| 556 | std::size_t | ||
| 557 | trailer_extent() const noexcept; | ||
| 558 | |||
| 559 | std::error_code | ||
| 560 | walk_chunks(chunk_fn f, bool dry = false); | ||
| 561 | |||
| 562 | std::error_code | ||
| 563 | flatten_chunks(); | ||
| 564 | |||
| 565 | std::size_t | ||
| 566 | decode_some( | ||
| 567 | std::span<capy::mutable_buffer const> buffers, | ||
| 568 | system::error_code& ec); | ||
| 569 | |||
| 570 | std::unique_ptr<char[]> buf_; | ||
| 571 | head_parser hp_; | ||
| 572 | decoder * dec_ = nullptr; | ||
| 573 | detail::circular_buffer in_; | ||
| 574 | detail::circular_buffer out_; | ||
| 575 | std::uint64_t rem_ = 0; | ||
| 576 | std::uint64_t body_limit_ = 0; | ||
| 577 | std::uint64_t limit_rem_ = 0; | ||
| 578 | std::error_code dec_err_; | ||
| 579 | http::payload payload_ = http::payload::none; | ||
| 580 | bool head_ : 1 = false; | ||
| 581 | bool started_ : 1 = false; | ||
| 582 | bool got_header_ : 1 = false; | ||
| 583 | bool got_body_ : 1 = false; | ||
| 584 | bool mid_chunk_ : 1 = false; | ||
| 585 | bool fin_chunk_ : 1 = false; | ||
| 586 | bool eof_ : 1 = false; | ||
| 587 | }; | ||
| 588 | |||
| 589 | } // namespace burl | ||
| 590 | } // namespace boost | ||
| 591 | |||
| 592 | #endif | ||
| 593 |