LCOV - code coverage report
Current view: top level - libs/http_proto/src - serializer.cpp (source / functions) Hit Total Coverage
Test: coverage_filtered.info Lines: 227 246 92.3 %
Date: 2024-04-24 06:37:24 Functions: 21 23 91.3 %

          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          27 : 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          27 :     auto p = buf + 16;
      58         459 :     for(std::size_t i = 16; i--;)
      59             :     {
      60         432 :         *--p = hexdig[size & 0xf];
      61         432 :         size >>= 4;
      62             :     }
      63          27 :     buf[16] = '\r';
      64          27 :     buf[17] = '\n';
      65          27 :     auto n = buffers::buffer_copy(
      66             :         dest0,
      67             :         buffers::const_buffer(
      68             :             buf, sizeof(buf)));
      69             :     ignore_unused(n);
      70          27 :     BOOST_ASSERT(n == 18);
      71          27 :     BOOST_ASSERT(
      72             :         buffers::buffer_size(dest0) == n);
      73          27 : }
      74             : 
      75             : template<class DynamicBuffer>
      76             : void
      77          19 : write_chunk_close(DynamicBuffer& db)
      78             : {
      79          38 :     db.commit(
      80          19 :         buffers::buffer_copy(
      81             :             db.prepare(2),
      82             :             buffers::const_buffer("\r\n", 2)));
      83          19 : }
      84             : 
      85             : template<class DynamicBuffer>
      86             : void
      87           3 : write_last_chunk(DynamicBuffer& db)
      88             : {
      89           6 :     db.commit(
      90           3 :         buffers::buffer_copy(
      91             :             db.prepare(5),
      92             :             buffers::const_buffer("0\r\n\r\n", 5)));
      93           3 : }
      94             : 
      95             : //------------------------------------------------
      96             : 
      97          20 : serializer::
      98          20 : ~serializer()
      99             : {
     100          20 : }
     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          20 : serializer::
     113             : serializer(
     114          20 :     std::size_t buffer_size)
     115          20 :     : ws_(buffer_size)
     116             : {
     117          20 : }
     118             : 
     119             : void
     120           0 : serializer::
     121             : reset() noexcept
     122             : {
     123           0 : }
     124             : 
     125             : //------------------------------------------------
     126             : 
     127             : auto
     128          67 : serializer::
     129             : prepare() ->
     130             :     system::result<
     131             :         const_buffers_type>
     132             : {
     133             :     // Precondition violation
     134          67 :     if(is_done_)
     135           1 :         detail::throw_logic_error();
     136             : 
     137             :     // Expect: 100-continue
     138          66 :     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          62 :     if(st_ == style::empty)
     148             :     {
     149           9 :         return const_buffers_type(
     150           3 :             out_.data(),
     151           3 :             out_.size());
     152             :     }
     153             : 
     154          59 :     if(st_ == style::buffers)
     155             :     {
     156           9 :         return const_buffers_type(
     157           3 :             out_.data(),
     158           3 :             out_.size());
     159             :     }
     160             : 
     161          56 :     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          34 :     if(st_ == style::stream)
     227             :     {
     228          34 :         std::size_t n = 0;
     229          34 :         if(out_.data() == hp_)
     230           6 :             ++n;
     231          34 :         if(tmp0_.size() == 0 && more_)
     232             :         {
     233           1 :             BOOST_HTTP_PROTO_RETURN_EC(
     234             :                 error::need_data);
     235             :         }
     236          99 :         for(buffers::const_buffer const& b : tmp0_.data())
     237          66 :             out_[n++] = b;
     238             : 
     239          99 :         return const_buffers_type(
     240          33 :             out_.data(),
     241          33 :             out_.size());
     242             :     }
     243             : 
     244             :     // should never get here
     245           0 :     detail::throw_logic_error();
     246             : }
     247             : 
     248             : void
     249        1745 : serializer::
     250             : consume(
     251             :     std::size_t n)
     252             : {
     253             :     // Precondition violation
     254        1745 :     if(is_done_)
     255           1 :         detail::throw_logic_error();
     256             : 
     257        1744 :     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        1741 :     else if(out_.data() == hp_)
     268             :     {
     269             :         // consume header
     270          26 :         if(n < hp_->size())
     271             :         {
     272          11 :             out_.consume(n);
     273          11 :             return;
     274             :         }
     275          15 :         n -= hp_->size();
     276          15 :         out_.consume(hp_->size());
     277             :     }
     278             : 
     279        1730 :     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        1724 :     case style::source:
     295             :     case style::stream:
     296        1724 :         tmp0_.consume(n);
     297        1763 :         if( tmp0_.size() == 0 &&
     298          39 :                 ! more_)
     299          11 :             is_done_ = true;
     300        1724 :         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          26 : serializer::
     319             : start_init(
     320             :     message_view_base const& m)
     321             : {
     322          26 :     ws_.clear();
     323             : 
     324             :     // VFALCO what do we do with
     325             :     // metadata error code failures?
     326             :     // m.ph_->md.maybe_throw();
     327             : 
     328          26 :     is_done_ = false;
     329             : 
     330          26 :     is_expect_continue_ =
     331          26 :         m.ph_->md.expect.is_100_continue;
     332             : 
     333             :     // Transfer-Encoding
     334             :     {
     335          26 :         auto const& te =
     336          26 :             m.ph_->md.transfer_encoding;
     337          26 :         is_chunked_ = te.is_chunked;
     338             :     }
     339          26 : }
     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           7 : serializer::
     495             : start_stream(
     496             :     message_view_base const& m) ->
     497             :         stream
     498             : {
     499           7 :     start_init(m);
     500             : 
     501           7 :     st_ = style::stream;
     502             :     out_ = make_array(
     503             :         1 + // header
     504           7 :         2); // tmp
     505             :     //if(! cod_)
     506             :     {
     507           7 :         tmp0_ = { ws_.data(), ws_.size() };
     508           7 :         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           7 :     hp_ = &out_[0];
     531           7 :     *hp_ = { m.ph_->cbuf, m.ph_->size };
     532             : 
     533           7 :     more_ = true;
     534             : 
     535           7 :     return stream{*this};
     536             : }
     537             : 
     538             : //------------------------------------------------
     539             : 
     540             : std::size_t
     541         140 : serializer::
     542             : stream::
     543             : capacity() const noexcept
     544             : {
     545         140 :     return sr_->tmp0_.capacity();
     546             : }
     547             : 
     548             : std::size_t
     549         132 : serializer::
     550             : stream::
     551             : size() const noexcept
     552             : {
     553         132 :     return sr_->tmp0_.size();
     554             : }
     555             : 
     556             : bool
     557          66 : serializer::
     558             : stream::
     559             : is_full() const noexcept
     560             : {
     561          66 :     if( sr_->is_chunked_ )
     562          36 :         return capacity() < chunked_overhead_ + 1;
     563             : 
     564          30 :     return capacity() == 0;
     565             : }
     566             : 
     567             : auto
     568          36 : serializer::
     569             : stream::
     570             : prepare() const ->
     571             :     buffers_type
     572             : {
     573          36 :     auto n = sr_->tmp0_.capacity();
     574          36 :     if( sr_->is_chunked_ )
     575             :     {
     576             :         // for chunked encoding, we want to unconditionally
     577             :         // reserve space for the complete chunk and the
     578             :         // last-chunk
     579             :         // this enables users to call:
     580             :         //
     581             :         //     stream.commit(n); stream.close();
     582             :         //
     583             :         // without needing to worry about draining the
     584             :         // serializer via `consume()` calls
     585          21 :         if( n < chunked_overhead_ + 1 )
     586           1 :             detail::throw_length_error();
     587             : 
     588          20 :         n -= chunked_overhead_;
     589             :         return buffers::sans_prefix(
     590          40 :             sr_->tmp0_.prepare(chunk_header_len_ + n),
     591          20 :             chunk_header_len_);
     592             :     }
     593             : 
     594          15 :     return sr_->tmp0_.prepare(n);
     595             : }
     596             : 
     597             : void
     598          35 : serializer::
     599             : stream::
     600             : commit(std::size_t n) const
     601             : {
     602          35 :     if(! sr_->is_chunked_ )
     603             :     {
     604          15 :         sr_->tmp0_.commit(n);
     605             :     }
     606             :     else
     607             :     {
     608             :         // Zero sized chunks are not valid. Call close()
     609             :         // if the intent is to signal the end of the body.
     610          20 :         if( n == 0 )
     611           1 :             detail::throw_logic_error();
     612             : 
     613          19 :         auto m = n + chunk_header_len_;
     614          19 :         auto dest = sr_->tmp0_.prepare(m);
     615          19 :         write_chunk_header(
     616          19 :             buffers::prefix(dest, chunk_header_len_), n);
     617          19 :         sr_->tmp0_.commit(m);
     618          19 :         write_chunk_close(sr_->tmp0_);
     619             :     }
     620          34 : }
     621             : 
     622             : void
     623           9 : serializer::
     624             : stream::
     625             : close() const
     626             : {
     627             :     // Precondition violation
     628           9 :     if(! sr_->more_ )
     629           4 :         detail::throw_logic_error();
     630             : 
     631           5 :     if( sr_->is_chunked_ )
     632           3 :         write_last_chunk(sr_->tmp0_);
     633             : 
     634           5 :     sr_->more_ = false;
     635           5 : }
     636             : 
     637             : //------------------------------------------------
     638             : 
     639             : } // http_proto
     640             : } // boost

Generated by: LCOV version 1.15