forgejo/routers/common/repo.go
Lunny Xiao 1bfb0a24d8
Refactor routers directory (#15800)
* refactor routers directory

* move func used for web and api to common

* make corsHandler a function to prohibit side efects

* rm unused func

Co-authored-by: 6543 <6543@obermui.de>
2021-06-09 01:33:54 +02:00

128 lines
3.4 KiB
Go

// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package common
import (
"fmt"
"io"
"net/http"
"path"
"path/filepath"
"strings"
"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/services/archiver"
)
// ServeBlob download a git.Blob
func ServeBlob(ctx *context.Context, blob *git.Blob) error {
if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`) {
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)
}
// Download an archive of a repository
func Download(ctx *context.Context) {
uri := ctx.Params("*")
aReq := archiver.DeriveRequestFrom(ctx, uri)
if aReq == nil {
ctx.Error(http.StatusNotFound)
return
}
downloadName := ctx.Repo.Repository.Name + "-" + aReq.GetArchiveName()
complete := aReq.IsComplete()
if !complete {
aReq = archiver.ArchiveRepository(aReq)
complete = aReq.WaitForCompletion(ctx)
}
if complete {
ctx.ServeFile(aReq.GetArchivePath(), downloadName)
} else {
ctx.Error(http.StatusNotFound)
}
}
// ServeData download file from io.Reader
func ServeData(ctx *context.Context, name string, size int64, reader io.Reader) error {
buf := make([]byte, 1024)
n, err := reader.Read(buf)
if err != nil && err != io.EOF {
return err
}
if n >= 0 {
buf = buf[:n]
}
ctx.Resp.Header().Set("Cache-Control", "public,max-age=86400")
if size >= 0 {
ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", size))
} else {
log.Error("ServeData called to serve data: %s with size < 0: %d", name, size)
}
name = path.Base(name)
// Google Chrome dislike commas in filenames, so let's change it to a space
name = strings.ReplaceAll(name, ",", " ")
st := typesniffer.DetectContentType(buf)
if st.IsText() || ctx.QueryBool("render") {
cs, err := charset.DetectEncoding(buf)
if err != nil {
log.Error("Detect raw file %s charset failed: %v, using by default utf-8", name, err)
cs = "utf-8"
}
ctx.Resp.Header().Set("Content-Type", "text/plain; charset="+strings.ToLower(cs))
} else {
ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
if (st.IsImage() || st.IsPDF()) && (setting.UI.SVG.Enabled || !st.IsSvgImage()) {
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, name))
if st.IsSvgImage() {
ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
ctx.Resp.Header().Set("Content-Type", typesniffer.SvgMimeType)
}
} else {
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, name))
if setting.MimeTypeMap.Enabled {
fileExtension := strings.ToLower(filepath.Ext(name))
if mimetype, ok := setting.MimeTypeMap.Map[fileExtension]; ok {
ctx.Resp.Header().Set("Content-Type", mimetype)
}
}
}
}
_, err = ctx.Resp.Write(buf)
if err != nil {
return err
}
_, err = io.Copy(ctx.Resp, reader)
return err
}