GCC Code Coverage Report


Directory: libs/http_proto/include/boost/http_proto/
File: boost/http_proto/impl/parser.ipp
Date: 2023-02-02 18:17:22
Exec Total Coverage
Lines: 101 161 62.7%
Functions: 10 17 58.8%
Branches: 35 76 46.1%

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/url/grammar/ci_string.hpp>
18 #include <boost/assert.hpp>
19 #include <boost/none.hpp>
20 #include <memory>
21
22 namespace boost {
23 namespace http_proto {
24
25 /*
26 Parser design:
27
28 The usage of the parser is thus:
29
30 pr.reset(); // prepare for a new stream
31
32 pr.start(); // prepare for a new message
33 pr.start_head_response(); // new message with no payload
34
35 read_header( ..., pr );
36 do
37 {
38 read_some(..., pr );
39 }
40 while(! got_header());
41
42 pr.set_body( ... );
43 // invalidates the headers? yes.
44
45 read_body( ..., pr );
46 while(! (pr.flags() &
47 parser::is_done_bit ) );
48 {
49 read_some(..., pr );
50 }
51
52 If these are called out of order, an
53 exception is thrown.
54
55 Every call to `prepare` must be
56 followed by a call to commit, reset,
57 or the destructor.
58 */
59 //------------------------------------------------
60
61 745 parser::
62 parser(
63 detail::kind k,
64 745 config_base const& cfg)
65 : cfg_(cfg)
66 745 , h_(detail::empty{k})
67 {
68 745 }
69
70 void
71 745 parser::
72 construct(
73 std::size_t extra_buffer_size)
74 {
75 // headers_limit too large
76
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 745 times.
745 if( cfg_.headers_limit >
77 BOOST_HTTP_PROTO_MAX_HEADER)
78 detail::throw_invalid_argument();
79
80 // start_line_limit too large
81 745 if( cfg_.start_line_limit >=
82
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 745 times.
745 cfg_.headers_limit)
83 detail::throw_invalid_argument();
84
85 // field_size_limit too large
86 745 if( cfg_.field_size_limit >=
87
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 745 times.
745 cfg_.headers_limit)
88 detail::throw_invalid_argument();
89
90 // fields_limit too large
91 745 if( cfg_.fields_limit >
92
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 745 times.
745 cfg_.headers_limit / 4)
93 detail::throw_invalid_argument();
94
95 // largest space needed
96 auto const bytes_needed =
97 745 detail::header::bytes_needed(
98 cfg_.headers_limit,
99 cfg_.fields_limit);
100
101 // prevent overflow
102 745 if(extra_buffer_size >
103
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 745 times.
745 std::size_t(-1) - bytes_needed)
104 detail::throw_invalid_argument();
105
106 // allocate max headers plus extra
107 1490 ws_ = detail::workspace(
108 bytes_needed +
109 745 extra_buffer_size);
110
111 745 h_.cap = bytes_needed;
112
113 745 reset();
114 745 }
115
116 //------------------------------------------------
117 //
118 // Special Members
119 //
120 //------------------------------------------------
121
122 745 parser::
123
3/4
✓ Branch 0 taken 745 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2235 times.
✓ Branch 3 taken 745 times.
2980 ~parser()
124 {
125 745 }
126
127 parser::
128 parser(
129 parser&&) noexcept = default;
130
131 //------------------------------------------------
132 //
133 // Observers
134 //
135 //------------------------------------------------
136
137 string_view
138 parser::
139 body() const noexcept
140 {
141 #if 0
142 // VFALCO How about some
143 // asserts or exceptions?
144 if(! m_.got_chunked)
145 return string_view(
146 h_.buf + h_.size,
147 m_.n_payload);
148 return string_view(
149 h_.buf +
150 h_.size +
151 m_.n_chunk,
152 m_.n_payload);
153 #else
154 return {};
155 #endif
156 }
157
158 //------------------------------------------------
159 //
160 // Modifiers
161 //
162 //------------------------------------------------
163
164 // prepare for a new stream
165 void
166 745 parser::
167 reset() noexcept
168 {
169 745 st_ = state::need_start;
170 745 got_eof_ = false;
171 745 }
172
173 void
174 770 parser::
175 start_impl(
176 bool head_response)
177 {
178 770 std::size_t initial_size = 0;
179
2/4
✓ Branch 0 taken 737 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 33 times.
770 switch(st_)
180 {
181 737 default:
182 case state::need_start:
183
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 737 times.
737 BOOST_ASSERT(h_.size == 0);
184
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 737 times.
737 BOOST_ASSERT(h_buf_.size() == 0);
185
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 737 times.
737 BOOST_ASSERT(! got_eof_);
186 737 break;
187
188 case state::headers:
189 // Can't call start twice.
190 detail::throw_logic_error();
191
192 case state::headers_done:
193 case state::body:
194 // Can't call start with
195 // an incomplete message.
196 detail::throw_logic_error();
197
198 33 case state::complete:
199
1/2
✓ Branch 1 taken 33 times.
✗ Branch 2 not taken.
33 if(h_buf_.size() > 0)
200 {
201 // headers with no body
202
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 33 times.
33 BOOST_ASSERT(h_.size > 0);
203 33 h_buf_.consume(h_.size);
204 33 initial_size = h_buf_.size();
205 // move unused octets to front
206 33 buffer_copy(
207 33 mutable_buffer(
208 ws_.data(),
209 initial_size),
210 66 h_buf_.data());
211 }
212 else
213 {
214 // leftover data after body
215 }
216 33 break;
217 }
218
219 770 ws_.clear();
220
221 // set up header read buffer
222 770 h_buf_ = {
223 ws_.data(),
224 cfg_.headers_limit,
225 initial_size };
226
227 // reset the header but
228 // preserve the capacity
229 770 auto const cap = h_.cap;
230 1540 h_ = detail::header(
231 770 detail::empty{h_.kind});
232 770 h_.buf = reinterpret_cast<
233 770 char*>(ws_.data());
234 770 h_.cbuf = h_.buf;
235 770 h_.cap = cap;
236
237 770 cfg_impl_ = {};
238 770 cfg_impl_.headers_limit = cfg_.headers_limit;
239 770 cfg_impl_.start_line_limit = cfg_.start_line_limit;
240 770 cfg_impl_.field_size_limit = cfg_.field_size_limit;
241 770 cfg_impl_.fields_limit = cfg_.fields_limit;
242
243 770 st_ = state::headers;
244
245
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 ||
246 h_.kind == detail::kind::response);
247 770 head_response_ = head_response;
248 770 }
249
250 auto
251 3185 parser::
252 prepare() ->
253 mutable_buffers_type
254 {
255
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_)
256 {
257 default:
258 case state::need_start:
259 // start must be called once
260 // before calling prepare.
261 if(st_ == state::need_start)
262 detail::throw_logic_error();
263
264 case state::headers:
265 // fill up to headers_limit
266 return {
267 h_buf_.prepare(
268 3185 cfg_.headers_limit -
269 3185 h_buf_.size()),
270 3185 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