文章目录
- [MIME类型检测库 - 代码介绍大纲](#MIME类型检测库 - 代码介绍大纲)
-
- 一、项目概述
-
- [1.1 项目目标](#1.1 项目目标)
- [1.2 主要特性](#1.2 主要特性)
- 二、核心接口设计
-
- [2.1 抽象接口类 `IMimeTypeDetector`](#2.1 抽象接口类
IMimeTypeDetector) - [2.2 核心接口方法](#2.2 核心接口方法)
- [2.3 工厂模式设计](#2.3 工厂模式设计)
- [2.1 抽象接口类 `IMimeTypeDetector`](#2.1 抽象接口类
- 三、平台实现详情
- 四、使用示例
-
- [4.1 基本使用](#4.1 基本使用)
- [4.2 高级使用场景](#4.2 高级使用场景)
- 五、技术要点
-
- [5.1 跨平台设计](#5.1 跨平台设计)
- [5.2 资源管理](#5.2 资源管理)
- [5.3 性能优化](#5.3 性能优化)
- 六、扩展性设计
-
- [6.1 添加新平台](#6.1 添加新平台)
- [6.2 自定义魔术字节识别](#6.2 自定义魔术字节识别)
- 七、依赖与编译
-
- [7.1 macOS依赖](#7.1 macOS依赖)
- [7.2 Windows依赖](#7.2 Windows依赖)
- [7.3 编译配置](#7.3 编译配置)
- 八、注意事项
-
- [8.1 平台差异](#8.1 平台差异)
- [8.2 权限要求](#8.2 权限要求)
- [8.3 错误处理](#8.3 错误处理)
- 九、未来扩展方向
- 参考实现
MIME类型检测库 - 代码介绍大纲
一、项目概述
1.1 项目目标
-
创建跨平台的文件MIME类型检测库
-
支持多种检测方式(扩展名、文件内容、组合检测)
-
提供统一的编程接口,屏蔽平台差异
1.2 主要特性
-
支持Windows、macOS平台,可扩展其他平台
-
提供文件路径、内存数据、文件描述符三种检测方式
-
包含便捷的全局访问函数
-
默认检测器提供基本功能,保证库的健壮性
二、核心接口设计
2.1 抽象接口类 IMimeTypeDetector
// 检测方法枚举
enum DetectionMethod {
METHOD_EXTENSION = 0, // 基于扩展名
METHOD_CONTENT, // 基于文件内容
METHOD_BOTH // 两者结合
};
2.2 核心接口方法
-
**
GetMimeType()** - 通过文件路径检测MIME类型 -
**
GetMimeTypeFromMemory()** - 通过内存数据检测MIME类型 -
**
GetFileExtension()** - 提取文件扩展名 -
**
GetMimeTypeFromFD()** - 通过文件描述符检测MIME类型
2.3 工厂模式设计
-
CreateMimeTypeDetector()- 创建平台特定的检测器实例 -
全局便捷函数封装,简化使用
三、平台实现详情
3.1 macOS实现 (MacMimeTypeDetector)
核心技术
-
使用Uniform Type Identifiers (UTI)系统
-
依赖CoreFoundation/CoreServices框架
-
文件扩展名与MIME类型映射
关键方法
-
UTI查询 - 通过
UTTypeCopyPreferredTagWithClass()获取MIME类型 -
内容检测 - 读取文件属性或魔术字节识别
-
文件描述符处理 - 支持已打开文件的类型检测
3.2 Windows实现 (WindowsMimeTypeDetector)
核心技术
-
Windows文件关联API (
AssocQueryStringW) -
URL Moniker API (
FindMimeFromData) -
Shell路径处理函数
关键方法
-
注册表查询 - 通过扩展名获取关联的MIME类型
-
内容探测 - 使用Windows内置的MIME类型检测
-
编码转换 - UTF-8与UTF-16字符串转换
3.3 默认实现 (DefaultMimeTypeDetector)
-
提供基本接口实现
-
保证代码在未实现平台上的可编译性
-
返回通用类型
application/octet-stream
四、使用示例
4.1 基本使用
// 方式1:使用全局函数
std::string mime = GetMimeType("test.pdf");
// 方式2:创建检测器实例
auto detector = CreateMimeTypeDetector();
std::string mime = detector->GetMimeType("test.jpg",
IMimeTypeDetector::METHOD_BOTH);
4.2 高级使用场景
// 内存数据检测
std::vector<BYTE> data = {0x89, 0x50, 0x4E, 0x47}; // PNG魔术字节
std::string mime = GetMimeTypeFromMemory(
data.data(),
data.size(),
"image.png");
// 文件描述符检测
int fd = open("test.txt", O_RDONLY);
std::string mime = GetMimeTypeFromFD(fd, "test.txt");
五、技术要点
5.1 跨平台设计
-
条件编译处理平台差异
-
统一的接口设计
-
智能指针管理资源
5.2 资源管理
-
RAII原则管理平台资源
-
正确的内存释放
-
错误处理机制
5.3 性能优化
-
缓存读取位置(文件描述符检测)
-
最小化数据读取
-
平台原生API调用
六、扩展性设计
6.1 添加新平台
-
继承
IMimeTypeDetector接口 -
实现所有纯虚函数
-
更新平台检测逻辑
-
修改
CreateMimeTypeDetector()工厂函数
6.2 自定义魔术字节识别
-
扩展
DetectMimeTypeFromData()方法 -
添加新的文件格式支持
-
维护扩展的MIME类型映射
七、依赖与编译
7.1 macOS依赖
-
CoreFoundation.framework
-
CoreServices.framework
-
UniformTypeIdentifiers.framework (macOS 11+)
7.2 Windows依赖
-
Windows SDK
-
urlmon.lib
-
shlwapi.lib
-
COM初始化要求
7.3 编译配置
# 示例CMake配置
set(PLATFORM_MAC 0) # 根据目标平台设置
set(PLATFORM_WINDOWS 1)
八、注意事项
8.1 平台差异
-
Windows需要COM初始化
-
macOS的UTI系统在旧版本中行为不同
-
文件描述符在Windows和POSIX系统中的差异
8.2 权限要求
-
文件读取权限
-
Windows注册表查询权限
-
内存访问权限
8.3 错误处理
-
返回默认MIME类型
application/octet-stream -
资源分配失败处理
-
API调用错误处理
九、未来扩展方向
-
添加Linux平台支持(使用libmagic/file命令)
-
支持自定义MIME类型映射
-
添加异步检测接口
-
增加缓存机制提升性能
-
支持更多文件格式的魔术字节检测
最后更新: 2025-01-05
版本: 1.0.0
作者: 跨平台开发团队
参考实现
cpp
//#include "BaseDataType.h"
#define PLATFORM_WINDOWS 1
#include <string>
#include <memory>
#include <vector>
// 定义统一的MimeTypeDetector接口类
class IMimeTypeDetector {
public:
enum DetectionMethod {
METHOD_EXTENSION = 0, // 基于扩展名
METHOD_CONTENT, // 基于文件内容
METHOD_BOTH // 两者结合
};
virtual ~IMimeTypeDetector() = default;
// 统一的接口函数
virtual std::string GetMimeType(const std::string& file_path,
DetectionMethod method = METHOD_BOTH) = 0;
virtual std::string GetMimeTypeFromMemory(const unsigned char* data,
size_t data_size,
const std::string& file_name = "",
const std::string& file_ext = "") = 0;
virtual std::string GetFileExtension(const std::string& file_name) = 0;
virtual std::string GetMimeTypeFromFD(int fd,
const std::string& file_name = "") = 0;
};
// 辅助函数:获取平台特定的检测器实例
std::unique_ptr<IMimeTypeDetector> CreateMimeTypeDetector();
// 全局便捷访问函数
inline std::string GetMimeType(const std::string& file_path,
IMimeTypeDetector::DetectionMethod method = IMimeTypeDetector::METHOD_BOTH) {
auto detector = CreateMimeTypeDetector();
return detector->GetMimeType(file_path, method);
}
inline std::string GetMimeTypeFromMemory(const unsigned char* data,
size_t data_size,
const std::string& file_name = "",
const std::string& file_ext = "") {
auto detector = CreateMimeTypeDetector();
return detector->GetMimeTypeFromMemory(data, data_size, file_name, file_ext);
}
inline std::string GetFileExtension(const std::string& file_name) {
auto detector = CreateMimeTypeDetector();
return detector->GetFileExtension(file_name);
}
inline std::string GetMimeTypeFromFD(int fd,
const std::string& file_name = "") {
auto detector = CreateMimeTypeDetector();
return detector->GetMimeTypeFromFD(fd, file_name);
}
// 平台特定的实现
#if PLATFORM_MAC
#include <cstring>
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>
#include <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#include <unistd.h>
class MacMimeTypeDetector : public IMimeTypeDetector {
public:
// 移除override关键字,使用正确的签名
std::string GetMimeType(const std::string& file_path,
DetectionMethod method = METHOD_BOTH) {
std::string mime_type = "application/octet-stream";
CFStringRef cf_path = CFStringCreateWithCString(kCFAllocatorDefault,
file_path.c_str(),
kCFStringEncodingUTF8);
if (!cf_path) {
return mime_type;
}
CFURLRef file_url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
cf_path,
kCFURLPOSIXPathStyle,
false);
CFRelease(cf_path);
if (!file_url) {
return mime_type;
}
mime_type = GetMimeTypeFromURL(file_url, method);
CFRelease(file_url);
return mime_type;
}
std::string GetMimeTypeFromMemory(const unsigned char* data,
size_t data_size,
const std::string& file_name = "",
const std::string& file_ext = "") {
std::string mime_type = "application/octet-stream";
// 如果有扩展名,先尝试通过扩展名获取
std::string ext = file_ext;
if (ext.empty() && !file_name.empty()) {
ext = GetFileExtension(file_name);
}
if (!ext.empty()) {
CFStringRef cf_ext = CFStringCreateWithCString(kCFAllocatorDefault,
ext.c_str(),
kCFStringEncodingUTF8);
if (cf_ext) {
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,
cf_ext,
nullptr);
CFRelease(cf_ext);
if (uti) {
mime_type = GetMimeTypeFromUTI(uti);
CFRelease(uti);
if (mime_type != "application/octet-stream") {
return mime_type;
}
}
}
}
// 通过内容检测
if (data && data_size > 0) {
mime_type = DetectMimeTypeFromData(data, data_size);
}
return mime_type;
}
std::string GetFileExtension(const std::string& file_name) {
size_t dot_pos = file_name.find_last_of('.');
if (dot_pos != std::string::npos && dot_pos < file_name.length() - 1) {
return file_name.substr(dot_pos + 1);
}
return "";
}
std::string GetMimeTypeFromFD(int fd,
const std::string& file_name = "") {
std::string mime_type = "application/octet-stream";
// 读取文件头
unsigned char buffer[4096];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer));
// 恢复文件指针
if (bytes_read > 0) {
lseek(fd, -bytes_read, SEEK_CUR);
mime_type = GetMimeTypeFromMemory(buffer, bytes_read, file_name);
}
return mime_type;
}
private:
std::string GetMimeTypeFromURL(CFURLRef file_url, DetectionMethod method) {
std::string mime_type = "application/octet-stream";
// 先尝试通过扩展名获取
if (method == METHOD_EXTENSION || method == METHOD_BOTH) {
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,
nullptr,
file_url);
if (uti) {
mime_type = GetMimeTypeFromUTI(uti);
CFRelease(uti);
}
}
// 如果需要通过内容检测
if (method == METHOD_CONTENT ||
(method == METHOD_BOTH && mime_type == "application/octet-stream")) {
CFStringRef keys[] = { kCFURLTypeIdentifierKey };
CFArrayRef key_array = CFArrayCreate(kCFAllocatorDefault,
(const void**)keys, 1,
&kCFTypeArrayCallBacks);
CFErrorRef error = nullptr;
CFDictionaryRef attrs = CFURLCopyResourcePropertiesForKeys(file_url,
key_array,
&error);
CFRelease(key_array);
if (attrs) {
CFStringRef uti = static_cast<CFStringRef>(
CFDictionaryGetValue(attrs, kCFURLTypeIdentifierKey));
if (uti) {
CFRetain(uti);
std::string content_mime = GetMimeTypeFromUTI(uti);
CFRelease(uti);
if (!content_mime.empty() &&
content_mime != "application/octet-stream") {
mime_type = content_mime;
}
}
CFRelease(attrs);
}
if (error) {
CFRelease(error);
}
}
return mime_type;
}
std::string GetMimeTypeFromUTI(CFStringRef uti) {
std::string mime_type = "application/octet-stream";
if (!uti) {
return mime_type;
}
CFStringRef mime = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType);
if (mime) {
char buffer[256];
if (CFStringGetCString(mime, buffer, sizeof(buffer), kCFStringEncodingUTF8)) {
mime_type = buffer;
}
CFRelease(mime);
}
return mime_type;
}
std::string DetectMimeTypeFromData(const unsigned char* data, size_t data_size) {
std::string mime_type = "application/octet-stream";
// 简单的魔术字节检测
if (data_size >= 8) {
if (data_size >= 4 && data[0] == 0x25 && data[1] == 0x50 &&
data[2] == 0x44 && data[3] == 0x46) {
return "application/pdf";
}
else if (data_size >= 3 && data[0] == 0xFF && data[1] == 0xD8 && data[2] == 0xFF) {
return "image/jpeg";
}
else if (data_size >= 8 && data[0] == 0x89 && data[1] == 0x50 &&
data[2] == 0x4E && data[3] == 0x47 && data[4] == 0x0D &&
data[5] == 0x0A && data[6] == 0x1A && data[7] == 0x0A) {
return "image/png";
}
else if (data_size >= 6 && data[0] == 0x47 && data[1] == 0x49 &&
data[2] == 0x46 && data[3] == 0x38) {
return "image/gif";
}
else if (data_size >= 2 && data[0] == 0x42 && data[1] == 0x4D) {
return "image/bmp";
}
}
return mime_type;
}
};
// 创建Mac平台检测器
std::unique_ptr<IMimeTypeDetector> CreateMimeTypeDetector() {
return std::make_unique<MacMimeTypeDetector>();
}
#elif PLATFORM_WINDOWS
#include <windows.h>
#include <urlmon.h>
#include <shlwapi.h>
#include <io.h>
#pragma comment(lib, "urlmon.lib")
#pragma comment(lib, "shlwapi.lib")
class WindowsMimeTypeDetector : public IMimeTypeDetector {
public:
// 移除override关键字,使用正确的签名
std::string GetMimeType(const std::string& file_path,
DetectionMethod method = METHOD_BOTH) {
std::wstring w_file_path = StringToWString(file_path);
std::wstring mime_type = L"application/octet-stream";
if (method == METHOD_EXTENSION || method == METHOD_BOTH) {
mime_type = GetByExtension(w_file_path);
}
// 如果基于扩展名没找到,或者需要基于内容
if (mime_type == L"application/octet-stream" ||
method == METHOD_CONTENT) {
std::wstring content_type = GetByContent(w_file_path);
if (!content_type.empty() &&
content_type != L"application/octet-stream") {
mime_type = content_type;
}
}
return WStringToString(mime_type);
}
std::string GetMimeTypeFromMemory(const unsigned char* data,
size_t data_size,
const std::string& file_name = "",
const std::string& file_ext = "") {
std::wstring w_file_name = StringToWString(file_name);
std::wstring w_file_ext = StringToWString(file_ext);
std::wstring mime_type = L"application/octet-stream";
// 如果有扩展名,先尝试通过扩展名获取
if (!w_file_ext.empty() || !w_file_name.empty()) {
std::wstring ext = w_file_ext;
if (ext.empty() && !w_file_name.empty()) {
LPCWSTR ext_ptr = PathFindExtensionW(w_file_name.c_str());
if (ext_ptr && wcslen(ext_ptr) > 0) {
ext = ext_ptr;
}
}
if (!ext.empty()) {
wchar_t buffer[MAX_PATH] = { 0 };
DWORD size = MAX_PATH;
if (SUCCEEDED(AssocQueryStringW(0, ASSOCSTR_CONTENTTYPE,
ext.c_str(), nullptr,
buffer, &size))) {
mime_type = buffer;
}
}
}
// 如果基于扩展名没找到,或者扩展名为空,则通过内容检测
if (mime_type == L"application/octet-stream" ||
(w_file_ext.empty() && w_file_name.empty())) {
std::wstring content_type = GetByContentFromMemory(
reinterpret_cast<const BYTE*>(data),
data_size,
w_file_name);
if (!content_type.empty() &&
content_type != L"application/octet-stream") {
mime_type = content_type;
}
}
return WStringToString(mime_type);
}
std::string GetFileExtension(const std::string& file_name) {
size_t dot_pos = file_name.find_last_of('.');
if (dot_pos != std::string::npos && dot_pos < file_name.length() - 1) {
return file_name.substr(dot_pos + 1);
}
return "";
}
std::string GetMimeTypeFromFD(int fd,
const std::string& file_name = "") {
// 在Windows上,文件描述符需要转换为HANDLE
HANDLE file_handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
if (file_handle == INVALID_HANDLE_VALUE || !file_handle) {
return "application/octet-stream";
}
// 保存当前位置
DWORD current_pos = SetFilePointer(file_handle, 0, nullptr, FILE_CURRENT);
// 读取数据
BYTE buffer[4096];
DWORD bytes_read = 0;
BOOL success = ReadFile(file_handle, buffer, sizeof(buffer),
&bytes_read, nullptr);
// 恢复位置
SetFilePointer(file_handle, current_pos, nullptr, FILE_BEGIN);
if (!success || bytes_read == 0) {
return "application/octet-stream";
}
std::wstring w_file_name = StringToWString(file_name);
std::wstring w_mime_type = GetByContentFromMemory(buffer, bytes_read, w_file_name);
return WStringToString(w_mime_type);
}
private:
std::wstring GetByExtension(const std::wstring& file_path) {
LPCWSTR ext = PathFindExtensionW(file_path.c_str());
if (!ext || wcslen(ext) == 0) {
return L"application/octet-stream";
}
wchar_t buffer[MAX_PATH] = { 0 };
DWORD size = MAX_PATH;
if (SUCCEEDED(AssocQueryStringW(0, ASSOCSTR_CONTENTTYPE,
ext, nullptr, buffer, &size))) {
return buffer;
}
return L"application/octet-stream";
}
std::wstring GetByContent(const std::wstring& file_path) {
HANDLE hFile = CreateFileW(file_path.c_str(), GENERIC_READ,
FILE_SHARE_READ, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile == INVALID_HANDLE_VALUE) {
return L"";
}
BYTE buffer[4096];
DWORD bytes_read = 0;
std::wstring mime_type = L"";
if (ReadFile(hFile, buffer, sizeof(buffer), &bytes_read, nullptr) &&
bytes_read > 0) {
LPWSTR pwz_mime = nullptr;
HRESULT hr = FindMimeFromData(nullptr,
file_path.c_str(),
buffer,
bytes_read,
nullptr,
0,
&pwz_mime,
0);
if (SUCCEEDED(hr) && pwz_mime) {
mime_type = pwz_mime;
CoTaskMemFree(pwz_mime);
}
}
CloseHandle(hFile);
return mime_type.empty() ? L"application/octet-stream" : mime_type;
}
std::wstring GetByContentFromMemory(const BYTE* data,
size_t data_size,
const std::wstring& file_name) {
if (!data || data_size == 0) {
return L"application/octet-stream";
}
std::wstring mime_type = L"application/octet-stream";
DWORD read_size = static_cast<DWORD>(data_size > 4096 ? 4096 : data_size);
std::vector<BYTE> mutable_buffer(data, data + read_size);
LPWSTR pwz_mime = nullptr;
HRESULT hr = FindMimeFromData(nullptr,
file_name.empty() ? nullptr : file_name.c_str(),
mutable_buffer.data(),
read_size,
nullptr,
FMFD_DEFAULT,
&pwz_mime,
0);
if (SUCCEEDED(hr) && pwz_mime) {
mime_type = pwz_mime;
CoTaskMemFree(pwz_mime);
}
return mime_type;
}
static std::string WStringToString(const std::wstring& wstr) {
if (wstr.empty()) return "";
int size_needed = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(),
(int)wstr.size(), nullptr, 0, nullptr, nullptr);
std::string str(size_needed, 0);
WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.size(),
&str[0], size_needed, nullptr, nullptr);
return str;
}
static std::wstring StringToWString(const std::string& str) {
if (str.empty()) return L"";
int size_needed = MultiByteToWideChar(CP_UTF8, 0, str.c_str(),
(int)str.size(), nullptr, 0);
std::wstring wstr(size_needed, 0);
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(),
&wstr[0], size_needed);
return wstr;
}
};
// 创建Windows平台检测器
std::unique_ptr<IMimeTypeDetector> CreateMimeTypeDetector() {
return std::make_unique<WindowsMimeTypeDetector>();
}
#else
// 其他平台
class DefaultMimeTypeDetector : public IMimeTypeDetector {
public:
// 移除override关键字,使用正确的签名
std::string GetMimeType(const std::string& file_path,
DetectionMethod method = METHOD_BOTH) {
return "application/octet-stream";
}
std::string GetMimeTypeFromMemory(const unsigned char* data,
size_t data_size,
const std::string& file_name = "",
const std::string& file_ext = "") {
return "application/octet-stream";
}
std::string GetFileExtension(const std::string& file_name) {
size_t dot_pos = file_name.find_last_of('.');
if (dot_pos != std::string::npos && dot_pos < file_name.length() - 1) {
return file_name.substr(dot_pos + 1);
}
return "";
}
std::string GetMimeTypeFromFD(int fd,
const std::string& file_name = "") {
return "application/octet-stream";
}
};
std::unique_ptr<IMimeTypeDetector> CreateMimeTypeDetector() {
return std::make_unique<DefaultMimeTypeDetector>();
}
#endif
#include<iostream>
int main() {
//// 初始化COM
//CoInitialize(nullptr);
//// 测试内存检测
//std::vector<BYTE> pdf_data = { 0x25, 0x50, 0x44, 0x46, 0x2D }; // PDF魔术字节
//std::wstring mime = MimeTypeDetector::GetMimeTypeFromMemory(
// pdf_data.data(),
// pdf_data.size(),
// L"document.pdf");
//std::wcout << L"Detected MIME: " << mime << std::endl;
//CoUninitialize();
// 使用全局便捷函数
std::string mime = GetMimeType("D:\\Users\\Pictures\\1.png");
// 或使用工厂函数创建
auto detector = CreateMimeTypeDetector();
std::string mime2 = detector->GetMimeType("D:\\Users\\Pictures\\1.png");
return 0;
}为我写一个代码的介绍大纲