debian-mirror-gitlab/workhorse/internal/headers/content_headers.go

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

159 lines
5.2 KiB
Go
Raw Normal View History

2021-02-22 17:27:13 +05:30
package headers
import (
2023-05-08 21:46:49 +05:30
"mime"
2021-02-22 17:27:13 +05:30
"net/http"
"regexp"
2021-10-27 15:23:28 +05:30
"gitlab.com/gitlab-org/gitlab/workhorse/internal/utils/svg"
2021-02-22 17:27:13 +05:30
)
var (
2022-06-21 17:19:12 +05:30
javaScriptTypeRegex = regexp.MustCompile(`^(text|application)\/javascript$`)
2021-02-22 17:27:13 +05:30
2022-06-21 17:19:12 +05:30
imageTypeRegex = regexp.MustCompile(`^image/*`)
svgMimeTypeRegex = regexp.MustCompile(`^image/svg\+xml$`)
2021-02-22 17:27:13 +05:30
2023-05-08 21:46:49 +05:30
textTypeRegex = regexp.MustCompile(`^text/*`)
xmlTypeRegex = regexp.MustCompile(`^text/xml`)
xhtmlTypeRegex = regexp.MustCompile(`^text/html`)
2022-06-21 17:19:12 +05:30
videoTypeRegex = regexp.MustCompile(`^video/*`)
2021-02-22 17:27:13 +05:30
2022-06-21 17:19:12 +05:30
pdfTypeRegex = regexp.MustCompile(`application\/pdf`)
attachmentRegex = regexp.MustCompile(`^attachment`)
inlineRegex = regexp.MustCompile(`^inline`)
2021-02-22 17:27:13 +05:30
)
// Mime types that can't be inlined. Usually subtypes of main types
2022-06-21 17:19:12 +05:30
var forbiddenInlineTypes = []*regexp.Regexp{svgMimeTypeRegex}
2021-02-22 17:27:13 +05:30
2023-05-08 21:46:49 +05:30
var htmlRenderingTypes = []*regexp.Regexp{xmlTypeRegex, xhtmlTypeRegex}
2021-02-22 17:27:13 +05:30
// Mime types that can be inlined. We can add global types like "image/" or
// specific types like "text/plain". If there is a specific type inside a global
// allowed type that can't be inlined we must add it to the forbiddenInlineTypes var.
// One example of this is the mime type "image". We allow all images to be
// inlined except for SVGs.
2022-06-21 17:19:12 +05:30
var allowedInlineTypes = []*regexp.Regexp{imageTypeRegex, textTypeRegex, videoTypeRegex, pdfTypeRegex}
const (
svgContentType = "image/svg+xml"
textPlainContentType = "text/plain; charset=utf-8"
attachmentDispositionText = "attachment"
inlineDispositionText = "inline"
2023-05-08 21:46:49 +05:30
dummyFilename = "blob"
2022-06-21 17:19:12 +05:30
)
2021-02-22 17:27:13 +05:30
func SafeContentHeaders(data []byte, contentDisposition string) (string, string) {
2023-05-08 21:46:49 +05:30
detectedContentType := detectContentType(data)
contentType := safeContentType(detectedContentType)
2021-02-22 17:27:13 +05:30
contentDisposition = safeContentDisposition(contentType, contentDisposition)
2022-10-11 01:57:18 +05:30
2023-05-08 21:46:49 +05:30
// Some browsers will render XML inline unless a filename directive is provided with a non-xml file extension
// This overrides the filename directive in the case of XML data
for _, element := range htmlRenderingTypes {
if isType(detectedContentType, element) {
disposition, directives, err := mime.ParseMediaType(contentDisposition)
if err == nil {
directives["filename"] = dummyFilename
contentDisposition = mime.FormatMediaType(disposition, directives)
break
}
}
}
2022-10-11 01:57:18 +05:30
// Set attachments to application/octet-stream since browsers can do
// a better job distinguishing certain types (for example: ZIP files
// vs. Microsoft .docx files). However, browsers may safely render SVGs even
// when Content-Disposition is an attachment but only if the SVG
// Content-Type is set. Note that scripts in an SVG file will only be executed
// if the file is downloaded separately with an inline Content-Disposition.
if attachmentRegex.MatchString(contentDisposition) && !isType(contentType, svgMimeTypeRegex) {
contentType = "application/octet-stream"
}
2021-02-22 17:27:13 +05:30
return contentType, contentDisposition
}
2023-05-08 21:46:49 +05:30
func detectContentType(data []byte) string {
2021-02-22 17:27:13 +05:30
// Special case for svg because DetectContentType detects it as text
if svg.Is(data) {
2022-06-21 17:19:12 +05:30
return svgContentType
2021-02-22 17:27:13 +05:30
}
// Override any existing Content-Type header from other ResponseWriters
2023-05-08 21:46:49 +05:30
return http.DetectContentType(data)
}
2021-02-22 17:27:13 +05:30
2023-05-08 21:46:49 +05:30
func safeContentType(contentType string) string {
2022-06-21 17:19:12 +05:30
// http.DetectContentType does not support JavaScript and would only
// return text/plain. But for cautionary measures, just in case they start supporting
// it down the road and start returning application/javascript, we want to handle it now
// to avoid regressions.
if isType(contentType, javaScriptTypeRegex) {
return textPlainContentType
}
2021-02-22 17:27:13 +05:30
// If the content is text type, we set to plain, because we don't
// want to render it inline if they're html or javascript
2022-06-21 17:19:12 +05:30
if isType(contentType, textTypeRegex) {
return textPlainContentType
2021-02-22 17:27:13 +05:30
}
return contentType
}
func safeContentDisposition(contentType string, contentDisposition string) string {
// If the existing disposition is attachment we return that. This allow us
// to force a download from GitLab (ie: RawController)
2022-06-21 17:19:12 +05:30
if attachmentRegex.MatchString(contentDisposition) {
2021-02-22 17:27:13 +05:30
return contentDisposition
}
// Checks for mime types that are forbidden to be inline
for _, element := range forbiddenInlineTypes {
if isType(contentType, element) {
return attachmentDisposition(contentDisposition)
}
}
// Checks for mime types allowed to be inline
for _, element := range allowedInlineTypes {
if isType(contentType, element) {
return inlineDisposition(contentDisposition)
}
}
// Anything else is set to attachment
return attachmentDisposition(contentDisposition)
}
func attachmentDisposition(contentDisposition string) string {
if contentDisposition == "" {
2022-06-21 17:19:12 +05:30
return attachmentDispositionText
2021-02-22 17:27:13 +05:30
}
2022-06-21 17:19:12 +05:30
if inlineRegex.MatchString(contentDisposition) {
return inlineRegex.ReplaceAllString(contentDisposition, attachmentDispositionText)
2021-02-22 17:27:13 +05:30
}
return contentDisposition
}
func inlineDisposition(contentDisposition string) string {
if contentDisposition == "" {
2022-06-21 17:19:12 +05:30
return inlineDispositionText
2021-02-22 17:27:13 +05:30
}
2022-06-21 17:19:12 +05:30
if attachmentRegex.MatchString(contentDisposition) {
return attachmentRegex.ReplaceAllString(contentDisposition, inlineDispositionText)
2021-02-22 17:27:13 +05:30
}
return contentDisposition
}
func isType(contentType string, mimeType *regexp.Regexp) bool {
return mimeType.MatchString(contentType)
}