Migration
This is my new public git source viewer. Source coming soon. Please contact with concerns/bugs.
Contact me
internal/modules/image_test.go
8d1bb5bfdd2cba82488775b46308efed740132a8
· 3.7 KB · 99 lines
raw
| 1 | package modules |
| 2 | |
| 3 | import "testing" |
| 4 | |
| 5 | // A file only earns an inline image MIME when its extension and its magic |
| 6 | // bytes agree and the format is a raster one. The cases that must return "" |
| 7 | // are the attacker-influenced ones: content that is not an image at all, and |
| 8 | // SVG, which is an active document rather than a picture. |
| 9 | func TestImageContentType(t *testing.T) { |
| 10 | png := []byte("\x89PNG\r\n\x1a\n" + "rest of the file") |
| 11 | tests := []struct { |
| 12 | name string |
| 13 | path string |
| 14 | data []byte |
| 15 | want string |
| 16 | }{ |
| 17 | {"png", "logo.png", png, "image/png"}, |
| 18 | {"uppercase extension", "LOGO.PNG", png, "image/png"}, |
| 19 | {"jpeg", "a.jpg", []byte{0xff, 0xd8, 0xff, 0xe0}, "image/jpeg"}, |
| 20 | {"jpeg long extension", "a.jpeg", []byte{0xff, 0xd8, 0xff, 0xe0}, "image/jpeg"}, |
| 21 | {"gif87", "a.gif", []byte("GIF87a...."), "image/gif"}, |
| 22 | {"gif89", "a.gif", []byte("GIF89a...."), "image/gif"}, |
| 23 | {"webp", "a.webp", []byte("RIFF\x00\x00\x00\x00WEBPVP8 "), "image/webp"}, |
| 24 | {"bmp", "a.bmp", []byte("BM...."), "image/bmp"}, |
| 25 | {"ico", "a.ico", []byte{0x00, 0x00, 0x01, 0x00}, "image/x-icon"}, |
| 26 | {"avif", "a.avif", []byte("\x00\x00\x00\x18ftypavif"), "image/avif"}, |
| 27 | |
| 28 | // HTML wearing a .png name, and a real PNG named .txt, must both |
| 29 | // fall back to the download path. |
| 30 | {"html named png", "evil.png", []byte("<html><script>x</script>"), ""}, |
| 31 | {"png extension without magic", "evil.png", []byte("not an image"), ""}, |
| 32 | {"magic without extension", "evil.txt", png, ""}, |
| 33 | {"truncated png", "evil.png", []byte("\x89PNG"), ""}, |
| 34 | |
| 35 | // SVG can carry <script> and would be stored XSS if served as a |
| 36 | // document on this origin, so it is never recognised here. |
| 37 | {"svg", "evil.svg", []byte(`<svg xmlns="http://www.w3.org/2000/svg"><script/></svg>`), ""}, |
| 38 | {"no extension", "Makefile", []byte("all:\n"), ""}, |
| 39 | } |
| 40 | for _, tc := range tests { |
| 41 | if got := imageContentType(tc.path, tc.data); got != tc.want { |
| 42 | t.Errorf("%s: imageContentType(%q) = %q, want %q", tc.name, tc.path, got, tc.want) |
| 43 | } |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | // The blob view only renders a preview for a validated raster image, and |
| 48 | // only while it is under the size cap; both facts are what tell the view |
| 49 | // whether to show an <img>, a download notice, or numbered bytes. |
| 50 | func TestImageBlobPreview(t *testing.T) { |
| 51 | png := append([]byte("\x89PNG\r\n\x1a\n"), make([]byte, 64)...) |
| 52 | big := append([]byte("\x89PNG\r\n\x1a\n"), make([]byte, maxPreviewSize)...) |
| 53 | c := blobCtx(t, map[string]string{ |
| 54 | "logo.png": string(png), |
| 55 | "big.png": string(big), |
| 56 | "evil.png": "<html>not an image</html>", |
| 57 | }) |
| 58 | |
| 59 | b := blobAt(t, c, "logo.png") |
| 60 | if b.Image != "image/png" || !b.Preview() || !b.Binary { |
| 61 | t.Errorf("small image: Image=%q Preview=%v Binary=%v", b.Image, b.Preview(), b.Binary) |
| 62 | } |
| 63 | if b.PreviewLimit() != maxPreviewSize { |
| 64 | t.Errorf("PreviewLimit = %d, want %d", b.PreviewLimit(), maxPreviewSize) |
| 65 | } |
| 66 | |
| 67 | bigBlob := blobAt(t, c, "big.png") |
| 68 | if bigBlob.Image != "image/png" { |
| 69 | t.Errorf("large image: Image = %q, want image/png", bigBlob.Image) |
| 70 | } |
| 71 | if bigBlob.Preview() { |
| 72 | t.Error("large image was previewed past the size cap") |
| 73 | } |
| 74 | |
| 75 | // Not an image, so it keeps the plain binary path rather than a preview. |
| 76 | evil := blobAt(t, c, "evil.png") |
| 77 | if evil.Image != "" || evil.Preview() { |
| 78 | t.Errorf("misnamed html: Image=%q Preview=%v, want none", evil.Image, evil.Preview()) |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | // /raw/ must declare the image MIME so the preview and direct navigation |
| 83 | // render a picture, and must expose it through the interface core checks. |
| 84 | func TestRawBlobInlineImage(t *testing.T) { |
| 85 | png := []byte("\x89PNG\r\n\x1a\n" + "body") |
| 86 | c := blobCtx(t, map[string]string{"logo.png": string(png)}) |
| 87 | c.Path = "logo.png" |
| 88 | v, err := blobRawMod(c) |
| 89 | if err != nil { |
| 90 | t.Fatal(err) |
| 91 | } |
| 92 | r, ok := v.(RawBlob) |
| 93 | if !ok { |
| 94 | t.Fatalf("got %T", v) |
| 95 | } |
| 96 | if r.InlineContentType() != "image/png" { |
| 97 | t.Errorf("InlineContentType = %q, want image/png", r.InlineContentType()) |
| 98 | } |
| 99 | } |