From 72137da62d7b6eda0398a23a33c5322767bf72a9 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Wed, 16 Nov 2022 11:52:04 +0000 Subject: [PATCH 1/2] Add support for HEAD requests. --- modules/context/context.go | 5 +- routers/api/packages/api.go | 1 + routers/api/packages/maven/api.go | 7 +-- routers/api/packages/maven/maven.go | 56 ++++++++++++++++++-- tests/integration/api_packages_maven_test.go | 30 ++++++++++- 5 files changed, 86 insertions(+), 13 deletions(-) diff --git a/modules/context/context.go b/modules/context/context.go index 4b6a21b217c3b..1b431bb8ebb58 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -16,6 +16,7 @@ import ( "io" "net" "net/http" + "net/textproto" "net/url" "path" "strconv" @@ -348,7 +349,9 @@ func (ctx *Context) RespHeader() http.Header { // SetServeHeaders sets necessary content serve headers func (ctx *Context) SetServeHeaders(filename string) { ctx.Resp.Header().Set("Content-Description", "File Transfer") - ctx.Resp.Header().Set("Content-Type", "application/octet-stream") + if _, has := ctx.Resp.Header()[textproto.CanonicalMIMEHeaderKey("Content-Type")]; !has { + ctx.Resp.Header().Set("Content-Type", "application/octet-stream") + } ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+filename) ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary") ctx.Resp.Header().Set("Expires", "0") diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index 11e7e5d6a67e3..0d8b9ce61eeb9 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -181,6 +181,7 @@ func CommonRoutes(ctx gocontext.Context) *web.Route { r.Group("/maven", func() { r.Put("/*", reqPackageAccess(perm.AccessModeWrite), maven.UploadPackageFile) r.Get("/*", maven.DownloadPackageFile) + r.Head("/*", maven.ProvidePackageFileHeader) }, reqPackageAccess(perm.AccessModeRead)) r.Group("/nuget", func() { r.Group("", func() { // Needs to be unauthenticated for the NuGet client. diff --git a/routers/api/packages/maven/api.go b/routers/api/packages/maven/api.go index b60a317814b4a..4ca541dd6f371 100644 --- a/routers/api/packages/maven/api.go +++ b/routers/api/packages/maven/api.go @@ -6,7 +6,6 @@ package maven import ( "encoding/xml" - "sort" "strings" packages_model "code.gitea.io/gitea/models/packages" @@ -23,12 +22,8 @@ type MetadataResponse struct { Version []string `xml:"versioning>versions>version"` } +// pds is expected to be sorted ascending by CreatedUnix func createMetadataResponse(pds []*packages_model.PackageDescriptor) *MetadataResponse { - sort.Slice(pds, func(i, j int) bool { - // Maven and Gradle order packages by their creation timestamp and not by their version string - return pds[i].Version.CreatedUnix < pds[j].Version.CreatedUnix - }) - var release *packages_model.PackageDescriptor versions := make([]string, 0, len(pds)) diff --git a/routers/api/packages/maven/maven.go b/routers/api/packages/maven/maven.go index de274b2046093..f05b3a05b9766 100644 --- a/routers/api/packages/maven/maven.go +++ b/routers/api/packages/maven/maven.go @@ -16,6 +16,8 @@ import ( "net/http" "path/filepath" "regexp" + "sort" + "strconv" "strings" packages_model "code.gitea.io/gitea/models/packages" @@ -34,6 +36,10 @@ const ( extensionSHA1 = ".sha1" extensionSHA256 = ".sha256" extensionSHA512 = ".sha512" + extensionPom = ".pom" + extensionJar = ".jar" + contentTypeJar = "application/java-archive" + contentTypeXml = "text/xml" ) var ( @@ -49,6 +55,15 @@ func apiError(ctx *context.Context, status int, obj interface{}) { // DownloadPackageFile serves the content of a package func DownloadPackageFile(ctx *context.Context) { + handlePackageFile(ctx, true) +} + +// ProvidePackageFileHeader provides only the headers describing a package +func ProvidePackageFileHeader(ctx *context.Context) { + handlePackageFile(ctx, false) +} + +func handlePackageFile(ctx *context.Context, serveContent bool) { params, err := extractPathParameters(ctx) if err != nil { apiError(ctx, http.StatusBadRequest, err) @@ -58,7 +73,7 @@ func DownloadPackageFile(ctx *context.Context) { if params.IsMeta && params.Version == "" { serveMavenMetadata(ctx, params) } else { - servePackageFile(ctx, params) + servePackageFile(ctx, params, serveContent) } } @@ -82,6 +97,11 @@ func serveMavenMetadata(ctx *context.Context, params parameters) { return } + sort.Slice(pds, func(i, j int) bool { + // Maven and Gradle order packages by their creation timestamp and not by their version string + return pds[i].Version.CreatedUnix < pds[j].Version.CreatedUnix + }) + xmlMetadata, err := xml.Marshal(createMetadataResponse(pds)) if err != nil { apiError(ctx, http.StatusInternalServerError, err) @@ -89,6 +109,9 @@ func serveMavenMetadata(ctx *context.Context, params parameters) { } xmlMetadataWithHeader := append([]byte(xml.Header), xmlMetadata...) + latest := pds[len(pds)-1] + ctx.Resp.Header().Set("Last-Modified", latest.Version.CreatedUnix.Format(http.TimeFormat)) + ext := strings.ToLower(filepath.Ext(params.Filename)) if isChecksumExtension(ext) { var hash []byte @@ -110,10 +133,15 @@ func serveMavenMetadata(ctx *context.Context, params parameters) { return } - ctx.PlainTextBytes(http.StatusOK, xmlMetadataWithHeader) + ctx.Resp.Header().Set("Content-Length", strconv.Itoa(len(xmlMetadataWithHeader))) + ctx.Resp.Header().Set("Content-Type", contentTypeXml) + + if _, err := ctx.Resp.Write(xmlMetadataWithHeader); err != nil { + log.Error("write bytes failed: %v", err) + } } -func servePackageFile(ctx *context.Context, params parameters) { +func servePackageFile(ctx *context.Context, params parameters, serveContent bool) { packageName := params.GroupID + "-" + params.ArtifactID pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, packageName, params.Version) @@ -149,6 +177,8 @@ func servePackageFile(ctx *context.Context, params parameters) { return } + ctx.Resp.Header().Set("Last-Modified", pf.CreatedUnix.Format(http.TimeFormat)) + if isChecksumExtension(ext) { var hash string switch ext { @@ -165,6 +195,24 @@ func servePackageFile(ctx *context.Context, params parameters) { return } + ctx.Resp.Header().Set("Content-Length", strconv.FormatInt(pb.Size, 10)) + + var contentType string + switch ext { + case extensionJar: + contentType = contentTypeJar + case extensionPom: + contentType = contentTypeXml + } + if contentType != "" { + ctx.Resp.Header().Set("Content-Type", contentType) + } + + if !serveContent { + ctx.Resp.WriteHeader(http.StatusOK) + return + } + s, err := packages_module.NewContentStore().Get(packages_module.BlobHash256Key(pb.HashSHA256)) if err != nil { apiError(ctx, http.StatusInternalServerError, err) @@ -273,7 +321,7 @@ func UploadPackageFile(ctx *context.Context) { } // If it's the package pom file extract the metadata - if ext == ".pom" { + if ext == extensionPom { pfci.IsLead = true var err error diff --git a/tests/integration/api_packages_maven_test.go b/tests/integration/api_packages_maven_test.go index 87d95557ce316..e71e1ff03baf2 100644 --- a/tests/integration/api_packages_maven_test.go +++ b/tests/integration/api_packages_maven_test.go @@ -7,6 +7,7 @@ package integration import ( "fmt" "net/http" + "strconv" "strings" "testing" @@ -39,6 +40,12 @@ func TestPackageMaven(t *testing.T) { MakeRequest(t, req, expectedStatus) } + checkHeaders := func(t *testing.T, h http.Header, contentType string, contentLength int64) { + assert.Equal(t, contentType, h.Get("Content-Type")) + assert.Equal(t, strconv.FormatInt(contentLength, 10), h.Get("Content-Length")) + assert.NotEmpty(t, h.Get("Last-Modified")) + } + t.Run("Upload", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -77,10 +84,18 @@ func TestPackageMaven(t *testing.T) { t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s", root, packageVersion, filename)) + req := NewRequest(t, "HEAD", fmt.Sprintf("%s/%s/%s", root, packageVersion, filename)) req = AddBasicAuthHeader(req, user.Name) resp := MakeRequest(t, req, http.StatusOK) + checkHeaders(t, resp.Header(), "application/java-archive", 4) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s", root, packageVersion, filename)) + req = AddBasicAuthHeader(req, user.Name) + resp = MakeRequest(t, req, http.StatusOK) + + checkHeaders(t, resp.Header(), "application/java-archive", 4) + assert.Equal(t, []byte("test"), resp.Body.Bytes()) pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeMaven) @@ -150,10 +165,18 @@ func TestPackageMaven(t *testing.T) { t.Run("DownloadPOM", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s.pom", root, packageVersion, filename)) + req := NewRequest(t, "HEAD", fmt.Sprintf("%s/%s/%s.pom", root, packageVersion, filename)) req = AddBasicAuthHeader(req, user.Name) resp := MakeRequest(t, req, http.StatusOK) + checkHeaders(t, resp.Header(), "text/xml", int64(len(pomContent))) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s.pom", root, packageVersion, filename)) + req = AddBasicAuthHeader(req, user.Name) + resp = MakeRequest(t, req, http.StatusOK) + + checkHeaders(t, resp.Header(), "text/xml", int64(len(pomContent))) + assert.Equal(t, []byte(pomContent), resp.Body.Bytes()) pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeMaven) @@ -191,6 +214,9 @@ func TestPackageMaven(t *testing.T) { resp := MakeRequest(t, req, http.StatusOK) expectedMetadata := `` + "\ncom.giteatest-project1.0.11.0.11.0.1" + + checkHeaders(t, resp.Header(), "text/xml", int64(len(expectedMetadata))) + assert.Equal(t, expectedMetadata, resp.Body.String()) for key, checksum := range map[string]string{ From 8252474d147891d36dcd1b83f932341a351dd282 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Wed, 16 Nov 2022 16:32:00 +0000 Subject: [PATCH 2/2] lint --- routers/api/packages/maven/maven.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/routers/api/packages/maven/maven.go b/routers/api/packages/maven/maven.go index f05b3a05b9766..869686d432a3c 100644 --- a/routers/api/packages/maven/maven.go +++ b/routers/api/packages/maven/maven.go @@ -39,7 +39,7 @@ const ( extensionPom = ".pom" extensionJar = ".jar" contentTypeJar = "application/java-archive" - contentTypeXml = "text/xml" + contentTypeXML = "text/xml" ) var ( @@ -134,7 +134,7 @@ func serveMavenMetadata(ctx *context.Context, params parameters) { } ctx.Resp.Header().Set("Content-Length", strconv.Itoa(len(xmlMetadataWithHeader))) - ctx.Resp.Header().Set("Content-Type", contentTypeXml) + ctx.Resp.Header().Set("Content-Type", contentTypeXML) if _, err := ctx.Resp.Write(xmlMetadataWithHeader); err != nil { log.Error("write bytes failed: %v", err) @@ -202,7 +202,7 @@ func servePackageFile(ctx *context.Context, params parameters, serveContent bool case extensionJar: contentType = contentTypeJar case extensionPom: - contentType = contentTypeXml + contentType = contentTypeXML } if contentType != "" { ctx.Resp.Header().Set("Content-Type", contentType)