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