package useCases import ( "RenewCMS/internal/domain/image" "bytes" "crypto/sha256" "encoding/hex" "encoding/xml" stdimage "image" "io" _ "golang.org/x/image/webp" _ "image/jpeg" _ "image/png" ) const svgRootElement = "svg" var rasterFormats = map[string]string{ "image/png": "png", "image/jpeg": "jpeg", "image/webp": "webp", } type CreateImageUseCase struct { imageRepository image.Repository } func NewCreateImageUseCase(imageRepository image.Repository) *CreateImageUseCase { return &CreateImageUseCase{imageRepository} } func (g *CreateImageUseCase) CreateImage(input image.ImageInput) (image.Image, error) { if !image.IsAllowedContentType(input.ContentType) { return image.Image{}, image.ErrImageTypeNotAllowed } data, err := readWithinLimit(input.Content) if err != nil { return image.Image{}, err } width, height, err := measure(data, input.ContentType) if err != nil { return image.Image{}, err } checksum := sha256.Sum256(data) return g.imageRepository.Create(image.ImageInput{ Content: bytes.NewReader(data), Filename: image.NormalizeUploadFilename(input.Filename, input.ContentType), Size: int64(len(data)), ContentType: input.ContentType, Width: width, Height: height, SHA256: hex.EncodeToString(checksum[:]), UploadedByID: input.UploadedByID, }) } func readWithinLimit(content io.Reader) ([]byte, error) { if content == nil { return nil, image.ErrImageUnreadable } data, err := io.ReadAll(io.LimitReader(content, image.MaxImageSize+1)) if err != nil { return nil, err } if int64(len(data)) > image.MaxImageSize { return nil, image.ErrImageTooLarge } return data, nil } func measure(data []byte, contentType string) (int, int, error) { if contentType == "image/svg+xml" { if !hasSvgRoot(data) { return 0, 0, image.ErrImageUnreadable } return 0, 0, nil } config, format, err := stdimage.DecodeConfig(bytes.NewReader(data)) if err != nil || format != rasterFormats[contentType] { return 0, 0, image.ErrImageUnreadable } return config.Width, config.Height, nil } func hasSvgRoot(data []byte) bool { decoder := xml.NewDecoder(bytes.NewReader(data)) for { token, err := decoder.Token() if err != nil { return false } if start, ok := token.(xml.StartElement); ok { return start.Name.Local == svgRootElement } } }