TLA Line data 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 : #include <boost/http/detail/except.hpp>
12 : #include <boost/http/detail/workspace.hpp>
13 : #include <boost/http/error.hpp>
14 : #include <boost/http/parser.hpp>
15 : #include <boost/http/static_request.hpp>
16 : #include <boost/http/static_response.hpp>
17 :
18 : #include <boost/assert.hpp>
19 : #include <boost/capy/buffers/circular_dynamic_buffer.hpp>
20 : #include <boost/capy/buffers/buffer_copy.hpp>
21 : #include <boost/capy/buffers/flat_dynamic_buffer.hpp>
22 : #include <boost/capy/buffers/front.hpp>
23 : #include <boost/capy/buffers/buffer_slice.hpp>
24 : #include <boost/capy/ex/system_context.hpp>
25 : #include <boost/http/brotli/decode.hpp>
26 : #include <boost/http/zlib/error.hpp>
27 : #include <boost/http/zlib/inflate.hpp>
28 : #include <boost/url/grammar/ci_string.hpp>
29 : #include <boost/url/grammar/error.hpp>
30 : #include <boost/url/grammar/hexdig_chars.hpp>
31 :
32 : #include "src/detail/brotli_filter_base.hpp"
33 : #include "src/detail/buffer_utils.hpp"
34 : #include "src/detail/zlib_filter_base.hpp"
35 :
36 : #include <array>
37 : #include <memory>
38 :
39 : namespace boost {
40 : namespace http {
41 :
42 : /*
43 : Principles for fixed-size buffer design
44 :
45 : axiom 1:
46 : To read data you must have a buffer.
47 :
48 : axiom 2:
49 : The size of the HTTP header is not
50 : known in advance.
51 :
52 : conclusion 3:
53 : A single I/O can produce a complete
54 : HTTP header and additional payload
55 : data.
56 :
57 : conclusion 4:
58 : A single I/O can produce multiple
59 : complete HTTP headers, complete
60 : payloads, and a partial header or
61 : payload.
62 :
63 : axiom 5:
64 : A process is in one of two states:
65 : 1. at or below capacity
66 : 2. above capacity
67 :
68 : axiom 6:
69 : A program which can allocate an
70 : unbounded number of resources can
71 : go above capacity.
72 :
73 : conclusion 7:
74 : A program can guarantee never going
75 : above capacity if all resources are
76 : provisioned at program startup.
77 :
78 : corollary 8:
79 : `parser` and `serializer` should each
80 : allocate a single buffer of calculated
81 : size, and never resize it.
82 :
83 : axiom #:
84 : A parser and a serializer are always
85 : used in pairs.
86 :
87 : Buffer Usage
88 :
89 : | | begin
90 : | H | p | | f | read headers
91 : | H | p | | T | f | set T body
92 : | H | p | | C | T | f | make codec C
93 : | H | p | b | C | T | f | decode p into b
94 : | H | p | b | C | T | f | read/parse loop
95 : | H | | T | f | destroy codec
96 : | H | | T | f | finished
97 :
98 : H headers
99 : C codec
100 : T body
101 : f table
102 : p partial payload
103 : b body data
104 :
105 : "payload" is the bytes coming in from
106 : the stream.
107 :
108 : "body" is the logical body, after transfer
109 : encoding is removed. This can be the
110 : same as the payload.
111 :
112 : A "plain payload" is when the payload and
113 : body are identical (no transfer encodings).
114 :
115 : A "buffered payload" is any payload which is
116 : not plain. A second buffer is required
117 : for reading.
118 :
119 : "overread" is additional data received past
120 : the end of the headers when reading headers,
121 : or additional data received past the end of
122 : the message payload.
123 : */
124 :
125 : namespace {
126 :
127 : // Construct a 2-element const_buffer pair representing the first
128 : // `n` bytes of `src`. Replaces the pre-#262 `capy::prefix(src, n)`
129 : // idiom which yielded a slice convertible to std::array.
130 : inline std::array<capy::const_buffer, 2>
131 HIT 41200 : prefix_pair(
132 : std::array<capy::const_buffer, 2> const& src,
133 : std::size_t n) noexcept
134 : {
135 41200 : std::array<capy::const_buffer, 2> result{};
136 41200 : if(n <= src[0].size())
137 : {
138 40701 : result[0] = capy::const_buffer(src[0].data(), n);
139 : }
140 : else
141 : {
142 499 : result[0] = src[0];
143 499 : std::size_t remaining = n - src[0].size();
144 499 : if(remaining > src[1].size())
145 MIS 0 : remaining = src[1].size();
146 HIT 499 : result[1] = capy::const_buffer(src[1].data(), remaining);
147 : }
148 41200 : return result;
149 : }
150 :
151 : class chained_sequence
152 : {
153 : char const* pos_;
154 : char const* end_;
155 : char const* begin_b_;
156 : char const* end_b_;
157 :
158 : public:
159 71617 : chained_sequence(std::array<capy::const_buffer, 2> const& cbp)
160 71617 : : pos_(static_cast<char const*>(cbp[0].data()))
161 71617 : , end_(pos_ + cbp[0].size())
162 71617 : , begin_b_(static_cast<char const*>(cbp[1].data()))
163 71617 : , end_b_(begin_b_ + cbp[1].size())
164 : {
165 71617 : }
166 :
167 : char const*
168 319930 : next() noexcept
169 : {
170 319930 : ++pos_;
171 : // most frequently taken branch
172 319930 : if(pos_ < end_)
173 297556 : return pos_;
174 :
175 : // bring the second range
176 22374 : if(begin_b_ != end_b_)
177 : {
178 MIS 0 : pos_ = begin_b_;
179 0 : end_ = end_b_;
180 0 : begin_b_ = end_b_;
181 0 : return pos_;
182 : }
183 :
184 : // undo the increament
185 HIT 22374 : pos_ = end_;
186 22374 : return nullptr;
187 : }
188 :
189 : bool
190 212674 : is_empty() const noexcept
191 : {
192 212674 : return pos_ == end_;
193 : }
194 :
195 : char
196 305475 : value() const noexcept
197 : {
198 305475 : return *pos_;
199 : }
200 :
201 : std::size_t
202 226936 : size() const noexcept
203 : {
204 226936 : return (end_ - pos_) + (end_b_ - begin_b_);
205 : }
206 : };
207 :
208 : std::uint64_t
209 66939 : parse_hex(
210 : chained_sequence& cs,
211 : system::error_code& ec) noexcept
212 : {
213 66939 : std::uint64_t v = 0;
214 66939 : std::size_t init_size = cs.size();
215 154117 : while(!cs.is_empty())
216 : {
217 134169 : auto n = grammar::hexdig_value(cs.value());
218 134169 : if(n < 0)
219 : {
220 46990 : if(init_size == cs.size())
221 : {
222 2 : ec = BOOST_HTTP_ERR(
223 : error::bad_payload);
224 1 : return 0;
225 : }
226 46989 : return v;
227 : }
228 :
229 : // at least 4 significant bits are free
230 87179 : if(v > (std::numeric_limits<std::uint64_t>::max)() >> 4)
231 : {
232 2 : ec = BOOST_HTTP_ERR(
233 : error::bad_payload);
234 1 : return 0;
235 : }
236 :
237 87178 : v = (v << 4) | static_cast<std::uint64_t>(n);
238 87178 : cs.next();
239 : }
240 39896 : ec = BOOST_HTTP_ERR(
241 : error::need_data);
242 19948 : return 0;
243 : }
244 :
245 : void
246 47341 : find_eol(
247 : chained_sequence& cs,
248 : system::error_code& ec) noexcept
249 : {
250 54030 : while(!cs.is_empty())
251 : {
252 53942 : if(cs.value() == '\r')
253 : {
254 47253 : if(!cs.next())
255 330 : break;
256 46923 : if(cs.value() != '\n')
257 : {
258 4 : ec = BOOST_HTTP_ERR(
259 : error::bad_payload);
260 2 : return;
261 : }
262 46921 : cs.next();
263 46921 : return;
264 : }
265 6689 : cs.next();
266 : }
267 836 : ec = BOOST_HTTP_ERR(
268 : error::need_data);
269 : }
270 :
271 : void
272 62239 : parse_eol(
273 : chained_sequence& cs,
274 : system::error_code& ec) noexcept
275 : {
276 62239 : if(cs.size() >= 2)
277 : {
278 : // we are sure size is at least 2
279 61807 : if(cs.value() == '\r' && *cs.next() == '\n')
280 : {
281 61804 : cs.next();
282 61804 : return;
283 : }
284 6 : ec = BOOST_HTTP_ERR(
285 : error::bad_payload);
286 3 : return;
287 : }
288 864 : ec = BOOST_HTTP_ERR(
289 : error::need_data);
290 : }
291 :
292 : void
293 4243 : skip_trailer_headers(
294 : chained_sequence& cs,
295 : system::error_code& ec) noexcept
296 : {
297 4527 : while(!cs.is_empty())
298 : {
299 4501 : if(cs.value() == '\r')
300 : {
301 4149 : if(!cs.next())
302 16 : break;
303 4133 : if(cs.value() != '\n')
304 : {
305 4 : ec = BOOST_HTTP_ERR(
306 : error::bad_payload);
307 2 : return;
308 : }
309 4131 : cs.next();
310 4131 : return;
311 : }
312 : // skip to the end of field
313 352 : find_eol(cs, ec);
314 352 : if(ec)
315 68 : return;
316 : }
317 84 : ec = BOOST_HTTP_ERR(
318 : error::need_data);
319 : }
320 :
321 : template<class UInt>
322 : std::size_t
323 190369 : clamp(
324 : UInt x,
325 : std::size_t limit = (std::numeric_limits<
326 : std::size_t>::max)()) noexcept
327 : {
328 190369 : if(x >= limit)
329 46135 : return limit;
330 144234 : return static_cast<std::size_t>(x);
331 : }
332 :
333 : class zlib_filter
334 : : public detail::zlib_filter_base
335 : {
336 : http::zlib::inflate_service& svc_;
337 :
338 : public:
339 MIS 0 : zlib_filter(
340 : http::zlib::inflate_service& svc,
341 : int window_bits)
342 0 : : svc_(svc)
343 : {
344 : system::error_code ec = static_cast<http::zlib::error>(
345 0 : svc_.init2(strm_, window_bits));
346 0 : if(ec != http::zlib::error::ok)
347 0 : detail::throw_system_error(ec);
348 0 : }
349 :
350 : private:
351 : virtual
352 : results
353 0 : do_process(
354 : capy::mutable_buffer out,
355 : capy::const_buffer in,
356 : bool more) noexcept override
357 : {
358 0 : strm_.next_out = static_cast<unsigned char*>(out.data());
359 0 : strm_.avail_out = saturate_cast(out.size());
360 0 : strm_.next_in = static_cast<unsigned char*>(const_cast<void *>(in.data()));
361 0 : strm_.avail_in = saturate_cast(in.size());
362 :
363 : auto rs = static_cast<http::zlib::error>(
364 0 : svc_.inflate(
365 0 : strm_,
366 : more ? http::zlib::no_flush : http::zlib::finish));
367 :
368 0 : results rv;
369 0 : rv.out_bytes = saturate_cast(out.size()) - strm_.avail_out;
370 0 : rv.in_bytes = saturate_cast(in.size()) - strm_.avail_in;
371 0 : rv.finished = (rs == http::zlib::error::stream_end);
372 :
373 0 : if(rs < http::zlib::error::ok && rs != http::zlib::error::buf_err)
374 0 : rv.ec = rs;
375 :
376 0 : return rv;
377 : }
378 : };
379 :
380 : class brotli_filter
381 : : public detail::brotli_filter_base
382 : {
383 : http::brotli::decode_service& svc_;
384 : http::brotli::decoder_state* state_;
385 :
386 : public:
387 0 : brotli_filter(http::brotli::decode_service& svc)
388 0 : : svc_(svc)
389 : {
390 0 : state_ = svc_.create_instance(nullptr, nullptr, nullptr);
391 0 : if(!state_)
392 0 : detail::throw_bad_alloc();
393 0 : }
394 :
395 0 : ~brotli_filter()
396 0 : {
397 0 : svc_.destroy_instance(state_);
398 0 : }
399 :
400 : private:
401 : virtual
402 : results
403 0 : do_process(
404 : capy::mutable_buffer out,
405 : capy::const_buffer in,
406 : bool more) noexcept override
407 : {
408 0 : auto* next_in = reinterpret_cast<const std::uint8_t*>(in.data());
409 0 : auto available_in = in.size();
410 0 : auto* next_out = reinterpret_cast<std::uint8_t*>(out.data());
411 0 : auto available_out = out.size();
412 :
413 0 : auto rs = svc_.decompress_stream(
414 : state_,
415 : &available_in,
416 : &next_in,
417 : &available_out,
418 : &next_out,
419 : nullptr);
420 :
421 0 : results rv;
422 0 : rv.in_bytes = in.size() - available_in;
423 0 : rv.out_bytes = out.size() - available_out;
424 0 : rv.finished = svc_.is_finished(state_);
425 :
426 0 : if(!more && rs == http::brotli::decoder_result::needs_more_input)
427 0 : rv.ec = BOOST_HTTP_ERR(error::bad_payload);
428 :
429 0 : if(rs == http::brotli::decoder_result::error)
430 0 : rv.ec = BOOST_HTTP_ERR(
431 : svc_.get_error_code(state_));
432 :
433 0 : return rv;
434 : }
435 : };
436 :
437 : } // namespace
438 :
439 : //------------------------------------------------
440 :
441 : class parser::impl
442 : {
443 : enum class state
444 : {
445 : reset,
446 : start,
447 : header,
448 : header_done,
449 : body,
450 : complete,
451 : };
452 :
453 : std::shared_ptr<parser_config_impl const> cfg_;
454 :
455 : detail::workspace ws_;
456 : static_request m_;
457 : std::uint64_t body_limit_;
458 : std::uint64_t body_total_;
459 : std::uint64_t payload_remain_;
460 : std::uint64_t chunk_remain_;
461 : std::size_t body_avail_;
462 : std::size_t nprepare_;
463 :
464 : capy::flat_dynamic_buffer fb_;
465 : capy::circular_dynamic_buffer cb0_;
466 : capy::circular_dynamic_buffer cb1_;
467 :
468 : std::array<capy::mutable_buffer, 2> mbp_;
469 : std::array<capy::const_buffer, 2> cbp_;
470 :
471 : std::unique_ptr<detail::filter> filter_;
472 :
473 : state state_;
474 : bool got_header_;
475 : bool got_eof_;
476 : bool head_response_;
477 : bool needs_chunk_close_;
478 : bool trailer_headers_;
479 : bool chunked_body_ended;
480 :
481 : public:
482 HIT 2067 : impl(std::shared_ptr<parser_config_impl const> cfg, detail::kind k)
483 2067 : : cfg_(std::move(cfg))
484 2067 : , ws_(cfg_->space_needed)
485 2067 : , m_(ws_.data(), ws_.size())
486 2067 : , state_(state::reset)
487 2067 : , got_header_(false)
488 : {
489 2067 : m_.h_ = detail::header(detail::empty{ k });
490 2067 : }
491 :
492 : bool
493 33237 : got_header() const noexcept
494 : {
495 33237 : return got_header_;
496 : }
497 :
498 : bool
499 58849 : is_complete() const noexcept
500 : {
501 58849 : return state_ == state::complete;
502 : }
503 :
504 : static_request const&
505 316 : safe_get_request() const
506 : {
507 : // headers must be received
508 316 : if(! got_header_)
509 MIS 0 : detail::throw_logic_error();
510 :
511 HIT 316 : return m_;
512 : }
513 :
514 : static_response const&
515 3 : safe_get_response() const
516 : {
517 : // headers must be received
518 3 : if(! got_header_)
519 MIS 0 : detail::throw_logic_error();
520 :
521 : // TODO: use a union
522 HIT 3 : return reinterpret_cast<static_response const&>(m_);
523 : }
524 :
525 : void
526 2614 : reset() noexcept
527 : {
528 2614 : ws_.clear();
529 2614 : state_ = state::start;
530 2614 : got_header_ = false;
531 2614 : got_eof_ = false;
532 2614 : }
533 :
534 : void
535 10543 : start(
536 : bool head_response)
537 : {
538 10543 : std::size_t leftover = 0;
539 10543 : switch(state_)
540 : {
541 1 : default:
542 : case state::reset:
543 : // reset must be called first
544 1 : detail::throw_logic_error();
545 :
546 2539 : case state::start:
547 : // reset required on eof
548 2539 : if(got_eof_)
549 MIS 0 : detail::throw_logic_error();
550 HIT 2539 : break;
551 :
552 3 : case state::header:
553 3 : if(fb_.size() == 0)
554 : {
555 : // start() called twice
556 2 : detail::throw_logic_error();
557 : }
558 : BOOST_FALLTHROUGH;
559 :
560 : case state::header_done:
561 : case state::body:
562 : // current message is incomplete
563 2 : detail::throw_logic_error();
564 :
565 7999 : case state::complete:
566 : {
567 : // remove available body.
568 7999 : if(is_plain())
569 4000 : cb0_.consume(body_avail_);
570 : // move leftovers to front
571 :
572 7999 : ws_.clear();
573 7999 : leftover = cb0_.size();
574 :
575 7999 : auto* dest = reinterpret_cast<char*>(ws_.data());
576 7999 : auto cbp = cb0_.data();
577 7999 : auto* a = static_cast<char const*>(cbp[0].data());
578 7999 : auto* b = static_cast<char const*>(cbp[1].data());
579 7999 : auto an = cbp[0].size();
580 7999 : auto bn = cbp[1].size();
581 :
582 7999 : if(bn == 0)
583 : {
584 7561 : std::memmove(dest, a, an);
585 : }
586 : else
587 : {
588 : // if `a` can fit between `dest` and `b`, shift `b` to the left
589 : // and copy `a` to its position. if `a` fits perfectly, the
590 : // shift will be of size 0.
591 : // if `a` requires more space, shift `b` to the right and
592 : // copy `a` to its position. this process may require multiple
593 : // iterations and should be done chunk by chunk to prevent `b`
594 : // from overlapping with `a`.
595 : do
596 : {
597 : // clamp right shifts to prevent overlap with `a`
598 438 : auto* bp = (std::min)(dest + an, const_cast<char*>(a) - bn);
599 438 : b = static_cast<char const*>(std::memmove(bp, b, bn));
600 :
601 : // a chunk or all of `a` based on available space
602 438 : auto chunk_a = static_cast<std::size_t>(b - dest);
603 438 : std::memcpy(dest, a, chunk_a); // never overlap
604 438 : an -= chunk_a;
605 438 : dest += chunk_a;
606 438 : a += chunk_a;
607 438 : } while(an);
608 : }
609 :
610 7999 : break;
611 : }
612 : }
613 :
614 10538 : ws_.clear();
615 :
616 21076 : fb_ = {
617 10538 : ws_.data(),
618 10538 : cfg_->headers.max_size + cfg_->min_buffer,
619 : leftover };
620 :
621 10538 : BOOST_ASSERT(
622 : fb_.capacity() == cfg_->max_overread() - leftover);
623 :
624 10538 : BOOST_ASSERT(
625 : head_response == false ||
626 : m_.h_.kind == detail::kind::response);
627 :
628 10538 : m_.h_ = detail::header(detail::empty{m_.h_.kind});
629 10538 : m_.h_.buf = reinterpret_cast<char*>(ws_.data());
630 10538 : m_.h_.cbuf = m_.h_.buf;
631 10538 : m_.h_.cap = ws_.size();
632 :
633 10538 : state_ = state::header;
634 :
635 : // reset to the configured default
636 10538 : body_limit_ = cfg_->body_limit;
637 :
638 10538 : body_total_ = 0;
639 10538 : payload_remain_ = 0;
640 10538 : chunk_remain_ = 0;
641 10538 : body_avail_ = 0;
642 10538 : nprepare_ = 0;
643 :
644 10538 : filter_.reset();
645 :
646 10538 : got_header_ = false;
647 10538 : head_response_ = head_response;
648 10538 : needs_chunk_close_ = false;
649 10538 : trailer_headers_ = false;
650 10538 : chunked_body_ended = false;
651 10538 : }
652 :
653 : auto
654 79053 : prepare() ->
655 : mutable_buffers_type
656 : {
657 79053 : nprepare_ = 0;
658 :
659 79053 : switch(state_)
660 : {
661 1 : default:
662 : case state::reset:
663 : // reset must be called first
664 1 : detail::throw_logic_error();
665 :
666 1 : case state::start:
667 : // start must be called first
668 1 : detail::throw_logic_error();
669 :
670 37174 : case state::header:
671 : {
672 37174 : BOOST_ASSERT(
673 : m_.h_.size < cfg_->headers.max_size);
674 37174 : std::size_t n = fb_.capacity();
675 37174 : BOOST_ASSERT(n <= cfg_->max_overread());
676 37174 : n = clamp(n, cfg_->max_prepare);
677 37174 : mbp_[0] = fb_.prepare(n);
678 37174 : nprepare_ = n;
679 37174 : return mutable_buffers_type(&mbp_[0], 1);
680 : }
681 :
682 MIS 0 : case state::header_done:
683 : // forgot to call parse()
684 0 : detail::throw_logic_error();
685 :
686 HIT 41876 : case state::body:
687 : {
688 41876 : if(got_eof_)
689 : {
690 : // forgot to call parse()
691 MIS 0 : detail::throw_logic_error();
692 : }
693 :
694 HIT 41876 : if(! is_plain())
695 : {
696 : // buffered payload
697 22017 : std::size_t n = cb0_.capacity();
698 22017 : n = clamp(n, cfg_->max_prepare);
699 22017 : nprepare_ = n;
700 22017 : mbp_ = cb0_.prepare(n);
701 22017 : return detail::make_span(mbp_);
702 : }
703 : else
704 : {
705 : // plain payload
706 19859 : std::size_t n = cb0_.capacity();
707 19859 : n = clamp(n, cfg_->max_prepare);
708 :
709 19859 : if(m_.payload() == payload::size)
710 : {
711 19845 : if(n > payload_remain_)
712 : {
713 18628 : std::size_t overread =
714 18628 : n - static_cast<std::size_t>(payload_remain_);
715 18628 : if(overread > cfg_->max_overread())
716 8712 : n = static_cast<std::size_t>(payload_remain_) +
717 8712 : cfg_->max_overread();
718 : }
719 : }
720 : else
721 : {
722 14 : BOOST_ASSERT(
723 : m_.payload() == payload::to_eof);
724 : // No more messages can be pipelined, so
725 : // limit the output buffer to the remaining
726 : // body limit plus one byte to detect
727 : // exhaustion.
728 14 : std::uint64_t r = body_limit_remain();
729 14 : if(r != std::uint64_t(-1))
730 14 : r += 1;
731 14 : n = clamp(r, n);
732 : }
733 :
734 19859 : nprepare_ = n;
735 19859 : mbp_ = cb0_.prepare(n);
736 19859 : return detail::make_span(mbp_);
737 : }
738 : }
739 :
740 1 : case state::complete:
741 : // already complete
742 1 : detail::throw_logic_error();
743 : }
744 : }
745 :
746 : void
747 78102 : commit(
748 : std::size_t n)
749 : {
750 78102 : switch(state_)
751 : {
752 1 : default:
753 : case state::reset:
754 : {
755 : // reset must be called first
756 1 : detail::throw_logic_error();
757 : }
758 :
759 1 : case state::start:
760 : {
761 : // forgot to call start()
762 1 : detail::throw_logic_error();
763 : }
764 :
765 36472 : case state::header:
766 : {
767 36472 : if(n > nprepare_)
768 : {
769 : // n can't be greater than size of
770 : // the buffers returned by prepare()
771 1 : detail::throw_invalid_argument();
772 : }
773 :
774 36471 : if(got_eof_)
775 : {
776 : // can't commit after EOF
777 1 : detail::throw_logic_error();
778 : }
779 :
780 36470 : nprepare_ = 0; // invalidate
781 36470 : fb_.commit(n);
782 36470 : break;
783 : }
784 :
785 MIS 0 : case state::header_done:
786 : {
787 : // forgot to call parse()
788 0 : detail::throw_logic_error();
789 : }
790 :
791 HIT 41628 : case state::body:
792 : {
793 41628 : if(n > nprepare_)
794 : {
795 : // n can't be greater than size of
796 : // the buffers returned by prepare()
797 2 : detail::throw_invalid_argument();
798 : }
799 :
800 41626 : if(got_eof_)
801 : {
802 : // can't commit after EOF
803 MIS 0 : detail::throw_logic_error();
804 : }
805 :
806 HIT 41626 : nprepare_ = 0; // invalidate
807 41626 : cb0_.commit(n);
808 41626 : break;
809 : }
810 :
811 MIS 0 : case state::complete:
812 : {
813 : // already complete
814 0 : detail::throw_logic_error();
815 : }
816 : }
817 HIT 78096 : }
818 :
819 : void
820 134 : commit_eof()
821 : {
822 134 : nprepare_ = 0; // invalidate
823 :
824 134 : switch(state_)
825 : {
826 1 : default:
827 : case state::reset:
828 : // reset must be called first
829 1 : detail::throw_logic_error();
830 :
831 1 : case state::start:
832 : // forgot to call start()
833 1 : detail::throw_logic_error();
834 :
835 14 : case state::header:
836 14 : got_eof_ = true;
837 14 : break;
838 :
839 MIS 0 : case state::header_done:
840 : // forgot to call parse()
841 0 : detail::throw_logic_error();
842 :
843 HIT 117 : case state::body:
844 117 : got_eof_ = true;
845 117 : break;
846 :
847 1 : case state::complete:
848 : // can't commit eof when complete
849 1 : detail::throw_logic_error();
850 : }
851 131 : }
852 :
853 : void
854 95877 : parse(
855 : system::error_code& ec)
856 : {
857 95877 : ec = {};
858 95877 : switch(state_)
859 : {
860 1 : default:
861 : case state::reset:
862 : // reset must be called first
863 1 : detail::throw_logic_error();
864 :
865 1 : case state::start:
866 : // start must be called first
867 1 : detail::throw_logic_error();
868 :
869 42347 : case state::header:
870 : {
871 42347 : BOOST_ASSERT(m_.h_.buf == static_cast<
872 : void const*>(ws_.data()));
873 42347 : BOOST_ASSERT(m_.h_.cbuf == static_cast<
874 : void const*>(ws_.data()));
875 :
876 42347 : m_.h_.parse(fb_.size(), cfg_->headers, ec);
877 :
878 42347 : if(ec == condition::need_more_input)
879 : {
880 32531 : if(! got_eof_)
881 : {
882 : // headers incomplete
883 32520 : return;
884 : }
885 :
886 11 : if(fb_.size() == 0)
887 : {
888 : // stream closed cleanly
889 6 : state_ = state::reset;
890 12 : ec = BOOST_HTTP_ERR(
891 : error::end_of_stream);
892 6 : return;
893 : }
894 :
895 : // stream closed with a
896 : // partial message received
897 5 : state_ = state::reset;
898 10 : ec = BOOST_HTTP_ERR(
899 : error::incomplete);
900 5 : return;
901 : }
902 9816 : else if(ec)
903 : {
904 : // other error,
905 : //
906 : // VFALCO map this to a bad
907 : // request or bad response error?
908 : //
909 259 : state_ = state::reset; // unrecoverable
910 259 : return;
911 : }
912 :
913 9557 : got_header_ = true;
914 :
915 : // reserve headers + table
916 9557 : ws_.reserve_front(m_.h_.size);
917 9557 : ws_.reserve_back(m_.h_.table_space());
918 :
919 : // no payload
920 18306 : if(m_.payload() == payload::none ||
921 8749 : head_response_)
922 : {
923 : // octets of the next message
924 808 : auto overread = fb_.size() - m_.h_.size;
925 808 : cb0_ = { ws_.data(), overread, overread };
926 808 : ws_.reserve_front(overread);
927 808 : state_ = state::complete;
928 808 : return;
929 : }
930 :
931 8749 : state_ = state::header_done;
932 8749 : break;
933 : }
934 :
935 8746 : case state::header_done:
936 : {
937 : // metadata error
938 8746 : if(m_.payload() == payload::error)
939 : {
940 : // VFALCO This needs looking at
941 120 : ec = BOOST_HTTP_ERR(
942 : error::bad_payload);
943 60 : state_ = state::reset; // unrecoverable
944 60 : return;
945 : }
946 :
947 : // overread currently includes any and all octets that
948 : // extend beyond the current end of the header
949 : // this can include associated body octets for the
950 : // current message or octets of the next message in the
951 : // stream, e.g. pipelining is being used
952 8686 : auto const overread = fb_.size() - m_.h_.size;
953 8686 : BOOST_ASSERT(overread <= cfg_->max_overread());
954 :
955 8686 : auto cap = fb_.capacity() + overread +
956 8686 : cfg_->min_buffer;
957 :
958 : // reserve body buffers first, as the decoder
959 : // must be installed after them.
960 8686 : auto const p = ws_.reserve_front(cap);
961 :
962 : // Content-Encoding
963 8686 : switch(m_.metadata().content_encoding.coding)
964 : {
965 MIS 0 : case content_coding::deflate:
966 0 : if(!cfg_->apply_deflate_decoder)
967 0 : goto no_filter;
968 0 : if(auto* svc = capy::get_system_context().find_service<http::zlib::inflate_service>())
969 : {
970 0 : filter_.reset(new zlib_filter(
971 : *svc,
972 0 : cfg_->zlib_window_bits));
973 : }
974 0 : break;
975 :
976 0 : case content_coding::gzip:
977 0 : if(!cfg_->apply_gzip_decoder)
978 0 : goto no_filter;
979 0 : if(auto* svc = capy::get_system_context().find_service<http::zlib::inflate_service>())
980 : {
981 0 : filter_.reset(new zlib_filter(
982 : *svc,
983 0 : cfg_->zlib_window_bits + 16));
984 : }
985 0 : break;
986 :
987 0 : case content_coding::br:
988 0 : if(!cfg_->apply_brotli_decoder)
989 0 : goto no_filter;
990 0 : if(auto* svc = capy::get_system_context().find_service<http::brotli::decode_service>())
991 : {
992 0 : filter_.reset(new brotli_filter(*svc));
993 : }
994 0 : break;
995 :
996 0 : no_filter:
997 HIT 8686 : default:
998 8686 : break;
999 : }
1000 :
1001 8686 : if(is_plain())
1002 : {
1003 4357 : cb0_ = { p, cap, overread };
1004 4357 : cb1_ = {};
1005 : }
1006 : else
1007 : {
1008 : // buffered payload
1009 4329 : std::size_t n0 = (overread > cfg_->min_buffer)
1010 8658 : ? overread
1011 4329 : : cfg_->min_buffer;
1012 4329 : std::size_t n1 = cfg_->min_buffer;
1013 :
1014 4329 : cb0_ = { p , n0, overread };
1015 4329 : cb1_ = { p + n0 , n1 };
1016 : }
1017 :
1018 8686 : if(m_.payload() == payload::size)
1019 : {
1020 8480 : if(!filter_ &&
1021 4240 : body_limit_ < m_.payload_size())
1022 : {
1023 6 : ec = BOOST_HTTP_ERR(
1024 : error::body_too_large);
1025 3 : state_ = state::reset;
1026 3 : return;
1027 : }
1028 4237 : payload_remain_ = m_.payload_size();
1029 : }
1030 :
1031 8683 : state_ = state::body;
1032 : BOOST_FALLTHROUGH;
1033 : }
1034 :
1035 51252 : case state::body:
1036 : {
1037 51252 : BOOST_ASSERT(state_ == state::body);
1038 51252 : BOOST_ASSERT(m_.payload() != payload::none);
1039 51252 : BOOST_ASSERT(m_.payload() != payload::error);
1040 :
1041 8362 : auto set_state_to_complete = [&]()
1042 : {
1043 8362 : state_ = state::complete;
1044 59614 : };
1045 :
1046 51252 : if(m_.payload() == payload::chunked)
1047 : {
1048 : for(;;)
1049 : {
1050 78651 : if(chunk_remain_ == 0
1051 75748 : && !chunked_body_ended)
1052 : {
1053 71617 : auto cs = chained_sequence(cb0_.data());
1054 20849 : auto check_ec = [&]()
1055 : {
1056 20849 : if(ec == condition::need_more_input && got_eof_)
1057 : {
1058 MIS 0 : ec = BOOST_HTTP_ERR(error::incomplete);
1059 0 : state_ = state::reset;
1060 : }
1061 HIT 92466 : };
1062 :
1063 71617 : if(needs_chunk_close_)
1064 : {
1065 62239 : parse_eol(cs, ec);
1066 62239 : if(ec)
1067 : {
1068 435 : check_ec();
1069 20849 : return;
1070 : }
1071 : }
1072 9378 : else if(trailer_headers_)
1073 : {
1074 4243 : skip_trailer_headers(cs, ec);
1075 4243 : if(ec)
1076 : {
1077 112 : check_ec();
1078 112 : return;
1079 : }
1080 4131 : cb0_.consume(cb0_.size() - cs.size());
1081 4131 : chunked_body_ended = true;
1082 8276 : continue;
1083 : }
1084 :
1085 66939 : auto chunk_size = parse_hex(cs, ec);
1086 66939 : if(ec)
1087 : {
1088 19950 : check_ec();
1089 19950 : return;
1090 : }
1091 :
1092 : // skip chunk extensions
1093 46989 : find_eol(cs, ec);
1094 46989 : if(ec)
1095 : {
1096 352 : check_ec();
1097 352 : return;
1098 : }
1099 :
1100 46637 : cb0_.consume(cb0_.size() - cs.size());
1101 46637 : chunk_remain_ = chunk_size;
1102 :
1103 46637 : needs_chunk_close_ = true;
1104 46637 : if(chunk_remain_ == 0)
1105 : {
1106 4145 : needs_chunk_close_ = false;
1107 4145 : trailer_headers_ = true;
1108 4145 : continue;
1109 : }
1110 : }
1111 :
1112 49526 : if(cb0_.size() == 0 && !chunked_body_ended)
1113 : {
1114 1830 : if(got_eof_)
1115 : {
1116 2 : ec = BOOST_HTTP_ERR(
1117 : error::incomplete);
1118 1 : state_ = state::reset;
1119 1 : return;
1120 : }
1121 :
1122 3658 : ec = BOOST_HTTP_ERR(
1123 : error::need_data);
1124 1829 : return;
1125 : }
1126 :
1127 47696 : if(filter_)
1128 : {
1129 MIS 0 : chunk_remain_ -= apply_filter(
1130 : ec,
1131 : clamp(chunk_remain_, cb0_.size()),
1132 0 : !chunked_body_ended);
1133 :
1134 0 : if(ec || chunked_body_ended)
1135 0 : return;
1136 : }
1137 : else
1138 : {
1139 : const std::size_t chunk_avail =
1140 HIT 47696 : clamp(chunk_remain_, cb0_.size());
1141 47696 : auto cb0_data = cb0_.data();
1142 47696 : auto chunk = capy::buffer_slice(
1143 : cb0_data, 0, chunk_avail);
1144 :
1145 47696 : if(body_limit_remain() < chunk_avail)
1146 : {
1147 MIS 0 : ec = BOOST_HTTP_ERR(
1148 : error::body_too_large);
1149 0 : state_ = state::reset;
1150 HIT 4131 : return;
1151 : }
1152 :
1153 : // in_place style
1154 47696 : auto copied = capy::buffer_copy(
1155 47696 : cb1_.prepare(cb1_.capacity()),
1156 47696 : chunk.data());
1157 47696 : chunk_remain_ -= copied;
1158 47696 : body_avail_ += copied;
1159 47696 : body_total_ += copied;
1160 47696 : cb0_.consume(copied);
1161 47696 : cb1_.commit(copied);
1162 47696 : if(cb1_.capacity() == 0
1163 47696 : && !chunked_body_ended)
1164 : {
1165 MIS 0 : ec = BOOST_HTTP_ERR(
1166 : error::in_place_overflow);
1167 0 : return;
1168 : }
1169 :
1170 HIT 47696 : if(chunked_body_ended)
1171 : {
1172 4131 : set_state_to_complete();
1173 4131 : return;
1174 : }
1175 : }
1176 51841 : }
1177 : }
1178 : else
1179 : {
1180 : // non-chunked payload
1181 :
1182 73326 : const std::size_t payload_avail = [&]()
1183 : {
1184 24442 : auto ret = cb0_.size();
1185 24442 : if(!filter_)
1186 24442 : ret -= body_avail_;
1187 24442 : if(m_.payload() == payload::size)
1188 24185 : return clamp(payload_remain_, ret);
1189 : // payload::eof
1190 257 : return ret;
1191 24442 : }();
1192 :
1193 73326 : const bool is_complete = [&]()
1194 : {
1195 24442 : if(m_.payload() == payload::size)
1196 24185 : return payload_avail == payload_remain_;
1197 : // payload::eof
1198 257 : return got_eof_;
1199 24442 : }();
1200 :
1201 24442 : if(filter_)
1202 : {
1203 MIS 0 : payload_remain_ -= apply_filter(
1204 0 : ec, payload_avail, !is_complete);
1205 0 : if(ec || is_complete)
1206 0 : return;
1207 : }
1208 : else
1209 : {
1210 : // plain body
1211 :
1212 HIT 24442 : if(m_.payload() == payload::to_eof)
1213 : {
1214 257 : if(body_limit_remain() < payload_avail)
1215 : {
1216 2 : ec = BOOST_HTTP_ERR(
1217 : error::body_too_large);
1218 1 : state_ = state::reset;
1219 1 : return;
1220 : }
1221 : }
1222 :
1223 : // in_place style
1224 24441 : payload_remain_ -= payload_avail;
1225 24441 : body_avail_ += payload_avail;
1226 24441 : body_total_ += payload_avail;
1227 24441 : if(cb0_.capacity() == 0 && !is_complete)
1228 : {
1229 14 : ec = BOOST_HTTP_ERR(
1230 : error::in_place_overflow);
1231 7 : return;
1232 : }
1233 :
1234 24434 : if(is_complete)
1235 : {
1236 4231 : set_state_to_complete();
1237 4231 : return;
1238 : }
1239 : }
1240 :
1241 20203 : if(m_.payload() == payload::size && got_eof_)
1242 : {
1243 2 : ec = BOOST_HTTP_ERR(
1244 : error::incomplete);
1245 1 : state_ = state::reset;
1246 1 : return;
1247 : }
1248 :
1249 40404 : ec = BOOST_HTTP_ERR(
1250 : error::need_data);
1251 20202 : return;
1252 : }
1253 :
1254 : break;
1255 : }
1256 :
1257 2213 : case state::complete:
1258 2213 : break;
1259 : }
1260 : }
1261 :
1262 : auto
1263 41202 : pull_body() ->
1264 : const_buffers_type
1265 : {
1266 41202 : switch(state_)
1267 : {
1268 MIS 0 : case state::header_done:
1269 0 : return {};
1270 HIT 41200 : case state::body:
1271 : case state::complete:
1272 41200 : cbp_ = prefix_pair(
1273 41200 : (is_plain() ? cb0_ : cb1_).data(),
1274 : body_avail_);
1275 41200 : return detail::make_span(cbp_);
1276 2 : case state::reset:
1277 2 : if(got_header_)
1278 2 : return {};
1279 : BOOST_FALLTHROUGH;
1280 : default:
1281 MIS 0 : detail::throw_logic_error();
1282 : }
1283 : }
1284 :
1285 : void
1286 HIT 39424 : consume_body(std::size_t n)
1287 : {
1288 39424 : switch(state_)
1289 : {
1290 MIS 0 : case state::header_done:
1291 0 : return;
1292 HIT 39424 : case state::body:
1293 : case state::complete:
1294 39424 : n = clamp(n, body_avail_);
1295 39424 : (is_plain() ? cb0_ : cb1_).consume(n);
1296 39424 : body_avail_ -= n;
1297 39424 : return;
1298 MIS 0 : case state::reset:
1299 0 : if(got_header_)
1300 0 : return;
1301 : BOOST_FALLTHROUGH;
1302 : default:
1303 0 : detail::throw_logic_error();
1304 : }
1305 : }
1306 :
1307 : core::string_view
1308 HIT 712 : body() const
1309 : {
1310 : // Precondition violation
1311 712 : if(state_ != state::complete)
1312 MIS 0 : detail::throw_logic_error();
1313 :
1314 : // Precondition violation
1315 HIT 712 : if(body_avail_ != body_total_)
1316 MIS 0 : detail::throw_logic_error();
1317 :
1318 HIT 712 : auto cbp = (is_plain() ? cb0_ : cb1_).data();
1319 712 : BOOST_ASSERT(body_avail_ <= cbp[0].size());
1320 712 : return core::string_view(
1321 712 : static_cast<char const*>(cbp[0].data()),
1322 1424 : body_avail_);
1323 : }
1324 :
1325 : bool
1326 9 : has_buffered_data() const noexcept
1327 : {
1328 9 : if(state_ != state::complete)
1329 1 : return false;
1330 :
1331 8 : if(is_plain())
1332 6 : return cb0_.size() > body_avail_;
1333 2 : return cb0_.size() > 0;
1334 : }
1335 :
1336 : void
1337 5 : set_body_limit(std::uint64_t n)
1338 : {
1339 5 : switch(state_)
1340 : {
1341 1 : case state::header:
1342 : case state::header_done:
1343 1 : body_limit_ = n;
1344 1 : break;
1345 2 : case state::complete:
1346 : // only allowed for empty bodies
1347 2 : if(body_total_ == 0)
1348 1 : break;
1349 : BOOST_FALLTHROUGH;
1350 : default:
1351 : // set body_limit before parsing the body
1352 3 : detail::throw_logic_error();
1353 : }
1354 2 : }
1355 :
1356 : private:
1357 : bool
1358 139905 : is_plain() const noexcept
1359 : {
1360 279810 : return ! filter_ &&
1361 279810 : m_.payload() != payload::chunked;
1362 : }
1363 :
1364 : std::uint64_t
1365 47967 : body_limit_remain() const noexcept
1366 : {
1367 47967 : return body_limit_ - body_total_;
1368 : }
1369 :
1370 : std::size_t
1371 MIS 0 : apply_filter(
1372 : system::error_code& ec,
1373 : std::size_t payload_avail,
1374 : bool more)
1375 : {
1376 0 : std::size_t p0 = payload_avail;
1377 : for(;;)
1378 : {
1379 0 : if(payload_avail == 0 && more)
1380 0 : break;
1381 :
1382 0 : auto f_rs = [&](){
1383 0 : BOOST_ASSERT(filter_ != nullptr);
1384 0 : std::size_t n = clamp(body_limit_remain());
1385 0 : n = clamp(n, cb1_.capacity());
1386 :
1387 0 : return filter_->process(
1388 0 : detail::make_span(cb1_.prepare(n)),
1389 0 : prefix_pair(cb0_.data(), payload_avail),
1390 0 : more);
1391 0 : }();
1392 :
1393 0 : cb0_.consume(f_rs.in_bytes);
1394 0 : payload_avail -= f_rs.in_bytes;
1395 0 : body_total_ += f_rs.out_bytes;
1396 :
1397 : // in_place style
1398 0 : cb1_.commit(f_rs.out_bytes);
1399 0 : body_avail_ += f_rs.out_bytes;
1400 0 : if(cb1_.capacity() == 0 &&
1401 0 : !f_rs.finished && f_rs.in_bytes == 0)
1402 : {
1403 0 : ec = BOOST_HTTP_ERR(
1404 : error::in_place_overflow);
1405 0 : goto done;
1406 : }
1407 :
1408 0 : if(f_rs.ec)
1409 : {
1410 0 : ec = f_rs.ec;
1411 0 : state_ = state::reset;
1412 0 : break;
1413 : }
1414 :
1415 0 : if(body_limit_remain() == 0 &&
1416 0 : !f_rs.finished && f_rs.in_bytes == 0)
1417 : {
1418 0 : ec = BOOST_HTTP_ERR(
1419 : error::body_too_large);
1420 0 : state_ = state::reset;
1421 0 : break;
1422 : }
1423 :
1424 0 : if(f_rs.finished)
1425 : {
1426 0 : if(!more)
1427 0 : state_ = state::complete;
1428 0 : break;
1429 : }
1430 0 : }
1431 :
1432 0 : done:
1433 0 : return p0 - payload_avail;
1434 : }
1435 : };
1436 :
1437 : //------------------------------------------------
1438 : //
1439 : // Special Members
1440 : //
1441 : //------------------------------------------------
1442 :
1443 HIT 2082 : parser::
1444 : ~parser()
1445 : {
1446 2082 : delete impl_;
1447 2082 : }
1448 :
1449 12 : parser::
1450 12 : parser() noexcept
1451 12 : : impl_(nullptr)
1452 : {
1453 12 : }
1454 :
1455 3 : parser::
1456 3 : parser(parser&& other) noexcept
1457 3 : : impl_(other.impl_)
1458 : {
1459 3 : other.impl_ = nullptr;
1460 3 : }
1461 :
1462 2067 : parser::
1463 : parser(
1464 : std::shared_ptr<parser_config_impl const> cfg,
1465 2067 : detail::kind k)
1466 2067 : : impl_(new impl(std::move(cfg), k))
1467 : {
1468 : // TODO: use a single allocation for
1469 : // impl and workspace buffer.
1470 2067 : }
1471 :
1472 : void
1473 4 : parser::
1474 : assign(parser&& other) noexcept
1475 : {
1476 4 : if(this == &other)
1477 MIS 0 : return;
1478 HIT 4 : delete impl_;
1479 4 : impl_ = other.impl_;
1480 4 : other.impl_ = nullptr;
1481 : }
1482 :
1483 : //--------------------------------------------
1484 : //
1485 : // Observers
1486 : //
1487 : //--------------------------------------------
1488 :
1489 : bool
1490 33237 : parser::got_header() const noexcept
1491 : {
1492 33237 : BOOST_ASSERT(impl_);
1493 33237 : return impl_->got_header();
1494 : }
1495 :
1496 : bool
1497 58849 : parser::is_complete() const noexcept
1498 : {
1499 58849 : BOOST_ASSERT(impl_);
1500 58849 : return impl_->is_complete();
1501 : }
1502 :
1503 : //------------------------------------------------
1504 : //
1505 : // Modifiers
1506 : //
1507 : //------------------------------------------------
1508 :
1509 : void
1510 2614 : parser::
1511 : reset() noexcept
1512 : {
1513 2614 : BOOST_ASSERT(impl_);
1514 2614 : impl_->reset();
1515 2614 : }
1516 :
1517 : void
1518 10543 : parser::start()
1519 : {
1520 10543 : BOOST_ASSERT(impl_);
1521 10543 : impl_->start(false);
1522 10538 : }
1523 :
1524 : auto
1525 79053 : parser::
1526 : prepare() ->
1527 : mutable_buffers_type
1528 : {
1529 79053 : BOOST_ASSERT(impl_);
1530 79053 : return impl_->prepare();
1531 : }
1532 :
1533 : void
1534 78102 : parser::
1535 : commit(
1536 : std::size_t n)
1537 : {
1538 78102 : BOOST_ASSERT(impl_);
1539 78102 : impl_->commit(n);
1540 78096 : }
1541 :
1542 : void
1543 134 : parser::
1544 : commit_eof()
1545 : {
1546 134 : BOOST_ASSERT(impl_);
1547 134 : impl_->commit_eof();
1548 131 : }
1549 :
1550 : void
1551 95877 : parser::
1552 : parse(
1553 : system::error_code& ec)
1554 : {
1555 95877 : BOOST_ASSERT(impl_);
1556 95877 : impl_->parse(ec);
1557 95875 : }
1558 :
1559 : auto
1560 41202 : parser::
1561 : pull_body() ->
1562 : const_buffers_type
1563 : {
1564 41202 : BOOST_ASSERT(impl_);
1565 41202 : return impl_->pull_body();
1566 : }
1567 :
1568 : void
1569 39424 : parser::
1570 : consume_body(std::size_t n)
1571 : {
1572 39424 : BOOST_ASSERT(impl_);
1573 39424 : impl_->consume_body(n);
1574 39424 : }
1575 :
1576 : core::string_view
1577 712 : parser::
1578 : body() const
1579 : {
1580 712 : BOOST_ASSERT(impl_);
1581 712 : return impl_->body();
1582 : }
1583 :
1584 : core::string_view
1585 MIS 0 : parser::
1586 : release_buffered_data() noexcept
1587 : {
1588 : // TODO
1589 0 : return {};
1590 : }
1591 :
1592 : bool
1593 HIT 9 : parser::
1594 : has_buffered_data() const noexcept
1595 : {
1596 9 : BOOST_ASSERT(impl_);
1597 9 : return impl_->has_buffered_data();
1598 : }
1599 :
1600 : void
1601 5 : parser::
1602 : set_body_limit(std::uint64_t n)
1603 : {
1604 5 : BOOST_ASSERT(impl_);
1605 5 : impl_->set_body_limit(n);
1606 2 : }
1607 :
1608 : //------------------------------------------------
1609 : //
1610 : // Implementation
1611 : //
1612 : //------------------------------------------------
1613 :
1614 : void
1615 MIS 0 : parser::
1616 : start_impl(bool head_response)
1617 : {
1618 0 : BOOST_ASSERT(impl_);
1619 0 : impl_->start(head_response);
1620 0 : }
1621 :
1622 : static_request const&
1623 HIT 316 : parser::
1624 : safe_get_request() const
1625 : {
1626 316 : BOOST_ASSERT(impl_);
1627 316 : return impl_->safe_get_request();
1628 : }
1629 :
1630 : static_response const&
1631 3 : parser::
1632 : safe_get_response() const
1633 : {
1634 3 : BOOST_ASSERT(impl_);
1635 3 : return impl_->safe_get_response();
1636 : }
1637 :
1638 : } // http
1639 : } // boost
|