You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

329 lines
8.8 KiB

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