// Package uploadpolicy contains the stateless filename and content policy // shared by upload handling and local file serving. package uploadpolicy import ( "errors" "path/filepath" "strings" ) var ( allowedExtensions = map[string]struct{}{ ".jpg": {}, ".jpeg": {}, ".png": {}, ".gif": {}, ".webp": {}, ".bmp": {}, ".ico": {}, ".avif": {}, ".mp3": {}, ".wav": {}, ".ogg": {}, ".m4a": {}, ".flac": {}, ".aac": {}, ".mp4": {}, ".webm": {}, ".mov": {}, ".avi": {}, ".mkv": {}, ".txt": {}, ".md": {}, ".csv": {}, ".json": {}, ".log": {}, ".pdf": {}, ".doc": {}, ".docx": {}, ".xls": {}, ".xlsx": {}, ".ppt": {}, ".pptx": {}, ".zip": {}, ".rar": {}, ".7z": {}, ".tar": {}, ".gz": {}, ".tgz": {}, ".bin": {}, } inlineExtensions = map[string]struct{}{ ".jpg": {}, ".jpeg": {}, ".png": {}, ".gif": {}, ".webp": {}, ".bmp": {}, ".ico": {}, ".avif": {}, ".mp3": {}, ".wav": {}, ".ogg": {}, ".m4a": {}, ".flac": {}, ".aac": {}, ".mp4": {}, ".webm": {}, ".mov": {}, ".avi": {}, ".mkv": {}, } ) var errFileExtensionNotAllowed = errors.New("file extension is not allowed") // ValidateFileName rejects paths, surrounding whitespace, and extensions // outside the upload allowlist. func ValidateFileName(filename string) error { if filename == "" || strings.TrimSpace(filename) != filename || strings.ContainsAny(filename, `/\`) { return errFileExtensionNotAllowed } if _, ok := allowedExtensions[strings.ToLower(filepath.Ext(filename))]; !ok { return errFileExtensionNotAllowed } return nil } // CanServeInline reports whether a stored object may be rendered by a // browser instead of being forced to download. func CanServeInline(filename string) bool { if ValidateFileName(filename) != nil { return false } _, ok := inlineExtensions[strings.ToLower(filepath.Ext(filename))] return ok } // MIMEByExtension returns the small extension fallback table used when a // client did not provide a content type. Unknown extensions are left for // content sniffing by the caller. func MIMEByExtension(ext string) string { switch strings.ToLower(ext) { case ".json": return "application/json" case ".xml": return "application/xml" case ".pdf": return "application/pdf" case ".zip": return "application/zip" case ".mp4": return "video/mp4" case ".mp3": return "audio/mpeg" case ".csv": return "text/csv" default: return "" } }