@ -7,12 +7,13 @@ package common
import (
import (
"fmt"
"fmt"
"io"
"io"
"net/url"
"path"
"path"
"path/filepath"
"path/filepath"
"strings"
"strings"
"time"
"time"
"code.gitea.io/gitea/modules/charset"
charsetModule "code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/httpcache"
@ -42,7 +43,7 @@ func ServeBlob(ctx *context.Context, blob *git.Blob, lastModified time.Time) err
}
}
// ServeData download file from io.Reader
// ServeData download file from io.Reader
func ServeData ( ctx * context . Context , name string , size int64 , reader io . Reader ) error {
func ServeData ( ctx * context . Context , filePath string , size int64 , reader io . Reader ) error {
buf := make ( [ ] byte , 1024 )
buf := make ( [ ] byte , 1024 )
n , err := util . ReadAtMost ( reader , buf )
n , err := util . ReadAtMost ( reader , buf )
if err != nil {
if err != nil {
@ -52,56 +53,73 @@ func ServeData(ctx *context.Context, name string, size int64, reader io.Reader)
buf = buf [ : n ]
buf = buf [ : n ]
}
}
ctx . Resp . Header ( ) . Set ( "Cache-Control" , "public,max-age=86400" )
httpcache . AddCacheControlToHeader ( ctx . Resp . Header ( ) , 5 * time . Minute )
if size >= 0 {
if size >= 0 {
ctx . Resp . Header ( ) . Set ( "Content-Length" , fmt . Sprintf ( "%d" , size ) )
ctx . Resp . Header ( ) . Set ( "Content-Length" , fmt . Sprintf ( "%d" , size ) )
} else {
} else {
log . Error ( "ServeData called to serve data: %s with size < 0: %d" , name , size )
log . Error ( "ServeData called to serve data: %s with size < 0: %d" , filePath , size )
}
}
name = path . Base ( name )
// Google Chrome dislike commas in filenames, so let's change it to a space
fileName := path . Base ( filePath )
name = strings . ReplaceAll ( name , "," , " " )
sniffedType := typesniffer . DetectContentType ( buf )
isPlain := sniffedType . IsText ( ) || ctx . FormBool ( "render" )
mimeType := ""
charset := ""
st := typesniffer . DetectContentType ( buf )
mappedMimeType := ""
if setting . MimeTypeMap . Enabled {
if setting . MimeTypeMap . Enabled {
fileExtension := strings . ToLower ( filepath . Ext ( name ) )
fileExtension := strings . ToLower ( filepath . Ext ( fileName ) )
mappedMimeType = setting . MimeTypeMap . Map [ fileExtension ]
mimeType = setting . MimeTypeMap . Map [ fileExtension ]
}
if mimeType == "" {
if sniffedType . IsBrowsableBinaryType ( ) {
mimeType = sniffedType . GetMimeType ( )
} else if isPlain {
mimeType = "text/plain"
} else {
mimeType = typesniffer . ApplicationOctetStream
}
}
}
if st . IsText ( ) || ctx . FormBool ( "render" ) {
cs , err := charset . DetectEncoding ( buf )
if isPlain {
charset , err = charsetModule . DetectEncoding ( buf )
if err != nil {
if err != nil {
log . Error ( "Detect raw file %s charset failed: %v, using by default utf-8" , name , err )
log . Error ( "Detect raw file %s charset failed: %v, using by default utf-8" , filePath , err )
cs = "utf-8"
char set = "utf-8"
}
}
if mappedMimeType == "" {
mappedMimeType = "text/plain"
}
}
ctx . Resp . Header ( ) . Set ( "Content-Type" , mappedMimeType + "; charset=" + strings . ToLower ( cs ) )
if charset != "" {
ctx . Resp . Header ( ) . Set ( "Content-Type" , mimeType + "; charset=" + strings . ToLower ( charset ) )
} else {
} else {
ctx . Resp . Header ( ) . Set ( "Access-Control-Expose-Headers" , "Content-Disposition" )
ctx . Resp . Header ( ) . Set ( "Content-Type" , mimeType )
if mappedMimeType != "" {
ctx . Resp . Header ( ) . Set ( "Content-Type" , mappedMimeType )
}
}
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 ( ) || st . IsPDF ( ) {
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 ( "X-Content-Type-Options" , "nosniff" )
if st . IsSvgImage ( ) {
ctx . Resp . Header ( ) . Set ( "Content-Type" , typesniffer . SvgMimeType )
isSVG := sniffedType . IsSvgImage ( )
} else {
ctx . Resp . Header ( ) . Set ( "Content-Type" , typesniffer . ApplicationOctetStream )
// 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 {
} else if sniffedType . IsPDF ( ) {
ctx . Resp . Header ( ) . Set ( "Content-Disposition" , fmt . Sprintf ( ` attachment; filename="%s" ` , name ) )
// 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'" )
}
}
disposition := "inline"
if isSVG && ! setting . UI . SVG . Enabled {
disposition = "attachment"
}
}
// encode filename per https://datatracker.ietf.org/doc/html/rfc5987
encodedFileName := ` filename*=UTF-8'' ` + url . PathEscape ( fileName )
ctx . Resp . Header ( ) . Set ( "Content-Disposition" , disposition + "; " + encodedFileName )
ctx . Resp . Header ( ) . Set ( "Access-Control-Expose-Headers" , "Content-Disposition" )
_ , err = ctx . Resp . Write ( buf )
_ , err = ctx . Resp . Write ( buf )
if err != nil {
if err != nil {
return err
return err