Files
sm-feedback-plugin/classes/image_guard.go
Jakub Zych 92e1b4e09c feat: feedback submissions with screenshots, the hide-widget preference and its user payload key
- POST /_feedback/api/v1/{key}/submit validates as PHP, refuses non-image screenshots (ImageContentGuard copy) and stores the submission, its public screenshot and the G15Office job in one transaction
- PUT /_feedback/api/v1/me/hidden (jwt.auth) stores the caller's preference; feedback_widget_hidden joins every golem15.user payload
- preflights stay with the framework CORS layer
2026-10-04 00:10:15 +02:00

60 lines
1.7 KiB
Go

// Package classes holds the golem15.feedback services: the screenshot
// content guard, the G15Office client and the job that files a submission
// as a G15Office task.
package classes
import (
"bytes"
"image"
"net/http"
// The decoders image.DecodeConfig needs for the allowed formats; the
// webp decoder is registered by lagoon/attach.
_ "image/gif"
_ "image/jpeg"
_ "image/png"
_ "git.golem15.com/golem15/summercms/modules/lagoon/attach"
)
// AllowedImageMIMEs is ImageContentGuard::ALLOWED_IMAGE_MIMES.
var AllowedImageMIMEs = []string{"image/jpeg", "image/png", "image/gif", "image/webp"}
// SniffImageMIME is the finfo magic-byte sniff of ImageContentGuard: the
// content type the bytes themselves declare, independent of any file name
// or client header.
func SniffImageMIME(data []byte) string {
return http.DetectContentType(data)
}
// IsAllowedImage ports ImageContentGuard::isAllowedImage: the bytes must
// sniff as JPEG, PNG, GIF or WebP and must decode as that image
// (getimagesize), which rejects a polyglot whose header alone looks right.
// It fails closed: empty or unreadable content is refused. The WinterCMS
// plugins keep one copy of the guard each, and so do their ports.
func IsAllowedImage(data []byte) bool {
if len(data) == 0 {
return false
}
sniffed := SniffImageMIME(data)
allowed := false
for _, m := range AllowedImageMIMEs {
if sniffed == m {
allowed = true
break
}
}
if !allowed {
return false
}
cfg, format, err := image.DecodeConfig(bytes.NewReader(data))
if err != nil || cfg.Width <= 0 || cfg.Height <= 0 {
return false
}
switch format {
case "jpeg", "png", "gif", "webp":
return true
}
return false
}