GCC Code Coverage Report


Directory: libs/http_proto/include/boost/http_proto/
File: boost/http_proto/impl/parser.ipp
Date: 2023-01-09 16:11:13
Exec Total Coverage
Lines: 105 163 64.4%
Functions: 11 20 55.0%
Branches: 41 80 51.2%

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/url/grammar/ci_string.hpp>
16 #include <boost/assert.hpp>
17 #include <boost/none.hpp>
18 #include <memory>
19
20 namespace boost {
21 namespace http_proto {
22
23 //------------------------------------------------
24
25 707 parser::
26 parser(
27 detail::kind k,
28 config const& cfg,
29 707 std::size_t buffer_bytes)
30 : cfg_(cfg)
31 , h_(detail::empty{k})
32 , committed_(0)
33 , state_(state::empty)
34 , got_eof_(false)
35 707 , m_{}
36 {
37 // buffer must be large enough to
38 // hold a complete header.
39 707 if( buffer_bytes <
40
1/2
✓ Branch 0 taken 707 times.
✗ Branch 1 not taken.
707 cfg.max_header_size)
41 707 buffer_bytes =
42 cfg.max_header_size;
43
44 707 h_.buf = new char[buffer_bytes];
45 707 h_.cbuf = h_.buf;
46 707 h_.cap = buffer_bytes;
47 707 h_.size = 0;
48 707 h_.prefix = 0;
49 707 }
50
51 //------------------------------------------------
52 //
53 // Special Members
54 //
55 //------------------------------------------------
56
57 707 parser::
58 707 ~parser()
59 {
60
1/2
✓ Branch 0 taken 707 times.
✗ Branch 1 not taken.
707 delete[] h_.buf;
61 707 }
62
63 //------------------------------------------------
64 //
65 // Observers
66 //
67 //------------------------------------------------
68
69 string_view
70 parser::
71 body() const noexcept
72 {
73 // VFALCO How about some
74 // asserts or exceptions?
75 if(! m_.got_chunked)
76 return string_view(
77 h_.buf + h_.size,
78 m_.n_payload);
79 return string_view(
80 h_.buf +
81 h_.size +
82 m_.n_chunk,
83 m_.n_payload);
84 }
85
86 //------------------------------------------------
87 //
88 // Input
89 //
90 //------------------------------------------------
91
92 void
93 parser::
94 clear() noexcept
95 {
96 reset();
97 }
98
99 void
100 parser::
101 reset()
102 {
103 if(got_eof_)
104 {
105 // new connection, throw
106 // out all previous state.
107 committed_ = 0;
108 got_eof_ = false;
109 }
110 else
111 {
112 // Throwing out partial data
113 // will desync the HTTP stream
114 //BOOST_ASSERT(is_complete());
115 if( committed_ > h_.size &&
116 h_.size > 0)
117 {
118 // move unused octets to front
119 std::memcpy(
120 h_.buf,
121 h_.buf + h_.size,
122 committed_ - h_.size);
123 committed_ -= h_.size;
124 h_.size = 0;
125 }
126 else
127 {
128 committed_ = 0;
129 }
130 }
131
132 // reset the header but
133 // preserve the capacity
134 detail::header h(
135 detail::empty{h_.kind});
136 h.assign_to(h_);
137
138 m_ = message{};
139 state_ = state::empty;
140 }
141
142 mutable_buffers
143 2969 parser::
144 prepare()
145 {
146 // Can't commit bytes after eof
147
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2969 times.
2969 BOOST_ASSERT(! got_eof_);
148
149 5938 mb_ = {
150 2969 h_.buf + committed_,
151 2969 h_.cap - committed_ };
152 2969 return { &mb_, 1 };
153 }
154
155 void
156 2969 parser::
157 commit(
158 std::size_t n,
159 error_code& ec)
160 {
161
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2969 times.
2969 BOOST_ASSERT(! got_eof_);
162
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2969 times.
2969 BOOST_ASSERT(
163 n <= h_.cap - committed_);
164 2969 committed_ += n;
165 2969 parse(ec);
166 2969 }
167
168 void
169 parser::
170 commit_eof(
171 error_code &ec)
172 {
173 got_eof_ = true;
174 parse(ec);
175 }
176
177 //------------------------------------------------
178 //
179 //
180 //
181 //------------------------------------------------
182
183 void
184 parser::
185 discard_header() noexcept
186 {
187 }
188
189 // discard all of the body,
190 // but don't discard chunk-ext
191 void
192 parser::
193 discard_body() noexcept
194 {
195 }
196
197 // discard all of the payload
198 // including any chunk-ext
199 void
200 parser::
201 discard_chunk() noexcept
202 {
203 }
204
205 //------------------------------------------------
206 //
207 // Implementation
208 //
209 //------------------------------------------------
210
211 void
212 2969 parser::
213 parse(
214 error_code& ec)
215 {
216 2969 ec.clear();
217
2/2
✓ Branch 0 taken 2969 times.
✓ Branch 1 taken 579 times.
3548 while(state_ != state::complete)
218 {
219
3/5
✓ Branch 0 taken 707 times.
✓ Branch 1 taken 1215 times.
✓ Branch 2 taken 1047 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
2969 switch(state_)
220 {
221 707 case state::empty:
222
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 707 times.
707 if(got_eof_)
223 {
224 BOOST_ASSERT(h_.size == 0);
225 ec = error::end_of_stream;
226 return;
227 }
228 707 state_ = state::start_line;
229 BOOST_FALLTHROUGH;
230
231 1922 case state::start_line:
232 1922 parse_start_line(ec);
233
2/2
✓ Branch 1 taken 1215 times.
✓ Branch 2 taken 707 times.
1922 if(ec.failed())
234 1215 return;
235 707 state_ = state::header_fields;
236 BOOST_FALLTHROUGH;
237
238 1754 case state::header_fields:
239 1754 parse_fields(ec);
240
2/2
✓ Branch 1 taken 1175 times.
✓ Branch 2 taken 579 times.
1754 if(ec.failed())
241 1175 return;
242 579 state_ = state::body;
243 BOOST_FALLTHROUGH;
244
245 579 case state::body:
246 579 parse_body(ec);
247
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 579 times.
579 if(ec.failed())
248 return;
249 579 state_ = state::complete;
250 BOOST_FALLTHROUGH;
251
252 579 default:
253 579 break;
254 }
255 }
256 }
257
258 void
259 1922 parser::
260 parse_start_line(
261 error_code& ec)
262 {
263
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1922 times.
1922 BOOST_ASSERT(h_.size == 0);
264 1922 std::size_t const new_size =
265 1922 committed_ <= cfg_.max_header_size
266
1/2
✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
1922 ? committed_
267 : cfg_.max_header_size;
268 string_view s(
269 1922 this->h_.buf, new_size);
270 auto rv =
271 1922 detail::parse_start_line(h_, s);
272
3/4
✓ Branch 2 taken 1922 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1215 times.
✓ Branch 5 taken 707 times.
1922 if(rv == grammar::error::need_more)
273 {
274 1215 if( new_size <
275
1/2
✓ Branch 0 taken 1215 times.
✗ Branch 1 not taken.
1215 cfg_.max_header_size)
276 {
277 1215 ec = rv.error();
278 1215 return;
279 }
280 ec = error::header_too_large;
281 return;
282 }
283
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 707 times.
707 if(! rv)
284 {
285 ec = rv.error();
286 return;
287 }
288 707 ec = {};
289 }
290
291 void
292 1754 parser::
293 parse_fields(
294 error_code& ec)
295 {
296 1754 std::size_t const new_size =
297 1754 committed_ <= cfg_.max_header_size
298
1/2
✓ Branch 0 taken 1754 times.
✗ Branch 1 not taken.
1754 ? committed_
299 : cfg_.max_header_size;
300 field id;
301 1754 string_view v;
302 for(;;)
303 {
304 2351 auto const size0 = h_.size;
305 2351 auto got_one = detail::parse_field(
306 2351 h_, new_size, id, v, ec);
307
2/2
✓ Branch 2 taken 1047 times.
✓ Branch 3 taken 1304 times.
2351 if(ec == grammar::error::need_more)
308 {
309 1047 if(new_size >=
310
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1047 times.
1047 cfg_.max_header_size)
311 ec = error::header_too_large;
312 }
313
2/2
✓ Branch 0 taken 1754 times.
✓ Branch 1 taken 597 times.
2351 if(! got_one)
314 1754 return;
315
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 597 times.
597 if(h_.count > cfg_.max_field_count)
316 {
317 ec = error::too_many_fields;
318 return;
319 }
320 597 if(h_.size >
321
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 597 times.
597 cfg_.max_field_size + size0)
322 {
323 ec = error::field_too_large;
324 return;
325 }
326
4/5
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 31 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 533 times.
597 switch(id)
327 {
328 32 case field::connection:
329 case field::proxy_connection:
330
1/2
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
32 do_connection(v, ec);
331
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 32 times.
32 if(ec.failed())
332 return;
333 32 break;
334 31 case field::content_length:
335
1/2
✓ Branch 1 taken 31 times.
✗ Branch 2 not taken.
31 do_content_length(v, ec);
336
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
31 if(ec.failed())
337 return;
338 31 break;
339 1 case field::transfer_encoding:
340
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 do_transfer_encoding(v, ec);
341
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if(ec.failed())
342 return;
343 1 break;
344 case field::upgrade:
345 do_upgrade(v, ec);
346 if(ec.failed())
347 return;
348 break;
349 533 default:
350 533 break;
351 }
352 597 }
353 }
354
355 void
356 579 parser::
357 parse_body(
358 error_code& ec)
359 {
360 (void)ec;
361 579 return;
362 // VFALCO TODO
363 #if 0
364 BOOST_ASSERT(state_ == state::body);
365
366 if(h_.kind == detail::kind::request)
367 {
368 // https://tools.ietf.org/html/rfc7230#section-3.3
369 if(m_.skip_body)
370 return;
371 if(m_.content_len.has_value())
372 {
373 if(*m_.content_len > cfg_.body_too_large)
374 {
375 ec = error::body_too_large;
376 return;
377 }
378 if(*m_.content_len == 0)
379 return;
380 }
381 else if(m_.got_chunked)
382 {
383 // VFALCO TODO
384 return;
385 }
386 else
387 {
388 // Content-Length: 0
389 return;
390 }
391 }
392 else
393 {
394 BOOST_ASSERT(h_.kind ==
395 detail::kind::response);
396
397 // https://tools.ietf.org/html/rfc7230#section-3.3
398 if((h_.res.status_int / 100 == 1) || // 1xx e.g. Continue
399 h_.res.status_int == 204 || // No Content
400 h_.res.status_int == 304) // Not Modified
401 {
402 // Content-Length may be present, but we
403 // treat the message as not having a body.
404 }
405 else if(m_.content_len.has_value())
406 {
407 if(*m_.content_len > 0)
408 {
409 if(*m_.content_len > cfg_.body_too_large)
410 {
411 ec = error::body_too_large;
412 return;
413 }
414 }
415 }
416 else
417 {
418 // No Content-Length
419 return;
420 }
421 }
422
423 auto avail = committed_ - size_;
424 if(m_.content_len.has_value())
425 {
426 // known payload length
427 BOOST_ASSERT(! m_.got_chunked);
428 BOOST_ASSERT(m_.n_remain > 0);
429 BOOST_ASSERT(m_.content_len <
430 cfg_.body_too_large);
431 if(avail == 0)
432 {
433 if(! got_eof_)
434 {
435 ec = grammar::error::need_more;
436 return;
437 }
438 ec = error::need_more;
439 return;
440 }
441 if( avail > m_.n_remain)
442 avail = static_cast<
443 std::size_t>(m_.n_remain);
444 size_ += avail;
445 m_.payload_seen += avail;
446 m_.n_payload += avail;
447 m_.n_remain -= avail;
448 if(m_.n_remain > 0)
449 {
450 ec = {};
451 return;
452 }
453 state_ = state::complete;
454 ec = error::end_of_message;
455 return;
456 }
457
458 if(! m_.got_chunked)
459 {
460 // end of body indicated by EOF
461 if(avail > 0)
462 {
463 if(avail > (std::size_t(
464 -1) - m_.n_payload))
465 {
466 // overflow size_t
467 // VFALCO revisit this
468 ec = error::numeric_overflow;
469 return;
470 }
471 size_ += avail;
472 m_.n_payload += avail;
473 ec = {};
474 return;
475 }
476 if(! got_eof_)
477 {
478 ec = grammar::error::need_more;
479 return;
480 }
481 state_ = state::complete;
482 ec = error::end_of_message;
483 return;
484 }
485 #if 0
486 if(m_.payload_left == 0)
487 {
488 // start of chunk
489 bnf::chunk_part p;
490 auto it = p.parse(
491 h_.buf + size_,
492 h_.buf + (
493 committed_ - size_),
494 ec);
495 if(ec)
496 return;
497 auto const v =
498 p.value();
499 m_.chunk.size = v.size;
500 m_.chunk.ext = v.ext;
501 m_.chunk.trailer = v.trailer;
502 m_.chunk.fresh = true;
503 m_.payload_left =
504 v.size - v.data.size();
505 }
506 else
507 {
508 // continuation of chunk
509
510 }
511 #endif
512 #endif
513 }
514
515 void
516 parser::
517 parse_chunk(
518 error_code& ec)
519 {
520 (void)ec;
521 #if 0
522 switch(state_)
523 {
524 case state::start_line:
525 case state::header_fields:
526 parse_header(ec);
527 if(ec.failed())
528 return;
529 BOOST_ASSERT(state_ >
530 state::header_fields);
531 break;
532 case state::body:
533 if(! m_.got_chunked)
534 return parse_body(ec);
535 break;
536 case state::complete:
537 ec = error::end_of_message;
538 if(! got_eof_)
539 return;
540 state_ = state::end_of_stream;
541 return;
542 case state::end_of_stream:
543 ec = error::end_of_stream;
544 return;
545 }
546
547 auto const avail = committed_ - size_;
548 auto const start = h_.buf + size_;
549 if(m_.payload_left == 0)
550 {
551 // start of chunk
552 // VFALCO What about chunk_part_next?
553 BOOST_ASSERT(
554 size_ == m_.header_size);
555 bnf::chunk_part p;
556 auto it = p.parse(start,
557 h_.buf + (
558 committed_ - size_), ec);
559 BOOST_ASSERT(it == start);
560 if(ec)
561 return;
562 auto const v = p.value();
563 m_.chunk.size = v.size;
564 m_.chunk.ext = v.ext;
565 m_.chunk.fresh = true;
566 if(v.size > 0)
567 {
568 // chunk
569 m_.chunk.trailer = {};
570 m_.payload_left =
571 v.size - v.data.size();
572 size_ += it - start; // excludes CRLF
573 return;
574 }
575 // last-chunk
576 BOOST_ASSERT(
577 v.data.empty());
578 m_.chunk.trailer =
579 v.trailer;
580 m_.body = {};
581 size_ += it - start; // excludes CRLF
582 state_ = state::complete;
583 }
584 else
585 {
586 // continuation of chunk
587
588 }
589 #endif
590 }
591
592 //------------------------------------------------
593
594 // https://datatracker.ietf.org/doc/html/rfc7230#section-6.1
595 void
596 32 parser::
597 do_connection(
598 string_view s, error_code& ec)
599 {
600 (void)ec;
601 (void)s;
602 #if 0
603 for(auto v : bnf::range<bnf::connection>(s))
604 {
605 if(grammar::ci_is_equal(v, "close"))
606 {
607 m_.got_close = true;
608 }
609 else if(grammar::ci_is_equal(v, "keep-alive"))
610 {
611 m_.got_keep_alive = true;
612 }
613 else if(grammar::ci_is_equal(v, "upgrade"))
614 {
615 m_.got_upgrade = true;
616 }
617 }
618 #endif
619 32 }
620
621 void
622 31 parser::
623 do_content_length(
624 string_view s,
625 error_code& ec)
626 {
627 (void)ec;
628 (void)s;
629 #if 0
630 // https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2
631 // Content-Length can be a comma separated
632 // list, with all the same values, as well
633 // as having multiple Content-Length fields
634 // with the same number. We handle these here.
635 auto list = bnf::range<
636 bnf::list_of_one_or_more<
637 bnf::dec_number>>(s);
638 auto it = list.begin(ec);
639 if(ec.failed())
640 {
641 ec = error::bad_content_length;
642 return;
643 }
644 BOOST_ASSERT(it != list.end());
645 auto v = *it;
646 if(! m_.content_len.has_value())
647 {
648 if(v > cfg_.max_body_size)
649 {
650 ec = error::body_too_large;
651 return;
652 }
653 m_.content_len = v;
654 }
655 else if(*m_.content_len != v)
656 {
657 // differing values
658 ec = error::bad_content_length;
659 return;
660 }
661 for(;;)
662 {
663 it.increment(ec);
664 if(ec.failed())
665 {
666 ec = error::bad_content_length;
667 return;
668 }
669 if(it == list.end())
670 break;
671 v = *it;
672 if(m_.content_len != v)
673 {
674 // differing lengths
675 ec = error::bad_content_length;
676 return;
677 }
678 }
679 if(! m_.skip_body)
680 m_.n_remain = *m_.content_len;
681 #endif
682 31 }
683
684 void
685 1 parser::
686 do_transfer_encoding(
687 string_view s, error_code& ec)
688 {
689 (void)s;
690 (void)ec;
691 #if 0
692 using namespace detail::string_literals;
693
694 bnf::range<bnf::transfer_encoding> te(s);
695 auto const end = te.end();
696 auto it = te.begin(ec);
697 if(ec.failed())
698 {
699 // expected at least one encoding
700 ec = error::bad_transfer_encoding;
701 return;
702 }
703 BOOST_ASSERT(it != end);
704 // handle multiple
705 // Transfer-Encoding header lines
706 m_.got_chunked = false;
707 // get last encoding
708 for(;;)
709 {
710 auto prev = it++;
711 if(it == end)
712 {
713 if(grammar::ci_is_equal(
714 prev->name, "chunked"))
715 m_.got_chunked = true;
716 break;
717 }
718 }
719 #endif
720 1 }
721
722 void
723 parser::
724 do_upgrade(
725 string_view s, error_code& ec)
726 {
727 (void)s;
728 (void)ec;
729 }
730
731 } // http_proto
732 } // boost
733
734 #endif
735