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