Files
tm_back/pkg/filestore/validation.go
T
2026-04-21 19:07:51 +03:30

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
}