src/parser.cpp

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