93.58% Lines (248/265) 100.00% Functions (34/34)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 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) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/http 7   // Official repository: https://github.com/cppalliance/http
8   // 8   //
9   9  
10   #include "src/server/route_abnf.hpp" 10   #include "src/server/route_abnf.hpp"
11   #include <boost/url/grammar/error.hpp> 11   #include <boost/url/grammar/error.hpp>
12   12  
13   namespace boost { 13   namespace boost {
14   namespace http { 14   namespace http {
15   namespace detail { 15   namespace detail {
16   16  
17   namespace { 17   namespace {
18   18  
19   //------------------------------------------------ 19   //------------------------------------------------
20   // Character classification 20   // Character classification
21   //------------------------------------------------ 21   //------------------------------------------------
22   22  
23   // Special characters that have meaning in patterns 23   // Special characters that have meaning in patterns
24   constexpr bool 24   constexpr bool
HITCBC 25   676 is_special(char c) noexcept 25   676 is_special(char c) noexcept
26   { 26   {
HITCBC 27   676 switch(c) 27   676 switch(c)
28   { 28   {
HITCBC 29   53 case '{': 29   53 case '{':
30   case '}': 30   case '}':
31   case '(': 31   case '(':
32   case ')': 32   case ')':
33   case '[': 33   case '[':
34   case ']': 34   case ']':
35   case '+': 35   case '+':
36   case '?': 36   case '?':
37   case '!': 37   case '!':
38   case ':': 38   case ':':
39   case '*': 39   case '*':
40   case '\\': 40   case '\\':
HITCBC 41   53 return true; 41   53 return true;
HITCBC 42   623 default: 42   623 default:
HITCBC 43   623 return false; 43   623 return false;
44   } 44   }
45   } 45   }
46   46  
47   // Reserved characters (parsed but invalid) 47   // Reserved characters (parsed but invalid)
48   constexpr bool 48   constexpr bool
HITCBC 49   203 is_reserved(char c) noexcept 49   203 is_reserved(char c) noexcept
50   { 50   {
HITCBC 51   203 switch(c) 51   203 switch(c)
52   { 52   {
HITCBC 53   5 case '(': 53   5 case '(':
54   case ')': 54   case ')':
55   case '[': 55   case '[':
56   case ']': 56   case ']':
57   case '+': 57   case '+':
58   case '?': 58   case '?':
59   case '!': 59   case '!':
HITCBC 60   5 return true; 60   5 return true;
HITCBC 61   198 default: 61   198 default:
HITCBC 62   198 return false; 62   198 return false;
63   } 63   }
64   } 64   }
65   65  
66   // Valid identifier start (ASCII subset of ID_Start) 66   // Valid identifier start (ASCII subset of ID_Start)
67   constexpr bool 67   constexpr bool
HITCBC 68   102 is_id_start(char c) noexcept 68   102 is_id_start(char c) noexcept
69   { 69   {
70   return 70   return
HITCBC 71   102 (c >= 'a' && c <= 'z') || 71   102 (c >= 'a' && c <= 'z') ||
HITCBC 72   19 (c >= 'A' && c <= 'Z') || 72   19 (c >= 'A' && c <= 'Z') ||
HITCBC 73   204 c == '_' || c == '$'; 73   204 c == '_' || c == '$';
74   } 74   }
75   75  
76   // Valid identifier continuation (ASCII subset of ID_Continue) 76   // Valid identifier continuation (ASCII subset of ID_Continue)
77   constexpr bool 77   constexpr bool
HITCBC 78   73 is_id_continue(char c) noexcept 78   73 is_id_continue(char c) noexcept
79   { 79   {
80   return 80   return
HITCBC 81   75 is_id_start(c) || 81   75 is_id_start(c) ||
HITCBC 82   75 (c >= '0' && c <= '9'); 82   75 (c >= '0' && c <= '9');
83   } 83   }
84   84  
85   //------------------------------------------------ 85   //------------------------------------------------
86   // Parser state 86   // Parser state
87   //------------------------------------------------ 87   //------------------------------------------------
88   88  
89   class parser 89   class parser
90   { 90   {
91   char const* it_; 91   char const* it_;
92   char const* end_; 92   char const* end_;
93   core::string_view original_; 93   core::string_view original_;
94   94  
95   public: 95   public:
HITCBC 96   136 parser(core::string_view s) 96   136 parser(core::string_view s)
HITCBC 97   136 : it_(s.data()) 97   136 : it_(s.data())
HITCBC 98   136 , end_(s.data() + s.size()) 98   136 , end_(s.data() + s.size())
HITCBC 99   136 , original_(s) 99   136 , original_(s)
100   { 100   {
HITCBC 101   136 } 101   136 }
102   102  
103   bool 103   bool
HITCBC 104   1564 at_end() const noexcept 104   1564 at_end() const noexcept
105   { 105   {
HITCBC 106   1564 return it_ == end_; 106   1564 return it_ == end_;
107   } 107   }
108   108  
109   char 109   char
HITCBC 110   1111 peek() const noexcept 110   1111 peek() const noexcept
111   { 111   {
HITCBC 112   1111 return *it_; 112   1111 return *it_;
113   } 113   }
114   114  
115   void 115   void
HITCBC 116   61 advance() noexcept 116   61 advance() noexcept
117   { 117   {
HITCBC 118   61 ++it_; 118   61 ++it_;
HITCBC 119   61 } 119   61 }
120   120  
121   char 121   char
HITCBC 122   745 get() noexcept 122   745 get() noexcept
123   { 123   {
HITCBC 124   745 return *it_++; 124   745 return *it_++;
125   } 125   }
126   126  
127   std::size_t 127   std::size_t
128   pos() const noexcept 128   pos() const noexcept
129   { 129   {
130   return static_cast<std::size_t>( 130   return static_cast<std::size_t>(
131   it_ - original_.data()); 131   it_ - original_.data());
132   } 132   }
133   133  
134   //-------------------------------------------- 134   //--------------------------------------------
135   // Name parsing 135   // Name parsing
136   //-------------------------------------------- 136   //--------------------------------------------
137   137  
138   // Parse identifier: id-start *id-continue 138   // Parse identifier: id-start *id-continue
139   system::result<std::string> 139   system::result<std::string>
HITCBC 140   29 parse_identifier() 140   29 parse_identifier()
141   { 141   {
HITCBC 142   29 if(at_end() || !is_id_start(peek())) 142   29 if(at_end() || !is_id_start(peek()))
MISUBC 143   return grammar::error::mismatch; 143   return grammar::error::mismatch;
144   144  
HITCBC 145   29 std::string result; 145   29 std::string result;
HITCBC 146   29 result += get(); 146   29 result += get();
147   147  
HITCBC 148   85 while(!at_end() && is_id_continue(peek())) 148   85 while(!at_end() && is_id_continue(peek()))
HITCBC 149   56 result += get(); 149   56 result += get();
150   150  
HITCBC 151   29 return result; 151   29 return result;
HITCBC 152   29 } 152   29 }
153   153  
154   // Parse quoted name: DQUOTE *quoted-char DQUOTE 154   // Parse quoted name: DQUOTE *quoted-char DQUOTE
155   system::result<std::string> 155   system::result<std::string>
HITCBC 156   4 parse_quoted_name() 156   4 parse_quoted_name()
157   { 157   {
HITCBC 158   4 if(at_end() || peek() != '"') 158   4 if(at_end() || peek() != '"')
MISUBC 159   return grammar::error::mismatch; 159   return grammar::error::mismatch;
160   160  
HITCBC 161   4 advance(); // skip opening quote 161   4 advance(); // skip opening quote
HITCBC 162   4 std::string result; 162   4 std::string result;
163   163  
HITCBC 164   36 while(!at_end()) 164   36 while(!at_end())
165   { 165   {
HITCBC 166   35 char c = peek(); 166   35 char c = peek();
167   167  
HITCBC 168   35 if(c == '"') 168   35 if(c == '"')
169   { 169   {
HITCBC 170   3 advance(); // skip closing quote 170   3 advance(); // skip closing quote
HITCBC 171   3 if(result.empty()) 171   3 if(result.empty())
HITCBC 172   1 return grammar::error::syntax; 172   1 return grammar::error::syntax;
HITCBC 173   2 return result; 173   2 return result;
174   } 174   }
175   175  
HITCBC 176   32 if(c == '\\') 176   32 if(c == '\\')
177   { 177   {
MISUBC 178   advance(); // skip backslash 178   advance(); // skip backslash
MISUBC 179   if(at_end()) 179   if(at_end())
MISUBC 180   return grammar::error::syntax; 180   return grammar::error::syntax;
MISUBC 181   result += get(); 181   result += get();
182   } 182   }
183   else 183   else
184   { 184   {
HITCBC 185   32 result += get(); 185   32 result += get();
186   } 186   }
187   } 187   }
188   188  
189   // Unterminated quote 189   // Unterminated quote
HITCBC 190   1 return grammar::error::syntax; 190   1 return grammar::error::syntax;
HITCBC 191   4 } 191   4 }
192   192  
193   // Parse name: identifier / quoted-name 193   // Parse name: identifier / quoted-name
194   system::result<std::string> 194   system::result<std::string>
HITCBC 195   35 parse_name() 195   35 parse_name()
196   { 196   {
HITCBC 197   35 if(at_end()) 197   35 if(at_end())
HITCBC 198   2 return grammar::error::syntax; 198   2 return grammar::error::syntax;
199   199  
HITCBC 200   33 if(peek() == '"') 200   33 if(peek() == '"')
HITCBC 201   4 return parse_quoted_name(); 201   4 return parse_quoted_name();
202   202  
HITCBC 203   29 return parse_identifier(); 203   29 return parse_identifier();
204   } 204   }
205   205  
206   //-------------------------------------------- 206   //--------------------------------------------
207   // Token parsing 207   // Token parsing
208   //-------------------------------------------- 208   //--------------------------------------------
209   209  
210   // Parse text: 1*(char / escaped-char) 210   // Parse text: 1*(char / escaped-char)
211   system::result<route_token> 211   system::result<route_token>
HITCBC 212   155 parse_text() 212   155 parse_text()
213   { 213   {
HITCBC 214   155 std::string result; 214   155 std::string result;
215   215  
HITCBC 216   783 while(!at_end()) 216   783 while(!at_end())
217   { 217   {
HITCBC 218   676 char c = peek(); 218   676 char c = peek();
219   219  
220   // Stop at special characters 220   // Stop at special characters
HITCBC 221   676 if(is_special(c)) 221   676 if(is_special(c))
222   { 222   {
HITCBC 223   53 if(c == '\\') 223   53 if(c == '\\')
224   { 224   {
225   // Escaped character 225   // Escaped character
HITCBC 226   6 advance(); 226   6 advance();
HITCBC 227   6 if(at_end()) 227   6 if(at_end())
HITCBC 228   1 return grammar::error::syntax; 228   1 return grammar::error::syntax;
HITCBC 229   5 result += get(); 229   5 result += get();
HITCBC 230   5 continue; 230   5 continue;
231   } 231   }
HITCBC 232   47 break; 232   47 break;
233   } 233   }
234   234  
HITCBC 235   623 result += get(); 235   623 result += get();
236   } 236   }
237   237  
HITCBC 238   154 if(result.empty()) 238   154 if(result.empty())
MISUBC 239   return grammar::error::mismatch; 239   return grammar::error::mismatch;
240   240  
HITCBC 241   154 return route_token(route_token_type::text, std::move(result)); 241   154 return route_token(route_token_type::text, std::move(result));
HITCBC 242   155 } 242   155 }
243   243  
244   // Parse param: ":" name 244   // Parse param: ":" name
245   system::result<route_token> 245   system::result<route_token>
HITCBC 246   30 parse_param() 246   30 parse_param()
247   { 247   {
HITCBC 248   30 if(at_end() || peek() != ':') 248   30 if(at_end() || peek() != ':')
MISUBC 249   return grammar::error::mismatch; 249   return grammar::error::mismatch;
250   250  
HITCBC 251   30 advance(); // skip ':' 251   30 advance(); // skip ':'
252   252  
HITCBC 253   30 auto rv = parse_name(); 253   30 auto rv = parse_name();
HITCBC 254   30 if(rv.has_error()) 254   30 if(rv.has_error())
HITCBC 255   3 return rv.error(); 255   3 return rv.error();
256   256  
HITCBC 257   54 return route_token( 257   54 return route_token(
HITCBC 258   81 route_token_type::param, std::move(rv.value())); 258   81 route_token_type::param, std::move(rv.value()));
HITCBC 259   30 } 259   30 }
260   260  
261   // Parse wildcard: "*" name 261   // Parse wildcard: "*" name
262   system::result<route_token> 262   system::result<route_token>
HITCBC 263   5 parse_wildcard() 263   5 parse_wildcard()
264   { 264   {
HITCBC 265   5 if(at_end() || peek() != '*') 265   5 if(at_end() || peek() != '*')
MISUBC 266   return grammar::error::mismatch; 266   return grammar::error::mismatch;
267   267  
HITCBC 268   5 advance(); // skip '*' 268   5 advance(); // skip '*'
269   269  
HITCBC 270   5 auto rv = parse_name(); 270   5 auto rv = parse_name();
HITCBC 271   5 if(rv.has_error()) 271   5 if(rv.has_error())
HITCBC 272   1 return rv.error(); 272   1 return rv.error();
273   273  
HITCBC 274   8 return route_token( 274   8 return route_token(
HITCBC 275   12 route_token_type::wildcard, std::move(rv.value())); 275   12 route_token_type::wildcard, std::move(rv.value()));
HITCBC 276   5 } 276   5 }
277   277  
278   // Parse group: "{" *token "}" 278   // Parse group: "{" *token "}"
279   system::result<route_token> 279   system::result<route_token>
HITCBC 280   7 parse_group() 280   7 parse_group()
281   { 281   {
HITCBC 282   7 if(at_end() || peek() != '{') 282   7 if(at_end() || peek() != '{')
MISUBC 283   return grammar::error::mismatch; 283   return grammar::error::mismatch;
284   284  
HITCBC 285   7 advance(); // skip '{' 285   7 advance(); // skip '{'
286   286  
HITCBC 287   7 route_token group; 287   7 route_token group;
HITCBC 288   7 group.type = route_token_type::group; 288   7 group.type = route_token_type::group;
289   289  
290   // Parse tokens until '}' 290   // Parse tokens until '}'
HITCBC 291   17 while(!at_end() && peek() != '}') 291   17 while(!at_end() && peek() != '}')
292   { 292   {
HITCBC 293   10 auto rv = parse_token(); 293   10 auto rv = parse_token();
HITCBC 294   10 if(rv.has_error()) 294   10 if(rv.has_error())
MISUBC 295   return rv.error(); 295   return rv.error();
HITCBC 296   10 group.children.push_back(std::move(rv.value())); 296   10 group.children.push_back(std::move(rv.value()));
HITCBC 297   10 } 297   10 }
298   298  
HITCBC 299   7 if(at_end()) 299   7 if(at_end())
HITCBC 300   1 return grammar::error::syntax; // unclosed group 300   1 return grammar::error::syntax; // unclosed group
301   301  
HITCBC 302   6 advance(); // skip '}' 302   6 advance(); // skip '}'
303   303  
HITCBC 304   6 return group; 304   6 return group;
HITCBC 305   7 } 305   7 }
306   306  
307   // Parse single token 307   // Parse single token
308   system::result<route_token> 308   system::result<route_token>
HITCBC 309   203 parse_token() 309   203 parse_token()
310   { 310   {
HITCBC 311   203 if(at_end()) 311   203 if(at_end())
MISUBC 312   return grammar::error::syntax; 312   return grammar::error::syntax;
313   313  
HITCBC 314   203 char c = peek(); 314   203 char c = peek();
315   315  
316   // Check for reserved characters 316   // Check for reserved characters
HITCBC 317   203 if(is_reserved(c)) 317   203 if(is_reserved(c))
HITCBC 318   5 return grammar::error::syntax; 318   5 return grammar::error::syntax;
319   319  
320   // Try each token type 320   // Try each token type
HITCBC 321   198 if(c == ':') 321   198 if(c == ':')
HITCBC 322   30 return parse_param(); 322   30 return parse_param();
323   323  
HITCBC 324   168 if(c == '*') 324   168 if(c == '*')
HITCBC 325   5 return parse_wildcard(); 325   5 return parse_wildcard();
326   326  
HITCBC 327   163 if(c == '{') 327   163 if(c == '{')
HITCBC 328   7 return parse_group(); 328   7 return parse_group();
329   329  
HITCBC 330   156 if(c == '}') 330   156 if(c == '}')
HITCBC 331   1 return grammar::error::syntax; // unexpected '}' 331   1 return grammar::error::syntax; // unexpected '}'
332   332  
333   // Must be text 333   // Must be text
HITCBC 334   155 return parse_text(); 334   155 return parse_text();
335   } 335   }
336   336  
337   // Parse entire pattern 337   // Parse entire pattern
338   system::result<std::vector<route_token>> 338   system::result<std::vector<route_token>>
HITCBC 339   136 parse_tokens() 339   136 parse_tokens()
340   { 340   {
HITCBC 341   136 std::vector<route_token> tokens; 341   136 std::vector<route_token> tokens;
342   342  
HITCBC 343   317 while(!at_end()) 343   317 while(!at_end())
344   { 344   {
HITCBC 345   193 auto rv = parse_token(); 345   193 auto rv = parse_token();
HITCBC 346   193 if(rv.has_error()) 346   193 if(rv.has_error())
HITCBC 347   12 return rv.error(); 347   12 return rv.error();
HITCBC 348   181 tokens.push_back(std::move(rv.value())); 348   181 tokens.push_back(std::move(rv.value()));
HITCBC 349   193 } 349   193 }
350   350  
HITCBC 351   124 return tokens; 351   124 return tokens;
HITCBC 352   136 } 352   136 }
353   }; 353   };
354   354  
355   //------------------------------------------------ 355   //------------------------------------------------
356   // Case-insensitive comparison 356   // Case-insensitive comparison
357   //------------------------------------------------ 357   //------------------------------------------------
358   358  
359   bool 359   bool
HITCBC 360   502 ci_equal(char a, char b) noexcept 360   502 ci_equal(char a, char b) noexcept
361   { 361   {
HITCBC 362   502 if(a >= 'A' && a <= 'Z') 362   502 if(a >= 'A' && a <= 'Z')
HITCBC 363   10 a = static_cast<char>(a + 32); 363   10 a = static_cast<char>(a + 32);
HITCBC 364   502 if(b >= 'A' && b <= 'Z') 364   502 if(b >= 'A' && b <= 'Z')
HITCBC 365   2 b = static_cast<char>(b + 32); 365   2 b = static_cast<char>(b + 32);
HITCBC 366   502 return a == b; 366   502 return a == b;
367   } 367   }
368   368  
369   bool 369   bool
HITCBC 370   123 ci_starts_with( 370   123 ci_starts_with(
371   core::string_view str, 371   core::string_view str,
372   core::string_view prefix) noexcept 372   core::string_view prefix) noexcept
373   { 373   {
HITCBC 374   123 if(prefix.size() > str.size()) 374   123 if(prefix.size() > str.size())
HITCBC 375   10 return false; 375   10 return false;
HITCBC 376   608 for(std::size_t i = 0; i < prefix.size(); ++i) 376   608 for(std::size_t i = 0; i < prefix.size(); ++i)
377   { 377   {
HITCBC 378   502 if(!ci_equal(str[i], prefix[i])) 378   502 if(!ci_equal(str[i], prefix[i]))
HITCBC 379   7 return false; 379   7 return false;
380   } 380   }
HITCBC 381   106 return true; 381   106 return true;
382   } 382   }
383   383  
384   //------------------------------------------------ 384   //------------------------------------------------
385   // Route matcher 385   // Route matcher
386   //------------------------------------------------ 386   //------------------------------------------------
387   387  
388   class route_matcher 388   class route_matcher
389   { 389   {
390   core::string_view path_; 390   core::string_view path_;
391   match_options const& opts_; 391   match_options const& opts_;
392   std::vector<std::pair<std::string, std::string>> params_; 392   std::vector<std::pair<std::string, std::string>> params_;
393   std::size_t pos_ = 0; 393   std::size_t pos_ = 0;
394   394  
395   public: 395   public:
HITCBC 396   102 route_matcher( 396   102 route_matcher(
397   core::string_view path, 397   core::string_view path,
398   match_options const& opts) 398   match_options const& opts)
HITCBC 399   102 : path_(path) 399   102 : path_(path)
HITCBC 400   102 , opts_(opts) 400   102 , opts_(opts)
401   { 401   {
HITCBC 402   102 } 402   102 }
403   403  
HITCBC 404   76 bool at_end() const noexcept 404   76 bool at_end() const noexcept
405   { 405   {
HITCBC 406   76 return pos_ >= path_.size(); 406   76 return pos_ >= path_.size();
407   } 407   }
408   408  
HITCBC 409   87 std::size_t pos() const noexcept 409   87 std::size_t pos() const noexcept
410   { 410   {
HITCBC 411   87 return pos_; 411   87 return pos_;
412   } 412   }
413   413  
414   std::vector<std::pair<std::string, std::string>> const& 414   std::vector<std::pair<std::string, std::string>> const&
HITCBC 415   87 params() const noexcept 415   87 params() const noexcept
416   { 416   {
HITCBC 417   87 return params_; 417   87 return params_;
418   } 418   }
419   419  
420   // Match text token 420   // Match text token
HITCBC 421   129 bool match_text(core::string_view text) 421   129 bool match_text(core::string_view text)
422   { 422   {
HITCBC 423   129 auto remaining = path_.substr(pos_); 423   129 auto remaining = path_.substr(pos_);
HITCBC 424   129 if(opts_.case_sensitive) 424   129 if(opts_.case_sensitive)
425   { 425   {
HITCBC 426   6 if(!remaining.starts_with(text)) 426   6 if(!remaining.starts_with(text))
HITCBC 427   3 return false; 427   3 return false;
428   } 428   }
429   else 429   else
430   { 430   {
HITCBC 431   123 if(!ci_starts_with(remaining, text)) 431   123 if(!ci_starts_with(remaining, text))
HITCBC 432   17 return false; 432   17 return false;
433   } 433   }
HITCBC 434   109 pos_ += text.size(); 434   109 pos_ += text.size();
HITCBC 435   109 return true; 435   109 return true;
436   } 436   }
437   437  
438   // Match param token - capture until stop_char, '/' or end 438   // Match param token - capture until stop_char, '/' or end
HITCBC 439   26 bool match_param(std::string const& name, char stop_char = '\0') 439   26 bool match_param(std::string const& name, char stop_char = '\0')
440   { 440   {
HITCBC 441   26 if(at_end()) 441   26 if(at_end())
MISUBC 442   return false; 442   return false;
443   443  
HITCBC 444   26 auto start = pos_; 444   26 auto start = pos_;
HITCBC 445   95 while(pos_ < path_.size() && path_[pos_] != '/') 445   95 while(pos_ < path_.size() && path_[pos_] != '/')
446   { 446   {
447   // Stop at delimiter if specified 447   // Stop at delimiter if specified
HITCBC 448   70 if(stop_char != '\0' && path_[pos_] == stop_char) 448   70 if(stop_char != '\0' && path_[pos_] == stop_char)
HITCBC 449   1 break; 449   1 break;
HITCBC 450   69 ++pos_; 450   69 ++pos_;
451   } 451   }
452   452  
453   // Param must capture at least one character 453   // Param must capture at least one character
HITCBC 454   26 if(pos_ == start) 454   26 if(pos_ == start)
HITCBC 455   1 return false; 455   1 return false;
456   456  
HITCBC 457   50 params_.emplace_back( 457   50 params_.emplace_back(
458   name, 458   name,
HITCBC 459   50 std::string(path_.substr(start, pos_ - start))); 459   50 std::string(path_.substr(start, pos_ - start)));
HITCBC 460   25 return true; 460   25 return true;
461   } 461   }
462   462  
463   // Match wildcard token - capture everything to end 463   // Match wildcard token - capture everything to end
HITCBC 464   4 bool match_wildcard(std::string const& name) 464   4 bool match_wildcard(std::string const& name)
465   { 465   {
HITCBC 466   4 if(at_end()) 466   4 if(at_end())
HITCBC 467   1 return false; 467   1 return false;
468   468  
HITCBC 469   3 auto start = pos_; 469   3 auto start = pos_;
HITCBC 470   3 pos_ = path_.size(); 470   3 pos_ = path_.size();
471   471  
472   // Wildcard must capture at least one character 472   // Wildcard must capture at least one character
HITCBC 473   3 if(pos_ == start) 473   3 if(pos_ == start)
MISUBC 474   return false; 474   return false;
475   475  
HITCBC 476   6 params_.emplace_back( 476   6 params_.emplace_back(
477   name, 477   name,
HITCBC 478   6 std::string(path_.substr(start))); 478   6 std::string(path_.substr(start)));
HITCBC 479   3 return true; 479   3 return true;
480   } 480   }
481   481  
482   // Get the first character of the next meaningful token 482   // Get the first character of the next meaningful token
483   // Returns '\0' if none exists or next token is not text 483   // Returns '\0' if none exists or next token is not text
484   static char 484   static char
HITCBC 485   176 get_stop_char( 485   176 get_stop_char(
486   std::vector<route_token> const& tokens, 486   std::vector<route_token> const& tokens,
487   std::size_t next_idx) 487   std::size_t next_idx)
488   { 488   {
HITCBC 489   176 if(next_idx >= tokens.size()) 489   176 if(next_idx >= tokens.size())
HITCBC 490   113 return '\0'; 490   113 return '\0';
491   491  
HITCBC 492   63 auto const& next = tokens[next_idx]; 492   63 auto const& next = tokens[next_idx];
HITCBC 493   63 if(next.type == route_token_type::text && !next.value.empty()) 493   63 if(next.type == route_token_type::text && !next.value.empty())
HITCBC 494   15 return next.value[0]; 494   15 return next.value[0];
495   495  
HITCBC 496   48 return '\0'; 496   48 return '\0';
497   } 497   }
498   498  
499   // Match a sequence of tokens 499   // Match a sequence of tokens
HITCBC 500   119 bool match_tokens(std::vector<route_token> const& tokens) 500   119 bool match_tokens(std::vector<route_token> const& tokens)
501   { 501   {
HITCBC 502   273 for(std::size_t i = 0; i < tokens.size(); ++i) 502   273 for(std::size_t i = 0; i < tokens.size(); ++i)
503   { 503   {
HITCBC 504   176 if(!match_token(tokens[i], get_stop_char(tokens, i + 1))) 504   176 if(!match_token(tokens[i], get_stop_char(tokens, i + 1)))
HITCBC 505   22 return false; 505   22 return false;
506   } 506   }
HITCBC 507   97 return true; 507   97 return true;
508   } 508   }
509   509  
510   // Match a single token 510   // Match a single token
HITCBC 511   176 bool match_token(route_token const& token, char stop_char = '\0') 511   176 bool match_token(route_token const& token, char stop_char = '\0')
512   { 512   {
HITCBC 513   176 switch(token.type) 513   176 switch(token.type)
514   { 514   {
HITCBC 515   129 case route_token_type::text: 515   129 case route_token_type::text:
HITCBC 516   129 return match_text(token.value); 516   129 return match_text(token.value);
517   517  
HITCBC 518   26 case route_token_type::param: 518   26 case route_token_type::param:
HITCBC 519   26 return match_param(token.value, stop_char); 519   26 return match_param(token.value, stop_char);
520   520  
HITCBC 521   4 case route_token_type::wildcard: 521   4 case route_token_type::wildcard:
HITCBC 522   4 return match_wildcard(token.value); 522   4 return match_wildcard(token.value);
523   523  
HITCBC 524   17 case route_token_type::group: 524   17 case route_token_type::group:
HITCBC 525   17 return match_group(token.children); 525   17 return match_group(token.children);
526   526  
MISUBC 527   default: 527   default:
MISUBC 528   return false; 528   return false;
529   } 529   }
530   } 530   }
531   531  
532   // Match group - try with contents, then without 532   // Match group - try with contents, then without
HITCBC 533   17 bool match_group(std::vector<route_token> const& children) 533   17 bool match_group(std::vector<route_token> const& children)
534   { 534   {
535   // Save state before trying group 535   // Save state before trying group
HITCBC 536   17 auto saved_pos = pos_; 536   17 auto saved_pos = pos_;
HITCBC 537   17 auto saved_params_size = params_.size(); 537   17 auto saved_params_size = params_.size();
538   538  
539   // Try matching with group contents 539   // Try matching with group contents
HITCBC 540   17 if(match_tokens(children)) 540   17 if(match_tokens(children))
HITCBC 541   9 return true; 541   9 return true;
542   542  
543   // Restore state and try without group 543   // Restore state and try without group
HITCBC 544   8 pos_ = saved_pos; 544   8 pos_ = saved_pos;
HITCBC 545   8 params_.resize(saved_params_size); 545   8 params_.resize(saved_params_size);
HITCBC 546   8 return true; // Group is optional, always succeeds if skipped 546   8 return true; // Group is optional, always succeeds if skipped
547   } 547   }
548   548  
549   // Check if match is complete based on options 549   // Check if match is complete based on options
HITCBC 550   88 bool is_complete() const 550   88 bool is_complete() const
551   { 551   {
HITCBC 552   88 if(!opts_.end) 552   88 if(!opts_.end)
HITCBC 553   42 return true; // Prefix match always succeeds 553   42 return true; // Prefix match always succeeds
554   554  
HITCBC 555   46 if(opts_.strict) 555   46 if(opts_.strict)
HITCBC 556   2 return at_end(); 556   2 return at_end();
557   557  
558   // Non-strict: allow trailing slash 558   // Non-strict: allow trailing slash
HITCBC 559   44 if(at_end()) 559   44 if(at_end())
HITCBC 560   42 return true; 560   42 return true;
HITCBC 561   2 if(pos_ == path_.size() - 1 && path_[pos_] == '/') 561   2 if(pos_ == path_.size() - 1 && path_[pos_] == '/')
HITCBC 562   2 return true; 562   2 return true;
563   563  
MISUBC 564   return false; 564   return false;
565   } 565   }
566   }; 566   };
567   567  
568   } // anonymous namespace 568   } // anonymous namespace
569   569  
570   //------------------------------------------------ 570   //------------------------------------------------
571   571  
572   system::result<route_pattern> 572   system::result<route_pattern>
HITCBC 573   136 parse_route_pattern(core::string_view pattern) 573   136 parse_route_pattern(core::string_view pattern)
574   { 574   {
HITCBC 575   136 parser p(pattern); 575   136 parser p(pattern);
HITCBC 576   136 auto rv = p.parse_tokens(); 576   136 auto rv = p.parse_tokens();
HITCBC 577   136 if(rv.has_error()) 577   136 if(rv.has_error())
HITCBC 578   12 return rv.error(); 578   12 return rv.error();
579   579  
HITCBC 580   124 route_pattern result; 580   124 route_pattern result;
HITCBC 581   124 result.tokens = std::move(rv.value()); 581   124 result.tokens = std::move(rv.value());
HITCBC 582   124 result.original = std::string(pattern); 582   124 result.original = std::string(pattern);
HITCBC 583   124 return result; 583   124 return result;
HITCBC 584   136 } 584   136 }
585   585  
586   //------------------------------------------------ 586   //------------------------------------------------
587   587  
588   system::result<match_params> 588   system::result<match_params>
HITCBC 589   102 match_route( 589   102 match_route(
590   core::string_view path, 590   core::string_view path,
591   route_pattern const& pattern, 591   route_pattern const& pattern,
592   match_options const& opts) 592   match_options const& opts)
593   { 593   {
HITCBC 594   102 route_matcher m(path, opts); 594   102 route_matcher m(path, opts);
595   595  
HITCBC 596   102 if(!m.match_tokens(pattern.tokens)) 596   102 if(!m.match_tokens(pattern.tokens))
HITCBC 597   14 return grammar::error::mismatch; 597   14 return grammar::error::mismatch;
598   598  
HITCBC 599   88 if(!m.is_complete()) 599   88 if(!m.is_complete())
HITCBC 600   1 return grammar::error::mismatch; 600   1 return grammar::error::mismatch;
601   601  
HITCBC 602   87 match_params result; 602   87 match_params result;
HITCBC 603   87 result.params = m.params(); 603   87 result.params = m.params();
HITCBC 604   87 result.matched_length = m.pos(); 604   87 result.matched_length = m.pos();
HITCBC 605   87 return result; 605   87 return result;
HITCBC 606   102 } 606   102 }
607   607  
608   } // detail 608   } // detail
609   } // http 609   } // http
610   } // boost 610   } // boost