Skip to content

Commit b6d1dd3

Browse files
committed
fix
1 parent 6708343 commit b6d1dd3

File tree

8 files changed

+149
-247
lines changed

8 files changed

+149
-247
lines changed

modules/web/router_path.go

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package web
66
import (
77
"net/http"
88
"regexp"
9+
"slices"
910
"strings"
1011

1112
"code.gitea.io/gitea/modules/container"
@@ -36,11 +37,21 @@ func (g *RouterPathGroup) ServeHTTP(resp http.ResponseWriter, req *http.Request)
3637
g.r.chiRouter.NotFoundHandler().ServeHTTP(resp, req)
3738
}
3839

40+
type RouterPathGroupPattern struct {
41+
re *regexp.Regexp
42+
params []routerPathParam
43+
middlewares []any
44+
}
45+
3946
// MatchPath matches the request method, and uses regexp to match the path.
40-
// The pattern uses "<...>" to define path parameters, for example: "/<name>" (different from chi router)
41-
// It is only designed to resolve some special cases which chi router can't handle.
47+
// The pattern uses "<...>" to define path parameters, for example, "/<name>" (different from chi router)
48+
// It is only designed to resolve some special cases that chi router can't handle.
4249
// For most cases, it shouldn't be used because it needs to iterate all rules to find the matched one (inefficient).
4350
func (g *RouterPathGroup) MatchPath(methods, pattern string, h ...any) {
51+
g.MatchPattern(methods, g.PatternRegexp(pattern), h...)
52+
}
53+
54+
func (g *RouterPathGroup) MatchPattern(methods string, pattern *RouterPathGroupPattern, h ...any) {
4455
g.matchers = append(g.matchers, newRouterPathMatcher(methods, pattern, h...))
4556
}
4657

@@ -96,8 +107,8 @@ func isValidMethod(name string) bool {
96107
return false
97108
}
98109

99-
func newRouterPathMatcher(methods, pattern string, h ...any) *routerPathMatcher {
100-
middlewares, handlerFunc := wrapMiddlewareAndHandler(nil, h)
110+
func newRouterPathMatcher(methods string, patternRegexp *RouterPathGroupPattern, h ...any) *routerPathMatcher {
111+
middlewares, handlerFunc := wrapMiddlewareAndHandler(patternRegexp.middlewares, h)
101112
p := &routerPathMatcher{methods: make(container.Set[string]), middlewares: middlewares, handlerFunc: handlerFunc}
102113
for method := range strings.SplitSeq(methods, ",") {
103114
method = strings.TrimSpace(method)
@@ -106,19 +117,25 @@ func newRouterPathMatcher(methods, pattern string, h ...any) *routerPathMatcher
106117
}
107118
p.methods.Add(method)
108119
}
120+
p.re, p.params = patternRegexp.re, patternRegexp.params
121+
return p
122+
}
123+
124+
func patternRegexp(pattern string, h ...any) *RouterPathGroupPattern {
125+
p := &RouterPathGroupPattern{middlewares: slices.Clone(h)}
109126
re := []byte{'^'}
110127
lastEnd := 0
111128
for lastEnd < len(pattern) {
112129
start := strings.IndexByte(pattern[lastEnd:], '<')
113130
if start == -1 {
114-
re = append(re, pattern[lastEnd:]...)
131+
re = append(re, regexp.QuoteMeta(pattern[lastEnd:])...)
115132
break
116133
}
117134
end := strings.IndexByte(pattern[lastEnd+start:], '>')
118135
if end == -1 {
119136
panic("invalid pattern: " + pattern)
120137
}
121-
re = append(re, pattern[lastEnd:lastEnd+start]...)
138+
re = append(re, regexp.QuoteMeta(pattern[lastEnd:lastEnd+start])...)
122139
partName, partExp, _ := strings.Cut(pattern[lastEnd+start+1:lastEnd+start+end], ":")
123140
lastEnd += start + end + 1
124141

@@ -140,7 +157,10 @@ func newRouterPathMatcher(methods, pattern string, h ...any) *routerPathMatcher
140157
p.params = append(p.params, param)
141158
}
142159
re = append(re, '$')
143-
reStr := string(re)
144-
p.re = regexp.MustCompile(reStr)
160+
p.re = regexp.MustCompile(string(re))
145161
return p
146162
}
163+
164+
func (g *RouterPathGroup) PatternRegexp(pattern string, h ...any) *RouterPathGroupPattern {
165+
return patternRegexp(pattern, h...)
166+
}

