Go mime 包详解
概述
mime 包实现了 MIME(多用途互联网邮件扩展)规范的一部分。该包提供了 MIME 类型的解析、格式化、扩展名关联等功能,广泛用于 HTTP 内容类型处理、文件类型识别、电子邮件处理等场景。
重要说明:
- ✓ 支持 MIME 类型解析和格式化(RFC 1521、RFC 2045、RFC 2616)
- ✓ 内置常见文件扩展名与 MIME 类型的映射表
- ✓ 在 Unix 系统上可读取系统 MIME 数据库进行扩展
- ✓ 在 Windows 上从注册表提取 MIME 类型
- ✓ 文本类型默认设置 charset 参数为 “utf-8”
- ✓ Go 1.0+ 引入,部分功能在 Go 1.5+ 增强
包导入
import "mime"
基本使用
1. 根据扩展名获取 MIME 类型
package main
import (
"fmt"
"mime"
)
func main() {
// 获取常见文件的 MIME 类型
fmt.Printf(".html: %s\n", mime.TypeByExtension(".html"))
fmt.Printf(".json: %s\n", mime.TypeByExtension(".json"))
fmt.Printf(".mp4: %s\n", mime.TypeByExtension(".mp4"))
fmt.Printf(".txt: %s\n", mime.TypeByExtension(".txt"))
}
运行结果:
.html: text/html; charset=utf-8
.json: application/json
.mp4: video/mp4
.txt: text/plain; charset=utf-8
2. 解析 MIME 类型和参数
package main
import (
"fmt"
"mime"
)
func main() {
// 解析 Content-Type 头部
mediaType, params, err := mime.ParseMediaType("text/html; charset=utf-8")
if err != nil {
panic(err)
}
fmt.Printf("Media type: %s\n", mediaType)
fmt.Printf("Parameters: %v\n", params)
fmt.Printf("Charset: %s\n", params["charset"])
}
运行结果:
Media type: text/html
Parameters: map[charset:utf-8]
Charset: utf-8
3. 格式化 MIME 类型
package main
import (
"fmt"
"mime"
)
func main() {
// 格式化带参数的 MIME 类型
contentType := mime.FormatMediaType("text/html", map[string]string{
"charset": "utf-8",
"boundary": "----Boundary123",
})
fmt.Printf("Content-Type: %s\n", contentType)
}
运行结果:
Content-Type: text/html; boundary=----Boundary123; charset=utf-8
一、常量
BEncoding
定义:
const BEncoding = WordEncoder('b')
说明:
- 功能:Base64 编码方式的 WordEncoder 常量
- 用途:用于 RFC 2047 编码字,适合编码非 ASCII 字符
- 特点:编码后的字符串较长,但能安全传输任意字符
示例:
// 使用 BEncoding 编码包含中文的字符串
encoder := mime.BEncoding
encoded := encoder.Encode("utf-8", "你好,世界!")
fmt.Println(encoded) // =?utf-8?b?5L2g5aW977yM1L2g5aW977yB?=
QEncoding
定义:
const QEncoding = WordEncoder('q')
说明:
- 功能:Quoted-Printable 编码方式的 WordEncoder 常量
- 用途:用于 RFC 2047 编码字,适合编码 mostly ASCII 的文本
- 特点:编码后的字符串较短,适合 mostly ASCII 的内容
示例:
// 使用 QEncoding 编码
encoder := mime.QEncoding
encoded := encoder.Encode("utf-8", "Hello, 世界!")
fmt.Println(encoded) // =?utf-8?q?Hello,_=E4=B8=96=E7=95=8C!?=
二、变量
ErrInvalidMediaParameter
定义:
var ErrInvalidMediaParameter = errors.New("mime: invalid media parameter")
说明:
- 功能:解析媒体类型参数错误时返回的错误
- 触发条件:ParseMediaType 解析可选参数时出错
- 用途:用于错误处理和判断
示例:
mediaType, params, err := mime.ParseMediaType("text/html; invalid")
if err == mime.ErrInvalidMediaParameter {
fmt.Println("参数解析失败,但媒体类型已返回")
fmt.Println("Media type:", mediaType) // text/html
}
三、函数(按 a-z 排序)
AddExtensionType
定义:
func AddExtensionType(ext, typ string) error
说明:
- 功能:设置文件扩展名 ext 与 MIME 类型 typ 的关联
- 参数:
ext- 文件扩展名,必须以点开头(如 “.html”)typ- MIME 类型字符串
- 返回:错误信息(如果扩展名格式不正确)
- 用途:添加自定义 MIME 类型映射
示例:
package main
import (
"fmt"
"mime"
)
func main() {
// 添加自定义扩展名映射
err := mime.AddExtensionType(".myapp", "application/x-myapp")
if err != nil {
panic(err)
}
// 使用自定义映射
mimeType := mime.TypeByExtension(".myapp")
fmt.Printf(".myapp: %s\n", mimeType)
// 错误示例:扩展名不以点开头的会报错
err = mime.AddExtensionType("txt", "text/plain")
fmt.Println("Error:", err) // mime: extension should begin with a dot
}
运行结果:
.myapp: application/x-myapp
Error: mime: extension should begin with a dot
注意事项:
- 扩展名必须以点开头(如 “.html”)
- 会覆盖已有的映射关系
- 仅影响当前程序,不会修改系统配置
ExtensionsByType
定义:
func ExtensionsByType(typ string) ([]string, error)
说明:
- 功能:返回与 MIME 类型 typ 关联的所有文件扩展名
- 参数:
typ- MIME 类型字符串
- 返回:
[]string- 扩展名切片,每个扩展名以点开头error- 错误信息(如果类型不存在)
- 用途:根据 MIME 类型查找可能的文件扩展名
示例:
package main
import (
"fmt"
"mime"
)
func main() {
// 查找音频类型的扩展名
extensions, err := mime.ExtensionsByType("audio/mpeg")
if err != nil {
panic(err)
}
fmt.Println("audio/mpeg:", extensions)
// 查找图片类型的扩展名
extensions, err = mime.ExtensionsByType("image/jpeg")
if err != nil {
panic(err)
}
fmt.Println("image/jpeg:", extensions)
// 不存在的类型
extensions, err = mime.ExtensionsByType("application/nonexistent")
fmt.Println("nonexistent:", extensions, "error:", err)
}
运行结果:
audio/mpeg: [.mp3]
image/jpeg: [.jpg .jpeg .jpe .jfif .pjpeg .pjp]
nonexistent: [] mime: no such MIME type
系统增强:
- Unix/Linux:从以下文件扩展内置表:
/usr/local/share/mime/globs2/usr/share/mime/globs2/etc/mime.types/etc/apache2/mime.types/etc/apache/mime.types/etc/httpd/conf/mime.types
- Windows:从注册表提取扩展信息
FormatMediaType
定义:
func FormatMediaType(t string, param map[string]string) string
说明:
- 功能:序列化媒体类型 t 和参数 param,生成符合 RFC 2045 和 RFC 2616 的 MIME 类型字符串
- 参数:
t- 媒体类型(如 “text/html”)param- 参数字典(如{"charset": "utf-8"})
- 返回:格式化后的 MIME 类型字符串,如果参数违规则返回空字符串
- 特点:类型和参数名会转换为小写,参数按字母顺序排序
示例:
package main
import (
"fmt"
"mime"
)
func main() {
// 基本用法
contentType := mime.FormatMediaType("text/html", map[string]string{
"charset": "utf-8",
})
fmt.Println(contentType)
// 多个参数(按字母顺序排序)
params := map[string]string{
"boundary": "----MyBoundary123",
"charset": "utf-8",
}
contentType = mime.FormatMediaType("multipart/form-data", params)
fmt.Println(contentType)
// 需要编码的参数值
params = map[string]string{
"title": "文档(测试).txt",
}
contentType = mime.FormatMediaType("text/plain", params)
fmt.Println(contentType)
// 违规的类型(返回空字符串)
contentType = mime.FormatMediaType("invalid/type!", nil)
fmt.Println("Invalid:", contentType == "")
}
运行结果:
text/html; charset=utf-8
multipart/form-data; boundary=----MyBoundary123; charset=utf-8
text/plain; title=utf-8''%E6%96%87%E6%A1%A3%EF%BC%88%E6%B5%8B%E8%AF%95%EF%BC%89.txt
Invalid: true
编码规则:
- 参数值包含特殊字符时使用 UTF-8 编码
- 格式:
param*=utf-8''%E7%BC%96%E7%A0%81%E5%80%BC - 普通参数值用引号包裹(如有必要)
ParseMediaType
定义:
func ParseMediaType(v string) (mediatype string, params map[string]string, err error)
说明:
- 功能:解析 MIME 媒体类型值和可选参数(RFC 1521)
- 参数:
v- MIME 类型字符串(如 Content-Type 头部值)
- 返回:
mediatype- 媒体类型(转换为小写并去除空格)params- 参数字典(键为小写,值保留原大小写)err- 错误信息
- 用途:解析 HTTP Content-Type、Content-Disposition 等头部
示例:
package main
import (
"fmt"
"mime"
)
func main() {
// 基本解析
mediaType, params, err := mime.ParseMediaType("text/html; charset=utf-8")
if err != nil {
panic(err)
}
fmt.Printf("Type: %s, Charset: %s\n", mediaType, params["charset"])
// 多个参数
mediaType, params, err = mime.ParseMediaType(`multipart/form-data; boundary="----WebKitFormBoundary7MA4YWxkTrZu0gW"`)
if err != nil {
panic(err)
}
fmt.Printf("Type: %s, Boundary: %s\n", mediaType, params["boundary"])
// 参数解析错误(但仍返回媒体类型)
mediaType, params, err = mime.ParseMediaType("text/plain; invalid")
if err == mime.ErrInvalidMediaParameter {
fmt.Printf("Partial: %s, Error: %v\n", mediaType, err)
}
// 编码的参数值
mediaType, params, err = mime.ParseMediaType(`text/plain; title*=utf-8''%E6%96%87%E6%A1%A3.txt`)
if err != nil {
panic(err)
}
fmt.Printf("Title: %s\n", params["title"])
}
运行结果:
Type: text/html, Charset: utf-8
Type: multipart/form-data, Boundary: ----WebKitFormBoundary7MA4YWxkTrZu0gW
Partial: text/plain, Error: mime: invalid media parameter
Title: 文档.txt
注意事项:
- 媒体类型会转换为小写
- 参数名会转换为小写
- 参数值保留原始大小写
- 支持 RFC 2231 编码参数值
- 参数解析错误时仍返回媒体类型
TypeByExtension
定义:
func TypeByExtension(ext string) string
说明:
- 功能:返回与文件扩展名 ext 关联的 MIME 类型
- 参数:
ext- 文件扩展名(应以点开头,如 “.html”)
- 返回:MIME 类型字符串,如果没有关联则返回空字符串
- 特点:先区分大小写查找,再不区分大小写查找
- 用途:Web 服务器设置 Content-Type、文件上传验证等
示例:
package main
import (
"fmt"
"mime"
"net/http"
"os"
)
func main() {
// 常见扩展名
extensions := []string{".html", ".json", ".png", ".mp4", ".txt", ".pdf"}
for _, ext := range extensions {
mimeType := mime.TypeByExtension(ext)
fmt.Printf("%-6s -> %s\n", ext, mimeType)
}
// 大小写不敏感
fmt.Println("\nCase insensitive:")
fmt.Println(".JPG:", mime.TypeByExtension(".JPG"))
fmt.Println(".jpg:", mime.TypeByExtension(".jpg"))
// 不存在的扩展名
fmt.Println("\nUnknown:", mime.TypeByExtension(".unknown"))
// 实际使用:HTTP 响应
http.HandleFunc("/file", func(w http.ResponseWriter, r *http.Request) {
ext := ".html"
mimeType := mime.TypeByExtension(ext)
w.Header().Set("Content-Type", mimeType)
w.Write([]byte("<h1>Hello</h1>"))
})
// 实际使用:文件上传验证
func validateUpload(filename string) bool {
ext := ".exe"
mimeType := mime.TypeByExtension(ext)
// 阻止可执行文件
return mimeType != "application/octet-stream" &&
mimeType != "application/x-msdownload"
}
fmt.Println("\nValidate .exe:", validateUpload("test.exe"))
}
运行结果:
.html -> text/html; charset=utf-8
.json -> application/json
.png -> image/png
.mp4 -> video/mp4
.txt -> text/plain; charset=utf-8
.pdf -> application/pdf
Case insensitive:
.JPG: image/jpeg
.jpg: image/jpeg
Unknown:
Validate .exe: false
文本类型特性:
- 文本类型(text/*)默认添加
charset=utf-8参数 - 例如:
.txt→text/plain; charset=utf-8 - 例如:
.html→text/html; charset=utf-8
四、类型(按 a-z 排序)
WordDecoder
定义:
type WordDecoder struct {
CharsetReader func(charset string, input io.Reader) (io.Reader, error)
}
说明:
- 功能:解码包含 RFC 2047 编码字的 MIME 头部
- 字段:
CharsetReader- 可选的字符集读取器,用于处理非 UTF-8 编码
- 用途:解码电子邮件头部、国际化域名等
方法:
Decode
定义:
func (d *WordDecoder) Decode(word string) (string, error)
说明:
- 功能:解码单个 RFC 2047 编码字
- 参数:
word- 编码字字符串(格式:=?charset?encoding?encoded?=)
- 返回:解码后的字符串和错误信息
示例:
package main
import (
"fmt"
"mime"
)
func main() {
decoder := &mime.WordDecoder{}
// 解码 Base64 编码的字
word := "=?utf-8?b?wqFIb2xhLCBzZcOxb3Ih?="
decoded, err := decoder.Decode(word)
if err != nil {
panic(err)
}
fmt.Println(decoded) // ¡Hola, señor!
// 解码 Quoted-Printable 编码的字
word = "=?ISO-8859-1?q?Caf=E9?="
decoded, err = decoder.Decode(word)
if err != nil {
panic(err)
}
fmt.Println(decoded) // Café
}
运行结果:
¡Hola, señor!
Café
DecodeHeader
定义:
func (d *WordDecoder) DecodeHeader(header string) (string, error)
说明:
- 功能:解码头部字符串中的所有编码字
- 参数:
header- 包含多个编码字的头部字符串
- 返回:解码后的完整字符串和错误信息
- 特点:自动处理多个编码字的拼接
示例:
package main
import (
"fmt"
"mime"
)
func main() {
decoder := &mime.WordDecoder{}
// 解码包含多个编码字的头部
header := "From: =?utf-8?b?5L2g5aW9?= <user@example.com>"
decoded, err := decoder.DecodeHeader(header)
if err != nil {
panic(err)
}
fmt.Println(decoded)
// 实际使用:解析邮件头部
subject := "Subject: =?utf-8?q?Re=3A_Hello_=E4=B8=96=E7=95=8C!?="
decoded, err = decoder.DecodeHeader(subject)
if err != nil {
panic(err)
}
fmt.Println(decoded)
}
运行结果:
From: 你好 <user@example.com>
Subject: Re: Hello 世界!
CharsetReader 使用:
// 处理非 UTF-8 编码
decoder := &mime.WordDecoder{
CharsetReader: func(charset string, input io.Reader) (io.Reader, error) {
// 使用 golang.org/x/text/encoding 转换编码
// 例如:return charset.NewReader(input, charset)
return nil, fmt.Errorf("unsupported charset: %s", charset)
},
}
WordEncoder
定义:
type WordEncoder byte
说明:
- 功能:RFC 2047 编码字编码器
- 类型:byte 类型,值为 ‘b’(Base64)或 ‘q’(Quoted-Printable)
- 用途:编码非 ASCII 字符用于 MIME 头部
方法:
Encode
定义:
func (e WordEncoder) Encode(charset, s string) string
说明:
- 功能:返回字符串 s 的编码字形式
- 参数:
charset- IANA 字符集名称(不区分大小写)s- 要编码的字符串
- 返回:编码后的字符串
- 特点:如果 s 是不含特殊字符的 ASCII,则返回原字符串
示例:
package main
import (
"fmt"
"mime"
)
func main() {
// Base64 编码
bEncoder := mime.BEncoding
encoded := bEncoder.Encode("utf-8", "¡Hola, señor!")
fmt.Println("B64:", encoded)
// Quoted-Printable 编码
qEncoder := mime.QEncoding
encoded = qEncoder.Encode("utf-8", "Hello!")
fmt.Println("QP:", encoded) // ASCII 不变
encoded = qEncoder.Encode("utf-8", "Café")
fmt.Println("QP:", encoded)
// 实际使用:设置邮件头部
subject := "你好,世界!"
encodedSubject := mime.BEncoding.Encode("utf-8", subject)
fmt.Printf("Subject: =?utf-8?b?%s?=\n", encodedSubject)
}
运行结果:
B64: =?utf-8?b?wqFIb2xhLCBzZcOxb3Ih?=
QP: Hello!
QP: =?utf-8?q?Caf=C3=A9?=
Subject: =?utf-8?b?5L2g5aW977yM1L2g5aW977yB?=
编码格式:
- 格式:
=?charset?encoding?encoded?= - encoding: ‘b’ 表示 Base64,‘q’ 表示 Quoted-Printable
- charset: IANA 字符集名称(如 utf-8、ISO-8859-1)
五、典型示例
示例 1:Web 服务器 Content-Type 设置
package main
import (
"fmt"
"mime"
"net/http"
)
func main() {
http.HandleFunc("/serve", func(w http.ResponseWriter, r *http.Request) {
// 根据文件扩展名设置 Content-Type
ext := ".html"
mimeType := mime.TypeByExtension(ext)
w.Header().Set("Content-Type", mimeType)
w.Write([]byte("<h1>Hello World</h1>"))
})
// 测试
extensions := []string{".html", ".json", ".png", ".css", ".js"}
for _, ext := range extensions {
mimeType := mime.TypeByExtension(ext)
fmt.Printf("%s: %s\n", ext, mimeType)
}
}
运行结果:
.html: text/html; charset=utf-8
.json: application/json
.png: image/png
.css: text/css; charset=utf-8
.js: text/javascript; charset=utf-8
示例 2:文件上传 MIME 类型验证
package main
import (
"fmt"
"mime"
)
// 允许的 MIME 类型白名单
var allowedMimeTypes = map[string]bool{
"image/jpeg": true,
"image/png": true,
"image/gif": true,
"application/pdf": true,
}
func validateFile(filename string) error {
ext := getFileExtension(filename)
mimeType := mime.TypeByExtension(ext)
if mimeType == "" {
return fmt.Errorf("unknown file type: %s", ext)
}
// 提取主类型(不含参数)
mainType, _, _ := mime.ParseMediaType(mimeType)
if !allowedMimeTypes[mainType] {
return fmt.Errorf("file type not allowed: %s", mimeType)
}
return nil
}
func getFileExtension(filename string) string {
// 简单实现,实际应使用 path/filepath.Ext
for i := len(filename) - 1; i >= 0; i-- {
if filename[i] == '.' {
return filename[i:]
}
}
return ""
}
func main() {
files := []string{
"photo.jpg",
"document.pdf",
"script.exe",
"image.png",
}
for _, file := range files {
if err := validateFile(file); err != nil {
fmt.Printf("❌ %s: %v\n", file, err)
} else {
fmt.Printf("✓ %s: allowed\n", file)
}
}
}
运行结果:
✓ photo.jpg: allowed
✓ document.pdf: allowed
❌ script.exe: file type not allowed: application/octet-stream
✓ image.png: allowed
示例 3:解析 HTTP Content-Type 头部
package main
import (
"fmt"
"mime"
)
func parseContentType(contentType string) {
mediaType, params, err := mime.ParseMediaType(contentType)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Content-Type: %s\n", mediaType)
for key, value := range params {
fmt.Printf(" %s: %s\n", key, value)
}
fmt.Println()
}
func main() {
// 各种 Content-Type 示例
headers := []string{
"text/html; charset=utf-8",
"application/json",
`multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW`,
"text/plain; charset=iso-8859-1",
`application/octet-stream; name="文件.txt"`,
}
for _, header := range headers {
fmt.Printf("Parsing: %s\n", header)
parseContentType(header)
}
}
运行结果:
Parsing: text/html; charset=utf-8
Content-Type: text/html
charset: utf-8
Parsing: application/json
Content-Type: application/json
Parsing: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Type: multipart/form-data
boundary: ----WebKitFormBoundary7MA4YWxkTrZu0gW
Parsing: text/plain; charset=iso-8859-1
Content-Type: text/plain
charset: iso-8859-1
Parsing: application/octet-stream; name="文件.txt"
Content-Type: application/octet-stream
name: 文件.txt
示例 4:电子邮件头部编码
package main
import (
"fmt"
"mime"
)
func main() {
// 编码包含非 ASCII 字符的邮件头部
subject := "会议通知:下周一下午 2 点"
from := "张三 <zhangsan@example.com>"
to := "李四 <lisi@example.com>"
// 使用 Base64 编码主题
encodedSubject := mime.BEncoding.Encode("utf-8", subject)
fmt.Printf("Subject: =?utf-8?b?%s?=\n", encodedSubject)
// 解码示例
decoder := &mime.WordDecoder{}
// 模拟接收到的编码头部
encodedHeader := "=?utf-8?b?5byA5LqM6ICF5Lq677y65Lq65LiW6LWE5pu0MOWFqTIw5Lq6?="
decoded, err := decoder.Decode(encodedHeader)
if err != nil {
panic(err)
}
fmt.Printf("Decoded: %s\n", decoded)
// 完整邮件头示例
fmt.Println("\n完整邮件头:")
fmt.Printf("From: %s\n", from)
fmt.Printf("To: %s\n", to)
fmt.Printf("Subject: =?utf-8?b?%s?=\n", encodedSubject)
}
运行结果:
Subject: =?utf-8?b?5byA5LqM6ICF5Lq677y65Lq65LiW6LWE5pu0MOWFqTIw5Lq6?=
Decoded: 会议通知:下周一下午 2 点
完整邮件头:
From: 张三 <zhangsan@example.com>
To: 李四 <lisi@example.com>
Subject: =?utf-8?b?5byA5LqM6ICF5Lq677y65Lq65LiW6LWE5pu0MOWFqTIw5Lq6?=
示例 5:添加自定义 MIME 类型
package main
import (
"fmt"
"mime"
)
func main() {
// 添加自定义扩展名映射
customTypes := map[string]string{
".myapp": "application/x-myapp",
".data": "application/x-custom-data",
".config": "application/x-config",
}
for ext, mimeType := range customTypes {
err := mime.AddExtensionType(ext, mimeType)
if err != nil {
fmt.Printf("Error adding %s: %v\n", ext, err)
}
}
// 验证自定义类型
fmt.Println("自定义 MIME 类型:")
for ext := range customTypes {
mimeType := mime.TypeByExtension(ext)
fmt.Printf("%s -> %s\n", ext, mimeType)
}
// 反向查找扩展名
fmt.Println("\n根据 MIME 类型查找扩展名:")
for _, mimeType := range customTypes {
extensions, err := mime.ExtensionsByType(mimeType)
if err != nil {
fmt.Printf("%s: %v\n", mimeType, err)
} else {
fmt.Printf("%s: %v\n", mimeType, extensions)
}
}
}
运行结果:
自定义 MIME 类型:
.myapp -> application/x-myapp
.data -> application/x-custom-data
.config -> application/x-config
根据 MIME 类型查找扩展名:
application/x-myapp: [.myapp]
application/x-custom-data: [.data]
application/x-config: [.config]
示例 6:格式化复杂的 MIME 类型
package main
import (
"fmt"
"mime"
)
func main() {
// 简单的 MIME 类型
contentType := mime.FormatMediaType("text/html", map[string]string{
"charset": "utf-8",
})
fmt.Println("Simple:", contentType)
// 多个参数(按字母顺序排序)
params := map[string]string{
"charset": "utf-8",
"boundary": "----MyBoundary123",
}
contentType = mime.FormatMediaType("multipart/form-data", params)
fmt.Println("Multi:", contentType)
// 包含特殊字符的参数
params = map[string]string{
"name": "文件(测试).txt",
}
contentType = mime.FormatMediaType("application/octet-stream", params)
fmt.Println("Special:", contentType)
// 违规的类型(返回空字符串)
contentType = mime.FormatMediaType("invalid!", nil)
fmt.Println("Invalid:", contentType == "")
}
运行结果:
Simple: text/html; charset=utf-8
Multi: multipart/form-data; boundary=----MyBoundary123; charset=utf-8
Special: application/octet-stream; name=utf-8''%E6%96%87%E4%BB%B6%EF%BC%88%E6%B5%8B%E8%AF%95%EF%BC%89.txt
Invalid: true
示例 7:批量文件类型检测
package main
import (
"fmt"
"mime"
"path/filepath"
)
func detectFileTypes(filenames []string) {
fmt.Printf("%-20s %-10s %-30s\n", "文件名", "扩展名", "MIME 类型")
fmt.Println(strings.Repeat("-", 65))
for _, filename := range filenames {
ext := filepath.Ext(filename)
mimeType := mime.TypeByExtension(ext)
if mimeType == "" {
mimeType = "unknown"
}
fmt.Printf("%-20s %-10s %-30s\n", filename, ext, mimeType)
}
}
func main() {
files := []string{
"index.html",
"style.css",
"script.js",
"data.json",
"image.png",
"photo.jpg",
"video.mp4",
"audio.mp3",
"document.pdf",
"archive.zip",
}
detectFileTypes(files)
}
运行结果:
文件名 扩展名 MIME 类型
-----------------------------------------------------------------
index.html .html text/html; charset=utf-8
style.css .css text/css; charset=utf-8
script.js .js text/javascript; charset=utf-8
data.json .json application/json
image.png .png image/png
photo.jpg .jpg image/jpeg
video.mp4 .mp4 video/mp4
audio.mp3 .mp3 audio/mpeg
document.pdf .pdf application/pdf
archive.zip .zip application/zip
示例 8:MIME 类型工具函数
package main
import (
"fmt"
"mime"
"strings"
)
// IsTextType 判断是否为文本类型
func IsTextType(mimeType string) bool {
mediaType, _, _ := mime.ParseMediaType(mimeType)
return strings.HasPrefix(mediaType, "text/") ||
mediaType == "application/json" ||
mediaType == "application/xml" ||
mediaType == "application/javascript"
}
// IsImageType 判断是否为图片类型
func IsImageType(mimeType string) bool {
mediaType, _, _ := mime.ParseMediaType(mimeType)
return strings.HasPrefix(mediaType, "image/")
}
// IsVideoType 判断是否为视频类型
func IsVideoType(mimeType string) bool {
mediaType, _, _ := mime.ParseMediaType(mimeType)
return strings.HasPrefix(mediaType, "video/")
}
// IsAudioType 判断是否为音频类型
func IsAudioType(mimeType string) bool {
mediaType, _, _ := mime.ParseMediaType(mimeType)
return strings.HasPrefix(mediaType, "audio/")
}
// GetCharset 从 MIME 类型中提取字符集
func GetCharset(mimeType string) string {
_, params, err := mime.ParseMediaType(mimeType)
if err != nil {
return ""
}
return params["charset"]
}
func main() {
mimeTypes := []string{
"text/html; charset=utf-8",
"application/json",
"image/png",
"video/mp4",
"audio/mpeg",
"application/pdf",
}
fmt.Printf("%-30s %-8s %-8s %-8s %-8s %-10s\n",
"MIME 类型", "文本", "图片", "视频", "音频", "字符集")
fmt.Println(strings.Repeat("-", 80))
for _, mimeType := range mimeTypes {
fmt.Printf("%-30s %-8v %-8v %-8v %-8v %-10s\n",
mimeType,
IsTextType(mimeType),
IsImageType(mimeType),
IsVideoType(mimeType),
IsAudioType(mimeType),
GetCharset(mimeType))
}
}
运行结果:
MIME 类型 文本 图片 视频 音频 字符集
--------------------------------------------------------------------------------
text/html; charset=utf-8 true false false false utf-8
application/json true false false false
image/png false true false false
video/mp4 false false true false
audio/mpeg false false false true
application/pdf false false false false
六、最佳实践
1. Web 服务器 Content-Type 设置
// ✓ 正确:使用 TypeByExtension 设置 Content-Type
func serveFile(w http.ResponseWriter, filename string) {
ext := filepath.Ext(filename)
mimeType := mime.TypeByExtension(ext)
if mimeType == "" {
mimeType = "application/octet-stream"
}
w.Header().Set("Content-Type", mimeType)
http.ServeFile(w, nil, filename)
}
// ✗ 错误:硬编码 MIME 类型
w.Header().Set("Content-Type", "text/html") // 不灵活
2. 文件上传验证
// ✓ 正确:使用白名单验证
var allowedTypes = map[string]bool{
"image/jpeg": true,
"image/png": true,
}
func validateUpload(filename string) error {
ext := filepath.Ext(filename)
mimeType := mime.TypeByExtension(ext)
mediaType, _, _ := mime.ParseMediaType(mimeType)
if !allowedTypes[mediaType] {
return fmt.Errorf("file type not allowed")
}
return nil
}
// ✗ 错误:仅检查扩展名
if ext != ".jpg" && ext != ".png" { // 不安全 }
3. 处理未知类型
// ✓ 正确:提供默认值
mimeType := mime.TypeByExtension(ext)
if mimeType == "" {
mimeType = "application/octet-stream" // 默认二进制流
}
// ✗ 错误:使用空字符串
if mimeType == "" {
mimeType = "" // 可能导致浏览器错误处理
}
4. 解析 Content-Type
// ✓ 正确:处理解析错误
mediaType, params, err := mime.ParseMediaType(contentType)
if err != nil {
if err == mime.ErrInvalidMediaParameter {
// 参数错误,但媒体类型可用
log.Printf("Warning: %v, using media type: %s", err, mediaType)
} else {
// 其他错误
return err
}
}
// ✗ 错误:忽略错误
mediaType, params, _ := mime.ParseMediaType(contentType)
5. 添加自定义类型
// ✓ 正确:检查错误
err := mime.AddExtensionType(".myapp", "application/x-myapp")
if err != nil {
log.Printf("Failed to add extension type: %v", err)
}
// ✗ 错误:扩展名不以点开头
mime.AddExtensionType("myapp", "application/x-myapp") // 会报错
七、与其他包配合
1. 与 net/http 配合
import (
"mime"
"net/http"
"path/filepath"
)
func handleFile(w http.ResponseWriter, r *http.Request) {
filename := "document.pdf"
// 设置 Content-Type
ext := filepath.Ext(filename)
mimeType := mime.TypeByExtension(ext)
w.Header().Set("Content-Type", mimeType)
// 设置 Content-Disposition
w.Header().Set("Content-Disposition",
fmt.Sprintf("attachment; filename=\"%s\"", filename))
http.ServeFile(w, r, filename)
}
2. 与 mime/multipart 配合
import (
"mime"
"mime/multipart"
)
func parseMultipartForm(r *http.Request) {
// 解析 Content-Type 获取 boundary
mediaType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil || !strings.HasPrefix(mediaType, "multipart/") {
panic(err)
}
// 创建 multipart reader
mr := multipart.NewReader(r.Body, params["boundary"])
// 处理各个部分
for {
part, err := mr.NextPart()
if err == io.EOF {
break
}
// 检查部分的 Content-Type
partType, partParams, _ := mime.ParseMediaType(part.Header.Get("Content-Type"))
// ...
}
}
3. 与 io 配合处理编码
import (
"io"
"mime"
"golang.org/x/text/encoding"
)
// 处理非 UTF-8 编码的邮件头部
decoder := &mime.WordDecoder{
CharsetReader: func(charset string, input io.Reader) (io.Reader, error) {
// 使用 golang.org/x/text/encoding 转换
enc, err := encoding.GetEncoding(charset)
if err != nil {
return nil, err
}
return enc.NewDecoder().Reader(input), nil
},
}
decoded, err := decoder.DecodeHeader(encodedHeader)
八、快速参考
常量
| 常量 | 值 | 说明 |
|---|---|---|
BEncoding | WordEncoder(‘b’) | Base64 编码方式 |
QEncoding | WordEncoder(‘q’) | Quoted-Printable 编码方式 |
变量
| 变量 | 类型 | 说明 |
|---|---|---|
ErrInvalidMediaParameter | error | 解析媒体类型参数错误 |
函数
| 函数 | 功能 | 返回值 |
|---|---|---|
AddExtensionType(ext, typ) | 添加扩展名映射 | error |
ExtensionsByType(typ) | 根据 MIME 类型查扩展名 | []string, error |
FormatMediaType(t, param) | 格式化 MIME 类型 | string |
ParseMediaType(v) | 解析 MIME 类型 | mediatype, params, error |
TypeByExtension(ext) | 根据扩展名获取 MIME 类型 | string |
类型
| 类型 | 功能 |
|---|---|
WordDecoder | RFC 2047 编码字解码器 |
WordEncoder | RFC 2047 编码字编码器 |
WordDecoder 方法
| 方法 | 功能 |
|---|---|
Decode(word) | 解码单个编码字 |
DecodeHeader(header) | 解码整个头部 |
WordEncoder 方法
| 方法 | 功能 |
|---|---|
Encode(charset, s) | 编码字符串 |
九、注意事项
1. 扩展名格式
// ✓ 正确:扩展名以点开头
mime.AddExtensionType(".html", "text/html")
mime.TypeByExtension(".html")
// ✗ 错误:扩展名不以点开头
mime.AddExtensionType("html", "text/html") // 报错
2. 大小写处理
// MIME 类型查找不区分大小写
mime.TypeByExtension(".JPG") // image/jpeg
mime.TypeByExtension(".jpg") // image/jpeg
// 但建议统一使用小写
mime.TypeByExtension(".html") // ✓
3. 文本类型默认 charset
// 文本类型自动添加 charset=utf-8
mime.TypeByExtension(".txt") // text/plain; charset=utf-8
mime.TypeByExtension(".html") // text/html; charset=utf-8
// 非文本类型不添加
mime.TypeByExtension(".png") // image/png
4. 系统依赖
// Unix/Linux: 读取系统 MIME 数据库
// - /usr/share/mime/globs2
// - /etc/mime.types
// 等文件
// Windows: 从注册表提取
// HKEY_CLASSES_ROOT\.ext
// 不同系统可能有不同的 MIME 类型映射
5. 错误处理
// ParseMediaType 可能返回部分结果
mediaType, params, err := mime.ParseMediaType("text/html; invalid")
if err == mime.ErrInvalidMediaParameter {
// mediaType 仍然可用
fmt.Println("Media type:", mediaType) // text/html
}
// FormatMediaType 违规时返回空字符串
result := mime.FormatMediaType("invalid!", nil)
if result == "" {
// 参数或类型违规
}
6. 性能考虑
// TypeByExtension 使用 sync.Once 初始化
// 第一次调用会加载系统 MIME 数据库
// 后续调用性能很高
// ✓ 正确:直接调用
mimeType := mime.TypeByExtension(ext)
// ✗ 错误:重复初始化
// 不需要手动缓存,包内部已优化
7. 安全考虑
// 不要仅依赖扩展名验证文件类型
// 攻击者可能上传恶意文件但使用合法扩展名
// ✓ 正确:结合内容检测
func validateFile(filename string, content []byte) error {
// 1. 检查扩展名
ext := filepath.Ext(filename)
mimeType := mime.TypeByExtension(ext)
// 2. 检查实际内容(使用 magic number)
actualType := detectContentType(content)
// 3. 对比是否一致
if mimeType != actualType {
return fmt.Errorf("file content mismatch")
}
return nil
}
最后更新: 2026-04-05
Go 版本: Go 1.0+(ExtensionsByType 和 WordDecoder/Encoder 为 Go 1.5+)
包文档: https://pkg.go.dev/mime