Files
RenewCMS/internal/infrastructure/useCases/CreateImage.go
T

113 lines
2.6 KiB
Go

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
}
}
}