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/serializer.hpp>
12 : #include <boost/http_proto/message_view_base.hpp>
13 : #include <boost/http_proto/detail/except.hpp>
14 : #include <boost/buffers/algorithm.hpp>
15 : #include <boost/buffers/buffer_copy.hpp>
16 : #include <boost/buffers/buffer_size.hpp>
17 : #include <boost/core/ignore_unused.hpp>
18 : #include <stddef.h>
19 :
20 : namespace boost {
21 : namespace http_proto {
22 :
23 : //------------------------------------------------
24 :
25 : void
26 0 : consume_buffers(
27 : buffers::const_buffer*& p,
28 : std::size_t& n,
29 : std::size_t bytes)
30 : {
31 0 : while(n > 0)
32 : {
33 0 : if(bytes < p->size())
34 : {
35 0 : *p += bytes;
36 0 : return;
37 : }
38 0 : bytes -= p->size();
39 0 : ++p;
40 0 : --n;
41 : }
42 :
43 : // Precondition violation
44 0 : if(bytes > 0)
45 0 : detail::throw_invalid_argument();
46 : }
47 :
48 : template<class MutableBuffers>
49 : void
50 32 : write_chunk_header(
51 : MutableBuffers const& dest0,
52 : std::size_t size) noexcept
53 : {
54 : static constexpr char hexdig[] =
55 : "0123456789ABCDEF";
56 : char buf[18];
57 32 : auto p = buf + 16;
58 544 : for(std::size_t i = 16; i--;)
59 : {
60 512 : *--p = hexdig[size & 0xf];
61 512 : size >>= 4;
62 : }
63 32 : buf[16] = '\r';
64 32 : buf[17] = '\n';
65 32 : auto n = buffers::buffer_copy(
66 : dest0,
67 : buffers::const_buffer(
68 : buf, sizeof(buf)));
69 : ignore_unused(n);
70 32 : BOOST_ASSERT(n == 18);
71 32 : BOOST_ASSERT(
72 : buffers::buffer_size(dest0) == n);
73 32 : }
74 :
75 : template<class DynamicBuffer>
76 : void
77 24 : write_chunk_close(DynamicBuffer& db)
78 : {
79 48 : db.commit(
80 24 : buffers::buffer_copy(
81 : db.prepare(2),
82 : buffers::const_buffer("\r\n", 2)));
83 24 : }
84 :
85 : template<class DynamicBuffer>
86 : void
87 2 : write_last_chunk(DynamicBuffer& db)
88 : {
89 4 : db.commit(
90 2 : buffers::buffer_copy(
91 : db.prepare(5),
92 : buffers::const_buffer("0\r\n\r\n", 5)));
93 2 : }
94 :
95 : //------------------------------------------------
96 :
97 19 : serializer::
98 19 : ~serializer()
99 : {
100 19 : }
101 :
102 9 : serializer::
103 9 : serializer()
104 9 : : serializer(65536)
105 : {
106 9 : }
107 :
108 : serializer::
109 : serializer(
110 : serializer&&) noexcept = default;
111 :
112 19 : serializer::
113 : serializer(
114 19 : std::size_t buffer_size)
115 19 : : ws_(buffer_size)
116 : {
117 19 : }
118 :
119 : void
120 0 : serializer::
121 : reset() noexcept
122 : {
123 0 : }
124 :
125 : //------------------------------------------------
126 :
127 : auto
128 78 : serializer::
129 : prepare() ->
130 : system::result<
131 : const_buffers_type>
132 : {
133 : // Precondition violation
134 78 : if(is_done_)
135 1 : detail::throw_logic_error();
136 :
137 : // Expect: 100-continue
138 77 : if(is_expect_continue_)
139 : {
140 4 : if(out_.data() == hp_)
141 2 : return const_buffers_type(hp_, 1);
142 2 : is_expect_continue_ = false;
143 2 : BOOST_HTTP_PROTO_RETURN_EC(
144 : error::expect_100_continue);
145 : }
146 :
147 73 : if(st_ == style::empty)
148 : {
149 9 : return const_buffers_type(
150 3 : out_.data(),
151 3 : out_.size());
152 : }
153 :
154 70 : if(st_ == style::buffers)
155 : {
156 9 : return const_buffers_type(
157 3 : out_.data(),
158 3 : out_.size());
159 : }
160 :
161 67 : if(st_ == style::source)
162 : {
163 22 : if(more_)
164 : {
165 17 : if(! is_chunked_)
166 : {
167 9 : auto rv = src_->read(
168 9 : tmp0_.prepare(tmp0_.capacity()));
169 9 : tmp0_.commit(rv.bytes);
170 9 : if(rv.ec.failed())
171 0 : return rv.ec;
172 9 : more_ = ! rv.finished;
173 : }
174 : else
175 : {
176 8 : if(tmp0_.capacity() > chunked_overhead_)
177 : {
178 : auto dest = tmp0_.prepare(
179 8 : tmp0_.capacity() -
180 : 2 - // CRLF
181 8 : 5); // final chunk
182 :
183 8 : auto rv = src_->read(
184 8 : buffers::sans_prefix(dest, 18));
185 :
186 8 : if(rv.ec.failed())
187 0 : return rv.ec;
188 :
189 8 : if(rv.bytes != 0)
190 : {
191 7 : write_chunk_header(
192 7 : buffers::prefix(dest, 18), rv.bytes);
193 7 : tmp0_.commit(rv.bytes + 18);
194 : // terminate chunk
195 7 : tmp0_.commit(
196 : buffers::buffer_copy(
197 7 : tmp0_.prepare(2),
198 14 : buffers::const_buffer(
199 : "\r\n", 2)));
200 : }
201 :
202 8 : if(rv.finished)
203 : {
204 2 : tmp0_.commit(
205 : buffers::buffer_copy(
206 2 : tmp0_.prepare(5),
207 2 : buffers::const_buffer(
208 : "0\r\n\r\n", 5)));
209 2 : more_ = false;
210 : }
211 : }
212 : }
213 : }
214 :
215 22 : std::size_t n = 0;
216 22 : if(out_.data() == hp_)
217 5 : ++n;
218 66 : for(buffers::const_buffer const& b : tmp0_.data())
219 44 : out_[n++] = b;
220 :
221 66 : return const_buffers_type(
222 22 : out_.data(),
223 22 : out_.size());
224 : }
225 :
226 45 : if(st_ == style::stream)
227 : {
228 45 : std::size_t n = 0;
229 45 : if(out_.data() == hp_)
230 5 : ++n;
231 45 : if(tmp0_.size() == 0 && more_)
232 : {
233 1 : BOOST_HTTP_PROTO_RETURN_EC(
234 : error::need_data);
235 : }
236 132 : for(buffers::const_buffer const& b : tmp0_.data())
237 88 : out_[n++] = b;
238 :
239 132 : return const_buffers_type(
240 44 : out_.data(),
241 44 : out_.size());
242 : }
243 :
244 : // should never get here
245 0 : detail::throw_logic_error();
246 : }
247 :
248 : void
249 352 : serializer::
250 : consume(
251 : std::size_t n)
252 : {
253 : // Precondition violation
254 352 : if(is_done_)
255 1 : detail::throw_logic_error();
256 :
257 351 : if(is_expect_continue_)
258 : {
259 : // Cannot consume more than
260 : // the header on 100-continue
261 3 : if(n > hp_->size())
262 1 : detail::throw_invalid_argument();
263 :
264 2 : out_.consume(n);
265 2 : return;
266 : }
267 348 : else if(out_.data() == hp_)
268 : {
269 : // consume header
270 22 : if(n < hp_->size())
271 : {
272 8 : out_.consume(n);
273 8 : return;
274 : }
275 14 : n -= hp_->size();
276 14 : out_.consume(hp_->size());
277 : }
278 :
279 340 : switch(st_)
280 : {
281 3 : default:
282 : case style::empty:
283 3 : out_.consume(n);
284 3 : if(out_.empty())
285 3 : is_done_ = true;
286 3 : return;
287 :
288 3 : case style::buffers:
289 3 : out_.consume(n);
290 3 : if(out_.empty())
291 3 : is_done_ = true;
292 3 : return;
293 :
294 334 : case style::source:
295 : case style::stream:
296 334 : tmp0_.consume(n);
297 384 : if( tmp0_.size() == 0 &&
298 50 : ! more_)
299 10 : is_done_ = true;
300 334 : return;
301 : }
302 : }
303 :
304 : //------------------------------------------------
305 :
306 : void
307 14 : serializer::
308 : copy(
309 : buffers::const_buffer* dest,
310 : buffers::const_buffer const* src,
311 : std::size_t n) noexcept
312 : {
313 14 : while(n--)
314 7 : *dest++ = *src++;
315 7 : }
316 :
317 : void
318 25 : serializer::
319 : start_init(
320 : message_view_base const& m)
321 : {
322 25 : ws_.clear();
323 :
324 : // VFALCO what do we do with
325 : // metadata error code failures?
326 : // m.ph_->md.maybe_throw();
327 :
328 25 : is_done_ = false;
329 :
330 25 : is_expect_continue_ =
331 25 : m.ph_->md.expect.is_100_continue;
332 :
333 : // Transfer-Encoding
334 : {
335 25 : auto const& te =
336 25 : m.ph_->md.transfer_encoding;
337 25 : is_chunked_ = te.is_chunked;
338 : }
339 25 : }
340 :
341 : void
342 4 : serializer::
343 : start_empty(
344 : message_view_base const& m)
345 : {
346 4 : start_init(m);
347 :
348 4 : st_ = style::empty;
349 :
350 4 : if(! is_chunked_)
351 : {
352 : out_ = make_array(
353 3 : 1); // header
354 : }
355 : else
356 : {
357 : out_ = make_array(
358 : 1 + // header
359 1 : 1); // final chunk
360 :
361 : // Buffer is too small
362 1 : if(ws_.size() < 5)
363 0 : detail::throw_length_error();
364 :
365 : buffers::mutable_buffer dest(
366 1 : ws_.data(), 5);
367 1 : buffers::buffer_copy(
368 : dest,
369 1 : buffers::const_buffer(
370 : "0\r\n\r\n", 5));
371 1 : out_[1] = dest;
372 : }
373 :
374 4 : hp_ = &out_[0];
375 4 : *hp_ = { m.ph_->cbuf, m.ph_->size };
376 4 : }
377 :
378 : void
379 7 : serializer::
380 : start_buffers(
381 : message_view_base const& m)
382 : {
383 7 : st_ = style::buffers;
384 :
385 7 : if(! is_chunked_)
386 : {
387 : //if(! cod_)
388 : {
389 : out_ = make_array(
390 : 1 + // header
391 6 : buf_.size()); // body
392 12 : copy(&out_[1],
393 6 : buf_.data(), buf_.size());
394 : }
395 : #if 0
396 : else
397 : {
398 : out_ = make_array(
399 : 1 + // header
400 : 2); // tmp1
401 : }
402 : #endif
403 : }
404 : else
405 : {
406 : //if(! cod_)
407 : {
408 : out_ = make_array(
409 : 1 + // header
410 : 1 + // chunk size
411 1 : buf_.size() + // body
412 1 : 1); // final chunk
413 2 : copy(&out_[2],
414 1 : buf_.data(), buf_.size());
415 :
416 : // Buffer is too small
417 1 : if(ws_.size() < 18 + 7)
418 0 : detail::throw_length_error();
419 1 : buffers::mutable_buffer s1(ws_.data(), 18);
420 1 : buffers::mutable_buffer s2(ws_.data(), 18 + 7);
421 1 : s2 += 18; // VFALCO HACK
422 1 : write_chunk_header(
423 : s1,
424 1 : buffers::buffer_size(buf_));
425 1 : buffers::buffer_copy(s2, buffers::const_buffer(
426 : "\r\n"
427 : "0\r\n"
428 : "\r\n", 7));
429 1 : out_[1] = s1;
430 1 : out_[out_.size() - 1] = s2;
431 : }
432 : #if 0
433 : else
434 : {
435 : out_ = make_array(
436 : 1 + // header
437 : 2); // tmp1
438 : }
439 : #endif
440 : }
441 :
442 7 : hp_ = &out_[0];
443 7 : *hp_ = { m.ph_->cbuf, m.ph_->size };
444 7 : }
445 :
446 : void
447 8 : serializer::
448 : start_source(
449 : message_view_base const& m,
450 : source* src)
451 : {
452 8 : st_ = style::source;
453 8 : src_ = src;
454 : out_ = make_array(
455 : 1 + // header
456 8 : 2); // tmp
457 : //if(! cod_)
458 : {
459 8 : tmp0_ = { ws_.data(), ws_.size() };
460 8 : if(tmp0_.capacity() <
461 : 18 + // chunk size
462 : 1 + // body (1 byte)
463 : 2 + // CRLF
464 : 5) // final chunk
465 0 : detail::throw_length_error();
466 : }
467 : #if 0
468 : else
469 : {
470 : buffers::buffered_base::allocator a(
471 : ws_.data(), ws_.size()/3, false);
472 : src->init(a);
473 : ws_.reserve(a.size_used());
474 :
475 : auto const n = ws_.size() / 2;
476 :
477 : tmp0_ = { ws_.data(), ws_.size() / 2 };
478 : ws_.reserve(n);
479 :
480 : // Buffer is too small
481 : if(ws_.size() < 1)
482 : detail::throw_length_error();
483 :
484 : tmp1_ = { ws_.data(), ws_.size() };
485 : }
486 : #endif
487 :
488 8 : hp_ = &out_[0];
489 8 : *hp_ = { m.ph_->cbuf, m.ph_->size };
490 8 : more_ = true;
491 8 : }
492 :
493 : auto
494 6 : serializer::
495 : start_stream(
496 : message_view_base const& m) ->
497 : stream
498 : {
499 6 : start_init(m);
500 :
501 6 : st_ = style::stream;
502 : out_ = make_array(
503 : 1 + // header
504 6 : 2); // tmp
505 : //if(! cod_)
506 : {
507 6 : tmp0_ = { ws_.data(), ws_.size() };
508 6 : if(tmp0_.capacity() <
509 : 18 + // chunk size
510 : 1 + // body (1 byte)
511 : 2 + // CRLF
512 : 5) // final chunk
513 0 : detail::throw_length_error();
514 : }
515 : #if 0
516 : else
517 : {
518 : auto const n = ws_.size() / 2;
519 : tmp0_ = { ws_.data(), n };
520 : ws_.reserve(n);
521 :
522 : // Buffer is too small
523 : if(ws_.size() < 1)
524 : detail::throw_length_error();
525 :
526 : tmp1_ = { ws_.data(), ws_.size() };
527 : }
528 : #endif
529 :
530 6 : hp_ = &out_[0];
531 6 : *hp_ = { m.ph_->cbuf, m.ph_->size };
532 :
533 6 : more_ = true;
534 :
535 6 : return stream{*this};
536 : }
537 :
538 : //------------------------------------------------
539 :
540 : std::size_t
541 0 : serializer::
542 : stream::
543 : capacity() const
544 : {
545 0 : auto const n =
546 : chunked_overhead_ +
547 : 2 + // CRLF
548 : 5; // final chunk
549 0 : return sr_->tmp0_.capacity() - n; // VFALCO ?
550 : }
551 :
552 : std::size_t
553 0 : serializer::
554 : stream::
555 : size() const
556 : {
557 0 : return sr_->tmp0_.size();
558 : }
559 :
560 : auto
561 47 : serializer::
562 : stream::
563 : prepare(
564 : std::size_t n) const ->
565 : buffers_type
566 : {
567 47 : if( sr_->is_chunked_ )
568 : return buffers::sans_prefix(
569 51 : sr_->tmp0_.prepare(n + chunk_header_len_),
570 25 : chunk_header_len_);
571 :
572 21 : return sr_->tmp0_.prepare(n);
573 : }
574 :
575 : void
576 46 : serializer::
577 : stream::
578 : commit(std::size_t n) const
579 : {
580 46 : if(! sr_->is_chunked_ )
581 : {
582 21 : sr_->tmp0_.commit(n);
583 : }
584 : else
585 : {
586 : // Zero sized chunks are not valid. Call close()
587 : // if the intent is to signal the end of the body.
588 25 : if( n == 0 )
589 1 : detail::throw_logic_error();
590 :
591 24 : auto m = n + chunk_header_len_;
592 24 : auto dest = sr_->tmp0_.prepare(m);
593 24 : write_chunk_header(
594 24 : buffers::prefix(dest, chunk_header_len_), n);
595 24 : sr_->tmp0_.commit(m);
596 24 : write_chunk_close(sr_->tmp0_);
597 : }
598 45 : }
599 :
600 : void
601 7 : serializer::
602 : stream::
603 : close() const
604 : {
605 : // Precondition violation
606 7 : if(! sr_->more_)
607 3 : detail::throw_logic_error();
608 :
609 4 : if (sr_->is_chunked_)
610 2 : write_last_chunk(sr_->tmp0_);
611 :
612 4 : sr_->more_ = false;
613 4 : }
614 :
615 : //------------------------------------------------
616 :
617 : } // http_proto
618 : } // boost
|