@@ -34,6 +34,7 @@ import (
34
34
"code.gitea.io/gitea/modules/setting"
35
35
"code.gitea.io/gitea/modules/templates"
36
36
"code.gitea.io/gitea/modules/translation"
37
+ "code.gitea.io/gitea/modules/typesniffer"
37
38
"code.gitea.io/gitea/modules/util"
38
39
"code.gitea.io/gitea/modules/web/middleware"
39
40
"code.gitea.io/gitea/services/auth"
@@ -322,9 +323,9 @@ func (ctx *Context) plainTextInternal(skip, status int, bs []byte) {
322
323
if statusPrefix == 4 || statusPrefix == 5 {
323
324
log .Log (skip , log .TRACE , "plainTextInternal (status=%d): %s" , status , string (bs ))
324
325
}
325
- ctx .Resp .WriteHeader (status )
326
326
ctx .Resp .Header ().Set ("Content-Type" , "text/plain;charset=utf-8" )
327
327
ctx .Resp .Header ().Set ("X-Content-Type-Options" , "nosniff" )
328
+ ctx .Resp .WriteHeader (status )
328
329
if _ , err := ctx .Resp .Write (bs ); err != nil {
329
330
log .ErrorWithSkip (skip , "plainTextInternal (status=%d): write bytes failed: %v" , status , err )
330
331
}
@@ -345,16 +346,45 @@ func (ctx *Context) RespHeader() http.Header {
345
346
return ctx .Resp .Header ()
346
347
}
347
348
349
+ type ServeHeaderOptions struct {
350
+ ContentType string // defaults to "application/octet-stream"
351
+ ContentTypeCharset string
352
+ Disposition string // defaults to "attachment"
353
+ Filename string
354
+ CacheDuration time.Duration // defaults to 5 minutes
355
+ }
356
+
348
357
// SetServeHeaders sets necessary content serve headers
349
- func (ctx * Context ) SetServeHeaders (filename string ) {
350
- ctx .Resp .Header ().Set ("Content-Description" , "File Transfer" )
351
- ctx .Resp .Header ().Set ("Content-Type" , "application/octet-stream" )
352
- ctx .Resp .Header ().Set ("Content-Disposition" , "attachment; filename=" + filename )
353
- ctx .Resp .Header ().Set ("Content-Transfer-Encoding" , "binary" )
354
- ctx .Resp .Header ().Set ("Expires" , "0" )
355
- ctx .Resp .Header ().Set ("Cache-Control" , "must-revalidate" )
356
- ctx .Resp .Header ().Set ("Pragma" , "public" )
357
- ctx .Resp .Header ().Set ("Access-Control-Expose-Headers" , "Content-Disposition" )
358
+ func (ctx * Context ) SetServeHeaders (opts * ServeHeaderOptions ) {
359
+ header := ctx .Resp .Header ()
360
+
361
+ contentType := typesniffer .ApplicationOctetStream
362
+ if opts .ContentType != "" {
363
+ if opts .ContentTypeCharset != "" {
364
+ contentType = opts .ContentType + "; charset=" + strings .ToLower (opts .ContentTypeCharset )
365
+ } else {
366
+ contentType = opts .ContentType
367
+ }
368
+ }
369
+ header .Set ("Content-Type" , contentType )
370
+ header .Set ("X-Content-Type-Options" , "nosniff" )
371
+
372
+ if opts .Filename != "" {
373
+ disposition := opts .Disposition
374
+ if disposition == "" {
375
+ disposition = "attachment"
376
+ }
377
+
378
+ backslashEscapedName := strings .ReplaceAll (strings .ReplaceAll (opts .Filename , `\` , `\\` ), `"` , `\"` ) // \ -> \\, " -> \"
379
+ header .Set ("Content-Disposition" , fmt .Sprintf (`%s; filename="%s"; filename*=UTF-8''%s` , disposition , backslashEscapedName , url .PathEscape (opts .Filename )))
380
+ header .Set ("Access-Control-Expose-Headers" , "Content-Disposition" )
381
+ }
382
+
383
+ duration := opts .CacheDuration
384
+ if duration == 0 {
385
+ duration = 5 * time .Minute
386
+ }
387
+ httpcache .AddCacheControlToHeader (header , duration )
358
388
}
359
389
360
390
// ServeContent serves content to http request
@@ -366,7 +396,9 @@ func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interfa
366
396
modTime = v
367
397
}
368
398
}
369
- ctx .SetServeHeaders (name )
399
+ ctx .SetServeHeaders (& ServeHeaderOptions {
400
+ Filename : name ,
401
+ })
370
402
http .ServeContent (ctx .Resp , ctx .Req , name , modTime , r )
371
403
}
372
404
@@ -378,13 +410,17 @@ func (ctx *Context) ServeFile(file string, names ...string) {
378
410
} else {
379
411
name = path .Base (file )
380
412
}
381
- ctx .SetServeHeaders (name )
413
+ ctx .SetServeHeaders (& ServeHeaderOptions {
414
+ Filename : name ,
415
+ })
382
416
http .ServeFile (ctx .Resp , ctx .Req , file )
383
417
}
384
418
385
419
// ServeStream serves file via io stream
386
420
func (ctx * Context ) ServeStream (rd io.Reader , name string ) {
387
- ctx .SetServeHeaders (name )
421
+ ctx .SetServeHeaders (& ServeHeaderOptions {
422
+ Filename : name ,
423
+ })
388
424
_ , err := io .Copy (ctx .Resp , rd )
389
425
if err != nil {
390
426
ctx .ServerError ("Download file failed" , err )
0 commit comments