mirror of
https://github.com/go-gitea/gitea.git
synced 2025-06-23 14:57:51 +00:00
Refactor packages (#34777)
This commit is contained in:
parent
f114c388ff
commit
1748045285
@ -6,6 +6,7 @@ package web
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
@ -36,11 +37,21 @@ func (g *RouterPathGroup) ServeHTTP(resp http.ResponseWriter, req *http.Request)
|
|||||||
g.r.chiRouter.NotFoundHandler().ServeHTTP(resp, req)
|
g.r.chiRouter.NotFoundHandler().ServeHTTP(resp, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RouterPathGroupPattern struct {
|
||||||
|
re *regexp.Regexp
|
||||||
|
params []routerPathParam
|
||||||
|
middlewares []any
|
||||||
|
}
|
||||||
|
|
||||||
// MatchPath matches the request method, and uses regexp to match the path.
|
// MatchPath matches the request method, and uses regexp to match the path.
|
||||||
// The pattern uses "<...>" to define path parameters, for example: "/<name>" (different from chi router)
|
// The pattern uses "<...>" to define path parameters, for example, "/<name>" (different from chi router)
|
||||||
// It is only designed to resolve some special cases which chi router can't handle.
|
// It is only designed to resolve some special cases that chi router can't handle.
|
||||||
// For most cases, it shouldn't be used because it needs to iterate all rules to find the matched one (inefficient).
|
// For most cases, it shouldn't be used because it needs to iterate all rules to find the matched one (inefficient).
|
||||||
func (g *RouterPathGroup) MatchPath(methods, pattern string, h ...any) {
|
func (g *RouterPathGroup) MatchPath(methods, pattern string, h ...any) {
|
||||||
|
g.MatchPattern(methods, g.PatternRegexp(pattern), h...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *RouterPathGroup) MatchPattern(methods string, pattern *RouterPathGroupPattern, h ...any) {
|
||||||
g.matchers = append(g.matchers, newRouterPathMatcher(methods, pattern, h...))
|
g.matchers = append(g.matchers, newRouterPathMatcher(methods, pattern, h...))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,8 +107,8 @@ func isValidMethod(name string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRouterPathMatcher(methods, pattern string, h ...any) *routerPathMatcher {
|
func newRouterPathMatcher(methods string, patternRegexp *RouterPathGroupPattern, h ...any) *routerPathMatcher {
|
||||||
middlewares, handlerFunc := wrapMiddlewareAndHandler(nil, h)
|
middlewares, handlerFunc := wrapMiddlewareAndHandler(patternRegexp.middlewares, h)
|
||||||
p := &routerPathMatcher{methods: make(container.Set[string]), middlewares: middlewares, handlerFunc: handlerFunc}
|
p := &routerPathMatcher{methods: make(container.Set[string]), middlewares: middlewares, handlerFunc: handlerFunc}
|
||||||
for method := range strings.SplitSeq(methods, ",") {
|
for method := range strings.SplitSeq(methods, ",") {
|
||||||
method = strings.TrimSpace(method)
|
method = strings.TrimSpace(method)
|
||||||
@ -106,19 +117,25 @@ func newRouterPathMatcher(methods, pattern string, h ...any) *routerPathMatcher
|
|||||||
}
|
}
|
||||||
p.methods.Add(method)
|
p.methods.Add(method)
|
||||||
}
|
}
|
||||||
|
p.re, p.params = patternRegexp.re, patternRegexp.params
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func patternRegexp(pattern string, h ...any) *RouterPathGroupPattern {
|
||||||
|
p := &RouterPathGroupPattern{middlewares: slices.Clone(h)}
|
||||||
re := []byte{'^'}
|
re := []byte{'^'}
|
||||||
lastEnd := 0
|
lastEnd := 0
|
||||||
for lastEnd < len(pattern) {
|
for lastEnd < len(pattern) {
|
||||||
start := strings.IndexByte(pattern[lastEnd:], '<')
|
start := strings.IndexByte(pattern[lastEnd:], '<')
|
||||||
if start == -1 {
|
if start == -1 {
|
||||||
re = append(re, pattern[lastEnd:]...)
|
re = append(re, regexp.QuoteMeta(pattern[lastEnd:])...)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
end := strings.IndexByte(pattern[lastEnd+start:], '>')
|
end := strings.IndexByte(pattern[lastEnd+start:], '>')
|
||||||
if end == -1 {
|
if end == -1 {
|
||||||
panic("invalid pattern: " + pattern)
|
panic("invalid pattern: " + pattern)
|
||||||
}
|
}
|
||||||
re = append(re, pattern[lastEnd:lastEnd+start]...)
|
re = append(re, regexp.QuoteMeta(pattern[lastEnd:lastEnd+start])...)
|
||||||
partName, partExp, _ := strings.Cut(pattern[lastEnd+start+1:lastEnd+start+end], ":")
|
partName, partExp, _ := strings.Cut(pattern[lastEnd+start+1:lastEnd+start+end], ":")
|
||||||
lastEnd += start + end + 1
|
lastEnd += start + end + 1
|
||||||
|
|
||||||
@ -140,7 +157,10 @@ func newRouterPathMatcher(methods, pattern string, h ...any) *routerPathMatcher
|
|||||||
p.params = append(p.params, param)
|
p.params = append(p.params, param)
|
||||||
}
|
}
|
||||||
re = append(re, '$')
|
re = append(re, '$')
|
||||||
reStr := string(re)
|
p.re = regexp.MustCompile(string(re))
|
||||||
p.re = regexp.MustCompile(reStr)
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *RouterPathGroup) PatternRegexp(pattern string, h ...any) *RouterPathGroupPattern {
|
||||||
|
return patternRegexp(pattern, h...)
|
||||||
|
}
|
||||||
|
@ -34,7 +34,7 @@ func TestPathProcessor(t *testing.T) {
|
|||||||
testProcess := func(pattern, uri string, expectedPathParams map[string]string) {
|
testProcess := func(pattern, uri string, expectedPathParams map[string]string) {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.RouteMethod = "GET"
|
chiCtx.RouteMethod = "GET"
|
||||||
p := newRouterPathMatcher("GET", pattern, http.NotFound)
|
p := newRouterPathMatcher("GET", patternRegexp(pattern), http.NotFound)
|
||||||
assert.True(t, p.matchPath(chiCtx, uri), "use pattern %s to process uri %s", pattern, uri)
|
assert.True(t, p.matchPath(chiCtx, uri), "use pattern %s to process uri %s", pattern, uri)
|
||||||
assert.Equal(t, expectedPathParams, chiURLParamsToMap(chiCtx), "use pattern %s to process uri %s", pattern, uri)
|
assert.Equal(t, expectedPathParams, chiURLParamsToMap(chiCtx), "use pattern %s to process uri %s", pattern, uri)
|
||||||
}
|
}
|
||||||
@ -56,18 +56,20 @@ func TestRouter(t *testing.T) {
|
|||||||
recorder.Body = buff
|
recorder.Body = buff
|
||||||
|
|
||||||
type resultStruct struct {
|
type resultStruct struct {
|
||||||
method string
|
method string
|
||||||
pathParams map[string]string
|
pathParams map[string]string
|
||||||
handlerMark string
|
handlerMarks []string
|
||||||
}
|
}
|
||||||
var res resultStruct
|
|
||||||
|
|
||||||
|
var res resultStruct
|
||||||
h := func(optMark ...string) func(resp http.ResponseWriter, req *http.Request) {
|
h := func(optMark ...string) func(resp http.ResponseWriter, req *http.Request) {
|
||||||
mark := util.OptionalArg(optMark, "")
|
mark := util.OptionalArg(optMark, "")
|
||||||
return func(resp http.ResponseWriter, req *http.Request) {
|
return func(resp http.ResponseWriter, req *http.Request) {
|
||||||
res.method = req.Method
|
res.method = req.Method
|
||||||
res.pathParams = chiURLParamsToMap(chi.RouteContext(req.Context()))
|
res.pathParams = chiURLParamsToMap(chi.RouteContext(req.Context()))
|
||||||
res.handlerMark = mark
|
if mark != "" {
|
||||||
|
res.handlerMarks = append(res.handlerMarks, mark)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +79,8 @@ func TestRouter(t *testing.T) {
|
|||||||
if stop := req.FormValue("stop"); stop != "" && (mark == "" || mark == stop) {
|
if stop := req.FormValue("stop"); stop != "" && (mark == "" || mark == stop) {
|
||||||
h(stop)(resp, req)
|
h(stop)(resp, req)
|
||||||
resp.WriteHeader(http.StatusOK)
|
resp.WriteHeader(http.StatusOK)
|
||||||
|
} else if mark != "" {
|
||||||
|
res.handlerMarks = append(res.handlerMarks, mark)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,7 +112,7 @@ func TestRouter(t *testing.T) {
|
|||||||
m.Delete("", h())
|
m.Delete("", h())
|
||||||
})
|
})
|
||||||
m.PathGroup("/*", func(g *RouterPathGroup) {
|
m.PathGroup("/*", func(g *RouterPathGroup) {
|
||||||
g.MatchPath("GET", `/<dir:*>/<file:[a-z]{1,2}>`, stopMark("s2"), h("match-path"))
|
g.MatchPattern("GET", g.PatternRegexp(`/<dir:*>/<file:[a-z]{1,2}>`, stopMark("s2")), stopMark("s3"), h("match-path"))
|
||||||
}, stopMark("s1"))
|
}, stopMark("s1"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -126,31 +130,31 @@ func TestRouter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Run("RootRouter", func(t *testing.T) {
|
t.Run("RootRouter", func(t *testing.T) {
|
||||||
testRoute(t, "GET /the-user/the-repo/other", resultStruct{method: "GET", handlerMark: "not-found:/"})
|
testRoute(t, "GET /the-user/the-repo/other", resultStruct{method: "GET", handlerMarks: []string{"not-found:/"}})
|
||||||
testRoute(t, "GET /the-user/the-repo/pulls", resultStruct{
|
testRoute(t, "GET /the-user/the-repo/pulls", resultStruct{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "pulls"},
|
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "pulls"},
|
||||||
handlerMark: "list-issues-b",
|
handlerMarks: []string{"list-issues-b"},
|
||||||
})
|
})
|
||||||
testRoute(t, "GET /the-user/the-repo/issues/123", resultStruct{
|
testRoute(t, "GET /the-user/the-repo/issues/123", resultStruct{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "issues", "index": "123"},
|
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "issues", "index": "123"},
|
||||||
handlerMark: "view-issue",
|
handlerMarks: []string{"view-issue"},
|
||||||
})
|
})
|
||||||
testRoute(t, "GET /the-user/the-repo/issues/123?stop=hijack", resultStruct{
|
testRoute(t, "GET /the-user/the-repo/issues/123?stop=hijack", resultStruct{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "issues", "index": "123"},
|
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "issues", "index": "123"},
|
||||||
handlerMark: "hijack",
|
handlerMarks: []string{"hijack"},
|
||||||
})
|
})
|
||||||
testRoute(t, "POST /the-user/the-repo/issues/123/update", resultStruct{
|
testRoute(t, "POST /the-user/the-repo/issues/123/update", resultStruct{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "index": "123"},
|
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "index": "123"},
|
||||||
handlerMark: "update-issue",
|
handlerMarks: []string{"update-issue"},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Sub Router", func(t *testing.T) {
|
t.Run("Sub Router", func(t *testing.T) {
|
||||||
testRoute(t, "GET /api/v1/other", resultStruct{method: "GET", handlerMark: "not-found:/api/v1"})
|
testRoute(t, "GET /api/v1/other", resultStruct{method: "GET", handlerMarks: []string{"not-found:/api/v1"}})
|
||||||
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches", resultStruct{
|
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches", resultStruct{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo"},
|
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo"},
|
||||||
@ -179,31 +183,37 @@ func TestRouter(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("MatchPath", func(t *testing.T) {
|
t.Run("MatchPath", func(t *testing.T) {
|
||||||
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/fn", resultStruct{
|
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/fn", resultStruct{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
|
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
|
||||||
handlerMark: "match-path",
|
handlerMarks: []string{"s1", "s2", "s3", "match-path"},
|
||||||
})
|
})
|
||||||
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1%2fd2/fn", resultStruct{
|
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1%2fd2/fn", resultStruct{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1%2fd2/fn", "dir": "d1%2fd2", "file": "fn"},
|
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1%2fd2/fn", "dir": "d1%2fd2", "file": "fn"},
|
||||||
handlerMark: "match-path",
|
handlerMarks: []string{"s1", "s2", "s3", "match-path"},
|
||||||
})
|
})
|
||||||
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/000", resultStruct{
|
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/000", resultStruct{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
pathParams: map[string]string{"reponame": "the-repo", "username": "the-user", "*": "d1/d2/000"},
|
pathParams: map[string]string{"reponame": "the-repo", "username": "the-user", "*": "d1/d2/000"},
|
||||||
handlerMark: "not-found:/api/v1",
|
handlerMarks: []string{"s1", "not-found:/api/v1"},
|
||||||
})
|
})
|
||||||
|
|
||||||
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/fn?stop=s1", resultStruct{
|
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/fn?stop=s1", resultStruct{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn"},
|
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn"},
|
||||||
handlerMark: "s1",
|
handlerMarks: []string{"s1"},
|
||||||
})
|
})
|
||||||
|
|
||||||
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/fn?stop=s2", resultStruct{
|
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/fn?stop=s2", resultStruct{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
|
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
|
||||||
handlerMark: "s2",
|
handlerMarks: []string{"s1", "s2"},
|
||||||
|
})
|
||||||
|
|
||||||
|
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/fn?stop=s3", resultStruct{
|
||||||
|
method: "GET",
|
||||||
|
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
|
||||||
|
handlerMarks: []string{"s1", "s2", "s3"},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,6 @@ package packages
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
@ -282,42 +280,10 @@ func CommonRoutes() *web.Router {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}, reqPackageAccess(perm.AccessModeRead))
|
}, reqPackageAccess(perm.AccessModeRead))
|
||||||
r.Group("/conda", func() {
|
r.PathGroup("/conda/*", func(g *web.RouterPathGroup) {
|
||||||
var (
|
g.MatchPath("GET", "/<architecture>/<filename>", conda.ListOrGetPackages)
|
||||||
downloadPattern = regexp.MustCompile(`\A(.+/)?(.+)/((?:[^/]+(?:\.tar\.bz2|\.conda))|(?:current_)?repodata\.json(?:\.bz2)?)\z`)
|
g.MatchPath("GET", "/<channel:*>/<architecture>/<filename>", conda.ListOrGetPackages)
|
||||||
uploadPattern = regexp.MustCompile(`\A(.+/)?([^/]+(?:\.tar\.bz2|\.conda))\z`)
|
g.MatchPath("PUT", "/<channel:*>/<filename>", reqPackageAccess(perm.AccessModeWrite), conda.UploadPackageFile)
|
||||||
)
|
|
||||||
|
|
||||||
r.Get("/*", func(ctx *context.Context) {
|
|
||||||
m := downloadPattern.FindStringSubmatch(ctx.PathParam("*"))
|
|
||||||
if len(m) == 0 {
|
|
||||||
ctx.Status(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.SetPathParam("channel", strings.TrimSuffix(m[1], "/"))
|
|
||||||
ctx.SetPathParam("architecture", m[2])
|
|
||||||
ctx.SetPathParam("filename", m[3])
|
|
||||||
|
|
||||||
switch m[3] {
|
|
||||||
case "repodata.json", "repodata.json.bz2", "current_repodata.json", "current_repodata.json.bz2":
|
|
||||||
conda.EnumeratePackages(ctx)
|
|
||||||
default:
|
|
||||||
conda.DownloadPackageFile(ctx)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
r.Put("/*", reqPackageAccess(perm.AccessModeWrite), func(ctx *context.Context) {
|
|
||||||
m := uploadPattern.FindStringSubmatch(ctx.PathParam("*"))
|
|
||||||
if len(m) == 0 {
|
|
||||||
ctx.Status(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.SetPathParam("channel", strings.TrimSuffix(m[1], "/"))
|
|
||||||
ctx.SetPathParam("filename", m[2])
|
|
||||||
|
|
||||||
conda.UploadPackageFile(ctx)
|
|
||||||
})
|
|
||||||
}, reqPackageAccess(perm.AccessModeRead))
|
}, reqPackageAccess(perm.AccessModeRead))
|
||||||
r.Group("/cran", func() {
|
r.Group("/cran", func() {
|
||||||
r.Group("/src", func() {
|
r.Group("/src", func() {
|
||||||
@ -358,60 +324,15 @@ func CommonRoutes() *web.Router {
|
|||||||
}, reqPackageAccess(perm.AccessModeRead))
|
}, reqPackageAccess(perm.AccessModeRead))
|
||||||
r.Group("/go", func() {
|
r.Group("/go", func() {
|
||||||
r.Put("/upload", reqPackageAccess(perm.AccessModeWrite), goproxy.UploadPackage)
|
r.Put("/upload", reqPackageAccess(perm.AccessModeWrite), goproxy.UploadPackage)
|
||||||
r.Get("/sumdb/sum.golang.org/supported", func(ctx *context.Context) {
|
r.Get("/sumdb/sum.golang.org/supported", http.NotFound)
|
||||||
ctx.Status(http.StatusNotFound)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Manual mapping of routes because the package name contains slashes which chi does not support
|
|
||||||
// https://go.dev/ref/mod#goproxy-protocol
|
// https://go.dev/ref/mod#goproxy-protocol
|
||||||
r.Get("/*", func(ctx *context.Context) {
|
r.PathGroup("/*", func(g *web.RouterPathGroup) {
|
||||||
path := ctx.PathParam("*")
|
g.MatchPath("GET", "/<name:*>/@<version:latest>", goproxy.PackageVersionMetadata)
|
||||||
|
g.MatchPath("GET", "/<name:*>/@v/list", goproxy.EnumeratePackageVersions)
|
||||||
if strings.HasSuffix(path, "/@latest") {
|
g.MatchPath("GET", "/<name:*>/@v/<version>.zip", goproxy.DownloadPackageFile)
|
||||||
ctx.SetPathParam("name", path[:len(path)-len("/@latest")])
|
g.MatchPath("GET", "/<name:*>/@v/<version>.info", goproxy.PackageVersionMetadata)
|
||||||
ctx.SetPathParam("version", "latest")
|
g.MatchPath("GET", "/<name:*>/@v/<version>.mod", goproxy.PackageVersionGoModContent)
|
||||||
|
|
||||||
goproxy.PackageVersionMetadata(ctx)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.SplitN(path, "/@v/", 2)
|
|
||||||
if len(parts) != 2 {
|
|
||||||
ctx.Status(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.SetPathParam("name", parts[0])
|
|
||||||
|
|
||||||
// <package/name>/@v/list
|
|
||||||
if parts[1] == "list" {
|
|
||||||
goproxy.EnumeratePackageVersions(ctx)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// <package/name>/@v/<version>.zip
|
|
||||||
if strings.HasSuffix(parts[1], ".zip") {
|
|
||||||
ctx.SetPathParam("version", parts[1][:len(parts[1])-len(".zip")])
|
|
||||||
|
|
||||||
goproxy.DownloadPackageFile(ctx)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// <package/name>/@v/<version>.info
|
|
||||||
if strings.HasSuffix(parts[1], ".info") {
|
|
||||||
ctx.SetPathParam("version", parts[1][:len(parts[1])-len(".info")])
|
|
||||||
|
|
||||||
goproxy.PackageVersionMetadata(ctx)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// <package/name>/@v/<version>.mod
|
|
||||||
if strings.HasSuffix(parts[1], ".mod") {
|
|
||||||
ctx.SetPathParam("version", parts[1][:len(parts[1])-len(".mod")])
|
|
||||||
|
|
||||||
goproxy.PackageVersionGoModContent(ctx)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Status(http.StatusNotFound)
|
|
||||||
})
|
})
|
||||||
}, reqPackageAccess(perm.AccessModeRead))
|
}, reqPackageAccess(perm.AccessModeRead))
|
||||||
r.Group("/generic", func() {
|
r.Group("/generic", func() {
|
||||||
@ -532,82 +453,24 @@ func CommonRoutes() *web.Router {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}, reqPackageAccess(perm.AccessModeRead))
|
}, reqPackageAccess(perm.AccessModeRead))
|
||||||
|
|
||||||
r.Group("/pypi", func() {
|
r.Group("/pypi", func() {
|
||||||
r.Post("/", reqPackageAccess(perm.AccessModeWrite), pypi.UploadPackageFile)
|
r.Post("/", reqPackageAccess(perm.AccessModeWrite), pypi.UploadPackageFile)
|
||||||
r.Get("/files/{id}/{version}/{filename}", pypi.DownloadPackageFile)
|
r.Get("/files/{id}/{version}/{filename}", pypi.DownloadPackageFile)
|
||||||
r.Get("/simple/{id}", pypi.PackageMetadata)
|
r.Get("/simple/{id}", pypi.PackageMetadata)
|
||||||
}, reqPackageAccess(perm.AccessModeRead))
|
}, reqPackageAccess(perm.AccessModeRead))
|
||||||
r.Group("/rpm", func() {
|
|
||||||
r.Group("/repository.key", func() {
|
|
||||||
r.Head("", rpm.GetRepositoryKey)
|
|
||||||
r.Get("", rpm.GetRepositoryKey)
|
|
||||||
})
|
|
||||||
|
|
||||||
var (
|
r.Methods("HEAD,GET", "/rpm.repo", reqPackageAccess(perm.AccessModeRead), rpm.GetRepositoryConfig)
|
||||||
repoPattern = regexp.MustCompile(`\A(.*?)\.repo\z`)
|
r.PathGroup("/rpm/*", func(g *web.RouterPathGroup) {
|
||||||
uploadPattern = regexp.MustCompile(`\A(.*?)/upload\z`)
|
g.MatchPath("HEAD,GET", "/repository.key", rpm.GetRepositoryKey)
|
||||||
filePattern = regexp.MustCompile(`\A(.*?)/package/([^/]+)/([^/]+)/([^/]+)(?:/([^/]+\.rpm)|)\z`)
|
g.MatchPath("HEAD,GET", "/<group:*>.repo", rpm.GetRepositoryConfig)
|
||||||
repoFilePattern = regexp.MustCompile(`\A(.*?)/repodata/([^/]+)\z`)
|
g.MatchPath("HEAD", "/<group:*>/repodata/<filename>", rpm.CheckRepositoryFileExistence)
|
||||||
)
|
g.MatchPath("GET", "/<group:*>/repodata/<filename>", rpm.GetRepositoryFile)
|
||||||
|
g.MatchPath("PUT", "/<group:*>/upload", reqPackageAccess(perm.AccessModeWrite), rpm.UploadPackageFile)
|
||||||
r.Methods("HEAD,GET,PUT,DELETE", "*", func(ctx *context.Context) {
|
g.MatchPath("HEAD,GET", "/<group:*>/package/<name>/<version>/<architecture>", rpm.DownloadPackageFile)
|
||||||
path := ctx.PathParam("*")
|
g.MatchPath("DELETE", "/<group:*>/package/<name>/<version>/<architecture>", reqPackageAccess(perm.AccessModeWrite), rpm.DeletePackageFile)
|
||||||
isHead := ctx.Req.Method == http.MethodHead
|
|
||||||
isGetHead := ctx.Req.Method == http.MethodHead || ctx.Req.Method == http.MethodGet
|
|
||||||
isPut := ctx.Req.Method == http.MethodPut
|
|
||||||
isDelete := ctx.Req.Method == http.MethodDelete
|
|
||||||
|
|
||||||
m := repoPattern.FindStringSubmatch(path)
|
|
||||||
if len(m) == 2 && isGetHead {
|
|
||||||
ctx.SetPathParam("group", strings.Trim(m[1], "/"))
|
|
||||||
rpm.GetRepositoryConfig(ctx)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m = repoFilePattern.FindStringSubmatch(path)
|
|
||||||
if len(m) == 3 && isGetHead {
|
|
||||||
ctx.SetPathParam("group", strings.Trim(m[1], "/"))
|
|
||||||
ctx.SetPathParam("filename", m[2])
|
|
||||||
if isHead {
|
|
||||||
rpm.CheckRepositoryFileExistence(ctx)
|
|
||||||
} else {
|
|
||||||
rpm.GetRepositoryFile(ctx)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m = uploadPattern.FindStringSubmatch(path)
|
|
||||||
if len(m) == 2 && isPut {
|
|
||||||
reqPackageAccess(perm.AccessModeWrite)(ctx)
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.SetPathParam("group", strings.Trim(m[1], "/"))
|
|
||||||
rpm.UploadPackageFile(ctx)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m = filePattern.FindStringSubmatch(path)
|
|
||||||
if len(m) == 6 && (isGetHead || isDelete) {
|
|
||||||
ctx.SetPathParam("group", strings.Trim(m[1], "/"))
|
|
||||||
ctx.SetPathParam("name", m[2])
|
|
||||||
ctx.SetPathParam("version", m[3])
|
|
||||||
ctx.SetPathParam("architecture", m[4])
|
|
||||||
if isGetHead {
|
|
||||||
rpm.DownloadPackageFile(ctx)
|
|
||||||
} else {
|
|
||||||
reqPackageAccess(perm.AccessModeWrite)(ctx)
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rpm.DeletePackageFile(ctx)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Status(http.StatusNotFound)
|
|
||||||
})
|
|
||||||
}, reqPackageAccess(perm.AccessModeRead))
|
}, reqPackageAccess(perm.AccessModeRead))
|
||||||
|
|
||||||
r.Group("/rubygems", func() {
|
r.Group("/rubygems", func() {
|
||||||
r.Get("/specs.4.8.gz", rubygems.EnumeratePackages)
|
r.Get("/specs.4.8.gz", rubygems.EnumeratePackages)
|
||||||
r.Get("/latest_specs.4.8.gz", rubygems.EnumeratePackagesLatest)
|
r.Get("/latest_specs.4.8.gz", rubygems.EnumeratePackagesLatest)
|
||||||
@ -621,6 +484,7 @@ func CommonRoutes() *web.Router {
|
|||||||
r.Delete("/yank", rubygems.DeletePackage)
|
r.Delete("/yank", rubygems.DeletePackage)
|
||||||
}, reqPackageAccess(perm.AccessModeWrite))
|
}, reqPackageAccess(perm.AccessModeWrite))
|
||||||
}, reqPackageAccess(perm.AccessModeRead))
|
}, reqPackageAccess(perm.AccessModeRead))
|
||||||
|
|
||||||
r.Group("/swift", func() {
|
r.Group("/swift", func() {
|
||||||
r.Group("", func() { // Needs to be unauthenticated.
|
r.Group("", func() { // Needs to be unauthenticated.
|
||||||
r.Post("", swift.CheckAuthenticate)
|
r.Post("", swift.CheckAuthenticate)
|
||||||
@ -632,31 +496,12 @@ func CommonRoutes() *web.Router {
|
|||||||
r.Get("", swift.EnumeratePackageVersions)
|
r.Get("", swift.EnumeratePackageVersions)
|
||||||
r.Get(".json", swift.EnumeratePackageVersions)
|
r.Get(".json", swift.EnumeratePackageVersions)
|
||||||
}, swift.CheckAcceptMediaType(swift.AcceptJSON))
|
}, swift.CheckAcceptMediaType(swift.AcceptJSON))
|
||||||
r.Group("/{version}", func() {
|
r.PathGroup("/*", func(g *web.RouterPathGroup) {
|
||||||
r.Get("/Package.swift", swift.CheckAcceptMediaType(swift.AcceptSwift), swift.DownloadManifest)
|
g.MatchPath("GET", "/<version>.json", swift.CheckAcceptMediaType(swift.AcceptJSON), swift.PackageVersionMetadata)
|
||||||
r.Put("", reqPackageAccess(perm.AccessModeWrite), swift.CheckAcceptMediaType(swift.AcceptJSON), swift.UploadPackageFile)
|
g.MatchPath("GET", "/<version>.zip", swift.CheckAcceptMediaType(swift.AcceptZip), swift.DownloadPackageFile)
|
||||||
r.Get("", func(ctx *context.Context) {
|
g.MatchPath("GET", "/<version>/Package.swift", swift.CheckAcceptMediaType(swift.AcceptSwift), swift.DownloadManifest)
|
||||||
// Can't use normal routes here: https://github.com/go-chi/chi/issues/781
|
g.MatchPath("GET", "/<version>", swift.CheckAcceptMediaType(swift.AcceptJSON), swift.PackageVersionMetadata)
|
||||||
|
g.MatchPath("PUT", "/<version>", reqPackageAccess(perm.AccessModeWrite), swift.CheckAcceptMediaType(swift.AcceptJSON), swift.UploadPackageFile)
|
||||||
version := ctx.PathParam("version")
|
|
||||||
if strings.HasSuffix(version, ".zip") {
|
|
||||||
swift.CheckAcceptMediaType(swift.AcceptZip)(ctx)
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.SetPathParam("version", version[:len(version)-4])
|
|
||||||
swift.DownloadPackageFile(ctx)
|
|
||||||
} else {
|
|
||||||
swift.CheckAcceptMediaType(swift.AcceptJSON)(ctx)
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(version, ".json") {
|
|
||||||
ctx.SetPathParam("version", version[:len(version)-5])
|
|
||||||
}
|
|
||||||
swift.PackageVersionMetadata(ctx)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
r.Get("/identifiers", swift.CheckAcceptMediaType(swift.AcceptJSON), swift.LookupPackageIdentifiers)
|
r.Get("/identifiers", swift.CheckAcceptMediaType(swift.AcceptJSON), swift.LookupPackageIdentifiers)
|
||||||
@ -705,18 +550,13 @@ func ContainerRoutes() *web.Router {
|
|||||||
r.PathGroup("/*", func(g *web.RouterPathGroup) {
|
r.PathGroup("/*", func(g *web.RouterPathGroup) {
|
||||||
g.MatchPath("POST", "/<image:*>/blobs/uploads", reqPackageAccess(perm.AccessModeWrite), container.VerifyImageName, container.PostBlobsUploads)
|
g.MatchPath("POST", "/<image:*>/blobs/uploads", reqPackageAccess(perm.AccessModeWrite), container.VerifyImageName, container.PostBlobsUploads)
|
||||||
g.MatchPath("GET", "/<image:*>/tags/list", container.VerifyImageName, container.GetTagsList)
|
g.MatchPath("GET", "/<image:*>/tags/list", container.VerifyImageName, container.GetTagsList)
|
||||||
g.MatchPath("GET,PATCH,PUT,DELETE", `/<image:*>/blobs/uploads/<uuid:[-.=\w]+>`, reqPackageAccess(perm.AccessModeWrite), container.VerifyImageName, func(ctx *context.Context) {
|
|
||||||
switch ctx.Req.Method {
|
patternBlobsUploadsUUID := g.PatternRegexp(`/<image:*>/blobs/uploads/<uuid:[-.=\w]+>`, reqPackageAccess(perm.AccessModeWrite), container.VerifyImageName)
|
||||||
case http.MethodGet:
|
g.MatchPattern("GET", patternBlobsUploadsUUID, container.GetBlobsUpload)
|
||||||
container.GetBlobsUpload(ctx)
|
g.MatchPattern("PATCH", patternBlobsUploadsUUID, container.PatchBlobsUpload)
|
||||||
case http.MethodPatch:
|
g.MatchPattern("PUT", patternBlobsUploadsUUID, container.PutBlobsUpload)
|
||||||
container.PatchBlobsUpload(ctx)
|
g.MatchPattern("DELETE", patternBlobsUploadsUUID, container.DeleteBlobsUpload)
|
||||||
case http.MethodPut:
|
|
||||||
container.PutBlobsUpload(ctx)
|
|
||||||
default: /* DELETE */
|
|
||||||
container.DeleteBlobsUpload(ctx)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
g.MatchPath("HEAD", `/<image:*>/blobs/<digest>`, container.VerifyImageName, container.HeadBlob)
|
g.MatchPath("HEAD", `/<image:*>/blobs/<digest>`, container.VerifyImageName, container.HeadBlob)
|
||||||
g.MatchPath("GET", `/<image:*>/blobs/<digest>`, container.VerifyImageName, container.GetBlob)
|
g.MatchPath("GET", `/<image:*>/blobs/<digest>`, container.VerifyImageName, container.GetBlob)
|
||||||
g.MatchPath("DELETE", `/<image:*>/blobs/<digest>`, container.VerifyImageName, reqPackageAccess(perm.AccessModeWrite), container.DeleteBlob)
|
g.MatchPath("DELETE", `/<image:*>/blobs/<digest>`, container.VerifyImageName, reqPackageAccess(perm.AccessModeWrite), container.DeleteBlob)
|
||||||
|
@ -36,6 +36,24 @@ func apiError(ctx *context.Context, status int, obj any) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isCondaPackageFileName(filename string) bool {
|
||||||
|
return strings.HasSuffix(filename, ".tar.bz2") || strings.HasSuffix(filename, ".conda")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListOrGetPackages(ctx *context.Context) {
|
||||||
|
filename := ctx.PathParam("filename")
|
||||||
|
switch filename {
|
||||||
|
case "repodata.json", "repodata.json.bz2", "current_repodata.json", "current_repodata.json.bz2":
|
||||||
|
EnumeratePackages(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if isCondaPackageFileName(filename) {
|
||||||
|
DownloadPackageFile(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.NotFound(nil)
|
||||||
|
}
|
||||||
|
|
||||||
func EnumeratePackages(ctx *context.Context) {
|
func EnumeratePackages(ctx *context.Context) {
|
||||||
type Info struct {
|
type Info struct {
|
||||||
Subdir string `json:"subdir"`
|
Subdir string `json:"subdir"`
|
||||||
@ -174,6 +192,12 @@ func EnumeratePackages(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UploadPackageFile(ctx *context.Context) {
|
func UploadPackageFile(ctx *context.Context) {
|
||||||
|
filename := ctx.PathParam("filename")
|
||||||
|
if !isCondaPackageFileName(filename) {
|
||||||
|
apiError(ctx, http.StatusBadRequest, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
upload, needToClose, err := ctx.UploadStream()
|
upload, needToClose, err := ctx.UploadStream()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiError(ctx, http.StatusInternalServerError, err)
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
@ -191,7 +215,7 @@ func UploadPackageFile(ctx *context.Context) {
|
|||||||
defer buf.Close()
|
defer buf.Close()
|
||||||
|
|
||||||
var pck *conda_module.Package
|
var pck *conda_module.Package
|
||||||
if strings.HasSuffix(strings.ToLower(ctx.PathParam("filename")), ".tar.bz2") {
|
if strings.HasSuffix(filename, ".tar.bz2") {
|
||||||
pck, err = conda_module.ParsePackageBZ2(buf)
|
pck, err = conda_module.ParsePackageBZ2(buf)
|
||||||
} else {
|
} else {
|
||||||
pck, err = conda_module.ParsePackageConda(buf, buf.Size())
|
pck, err = conda_module.ParsePackageConda(buf, buf.Size())
|
||||||
|
@ -90,14 +90,14 @@ func mountBlob(ctx context.Context, pi *packages_service.PackageInfo, pb *packag
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func containerPkgName(piOwnerID int64, piName string) string {
|
func containerGlobalLockKey(piOwnerID int64, piName, usage string) string {
|
||||||
return fmt.Sprintf("pkg_%d_container_%s", piOwnerID, strings.ToLower(piName))
|
return fmt.Sprintf("pkg_%d_container_%s_%s", piOwnerID, strings.ToLower(piName), usage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageInfo) (*packages_model.PackageVersion, error) {
|
func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageInfo) (*packages_model.PackageVersion, error) {
|
||||||
var uploadVersion *packages_model.PackageVersion
|
var uploadVersion *packages_model.PackageVersion
|
||||||
|
|
||||||
releaser, err := globallock.Lock(ctx, containerPkgName(pi.Owner.ID, pi.Name))
|
releaser, err := globallock.Lock(ctx, containerGlobalLockKey(pi.Owner.ID, pi.Name, "package"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -178,7 +178,7 @@ func createFileForBlob(ctx context.Context, pv *packages_model.PackageVersion, p
|
|||||||
}
|
}
|
||||||
|
|
||||||
func deleteBlob(ctx context.Context, ownerID int64, image string, digest digest.Digest) error {
|
func deleteBlob(ctx context.Context, ownerID int64, image string, digest digest.Digest) error {
|
||||||
releaser, err := globallock.Lock(ctx, containerPkgName(ownerID, image))
|
releaser, err := globallock.Lock(ctx, containerGlobalLockKey(ownerID, image, "blob"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ import (
|
|||||||
packages_service "code.gitea.io/gitea/services/packages"
|
packages_service "code.gitea.io/gitea/services/packages"
|
||||||
container_service "code.gitea.io/gitea/services/packages/container"
|
container_service "code.gitea.io/gitea/services/packages/container"
|
||||||
|
|
||||||
digest "github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
)
|
)
|
||||||
|
|
||||||
// maximum size of a container manifest
|
// maximum size of a container manifest
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
packages_model "code.gitea.io/gitea/models/packages"
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
container_model "code.gitea.io/gitea/models/packages/container"
|
container_model "code.gitea.io/gitea/models/packages/container"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/globallock"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
packages_module "code.gitea.io/gitea/modules/packages"
|
packages_module "code.gitea.io/gitea/modules/packages"
|
||||||
@ -61,6 +62,13 @@ func processManifest(ctx context.Context, mci *manifestCreationInfo, buf *packag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// .../container/manifest.go:453:createManifestBlob() [E] Error inserting package blob: Error 1062 (23000): Duplicate entry '..........' for key 'package_blob.UQE_package_blob_md5'
|
||||||
|
releaser, err := globallock.Lock(ctx, containerGlobalLockKey(mci.Owner.ID, mci.Image, "manifest"))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer releaser()
|
||||||
|
|
||||||
if container_module.IsMediaTypeImageManifest(mci.MediaType) {
|
if container_module.IsMediaTypeImageManifest(mci.MediaType) {
|
||||||
return processOciImageManifest(ctx, mci, buf)
|
return processOciImageManifest(ctx, mci, buf)
|
||||||
} else if container_module.IsMediaTypeImageIndex(mci.MediaType) {
|
} else if container_module.IsMediaTypeImageIndex(mci.MediaType) {
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
container_module "code.gitea.io/gitea/modules/packages/container"
|
container_module "code.gitea.io/gitea/modules/packages/container"
|
||||||
packages_service "code.gitea.io/gitea/services/packages"
|
packages_service "code.gitea.io/gitea/services/packages"
|
||||||
|
|
||||||
digest "github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cleanup removes expired container data
|
// Cleanup removes expired container data
|
||||||
|
Loading…
Reference in New Issue
Block a user