LCOV - code coverage report
Current view: top level - src/server - range_parser.cpp (source / functions) Coverage Total Missed
Test: coverage_remapped.info Lines: 0.0 % 91 91
Test Date: 2026-06-13 19:44:58 Functions: 0.0 % 4 4

           TLA  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/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 MIS           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