GCC Code Coverage Report


Directory: libs/http_proto/include/boost/http_proto/
File: boost/http_proto/impl/parser.ipp
Date: 2023-02-10 23:49:12
Exec Total Coverage
Lines: 102 161 63.4%
Functions: 10 17 58.8%
Branches: 35 74 47.3%

Line Branch Exec Source
1 //
2 // Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
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/http_proto
8 //
9
10 #ifndef BOOST_HTTP_PROTO_IMPL_PARSER_IPP
11 #define BOOST_HTTP_PROTO_IMPL_PARSER_IPP
12
13 #include <boost/http_proto/error.hpp>
14 #include <boost/http_proto/parser.hpp>
15 #include <boost/http_proto/detail/codec.hpp>
16 #include <boost/http_proto/detail/except.hpp>
17 #include <boost/buffers/buffer_copy.hpp>
18 #include <boost/url/grammar/ci_string.hpp>
19 #include <boost/assert.hpp>
20 #include <boost/none.hpp>
21 #include <memory>
22
23 namespace boost {
24 namespace http_proto {
25
26 /*
27 Parser design:
28
29 The usage of the parser is thus:
30
31 pr.reset(); // prepare for a new stream
32
33 pr.start(); // prepare for a new message
34 pr.start_head_response(); // new message with no payload
35
36 read_header( ..., pr );
37 do
38 {
39 read_some(..., pr );
40 }
41 while(! got_header());
42
43 pr.set_body( ... );
44 // invalidates the headers? yes.
45
46 read_body( ..., pr );
47 while(! (pr.flags() &
48 parser::is_done_bit ) );
49 {
50 read_some(..., pr );
51 }
52
53 If these are called out of order, an
54 exception is thrown.
55
56 Every call to `prepare` must be
57 followed by a call to commit, reset,
58 or the destructor.
59 */
60 //------------------------------------------------
61
62 745 parser::
63 parser(
64 detail::kind k,
65 745 config_base const& cfg)
66 : cfg_(cfg)
67 745 , h_(detail::empty{k})
68 {
69 745 }
70
71 void
72 745 parser::
73 construct(
74 std::size_t extra_buffer_size)
75 {
76 // headers_limit too large
77
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 745 times.
745 if( cfg_.headers_limit >
78 BOOST_HTTP_PROTO_MAX_HEADER)
79 detail::throw_invalid_argument();
80
81 // start_line_limit too large
82 745 if( cfg_.start_line_limit >=
83
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 745 times.
745 cfg_.headers_limit)
84 detail::throw_invalid_argument();
85
86 // field_size_limit too large
87 745 if( cfg_.field_size_limit >=
88
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 745 times.
745 cfg_.headers_limit)
89 detail::throw_invalid_argument();
90
91 // fields_limit too large
92 745 if( cfg_.fields_limit >
93
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 745 times.
745 cfg_.headers_limit / 4)
94 detail::throw_invalid_argument();
95
96 // largest space needed
97 auto const bytes_needed =
98 745 detail::header::bytes_needed(
99 cfg_.headers_limit,
100 cfg_.fields_limit);
101
102 // prevent overflow
103 745 if(extra_buffer_size >
104
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 745 times.
745 std::size_t(-1) - bytes_needed)
105 detail::throw_invalid_argument();
106
107 // allocate max headers plus extra
108 745 ws_.allocate(
109 bytes_needed +
110 extra_buffer_size);
111
112 745 h_.cap = bytes_needed;
113
114 745 reset();
115 745 }
116
117 //------------------------------------------------
118 //
119 // Special Members
120 //
121 //------------------------------------------------
122
123 745 parser::
124
3/4
✓ Branch 0 taken 745 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2235 times.
✓ Branch 3 taken 745 times.
2980 ~parser()
125 {
126 745 }
127
128 parser::
129 parser(
130 parser&&) noexcept = default;
131
132 //------------------------------------------------
133 //
134 // Observers
135 //
136 //------------------------------------------------
137
138 string_view
139 parser::
140 body() const noexcept
141 {
142 #if 0
143 // VFALCO How about some
144 // asserts or exceptions?
145 if(! m_.got_chunked)
146 return string_view(
147 h_.buf + h_.size,
148 m_.n_payload);
149 return string_view(
150 h_.buf +
151 h_.size +
152 m_.n_chunk,
153 m_.n_payload);
154 #else
155 return {};
156 #endif
157 }
158
159 //------------------------------------------------
160 //
161 // Modifiers
162 //
163 //------------------------------------------------
164
165 // prepare for a new stream
166 void
167 745 parser::
168 reset() noexcept
169 {
170 745 st_ = state::need_start;
171 745 got_eof_ = false;
172 745 }
173
174 void
175 770 parser::
176 start_impl(
177 bool head_response)
178 {
179 770 std::size_t initial_size = 0;
180
2/4
✓ Branch 0 taken 737 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 33 times.
770 switch(st_)
181 {
182 737 default:
183 case state::need_start:
184
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 737 times.
737 BOOST_ASSERT(h_.size == 0);
185
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 737 times.
737 BOOST_ASSERT(h_buf_.size() == 0);
186
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 737 times.
737 BOOST_ASSERT(! got_eof_);
187 737 break;
188
189 case state::headers:
190 // Can't call start twice.
191 detail::throw_logic_error();
192
193 case state::headers_done:
194 case state::body:
195 // Can't call start with
196 // an incomplete message.
197 detail::throw_logic_error();
198
199 33 case state::complete:
200
1/2
✓ Branch 1 taken 33 times.
✗ Branch 2 not taken.
33 if(h_buf_.size() > 0)
201 {
202 // headers with no body
203
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 33 times.
33 BOOST_ASSERT(h_.size > 0);
204 33 h_buf_.consume(h_.size);
205 33 initial_size = h_buf_.size();
206 // move unused octets to front
207 33 buffers::buffer_copy(
208 33 buffers::mutable_buffer(
209 ws_.data(),
210 initial_size),
211 66 h_buf_.data());
212 }
213 else
214 {
215 // leftover data after body
216 }
217 33 break;
218 }
219
220 770 ws_.clear();
221
222 // set up header read buffer
223 770 h_buf_ = {
224 ws_.data(),
225 cfg_.headers_limit,
226 initial_size };
227
228 // reset the header but
229 // preserve the capacity
230 770 auto const cap = h_.cap;
231 1540 h_ = detail::header(
232 770 detail::empty{h_.kind});
233 770 h_.buf = reinterpret_cast<
234 770 char*>(ws_.data());
235 770 h_.cbuf = h_.buf;
236 770 h_.cap = cap;
237
238 770 cfg_impl_ = {};
239 770 cfg_impl_.headers_limit = cfg_.headers_limit;
240 770 cfg_impl_.start_line_limit = cfg_.start_line_limit;
241 770 cfg_impl_.field_size_limit = cfg_.field_size_limit;
242 770 cfg_impl_.fields_limit = cfg_.fields_limit;
243
244 770 st_ = state::headers;
245
246
3/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 769 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
770 BOOST_ASSERT(! head_response ||
247 h_.kind == detail::kind::response);
248 770 head_response_ = head_response;
249 770 }
250
251 auto
252 3185 parser::
253 prepare() ->
254 mutable_buffers_type
255 {
256
1/5
✗ Branch 0 not taken.
✓ Branch 1 taken 3185 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
3185 switch(st_)
257 {
258 default:
259 case state::need_start:
260 // start must be called once
261 // before calling prepare.
262 detail::throw_logic_error();
263
264 3185 case state::headers:
265 // fill up to headers_limit
266 return {
267 3185 h_buf_.prepare(
268 3185 cfg_.headers_limit -
269 3185 h_buf_.size()),
270 3185 buffers::mutable_buffer{} };
271
272 case state::headers_done:
273 {
274 // discard headers and move
275 // any leftover stream data.
276 std::memmove(
277 ws_.data(),
278 h_.cbuf + h_.size,
279 h_buf_.size() - h_.size);
280 st_ = state::body;
281 // VFALCO set up body buffer
282 BOOST_FALLTHROUGH;
283 }
284
285 case state::body:
286 {
287 return {};
288 }
289
290 case state::complete:
291 // Can't call `prepare` again after
292 // a complete message is parsed,
293 // call `start` first.
294 detail::throw_logic_error();
295 }
296 }
297
298 void
299 3185 parser::
300 commit(
301 std::size_t n)
302 {
303 // Can't commit after eof
304
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3185 times.
3185 if(got_eof_)
305 detail::throw_logic_error();
306
307
1/2
✓ Branch 0 taken 3185 times.
✗ Branch 1 not taken.
3185 switch(st_)
308 {
309 3185 default:
310 case state::need_start:
311 case state::headers:
312 3185 h_buf_.commit(n);
313 3185 break;
314
315 case state::headers_done:
316 case state::body:
317 case state::complete:
318 break;
319 }
320 3185 }
321
322 void
323 parser::
324 commit_eof()
325 {
326 switch(st_)
327 {
328 default:
329 case state::need_start:
330 // Can't commit eof
331 // before calling start.
332 detail::throw_logic_error();
333
334 case state::headers:
335 case state::headers_done:
336 case state::body:
337 got_eof_ = true;
338 break;
339
340 case state::complete:
341 // Can't commit eof when
342 // message is complete.
343 detail::throw_logic_error();
344 }
345 }
346
347 // process input data then
348 // eof if input data runs out.
349 void
350 3185 parser::
351 parse(
352 error_code& ec)
353 {
354
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 3185 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3185 switch(st_)
355 {
356 default:
357 case state::need_start:
358 // You must call start before
359 // calling parse on a new message.
360 detail::throw_logic_error();
361
362 3185 case state::headers:
363 {
364
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3185 times.
3185 BOOST_ASSERT(h_.buf == ws_.data());
365
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3185 times.
3185 BOOST_ASSERT(h_.cbuf == ws_.data());
366 3185 auto const new_size = h_buf_.size();
367 3185 h_.parse(cfg_impl_, new_size, ec);
368
2/2
✓ Branch 1 taken 584 times.
✓ Branch 2 taken 2601 times.
3185 if(! ec.failed())
369 {
370
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 581 times.
584 if( h_.md.payload != payload::none &&
371
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 ! head_response_)
372 {
373 // Deliver headers to caller
374 3 st_ = state::headers_done;
375 3 break;
376 }
377 // no payload
378 581 st_ = state::complete;
379 581 break;
380 }
381
3/4
✓ Branch 2 taken 2473 times.
✓ Branch 3 taken 128 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2601 times.
5074 if( ec == grammar::error::need_more &&
382
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2473 times.
2473 got_eof_)
383 {
384 if(h_.size > 0)
385 {
386 // Connection closed before
387 // message is complete.
388 ec = BOOST_HTTP_PROTO_ERR(
389 error::incomplete);
390
391 return;
392 }
393
394 // Connection closed
395 // cleanly.
396 ec = BOOST_HTTP_PROTO_ERR(
397 error::end_of_stream);
398
399 return;
400 }
401 2601 return;
402 }
403
404 case state::headers_done:
405 {
406 // This is a no-op
407 ec = {};
408 break;
409 }
410
411 case state::body:
412 {
413 parse_body(ec);
414 if(ec.failed())
415 return;
416 st_ = state::complete;
417 break;
418 }
419 }
420 }
421
422 //------------------------------------------------
423
424 string_view
425 parser::
426 release_buffered_data() noexcept
427 {
428 return {};
429 }
430
431 //------------------------------------------------
432 //
433 // Implementation
434 //
435 //------------------------------------------------
436
437 void
438 2 parser::
439 apply_param(
440 config_base const& cfg) noexcept
441 {
442 2 cfg_ = cfg;
443 2 }
444
445 //------------------------------------------------
446
447 auto
448 37 parser::
449 safe_get_header() const ->
450 detail::header const*
451 {
452
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 34 times.
37 switch(st_)
453 {
454 default:
455 case state::need_start:
456 case state::headers:
457 // Headers not received yet
458 detail::throw_logic_error();
459
460 3 case state::headers_done:
461 3 break;
462
463 case state::body:
464 // Headers received and discarded
465 detail::throw_logic_error();
466
467 34 case state::complete:
468 // VFALCO Could be OK
469 34 break;
470 }
471 37 return &h_;
472 }
473
474 void
475 parser::
476 parse_body(
477 error_code& ec)
478 {
479 (void)ec;
480 return;
481 // VFALCO TODO
482 #if 0
483 BOOST_ASSERT(st_ == state::body);
484
485 if(h_.kind == detail::kind::request)
486 {
487 // https://tools.ietf.org/html/rfc7230#section-3.3
488 if(m_.skip_body)
489 return;
490 if(m_.content_len.has_value())
491 {
492 if(*m_.content_len > cfg_.body_too_large)
493 {
494 ec = error::body_too_large;
495 return;
496 }
497 if(*m_.content_len == 0)
498 return;
499 }
500 else if(m_.got_chunked)
501 {
502 // VFALCO TODO
503 return;
504 }
505 else
506 {
507 // Content-Length: 0
508 return;
509 }
510 }
511 else
512 {
513 BOOST_ASSERT(h_.kind ==
514 detail::kind::response);
515
516 // https://tools.ietf.org/html/rfc7230#section-3.3
517 if((h_.res.status_int / 100 == 1) || // 1xx e.g. Continue
518 h_.res.status_int == 204 || // No Content
519 h_.res.status_int == 304) // Not Modified
520 {
521 // Content-Length may be present, but we
522 // treat the message as not having a body.
523 }
524 else if(m_.content_len.has_value())
525 {
526 if(*m_.content_len > 0)
527 {
528 if(*m_.content_len > cfg_.body_too_large)
529 {
530 ec = error::body_too_large;
531 return;
532 }
533 }
534 }
535 else
536 {
537 // No Content-Length
538 return;
539 }
540 }
541
542 auto avail = committed_ - size_;
543 if(m_.content_len.has_value())
544 {
545 // known payload length
546 BOOST_ASSERT(! m_.got_chunked);
547 BOOST_ASSERT(m_.n_remain > 0);
548 BOOST_ASSERT(m_.content_len <
549 cfg_.body_too_large);
550 if(avail == 0)
551 {
552 if(! got_eof_)
553 {
554 ec = grammar::error::need_more;
555 return;
556 }
557 ec = error::need_more;
558 return;
559 }
560 if( avail > m_.n_remain)
561 avail = static_cast<
562 std::size_t>(m_.n_remain);
563 size_ += avail;
564 m_.payload_seen += avail;
565 m_.n_payload += avail;
566 m_.n_remain -= avail;
567 if(m_.n_remain > 0)
568 {
569 ec = {};
570 return;
571 }
572 st_ = state::complete;
573 ec = error::end_of_message;
574 return;
575 }
576
577 if(! m_.got_chunked)
578 {
579 // end of body indicated by EOF
580 if(avail > 0)
581 {
582 if(avail > (std::size_t(
583 -1) - m_.n_payload))
584 {
585 // overflow size_t
586 // VFALCO revisit this
587 ec = error::numeric_overflow;
588 return;
589 }
590 size_ += avail;
591 m_.n_payload += avail;
592 ec = {};
593 return;
594 }
595 if(! got_eof_)
596 {
597 ec = grammar::error::need_more;
598 return;
599 }
600 st_ = state::complete;
601 ec = error::end_of_message;
602 return;
603 }
604 #if 0
605 if(m_.payload_left == 0)
606 {
607 // start of chunk
608 bnf::chunk_part p;
609 auto it = p.parse(
610 h_.buf + size_,
611 h_.buf + (
612 committed_ - size_),
613 ec);
614 if(ec)
615 return;
616 auto const v =
617 p.value();
618 m_.chunk.size = v.size;
619 m_.chunk.ext = v.ext;
620 m_.chunk.trailer = v.trailer;
621 m_.chunk.fresh = true;
622 m_.payload_left =
623 v.size - v.data.size();
624 }
625 else
626 {
627 // continuation of chunk
628
629 }
630 #endif
631 #endif
632 }
633
634 void
635 parser::
636 parse_chunk(
637 error_code& ec)
638 {
639 (void)ec;
640 #if 0
641 switch(st_)
642 {
643 case state::start_line_line:
644 case state::header_fields:
645 parse_header(ec);
646 if(ec.failed())
647 return;
648 BOOST_ASSERT(st_ >
649 state::header_fields);
650 break;
651 case state::body:
652 if(! m_.got_chunked)
653 return parse_body(ec);
654 break;
655 case state::complete:
656 ec = error::end_of_message;
657 if(! got_eof_)
658 return;
659 st_ = state::end_of_stream;
660 return;
661 case state::end_of_stream:
662 ec = error::end_of_stream;
663 return;
664 }
665
666 auto const avail = committed_ - size_;
667 auto const start = h_.buf + size_;
668 if(m_.payload_left == 0)
669 {
670 // start of chunk
671 // VFALCO What about chunk_part_next?
672 BOOST_ASSERT(
673 size_ == m_.header_size);
674 bnf::chunk_part p;
675 auto it = p.parse(start,
676 h_.buf + (
677 committed_ - size_), ec);
678 BOOST_ASSERT(it == start);
679 if(ec)
680 return;
681 auto const v = p.value();
682 m_.chunk.size = v.size;
683 m_.chunk.ext = v.ext;
684 m_.chunk.fresh = true;
685 if(v.size > 0)
686 {
687 // chunk
688 m_.chunk.trailer = {};
689 m_.payload_left =
690 v.size - v.data.size();
691 size_ += it - start; // excludes CRLF
692 return;
693 }
694 // last-chunk
695 BOOST_ASSERT(
696 v.data.empty());
697 m_.chunk.trailer =
698 v.trailer;
699 m_.body = {};
700 size_ += it - start; // excludes CRLF
701 st_ = state::complete;
702 }
703 else
704 {
705 // continuation of chunk
706
707 }
708 #endif
709 }
710
711 } // http_proto
712 } // boost
713
714 #endif
715