Skip to content

Commit 00f95b8

Browse files
committed
fix
1 parent 6708343 commit 00f95b8

File tree

4 files changed

+94
-203
lines changed

4 files changed

+94
-203
lines changed

modules/web/router_path.go

Lines changed: 26 additions & 5 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,12 +37,22 @@ func (g *RouterPathGroup) ServeHTTP(resp http.ResponseWriter, req *http.Request)
3637
g.r.chiRouter.NotFoundHandler().ServeHTTP(resp, req)
3738
}
3839

40+
type RouterPathRegexp 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) {
44-
g.matchers = append(g.matchers, newRouterPathMatcher(methods, pattern, h...))
51+
g.MatchRegexp(methods, g.PatternRegexp(pattern), h...)
52+
}
53+
54+
func (g *RouterPathGroup) MatchRegexp(methods string, patternRegexp *RouterPathRegexp, h ...any) {
55+
g.matchers = append(g.matchers, newRouterPathMatcher(methods, patternRegexp, h...))
4556
}
4657

4758
type routerPathParam struct {
@@ -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 *RouterPathRegexp, 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,6 +117,12 @@ 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) *RouterPathRegexp {
125+
p := &RouterPathRegexp{middlewares: slices.Clone(h)}
109126
re := []byte{'^'}
110127
lastEnd := 0
111128
for lastEnd < len(pattern) {
@@ -144,3 +161,7 @@ func newRouterPathMatcher(methods, pattern string, h ...any) *routerPathMatcher
144161
p.re = regexp.MustCompile(reStr)
145162
return p
146163
}
164+
165+
func (g *RouterPathGroup) PatternRegexp(pattern string, h ...any) *RouterPathRegexp {
166+
return patternRegexp(pattern, h...)
167+
}

modules/web/router_test.go

Lines changed: 8 additions & 2 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
}
@@ -108,7 +108,7 @@ func TestRouter(t *testing.T) {
108108
m.Delete("", h())
109109
})
110110
m.PathGroup("/*", func(g *RouterPathGroup) {
111-
g.MatchPath("GET", `/<dir:*>/<file:[a-z]{1,2}>`, stopMark("s2"), h("match-path"))
111+
g.MatchRegexp("GET", g.PatternRegexp(`/<dir:*>/<file:[a-z]{1,2}>`, stopMark("s3")), stopMark("s2"), h("match-path"))
112112
}, stopMark("s1"))
113113
})
114114
})
@@ -205,6 +205,12 @@ func TestRouter(t *testing.T) {
205205
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
206206
handlerMark: "s2",
207207
})
208+
209+
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/fn?stop=s3", resultStruct{
210+
method: "GET",
211+
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
212+
handlerMark: "s3",
213+
})
208214
})
209215
}
210216

routers/api/packages/api.go

Lines changed: 35 additions & 195 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ package packages
55

