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