188 lines
4.8 KiB
Go
188 lines
4.8 KiB
Go
package file
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
dom_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/collection"
|
|
)
|
|
|
|
// File size limits (in bytes)
|
|
const (
|
|
// MaxFileSizeGeneral is the maximum file size for general folders (500MB)
|
|
MaxFileSizeGeneral = 500 * 1024 * 1024
|
|
|
|
// MaxFileSizeAlbum is the maximum file size for album uploads (100MB)
|
|
// Albums are for photos/videos, so we use a more restrictive limit
|
|
MaxFileSizeAlbum = 100 * 1024 * 1024
|
|
|
|
// MaxThumbnailSize is the maximum thumbnail size (10MB)
|
|
MaxThumbnailSize = 10 * 1024 * 1024
|
|
)
|
|
|
|
// Allowed content types for albums (photos and videos only)
|
|
var AlbumAllowedContentTypes = []string{
|
|
// Image formats
|
|
"image/jpeg",
|
|
"image/jpg",
|
|
"image/png",
|
|
"image/gif",
|
|
"image/webp",
|
|
"image/heic",
|
|
"image/heif",
|
|
"image/bmp",
|
|
"image/tiff",
|
|
"image/svg+xml",
|
|
|
|
// Video formats
|
|
"video/mp4",
|
|
"video/mpeg",
|
|
"video/quicktime", // .mov files
|
|
"video/x-msvideo", // .avi files
|
|
"video/x-matroska", // .mkv files
|
|
"video/webm",
|
|
"video/3gpp",
|
|
"video/x-flv",
|
|
}
|
|
|
|
// FileValidator provides file upload validation based on collection type
|
|
type FileValidator struct{}
|
|
|
|
// NewFileValidator creates a new file validator
|
|
func NewFileValidator() *FileValidator {
|
|
return &FileValidator{}
|
|
}
|
|
|
|
// ValidateFileUpload validates a file upload based on collection type and file properties
|
|
// CWE-434: Unrestricted Upload of File with Dangerous Type
|
|
// OWASP A01:2021: Broken Access Control - File upload restrictions
|
|
func (v *FileValidator) ValidateFileUpload(
|
|
collectionType string,
|
|
fileSize int64,
|
|
thumbnailSize int64,
|
|
contentType string,
|
|
) error {
|
|
// Validate file size based on collection type
|
|
if err := v.validateFileSize(collectionType, fileSize); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Validate thumbnail size if provided
|
|
if thumbnailSize > 0 {
|
|
if err := v.validateThumbnailSize(thumbnailSize); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Validate content type for albums (photos/videos only)
|
|
if collectionType == dom_collection.CollectionTypeAlbum {
|
|
if err := v.validateContentType(contentType); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// For folders (non-albums), no content-type restrictions
|
|
// Users can upload any file type to regular folders
|
|
|
|
return nil
|
|
}
|
|
|
|
// validateFileSize checks if the file size is within limits
|
|
func (v *FileValidator) validateFileSize(collectionType string, fileSize int64) error {
|
|
if fileSize <= 0 {
|
|
return fmt.Errorf("file size must be greater than 0")
|
|
}
|
|
|
|
var maxSize int64
|
|
var collectionTypeName string
|
|
|
|
if collectionType == dom_collection.CollectionTypeAlbum {
|
|
maxSize = MaxFileSizeAlbum
|
|
collectionTypeName = "album"
|
|
} else {
|
|
maxSize = MaxFileSizeGeneral
|
|
collectionTypeName = "folder"
|
|
}
|
|
|
|
if fileSize > maxSize {
|
|
return fmt.Errorf(
|
|
"file size (%s) exceeds maximum allowed size for %s (%s)",
|
|
formatBytes(fileSize),
|
|
collectionTypeName,
|
|
formatBytes(maxSize),
|
|
)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// validateThumbnailSize checks if the thumbnail size is within limits
|
|
func (v *FileValidator) validateThumbnailSize(thumbnailSize int64) error {
|
|
if thumbnailSize <= 0 {
|
|
return nil // Thumbnail is optional
|
|
}
|
|
|
|
if thumbnailSize > MaxThumbnailSize {
|
|
return fmt.Errorf(
|
|
"thumbnail size (%s) exceeds maximum allowed size (%s)",
|
|
formatBytes(thumbnailSize),
|
|
formatBytes(MaxThumbnailSize),
|
|
)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// validateContentType checks if the content type is allowed for albums
|
|
func (v *FileValidator) validateContentType(contentType string) error {
|
|
if contentType == "" {
|
|
return fmt.Errorf("content type is required for album uploads")
|
|
}
|
|
|
|
// Normalize content type (lowercase and trim)
|
|
normalizedType := strings.ToLower(strings.TrimSpace(contentType))
|
|
|
|
// Check if content type is in allowed list
|
|
for _, allowedType := range AlbumAllowedContentTypes {
|
|
if normalizedType == allowedType {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf(
|
|
"content type '%s' is not allowed in albums. Only photos and videos are permitted",
|
|
contentType,
|
|
)
|
|
}
|
|
|
|
// GetAllowedContentTypes returns the list of allowed content types for albums
|
|
func (v *FileValidator) GetAllowedContentTypes() []string {
|
|
return AlbumAllowedContentTypes
|
|
}
|
|
|
|
// GetMaxFileSize returns the maximum file size for a collection type
|
|
func (v *FileValidator) GetMaxFileSize(collectionType string) int64 {
|
|
if collectionType == dom_collection.CollectionTypeAlbum {
|
|
return MaxFileSizeAlbum
|
|
}
|
|
return MaxFileSizeGeneral
|
|
}
|
|
|
|
// GetMaxThumbnailSize returns the maximum thumbnail size
|
|
func (v *FileValidator) GetMaxThumbnailSize() int64 {
|
|
return MaxThumbnailSize
|
|
}
|
|
|
|
// formatBytes formats byte size into human-readable format
|
|
func formatBytes(bytes int64) string {
|
|
const unit = 1024
|
|
if bytes < unit {
|
|
return fmt.Sprintf("%d B", bytes)
|
|
}
|
|
div, exp := int64(unit), 0
|
|
for n := bytes / unit; n >= unit; n /= unit {
|
|
div *= unit
|
|
exp++
|
|
}
|
|
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
|
|
}
|