gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/mux/regexp.go (about) 1 // Copyright 2012 The Gorilla Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package mux 6 7 import ( 8 "bytes" 9 "fmt" 10 "net/url" 11 "regexp" 12 "strconv" 13 "strings" 14 15 http "gitee.com/ks-custle/core-gm/gmhttp" 16 ) 17 18 type routeRegexpOptions struct { 19 strictSlash bool 20 useEncodedPath bool 21 } 22 23 type regexpType int 24 25 const ( 26 regexpTypePath regexpType = 0 27 regexpTypeHost regexpType = 1 28 regexpTypePrefix regexpType = 2 29 regexpTypeQuery regexpType = 3 30 ) 31 32 // newRouteRegexp parses a route template and returns a routeRegexp, 33 // used to match a host, a path or a query string. 34 // 35 // It will extract named variables, assemble a regexp to be matched, create 36 // a "reverse" template to build URLs and compile regexps to validate variable 37 // values used in URL building. 38 // 39 // Previously we accepted only Python-like identifiers for variable 40 // names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that 41 // name and pattern can't be empty, and names can't contain a colon. 42 func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*routeRegexp, error) { 43 // Check if it is well-formed. 44 idxs, errBraces := braceIndices(tpl) 45 if errBraces != nil { 46 return nil, errBraces 47 } 48 // Backup the original. 49 template := tpl 50 // Now let's parse it. 51 defaultPattern := "[^/]+" 52 if typ == regexpTypeQuery { 53 defaultPattern = ".*" 54 } else if typ == regexpTypeHost { 55 defaultPattern = "[^.]+" 56 } 57 // Only match strict slash if not matching 58 if typ != regexpTypePath { 59 options.strictSlash = false 60 } 61 // Set a flag for strictSlash. 62 endSlash := false 63 if options.strictSlash && strings.HasSuffix(tpl, "/") { 64 tpl = tpl[:len(tpl)-1] 65 endSlash = true 66 } 67 varsN := make([]string, len(idxs)/2) 68 varsR := make([]*regexp.Regexp, len(idxs)/2) 69 pattern := bytes.NewBufferString("") 70 pattern.WriteByte('^') 71 reverse := bytes.NewBufferString("") 72 var end int 73 var err error 74 for i := 0; i < len(idxs); i += 2 { 75 // Set all values we are interested in. 76 raw := tpl[end:idxs[i]] 77 end = idxs[i+1] 78 parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2) 79 name := parts[0] 80 patt := defaultPattern 81 if len(parts) == 2 { 82 patt = parts[1] 83 } 84 // Name or pattern can't be empty. 85 if name == "" || patt == "" { 86 return nil, fmt.Errorf("mux: missing name or pattern in %q", 87 tpl[idxs[i]:end]) 88 } 89 // Build the regexp pattern. 90 fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt) 91 92 // Build the reverse template. 93 fmt.Fprintf(reverse, "%s%%s", raw) 94 95 // Append variable name and compiled pattern. 96 varsN[i/2] = name 97 varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt)) 98 if err != nil { 99 return nil, err 100 } 101 } 102 // Add the remaining. 103 raw := tpl[end:] 104 pattern.WriteString(regexp.QuoteMeta(raw)) 105 if options.strictSlash { 106 pattern.WriteString("[/]?") 107 } 108 if typ == regexpTypeQuery { 109 // Add the default pattern if the query value is empty 110 if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" { 111 pattern.WriteString(defaultPattern) 112 } 113 } 114 if typ != regexpTypePrefix { 115 pattern.WriteByte('$') 116 } 117 118 var wildcardHostPort bool 119 if typ == regexpTypeHost { 120 if !strings.Contains(pattern.String(), ":") { 121 wildcardHostPort = true 122 } 123 } 124 reverse.WriteString(raw) 125 if endSlash { 126 reverse.WriteByte('/') 127 } 128 // Compile full regexp. 129 reg, errCompile := regexp.Compile(pattern.String()) 130 if errCompile != nil { 131 return nil, errCompile 132 } 133 134 // Check for capturing groups which used to work in older versions 135 if reg.NumSubexp() != len(idxs)/2 { 136 panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) + 137 "Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)") 138 } 139 140 // Done! 141 return &routeRegexp{ 142 template: template, 143 regexpType: typ, 144 options: options, 145 regexp: reg, 146 reverse: reverse.String(), 147 varsN: varsN, 148 varsR: varsR, 149 wildcardHostPort: wildcardHostPort, 150 }, nil 151 } 152 153 // routeRegexp stores a regexp to match a host or path and information to 154 // collect and validate route variables. 155 type routeRegexp struct { 156 // The unmodified template. 157 template string 158 // The type of match 159 regexpType regexpType 160 // Options for matching 161 options routeRegexpOptions 162 // Expanded regexp. 163 regexp *regexp.Regexp 164 // Reverse template. 165 reverse string 166 // Variable names. 167 varsN []string 168 // Variable regexps (validators). 169 varsR []*regexp.Regexp 170 // Wildcard host-port (no strict port match in hostname) 171 wildcardHostPort bool 172 } 173 174 // Match matches the regexp against the URL host or path. 175 func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { 176 if r.regexpType == regexpTypeHost { 177 host := getHost(req) 178 if r.wildcardHostPort { 179 // Don't be strict on the port match 180 if i := strings.Index(host, ":"); i != -1 { 181 host = host[:i] 182 } 183 } 184 return r.regexp.MatchString(host) 185 } 186 187 if r.regexpType == regexpTypeQuery { 188 return r.matchQueryString(req) 189 } 190 path := req.URL.Path 191 if r.options.useEncodedPath { 192 path = req.URL.EscapedPath() 193 } 194 return r.regexp.MatchString(path) 195 } 196 197 // url builds a URL part using the given values. 198 func (r *routeRegexp) url(values map[string]string) (string, error) { 199 urlValues := make([]interface{}, len(r.varsN), len(r.varsN)) 200 for k, v := range r.varsN { 201 value, ok := values[v] 202 if !ok { 203 return "", fmt.Errorf("mux: missing route variable %q", v) 204 } 205 if r.regexpType == regexpTypeQuery { 206 value = url.QueryEscape(value) 207 } 208 urlValues[k] = value 209 } 210 rv := fmt.Sprintf(r.reverse, urlValues...) 211 if !r.regexp.MatchString(rv) { 212 // The URL is checked against the full regexp, instead of checking 213 // individual variables. This is faster but to provide a good error 214 // message, we check individual regexps if the URL doesn't match. 215 for k, v := range r.varsN { 216 if !r.varsR[k].MatchString(values[v]) { 217 return "", fmt.Errorf( 218 "mux: variable %q doesn't match, expected %q", values[v], 219 r.varsR[k].String()) 220 } 221 } 222 } 223 return rv, nil 224 } 225 226 // getURLQuery returns a single query parameter from a request URL. 227 // For a URL with foo=bar&baz=ding, we return only the relevant key 228 // value pair for the routeRegexp. 229 func (r *routeRegexp) getURLQuery(req *http.Request) string { 230 if r.regexpType != regexpTypeQuery { 231 return "" 232 } 233 templateKey := strings.SplitN(r.template, "=", 2)[0] 234 val, ok := findFirstQueryKey(req.URL.RawQuery, templateKey) 235 if ok { 236 return templateKey + "=" + val 237 } 238 return "" 239 } 240 241 // findFirstQueryKey returns the same result as (*url.URL).Query()[key][0]. 242 // If key was not found, empty string and false is returned. 243 func findFirstQueryKey(rawQuery, key string) (value string, ok bool) { 244 query := []byte(rawQuery) 245 for len(query) > 0 { 246 foundKey := query 247 if i := bytes.IndexAny(foundKey, "&;"); i >= 0 { 248 foundKey, query = foundKey[:i], foundKey[i+1:] 249 } else { 250 query = query[:0] 251 } 252 if len(foundKey) == 0 { 253 continue 254 } 255 var value []byte 256 if i := bytes.IndexByte(foundKey, '='); i >= 0 { 257 foundKey, value = foundKey[:i], foundKey[i+1:] 258 } 259 if len(foundKey) < len(key) { 260 // Cannot possibly be key. 261 continue 262 } 263 keyString, err := url.QueryUnescape(string(foundKey)) 264 if err != nil { 265 continue 266 } 267 if keyString != key { 268 continue 269 } 270 valueString, err := url.QueryUnescape(string(value)) 271 if err != nil { 272 continue 273 } 274 return valueString, true 275 } 276 return "", false 277 } 278 279 func (r *routeRegexp) matchQueryString(req *http.Request) bool { 280 return r.regexp.MatchString(r.getURLQuery(req)) 281 } 282 283 // braceIndices returns the first level curly brace indices from a string. 284 // It returns an error in case of unbalanced braces. 285 func braceIndices(s string) ([]int, error) { 286 var level, idx int 287 var idxs []int 288 for i := 0; i < len(s); i++ { 289 switch s[i] { 290 case '{': 291 if level++; level == 1 { 292 idx = i 293 } 294 case '}': 295 if level--; level == 0 { 296 idxs = append(idxs, idx, i+1) 297 } else if level < 0 { 298 return nil, fmt.Errorf("mux: unbalanced braces in %q", s) 299 } 300 } 301 } 302 if level != 0 { 303 return nil, fmt.Errorf("mux: unbalanced braces in %q", s) 304 } 305 return idxs, nil 306 } 307 308 // varGroupName builds a capturing group name for the indexed variable. 309 func varGroupName(idx int) string { 310 return "v" + strconv.Itoa(idx) 311 } 312 313 // ---------------------------------------------------------------------------- 314 // routeRegexpGroup 315 // ---------------------------------------------------------------------------- 316 317 // routeRegexpGroup groups the route matchers that carry variables. 318 type routeRegexpGroup struct { 319 host *routeRegexp 320 path *routeRegexp 321 queries []*routeRegexp 322 } 323 324 // setMatch extracts the variables from the URL once a route matches. 325 func (v routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) { 326 // Store host variables. 327 if v.host != nil { 328 host := getHost(req) 329 if v.host.wildcardHostPort { 330 // Don't be strict on the port match 331 if i := strings.Index(host, ":"); i != -1 { 332 host = host[:i] 333 } 334 } 335 matches := v.host.regexp.FindStringSubmatchIndex(host) 336 if len(matches) > 0 { 337 extractVars(host, matches, v.host.varsN, m.Vars) 338 } 339 } 340 path := req.URL.Path 341 if r.useEncodedPath { 342 path = req.URL.EscapedPath() 343 } 344 // Store path variables. 345 if v.path != nil { 346 matches := v.path.regexp.FindStringSubmatchIndex(path) 347 if len(matches) > 0 { 348 extractVars(path, matches, v.path.varsN, m.Vars) 349 // Check if we should redirect. 350 if v.path.options.strictSlash { 351 p1 := strings.HasSuffix(path, "/") 352 p2 := strings.HasSuffix(v.path.template, "/") 353 if p1 != p2 { 354 u, _ := url.Parse(req.URL.String()) 355 if p1 { 356 u.Path = u.Path[:len(u.Path)-1] 357 } else { 358 u.Path += "/" 359 } 360 m.Handler = http.RedirectHandler(u.String(), http.StatusMovedPermanently) 361 } 362 } 363 } 364 } 365 // Store query string variables. 366 for _, q := range v.queries { 367 queryURL := q.getURLQuery(req) 368 matches := q.regexp.FindStringSubmatchIndex(queryURL) 369 if len(matches) > 0 { 370 extractVars(queryURL, matches, q.varsN, m.Vars) 371 } 372 } 373 } 374 375 // getHost tries its best to return the request host. 376 // According to section 14.23 of RFC 2616 the Host header 377 // can include the port number if the default value of 80 is not used. 378 func getHost(r *http.Request) string { 379 if r.URL.IsAbs() { 380 return r.URL.Host 381 } 382 return r.Host 383 } 384 385 func extractVars(input string, matches []int, names []string, output map[string]string) { 386 for i, name := range names { 387 output[name] = input[matches[2*i+2]:matches[2*i+3]] 388 } 389 }