package attach import ( "bytes" "image" "net/http" "slices" ) // AllowedImageMIMEs are the content types IsAllowedImage accepts, as // sniffed from the bytes: JPEG, PNG, GIF and WebP, the formats the // thumbnailer decodes. var AllowedImageMIMEs = []string{"image/jpeg", "image/png", "image/gif", "image/webp"} // MaxImagePixels is the largest image (width times height) IsAllowedImage // accepts, the same ceiling File.Thumb applies before decoding, so every // accepted image can be thumbnailed. const MaxImagePixels = maxThumbSourcePixels // IsAllowedImage reports whether data is a JPEG, PNG, GIF or WebP image: // the bytes must sniff as one of AllowedImageMIMEs (http.DetectContentType, // independent of any file name or client header), the header must decode // through image.DecodeConfig as that format with a positive width and // height, and the image must not exceed MaxImagePixels. Decoding the header // rejects a polyglot whose first bytes alone look right. It fails closed: // empty or unreadable content is refused. data may be a prefix of the file // as long as it holds the image header. func IsAllowedImage(data []byte) bool { if len(data) == 0 { return false } if !slices.Contains(AllowedImageMIMEs, http.DetectContentType(data)) { return false } cfg, format, err := image.DecodeConfig(bytes.NewReader(data)) if err != nil || cfg.Width <= 0 || cfg.Height <= 0 { return false } if int64(cfg.Width)*int64(cfg.Height) > MaxImagePixels { return false } switch format { case "jpeg", "png", "gif", "webp": return true } return false }