Line data Source code
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 0 : operator()(char ch) const noexcept
96 : {
97 0 : return ch >= 0x20 && ch <= 0x7E && ch != ')';
98 : }
99 : };
100 :
101 : struct ident_char
102 : {
103 : constexpr
104 : bool
105 0 : operator()(char ch) const noexcept
106 : {
107 : return
108 0 : (ch >= 'a' && ch <= 'z') ||
109 0 : (ch >= '0' && ch <= '9') ||
110 0 : (ch >= 'A' && ch <= 'Z') ||
111 0 : (ch == '_');
112 : }
113 : };
114 :
115 : constexpr struct
116 : {
117 : // empty for no constraint
118 : using value_type = core::string_view;
119 :
120 : auto
121 0 : parse(
122 : char const*& it,
123 : char const* end) const noexcept ->
124 : system::result<value_type>
125 : {
126 0 : if(it == end || *it != '(')
127 0 : return "";
128 0 : if(it == end)
129 0 : BOOST_HTTP_RETURN_EC(
130 : grammar::error::syntax);
131 0 : auto it0 = it;
132 0 : it = grammar::find_if_not(
133 0 : it, end, constraint_char{});
134 0 : if(it - it0 <= 1)
135 : {
136 : // too small
137 0 : it = it0;
138 0 : BOOST_HTTP_RETURN_EC(
139 : grammar::error::syntax);
140 : }
141 0 : if(it == end)
142 : {
143 0 : it = it0;
144 0 : BOOST_HTTP_RETURN_EC(
145 : grammar::error::syntax);
146 : }
147 0 : if(*it != ')')
148 : {
149 0 : it0 = it;
150 0 : BOOST_HTTP_RETURN_EC(
151 : grammar::error::syntax);
152 : }
153 0 : 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 0 : parse(
163 : char const*& it,
164 : char const* end) const noexcept ->
165 : system::result<value_type>
166 : {
167 0 : if(it == end)
168 0 : BOOST_HTTP_RETURN_EC(
169 : grammar::error::syntax);
170 0 : if(! grammar::alpha_chars(*it))
171 0 : BOOST_HTTP_RETURN_EC(
172 : grammar::error::syntax);
173 0 : auto it0 = it++;
174 0 : it = grammar::find_if_not(
175 0 : it, end, ident_char{});
176 0 : 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 0 : parse(
200 : char const*& it,
201 : char const* end) const noexcept ->
202 : system::result<value_type>
203 : {
204 0 : if(it == end)
205 0 : BOOST_HTTP_RETURN_EC(
206 : grammar::error::syntax);
207 0 : if(*it != ':' && *it != '*')
208 0 : BOOST_HTTP_RETURN_EC(
209 : grammar::error::mismatch);
210 0 : value_type v;
211 0 : v.ptype = *it++;
212 : {
213 : // param-name
214 0 : auto rv = grammar::parse(
215 : it, end, param_name_rule);
216 0 : if(rv.has_error())
217 0 : return rv.error();
218 0 : v.name = rv.value();
219 : }
220 : {
221 : // constraint
222 0 : auto rv = grammar::parse(
223 : it, end, constraint_rule);
224 0 : if( rv.has_error())
225 0 : return rv.error();
226 0 : v.constraint = rv.value();
227 : }
228 : // modifier
229 0 : if( it != end && (
230 0 : *it == '?' || *it == '*' || *it == '+'))
231 0 : v.modifier = *it++;
232 0 : 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 293 : while(it != end)
261 : {
262 219 : if( *it == ':' ||
263 219 : *it == '*')
264 : {
265 0 : auto const it2 = it;
266 0 : auto rv1 = urls::grammar::parse(
267 : core::string_view(it, end),
268 : param_segment_rule);
269 0 : if(rv1.has_error())
270 0 : return rv1.error();
271 0 : route_seg rs = rv1.value();
272 0 : rs.prefix = { it2, it1 };
273 0 : rv.segs.push_back(rs);
274 0 : it1 = it;
275 0 : continue;
276 0 : }
277 219 : ++it;
278 : }
279 74 : if(it1 != it)
280 : {
281 74 : route_seg rs;
282 74 : rs.prefix = core::string_view(it1, end);
283 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
|