LCOV - code coverage report
Current view: top level - libs/http_proto/src - multipart_form_sink.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 62.1 % 177 110
Test Date: 2025-12-23 17:59:40 Functions: 80.0 % 5 4

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2025 Mohammad Nejati
       3              : //
       4              : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       5              : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       6              : //
       7              : // Official repository: https://github.com/cppalliance/http_proto
       8              : //
       9              : 
      10              : #include <boost/http_proto/multipart_form_sink.hpp>
      11              : #include <boost/http_proto/rfc/content_disposition_rule.hpp>
      12              : 
      13              : #include "src/rfc/detail/rules.hpp"
      14              : 
      15              : #include <boost/url/grammar/ci_string.hpp>
      16              : 
      17              : namespace boost {
      18              : namespace http_proto {
      19              : 
      20              : namespace {
      21              : std::string
      22            1 : unquote(quoted_token_view qtv)
      23              : {
      24            1 :     if(qtv.has_escapes())
      25            0 :         return { qtv.begin(), qtv.end() };
      26              : 
      27            1 :     auto rs = std::string{};
      28            9 :     for(auto it = qtv.begin(); it != qtv.end(); it++)
      29              :     {
      30            8 :         if(*it == '\\')
      31            0 :             it++;
      32            8 :         rs.push_back(*it);
      33              :     }
      34            1 :     return rs;
      35            1 : }
      36              : } // namespace
      37              : 
      38            1 : multipart_form_sink::
      39              : multipart_form_sink(
      40            1 :     core::string_view boundary)
      41              : {
      42            1 :     leftover_.append("\r\n");
      43            1 :     needle_.append("\r\n--");
      44            1 :     needle_.append(boundary);
      45            1 : }
      46              : 
      47              : auto
      48            0 : multipart_form_sink::
      49              : parts() const noexcept
      50              :     -> boost::span<part const>
      51              : {
      52            0 :     return parts_;
      53              : }
      54              : 
      55              : auto
      56            1 : multipart_form_sink::
      57              : on_write(
      58              :     buffers::const_buffer b,
      59              :     bool more) -> results
      60              : {
      61            1 :     system::error_code ec;
      62              :     core::string_view sv(
      63            1 :         static_cast<char const*>(b.data()), b.size());
      64              : 
      65            1 :     if(!leftover_.empty())
      66              :     {
      67            1 :         for(std::size_t i = 0; i < leftover_.size(); ++i)
      68              :         {
      69            1 :             core::string_view nd(needle_);
      70            1 :             if(!nd.starts_with({ &leftover_[i], &*leftover_.cend() }))
      71            0 :                 continue;
      72            1 :             nd.remove_prefix(leftover_.size() - i);
      73            1 :             if(sv.size() >= nd.size())
      74              :             {
      75            1 :                 if(!sv.starts_with(nd))
      76            0 :                     continue;
      77            1 :                 parse(true, { leftover_.data(), i }, ec);
      78            1 :                 if(ec.failed())
      79            0 :                     goto upcall;
      80            1 :                 leftover_.clear();
      81            1 :                 sv.remove_prefix(nd.size());
      82            1 :                 goto loop_sv;
      83              :             }
      84            0 :             if(!more)
      85            0 :                 break;
      86            0 :             if(!nd.starts_with(sv))
      87            0 :                 continue;
      88            0 :             parse(false, { leftover_.data(), i }, ec);
      89            0 :             if(ec.failed())
      90            0 :                 goto upcall;
      91            0 :             leftover_.erase(0, i);
      92            0 :             leftover_.append(sv);
      93            0 :             return { ec , sv.size() };
      94              :         }
      95              :         // leftover_ cannot contain a needle
      96            0 :         parse(false, leftover_, ec);
      97            0 :         if(ec.failed())
      98            0 :             goto upcall;
      99            0 :         leftover_.clear();
     100              :     }
     101              : 
     102            1 : loop_sv:
     103           62 :     for(char const* it = sv.begin(); it != sv.end(); ++it)
     104              :     {
     105           62 :         if(*it == '\r')
     106              :         {
     107            5 :             core::string_view rm(it, sv.end());
     108            5 :             if(rm.size() >= needle_.size())
     109              :             {
     110            4 :                 if(std::equal(
     111            4 :                     needle_.rbegin(),
     112            4 :                     needle_.rend(),
     113            8 :                     rm.rend() - needle_.size()))
     114              :                 {
     115            1 :                     parse(true, { sv.begin(), it }, ec);
     116            1 :                     if(ec.failed())
     117            0 :                         goto upcall;
     118            1 :                     sv = { it + needle_.size(), sv.end() };
     119            1 :                     goto loop_sv;
     120              :                 }
     121            3 :                 continue;
     122              :             }
     123            1 :             if(!more)
     124            1 :                 break;
     125            0 :             if(!core::string_view(needle_).starts_with(rm))
     126            0 :                 continue;
     127            0 :             parse(false, { sv.begin(), it }, ec);
     128            0 :             if(ec.failed())
     129            0 :                 goto upcall;
     130            0 :             leftover_.append(it, sv.end());
     131            0 :             sv = {};
     132            0 :             goto upcall;
     133              :         }
     134              :     }
     135            1 :     parse(false, sv, ec);
     136            1 :     if(!ec.failed())
     137            1 :         sv= {};
     138              : 
     139            0 : upcall:
     140            1 :     return { ec, b.size() - sv.size() };
     141              : }
     142              : 
     143              : void
     144            3 : multipart_form_sink::
     145              : parse(
     146              :     bool match,
     147              :     core::string_view b,
     148              :     system::error_code& ec)
     149              : {
     150            3 : loop:
     151            6 :     switch(state_)
     152              :     {
     153            1 :     case state::preamble:
     154            1 :         if(match)
     155            1 :             state_ = state::post_boundary0;
     156            1 :         break;
     157            2 :     case state::post_boundary0:
     158            2 :         if(b.empty())
     159            0 :             break;
     160            2 :         switch(b[0])
     161              :         {
     162            1 :         case '\r':
     163            1 :             state_ = state::post_boundary1;
     164            1 :             break;
     165            1 :         case '-':
     166            1 :             state_ = state::post_boundary2;
     167            1 :             break;
     168            0 :         default:
     169            0 :             ec = http_proto::error::bad_payload;
     170            0 :             return;
     171              :         }
     172            2 :         b.remove_prefix(1);
     173            2 :         goto loop;
     174            1 :     case state::post_boundary1:
     175            1 :         if(b.empty())
     176            0 :             break;
     177            1 :         if(b[0] != '\n')
     178              :         {
     179            0 :             ec = http_proto::error::bad_payload;
     180            0 :             return;
     181              :         }
     182            1 :         b.remove_prefix(1);
     183            1 :         state_ = state::header;
     184            1 :         goto loop;
     185            1 :     case state::post_boundary2:
     186            1 :         if(b.empty())
     187            0 :             break;
     188            1 :         if(b[0] != '-')
     189              :         {
     190            0 :             ec = http_proto::error::bad_payload;
     191            0 :             return;
     192              :         }
     193            1 :         b.remove_prefix(1);
     194            1 :         state_ = state::finished;
     195            1 :         break;
     196            1 :     case state::header:
     197              :     {
     198            1 :         auto const s0 = header_.size();
     199            1 :         header_.append(b);
     200            1 :         auto const pos = header_.find("\r\n\r\n");
     201            1 :         if(pos == std::string::npos)
     202            0 :             break;
     203            1 :         header_.erase(pos + 4);
     204            1 :         b.remove_prefix(header_.size() - s0);
     205              : 
     206              :         // parse fields
     207              : 
     208              :         auto const fields = grammar::parse(
     209            1 :             header_,
     210            1 :             grammar::range_rule(detail::field_rule));
     211              : 
     212            1 :         if(!fields)
     213              :         {
     214            0 :             ec = http_proto::error::bad_payload;
     215            0 :             return;
     216              :         }
     217              : 
     218            1 :         part part{};
     219              : 
     220            2 :         for(auto&& field : fields.value())
     221              :         {
     222            1 :             if(field.has_obs_fold)
     223            0 :                 continue; // TODO
     224              : 
     225            1 :             if(grammar::ci_is_equal(field.name, "Content-Disposition"))
     226              :             {
     227              :                 // parse Content-Disposition
     228            1 :                 auto cd = grammar::parse(field.value, content_disposition_rule);
     229            1 :                 if(!cd || !grammar::ci_is_equal(cd->type, "form-data"))
     230              :                 {
     231            0 :                     ec = http_proto::error::bad_payload;
     232            0 :                     return;
     233              :                 }
     234            2 :                 for(auto && param : cd->params)
     235              :                 {
     236            1 :                     if(grammar::ci_is_equal(param.name, "name"))
     237              :                     {
     238            1 :                         part.name = unquote(param.value);
     239              :                     }
     240            0 :                     else if(grammar::ci_is_equal(param.name, "filename"))
     241              :                     {
     242            0 :                         auto& ff = part.content.emplace<file_field>();
     243            0 :                         ff.name = unquote(param.value);
     244              : 
     245              :                         // TODO
     246            0 :                         ff.path = ff.name;
     247            0 :                         file_.open(
     248              :                             ff.path.c_str(),
     249              :                             capy::file_mode::write_new,
     250              :                             ec);
     251            0 :                         if(ec.failed())
     252            0 :                             return;
     253              :                     }
     254              :                 }
     255            1 :             }
     256            0 :             else if(grammar::ci_is_equal(field.name, "Content-Type"))
     257              :             {
     258            0 :                 part.content_type = field.value;
     259              :             }
     260              :         }
     261              : 
     262            1 :         if(part.name.empty())
     263              :         {
     264            0 :             ec = http_proto::error::bad_payload;
     265            0 :             return;
     266              :         }
     267              : 
     268            1 :         header_.clear();
     269            1 :         parts_.push_back(std::move(part));
     270            1 :         state_ = state::content;
     271              :         BOOST_FALLTHROUGH;
     272            1 :     }
     273              :     case state::content:
     274            1 :         if(auto* p = variant2::get_if<text_field>(
     275            1 :             &parts_.back().content))
     276              :         {
     277            1 :             p->data.append(b);
     278              :         }
     279              :         else
     280              :         {
     281            0 :             file_.write(b.data(), b.size(), ec);
     282            0 :             if(ec.failed())
     283            0 :                 return;
     284            0 :             if(match)
     285              :             {
     286            0 :                 file_.close(ec);
     287            0 :                 if(ec.failed())
     288            0 :                     return;
     289              :             }
     290              :         }
     291            1 :         if(match)
     292            1 :             state_ = state::post_boundary0;
     293            1 :         break;
     294            0 :     case state::finished:
     295            0 :         break;
     296              :     }
     297              : }
     298              : 
     299              : } // http_proto
     300              : } // boost
        

Generated by: LCOV version 2.1