modules/web/router_test.go

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func TestPathProcessor(t *testing.T) {
3434
testProcess := func(pattern, uri string, expectedPathParams map[string]string) {
3535
chiCtx := chi.NewRouteContext()
3636
chiCtx.RouteMethod = "GET"
37-
p := newRouterPathMatcher("GET", pattern, http.NotFound)
37+
p := newRouterPathMatcher("GET", patternRegexp(pattern), http.NotFound)
3838
assert.True(t, p.matchPath(chiCtx, uri), "use pattern %s to process uri %s", pattern, uri)
3939
assert.Equal(t, expectedPathParams, chiURLParamsToMap(chiCtx), "use pattern %s to process uri %s", pattern, uri)
4040
}
@@ -56,18 +56,20 @@ func TestRouter(t *testing.T) {
5656
recorder.Body = buff
5757

5858
type resultStruct struct {
59-
method string
60-
pathParams map[string]string
61-
handlerMark string
59+
method string
60+
pathParams map[string]string
61+
handlerMarks []string
6262
}
63-
var res resultStruct
6463

64+
var res resultStruct
6565
h := func(optMark ...string) func(resp http.ResponseWriter, req *http.Request) {
6666
mark := util.OptionalArg(optMark, "")
6767
return func(resp http.ResponseWriter, req *http.Request) {
6868
res.method = req.Method
6969
res.pathParams = chiURLParamsToMap(chi.RouteContext(req.Context()))
70-
res.handlerMark = mark
70+
if mark != "" {
71+
res.handlerMarks = append(res.handlerMarks, mark)
72+
}
7173
}
7274
}
7375

@@ -77,6 +79,8 @@ func TestRouter(t *testing.T) {
7779
if stop := req.FormValue("stop"); stop != "" && (mark == "" || mark == stop) {
7880
h(stop)(resp, req)
7981
resp.WriteHeader(http.StatusOK)
82+
} else if mark != "" {
83+
res.handlerMarks = append(res.handlerMarks, mark)
8084
}
8185
}
8286
}
@@ -108,7 +112,7 @@ func TestRouter(t *testing.T) {
108112
m.Delete("", h())
109113
})
110114
m.PathGroup("/*", func(g *RouterPathGroup) {
111-
g.MatchPath("GET", `/<dir:*>/<file:[a-z]{1,2}>`, stopMark("s2"), h("match-path"))
115+
g.MatchPattern("GET", g.PatternRegexp(`/<dir:*>/<file:[a-z]{1,2}>`, stopMark("s2")), stopMark("s3"), h("match-path"))
112116
}, stopMark("s1"))
113117
})
114118
})
@@ -126,31 +130,31 @@ func TestRouter(t *testing.T) {
126130
}
127131

128132
t.Run("RootRouter", func(t *testing.T) {
129-
testRoute(t, "GET /the-user/the-repo/other", resultStruct{method: "GET", handlerMark: "not-found:/"})
133+
testRoute(t, "GET /the-user/the-repo/other", resultStruct{method: "GET", handlerMarks: []string{"not-found:/"}})
130134
testRoute(t, "GET /the-user/the-repo/pulls", resultStruct{
131-
method: "GET",
132-
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "pulls"},
133-
handlerMark: "list-issues-b",
135+
method: "GET",
136+
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "pulls"},
137+
handlerMarks: []string{"list-issues-b"},
134138
})
135139
testRoute(t, "GET /the-user/the-repo/issues/123", resultStruct{
136-
method: "GET",
137-
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "issues", "index": "123"},
138-
handlerMark: "view-issue",
140+
method: "GET",
141+
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "issues", "index": "123"},
142+
handlerMarks: []string{"view-issue"},
139143
})
140144
testRoute(t, "GET /the-user/the-repo/issues/123?stop=hijack", resultStruct{
141-
method: "GET",
142-
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "issues", "index": "123"},
143-
handlerMark: "hijack",
145+
method: "GET",
146+
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "issues", "index": "123"},
147+
handlerMarks: []string{"hijack"},
144148
})
145149
testRoute(t, "POST /the-user/the-repo/issues/123/update", resultStruct{
146-
method: "POST",
147-
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "index": "123"},
148-
handlerMark: "update-issue",
150+
method: "POST",
151+
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "index": "123"},
152+
handlerMarks: []string{"update-issue"},
149153
})
150154
})
151155

152156
t.Run("Sub Router", func(t *testing.T) {
153-
testRoute(t, "GET /api/v1/other", resultStruct{method: "GET", handlerMark: "not-found:/api/v1"})
157+
testRoute(t, "GET /api/v1/other", resultStruct{method: "GET", handlerMarks: []string{"not-found:/api/v1"}})
154158
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches", resultStruct{
155159
method: "GET",
156160
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo"},
@@ -179,31 +183,37 @@ func TestRouter(t *testing.T) {
179183

180184
t.Run("MatchPath", func(t *testing.T) {
181185
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/fn", resultStruct{
182-
method: "GET",
183-
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
184-
handlerMark: "match-path",
186+
method: "GET",
187+
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
188+
handlerMarks: []string{"s1", "s2", "s3", "match-path"},
185189
})
186190
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1%2fd2/fn", resultStruct{
187-
method: "GET",
188-
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1%2fd2/fn", "dir": "d1%2fd2", "file": "fn"},
189-
handlerMark: "match-path",
191+
method: "GET",
192+
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1%2fd2/fn", "dir": "d1%2fd2", "file": "fn"},
193+
handlerMarks: []string{"s1", "s2", "s3", "match-path"},
190194
})
191195
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/000", resultStruct{
192-
method: "GET",
193-
pathParams: map[string]string{"reponame": "the-repo", "username": "the-user", "*": "d1/d2/000"},
194-
handlerMark: "not-found:/api/v1",
196+
method: "GET",
197+
pathParams: map[string]string{"reponame": "the-repo", "username": "the-user", "*": "d1/d2/000"},
198+
handlerMarks: []string{"s1", "not-found:/api/v1"},
195199
})
196200

197201
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/fn?stop=s1", resultStruct{
198-
method: "GET",
199-
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn"},
200-
handlerMark: "s1",
202+
method: "GET",
203+
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn"},
204+
handlerMarks: []string{"s1"},
201205
})
202206

203207
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/fn?stop=s2", resultStruct{
204-
method: "GET",
205-
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
206-
handlerMark: "s2",
208+
method: "GET",
209+
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
210+
handlerMarks: []string{"s1", "s2"},
211+
})
212+
213+
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/fn?stop=s3", resultStruct{
214+
method: "GET",
215+
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
216+
handlerMarks: []string{"s1", "s2", "s3"},
207217
})
208218
})
209219
}

0 commit comments

Comments
 (0)