diff --git a/go.mod b/go.mod index 0547c3524c..40269c4af7 100644 --- a/go.mod +++ b/go.mod @@ -413,7 +413,7 @@ require ( github.com/fluxcd/pkg/apis/acl v0.10.0 // indirect github.com/fsnotify/fsnotify v1.10.1 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.13 + github.com/gabriel-vasile/mimetype v1.4.15 github.com/gdamore/encoding v1.0.1 // indirect github.com/github/go-spdx/v2 v2.7.0 // indirect github.com/glebarez/go-sqlite v1.22.0 // indirect diff --git a/go.sum b/go.sum index a9ab2cde47..2ef368d59e 100644 --- a/go.sum +++ b/go.sum @@ -711,8 +711,8 @@ github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQ github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78= github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= -github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI= +github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ= github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= diff --git a/vendor/github.com/gabriel-vasile/mimetype/.golangci.yml b/vendor/github.com/gabriel-vasile/mimetype/.golangci.yml index 5b30cd614d..835dea6fa7 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/.golangci.yml +++ b/vendor/github.com/gabriel-vasile/mimetype/.golangci.yml @@ -7,6 +7,12 @@ linters: exclusions: presets: - std-error-handling + rules: + # Test fixtures construct CDF binary blobs from known small constants, so + # gosec's integer-overflow checks (G115) add no value there. + - path: internal/cdf/cdf_test\.go + linters: + - gosec enable: - gosec # Detects security problems. # Keep all extras disabled for now to focus on the integer overflow problem. @@ -31,6 +37,7 @@ linters: - unused - usestdlibvars # Detects the possibility to use variables/constants from the Go standard library. - usetesting # Reports uses of functions with replacement inside the testing package. + - asciicheck # https://daniel.haxx.se/blog/2025/05/16/detecting-malicious-unicode/ settings: govet: disable: diff --git a/vendor/github.com/gabriel-vasile/mimetype/README.md b/vendor/github.com/gabriel-vasile/mimetype/README.md index 9fe71ac945..b024316999 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/README.md +++ b/vendor/github.com/gabriel-vasile/mimetype/README.md @@ -13,8 +13,8 @@ Go Reference - - Go report card + + Code coverage License @@ -103,3 +103,10 @@ shows which file formats are most often misidentified and can help prioritise. When submitting a PR for detection of a new file format, please make sure to add a record to the list of testcases in [mimetype_test.go](mimetype_test.go). For complex files a record can be added in the [testdata](testdata) directory. +Code contributions must respect following rules: + - code must be test covered + - code must be formatted using the `gofmt` tool + - exported names must be documented + +**Important**: By submitting a pull request, you agree to allow the project +owner to license your work under the same license as that used by the project. diff --git a/vendor/github.com/gabriel-vasile/mimetype/codecov.yml b/vendor/github.com/gabriel-vasile/mimetype/codecov.yml new file mode 100644 index 0000000000..69cb76019a --- /dev/null +++ b/vendor/github.com/gabriel-vasile/mimetype/codecov.yml @@ -0,0 +1 @@ +comment: false diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/cdf/cdf.go b/vendor/github.com/gabriel-vasile/mimetype/internal/cdf/cdf.go new file mode 100644 index 0000000000..926cef7b0a --- /dev/null +++ b/vendor/github.com/gabriel-vasile/mimetype/internal/cdf/cdf.go @@ -0,0 +1,667 @@ +// Package cdf implements parsing of CDF (OLE2) files. It is greatly inspired +// by src/readcdf.c from libmagic. One difference is this implementation is +// permissive of truncated inputs. See readLimit in mimetype.go for the +// reason why truncated inputs need to be handled. +// http://sc.openoffice.org/compdocfileformat.pdf +package cdf + +import ( + "bytes" + "encoding/binary" + + "github.com/gabriel-vasile/mimetype/internal/scan" +) + +type CDFType int8 + +const ( + CDFTypeGeneric CDFType = iota + CDFTypeInstaller + CDFTypeDoc + CDFTypePpt + CDFTypeXls + CDFTypeMsg +) + +// Detect parses raw as a CDF (OLE2) compound file and returns the document type +// it contains. It returns CDFTypeGeneric for input that is not a CDF file or +// whose type cannot be narrowed down. +func Detect(raw []byte) CDFType { + if len(raw) < 512 { + return CDFTypeGeneric + } + var c cdf + if !parse(raw, &c) { + return CDFTypeGeneric + } + return c.detect() +} + +// cdf holds everything we need from a CDF file to do detection. +type cdf struct { + data []byte + secSize int + shortSecSize int + minStdStream uint32 + satSecs int32s // list of SAT sector ids; usually a sub-slice of raw input + satEntries int // number of valid SAT entries reachable through satSecs + firstSSAT int32 + dirRaw []byte // directory stream bytes (entries are decoded on demand) + sst []byte // short-stream pool (root storage's stream) + sstBuilt bool // whether sst was already loaded (it is loaded lazily) + rootStreamFirst int32 // first sector of the root storage short-stream pool + rootStreamSize uint32 // size of the root storage short-stream pool + rootStorageUUID []byte +} + +// parse reads the entire on-disk structure required for type detection. It +// returns true on success and false if the header does not look like a CDF file. +// Truncated or partially malformed bodies are tolerated: sector reads degrade +// to whatever could be collected so detection can still succeed from partial data. +func parse(raw []byte, c *cdf) bool { + if len(raw) < 512 || binary.LittleEndian.Uint64(raw) != cdfMagic { + return false + } + secP2 := binary.LittleEndian.Uint16(raw[30:32]) + shortP2 := binary.LittleEndian.Uint16(raw[32:34]) + if secP2 > 20 || shortP2 > 20 { + return false + } + c.data = raw + c.secSize = 1 << secP2 + c.shortSecSize = 1 << shortP2 + c.minStdStream = binary.LittleEndian.Uint32(raw[56:60]) + if c.secSize < dirEntrySize { + return false + } + firstDirSec := readSecID(raw[48:52]) + c.firstSSAT = readSecID(raw[60:64]) + firstMSAT := readSecID(raw[68:72]) + nMSAT := binary.LittleEndian.Uint32(raw[72:76]) + masterSAT := int32s{b: raw[76 : 76+4*masterSATSize]} + + c.buildSAT(masterSAT, firstMSAT, nMSAT) + c.dirRaw = c.readLong(firstDirSec, 0) + + c.rootStreamFirst = -1 + var d dirEntry + for i, n := 0, c.dirLen(); i < n; i++ { + c.dirAt(i, &d) + if d.typ != dirTypeRootStorage || d.streamFirst < 0 { + continue + } + c.rootStorageUUID = d.storageUUID[:] + // Record where the short-stream pool lives; it is loaded lazily by + // shortStream the first time a short stream is actually read. + c.rootStreamFirst = d.streamFirst + c.rootStreamSize = d.size + break + } + return true +} + +func (c *cdf) detect() CDFType { + for _, name := range []string{"\x05SummaryInformation", "\x05DocumentSummaryInformation"} { + if t, ok := c.detectFromSummary(name); ok { + return t + } + } + var d dirEntry + for i, n := 0, c.dirLen(); i < n; i++ { + c.dirAt(i, &d) + if t, ok := lookupSection(d.nameBytes(), d.typ); ok { + return t + } + } + return CDFTypeGeneric +} + +// detectFromSummary inspects a (Doc)SummaryInformation stream and tries to +// derive a CDFType from the root-storage CLSID, the property NameOfApplication, +// and finally the names of sibling user streams. +func (c *cdf) detectFromSummary(streamName string) (CDFType, bool) { + if c.rootStorageUUID != nil && bytes.Equal(c.rootStorageUUID, msiCLSID) { + return CDFTypeInstaller, true + } + raw, ok := c.userStream(streamName) + if !ok { + return CDFTypeGeneric, false + } + if app := summaryAppName(raw); len(app) > 0 { + if t, ok := lookupSubstring(app, app2type); ok { + return t, true + } + } + for i, n := 0, c.dirLen(); i < n; i++ { + var d dirEntry + c.dirAt(i, &d) + if d.nameLen == 0 { + continue + } + if t, ok := lookupSubstring(d.nameBytes(), name2type); ok { + return t, true + } + } + return CDFTypeGeneric, true +} + +const ( + cdfMagic uint64 = 0xE11AB1A1E011CFD0 + + dirTypeUserStorage = 1 + dirTypeUserStream = 2 + dirTypeRootStorage = 5 + + dirEntrySize = 128 + masterSATSize = 109 // first 109 SAT secids live in the file header +) + +// dirEntry is a single CDF directory record. The UTF-16LE name is pre-decoded +// into an inline ASCII buffer at parse time, avoiding a per-entry heap +// allocation while keeping comparisons trivial. CDF names are at most 32 +// UTF-16 code units, so 32 bytes always suffice. +type dirEntry struct { + name [32]byte + nameLen uint8 + typ uint8 + streamFirst int32 + size uint32 + storageUUID [16]byte +} + +// nameBytes returns the decoded ASCII name without copying. +func (d *dirEntry) nameBytes() []byte { return d.name[:d.nameLen] } + +func (c *cdf) ssatAt(i int32) int32 { + for sid := c.firstSSAT; sid >= 0; { + if int(sid) >= c.satLen() { + break // SAT is truncated; stop collecting + } + buf, ok := c.sector(sid) + if !ok { + break + } + lbuf := int32(len(buf) / 4) //nolint:gosec // anything divided by 4 fits int32 + if i < lbuf { + return int32(binary.LittleEndian.Uint32(buf[4*i:])) //nolint:gosec // intentional two's-complement reinterpretation of a sector id + } + i -= lbuf + sid = c.satAt(sid) + } + return -1 +} + +// shortStream returns the root storage short-stream pool, loading it on first +// use. Detection often finishes (e.g. via the root CLSID or a long-stream +// summary) without ever reading a short stream, so building this eagerly would +// be wasted work. +func (c *cdf) shortStream() []byte { + if !c.sstBuilt { + c.sstBuilt = true + if c.rootStreamFirst >= 0 { + c.sst = c.readLong(c.rootStreamFirst, c.rootStreamSize) + } + } + return c.sst +} + +// int32s works like a slice of LE int32 and is backed by a slice of bytes. +// int32s could very well be type int32s []byte, but that would mean +// len function can be called on it. We don't want that, we always want to use +// the len method. +type int32s struct { + b []byte +} + +func (b int32s) at(i int) int32 { + //nolint:gosec // intentional two's-complement reinterpretation of a sector id + return int32(binary.LittleEndian.Uint32(b.b[4*i:])) +} +func (b int32s) len() int { + return len(b.b) / 4 +} + +// readSecID reinterprets four little-endian bytes as a signed sector id. +// Every 32-bit pattern is a valid id (values >= 0 are sector numbers, +// negatives are CDF sentinels such as -2 end-of-chain), so the conversion is +// an intentional two's-complement reinterpretation rather than an overflow. +func readSecID(b []byte) int32 { + return int32(binary.LittleEndian.Uint32(b)) //nolint:gosec // intentional two's-complement reinterpretation +} + +// satLen is the number of sector ids reachable through the SAT. +func (c *cdf) satLen() int { return c.satEntries } + +// satAt returns the i-th sector id from the SAT. Callers must ensure +// i < satLen(). The SAT is not materialized; the entry is fetched directly +// from the input by translating i into (SAT sector index, entry offset). +func (c *cdf) satAt(i int32) int32 { + perSec := c.secSize / 4 + secIdx := int(i) / perSec + entryIdx := int(i) % perSec + secID := c.satSecs.at(secIdx) + off := c.secSize*(1+int(secID)) + 4*entryIdx + return readSecID(c.data[off:]) +} + +// sector returns the bytes of long sector secid. If the file is truncated +// inside the requested sector the result is the available bytes (no padding). +// If the sector starts past EOF or secid is negative, then ok is false. +func (c *cdf) sector(secid int32) (_ []byte, ok bool) { + if secid < 0 { + return nil, false + } + off := int64(c.secSize) * (1 + int64(secid)) + if off >= int64(len(c.data)) { + return nil, false + } + // The returned sector might be truncated, + // but we still return it as best effort. + end := min(off+int64(c.secSize), int64(len(c.data))) + // If not even one int32 fits, then fail. + if end-off < 4 { + return nil, false + } + return c.data[off:end], true +} + +func (c *cdf) sectorIDs(secid int32) (int32s, bool) { + buf, ok := c.sector(secid) + if !ok { + return int32s{}, ok + } + return int32s{b: buf}, true +} + +// buildSAT records the list of SAT sector ids from the master-SAT (header) +// plus any extension blocks chained via firstMSAT. The SAT itself is not +// materialized: satAt computes the requested entry directly from c.data via +// satSecs. In the common case (no extension chain) satSecs is a zero-copy +// sub-slice of the input header. +func (c *cdf) buildSAT(masterSAT int32s, firstMSAT int32, nMSAT uint32) { + // Fast path: no extension chain. masterSAT is already a sub-slice of raw + // input; reuse it directly. + if firstMSAT < 0 || nMSAT == 0 { + c.satSecs = masterSAT + c.satEntries = c.computeSATLen() + return + } + + // Slow path: gather sector ids from the header plus the extension chain + // into a fresh buffer. Even here we only allocate space for ids (4 bytes + // each), not the full SAT contents. + maxIDs := len(c.data)/c.secSize + 1 + buf := make([]byte, 0, 4*masterSATSize) + for i := 0; i < masterSAT.len(); i++ { + if masterSAT.at(i) < 0 { + break + } + buf = append(buf, masterSAT.b[4*i:4*i+4]...) + } + perSec := c.secSize/4 - 1 + mid := firstMSAT +chain: + for j := uint32(0); j < nMSAT && mid >= 0; j++ { + msa, ok := c.sectorIDs(mid) + if !ok { + break + } + for k := 0; k < perSec; k++ { + if k >= msa.len() || msa.at(k) < 0 { + break chain + } + buf = append(buf, msa.b[4*k:4*k+4]...) + if len(buf)/4 > maxIDs { + break chain // cyclic MSAT chain; stop allocating + } + } + if perSec >= msa.len() { + break // no next-MSAT pointer available + } + mid = msa.at(perSec) + } + c.satSecs = int32s{b: buf} + c.satEntries = c.computeSATLen() +} + +// computeSATLen walks satSecs and counts how many SAT entries are actually +// reachable in c.data, stopping at the first sentinel id or sector that is not +// fully present in the file. +func (c *cdf) computeSATLen() int { + perSec := c.secSize / 4 + total := 0 + for i := 0; i < c.satSecs.len(); i++ { + sec := c.satSecs.at(i) + if sec < 0 { + break + } + off := int64(c.secSize) * (1 + int64(sec)) + if off >= int64(len(c.data)) { + break + } + avail := int64(len(c.data)) - off + if avail >= int64(c.secSize) { + total += perSec + continue + } + total += int(avail / 4) + break + } + return total +} + +// readLong reads a long-sector chain starting at sid. If length > 0 the +// result is truncated to that many bytes. On truncation or any other failure +// it returns whatever sectors were readable. +func (c *cdf) readLong(sid int32, length uint32) []byte { + // Fast path: when the chain is a single physically contiguous run of + // sectors (the common case for the directory and summary streams) the data + // is already laid out sequentially in the input, so return a sub-slice of + // it instead of allocating a buffer and copying every sector. + if sid >= 0 { + maxSec := len(c.data)/c.secSize + 1 + n, s := 0, sid + contiguous := true + for s >= 0 { + if int(s) >= c.satLen() { + break // SAT truncated; what remains is still contiguous + } + n++ + if n > maxSec { + contiguous = false // cyclic chain; let the slow path guard it + break + } + next := c.satAt(s) + if next >= 0 && int64(next) != int64(s)+1 { + contiguous = false + break + } + s = next + } + if contiguous { + off64 := int64(c.secSize) * (1 + int64(sid)) + if off64 >= int64(len(c.data)) { + return nil + } + end64 := min(off64+int64(n)*int64(c.secSize), int64(len(c.data))) + out := c.data[off64:end64] + if length > 0 && int64(length) < int64(len(out)) { + out = out[:length] + } + return out + } + } + + // Slow path: gather a fragmented chain into a fresh buffer. Real-world + // writers (MSI builders, edited Office documents) routinely produce + // non-contiguous directory and stream chains, so this fallback is required + // for correct detection on those files. + maxBytes := len(c.data) + out := make([]byte, 0, c.secSize) + for sid >= 0 { + if int(sid) >= c.satLen() { + break // SAT truncated; return what we have + } + buf, ok := c.sector(sid) + if !ok { + break + } + out = append(out, buf...) + if len(out) >= maxBytes { + break // chain longer than the file: cyclic SAT, stop + } + sid = c.satAt(sid) + } + if length > 0 && int64(length) < int64(len(out)) { + out = out[:length] + } + return out +} + +// readShort reads a short-sector chain at sid by indexing into the short-stream +// pool. On truncation or if the pool is unavailable it returns whatever was +// readable (possibly nil). +func (c *cdf) readShort(sid int32, length uint32) []byte { + sst := c.shortStream() + if sst == nil { + return nil + } + // TODO: anyway to avoid allocating and copying the bytes? + out := make([]byte, 0, c.shortSecSize) + for sid >= 0 { + off64 := int64(sid) * int64(c.shortSecSize) + if off64+int64(c.shortSecSize) > int64(len(sst)) { + break // short-stream pool truncated or sid out of range + } + off := int(off64) + out = append(out, sst[off:off+c.shortSecSize]...) + if len(out) >= len(sst) { + break // chain longer than the pool: cyclic SSAT, stop + } + sid = c.ssatAt(sid) + } + if length > 0 && int64(length) < int64(len(out)) { + out = out[:length] + } + return out +} + +// readChain dispatches to the long or short reader depending on stream size. +func (c *cdf) readChain(sid int32, length uint32) []byte { + if length < c.minStdStream && c.rootStreamFirst >= 0 { + return c.readShort(sid, length) + } + return c.readLong(sid, length) +} + +// dirLen returns the number of directory entries in dirRaw. +func (c *cdf) dirLen() int { return len(c.dirRaw) / dirEntrySize } + +// dirAt decodes the i-th directory entry into *out. Callers must ensure +// i < dirLen(). The UTF-16LE name is decoded into out.name, ASCII-style, +// stopping at the first NUL. +func (c *cdf) dirAt(i int, out *dirEntry) { + raw := c.dirRaw[i*dirEntrySize:] + nameLen := min(int(binary.LittleEndian.Uint16(raw[64:])), 64) + k := uint8(0) + for j := 0; j < nameLen/2; j++ { + // Names are ASCII; keep the low byte of each little-endian UTF-16 + // code unit and stop at the first NUL. + lo, hi := raw[2*j], raw[2*j+1] + if lo == 0 && hi == 0 { + break + } + out.name[k] = lo + k++ + } + out.nameLen = k + out.typ = raw[66] + out.streamFirst = readSecID(raw[116:120]) + out.size = binary.LittleEndian.Uint32(raw[120:]) + copy(out.storageUUID[:], raw[80:96]) +} + +// userStream finds a user stream by name and returns its bytes. +func (c *cdf) userStream(name string) ([]byte, bool) { + var d dirEntry + for i, n := 0, c.dirLen(); i < n; i++ { + c.dirAt(i, &d) + if d.typ == dirTypeUserStream && string(d.nameBytes()) == name { + buf := c.readChain(d.streamFirst, d.size) + if buf == nil { + return nil, false + } + return buf, true + } + } + return nil, false +} + +const ( + propIDNameOfApplication = 0x12 + + typeMask = 0x0fff + typeVector = 0x1000 + typeStringASCII = 0x1e + typeStringWide = 0x1f + + sectionDeclOffset = 0x1c // section declaration in property-set header +) + +// summaryAppName parses a (Doc)SummaryInformation stream and returns the +// value of property NameOfApplication (0x12) as printable ASCII, or nil if +// not present or the stream is malformed. This is the only summary property +// the detection logic ever consults. +func summaryAppName(stream []byte) []byte { + if len(stream) < sectionDeclOffset+20 { + return nil + } + sdOff := binary.LittleEndian.Uint32(stream[sectionDeclOffset+16:]) + if uint64(sdOff)+8 > uint64(len(stream)) { + return nil + } + section := stream[sdOff:] + shLen := binary.LittleEndian.Uint32(section[0:]) + nProps := binary.LittleEndian.Uint32(section[4:]) + if uint64(shLen) > uint64(len(section)) || nProps > 1<<16 || 8+8*nProps > shLen { + return nil + } + for i := uint32(0); i < nProps; i++ { + base := 8 + 8*i + id := binary.LittleEndian.Uint32(section[base:]) + if id != propIDNameOfApplication { + continue + } + off := binary.LittleEndian.Uint32(section[base+4:]) + if uint64(off)+8 > uint64(shLen) { + return nil + } + typ := binary.LittleEndian.Uint32(section[off:]) + if typ&typeVector != 0 { + return nil + } + step := uint32(0) + switch typ & typeMask { + case typeStringASCII: + step = 1 + case typeStringWide: + step = 2 + default: + return nil + } + slen := binary.LittleEndian.Uint32(section[off+4:]) + start := uint64(off) + 8 + end := start + uint64(slen)*uint64(step) + if end > uint64(shLen) { + return nil + } + return printableLowBytes(section[start:end], int(step)) + } + return nil +} + +// printableLowBytes copies the printable low byte of each step-byte unit +// in b, stopping at the first NUL. +func printableLowBytes(b []byte, step int) []byte { + out := make([]byte, 0, len(b)/step) + for i := 0; i+step <= len(b); i += step { + c := b[i] + if c == 0 { + break + } + if c >= 0x20 && c < 0x7f { + out = append(out, c) + } + } + return out +} + +// pattern is a case-insensitive substring → CDFType mapping. Entries are +// tested in order; first match wins. needle is stored upper-cased so it can be +// matched case-insensitively by scan.Bytes.Search with scan.IgnoreCase. +type pattern struct { + needle []byte + typ CDFType +} + +// app2type maps NameOfApplication values to CDFTypes. +// Mirrors app2mime[] in libmagic. Needles are upper-cased for case-insensitive +// matching via scan.IgnoreCase. +var app2type = []pattern{ + {[]byte("WORD"), CDFTypeDoc}, + {[]byte("EXCEL"), CDFTypeXls}, + {[]byte("POWERPOINT"), CDFTypePpt}, + {[]byte("ADVANCED INSTALLER"), CDFTypeInstaller}, + {[]byte("INSTALLSHIELD"), CDFTypeInstaller}, + {[]byte("MICROSOFT PATCH COMPILER"), CDFTypeInstaller}, + {[]byte("NANT"), CDFTypeInstaller}, + {[]byte("WINDOWS INSTALLER"), CDFTypeInstaller}, +} + +// name2type maps directory entry names to CDFTypes. +// Mirrors name2mime[] in libmagic. Needles are upper-cased for case-insensitive +// matching via scan.IgnoreCase. +var name2type = []pattern{ + {[]byte("BOOK"), CDFTypeXls}, + {[]byte("WORKBOOK"), CDFTypeXls}, + {[]byte("WORDDOCUMENT"), CDFTypeDoc}, + {[]byte("POWERPOINT"), CDFTypePpt}, + {[]byte("DIGITALSIGNATURE"), CDFTypeInstaller}, +} + +// lookupSubstring returns the CDFType for the first entry in t whose needle +// is a case-insensitive substring of v. Mirrors C's strcasestr semantics +// under the C locale. It allocates nothing: scan.IgnoreCase matches the +// upper-cased needle against input of either case. +func lookupSubstring(v []byte, t []pattern) (CDFType, bool) { + s := scan.Bytes(v) + for _, p := range t { + if i, _ := s.Search(p.needle, scan.IgnoreCase); i != -1 { + return p.typ, true + } + } + return CDFTypeGeneric, false +} + +// msiCLSID is the Microsoft Installer root-storage CLSID, in on-disk byte +// order (cdf_directory_t.d_storage_uuid stores two little-endian uint64s). +var msiCLSID = []byte{ + 0x84, 0x10, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, +} + +// section is a (directory entry name, type) → CDFType mapping. +type section struct { + name string + typ uint8 + cdf CDFType +} + +// sectionTypes maps distinctive directory entries to CDFTypes — a flattened +// equivalent of sectioninfo[] in libmagic. Used as a fallback when no +// SummaryInformation stream is present. A slice (rather than a map) lets +// lookupSection compare entry names without allocating a string key. +var sectionTypes = []section{ + // libmagic uses application/encrypted, but that is not a registered media type. + // For now, we skip identifying that and fall-back on CDFTypeGeneric + // {"EncryptedPackage", dirTypeUserStream, CDFTypeEncrypted}, + // {"EncryptedSummary", dirTypeUserStream, CDFTypeEncrypted}, + {"Book", dirTypeUserStream, CDFTypeXls}, + {"Workbook", dirTypeUserStream, CDFTypeXls}, + {"WordDocument", dirTypeUserStream, CDFTypeDoc}, + {"PowerPoint Document", dirTypeUserStream, CDFTypePpt}, + {"__properties_version1.0", dirTypeUserStream, CDFTypeMsg}, + {"__recip_version1.0_#00000000", dirTypeUserStorage, CDFTypeMsg}, +} + +// lookupSection returns the CDFType for a directory entry whose name and type +// match a sectionTypes entry exactly. The string(name) == comparison is +// optimized by the compiler to avoid allocating. +func lookupSection(name []byte, typ uint8) (CDFType, bool) { + for _, s := range sectionTypes { + if s.typ == typ && string(name) == s.name { + return s.cdf, true + } + } + return CDFTypeGeneric, false +} diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/charset/charset.go b/vendor/github.com/gabriel-vasile/mimetype/internal/charset/charset.go index 3373274ad9..d17e629180 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/internal/charset/charset.go +++ b/vendor/github.com/gabriel-vasile/mimetype/internal/charset/charset.go @@ -84,19 +84,8 @@ func FromPlain(content []byte) string { break } } - hasHighBit := false - for _, c := range content { - if c >= 0x80 { - hasHighBit = true - break - } - } - if hasHighBit && utf8.Valid(content) { - return "utf-8" - } - // ASCII is a subset of UTF8. Follow W3C recommendation and replace with UTF8. - if ascii(origContent) { + if utf8.Valid(content) { return "utf-8" } @@ -123,15 +112,6 @@ func latin(content []byte) string { return "iso-8859-1" } -func ascii(content []byte) bool { - for _, b := range content { - if textChars[b] != T { - return false - } - } - return true -} - // FromXML returns the charset of an XML document. It relies on the XML // header and falls back on the plain // text content. diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/csv/parser.go b/vendor/github.com/gabriel-vasile/mimetype/internal/csv/parser.go index 87ff697b9f..0cd797e18f 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/internal/csv/parser.go +++ b/vendor/github.com/gabriel-vasile/mimetype/internal/csv/parser.go @@ -12,10 +12,10 @@ import ( type Parser struct { comma byte comment byte - s scan.Bytes + s *scan.Bytes } -func NewParser(comma, comment byte, s scan.Bytes) *Parser { +func NewParser(comma, comment byte, s *scan.Bytes) *Parser { return &Parser{ comma: comma, comment: comment, @@ -55,7 +55,7 @@ func (r *Parser) CountFields(collectIndexes bool) (fields int, fieldPos []int, h if finished { return 0, nil, false } - finished = len(r.s) == 0 && len(line) == 0 + finished = len(*r.s) == 0 && len(line) == 0 if len(line) == lengthNL(line) { line = nil continue // Skip empty lines. diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/json/parser.go b/vendor/github.com/gabriel-vasile/mimetype/internal/json/parser.go index 570889b7b1..a1c6912dc3 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/internal/json/parser.go +++ b/vendor/github.com/gabriel-vasile/mimetype/internal/json/parser.go @@ -10,6 +10,7 @@ const ( QueryGeo = "geo" QueryHAR = "har" QueryGLTF = "gltf" + QueryCDX = "cdx" maxRecursion = 4096 ) @@ -40,6 +41,10 @@ var queries = map[string][]query{ SearchPath: [][]byte{[]byte("asset"), []byte("version")}, SearchVals: [][]byte{[]byte(`"1.0"`), []byte(`"2.0"`)}, }}, + QueryCDX: {{ + SearchPath: [][]byte{[]byte("bomFormat")}, + SearchVals: [][]byte{[]byte(`"CycloneDX"`)}, + }}, } var parserPool = sync.Pool{ diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/audio.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/audio.go index a285001709..ad48d8fb8d 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/audio.go +++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/audio.go @@ -3,6 +3,8 @@ package magic import ( "bytes" "encoding/binary" + + "github.com/gabriel-vasile/mimetype/internal/mp3" ) // Flac matches a Free Lossless Audio Codec file. @@ -51,32 +53,83 @@ func AAC(raw []byte, _ uint32) bool { return len(raw) > 1 && ((raw[0] == 0xFF && raw[1] == 0xF1) || (raw[0] == 0xFF && raw[1] == 0xF9)) } -// Mp3 matches an mp3 file. -func Mp3(raw []byte, limit uint32) bool { +// MP3 matches a .mp3 file. +func MP3(raw []byte, limit uint32) bool { if len(raw) < 3 { return false } - if bytes.HasPrefix(raw, []byte("ID3")) { - // MP3s with an ID3v2 tag will start with "ID3" - // ID3v1 tags, however appear at the end of the file. + // Any ID3v2 is reported as MP3. Not entirely correct, but the mimesniff + // standard says so. https://mimesniff.spec.whatwg.org/#matching-an-audio-or-video-type-pattern + // Despite the standard only checking for "ID3", we do more validations to + // avoid false positives. + if id3v2(raw) { return true } - // Match MP3 files without tags + // If no ID3v2 tag found, then we will look for MP3 frames, but: + // a. Layer III files are a lot more prevalent than Layer I and II. + // b. Layer I frame header has looser constraints than the others: many files + // with regularly repeating 0xFFFF bytes can be misidentified as MP3. + // c. MP3 files are composed of individual frames and those frames can have + // leading garbage bytes: if we want to find all valid MP3s, we have to do a + // linear search. #775, #310 + // d. There are file formats that contain MP3s inside: .mo3 and .swa + // + // Given a, b, c and d, this code: + // - initially tries to match by first two bytes in header + // - checks for .mo3 and .swa and disqualifies them + // - does linear search for Layer III switch binary.BigEndian.Uint16(raw[:2]) & 0xFFFE { - case 0xFFFA: - // MPEG ADTS, layer III, v1 - return true - case 0xFFF2: - // MPEG ADTS, layer III, v2 - return true - case 0xFFE2: - // MPEG ADTS, layer III, v2.5 + case 0xFFFA, 0xFFF2, 0xFFE2, // layer III: v1, v2, v2.5 + 0xFFFC, 0xFFF4, // layer II: v1, v2 + 0xFFF5: // layer I: v2 return true } + // http://lclevy.free.fr/mo3/ + if bytes.HasPrefix(raw, []byte("MO3")) { + return false + } + + // From PRONOM: + // Macromedia licensed the MP3 technology in 1995 to use in their Shockwave + // product. .swa or Shockwave Audio was originally added as a free plugin + // (Xtras) to SoundEdit 16 to export AIFF files to .swa. + // There is no media type assigned for .swa. + if bytes.HasPrefix(raw, []byte{0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00}) { + return false + } + + _, size := mp3.ExtractFrame(raw) + return size > 0 +} + +// Based on https://id3.org/Developer%20Information. +func id3v2(raw []byte) bool { + if len(raw) < 10 || !bytes.HasPrefix(raw, []byte("ID3")) { + return false + } + if raw[3] < 2 || raw[3] > 4 { // Version: ID3v2.2 - ID3v2.4. + return false + } + if raw[4] != 0 { // Revision is 0 for all versions. + return false + } + + // v2.2 uses 2 bits, v2.3 uses 3 bits and v2.4 uses 4. + // For all versions least significant 4 bits should be 0 + if raw[5]&0b1111 != 0 { + return false + } + + // Size bytes are synchsafe: most significant bit always 0. + if raw[6]&0x80 != 0 || raw[7]&0x80 != 0 || raw[8]&0x80 != 0 || raw[9]&0x80 != 0 { + return false + } - return false + size := uint32(raw[6])<<21 | uint32(raw[7])<<14 | uint32(raw[8])<<7 | uint32(raw[9]) + // Disallow too big frames, let's say 10MB. + return size > 0 && size < 10*1024*1024 } // Wav matches a Waveform Audio File Format file. diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/binary.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/binary.go index 37ad6a9fb1..d217968735 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/binary.go +++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/binary.go @@ -4,6 +4,7 @@ import ( "bytes" "debug/macho" "encoding/binary" + "slices" ) // Lnk matches Microsoft lnk binary format. @@ -117,13 +118,7 @@ func Dbf(raw []byte, limit uint32) bool { 0x02, 0x03, 0x04, 0x05, 0x30, 0x31, 0x32, 0x42, 0x62, 0x7B, 0x82, 0x83, 0x87, 0x8A, 0x8B, 0x8E, 0xB3, 0xCB, 0xE5, 0xF5, 0xF4, 0xFB, } - for _, b := range dbfTypes { - if raw[0] == b { - return true - } - } - - return false + return slices.Contains(dbfTypes, raw[0]) } // ElfObj matches an object file. @@ -229,3 +224,63 @@ func TzIf(raw []byte, limit uint32) bool { // Version has to be NUL (0x00), '2' (0x32) or '3' (0x33). return raw[4] == 0x00 || raw[4] == 0x32 || raw[4] == 0x33 } + +// Pyc matches a Python compiled file. +// The signatures are sourced from libmagic v5.47 +func Pyc(raw []byte, limit uint32) bool { + if len(raw) < 8 { + return false + } + + // python 1.0 through 3.7 signatures, magic/Magdir/python:13:190 + pycMagic := []uint32{ + 0x02099900, 0x03099900, 0x892e0d0a, 0x04170d0a, 0x994e0d0a, 0xfcc40d0a, + 0xfdc40d0a, 0x87c60d0a, 0x88c60d0a, 0x2aeb0d0a, 0x2beb0d0a, 0x2ded0d0a, + 0x2eed0d0a, 0x3bf20d0a, 0x3cf20d0a, 0x45f20d0a, 0x59f20d0a, 0x63f20d0a, + 0x6df20d0a, 0x6ef20d0a, 0x77f20d0a, 0x81f20d0a, 0x8bf20d0a, 0x8cf20d0a, + 0x95f20d0a, 0x9ff20d0a, 0xa9f20d0a, 0xb3f20d0a, 0xb4f20d0a, 0xc7f20d0a, + 0xd1f20d0a, 0xd2f20d0a, 0xdbf20d0a, 0xe5f20d0a, 0xeff20d0a, 0xf9f20d0a, + 0x03f30d0a, 0x04f30d0a, 0x0af30d0a, 0xb80b0d0a, 0xc20b0d0a, 0xcc0b0d0a, + 0xd60b0d0a, 0xe00b0d0a, 0xea0b0d0a, 0xf40b0d0a, 0xf50b0d0a, 0xff0b0d0a, + 0x090c0d0a, 0x130c0d0a, 0x1d0c0d0a, 0x1f0c0d0a, 0x270c0d0a, 0x3b0c0d0a, + 0x450c0d0a, 0x4f0c0d0a, 0x580c0d0a, 0x620c0d0a, 0x6c0c0d0a, 0x760c0d0a, + 0x800c0d0a, 0x8a0c0d0a, 0x940c0d0a, 0x9e0c0d0a, 0xb20c0d0a, 0xbc0c0d0a, + 0xc60c0d0a, 0xd00c0d0a, 0xda0c0d0a, 0xe40c0d0a, 0xee0c0d0a, 0xf80c0d0a, + 0x020d0d0a, 0x0c0d0d0a, 0x160d0d0a, 0x170d0d0a, 0x200d0d0a, 0x210d0d0a, + 0x2a0d0d0a, 0x2b0d0d0a, 0x2c0d0d0a, 0x2d0d0d0a, 0x2f0d0d0a, 0x300d0d0a, + 0x310d0d0a, 0x320d0d0a, 0x330d0d0a, 0x3e0d0d0a, 0x3f0d0d0a, + } + + n := binary.BigEndian.Uint32(raw) + + if slices.Contains(pycMagic, n) { + return true + } + + if raw[2] == 0x0d && raw[3] == 0x0a { + // Only two bits of flag field are currently used. + if l := binary.LittleEndian.Uint32(raw[4:]); l > 3 { + return false + } + if raw[1] == 0x0d || raw[1] == 0x0e { + return true + } + // PyPy magic numbers, magic/Magdir/python:233 + n := binary.LittleEndian.Uint16(raw) + return n == 240 || n == 256 || n == 336 || n == 384 || n == 416 + } + + return false +} + +// Pcap identifies "libpcap" capture files. +// https://www.tcpdump.org/manpages/pcap-savefile.5.html +func Pcap(raw []byte, _ uint32) bool { + if len(raw) < 4 { + return false + } + be := binary.BigEndian.Uint32(raw) + le := binary.LittleEndian.Uint32(raw) + return be == 0xa1b2c3d4 || be == 0xa1b23c4d || + le == 0xa1b2c3d4 || le == 0xa1b23c4d +} diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/font.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/font.go index e1dda7cf06..9a76d5340f 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/font.go +++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/font.go @@ -3,6 +3,7 @@ package magic import ( "bytes" "encoding/binary" + "slices" ) // Woff matches a Web Open Font Format file. @@ -29,12 +30,13 @@ func Ttf(raw []byte, limit uint32) bool { if !bytes.HasPrefix(raw, []byte{0x00, 0x01, 0x00, 0x00}) { return false } + // We cannot rely on the first 4 bytes because of false-positives. + // We have to digg deeper into the SFNT tables. return hasSFNTTable(raw) } func hasSFNTTable(raw []byte) bool { - // 49 possible tables as explained below - if len(raw) < 16 || binary.BigEndian.Uint16(raw[4:]) >= 49 { + if len(raw) < 16 { return false } @@ -87,14 +89,45 @@ func hasSFNTTable(raw []byte) bool { 0x6e616d65, // "name" 0x6f706264, // "opbd" 0x4f532f32, // "OS/2" + // The above tables come from the original Apple TTF specification, + // but the later Microsoft specification has additional tables. + // Common tables: https://learn.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats + // Layout tables: https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2 + // Even if the Microsoft specification says OpenType, the tables are + // valid for TrueType as well. + 0x47535542, // "GSUB" + 0x47504f53, // "GPOS" + 0x42415345, // "BASE" + 0x4a535446, // "JSTF" + 0x47444546, // "GDEF" + 0x4d415448, // "MATH" + 0x43424454, // "CBDT" + 0x43424c43, // "CBLC" + 0x43464620, // "CFF " + 0x43464632, // "CFF2" + 0x434f4c52, // "COLR" + 0x4350414c, // "CPAL" + 0x44534947, // "DSIG" + 0x45424454, // "EBDT" + 0x45424c43, // "EBLC" + 0x48564152, // "HVAR" + 0x4c545348, // "LTSH" + 0x4d455247, // "MERG" + 0x4d564152, // "MVAR" + 0x50434c54, // "PCLT" + 0x706f7374, // "post" + 0x70726570, // "prep" + 0x73626978, // "sbix" + 0x53544154, // "STAT" + 0x53564720, // "SVG " + 0x56444d58, // "VDMX" + 0x76686561, // "vhea" + 0x766d7478, // "vmtx" + 0x564f5247, // "VORG" + 0x56564152, // "VVAR" } ourTable := binary.BigEndian.Uint32(raw[12:16]) - for _, t := range possibleTables { - if ourTable == t { - return true - } - } - return false + return slices.Contains(possibleTables, ourTable) } // Eot matches an Embedded OpenType font file. diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/geo.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/geo.go index cade91f18c..6cd479cc40 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/geo.go +++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/geo.go @@ -3,6 +3,7 @@ package magic import ( "bytes" "encoding/binary" + "slices" ) // Shp matches a shape format file. @@ -39,13 +40,7 @@ func Shp(raw []byte, limit uint32) bool { 31, // MultiPatch } - for _, st := range shapeTypes { - if st == int(binary.LittleEndian.Uint32(raw[108:112])) { - return true - } - } - - return false + return slices.Contains(shapeTypes, int(binary.LittleEndian.Uint32(raw[108:112]))) } // Shx matches a shape index format file. diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/image.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/image.go index 3a86858684..d46faca737 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/image.go +++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/image.go @@ -4,6 +4,8 @@ import ( "bytes" "encoding/binary" "slices" + + "github.com/gabriel-vasile/mimetype/internal/scan" ) // Png matches a Portable Network Graphics file. @@ -15,7 +17,31 @@ func Png(raw []byte, _ uint32) bool { // Apng matches an Animated Portable Network Graphics file. // https://wiki.mozilla.org/APNG_Specification func Apng(raw []byte, _ uint32) bool { - return offset(raw, []byte("acTL"), 37) + b := scan.Bytes(raw) + b.Advance(8) // the first 8 bytes matched by regular png + + // PNG chunks are composed of: + // 4 bytes: length in big endian + // 4 bytes: chunk type + // length bytes: chunk data + // 4 bytes: CRC + // + // Limit to 32, so we don't waste time on huge inputs. + // acTL chunk must come before any IDAT chunks. + // https://www.w3.org/TR/png-3/#structure + for i := 0; i < 32 && len(b) > 0; i++ { + sz, _ := b.Uint32be() + if bytes.HasPrefix(b, []byte("acTL")) { + return true + } + if bytes.HasPrefix(b, []byte("IDAT")) { + return false + } + if !b.Advance(int(sz + 8)) { + return false + } + } + return false } // Jpg matches a Joint Photographic Experts Group file. diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/magic.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/magic.go index 6103c12d36..f078ff3322 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/magic.go +++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/magic.go @@ -136,6 +136,11 @@ func ftyp(raw []byte, sigs ...[]byte) bool { return false } +type shebangSig struct { + sig []byte + flag scan.Flags +} + // A valid shebang starts with the "#!" characters, // followed by any number of spaces, // followed by the path to the interpreter, @@ -146,7 +151,7 @@ func ftyp(raw []byte, sigs ...[]byte) bool { // #! /usr/bin/env php // // /usr/bin/env is the interpreter, php is the first and only argument. -func shebang(b scan.Bytes, matchFlags scan.Flags, sigs ...[]byte) bool { +func shebang(b scan.Bytes, sigs ...shebangSig) bool { line := b.Line() if len(line) < 2 || line[0] != '#' || line[1] != '!' { return false @@ -154,7 +159,7 @@ func shebang(b scan.Bytes, matchFlags scan.Flags, sigs ...[]byte) bool { line = line[2:] line.TrimLWS() for _, s := range sigs { - if line.Match(s, matchFlags) != -1 { + if line.Match(s.sig, s.flag) != -1 { return true } } diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/ms_office.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/ms_office.go index e689e92a36..62cced078c 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/ms_office.go +++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/ms_office.go @@ -3,6 +3,8 @@ package magic import ( "bytes" "encoding/binary" + + "github.com/gabriel-vasile/mimetype/internal/cdf" ) // Xlsx matches a Microsoft Excel 2007 file. @@ -47,6 +49,15 @@ func Ole(raw []byte, limit uint32) bool { // Doc matches a Microsoft Word 97-2003 file. // See: https://github.com/decalage2/oletools/blob/412ee36ae45e70f42123e835871bac956d958461/oletools/common/clsid.py func Doc(raw []byte, _ uint32) bool { + fromParsing := cdf.Detect(raw) + if fromParsing == cdf.CDFTypeDoc { + return true + } + if fromParsing != cdf.CDFTypeGeneric { + return false + } + // Fallback for inputs where the CDF directory is past the read limit: match + // the root storage CLSID, which often lies within the first sectors. clsids := [][]byte{ // Microsoft Word 97-2003 Document (Word.Document.8) {0x06, 0x09, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}, @@ -55,19 +66,25 @@ func Doc(raw []byte, _ uint32) bool { // Microsoft Word Picture (Word.Picture.8) {0x07, 0x09, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}, } - for _, clsid := range clsids { if matchOleClsid(raw, clsid) { return true } } - return false } // Ppt matches a Microsoft PowerPoint 97-2003 file or a PowerPoint 95 presentation. func Ppt(raw []byte, limit uint32) bool { - // Root CLSID test is the safest way to detect identify OLE, however, the format + fromParsing := cdf.Detect(raw) + if fromParsing == cdf.CDFTypePpt { + return true + } + if fromParsing != cdf.CDFTypeGeneric { + return false + } + // Fallback for inputs where the CDF directory is past the read limit. + // Root CLSID test is the safest way to identify the OLE, however, the format // often places the root CLSID at the end of the file. if matchOleClsid(raw, []byte{ 0x10, 0x8d, 0x81, 0x64, 0x9b, 0x4f, 0xcf, 0x11, @@ -94,18 +111,21 @@ func Ppt(raw []byte, limit uint32) bool { } } - if bytes.HasPrefix(raw[512:], []byte{0xFD, 0xFF, 0xFF, 0xFF}) && - raw[518] == 0x00 && raw[519] == 0x00 { - return true - } - return lin > 1152 && bytes.Contains(raw[1152:min(4096, lin)], []byte("P\x00o\x00w\x00e\x00r\x00P\x00o\x00i\x00n\x00t\x00 D\x00o\x00c\x00u\x00m\x00e\x00n\x00t")) } // Xls matches a Microsoft Excel 97-2003 file. func Xls(raw []byte, limit uint32) bool { - // Root CLSID test is the safest way to detect identify OLE, however, the format + fromParsing := cdf.Detect(raw) + if fromParsing == cdf.CDFTypeXls { + return true + } + if fromParsing != cdf.CDFTypeGeneric { + return false + } + // Fallback for inputs where the CDF directory is past the read limit. + // Root CLSID test is the safest way to identify the OLE, however, the format // often places the root CLSID at the end of the file. if matchOleClsid(raw, []byte{ 0x10, 0x08, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -148,6 +168,15 @@ func Pub(raw []byte, limit uint32) bool { // Msg matches a Microsoft Outlook email file. func Msg(raw []byte, limit uint32) bool { + fromParsing := cdf.Detect(raw) + if fromParsing == cdf.CDFTypeMsg { + return true + } + if fromParsing != cdf.CDFTypeGeneric { + return false + } + // Fallback for inputs where the CDF directory does not carry the streams the + // parser keys on: match the root storage CLSID instead. return matchOleClsid(raw, []byte{ 0x0B, 0x0D, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, @@ -157,10 +186,7 @@ func Msg(raw []byte, limit uint32) bool { // Msi matches a Microsoft Windows Installer file. // http://fileformats.archiveteam.org/wiki/Microsoft_Compound_File func Msi(raw []byte, limit uint32) bool { - return matchOleClsid(raw, []byte{ - 0x84, 0x10, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, - }) + return cdf.Detect(raw) == cdf.CDFTypeInstaller } // One matches a Microsoft OneNote file. diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/text.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/text.go index 3fa6711813..d36dc3ccd4 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/text.go +++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/text.go @@ -130,6 +130,14 @@ func Xfdf(raw []byte, _ uint32) bool { return xml(raw, xmlSig{[]byte(" 0 && uint64(len(in)) >= uint64(limit) + if maybeTruncated && fields < headerFields { + // Allow the last row to have any number of fields + // if the input is maybeTruncated. + // BUG: if len(input) == limit, then the input is not truncated + // but it is still allowed to have the wrong number of fields + // and it will be reported as valid CSV. + if len(s) == 0 { + break + } + } return false } if csvLines >= 10 { diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/video.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/video.go index 23e30da2b9..a730a24543 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/video.go +++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/video.go @@ -49,10 +49,8 @@ func isMatroskaFileTypeMatched(in []byte, flType string) bool { // The logic of search is: find first instance of \x42\x82 and then // search for given string after n bytes of above instance. func isFileTypeNamePresent(in []byte, flType string) bool { - ind, maxInd, lenIn := 0, 4096, len(in) - if lenIn < maxInd { // restricting length to 4096 - maxInd = lenIn - } + ind, lenIn := 0, len(in) + maxInd := min(4096, lenIn) ind = bytes.Index(in[:maxInd], []byte("\x42\x82")) if ind > 0 && lenIn > ind+2 { ind += 2 diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/zip.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/zip.go index f3bfa2ac37..9b2611bbb6 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/zip.go +++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/zip.go @@ -187,11 +187,11 @@ func msoxml(raw scan.Bytes, searchFor zipEntries, stopAfter int) bool { return false } +var zipLocalFileHeader = []byte("PK\003\004") + // next extracts the name of the next zip entry. func (i *zipIterator) next() []byte { - pk := []byte("PK\003\004") - - n := bytes.Index(i.b, pk) + n := bytes.Index(i.b, zipLocalFileHeader) if n == -1 { return nil } @@ -212,10 +212,85 @@ func (i *zipIterator) next() []byte { return i.b[:l] } +// skipZipflingerEntry tries to detect a Zipflinger virtual entry and skips it. +// The detection is based on the following properties: +// - compression method is 0 +// - CRC32 is 0 +// - compressed size is 0 +// - uncompressed size is 0 +// - file name is empty +// Returns true if it was found and skipped. +func (i *zipIterator) skipZipflingerEntry() (skipped bool) { + // Make a backup of the data so the inspection does not loses it. + b := i.b + defer func() { + // If no zipflinger was found, restore the original data. + if !skipped { + i.b = b + } + }() + + n := bytes.Index(i.b, zipLocalFileHeader) + if n == -1 { + return false + } + if !i.b.Advance(0x08) { + return false + } + + // Check compression method + if cm, ok := i.b.Uint16(); !ok || cm != 0 { + return false + } + + // Advance up to the CRC32 field + if !i.b.Advance(0x04) { + return false + } + + // Check CRC32 + if crc32, ok := i.b.Uint32(); !ok || crc32 != 0 { + return false + } + + // Check compressed size + if compressedSize, ok := i.b.Uint32(); !ok || compressedSize != 0 { + return false + } + + // Check uncompressed size + if uncompressedSize, ok := i.b.Uint32(); !ok || uncompressedSize != 0 { + return false + } + + // Check for empty file name + if l, ok := i.b.Uint16(); !ok || l != 0 { + return false + } + + // Reached a zipflinger virtual entry: skip extra data + l, ok := i.b.Uint16() + if !ok { + return false + } + + if !i.b.Advance(int(l)) { + return false + } + return true +} + // APK matches an Android Package Archive. // The source of signatures is https://github.com/file/file/blob/1778642b8ba3d947a779a36fcd81f8e807220a19/magic/Magdir/archive#L1820-L1887 func APK(raw []byte, _ uint32) bool { - return zipHas(raw, zipEntries{{ + iter := zipIterator{raw} + + // If a Zipflinger Virtual Entry is detected, then the data is considered APK + if iter.skipZipflingerEntry() { + return true + } + + return zipHas(iter.b, zipEntries{{ name: []byte("AndroidManifest.xml"), }, { name: []byte("META-INF/com/android/build/gradle/app-metadata.properties"), diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/mp3/frame.go b/vendor/github.com/gabriel-vasile/mimetype/internal/mp3/frame.go new file mode 100644 index 0000000000..e1ea6f25e7 --- /dev/null +++ b/vendor/github.com/gabriel-vasile/mimetype/internal/mp3/frame.go @@ -0,0 +1,140 @@ +package mp3 + +import "bytes" + +// minTruncatedSyncMatches is the minimum number of confirmed successive +// header matches required to accept a candidate frame when the buffer ends +// before maxFrameSyncMatches confirmations can be performed. +const minTruncatedSyncMatches = 2 + +func ExtractFrame(b []byte) (start, size int) { + limit := min(len(b), 2048+headerSize) + for i := 0; i < limit-headerSize; i++ { + j := bytes.IndexByte(b[i:limit-headerSize], 0xFF) + if j < 0 { + break + } + i += j + hdr := header{b[i], b[i+1], b[i+2], b[i+3]} + if !hdr.valid() { + continue + } + frameBytes := hdr.frameBytes() + frameAndPad := frameBytes + hdr.padding() + + validHere := frameBytes > 0 && i+frameAndPad <= len(b) && matchFrame(b[i:]) + // When the buffer is exactly one frame, matchFrame cannot look ahead for + // a subsequent header to confirm the stream. Trust the validated header. + exact := i == 0 && frameAndPad == len(b) + if validHere || exact { + return i, frameAndPad + } + } + return 0, 0 +} + +// matchFrame confirms a candidate header by stepping forward and checking that +// subsequent headers are consistent. +func matchFrame(buf []byte) bool { + // maxFrameSyncMatches limits how many valid frames we look at. + const maxFrameSyncMatches = 10 + hdr := header{buf[0], buf[1], buf[2], buf[3]} + i := hdr.frameBytes() + hdr.padding() + for nmatch := 0; nmatch < maxFrameSyncMatches; nmatch++ { + if i+headerSize > len(buf) { + return nmatch >= minTruncatedSyncMatches + } + cmp := header{buf[i], buf[i+1], buf[i+2], buf[i+3]} + if !hdr.compatibleWith(cmp) { + return false + } + i += cmp.frameBytes() + cmp.padding() + } + return true +} + +const headerSize = 4 + +type header [headerSize]byte + +func (h header) isFreeFormat() bool { return h[2]&0xF0 == 0 } +func (h header) isMPEG1() bool { return h[1]&0x8 != 0 } +func (h header) isMPEG25() bool { return h[1]&0x10 == 0 } +func (h header) rawLayer() byte { return h[1] >> 1 & 3 } +func (h header) rawBitrate() byte { return h[2] >> 4 } +func (h header) rawSampleRate() byte { return h[2] >> 2 & 3 } +func (h header) rawEmphasis() byte { return h[3] & 0b11 } +func (h header) isFrame576() bool { return h[1]&14 == 2 } +func (h header) padding() int { + if h[2]&0x2 != 0 { + return 1 + } + return 0 +} + +// valid reports whether the four bytes form a syntactically valid MP3 header. +func (h header) valid() bool { + return h[0] == 0xff && + ((h[1]&0xF0) == 0xf0 || (h[1]&0xFE) == 0xe2) && + h.rawLayer() == 1 && // Layer III + h.rawBitrate() != 15 && // Not allowed by spec. + h.rawSampleRate() != 3 && + h.rawEmphasis() != 2 && + // The code for extracting frame size for free-format is tedious and + // free-format MP3s are extinct. + !h.isFreeFormat() +} + +// compatibleWith reports whether two headers describe frames belonging to the +// same MP3 stream — same MPEG version, layer, sample-rate index. +func (h header) compatibleWith(o header) bool { + return o.valid() && + (h[1]^o[1])&0xFE == 0 && + (h[2]^o[2])&0x0C == 0 +} + +// bitrateKbps returns the bitrate of the frame in kilobits per second. +func (h header) bitrateKbps() int { + // halfrate[mpeg1?][bitrate_idx] holds bitrate/2 in kbps. + halfrate := [2][15]uint8{ + {0, 4, 8, 12, 16, 20, 24, 28, 32, 40, 48, 56, 64, 72, 80}, + {0, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160}, + } + mpeg1 := 0 + if h.isMPEG1() { + mpeg1 = 1 + } + return 2 * int(halfrate[mpeg1][h.rawBitrate()]) +} + +// sampleRateHz returns the sampling rate of the frame in Hz. +func (h header) sampleRateHz() int { + base := [3]int{44100, 48000, 32000}[h.rawSampleRate()] + if !h.isMPEG1() { + base >>= 1 + } + if h.isMPEG25() { + base >>= 1 + } + return base +} + +// frameSamples returns the number of audio samples per channel encoded in +// the frame. +func (h header) frameSamples() int { + if h.isFrame576() { + return 576 + } + return 1152 +} + +// frameBytes returns the size of the frame body (header + side info + audio +// data, excluding padding) in bytes. +func (h header) frameBytes() int { + br := h.bitrateKbps() + sr := h.sampleRateHz() + if br == 0 || sr == 0 { + return 0 + } + return h.frameSamples() * br * 125 / sr +} diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/scan/bytes.go b/vendor/github.com/gabriel-vasile/mimetype/internal/scan/bytes.go index 552b4ead90..0503719f3e 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/internal/scan/bytes.go +++ b/vendor/github.com/gabriel-vasile/mimetype/internal/scan/bytes.go @@ -122,32 +122,30 @@ func (b *Bytes) Line() Bytes { return line } -// DropLastLine drops the last incomplete line from b. -// -// mimetype limits itself to ReadLimit bytes when performing a detection. -// This means, for file formats like CSV for NDJSON, the last line of the input -// can be an incomplete line. -// If b length is less than readLimit, it means we received an incomplete file -// and proceed with dropping the last line. -func (b *Bytes) DropLastLine(readLimit uint32) { - if readLimit == 0 || uint64(len(*b)) < uint64(readLimit) { - return +func (b *Bytes) Uint16() (uint16, bool) { + if len(*b) < 2 { + return 0, false } + v := binary.LittleEndian.Uint16(*b) + *b = (*b)[2:] + return v, true +} - for i := len(*b) - 1; i > 0; i-- { - if (*b)[i] == '\n' { - *b = (*b)[:i] - return - } +func (b *Bytes) Uint32() (uint32, bool) { + if len(*b) < 4 { + return 0, false } + v := binary.LittleEndian.Uint32(*b) + *b = (*b)[4:] + return v, true } -func (b *Bytes) Uint16() (uint16, bool) { - if len(*b) < 2 { +func (b *Bytes) Uint32be() (uint32, bool) { + if len(*b) < 4 { return 0, false } - v := binary.LittleEndian.Uint16(*b) - *b = (*b)[2:] + v := binary.BigEndian.Uint32(*b) + *b = (*b)[4:] return v, true } @@ -205,10 +203,8 @@ func (b Bytes) Match(p []byte, flags Flags) int { if l == 0 { return -1 } - // If no flags, or scanning for full word at the end of pattern then - // do a fast HasPrefix check. - // For other flags it's not possible to use HasPrefix. - if flags == 0 || flags&FullWord > 0 { + // Some cases we can handle with a simple bytes.HasPrefix. + if flags == 0 || flags == FullWord { if bytes.HasPrefix(b, p) { b = b[len(p):] p = p[len(p):] @@ -232,7 +228,7 @@ func (b Bytes) Match(p []byte, flags Flags) int { return -1 } b = b[1:] - if !ByteIsWS(p[0]) { + if len(p) > 0 && !ByteIsWS(p[0]) { b.TrimLWS() } } else { diff --git a/vendor/github.com/gabriel-vasile/mimetype/mime.go b/vendor/github.com/gabriel-vasile/mimetype/mime.go index 30c41ac04c..5382fb1c8a 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/mime.go +++ b/vendor/github.com/gabriel-vasile/mimetype/mime.go @@ -23,6 +23,14 @@ type MIME struct { } // String returns the string representation of the MIME type, e.g., "application/zip". +// String return values can change between releases, for example, when [IANA] +// assigns a new media type. Use [MIME.Is] to avoid breaking changes. +// +// mtype := mimetype.Detect(zipFile) +// if mtype.String() == "application/zip" { /* Plain string comparison is brittle. */ } +// if mtype.Is("application/zip") { /* Will continue to work between releases */ } +// +// [IANA]: https://www.iana.org/assignments/media-types/media-types.xhtml func (m *MIME) String() string { return m.mime } @@ -38,17 +46,19 @@ func (m *MIME) Extension() string { // Each MIME type has a non-nil parent, except for the root MIME type. // // For example, the application/json and text/html MIME types have text/plain as -// their parent because they are text files who happen to contain JSON or HTML. +// their parent because they are text files that happen to contain JSON or HTML. // Another example is the ZIP format, which is used as container // for Microsoft Office files, EPUB files, JAR files, and others. func (m *MIME) Parent() *MIME { return m.parent } -// Is checks whether this MIME type, or any of its aliases, is equal to the +// Is checks whether this MIME type, or any of its [aliases], is equal to the // expected MIME type. MIME type equality test is done on the "type/subtype" // section, ignores any optional MIME parameters, ignores any leading and // trailing whitespace, and is case insensitive. +// +// [aliases]: https://github.com/gabriel-vasile/mimetype/blob/master/supported_mimes.md func (m *MIME) Is(expectedMIME string) bool { // Parsing is needed because some detected MIME types contain parameters // that need to be stripped for the comparison. @@ -129,7 +139,7 @@ func (m *MIME) flatten() []*MIME { // hierarchy returns an easy to read list of ancestors for m. // For example, application/json would return json>txt>root. func (m *MIME) hierarchy() string { - h := "" + var h strings.Builder for m := m; m != nil; m = m.Parent() { e := strings.TrimPrefix(m.Extension(), ".") if e == "" { @@ -142,9 +152,9 @@ func (m *MIME) hierarchy() string { e = "root" } } - h += ">" + e + h.WriteString(">" + e) } - return strings.TrimPrefix(h, ">") + return strings.TrimPrefix(h.String(), ">") } // clone creates a new MIME with the provided optional MIME parameters. diff --git a/vendor/github.com/gabriel-vasile/mimetype/mimetype.go b/vendor/github.com/gabriel-vasile/mimetype/mimetype.go index 792741732b..e6f1e77a49 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/mimetype.go +++ b/vendor/github.com/gabriel-vasile/mimetype/mimetype.go @@ -1,6 +1,6 @@ // Package mimetype uses magic number signatures to detect the MIME type of a file. // -// File formats are stored in a hierarchy with application/octet-stream at its root. +// File formats are stored in a hierarchy with "application/octet-stream" at its root. // For example, the hierarchy for HTML format is application/octet-stream -> // text/plain -> text/html. package mimetype @@ -12,14 +12,14 @@ import ( "sync/atomic" ) -const defaultLimit uint32 = 3072 +const defaultLimit uint32 = 4096 // readLimit is the maximum number of bytes from the input used when detecting. var readLimit uint32 = defaultLimit // Detect returns the MIME type found from the provided byte slice. // -// The result is always a valid MIME type, with application/octet-stream +// The result is always a valid MIME type, with "application/octet-stream" // returned when identification failed. func Detect(in []byte) *MIME { // Using atomic because readLimit can be written at the same time in other goroutine. @@ -34,7 +34,7 @@ func Detect(in []byte) *MIME { // DetectReader returns the MIME type of the provided reader. // -// The result is always a valid MIME type, with application/octet-stream +// The result is always a valid MIME type, with "application/octet-stream" // returned when identification failed with or without an error. // Any error returned is related to the reading from the input reader. // @@ -72,7 +72,7 @@ func DetectReader(r io.Reader) (*MIME, error) { // DetectFile returns the MIME type of the provided file. // -// The result is always a valid MIME type, with application/octet-stream +// The result is always a valid MIME type, with "application/octet-stream" // returned when identification failed with or without an error. // Any error returned is related to the opening and reading from the input file. func DetectFile(path string) (*MIME, error) { @@ -112,7 +112,7 @@ func SetLimit(limit uint32) { } // Extend adds detection for other file formats. -// It is equivalent to calling Extend() on the root MIME type "application/octet-stream". +// It is equivalent to calling [MIME.Extend] on the root MIME type "application/octet-stream". func Extend(detector func(raw []byte, limit uint32) bool, mime, extension string, aliases ...string) { root.Extend(detector, mime, extension, aliases...) } diff --git a/vendor/github.com/gabriel-vasile/mimetype/supported_mimes.md b/vendor/github.com/gabriel-vasile/mimetype/supported_mimes.md index 79a3617fcf..f014ff4b50 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/supported_mimes.md +++ b/vendor/github.com/gabriel-vasile/mimetype/supported_mimes.md @@ -1,4 +1,4 @@ -## 199 Supported MIME types +## 204 Supported MIME types This file is automatically generated when running tests. Do not edit manually. Extension | MIME type
Aliases | Hierarchy @@ -42,7 +42,7 @@ Extension | MIME type
Aliases | Hierarchy **.oga** | **audio/ogg** | oga>ogg>root **.ogv** | **video/ogg** | ogv>ogg>root **.png** | **image/png** | png>root -**.png** | **image/vnd.mozilla.apng** | png>png>root +**.apng** | **image/apng**
image/vnd.mozilla.apng | apng>png>root **.jpg** | **image/jpeg** | jpg>root **.jxl** | **image/jxl** | jxl>root **.jp2** | **image/jp2** | jp2>root @@ -67,7 +67,6 @@ Extension | MIME type
Aliases | Hierarchy **.bmp** | **image/bmp**
image/x-bmp, image/x-ms-bmp | bmp>root **.123** | **application/vnd.lotus-1-2-3** | 123>root **.ico** | **image/x-icon** | ico>root -**.mp3** | **audio/mpeg**
audio/x-mpeg, audio/mp3 | mp3>root **.flac** | **audio/flac** | flac>root **.midi** | **audio/midi**
audio/mid, audio/sp-midi, audio/x-mid, audio/x-midi | midi>root **.ape** | **audio/ape** | ape>root @@ -95,7 +94,7 @@ Extension | MIME type
Aliases | Hierarchy **.webm** | **video/webm**
audio/webm | webm>root **.avi** | **video/x-msvideo**
video/avi, video/msvideo | avi>root **.flv** | **video/x-flv** | flv>root -**.mkv** | **video/x-matroska** | mkv>root +**.mkv** | **video/matroska**
video/x-matroska | mkv>root **.asf** | **video/x-ms-asf**
video/asf, video/x-ms-wmv | asf>root **.aac** | **audio/aac** | aac>root **.voc** | **audio/x-unknown** | voc>root @@ -116,7 +115,7 @@ Extension | MIME type
Aliases | Hierarchy **.shp** | **application/vnd.shp** | shp>shx>root **.dbf** | **application/x-dbf** | dbf>root **.dcm** | **application/dicom** | dcm>root -**.rar** | **application/x-rar-compressed**
application/x-rar | rar>root +**.rar** | **application/vnd.rar**
application/x-rar-compressed, application/x-rar | rar>root **.djvu** | **image/vnd.djvu** | djvu>root **.mobi** | **application/x-mobipocket-ebook** | mobi>root **.lit** | **application/x-ms-reader** | lit>root @@ -158,6 +157,9 @@ Extension | MIME type
Aliases | Hierarchy **.hlp** | **application/x-os2-hlp** | hlp>root **.fm** | **application/vnd.framemaker** | fm>root **.bufr** | **application/bufr** | bufr>root +**.pyc** | **application/x-bytecode.python** | pyc>root +**.pcap** | **application/vnd.tcpdump.pcap** | pcap>root +**.mp3** | **audio/mpeg**
audio/x-mpeg, audio/mp3 | mp3>root **.txt** | **text/plain** | txt>root **.svg** | **image/svg+xml** | svg>txt>root **.html** | **text/html** | html>txt>root @@ -176,6 +178,7 @@ Extension | MIME type
Aliases | Hierarchy **.xfdf** | **application/vnd.adobe.xfdf** | xfdf>xml>txt>root **.owl** | **application/owl+xml** | owl>xml>txt>root **.html** | **application/xhtml+xml** | html>xml>txt>root +**.xml** | **application/vnd.cyclonedx+xml** | xml>xml>txt>root **.php** | **text/x-php** | php>txt>root **.js** | **text/javascript**
application/x-javascript, application/javascript | js>txt>root **.lua** | **text/x-lua** | lua>txt>root @@ -186,6 +189,7 @@ Extension | MIME type
Aliases | Hierarchy **.geojson** | **application/geo+json** | geojson>json>txt>root **.har** | **application/json** | har>json>txt>root **.gltf** | **model/gltf+json** | gltf>json>txt>root +**.json** | **application/vnd.cyclonedx+json** | json>json>txt>root **.ndjson** | **application/x-ndjson** | ndjson>txt>root **.rtf** | **text/rtf**
application/rtf | rtf>txt>root **.srt** | **application/x-subrip**
application/x-srt, text/x-srt | srt>txt>root @@ -202,3 +206,4 @@ Extension | MIME type
Aliases | Hierarchy **.ppm** | **image/x-portable-pixmap** | ppm>txt>root **.pam** | **image/x-portable-arbitrarymap** | pam>txt>root **.eml** | **message/rfc822** | eml>txt>root +**.ged** | **text/vnd.familysearch.gedcom** | ged>txt>root diff --git a/vendor/github.com/gabriel-vasile/mimetype/tree.go b/vendor/github.com/gabriel-vasile/mimetype/tree.go index 55023baef6..bec8b43f21 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/tree.go +++ b/vendor/github.com/gabriel-vasile/mimetype/tree.go @@ -19,12 +19,16 @@ var root = newMIME("application/octet-stream", "", func([]byte, uint32) bool { return true }, xpm, sevenZ, zip, pdf, fdf, ole, ps, psd, p7s, ogg, png, jpg, jxl, jp2, jpx, jpm, jxs, gif, webp, exe, elf, ar, tar, xar, bz2, fits, tiff, bmp, lotus, ico, - mp3, flac, midi, ape, musePack, amr, wav, aiff, au, mpeg, quickTime, mp4, webM, + flac, midi, ape, musePack, amr, wav, aiff, au, mpeg, quickTime, mp4, webM, avi, flv, mkv, asf, aac, voc, m3u, rmvb, gzip, class, swf, crx, ttf, woff, woff2, otf, ttc, eot, wasm, shx, dbf, dcm, rar, djvu, mobi, lit, bpg, cbor, sqlite3, dwg, nes, lnk, macho, qcp, icns, hdr, mrc, mdb, accdb, zstd, cab, rpm, xz, lzip, torrent, cpio, tzif, xcf, pat, gbr, glb, cabIS, jxr, parquet, - oneNote, chm, wpd, dxf, grib, zlib, inf, hlp, fm, bufr, + oneNote, chm, wpd, dxf, grib, zlib, inf, hlp, fm, bufr, pyc, pcap, + // MP3 is late because it does a linear search in the input. That means + // containers that embed an MP3, for example: an mp4 file, or a zip without + // compression, would pass as MP3s. + mp3, // Keep text last because it is the slowest check. text, ) @@ -82,16 +86,17 @@ var ( alias("application/x-ogg") oggAudio = newMIME("audio/ogg", ".oga", magic.OggAudio) oggVideo = newMIME("video/ogg", ".ogv", magic.OggVideo) - text = newMIME("text/plain", ".txt", magic.Text, svg, html, xml, php, js, lua, perl, python, ruby, json, ndJSON, rtf, srt, tcl, csv, tsv, vCard, iCalendar, warc, vtt, shell, netpbm, netpgm, netppm, netpam, rfc822) - xml = newMIME("text/xml", ".xml", magic.XML, rss, atom, x3d, kml, xliff, collada, gml, gpx, tcx, amf, threemf, xfdf, owl2, xhtml). + text = newMIME("text/plain", ".txt", magic.Text, svg, html, xml, php, js, lua, perl, python, ruby, json, ndJSON, rtf, srt, tcl, csv, tsv, vCard, iCalendar, warc, vtt, shell, netpbm, netpgm, netppm, netpam, rfc822, gedcom) + xml = newMIME("text/xml", ".xml", magic.XML, rss, atom, x3d, kml, xliff, collada, gml, gpx, tcx, amf, threemf, xfdf, owl2, xhtml, cdxxml). alias("application/xml") xhtml = newMIME("application/xhtml+xml", ".html", magic.XHTML) - json = newMIME("application/json", ".json", magic.JSON, geoJSON, har, gltf) + json = newMIME("application/json", ".json", magic.JSON, geoJSON, har, gltf, cdxJSON) har = newMIME("application/json", ".har", magic.HAR) csv = newMIME("text/csv", ".csv", magic.CSV) tsv = newMIME("text/tab-separated-values", ".tsv", magic.TSV) geoJSON = newMIME("application/geo+json", ".geojson", magic.GeoJSON) ndJSON = newMIME("application/x-ndjson", ".ndjson", magic.NdJSON) + cdxJSON = newMIME("application/vnd.cyclonedx+json", ".json", magic.CDXJSON) html = newMIME("text/html", ".html", magic.HTML) php = newMIME("text/x-php", ".php", magic.Php) rtf = newMIME("text/rtf", ".rtf", magic.Rtf).alias("application/rtf") @@ -104,6 +109,7 @@ var ( perl = newMIME("text/x-perl", ".pl", magic.Perl) python = newMIME("text/x-python", ".py", magic.Python). alias("text/x-script.python", "application/x-python") + pyc = newMIME("application/x-bytecode.python", ".pyc", magic.Pyc) ruby = newMIME("text/x-ruby", ".rb", magic.Ruby). alias("application/x-ruby") shell = newMIME("text/x-shellscript", ".sh", magic.Shell). @@ -127,13 +133,15 @@ var ( tcx = newMIME("application/vnd.garmin.tcx+xml", ".tcx", magic.Tcx) amf = newMIME("application/x-amf", ".amf", magic.Amf) threemf = newMIME("application/vnd.ms-package.3dmanufacturing-3dmodel+xml", ".3mf", magic.Threemf) + cdxxml = newMIME("application/vnd.cyclonedx+xml", ".xml", magic.CDXXML) png = newMIME("image/png", ".png", magic.Png, apng) - apng = newMIME("image/vnd.mozilla.apng", ".png", magic.Apng) - jpg = newMIME("image/jpeg", ".jpg", magic.Jpg) - jxl = newMIME("image/jxl", ".jxl", magic.Jxl) - jp2 = newMIME("image/jp2", ".jp2", magic.Jp2) - jpx = newMIME("image/jpx", ".jpf", magic.Jpx) - jpm = newMIME("image/jpm", ".jpm", magic.Jpm). + apng = newMIME("image/apng", ".apng", magic.Apng). + alias("image/vnd.mozilla.apng") + jpg = newMIME("image/jpeg", ".jpg", magic.Jpg) + jxl = newMIME("image/jxl", ".jxl", magic.Jxl) + jp2 = newMIME("image/jp2", ".jp2", magic.Jp2) + jpx = newMIME("image/jpx", ".jpf", magic.Jpx) + jpm = newMIME("image/jpm", ".jpm", magic.Jpm). alias("video/jpm") jxs = newMIME("image/jxs", ".jxs", magic.Jxs) xpm = newMIME("image/x-xpixmap", ".xpm", magic.Xpm) @@ -156,7 +164,7 @@ var ( heifSeq = newMIME("image/heif-sequence", ".heif", magic.HeifSequence) hdr = newMIME("image/vnd.radiance", ".hdr", magic.Hdr) avif = newMIME("image/avif", ".avif", magic.AVIF) - mp3 = newMIME("audio/mpeg", ".mp3", magic.Mp3). + mp3 = newMIME("audio/mpeg", ".mp3", magic.MP3). alias("audio/x-mpeg", "audio/mp3") flac = newMIME("audio/flac", ".flac", magic.Flac) midi = newMIME("audio/midi", ".midi", magic.Midi). @@ -192,7 +200,8 @@ var ( avi = newMIME("video/x-msvideo", ".avi", magic.Avi). alias("video/avi", "video/msvideo") flv = newMIME("video/x-flv", ".flv", magic.Flv) - mkv = newMIME("video/x-matroska", ".mkv", magic.Mkv) + mkv = newMIME("video/matroska", ".mkv", magic.Mkv). + alias("video/x-matroska") asf = newMIME("video/x-ms-asf", ".asf", magic.Asf). alias("video/asf", "video/x-ms-wmv") rmvb = newMIME("application/vnd.rn-realmedia-vbr", ".rmvb", magic.Rmvb) @@ -242,8 +251,8 @@ var ( odc = newMIME("application/vnd.oasis.opendocument.chart", ".odc", magic.Odc). alias("application/x-vnd.oasis.opendocument.chart") sxc = newMIME("application/vnd.sun.xml.calc", ".sxc", magic.Sxc) - rar = newMIME("application/x-rar-compressed", ".rar", magic.RAR). - alias("application/x-rar") + rar = newMIME("application/vnd.rar", ".rar", magic.RAR). + alias("application/x-rar-compressed", "application/x-rar") djvu = newMIME("image/vnd.djvu", ".djvu", magic.DjVu) mobi = newMIME("application/x-mobipocket-ebook", ".mobi", magic.Mobi) lit = newMIME("application/x-ms-reader", ".lit", magic.Lit) @@ -294,4 +303,6 @@ var ( hlp = newMIME("application/x-os2-hlp", ".hlp", magic.Hlp) fm = newMIME("application/vnd.framemaker", ".fm", magic.FrameMaker) bufr = newMIME("application/bufr", ".bufr", magic.BUFR) + gedcom = newMIME("text/vnd.familysearch.gedcom", ".ged", magic.GEDCOM) + pcap = newMIME("application/vnd.tcpdump.pcap", ".pcap", magic.Pcap) ) diff --git a/vendor/modules.txt b/vendor/modules.txt index fa7e86fbc7..d4fb63a5df 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1588,14 +1588,16 @@ github.com/fvbommel/sortorder # github.com/fxamacker/cbor/v2 v2.9.2 ## explicit; go 1.20 github.com/fxamacker/cbor/v2 -# github.com/gabriel-vasile/mimetype v1.4.13 +# github.com/gabriel-vasile/mimetype v1.4.15 ## explicit; go 1.21 github.com/gabriel-vasile/mimetype +github.com/gabriel-vasile/mimetype/internal/cdf github.com/gabriel-vasile/mimetype/internal/charset github.com/gabriel-vasile/mimetype/internal/csv github.com/gabriel-vasile/mimetype/internal/json github.com/gabriel-vasile/mimetype/internal/magic github.com/gabriel-vasile/mimetype/internal/markup +github.com/gabriel-vasile/mimetype/internal/mp3 github.com/gabriel-vasile/mimetype/internal/scan # github.com/gdamore/encoding v1.0.1 ## explicit; go 1.9