113 lines
2.6 KiB
Go
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
|
|
}
|
|
}
|
|
}
|