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