Skip to content

Commit

Permalink
Add support for HEAD requests in Maven registry (#21834)
Browse files Browse the repository at this point in the history
Related #18543

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
  • Loading branch information
KN4CK3R and lunny authored Nov 24, 2022
1 parent 26f941f commit fc7a2d5
Show file tree
Hide file tree
Showing 19 changed files with 161 additions and 39 deletions.
18 changes: 13 additions & 5 deletions modules/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,9 +349,11 @@ func (ctx *Context) RespHeader() http.Header {
type ServeHeaderOptions struct {
ContentType string // defaults to "application/octet-stream"
ContentTypeCharset string
ContentLength *int64
Disposition string // defaults to "attachment"
Filename string
CacheDuration time.Duration // defaults to 5 minutes
LastModified time.Time
}

// SetServeHeaders sets necessary content serve headers
Expand All @@ -369,6 +371,10 @@ func (ctx *Context) SetServeHeaders(opts *ServeHeaderOptions) {
header.Set("Content-Type", contentType)
header.Set("X-Content-Type-Options", "nosniff")

if opts.ContentLength != nil {
header.Set("Content-Length", strconv.FormatInt(*opts.ContentLength, 10))
}

if opts.Filename != "" {
disposition := opts.Disposition
if disposition == "" {
Expand All @@ -385,14 +391,16 @@ func (ctx *Context) SetServeHeaders(opts *ServeHeaderOptions) {
duration = 5 * time.Minute
}
httpcache.AddCacheControlToHeader(header, duration)

if !opts.LastModified.IsZero() {
header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat))
}
}

// ServeContent serves content to http request
func (ctx *Context) ServeContent(name string, r io.ReadSeeker, modTime time.Time) {
ctx.SetServeHeaders(&ServeHeaderOptions{
Filename: name,
})
http.ServeContent(ctx.Resp, ctx.Req, name, modTime, r)
func (ctx *Context) ServeContent(r io.ReadSeeker, opts *ServeHeaderOptions) {
ctx.SetServeHeaders(opts)
http.ServeContent(ctx.Resp, ctx.Req, opts.Filename, opts.LastModified, r)
}

// UploadStream returns the request body or the first form file
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
5 changes: 4 additions & 1 deletion routers/api/packages/composer/composer.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,10 @@ func DownloadPackageFile(ctx *context.Context) {
}
defer s.Close()

ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}

// UploadPackage creates a new package
Expand Down
5 changes: 4 additions & 1 deletion routers/api/packages/conan/conan.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,10 @@ func downloadFile(ctx *context.Context, fileFilter container.Set[string], fileKe
}
defer s.Close()

ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}

// DeleteRecipeV1 deletes the requested recipe(s)
Expand Down
5 changes: 4 additions & 1 deletion routers/api/packages/generic/generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ func DownloadPackageFile(ctx *context.Context) {
}
defer s.Close()

ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}

// UploadPackage uploads the specific generic package.
Expand Down
5 changes: 4 additions & 1 deletion routers/api/packages/helm/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,10 @@ func DownloadPackageFile(ctx *context.Context) {
}
defer s.Close()

ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}

// UploadPackage creates a new package
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
57 changes: 52 additions & 5 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 @@ -165,6 +193,23 @@ func servePackageFile(ctx *context.Context, params parameters) {
return
}

opts := &context.ServeHeaderOptions{
ContentLength: &pb.Size,
LastModified: pf.CreatedUnix.AsLocalTime(),
}
switch ext {
case extensionJar:
opts.ContentType = contentTypeJar
case extensionPom:
opts.ContentType = contentTypeXML
}

if !serveContent {
ctx.SetServeHeaders(opts)
ctx.Status(http.StatusOK)
return
}

s, err := packages_module.NewContentStore().Get(packages_module.BlobHash256Key(pb.HashSHA256))
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
Expand All @@ -177,7 +222,9 @@ func servePackageFile(ctx *context.Context, params parameters) {
}
}

ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
opts.Filename = pf.Name

ctx.ServeContent(s, opts)
}

// UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
Expand Down Expand Up @@ -273,7 +320,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
10 changes: 8 additions & 2 deletions routers/api/packages/npm/npm.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ func DownloadPackageFile(ctx *context.Context) {
}
defer s.Close()

ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}

// DownloadPackageFileByName finds the version and serves the contents of a package
Expand Down Expand Up @@ -146,7 +149,10 @@ func DownloadPackageFileByName(ctx *context.Context) {
}
defer s.Close()

ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}

// UploadPackage creates a new package
Expand Down
10 changes: 8 additions & 2 deletions routers/api/packages/nuget/nuget.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,10 @@ func DownloadPackageFile(ctx *context.Context) {
}
defer s.Close()

ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}

// UploadPackage creates a new package with the metadata contained in the uploaded nupgk file
Expand Down Expand Up @@ -562,7 +565,10 @@ func DownloadSymbolFile(ctx *context.Context) {
}
defer s.Close()

ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}

// DeletePackage hard deletes the package
Expand Down
5 changes: 4 additions & 1 deletion routers/api/packages/pub/pub.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,5 +275,8 @@ func DownloadPackageFile(ctx *context.Context) {
}
defer s.Close()

ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}
5 changes: 4 additions & 1 deletion routers/api/packages/pypi/pypi.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ func DownloadPackageFile(ctx *context.Context) {
}
defer s.Close()

ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}

// UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
Expand Down
5 changes: 4 additions & 1 deletion routers/api/packages/rubygems/rubygems.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,10 @@ func DownloadPackageFile(ctx *context.Context) {
}
defer s.Close()

ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}

// UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
Expand Down
5 changes: 4 additions & 1 deletion routers/api/packages/vagrant/vagrant.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,5 +239,8 @@ func DownloadPackageFile(ctx *context.Context) {
}
defer s.Close()

ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}
6 changes: 5 additions & 1 deletion routers/api/v1/repo/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,11 @@ func download(ctx *context.APIContext, archiveName string, archiver *repo_model.
return
}
defer fr.Close()
ctx.ServeContent(downloadName, fr, archiver.CreatedUnix.AsLocalTime())

ctx.ServeContent(fr, &context.ServeHeaderOptions{
Filename: downloadName,
LastModified: archiver.CreatedUnix.AsLocalTime(),
})
}

// GetEditorconfig get editor config of a repository
Expand Down
11 changes: 5 additions & 6 deletions routers/common/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package common

import (
"fmt"
"io"
"path"
"path/filepath"
Expand Down Expand Up @@ -52,16 +51,16 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read
buf = buf[:n]
}

opts := &context.ServeHeaderOptions{
Filename: path.Base(filePath),
}

if size >= 0 {
ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", size))
opts.ContentLength = &size
} else {
log.Error("ServeData called to serve data: %s with size < 0: %d", filePath, size)
}

opts := &context.ServeHeaderOptions{
Filename: path.Base(filePath),
}

sniffedType := typesniffer.DetectContentType(buf)
isPlain := sniffedType.IsText() || ctx.FormBool("render")

Expand Down
5 changes: 4 additions & 1 deletion routers/web/repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,10 @@ func download(ctx *context.Context, archiveName string, archiver *repo_model.Rep
}
defer fr.Close()

ctx.ServeContent(downloadName, fr, archiver.CreatedUnix.AsLocalTime())
ctx.ServeContent(fr, &context.ServeHeaderOptions{
Filename: downloadName,
LastModified: archiver.CreatedUnix.AsLocalTime(),
})
}

// InitiateDownload will enqueue an archival request, as needed. It may submit
Expand Down
Loading

0 comments on commit fc7a2d5

Please sign in to comment.