MimeSpy 0.1.2
dotnet add package MimeSpy --version 0.1.2
NuGet\Install-Package MimeSpy -Version 0.1.2
<PackageReference Include="MimeSpy" Version="0.1.2" />
<PackageVersion Include="MimeSpy" Version="0.1.2" />
<PackageReference Include="MimeSpy" />
paket add MimeSpy --version 0.1.2
#r "nuget: MimeSpy, 0.1.2"
#:package MimeSpy@0.1.2
#addin nuget:?package=MimeSpy&version=0.1.2
#tool nuget:?package=MimeSpy&version=0.1.2

A library to detect mime type from file headers.
Usage
var spy = new MimeSpy();
IReadOnlyList<Result> results = spy.Spy(bytes);
foreach (var result in results)
{
Console.WriteLine($"{result.PrimaryMimeType} ({string.Join(", ", result.Extensions)}): {result.Description}");
}
Spy takes a ReadOnlySpan<byte> (a full file or just its leading bytes) and returns every signature that matches at the longest matched header length. A single header can genuinely match more than one format, so callers should expect more than one Result back - see docs/adr/0001-ambiguous-matches-returned-as-ties.md.
Passing fewer bytes never produces a wrong answer, only a less specific one: 532 bytes is the true minimum, reaching every fixed-offset signature in the embedded table, but a general-purpose caller reading its own byte buffer (rather than going through the Stream/SpyAsync overloads, which already read this much for you) should read 8192 bytes - that's what's needed for the ASF/WMA/WMV disambiguation below, and it's the number Spy(Stream) itself reads.
Spy also takes a Stream, and that overload has an async twin, SpyAsync(Stream, CancellationToken), for callers on an async path (e.g. reading a file upload in an ASP.NET Core handler) who don't want a blocking read on a thread-pool thread. There's no async overload of the ReadOnlySpan<byte> form - see docs/adr/0014-async-overload-only-on-the-stream-api.md.
Dependency injection
MimeSpy has no constructor arguments and no mutable state - Spy/SpyAsync are safe to call concurrently from multiple requests on the same instance - so it only needs registering as a singleton, with no MimeSpy-specific extension method required:
services.AddSingleton<IMimeSpy, MimeSpy>();
Consumers then take an IMimeSpy constructor dependency like any other singleton service. The interface exists purely as a mockable seam for callers whose own flow calls Spy/SpyAsync inline rather than taking a pre-computed Result as input - if your code already separates "call Spy at the boundary" from "react to the Result", you can construct Result directly in tests and don't need the interface at all.
ZIP-based formats
Plain ZIP archives and everything that's "just a zip" underneath (docx/xlsx/pptx, jar, apk, odt/odp/ott, epub, kmz...) share the same 4-byte header. MimeSpy peeks at the name - and, for OpenDocument files, the stored content - of the archive's first entry to narrow this down when those bytes are available, without ever requiring the file's central directory. See docs/adr/0002-zip-disambiguation-reads-only-supplied-bytes.md.
Ogg-based formats
Ogg's OggS container header is shared by audio (Vorbis, Opus, Speex, FLAC-in-Ogg), video (Theora, OGM, Skeleton), and other payloads (Kate) alike, so MimeSpy peeks at the codec identification packet right after the first page's header to report audio/ogg, video/ogg, or application/ogg correctly instead of always guessing audio. See docs/adr/0007-ogg-mime-type-resolved-by-content-sniffing.md.
ASF/WMA/WMV
ASF, WMA, and WMV all share the same Header Object GUID, so MimeSpy searches the first 8192 bytes for the codec-name string a Windows Media encoder writes into the file, the same heuristic Apache Tika uses, to report audio/x-ms-wma or video/x-ms-wmv instead of always guessing video/x-ms-asf. See docs/adr/0008-asf-mime-type-resolved-by-codec-name-search.md.
Building & testing
dotnet build
dotnet test
Tests use xunit v3 on the Microsoft.Testing.Platform runner; both test projects build as executables, so dotnet run --project MimeSpy.Tests (or MimeSpy.IntegrationTests) also works.
MimeSpy.Tests is mostly hand-built byte arrays covering specific signature-matching and disambiguation rules. MimeSpy.IntegrationTests runs the same Spy() API against real, genuinely-encoded sample files - one per mainstream format - committed under MimeSpy.IntegrationTests/Fixtures/; see that project's README for where each one came from.
See docs/releasing.md for how to cut a new NuGet release.
Data
File headers are from https://www.garykessler.net/software/index.html#filesigs.
Extension to MIME type mapping is from: https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
Ogg codec identification patterns are from file(1)'s libmagic rules: https://raw.githubusercontent.com/file/file/master/magic/Magdir/vorbis
ASF/WMA/WMV codec-name markers are from Apache Tika's mime-type table: https://raw.githubusercontent.com/apache/tika/main/tika-core/src/main/resources/org/apache/tika/mime/tika-mimetypes.xml
The first three tables are embedded as resources under Resources/ in their native formats and parsed once into in-memory indexes at first use - see docs/adr/0003, docs/adr/0005, docs/adr/0006, and docs/adr/0007. The ASF markers are few enough to live as constants directly in AsfContainerSniffer rather than a data file - see docs/adr/0008.
Resources/file_signatures_supplemental.csv holds signature-table rows MimeSpy adds on top of Gary Kessler's table (same format, no header row) rather than editing them into file_signatures.csv directly, so that file stays a straight, overwritable copy of the upstream source - see docs/adr/0010.
License
MIT.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- System.Memory (>= 4.6.0)
-
net10.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.