Skip to content

Commit 160de9f

Browse files
authoredJan 23, 2022
Fix mime-type detection for HTTP server (#18371)
1 parent d644289 commit 160de9f

File tree

4 files changed

+61
-20
lines changed

4 files changed

+61
-20
lines changed
 

‎modules/public/mime_types.go

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2022 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package public
6+
7+
import "strings"
8+
9+
// wellKnownMimeTypesLower comes from Golang's builtin mime package: `builtinTypesLower`, see the comment of detectWellKnownMimeType
10+
var wellKnownMimeTypesLower = map[string]string{
11+
".avif": "image/avif",
12+
".css": "text/css; charset=utf-8",
13+
".gif": "image/gif",
14+
".htm": "text/html; charset=utf-8",
15+
".html": "text/html; charset=utf-8",
16+
".jpeg": "image/jpeg",
17+
".jpg": "image/jpeg",
18+
".js": "text/javascript; charset=utf-8",
19+
".json": "application/json",
20+
".mjs": "text/javascript; charset=utf-8",
21+
".pdf": "application/pdf",
22+
".png": "image/png",
23+
".svg": "image/svg+xml",
24+
".wasm": "application/wasm",
25+
".webp": "image/webp",
26+
".xml": "text/xml; charset=utf-8",
27+
28+
// well, there are some types missing from the builtin list
29+
".txt": "text/plain; charset=utf-8",
30+
}
31+
32+
// detectWellKnownMimeType will return the mime-type for a well-known file ext name
33+
// The purpose of this function is to bypass the unstable behavior of Golang's mime.TypeByExtension
34+
// mime.TypeByExtension would use OS's mime-type config to overwrite the well-known types (see its document).
35+
// If the user's OS has incorrect mime-type config, it would make Gitea can not respond a correct Content-Type to browsers.
36+
// For example, if Gitea returns `text/plain` for a `.js` file, the browser couldn't run the JS due to security reasons.
37+
// detectWellKnownMimeType makes the Content-Type for well-known files stable.
38+
func detectWellKnownMimeType(ext string) string {
39+
ext = strings.ToLower(ext)
40+
return wellKnownMimeTypesLower[ext]
41+
}

‎modules/public/public.go

+11
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,15 @@ func parseAcceptEncoding(val string) map[string]bool {
9595
return types
9696
}
9797

98+
// setWellKnownContentType will set the Content-Type if the file is a well-known type.
99+
// See the comments of detectWellKnownMimeType
100+
func setWellKnownContentType(w http.ResponseWriter, file string) {
101+
mimeType := detectWellKnownMimeType(filepath.Ext(file))
102+
if mimeType != "" {
103+
w.Header().Set("Content-Type", mimeType)
104+
}
105+
}
106+
98107
func (opts *Options) handle(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) bool {
99108
// use clean to keep the file is a valid path with no . or ..
100109
f, err := fs.Open(path.Clean(file))
@@ -125,6 +134,8 @@ func (opts *Options) handle(w http.ResponseWriter, req *http.Request, fs http.Fi
125134
return true
126135
}
127136

137+
setWellKnownContentType(w, file)
138+
128139
serveContent(w, req, fi, fi.ModTime(), f)
129140
return true
130141
}
File renamed without changes.

‎modules/public/static.go ‎modules/public/serve_static.go

+9-20
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,12 @@ package public
99

1010
import (
1111
"bytes"
12-
"compress/gzip"
1312
"io"
14-
"mime"
1513
"net/http"
1614
"os"
1715
"path/filepath"
1816
"time"
1917

20-
"code.gitea.io/gitea/modules/log"
2118
"code.gitea.io/gitea/modules/timeutil"
2219
)
2320

@@ -66,24 +63,16 @@ func serveContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modt
6663
encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding"))
6764
if encodings["gzip"] {
6865
if cf, ok := fi.(*vfsgen۰CompressedFileInfo); ok {
69-
rd := bytes.NewReader(cf.GzipBytes())
70-
w.Header().Set("Content-Encoding", "gzip")
71-
ctype := mime.TypeByExtension(filepath.Ext(fi.Name()))
72-
if ctype == "" {
73-
// read a chunk to decide between utf-8 text and binary
74-
var buf [512]byte
75-
grd, _ := gzip.NewReader(rd)
76-
n, _ := io.ReadFull(grd, buf[:])
77-
ctype = http.DetectContentType(buf[:n])
78-
_, err := rd.Seek(0, io.SeekStart) // rewind to output whole file
79-
if err != nil {
80-
log.Error("rd.Seek error: %v", err)
81-
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
82-
return
83-
}
66+
rdGzip := bytes.NewReader(cf.GzipBytes())
67+
// all static files are managed by Gitea, so we can make sure every file has the correct ext name
68+
// then we can get the correct Content-Type, we do not need to do http.DetectContentType on the decompressed data
69+
mimeType := detectWellKnownMimeType(filepath.Ext(fi.Name()))
70+
if mimeType == "" {
71+
mimeType = "application/octet-stream"
8472
}
85-
w.Header().Set("Content-Type", ctype)
86-
http.ServeContent(w, req, fi.Name(), modtime, rd)
73+
w.Header().Set("Content-Type", mimeType)
74+
w.Header().Set("Content-Encoding", "gzip")
75+
http.ServeContent(w, req, fi.Name(), modtime, rdGzip)
8776
return
8877
}
8978
}

0 commit comments

Comments
 (0)
Please sign in to comment.