LCOV - code coverage report
Current view: top level - src/server - accepts.cpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 86.1 % 316 272
Test Date: 2026-02-09 01:37:05 Functions: 100.0 % 24 24

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
       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
       8              : //
       9              : 
      10              : #include <boost/http/server/accepts.hpp>
      11              : #include <boost/http/server/mime_types.hpp>
      12              : #include <boost/http/field.hpp>
      13              : #include <algorithm>
      14              : 
      15              : namespace boost {
      16              : namespace http {
      17              : 
      18              : namespace {
      19              : 
      20              : //----------------------------------------------------------
      21              : // Helpers
      22              : //----------------------------------------------------------
      23              : 
      24              : std::string_view
      25          142 : trim_ows( std::string_view s ) noexcept
      26              : {
      27          338 :     while( ! s.empty() &&
      28          169 :         ( s.front() == ' ' || s.front() == '\t' ) )
      29           27 :         s.remove_prefix( 1 );
      30          284 :     while( ! s.empty() &&
      31          142 :         ( s.back() == ' ' || s.back() == '\t' ) )
      32            0 :         s.remove_suffix( 1 );
      33          142 :     return s;
      34              : }
      35              : 
      36              : bool
      37           75 : iequals(
      38              :     std::string_view a,
      39              :     std::string_view b ) noexcept
      40              : {
      41           75 :     if( a.size() != b.size() )
      42           26 :         return false;
      43          202 :     for( std::size_t i = 0; i < a.size(); ++i )
      44              :     {
      45          174 :         unsigned char ca = a[i];
      46          174 :         unsigned char cb = b[i];
      47          174 :         if( ca >= 'A' && ca <= 'Z' )
      48            0 :             ca += 32;
      49          174 :         if( cb >= 'A' && cb <= 'Z' )
      50            0 :             cb += 32;
      51          174 :         if( ca != cb )
      52           21 :             return false;
      53              :     }
      54           28 :     return true;
      55              : }
      56              : 
      57              : // Returns quality as integer 0-1000
      58              : int
      59           16 : parse_q( std::string_view s ) noexcept
      60              : {
      61           16 :     s = trim_ows( s );
      62           16 :     if( s.empty() )
      63            0 :         return 1000;
      64           16 :     if( s[0] == '1' )
      65            0 :         return 1000;
      66           16 :     if( s[0] != '0' )
      67            1 :         return 0;
      68           15 :     if( s.size() < 2 || s[1] != '.' )
      69            1 :         return 0;
      70           14 :     int result = 0;
      71           14 :     int mult = 100;
      72           14 :     for( std::size_t i = 2;
      73           28 :         i < s.size() && i < 5; ++i )
      74              :     {
      75           14 :         if( s[i] < '0' || s[i] > '9' )
      76            0 :             break;
      77           14 :         result += ( s[i] - '0' ) * mult;
      78           14 :         mult /= 10;
      79              :     }
      80           14 :     return result;
      81              : }
      82              : 
      83              : // Extract q-value from parameters after first semicolon
      84              : int
      85           16 : extract_q( std::string_view params ) noexcept
      86              : {
      87           16 :     while( ! params.empty() )
      88              :     {
      89           16 :         auto semi = params.find( ';' );
      90           16 :         auto param = trim_ows(
      91              :             semi != std::string_view::npos
      92            0 :                 ? params.substr( 0, semi )
      93              :                 : params );
      94           16 :         if( param.size() >= 2 &&
      95           32 :             ( param[0] == 'q' || param[0] == 'Q' ) &&
      96           16 :             param[1] == '=' )
      97              :         {
      98           16 :             return parse_q( param.substr( 2 ) );
      99              :         }
     100            0 :         if( semi != std::string_view::npos )
     101            0 :             params.remove_prefix( semi + 1 );
     102              :         else
     103            0 :             break;
     104              :     }
     105            0 :     return 1000;
     106              : }
     107              : 
     108              : //----------------------------------------------------------
     109              : // Negotiation priority
     110              : //----------------------------------------------------------
     111              : 
     112              : struct priority
     113              : {
     114              :     int q;
     115              :     int specificity;
     116              :     int order;
     117              : };
     118              : 
     119              : bool
     120            6 : is_better(
     121              :     priority const& a,
     122              :     priority const& b ) noexcept
     123              : {
     124            6 :     if( a.q != b.q )
     125            4 :         return a.q > b.q;
     126            2 :     if( a.specificity != b.specificity )
     127            1 :         return a.specificity > b.specificity;
     128            1 :     return a.order < b.order;
     129              : }
     130              : 
     131              : //----------------------------------------------------------
     132              : // Media type parsing (Accept header)
     133              : //----------------------------------------------------------
     134              : 
     135              : struct media_range
     136              : {
     137              :     std::string_view type;
     138              :     std::string_view subtype;
     139              :     std::string_view full;
     140              :     int q;
     141              :     int order;
     142              : };
     143              : 
     144              : std::vector<media_range>
     145           13 : parse_accept( std::string_view header )
     146              : {
     147           13 :     std::vector<media_range> result;
     148           13 :     int order = 0;
     149              : 
     150           35 :     while( ! header.empty() )
     151              :     {
     152           22 :         auto comma = header.find( ',' );
     153              :         auto entry = ( comma != std::string_view::npos )
     154           22 :             ? header.substr( 0, comma )
     155           13 :             : header;
     156           22 :         if( comma != std::string_view::npos )
     157            9 :             header.remove_prefix( comma + 1 );
     158              :         else
     159           13 :             header = {};
     160              : 
     161           22 :         entry = trim_ows( entry );
     162           22 :         if( entry.empty() )
     163            0 :             continue;
     164              : 
     165           22 :         auto semi = entry.find( ';' );
     166           26 :         auto mime_part = trim_ows(
     167              :             semi != std::string_view::npos
     168            4 :                 ? entry.substr( 0, semi )
     169              :                 : entry );
     170              : 
     171           22 :         auto slash = mime_part.find( '/' );
     172           22 :         if( slash == std::string_view::npos )
     173            0 :             continue;
     174              : 
     175           22 :         media_range mr;
     176           22 :         mr.type = mime_part.substr( 0, slash );
     177           22 :         mr.subtype = mime_part.substr( slash + 1 );
     178           22 :         mr.full = mime_part;
     179           22 :         mr.q = ( semi != std::string_view::npos )
     180           22 :             ? extract_q( entry.substr( semi + 1 ) )
     181              :             : 1000;
     182           22 :         mr.order = order++;
     183           22 :         result.push_back( mr );
     184              :     }
     185              : 
     186           13 :     return result;
     187            0 : }
     188              : 
     189              : // Returns specificity (0-6) or -1 for no match
     190              : int
     191           16 : match_media(
     192              :     media_range const& range,
     193              :     std::string_view type,
     194              :     std::string_view subtype ) noexcept
     195              : {
     196           16 :     int s = 0;
     197              : 
     198           16 :     if( range.type == "*" )
     199              :     {
     200              :         // wildcard type
     201              :     }
     202           15 :     else if( iequals( range.type, type ) )
     203              :     {
     204            7 :         s |= 4;
     205              :     }
     206              :     else
     207              :     {
     208            8 :         return -1;
     209              :     }
     210              : 
     211            8 :     if( range.subtype == "*" )
     212              :     {
     213              :         // wildcard subtype
     214              :     }
     215            5 :     else if( iequals( range.subtype, subtype ) )
     216              :     {
     217            5 :         s |= 2;
     218              :     }
     219              :     else
     220              :     {
     221            0 :         return -1;
     222              :     }
     223              : 
     224            8 :     return s;
     225              : }
     226              : 
     227              : //----------------------------------------------------------
     228              : // Simple token parsing (Accept-Encoding/Charset/Language)
     229              : //----------------------------------------------------------
     230              : 
     231              : struct simple_entry
     232              : {
     233              :     std::string_view value;
     234              :     int q;
     235              :     int order;
     236              : };
     237              : 
     238              : std::vector<simple_entry>
     239           15 : parse_simple( std::string_view header )
     240              : {
     241           15 :     std::vector<simple_entry> result;
     242           15 :     int order = 0;
     243              : 
     244           48 :     while( ! header.empty() )
     245              :     {
     246           33 :         auto comma = header.find( ',' );
     247              :         auto entry = ( comma != std::string_view::npos )
     248           33 :             ? header.substr( 0, comma )
     249           15 :             : header;
     250           33 :         if( comma != std::string_view::npos )
     251           18 :             header.remove_prefix( comma + 1 );
     252              :         else
     253           15 :             header = {};
     254              : 
     255           33 :         entry = trim_ows( entry );
     256           33 :         if( entry.empty() )
     257            0 :             continue;
     258              : 
     259           33 :         auto semi = entry.find( ';' );
     260           45 :         auto value = trim_ows(
     261              :             semi != std::string_view::npos
     262           12 :                 ? entry.substr( 0, semi )
     263              :                 : entry );
     264           33 :         if( value.empty() )
     265            0 :             continue;
     266              : 
     267           33 :         simple_entry se;
     268           33 :         se.value = value;
     269           33 :         se.q = ( semi != std::string_view::npos )
     270           33 :             ? extract_q( entry.substr( semi + 1 ) )
     271              :             : 1000;
     272           33 :         se.order = order++;
     273           33 :         result.push_back( se );
     274              :     }
     275              : 
     276           15 :     return result;
     277            0 : }
     278              : 
     279              : //----------------------------------------------------------
     280              : // Matching helpers
     281              : //----------------------------------------------------------
     282              : 
     283              : // Exact or wildcard match (encoding, charset)
     284              : int
     285           25 : match_exact(
     286              :     std::string_view spec,
     287              :     std::string_view offered ) noexcept
     288              : {
     289           25 :     if( iequals( spec, offered ) )
     290            9 :         return 1;
     291           16 :     if( spec == "*" )
     292            1 :         return 0;
     293           15 :     return -1;
     294              : }
     295              : 
     296              : // Language prefix: "en-US" -> "en"
     297              : std::string_view
     298           17 : lang_prefix( std::string_view tag ) noexcept
     299              : {
     300           17 :     auto dash = tag.find( '-' );
     301           17 :     if( dash != std::string_view::npos )
     302            3 :         return tag.substr( 0, dash );
     303           14 :     return tag;
     304              : }
     305              : 
     306              : // Language match with prefix support
     307              : int
     308           13 : match_language(
     309              :     std::string_view spec,
     310              :     std::string_view offered ) noexcept
     311              : {
     312           13 :     if( iequals( spec, offered ) )
     313            4 :         return 4;
     314            9 :     if( iequals( lang_prefix( spec ), offered ) )
     315            1 :         return 2;
     316            8 :     if( iequals( spec, lang_prefix( offered ) ) )
     317            2 :         return 1;
     318            6 :     if( spec == "*" )
     319            0 :         return 0;
     320            6 :     return -1;
     321              : }
     322              : 
     323              : //----------------------------------------------------------
     324              : // Generic negotiation for simple headers
     325              : //----------------------------------------------------------
     326              : 
     327              : template< class MatchFn >
     328              : std::string_view
     329           12 : negotiate(
     330              :     std::vector<simple_entry> const& entries,
     331              :     std::initializer_list<std::string_view> offered,
     332              :     MatchFn match )
     333              : {
     334           12 :     std::string_view best_val;
     335           12 :     priority best_pri{ -1, -1, 0 };
     336           12 :     bool found = false;
     337              : 
     338           30 :     for( auto const& o : offered )
     339              :     {
     340           18 :         priority pri{ -1, -1, 0 };
     341           18 :         bool matched = false;
     342              : 
     343           56 :         for( auto const& e : entries )
     344              :         {
     345           38 :             if( e.q <= 0 )
     346           21 :                 continue;
     347           38 :             auto s = match( e.value, o );
     348           38 :             if( s < 0 )
     349           21 :                 continue;
     350           17 :             priority p{ e.q, s, e.order };
     351           17 :             if( ! matched ||
     352            0 :                 p.specificity > pri.specificity ||
     353            0 :                 ( p.specificity == pri.specificity &&
     354            0 :                     p.q > pri.q ) ||
     355            0 :                 ( p.specificity == pri.specificity &&
     356            0 :                     p.q == pri.q &&
     357            0 :                     p.order < pri.order ) )
     358              :             {
     359           17 :                 pri = p;
     360           17 :                 matched = true;
     361              :             }
     362              :         }
     363              : 
     364           18 :         if( ! matched || pri.q <= 0 )
     365            1 :             continue;
     366              : 
     367           17 :         if( ! found || is_better( pri, best_pri ) )
     368              :         {
     369           13 :             best_val = o;
     370           13 :             best_pri = pri;
     371           13 :             found = true;
     372              :         }
     373              :     }
     374              : 
     375           12 :     return found ? best_val : std::string_view{};
     376              : }
     377              : 
     378              : // Return sorted values from simple entries
     379              : std::vector<std::string_view>
     380            3 : sorted_values(
     381              :     std::vector<simple_entry>& entries )
     382              : {
     383            3 :     std::sort( entries.begin(), entries.end(),
     384           13 :         []( simple_entry const& a,
     385              :             simple_entry const& b )
     386              :         {
     387           13 :             if( a.q != b.q )
     388           11 :                 return a.q > b.q;
     389            2 :             return a.order < b.order;
     390              :         });
     391              : 
     392            3 :     std::vector<std::string_view> result;
     393            3 :     result.reserve( entries.size() );
     394           12 :     for( auto const& e : entries )
     395              :     {
     396            9 :         if( e.q <= 0 )
     397            0 :             continue;
     398            9 :         result.push_back( e.value );
     399              :     }
     400            3 :     return result;
     401            0 : }
     402              : 
     403              : } // (anon)
     404              : 
     405              : //----------------------------------------------------------
     406              : 
     407           26 : accepts::accepts(
     408           26 :     fields_base const& fields ) noexcept
     409           26 :     : fields_( fields )
     410              : {
     411           26 : }
     412              : 
     413              : std::string_view
     414           12 : accepts::type(
     415              :     std::initializer_list<
     416              :         std::string_view> offered ) const
     417              : {
     418           12 :     if( offered.size() == 0 )
     419            1 :         return {};
     420              : 
     421           11 :     auto accept = fields_.value_or(
     422              :         field::accept, "" );
     423              : 
     424           11 :     if( accept.empty() )
     425            1 :         return *offered.begin();
     426              : 
     427           10 :     auto ranges = parse_accept( accept );
     428           10 :     if( ranges.empty() )
     429            0 :         return *offered.begin();
     430              : 
     431           10 :     std::string_view best_val;
     432           10 :     priority best_pri{ -1, -1, 0 };
     433           10 :     bool found = false;
     434              : 
     435           22 :     for( auto const& o : offered )
     436              :     {
     437              :         // Convert extension to MIME if needed
     438           12 :         std::string_view mime_str = o;
     439           12 :         if( o.find( '/' ) == std::string_view::npos )
     440              :         {
     441            9 :             auto looked = mime_types::lookup( o );
     442            9 :             if( ! looked.empty() )
     443            8 :                 mime_str = looked;
     444              :             else
     445            1 :                 continue;
     446              :         }
     447              : 
     448           11 :         auto slash = mime_str.find( '/' );
     449           11 :         if( slash == std::string_view::npos )
     450            0 :             continue;
     451              : 
     452           11 :         auto type = mime_str.substr( 0, slash );
     453           11 :         auto subtype = mime_str.substr( slash + 1 );
     454              : 
     455              :         // Find best matching range for this type
     456           11 :         priority pri{ -1, -1, 0 };
     457           11 :         bool matched = false;
     458              : 
     459           29 :         for( auto const& r : ranges )
     460              :         {
     461           18 :             if( r.q <= 0 )
     462           10 :                 continue;
     463           16 :             auto s = match_media( r, type, subtype );
     464           16 :             if( s < 0 )
     465            8 :                 continue;
     466            8 :             priority p{ r.q, s, r.order };
     467            8 :             if( ! matched ||
     468            0 :                 p.specificity > pri.specificity ||
     469            0 :                 ( p.specificity == pri.specificity &&
     470            0 :                     p.q > pri.q ) ||
     471            0 :                 ( p.specificity == pri.specificity &&
     472            0 :                     p.q == pri.q &&
     473            0 :                     p.order < pri.order ) )
     474              :             {
     475            8 :                 pri = p;
     476            8 :                 matched = true;
     477              :             }
     478              :         }
     479              : 
     480           11 :         if( ! matched || pri.q <= 0 )
     481            3 :             continue;
     482              : 
     483            8 :         if( ! found || is_better( pri, best_pri ) )
     484              :         {
     485            8 :             best_val = o;
     486            8 :             best_pri = pri;
     487            8 :             found = true;
     488              :         }
     489              :     }
     490              : 
     491           10 :     return found ? best_val : std::string_view{};
     492           10 : }
     493              : 
     494              : std::vector<std::string_view>
     495            4 : accepts::types() const
     496              : {
     497            4 :     auto accept = fields_.value_or(
     498              :         field::accept, "" );
     499            4 :     if( accept.empty() )
     500            1 :         return {};
     501              : 
     502            3 :     auto ranges = parse_accept( accept );
     503              : 
     504            3 :     std::sort( ranges.begin(), ranges.end(),
     505            6 :         []( media_range const& a,
     506              :             media_range const& b )
     507              :         {
     508            6 :             if( a.q != b.q )
     509            6 :                 return a.q > b.q;
     510            0 :             return a.order < b.order;
     511              :         });
     512              : 
     513            3 :     std::vector<std::string_view> result;
     514            3 :     result.reserve( ranges.size() );
     515            9 :     for( auto const& r : ranges )
     516              :     {
     517            6 :         if( r.q <= 0 )
     518            1 :             continue;
     519            5 :         result.push_back( r.full );
     520              :     }
     521            3 :     return result;
     522            3 : }
     523              : 
     524              : std::string_view
     525            6 : accepts::encoding(
     526              :     std::initializer_list<
     527              :         std::string_view> offered ) const
     528              : {
     529            6 :     if( offered.size() == 0 )
     530            0 :         return {};
     531              : 
     532            6 :     auto header = fields_.value_or(
     533              :         field::accept_encoding, "" );
     534              : 
     535            6 :     if( header.empty() )
     536            1 :         return *offered.begin();
     537              : 
     538            5 :     auto entries = parse_simple( header );
     539            5 :     if( entries.empty() )
     540            0 :         return *offered.begin();
     541              : 
     542            5 :     return negotiate( entries, offered, match_exact );
     543            5 : }
     544              : 
     545              : std::vector<std::string_view>
     546            2 : accepts::encodings() const
     547              : {
     548            2 :     auto header = fields_.value_or(
     549              :         field::accept_encoding, "" );
     550            2 :     if( header.empty() )
     551            1 :         return {};
     552              : 
     553            1 :     auto entries = parse_simple( header );
     554            1 :     return sorted_values( entries );
     555            1 : }
     556              : 
     557              : std::string_view
     558            3 : accepts::charset(
     559              :     std::initializer_list<
     560              :         std::string_view> offered ) const
     561              : {
     562            3 :     if( offered.size() == 0 )
     563            0 :         return {};
     564              : 
     565            3 :     auto header = fields_.value_or(
     566              :         field::accept_charset, "" );
     567              : 
     568            3 :     if( header.empty() )
     569            1 :         return *offered.begin();
     570              : 
     571            2 :     auto entries = parse_simple( header );
     572            2 :     if( entries.empty() )
     573            0 :         return *offered.begin();
     574              : 
     575            2 :     return negotiate( entries, offered, match_exact );
     576            2 : }
     577              : 
     578              : std::vector<std::string_view>
     579            1 : accepts::charsets() const
     580              : {
     581            1 :     auto header = fields_.value_or(
     582              :         field::accept_charset, "" );
     583            1 :     if( header.empty() )
     584            0 :         return {};
     585              : 
     586            1 :     auto entries = parse_simple( header );
     587            1 :     return sorted_values( entries );
     588            1 : }
     589              : 
     590              : std::string_view
     591            6 : accepts::language(
     592              :     std::initializer_list<
     593              :         std::string_view> offered ) const
     594              : {
     595            6 :     if( offered.size() == 0 )
     596            0 :         return {};
     597              : 
     598            6 :     auto header = fields_.value_or(
     599              :         field::accept_language, "" );
     600              : 
     601            6 :     if( header.empty() )
     602            1 :         return *offered.begin();
     603              : 
     604            5 :     auto entries = parse_simple( header );
     605            5 :     if( entries.empty() )
     606            0 :         return *offered.begin();
     607              : 
     608            5 :     return negotiate( entries, offered, match_language );
     609            5 : }
     610              : 
     611              : std::vector<std::string_view>
     612            1 : accepts::languages() const
     613              : {
     614            1 :     auto header = fields_.value_or(
     615              :         field::accept_language, "" );
     616            1 :     if( header.empty() )
     617            0 :         return {};
     618              : 
     619            1 :     auto entries = parse_simple( header );
     620            1 :     return sorted_values( entries );
     621            1 : }
     622              : 
     623              : } // http
     624              : } // boost
        

Generated by: LCOV version 2.3