package filestore import ( "mime" "path/filepath" "strings" ) // SupportedMimeTypes defines the allowed MIME types for uploads var SupportedMimeTypes = map[string]bool{ "image/jpeg": true, "image/png": true, "image/gif": true, "image/webp": true, "image/svg+xml": true, "application/pdf": true, "application/msword": true, "application/vnd.openxmlformats-officedocument.wordprocessingml.document": true, "application/vnd.ms-excel": true, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": true, "text/plain": true, "text/csv": true, "application/json": true, "application/zip": true, "application/x-rar-compressed": true, "application/x-7z-compressed": true, } // ProfileImageMimeTypes defines allowed MIME types for profile images var ProfileImageMimeTypes = map[string]bool{ "image/jpeg": true, "image/png": true, "image/gif": true, "image/webp": true, } // FileValidator validates file uploads type FileValidator struct { MaxSize int64 AllowedMimeTypes map[string]bool AllowedExtensions map[string]bool } // NewFileValidator creates a new file validator func NewFileValidator(maxSize int64, mimeTypes map[string]bool) *FileValidator { return &FileValidator{ MaxSize: maxSize, AllowedMimeTypes: mimeTypes, AllowedExtensions: make(map[string]bool), } } // NewProfileImageValidator creates a validator for profile images func NewProfileImageValidator() *FileValidator { return &FileValidator{ MaxSize: 5 * 1024 * 1024, // 5 MB AllowedMimeTypes: ProfileImageMimeTypes, AllowedExtensions: map[string]bool{".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".webp": true}, } } // ValidateFileSize validates that the file size is within limits func (v *FileValidator) ValidateFileSize(size int64) error { if size <= 0 { return NewError(ErrCodeInvalidInput, "file size must be greater than 0") } if size > v.MaxSize { return NewError(ErrCodeFileTooLarge, "file size exceeds maximum limit") } return nil } // ValidateMimeType validates that the MIME type is supported func (v *FileValidator) ValidateMimeType(contentType string) error { if contentType == "" { return NewError(ErrCodeInvalidInput, "content type is required") } if !v.AllowedMimeTypes[contentType] { return NewError(ErrCodeUnsupported, "unsupported file type: "+contentType) } return nil } // ValidateFilename validates that the filename is valid and has an allowed extension func (v *FileValidator) ValidateFilename(filename string) error { if filename == "" { return NewError(ErrCodeInvalidInput, "filename is required") } // Check for path traversal if strings.Contains(filename, "..") || strings.Contains(filename, "/") || strings.Contains(filename, "\\") { return NewError(ErrCodeInvalidInput, "invalid filename: path traversal detected") } // Check extension if configured if len(v.AllowedExtensions) > 0 { ext := strings.ToLower(filepath.Ext(filename)) if !v.AllowedExtensions[ext] { return NewError(ErrCodeUnsupported, "unsupported file extension: "+ext) } } return nil } // ValidateFile validates all file properties func (v *FileValidator) ValidateFile(filename string, contentType string, size int64) error { if err := v.ValidateFilename(filename); err != nil { return err } if err := v.ValidateFileSize(size); err != nil { return err } if err := v.ValidateMimeType(contentType); err != nil { return err } return nil } // DetectMimeType detects MIME type from filename func DetectMimeType(filename string) string { ext := filepath.Ext(filename) if detected := mime.TypeByExtension(ext); detected != "" { return detected } return "application/octet-stream" } // SanitizeFilename removes potentially dangerous characters from filename func SanitizeFilename(filename string) string { // Remove path separators filename = strings.ReplaceAll(filename, "/", "_") filename = strings.ReplaceAll(filename, "\\", "_") // Remove null bytes filename = strings.ReplaceAll(filename, "\x00", "") // Remove other potentially dangerous characters dangerous := []string{"..", "~", "`", "!", "@", "#", "$", "%", "^", "&", "*", "?", ":", ";", "<", ">", "|"} for _, char := range dangerous { filename = strings.ReplaceAll(filename, char, "_") } // Trim whitespace filename = strings.TrimSpace(filename) // Ensure filename is not empty if filename == "" { filename = "file" } return filename }