LCOV - code coverage report
Current view: top level - libs/http_proto/src/fields_base.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 97.4 % 701 683
Test Date: 2025-10-27 13:02:03 Functions: 98.6 % 70 69

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2021 Vinnie Falco (vinnie.falco@gmail.com)
       3              : // Copyright (c) 2025 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_proto
       9              : //
      10              : 
      11              : #include <boost/http_proto/detail/config.hpp>
      12              : #include <boost/http_proto/detail/except.hpp>
      13              : #include <boost/http_proto/detail/header.hpp>
      14              : #include <boost/http_proto/error.hpp>
      15              : #include <boost/http_proto/field.hpp>
      16              : #include <boost/http_proto/fields_base.hpp>
      17              : #include <boost/http_proto/header_limits.hpp>
      18              : #include <boost/http_proto/rfc/token_rule.hpp>
      19              : 
      20              : #include "src/detail/move_chars.hpp"
      21              : #include "src/rfc/detail/rules.hpp"
      22              : 
      23              : #include <boost/assert.hpp>
      24              : #include <boost/assert/source_location.hpp>
      25              : #include <boost/core/detail/string_view.hpp>
      26              : #include <boost/system/result.hpp>
      27              : #include <boost/url/grammar/ci_string.hpp>
      28              : #include <boost/url/grammar/error.hpp>
      29              : #include <boost/url/grammar/parse.hpp>
      30              : #include <boost/url/grammar/token_rule.hpp>
      31              : 
      32              : namespace boost {
      33              : namespace http_proto {
      34              : 
      35              : namespace {
      36              : 
      37              : std::size_t
      38         1066 : align_down(
      39              :     void * ptr,
      40              :     std::size_t size,
      41              :     std::size_t alignment)
      42              : {
      43         1066 :     auto addr = reinterpret_cast<std::uintptr_t>(ptr);
      44         1066 :     auto aligned_end = (addr + size) & ~(alignment - 1);
      45              : 
      46         1066 :     if(aligned_end > addr)
      47         1066 :         return aligned_end - addr;
      48              : 
      49            0 :     return 0;
      50              : }
      51              : 
      52              : void
      53          235 : verify_field_name(
      54              :     core::string_view name,
      55              :     system::error_code& ec)
      56              : {
      57          235 :     auto rv = grammar::parse(
      58              :         name, detail::field_name_rule);
      59          235 :     if(rv.has_error())
      60              :     {
      61           18 :         ec = BOOST_HTTP_PROTO_ERR(
      62              :             error::bad_field_name);
      63              :     }
      64          235 : }
      65              : 
      66              : system::result<detail::field_value_rule_t::value_type>
      67          365 : verify_field_value(
      68              :     core::string_view value)
      69              : {
      70          365 :     auto it = value.begin();
      71          365 :     auto end = value.end();
      72              :     auto rv =
      73          365 :         grammar::parse(it, end, detail::field_value_rule);
      74          365 :     if( rv.has_error() )
      75              :     {
      76            7 :         if( rv.error() == condition::need_more_input )
      77            7 :             return error::bad_field_value;
      78            0 :         return rv.error();
      79              :     }
      80              : 
      81          358 :     if( rv->has_crlf )
      82           16 :         return error::bad_field_smuggle;
      83              : 
      84          342 :     if( it != end )
      85            7 :         return error::bad_field_value;
      86              : 
      87          335 :     return rv;
      88              : }
      89              : 
      90              : } // namespace
      91              : 
      92              : class fields_base::
      93              :     op_t
      94              : {
      95              :     fields_base& self_;
      96              :     core::string_view* s0_;
      97              :     core::string_view* s1_;
      98              :     char* buf_ = nullptr;
      99              :     char const* cbuf_ = nullptr;
     100              :     std::size_t cap_ = 0;
     101              : 
     102              : public:
     103              :     explicit
     104          989 :     op_t(
     105              :         fields_base& self,
     106              :         core::string_view* s0 = nullptr,
     107              :         core::string_view* s1 = nullptr) noexcept
     108          989 :         : self_(self)
     109          989 :         , s0_(s0)
     110          989 :         , s1_(s1)
     111              :     {
     112          989 :     }
     113              : 
     114          989 :     ~op_t()
     115              :     {
     116          989 :         if(buf_)
     117          163 :             delete[] buf_;
     118          989 :     }
     119              : 
     120              :     char const*
     121           12 :     buf() const noexcept
     122              :     {
     123           12 :         return buf_;
     124              :     }
     125              : 
     126              :     char const*
     127          452 :     cbuf() const noexcept
     128              :     {
     129          452 :         return cbuf_;
     130              :     }
     131              : 
     132              :     char*
     133           12 :     end() const noexcept
     134              :     {
     135           12 :         return buf_ + cap_;
     136              :     }
     137              : 
     138              :     table
     139            6 :     tab() const noexcept
     140              :     {
     141            6 :         return table(end());
     142              :     }
     143              : 
     144              :     bool
     145              :     reserve(std::size_t n);
     146              : 
     147              :     bool
     148              :     grow(
     149              :         std::size_t extra_char,
     150              :         std::size_t extra_field);
     151              : 
     152              :     void
     153              :     move_chars(
     154              :         char* dest,
     155              :         char const* src,
     156              :         std::size_t n) const noexcept;
     157              : };
     158              : 
     159              : bool
     160          969 : fields_base::
     161              : op_t::
     162              : reserve(
     163              :     std::size_t n)
     164              : {
     165              :     // TODO: consider using a growth factor
     166          969 :     if(n > self_.max_cap_)
     167              :     {
     168              :         // max capacity exceeded
     169           18 :         detail::throw_length_error();
     170              :     }
     171          951 :     if(n <= self_.h_.cap)
     172          133 :         return false;
     173          818 :     auto buf = new char[n];
     174          818 :     buf_ = self_.h_.buf;
     175          818 :     cbuf_ = self_.h_.cbuf;
     176          818 :     cap_ = self_.h_.cap;
     177          818 :     self_.h_.buf = buf;
     178          818 :     self_.h_.cbuf = buf;
     179          818 :     self_.h_.cap = n;
     180          818 :     return true;
     181              : }
     182              : 
     183              : bool
     184          876 : fields_base::
     185              : op_t::
     186              : grow(
     187              :     std::size_t extra_char,
     188              :     std::size_t extra_field)
     189              : {
     190          876 :     if(extra_field > detail::header::max_offset - self_.h_.count)
     191            0 :         detail::throw_length_error();
     192              : 
     193          876 :     if(extra_char > detail::header::max_offset - self_.h_.size)
     194            2 :         detail::throw_length_error();
     195              : 
     196          874 :     return reserve(
     197              :         detail::header::bytes_needed(
     198          874 :             self_.h_.size + extra_char,
     199         1743 :             self_.h_.count + extra_field));
     200              : }
     201              : 
     202              : void
     203          103 : fields_base::
     204              : op_t::
     205              : move_chars(
     206              :     char* dest,
     207              :     char const* src,
     208              :     std::size_t n) const noexcept
     209              : {
     210          103 :     detail::move_chars(
     211          103 :         dest, src, n, s0_, s1_);
     212          103 : }
     213              : 
     214              : //------------------------------------------------
     215              : 
     216           44 : fields_base::
     217              : prefix_op_t::
     218              : prefix_op_t(
     219              :     fields_base& self,
     220              :     std::size_t new_prefix,
     221              :     core::string_view* s0,
     222           44 :     core::string_view* s1)
     223           44 :     : self_(self)
     224           44 :     , new_prefix_(static_cast<
     225           44 :         offset_type>(new_prefix))
     226              : {
     227           44 :     if(self.h_.size - self.h_.prefix + new_prefix
     228              :         > detail::header::max_offset)
     229            2 :         detail::throw_length_error();
     230              : 
     231              :     // memmove happens in the destructor
     232              :     // to avoid overlaping with start line.
     233           84 :     if(new_prefix_ < self_.h_.prefix
     234           42 :         && !self.h_.is_default())
     235            6 :         return;
     236              : 
     237           36 :     auto new_size = static_cast<offset_type>(
     238           36 :         self.h_.size - self.h_.prefix + new_prefix_);
     239              : 
     240              :     auto bytes_needed =
     241           36 :         detail::header::bytes_needed(
     242              :             new_size,
     243           36 :             self.h_.count);
     244              : 
     245           36 :     if(bytes_needed > self.h_.cap)
     246              :     {
     247              :         // static storage will always throw which is
     248              :         // intended since they cannot reallocate.
     249           29 :         if(self.max_cap_ < bytes_needed)
     250            1 :             detail::throw_length_error();
     251              :         // TODO: consider using a growth factor
     252           28 :         char* p = new char[bytes_needed];
     253           28 :         std::memcpy(
     254           28 :             p + new_prefix_,
     255           28 :             self.h_.cbuf + self.h_.prefix,
     256           28 :             self.h_.size - self.h_.prefix);
     257           28 :         self.h_.copy_table(p + bytes_needed);
     258              : 
     259              :         // old buffer gets released in the destructor
     260              :         // to avoid invalidating any string_views
     261              :         // that may still reference it.
     262           28 :         buf_        = self.h_.buf;
     263           28 :         self.h_.buf = p;
     264           28 :         self.h_.cap = bytes_needed;
     265              :     }
     266              :     else
     267              :     {
     268              :         // memmove to the right and update any
     269              :         // string_views that reference that region.
     270            7 :         detail::move_chars(
     271            7 :             self.h_.buf + new_prefix_,
     272            7 :             self.h_.cbuf + self.h_.prefix,
     273            7 :             self.h_.size - self.h_.prefix,
     274              :             s0,
     275              :             s1);
     276              :     }
     277              : 
     278           35 :     self.h_.cbuf   = self.h_.buf;
     279           35 :     self.h_.size   = new_size;
     280           35 :     self.h_.prefix = new_prefix_;
     281              : }
     282              : 
     283           41 : fields_base::
     284              : prefix_op_t::
     285              : ~prefix_op_t()
     286              : {
     287           41 :     if(new_prefix_ < self_.h_.prefix)
     288              :     {
     289            6 :         std::memmove(
     290            6 :             self_.h_.buf + new_prefix_,
     291            6 :             self_.h_.cbuf + self_.h_.prefix,
     292            6 :             self_.h_.size - self_.h_.prefix);
     293              : 
     294            6 :         self_.h_.size =
     295            6 :             self_.h_.size - self_.h_.prefix + new_prefix_;
     296            6 :         self_.h_.prefix = new_prefix_;
     297              :     }
     298           35 :     else if(buf_)
     299              :     {
     300            5 :         delete[] buf_;
     301              :     }
     302           41 : }
     303              : 
     304              : //------------------------------------------------
     305              : 
     306          200 : fields_base::
     307              : fields_base(
     308          200 :     detail::kind k) noexcept
     309          200 :     : h_(k)
     310              : {
     311          200 : }
     312              : 
     313         1066 : fields_base::
     314              : fields_base(
     315              :     detail::kind k,
     316              :     void* storage,
     317         1066 :     std::size_t cap) noexcept
     318              :     : fields_base(
     319         1066 :         *detail::header::get_default(k), storage, cap)
     320              : {
     321         1066 : }
     322              : 
     323              : // copy s and parse it
     324          546 : fields_base::
     325              : fields_base(
     326              :     detail::kind k,
     327          546 :     core::string_view s)
     328          546 :     : h_(detail::empty{k})
     329              : {
     330          546 :     auto n = detail::header::count_crlf(s);
     331          546 :     if(h_.kind == detail::kind::fields)
     332              :     {
     333          235 :         if(n < 1)
     334            1 :             detail::throw_invalid_argument();
     335          234 :         n -= 1;
     336              :     }
     337              :     else
     338              :     {
     339          311 :         if(n < 2)
     340            2 :             detail::throw_invalid_argument();
     341          309 :         n -= 2;
     342              :     }
     343          543 :     op_t op(*this);
     344          543 :     op.grow(s.size(), n);
     345          543 :     s.copy(h_.buf, s.size());
     346          543 :     system::error_code ec;
     347              :     // VFALCO This is using defaults?
     348          543 :     header_limits lim;
     349          543 :     h_.parse(s.size(), lim, ec);
     350          543 :     if(ec.failed())
     351            0 :         detail::throw_system_error(ec);
     352          543 : }
     353              : 
     354              : // construct a complete copy of h
     355           25 : fields_base::
     356              : fields_base(
     357           25 :     detail::header const& h)
     358           25 :     : h_(h.kind)
     359              : {
     360           25 :     if(h.is_default())
     361            9 :         return;
     362              : 
     363              :     // allocate and copy the buffer
     364           16 :     op_t op(*this);
     365           16 :     op.grow(h.size, h.count);
     366           16 :     h.assign_to(h_);
     367           16 :     std::memcpy(
     368           16 :         h_.buf, h.cbuf, h.size);
     369           16 :     h.copy_table(h_.buf + h_.cap);
     370           16 : }
     371              : 
     372              : // construct a complete copy of h
     373         1066 : fields_base::
     374              : fields_base(
     375              :     detail::header const& h,
     376              :     void* storage,
     377         1066 :     std::size_t cap)
     378         1066 :     : h_(h.kind)
     379         1066 :     , external_storage_(true)
     380              : {
     381         1066 :     h_.cbuf = static_cast<char*>(storage);
     382         1066 :     h_.buf = static_cast<char*>(storage);
     383         1066 :     h_.cap = align_down(
     384              :         storage,
     385              :         cap,
     386              :         alignof(detail::header::entry));
     387         1066 :     max_cap_ = h_.cap;
     388              : 
     389         2132 :     if(detail::header::bytes_needed(
     390         1066 :         h.size, h.count)
     391         1066 :             >= h_.cap)
     392            0 :         detail::throw_length_error();
     393              : 
     394         1066 :     h.assign_to(h_);
     395         1066 :     std::memcpy(
     396         1066 :         h_.buf, h.cbuf, h.size);
     397         1066 :     h.copy_table(h_.buf + h_.cap);
     398         1066 : }
     399              : 
     400              : //------------------------------------------------
     401              : 
     402           13 : fields_base::
     403           13 : fields_base(fields_base const& other)
     404           13 :     : fields_base(other.h_)
     405              : {
     406           13 : }
     407              : 
     408         1834 : fields_base::
     409              : ~fields_base()
     410              : {
     411         1834 :     if(h_.buf && !external_storage_)
     412          678 :         delete[] h_.buf;
     413         1834 : }
     414              : 
     415              : //------------------------------------------------
     416              : //
     417              : // Capacity
     418              : //
     419              : //------------------------------------------------
     420              : 
     421              : void
     422           10 : fields_base::
     423              : clear() noexcept
     424              : {
     425           10 :     if(! h_.buf)
     426            5 :         return;
     427              :     using H =
     428              :         detail::header;
     429              :     auto const& h =
     430            5 :         *H::get_default(
     431            5 :             h_.kind);
     432            5 :     h.assign_to(h_);
     433            5 :     std::memcpy(
     434            5 :         h_.buf,
     435            5 :         h.cbuf,
     436            5 :         h_.size);
     437              : }
     438              : 
     439              : void
     440           95 : fields_base::
     441              : reserve_bytes(
     442              :     std::size_t n)
     443              : {
     444           95 :     op_t op(*this);
     445           95 :     if(! op.reserve(n))
     446           48 :         return;
     447           68 :     std::memcpy(
     448           34 :         h_.buf, op.cbuf(), h_.size);
     449           34 :     auto const nt =
     450           34 :         sizeof(entry) * h_.count;
     451           34 :     if(nt > 0)
     452            6 :         std::memcpy(
     453            6 :             h_.buf + h_.cap - nt,
     454            6 :             op.end() - nt,
     455              :             nt);
     456           95 : }
     457              : 
     458              : void
     459            7 : fields_base::
     460              : shrink_to_fit()
     461              : {
     462           14 :     if(detail::header::bytes_needed(
     463            7 :         h_.size, h_.count) >=
     464            7 :             h_.cap)
     465            3 :         return;
     466              : 
     467            4 :     if(external_storage_)
     468            0 :         return;
     469              : 
     470            4 :     fields_base tmp(h_);
     471            4 :     tmp.h_.swap(h_);
     472            4 : }
     473              : 
     474              : 
     475              : void
     476           30 : fields_base::
     477              : set_max_capacity_in_bytes(std::size_t n)
     478              : {
     479           30 :     if(n < h_.cap)
     480            6 :         detail::throw_invalid_argument();
     481           24 :     max_cap_ = n;
     482           24 : }
     483              : 
     484              : //--------------------------------------------
     485              : //
     486              : // Observers
     487              : //
     488              : //--------------------------------------------
     489              : 
     490              : 
     491            0 : fields_base::
     492              : value_type::
     493              : value_type(
     494            0 :     reference const& other)
     495            0 :     : id(other.id)
     496            0 :     , name(other.name)
     497            0 :     , value(other.value)
     498              : {
     499            0 : }
     500              : 
     501              : //------------------------------------------------
     502              : 
     503              : auto
     504         1816 : fields_base::
     505              : iterator::
     506              : operator*() const noexcept ->
     507              :     reference
     508              : {
     509         1816 :     BOOST_ASSERT(i_ < ph_->count);
     510              :     auto tab =
     511         1816 :         ph_->tab();
     512              :     auto const& e =
     513         1816 :         tab[i_];
     514         1816 :     auto const* p =
     515         1816 :         ph_->cbuf + ph_->prefix;
     516              :     return {
     517         1816 :         (e.id == detail::header::unknown_field)
     518         1816 :             ? optional<field>{} : e.id,
     519              :         core::string_view(
     520         1816 :             p + e.np, e.nn),
     521              :         core::string_view(
     522         1816 :             p + e.vp, e.vn) };
     523              : }
     524              : 
     525              : //------------------------------------------------
     526              : 
     527              : auto
     528           24 : fields_base::
     529              : reverse_iterator::
     530              : operator*() const noexcept ->
     531              :     reference
     532              : {
     533           24 :     BOOST_ASSERT(i_ > 0);
     534              :     auto tab =
     535           24 :       ph_->tab();
     536              :     auto const& e =
     537           24 :         tab[i_-1];
     538           24 :     auto const* p =
     539           24 :         ph_->cbuf + ph_->prefix;
     540              :     return {
     541           24 :         (e.id == detail::header::unknown_field)
     542           24 :             ? optional<field>{} : e.id,
     543              :         core::string_view(
     544           24 :             p + e.np, e.nn),
     545              :         core::string_view(
     546           24 :             p + e.vp, e.vn) };
     547              : }
     548              : 
     549              : //------------------------------------------------
     550              : 
     551           21 : fields_base::
     552              : subrange::
     553              : iterator::
     554              : iterator(
     555              :     detail::header const* ph,
     556           21 :     std::size_t i) noexcept
     557           21 :     : ph_(ph)
     558           21 :     , i_(i)
     559              : {
     560           21 :     BOOST_ASSERT(i <= ph_->count);
     561           21 : }
     562              : 
     563           21 : fields_base::
     564              : subrange::
     565              : iterator::
     566              : iterator(
     567           21 :     detail::header const* ph) noexcept
     568           21 :     : ph_(ph)
     569           21 :     , i_(ph->count)
     570              : {
     571           21 : }
     572              : 
     573              : auto
     574           11 : fields_base::
     575              : subrange::
     576              : iterator::
     577              : operator*() const noexcept ->
     578              :     reference const
     579              : {
     580              :     auto tab =
     581           11 :         ph_->tab();
     582              :     auto const& e =
     583           11 :         tab[i_];
     584           11 :     auto const p =
     585           11 :         ph_->cbuf + ph_->prefix;
     586           22 :     return core::string_view(
     587           11 :         p + e.vp, e.vn);
     588              : }
     589              : 
     590              : auto
     591           27 : fields_base::
     592              : subrange::
     593              : iterator::
     594              : operator++() noexcept ->
     595              :     iterator&
     596              : {
     597           27 :     BOOST_ASSERT(i_ < ph_->count);
     598           27 :     auto const* e = &ph_->tab()[i_];
     599           27 :     auto const id = e->id;
     600           27 :     if(id != detail::header::unknown_field)
     601              :     {
     602           20 :         ++i_;
     603           20 :         --e;
     604           38 :         while(i_ != ph_->count)
     605              :         {
     606           26 :             if(e->id == id)
     607            8 :                 break;
     608           18 :             ++i_;
     609           18 :             --e;
     610              :         }
     611           20 :         return *this;
     612              :     }
     613            7 :     auto const p =
     614            7 :         ph_->cbuf + ph_->prefix;
     615              :     auto name = core::string_view(
     616            7 :         p + e->np, e->nn);
     617            7 :     ++i_;
     618            7 :     --e;
     619           24 :     while(i_ != ph_->count)
     620              :     {
     621           20 :         if(grammar::ci_is_equal(
     622              :             name, core::string_view(
     623           20 :                 p + e->np, e->nn)))
     624            3 :             break;
     625           17 :         ++i_;
     626           17 :         --e;
     627              :     }
     628            7 :     return *this;
     629              : }
     630              : 
     631              : //------------------------------------------------
     632              : //
     633              : // fields_base
     634              : //
     635              : //------------------------------------------------
     636              : 
     637              : core::string_view
     638            2 : fields_base::
     639              : at(
     640              :     field id) const
     641              : {
     642            2 :     auto const it = find(id);
     643            2 :     if(it == end())
     644            2 :         BOOST_THROW_EXCEPTION(
     645              :             std::out_of_range{ "field not found" });
     646            1 :     return it->value;
     647              : }
     648              : 
     649              : core::string_view
     650            2 : fields_base::
     651              : at(
     652              :     core::string_view name) const
     653              : {
     654            2 :     auto const it = find(name);
     655            2 :     if(it == end())
     656            2 :         BOOST_THROW_EXCEPTION(
     657              :             std::out_of_range{ "field not found" });
     658            1 :     return it->value;
     659              : }
     660              : 
     661              : bool
     662            4 : fields_base::
     663              : exists(
     664              :     field id) const noexcept
     665              : {
     666            4 :     return find(id) != end();
     667              : }
     668              : 
     669              : bool
     670            7 : fields_base::
     671              : exists(
     672              :     core::string_view name) const noexcept
     673              : {
     674            7 :     return find(name) != end();
     675              : }
     676              : 
     677              : std::size_t
     678           12 : fields_base::
     679              : count(field id) const noexcept
     680              : {
     681           12 :     std::size_t n = 0;
     682           57 :     for(auto v : *this)
     683           45 :         if(v.id == id)
     684           11 :             ++n;
     685           12 :     return n;
     686              : }
     687              : 
     688              : std::size_t
     689           14 : fields_base::
     690              : count(
     691              :     core::string_view name) const noexcept
     692              : {
     693           14 :     std::size_t n = 0;
     694           76 :     for(auto v : *this)
     695           62 :         if(grammar::ci_is_equal(
     696              :                 v.name, name))
     697           19 :             ++n;
     698           14 :     return n;
     699              : }
     700              : 
     701              : auto
     702           94 : fields_base::
     703              : find(field id) const noexcept ->
     704              :     iterator
     705              : {
     706           94 :     auto it = begin();
     707           94 :     auto const last = end();
     708          208 :     while(it != last)
     709              :     {
     710          199 :         if(it->id == id)
     711           85 :             break;
     712          114 :         ++it;
     713              :     }
     714           94 :     return it;
     715              : }
     716              : 
     717              : auto
     718           93 : fields_base::
     719              : find(
     720              :     core::string_view name) const noexcept ->
     721              :     iterator
     722              : {
     723           93 :     auto it = begin();
     724           93 :     auto const last = end();
     725          206 :     while(it != last)
     726              :     {
     727          200 :         if(grammar::ci_is_equal(
     728          400 :                 it->name, name))
     729           87 :             break;
     730          113 :         ++it;
     731              :     }
     732           93 :     return it;
     733              : }
     734              : 
     735              : auto
     736            2 : fields_base::
     737              : find(
     738              :     iterator from,
     739              :     field id) const noexcept ->
     740              :         iterator
     741              : {
     742            2 :     auto const last = end();
     743           11 :     while(from != last)
     744              :     {
     745           10 :         if(from->id == id)
     746            1 :             break;
     747            9 :         ++from;
     748              :     }
     749            2 :     return from;
     750              : }
     751              : 
     752              : auto
     753            2 : fields_base::
     754              : find(
     755              :     iterator from,
     756              :     core::string_view name) const noexcept ->
     757              :         iterator
     758              : {
     759            2 :     auto const last = end();
     760           12 :     while(from != last)
     761              :     {
     762           11 :         if(grammar::ci_is_equal(
     763           22 :                 name, from->name))
     764            1 :             break;
     765           10 :         ++from;
     766              :     }
     767            2 :     return from;
     768              : }
     769              : 
     770              : auto
     771            3 : fields_base::
     772              : find_last(
     773              :     iterator it,
     774              :     field id) const noexcept ->
     775              :         iterator
     776              : {
     777            3 :     auto const it0 = begin();
     778              :     for(;;)
     779              :     {
     780           10 :         if(it == it0)
     781            1 :             return end();
     782            9 :         --it;
     783            9 :         if(it->id == id)
     784            2 :             return it;
     785              :     }
     786              : }
     787              : 
     788              : auto
     789            3 : fields_base::
     790              : find_last(
     791              :     iterator it,
     792              :     core::string_view name) const noexcept ->
     793              :         iterator
     794              : {
     795            3 :     auto const it0 = begin();
     796              :     for(;;)
     797              :     {
     798           14 :         if(it == it0)
     799            1 :             return end();
     800           13 :         --it;
     801           13 :         if(grammar::ci_is_equal(
     802           26 :                 it->name, name))
     803            2 :             return it;
     804              :     }
     805              : }
     806              : 
     807              : core::string_view
     808            2 : fields_base::
     809              : value_or(
     810              :     field id,
     811              :     core::string_view s) const noexcept
     812              : {
     813            2 :     auto it = find(id);
     814            2 :     if(it != end())
     815            1 :         return it->value;
     816            1 :     return s;
     817              : }
     818              : 
     819              : core::string_view
     820            2 : fields_base::
     821              : value_or(
     822              :     core::string_view name,
     823              :     core::string_view s) const noexcept
     824              : {
     825            2 :     auto it = find(name);
     826            2 :     if(it != end())
     827            1 :         return it->value;
     828            1 :     return s;
     829              : }
     830              : 
     831              : //------------------------------------------------
     832              : 
     833              : auto
     834           16 : fields_base::
     835              : find_all(
     836              :     field id) const noexcept ->
     837              :         subrange
     838              : {
     839           16 :     return subrange(
     840           32 :         &h_, find(id).i_);
     841              : }
     842              : 
     843              : auto
     844            5 : fields_base::
     845              : find_all(
     846              :     core::string_view name) const noexcept ->
     847              :         subrange
     848              : {
     849            5 :     return subrange(
     850           10 :         &h_, find(name).i_);
     851              : }
     852              : 
     853              : std::ostream&
     854            1 : operator<<(
     855              :     std::ostream& os,
     856              :     const fields_base& f)
     857              : {
     858            1 :     if(f.h_.prefix != 0)
     859            1 :         os << core::string_view(f.h_.cbuf, f.h_.prefix - 2) << '\n';
     860              : 
     861            3 :     for(auto ref : f)
     862            2 :         os << ref.name << ": " << ref.value << '\n';
     863              : 
     864            1 :     return os;
     865              : }
     866              : 
     867              : //------------------------------------------------
     868              : //
     869              : // Modifiers
     870              : //
     871              : //------------------------------------------------
     872              : 
     873              : auto
     874           30 : fields_base::
     875              : erase(
     876              :     iterator it) noexcept -> iterator
     877              : {
     878           30 :     auto const id = it->id.value_or(
     879              :         detail::header::unknown_field);
     880           30 :     raw_erase(it.i_);
     881           30 :     h_.on_erase(id);
     882           30 :     return it;
     883              : }
     884              : 
     885              : std::size_t
     886           30 : fields_base::
     887              : erase(
     888              :     field id) noexcept
     889              : {
     890           30 :     auto const i0 = h_.find(id);
     891           30 :     if(i0 == h_.count)
     892            3 :         return 0;
     893           27 :     return erase_all(i0, id);
     894              : }
     895              : 
     896              : std::size_t
     897           18 : fields_base::
     898              : erase(
     899              :     core::string_view name) noexcept
     900              : {
     901           18 :     auto const i0 = h_.find(name);
     902           18 :     if(i0 == h_.count)
     903            3 :         return 0;
     904           15 :     auto const ft = h_.tab();
     905           15 :     auto const id = ft[i0].id;
     906           15 :     if(id == detail::header::unknown_field)
     907            6 :         return erase_all(i0, name);
     908            9 :     return erase_all(i0, id);
     909              : }
     910              : 
     911              : //------------------------------------------------
     912              : 
     913              : void
     914           28 : fields_base::
     915              : set(
     916              :     iterator it,
     917              :     core::string_view value,
     918              :     system::error_code& ec)
     919              : {
     920           28 :     auto rv = verify_field_value(value);
     921           28 :     if(rv.has_error())
     922              :     {
     923            4 :         ec = rv.error();
     924            4 :         return;
     925              :     }
     926              : 
     927           24 :     value = rv->value;
     928           24 :     bool has_obs_fold = rv->has_obs_fold;
     929              : 
     930           24 :     auto const i = it.i_;
     931           24 :     auto tab = h_.tab();
     932           24 :     auto const& e0 = tab[i];
     933           24 :     auto const pos0 = offset(i);
     934           24 :     auto const pos1 = offset(i + 1);
     935              :     std::ptrdiff_t dn =
     936           24 :         value.size() -
     937           24 :         it->value.size();
     938           24 :     if( value.empty() &&
     939           24 :         ! it->value.empty())
     940            0 :         --dn; // remove SP
     941           24 :     else if(
     942           24 :         it->value.empty() &&
     943            0 :         ! value.empty())
     944            0 :         ++dn; // add SP
     945              : 
     946           24 :     op_t op(*this, &value);
     947           30 :     if( dn > 0 &&
     948           12 :         op.grow(value.size() -
     949           30 :             it->value.size(), 0))
     950              :     {
     951              :         // reallocated
     952            6 :         auto dest = h_.buf +
     953            6 :             pos0 + e0.nn + 1;
     954           12 :         std::memcpy(
     955            6 :             h_.buf,
     956            6 :             op.buf(),
     957            6 :             dest - h_.buf);
     958            6 :         if(! value.empty())
     959              :         {
     960            6 :             *dest++ = ' ';
     961            6 :             value.copy(
     962              :                 dest,
     963              :                 value.size());
     964            6 :             if( has_obs_fold )
     965            3 :                 detail::remove_obs_fold(
     966            3 :                     dest, dest + value.size());
     967            6 :             dest += value.size();
     968              :         }
     969            6 :         *dest++ = '\r';
     970            6 :         *dest++ = '\n';
     971           12 :         std::memcpy(
     972            6 :             h_.buf + pos1 + dn,
     973           12 :             op.buf() + pos1,
     974            6 :             h_.size - pos1);
     975           12 :         std::memcpy(
     976            6 :             h_.buf + h_.cap -
     977            6 :                 sizeof(entry) * h_.count,
     978            6 :             &op.tab()[h_.count - 1],
     979            6 :             sizeof(entry) * h_.count);
     980              :     }
     981              :     else
     982              :     {
     983              :         // copy the value first
     984           36 :         auto dest = h_.buf + pos0 +
     985           18 :             it->name.size() + 1;
     986           18 :         if(! value.empty())
     987              :         {
     988           18 :             *dest++ = ' ';
     989           18 :             value.copy(
     990              :                 dest,
     991              :                 value.size());
     992           18 :             if( has_obs_fold )
     993            0 :                 detail::remove_obs_fold(
     994            0 :                     dest, dest + value.size());
     995           18 :             dest += value.size();
     996              :         }
     997           18 :         op.move_chars(
     998           18 :             h_.buf + pos1 + dn,
     999           18 :             h_.buf + pos1,
    1000           18 :             h_.size - pos1);
    1001           18 :         *dest++ = '\r';
    1002           18 :         *dest++ = '\n';
    1003              :     }
    1004              :     {
    1005              :         // update tab
    1006           24 :         auto ft = h_.tab();
    1007           31 :         for(std::size_t j = h_.count - 1;
    1008           31 :                 j > i; --j)
    1009            7 :             ft[j] = ft[j] + dn;
    1010           24 :         auto& e = ft[i];
    1011           48 :         e.vp = e.np + e.nn +
    1012           24 :             1 + ! value.empty();
    1013           24 :         e.vn = static_cast<
    1014           24 :             offset_type>(value.size());
    1015           24 :         h_.size = static_cast<
    1016           24 :             offset_type>(h_.size + dn);
    1017              :     }
    1018           24 :     auto const id = it->id.value_or(
    1019              :         detail::header::unknown_field);
    1020           24 :     if(h_.is_special(id))
    1021              :     {
    1022              :         // replace first char of name
    1023              :         // with null to hide metadata
    1024            9 :         char saved = h_.buf[pos0];
    1025            9 :         auto& e = h_.tab()[i];
    1026            9 :         e.id = detail::header::unknown_field;
    1027            9 :         h_.buf[pos0] = '\0';
    1028            9 :         h_.on_erase(id);
    1029            9 :         h_.buf[pos0] = saved; // restore
    1030            9 :         e.id = id;
    1031            9 :         h_.on_insert(id, it->value);
    1032              :     }
    1033           24 : }
    1034              : 
    1035              : // erase existing fields with id
    1036              : // and then add the field with value
    1037              : void
    1038          111 : fields_base::
    1039              : set(
    1040              :     field id,
    1041              :     core::string_view value,
    1042              :     system::error_code& ec)
    1043              : {
    1044          111 :     auto rv = verify_field_value(value);
    1045          111 :     if(rv.has_error())
    1046              :     {
    1047            4 :         ec = rv.error();
    1048            4 :         return;
    1049              :     }
    1050              : 
    1051          107 :     auto const i0 = h_.find(id);
    1052          107 :     if(i0 != h_.count)
    1053              :     {
    1054              :         // field exists
    1055           21 :         auto const ft = h_.tab();
    1056              :         {
    1057              :             // provide strong guarantee
    1058              :             auto const n0 =
    1059           21 :                 h_.size - length(i0);
    1060              :             auto const n =
    1061           21 :                 ft[i0].nn + 2 +
    1062           21 :                     rv->value.size() + 2;
    1063              :             // VFALCO missing overflow check
    1064           21 :             reserve_bytes(n0 + n);
    1065              :         }
    1066           21 :         erase_all(i0, id);
    1067              :     }
    1068              : 
    1069          107 :     insert_unchecked(
    1070              :         id,
    1071              :         to_string(id),
    1072          107 :         rv->value,
    1073          107 :         h_.count,
    1074          107 :         rv->has_obs_fold);
    1075              : }
    1076              : 
    1077              : // erase existing fields with name
    1078              : // and then add the field with value
    1079              : void
    1080           32 : fields_base::
    1081              : set(
    1082              :     core::string_view name,
    1083              :     core::string_view value,
    1084              :     system::error_code& ec)
    1085              : {
    1086           32 :     verify_field_name(name , ec);
    1087           32 :     if(ec.failed())
    1088            8 :         return;
    1089              : 
    1090           28 :     auto rv = verify_field_value(value);
    1091           28 :     if(rv.has_error())
    1092              :     {
    1093            4 :         ec = rv.error();
    1094            4 :         return;
    1095              :     }
    1096              : 
    1097           24 :     auto const i0 = h_.find(name);
    1098           24 :     if(i0 != h_.count)
    1099              :     {
    1100              :         // field exists
    1101           18 :         auto const ft = h_.tab();
    1102           18 :         auto const id = ft[i0].id;
    1103              :         {
    1104              :             // provide strong guarantee
    1105              :             auto const n0 =
    1106           18 :                 h_.size - length(i0);
    1107              :             auto const n =
    1108           18 :                 ft[i0].nn + 2 +
    1109           18 :                     rv->value.size() + 2;
    1110              :             // VFALCO missing overflow check
    1111           18 :             reserve_bytes(n0 + n);
    1112              :         }
    1113              :         // VFALCO simple algorithm but
    1114              :         // costs one extra memmove
    1115           18 :         if(id != detail::header::unknown_field)
    1116           15 :             erase_all(i0, id);
    1117              :         else
    1118            3 :             erase_all(i0, name);
    1119              :     }
    1120           24 :     insert_unchecked(
    1121              :         string_to_field(name),
    1122              :         name,
    1123           24 :         rv->value,
    1124           24 :         h_.count,
    1125           24 :         rv->has_obs_fold);
    1126              : }
    1127              : 
    1128              : auto
    1129           26 : fields_base::
    1130              : insert(
    1131              :     iterator before,
    1132              :     field id,
    1133              :     core::string_view value)
    1134              :     -> iterator
    1135              : {
    1136           26 :     system::error_code ec;
    1137           26 :     auto const it = insert(before, id, value, ec);
    1138           26 :     if(ec.failed())
    1139            1 :         detail::throw_system_error(ec);
    1140           25 :     return it;
    1141              : }
    1142              : 
    1143              : auto
    1144           33 : fields_base::
    1145              : insert(
    1146              :     iterator before,
    1147              :     field id,
    1148              :     core::string_view value,
    1149              :     system::error_code& ec)
    1150              :     -> iterator
    1151              : {
    1152           33 :     insert_impl(
    1153              :         id,
    1154              :         to_string(id),
    1155              :         value,
    1156              :         before.i_, ec);
    1157           33 :     return before;
    1158              : }
    1159              : 
    1160              : auto
    1161           13 : fields_base::
    1162              : insert(
    1163              :     iterator before,
    1164              :     core::string_view name,
    1165              :     core::string_view value)
    1166              :     -> iterator
    1167              : {
    1168           13 :     system::error_code ec;
    1169           13 :     insert(before, name, value, ec);
    1170           13 :     if(ec.failed())
    1171            1 :         detail::throw_system_error(ec);
    1172           12 :     return before;
    1173              : }
    1174              : 
    1175              : auto
    1176           16 : fields_base::
    1177              : insert(
    1178              :     iterator before,
    1179              :     core::string_view name,
    1180              :     core::string_view value,
    1181              :     system::error_code& ec)
    1182              :     -> iterator
    1183              : {
    1184           16 :     insert_impl(
    1185              :         string_to_field(name),
    1186              :         name,
    1187              :         value,
    1188              :         before.i_,
    1189              :         ec);
    1190           16 :     return before;
    1191              : }
    1192              : 
    1193              : void
    1194           23 : fields_base::
    1195              : set(
    1196              :     iterator it,
    1197              :     core::string_view value)
    1198              : {
    1199           23 :     system::error_code ec;
    1200           23 :     set(it, value, ec);
    1201           23 :     if(ec.failed())
    1202            2 :         detail::throw_system_error(ec);
    1203           21 : }
    1204              : 
    1205              : //------------------------------------------------
    1206              : //
    1207              : // (implementation)
    1208              : //
    1209              : //------------------------------------------------
    1210              : 
    1211              : // copy start line and fields
    1212              : void
    1213           16 : fields_base::
    1214              : copy_impl(
    1215              :     detail::header const& h)
    1216              : {
    1217           16 :     BOOST_ASSERT(
    1218              :         h.kind == h_.kind);
    1219              : 
    1220              :     auto const n =
    1221           16 :         detail::header::bytes_needed(
    1222           16 :             h.size, h.count);
    1223           16 :     if(n <= h_.cap && (!h.is_default() || external_storage_))
    1224              :     {
    1225              :         // no realloc
    1226            8 :         h.assign_to(h_);
    1227            8 :         h.copy_table(
    1228            8 :             h_.buf + h_.cap);
    1229            8 :         std::memcpy(
    1230            8 :             h_.buf,
    1231            8 :             h.cbuf,
    1232            8 :             h.size);
    1233            8 :         return;
    1234              :     }
    1235              : 
    1236              :     // static storages cannot reallocate
    1237            8 :     if(external_storage_)
    1238            0 :         detail::throw_length_error();
    1239              : 
    1240            8 :     fields_base tmp(h);
    1241            8 :     tmp.h_.swap(h_);
    1242            8 : }
    1243              : 
    1244              : void
    1245          203 : fields_base::
    1246              : insert_impl(
    1247              :     optional<field> id,
    1248              :     core::string_view name,
    1249              :     core::string_view value,
    1250              :     std::size_t before,
    1251              :     system::error_code& ec)
    1252              : {
    1253          203 :     verify_field_name(name, ec);
    1254          203 :     if(ec.failed())
    1255           23 :         return;
    1256              : 
    1257          198 :     auto rv = verify_field_value(value);
    1258          198 :     if(rv.has_error())
    1259              :     {
    1260           18 :         ec = rv.error();
    1261           18 :         return;
    1262              :     }
    1263              : 
    1264          180 :     insert_unchecked(
    1265              :         id,
    1266              :         name,
    1267          180 :         rv->value,
    1268              :         before,
    1269          180 :         rv->has_obs_fold);
    1270              : }
    1271              : 
    1272              : void
    1273          311 : fields_base::
    1274              : insert_unchecked(
    1275              :     optional<field> id,
    1276              :     core::string_view name,
    1277              :     core::string_view value,
    1278              :     std::size_t before,
    1279              :     bool has_obs_fold)
    1280              : {
    1281          311 :     auto const tab0 = h_.tab_();
    1282          311 :     auto const pos = offset(before);
    1283              :     auto const n =
    1284          311 :         name.size() +       // name
    1285          311 :         1 +                 // ':'
    1286          311 :         ! value.empty() +   // [SP]
    1287          311 :         value.size() +      // value
    1288          311 :         2;                  // CRLF
    1289              : 
    1290          311 :     op_t op(*this, &name, &value);
    1291          311 :     if(op.grow(n, 1))
    1292              :     {
    1293              :         // reallocated
    1294          219 :         if(pos > 0)
    1295          199 :             std::memcpy(
    1296          199 :                 h_.buf,
    1297          199 :                 op.cbuf(),
    1298              :                 pos);
    1299          219 :         if(before > 0)
    1300          180 :             std::memcpy(
    1301           90 :                 h_.tab_() - before,
    1302           90 :                 tab0 - before,
    1303              :                 before * sizeof(entry));
    1304          438 :         std::memcpy(
    1305          219 :             h_.buf + pos + n,
    1306          219 :             op.cbuf() + pos,
    1307          219 :             h_.size - pos);
    1308              :     }
    1309              :     else
    1310              :     {
    1311           85 :         op.move_chars(
    1312           85 :             h_.buf + pos + n,
    1313           85 :             h_.buf + pos,
    1314           85 :             h_.size - pos);
    1315              :     }
    1316              : 
    1317              :     // serialize
    1318              :     {
    1319          304 :         auto dest = h_.buf + pos;
    1320          304 :         name.copy(dest, name.size());
    1321          304 :         dest += name.size();
    1322          304 :         *dest++ = ':';
    1323          304 :         if(! value.empty())
    1324              :         {
    1325          292 :             *dest++ = ' ';
    1326          292 :             value.copy(
    1327              :                 dest, value.size());
    1328          292 :             if( has_obs_fold )
    1329           18 :                 detail::remove_obs_fold(
    1330           18 :                     dest, dest + value.size());
    1331          292 :             dest += value.size();
    1332              :         }
    1333          304 :         *dest++ = '\r';
    1334          304 :         *dest = '\n';
    1335              :     }
    1336              : 
    1337              :     // update table
    1338          304 :     auto const tab = h_.tab_();
    1339              :     {
    1340          304 :         auto i = h_.count - before;
    1341          304 :         if(i > 0)
    1342              :         {
    1343           43 :             auto p0 = tab0 - h_.count;
    1344           43 :             auto p = tab - h_.count - 1;
    1345              :             do
    1346              :             {
    1347           80 :                 *p++ = *p0++ + n;
    1348              :             }
    1349           80 :             while(--i);
    1350              :         }
    1351              :     }
    1352          304 :     auto& e = tab[0 - static_cast<std::ptrdiff_t>(before) - 1];
    1353          304 :     e.np = static_cast<offset_type>(
    1354          304 :         pos - h_.prefix);
    1355          304 :     e.nn = static_cast<
    1356          304 :         offset_type>(name.size());
    1357          304 :     e.vp = static_cast<offset_type>(
    1358          608 :         pos - h_.prefix +
    1359          304 :             name.size() + 1 +
    1360          304 :             ! value.empty());
    1361          304 :     e.vn = static_cast<
    1362          304 :         offset_type>(value.size());
    1363          304 :     e.id = id.value_or(
    1364              :         detail::header::unknown_field);
    1365              : 
    1366              :     // update container
    1367          304 :     h_.count++;
    1368          304 :     h_.size = static_cast<
    1369          304 :         offset_type>(h_.size + n);
    1370          304 :     h_.on_insert(e.id, value);
    1371          311 : }
    1372              : 
    1373              : void
    1374          169 : fields_base::
    1375              : raw_erase(
    1376              :     std::size_t i) noexcept
    1377              : {
    1378          169 :     BOOST_ASSERT(i < h_.count);
    1379          169 :     BOOST_ASSERT(h_.buf != nullptr);
    1380          169 :     auto const p0 = offset(i);
    1381          169 :     auto const p1 = offset(i + 1);
    1382          169 :     std::memmove(
    1383          169 :         h_.buf + p0,
    1384          169 :         h_.buf + p1,
    1385          169 :         h_.size - p1);
    1386          169 :     auto const n = p1 - p0;
    1387          169 :     --h_.count;
    1388          169 :     auto ft = h_.tab();
    1389          270 :     for(;i < h_.count; ++i)
    1390          101 :         ft[i] = ft[i + 1] - n;
    1391          169 :     h_.size = static_cast<
    1392          169 :         offset_type>(h_.size - n);
    1393          169 : }
    1394              : 
    1395              : // erase n fields matching id
    1396              : // without updating metadata
    1397              : void
    1398            4 : fields_base::
    1399              : raw_erase_n(
    1400              :     field id,
    1401              :     std::size_t n) noexcept
    1402              : {
    1403              :     // iterate in reverse
    1404            4 :     auto e = &h_.tab()[h_.count];
    1405            4 :     auto const e0 = &h_.tab()[0];
    1406           10 :     while(n > 0)
    1407              :     {
    1408            6 :         BOOST_ASSERT(e != e0);
    1409            6 :         ++e; // decrement
    1410            6 :         if(e->id == id)
    1411              :         {
    1412            5 :             raw_erase(e0 - e);
    1413            5 :             --n;
    1414              :         }
    1415              :     }
    1416            4 : }
    1417              : 
    1418              : // erase all fields with id
    1419              : // and update metadata
    1420              : std::size_t
    1421           72 : fields_base::
    1422              : erase_all(
    1423              :     std::size_t i0,
    1424              :     field id) noexcept
    1425              : {
    1426           72 :     BOOST_ASSERT(
    1427              :         id != detail::header::unknown_field);
    1428           72 :     std::size_t n = 1;
    1429           72 :     std::size_t i = h_.count - 1;
    1430           72 :     auto const ft = h_.tab();
    1431          149 :     while(i > i0)
    1432              :     {
    1433           77 :         if(ft[i].id == id)
    1434              :         {
    1435           44 :             raw_erase(i);
    1436           44 :             ++n;
    1437              :         }
    1438              :         // go backwards to
    1439              :         // reduce memmoves
    1440           77 :         --i;
    1441              :     }
    1442           72 :     raw_erase(i0);
    1443           72 :     h_.on_erase_all(id);
    1444           72 :     return n;
    1445              : }
    1446              : 
    1447              : // erase all fields with name
    1448              : // when id == detail::header::unknown_field
    1449              : std::size_t
    1450            9 : fields_base::
    1451              : erase_all(
    1452              :     std::size_t i0,
    1453              :     core::string_view name) noexcept
    1454              : {
    1455            9 :     std::size_t n = 1;
    1456            9 :     std::size_t i = h_.count - 1;
    1457            9 :     auto const ft = h_.tab();
    1458            9 :     auto const* p = h_.cbuf + h_.prefix;
    1459           36 :     while(i > i0)
    1460              :     {
    1461              :         core::string_view s(
    1462           27 :             p + ft[i].np, ft[i].nn);
    1463           27 :         if(s == name)
    1464              :         {
    1465            9 :             raw_erase(i);
    1466            9 :             ++n;
    1467              :         }
    1468              :         // go backwards to
    1469              :         // reduce memmoves
    1470           27 :         --i;
    1471              :     }
    1472            9 :     raw_erase(i0);
    1473            9 :     return n;
    1474              : }
    1475              : 
    1476              : // return i-th field absolute offset
    1477              : std::size_t
    1478          775 : fields_base::
    1479              : offset(
    1480              :     std::size_t i) const noexcept
    1481              : {
    1482          775 :     if(i == 0)
    1483          310 :         return h_.prefix;
    1484          465 :     if(i < h_.count)
    1485          219 :         return h_.prefix + h_.tab()[i].np;
    1486              :     // make final CRLF the last "field"
    1487          246 :     return h_.size - 2;
    1488              : }
    1489              : 
    1490              : // return i-th field absolute length
    1491              : std::size_t
    1492           39 : fields_base::
    1493              : length(
    1494              :     std::size_t i) const noexcept
    1495              : {
    1496              :     return
    1497           39 :         offset(i + 1) -
    1498           39 :         offset(i);
    1499              : }
    1500              : 
    1501              : } // http_proto
    1502              : } // boost
        

Generated by: LCOV version 2.1