summaryrefslogtreecommitdiffstats
path: root/modules/context/context_serve.go
diff options
context:
space:
mode:
Diffstat (limited to 'modules/context/context_serve.go')
-rw-r--r--modules/context/context_serve.go74
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)
+}