github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/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 "github.com/hxx258456/ccgo/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  }