1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
package files
import (
"mime"
"net/http"
"os"
"path/filepath"
"strings"
)
// imageTypes pins the Content-Type of the image formats the preview pane
// renders, independently of the host's mime database (which may lack
// entries such as .webp or .avif, or spell .ico differently).
var imageTypes = map[string]string{
".avif": "image/avif",
".bmp": "image/bmp",
".gif": "image/gif",
".ico": "image/x-icon",
".jpeg": "image/jpeg",
".jpg": "image/jpeg",
".png": "image/png",
".svg": "image/svg+xml",
".webp": "image/webp",
}
// ContentTypeFor returns the Content-Type the raw endpoint serves a file
// under: pinned image types first, then the host mime database, and a
// content sniff of the first bytes as a last resort.
//
// Example:
//
// files.ContentTypeFor("logo.webp", nil) // "image/webp"
func ContentTypeFor(path string, head []byte) string {
ext := strings.ToLower(filepath.Ext(path))
if typ, ok := imageTypes[ext]; ok {
return typ
}
if typ := mime.TypeByExtension(ext); typ != "" {
return typ
}
return http.DetectContentType(head)
}
// rawFile streams a file's bytes as-is, for the browser to render (images)
// or download. Unlike readFile it has no size cap and no text requirement:
// http.ServeContent streams and honours Range requests.
func (s *Service) rawFile(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("path") == "" {
writeError(w, http.StatusBadRequest, "path query parameter is required")
return
}
path := s.resolve(r.URL.Query().Get("path"))
info, err := os.Stat(path)
if err != nil {
writeFSError(w, err)
return
}
if info.IsDir() {
writeError(w, http.StatusBadRequest, "path is a directory")
return
}
file, err := os.Open(path)
if err != nil {
writeFSError(w, err)
return
}
// Read-only handle: a Close error carries no information the client
// could act on, and the response headers are already committed.
defer func() { _ = file.Close() }()
head := make([]byte, 512)
n, _ := file.Read(head)
if _, err := file.Seek(0, 0); err != nil {
writeFSError(w, err)
return
}
w.Header().Set("Content-Type", ContentTypeFor(path, head[:n]))
w.Header().Set("X-Content-Type-Options", "nosniff")
http.ServeContent(w, r, info.Name(), info.ModTime(), file)
}
|