@@ -34,6 +34,7 @@ import (
3434 "code.gitea.io/gitea/modules/setting"
3535 "code.gitea.io/gitea/modules/templates"
3636 "code.gitea.io/gitea/modules/translation"
37+ "code.gitea.io/gitea/modules/typesniffer"
3738 "code.gitea.io/gitea/modules/util"
3839 "code.gitea.io/gitea/modules/web/middleware"
3940 "code.gitea.io/gitea/services/auth"
@@ -322,9 +323,9 @@ func (ctx *Context) plainTextInternal(skip, status int, bs []byte) {
322323 if statusPrefix == 4 || statusPrefix == 5 {
323324 log .Log (skip , log .TRACE , "plainTextInternal (status=%d): %s" , status , string (bs ))
324325 }
325- ctx .Resp .WriteHeader (status )
326326 ctx .Resp .Header ().Set ("Content-Type" , "text/plain;charset=utf-8" )
327327 ctx .Resp .Header ().Set ("X-Content-Type-Options" , "nosniff" )
328+ ctx .Resp .WriteHeader (status )
328329 if _ , err := ctx .Resp .Write (bs ); err != nil {
329330 log .ErrorWithSkip (skip , "plainTextInternal (status=%d): write bytes failed: %v" , status , err )
330331 }
@@ -345,36 +346,55 @@ func (ctx *Context) RespHeader() http.Header {
345346 return ctx .Resp .Header ()
346347}
347348
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+
348357// 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 )
358388}
359389
360390// ServeContent serves content to http request
361391func (ctx * Context ) ServeContent (name string , r io.ReadSeeker , modTime time.Time ) {
362- ctx .SetServeHeaders (name )
392+ ctx .SetServeHeaders (& ServeHeaderOptions {
393+ Filename : name ,
394+ })
363395 http .ServeContent (ctx .Resp , ctx .Req , name , modTime , r )
364396}
365397
366- // ServeFile serves given file to response.
367- func (ctx * Context ) ServeFile (file string , names ... string ) {
368- var name string
369- if len (names ) > 0 {
370- name = names [0 ]
371- } else {
372- name = path .Base (file )
373- }
374- ctx .SetServeHeaders (name )
375- http .ServeFile (ctx .Resp , ctx .Req , file )
376- }
377-
378398// UploadStream returns the request body or the first form file
379399// Only form files need to get closed.
380400func (ctx * Context ) UploadStream () (rd io.ReadCloser , needToClose bool , err error ) {
0 commit comments