Line data Source code
1 : //
2 : // Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
3 : // Copyright (c) 2024 Christian Mazakas
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_proto
9 : //
10 :
11 : #include <boost/http_proto/detail/except.hpp>
12 : #include <boost/http_proto/message_view_base.hpp>
13 : #include <boost/http_proto/serializer.hpp>
14 : #include <boost/http_proto/service/zlib_service.hpp>
15 : #include <boost/buffers/algorithm.hpp>
16 : #include <boost/buffers/buffer_copy.hpp>
17 : #include <boost/buffers/buffer_size.hpp>
18 : #include <boost/core/ignore_unused.hpp>
19 : #include <stddef.h>
20 :
21 : #include "detail/filter.hpp"
22 : namespace boost {
23 : namespace http_proto {
24 :
25 : namespace {
26 : class deflator_filter
27 : : public http_proto::detail::filter
28 : {
29 : using stream_t = zlib::service::stream;
30 : stream_t& deflator_;
31 :
32 : public:
33 48 : deflator_filter(
34 : context& ctx,
35 : http_proto::detail::workspace& ws,
36 : bool use_gzip)
37 192 : : deflator_{ ctx.get_service<zlib::service>()
38 48 : .make_deflator(ws, -1, use_gzip ? 31 : 15, 8) }
39 : {
40 48 : }
41 :
42 : virtual filter::results
43 23756 : on_process(
44 : buffers::mutable_buffer out,
45 : buffers::const_buffer in,
46 : bool more) override
47 : {
48 23756 : auto flush =
49 23756 : more ? stream_t::flush::none : stream_t::flush::finish;
50 23756 : filter::results results;
51 :
52 : for(;;)
53 : {
54 36236 : auto r = deflator_.write(out, in, flush);
55 :
56 36236 : results.out_bytes += r.out_bytes;
57 36236 : results.in_bytes += r.in_bytes;
58 36236 : results.ec = r.ec;
59 36236 : results.finished = r.finished;
60 :
61 36236 : if(r.ec || r.finished)
62 23756 : return results;
63 :
64 23820 : out = buffers::sans_prefix(out, r.out_bytes);
65 23820 : in = buffers::sans_prefix(in, r.in_bytes);
66 :
67 23820 : if(in.size() == 0)
68 : {
69 22444 : if(r.out_bytes == 0)
70 : {
71 : // TODO: is this necessary?
72 11104 : flush = stream_t::flush::sync;
73 11104 : continue;
74 : }
75 11340 : return results;
76 : }
77 12480 : }
78 : }
79 : };
80 : } // namespace
81 :
82 : void
83 0 : consume_buffers(
84 : buffers::const_buffer*& p,
85 : std::size_t& n,
86 : std::size_t bytes)
87 : {
88 0 : while(n > 0)
89 : {
90 0 : if(bytes < p->size())
91 : {
92 0 : *p += bytes;
93 0 : return;
94 : }
95 0 : bytes -= p->size();
96 0 : ++p;
97 0 : --n;
98 : }
99 :
100 : // Precondition violation
101 0 : if(bytes > 0)
102 0 : detail::throw_invalid_argument();
103 : }
104 :
105 : template<class MutableBuffers>
106 : void
107 6312 : write_chunk_header(
108 : MutableBuffers const& dest0,
109 : std::size_t size) noexcept
110 : {
111 : static constexpr char hexdig[] =
112 : "0123456789ABCDEF";
113 : char buf[18];
114 6312 : auto p = buf + 16;
115 107304 : for(std::size_t i = 16; i--;)
116 : {
117 100992 : *--p = hexdig[size & 0xf];
118 100992 : size >>= 4;
119 : }
120 6312 : buf[16] = '\r';
121 6312 : buf[17] = '\n';
122 6312 : auto n = buffers::buffer_copy(
123 : dest0,
124 12624 : buffers::const_buffer(
125 : buf, sizeof(buf)));
126 : ignore_unused(n);
127 6312 : BOOST_ASSERT(n == 18);
128 6312 : BOOST_ASSERT(
129 : buffers::buffer_size(dest0) == n);
130 6312 : }
131 :
132 : template<class DynamicBuffer>
133 : void
134 : write_chunk_close(DynamicBuffer& db)
135 : {
136 : db.commit(
137 : buffers::buffer_copy(
138 : db.prepare(2),
139 : buffers::const_buffer("\r\n", 2)));
140 : }
141 :
142 : template<class DynamicBuffer>
143 : void
144 : write_last_chunk(DynamicBuffer& db)
145 : {
146 : db.commit(
147 : buffers::buffer_copy(
148 : db.prepare(5),
149 : buffers::const_buffer("0\r\n\r\n", 5)));
150 : }
151 :
152 : //------------------------------------------------
153 :
154 43 : serializer::
155 : ~serializer()
156 : {
157 43 : }
158 :
159 0 : serializer::
160 : serializer(
161 : serializer&&) noexcept = default;
162 :
163 9 : serializer::
164 : serializer(
165 9 : context& ctx)
166 9 : : serializer(ctx, 65536)
167 : {
168 9 : }
169 :
170 43 : serializer::
171 : serializer(
172 : context& ctx,
173 43 : std::size_t buffer_size)
174 43 : : ws_(buffer_size)
175 43 : , ctx_(ctx)
176 : {
177 43 : }
178 :
179 : void
180 56 : serializer::
181 : reset() noexcept
182 : {
183 56 : chunk_header_ = {};
184 56 : chunk_close_ = {};
185 56 : last_chunk_ = {};
186 56 : filter_ = nullptr;
187 56 : more_ = false;
188 56 : is_done_ = false;
189 56 : is_chunked_ = false;
190 56 : is_expect_continue_ = false;
191 56 : is_compressed_ = false;
192 56 : filter_done_ = false;
193 56 : in_ = nullptr;
194 56 : out_ = nullptr;
195 56 : ws_.clear();
196 56 : }
197 :
198 : //------------------------------------------------
199 :
200 : auto
201 12604 : serializer::
202 : prepare() ->
203 : system::result<
204 : const_buffers_type>
205 : {
206 : // Precondition violation
207 12604 : if( is_done_ )
208 1 : detail::throw_logic_error();
209 :
210 : // Expect: 100-continue
211 12603 : if( is_expect_continue_ )
212 : {
213 4 : if( !is_header_done_ )
214 2 : return const_buffers_type(hp_, 1);
215 2 : is_expect_continue_ = false;
216 2 : BOOST_HTTP_PROTO_RETURN_EC(
217 : error::expect_100_continue);
218 : }
219 :
220 12599 : if( st_ == style::empty )
221 9 : return const_buffers_type(
222 6 : prepped_.data(), prepped_.size());
223 :
224 12596 : if( st_ == style::buffers && !filter_ )
225 9 : return const_buffers_type(
226 6 : prepped_.data(), prepped_.size());
227 :
228 : // callers must consume() everything before invoking
229 : // prepare() again
230 12652 : if( !is_header_done_ &&
231 59 : buffers::buffer_size(prepped_) != prepped_[0].size() )
232 0 : detail::throw_logic_error();
233 :
234 25127 : if( is_header_done_ &&
235 12534 : buffers::buffer_size(prepped_) > 0 )
236 0 : detail::throw_logic_error();
237 :
238 12593 : auto& input = *in_;
239 12593 : auto& output = *out_;
240 12593 : if( st_ == style::source && more_ )
241 : {
242 5490 : auto results = src_->read(
243 5490 : input.prepare(input.capacity()));
244 5490 : more_ = !results.finished;
245 5490 : input.commit(results.bytes);
246 : }
247 :
248 30717 : if( st_ == style::stream &&
249 18103 : more_ &&
250 5510 : in_->size() == 0 )
251 1 : BOOST_HTTP_PROTO_RETURN_EC(error::need_data);
252 :
253 : bool has_avail_out =
254 25145 : ((!filter_ && (more_ || input.size() > 0)) ||
255 12553 : (filter_ && !filter_done_));
256 :
257 25312 : auto get_input = [&]() -> buffers::const_buffer
258 : {
259 25312 : if( st_ == style::buffers )
260 : {
261 3360 : if( buffers::buffer_size(buf_) == 0 )
262 56 : return {};
263 :
264 3304 : auto buf = *(buf_.data());
265 3304 : BOOST_ASSERT(buf.size() > 0);
266 3304 : return buf;
267 : }
268 : else
269 : {
270 21952 : if( input.size() == 0 )
271 10992 : return {};
272 :
273 10960 : auto cbs = input.data();
274 10960 : auto buf = *cbs.begin();
275 10960 : if( buf.size() == 0 )
276 : {
277 0 : auto p = cbs.begin();
278 0 : ++p;
279 0 : buf = *p;
280 : }
281 10960 : if( buf.size() == 0 )
282 0 : detail::throw_logic_error();
283 10960 : return buf;
284 : }
285 12592 : };
286 :
287 25312 : auto get_output = [&]() -> buffers::mutable_buffer
288 : {
289 25312 : auto mbs = output.prepare(output.capacity());
290 25312 : auto buf = *mbs.begin();
291 25312 : if( buf.size() == 0 )
292 : {
293 1556 : auto p = mbs.begin();
294 1556 : ++p;
295 1556 : buf = *p;
296 : }
297 25312 : return buf;
298 12592 : };
299 :
300 23756 : auto consume = [&](std::size_t n)
301 : {
302 23756 : if( st_ == style::buffers )
303 : {
304 1804 : buf_.consume(n);
305 1804 : if( buffers::buffer_size(buf_) == 0 )
306 60 : more_ = false;
307 : }
308 : else
309 21952 : input.consume(n);
310 36348 : };
311 :
312 12592 : std::size_t num_written = 0;
313 12592 : if( !filter_ )
314 44 : num_written += input.size();
315 : else
316 : {
317 : for(;;)
318 : {
319 25312 : auto in = get_input();
320 25312 : auto out = get_output();
321 25312 : if( out.size() == 0 )
322 : {
323 1556 : if( output.size() == 0 )
324 0 : detail::throw_logic_error();
325 12548 : break;
326 : }
327 :
328 23756 : auto rs = filter_->process(
329 23756 : out, in, more_);
330 :
331 23756 : if( rs.finished )
332 96 : filter_done_ = true;
333 :
334 23756 : consume(rs.in_bytes);
335 :
336 23756 : if( rs.out_bytes == 0 )
337 10992 : break;
338 :
339 12764 : num_written += rs.out_bytes;
340 12764 : output.commit(rs.out_bytes);
341 12764 : }
342 : }
343 :
344 : // end:
345 12592 : std::size_t n = 0;
346 12592 : if( !is_header_done_ )
347 : {
348 58 : BOOST_ASSERT(hp_ == &prepped_[0]);
349 58 : ++n;
350 : }
351 : else
352 12534 : prepped_.reset(prepped_.capacity());
353 :
354 12592 : if( !is_chunked_ )
355 : {
356 18834 : for(buffers::const_buffer const& b : output.data())
357 12556 : prepped_[n++] = b;
358 : }
359 : else
360 : {
361 6314 : if( has_avail_out )
362 : {
363 6311 : write_chunk_header(
364 6311 : chunk_header_, num_written);
365 6311 : prepped_[n++] = chunk_header_;
366 :
367 18933 : for(buffers::const_buffer const& b : output.data())
368 12622 : prepped_[n++] = b;
369 :
370 6311 : prepped_[n++] = chunk_close_;
371 : }
372 :
373 6314 : if( (filter_ && filter_done_) ||
374 6290 : (!filter_ && !more_) )
375 29 : prepped_[n++] = last_chunk_;
376 : }
377 :
378 : auto cbs = const_buffers_type(
379 12592 : prepped_.data(), prepped_.size());
380 :
381 12592 : BOOST_ASSERT(buffers::buffer_size(cbs) > 0);
382 12592 : return cbs;
383 : }
384 :
385 : void
386 14345 : serializer::
387 : consume(
388 : std::size_t n)
389 : {
390 : // Precondition violation
391 14345 : if( is_done_ )
392 1 : detail::throw_logic_error();
393 :
394 14344 : if( is_expect_continue_ )
395 : {
396 : // Cannot consume more than
397 : // the header on 100-continue
398 3 : if( n > hp_->size() )
399 1 : detail::throw_invalid_argument();
400 : }
401 :
402 14343 : if( !is_header_done_ )
403 : {
404 : // consume header
405 76 : if( n < hp_->size() )
406 : {
407 11 : prepped_.consume(n);
408 11 : return;
409 : }
410 65 : n -= hp_->size();
411 65 : prepped_.consume(hp_->size());
412 65 : is_header_done_ = true;
413 : }
414 :
415 14332 : prepped_.consume(n);
416 14332 : auto is_empty = (buffers::buffer_size(prepped_) == 0);
417 :
418 14332 : if( st_ == style::buffers && !filter_ && is_empty )
419 3 : more_ = false;
420 :
421 14332 : if( st_ == style::empty &&
422 4 : is_empty &&
423 4 : !is_expect_continue_ )
424 3 : more_ = false;
425 :
426 14332 : if( is_empty )
427 : {
428 12600 : if( out_ && out_->size() )
429 : {
430 12587 : BOOST_ASSERT(st_ != style::empty);
431 12587 : out_->consume(out_->size());
432 : }
433 12600 : is_done_ = filter_ ? filter_done_ : !more_;
434 : }
435 : }
436 :
437 : void
438 24 : serializer::
439 : use_deflate_encoding()
440 : {
441 : // can only apply one encoding
442 24 : if(filter_)
443 0 : detail::throw_logic_error();
444 :
445 24 : is_compressed_ = true;
446 24 : filter_ = &ws_.emplace<deflator_filter>(ctx_, ws_, false);
447 24 : }
448 :
449 : void
450 24 : serializer::
451 : use_gzip_encoding()
452 : {
453 : // can only apply one encoding
454 24 : if( filter_ )
455 0 : detail::throw_logic_error();
456 :
457 24 : is_compressed_ = true;
458 24 : filter_ = &ws_.emplace<deflator_filter>(ctx_, ws_, true);
459 24 : }
460 :
461 : //------------------------------------------------
462 :
463 : void
464 7 : serializer::
465 : copy(
466 : buffers::const_buffer* dest,
467 : buffers::const_buffer const* src,
468 : std::size_t n) noexcept
469 : {
470 14 : while(n--)
471 7 : *dest++ = *src++;
472 7 : }
473 :
474 : void
475 73 : serializer::
476 : start_init(
477 : message_view_base const& m)
478 : {
479 : // VFALCO what do we do with
480 : // metadata error code failures?
481 : // m.ph_->md.maybe_throw();
482 :
483 73 : auto const& md = m.metadata();
484 :
485 73 : is_done_ = false;
486 73 : is_header_done_ = false;
487 73 : is_expect_continue_ = md.expect.is_100_continue;
488 :
489 : // Transfer-Encoding
490 : {
491 73 : auto const& te = md.transfer_encoding;
492 73 : is_chunked_ = te.is_chunked;
493 : }
494 :
495 73 : if( is_chunked_)
496 : {
497 31 : auto* p = ws_.reserve_front(chunked_overhead_);
498 31 : chunk_header_ =
499 31 : buffers::mutable_buffer(p, chunk_header_len_);
500 31 : chunk_close_ =
501 62 : buffers::mutable_buffer(
502 31 : p + chunk_header_len_, crlf_len_);
503 31 : last_chunk_ =
504 62 : buffers::mutable_buffer(
505 31 : p + chunk_header_len_ + crlf_len_,
506 : last_chunk_len_);
507 :
508 31 : buffers::buffer_copy(
509 31 : chunk_close_, buffers::const_buffer("\r\n", 2));
510 31 : buffers::buffer_copy(
511 31 : last_chunk_,
512 62 : buffers::const_buffer("0\r\n\r\n", 5));
513 : }
514 73 : }
515 :
516 : void
517 4 : serializer::
518 : start_empty(
519 : message_view_base const& m)
520 : {
521 4 : start_init(m);
522 :
523 4 : st_ = style::empty;
524 4 : more_ = true;
525 :
526 4 : if(! is_chunked_)
527 : {
528 3 : prepped_ = make_array(
529 : 1); // header
530 : }
531 : else
532 : {
533 1 : prepped_ = make_array(
534 : 1 + // header
535 : 1); // final chunk
536 :
537 : // Buffer is too small
538 1 : if(ws_.size() < 5)
539 0 : detail::throw_length_error();
540 :
541 : buffers::mutable_buffer dest(
542 1 : ws_.data(), 5);
543 1 : buffers::buffer_copy(
544 : dest,
545 1 : buffers::const_buffer(
546 : "0\r\n\r\n", 5));
547 1 : prepped_[1] = dest;
548 : }
549 :
550 4 : hp_ = &prepped_[0];
551 4 : *hp_ = { m.ph_->cbuf, m.ph_->size };
552 4 : }
553 :
554 : void
555 23 : serializer::
556 : start_buffers(
557 : message_view_base const& m)
558 : {
559 23 : st_ = style::buffers;
560 23 : tmp1_ = {};
561 :
562 23 : if( !filter_ && !is_chunked_ )
563 : {
564 6 : prepped_ = make_array(
565 : 1 + // header
566 6 : buf_.size()); // user input
567 :
568 6 : hp_ = &prepped_[0];
569 6 : *hp_ = { m.ph_->cbuf, m.ph_->size };
570 :
571 6 : copy(&prepped_[1], buf_.data(), buf_.size());
572 :
573 6 : more_ = (buffers::buffer_size(buf_) > 0);
574 6 : return;
575 : }
576 :
577 17 : if( !filter_ && is_chunked_ )
578 : {
579 1 : if( buffers::buffer_size(buf_) == 0 )
580 : {
581 0 : prepped_ = make_array(
582 : 1 + // header
583 : 1); // last chunk
584 :
585 0 : hp_ = &prepped_[0];
586 0 : *hp_ = { m.ph_->cbuf, m.ph_->size };
587 0 : prepped_[1] = last_chunk_;
588 0 : more_ = false;
589 0 : return;
590 : }
591 :
592 2 : write_chunk_header(
593 1 : chunk_header_, buffers::buffer_size(buf_));
594 :
595 1 : prepped_ = make_array(
596 : 1 + // header
597 : 1 + // chunk header
598 1 : buf_.size() + // user input
599 : 1 + // chunk close
600 : 1); // last chunk
601 :
602 1 : hp_ = &prepped_[0];
603 1 : *hp_ = { m.ph_->cbuf, m.ph_->size };
604 1 : prepped_[1] = chunk_header_;
605 1 : copy(&prepped_[2], buf_.data(), buf_.size());
606 :
607 1 : prepped_[prepped_.size() - 2] = chunk_close_;
608 1 : prepped_[prepped_.size() - 1] = last_chunk_;
609 1 : more_ = true;
610 1 : return;
611 : }
612 :
613 16 : if( is_chunked_ )
614 : {
615 8 : prepped_ = make_array(
616 : 1 + // header
617 : 1 + // chunk header
618 : 2 + // tmp
619 : 1 + // chunk close
620 : 1); // last chunk
621 : }
622 : else
623 8 : prepped_ = make_array(
624 : 1 + // header
625 : 2); // tmp
626 :
627 16 : hp_ = &prepped_[0];
628 16 : *hp_ = { m.ph_->cbuf, m.ph_->size };
629 16 : tmp0_ = { ws_.data(), ws_.size() };
630 16 : out_ = &tmp0_;
631 16 : in_ = out_;
632 16 : more_ = true;
633 : }
634 :
635 : void
636 24 : serializer::
637 : start_source(
638 : message_view_base const& m,
639 : source* src)
640 : {
641 24 : st_ = style::source;
642 24 : src_ = src;
643 :
644 24 : if( is_chunked_ )
645 : {
646 10 : prepped_ = make_array(
647 : 1 + // header
648 : 1 + // chunk header
649 : 2 + // tmp
650 : 1 + // chunk close
651 : 1); // last chunk
652 : }
653 : else
654 14 : prepped_ = make_array(
655 : 1 + // header
656 : 2); // tmp
657 :
658 24 : if( !filter_ )
659 : {
660 8 : tmp0_ = { ws_.data(), ws_.size() };
661 8 : if( tmp0_.capacity() < 1 )
662 0 : detail::throw_length_error();
663 :
664 8 : in_ = &tmp0_;
665 8 : out_ = &tmp0_;
666 : }
667 : else
668 : {
669 16 : auto n = ws_.size() / 2;
670 16 : auto* p = ws_.reserve_front(n);
671 16 : tmp1_ = buffers::circular_buffer(p, n);
672 :
673 16 : tmp0_ = { ws_.data(), ws_.size() };
674 16 : if( tmp0_.capacity() < 1 )
675 0 : detail::throw_length_error();
676 :
677 16 : in_ = &tmp1_;
678 16 : out_ = &tmp0_;
679 : }
680 :
681 24 : hp_ = &prepped_[0];
682 24 : *hp_ = { m.ph_->cbuf, m.ph_->size };
683 24 : more_ = true;
684 24 : }
685 :
686 : auto
687 22 : serializer::
688 : start_stream(
689 : message_view_base const& m) ->
690 : stream
691 : {
692 22 : start_init(m);
693 :
694 22 : st_ = style::stream;
695 22 : if( is_chunked_ )
696 : {
697 11 : prepped_ = make_array(
698 : 1 + // header
699 : 1 + // chunk header
700 : 2 + // tmp
701 : 1 + // chunk close
702 : 1); // last chunk
703 : }
704 : else
705 11 : prepped_ = make_array(
706 : 1 + // header
707 : 2); // tmp
708 :
709 22 : if( !filter_ )
710 : {
711 6 : tmp0_ = { ws_.data(), ws_.size() };
712 6 : if( tmp0_.capacity() < 1 )
713 0 : detail::throw_length_error();
714 :
715 6 : in_ = &tmp0_;
716 6 : out_ = &tmp0_;
717 : }
718 : else
719 : {
720 16 : auto n = ws_.size() / 2;
721 16 : auto* p = ws_.reserve_front(n);
722 16 : tmp1_ = buffers::circular_buffer(p, n);
723 :
724 16 : tmp0_ = { ws_.data(), ws_.size() };
725 16 : if( tmp0_.capacity() < 1 )
726 0 : detail::throw_length_error();
727 :
728 16 : in_ = &tmp1_;
729 16 : out_ = &tmp0_;
730 : }
731 :
732 22 : hp_ = &prepped_[0];
733 22 : *hp_ = { m.ph_->cbuf, m.ph_->size };
734 22 : more_ = true;
735 22 : return stream{*this};
736 : }
737 :
738 : //------------------------------------------------
739 :
740 : std::size_t
741 139 : serializer::
742 : stream::
743 : capacity() const noexcept
744 : {
745 139 : return sr_->in_->capacity();
746 : }
747 :
748 : std::size_t
749 72 : serializer::
750 : stream::
751 : size() const noexcept
752 : {
753 72 : return sr_->in_->size();
754 : }
755 :
756 : bool
757 63 : serializer::
758 : stream::
759 : is_full() const noexcept
760 : {
761 63 : return capacity() == 0;
762 : }
763 :
764 : auto
765 5512 : serializer::
766 : stream::
767 : prepare() const ->
768 : buffers_type
769 : {
770 5512 : return sr_->in_->prepare(sr_->in_->capacity());
771 : }
772 :
773 : void
774 5512 : serializer::
775 : stream::
776 : commit(std::size_t n) const
777 : {
778 : // the stream must make a non-zero amount of bytes
779 : // available to the serializer
780 5512 : if( n == 0 )
781 1 : detail::throw_logic_error();
782 :
783 5511 : sr_->in_->commit(n);
784 5511 : }
785 :
786 : void
787 25 : serializer::
788 : stream::
789 : close() const
790 : {
791 : // Precondition violation
792 25 : if(! sr_->more_ )
793 4 : detail::throw_logic_error();
794 21 : sr_->more_ = false;
795 21 : }
796 :
797 : //------------------------------------------------
798 :
799 : } // http_proto
800 : } // boost
|