面向 Rust 服务的 MIME 类型检测工具,基于文件名 glob 规则和内容魔数规则。
概述
Qubit MIME 是一个基于仓库的 Rust MIME 类型检测库。它使用 freedesktop shared MIME-info 数据模型:规范 MIME 类型名、别名、本地化说明、文件名 glob、内容魔数规则和父类型关系。
公开 API 分为三层:
MimeDetector:顶层检测器 trait。业务代码需要兼容不同检测器实现时依赖它。detector:检测器实现和共享检测逻辑,包括MediaStreamClassifier:顶层媒体流分类 trait。classifier模块提供MimeRepository:底层仓库 API,返回MimeType元数据和所有最佳候选项,适合
MimeDetectorCore、MimeDetectorBackend、RepositoryMimeDetector 和 FileCommandMimeDetector。
FfprobeCommandMediaStreamClassifier、MediaStreamClassifierBackend 和 FileBasedMediaStreamClassifier,用于用更少重复入口代码实现 stream-backed 或 file-backed classifier。
需要进一步检查规则和说明的调用方。
设计目标
- Freedesktop 数据模型:遵循 shared MIME-info 名称、别名、glob 规则和
- 实用默认值:内置 freedesktop MIME 数据库。
- 文件名与内容检测:同时支持 glob 检测和 magic 检测,可单独使用也可组合使用。
- 检测器与分类器层次:分离 MIME 检测和媒体流细化逻辑,并保留 Rust 的
- 可预测的冲突处理:优先选择更高 glob 权重、更长 glob 模式和更高 magic 优先级。
- 符合 Rust 习惯:使用借用仓库、具体错误类型和标准
Read + Seek检测入口。 - 依赖面小:运行时依赖保持聚焦和稳定。
magic 规则。
所有权与错误处理方式。
特性
文件名检测
- 支持 literal、extension 和通用 glob 匹配。
- 默认大小写不敏感,同时支持大小写敏感 glob。
- 按 glob 权重和模式长度解决冲突。
- 对路径安全,只使用最后一个文件名组件参与检测。
内容魔数检测
- 支持 freedesktop magic 类型:
string、byte、host16、host32、 - 支持
0、0:1024等 offset 范围。 - 支持二进制 magic 的可选 mask。
- 支持嵌套 magic matcher。
- 按 magic 优先级解决冲突。
big16、big32、little16 和 little32。
仓库元数据
- 规范名称和别名。
- 本地化 comment 与 description。
- 推荐扩展名和完整扩展名列表。
- 解析
sub-class-of父类型元数据。 - 计算 magic 规则需要读取的最大字节数。
检测入口
- 顶层 trait:
MimeDetector。 - 仓库检测器:
RepositoryMimeDetector。 - 系统命令检测器:
FileCommandMimeDetector。 - 仅文件名:
detect_by_filename。 - 仅内容:
detect_by_content。 - 文件名与字节组合:
detect或detect_bytes。 - 文件名与 reader 组合:
detect_reader。 - 本地文件路径:
detect_file。
媒体流分类
- 顶层 trait:
MediaStreamClassifier。 - 流结果枚举:
MediaStreamType。 - FFprobe 实现:
FfprobeCommandMediaStreamClassifier。 - 当
MimeDetectorCore配置了 classifier 时,可对 WebM、Ogg 等有歧义的
媒体 MIME 类型做更精确的音视频区分。
安装
在 Cargo.toml 中添加:
[dependencies]
qubit-mime = "0.6"
快速开始
根据文件名和内容检测
use qubit_mime::{
MimeError,
MimeDetectionPolicy,
RepositoryMimeDetector,
};
fn main() -> Result<(), MimeError> {
let detector = RepositoryMimeDetector::new()?;
let by_name = detector.detect_by_filename("photo.JPG");
assert_eq!(Some("image/jpeg".to_owned()), by_name);
let by_content = detector.detect_by_content(b"%PDF-1.7\n");
assert_eq!(Some("application/pdf".to_owned()), by_content);
let combined = detector.detect_bytes(
b"%PDF-1.7\n",
Some("report.pdf"),
MimeDetectionPolicy::VerifyContent,
);
assert_eq!(Some("application/pdf".to_owned()), combined);
Ok(())
}
使用 Rust 风格的 MimeDetector trait
MimeDetectorRegistry 根据 MimeConfig 创建 boxed 或 shared MimeDetector trait object。只需要 MIME 名称的代码可以依赖 trait,而不是依赖 具体检测器类型。
use qubit_mime::{
MimeConfig,
MimeDetectionPolicy,
MimeDetector,
MimeDetectorRegistry,
MimeError,
};
fn detect_upload(detector: &dyn MimeDetector, filename: &str, content: &[u8]) -> Option<String> {
detector.detect(content, Some(filename), MimeDetectionPolicy::VerifyContent)
}
fn main() -> Result<(), MimeError> {
let detector =
MimeDetectorRegistry::default_registry()?.create_default_box(&MimeConfig::default())?;
assert_eq!(
Some("application/pdf".to_owned()),
detect_upload(detector.as_ref(), "upload.bin", b"%PDF-1.7\n"),
);
Ok(())
}
使用 Config 配置全局默认值
MimeConfig::default() 返回当前全局默认 MIME 配置的快照。使用 MimeConfig::reload_default() 可以从 rs-config 的 Config 替换全局默认值; 使用 MimeConfig::reload_default_from_env() 可以通过 Config::from_env() 从 进程环境载入。
use qubit_config::Config;
use qubit_mime::{
CONFIG_MEDIA_STREAM_CLASSIFIER_DEFAULT,
CONFIG_MIME_AMBIGUOUS_MIME_MAPPING,
CONFIG_MIME_DETECTOR_DEFAULT,
CONFIG_MIME_DETECTOR_FALLBACKS,
CONFIG_MIME_ENABLE_PRECISE_DETECTION,
CONFIG_MIME_MAX_BUFFER_SIZE,
CONFIG_MIME_PRECISE_DETECTION_PATTERNS,
DEFAULT_AMBIGUOUS_MIME_MAPPING,
DEFAULT_MIME_MAX_BUFFER_SIZE,
DEFAULT_PRECISE_DETECTION_PATTERNS,
MimeConfig,
MimeDetector,
MimeDetectorRegistry,
MimeError,
};
fn main() -> Result<(), MimeError> {
let original = MimeConfig::default();
let mut config = Config::new();
config.set(CONFIG_MIME_DETECTOR_DEFAULT, "repository")?;
config.set(CONFIG_MIME_DETECTOR_FALLBACKS, "")?;
config.set(CONFIG_MEDIA_STREAM_CLASSIFIER_DEFAULT, "ffprobe")?;
config.set(CONFIG_MIME_ENABLE_PRECISE_DETECTION, true)?;
config.set(CONFIG_MIME_PRECISE_DETECTION_PATTERNS, DEFAULT_PRECISE_DETECTION_PATTERNS)?;
config.set(CONFIG_MIME_AMBIGUOUS_MIME_MAPPING, DEFAULT_AMBIGUOUS_MIME_MAPPING)?;
config.set(CONFIG_MIME_MAX_BUFFER_SIZE, DEFAULT_MIME_MAX_BUFFER_SIZE)?;
MimeConfig::reload_default(&config)?;
let detector =
MimeDetectorRegistry::default_registry()?.create_default_box(&MimeConfig::default())?;
assert_eq!(
Some("application/pdf".to_owned()),
detector.detect_by_filename("document.pdf"),
);
MimeConfig::set_default(original);
Ok(())
}
使用 registry 和 fallback 选择检测器
MimeDetectorRegistry::create_default_box() 和 MimeDetectorRegistry::create_default_arc() 先尝试配置的默认 detector;如果该 provider 未知、不可用或初始化失败,则按配置的 fallback 链继续尝试。把默认 selector 设置为 auto 时,会从 registry 中按 provider 优先级选择当前可用的实现。
默认 registry 初始包含 MimeDetectorRegistry::builtin() 返回的内置 provider。 扩展 crate 可以在应用启动阶段调用 MimeDetectorRegistry::register_default(provider),把自己的 provider 加入这个 进程级默认 registry。注册成功后,后续任何地方调用 MimeDetectorRegistry::default_registry() 获取到的 registry 快照,都会通过同一个 进程级默认 registry 看到该 provider。
MimeDetectorRegistry::default_registry() 返回当前进程级 registry 的快照克隆。 修改这个快照不会更新全局 registry。需要让 provider 对默认构造器全局可见时, 使用 register_default();需要隔离环境、测试自定义 provider,或限制可选 provider 集合时,使用显式的 MimeDetectorRegistry::builtin() 或 MimeDetectorRegistry::new()。
全局注册只在当前进程内生效,通常应在根据配置创建 detector 之前执行一次。provider id 或 alias 重复时会返回 MimeError::DuplicateDetectorName;全局 registry 锁中毒时 会返回 MimeError::DetectorBackend。
内置 detector selector:
| Selector | 别名 | 行为 |
|---|---|---|
repository | repository-mime-detector | 使用内置 freedesktop MIME 仓库 |
file | file-command, file-command-mime-detector | 文件名使用仓库,内容使用 file --mime-type --brief |
auto | - | 按优先级和 provider id 选择可用 provider |
use qubit_config::Config;
use qubit_mime::{
CONFIG_MIME_DETECTOR_DEFAULT,
CONFIG_MIME_DETECTOR_FALLBACKS,
MimeConfig,
MimeDetector,
MimeDetectorRegistry,
MimeError,
};
fn main() -> Result<(), MimeError> {
let mut source = Config::new();
source.set(CONFIG_MIME_DETECTOR_DEFAULT, "file")?;
source.set(CONFIG_MIME_DETECTOR_FALLBACKS, "repository")?;
let config = MimeConfig::from_config(&source)?;
let detector = MimeDetectorRegistry::default_registry()?.create_default_box(&config)?;
assert_eq!(
Some("image/png".to_owned()),
detector.detect_by_filename("image.png"),
);
Ok(())
}
需要自定义 provider 时,使用显式 registry:
use qubit_mime::{
MimeConfig,
MimeDetector,
MimeDetectorRegistry,
MimeError,
};
fn main() -> Result<(), MimeError> {
let registry = MimeDetectorRegistry::builtin();
let detector = registry.create_box("repository-mime-detector", &MimeConfig::default())?;
assert_eq!(
Some("text/plain".to_owned()),
detector.detect_by_filename("notes.txt"),
);
Ok(())
}
Registry 选择相关错误:
| 错误 | 含义 |
|---|---|
DuplicateDetectorName | provider id 或 alias 与已有 provider 冲突 |
UnknownDetector | 没有已注册 provider 匹配请求的 selector |
DetectorUnavailable | provider 存在,但当前进程环境中后端不可用 |
NoAvailableDetector | 默认候选和所有 fallback 候选都失败 |
DetectorBackend | provider 后端在初始化或检测过程中失败 |
配置键
MimeConfig::from_config() 同时接受逻辑键和环境变量风格键。环境变量使用环境变量 风格名称。列表值既可以是数组,也可以是用 , 或 ; 分隔的字符串;空项会被忽略。 有歧义 MIME 映射按 ; 分隔,每项格式为 extension:video-mime,audio-mime。
| 设置 | 逻辑键 | 环境键 | 默认值 |
|---|---|---|---|
| 默认 MIME detector | mime.detector.default | QUBIT_MIME_DETECTOR_DEFAULT | repository |
| MIME detector fallback | mime.detector.fallbacks | QUBIT_MIME_DETECTOR_FALLBACKS | 空 |
| 媒体流 classifier | mime.media.stream.classifier.default | QUBIT_MEDIA_STREAM_CLASSIFIER_DEFAULT | ffprobe |
| 启用精确检测 | mime.enable.precise.detection | QUBIT_MIME_ENABLE_PRECISE_DETECTION | true |
| 精确检测扩展名 | mime.precise.detection.patterns | QUBIT_MIME_PRECISE_DETECTION_PATTERNS | webm,ogg |
| 有歧义 MIME 映射 | mime.ambiguous.mime.mapping | QUBIT_MIME_AMBIGUOUS_MIME_MAPPING | webm:video/webm,audio/webm;ogg:video/ogg,audio/ogg |
| detector 最大 buffer 大小 | mime.max.buffer.size | QUBIT_MIME_MAX_BUFFER_SIZE | 16777216 |
检测文件系统路径
use qubit_mime::{
MimeDetectionPolicy,
MimeError,
RepositoryMimeDetector,
};
fn main() -> Result<(), MimeError> {
let detector = RepositoryMimeDetector::new()?;
let path = std::env::temp_dir().join("qubit-mime-example.pdf");
std::fs::write(&path, b"%PDF-1.7\n")?;
let detected = detector.detect_file(&path, MimeDetectionPolicy::VerifyContent)?;
std::fs::remove_file(&path).ok();
assert_eq!(Some("application/pdf".to_owned()), detected);
Ok(())
}
使用系统 file 命令检测器
FileCommandMimeDetector 使用内置仓库做文件名候选检测,并使用 file --mime-type --brief 做内容检测。
use std::time::Duration;
use qubit_command::CommandRunner;
use qubit_mime::{
FileCommandMimeDetector,
MimeDetectionPolicy,
MimeDetector,
MimeError,
};
fn main() -> Result<(), MimeError> {
if !FileCommandMimeDetector::is_available() {
return Ok(());
}
let detector = FileCommandMimeDetector::new();
let detected = detector.detect(
b"%PDF-1.7\n",
Some("report.bin"),
MimeDetectionPolicy::VerifyContent,
);
assert_eq!(Some("application/pdf".to_owned()), detected);
let runner = CommandRunner::new()
.timeout(Duration::from_secs(2))
.disable_logging(true);
let detector = FileCommandMimeDetector::new().with_command_runner(runner);
assert!(detector.command_runner().configured_timeout().is_some());
Ok(())
}
使用 FFprobe 分类媒体流
FfprobeCommandMediaStreamClassifier 可以把媒体文件分类为无媒体流、纯音频、 纯视频或音视频都有。
use std::path::Path;
use qubit_mime::{
FfprobeCommandMediaStreamClassifier,
MediaStreamClassifier,
MediaStreamType,
MimeError,
};
fn main() -> Result<(), MimeError> {
if !FfprobeCommandMediaStreamClassifier::is_available() {
return Ok(());
}
let classifier = FfprobeCommandMediaStreamClassifier::new();
let stream_type = classifier.classify_file(Path::new("sample.webm"))?;
assert!(matches!(
stream_type,
MediaStreamType::AudioOnly
| MediaStreamType::VideoOnly
| MediaStreamType::VideoWithAudio
| MediaStreamType::None,
));
Ok(())
}
从可 seek 的 reader 检测
detect_reader 会读取仓库中 magic 规则所需的最大字节数,然后恢复 reader 原来的 流位置。
use std::io::{
Cursor,
Seek,
};
use qubit_mime::{
MimeDetectionPolicy,
MimeError,
RepositoryMimeDetector,
};
fn main() -> Result<(), MimeError> {
let detector = RepositoryMimeDetector::new()?;
let mut reader = Cursor::new(b"%PDF-1.7\npayload".to_vec());
let detected = detector.detect_reader(
&mut reader,
Some("document.bin"),
MimeDetectionPolicy::VerifyContent,
)?;
assert_eq!(Some("application/pdf".to_owned()), detected);
assert_eq!(0, reader.stream_position()?);
Ok(())
}
组合检测策略
组合检测同时接收文件名和内容字节。MimeDetectionPolicy 会让每次调用都显式声明 文件名结果和内容结果的取舍策略。
use qubit_mime::{
MimeDetectionPolicy,
MimeError,
RepositoryMimeDetector,
};
fn main() -> Result<(), MimeError> {
let detector = RepositoryMimeDetector::new()?;
let pdf_bytes = b"%PDF-1.7\n";
// 优先使用确定的文件名结果,避免额外检查内容。
let by_filename = detector.detect_bytes(
pdf_bytes,
Some("photo.jpg"),
MimeDetectionPolicy::PreferFilename,
);
assert_eq!(Some("image/jpeg".to_owned()), by_filename);
// 当内容更可信时,明确检查 magic。
let by_magic = detector.detect_bytes(
pdf_bytes,
Some("photo.jpg"),
MimeDetectionPolicy::VerifyContent,
);
assert_eq!(Some("application/pdf".to_owned()), by_magic);
Ok(())
}
当文件名来自可信来源且希望减少 I/O 时,使用 MimeDetectionPolicy::PreferFilename。当文件名来自用户上传或可能被伪造时,使用 MimeDetectionPolicy::VerifyContent 更稳妥。
仓库元数据
默认检测器会暴露已解析的仓库。当你不仅需要 MIME 名称,还需要扩展名、别名或说明时, 可以直接使用仓库 API。
use qubit_mime::{
MimeError,
RepositoryMimeDetector,
};
fn main() -> Result<(), MimeError> {
let detector = RepositoryMimeDetector::new()?;
let repository = detector.repository();
let png = repository
.get("image/png")
.expect("default repository should contain image/png");
assert_eq!("image/png", png.name());
assert_eq!(Some("png"), png.preferred_extension());
assert!(png.description().is_some());
assert!(png.matches_filename("ICON.PNG"));
let extensions = png.all_extensions();
assert!(extensions.contains(&"png"));
Ok(())
}
MimeRepository::detect_by_filename 和 MimeRepository::detect_by_content 会返回所有最佳候选项,而不是只返回一个字符串:
use qubit_mime::{
MimeError,
RepositoryMimeDetector,
};
fn main() -> Result<(), MimeError> {
let detector = RepositoryMimeDetector::new()?;
let repository = detector.repository();
let candidates = repository.detect_by_filename("archive.tar.gz");
assert!(!candidates.is_empty());
for candidate in candidates {
println!("{} {:?}", candidate.name(), candidate.description());
}
Ok(())
}
自定义仓库
当需要小型测试仓库、产品自定义 MIME 数据库,或使用其他来源生成的数据库时,可使用 MimeRepository::from_xml。
use qubit_mime::{
MimeError,
MimeRepository,
RepositoryMimeDetector,
};
fn main() -> Result<(), MimeError> {
let xml = r#"
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-example">
<comment>Example bundle</comment>
<alias type="application/example"/>
<glob pattern="*.example" weight="80"/>
<magic priority="90">
<match type="string" value="EXAMPLE" offset="0"/>
</magic>
</mime-type>
</mime-info>
"#;
let repository = MimeRepository::from_xml(xml)?;
let detector = RepositoryMimeDetector::with_repository(&repository);
assert_eq!(
Some("application/x-example".to_owned()),
detector.detect_by_filename("demo.example"),
);
assert_eq!(
Some("application/x-example".to_owned()),
detector.detect_by_content(b"EXAMPLE payload"),
);
let mime_type = repository
.get("application/example")
.expect("alias should resolve to the canonical MIME type");
assert_eq!("application/x-example", mime_type.name());
assert_eq!(Some("example"), mime_type.preferred_extension());
Ok(())
}
底层规则类型
底层类型适合在测试或集成代码中直接检查、构造或验证 MIME 规则。
use qubit_mime::{
MagicValueType,
MimeError,
MimeGlob,
MimeMagic,
MimeMagicMatcher,
MimeType,
};
fn main() -> Result<(), MimeError> {
let glob = MimeGlob::new("*.png", MimeGlob::DEFAULT_WEIGHT, false)?;
assert!(glob.matches("ICON.PNG"));
let matcher = MimeMagicMatcher::new(
MagicValueType::String,
0,
0,
b"\x89PNG\r\n\x1a\n".to_vec(),
None,
vec![],
)?;
let magic = MimeMagic::new(80, vec![matcher]);
let png = MimeType::builder("image/png")
.description("en", "PNG image")
.alias("image/x-png")
.glob(glob)
.magic(magic)
.build();
assert_eq!("image/png", png.name());
assert_eq!(Some("png"), png.preferred_extension());
assert!(png.matches_filename("icon.png"));
assert!(png.magics()[0].matches(b"\x89PNG\r\n\x1a\npayload"));
Ok(())
}
API 参考
MimeDetector
| 方法 | 描述 |
|---|---|
MimeDetectorRegistry::builtin() | 创建只包含内置 detector provider 的隔离 registry |
MimeDetectorRegistry::default_registry() | 获取进程级默认 detector registry 的快照 |
MimeDetectorRegistry::register_default(provider) | 将外部 detector provider 注册到全局默认 registry |
MimeDetectorRegistry::register(provider) | 将外部 detector provider 注册到显式 registry |
MimeDetectorRegistry::create_box(name, config) | 按 provider id 或 alias 创建 boxed 检测器 |
MimeDetectorRegistry::create_arc(name, config) | 按 provider id 或 alias 创建共享检测器 |
MimeDetectorRegistry::create_default_box(config) | 将配置的默认值、auto 和 fallback 链解析为 boxed 检测器 |
MimeDetectorRegistry::create_default_arc(config) | 将配置的默认值、auto 和 fallback 链解析为共享检测器 |
MimeDetectorProvider | 可插拔 detector 实现的工厂 trait |
detect_by_filename(filename) | 根据文件名检测一个 MIME 名称 |
detect_by_content(bytes) | 根据内容字节检测一个 MIME 名称 |
detect(bytes, filename, policy) | 根据字节和可选文件名检测 |
RepositoryMimeDetector
| 方法 | 描述 |
|---|---|
new() | 创建使用内置 freedesktop 仓库的检测器 |
with_repository(repository) | 创建借用显式仓库的检测器 |
with_repository_and_config(repository, config) | 创建借用显式仓库和 MIME 配置的检测器 |
repository() | 借用底层仓库 |
detect_by_filename(filename) | 返回文件名匹配到的第一个 MIME 名称 |
detect_by_content(bytes) | 返回内容 magic 匹配到的第一个 MIME 名称 |
detect_bytes(bytes, filename, policy) | 根据字节和可选文件名检测 |
detect_reader(reader, filename, policy) | 从 Read + Seek reader 检测并恢复位置 |
detect_file(file, policy) | 打开并检测本地文件路径 |
FileCommandMimeDetector
| 方法 | 描述 |
|---|---|
new() | 创建使用内置仓库和系统 file 命令的检测器 |
with_repository(repository) | 创建借用显式仓库的检测器 |
with_repository_and_runner(repository, runner) | 使用显式 qubit_command::CommandRunner 创建检测器 |
with_repository_runner_and_config(repository, runner, config) | 使用显式仓库、runner 和 MIME 配置创建检测器 |
command_runner() | 借用用于命令执行的 runner |
set_command_runner(runner) | 替换用于命令执行的 runner |
is_available() | 检查 file 命令是否可执行 |
detect_file_by_content(file) | 只根据命令输出检测本地文件 |
detect_file(file, policy) | 根据文件名和命令支持的内容检测来检测本地文件 |
detect_reader(reader, filename, policy) | 通过 file-backed 路径检测可 seek reader |
MediaStreamClassifier
| 方法 | 描述 |
|---|---|
MediaStreamClassifierRegistry::builtin() | 创建只包含内置 classifier provider 的隔离 registry |
MediaStreamClassifierRegistry::default_registry() | 获取进程级默认 classifier registry 的快照 |
MediaStreamClassifierRegistry::register_default(provider) | 将外部 classifier provider 注册到全局默认 registry |
MediaStreamClassifierRegistry::register(provider) | 将外部 classifier provider 注册到显式 registry |
MediaStreamClassifierRegistry::create_box(name, config) | 按 provider id 或 alias 创建 boxed classifier |
MediaStreamClassifierRegistry::create_arc(name, config) | 按 provider id 或 alias 创建共享 classifier |
MediaStreamClassifierRegistry::create_default_box(config) | 将配置的默认值或 auto 解析为 boxed classifier |
MediaStreamClassifierRegistry::create_default_arc(config) | 将配置的默认值或 auto 解析为共享 classifier |
MediaStreamClassifierProvider | 可插拔 classifier 实现的工厂 trait |
classify_file(file) | 分类本地媒体文件 |
classify_reader(reader) | 从 reader 分类媒体内容 |
classify_content(bytes) | 分类内存中的媒体内容 |
FfprobeCommandMediaStreamClassifier
| 方法 | 描述 |
|---|---|
new() | 创建基于 FFprobe 的 classifier |
is_available() | 检查 ffprobe 是否可执行 |
classify_stream_listing(output) | 分类 FFprobe codec_type 输出 |
set_working_directory(directory) | 设置命令工作目录 |
MimeRepository
| 方法 | 描述 |
|---|---|
from_xml(xml) | 解析 freedesktop shared MIME-info XML 文档 |
empty() | 创建空仓库 |
all() | 按数据库顺序返回所有 MIME 类型 |
get(name) | 解析规范 MIME 名称或别名 |
max_test_bytes() | 返回 magic 规则需要的最大内容前缀长度 |
detect_by_filename(filename) | 返回最佳文件名候选项 |
detect_by_content(bytes) | 返回最佳内容候选项 |
detect(filename, bytes, policy) | 合并文件名和内容检测 |
元数据与规则类型
| 类型 | 用途 |
|---|---|
MimeType | 单个 MIME 类型的元数据和规则 |
MimeTypeBuilder | 构造独立 MimeType 值的 builder |
MimeGlob | 带权重和大小写敏感设置的文件名 glob 规则 |
MimeMagic | 带优先级的一组 magic matcher |
MimeMagicMatcher | 带 offset、value、mask 和子 matcher 的单个 magic 匹配器 |
MagicValueType | freedesktop magic value type 枚举 |
MediaStreamType | 音视频流分类结果 |
MimeConfig | 精确检测和有歧义媒体映射配置 |
MimeError | XML 解析、规则校验和 I/O 错误类型 |
MimeConfig
| 方法 | 描述 |
|---|---|
from_config(config) | 从 qubit_config::Config 解析 MIME 配置 |
from_env() | 从 Config::from_env() 解析 MIME 配置 |
default() | 克隆当前全局默认 MIME 配置 |
set_default(config) | 替换未来默认实例使用的全局默认配置 |
reload_default(config) | 从 Config 解析并替换全局默认配置 |
reload_default_from_env() | 从进程环境解析并替换全局默认配置 |
mime_detector_default() | 读取配置的 detector selector |
mime_detector_fallbacks() | 读取配置的 detector fallback 链 |
media_stream_classifier_default() | 读取配置的媒体 classifier selector |
模块结构
源码结构按 detector、classifier 和 repository 职责组织:
src/
mime_detector.rs # 顶层 MimeDetector trait
mime_config.rs # 精确检测配置
detector/ # detector 实现
classifier/ # 媒体流 classifier 接口与实现
repository/ # MIME 数据库、glob、magic 和元数据类型
普通业务代码优先使用 crate root 的 re-export。只有在需要查看或扩展具体 detector、 classifier 或 repository 组件时,才需要直接使用嵌套模块。
检测规则
文件名规则
文件名检测只使用路径的最后一个组件。匹配结果按以下顺序排序:
- glob 权重更高者优先。
- 权重相同时,glob 模式更长者优先。
- 仓库 API 在多个候选项完全并列时保留数据库顺序。
内容规则
内容检测会用传入的字节前缀检查每个 MIME 类型的直接 magic 规则。结果按更高 magic 优先级排序。可使用 repository.max_test_bytes() 获取当前仓库最值得读取的 最大前缀长度。
组合规则
组合检测会先执行文件名 glob 检测。当文件名只有一个匹配项且未强制检查 magic 时, 直接使用该文件名结果。否则继续执行内容 magic 检测,并与文件名候选项合并。
与 Java common-mime 对比
| 方面 | Java common-mime | Qubit MIME |
|---|---|---|
| 数据库模型 | Freedesktop shared MIME-info | 相同模型 |
| 文件名检测 | Glob 规则 | Glob 规则 |
| 内容检测 | Magic 规则 | Magic 规则 |
| 别名查找 | 支持 | 支持 |
| 检测器接口 | MimeDetector | MimeDetector trait |
| 媒体流分类接口 | MediaStreamClassifier | MediaStreamClassifier trait |
| 仓库检测器 | RepositoryMimeDetector | RepositoryMimeDetector |
| file 命令检测器 | FileCommandMimeDetector | FileCommandMimeDetector |
| FFprobe classifier | FfprobeCommandMediaStreamClassifier | FfprobeCommandMediaStreamClassifier |
| 仓库加载 | XML 资源 | 内置 XML 或显式 XML |
| 返回风格 | Java 对象与集合 | Rust Option、slice 和 vector |
| 错误处理 | Java 异常 | 具体 MimeError |
测试与代码覆盖率
本项目测试统一放在 tests/ 目录下,覆盖仓库解析、文件名匹配、内容 magic 匹配、 reader/path 检测和覆盖率阈值。
运行测试
# 运行所有测试
cargo test
# 生成覆盖率报告
./coverage.sh
# 生成文本格式覆盖率报告
./coverage.sh text
# 运行 CI 检查(格式化、clippy、测试、文档、覆盖率、audit)
./ci-check.sh
依赖项
运行时依赖保持很少:
qubit-command用于执行外部file命令,支持超时和输出捕获。qubit-config用于从配置对象和环境载入 MIME 默认值。regex用于编译和运行文件名 glob 匹配器。roxmltree用于解析 shared MIME-info XML。thiserror用于实现具体的MimeError。
许可证
Copyright (c) 2026. Haixing Hu, Qubit Co. Ltd. All rights reserved.
根据 Apache 许可证 2.0 版("许可证")授权; 除非遵守许可证,否则您不得使用此文件。 您可以在以下位置获取许可证副本:
<http://www.apache.org/licenses/LICENSE-2.0>
除非适用法律要求或书面同意,否则根据许可证分发的软件 按"原样"分发,不附带任何明示或暗示的担保或条件。 有关许可证下的特定语言管理权限和限制,请参阅许可证。
完整的许可证文本请参阅 LICENSE。
贡献
欢迎贡献。请保持改动与现有 Rust 项目结构一致,并在提交 Pull Request 前运行 ./ci-check.sh。
作者
胡海星 - Qubit Co. Ltd.
相关项目
Qubit 旗下的更多 Rust 库发布在 GitHub 组织 qubit-ltd。
---