GCC Code Coverage Report


Directory: ./
File: libs/http/src/server/detail/route_rule.hpp
Date: 2026-01-20 00:11:35
Exec Total Coverage
Lines: 15 78 19.2%
Functions: 1 6 16.7%
Branches: 6 67 9.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 #ifndef BOOST_HTTP_SERVER_DETAIL_ROUTE_RULE_HPP
11 #define BOOST_HTTP_SERVER_DETAIL_ROUTE_RULE_HPP
12
13 #include <boost/http/detail/config.hpp>
14 #include <boost/url/decode_view.hpp>
15 #include <boost/url/segments_encoded_view.hpp>
16 #include <boost/url/grammar/alpha_chars.hpp>
17 #include <boost/url/grammar/charset.hpp>
18 #include <boost/url/grammar/parse.hpp>
19 #include <vector>
20
21 #include "src/server/detail/stable_string.hpp"
22
23 namespace boost {
24 namespace http {
25 namespace detail {
26
27 namespace grammar = urls::grammar;
28
29 /** Rule for parsing a non-empty token of chars
30
31 @par Requires
32 @code
33 std::is_empty<CharSet>::value == true
34 @endcode
35 */
36 template<class CharSet>
37 struct token_rule
38 {
39 using value_type = core::string_view;
40
41 auto
42 parse(
43 char const*& it,
44 char const* end) const noexcept ->
45 system::result<value_type>
46 {
47 static_assert(std::is_empty<CharSet>::value, "");
48 if(it == end)
49 return grammar::error::syntax;
50 auto it1 = grammar::find_if_not(it, end, CharSet{});
51 if(it1 == it)
52 return grammar::error::mismatch;
53 auto s = core::string_view(it, it1);
54 it = it1;
55 return s;
56 }
57 };
58
59 //------------------------------------------------
60
61 /*
62 route-pattern = *( "/" segment ) [ "/" ]
63 segment = literal-segment / param-segment
64 literal-segment = 1*( unreserved-char )
65 unreserved-char = %x21-2F / %x30-3B / %x3D-5A / %x5C-7E ; all printable except slash
66 param-segment = param-prefix param-name [ constraint ] [ modifier ]
67 param-prefix = ":" / "*" ; either named param ":" or named wildcard "*"
68 param-name = ident
69 constraint = "(" 1*( constraint-char ) ")"
70 modifier = "?" / "*" / "+"
71 ident = ALPHA *( ALPHA / DIGIT / "_" )
72 constraint-char = %x20-7E except ( ")" )
73 */
74
75 //------------------------------------------------
76
77 struct unreserved_char
78 {
79 constexpr
80 bool
81 operator()(char ch) const noexcept
82 {
83 return ch != '/' && (
84 (ch >= 0x21 && ch <= 0x2F) ||
85 (ch >= 0x30 && ch <= 0x3B) ||
86 (ch >= 0x3D && ch <= 0x5A) ||
87 (ch >= 0x5C && ch <= 0x7E));
88 }
89 };
90
91 struct constraint_char
92 {
93 constexpr
94 bool
95 operator()(char ch) const noexcept
96 {
97 return ch >= 0x20 && ch <= 0x7E && ch != ')';
98 }
99 };
100
101 struct ident_char
102 {
103 constexpr
104 bool
105 operator()(char ch) const noexcept
106 {
107 return
108 (ch >= 'a' && ch <= 'z') ||
109 (ch >= '0' && ch <= '9') ||
110 (ch >= 'A' && ch <= 'Z') ||
111 (ch == '_');
112 }
113 };
114
115 constexpr struct
116 {
117 // empty for no constraint
118 using value_type = core::string_view;
119
120 auto
121 parse(
122 char const*& it,
123 char const* end) const noexcept ->
124 system::result<value_type>
125 {
126 if(it == end || *it != '(')
127 return "";
128 if(it == end)
129 BOOST_HTTP_RETURN_EC(
130 grammar::error::syntax);
131 auto it0 = it;
132 it = grammar::find_if_not(
133 it, end, constraint_char{});
134 if(it - it0 <= 1)
135 {
136 // too small
137 it = it0;
138 BOOST_HTTP_RETURN_EC(
139 grammar::error::syntax);
140 }
141 if(it == end)
142 {
143 it = it0;
144 BOOST_HTTP_RETURN_EC(
145 grammar::error::syntax);
146 }
147 if(*it != ')')
148 {
149 it0 = it;
150 BOOST_HTTP_RETURN_EC(
151 grammar::error::syntax);
152 }
153 return core::string_view(++it0, it++);
154 }
155 } constraint_rule{};
156
157 constexpr struct
158 {
159 using value_type = core::string_view;
160
161 auto
162 parse(
163 char const*& it,
164 char const* end) const noexcept ->
165 system::result<value_type>
166 {
167 if(it == end)
168 BOOST_HTTP_RETURN_EC(
169 grammar::error::syntax);
170 if(! grammar::alpha_chars(*it))
171 BOOST_HTTP_RETURN_EC(
172 grammar::error::syntax);
173 auto it0 = it++;
174 it = grammar::find_if_not(
175 it, end, ident_char{});
176 return core::string_view(it0, it);
177 }
178 } param_name_rule{};
179
180 //------------------------------------------------
181
182 /** A unit of matching in a route pattern
183 */
184 struct route_seg
185 {
186 // literal prefix which must match
187 core::string_view prefix;
188 core::string_view name;
189 core::string_view constraint;
190 char ptype = 0; // ':' | '?' | NULL
191 char modifier = 0;
192 };
193
194 struct param_segment_rule_t
195 {
196 using value_type = route_seg;
197
198 auto
199 parse(
200 char const*& it,
201 char const* end) const noexcept ->
202 system::result<value_type>
203 {
204 if(it == end)
205 BOOST_HTTP_RETURN_EC(
206 grammar::error::syntax);
207 if(*it != ':' && *it != '*')
208 BOOST_HTTP_RETURN_EC(
209 grammar::error::mismatch);
210 value_type v;
211 v.ptype = *it++;
212 {
213 // param-name
214 auto rv = grammar::parse(
215 it, end, param_name_rule);
216 if(rv.has_error())
217 return rv.error();
218 v.name = rv.value();
219 }
220 {
221 // constraint
222 auto rv = grammar::parse(
223 it, end, constraint_rule);
224 if( rv.has_error())
225 return rv.error();
226 v.constraint = rv.value();
227 }
228 // modifier
229 if( it != end && (
230 *it == '?' || *it == '*' || *it == '+'))
231 v.modifier = *it++;
232 return v;
233 }
234 };
235
236 constexpr param_segment_rule_t param_segment_rule{};
237
238 //------------------------------------------------
239
240 constexpr token_rule<unreserved_char> literal_segment_rule{};
241
242 //------------------------------------------------
243
244 struct path_rule_t
245 {
246 struct value_type
247 {
248 std::vector<route_seg> segs;
249 };
250
251 auto
252 74 parse(
253 char const*& it0,
254 char const* const end) const ->
255 system::result<value_type>
256 {
257 74 value_type rv;
258 74 auto it = it0;
259 74 auto it1 = it;
260
2/2
✓ Branch 0 taken 219 times.
✓ Branch 1 taken 74 times.
293 while(it != end)
261 {
262
1/2
✓ Branch 0 taken 219 times.
✗ Branch 1 not taken.
219 if( *it == ':' ||
263
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 219 times.
219 *it == '*')
264 {
265 auto const it2 = it;
266 auto rv1 = urls::grammar::parse(
267 core::string_view(it, end),
268 param_segment_rule);
269 if(rv1.has_error())
270 return rv1.error();
271 route_seg rs = rv1.value();
272 rs.prefix = { it2, it1 };
273 rv.segs.push_back(rs);
274 it1 = it;
275 continue;
276 }
277 219 ++it;
278 }
279
1/2
✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
74 if(it1 != it)
280 {
281 74 route_seg rs;
282 74 rs.prefix = core::string_view(it1, end);
283
1/1
✓ Branch 1 taken 74 times.
74 rv.segs.push_back(rs);
284 }
285 74 it0 = it0 + (it - it1);
286 // gcc 7 bug workaround
287 74 return system::result<value_type>(std::move(rv));
288 74 }
289 };
290
291 constexpr path_rule_t path_rule{};
292
293 struct route_match
294 {
295 using iterator = urls::segments_encoded_view::iterator;
296
297 urls::segments_encoded_view base;
298 urls::segments_encoded_view path;
299 };
300
301 } // detail
302 } // http
303 } // boost
304
305 #endif
306