155 lines
4.4 KiB
Go
155 lines
4.4 KiB
Go
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
|
|
}
|