gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. // Copyright 2024 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package web
  4. import (
  5. "net/http"
  6. "regexp"
  7. "slices"
  8. "strings"
  9. "code.gitea.io/gitea/modules/container"
  10. "code.gitea.io/gitea/modules/util"
  11. "github.com/go-chi/chi/v5"
  12. )
  13. type RouterPathGroup struct {
  14. r *Router
  15. pathParam string
  16. matchers []*routerPathMatcher
  17. }
  18. func (g *RouterPathGroup) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
  19. chiCtx := chi.RouteContext(req.Context())
  20. path := chiCtx.URLParam(g.pathParam)
  21. for _, m := range g.matchers {
  22. if m.matchPath(chiCtx, path) {
  23. chiCtx.RoutePatterns = append(chiCtx.RoutePatterns, m.pattern)
  24. handler := m.handlerFunc
  25. for i := len(m.middlewares) - 1; i >= 0; i-- {
  26. handler = m.middlewares[i](handler).ServeHTTP
  27. }
  28. handler(resp, req)
  29. return
  30. }
  31. }
  32. g.r.chiRouter.NotFoundHandler().ServeHTTP(resp, req)
  33. }
  34. type RouterPathGroupPattern struct {
  35. pattern string
  36. re *regexp.Regexp
  37. params []routerPathParam
  38. middlewares []any
  39. }
  40. // MatchPath matches the request method, and uses regexp to match the path.
  41. // The pattern uses "<...>" to define path parameters, for example, "/<name>" (different from chi router)
  42. // It is only designed to resolve some special cases that chi router can't handle.
  43. // For most cases, it shouldn't be used because it needs to iterate all rules to find the matched one (inefficient).
  44. func (g *RouterPathGroup) MatchPath(methods, pattern string, h ...any) {
  45. g.MatchPattern(methods, g.PatternRegexp(pattern), h...)
  46. }
  47. func (g *RouterPathGroup) MatchPattern(methods string, pattern *RouterPathGroupPattern, h ...any) {
  48. g.matchers = append(g.matchers, newRouterPathMatcher(methods, pattern, h...))
  49. }
  50. type routerPathParam struct {
  51. name string
  52. captureGroup int
  53. }
  54. type routerPathMatcher struct {
  55. methods container.Set[string]
  56. pattern string
  57. re *regexp.Regexp
  58. params []routerPathParam
  59. middlewares []func(http.Handler) http.Handler
  60. handlerFunc http.HandlerFunc
  61. }
  62. func (p *routerPathMatcher) matchPath(chiCtx *chi.Context, path string) bool {
  63. if !p.methods.Contains(chiCtx.RouteMethod) {
  64. return false
  65. }
  66. if !strings.HasPrefix(path, "/") {
  67. path = "/" + path
  68. }
  69. pathMatches := p.re.FindStringSubmatchIndex(path) // Golang regexp match pairs [start, end, start, end, ...]
  70. if pathMatches == nil {
  71. return false
  72. }
  73. var paramMatches [][]int
  74. for i := 2; i < len(pathMatches); {
  75. paramMatches = append(paramMatches, []int{pathMatches[i], pathMatches[i+1]})
  76. pmIdx := len(paramMatches) - 1
  77. end := pathMatches[i+1]
  78. i += 2
  79. for ; i < len(pathMatches); i += 2 {
  80. if pathMatches[i] >= end {
  81. break
  82. }
  83. paramMatches[pmIdx] = append(paramMatches[pmIdx], pathMatches[i], pathMatches[i+1])
  84. }
  85. }
  86. for i, pm := range paramMatches {
  87. groupIdx := p.params[i].captureGroup * 2
  88. chiCtx.URLParams.Add(p.params[i].name, path[pm[groupIdx]:pm[groupIdx+1]])
  89. }
  90. return true
  91. }
  92. func isValidMethod(name string) bool {
  93. switch name {
  94. case http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodHead, http.MethodOptions, http.MethodConnect, http.MethodTrace:
  95. return true
  96. }
  97. return false
  98. }
  99. func newRouterPathMatcher(methods string, patternRegexp *RouterPathGroupPattern, h ...any) *routerPathMatcher {
  100. middlewares, handlerFunc := wrapMiddlewareAndHandler(patternRegexp.middlewares, h)
  101. p := &routerPathMatcher{methods: make(container.Set[string]), middlewares: middlewares, handlerFunc: handlerFunc}
  102. for method := range strings.SplitSeq(methods, ",") {
  103. method = strings.TrimSpace(method)
  104. if !isValidMethod(method) {
  105. panic("invalid HTTP method: " + method)
  106. }
  107. p.methods.Add(method)
  108. }
  109. p.pattern, p.re, p.params = patternRegexp.pattern, patternRegexp.re, patternRegexp.params
  110. return p
  111. }
  112. func patternRegexp(pattern string, h ...any) *RouterPathGroupPattern {
  113. p := &RouterPathGroupPattern{middlewares: slices.Clone(h)}
  114. re := []byte{'^'}
  115. lastEnd := 0
  116. for lastEnd < len(pattern) {
  117. start := strings.IndexByte(pattern[lastEnd:], '<')
  118. if start == -1 {
  119. re = append(re, regexp.QuoteMeta(pattern[lastEnd:])...)
  120. break
  121. }
  122. end := strings.IndexByte(pattern[lastEnd+start:], '>')
  123. if end == -1 {
  124. panic("invalid pattern: " + pattern)
  125. }
  126. re = append(re, regexp.QuoteMeta(pattern[lastEnd:lastEnd+start])...)
  127. partName, partExp, _ := strings.Cut(pattern[lastEnd+start+1:lastEnd+start+end], ":")
  128. lastEnd += start + end + 1
  129. // TODO: it could support to specify a "capture group" for the name, for example: "/<name[2]:(\d)-(\d)>"
  130. // it is not used so no need to implement it now
  131. param := routerPathParam{}
  132. if partExp == "*" {
  133. re = append(re, "(.*?)/?"...)
  134. if lastEnd < len(pattern) && pattern[lastEnd] == '/' {
  135. lastEnd++ // the "*" pattern is able to handle the last slash, so skip it
  136. }
  137. } else {
  138. partExp = util.IfZero(partExp, "[^/]+")
  139. re = append(re, '(')
  140. re = append(re, partExp...)
  141. re = append(re, ')')
  142. }
  143. param.name = partName
  144. p.params = append(p.params, param)
  145. }
  146. re = append(re, '$')
  147. p.pattern, p.re = pattern, regexp.MustCompile(string(re))
  148. return p
  149. }
  150. func (g *RouterPathGroup) PatternRegexp(pattern string, h ...any) *RouterPathGroupPattern {
  151. return patternRegexp(pattern, h...)
  152. }