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