Make repository response support HTTP range request (#24592)
Replace #20480 Replace #18448 Close #16414
This commit is contained in:
parent
c090f87a8d
commit
023a048f52
12 changed files with 434 additions and 212 deletions
|
@ -150,6 +150,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
// FIXME: code from #19689, what if the file is large ... OOM ...
|
||||
buf, err := io.ReadAll(dataRc)
|
||||
if err != nil {
|
||||
_ = dataRc.Close()
|
||||
|
@ -164,7 +165,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
|
|||
// Check if the blob represents a pointer
|
||||
pointer, _ := lfs.ReadPointer(bytes.NewReader(buf))
|
||||
|
||||
// if its not a pointer just serve the data directly
|
||||
// if it's not a pointer, just serve the data directly
|
||||
if !pointer.IsValid() {
|
||||
// First handle caching for the blob
|
||||
if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
|
||||
|
@ -172,25 +173,21 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
|
|||
}
|
||||
|
||||
// OK not cached - serve!
|
||||
if err := common.ServeData(ctx.Context, ctx.Repo.TreePath, blob.Size(), bytes.NewReader(buf)); err != nil {
|
||||
ctx.ServerError("ServeBlob", err)
|
||||
}
|
||||
common.ServeContentByReader(ctx.Context, ctx.Repo.TreePath, blob.Size(), bytes.NewReader(buf))
|
||||
return
|
||||
}
|
||||
|
||||
// Now check if there is a meta object for this pointer
|
||||
// Now check if there is a MetaObject for this pointer
|
||||
meta, err := git_model.GetLFSMetaObjectByOid(ctx, ctx.Repo.Repository.ID, pointer.Oid)
|
||||
|
||||
// If there isn't one just serve the data directly
|
||||
// If there isn't one, just serve the data directly
|
||||
if err == git_model.ErrLFSObjectNotExist {
|
||||
// Handle caching for the blob SHA (not the LFS object OID)
|
||||
if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := common.ServeData(ctx.Context, ctx.Repo.TreePath, blob.Size(), bytes.NewReader(buf)); err != nil {
|
||||
ctx.ServerError("ServeBlob", err)
|
||||
}
|
||||
common.ServeContentByReader(ctx.Context, ctx.Repo.TreePath, blob.Size(), bytes.NewReader(buf))
|
||||
return
|
||||
} else if err != nil {
|
||||
ctx.ServerError("GetLFSMetaObjectByOid", err)
|
||||
|
@ -218,9 +215,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
|
|||
}
|
||||
defer lfsDataRc.Close()
|
||||
|
||||
if err := common.ServeData(ctx.Context, ctx.Repo.TreePath, meta.Size, lfsDataRc); err != nil {
|
||||
ctx.ServerError("ServeData", err)
|
||||
}
|
||||
common.ServeContentByReadSeeker(ctx.Context, ctx.Repo.TreePath, lastModified, lfsDataRc)
|
||||
}
|
||||
|
||||
func getBlobForEntry(ctx *context.APIContext) (blob *git.Blob, entry *git.TreeEntry, lastModified time.Time) {
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"io"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
charsetModule "code.gitea.io/gitea/modules/charset"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/httpcache"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/typesniffer"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// ServeBlob download a git.Blob
|
||||
func ServeBlob(ctx *context.Context, blob *git.Blob, lastModified time.Time) error {
|
||||
if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
|
||||
return nil
|
||||
}
|
||||
|
||||
dataRc, err := blob.DataAsync()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err = dataRc.Close(); err != nil {
|
||||
log.Error("ServeBlob: Close: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return ServeData(ctx, ctx.Repo.TreePath, blob.Size(), dataRc)
|
||||
}
|
||||
|
||||
// ServeData download file from io.Reader
|
||||
func ServeData(ctx *context.Context, filePath string, size int64, reader io.Reader) error {
|
||||
buf := make([]byte, 1024)
|
||||
n, err := util.ReadAtMost(reader, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n >= 0 {
|
||||
buf = buf[:n]
|
||||
}
|
||||
|
||||
opts := &context.ServeHeaderOptions{
|
||||
Filename: path.Base(filePath),
|
||||
}
|
||||
|
||||
if size >= 0 {
|
||||
opts.ContentLength = &size
|
||||
} else {
|
||||
log.Error("ServeData called to serve data: %s with size < 0: %d", filePath, size)
|
||||
}
|
||||
|
||||
sniffedType := typesniffer.DetectContentType(buf)
|
||||
isPlain := sniffedType.IsText() || ctx.FormBool("render")
|
||||
|
||||
if setting.MimeTypeMap.Enabled {
|
||||
fileExtension := strings.ToLower(filepath.Ext(filePath))
|
||||
opts.ContentType = setting.MimeTypeMap.Map[fileExtension]
|
||||
}
|
||||
|
||||
if opts.ContentType == "" {
|
||||
if sniffedType.IsBrowsableBinaryType() {
|
||||
opts.ContentType = sniffedType.GetMimeType()
|
||||
} else if isPlain {
|
||||
opts.ContentType = "text/plain"
|
||||
} else {
|
||||
opts.ContentType = typesniffer.ApplicationOctetStream
|
||||
}
|
||||
}
|
||||
|
||||
if isPlain {
|
||||
var charset string
|
||||
charset, err = charsetModule.DetectEncoding(buf)
|
||||
if err != nil {
|
||||
log.Error("Detect raw file %s charset failed: %v, using by default utf-8", filePath, err)
|
||||
charset = "utf-8"
|
||||
}
|
||||
opts.ContentTypeCharset = strings.ToLower(charset)
|
||||
}
|
||||
|
||||
isSVG := sniffedType.IsSvgImage()
|
||||
|
||||
// serve types that can present a security risk with CSP
|
||||
if isSVG {
|
||||
ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
|
||||
} else if sniffedType.IsPDF() {
|
||||
// no sandbox attribute for pdf as it breaks rendering in at least safari. this
|
||||
// should generally be safe as scripts inside PDF can not escape the PDF document
|
||||
// see https://bugs.chromium.org/p/chromium/issues/detail?id=413851 for more discussion
|
||||
ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'")
|
||||
}
|
||||
|
||||
opts.Disposition = "inline"
|
||||
if isSVG && !setting.UI.SVG.Enabled {
|
||||
opts.Disposition = "attachment"
|
||||
}
|
||||
|
||||
ctx.SetServeHeaders(opts)
|
||||
|
||||
_, err = ctx.Resp.Write(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(ctx.Resp, reader)
|
||||
return err
|
||||
}
|
43
routers/common/serve.go
Normal file
43
routers/common/serve.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/httpcache"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
// ServeBlob download a git.Blob
|
||||
func ServeBlob(ctx *context.Context, blob *git.Blob, lastModified time.Time) error {
|
||||
if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
|
||||
return nil
|
||||
}
|
||||
|
||||
dataRc, err := blob.DataAsync()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err = dataRc.Close(); err != nil {
|
||||
log.Error("ServeBlob: Close: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
httplib.ServeContentByReader(ctx.Req, ctx.Resp, ctx.Repo.TreePath, blob.Size(), dataRc)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ServeContentByReader(ctx *context.Context, filePath string, size int64, reader io.Reader) {
|
||||
httplib.ServeContentByReader(ctx.Req, ctx.Resp, filePath, size, reader)
|
||||
}
|
||||
|
||||
func ServeContentByReadSeeker(ctx *context.Context, filePath string, modTime time.Time, reader io.ReadSeeker) {
|
||||
httplib.ServeContentByReadSeeker(ctx.Req, ctx.Resp, filePath, modTime, reader)
|
||||
}
|
|
@ -153,10 +153,7 @@ func ServeAttachment(ctx *context.Context, uuid string) {
|
|||
}
|
||||
defer fr.Close()
|
||||
|
||||
if err = common.ServeData(ctx, attach.Name, attach.Size, fr); err != nil {
|
||||
ctx.ServerError("ServeData", err)
|
||||
return
|
||||
}
|
||||
common.ServeContentByReadSeeker(ctx, attach.Name, attach.CreatedUnix.AsTime(), fr)
|
||||
}
|
||||
|
||||
// GetAttachment serve attachments
|
||||
|
|
|
@ -71,7 +71,8 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob, lastModified time.Time
|
|||
log.Error("ServeBlobOrLFS: Close: %v", err)
|
||||
}
|
||||
}()
|
||||
return common.ServeData(ctx, ctx.Repo.TreePath, meta.Size, lfsDataRc)
|
||||
common.ServeContentByReadSeeker(ctx, ctx.Repo.TreePath, lastModified, lfsDataRc)
|
||||
return nil
|
||||
}
|
||||
if err = dataRc.Close(); err != nil {
|
||||
log.Error("ServeBlobOrLFS: Close: %v", err)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue