|  | // Copyright 2012 The Gorilla Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package mux | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "fmt" | 
|  | "net/http" | 
|  | "net/url" | 
|  | "regexp" | 
|  | "strconv" | 
|  | "strings" | 
|  | ) | 
|  |  | 
|  | // newRouteRegexp parses a route template and returns a routeRegexp, | 
|  | // used to match a host, a path or a query string. | 
|  | // | 
|  | // It will extract named variables, assemble a regexp to be matched, create | 
|  | // a "reverse" template to build URLs and compile regexps to validate variable | 
|  | // values used in URL building. | 
|  | // | 
|  | // Previously we accepted only Python-like identifiers for variable | 
|  | // names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that | 
|  | // name and pattern can't be empty, and names can't contain a colon. | 
|  | func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash, useEncodedPath bool) (*routeRegexp, error) { | 
|  | // Check if it is well-formed. | 
|  | idxs, errBraces := braceIndices(tpl) | 
|  | if errBraces != nil { | 
|  | return nil, errBraces | 
|  | } | 
|  | // Backup the original. | 
|  | template := tpl | 
|  | // Now let's parse it. | 
|  | defaultPattern := "[^/]+" | 
|  | if matchQuery { | 
|  | defaultPattern = "[^?&]*" | 
|  | } else if matchHost { | 
|  | defaultPattern = "[^.]+" | 
|  | matchPrefix = false | 
|  | } | 
|  | // Only match strict slash if not matching | 
|  | if matchPrefix || matchHost || matchQuery { | 
|  | strictSlash = false | 
|  | } | 
|  | // Set a flag for strictSlash. | 
|  | endSlash := false | 
|  | if strictSlash && strings.HasSuffix(tpl, "/") { | 
|  | tpl = tpl[:len(tpl)-1] | 
|  | endSlash = true | 
|  | } | 
|  | varsN := make([]string, len(idxs)/2) | 
|  | varsR := make([]*regexp.Regexp, len(idxs)/2) | 
|  | pattern := bytes.NewBufferString("") | 
|  | pattern.WriteByte('^') | 
|  | reverse := bytes.NewBufferString("") | 
|  | var end int | 
|  | var err error | 
|  | for i := 0; i < len(idxs); i += 2 { | 
|  | // Set all values we are interested in. | 
|  | raw := tpl[end:idxs[i]] | 
|  | end = idxs[i+1] | 
|  | parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2) | 
|  | name := parts[0] | 
|  | patt := defaultPattern | 
|  | if len(parts) == 2 { | 
|  | patt = parts[1] | 
|  | } | 
|  | // Name or pattern can't be empty. | 
|  | if name == "" || patt == "" { | 
|  | return nil, fmt.Errorf("mux: missing name or pattern in %q", | 
|  | tpl[idxs[i]:end]) | 
|  | } | 
|  | // Build the regexp pattern. | 
|  | fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt) | 
|  |  | 
|  | // Build the reverse template. | 
|  | fmt.Fprintf(reverse, "%s%%s", raw) | 
|  |  | 
|  | // Append variable name and compiled pattern. | 
|  | varsN[i/2] = name | 
|  | varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt)) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | } | 
|  | // Add the remaining. | 
|  | raw := tpl[end:] | 
|  | pattern.WriteString(regexp.QuoteMeta(raw)) | 
|  | if strictSlash { | 
|  | pattern.WriteString("[/]?") | 
|  | } | 
|  | if matchQuery { | 
|  | // Add the default pattern if the query value is empty | 
|  | if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" { | 
|  | pattern.WriteString(defaultPattern) | 
|  | } | 
|  | } | 
|  | if !matchPrefix { | 
|  | pattern.WriteByte('$') | 
|  | } | 
|  | reverse.WriteString(raw) | 
|  | if endSlash { | 
|  | reverse.WriteByte('/') | 
|  | } | 
|  | // Compile full regexp. | 
|  | reg, errCompile := regexp.Compile(pattern.String()) | 
|  | if errCompile != nil { | 
|  | return nil, errCompile | 
|  | } | 
|  |  | 
|  | // Check for capturing groups which used to work in older versions | 
|  | if reg.NumSubexp() != len(idxs)/2 { | 
|  | panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) + | 
|  | "Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)") | 
|  | } | 
|  |  | 
|  | // Done! | 
|  | return &routeRegexp{ | 
|  | template:       template, | 
|  | matchHost:      matchHost, | 
|  | matchQuery:     matchQuery, | 
|  | strictSlash:    strictSlash, | 
|  | useEncodedPath: useEncodedPath, | 
|  | regexp:         reg, | 
|  | reverse:        reverse.String(), | 
|  | varsN:          varsN, | 
|  | varsR:          varsR, | 
|  | }, nil | 
|  | } | 
|  |  | 
|  | // routeRegexp stores a regexp to match a host or path and information to | 
|  | // collect and validate route variables. | 
|  | type routeRegexp struct { | 
|  | // The unmodified template. | 
|  | template string | 
|  | // True for host match, false for path or query string match. | 
|  | matchHost bool | 
|  | // True for query string match, false for path and host match. | 
|  | matchQuery bool | 
|  | // The strictSlash value defined on the route, but disabled if PathPrefix was used. | 
|  | strictSlash bool | 
|  | // Determines whether to use encoded path from getPath function or unencoded | 
|  | // req.URL.Path for path matching | 
|  | useEncodedPath bool | 
|  | // Expanded regexp. | 
|  | regexp *regexp.Regexp | 
|  | // Reverse template. | 
|  | reverse string | 
|  | // Variable names. | 
|  | varsN []string | 
|  | // Variable regexps (validators). | 
|  | varsR []*regexp.Regexp | 
|  | } | 
|  |  | 
|  | // Match matches the regexp against the URL host or path. | 
|  | func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { | 
|  | if !r.matchHost { | 
|  | if r.matchQuery { | 
|  | return r.matchQueryString(req) | 
|  | } | 
|  | path := req.URL.Path | 
|  | if r.useEncodedPath { | 
|  | path = getPath(req) | 
|  | } | 
|  | return r.regexp.MatchString(path) | 
|  | } | 
|  |  | 
|  | return r.regexp.MatchString(getHost(req)) | 
|  | } | 
|  |  | 
|  | // url builds a URL part using the given values. | 
|  | func (r *routeRegexp) url(values map[string]string) (string, error) { | 
|  | urlValues := make([]interface{}, len(r.varsN)) | 
|  | for k, v := range r.varsN { | 
|  | value, ok := values[v] | 
|  | if !ok { | 
|  | return "", fmt.Errorf("mux: missing route variable %q", v) | 
|  | } | 
|  | urlValues[k] = value | 
|  | } | 
|  | rv := fmt.Sprintf(r.reverse, urlValues...) | 
|  | if !r.regexp.MatchString(rv) { | 
|  | // The URL is checked against the full regexp, instead of checking | 
|  | // individual variables. This is faster but to provide a good error | 
|  | // message, we check individual regexps if the URL doesn't match. | 
|  | for k, v := range r.varsN { | 
|  | if !r.varsR[k].MatchString(values[v]) { | 
|  | return "", fmt.Errorf( | 
|  | "mux: variable %q doesn't match, expected %q", values[v], | 
|  | r.varsR[k].String()) | 
|  | } | 
|  | } | 
|  | } | 
|  | return rv, nil | 
|  | } | 
|  |  | 
|  | // getURLQuery returns a single query parameter from a request URL. | 
|  | // For a URL with foo=bar&baz=ding, we return only the relevant key | 
|  | // value pair for the routeRegexp. | 
|  | func (r *routeRegexp) getURLQuery(req *http.Request) string { | 
|  | if !r.matchQuery { | 
|  | return "" | 
|  | } | 
|  | templateKey := strings.SplitN(r.template, "=", 2)[0] | 
|  | for key, vals := range req.URL.Query() { | 
|  | if key == templateKey && len(vals) > 0 { | 
|  | return key + "=" + vals[0] | 
|  | } | 
|  | } | 
|  | return "" | 
|  | } | 
|  |  | 
|  | func (r *routeRegexp) matchQueryString(req *http.Request) bool { | 
|  | return r.regexp.MatchString(r.getURLQuery(req)) | 
|  | } | 
|  |  | 
|  | // braceIndices returns the first level curly brace indices from a string. | 
|  | // It returns an error in case of unbalanced braces. | 
|  | func braceIndices(s string) ([]int, error) { | 
|  | var level, idx int | 
|  | var idxs []int | 
|  | for i := 0; i < len(s); i++ { | 
|  | switch s[i] { | 
|  | case '{': | 
|  | if level++; level == 1 { | 
|  | idx = i | 
|  | } | 
|  | case '}': | 
|  | if level--; level == 0 { | 
|  | idxs = append(idxs, idx, i+1) | 
|  | } else if level < 0 { | 
|  | return nil, fmt.Errorf("mux: unbalanced braces in %q", s) | 
|  | } | 
|  | } | 
|  | } | 
|  | if level != 0 { | 
|  | return nil, fmt.Errorf("mux: unbalanced braces in %q", s) | 
|  | } | 
|  | return idxs, nil | 
|  | } | 
|  |  | 
|  | // varGroupName builds a capturing group name for the indexed variable. | 
|  | func varGroupName(idx int) string { | 
|  | return "v" + strconv.Itoa(idx) | 
|  | } | 
|  |  | 
|  | // ---------------------------------------------------------------------------- | 
|  | // routeRegexpGroup | 
|  | // ---------------------------------------------------------------------------- | 
|  |  | 
|  | // routeRegexpGroup groups the route matchers that carry variables. | 
|  | type routeRegexpGroup struct { | 
|  | host    *routeRegexp | 
|  | path    *routeRegexp | 
|  | queries []*routeRegexp | 
|  | } | 
|  |  | 
|  | // setMatch extracts the variables from the URL once a route matches. | 
|  | func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) { | 
|  | // Store host variables. | 
|  | if v.host != nil { | 
|  | host := getHost(req) | 
|  | matches := v.host.regexp.FindStringSubmatchIndex(host) | 
|  | if len(matches) > 0 { | 
|  | extractVars(host, matches, v.host.varsN, m.Vars) | 
|  | } | 
|  | } | 
|  | path := req.URL.Path | 
|  | if r.useEncodedPath { | 
|  | path = getPath(req) | 
|  | } | 
|  | // Store path variables. | 
|  | if v.path != nil { | 
|  | matches := v.path.regexp.FindStringSubmatchIndex(path) | 
|  | if len(matches) > 0 { | 
|  | extractVars(path, matches, v.path.varsN, m.Vars) | 
|  | // Check if we should redirect. | 
|  | if v.path.strictSlash { | 
|  | p1 := strings.HasSuffix(path, "/") | 
|  | p2 := strings.HasSuffix(v.path.template, "/") | 
|  | if p1 != p2 { | 
|  | u, _ := url.Parse(req.URL.String()) | 
|  | if p1 { | 
|  | u.Path = u.Path[:len(u.Path)-1] | 
|  | } else { | 
|  | u.Path += "/" | 
|  | } | 
|  | m.Handler = http.RedirectHandler(u.String(), 301) | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | // Store query string variables. | 
|  | for _, q := range v.queries { | 
|  | queryURL := q.getURLQuery(req) | 
|  | matches := q.regexp.FindStringSubmatchIndex(queryURL) | 
|  | if len(matches) > 0 { | 
|  | extractVars(queryURL, matches, q.varsN, m.Vars) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // getHost tries its best to return the request host. | 
|  | func getHost(r *http.Request) string { | 
|  | if r.URL.IsAbs() { | 
|  | return r.URL.Host | 
|  | } | 
|  | host := r.Host | 
|  | // Slice off any port information. | 
|  | if i := strings.Index(host, ":"); i != -1 { | 
|  | host = host[:i] | 
|  | } | 
|  | return host | 
|  |  | 
|  | } | 
|  |  | 
|  | func extractVars(input string, matches []int, names []string, output map[string]string) { | 
|  | for i, name := range names { | 
|  | output[name] = input[matches[2*i+2]:matches[2*i+3]] | 
|  | } | 
|  | } |