GCC Code Coverage Report


Directory: ./
File: libs/http/src/server/range_parser.cpp
Date: 2026-01-20 00:11:35
Exec Total Coverage
Lines: 0 91 0.0%
Functions: 0 4 0.0%
Branches: 0 83 0.0%

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