diff --git a/match_test.go b/match_test.go index 7be2756..0f70eca 100644 --- a/match_test.go +++ b/match_test.go @@ -18,6 +18,16 @@ func TestMatch(t *testing.T) { {[]byte{0xFF, 0xD8, 0xFF}, "jpg"}, {[]byte{0xFF, 0xD8, 0x00}, "unknown"}, {[]byte{0x89, 0x50, 0x4E, 0x47}, "png"}, + {[]byte("BMW"), "unknown"}, + { + []byte{ + 'B', 'M', + 0x3A, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x36, 0x00, 0x00, 0x00, + }, + "bmp", + }, } for _, test := range cases { @@ -32,6 +42,30 @@ func TestMatch(t *testing.T) { } } +func TestBmpNotAsciiPrefix(t *testing.T) { + // Issue #117: "BMW" starts with BM but is not a BMP file. + kind, err := Match([]byte("BMW")) + if err != nil { + t.Fatalf("Match: %v", err) + } + if kind.Extension == "bmp" { + t.Fatalf("ASCII BMW matched as bmp") + } + if matchers.Bmp([]byte("BMW")) { + t.Fatalf("Bmp matcher accepted ASCII BMW") + } + + header := make([]byte, 14) + header[0], header[1] = 'B', 'M' + if !matchers.Bmp(header) { + t.Fatalf("14-byte BM header with reserved=0 should match") + } + header[6] = 1 + if matchers.Bmp(header) { + t.Fatalf("non-zero reserved field should not match") + } +} + func TestMatchFile(t *testing.T) { cases := []struct { ext string @@ -100,6 +134,7 @@ func TestMatches(t *testing.T) { {[]byte{0xFF, 0xD8, 0xFF}, true}, {[]byte{0xFF, 0x0, 0x0}, false}, {[]byte{0x89, 0x50, 0x4E, 0x47}, true}, + {[]byte("BMW"), false}, } for _, test := range cases { diff --git a/matchers/image.go b/matchers/image.go index 6cd6f93..d60a3c2 100644 --- a/matchers/image.go +++ b/matchers/image.go @@ -95,9 +95,15 @@ func Tiff(buf []byte) bool { } func Bmp(buf []byte) bool { - return len(buf) > 1 && - buf[0] == 0x42 && - buf[1] == 0x4D + // BITMAPFILEHEADER is 14 bytes: "BM" + size + reserved (must be 0) + pixel offset. + // Matching only "BM" treats short ASCII like "BMW" as image/bmp (issue #117). + if len(buf) < 14 { + return false + } + if buf[0] != 0x42 || buf[1] != 0x4D { + return false + } + return buf[6] == 0 && buf[7] == 0 && buf[8] == 0 && buf[9] == 0 } func Jxr(buf []byte) bool {