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) }