Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 参数
  • 例如:.txttext/plain; charset=utf-8
  • 例如:.htmltext/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)

八、快速参考

常量

常量说明
BEncodingWordEncoder(‘b’)Base64 编码方式
QEncodingWordEncoder(‘q’)Quoted-Printable 编码方式

变量

变量类型说明
ErrInvalidMediaParametererror解析媒体类型参数错误

函数

函数功能返回值
AddExtensionType(ext, typ)添加扩展名映射error
ExtensionsByType(typ)根据 MIME 类型查扩展名[]string, error
FormatMediaType(t, param)格式化 MIME 类型string
ParseMediaType(v)解析 MIME 类型mediatype, params, error
TypeByExtension(ext)根据扩展名获取 MIME 类型string

类型

类型功能
WordDecoderRFC 2047 编码字解码器
WordEncoderRFC 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