Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 7f68f12

Browse files
committedMar 7, 2025
handler: Allow more filetypes as Content-Type
1 parent 75a0dcb commit 7f68f12

File tree

2 files changed

+77
-19
lines changed

2 files changed

+77
-19
lines changed
 

‎pkg/handler/unrouted_handler.go

+18-16
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const (
3434
var (
3535
reForwardedHost = regexp.MustCompile(`host="?([^;"]+)`)
3636
reForwardedProto = regexp.MustCompile(`proto=(https?)`)
37-
reMimeType = regexp.MustCompile(`^[a-z]+\/[a-z0-9\-\+\.]+$`)
37+
reMimeType = regexp.MustCompile(`^(?:application|audio|example|font|haptics|image|message|model|multipart|text|video|x-(?:[0-9A-Za-z!#$%&'*+.^_` + "`" + `|~-]+))\/([0-9A-Za-z!#$%&'*+.^_` + "`" + `|~-]+)((?:[ \t]*;[ \t]*[0-9A-Za-z!#$%&'*+.^_` + "`" + `|~-]+=(?:[0-9A-Za-z!#$%&'*+.^_` + "`" + `|~-]+|"(?:[^"\\]|\.)*"))*)$`)
3838
// We only allow certain URL-safe characters in upload IDs. URL-safe in this means
3939
// that their are allowed in a URI's path component according to RFC 3986.
4040
// See https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
@@ -1112,9 +1112,9 @@ func (handler *UnroutedHandler) GetFile(w http.ResponseWriter, r *http.Request)
11121112
// mimeInlineBrowserWhitelist is a map containing MIME types which should be
11131113
// allowed to be rendered by browser inline, instead of being forced to be
11141114
// downloaded. For example, HTML or SVG files are not allowed, since they may
1115-
// contain malicious JavaScript. In a similiar fashion PDF is not on this list
1115+
// contain malicious JavaScript. In a similar fashion, PDF is not on this list
11161116
// as their parsers commonly contain vulnerabilities which can be exploited.
1117-
// The values of this map does not convey any meaning and are therefore just
1117+
// The values of this map do not convey any meaning and are therefore just
11181118
// empty structs.
11191119
var mimeInlineBrowserWhitelist = map[string]struct{}{
11201120
"text/plain": {},
@@ -1125,38 +1125,40 @@ var mimeInlineBrowserWhitelist = map[string]struct{}{
11251125
"image/bmp": {},
11261126
"image/webp": {},
11271127

1128-
"audio/wave": {},
1129-
"audio/wav": {},
1130-
"audio/x-wav": {},
1131-
"audio/x-pn-wav": {},
1132-
"audio/webm": {},
1133-
"video/webm": {},
1134-
"audio/ogg": {},
1135-
"video/ogg": {},
1128+
"audio/wave": {},
1129+
"audio/wav": {},
1130+
"audio/x-wav": {},
1131+
"audio/x-pn-wav": {},
1132+
"audio/webm": {},
1133+
"audio/ogg": {},
1134+
1135+
"video/mp4": {},
1136+
"video/webm": {},
1137+
"video/ogg": {},
1138+
11361139
"application/ogg": {},
11371140
}
11381141

11391142
// filterContentType returns the values for the Content-Type and
11401143
// Content-Disposition headers for a given upload. These values should be used
11411144
// in responses for GET requests to ensure that only non-malicious file types
11421145
// are shown directly in the browser. It will extract the file name and type
1143-
// from the "fileame" and "filetype".
1146+
// from the "filename" and "filetype".
11441147
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
11451148
func filterContentType(info FileInfo) (contentType string, contentDisposition string) {
11461149
filetype := info.MetaData["filetype"]
11471150

11481151
if reMimeType.MatchString(filetype) {
1149-
// If the filetype from metadata is well formed, we forward use this
1150-
// for the Content-Type header. However, only whitelisted mime types
1151-
// will be allowed to be shown inline in the browser
1152+
// If the filetype from metadata is well-formed, we forward use this for the Content-Type header.
1153+
// However, only allowlisted mime types will be allowed to be shown inline in the browser
11521154
contentType = filetype
11531155
if _, isWhitelisted := mimeInlineBrowserWhitelist[filetype]; isWhitelisted {
11541156
contentDisposition = "inline"
11551157
} else {
11561158
contentDisposition = "attachment"
11571159
}
11581160
} else {
1159-
// If the filetype from the metadata is not well formed, we use a
1161+
// If the filetype from the metadata is not well-formed, we use a
11601162
// default type and force the browser to download the content.
11611163
contentType = "application/octet-stream"
11621164
contentDisposition = "attachment"

‎pkg/handler/unrouted_handler_test.go

+59-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
package handler_test
1+
package handler
22

33
import (
44
"testing"
55

66
"github.com/stretchr/testify/assert"
7-
8-
. "github.com/tus/tusd/v2/pkg/handler"
97
)
108

119
func TestParseMetadataHeader(t *testing.T) {
@@ -33,3 +31,61 @@ func TestParseMetadataHeader(t *testing.T) {
3331
"k4": "world",
3432
})
3533
}
34+
35+
func TestFilterContentType(t *testing.T) {
36+
tests := map[string]struct {
37+
input FileInfo
38+
contentType string
39+
contentDisposition string
40+
}{
41+
"without metadata": {
42+
input: FileInfo{MetaData: map[string]string{}},
43+
contentType: "application/octet-stream",
44+
contentDisposition: "attachment",
45+
},
46+
"filetype allowlisted": {
47+
input: FileInfo{MetaData: map[string]string{
48+
"filetype": "image/png",
49+
"filename": "pet.png",
50+
}},
51+
contentType: "image/png",
52+
contentDisposition: "inline;filename=\"pet.png\"",
53+
},
54+
"filetype not allowlisted": {
55+
input: FileInfo{MetaData: map[string]string{
56+
"filetype": "application/zip",
57+
"filename": "pets.zip",
58+
}},
59+
contentType: "application/zip",
60+
contentDisposition: "attachment;filename=\"pets.zip\"",
61+
},
62+
"filetype invalid": {
63+
input: FileInfo{MetaData: map[string]string{
64+
"filetype": "image_png",
65+
"filename": "pet.png",
66+
}},
67+
contentType: "application/octet-stream",
68+
contentDisposition: "attachment;filename=\"pet.png\"",
69+
},
70+
"filetype audio/ogg; codecs=opus": {
71+
input: FileInfo{MetaData: map[string]string{
72+
"filetype": "audio/ogg; codecs=opus",
73+
"filename": "pet.opus",
74+
}},
75+
contentType: "audio/ogg; codecs=opus",
76+
contentDisposition: "attachment;filename=\"pet.opus\"",
77+
},
78+
}
79+
80+
for name, test := range tests {
81+
t.Run(name, func(t *testing.T) {
82+
t.Parallel()
83+
a := assert.New(t)
84+
85+
gotContentType, gotContentDisposition := filterContentType(test.input)
86+
87+
a.Equal(test.contentType, gotContentType)
88+
a.Equal(test.contentDisposition, gotContentDisposition)
89+
})
90+
}
91+
}

0 commit comments

Comments
 (0)
Please sign in to comment.