Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 

389 Zeilen
10 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. var wildcardHostPort bool
  109. if typ == regexpTypeHost {
  110. if !strings.Contains(pattern.String(), ":") {
  111. wildcardHostPort = true
  112. }
  113. }
  114. reverse.WriteString(raw)
  115. if endSlash {
  116. reverse.WriteByte('/')
  117. }
  118. // Compile full regexp.
  119. reg, errCompile := regexp.Compile(pattern.String())
  120. if errCompile != nil {
  121. return nil, errCompile
  122. }
  123. // Check for capturing groups which used to work in older versions
  124. if reg.NumSubexp() != len(idxs)/2 {
  125. panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) +
  126. "Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)")
  127. }
  128. // Done!
  129. return &routeRegexp{
  130. template: template,
  131. regexpType: typ,
  132. options: options,
  133. regexp: reg,
  134. reverse: reverse.String(),
  135. varsN: varsN,
  136. varsR: varsR,
  137. wildcardHostPort: wildcardHostPort,
  138. }, nil
  139. }
  140. // routeRegexp stores a regexp to match a host or path and information to
  141. // collect and validate route variables.
  142. type routeRegexp struct {
  143. // The unmodified template.
  144. template string
  145. // The type of match
  146. regexpType regexpType
  147. // Options for matching
  148. options routeRegexpOptions
  149. // Expanded regexp.
  150. regexp *regexp.Regexp
  151. // Reverse template.
  152. reverse string
  153. // Variable names.
  154. varsN []string
  155. // Variable regexps (validators).
  156. varsR []*regexp.Regexp
  157. // Wildcard host-port (no strict port match in hostname)
  158. wildcardHostPort bool
  159. }
  160. // Match matches the regexp against the URL host or path.
  161. func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
  162. if r.regexpType == regexpTypeHost {
  163. host := getHost(req)
  164. if r.wildcardHostPort {
  165. // Don't be strict on the port match
  166. if i := strings.Index(host, ":"); i != -1 {
  167. host = host[:i]
  168. }
  169. }
  170. return r.regexp.MatchString(host)
  171. }
  172. if r.regexpType == regexpTypeQuery {
  173. return r.matchQueryString(req)
  174. }
  175. path := req.URL.Path
  176. if r.options.useEncodedPath {
  177. path = req.URL.EscapedPath()
  178. }
  179. return r.regexp.MatchString(path)
  180. }
  181. // url builds a URL part using the given values.
  182. func (r *routeRegexp) url(values map[string]string) (string, error) {
  183. urlValues := make([]interface{}, len(r.varsN), len(r.varsN))
  184. for k, v := range r.varsN {
  185. value, ok := values[v]
  186. if !ok {
  187. return "", fmt.Errorf("mux: missing route variable %q", v)
  188. }
  189. if r.regexpType == regexpTypeQuery {
  190. value = url.QueryEscape(value)
  191. }
  192. urlValues[k] = value
  193. }
  194. rv := fmt.Sprintf(r.reverse, urlValues...)
  195. if !r.regexp.MatchString(rv) {
  196. // The URL is checked against the full regexp, instead of checking
  197. // individual variables. This is faster but to provide a good error
  198. // message, we check individual regexps if the URL doesn't match.
  199. for k, v := range r.varsN {
  200. if !r.varsR[k].MatchString(values[v]) {
  201. return "", fmt.Errorf(
  202. "mux: variable %q doesn't match, expected %q", values[v],
  203. r.varsR[k].String())
  204. }
  205. }
  206. }
  207. return rv, nil
  208. }
  209. // getURLQuery returns a single query parameter from a request URL.
  210. // For a URL with foo=bar&baz=ding, we return only the relevant key
  211. // value pair for the routeRegexp.
  212. func (r *routeRegexp) getURLQuery(req *http.Request) string {
  213. if r.regexpType != regexpTypeQuery {
  214. return ""
  215. }
  216. templateKey := strings.SplitN(r.template, "=", 2)[0]
  217. val, ok := findFirstQueryKey(req.URL.RawQuery, templateKey)
  218. if ok {
  219. return templateKey + "=" + val
  220. }
  221. return ""
  222. }
  223. // findFirstQueryKey returns the same result as (*url.URL).Query()[key][0].
  224. // If key was not found, empty string and false is returned.
  225. func findFirstQueryKey(rawQuery, key string) (value string, ok bool) {
  226. query := []byte(rawQuery)
  227. for len(query) > 0 {
  228. foundKey := query
  229. if i := bytes.IndexAny(foundKey, "&;"); i >= 0 {
  230. foundKey, query = foundKey[:i], foundKey[i+1:]
  231. } else {
  232. query = query[:0]
  233. }
  234. if len(foundKey) == 0 {
  235. continue
  236. }
  237. var value []byte
  238. if i := bytes.IndexByte(foundKey, '='); i >= 0 {
  239. foundKey, value = foundKey[:i], foundKey[i+1:]
  240. }
  241. if len(foundKey) < len(key) {
  242. // Cannot possibly be key.
  243. continue
  244. }
  245. keyString, err := url.QueryUnescape(string(foundKey))
  246. if err != nil {
  247. continue
  248. }
  249. if keyString != key {
  250. continue
  251. }
  252. valueString, err := url.QueryUnescape(string(value))
  253. if err != nil {
  254. continue
  255. }
  256. return valueString, true
  257. }
  258. return "", false
  259. }
  260. func (r *routeRegexp) matchQueryString(req *http.Request) bool {
  261. return r.regexp.MatchString(r.getURLQuery(req))
  262. }
  263. // braceIndices returns the first level curly brace indices from a string.
  264. // It returns an error in case of unbalanced braces.
  265. func braceIndices(s string) ([]int, error) {
  266. var level, idx int
  267. var idxs []int
  268. for i := 0; i < len(s); i++ {
  269. switch s[i] {
  270. case '{':
  271. if level++; level == 1 {
  272. idx = i
  273. }
  274. case '}':
  275. if level--; level == 0 {
  276. idxs = append(idxs, idx, i+1)
  277. } else if level < 0 {
  278. return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
  279. }
  280. }
  281. }
  282. if level != 0 {
  283. return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
  284. }
  285. return idxs, nil
  286. }
  287. // varGroupName builds a capturing group name for the indexed variable.
  288. func varGroupName(idx int) string {
  289. return "v" + strconv.Itoa(idx)
  290. }
  291. // ----------------------------------------------------------------------------
  292. // routeRegexpGroup
  293. // ----------------------------------------------------------------------------
  294. // routeRegexpGroup groups the route matchers that carry variables.
  295. type routeRegexpGroup struct {
  296. host *routeRegexp
  297. path *routeRegexp
  298. queries []*routeRegexp
  299. }
  300. // setMatch extracts the variables from the URL once a route matches.
  301. func (v routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
  302. // Store host variables.
  303. if v.host != nil {
  304. host := getHost(req)
  305. if v.host.wildcardHostPort {
  306. // Don't be strict on the port match
  307. if i := strings.Index(host, ":"); i != -1 {
  308. host = host[:i]
  309. }
  310. }
  311. matches := v.host.regexp.FindStringSubmatchIndex(host)
  312. if len(matches) > 0 {
  313. extractVars(host, matches, v.host.varsN, m.Vars)
  314. }
  315. }
  316. path := req.URL.Path
  317. if r.useEncodedPath {
  318. path = req.URL.EscapedPath()
  319. }
  320. // Store path variables.
  321. if v.path != nil {
  322. matches := v.path.regexp.FindStringSubmatchIndex(path)
  323. if len(matches) > 0 {
  324. extractVars(path, matches, v.path.varsN, m.Vars)
  325. // Check if we should redirect.
  326. if v.path.options.strictSlash {
  327. p1 := strings.HasSuffix(path, "/")
  328. p2 := strings.HasSuffix(v.path.template, "/")
  329. if p1 != p2 {
  330. u, _ := url.Parse(req.URL.String())
  331. if p1 {
  332. u.Path = u.Path[:len(u.Path)-1]
  333. } else {
  334. u.Path += "/"
  335. }
  336. m.Handler = http.RedirectHandler(u.String(), http.StatusMovedPermanently)
  337. }
  338. }
  339. }
  340. }
  341. // Store query string variables.
  342. for _, q := range v.queries {
  343. queryURL := q.getURLQuery(req)
  344. matches := q.regexp.FindStringSubmatchIndex(queryURL)
  345. if len(matches) > 0 {
  346. extractVars(queryURL, matches, q.varsN, m.Vars)
  347. }
  348. }
  349. }
  350. // getHost tries its best to return the request host.
  351. // According to section 14.23 of RFC 2616 the Host header
  352. // can include the port number if the default value of 80 is not used.
  353. func getHost(r *http.Request) string {
  354. if r.URL.IsAbs() {
  355. return r.URL.Host
  356. }
  357. return r.Host
  358. }
  359. func extractVars(input string, matches []int, names []string, output map[string]string) {
  360. for i, name := range names {
  361. output[name] = input[matches[2*i+2]:matches[2*i+3]]
  362. }
  363. }