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 : ~stable_string()
36 : {
37 : if(p_)
38 : delete[] p_;
39 : }
40 :
41 : stable_string() = default;
42 :
43 : stable_string(
44 : stable_string&& other) noexcept
45 : : p_(other.p_)
46 : , n_(other.n_)
47 : {
48 : other.p_ = nullptr;
49 : other.n_ = 0;
50 : }
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 : stable_string(
66 : core::string_view s)
67 : : p_(
68 : [&]
69 : {
70 : auto p =new char[s.size()];
71 : std::memcpy(p, s.data(), s.size());
72 : return p;
73 : }())
74 : , n_(s.size())
75 : {
76 : }
77 :
78 : stable_string(
79 : char const* it, char const* end)
80 : : stable_string(core::string_view(it, end))
81 : {
82 : }
83 :
84 : char const* data() const noexcept
85 : {
86 : return p_;
87 : }
88 :
89 : std::size_t size() const noexcept
90 : {
91 : return n_;
92 : }
93 :
94 : operator core::string_view() const noexcept
95 : {
96 : 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 : char term; // param terminator or NULL
266 : };
267 :
268 : struct param_segment_rule_t
269 : {
270 : using value_type = route_seg;
271 :
272 : auto
273 : parse(
274 : char const*& it,
275 : char const* end) const noexcept ->
276 : system::result<value_type>
277 : {
278 : if(it == end)
279 : BOOST_BEAST2_RETURN_EC(
280 : grammar::error::syntax);
281 : if(*it != ':' && *it != '*')
282 : BOOST_BEAST2_RETURN_EC(
283 : grammar::error::mismatch);
284 : value_type v;
285 : v.ptype = *it++;
286 : {
287 : // param-name
288 : auto rv = grammar::parse(
289 : it, end, param_name_rule);
290 : if(rv.has_error())
291 : return rv.error();
292 : v.name = rv.value();
293 : }
294 : {
295 : // constraint
296 : auto rv = grammar::parse(
297 : it, end, constraint_rule);
298 : if( rv.has_error())
299 : return rv.error();
300 : v.constraint = rv.value();
301 : }
302 : // modifier
303 : if( it != end && (
304 : *it == '?' || *it == '*' || *it == '+'))
305 : v.modifier = *it++;
306 : return v;
307 : }
308 : };
309 :
310 : constexpr param_segment_rule_t param_segment_rule{};
311 :
312 : //------------------------------------------------
313 :
314 : constexpr token_rule<unreserved_char> literal_segment_rule{};
315 :
316 : //------------------------------------------------
317 :
318 : struct path_rule_t
319 : {
320 : struct value_type
321 : {
322 : std::vector<route_seg> segs;
323 : };
324 :
325 : auto
326 : parse(
327 : char const*& it0,
328 : char const* const end) const ->
329 : system::result<value_type>
330 : {
331 : value_type rv;
332 : auto it = it0;
333 : auto it1 = it;
334 : while(it != end)
335 : {
336 : if( *it == ':' ||
337 : *it == '*')
338 : {
339 : auto const it2 = it;
340 : auto rv1 = urls::grammar::parse(
341 : core::string_view(it, end),
342 : param_segment_rule);
343 : if(rv1.has_error())
344 : return rv1.error();
345 : route_seg rs = rv1.value();
346 : rs.prefix = { it2, it1 };
347 : if(it != end)
348 : {
349 : if( *it == ':' ||
350 : *it == '*')
351 : {
352 : // can't have ":id:id"
353 : return grammar::error::syntax;
354 : }
355 : }
356 : rv.segs.push_back(rs);
357 : it1 = it;
358 : continue;
359 : }
360 : ++it;
361 : }
362 : if(it1 != it)
363 : {
364 : route_seg rs;
365 : rs.prefix = core::string_view(it1, end);
366 : rv.segs.push_back(rs);
367 : }
368 : it0 = it0 + (it - it1);
369 : // gcc 7 bug workaround
370 : return system::result<value_type>(std::move(rv));
371 : }
372 : };
373 :
374 : constexpr path_rule_t path_rule{};
375 :
376 : struct route_match
377 : {
378 : using iterator = urls::segments_encoded_view::iterator;
379 :
380 : urls::segments_encoded_view base;
381 : urls::segments_encoded_view path;
382 : };
383 :
384 : } // beast2
385 : } // boost
386 :
387 : #endif
|