Skip to content

Commit ff4e292

Browse files
KN4CK3Rlunny
andauthored
Add support for HEAD requests in Maven registry (#21834) (#21929)
Backport of #21834 Co-authored-by: Lunny Xiao <[email protected]>
1 parent 9ba4ef9 commit ff4e292

File tree

19 files changed

+161
-39
lines changed

19 files changed

+161
-39
lines changed

modules/context/context.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -349,9 +349,11 @@ func (ctx *Context) RespHeader() http.Header {
349349
type ServeHeaderOptions struct {
350350
ContentType string // defaults to "application/octet-stream"
351351
ContentTypeCharset string
352+
ContentLength *int64
352353
Disposition string // defaults to "attachment"
353354
Filename string
354355
CacheDuration time.Duration // defaults to 5 minutes
356+
LastModified time.Time
355357
}
356358

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

374+
if opts.ContentLength != nil {
375+
header.Set("Content-Length", strconv.FormatInt(*opts.ContentLength, 10))
376+
}
377+
372378
if opts.Filename != "" {
373379
disposition := opts.Disposition
374380
if disposition == "" {
@@ -385,14 +391,16 @@ func (ctx *Context) SetServeHeaders(opts *ServeHeaderOptions) {
385391
duration = 5 * time.Minute
386392
}
387393
httpcache.AddCacheControlToHeader(header, duration)
394+
395+
if !opts.LastModified.IsZero() {
396+
header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat))
397+
}
388398
}
389399

390400
// ServeContent serves content to http request
391-
func (ctx *Context) ServeContent(name string, r io.ReadSeeker, modTime time.Time) {
392-
ctx.SetServeHeaders(&ServeHeaderOptions{
393-
Filename: name,
394-
})
395-
http.ServeContent(ctx.Resp, ctx.Req, name, modTime, r)
401+
func (ctx *Context) ServeContent(r io.ReadSeeker, opts *ServeHeaderOptions) {
402+
ctx.SetServeHeaders(opts)
403+
http.ServeContent(ctx.Resp, ctx.Req, opts.Filename, opts.LastModified, r)
396404
}
397405

398406
// UploadStream returns the request body or the first form file

routers/api/packages/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ func Routes(ctx gocontext.Context) *web.Route {
179179
r.Group("/maven", func() {
180180
r.Put("/*", reqPackageAccess(perm.AccessModeWrite), maven.UploadPackageFile)
181181
r.Get("/*", maven.DownloadPackageFile)
182+
r.Head("/*", maven.ProvidePackageFileHeader)
182183
}, reqPackageAccess(perm.AccessModeRead))
183184
r.Group("/nuget", func() {
184185
r.Group("", func() { // Needs to be unauthenticated for the NuGet client.

routers/api/packages/composer/composer.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,10 @@ func DownloadPackageFile(ctx *context.Context) {
184184
}
185185
defer s.Close()
186186

187-
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
187+
ctx.ServeContent(s, &context.ServeHeaderOptions{
188+
Filename: pf.Name,
189+
LastModified: pf.CreatedUnix.AsLocalTime(),
190+
})
188191
}
189192

190193
// UploadPackage creates a new package

routers/api/packages/conan/conan.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,10 @@ func downloadFile(ctx *context.Context, fileFilter container.Set[string], fileKe
473473
}
474474
defer s.Close()
475475

476-
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
476+
ctx.ServeContent(s, &context.ServeHeaderOptions{
477+
Filename: pf.Name,
478+
LastModified: pf.CreatedUnix.AsLocalTime(),
479+
})
477480
}
478481

479482
// DeleteRecipeV1 deletes the requested recipe(s)

routers/api/packages/generic/generic.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ func DownloadPackageFile(ctx *context.Context) {
5353
}
5454
defer s.Close()
5555

56-
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
56+
ctx.ServeContent(s, &context.ServeHeaderOptions{
57+
Filename: pf.Name,
58+
LastModified: pf.CreatedUnix.AsLocalTime(),
59+
})
5760
}
5861

5962
// UploadPackage uploads the specific generic package.

routers/api/packages/helm/helm.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,10 @@ func DownloadPackageFile(ctx *context.Context) {
138138
}
139139
defer s.Close()
140140

141-
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
141+
ctx.ServeContent(s, &context.ServeHeaderOptions{
142+
Filename: pf.Name,
143+
LastModified: pf.CreatedUnix.AsLocalTime(),
144+
})
142145
}
143146

144147
// UploadPackage creates a new package

routers/api/packages/maven/api.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package maven
66

77
import (
88
"encoding/xml"
9-
"sort"
109
"strings"
1110

1211
packages_model "code.gitea.io/gitea/models/packages"
@@ -23,12 +22,8 @@ type MetadataResponse struct {
2322
Version []string `xml:"versioning>versions>version"`
2423
}
2524

25+
// pds is expected to be sorted ascending by CreatedUnix
2626
func createMetadataResponse(pds []*packages_model.PackageDescriptor) *MetadataResponse {
27-
sort.Slice(pds, func(i, j int) bool {
28-
// Maven and Gradle order packages by their creation timestamp and not by their version string
29-
return pds[i].Version.CreatedUnix < pds[j].Version.CreatedUnix
30-
})
31-
3227
var release *packages_model.PackageDescriptor
3328

3429
versions := make([]string, 0, len(pds))

routers/api/packages/maven/maven.go

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616
"net/http"
1717
"path/filepath"
1818
"regexp"
19+
"sort"
20+
"strconv"
1921
"strings"
2022

2123
packages_model "code.gitea.io/gitea/models/packages"
@@ -34,6 +36,10 @@ const (
3436
extensionSHA1 = ".sha1"
3537
extensionSHA256 = ".sha256"
3638
extensionSHA512 = ".sha512"
39+
extensionPom = ".pom"
40+
extensionJar = ".jar"
41+
contentTypeJar = "application/java-archive"
42+
contentTypeXML = "text/xml"
3743
)
3844

3945
var (
@@ -49,6 +55,15 @@ func apiError(ctx *context.Context, status int, obj interface{}) {
4955

5056
// DownloadPackageFile serves the content of a package
5157
func DownloadPackageFile(ctx *context.Context) {
58+
handlePackageFile(ctx, true)
59+
}
60+
61+
// ProvidePackageFileHeader provides only the headers describing a package
62+
func ProvidePackageFileHeader(ctx *context.Context) {
63+
handlePackageFile(ctx, false)
64+
}
65+
66+
func handlePackageFile(ctx *context.Context, serveContent bool) {
5267
params, err := extractPathParameters(ctx)
5368
if err != nil {
5469
apiError(ctx, http.StatusBadRequest, err)
@@ -58,7 +73,7 @@ func DownloadPackageFile(ctx *context.Context) {
5873
if params.IsMeta && params.Version == "" {
5974
serveMavenMetadata(ctx, params)
6075
} else {
61-
servePackageFile(ctx, params)
76+
servePackageFile(ctx, params, serveContent)
6277
}
6378
}
6479

@@ -82,13 +97,21 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {
8297
return
8398
}
8499

100+
sort.Slice(pds, func(i, j int) bool {
101+
// Maven and Gradle order packages by their creation timestamp and not by their version string
102+
return pds[i].Version.CreatedUnix < pds[j].Version.CreatedUnix
103+
})
104+
85105
xmlMetadata, err := xml.Marshal(createMetadataResponse(pds))
86106
if err != nil {
87107
apiError(ctx, http.StatusInternalServerError, err)
88108
return
89109
}
90110
xmlMetadataWithHeader := append([]byte(xml.Header), xmlMetadata...)
91111

112+
latest := pds[len(pds)-1]
113+
ctx.Resp.Header().Set("Last-Modified", latest.Version.CreatedUnix.Format(http.TimeFormat))
114+
92115
ext := strings.ToLower(filepath.Ext(params.Filename))
93116
if isChecksumExtension(ext) {
94117
var hash []byte
@@ -110,10 +133,15 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {
110133
return
111134
}
112135

113-
ctx.PlainTextBytes(http.StatusOK, xmlMetadataWithHeader)
136+
ctx.Resp.Header().Set("Content-Length", strconv.Itoa(len(xmlMetadataWithHeader)))
137+
ctx.Resp.Header().Set("Content-Type", contentTypeXML)
138+
139+
if _, err := ctx.Resp.Write(xmlMetadataWithHeader); err != nil {
140+
log.Error("write bytes failed: %v", err)
141+
}
114142
}
115143

116-
func servePackageFile(ctx *context.Context, params parameters) {
144+
func servePackageFile(ctx *context.Context, params parameters, serveContent bool) {
117145
packageName := params.GroupID + "-" + params.ArtifactID
118146

119147
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, packageName, params.Version)
@@ -165,6 +193,23 @@ func servePackageFile(ctx *context.Context, params parameters) {
165193
return
166194
}
167195

196+
opts := &context.ServeHeaderOptions{
197+
ContentLength: &pb.Size,
198+
LastModified: pf.CreatedUnix.AsLocalTime(),
199+
}
200+
switch ext {
201+
case extensionJar:
202+
opts.ContentType = contentTypeJar
203+
case extensionPom:
204+
opts.ContentType = contentTypeXML
205+
}
206+
207+
if !serveContent {
208+
ctx.SetServeHeaders(opts)
209+
ctx.Status(http.StatusOK)
210+
return
211+
}
212+
168213
s, err := packages_module.NewContentStore().Get(packages_module.BlobHash256Key(pb.HashSHA256))
169214
if err != nil {
170215
apiError(ctx, http.StatusInternalServerError, err)
@@ -177,7 +222,9 @@ func servePackageFile(ctx *context.Context, params parameters) {
177222
}
178223
}
179224

180-
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
225+
opts.Filename = pf.Name
226+
227+
ctx.ServeContent(s, opts)
181228
}
182229

183230
// UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
@@ -272,7 +319,7 @@ func UploadPackageFile(ctx *context.Context) {
272319
}
273320

274321
// If it's the package pom file extract the metadata
275-
if ext == ".pom" {
322+
if ext == extensionPom {
276323
pfci.IsLead = true
277324

278325
var err error

routers/api/packages/npm/npm.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,10 @@ func DownloadPackageFile(ctx *context.Context) {
103103
}
104104
defer s.Close()
105105

106-
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
106+
ctx.ServeContent(s, &context.ServeHeaderOptions{
107+
Filename: pf.Name,
108+
LastModified: pf.CreatedUnix.AsLocalTime(),
109+
})
107110
}
108111

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

149-
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
152+
ctx.ServeContent(s, &context.ServeHeaderOptions{
153+
Filename: pf.Name,
154+
LastModified: pf.CreatedUnix.AsLocalTime(),
155+
})
150156
}
151157

152158
// UploadPackage creates a new package

routers/api/packages/nuget/nuget.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,10 @@ func DownloadPackageFile(ctx *context.Context) {
342342
}
343343
defer s.Close()
344344

345-
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
345+
ctx.ServeContent(s, &context.ServeHeaderOptions{
346+
Filename: pf.Name,
347+
LastModified: pf.CreatedUnix.AsLocalTime(),
348+
})
346349
}
347350

348351
// UploadPackage creates a new package with the metadata contained in the uploaded nupgk file
@@ -552,7 +555,10 @@ func DownloadSymbolFile(ctx *context.Context) {
552555
}
553556
defer s.Close()
554557

555-
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
558+
ctx.ServeContent(s, &context.ServeHeaderOptions{
559+
Filename: pf.Name,
560+
LastModified: pf.CreatedUnix.AsLocalTime(),
561+
})
556562
}
557563

558564
// DeletePackage hard deletes the package

routers/api/packages/pub/pub.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,5 +271,8 @@ func DownloadPackageFile(ctx *context.Context) {
271271
}
272272
defer s.Close()
273273

274-
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
274+
ctx.ServeContent(s, &context.ServeHeaderOptions{
275+
Filename: pf.Name,
276+
LastModified: pf.CreatedUnix.AsLocalTime(),
277+
})
275278
}

routers/api/packages/pypi/pypi.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,10 @@ func DownloadPackageFile(ctx *context.Context) {
9595
}
9696
defer s.Close()
9797

98-
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
98+
ctx.ServeContent(s, &context.ServeHeaderOptions{
99+
Filename: pf.Name,
100+
LastModified: pf.CreatedUnix.AsLocalTime(),
101+
})
99102
}
100103

101104
// UploadPackageFile adds a file to the package. If the package does not exist, it gets created.

routers/api/packages/rubygems/rubygems.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,10 @@ func DownloadPackageFile(ctx *context.Context) {
192192
}
193193
defer s.Close()
194194

195-
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
195+
ctx.ServeContent(s, &context.ServeHeaderOptions{
196+
Filename: pf.Name,
197+
LastModified: pf.CreatedUnix.AsLocalTime(),
198+
})
196199
}
197200

198201
// UploadPackageFile adds a file to the package. If the package does not exist, it gets created.

routers/api/packages/vagrant/vagrant.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,5 +235,8 @@ func DownloadPackageFile(ctx *context.Context) {
235235
}
236236
defer s.Close()
237237

238-
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
238+
ctx.ServeContent(s, &context.ServeHeaderOptions{
239+
Filename: pf.Name,
240+
LastModified: pf.CreatedUnix.AsLocalTime(),
241+
})
239242
}

routers/api/v1/repo/file.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,11 @@ func download(ctx *context.APIContext, archiveName string, archiver *repo_model.
341341
return
342342
}
343343
defer fr.Close()
344-
ctx.ServeContent(downloadName, fr, archiver.CreatedUnix.AsLocalTime())
344+
345+
ctx.ServeContent(fr, &context.ServeHeaderOptions{
346+
Filename: downloadName,
347+
LastModified: archiver.CreatedUnix.AsLocalTime(),
348+
})
345349
}
346350

347351
// GetEditorconfig get editor config of a repository

routers/common/repo.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
package common
66

77
import (
8-
"fmt"
98
"io"
109
"path"
1110
"path/filepath"
@@ -52,16 +51,16 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read
5251
buf = buf[:n]
5352
}
5453

54+
opts := &context.ServeHeaderOptions{
55+
Filename: path.Base(filePath),
56+
}
57+
5558
if size >= 0 {
56-
ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", size))
59+
opts.ContentLength = &size
5760
} else {
5861
log.Error("ServeData called to serve data: %s with size < 0: %d", filePath, size)
5962
}
6063

61-
opts := &context.ServeHeaderOptions{
62-
Filename: path.Base(filePath),
63-
}
64-
6564
sniffedType := typesniffer.DetectContentType(buf)
6665
isPlain := sniffedType.IsText() || ctx.FormBool("render")
6766

routers/web/repo/repo.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,10 @@ func download(ctx *context.Context, archiveName string, archiver *repo_model.Rep
426426
}
427427
defer fr.Close()
428428

429-
ctx.ServeContent(downloadName, fr, archiver.CreatedUnix.AsLocalTime())
429+
ctx.ServeContent(fr, &context.ServeHeaderOptions{
430+
Filename: downloadName,
431+
LastModified: archiver.CreatedUnix.AsLocalTime(),
432+
})
430433
}
431434

432435
// InitiateDownload will enqueue an archival request, as needed. It may submit

0 commit comments

Comments
 (0)