@@ -7,12 +7,13 @@ package common
7
7
import (
8
8
"fmt"
9
9
"io"
10
+ "net/url"
10
11
"path"
11
12
"path/filepath"
12
13
"strings"
13
14
"time"
14
15
15
- "code.gitea.io/gitea/modules/charset"
16
+ charsetModule "code.gitea.io/gitea/modules/charset"
16
17
"code.gitea.io/gitea/modules/context"
17
18
"code.gitea.io/gitea/modules/git"
18
19
"code.gitea.io/gitea/modules/httpcache"
@@ -42,7 +43,7 @@ func ServeBlob(ctx *context.Context, blob *git.Blob, lastModified time.Time) err
42
43
}
43
44
44
45
// ServeData download file from io.Reader
45
- func ServeData (ctx * context.Context , name string , size int64 , reader io.Reader ) error {
46
+ func ServeData (ctx * context.Context , filePath string , size int64 , reader io.Reader ) error {
46
47
buf := make ([]byte , 1024 )
47
48
n , err := util .ReadAtMost (reader , buf )
48
49
if err != nil {
@@ -52,56 +53,73 @@ func ServeData(ctx *context.Context, name string, size int64, reader io.Reader)
52
53
buf = buf [:n ]
53
54
}
54
55
55
- ctx .Resp .Header (). Set ( "Cache-Control" , "public,max-age=86400" )
56
+ httpcache . AddCacheControlToHeader ( ctx .Resp .Header (), 5 * time . Minute )
56
57
57
58
if size >= 0 {
58
59
ctx .Resp .Header ().Set ("Content-Length" , fmt .Sprintf ("%d" , size ))
59
60
} else {
60
- log .Error ("ServeData called to serve data: %s with size < 0: %d" , name , size )
61
+ log .Error ("ServeData called to serve data: %s with size < 0: %d" , filePath , size )
61
62
}
62
- name = path .Base (name )
63
63
64
- // Google Chrome dislike commas in filenames, so let's change it to a space
65
- name = strings .ReplaceAll (name , "," , " " )
64
+ fileName := path .Base (filePath )
65
+ sniffedType := typesniffer .DetectContentType (buf )
66
+ isPlain := sniffedType .IsText () || ctx .FormBool ("render" )
67
+ mimeType := ""
68
+ charset := ""
66
69
67
- st := typesniffer .DetectContentType (buf )
68
-
69
- mappedMimeType := ""
70
70
if setting .MimeTypeMap .Enabled {
71
- fileExtension := strings .ToLower (filepath .Ext (name ))
72
- mappedMimeType = setting .MimeTypeMap .Map [fileExtension ]
71
+ fileExtension := strings .ToLower (filepath .Ext (fileName ))
72
+ mimeType = setting .MimeTypeMap .Map [fileExtension ]
73
73
}
74
- if st .IsText () || ctx .FormBool ("render" ) {
75
- cs , err := charset .DetectEncoding (buf )
76
- if err != nil {
77
- log .Error ("Detect raw file %s charset failed: %v, using by default utf-8" , name , err )
78
- cs = "utf-8"
74
+
75
+ if mimeType == "" {
76
+ if sniffedType .IsBrowsableBinaryType () {
77
+ mimeType = sniffedType .GetMimeType ()
78
+ } else if isPlain {
79
+ mimeType = "text/plain"
80
+ } else {
81
+ mimeType = typesniffer .ApplicationOctetStream
79
82
}
80
- if mappedMimeType == "" {
81
- mappedMimeType = "text/plain"
83
+ }
84
+
85
+ if isPlain {
86
+ charset , err = charsetModule .DetectEncoding (buf )
87
+ if err != nil {
88
+ log .Error ("Detect raw file %s charset failed: %v, using by default utf-8" , filePath , err )
89
+ charset = "utf-8"
82
90
}
83
- ctx .Resp .Header ().Set ("Content-Type" , mappedMimeType + "; charset=" + strings .ToLower (cs ))
91
+ }
92
+
93
+ if charset != "" {
94
+ ctx .Resp .Header ().Set ("Content-Type" , mimeType + "; charset=" + strings .ToLower (charset ))
84
95
} else {
85
- ctx .Resp .Header ().Set ("Access-Control-Expose-Headers" , "Content-Disposition" )
86
- if mappedMimeType != "" {
87
- ctx .Resp .Header ().Set ("Content-Type" , mappedMimeType )
88
- }
89
- if (st .IsImage () || st .IsPDF ()) && (setting .UI .SVG .Enabled || ! st .IsSvgImage ()) {
90
- ctx .Resp .Header ().Set ("Content-Disposition" , fmt .Sprintf (`inline; filename="%s"` , name ))
91
- if st .IsSvgImage () || st .IsPDF () {
92
- ctx .Resp .Header ().Set ("Content-Security-Policy" , "default-src 'none'; style-src 'unsafe-inline'; sandbox" )
93
- ctx .Resp .Header ().Set ("X-Content-Type-Options" , "nosniff" )
94
- if st .IsSvgImage () {
95
- ctx .Resp .Header ().Set ("Content-Type" , typesniffer .SvgMimeType )
96
- } else {
97
- ctx .Resp .Header ().Set ("Content-Type" , typesniffer .ApplicationOctetStream )
98
- }
99
- }
100
- } else {
101
- ctx .Resp .Header ().Set ("Content-Disposition" , fmt .Sprintf (`attachment; filename="%s"` , name ))
102
- }
96
+ ctx .Resp .Header ().Set ("Content-Type" , mimeType )
97
+ }
98
+ ctx .Resp .Header ().Set ("X-Content-Type-Options" , "nosniff" )
99
+
100
+ isSVG := sniffedType .IsSvgImage ()
101
+
102
+ // serve types that can present a security risk with CSP
103
+ if isSVG {
104
+ ctx .Resp .Header ().Set ("Content-Security-Policy" , "default-src 'none'; style-src 'unsafe-inline'; sandbox" )
105
+ } else if sniffedType .IsPDF () {
106
+ // no sandbox attribute for pdf as it breaks rendering in at least safari. this
107
+ // should generally be safe as scripts inside PDF can not escape the PDF document
108
+ // see https://bugs.chromium.org/p/chromium/issues/detail?id=413851 for more discussion
109
+ ctx .Resp .Header ().Set ("Content-Security-Policy" , "default-src 'none'; style-src 'unsafe-inline'" )
103
110
}
104
111
112
+ disposition := "inline"
113
+ if isSVG && ! setting .UI .SVG .Enabled {
114
+ disposition = "attachment"
115
+ }
116
+
117
+ // encode filename per https://datatracker.ietf.org/doc/html/rfc5987
118
+ encodedFileName := `filename*=UTF-8''` + url .PathEscape (fileName )
119
+
120
+ ctx .Resp .Header ().Set ("Content-Disposition" , disposition + "; " + encodedFileName )
121
+ ctx .Resp .Header ().Set ("Access-Control-Expose-Headers" , "Content-Disposition" )
122
+
105
123
_ , err = ctx .Resp .Write (buf )
106
124
if err != nil {
107
125
return err
0 commit comments