regexp.go 7.2 KB

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