diff options
Diffstat (limited to 'modules/context/context_serve.go')
-rw-r--r-- | modules/context/context_serve.go | 74 |
1 files changed, 74 insertions, 0 deletions
diff --git a/modules/context/context_serve.go b/modules/context/context_serve.go new file mode 100644 index 0000000000..44dd739eff --- /dev/null +++ b/modules/context/context_serve.go @@ -0,0 +1,74 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package context + +import ( + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "code.gitea.io/gitea/modules/httpcache" + "code.gitea.io/gitea/modules/typesniffer" +) + +type ServeHeaderOptions struct { + ContentType string // defaults to "application/octet-stream" + ContentTypeCharset string + ContentLength *int64 + Disposition string // defaults to "attachment" + Filename string + CacheDuration time.Duration // defaults to 5 minutes + LastModified time.Time +} + +// SetServeHeaders sets necessary content serve headers +func (ctx *Context) SetServeHeaders(opts *ServeHeaderOptions) { + header := ctx.Resp.Header() + + contentType := typesniffer.ApplicationOctetStream + if opts.ContentType != "" { + if opts.ContentTypeCharset != "" { + contentType = opts.ContentType + "; charset=" + strings.ToLower(opts.ContentTypeCharset) + } else { + contentType = opts.ContentType + } + } + header.Set("Content-Type", contentType) + header.Set("X-Content-Type-Options", "nosniff") + + if opts.ContentLength != nil { + header.Set("Content-Length", strconv.FormatInt(*opts.ContentLength, 10)) + } + + if opts.Filename != "" { + disposition := opts.Disposition + if disposition == "" { + disposition = "attachment" + } + + backslashEscapedName := strings.ReplaceAll(strings.ReplaceAll(opts.Filename, `\`, `\\`), `"`, `\"`) // \ -> \\, " -> \" + header.Set("Content-Disposition", fmt.Sprintf(`%s; filename="%s"; filename*=UTF-8''%s`, disposition, backslashEscapedName, url.PathEscape(opts.Filename))) + header.Set("Access-Control-Expose-Headers", "Content-Disposition") + } + + duration := opts.CacheDuration + if duration == 0 { + duration = 5 * time.Minute + } + httpcache.SetCacheControlInHeader(header, duration) + + if !opts.LastModified.IsZero() { + header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat)) + } +} + +// ServeContent serves content to http request +func (ctx *Context) ServeContent(r io.ReadSeeker, opts *ServeHeaderOptions) { + ctx.SetServeHeaders(opts) + http.ServeContent(ctx.Resp, ctx.Req, opts.Filename, opts.LastModified, r) +} |