- Rust 96.9%
- Python 3.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| benches | ||
| magic | ||
| src | ||
| tools | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| clippy.toml | ||
| LICENSE | ||
| README.md | ||
| THIRD_PARTY_LICENSES.md | ||
Rin Cat's MIME
rcmime provides small, dependency-free,
cross-platform MIME detection from file content.
Filenames and file extensions are never used, and there are no runtime
dependencies. The project targets fast, safe, and predictable detection; it
trades accuracy and exhaustive validation for bounded work.
What it supports
- 2,274 signatures covering 901 MIME values.
- Current IANA-registered names are preferred when they exactly describe the detected format; established unregistered names remain where no exact registered replacement exists.
- Images, documents, archives, audio, video, fonts, databases, executables, structured text, source code, and plain text.
- UTF-8, recognized UTF-16, and BOM-marked UTF-32 text.
- Distinct container formats such as JAR, DOCX, EPUB, and generic ZIP when the content contains enough information to tell them apart.
- Buffers, seekable readers, and file paths without loading an entire seekable file into memory.
- Immutable detectors that can be shared across tasks and threads without a lock or queue.
Usage
init() returns the process-wide immutable Detector. Store that reference and
reuse its methods across detections, tasks, and threads. The first call
initializes the detector; later calls return the same instance.
use std::io::Cursor;
let detector = rcmime::init();
println!("{}", detector.from_file("public/logo.png")?);
let body = b"%PDF-1.7\n";
let mime = detector.from_buf(body)
.unwrap_or("application/octet-stream");
assert_eq!(mime, "application/pdf");
let mut reader = Cursor::new(b"GIF89a".as_slice());
let mime = detector.from_reader(&mut reader)?
.unwrap_or("application/octet-stream");
assert_eq!(mime, "image/gif");
# Ok::<(), std::io::Error>(())
Return values
| API | Recognized | Unknown | I/O failure |
|---|---|---|---|
Detector::from_buf, Detector::from_bytes |
Some(mime) |
None |
Not applicable |
Detector::from_reader |
Ok(Some(mime)) |
Ok(None) |
Err(io::Error) |
Detector::from_file |
Ok(mime) |
Ok("application/octet-stream") |
Err(io::Error) |
Detector::from_buf and Detector::from_bytes are equivalent.
Detector::from_reader restores the stream's original position before
returning.
Bounds and safety
- Seekable inputs are never loaded completely into memory.
- Reads and searches are bounded, and offset arithmetic is checked.
- Detection performs no decompression, regex evaluation, semantic parsing, or runtime recursion.
- Reader positions are restored even after an I/O error.
There is intentionally no timeout. The fixed search and rule bounds limit work, while cancellation or I/O deadlines remain the caller's responsibility. Detection identifies a media type; it does not prove that a file is safe to render or execute.
Command line
For occasional command-line use, the package also includes a small binary:
rcmime path/to/file
rcmime first-file second-file
From the source tree, install it with
cargo install --path . --features cli, or run it without installing with
cargo run --features cli -- path/to/file.
One path prints only its MIME value. With multiple paths, each line is printed
as path: MIME. The detector is initialized once per invocation. File errors
are written to stderr and produce exit code 1; invalid options or missing
arguments produce exit code 2. The binary is gated behind the cli feature and
is not built for ordinary library builds.
Limits
Some MIME types are unsupported by design, especially when identifying them would require decompression, unbounded searching, or semantic parsing. Results are best-effort hints, not validation: rules inspect only selected parts of the content, so false positives and false negatives are possible. Ambiguous content may return a generic MIME type or no match. Signatures can be spoofed by placing expected bytes at checked locations. A returned MIME type does not establish that the input is a structurally valid file of that format and must not be the sole basis for a security or access control decision.
For example, gzip data returns application/gzip. A .tar.gz also returns
application/gzip: detecting the inner TAR stream would require decompression,
which is intentionally outside this crate's scope. An uncompressed TAR can be
recognized directly from its fixed-offset ustar marker.
Shared container syntax such as JSON, XML, or ZIP is not assigned a more specific MIME type unless the content has a subtype-specific marker.
Quality and performance
A fresh audit of the current detector used 4,335 fixtures from
Google Magika,
Apache Tika,
Apache POI,
sindresorhus/file-type,
h2non/filetype,
gabriel-vasile/mimetype,
mmalecot/file-format,
Kaitai Struct, and locally
generated format-gap fixtures. Of those, 54 incomplete or invalid fixtures
were excluded. Among the remaining 4,281 complete samples, there were
4,100 correct detections, including valid MIME aliases (95.77%), 181
degraded detections returning a generic type (4.23%), and 0 incorrect
detections (0.00%).
Release-mode results from an AMD Ryzen 9 5950X with Rust 1.97.1:
| Input | Time per detection |
|---|---|
| GIF | 0.43 µs |
| 0.52 µs | |
| SVG | 3.31 µs |
| PNG | 4.95 µs |
| Parquet | 5.06 µs |
| HTML | 5.25 µs |
| Matroska | 5.29 µs |
| Zstandard | 5.43 µs |
| JPEG | 6.57 µs |
| age | 6.86 µs |
| WebAssembly | 6.94 µs |
| ORC | 7.24 µs |
| gzip | 7.72 µs |
| WebP | 7.98 µs |
| AVIF | 8.51 µs |
| raw JPEG XL | 8.64 µs |
| Avro | 8.64 µs |
| JSON text | 9.83 µs |
| generic ZIP | 11.12 µs |
| DOCX | 11.20 µs |
| SQLite | 15.41 µs |
| USDZ | 24.97 µs |
| TAR | 40.89 µs |
| HDF5 | 129.52 µs |
| 1 GiB seekable input | 0.54 ms |
| 1 MiB worst-case search | 0.51 ms |
The first init() took about 0.44 ms; later calls returning the existing
detector took about 1 ns. These are local measurements, not performance
guarantees.
Size and memory
Incremental cost measured with a minimal x86-64 Linux release program:
| Resource | Increase |
|---|---|
| Linked executable | About 1.10 MiB |
| Initialized detector and resident database | About 1.80 MiB RSS |
| Each active detection | About 12 KiB stack |
Seekable input is not loaded into memory. Actual linked size and resident memory vary by target, linker, allocator, and surrounding application.
License
Project code is Copyright (c) 2026 Rin Cat (鈴猫) rincat@rincat.dev and licensed under the MIT License. The generated database combines project-authored signatures with material derived from separately licensed sources; see the third-party notices.