mirror of
https://codeberg.org/forgejo/forgejo
synced 2024-11-26 11:46:09 +01:00
75 lines
2.1 KiB
Go
75 lines
2.1 KiB
Go
|
// 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)
|
||
|
}
|