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 storage image.Storage } func NewCreateImageUseCase(imageRepository image.Repository, storage image.Storage) *CreateImageUseCase { return &CreateImageUseCase{imageRepository, storage} } 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) storedName, err := g.storage.Put(input.ContentType, bytes.NewReader(data)) if err != nil { return image.Image{}, err } created, err := g.imageRepository.Create(image.Image{ Path: image.PublicPath(storedName), 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, }) if err != nil { _ = g.storage.Delete(storedName) return image.Image{}, err } return created, nil } 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 } } }