LCOV - code coverage report
Current view: top level - libs/http/src/server - range_parser.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 0.0 % 91 0
Test Date: 2026-01-20 00:11:34 Functions: 0.0 % 4 0

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot 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/range_parser.hpp>
      11              : #include <algorithm>
      12              : #include <cctype>
      13              : #include <charconv>
      14              : 
      15              : namespace boost {
      16              : namespace http {
      17              : 
      18              : namespace {
      19              : 
      20              : // Skip whitespace
      21              : void
      22            0 : skip_ws( core::string_view& s ) noexcept
      23              : {
      24            0 :     while( ! s.empty() && std::isspace(
      25            0 :         static_cast<unsigned char>( s.front() ) ) )
      26            0 :         s.remove_prefix( 1 );
      27            0 : }
      28              : 
      29              : // Parse integer
      30              : bool
      31            0 : parse_int( core::string_view& s, std::int64_t& out ) noexcept
      32              : {
      33            0 :     skip_ws( s );
      34            0 :     if( s.empty() )
      35            0 :         return false;
      36              : 
      37            0 :     auto const* begin = s.data();
      38            0 :     auto const* end = s.data() + s.size();
      39            0 :     auto [ptr, ec] = std::from_chars( begin, end, out );
      40            0 :     if( ec != std::errc() || ptr == begin )
      41            0 :         return false;
      42              : 
      43            0 :     s.remove_prefix( static_cast<std::size_t>( ptr - begin ) );
      44            0 :     return true;
      45              : }
      46              : 
      47              : // Parse a single range spec: "start-end" or "-suffix" or "start-"
      48              : bool
      49            0 : parse_range_spec(
      50              :     core::string_view& s,
      51              :     std::int64_t size,
      52              :     byte_range& out )
      53              : {
      54            0 :     skip_ws( s );
      55            0 :     if( s.empty() )
      56            0 :         return false;
      57              : 
      58            0 :     std::int64_t start = -1;
      59            0 :     std::int64_t end = -1;
      60              : 
      61              :     // Check for suffix range: "-suffix"
      62            0 :     if( s.front() == '-' )
      63              :     {
      64            0 :         s.remove_prefix( 1 );
      65              :         std::int64_t suffix;
      66            0 :         if( ! parse_int( s, suffix ) || suffix < 0 )
      67            0 :             return false;
      68              : 
      69              :         // Last 'suffix' bytes
      70            0 :         if( suffix == 0 )
      71            0 :             return false;
      72              : 
      73            0 :         if( suffix > size )
      74            0 :             suffix = size;
      75              : 
      76            0 :         out.start = size - suffix;
      77            0 :         out.end = size - 1;
      78            0 :         return true;
      79              :     }
      80              : 
      81              :     // Parse start
      82            0 :     if( ! parse_int( s, start ) || start < 0 )
      83            0 :         return false;
      84              : 
      85            0 :     skip_ws( s );
      86            0 :     if( s.empty() || s.front() != '-' )
      87            0 :         return false;
      88              : 
      89            0 :     s.remove_prefix( 1 ); // consume '-'
      90              : 
      91              :     // Check for "start-" (open-ended)
      92            0 :     skip_ws( s );
      93            0 :     if( s.empty() || s.front() == ',' )
      94              :     {
      95              :         // Open-ended: start to end of file
      96            0 :         out.start = start;
      97            0 :         out.end = size - 1;
      98            0 :         return start < size;
      99              :     }
     100              : 
     101              :     // Parse end
     102            0 :     if( ! parse_int( s, end ) || end < 0 )
     103            0 :         return false;
     104              : 
     105              :     // Validate
     106            0 :     if( start > end )
     107            0 :         return false;
     108              : 
     109            0 :     out.start = start;
     110            0 :     out.end = ( std::min )( end, size - 1 );
     111              : 
     112            0 :     return start < size;
     113              : }
     114              : 
     115              : } // (anon)
     116              : 
     117              : range_result
     118            0 : parse_range( std::int64_t size, core::string_view header )
     119              : {
     120            0 :     range_result result;
     121            0 :     result.type = range_result_type::malformed;
     122              : 
     123            0 :     if( size <= 0 )
     124              :     {
     125            0 :         result.type = range_result_type::unsatisfiable;
     126            0 :         return result;
     127              :     }
     128              : 
     129              :     // Must start with "bytes="
     130            0 :     skip_ws( header );
     131            0 :     if( header.size() < 6 )
     132            0 :         return result;
     133              : 
     134              :     // Case-insensitive "bytes=" check
     135            0 :     auto prefix = header.substr( 0, 6 );
     136            0 :     bool is_bytes = true;
     137            0 :     for( std::size_t i = 0; i < 5; ++i )
     138              :     {
     139            0 :         char c = static_cast<char>( std::tolower(
     140            0 :             static_cast<unsigned char>( prefix[i] ) ) );
     141            0 :         if( c != "bytes"[i] )
     142              :         {
     143            0 :             is_bytes = false;
     144            0 :             break;
     145              :         }
     146              :     }
     147            0 :     if( ! is_bytes || prefix[5] != '=' )
     148            0 :         return result;
     149              : 
     150            0 :     header.remove_prefix( 6 );
     151              : 
     152              :     // Parse range specs
     153            0 :     bool any_satisfiable = false;
     154              : 
     155            0 :     while( ! header.empty() )
     156              :     {
     157            0 :         skip_ws( header );
     158            0 :         if( header.empty() )
     159            0 :             break;
     160              : 
     161            0 :         byte_range range;
     162            0 :         if( parse_range_spec( header, size, range ) )
     163              :         {
     164            0 :             result.ranges.push_back( range );
     165            0 :             any_satisfiable = true;
     166              :         }
     167              : 
     168            0 :         skip_ws( header );
     169            0 :         if( ! header.empty() )
     170              :         {
     171            0 :             if( header.front() == ',' )
     172            0 :                 header.remove_prefix( 1 );
     173              :             else
     174            0 :                 break; // Invalid
     175              :         }
     176              :     }
     177              : 
     178            0 :     if( result.ranges.empty() )
     179              :     {
     180            0 :         result.type = range_result_type::unsatisfiable;
     181              :     }
     182            0 :     else if( any_satisfiable )
     183              :     {
     184            0 :         result.type = range_result_type::ok;
     185              :     }
     186              : 
     187            0 :     return result;
     188            0 : }
     189              : 
     190              : } // http
     191              : } // boost
        

Generated by: LCOV version 2.3