66
import (
77
"net/http"
8-
"regexp"
9-
"strings"
108

119
auth_model "code.gitea.io/gitea/models/auth"
1210
"code.gitea.io/gitea/models/perm"
@@ -282,42 +280,10 @@ func CommonRoutes() *web.Router {
282280
})
283281
})
284282
}, reqPackageAccess(perm.AccessModeRead))
285-
r.Group("/conda", func() {
286-
var (
287-
downloadPattern = regexp.MustCompile(`\A(.+/)?(.+)/((?:[^/]+(?:\.tar\.bz2|\.conda))|(?:current_)?repodata\.json(?:\.bz2)?)\z`)
288-
uploadPattern = regexp.MustCompile(`\A(.+/)?([^/]+(?:\.tar\.bz2|\.conda))\z`)
289-
)
290-
291-
r.Get("/*", func(ctx *context.Context) {
292-
m := downloadPattern.FindStringSubmatch(ctx.PathParam("*"))
293-
if len(m) == 0 {
294-
ctx.Status(http.StatusNotFound)
295-
return
296-
}
297-
298-
ctx.SetPathParam("channel", strings.TrimSuffix(m[1], "/"))
299-
ctx.SetPathParam("architecture", m[2])
300-
ctx.SetPathParam("filename", m[3])
301-
302-
switch m[3] {
303-
case "repodata.json", "repodata.json.bz2", "current_repodata.json", "current_repodata.json.bz2":
304-
conda.EnumeratePackages(ctx)
305-
default:
306-
conda.DownloadPackageFile(ctx)
307-
}
308-
})
309-
r.Put("/*", reqPackageAccess(perm.AccessModeWrite), func(ctx *context.Context) {
310-
m := uploadPattern.FindStringSubmatch(ctx.PathParam("*"))
311-
if len(m) == 0 {
312-
ctx.Status(http.StatusNotFound)
313-
return
314-
}
315-
316-
ctx.SetPathParam("channel", strings.TrimSuffix(m[1], "/"))
317-
ctx.SetPathParam("filename", m[2])
318-
319-
conda.UploadPackageFile(ctx)
320-
})
283+
r.PathGroup("/conda", func(g *web.RouterPathGroup) {
284+
g.MatchPath("GET", "/<architecture>/<filename>", conda.ListOrGetPackages)
285+
g.MatchPath("GET", "/<channel:*>/<architecture>/<filename>", conda.ListOrGetPackages)
286+
g.MatchPath("PUT", "/<channel:*>/<filename>", reqPackageAccess(perm.AccessModeWrite), conda.UploadPackageFile)
321287
}, reqPackageAccess(perm.AccessModeRead))
322288
r.Group("/cran", func() {
323289
r.Group("/src", func() {
@@ -358,60 +324,16 @@ func CommonRoutes() *web.Router {
358324
}, reqPackageAccess(perm.AccessModeRead))
359325
r.Group("/go", func() {
360326
r.Put("/upload", reqPackageAccess(perm.AccessModeWrite), goproxy.UploadPackage)
361-
r.Get("/sumdb/sum.golang.org/supported", func(ctx *context.Context) {
362-
ctx.Status(http.StatusNotFound)
363-
})
327+
r.Get("/sumdb/sum.golang.org/supported", http.NotFound)
364328

365329
// Manual mapping of routes because the package name contains slashes which chi does not support
366330
// https://go.dev/ref/mod#goproxy-protocol
367-
r.Get("/*", func(ctx *context.Context) {
368-
path := ctx.PathParam("*")
369-
370-
if strings.HasSuffix(path, "/@latest") {
371-
ctx.SetPathParam("name", path[:len(path)-len("/@latest")])
372-
ctx.SetPathParam("version", "latest")
373-
374-
goproxy.PackageVersionMetadata(ctx)
375-
return
376-
}
377-
378-
parts := strings.SplitN(path, "/@v/", 2)
379-
if len(parts) != 2 {
380-
ctx.Status(http.StatusNotFound)
381-
return
382-
}
383-
384-
ctx.SetPathParam("name", parts[0])
385-
386-
// <package/name>/@v/list
387-
if parts[1] == "list" {
388-
goproxy.EnumeratePackageVersions(ctx)
389-
return
390-
}
391-
392-
// <package/name>/@v/<version>.zip
393-
if strings.HasSuffix(parts[1], ".zip") {
394-
ctx.SetPathParam("version", parts[1][:len(parts[1])-len(".zip")])
395-
396-
goproxy.DownloadPackageFile(ctx)
397-
return
398-
}
399-
// <package/name>/@v/<version>.info
400-
if strings.HasSuffix(parts[1], ".info") {
401-
ctx.SetPathParam("version", parts[1][:len(parts[1])-len(".info")])
402-
403-
goproxy.PackageVersionMetadata(ctx)
404-
return
405-
}
406-
// <package/name>/@v/<version>.mod
407-
if strings.HasSuffix(parts[1], ".mod") {
408-
ctx.SetPathParam("version", parts[1][:len(parts[1])-len(".mod")])
409-
410-
goproxy.PackageVersionGoModContent(ctx)
411-
return
412-
}
413-
414-
ctx.Status(http.StatusNotFound)
331+
r.PathGroup("/*", func(g *web.RouterPathGroup) {
332+
g.MatchPath("GET", "/<name:*>/@<version:latest>", goproxy.PackageVersionMetadata) // <package/name>/@latest
333+
g.MatchPath("GET", "/<name:*>/@v/list", goproxy.EnumeratePackageVersions) // <package/name>/@v/list
334+
g.MatchPath("GET", "/<name:*>/@v/<version>.zip", goproxy.DownloadPackageFile) // <package/name>/@v/<version>.zip
335+
g.MatchPath("GET", "/<name:*>/@v/<version>.info", goproxy.PackageVersionMetadata) // <package/name>/@v/<version>.info
336+
g.MatchPath("GET", "/<name:*>/@v/<version>.mod", goproxy.PackageVersionGoModContent) // <package/name>/@v/<version>.mod
415337
})
416338
}, reqPackageAccess(perm.AccessModeRead))
417339
r.Group("/generic", func() {
@@ -532,82 +454,23 @@ func CommonRoutes() *web.Router {
532454
})
533455
})
534456
}, reqPackageAccess(perm.AccessModeRead))
457+
535458
r.Group("/pypi", func() {
536459
r.Post("/", reqPackageAccess(perm.AccessModeWrite), pypi.UploadPackageFile)
537460
r.Get("/files/{id}/{version}/{filename}", pypi.DownloadPackageFile)
538461
r.Get("/simple/{id}", pypi.PackageMetadata)
539462
}, reqPackageAccess(perm.AccessModeRead))
540-
r.Group("/rpm", func() {
541-
r.Group("/repository.key", func() {
542-
r.Head("", rpm.GetRepositoryKey)
543-
r.Get("", rpm.GetRepositoryKey)
544-
})
545463

546-
var (
547-
repoPattern = regexp.MustCompile(`\A(.*?)\.repo\z`)
548-
uploadPattern = regexp.MustCompile(`\A(.*?)/upload\z`)
549-
filePattern = regexp.MustCompile(`\A(.*?)/package/([^/]+)/([^/]+)/([^/]+)(?:/([^/]+\.rpm)|)\z`)
550-
repoFilePattern = regexp.MustCompile(`\A(.*?)/repodata/([^/]+)\z`)
551-
)
552-
553-
r.Methods("HEAD,GET,PUT,DELETE", "*", func(ctx *context.Context) {
554-
path := ctx.PathParam("*")
555-
isHead := ctx.Req.Method == http.MethodHead
556-
isGetHead := ctx.Req.Method == http.MethodHead || ctx.Req.Method == http.MethodGet
557-
isPut := ctx.Req.Method == http.MethodPut
558-
isDelete := ctx.Req.Method == http.MethodDelete
559-
560-
m := repoPattern.FindStringSubmatch(path)
561-
if len(m) == 2 && isGetHead {
562-
ctx.SetPathParam("group", strings.Trim(m[1], "/"))
563-
rpm.GetRepositoryConfig(ctx)
564-
return
565-
}
566-
567-
m = repoFilePattern.FindStringSubmatch(path)
568-
if len(m) == 3 && isGetHead {
569-
ctx.SetPathParam("group", strings.Trim(m[1], "/"))
570-
ctx.SetPathParam("filename", m[2])
571-
if isHead {
572-
rpm.CheckRepositoryFileExistence(ctx)
573-
} else {
574-
rpm.GetRepositoryFile(ctx)
575-
}
576-
return
577-
}
578-
579-
m = uploadPattern.FindStringSubmatch(path)
580-
if len(m) == 2 && isPut {
581-
reqPackageAccess(perm.AccessModeWrite)(ctx)
582-
if ctx.Written() {
583-
return
584-
}
585-
ctx.SetPathParam("group", strings.Trim(m[1], "/"))
586-
rpm.UploadPackageFile(ctx)
587-
return
588-
}
589-
590-
m = filePattern.FindStringSubmatch(path)
591-
if len(m) == 6 && (isGetHead || isDelete) {
592-
ctx.SetPathParam("group", strings.Trim(m[1], "/"))
593-
ctx.SetPathParam("name", m[2])
594-
ctx.SetPathParam("version", m[3])
595-
ctx.SetPathParam("architecture", m[4])
596-
if isGetHead {
597-
rpm.DownloadPackageFile(ctx)
598-
} else {
599-
reqPackageAccess(perm.AccessModeWrite)(ctx)
600-
if ctx.Written() {
601-
return
602-
}
603-
rpm.DeletePackageFile(ctx)
604-
}
605-
return
606-
}
607-
608-
ctx.Status(http.StatusNotFound)
609-
})
464+
r.PathGroup("/rpm", func(g *web.RouterPathGroup) {
465+
r.Methods("HEAD,GET", "/repository.key", rpm.GetRepositoryKey)
466+
g.MatchPath("HEAD,GET", "/<group:*>.repo", rpm.GetRepositoryConfig)
467+
g.MatchPath("HEAD", "/<group:*>/repodata/<filename>", rpm.CheckRepositoryFileExistence)
468+
g.MatchPath("GET", "/<group:*>/repodata/<filename>", rpm.GetRepositoryFile)
469+
g.MatchPath("PUT", "/<group:*>/upload", reqPackageAccess(perm.AccessModeWrite), rpm.UploadPackageFile)
470+
g.MatchPath("HEAD,GET", "/<group:*>/package/<name>/<version>/<architecture>", rpm.DownloadPackageFile)
471+
g.MatchPath("DELETE", "/<group:*>/package/<name>/<version>/<architecture>", reqPackageAccess(perm.AccessModeWrite), rpm.DeletePackageFile)
610472
}, reqPackageAccess(perm.AccessModeRead))
473+
611474
r.Group("/rubygems", func() {
612475
r.Get("/specs.4.8.gz", rubygems.EnumeratePackages)
613476
r.Get("/latest_specs.4.8.gz", rubygems.EnumeratePackagesLatest)
@@ -621,6 +484,7 @@ func CommonRoutes() *web.Router {
621484
r.Delete("/yank", rubygems.DeletePackage)
622485
}, reqPackageAccess(perm.AccessModeWrite))
623486
}, reqPackageAccess(perm.AccessModeRead))
487+
624488
r.Group("/swift", func() {
625489
r.Group("", func() { // Needs to be unauthenticated.
626490
r.Post("", swift.CheckAuthenticate)
@@ -632,31 +496,12 @@ func CommonRoutes() *web.Router {
632496
r.Get("", swift.EnumeratePackageVersions)
633497
r.Get(".json", swift.EnumeratePackageVersions)
634498
}, swift.CheckAcceptMediaType(swift.AcceptJSON))
635-
r.Group("/{version}", func() {
636-
r.Get("/Package.swift", swift.CheckAcceptMediaType(swift.AcceptSwift), swift.DownloadManifest)
637-
r.Put("", reqPackageAccess(perm.AccessModeWrite), swift.CheckAcceptMediaType(swift.AcceptJSON), swift.UploadPackageFile)
638-
r.Get("", func(ctx *context.Context) {
639-
// Can't use normal routes here: https://github.com/go-chi/chi/issues/781
640-
641-
version := ctx.PathParam("version")
642-
if strings.HasSuffix(version, ".zip") {
643-
swift.CheckAcceptMediaType(swift.AcceptZip)(ctx)
644-
if ctx.Written() {
645-
return
646-
}
647-
ctx.SetPathParam("version", version[:len(version)-4])
648-
swift.DownloadPackageFile(ctx)
649-
} else {
650-
swift.CheckAcceptMediaType(swift.AcceptJSON)(ctx)
651-
if ctx.Written() {
652-
return
653-
}
654-
if strings.HasSuffix(version, ".json") {
655-
ctx.SetPathParam("version", version[:len(version)-5])
656-
}
657-
swift.PackageVersionMetadata(ctx)
658-
}
659-
})
499+
r.PathGroup("", func(g *web.RouterPathGroup) {
500+
g.MatchPath("PUT", "/<version>", reqPackageAccess(perm.AccessModeWrite), swift.CheckAcceptMediaType(swift.AcceptJSON), swift.UploadPackageFile)
501+
g.MatchPath("GET", "/<version>", swift.CheckAcceptMediaType(swift.AcceptJSON), swift.PackageVersionMetadata)
502+
g.MatchPath("GET", "/<version>.json", swift.CheckAcceptMediaType(swift.AcceptJSON), swift.PackageVersionMetadata)
503+
g.MatchPath("GET", "/<version>.zip", swift.CheckAcceptMediaType(swift.AcceptZip), swift.DownloadPackageFile)
504+
g.MatchPath("GET", "/<version>/Package.swift", swift.CheckAcceptMediaType(swift.AcceptSwift), swift.DownloadManifest)
660505
})
661506
})
662507
r.Get("/identifiers", swift.CheckAcceptMediaType(swift.AcceptJSON), swift.LookupPackageIdentifiers)
@@ -703,18 +548,13 @@ func ContainerRoutes() *web.Router {
703548
r.PathGroup("/*", func(g *web.RouterPathGroup) {
704549
g.MatchPath("POST", "/<image:*>/blobs/uploads", reqPackageAccess(perm.AccessModeWrite), container.VerifyImageName, container.PostBlobsUploads)
705550
g.MatchPath("GET", "/<image:*>/tags/list", container.VerifyImageName, container.GetTagsList)
706-
g.MatchPath("GET,PATCH,PUT,DELETE", `/<image:*>/blobs/uploads/<uuid:[-.=\w]+>`, reqPackageAccess(perm.AccessModeWrite), container.VerifyImageName, func(ctx *context.Context) {
707-
switch ctx.Req.Method {
708-
case http.MethodGet:
709-
container.GetBlobsUpload(ctx)
710-
case http.MethodPatch:
711-
container.PatchBlobsUpload(ctx)
712-
case http.MethodPut:
713-
container.PutBlobsUpload(ctx)
714-
default: /* DELETE */
715-
container.DeleteBlobsUpload(ctx)
716-
}
717-
})
551+
552+
patternBlobsUploadsUuid := g.PatternRegexp(`/<image:*>/blobs/uploads/<uuid:[-.=\w]+>`, reqPackageAccess(perm.AccessModeWrite), container.VerifyImageName)
553+
g.MatchRegexp("GET", patternBlobsUploadsUuid, container.GetBlobsUpload)
554+
g.MatchRegexp("PATCH", patternBlobsUploadsUuid, container.PatchBlobsUpload)
555+
g.MatchRegexp("PUT", patternBlobsUploadsUuid, container.PutBlobsUpload)
556+
g.MatchRegexp("DELETE", patternBlobsUploadsUuid, container.DeleteBlobsUpload)
557+
718558
g.MatchPath("HEAD", `/<image:*>/blobs/<digest>`, container.VerifyImageName, container.HeadBlob)
719559
g.MatchPath("GET", `/<image:*>/blobs/<digest>`, container.VerifyImageName, container.GetBlob)
720560
g.MatchPath("DELETE", `/<image:*>/blobs/<digest>`, container.VerifyImageName, reqPackageAccess(perm.AccessModeWrite), container.DeleteBlob)

0 commit comments

Comments
 (0)