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