Add direct serving of package content (#25543)
Fixes #24723 Direct serving of content aka HTTP redirect is not mentioned in any of the package registry specs but lots of official registries do that so it should be supported by the usual clients.
This commit is contained in:
parent
f1cb461c1f
commit
c890454769
|
@ -5,9 +5,11 @@ package packages
|
|||
|
||||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
@ -31,6 +33,14 @@ func (s *ContentStore) Get(key BlobHash256Key) (storage.Object, error) {
|
|||
return s.store.Open(KeyToRelativePath(key))
|
||||
}
|
||||
|
||||
func (s *ContentStore) ShouldServeDirect() bool {
|
||||
return setting.Packages.Storage.MinioConfig.ServeDirect
|
||||
}
|
||||
|
||||
func (s *ContentStore) GetServeDirectURL(key BlobHash256Key, filename string) (*url.URL, error) {
|
||||
return s.store.URL(KeyToRelativePath(key), filename)
|
||||
}
|
||||
|
||||
// FIXME: Workaround to be removed in v1.20
|
||||
// https://github.com/go-gitea/gitea/issues/19586
|
||||
func (s *ContentStore) Has(key BlobHash256Key) error {
|
||||
|
|
|
@ -68,7 +68,7 @@ func GetRepositoryFile(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
s, pf, err := packages_service.GetFileStreamByPackageVersion(
|
||||
s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
|
||||
ctx,
|
||||
pv,
|
||||
&packages_service.PackageFileInfo{
|
||||
|
@ -84,12 +84,8 @@ func GetRepositoryFile(ctx *context.Context) {
|
|||
}
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
||||
func UploadPackageFile(ctx *context.Context) {
|
||||
|
@ -200,7 +196,7 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
s, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
|
||||
s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
apiError(ctx, http.StatusNotFound, err)
|
||||
|
@ -209,12 +205,8 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
}
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
||||
func DeletePackageFile(ctx *context.Context) {
|
||||
|
|
|
@ -165,7 +165,7 @@ func ListOwners(ctx *context.Context) {
|
|||
|
||||
// DownloadPackageFile serves the content of a package
|
||||
func DownloadPackageFile(ctx *context.Context) {
|
||||
s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
||||
s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
||||
ctx,
|
||||
&packages_service.PackageInfo{
|
||||
Owner: ctx.Package.Owner,
|
||||
|
@ -185,12 +185,8 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
||||
// https://doc.rust-lang.org/cargo/reference/registries.html#publish
|
||||
|
|
|
@ -341,17 +341,13 @@ func DownloadPackage(ctx *context.Context) {
|
|||
|
||||
pf := pd.Files[0].File
|
||||
|
||||
s, _, err := packages_service.GetPackageFileStream(ctx, pf)
|
||||
s, u, _, err := packages_service.GetPackageFileStream(ctx, pf)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
||||
// https://github.com/chef/chef/blob/main/knife/lib/chef/knife/supermarket_unshare.rb
|
||||
|
|
|
@ -162,7 +162,7 @@ func PackageMetadata(ctx *context.Context) {
|
|||
|
||||
// DownloadPackageFile serves the content of a package
|
||||
func DownloadPackageFile(ctx *context.Context) {
|
||||
s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
||||
s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
||||
ctx,
|
||||
&packages_service.PackageInfo{
|
||||
Owner: ctx.Package.Owner,
|
||||
|
@ -182,12 +182,8 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
||||
// UploadPackage creates a new package
|
||||
|
|
|
@ -453,7 +453,7 @@ func downloadFile(ctx *context.Context, fileFilter container.Set[string], fileKe
|
|||
return
|
||||
}
|
||||
|
||||
s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
||||
s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
||||
ctx,
|
||||
&packages_service.PackageInfo{
|
||||
Owner: ctx.Package.Owner,
|
||||
|
@ -474,12 +474,8 @@ func downloadFile(ctx *context.Context, fileFilter container.Set[string], fileKe
|
|||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
||||
// DeleteRecipeV1 deletes the requested recipe(s)
|
||||
|
|
|
@ -292,15 +292,11 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
|
||||
pf := pfs[0]
|
||||
|
||||
s, _, err := packages_service.GetPackageFileStream(ctx, pf)
|
||||
s, u, _, err := packages_service.GetPackageFileStream(ctx, pf)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
|
|
@ -482,22 +482,7 @@ func GetBlob(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
s, _, err := packages_service.GetPackageFileStream(ctx, blob.File)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
setResponseHeaders(ctx.Resp, &containerHeaders{
|
||||
ContentDigest: blob.Properties.GetByName(container_module.PropertyDigest),
|
||||
ContentType: blob.Properties.GetByName(container_module.PropertyMediaType),
|
||||
ContentLength: blob.Blob.Size,
|
||||
Status: http.StatusOK,
|
||||
})
|
||||
if _, err := io.Copy(ctx.Resp, s); err != nil {
|
||||
log.Error("Error whilst copying content to response: %v", err)
|
||||
}
|
||||
serveBlob(ctx, blob)
|
||||
}
|
||||
|
||||
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#deleting-blobs
|
||||
|
@ -636,22 +621,7 @@ func GetManifest(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
s, _, err := packages_service.GetPackageFileStream(ctx, manifest.File)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
setResponseHeaders(ctx.Resp, &containerHeaders{
|
||||
ContentDigest: manifest.Properties.GetByName(container_module.PropertyDigest),
|
||||
ContentType: manifest.Properties.GetByName(container_module.PropertyMediaType),
|
||||
ContentLength: manifest.Blob.Size,
|
||||
Status: http.StatusOK,
|
||||
})
|
||||
if _, err := io.Copy(ctx.Resp, s); err != nil {
|
||||
log.Error("Error whilst copying content to response: %v", err)
|
||||
}
|
||||
serveBlob(ctx, manifest)
|
||||
}
|
||||
|
||||
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#deleting-tags
|
||||
|
@ -686,6 +656,36 @@ func DeleteManifest(ctx *context.Context) {
|
|||
})
|
||||
}
|
||||
|
||||
func serveBlob(ctx *context.Context, pfd *packages_model.PackageFileDescriptor) {
|
||||
s, u, _, err := packages_service.GetPackageBlobStream(ctx, pfd.File, pfd.Blob)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
headers := &containerHeaders{
|
||||
ContentDigest: pfd.Properties.GetByName(container_module.PropertyDigest),
|
||||
ContentType: pfd.Properties.GetByName(container_module.PropertyMediaType),
|
||||
ContentLength: pfd.Blob.Size,
|
||||
Status: http.StatusOK,
|
||||
}
|
||||
|
||||
if u != nil {
|
||||
headers.Status = http.StatusTemporaryRedirect
|
||||
headers.Location = u.String()
|
||||
|
||||
setResponseHeaders(ctx.Resp, headers)
|
||||
return
|
||||
}
|
||||
|
||||
defer s.Close()
|
||||
|
||||
setResponseHeaders(ctx.Resp, headers)
|
||||
if _, err := io.Copy(ctx.Resp, s); err != nil {
|
||||
log.Error("Error whilst copying content to response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#content-discovery
|
||||
func GetTagList(ctx *context.Context) {
|
||||
image := ctx.Params("image")
|
||||
|
|
|
@ -249,7 +249,7 @@ func downloadPackageFile(ctx *context.Context, opts *cran_model.SearchOptions) {
|
|||
return
|
||||
}
|
||||
|
||||
s, _, err := packages_service.GetPackageFileStream(ctx, pf)
|
||||
s, u, _, err := packages_service.GetPackageFileStream(ctx, pf)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
apiError(ctx, http.StatusNotFound, err)
|
||||
|
@ -258,10 +258,6 @@ func downloadPackageFile(ctx *context.Context, opts *cran_model.SearchOptions) {
|
|||
}
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ func GetRepositoryFile(ctx *context.Context) {
|
|||
key += "|" + component + "|" + architecture
|
||||
}
|
||||
|
||||
s, pf, err := packages_service.GetFileStreamByPackageVersion(
|
||||
s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
|
||||
ctx,
|
||||
pv,
|
||||
&packages_service.PackageFileInfo{
|
||||
|
@ -75,12 +75,8 @@ func GetRepositoryFile(ctx *context.Context) {
|
|||
}
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
||||
// https://wiki.debian.org/DebianRepository/Format#indices_acquisition_via_hashsums_.28by-hash.29
|
||||
|
@ -110,7 +106,7 @@ func GetRepositoryFileByHash(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
s, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
|
||||
s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
apiError(ctx, http.StatusNotFound, err)
|
||||
|
@ -119,12 +115,8 @@ func GetRepositoryFileByHash(ctx *context.Context) {
|
|||
}
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
||||
func UploadPackageFile(ctx *context.Context) {
|
||||
|
@ -217,7 +209,7 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
name := ctx.Params("name")
|
||||
version := ctx.Params("version")
|
||||
|
||||
s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
||||
s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
||||
ctx,
|
||||
&packages_service.PackageInfo{
|
||||
Owner: ctx.Package.Owner,
|
||||
|
@ -238,9 +230,8 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
}
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
helper.ServePackageFile(ctx, s, u, pf, &context.ServeHeaderOptions{
|
||||
ContentType: "application/vnd.debian.binary-package",
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
|
|
|
@ -30,7 +30,7 @@ func apiError(ctx *context.Context, status int, obj interface{}) {
|
|||
|
||||
// DownloadPackageFile serves the specific generic package.
|
||||
func DownloadPackageFile(ctx *context.Context) {
|
||||
s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
||||
s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
||||
ctx,
|
||||
&packages_service.PackageInfo{
|
||||
Owner: ctx.Package.Owner,
|
||||
|
@ -50,12 +50,8 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
||||
// UploadPackage uploads the specific generic package.
|
||||
|
|
|
@ -105,7 +105,7 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
s, _, err := packages_service.GetPackageFileStream(ctx, pfs[0])
|
||||
s, u, _, err := packages_service.GetPackageFileStream(ctx, pfs[0])
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
apiError(ctx, http.StatusNotFound, err)
|
||||
|
@ -114,12 +114,8 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
}
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pfs[0].Name,
|
||||
LastModified: pfs[0].CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pfs[0])
|
||||
}
|
||||
|
||||
func resolvePackage(ctx *context.Context, ownerID int64, name, version string) (*packages_model.PackageVersion, error) {
|
||||
|
|
|
@ -121,7 +121,7 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
s, pf, err := packages_service.GetFileStreamByPackageVersion(
|
||||
s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
|
||||
ctx,
|
||||
pvs[0],
|
||||
&packages_service.PackageFileInfo{
|
||||
|
@ -136,12 +136,8 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
||||
// UploadPackage creates a new package
|
||||
|
|
|
@ -5,8 +5,11 @@ package helper
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
@ -35,3 +38,26 @@ func LogAndProcessError(ctx *context.Context, status int, obj interface{}, cb fu
|
|||
cb(message)
|
||||
}
|
||||
}
|
||||
|
||||
// Serves the content of the package file
|
||||
// If the url is set it will redirect the request, otherwise the content is copied to the response.
|
||||
func ServePackageFile(ctx *context.Context, s io.ReadSeekCloser, u *url.URL, pf *packages_model.PackageFile, forceOpts ...*context.ServeHeaderOptions) {
|
||||
if u != nil {
|
||||
ctx.Redirect(u.String())
|
||||
return
|
||||
}
|
||||
|
||||
defer s.Close()
|
||||
|
||||
var opts *context.ServeHeaderOptions
|
||||
if len(forceOpts) > 0 {
|
||||
opts = forceOpts[0]
|
||||
} else {
|
||||
opts = &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
}
|
||||
}
|
||||
|
||||
ctx.ServeContent(s, opts)
|
||||
}
|
||||
|
|
|
@ -210,21 +210,15 @@ func servePackageFile(ctx *context.Context, params parameters, serveContent bool
|
|||
return
|
||||
}
|
||||
|
||||
s, err := packages_module.NewContentStore().Get(packages_module.BlobHash256Key(pb.HashSHA256))
|
||||
s, u, _, err := packages_service.GetPackageBlobStream(ctx, pf, pb)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
if pf.IsLead {
|
||||
if err := packages_model.IncrementDownloadCounter(ctx, pv.ID); err != nil {
|
||||
log.Error("Error incrementing download counter: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
opts.Filename = pf.Name
|
||||
|
||||
ctx.ServeContent(s, opts)
|
||||
helper.ServePackageFile(ctx, s, u, pf, opts)
|
||||
}
|
||||
|
||||
// UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
|
||||
|
|
|
@ -83,7 +83,7 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
packageVersion := ctx.Params("version")
|
||||
filename := ctx.Params("filename")
|
||||
|
||||
s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
||||
s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
||||
ctx,
|
||||
&packages_service.PackageInfo{
|
||||
Owner: ctx.Package.Owner,
|
||||
|
@ -103,12 +103,8 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
||||
// DownloadPackageFileByName finds the version and serves the contents of a package
|
||||
|
@ -134,7 +130,7 @@ func DownloadPackageFileByName(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
s, pf, err := packages_service.GetFileStreamByPackageVersion(
|
||||
s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
|
||||
ctx,
|
||||
pvs[0],
|
||||
&packages_service.PackageFileInfo{
|
||||
|
@ -149,12 +145,8 @@ func DownloadPackageFileByName(ctx *context.Context) {
|
|||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
||||
// UploadPackage creates a new package
|
||||
|
|
|
@ -362,7 +362,7 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
packageVersion := ctx.Params("version")
|
||||
filename := ctx.Params("filename")
|
||||
|
||||
s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
||||
s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
||||
ctx,
|
||||
&packages_service.PackageInfo{
|
||||
Owner: ctx.Package.Owner,
|
||||
|
@ -382,12 +382,8 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
||||
// UploadPackage creates a new package with the metadata contained in the uploaded nupgk file
|
||||
|
@ -600,7 +596,7 @@ func DownloadSymbolFile(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
s, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
|
||||
s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
|
||||
if err != nil {
|
||||
if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
|
||||
apiError(ctx, http.StatusNotFound, err)
|
||||
|
@ -609,12 +605,8 @@ func DownloadSymbolFile(ctx *context.Context) {
|
|||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
||||
// DeletePackage hard deletes the package
|
||||
|
|
|
@ -273,15 +273,11 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
|
||||
pf := pd.Files[0].File
|
||||
|
||||
s, _, err := packages_service.GetPackageFileStream(ctx, pf)
|
||||
s, u, _, err := packages_service.GetPackageFileStream(ctx, pf)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
packageVersion := ctx.Params("version")
|
||||
filename := ctx.Params("filename")
|
||||
|
||||
s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
||||
s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
||||
ctx,
|
||||
&packages_service.PackageInfo{
|
||||
Owner: ctx.Package.Owner,
|
||||
|
@ -100,12 +100,8 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
||||
// UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
|
||||
|
|
|
@ -65,7 +65,7 @@ func GetRepositoryFile(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
s, pf, err := packages_service.GetFileStreamByPackageVersion(
|
||||
s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
|
||||
ctx,
|
||||
pv,
|
||||
&packages_service.PackageFileInfo{
|
||||
|
@ -80,12 +80,8 @@ func GetRepositoryFile(ctx *context.Context) {
|
|||
}
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
||||
func UploadPackageFile(ctx *context.Context) {
|
||||
|
@ -173,7 +169,7 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
name := ctx.Params("name")
|
||||
version := ctx.Params("version")
|
||||
|
||||
s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
||||
s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
||||
ctx,
|
||||
&packages_service.PackageInfo{
|
||||
Owner: ctx.Package.Owner,
|
||||
|
@ -193,13 +189,8 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
}
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
ContentType: "application/x-rpm",
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
||||
func DeletePackageFile(webctx *context.Context) {
|
||||
|
|
|
@ -175,7 +175,7 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
s, pf, err := packages_service.GetFileStreamByPackageVersion(
|
||||
s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
|
||||
ctx,
|
||||
pvs[0],
|
||||
&packages_service.PackageFileInfo{
|
||||
|
@ -190,12 +190,8 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
||||
// UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
|
||||
|
|
|
@ -397,18 +397,17 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
|
||||
pf := pd.Files[0].File
|
||||
|
||||
s, _, err := packages_service.GetPackageFileStream(ctx, pf)
|
||||
s, u, _, err := packages_service.GetPackageFileStream(ctx, pf)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
setResponseHeaders(ctx.Resp, &headers{
|
||||
Digest: pd.Files[0].Blob.HashSHA256,
|
||||
})
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
helper.ServePackageFile(ctx, s, u, pf, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
ContentType: "application/zip",
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
|
|
|
@ -216,7 +216,7 @@ func UploadPackageFile(ctx *context.Context) {
|
|||
}
|
||||
|
||||
func DownloadPackageFile(ctx *context.Context) {
|
||||
s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
||||
s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
||||
ctx,
|
||||
&packages_service.PackageInfo{
|
||||
Owner: ctx.Package.Owner,
|
||||
|
@ -236,10 +236,6 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
packages_helper "code.gitea.io/gitea/routers/api/packages/helper"
|
||||
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
|
@ -443,18 +444,11 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
s, _, err := packages_service.GetPackageFileStream(
|
||||
ctx,
|
||||
pf,
|
||||
)
|
||||
s, u, _, err := packages_service.GetPackageFileStream(ctx, pf)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetPackageFileStream", err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
packages_helper.ServePackageFile(ctx, s, u, pf)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
|
@ -20,6 +21,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/notification"
|
||||
packages_module "code.gitea.io/gitea/modules/packages"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
|
@ -562,70 +564,62 @@ func DeletePackageFile(ctx context.Context, pf *packages_model.PackageFile) erro
|
|||
}
|
||||
|
||||
// GetFileStreamByPackageNameAndVersion returns the content of the specific package file
|
||||
func GetFileStreamByPackageNameAndVersion(ctx context.Context, pvi *PackageInfo, pfi *PackageFileInfo) (io.ReadSeekCloser, *packages_model.PackageFile, error) {
|
||||
func GetFileStreamByPackageNameAndVersion(ctx context.Context, pvi *PackageInfo, pfi *PackageFileInfo) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
|
||||
log.Trace("Getting package file stream: %v, %v, %s, %s, %s, %s", pvi.Owner.ID, pvi.PackageType, pvi.Name, pvi.Version, pfi.Filename, pfi.CompositeKey)
|
||||
|
||||
pv, err := packages_model.GetVersionByNameAndVersion(ctx, pvi.Owner.ID, pvi.PackageType, pvi.Name, pvi.Version)
|
||||
if err != nil {
|
||||
if err == packages_model.ErrPackageNotExist {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
log.Error("Error getting package: %v", err)
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return GetFileStreamByPackageVersion(ctx, pv, pfi)
|
||||
}
|
||||
|
||||
// GetFileStreamByPackageVersionAndFileID returns the content of the specific package file
|
||||
func GetFileStreamByPackageVersionAndFileID(ctx context.Context, owner *user_model.User, versionID, fileID int64) (io.ReadSeekCloser, *packages_model.PackageFile, error) {
|
||||
log.Trace("Getting package file stream: %v, %v, %v", owner.ID, versionID, fileID)
|
||||
|
||||
pv, err := packages_model.GetVersionByID(ctx, versionID)
|
||||
if err != nil {
|
||||
if err != packages_model.ErrPackageNotExist {
|
||||
log.Error("Error getting package version: %v", err)
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
p, err := packages_model.GetPackageByID(ctx, pv.PackageID)
|
||||
if err != nil {
|
||||
log.Error("Error getting package: %v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if p.OwnerID != owner.ID {
|
||||
return nil, nil, packages_model.ErrPackageNotExist
|
||||
}
|
||||
|
||||
pf, err := packages_model.GetFileForVersionByID(ctx, versionID, fileID)
|
||||
if err != nil {
|
||||
log.Error("Error getting file: %v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return GetPackageFileStream(ctx, pf)
|
||||
}
|
||||
|
||||
// GetFileStreamByPackageVersion returns the content of the specific package file
|
||||
func GetFileStreamByPackageVersion(ctx context.Context, pv *packages_model.PackageVersion, pfi *PackageFileInfo) (io.ReadSeekCloser, *packages_model.PackageFile, error) {
|
||||
func GetFileStreamByPackageVersion(ctx context.Context, pv *packages_model.PackageVersion, pfi *PackageFileInfo) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
|
||||
pf, err := packages_model.GetFileForVersionByName(ctx, pv.ID, pfi.Filename, pfi.CompositeKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return GetPackageFileStream(ctx, pf)
|
||||
}
|
||||
|
||||
// GetPackageFileStream returns the content of the specific package file
|
||||
func GetPackageFileStream(ctx context.Context, pf *packages_model.PackageFile) (io.ReadSeekCloser, *packages_model.PackageFile, error) {
|
||||
func GetPackageFileStream(ctx context.Context, pf *packages_model.PackageFile) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
|
||||
pb, err := packages_model.GetBlobByID(ctx, pf.BlobID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return GetPackageBlobStream(ctx, pf, pb)
|
||||
}
|
||||
|
||||
// GetPackageBlobStream returns the content of the specific package blob
|
||||
// If the storage supports direct serving and it's enabled, only the direct serving url is returned.
|
||||
func GetPackageBlobStream(ctx context.Context, pf *packages_model.PackageFile, pb *packages_model.PackageBlob) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
|
||||
key := packages_module.BlobHash256Key(pb.HashSHA256)
|
||||
|
||||
cs := packages_module.NewContentStore()
|
||||
|
||||
var s io.ReadSeekCloser
|
||||
var u *url.URL
|
||||
var err error
|
||||
|
||||
if cs.ShouldServeDirect() {
|
||||
u, err = cs.GetServeDirectURL(key, pf.Name)
|
||||
if err != nil && !errors.Is(err, storage.ErrURLNotSupported) {
|
||||
log.Error("Error getting serve direct url: %v", err)
|
||||
}
|
||||
}
|
||||
if u == nil {
|
||||
s, err = cs.Get(key)
|
||||
}
|
||||
|
||||
s, err := packages_module.NewContentStore().Get(packages_module.BlobHash256Key(pb.HashSHA256))
|
||||
if err == nil {
|
||||
if pf.IsLead {
|
||||
if err := packages_model.IncrementDownloadCounter(ctx, pf.VersionID); err != nil {
|
||||
|
@ -633,7 +627,7 @@ func GetPackageFileStream(ctx context.Context, pf *packages_model.PackageFile) (
|
|||
}
|
||||
}
|
||||
}
|
||||
return s, pf, err
|
||||
return s, u, pf, err
|
||||
}
|
||||
|
||||
// RemoveAllPackages for User
|
||||
|
|
|
@ -6,6 +6,7 @@ package integration
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
|
@ -139,6 +140,42 @@ func TestPackageGeneric(t *testing.T) {
|
|||
req = NewRequest(t, "GET", url+"/dummy.bin")
|
||||
MakeRequest(t, req, http.StatusUnauthorized)
|
||||
})
|
||||
|
||||
t.Run("ServeDirect", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
if setting.Packages.Storage.Type != setting.MinioStorageType {
|
||||
t.Skip("Test skipped for non-Minio-storage.")
|
||||
return
|
||||
}
|
||||
|
||||
if !setting.Packages.Storage.MinioConfig.ServeDirect {
|
||||
old := setting.Packages.Storage.MinioConfig.ServeDirect
|
||||
defer func() {
|
||||
setting.Packages.Storage.MinioConfig.ServeDirect = old
|
||||
}()
|
||||
|
||||
setting.Packages.Storage.MinioConfig.ServeDirect = true
|
||||
}
|
||||
|
||||
req := NewRequest(t, "GET", url+"/"+filename)
|
||||
resp := MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
checkDownloadCount(3)
|
||||
|
||||
location := resp.Header().Get("Location")
|
||||
assert.NotEmpty(t, location)
|
||||
|
||||
resp2, err := (&http.Client{}).Get(location)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp2.StatusCode)
|
||||
|
||||
body, err := io.ReadAll(resp2.Body)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, content, body)
|
||||
|
||||
checkDownloadCount(3)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
|
|
Loading…
Reference in a new issue