Files
Algis DumbrisandClaude Opus 4.6 0e28c0b45e feat: fix attachment handling — display in DMs, enrich in MCP, allow all file types
- Show attachment previews on DM messages (was missing, only channels had it)
- Add file upload button to DM compose bar with paperclip icon
- Enrich messages with attachment data in all MCP bridge functions
  (read_inbox, claim_messages, search, channel_messages, list_by_state)
- Remove file type restrictions — allow any file type, keep 50MB size limit
- Rebuild web dist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 13:00:28 +02:00

127 lines
3.8 KiB
Go

package attachments
import (
"net/http"
"path/filepath"
"strings"
)
// extensionMIMETypes maps file extensions to MIME types for common types
// that net/http.DetectContentType may not identify from magic bytes alone.
var extensionMIMETypes = map[string]string{
".css": "text/css",
".csv": "text/csv",
".doc": "application/msword",
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".gif": "image/gif",
".html": "text/html",
".jpeg": "image/jpeg",
".jpg": "image/jpeg",
".js": "application/javascript",
".json": "application/json",
".md": "text/markdown",
".mp3": "audio/mpeg",
".mp4": "video/mp4",
".pdf": "application/pdf",
".png": "image/png",
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
".svg": "image/svg+xml",
".tar": "application/x-tar",
".txt": "text/plain",
".wav": "audio/wav",
".webp": "image/webp",
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".xml": "application/xml",
".yaml": "text/yaml",
".yml": "text/yaml",
".zip": "application/zip",
}
// imageTypes is the set of MIME types considered "image" for inline preview.
var imageTypes = map[string]bool{
"image/jpeg": true,
"image/png": true,
"image/gif": true,
"image/webp": true,
"image/svg+xml": true,
}
// DetectMIMEType detects the MIME type from file content (magic bytes) with
// fallback to extension-based detection. Returns "application/octet-stream"
// if detection fails entirely.
//
// When magic-byte detection returns a generic type like "text/plain" or
// "application/octet-stream", the extension is checked for a more specific
// type (e.g., .csv -> text/csv, .json -> application/json).
func DetectMIMEType(content []byte, filename string) string {
var detected string
if len(content) > 0 {
detected = http.DetectContentType(content)
}
// If detection returned a specific, non-generic type, use it.
if detected != "" && !isGenericMIME(detected) {
return detected
}
// Extension-based lookup for more specificity.
if filename != "" {
ext := strings.ToLower(filepath.Ext(filename))
if mime, ok := extensionMIMETypes[ext]; ok {
return mime
}
}
// Return whatever detection found, or fall back to octet-stream.
if detected != "" {
return detected
}
return "application/octet-stream"
}
// isGenericMIME returns true if the MIME type is a generic catch-all that
// should be overridden by extension-based detection when available.
func isGenericMIME(mimeType string) bool {
// Normalize: strip parameters like "; charset=utf-8".
base := mimeType
if idx := strings.Index(mimeType, ";"); idx >= 0 {
base = strings.TrimSpace(mimeType[:idx])
}
return base == "application/octet-stream" || base == "text/plain"
}
// DefaultFilename returns a default filename based on MIME type when the
// caller provides none.
func DefaultFilename(mimeType string) string {
switch {
case strings.HasPrefix(mimeType, "image/png"):
return "untitled.png"
case strings.HasPrefix(mimeType, "image/jpeg"):
return "untitled.jpg"
case strings.HasPrefix(mimeType, "image/gif"):
return "untitled.gif"
case strings.HasPrefix(mimeType, "image/webp"):
return "untitled.webp"
case strings.HasPrefix(mimeType, "image/svg"):
return "untitled.svg"
case mimeType == "application/pdf":
return "untitled.pdf"
case strings.HasPrefix(mimeType, "text/"):
return "untitled.txt"
default:
return "untitled.bin"
}
}
// IsImageType returns true if the MIME type is a supported image type for
// inline preview in the Web UI.
func IsImageType(mimeType string) bool {
return imageTypes[mimeType]
}
// IsAllowedType returns true for all MIME types. Any file type is allowed;
// only size is restricted (50 MB max).
func IsAllowedType(mimeType string) bool {
return true
}