include/boost/http/parser.hpp

100.0% Lines (17/17) 76.9% List of functions (10/13)
parser.hpp
f(x) Functions (13)
Function Calls Lines Blocks
boost::http::parser::source<boost::capy::test::read_stream>::source(boost::capy::test::read_stream&, boost::http::parser&) :508 354x 100.0% 100.0% boost::http::parser::source<boost::capy::test::stream>::source(boost::capy::test::stream&, boost::http::parser&) :508 1x 100.0% 100.0% boost::capy::task<boost::capy::io_result<> > boost::http::parser::read_header<boost::capy::any_read_stream>(boost::capy::any_read_stream&) :541 1x 100.0% 44.0% boost::capy::task<boost::capy::io_result<> > boost::http::parser::read_header<boost::capy::test::read_stream>(boost::capy::test::read_stream&) :541 748x 100.0% 44.0% boost::capy::task<boost::capy::io_result<> > boost::http::parser::read_header<boost::capy::test::stream>(boost::capy::test::stream&) :541 0 0.0% 0.0% boost::capy::task<boost::capy::io_result<> > boost::http::parser::read<boost::capy::test::read_stream>(boost::capy::test::read_stream&) :569 274x 100.0% 44.0% boost::http::parser::source<boost::capy::test::read_stream> boost::http::parser::source_for<boost::capy::test::read_stream>(boost::capy::test::read_stream&) :654 354x 100.0% 100.0% boost::http::parser::source<boost::capy::test::stream> boost::http::parser::source_for<boost::capy::test::stream>(boost::capy::test::stream&) :654 1x 100.0% 100.0% boost::http::parser::source<boost::capy::test::read_stream>::pull(std::span<boost::capy::const_buffer, 18446744073709551615ul>) :662 922x 100.0% 44.0% boost::http::parser::source<boost::capy::test::stream>::pull(std::span<boost::capy::const_buffer, 18446744073709551615ul>) :662 0 0.0% 0.0% boost::http::parser::source<boost::capy::test::read_stream>::consume(unsigned long) :712 568x 100.0% 100.0% boost::http::parser::source<boost::capy::test::stream>::consume(unsigned long) :712 0 0.0% 0.0% boost::capy::task<boost::capy::io_result<> > boost::http::parser::read<boost::capy::test::write_sink&, boost::capy::test::read_stream>(boost::capy::test::read_stream&, boost::capy::test::write_sink&) :720 138x 100.0% 44.0%
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
3 // Copyright (c) 2024 Mohammad Nejati
4 //
5 // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 //
8 // Official repository: https://github.com/cppalliance/http
9 //
10
11 #ifndef BOOST_HTTP_PARSER_HPP
12 #define BOOST_HTTP_PARSER_HPP
13
14 #include <boost/http/config.hpp>
15 #include <boost/http/detail/header.hpp>
16 #include <boost/http/detail/type_traits.hpp>
17 #include <boost/http/error.hpp>
18
19 #include <boost/capy/buffers/buffer_copy.hpp>
20 #include <boost/capy/buffers/buffer_slice.hpp>
21 #include <boost/capy/concept/read_stream.hpp>
22 #include <boost/capy/concept/write_sink.hpp>
23 #include <boost/capy/cond.hpp>
24 #include <boost/capy/error.hpp>
25 #include <boost/capy/io_task.hpp>
26 #include <boost/core/span.hpp>
27
28 #include <array>
29 #include <cstddef>
30 #include <cstdint>
31 #include <memory>
32
33 namespace boost {
34 namespace http {
35
36 // Forward declaration
37 class request_parser;
38 class response_parser;
39 class static_request;
40 class static_response;
41
42 //------------------------------------------------
43
44 /** A parser for HTTP/1 messages.
45
46 This parser uses a single block of memory allocated
47 during construction and guarantees it will never
48 exceed the specified size. This space is reused for
49 parsing multiple HTTP messages ( one at a time ).
50
51 The allocated space is used for:
52
53 @li Buffering raw input from a socket
54 @li Storing HTTP headers with O(1) access to
55 method, target, and status code
56 @li Storing all or part of an HTTP message body
57 @li Storing state for inflate algorithms
58
59 The parser is strict. Any malformed input according
60 to the HTTP ABNFs is treated as an unrecoverable
61 error.
62
63 @see
64 @ref response_parser,
65 @ref request_parser.
66 */
67 class parser
68 {
69 public:
70 template<capy::ReadStream Stream>
71 class source;
72
73 /// Buffer type returned from @ref prepare.
74 using mutable_buffers_type =
75 boost::span<capy::mutable_buffer const>;
76
77 /// Buffer type returned from @ref pull_body.
78 using const_buffers_type =
79 boost::span<capy::const_buffer const>;
80
81 //--------------------------------------------
82 //
83 // Observers
84 //
85 //--------------------------------------------
86
87 /// Check if a complete header has been parsed.
88 BOOST_HTTP_DECL
89 bool
90 got_header() const noexcept;
91
92 /// Check if a complete message has been parsed.
93 BOOST_HTTP_DECL
94 bool
95 is_complete() const noexcept;
96
97 //--------------------------------------------
98 //
99 // Modifiers
100 //
101 //--------------------------------------------
102
103 /// Prepare for a new stream.
104 BOOST_HTTP_DECL
105 void
106 reset() noexcept;
107
108 /** Prepare for a new message.
109
110 @par Preconditions
111 Either this is the first message in the stream,
112 or the previous message has been fully parsed.
113 */
114 BOOST_HTTP_DECL
115 void
116 start();
117
118 /** Return a buffer for reading input.
119
120 After writing to the buffer, call @ref commit
121 with the number of bytes written.
122
123 @par Preconditions
124 @ref parse returned @ref condition::need_more_input.
125
126 @par Postconditions
127 A call to @ref commit or @ref commit_eof is
128 required before calling @ref prepare again.
129
130 @par Exception Safety
131 Strong guarantee.
132
133 @return A non-empty mutable buffer.
134
135 @see @ref commit, @ref commit_eof.
136 */
137 BOOST_HTTP_DECL
138 mutable_buffers_type
139 prepare();
140
141 /** Commit bytes to the input buffer.
142
143 @par Preconditions
144 @li `n <= capy::buffer_size( this->prepare() )`
145 @li No prior call to @ref commit or @ref commit_eof
146 since the last @ref prepare
147
148 @par Postconditions
149 Buffers from @ref prepare are invalidated.
150
151 @par Exception Safety
152 Strong guarantee.
153
154 @param n The number of bytes written.
155
156 @see @ref parse, @ref prepare.
157 */
158 BOOST_HTTP_DECL
159 void
160 commit(
161 std::size_t n);
162
163 /** Indicate end of input.
164
165 Call this when the underlying stream has closed
166 and no more data will arrive.
167
168 @par Postconditions
169 Buffers from @ref prepare are invalidated.
170
171 @par Exception Safety
172 Strong guarantee.
173
174 @see @ref parse, @ref prepare.
175 */
176 BOOST_HTTP_DECL
177 void
178 commit_eof();
179
180 /** Parse pending input data.
181
182 Returns immediately after the header is fully
183 parsed to allow @ref set_body_limit to be called
184 before body parsing begins. If an error occurs
185 during body parsing, the parsed header remains
186 valid and accessible.
187
188 When `ec == condition::need_more_input`, read
189 more data and call @ref commit before calling
190 this function again.
191
192 When `ec == error::end_of_stream`, the stream
193 closed cleanly. Call @ref reset to reuse the
194 parser for a new stream.
195
196 @param ec Set to the error, if any occurred.
197
198 @see @ref start, @ref prepare, @ref commit.
199 */
200 BOOST_HTTP_DECL
201 void
202 parse(
203 system::error_code& ec);
204
205 /** Set maximum body size for the current message.
206
207 Overrides @ref parser_config::body_limit for this
208 message only. The limit resets to the default
209 for subsequent messages.
210
211 @par Preconditions
212 `this->got_header() == true` and body parsing
213 has not started.
214
215 @par Exception Safety
216 Strong guarantee.
217
218 @param n The body size limit in bytes.
219
220 @see @ref parser_config::body_limit.
221 */
222 BOOST_HTTP_DECL
223 void
224 set_body_limit(std::uint64_t n);
225
226 /** Return available body data.
227
228 Use this to incrementally process body data.
229 Call @ref consume_body after processing to
230 release the buffer space.
231
232 @par Example
233 @code
234 request_parser pr( ctx );
235 pr.start();
236 co_await pr.read_header( stream );
237
238 while( ! pr.is_complete() )
239 {
240 co_await read_some( stream, pr );
241 auto cbs = pr.pull_body();
242 // process cbs ...
243 pr.consume_body( capy::buffer_size( cbs ) );
244 }
245 @endcode
246
247 @par Preconditions
248 `this->got_header() == true`
249
250 @par Postconditions
251 The returned buffer is invalidated by any
252 modifying member function.
253
254 @par Exception Safety
255 Strong guarantee.
256
257 @return Buffers containing available body data.
258
259 @see @ref consume_body.
260 */
261 BOOST_HTTP_DECL
262 const_buffers_type
263 pull_body();
264
265 /** Consume bytes from available body data.
266
267 @par Preconditions
268 `n <= capy::buffer_size( this->pull_body() )`
269
270 @par Exception Safety
271 Strong guarantee.
272
273 @param n The number of bytes to consume.
274
275 @see @ref pull_body.
276 */
277 BOOST_HTTP_DECL
278 void
279 consume_body(std::size_t n);
280
281 /** Return the complete body.
282
283 Use this when the entire message fits within
284 the parser's internal buffer.
285
286 @par Example
287 @code
288 request_parser pr( ctx );
289 pr.start();
290 co_await pr.read_header( stream );
291 // ... read entire body ...
292 core::string_view body = pr.body();
293 @endcode
294
295 @par Preconditions
296 @li `this->is_complete() == true`
297 @li No previous call to @ref consume_body
298
299 @par Exception Safety
300 Strong guarantee.
301
302 @return A string view of the complete body.
303
304 @see @ref is_complete.
305 */
306 BOOST_HTTP_DECL
307 core::string_view
308 body() const;
309
310 /** Return true if data is buffered past the message.
311
312 After a complete message, returns true when the
313 parser's buffer still holds octets that lie
314 beyond it, such as the start of a pipelined
315 message or data the peer sent past the message
316 framing. Returns false before the message is
317 complete.
318
319 This does not include the message body, which is
320 retrieved separately via @ref body or
321 @ref pull_body.
322
323 @return true if octets remain buffered past the
324 completed message.
325
326 @see @ref is_complete, @ref release_buffered_data.
327 */
328 BOOST_HTTP_DECL
329 bool
330 has_buffered_data() const noexcept;
331
332 /** Return unconsumed data past the last message.
333
334 Use this after an upgrade or CONNECT request
335 to retrieve protocol-dependent data that
336 follows the HTTP message.
337
338 @return A string view of leftover data.
339
340 @see @ref metadata::upgrade, @ref metadata::connection.
341 */
342 BOOST_HTTP_DECL
343 core::string_view
344 release_buffered_data() noexcept;
345
346 /** Asynchronously read the HTTP headers.
347
348 Reads from the stream until the headers are
349 complete or an error occurs.
350
351 @par Preconditions
352 @li @ref reset has been called
353 @li @ref start has been called
354
355 @param stream The stream to read from.
356
357 @return An awaitable yielding `(error_code)`.
358
359 @see @ref read.
360 */
361 template<capy::ReadStream Stream>
362 capy::io_task<>
363 read_header(Stream& stream);
364
365 /** Asynchronously read a complete HTTP message.
366
367 Reads from the stream until the message is fully
368 parsed or an error occurs. The body is accumulated
369 in the parser's internal buffer and can be retrieved
370 via @ref body after completion.
371
372 If the parser's internal buffer fills before the
373 message is complete, the operation completes with
374 @ref error::in_place_overflow.
375
376 @par Preconditions
377 @li @ref reset has been called
378 @li @ref start has been called
379
380 @param stream The stream to read from.
381
382 @return An awaitable yielding `(error_code)`.
383
384 @see @ref body, @ref read_header.
385 */
386 template<capy::ReadStream Stream>
387 capy::io_task<>
388 read(Stream& stream);
389
390 /** Asynchronously read body data into buffers.
391
392 Reads from the stream and copies body data into
393 the provided buffers with complete-fill semantics.
394 Returns `capy::error::eof` when the body is complete.
395
396 @par Preconditions
397 @li @ref reset has been called
398 @li @ref start has been called
399
400 @param stream The stream to read from.
401
402 @param buffers The buffers to read into.
403
404 @return An awaitable yielding `(error_code,std::size_t)`.
405
406 @see @ref read_header.
407 */
408 template<capy::ReadStream Stream, capy::MutableBufferSequence MB>
409 capy::io_task<std::size_t>
410 read(Stream& stream, MB buffers);
411
412 /** Return a source for reading body data.
413
414 The returned source satisfies @ref capy::BufferSource.
415 On first pull, headers are automatically parsed if
416 not yet received.
417
418 @par Example
419 @code
420 request_parser pr( ctx );
421 pr.start();
422 auto body = pr.source_for( socket );
423
424 capy::const_buffer arr[16];
425 auto [ec, bufs] = co_await body.pull( arr );
426 body.consume( buffer_size( bufs ) );
427 @endcode
428
429 @param stream The stream to read from.
430
431 @return A source satisfying @ref capy::BufferSource.
432
433 @see @ref read_header, @ref capy::BufferSource.
434 */
435 template<capy::ReadStream Stream>
436 source<Stream>
437 source_for(Stream& stream) noexcept;
438
439 /** Read body from stream and push to a WriteSink.
440
441 Reads body data from the stream and pushes each chunk to
442 the sink. The sink must consume all bytes from each write.
443
444 @param stream The stream to read body data from.
445
446 @param sink The sink to receive body data.
447
448 @return An awaitable yielding `(error_code)`.
449
450 @see WriteSink.
451 */
452 template<capy::WriteSink Sink>
453 capy::io_task<>
454 read(capy::ReadStream auto& stream, Sink&& sink);
455
456 private:
457 friend class request_parser;
458 friend class response_parser;
459 class impl;
460
461 BOOST_HTTP_DECL ~parser();
462 BOOST_HTTP_DECL parser() noexcept;
463 BOOST_HTTP_DECL parser(parser&& other) noexcept;
464 BOOST_HTTP_DECL parser(
465 std::shared_ptr<parser_config_impl const> cfg,
466 detail::kind k);
467 BOOST_HTTP_DECL void assign(parser&& other) noexcept;
468
469 BOOST_HTTP_DECL
470 void
471 start_impl(bool);
472
473 static_request const&
474 safe_get_request() const;
475
476 static_response const&
477 safe_get_response() const;
478
479 impl* impl_;
480 };
481
482 /** A source for reading the message body.
483
484 This type satisfies @ref capy::BufferSource. It can be
485 constructed immediately after parser construction; on
486 first pull, headers are automatically parsed if not
487 yet received.
488
489 @tparam Stream A type satisfying @ref capy::ReadStream.
490
491 @see @ref parser::source_for.
492 */
493 template<capy::ReadStream Stream>
494 class parser::source
495 {
496 Stream* stream_;
497 parser* pr_;
498
499 public:
500 /// Default constructor.
501 source() noexcept
502 : stream_(nullptr)
503 , pr_(nullptr)
504 {
505 }
506
507 /// Construct a source for reading body data.
508 355x source(Stream& stream, parser& pr) noexcept
509 355x : stream_(&stream)
510 355x , pr_(&pr)
511 {
512 355x }
513
514 /** Pull buffer data from the body.
515
516 On first invocation, reads headers if not yet parsed.
517 Returns buffer descriptors pointing to internal parser
518 memory. When the body is complete, returns an empty span.
519
520 @param dest Span of const_buffer to fill.
521
522 @return An awaitable yielding `(error_code,std::span<const_buffer>)`.
523 */
524 capy::io_task<std::span<capy::const_buffer>>
525 pull(std::span<capy::const_buffer> dest);
526
527 /** Consume bytes from pulled body data.
528
529 Advances the read position by the specified number of
530 bytes. The next pull returns data starting after the
531 consumed bytes.
532
533 @param n The number of bytes to consume.
534 */
535 void
536 consume(std::size_t n) noexcept;
537 };
538
539 template<capy::ReadStream Stream>
540 capy::io_task<>
541 749x parser::
542 read_header(Stream& stream)
543 {
544 system::error_code ec;
545 for(;;)
546 {
547 parse(ec);
548
549 if(got_header())
550 co_return {};
551
552 if(ec != condition::need_more_input)
553 co_return {std::error_code(ec)};
554
555 auto mbs = prepare();
556
557 auto [read_ec, n] = co_await stream.read_some(mbs);
558 if(read_ec == capy::cond::eof)
559 commit_eof();
560 else if(!read_ec)
561 commit(n);
562 else
563 co_return {read_ec};
564 }
565 1498x }
566
567 template<capy::ReadStream Stream>
568 capy::io_task<>
569 274x parser::
570 read(Stream& stream)
571 {
572 system::error_code ec;
573 for(;;)
574 {
575 parse(ec);
576
577 if(is_complete())
578 co_return {};
579
580 if(ec && ec != condition::need_more_input)
581 co_return {std::error_code(ec)};
582
583 if(ec == condition::need_more_input)
584 {
585 auto mbs = prepare();
586
587 auto [read_ec, n] = co_await stream.read_some(mbs);
588 if(read_ec == capy::cond::eof)
589 commit_eof();
590 else if(!read_ec)
591 commit(n);
592 else
593 co_return {read_ec};
594 }
595 }
596 548x }
597
598 template<capy::ReadStream Stream, capy::MutableBufferSequence MB>
599 capy::io_task<std::size_t>
600 parser::
601 read(Stream& stream, MB buffers)
602 {
603 if(capy::buffer_empty(buffers))
604 co_return {{}, 0};
605
606 std::size_t total = 0;
607 auto dest = capy::buffer_slice(buffers);
608
609 for(;;)
610 {
611 system::error_code ec;
612 parse(ec);
613
614 if(got_header())
615 {
616 auto body_data = pull_body();
617 if(capy::buffer_size(body_data) > 0)
618 {
619 std::size_t copied = capy::buffer_copy(dest.data(), body_data);
620 consume_body(copied);
621 total += copied;
622 dest.remove_prefix(copied);
623
624 if(capy::buffer_empty(dest.data()))
625 co_return {{}, total};
626 }
627
628 if(is_complete())
629 co_return {capy::error::eof, total};
630 }
631
632 if(ec == condition::need_more_input)
633 {
634 auto mbs = prepare();
635 auto [read_ec, n] = co_await stream.read_some(mbs);
636
637 if(read_ec == capy::cond::eof)
638 commit_eof();
639 else if(!read_ec)
640 commit(n);
641 else
642 co_return {read_ec, total};
643
644 continue;
645 }
646
647 if(ec)
648 co_return {ec, total};
649 }
650 }
651
652 template<capy::ReadStream Stream>
653 parser::source<Stream>
654 355x parser::
655 source_for(Stream& stream) noexcept
656 {
657 355x return source<Stream>(stream, *this);
658 }
659
660 template<capy::ReadStream Stream>
661 capy::io_task<std::span<capy::const_buffer>>
662 922x parser::source<Stream>::
663 pull(std::span<capy::const_buffer> dest)
664 {
665 // Read headers if not yet parsed
666 if(!pr_->got_header())
667 {
668 auto [ec] = co_await pr_->read_header(*stream_);
669 if(ec)
670 co_return {ec, {}};
671 }
672
673 for(;;)
674 {
675 system::error_code ec;
676 pr_->parse(ec);
677
678 auto body_data = pr_->pull_body();
679 if(capy::buffer_size(body_data) > 0)
680 {
681 std::size_t count = (std::min)(body_data.size(), dest.size());
682 for(std::size_t i = 0; i < count; ++i)
683 dest[i] = body_data[i];
684 co_return {{}, dest.first(count)};
685 }
686
687 if(pr_->is_complete())
688 co_return {capy::error::eof, {}};
689
690 if(ec == condition::need_more_input)
691 {
692 auto mbs = pr_->prepare();
693 auto [read_ec, n] = co_await stream_->read_some(mbs);
694
695 if(read_ec == capy::cond::eof)
696 pr_->commit_eof();
697 else if(!read_ec)
698 pr_->commit(n);
699 else
700 co_return {read_ec, {}};
701
702 continue;
703 }
704
705 if(ec)
706 co_return {ec, {}};
707 }
708 1844x }
709
710 template<capy::ReadStream Stream>
711 void
712 568x parser::source<Stream>::
713 consume(std::size_t n) noexcept
714 {
715 568x pr_->consume_body(n);
716 568x }
717
718 template<capy::WriteSink Sink>
719 capy::io_task<>
720 138x parser::
721 read(capy::ReadStream auto& stream, Sink&& sink)
722 {
723 for(;;)
724 {
725 system::error_code ec;
726 parse(ec);
727
728 if(got_header())
729 {
730 auto body_data = pull_body();
731 if(capy::buffer_size(body_data) > 0)
732 {
733 auto [write_ec, n] = co_await sink.write(body_data);
734 if(write_ec)
735 co_return {write_ec};
736 consume_body(n);
737 }
738
739 if(is_complete())
740 {
741 auto [eof_ec] = co_await sink.write_eof();
742 co_return {eof_ec};
743 }
744 }
745
746 if(ec == condition::need_more_input)
747 {
748 auto mbs = prepare();
749 auto [read_ec, n] = co_await stream.read_some(mbs);
750
751 if(read_ec == capy::cond::eof)
752 commit_eof();
753 else if(!read_ec)
754 commit(n);
755 else
756 co_return {read_ec};
757
758 continue;
759 }
760
761 if(ec)
762 co_return {std::error_code(ec)};
763 }
764 276x }
765
766 } // http
767 } // boost
768
769 #endif
770