Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion modules/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"io"
"net"
"net/http"
"net/textproto"
"net/url"
"path"
"strconv"
Expand Down Expand Up @@ -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")
Expand Down
1 change: 1 addition & 0 deletions routers/api/packages/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 1 addition & 6 deletions routers/api/packages/maven/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package maven

import (
"encoding/xml"
"sort"
"strings"

packages_model "code.gitea.io/gitea/models/packages"
Expand All @@ -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))
Expand Down
56 changes: 52 additions & 4 deletions routers/api/packages/maven/maven.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"net/http"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"

packages_model "code.gitea.io/gitea/models/packages"
Expand All @@ -34,6 +36,10 @@ const (
extensionSHA1 = ".sha1"
extensionSHA256 = ".sha256"
extensionSHA512 = ".sha512"
extensionPom = ".pom"
extensionJar = ".jar"
contentTypeJar = "application/java-archive"
contentTypeXML = "text/xml"
)

var (
Expand All @@ -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)
Expand All @@ -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)
}
}

Expand All @@ -82,13 +97,21 @@ 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)
return
}
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
Expand All @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
30 changes: 28 additions & 2 deletions tests/integration/api_packages_maven_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package integration
import (
"fmt"
"net/http"
"strconv"
"strings"
"testing"

Expand Down Expand Up @@ -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)()

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -191,6 +214,9 @@ func TestPackageMaven(t *testing.T) {
resp := MakeRequest(t, req, http.StatusOK)

expectedMetadata := `<?xml version="1.0" encoding="UTF-8"?>` + "\n<metadata><groupId>com.gitea</groupId><artifactId>test-project</artifactId><versioning><release>1.0.1</release><latest>1.0.1</latest><versions><version>1.0.1</version></versions></versioning></metadata>"

checkHeaders(t, resp.Header(), "text/xml", int64(len(expectedMetadata)))

assert.Equal(t, expectedMetadata, resp.Body.String())

for key, checksum := range map[string]string{
Expand Down