package classes_test import ( "bytes" "encoding/hex" "image" "image/color" "image/gif" "image/jpeg" "image/png" "testing" "git.golem15.com/golem15/sm-feedback-plugin/classes" "git.golem15.com/golem15/summercms/modules/lagoon/attach" ) func mustHex(t *testing.T, s string) []byte { t.Helper() b, err := hex.DecodeString(s) if err != nil { t.Fatal(err) } return b } func encoded(t *testing.T, enc func(*bytes.Buffer, image.Image) error) []byte { t.Helper() img := image.NewRGBA(image.Rect(0, 0, 3, 2)) img.Set(1, 1, color.RGBA{R: 200, A: 255}) var buf bytes.Buffer if err := enc(&buf, img); err != nil { t.Fatal(err) } return buf.Bytes() } // TestImageGuardCopy pins the feedback copy of ImageContentGuard: the // shared vectors (the PHP-recorded screenshot fixtures among them) get the // verdicts PHP gave and the framework's own guard (attach.IsAllowedImage) // gives, and the sniffed type of each accepted image is one of the four. func TestImageGuardCopy(t *testing.T) { cases := []struct { name string data []byte want bool }{ {"png", encoded(t, func(b *bytes.Buffer, i image.Image) error { return png.Encode(b, i) }), true}, {"jpeg", encoded(t, func(b *bytes.Buffer, i image.Image) error { return jpeg.Encode(b, i, nil) }), true}, {"gif", encoded(t, func(b *bytes.Buffer, i image.Image) error { return gif.Encode(b, i, nil) }), true}, // The parity fixtures: a PNG and a WebP PHP accepted, and a JPEG // header without an image PHP refused ("The file is not a valid // image."). {"fixture-png", mustHex(t, "89504e470d0a1a0a0000000d49484452000000080000000808020000004b6d29dc0000001949444154789c626160a8906360c0442c20021b189c128001005509023d9970f43c0000000049454e44ae426082"), true}, {"fixture-webp", mustHex(t, "524946464800000057454250565038203c0000005003009d012a180010003e6d2c9245a422a19804004006c4a009d3284600fc000108a000feec35853fff497f283d1d7ff3f3396879687ee0b7790000"), true}, {"fixture-spoofed-jpeg", mustHex(t, "ffd8ffe000104a46494600010100000100010000706f6c79676c6f743a2061204a5045472068656164657220776974686f757420616e7920696d616765ffd9"), false}, {"text", []byte("plain text, not a picture"), false}, {"empty", nil, false}, {"svg", []byte(``), false}, {"bmp", append([]byte("BM"), make([]byte, 60)...), false}, {"png-header-only", mustHex(t, "89504e470d0a1a0a"), false}, } for _, c := range cases { got := classes.IsAllowedImage(c.data) if got != c.want { t.Errorf("%s: IsAllowedImage = %v, want %v", c.name, got, c.want) } if fw := attach.IsAllowedImage(c.data); fw != got { t.Errorf("%s: the framework guard says %v, the copy %v", c.name, fw, got) } if got { switch classes.SniffImageMIME(c.data) { case "image/png", "image/jpeg", "image/gif", "image/webp": default: t.Errorf("%s: sniffed %q", c.name, classes.SniffImageMIME(c.data)) } } } }