44package web
55
66import (
7- "fmt"
87 "net/http"
98 "net/url"
109 "reflect"
11- "regexp"
1210 "strings"
1311
14- "code.gitea.io/gitea/modules/container"
1512 "code.gitea.io/gitea/modules/htmlutil"
1613 "code.gitea.io/gitea/modules/reqctx"
1714 "code.gitea.io/gitea/modules/setting"
18- "code.gitea.io/gitea/modules/util"
1915 "code.gitea.io/gitea/modules/web/middleware"
2016
2117 "gitea.com/go-chi/binding"
@@ -45,7 +41,7 @@ func GetForm(dataStore reqctx.RequestDataStore) any {
4541
4642// Router defines a route based on chi's router
4743type Router struct {
48- chiRouter chi.Router
44+ chiRouter * chi.Mux
4945 curGroupPrefix string
5046 curMiddlewares []any
5147}
@@ -97,16 +93,21 @@ func isNilOrFuncNil(v any) bool {
9793 return r .Kind () == reflect .Func && r .IsNil ()
9894}
9995
100- func ( r * Router ) wrapMiddlewareAndHandler (h []any ) ([]func (http.Handler ) http.Handler , http.HandlerFunc ) {
101- handlerProviders := make ([]func (http.Handler ) http.Handler , 0 , len (r . curMiddlewares )+ len (h )+ 1 )
102- for _ , m := range r . curMiddlewares {
96+ func wrapMiddlewareAndHandler (curMiddlewares , h []any ) ([]func (http.Handler ) http.Handler , http.HandlerFunc ) {
97+ handlerProviders := make ([]func (http.Handler ) http.Handler , 0 , len (curMiddlewares )+ len (h )+ 1 )
98+ for _ , m := range curMiddlewares {
10399 if ! isNilOrFuncNil (m ) {
104100 handlerProviders = append (handlerProviders , toHandlerProvider (m ))
105101 }
106102 }
107- for _ , m := range h {
103+ if len (h ) == 0 {
104+ panic ("no endpoint handler provided" )
105+ }
106+ for i , m := range h {
108107 if ! isNilOrFuncNil (m ) {
109108 handlerProviders = append (handlerProviders , toHandlerProvider (m ))
109+ } else if i == len (h )- 1 {
110+ panic ("endpoint handler can't be nil" )
110111 }
111112 }
112113 middlewares := handlerProviders [:len (handlerProviders )- 1 ]
@@ -121,7 +122,7 @@ func (r *Router) wrapMiddlewareAndHandler(h []any) ([]func(http.Handler) http.Ha
121122// Methods adds the same handlers for multiple http "methods" (separated by ",").
122123// If any method is invalid, the lower level router will panic.
123124func (r * Router ) Methods (methods , pattern string , h ... any ) {
124- middlewares , handlerFunc := r . wrapMiddlewareAndHandler (h )
125+ middlewares , handlerFunc := wrapMiddlewareAndHandler (r . curMiddlewares , h )
125126 fullPattern := r .getPattern (pattern )
126127 if strings .Contains (methods , "," ) {
127128 methods := strings .Split (methods , "," )
@@ -141,7 +142,7 @@ func (r *Router) Mount(pattern string, subRouter *Router) {
141142
142143// Any delegate requests for all methods
143144func (r * Router ) Any (pattern string , h ... any ) {
144- middlewares , handlerFunc := r . wrapMiddlewareAndHandler (h )
145+ middlewares , handlerFunc := wrapMiddlewareAndHandler (r . curMiddlewares , h )
145146 r .chiRouter .With (middlewares ... ).HandleFunc (r .getPattern (pattern ), handlerFunc )
146147}
147148
@@ -185,17 +186,6 @@ func (r *Router) NotFound(h http.HandlerFunc) {
185186 r .chiRouter .NotFound (h )
186187}
187188
188- type pathProcessorParam struct {
189- name string
190- captureGroup int
191- }
192-
193- type PathProcessor struct {
194- methods container.Set [string ]
195- re * regexp.Regexp
196- params []pathProcessorParam
197- }
198-
199189func (r * Router ) normalizeRequestPath (resp http.ResponseWriter , req * http.Request , next http.Handler ) {
200190 normalized := false
201191 normalizedPath := req .URL .EscapedPath ()
@@ -253,121 +243,16 @@ func (r *Router) normalizeRequestPath(resp http.ResponseWriter, req *http.Reques
253243 next .ServeHTTP (resp , req )
254244}
255245
256- func (p * PathProcessor ) ProcessRequestPath (chiCtx * chi.Context , path string ) bool {
257- if ! p .methods .Contains (chiCtx .RouteMethod ) {
258- return false
259- }
260- if ! strings .HasPrefix (path , "/" ) {
261- path = "/" + path
262- }
263- pathMatches := p .re .FindStringSubmatchIndex (path ) // Golang regexp match pairs [start, end, start, end, ...]
264- if pathMatches == nil {
265- return false
266- }
267- var paramMatches [][]int
268- for i := 2 ; i < len (pathMatches ); {
269- paramMatches = append (paramMatches , []int {pathMatches [i ], pathMatches [i + 1 ]})
270- pmIdx := len (paramMatches ) - 1
271- end := pathMatches [i + 1 ]
272- i += 2
273- for ; i < len (pathMatches ); i += 2 {
274- if pathMatches [i ] >= end {
275- break
276- }
277- paramMatches [pmIdx ] = append (paramMatches [pmIdx ], pathMatches [i ], pathMatches [i + 1 ])
278- }
279- }
280- for i , pm := range paramMatches {
281- groupIdx := p .params [i ].captureGroup * 2
282- chiCtx .URLParams .Add (p .params [i ].name , path [pm [groupIdx ]:pm [groupIdx + 1 ]])
283- }
284- return true
285- }
286-
287- func NewPathProcessor (methods , pattern string ) * PathProcessor {
288- p := & PathProcessor {methods : make (container.Set [string ])}
289- for _ , method := range strings .Split (methods , "," ) {
290- p .methods .Add (strings .TrimSpace (method ))
291- }
292- re := []byte {'^' }
293- lastEnd := 0
294- for lastEnd < len (pattern ) {
295- start := strings .IndexByte (pattern [lastEnd :], '<' )
296- if start == - 1 {
297- re = append (re , pattern [lastEnd :]... )
298- break
299- }
300- end := strings .IndexByte (pattern [lastEnd + start :], '>' )
301- if end == - 1 {
302- panic (fmt .Sprintf ("invalid pattern: %s" , pattern ))
303- }
304- re = append (re , pattern [lastEnd :lastEnd + start ]... )
305- partName , partExp , _ := strings .Cut (pattern [lastEnd + start + 1 :lastEnd + start + end ], ":" )
306- lastEnd += start + end + 1
307-
308- // TODO: it could support to specify a "capture group" for the name, for example: "/<name[2]:(\d)-(\d)>"
309- // it is not used so no need to implement it now
310- param := pathProcessorParam {}
311- if partExp == "*" {
312- re = append (re , "(.*?)/?" ... )
313- if lastEnd < len (pattern ) {
314- if pattern [lastEnd ] == '/' {
315- lastEnd ++
316- }
317- }
318- } else {
319- partExp = util .IfZero (partExp , "[^/]+" )
320- re = append (re , '(' )
321- re = append (re , partExp ... )
322- re = append (re , ')' )
323- }
324- param .name = partName
325- p .params = append (p .params , param )
326- }
327- re = append (re , '$' )
328- reStr := string (re )
329- p .re = regexp .MustCompile (reStr )
330- return p
331- }
332-
333246// Combo delegates requests to Combo
334247func (r * Router ) Combo (pattern string , h ... any ) * Combo {
335248 return & Combo {r , pattern , h }
336249}
337250
338- // Combo represents a tiny group routes with same pattern
339- type Combo struct {
340- r * Router
341- pattern string
342- h []any
343- }
344-
345- // Get delegates Get method
346- func (c * Combo ) Get (h ... any ) * Combo {
347- c .r .Get (c .pattern , append (c .h , h ... )... )
348- return c
349- }
350-
351- // Post delegates Post method
352- func (c * Combo ) Post (h ... any ) * Combo {
353- c .r .Post (c .pattern , append (c .h , h ... )... )
354- return c
355- }
356-
357- // Delete delegates Delete method
358- func (c * Combo ) Delete (h ... any ) * Combo {
359- c .r .Delete (c .pattern , append (c .h , h ... )... )
360- return c
361- }
362-
363- // Put delegates Put method
364- func (c * Combo ) Put (h ... any ) * Combo {
365- c .r .Put (c .pattern , append (c .h , h ... )... )
366- return c
367- }
368-
369- // Patch delegates Patch method
370- func (c * Combo ) Patch (h ... any ) * Combo {
371- c .r .Patch (c .pattern , append (c .h , h ... )... )
372- return c
251+ // PathGroup creates a group of paths which could be matched by regexp.
252+ // It is only designed to resolve some special cases which chi router can't handle.
253+ // For most cases, it shouldn't be used because it needs to iterate all rules to find the matched one (inefficient).
254+ func (r * Router ) PathGroup (pattern string , fn func (g * RouterPathGroup ), h ... any ) {
255+ g := & RouterPathGroup {r : r , pathParam : "*" }
256+ fn (g )
257+ r .Any (pattern , append (h , g .ServeHTTP )... )
373258}
0 commit comments