regexp.go 8.8 KB

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