From 30ae9750129f318704052cfb2ff38edeb5eadc0b Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Mon, 3 Aug 2026 13:37:03 +0100 Subject: [PATCH 1/2] Add executable and object file signatures Detect ELF, Mach-O (thin and universal, both byte orders), PE/COFF, WebAssembly, ar, and zstd. PE follows e_lfanew within the 512-byte sniff window. Universal Mach-O is gated on a plausible architecture count so Java class files sharing the CA FE BA BE prefix are not misclassified. Export the Format* constants so callers can switch on Result.Format without string literals. Closes #1 --- README.md | 13 ++-- magic.go | 45 ++++++++---- magic_test.go | 6 +- signatures.go | 91 ++++++++++++++++++++---- signatures_test.go | 174 ++++++++++++++++++++++++++++++++++----------- text_test.go | 16 ++--- 6 files changed, 261 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index a7eaf5c..4dac7de 100644 --- a/README.md +++ b/README.md @@ -49,15 +49,20 @@ mutable package state. `Kind` is `text`, `binary`, or `unknown`. `Format` and `MIME` describe the physical content. `Encoding` is set for accepted UTF-8, UTF-16LE, or UTF-16BE -text and never appears as a MIME charset parameter. +text and never appears as a MIME charset parameter. Compare `Format` against +the exported `Format*` constants rather than string literals. -The first format registry contains: +The format registry contains: -- ZIP, TAR, gzip, bzip2, xz, PDF, CFBF, PNG, JPEG, and GIF +- ZIP, TAR, ar, gzip, bzip2, xz, zstd, PDF, CFBF, PNG, JPEG, and GIF +- ELF, Mach-O (thin and universal), PE/COFF, and WebAssembly - plain text, HTML, XML, and SVG Detection uses bytes only. ZIP-based package types such as JAR, wheel, and -NuGet remain `zip`, and compressed payloads are not opened. A caller can +NuGet remain `zip`, and compressed payloads are not opened. A `CA FE BA BE` +prefix is reported as Mach-O only when the following architecture count is +plausible, so Java class files fall through unclassified. PE requires the +`PE\0\0` signature to be reachable within the first 512 bytes. A caller can combine the result with filename or domain rules when it needs a semantic type. diff --git a/magic.go b/magic.go index 6b6648b..4b265ae 100644 --- a/magic.go +++ b/magic.go @@ -40,22 +40,31 @@ type Result struct { NeedBytes int } +// Format values reported in Result.Format. const ( - formatText = "text" - formatHTML = "html" - formatXML = "xml" - formatSVG = "svg" - formatZIP = "zip" - formatTAR = "tar" - formatGZIP = "gzip" - formatBZIP2 = "bzip2" - formatXZ = "xz" - formatPDF = "pdf" - formatCFBF = "cfbf" - formatPNG = "png" - formatJPEG = "jpeg" - formatGIF = "gif" + FormatText = "text" + FormatHTML = "html" + FormatXML = "xml" + FormatSVG = "svg" + FormatZIP = "zip" + FormatTAR = "tar" + FormatGZIP = "gzip" + FormatBZIP2 = "bzip2" + FormatXZ = "xz" + FormatZstd = "zstd" + FormatPDF = "pdf" + FormatCFBF = "cfbf" + FormatPNG = "png" + FormatJPEG = "jpeg" + FormatGIF = "gif" + FormatELF = "elf" + FormatMachO = "mach-o" + FormatPE = "pe" + FormatWASM = "wasm" + FormatAR = "ar" +) +const ( mimeText = "text/plain" mimeHTML = "text/html" mimeXML = "text/xml" @@ -70,6 +79,12 @@ const ( mimePNG = "image/png" mimeJPEG = "image/jpeg" mimeGIF = "image/gif" + mimeZstd = "application/zstd" + mimeELF = "application/x-elf" + mimeMachO = "application/x-mach-binary" + mimePE = "application/vnd.microsoft.portable-executable" + mimeWASM = "application/wasm" + mimeAR = "application/x-archive" encodingUTF8 = "utf-8" encodingUTF16LE = "utf-16le" @@ -107,7 +122,7 @@ func detect(data []byte, prefix bool) Result { result.Format = format result.MIME = mime } else if result.Kind == KindText { - result.Format = formatText + result.Format = FormatText result.MIME = mimeText } diff --git a/magic_test.go b/magic_test.go index e10a527..39c7bf0 100644 --- a/magic_test.go +++ b/magic_test.go @@ -24,7 +24,7 @@ func TestDetectPrefix(t *testing.T) { expect: Result{ Kind: KindText, MIME: mimeText, - Format: formatText, + Format: FormatText, Encoding: encodingUTF8, Reason: ReasonNeedMore, }, @@ -43,7 +43,7 @@ func TestDetectPrefix(t *testing.T) { expect: Result{ Kind: KindBinary, MIME: mimePNG, - Format: formatPNG, + Format: FormatPNG, }, }, { @@ -124,7 +124,7 @@ func TestDetectDoesNotRetainInput(t *testing.T) { assertResult(t, got, Result{ Kind: KindText, MIME: mimeText, - Format: formatText, + Format: FormatText, Encoding: encodingUTF8, }) } diff --git a/signatures.go b/signatures.go index 6badeb2..ceb72ac 100644 --- a/signatures.go +++ b/signatures.go @@ -7,6 +7,8 @@ package magic +import "encoding/binary" + const ( // sniffLength is the furthest byte inspected by any signature. A binary // rule that reads beyond it must also update prefixResultCanChange. @@ -17,6 +19,20 @@ const ( tarChecksumTo = 156 xmlCloseLength = 2 octalBase = 8 + + // peHeaderOffsetAt is the location of the uint32le e_lfanew field in the + // DOS header, which holds the offset of the "PE\0\0" signature. + peHeaderOffsetAt = 0x3c + peSignatureLen = 4 + + // machOFatArchLimit separates a Mach-O universal binary from a Java + // class file, which share the CA FE BA BE prefix. Bytes 4-7 are the + // big-endian architecture count in a fat header and (minor||major) + // version in a class file; the class-file major version has been at + // least 45 since JDK 1.0.2 while no fat binary approaches that many + // architectures. + machOFatHeaderLen = 8 + machOFatArchLimit = 40 ) var htmlSignatures = [...]string{ @@ -43,32 +59,81 @@ func binaryFormat(data []byte) (format, mime string) { case hasPrefix(data, "PK\x03\x04"), hasPrefix(data, "PK\x05\x06"), hasPrefix(data, "PK\x07\x08"): - return formatZIP, mimeZIP + return FormatZIP, mimeZIP case hasPrefix(data, "\x1f\x8b\x08"): - return formatGZIP, mimeGZIP + return FormatGZIP, mimeGZIP case len(data) >= 4 && hasPrefix(data, "BZh") && data[3] >= '1' && data[3] <= '9': - return formatBZIP2, mimeBZIP2 + return FormatBZIP2, mimeBZIP2 case hasPrefix(data, "\xfd7zXZ\x00"): - return formatXZ, mimeXZ + return FormatXZ, mimeXZ case hasPrefix(data, "%PDF-"): - return formatPDF, mimePDF + return FormatPDF, mimePDF case hasPrefix(data, "\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1"): - return formatCFBF, mimeCFBF + return FormatCFBF, mimeCFBF case hasPrefix(data, "\x89PNG\r\n\x1a\n"): - return formatPNG, mimePNG + return FormatPNG, mimePNG case hasPrefix(data, "\xff\xd8\xff"): - return formatJPEG, mimeJPEG + return FormatJPEG, mimeJPEG case hasPrefix(data, "GIF87a"), hasPrefix(data, "GIF89a"): - return formatGIF, mimeGIF + return FormatGIF, mimeGIF + case hasPrefix(data, "\x28\xb5\x2f\xfd"): + return FormatZstd, mimeZstd + case hasPrefix(data, "\x7fELF"): + return FormatELF, mimeELF + case hasPrefix(data, "\xcf\xfa\xed\xfe"), + hasPrefix(data, "\xce\xfa\xed\xfe"), + hasPrefix(data, "\xfe\xed\xfa\xcf"), + hasPrefix(data, "\xfe\xed\xfa\xce"): + return FormatMachO, mimeMachO + case machOFatHeader(data): + return FormatMachO, mimeMachO + case hasPrefix(data, "\x00asm"): + return FormatWASM, mimeWASM + case hasPrefix(data, "!\n"): + return FormatAR, mimeAR + case peHeader(data): + return FormatPE, mimePE case validTARHeader(data): - return formatTAR, mimeTAR + return FormatTAR, mimeTAR default: return "", "" } } +func machOFatHeader(data []byte) bool { + if len(data) < machOFatHeaderLen { + return false + } + var nfat uint32 + switch { + case hasPrefix(data, "\xca\xfe\xba\xbe"), + hasPrefix(data, "\xca\xfe\xba\xbf"): + nfat = binary.BigEndian.Uint32(data[4:machOFatHeaderLen]) + case hasPrefix(data, "\xbe\xba\xfe\xca"), + hasPrefix(data, "\xbf\xba\xfe\xca"): + nfat = binary.LittleEndian.Uint32(data[4:machOFatHeaderLen]) + default: + return false + } + return nfat > 0 && nfat < machOFatArchLimit +} + +func peHeader(data []byte) bool { + if !hasPrefix(data, "MZ") || len(data) < peHeaderOffsetAt+4 { + return false + } + offset := binary.LittleEndian.Uint32(data[peHeaderOffsetAt:]) + // Bound to sniffLength so prefixResultCanChange stays correct. PE files + // with a DOS stub larger than the sniff window are not recognised. + if offset < peHeaderOffsetAt+4 || offset > sniffLength-peSignatureLen || + int(offset)+peSignatureLen > len(data) { + return false + } + return hasPrefix(data[offset:], "PE\x00\x00") +} + func textFormat(data []byte) (format, mime string) { if len(data) > sniffLength { data = data[:sniffLength] @@ -76,13 +141,13 @@ func textFormat(data []byte) (format, mime string) { first := skipWhitespace(data, 0) if isSVG(data, first) { - return formatSVG, mimeSVG + return FormatSVG, mimeSVG } if hasPrefix(data[first:], "\n"), format: FormatAR, mime: mimeAR}, + {name: "PE executable", input: makePE(0x40), format: FormatPE, mime: mimePE}, + {name: "PE at sniff boundary", input: makePE(sniffLength - peSignatureLen), format: FormatPE, mime: mimePE}, } for _, test := range tests { @@ -56,15 +70,20 @@ func TestTruncatedBinarySignaturesDoNotMatch(t *testing.T) { signature []byte format string }{ - {name: "ZIP record", signature: []byte("PK\x03\x04"), format: formatZIP}, - {name: "gzip stream", signature: []byte("\x1f\x8b\x08"), format: formatGZIP}, - {name: "bzip2 stream", signature: []byte("BZh1"), format: formatBZIP2}, - {name: "xz stream", signature: []byte("\xfd7zXZ\x00"), format: formatXZ}, - {name: "PDF document", signature: []byte("%PDF-"), format: formatPDF}, - {name: "CFBF document", signature: []byte("\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1"), format: formatCFBF}, - {name: "PNG image", signature: []byte("\x89PNG\r\n\x1a\n"), format: formatPNG}, - {name: "JPEG image", signature: []byte("\xff\xd8\xff"), format: formatJPEG}, - {name: "GIF image", signature: []byte("GIF89a"), format: formatGIF}, + {name: "ZIP record", signature: []byte("PK\x03\x04"), format: FormatZIP}, + {name: "gzip stream", signature: []byte("\x1f\x8b\x08"), format: FormatGZIP}, + {name: "bzip2 stream", signature: []byte("BZh1"), format: FormatBZIP2}, + {name: "xz stream", signature: []byte("\xfd7zXZ\x00"), format: FormatXZ}, + {name: "PDF document", signature: []byte("%PDF-"), format: FormatPDF}, + {name: "CFBF document", signature: []byte("\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1"), format: FormatCFBF}, + {name: "PNG image", signature: []byte("\x89PNG\r\n\x1a\n"), format: FormatPNG}, + {name: "JPEG image", signature: []byte("\xff\xd8\xff"), format: FormatJPEG}, + {name: "GIF image", signature: []byte("GIF89a"), format: FormatGIF}, + {name: "zstd frame", signature: []byte("\x28\xb5\x2f\xfd"), format: FormatZstd}, + {name: "ELF object", signature: []byte("\x7fELF"), format: FormatELF}, + {name: "Mach-O 64 LE", signature: []byte("\xcf\xfa\xed\xfe"), format: FormatMachO}, + {name: "WASM module", signature: []byte("\x00asm"), format: FormatWASM}, + {name: "ar archive", signature: []byte("!\n"), format: FormatAR}, } for _, test := range tests { @@ -83,6 +102,61 @@ func TestTruncatedBinarySignaturesDoNotMatch(t *testing.T) { } } +func TestPEHeaderBounds(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input []byte + }{ + {name: "MZ without DOS header", input: []byte("MZ")}, + {name: "MZ with zero e_lfanew", input: makePE(0)}, + {name: "e_lfanew inside DOS header", input: makePE(peHeaderOffsetAt)}, + {name: "e_lfanew past sniff window", input: makePE(sniffLength)}, + {name: "e_lfanew past data", input: makePE(0x80)[:0x80]}, + {name: "MZ without PE signature", input: bytes.Repeat([]byte("MZ"), 0x40)}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + if got := Detect(test.input); got.Format == FormatPE { + t.Fatalf("Detect() = %#v, want non-PE", got) + } + }) + } + + if got := DetectPrefix(makePE(0x80)[:0x40]); got.Reason != ReasonNeedMore { + t.Fatalf("DetectPrefix on truncated PE reason = %q, want %q", got.Reason, ReasonNeedMore) + } +} + +func TestMachOFatIsNotJavaClass(t *testing.T) { + t.Parallel() + + // Java class file: CA FE BA BE, minor 0, major 52 (Java 8). + class := []byte("\xca\xfe\xba\xbe\x00\x00\x00\x34") + if got := Detect(class); got.Format == FormatMachO { + t.Fatalf("Java class file matched as Mach-O: %#v", got) + } + + // Zero-arch fat header is not a valid universal binary. + empty := []byte("\xca\xfe\xba\xbe\x00\x00\x00\x00") + if got := Detect(empty); got.Format == FormatMachO { + t.Fatalf("zero-arch fat header matched as Mach-O: %#v", got) + } + + // Byte-swapped magic with a big-endian count would be > 2^24 read LE. + swapped := []byte("\xbe\xba\xfe\xca\x00\x00\x00\x02") + if got := Detect(swapped); got.Format == FormatMachO { + t.Fatalf("swapped fat header with BE count matched as Mach-O: %#v", got) + } + + if got := DetectPrefix([]byte("\xca\xfe\xba\xbe")); got.Reason != ReasonNeedMore { + t.Fatalf("bare CA FE BA BE reason = %q, want %q", got.Reason, ReasonNeedMore) + } +} + func TestTARRequiresMagicAndChecksum(t *testing.T) { t.Parallel() @@ -90,19 +164,19 @@ func TestTARRequiresMagicAndChecksum(t *testing.T) { badChecksum := bytes.Clone(valid) badChecksum[0] ^= 1 - if got := Detect(badChecksum); got.Format == formatTAR { + if got := Detect(badChecksum); got.Format == FormatTAR { t.Fatal("changed TAR header matched") } invalidChecksum := bytes.Clone(valid) invalidChecksum[tarChecksumFrom] = 'x' - if got := Detect(invalidChecksum); got.Format == formatTAR { + if got := Detect(invalidChecksum); got.Format == FormatTAR { t.Fatal("non-octal TAR checksum matched") } magicOnly := make([]byte, sniffLength) copy(magicOnly[tarMagicOffset:], "ustar\x00") - if got := Detect(magicOnly); got.Format == formatTAR { + if got := Detect(magicOnly); got.Format == FormatTAR { t.Fatal("bare ustar marker matched") } @@ -134,15 +208,15 @@ func TestTextFormatRegistryAndPrecedence(t *testing.T) { format string mime string }{ - {name: "HTML mixed case", input: " \n", format: formatHTML, mime: mimeHTML}, - {name: "HTML comment without terminator", input: "", format: formatXML, mime: mimeXML}, - {name: "XML wins with doctype before SVG", input: "\n", format: formatXML, mime: mimeXML}, - {name: "HTML comment wins without declaration", input: "", format: formatHTML, mime: mimeHTML}, - {name: "SVG doctype is plain text", input: "", format: formatText, mime: mimeText}, + {name: "HTML mixed case", input: " \n", format: FormatHTML, mime: mimeHTML}, + {name: "HTML comment without terminator", input: "", format: FormatXML, mime: mimeXML}, + {name: "XML wins with doctype before SVG", input: "\n", format: FormatXML, mime: mimeXML}, + {name: "HTML comment wins without declaration", input: "", format: FormatHTML, mime: mimeHTML}, + {name: "SVG doctype is plain text", input: "", format: FormatText, mime: mimeText}, } for _, test := range tests { @@ -163,13 +237,13 @@ func TestTextSignatureMetadataSurvivesClassification(t *testing.T) { assertResult(t, Detect([]byte("\xff")), Result{ Kind: KindUnknown, MIME: mimeHTML, - Format: formatHTML, + Format: FormatHTML, Reason: ReasonInvalidText, }) assertResult(t, Detect([]byte("\x01")), Result{ Kind: KindBinary, MIME: mimeHTML, - Format: formatHTML, + Format: FormatHTML, }) } @@ -189,7 +263,7 @@ func TestTextSignatureBoundaries(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() got := Detect([]byte(test.input)) - if got.Format != formatText || got.MIME != mimeText { + if got.Format != FormatText || got.MIME != mimeText { t.Fatalf("Detect(%q) = %#v, want plain text", test.input, got) } }) @@ -197,16 +271,34 @@ func TestTextSignatureBoundaries(t *testing.T) { input := append(bytes.Repeat([]byte{' '}, sniffLength), []byte("")...) got := Detect(input) - if got.Format != formatText || got.MIME != mimeText { + if got.Format != FormatText || got.MIME != mimeText { t.Fatalf("signature after sniff window matched: %#v", got) } got = Detect([]byte("")) - if got.Format != formatXML || got.MIME != mimeXML { + if got.Format != FormatXML || got.MIME != mimeXML { t.Fatalf("unterminated XML declaration = %#v, want XML", got) } } +func makePE(peOffset int) []byte { + size := peOffset + peSignatureLen + if size < peHeaderOffsetAt+4 { + size = peHeaderOffsetAt + 4 + } + data := make([]byte, size) + data[0] = 'M' + data[1] = 'Z' + data[peHeaderOffsetAt] = byte(peOffset) + data[peHeaderOffsetAt+1] = byte(peOffset >> 8) + data[peHeaderOffsetAt+2] = byte(peOffset >> 16) + data[peHeaderOffsetAt+3] = byte(peOffset >> 24) + if peOffset >= peHeaderOffsetAt+4 && peOffset+peSignatureLen <= len(data) { + copy(data[peOffset:], "PE\x00\x00") + } + return data +} + func makeTAR(t testing.TB) []byte { t.Helper() return makeTARWithFormat(t, tar.FormatUSTAR) diff --git a/text_test.go b/text_test.go index 75bdc49..bbb8a9d 100644 --- a/text_test.go +++ b/text_test.go @@ -16,7 +16,7 @@ func TestTextDecisionTable(t *testing.T) { expect: Result{ Kind: KindText, MIME: mimeText, - Format: formatText, + Format: FormatText, }, }, { @@ -25,7 +25,7 @@ func TestTextDecisionTable(t *testing.T) { expect: Result{ Kind: KindText, MIME: mimeText, - Format: formatText, + Format: FormatText, Encoding: encodingUTF8, }, }, @@ -35,7 +35,7 @@ func TestTextDecisionTable(t *testing.T) { expect: Result{ Kind: KindText, MIME: mimeText, - Format: formatText, + Format: FormatText, Encoding: encodingUTF16LE, }, }, @@ -45,7 +45,7 @@ func TestTextDecisionTable(t *testing.T) { expect: Result{ Kind: KindText, MIME: mimeText, - Format: formatText, + Format: FormatText, Encoding: encodingUTF16BE, }, }, @@ -80,7 +80,7 @@ func TestTextDecisionTable(t *testing.T) { expect: Result{ Kind: KindText, MIME: mimeText, - Format: formatText, + Format: FormatText, Encoding: encodingUTF8, }, }, @@ -118,7 +118,7 @@ func TestUTF16Validation(t *testing.T) { expect: Result{ Kind: KindText, MIME: mimeText, - Format: formatText, + Format: FormatText, Encoding: encodingUTF16LE, }, }, @@ -128,7 +128,7 @@ func TestUTF16Validation(t *testing.T) { expect: Result{ Kind: KindText, MIME: mimeText, - Format: formatText, + Format: FormatText, Encoding: encodingUTF16LE, }, }, @@ -185,7 +185,7 @@ func TestPlainText(t *testing.T) { assertResult(t, Detect(test.input), Result{ Kind: KindText, MIME: mimeText, - Format: formatText, + Format: FormatText, Encoding: encodingUTF8, }) }) From 0cceda04b09db7cd309432c7634ef7442ad10a79 Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Mon, 3 Aug 2026 14:35:30 +0100 Subject: [PATCH 2/2] Drop byte-swapped fat Mach-O signatures Per mach-o/fat.h the fat header is always big-endian on disk; FAT_CIGAM and FAT_CIGAM_64 are memory-order constants for LE readers, not alternative on-disk byte sequences. Keep FAT_MAGIC and FAT_MAGIC_64 gated on the big-endian nfat_arch check and add a negative test for the swapped prefix. --- signatures.go | 14 +++++--------- signatures_test.go | 8 +++----- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/signatures.go b/signatures.go index ceb72ac..dc4cfee 100644 --- a/signatures.go +++ b/signatures.go @@ -106,17 +106,13 @@ func machOFatHeader(data []byte) bool { if len(data) < machOFatHeaderLen { return false } - var nfat uint32 - switch { - case hasPrefix(data, "\xca\xfe\xba\xbe"), - hasPrefix(data, "\xca\xfe\xba\xbf"): - nfat = binary.BigEndian.Uint32(data[4:machOFatHeaderLen]) - case hasPrefix(data, "\xbe\xba\xfe\xca"), - hasPrefix(data, "\xbf\xba\xfe\xca"): - nfat = binary.LittleEndian.Uint32(data[4:machOFatHeaderLen]) - default: + // Fat headers are always big-endian on disk per mach-o/fat.h; FAT_CIGAM + // is a memory-order constant, not an alternative on-disk signature. + if !hasPrefix(data, "\xca\xfe\xba\xbe") && + !hasPrefix(data, "\xca\xfe\xba\xbf") { return false } + nfat := binary.BigEndian.Uint32(data[4:machOFatHeaderLen]) return nfat > 0 && nfat < machOFatArchLimit } diff --git a/signatures_test.go b/signatures_test.go index a90401b..ae8cf58 100644 --- a/signatures_test.go +++ b/signatures_test.go @@ -42,8 +42,6 @@ func TestBinaryFormatRegistry(t *testing.T) { {name: "Mach-O 32 BE", input: []byte("\xfe\xed\xfa\xce"), format: FormatMachO, mime: mimeMachO}, {name: "Mach-O universal 64", input: []byte("\xca\xfe\xba\xbf\x00\x00\x00\x02"), format: FormatMachO, mime: mimeMachO}, {name: "Mach-O universal 32", input: []byte("\xca\xfe\xba\xbe\x00\x00\x00\x02"), format: FormatMachO, mime: mimeMachO}, - {name: "Mach-O universal 64 swapped", input: []byte("\xbf\xba\xfe\xca\x02\x00\x00\x00"), format: FormatMachO, mime: mimeMachO}, - {name: "Mach-O universal 32 swapped", input: []byte("\xbe\xba\xfe\xca\x02\x00\x00\x00"), format: FormatMachO, mime: mimeMachO}, {name: "WASM module", input: []byte("\x00asm\x01\x00\x00\x00"), format: FormatWASM, mime: mimeWASM}, {name: "ar archive", input: []byte("!\n"), format: FormatAR, mime: mimeAR}, {name: "PE executable", input: makePE(0x40), format: FormatPE, mime: mimePE}, @@ -146,10 +144,10 @@ func TestMachOFatIsNotJavaClass(t *testing.T) { t.Fatalf("zero-arch fat header matched as Mach-O: %#v", got) } - // Byte-swapped magic with a big-endian count would be > 2^24 read LE. - swapped := []byte("\xbe\xba\xfe\xca\x00\x00\x00\x02") + // Byte-swapped magic is not a valid on-disk fat header. + swapped := []byte("\xbe\xba\xfe\xca\x02\x00\x00\x00") if got := Detect(swapped); got.Format == FormatMachO { - t.Fatalf("swapped fat header with BE count matched as Mach-O: %#v", got) + t.Fatalf("byte-swapped fat magic matched as Mach-O: %#v", got) } if got := DetectPrefix([]byte("\xca\xfe\xba\xbe")); got.Reason != ReasonNeedMore {