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/beast2
8 : //
9 :
10 : #ifndef BOOST_BEAST2_SERVER_ROUTE_RULE_HPP
11 : #define BOOST_BEAST2_SERVER_ROUTE_RULE_HPP
12 :
13 : #include <boost/beast2/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 : namespace boost {
22 : namespace beast2 {
23 :
24 : namespace grammar = urls::grammar;
25 :
26 : //------------------------------------------------
27 :
28 : // avoids SBO
29 : class stable_chars
30 : {
31 : char const* p_ = 0;
32 : std::size_t n_ = 0;
33 :
34 : public:
35 48 : ~stable_chars()
36 : {
37 48 : if(p_)
38 9 : delete[] p_;
39 48 : }
40 :
41 9 : stable_chars() = default;
42 :
43 30 : stable_chars(
44 : stable_chars&& other) noexcept
45 30 : : p_(other.p_)
46 30 : , n_(other.n_)
47 : {
48 30 : other.p_ = nullptr;
49 30 : other.n_ = 0;
50 30 : }
51 :
52 9 : stable_chars& operator=(
53 : stable_chars&& other) noexcept
54 : {
55 9 : auto p = p_;
56 9 : auto n = n_;
57 9 : p_ = other.p_;
58 9 : n_ = other.n_;
59 9 : other.p_ = p;
60 9 : other.n_ = n;
61 9 : return *this;
62 : }
63 :
64 : explicit
65 9 : stable_chars(
66 : core::string_view s)
67 18 : : p_(
68 27 : [&]
69 : {
70 9 : auto p =new char[s.size()];
71 9 : std::memcpy(p, s.data(), s.size());
72 9 : return p;
73 9 : }())
74 9 : , n_(s.size())
75 : {
76 9 : }
77 :
78 9 : stable_chars(
79 : char const* it, char const* end)
80 9 : : stable_chars(core::string_view(it, end))
81 : {
82 9 : }
83 :
84 : char const* data() const noexcept
85 : {
86 : return p_;
87 : }
88 :
89 9 : std::size_t size() const noexcept
90 : {
91 9 : return n_;
92 : }
93 : };
94 :
95 : //------------------------------------------------
96 :
97 : /** Rule for parsing a non-empty token of chars
98 :
99 : @par Requires
100 : @code
101 : std::is_empty<CharSet>::value == true
102 : @endcode
103 : */
104 : template<class CharSet>
105 : struct token_rule
106 : {
107 : using value_type = core::string_view;
108 :
109 : auto
110 : parse(
111 : char const*& it,
112 : char const* end) const noexcept ->
113 : system::result<value_type>
114 : {
115 : static_assert(std::is_empty<CharSet>::value, "");
116 : if(it == end)
117 : return grammar::error::syntax;
118 : auto it1 = grammar::find_if_not(it, end, CharSet{});
119 : if(it1 == it)
120 : return grammar::error::mismatch;
121 : auto s = core::string_view(it, it1);
122 : it = it1;
123 : return s;
124 : }
125 : };
126 :
127 : //------------------------------------------------
128 :
129 : /*
130 : route-pattern = *( "/" segment ) [ "/" ]
131 : segment = literal-segment / param-segment
132 : literal-segment = 1*( unreserved-char )
133 : unreserved-char = %x21-2F / %x30-3B / %x3D-5A / %x5C-7E ; all printable except slash
134 : param-segment = param-prefix param-name [ constraint ] [ modifier ]
135 : param-prefix = ":" / "*" ; either named param ":" or named wildcard "*"
136 : param-name = ident
137 : constraint = "(" 1*( constraint-char ) ")"
138 : modifier = "?" / "*" / "+"
139 : ident = ALPHA *( ALPHA / DIGIT / "_" )
140 : constraint-char = %x20-7E except ( ")" )
141 : */
142 :
143 : //------------------------------------------------
144 :
145 : struct unreserved_char
146 : {
147 : constexpr
148 : bool
149 : operator()(char ch) const noexcept
150 : {
151 : return ch != '/' && (
152 : (ch >= 0x21 && ch <= 0x2F) ||
153 : (ch >= 0x30 && ch <= 0x3B) ||
154 : (ch >= 0x3D && ch <= 0x5A) ||
155 : (ch >= 0x5C && ch <= 0x7E));
156 : }
157 : };
158 :
159 : struct constraint_char
160 : {
161 : constexpr
162 : bool
163 0 : operator()(char ch) const noexcept
164 : {
165 0 : return ch >= 0x20 && ch <= 0x7E && ch != ')';
166 : }
167 : };
168 :
169 : struct ident_char
170 : {
171 : constexpr
172 : bool
173 0 : operator()(char ch) const noexcept
174 : {
175 : return
176 0 : (ch >= 'a' && ch <= 'z') ||
177 0 : (ch >= '0' && ch <= '9') ||
178 0 : (ch >= 'A' && ch <= 'Z') ||
179 0 : (ch == '_');
180 : }
181 : };
182 :
183 : constexpr struct
184 : {
185 : // empty for no constraint
186 : using value_type = core::string_view;
187 :
188 : auto
189 0 : parse(
190 : char const*& it,
191 : char const* end) const noexcept ->
192 : system::result<value_type>
193 : {
194 0 : if(it == end || *it != '(')
195 0 : return "";
196 0 : if(it == end)
197 0 : BOOST_BEAST2_RETURN_EC(
198 : grammar::error::syntax);
199 0 : auto it0 = it;
200 0 : it = grammar::find_if_not(
201 0 : it, end, constraint_char{});
202 0 : if(it - it0 <= 1)
203 : {
204 : // too small
205 0 : it = it0;
206 0 : BOOST_BEAST2_RETURN_EC(
207 : grammar::error::syntax);
208 : }
209 0 : if(it == end)
210 : {
211 0 : it = it0;
212 0 : BOOST_BEAST2_RETURN_EC(
213 : grammar::error::syntax);
214 : }
215 0 : if(*it != ')')
216 : {
217 0 : it0 = it;
218 0 : BOOST_BEAST2_RETURN_EC(
219 : grammar::error::syntax);
220 : }
221 0 : return core::string_view(++it0, it++);
222 : }
223 : } constraint_rule{};
224 :
225 : constexpr struct
226 : {
227 : using value_type = core::string_view;
228 :
229 : auto
230 0 : parse(
231 : char const*& it,
232 : char const* end) const noexcept ->
233 : system::result<value_type>
234 : {
235 0 : if(it == end)
236 0 : BOOST_BEAST2_RETURN_EC(
237 : grammar::error::syntax);
238 0 : if(! grammar::alpha_chars(*it))
239 0 : BOOST_BEAST2_RETURN_EC(
240 : grammar::error::syntax);
241 0 : auto it0 = it++;
242 0 : it = grammar::find_if_not(
243 0 : it, end, ident_char{});
244 0 : return core::string_view(it0, it);
245 : }
246 : } param_name_rule{};
247 :
248 : //------------------------------------------------
249 :
250 : /** A unit of matching in a route pattern
251 : */
252 : struct route_seg
253 : {
254 : // literal prefix which must match
255 : core::string_view prefix;
256 : core::string_view name;
257 : core::string_view constraint;
258 : char ptype = 0; // ':' | '?' | NULL
259 : char modifier = 0;
260 : };
261 :
262 : struct param_segment_rule_t
263 : {
264 : using value_type = route_seg;
265 :
266 : auto
267 0 : parse(
268 : char const*& it,
269 : char const* end) const noexcept ->
270 : system::result<value_type>
271 : {
272 0 : if(it == end)
273 0 : BOOST_BEAST2_RETURN_EC(
274 : grammar::error::syntax);
275 0 : if(*it != ':' && *it != '*')
276 0 : BOOST_BEAST2_RETURN_EC(
277 : grammar::error::mismatch);
278 0 : value_type v;
279 0 : v.ptype = *it++;
280 : {
281 : // param-name
282 0 : auto rv = grammar::parse(
283 : it, end, param_name_rule);
284 0 : if(rv.has_error())
285 0 : return rv.error();
286 0 : v.name = rv.value();
287 : }
288 : {
289 : // constraint
290 0 : auto rv = grammar::parse(
291 : it, end, constraint_rule);
292 0 : if( rv.has_error())
293 0 : return rv.error();
294 0 : v.constraint = rv.value();
295 : }
296 : // modifier
297 0 : if( it != end && (
298 0 : *it == '?' || *it == '*' || *it == '+'))
299 0 : v.modifier = *it++;
300 0 : return v;
301 : }
302 : };
303 :
304 : constexpr param_segment_rule_t param_segment_rule{};
305 :
306 : //------------------------------------------------
307 :
308 : constexpr token_rule<unreserved_char> literal_segment_rule{};
309 :
310 : //------------------------------------------------
311 :
312 : struct path_rule_t
313 : {
314 : struct value_type
315 : {
316 : std::vector<route_seg> segs;
317 : private:
318 : friend struct path_rule_t;
319 : stable_chars chars;
320 : };
321 :
322 : auto
323 9 : parse(
324 : char const*& it0,
325 : char const* const end0) const ->
326 : system::result<value_type>
327 : {
328 9 : value_type rv;
329 9 : rv.chars = stable_chars(it0, end0);
330 9 : auto it = it0;
331 9 : auto it1 = it;
332 9 : auto const end = it + rv.chars.size();
333 26 : while(it != end)
334 : {
335 17 : if( *it == ':' ||
336 17 : *it == '*')
337 : {
338 0 : auto const it2 = it;
339 0 : auto rv1 = urls::grammar::parse(
340 : core::string_view(it, end),
341 : param_segment_rule);
342 0 : if(rv1.has_error())
343 0 : return rv1.error();
344 0 : route_seg rs = rv1.value();
345 0 : rs.prefix = { it2, it1 };
346 0 : rv.segs.push_back(rs);
347 0 : it1 = it;
348 0 : continue;
349 0 : }
350 17 : ++it;
351 : }
352 9 : if(it1 != it)
353 : {
354 9 : route_seg rs;
355 9 : rs.prefix = core::string_view(it1, end);
356 9 : rv.segs.push_back(rs);
357 : }
358 9 : it0 = it0 + (it - it1);
359 : // gcc 7 bug workaround
360 9 : return system::result<value_type>(std::move(rv));
361 9 : }
362 : };
363 :
364 : constexpr path_rule_t path_rule{};
365 :
366 : struct route_match
367 : {
368 : using iterator = urls::segments_encoded_view::iterator;
369 :
370 : urls::segments_encoded_view base;
371 : urls::segments_encoded_view path;
372 : };
373 :
374 : } // beast2
375 : } // boost
376 :
377 : #endif
|