Line data Source code
1 : //
2 : // Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
3 : // Copyright (c) 2024 Christian Mazakas
4 : // Copyright (c) 2024 Mohammad Nejati
5 : //
6 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
7 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
8 : //
9 : // Official repository: https://github.com/cppalliance/http_proto
10 : //
11 :
12 : #include <boost/http_proto/detail/except.hpp>
13 : #include <boost/http_proto/message_view_base.hpp>
14 : #include <boost/http_proto/serializer.hpp>
15 : #include <boost/http_proto/service/zlib_service.hpp>
16 :
17 : #include "src/detail/filter.hpp"
18 :
19 : #include <boost/buffers/copy.hpp>
20 : #include <boost/buffers/prefix.hpp>
21 : #include <boost/buffers/sans_prefix.hpp>
22 : #include <boost/buffers/sans_suffix.hpp>
23 : #include <boost/buffers/suffix.hpp>
24 : #include <boost/buffers/size.hpp>
25 : #include <boost/core/ignore_unused.hpp>
26 :
27 : #include <stddef.h>
28 :
29 : namespace boost {
30 : namespace http_proto {
31 :
32 : namespace {
33 :
34 : class deflator_filter
35 : : public http_proto::detail::filter
36 : {
37 : zlib::stream& deflator_;
38 :
39 : public:
40 49 : deflator_filter(
41 : context& ctx,
42 : http_proto::detail::workspace& ws,
43 : bool use_gzip)
44 196 : : deflator_{ ctx.get_service<zlib::service>()
45 49 : .make_deflator(ws, -1, use_gzip ? 31 : 15, 8) }
46 : {
47 49 : }
48 :
49 : virtual filter::results
50 21237 : on_process(
51 : buffers::mutable_buffer out,
52 : buffers::const_buffer in,
53 : bool more) override
54 : {
55 21237 : auto flush =
56 21237 : more ? zlib::flush::none : zlib::flush::finish;
57 21237 : filter::results results;
58 :
59 : for(;;)
60 : {
61 38871 : auto params = zlib::params{in.data(), in.size(),
62 38871 : out.data(), out.size() };
63 38871 : auto ec = deflator_.write(params, flush);
64 :
65 38871 : results.in_bytes += in.size() - params.avail_in;
66 38871 : results.out_bytes += out.size() - params.avail_out;
67 :
68 47796 : if( ec.failed() &&
69 47796 : ec != zlib::error::buf_err)
70 : {
71 1 : results.ec = ec;
72 21237 : return results;
73 : }
74 :
75 38870 : if( ec == zlib::error::stream_end )
76 : {
77 48 : results.finished = true;
78 48 : return results;
79 : }
80 :
81 38822 : in = buffers::suffix(in, params.avail_in);
82 38822 : out = buffers::suffix(out, params.avail_out);
83 :
84 38822 : if( out.size() == 0 )
85 3524 : return results;
86 :
87 35298 : if( in.size() == 0 )
88 : {
89 35298 : if( results.out_bytes == 0 &&
90 : flush == zlib::flush::none )
91 : {
92 : // TODO: Is flush::block the right choice?
93 : // We might need a filter::flush() interface
94 : // so that the caller can decide when to flush.
95 17634 : flush = zlib::flush::block;
96 17634 : continue;
97 : }
98 17664 : return results;
99 : }
100 17634 : }
101 : }
102 : };
103 :
104 : //------------------------------------------------
105 :
106 : constexpr
107 : std::size_t
108 : crlf_len = 2;
109 :
110 : constexpr
111 : std::size_t
112 : chunk_header_len = 16 + crlf_len;
113 :
114 : constexpr
115 : std::size_t
116 : final_chunk_len = 1 + crlf_len + crlf_len;
117 :
118 : constexpr
119 : std::size_t
120 : chunked_overhead_ =
121 : chunk_header_len +
122 : crlf_len +
123 : final_chunk_len;
124 :
125 : template<class MutableBufferSequence>
126 : void
127 4567 : write_chunk_header(
128 : const MutableBufferSequence& mbs,
129 : std::size_t size) noexcept
130 : {
131 : static constexpr char hexdig[] =
132 : "0123456789ABCDEF";
133 : char buf[18];
134 4567 : auto p = buf + 16;
135 77639 : for(std::size_t i = 16; i--;)
136 : {
137 73072 : *--p = hexdig[size & 0xf];
138 73072 : size >>= 4;
139 : }
140 4567 : buf[16] = '\r';
141 4567 : buf[17] = '\n';
142 4567 : auto n = buffers::copy(
143 : mbs,
144 9134 : buffers::const_buffer(
145 : buf, sizeof(buf)));
146 : ignore_unused(n);
147 4567 : BOOST_ASSERT(n == 18);
148 4567 : }
149 :
150 : template<class MutableBufferSequence>
151 : void
152 4567 : write_crlf(
153 : const MutableBufferSequence& mbs) noexcept
154 : {
155 4567 : auto n = buffers::copy(
156 : mbs,
157 9134 : buffers::const_buffer(
158 : "\r\n", 2));
159 : ignore_unused(n);
160 4567 : BOOST_ASSERT(n == 2);
161 4567 : }
162 :
163 : template<class MutableBufferSequence>
164 : void
165 31 : write_final_chunk(
166 : const MutableBufferSequence& mbs) noexcept
167 : {
168 31 : auto n = buffers::copy(
169 : mbs,
170 62 : buffers::const_buffer(
171 : "0\r\n\r\n", 5));
172 : ignore_unused(n);
173 31 : BOOST_ASSERT(n == 5);
174 31 : }
175 :
176 : //------------------------------------------------
177 :
178 : class appender
179 : {
180 : buffers::circular_buffer& cb_;
181 : buffers::mutable_buffer_pair mbp_;
182 : std::size_t n_ = 0;
183 : bool is_chunked_ = false;
184 : bool more_input_ = true;
185 :
186 : public:
187 9071 : appender(
188 : buffers::circular_buffer& cb,
189 : bool is_chunked)
190 9071 : : cb_(cb)
191 9071 : , mbp_(cb.prepare(cb.capacity()))
192 9071 : , is_chunked_(is_chunked)
193 : {
194 9071 : }
195 :
196 : bool
197 21335 : is_full() const noexcept
198 : {
199 21335 : auto remaining = cb_.capacity() - n_;
200 21335 : if(is_chunked_)
201 10688 : return remaining <= chunked_overhead_;
202 :
203 10647 : return remaining == 0;
204 : }
205 :
206 : buffers::mutable_buffer_pair
207 12331 : prepare() noexcept
208 : {
209 12331 : if(is_chunked_)
210 : {
211 6168 : return buffers::sans_suffix(
212 6168 : buffers::sans_prefix(
213 6168 : mbp_,
214 6168 : chunk_header_len + n_)
215 6168 : , final_chunk_len + crlf_len);
216 : }
217 6163 : return buffers::sans_prefix(mbp_, n_);
218 : }
219 :
220 : void
221 12329 : commit(std::size_t n, bool more) noexcept
222 : {
223 12329 : BOOST_ASSERT(more_input_);
224 12329 : n_ += n;
225 12329 : more_input_ = more;
226 12329 : }
227 :
228 9071 : ~appender()
229 : {
230 9071 : if(is_chunked_)
231 : {
232 4552 : if(n_)
233 : {
234 4551 : write_chunk_header(mbp_, n_);
235 4551 : cb_.commit(n_ + chunk_header_len);
236 :
237 4551 : write_crlf(
238 4551 : cb_.prepare(crlf_len));
239 4551 : cb_.commit(crlf_len);
240 : }
241 :
242 4552 : if(!more_input_)
243 : {
244 26 : write_final_chunk(
245 26 : cb_.prepare(final_chunk_len));
246 26 : cb_.commit(final_chunk_len);
247 : }
248 : }
249 : else // is_chunked_ == false
250 : {
251 4519 : cb_.commit(n_);
252 : }
253 9071 : }
254 : };
255 :
256 : } // namespace
257 :
258 : //------------------------------------------------
259 :
260 45 : serializer::
261 : ~serializer()
262 : {
263 45 : }
264 :
265 0 : serializer::
266 : serializer(
267 : serializer&&) noexcept = default;
268 :
269 11 : serializer::
270 : serializer(
271 11 : context& ctx)
272 11 : : serializer(ctx, 65536)
273 : {
274 11 : }
275 :
276 45 : serializer::
277 : serializer(
278 : context& ctx,
279 45 : std::size_t buffer_size)
280 45 : : ctx_(ctx)
281 45 : , ws_(buffer_size)
282 : {
283 45 : }
284 :
285 : void
286 131 : serializer::
287 : reset() noexcept
288 : {
289 131 : filter_ = nullptr;
290 :
291 131 : cb0_ = {};
292 131 : tmp_ = {};
293 :
294 131 : more_input_ = false;
295 131 : is_done_ = false;
296 131 : is_header_done_ = false;
297 131 : is_chunked_ = false;
298 131 : needs_exp100_continue_ = false;
299 131 : filter_done_ = false;
300 :
301 131 : ws_.clear();
302 131 : }
303 :
304 : //------------------------------------------------
305 :
306 : auto
307 9138 : serializer::
308 : prepare() ->
309 : system::result<const_buffers_type>
310 : {
311 : // Precondition violation
312 9138 : if(is_done_)
313 1 : detail::throw_logic_error();
314 :
315 : // Expect: 100-continue
316 9137 : if(needs_exp100_continue_)
317 : {
318 4 : if(!is_header_done_)
319 4 : return const_buffers_type(
320 2 : prepped_.begin(),
321 2 : 1); // limit to header
322 :
323 2 : needs_exp100_continue_ = false;
324 :
325 2 : BOOST_HTTP_PROTO_RETURN_EC(
326 : error::expect_100_continue);
327 : }
328 :
329 9133 : if(!filter_)
330 : {
331 80 : switch(st_)
332 : {
333 3 : case style::empty:
334 6 : return const_buffers_type(
335 3 : prepped_.begin(),
336 6 : prepped_.size());
337 :
338 19 : case style::buffers:
339 : // add more buffers if prepped_ is half empty.
340 29 : if(more_input_ &&
341 10 : prepped_.capacity() >= prepped_.size())
342 : {
343 2 : prepped_.slide_to_front();
344 15 : while(prepped_.capacity() != 0)
345 : {
346 15 : auto buf = buf_gen_->operator()();
347 15 : if(buf.size() != 0)
348 : {
349 13 : prepped_.append(buf);
350 : }
351 : else // buf_gen_ is empty
352 : {
353 : // crlf and final chunk
354 2 : if(tmp_.size() != 0)
355 : {
356 1 : prepped_.append(tmp_);
357 1 : tmp_ = {};
358 : }
359 2 : break;
360 : }
361 : }
362 2 : if(buf_gen_->is_empty() && tmp_.size() == 0)
363 2 : more_input_ = false;
364 : }
365 38 : return const_buffers_type(
366 19 : prepped_.begin(),
367 38 : prepped_.size());
368 :
369 23 : case style::source:
370 : {
371 23 : if(!more_input_)
372 22 : break;
373 :
374 : // handles chunked payloads automatically
375 18 : appender apndr(cb0_, is_chunked_);
376 :
377 18 : if(apndr.is_full())
378 0 : break;
379 :
380 18 : auto rs = source_->read(
381 18 : apndr.prepare());
382 :
383 18 : if(rs.ec.failed())
384 : {
385 1 : is_done_ = true;
386 1 : BOOST_HTTP_PROTO_RETURN_EC(rs.ec);
387 : }
388 :
389 17 : more_input_ = !rs.finished;
390 17 : apndr.commit(rs.bytes, more_input_);
391 17 : break;
392 18 : }
393 :
394 35 : case style::stream:
395 35 : if(is_header_done_ && cb0_.size() == 0)
396 1 : BOOST_HTTP_PROTO_RETURN_EC(
397 : error::need_data);
398 34 : break;
399 : }
400 : }
401 : else // filter
402 : {
403 9053 : if(st_ == style::empty)
404 0 : return const_buffers_type(
405 0 : prepped_.begin(),
406 0 : prepped_.size());
407 :
408 17793 : auto get_input = [&]()
409 : {
410 17793 : if(st_ == style::buffers)
411 : {
412 : // TODO: for efficiency of deflator, we might
413 : // need to return multiple buffers at once
414 1329 : if(tmp_.size() == 0)
415 : {
416 385 : tmp_ = buf_gen_->operator()();
417 385 : more_input_ = !buf_gen_->is_empty();
418 : }
419 : return buffers::
420 1329 : const_buffer_pair{ tmp_, {} };
421 : }
422 :
423 16464 : BOOST_ASSERT(
424 : st_ == style::source ||
425 : st_ == style::stream);
426 :
427 38416 : if(st_ == style::source &&
428 21944 : more_input_ &&
429 5480 : cb1_.capacity() != 0)
430 : {
431 : // TODO: handle source error
432 5480 : auto rs = source_->read(
433 5480 : cb1_.prepare(cb1_.capacity()));
434 5480 : if(rs.finished)
435 16 : more_input_ = false;
436 5480 : cb1_.commit(rs.bytes);
437 : }
438 :
439 16464 : return cb1_.data();
440 9053 : };
441 :
442 12312 : auto consume = [&](std::size_t n)
443 : {
444 12312 : if(st_ == style::buffers)
445 : {
446 1328 : tmp_ = buffers::sans_prefix(
447 1328 : tmp_, n);
448 1328 : return;
449 : }
450 10984 : BOOST_ASSERT(
451 : st_ == style::source ||
452 : st_ == style::stream);
453 10984 : cb1_.consume(n);
454 9053 : };
455 :
456 : // handles chunked payloads automatically
457 9053 : appender apndr(cb0_, is_chunked_);
458 : for(;;)
459 : {
460 21317 : if(apndr.is_full())
461 3524 : break;
462 :
463 17793 : auto cbs = get_input();
464 :
465 17793 : if(more_input_ && buffers::size(cbs) == 0)
466 5480 : break;
467 :
468 12313 : auto rs = filter_->process(
469 0 : apndr.prepare(),
470 : cbs,
471 12313 : more_input_);
472 :
473 12313 : if(rs.ec.failed())
474 : {
475 1 : is_done_ = true;
476 1 : BOOST_HTTP_PROTO_RETURN_EC(rs.ec);
477 : }
478 :
479 12312 : consume(rs.in_bytes);
480 12312 : apndr.commit(rs.out_bytes, !rs.finished);
481 :
482 12312 : if(rs.finished)
483 : {
484 48 : filter_done_ = true;
485 48 : break;
486 : }
487 12264 : }
488 9053 : }
489 :
490 9108 : prepped_.reset(!is_header_done_);
491 9108 : const auto cbp = cb0_.data();
492 9108 : if(cbp[0].size() != 0)
493 9105 : prepped_.append(cbp[0]);
494 9108 : if(cbp[1].size() != 0)
495 14 : prepped_.append(cbp[1]);
496 :
497 9108 : BOOST_ASSERT(
498 : buffers::size(prepped_) > 0);
499 :
500 18216 : return const_buffers_type(
501 9108 : prepped_.begin(),
502 18216 : prepped_.size());
503 : }
504 :
505 : void
506 10871 : serializer::
507 : consume(
508 : std::size_t n)
509 : {
510 : // Precondition violation
511 10871 : if(is_done_ && n != 0)
512 1 : detail::throw_logic_error();
513 :
514 10870 : if(!is_header_done_)
515 : {
516 : const auto header_remain =
517 76 : prepped_[0].size();
518 76 : if(n < header_remain)
519 : {
520 11 : prepped_.consume(n);
521 11 : return;
522 : }
523 65 : n -= header_remain;
524 65 : prepped_.consume(header_remain);
525 65 : is_header_done_ = true;
526 : }
527 :
528 10859 : prepped_.consume(n);
529 :
530 : // no-op when cb0_ is not in use
531 10859 : cb0_.consume(n);
532 :
533 10859 : if(!prepped_.empty())
534 1760 : return;
535 :
536 9099 : if(needs_exp100_continue_)
537 1 : return;
538 :
539 9098 : if(more_input_)
540 8981 : return;
541 :
542 117 : if(filter_ && !filter_done_)
543 52 : return;
544 :
545 65 : is_done_ = true;
546 : }
547 :
548 : //------------------------------------------------
549 :
550 : detail::array_of_const_buffers
551 75 : serializer::
552 : make_array(std::size_t n)
553 : {
554 75 : if(n > std::numeric_limits<std::uint16_t>::max())
555 0 : detail::throw_length_error();
556 :
557 : return {
558 75 : ws_.push_array(n,
559 0 : buffers::const_buffer{}),
560 75 : static_cast<std::uint16_t>(n) };
561 : }
562 :
563 : void
564 75 : serializer::
565 : start_init(
566 : message_view_base const& m)
567 : {
568 75 : reset();
569 :
570 : // VFALCO what do we do with
571 : // metadata error code failures?
572 : // m.ph_->md.maybe_throw();
573 :
574 75 : auto const& md = m.metadata();
575 75 : needs_exp100_continue_ = md.expect.is_100_continue;
576 :
577 : // Transfer-Encoding
578 75 : is_chunked_ = md.transfer_encoding.is_chunked;
579 :
580 : // Content-Encoding
581 75 : auto const& ce = md.content_encoding;
582 75 : if(ce.encoding == encoding::deflate)
583 : {
584 24 : filter_ = &ws_.emplace<
585 24 : deflator_filter>(ctx_, ws_, false);
586 : }
587 51 : else if(ce.encoding == encoding::gzip)
588 : {
589 25 : filter_ = &ws_.emplace<
590 25 : deflator_filter>(ctx_, ws_, true);
591 : }
592 75 : }
593 :
594 : void
595 4 : serializer::
596 : start_empty(
597 : message_view_base const& m)
598 : {
599 : using mutable_buffer =
600 : buffers::mutable_buffer;
601 :
602 4 : start_init(m);
603 4 : st_ = style::empty;
604 :
605 4 : if(!is_chunked_)
606 : {
607 3 : prepped_ = make_array(
608 : 1); // header
609 : }
610 : else
611 : {
612 1 : prepped_ = make_array(
613 : 1 + // header
614 : 1); // final chunk
615 :
616 : mutable_buffer final_chunk = {
617 1 : ws_.reserve_front(
618 : final_chunk_len),
619 1 : final_chunk_len };
620 1 : write_final_chunk(final_chunk);
621 :
622 1 : prepped_[1] = final_chunk;
623 : }
624 :
625 4 : prepped_[0] = { m.ph_->cbuf, m.ph_->size };
626 4 : }
627 :
628 : void
629 24 : serializer::
630 : start_buffers(
631 : message_view_base const& m)
632 : {
633 : using mutable_buffer =
634 : buffers::mutable_buffer;
635 :
636 : // start_init() already called
637 24 : st_ = style::buffers;
638 :
639 24 : const auto buffers_max = (std::min)(
640 48 : std::size_t{ 16 },
641 24 : buf_gen_->count());
642 :
643 24 : if(!filter_)
644 : {
645 7 : if(!is_chunked_)
646 : {
647 : // no filter and no chunked
648 :
649 6 : prepped_ = make_array(
650 : 1 + // header
651 : buffers_max ); // buffers
652 :
653 6 : prepped_[0] = { m.ph_->cbuf, m.ph_->size };
654 12 : std::generate(
655 6 : prepped_.begin() + 1,
656 : prepped_.end(),
657 6 : std::ref(*buf_gen_));
658 6 : more_input_ = !buf_gen_->is_empty();
659 6 : return;
660 : }
661 :
662 : // no filter and chunked
663 :
664 1 : if(buf_gen_->is_empty())
665 : {
666 0 : prepped_ = make_array(
667 : 1 + // header
668 : 1); // final chunk
669 :
670 : mutable_buffer final_chunk = {
671 0 : ws_.reserve_front(
672 : final_chunk_len),
673 0 : final_chunk_len };
674 0 : write_final_chunk(
675 : final_chunk);
676 :
677 0 : prepped_[0] = { m.ph_->cbuf, m.ph_->size };
678 0 : prepped_[1] = final_chunk;
679 0 : return;
680 : }
681 :
682 : // Write entire buffers as a single chunk
683 : // since total size is known
684 :
685 : mutable_buffer chunk_header = {
686 1 : ws_.reserve_front(
687 : chunk_header_len),
688 1 : chunk_header_len };
689 :
690 1 : write_chunk_header(
691 : chunk_header,
692 1 : buf_gen_->size());
693 :
694 : mutable_buffer crlf_and_final_chunk = {
695 1 : ws_.reserve_front(
696 : crlf_len + final_chunk_len),
697 1 : crlf_len + final_chunk_len };
698 :
699 1 : write_crlf(
700 1 : buffers::prefix(
701 : crlf_and_final_chunk,
702 : crlf_len));
703 :
704 1 : write_final_chunk(
705 1 : buffers::sans_prefix(
706 : crlf_and_final_chunk,
707 : crlf_len));
708 :
709 1 : prepped_ = make_array(
710 : 1 + // header
711 : 1 + // chunk header
712 : buffers_max + // buffers
713 : 1); // buffer or (crlf and final chunk)
714 :
715 1 : prepped_[0] = { m.ph_->cbuf, m.ph_->size };
716 1 : prepped_[1] = chunk_header;
717 1 : std::generate(
718 1 : prepped_.begin() + 2,
719 1 : prepped_.end() - 1,
720 1 : std::ref(*buf_gen_));
721 :
722 1 : more_input_ = !buf_gen_->is_empty();
723 : // assigning the last slot
724 1 : if(more_input_)
725 : {
726 1 : prepped_[prepped_.size() - 1] =
727 2 : buf_gen_->operator()();
728 :
729 : // deferred until buf_gen_ is drained
730 1 : tmp_ = crlf_and_final_chunk;
731 : }
732 : else
733 : {
734 0 : prepped_[prepped_.size() - 1] =
735 : crlf_and_final_chunk;
736 : }
737 1 : return;
738 : }
739 :
740 : // filter
741 :
742 17 : prepped_ = make_array(
743 : 1 + // header
744 : 2); // circular buffer
745 :
746 17 : const auto n = ws_.size() - 1;
747 17 : cb0_ = { ws_.reserve_front(n), n };
748 :
749 17 : if(is_chunked_)
750 : {
751 8 : if(cb0_.capacity() <= chunked_overhead_)
752 0 : detail::throw_length_error();
753 : }
754 : else
755 : {
756 9 : if(cb0_.capacity() == 0)
757 0 : detail::throw_length_error();
758 : }
759 :
760 17 : prepped_[0] = { m.ph_->cbuf, m.ph_->size };
761 17 : more_input_ = !buf_gen_->is_empty();
762 : }
763 :
764 : void
765 25 : serializer::
766 : start_source(
767 : message_view_base const& m)
768 : {
769 : // start_init() already called
770 25 : st_ = style::source;
771 :
772 25 : prepped_ = make_array(
773 : 1 + // header
774 : 2); // circular buffer
775 :
776 25 : if(filter_)
777 : {
778 : // TODO: Optimize buffer distribution
779 16 : const auto n = (ws_.size() - 1) / 2;
780 16 : cb0_ = { ws_.reserve_front(n), n };
781 16 : cb1_ = { ws_.reserve_front(n), n };
782 : }
783 : else
784 : {
785 9 : const auto n = ws_.size() - 1;
786 9 : cb0_ = { ws_.reserve_front(n), n };
787 : }
788 :
789 25 : if(is_chunked_)
790 : {
791 10 : if(cb0_.capacity() <= chunked_overhead_)
792 0 : detail::throw_length_error();
793 : }
794 : else
795 : {
796 15 : if(cb0_.capacity() == 0)
797 0 : detail::throw_length_error();
798 : }
799 :
800 25 : prepped_[0] = { m.ph_->cbuf, m.ph_->size };
801 25 : more_input_ = true;
802 25 : }
803 :
804 : auto
805 22 : serializer::
806 : start_stream(
807 : message_view_base const& m) ->
808 : stream
809 : {
810 22 : start_init(m);
811 22 : st_ = style::stream;
812 :
813 22 : prepped_ = make_array(
814 : 1 + // header
815 : 2); // circular buffer
816 :
817 22 : if(filter_)
818 : {
819 : // TODO: Optimize buffer distribution
820 16 : const auto n = (ws_.size() - 1) / 2;
821 16 : cb0_ = { ws_.reserve_front(n), n };
822 16 : cb1_ = { ws_.reserve_front(n), n };
823 : }
824 : else
825 : {
826 6 : const auto n = ws_.size() - 1;
827 6 : cb0_ = { ws_.reserve_front(n), n };
828 : }
829 :
830 22 : if(is_chunked_)
831 : {
832 11 : if(cb0_.capacity() <= chunked_overhead_)
833 0 : detail::throw_length_error();
834 : }
835 : else
836 : {
837 11 : if(cb0_.capacity() == 0)
838 0 : detail::throw_length_error();
839 : }
840 :
841 22 : prepped_[0] = { m.ph_->cbuf, m.ph_->size };
842 22 : more_input_ = true;
843 22 : return stream{ *this };
844 : }
845 :
846 : //------------------------------------------------
847 :
848 : std::size_t
849 69 : serializer::
850 : stream::
851 : capacity() const noexcept
852 : {
853 69 : if(sr_->filter_)
854 0 : return sr_->cb1_.capacity();
855 :
856 69 : if(!sr_->is_chunked_)
857 34 : return sr_->cb0_.capacity();
858 :
859 : // chunked with no filter
860 35 : const auto cap = sr_->cb0_.capacity();
861 35 : if(cap > chunked_overhead_)
862 21 : return cap - chunked_overhead_;
863 :
864 14 : return 0;
865 : }
866 :
867 : bool
868 61 : serializer::
869 : stream::
870 : is_full() const noexcept
871 : {
872 61 : return capacity() == 0;
873 : }
874 :
875 : auto
876 5511 : serializer::
877 : stream::
878 : prepare() const ->
879 : buffers_type
880 : {
881 5511 : if(sr_->filter_)
882 5480 : return sr_->cb1_.prepare(
883 10960 : sr_->cb1_.capacity());
884 :
885 31 : if(!sr_->is_chunked_)
886 15 : return sr_->cb0_.prepare(
887 30 : sr_->cb0_.capacity());
888 :
889 : // chunked with no filter
890 16 : const auto cap = sr_->cb0_.capacity();
891 16 : if(cap <= chunked_overhead_)
892 0 : detail::throw_length_error();
893 :
894 16 : return buffers::sans_prefix(
895 32 : sr_->cb0_.prepare(
896 : cap - crlf_len - final_chunk_len),
897 16 : chunk_header_len);
898 : }
899 :
900 : void
901 5511 : serializer::
902 : stream::
903 : commit(std::size_t n) const
904 : {
905 5511 : if(sr_->filter_)
906 5480 : return sr_->cb1_.commit(n);
907 :
908 31 : if(!sr_->is_chunked_)
909 15 : return sr_->cb0_.commit(n);
910 :
911 : // chunked with no filter
912 16 : if(n != 0)
913 : {
914 15 : write_chunk_header(
915 15 : sr_->cb0_.prepare(
916 : chunk_header_len),
917 : n);
918 15 : sr_->cb0_.commit(
919 : chunk_header_len);
920 :
921 15 : sr_->cb0_.prepare(n);
922 15 : sr_->cb0_.commit(n);
923 :
924 15 : write_crlf(
925 15 : sr_->cb0_.prepare(crlf_len));
926 15 : sr_->cb0_.commit(crlf_len);
927 : }
928 : }
929 :
930 : void
931 25 : serializer::
932 : stream::
933 : close() const
934 : {
935 : // Precondition violation
936 25 : if(!sr_->more_input_)
937 4 : detail::throw_logic_error();
938 :
939 21 : sr_->more_input_ = false;
940 :
941 21 : if(sr_->filter_)
942 16 : return;
943 :
944 5 : if(!sr_->is_chunked_)
945 2 : return;
946 :
947 : // chunked with no filter
948 3 : write_final_chunk(
949 3 : sr_->cb0_.prepare(
950 : final_chunk_len));
951 3 : sr_->cb0_.commit(final_chunk_len);
952 : }
953 :
954 : //------------------------------------------------
955 :
956 : } // http_proto
957 : } // boost
|