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
This commit is contained in:
59
classes/image_guard.go
Normal file
59
classes/image_guard.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user