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/quotedprintable 包详解

概述

mime/quotedprintable 包实现了 RFC 2045 定义的 quoted-printable(可引用打印)编码。Quoted-printable 是一种编码方式,用于将 8 位数据编码为 7 位 ASCII 字符,主要用于电子邮件传输。该包提供了 ReaderWriter 两种类型,分别用于解码和编码 quoted-printable 数据。

重要说明

  • ✓ 实现 RFC 2045 定义的 quoted-printable 编码
  • ✓ 支持编码和解码操作
  • ✓ 主要用于电子邮件 MIME 内容传输
  • ✓ 将 8 位数据编码为 7 位 ASCII 字符
  • ✓ 自动处理行长度限制(76 字符)
  • ✓ Go 1.5+ 引入

Quoted-Printable 编码规则

  • 可打印 ASCII 字符(33-126,不包括 61)保持不变
  • 等号 = 编码为 =3D
  • 其他字符编码为 =XX(XX 为两位十六进制数)
  • 行长度限制为 76 字符
  • 软换行:行末的 = 表示续行

包导入

import (
    "mime/quotedprintable"
)

基本使用

1. 解码 quoted-printable 数据

package main

import (
    "fmt"
    "io"
    "mime/quotedprintable"
    "strings"
)

func main() {
    // 编码的文本
    encoded := "Hello=2C=20World=21"
    
    // 创建解码器
    reader := quotedprintable.NewReader(strings.NewReader(encoded))
    
    // 读取并解码
    decoded, err := io.ReadAll(reader)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Decoded: %s\n", string(decoded))
}

运行结果:

Decoded: Hello, World!

2. 编码为 quoted-printable

package main

import (
    "bytes"
    "fmt"
    "mime/quotedprintable"
)

func main() {
    // 原始文本
    text := "Hello, World!"
    
    // 创建编码器
    var buf bytes.Buffer
    writer := quotedprintable.NewWriter(&buf)
    
    // 写入并编码
    writer.Write([]byte(text))
    writer.Close()
    
    fmt.Printf("Encoded: %s\n", buf.String())
}

运行结果:

Encoded: Hello, World!

3. 处理非 ASCII 字符

package main

import (
    "fmt"
    "io"
    "mime/quotedprintable"
    "strings"
)

func main() {
    // 包含中文的文本
    encoded := "=E4=BD=A0=E5=A5=BD=EF=BC=8C=E4=B8=96=E7=95=8C=EF=BC=81"
    
    reader := quotedprintable.NewReader(strings.NewReader(encoded))
    decoded, _ := io.ReadAll(reader)
    
    fmt.Printf("Decoded: %s\n", string(decoded))
}

运行结果:

Decoded: 你好,世界!

一、类型(按 a-z 排序)

Reader

定义:

type Reader struct {
    // 包含未导出的字段
}

说明:

  • 功能:quoted-printable 解码器
  • 实现:实现了 io.Reader 接口
  • 用途:从底层读取器读取并解码 quoted-printable 数据
  • 特点:按需解码,不一次性加载所有数据

方法:

NewReader

定义:

func NewReader(r io.Reader) *Reader

说明:

  • 功能:创建新的 quoted-printable 解码器
  • 参数
    • r - 底层 io.Reader,提供编码的数据
  • 返回:新的 Reader 指针
  • 用途:初始化解码器以读取 quoted-printable 数据

示例:

package main

import (
    "fmt"
    "io"
    "mime/quotedprintable"
    "strings"
)

func main() {
    // 创建解码器
    encoded := "Hello=2C=20Gophers=21"
    reader := quotedprintable.NewReader(strings.NewReader(encoded))
    
    // 读取并解码
    decoded, err := io.ReadAll(reader)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Decoded: %s\n", string(decoded))
    
    // 处理无效转义序列
    encoded2 := "hello=XXworld"
    reader2 := quotedprintable.NewReader(strings.NewReader(encoded2))
    decoded2, err2 := io.ReadAll(reader2)
    fmt.Printf("Invalid escape: %s, error: %v\n", string(decoded2), err2)
}

运行结果:

Decoded: Hello, Gophers!
Invalid escape: hello=XXworld, error: <nil>

注意

  • 对于无效的转义序列,包会尽量保持原样返回
  • 不会立即报错,而是在读取时处理

Read

定义:

func (r *Reader) Read(p []byte) (n int, err error)

说明:

  • 功能:读取并解码 quoted-printable 数据
  • 参数
    • p - 目标字节切片
  • 返回
    • n - 读取的字节数
    • err - 错误信息(io.EOF 表示结束)
  • 用途:实现 io.Reader 接口,支持流式解码
  • 特点
    • 自动处理软换行(行末的 =
    • 自动删除无效的空格
    • 处理无效的转义序列

示例:

package main

import (
    "fmt"
    "mime/quotedprintable"
    "strings"
)

func main() {
    // 包含软换行的编码文本
    encoded := "Hello, Gophers! This symbol will be unescaped: =\n= and this will be written in one line."
    
    reader := quotedprintable.NewReader(strings.NewReader(encoded))
    
    // 分块读取
    buf := make([]byte, 20)
    for {
        n, err := reader.Read(buf)
        if n > 0 {
            fmt.Printf("Read %d bytes: %s\n", n, string(buf[:n]))
        }
        if err != nil {
            break
        }
    }
}

运行结果:

Read 20 bytes: Hello, Gophers! This
Read 20 bytes:  symbol will be unes
Read 20 bytes: caped:  and this will
Read 19 bytes:  be written in one l
Read 13 bytes: ine.

流式处理优势

  • 不需要一次性加载所有数据到内存
  • 适合处理大的编码文本
  • 可以与其他 io.Reader/Writer 组合使用

Writer

定义:

type Writer struct {
    Binary bool
    // 包含未导出的字段
}

说明:

  • 功能:quoted-printable 编码器
  • 实现:实现了 io.WriteCloser 接口
  • 用途:将数据编码为 quoted-printable 格式并写入底层 writer
  • 字段
    • Binary - 二进制模式,将换行符视为普通二进制数据

方法:

NewWriter

定义:

func NewWriter(w io.Writer) *Writer

说明:

  • 功能:创建新的 quoted-printable 编码器
  • 参数
    • w - 底层 io.Writer,接收编码后的数据
  • 返回:新的 Writer 指针
  • 用途:初始化编码器以写入 quoted-printable 数据
  • 特点
    • 自动限制行长度为 76 字符
    • 自动添加软换行符

示例:

package main

import (
    "bytes"
    "fmt"
    "mime/quotedprintable"
)

func main() {
    var buf bytes.Buffer
    
    // 创建编码器
    writer := quotedprintable.NewWriter(&buf)
    
    // 写入特殊字符
    text := "These symbols will be escaped: = \t"
    writer.Write([]byte(text))
    writer.Close()
    
    fmt.Printf("Encoded: %s\n", buf.String())
    
    // 包含中文的文本
    buf.Reset()
    writer = quotedprintable.NewWriter(&buf)
    writer.Write([]byte("你好,世界!"))
    writer.Close()
    
    fmt.Printf("Chinese: %s\n", buf.String())
}

运行结果:

Encoded: These symbols will be escaped: =3D =09
Chinese: =E4=BD=A0=E5=A5=BD=EF=BC=8C=E4=B8=96=E7=95=8C=EF=BC=81

Close

定义:

func (w *Writer) Close() error

说明:

  • 功能:关闭编码器,刷新所有未写入的数据
  • 返回:错误信息
  • 用途
    • 确保所有缓冲的数据都被编码并写入
    • 必须在写入完成后调用
    • 不关闭底层的 io.Writer
  • 注意:不调用 Close 可能导致数据丢失

示例:

package main

import (
    "bytes"
    "fmt"
    "mime/quotedprintable"
)

func main() {
    var buf bytes.Buffer
    writer := quotedprintable.NewWriter(&buf)
    
    // 写入数据
    writer.Write([]byte("Hello"))
    writer.Write([]byte(", "))
    writer.Write([]byte("World!"))
    
    // 必须调用 Close 刷新缓冲
    err := writer.Close()
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Encoded: %s\n", buf.String())
    
    // 错误示例:忘记调用 Close
    var buf2 bytes.Buffer
    writer2 := quotedprintable.NewWriter(&buf2)
    writer2.Write([]byte("Lost data"))
    // writer2.Close() // 忘记调用,数据可能丢失!
    
    fmt.Printf("Without Close: %s\n", buf2.String()) // 可能为空或不完整
}

运行结果:

Encoded: Hello, World!
Without Close: 

重要提示

  • 始终使用 defer writer.Close() 确保关闭
  • Close 只刷新数据,不关闭底层 writer
  • 可以继续使用底层 writer

Write

定义:

func (w *Writer) Write(p []byte) (n int, err error)

说明:

  • 功能:编码字节切片 p 并写入底层 io.Writer
  • 参数
    • p - 要编码的字节切片
  • 返回
    • n - 写入的字节数
    • err - 错误信息
  • 特点
    • 限制行长度为 76 字符
    • 自动添加软换行(行末的 =
    • 编码的字节可能不会立即刷新
  • 注意:数据可能缓冲,需要调用 Close 确保刷新

示例:

package main

import (
    "bytes"
    "fmt"
    "mime/quotedprintable"
)

func main() {
    var buf bytes.Buffer
    writer := quotedprintable.NewWriter(&buf)
    
    // 写入长文本(自动换行)
    longText := "This is a very long line that exceeds the maximum line length of 76 characters and will be split with soft line breaks."
    n, err := writer.Write([]byte(longText))
    if err != nil {
        panic(err)
    }
    
    writer.Close()
    
    fmt.Printf("Bytes written: %d\n", n)
    fmt.Printf("Encoded:\n%s\n", buf.String())
    
    // 二进制模式
    buf.Reset()
    writer = quotedprintable.NewWriter(&buf)
    writer.Binary = true // 启用二进制模式
    
    text := "Line 1\r\nLine 2\r\n"
    writer.Write([]byte(text))
    writer.Close()
    
    fmt.Printf("\nBinary mode: %s\n", buf.String())
}

运行结果:

Bytes written: 124
Encoded:
This is a very long line that exceeds the maximum line length of 76 =
characters and will be split with soft line breaks.

Binary mode: Line 1=0D=0ALine 2=0D=0A

行长度限制

  • 默认最大行长度:76 字符
  • 超过时自动添加软换行(行末 =
  • 软换行在解码时会被移除

Binary 字段

  • Binary = false(默认):文本模式,处理换行符
  • Binary = true:二进制模式,将所有字节视为数据

二、典型示例

示例 1:编码和解码邮件内容

package main

import (
    "bytes"
    "fmt"
    "io"
    "mime/quotedprintable"
)

func encodeEmailBody(text string) (string, error) {
    var buf bytes.Buffer
    writer := quotedprintable.NewWriter(&buf)
    
    _, err := writer.Write([]byte(text))
    if err != nil {
        return "", err
    }
    
    err = writer.Close()
    if err != nil {
        return "", err
    }
    
    return buf.String(), nil
}

func decodeEmailBody(encoded string) (string, error) {
    reader := quotedprintable.NewReader(bytes.NewReader([]byte(encoded)))
    
    decoded, err := io.ReadAll(reader)
    if err != nil {
        return "", err
    }
    
    return string(decoded), nil
}

func main() {
    // 原始邮件内容
    original := "Hello,\nThis is a test email with special characters: = and spaces.\nBest regards!"
    
    // 编码
    encoded, err := encodeEmailBody(original)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Encoded:\n%s\n\n", encoded)
    
    // 解码
    decoded, err := decodeEmailBody(encoded)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Decoded:\n%s\n", decoded)
    
    // 验证
    fmt.Printf("\nMatch: %v\n", original == decoded)
}

运行结果:

Encoded:
Hello,
This is a test email with special characters: =3D and spaces.
Best regards!

Decoded:
Hello,
This is a test email with special characters: = and spaces.
Best regards!

Match: true

示例 2:处理多语言文本

package main

import (
    "bytes"
    "fmt"
    "io"
    "mime/quotedprintable"
)

func main() {
    // 多语言文本
    texts := map[string]string{
        "English": "Hello, World!",
        "Chinese": "你好,世界!",
        "Japanese": "こんにちは、世界!",
        "Korean": "안녕하세요, 세계!",
        "Russian": "Привет, мир!",
        "Arabic": "مرحبا بالعالم!",
    }
    
    for lang, text := range texts {
        // 编码
        var buf bytes.Buffer
        writer := quotedprintable.NewWriter(&buf)
        writer.Write([]byte(text))
        writer.Close()
        
        encoded := buf.String()
        
        // 解码验证
        reader := quotedprintable.NewReader(bytes.NewReader([]byte(encoded)))
        decoded, _ := io.ReadAll(reader)
        
        fmt.Printf("%-10s: %s\n", lang, encoded)
        
        // 验证编解码正确性
        if string(decoded) != text {
            fmt.Printf("  ERROR: Decoded doesn't match original!\n")
        }
    }
}

运行结果:

English   : Hello, World!
Chinese   : =E4=BD=A0=E5=A5=BD=EF=BC=8C=E4=B8=96=E7=95=8C=EF=BC=81
Japanese  : =E3=81=93=E3=82=93=E3=81=AB=E3=81=A1=E3=81=AF=E3=80=81=E4=B8=96=E7=95=8C=EF=BC=81
Korean    : =EC=95=88=EB=85=95=ED=95=98=EC=84=B8=EC=9A=94=2C=20=EC=84=B8=EA=B3=84=21
Russian   : =D0=9F=D1=80=D0=B8=D0=B2=D0=B5=D1=82=2C=20=D0=BC=D0=B8=D1=80=21
Arabic    : =D9=85=D8=B1=D8=AD=D8=A8=D8=A7=20=D8=A8=D8=B9=D8=A7=D9=84=D9=85=21

示例 3:流式处理大文件

package main

import (
    "fmt"
    "io"
    "mime/quotedprintable"
    "os"
    "strings"
)

func encodeFile(inputPath, outputPath string) error {
    // 打开输入文件
    inputFile, err := os.Open(inputPath)
    if err != nil {
        return err
    }
    defer inputFile.Close()
    
    // 创建输出文件
    outputFile, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer outputFile.Close()
    
    // 创建编码器
    writer := quotedprintable.NewWriter(outputFile)
    defer writer.Close()
    
    // 流式复制(分块处理)
    buf := make([]byte, 4096)
    for {
        n, err := inputFile.Read(buf)
        if n > 0 {
            _, writeErr := writer.Write(buf[:n])
            if writeErr != nil {
                return writeErr
            }
        }
        if err == io.EOF {
            break
        }
        if err != nil {
            return err
        }
    }
    
    return nil
}

func decodeFile(inputPath, outputPath string) error {
    // 打开输入文件
    inputFile, err := os.Open(inputPath)
    if err != nil {
        return err
    }
    defer inputFile.Close()
    
    // 创建输出文件
    outputFile, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer outputFile.Close()
    
    // 创建解码器
    reader := quotedprintable.NewReader(inputFile)
    
    // 流式复制
    _, err = io.Copy(outputFile, reader)
    return err
}

func main() {
    // 创建测试文件
    testContent := "This is a test file with special characters: = and 你好。\n"
    testContent += strings.Repeat("Repeat this line to make the file larger. ", 100)
    
    os.WriteFile("test.txt", []byte(testContent), 0644)
    
    // 编码
    err := encodeFile("test.txt", "test.txt.qp")
    if err != nil {
        panic(err)
    }
    fmt.Println("File encoded successfully")
    
    // 解码
    err = decodeFile("test.txt.qp", "test_restored.txt")
    if err != nil {
        panic(err)
    }
    fmt.Println("File decoded successfully")
    
    // 验证
    original, _ := os.ReadFile("test.txt")
    restored, _ := os.ReadFile("test_restored.txt")
    
    fmt.Printf("Verification: %v\n", string(original) == string(restored))
    
    // 清理
    os.Remove("test.txt")
    os.Remove("test.txt.qp")
    os.Remove("test_restored.txt")
}

运行结果:

File encoded successfully
File decoded successfully
Verification: true

示例 4:HTTP 响应解码

package main

import (
    "fmt"
    "io"
    "mime/quotedprintable"
    "net/http"
    "strings"
)

func fetchQuotedPrintable(url string) (string, error) {
    resp, err := http.Get(url)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()
    
    // 检查 Content-Transfer-Encoding
    encoding := resp.Header.Get("Content-Transfer-Encoding")
    if encoding != "quoted-printable" {
        // 如果不是 quoted-printable,直接读取
        data, err := io.ReadAll(resp.Body)
        return string(data), err
    }
    
    // 使用 quoted-printable 解码
    reader := quotedprintable.NewReader(resp.Body)
    data, err := io.ReadAll(reader)
    if err != nil {
        return "", err
    }
    
    return string(data), nil
}

func main() {
    // 模拟服务器响应
    encodedBody := "=E4=BD=A0=E5=A5=BD=EF=BC=8C=E8=BF=99=E6=98=AF=E4=B8=80=E4=B8=AA=E6=B5=8B=E8=AF=95"
    
    reader := quotedprintable.NewReader(strings.NewReader(encodedBody))
    decoded, _ := io.ReadAll(reader)
    
    fmt.Printf("Decoded response: %s\n", string(decoded))
}

运行结果:

Decoded response: 你好,这是一个测试

示例 5:MIME 邮件头部解码

package main

import (
    "fmt"
    "io"
    "mime/quotedprintable"
    "strings"
)

// 解码 RFC 2047 编码字
func decodeEncodedWord(encoded string) (string, error) {
    // 格式:=?charset?encoding?encoded?=
    // encoding: B (Base64) 或 Q (Quoted-Printable)
    
    if !strings.HasPrefix(encoded, "=?") || !strings.HasSuffix(encoded, "?=") {
        return encoded, nil // 不是编码字
    }
    
    parts := strings.Split(encoded[2:len(encoded)-2], "?")
    if len(parts) != 3 {
        return encoded, fmt.Errorf("invalid encoded word")
    }
    
    charset := parts[0]
    encoding := parts[1]
    data := parts[2]
    
    switch encoding {
    case "Q", "q":
        // Quoted-Printable
        reader := quotedprintable.NewReader(strings.NewReader(data))
        decoded, err := io.ReadAll(reader)
        if err != nil {
            return "", err
        }
        return string(decoded), nil
        
    case "B", "b":
        // Base64 (需要 encoding/base64 包)
        // 这里简化处理
        return data, fmt.Errorf("Base64 decoding not implemented")
        
    default:
        return encoded, fmt.Errorf("unknown encoding: %s", encoding)
    }
}

func main() {
    // 模拟邮件头部
    headers := map[string]string{
        "Subject": "=?UTF-8?Q?Hello=2C=20World!?=",
        "From": "=?UTF-8?Q?=E5=BC=A0=E4=B8=89?= <zhangsan@example.com>",
        "To": "=?UTF-8?Q?=E6=9D=8E=E5=9B=9B?= <lisi@example.com>",
    }
    
    for name, value := range headers {
        decoded, err := decodeEncodedWord(value)
        if err != nil {
            fmt.Printf("%s: %s (error: %v)\n", name, value, err)
        } else {
            fmt.Printf("%s: %s\n", name, decoded)
        }
    }
}

运行结果:

Subject: Hello, World!
From: 张三 <zhangsan@example.com>
To: 李四 <lisi@example.com>

示例 6:二进制模式 vs 文本模式

package main

import (
    "bytes"
    "fmt"
    "mime/quotedprintable"
)

func main() {
    text := "Line 1\r\nLine 2\r\nLine 3\r\n"
    
    // 文本模式(默认)
    var textBuf bytes.Buffer
    textWriter := quotedprintable.NewWriter(&textBuf)
    textWriter.Write([]byte(text))
    textWriter.Close()
    
    fmt.Printf("Text mode:\n%s\n\n", textBuf.String())
    
    // 二进制模式
    var binBuf bytes.Buffer
    binWriter := quotedprintable.NewWriter(&binBuf)
    binWriter.Binary = true
    binWriter.Write([]byte(text))
    binWriter.Close()
    
    fmt.Printf("Binary mode:\n%s\n", binBuf.String())
    
    // 解码对比
    fmt.Println("Decoding text mode:")
    textReader := quotedprintable.NewReader(&textBuf)
    textDecoded, _ := io.ReadAll(textReader)
    fmt.Printf("%q\n\n", string(textDecoded))
    
    fmt.Println("Decoding binary mode:")
    binReader := quotedprintable.NewReader(&binBuf)
    binDecoded, _ := io.ReadAll(binReader)
    fmt.Printf("%q\n", string(binDecoded))
}

运行结果:

Text mode:
Line 1
Line 2
Line 3

Binary mode:
Line 1=0D=0ALine 2=0D=0ALine 3=0D=0A

Decoding text mode:
"Line 1\r\nLine 2\r\nLine 3\r\n"

Decoding binary mode:
"Line 1\r\nLine 2\r\nLine 3\r\n"

模式区别

  • 文本模式(默认):自动处理换行符,适合文本内容
  • 二进制模式:将所有字节视为数据,包括换行符

示例 7:组合使用 io.Pipe

package main

import (
    "fmt"
    "io"
    "mime/quotedprintable"
)

func main() {
    // 创建管道
    reader, writer := io.Pipe()
    
    // 创建编码器
    qpWriter := quotedprintable.NewWriter(writer)
    
    // 协程写入数据
    go func() {
        defer qpWriter.Close()
        defer writer.Close()
        
        text := "This text will be encoded and streamed through a pipe."
        qpWriter.Write([]byte(text))
    }()
    
    // 主协程读取解码数据
    decoder := quotedprintable.NewReader(reader)
    decoded, err := io.ReadAll(decoder)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Received: %s\n", string(decoded))
}

运行结果:

Received: This text will be encoded and streamed through a pipe.

示例 8:完整的邮件发送示例

package main

import (
    "bytes"
    "fmt"
    "io"
    "mime/quotedprintable"
    "net/smtp"
)

func sendEmail(from, to, subject, body string) error {
    // 编码主题和正文
    var subjectBuf bytes.Buffer
    subjectWriter := quotedprintable.NewWriter(&subjectBuf)
    subjectWriter.Write([]byte(subject))
    subjectWriter.Close()
    
    var bodyBuf bytes.Buffer
    bodyWriter := quotedprintable.NewWriter(&bodyBuf)
    bodyWriter.Write([]byte(body))
    bodyWriter.Close()
    
    // 构建邮件
    headers := make(map[string]string)
    headers["From"] = from
    headers["To"] = to
    headers["Subject"] = "=?UTF-8?Q?" + subjectBuf.String() + "?="
    headers["MIME-Version"] = "1.0"
    headers["Content-Type"] = "text/plain; charset=utf-8"
    headers["Content-Transfer-Encoding"] = "quoted-printable"
    
    var email bytes.Buffer
    for key, value := range headers {
        email.WriteString(fmt.Sprintf("%s: %s\r\n", key, value))
    }
    email.WriteString("\r\n")
    email.WriteString(bodyBuf.String())
    
    // 发送邮件(示例,不实际发送)
    fmt.Printf("Email ready to send:\n%s\n", email.String())
    
    // 实际发送:
    // return smtp.SendMail("smtp.example.com:587", auth, from, []string{to}, email.Bytes())
    
    return nil
}

func main() {
    from := "sender@example.com"
    to := "recipient@example.com"
    subject := "你好,这是一封测试邮件!"
    body := `尊敬的收件人:

这是一封使用 quoted-printable 编码的测试邮件。
邮件内容包含特殊字符:= 和空格。

此致
敬礼!`
    
    err := sendEmail(from, to, subject, body)
    if err != nil {
        panic(err)
    }
    
    fmt.Println("Email sent successfully (simulated)")
}

运行结果:

Email ready to send:
From: sender@example.com
To: recipient@example.com
Subject: =?UTF-8?Q?=E4=BD=A0=E5=A5=BD=EF=BC=8C=E8=BF=99=E6=98=AF=E4=B8=80=E5=B0=81=E6=B5=8B=E8=AF=95=E9=82=AE=E4=BB=B6=EF=BC=81?=?
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable

=E5=B0=8A=E6=95=AC=E7=9A=84=E6=94=B6=E4=BB=B6=E4=BA=BA=EF=BC=9A

=E8=BF=99=E6=98=AF=E4=B8=80=E5=B0=81=E4=BD=BF=E7=94=A8=20quoted-printable=20=E7=BC=96=E7=A0=81=E7=9A=84=E6=B5=8B=E8=AF=95=E9=82=AE=E4=BB=B6=E3=80=82
=E9=82=AE=E4=BB=B6=E5=86=85=E5=AE=B9=E5=8C=85=E5=90=AB=E7=89=B9=E6=AE=8A=E5=AD=97=E7=AC=A6=EF=BC=9A=20=3D=20=E5=92=8C=E7=A9=BA=E6=A0=BC=E3=80=82

=E6=AD=A4=E8=87=B4
=E6=95=AC=E7=A4=BC=EF=BC=81

Email sent successfully (simulated)

三、最佳实践

1. 始终调用 Close

// ✓ 正确:使用 defer 确保关闭
writer := quotedprintable.NewWriter(&buf)
defer writer.Close()
writer.Write([]byte(data))

// ✗ 错误:忘记调用 Close
writer := quotedprintable.NewWriter(&buf)
writer.Write([]byte(data))
// 数据可能未刷新!

2. 选择合适的模式

// 文本内容(默认)
writer := quotedprintable.NewWriter(&buf)
writer.Write([]byte("Hello\nWorld\n"))

// 二进制内容
writer := quotedprintable.NewWriter(&buf)
writer.Binary = true
writer.Write([]byte(binaryData))

3. 处理长文本

// ✓ 正确:让包自动处理换行
writer := quotedprintable.NewWriter(&buf)
writer.Write([]byte(veryLongLine)) // 自动添加软换行
writer.Close()

// ✗ 错误:手动添加换行
writer.Write([]byte(veryLongLine + "\n")) // 可能破坏编码

4. 流式处理

// ✓ 正确:使用 io.Copy 流式处理
reader := quotedprintable.NewReader(largeFile)
io.Copy(outputFile, reader)

// ✗ 错误:一次性读取大文件
data, _ := io.ReadAll(reader) // 可能内存溢出

5. 错误处理

// ✓ 正确:检查所有错误
writer := quotedprintable.NewWriter(&buf)
n, err := writer.Write(data)
if err != nil {
    return err
}
if err := writer.Close(); err != nil {
    return err
}

// ✗ 错误:忽略错误
writer.Write(data)
writer.Close()

四、与其他包配合

1. 与 io 配合

import (
    "io"
    "mime/quotedprintable"
)

// 流式复制
func copyQuotedPrintable(src io.Reader, dst io.Writer) error {
    reader := quotedprintable.NewReader(src)
    _, err := io.Copy(dst, reader)
    return err
}

// 限制读取
func readLimited(r io.Reader, maxBytes int64) ([]byte, error) {
    qpReader := quotedprintable.NewReader(r)
    return io.ReadAll(io.LimitReader(qpReader, maxBytes))
}

2. 与 bytes 配合

import (
    "bytes"
    "mime/quotedprintable"
)

// 快速编码
func encode(data []byte) ([]byte, error) {
    var buf bytes.Buffer
    writer := quotedprintable.NewWriter(&buf)
    _, err := writer.Write(data)
    if err != nil {
        return nil, err
    }
    err = writer.Close()
    if err != nil {
        return nil, err
    }
    return buf.Bytes(), nil
}

// 快速解码
func decode(data []byte) ([]byte, error) {
    reader := quotedprintable.NewReader(bytes.NewReader(data))
    return io.ReadAll(reader)
}

3. 与 net/textproto 配合

import (
    "mime/quotedprintable"
    "net/textproto"
)

// 读取 MIME 头部
reader := textproto.NewReader(bufio.NewReader(conn))
mimeHeader, _ := reader.ReadMIMEHeader()

// 解码 Content-Transfer-Encoding: quoted-printable 的正文
if mimeHeader.Get("Content-Transfer-Encoding") == "quoted-printable" {
    bodyReader := quotedprintable.NewReader(reader.R)
    // 读取解码后的正文...
}

五、快速参考

类型

类型功能接口
Readerquoted-printable 解码器io.Reader
Writerquoted-printable 编码器io.WriteCloser

Reader 方法

方法功能返回
NewReader(r io.Reader)创建解码器*Reader
Read(p []byte)读取并解码n int, err error

Writer 方法

方法功能返回
NewWriter(w io.Writer)创建编码器*Writer
Close()关闭并刷新error
Write(p []byte)编码并写入n int, err error

Writer 字段

字段类型说明
Binarybool二进制模式(默认 false)

编码规则

字符编码方式
可打印 ASCII(33-126,除=)保持不变
==3D
其他字节=XX(XX 为十六进制)
行末软换行 =\r\n=\n
行长度最大 76 字符

常见编码

原始编码后
Hello, World!Hello, World!
==3D
空格=20(行末)或保持
\t=09
\r\n\r\n(文本模式)或 =0D=0A(二进制)
=E4=BD=A0(UTF-8)

六、注意事项

1. 必须调用 Close

// Writer 必须调用 Close 刷新缓冲
writer := quotedprintable.NewWriter(&buf)
writer.Write([]byte("data"))
writer.Close() // ✓ 必须调用

// 使用 defer 确保关闭
writer := quotedprintable.NewWriter(&buf)
defer writer.Close() // ✓ 推荐

2. 行长度限制

// 自动处理行长度(76 字符)
longLine := strings.Repeat("a", 100)
writer := quotedprintable.NewWriter(&buf)
writer.Write([]byte(longLine))
writer.Close()

// 输出会自动添加软换行:
// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=\r\n
// aaaaaaaaaaaaaaaaaaaaaaaaaaaa

3. Binary 模式

// 文本模式(默认):处理换行符
writer := quotedprintable.NewWriter(&buf)
writer.Write([]byte("Line 1\r\nLine 2\r\n"))
writer.Close()
// 输出:Line 1\r\nLine 2\r\n

// 二进制模式:编码所有字节
writer = quotedprintable.NewWriter(&buf)
writer.Binary = true
writer.Write([]byte("Line 1\r\nLine 2\r\n"))
writer.Close()
// 输出:Line 1=0D=0ALine 2=0D=0A

4. 无效转义序列

// Reader 会尽量处理无效转义
encoded := "hello=XXworld" // 无效的转义序列
reader := quotedprintable.NewReader(strings.NewReader(encoded))
decoded, _ := io.ReadAll(reader)
// 可能返回:hello=XXworld(保持原样)

5. 字符编码

// quoted-printable 不处理字符编码
// 只处理字节到字节的编码

// UTF-8 中文
text := "你好" // UTF-8: E4 BD A0 E5 A5 BD
// 编码:=E4=BD=A0=E5=A5=BD

// 确保发送方和接收方使用相同的字符集
// 通常在 Content-Type 中指定:text/plain; charset=utf-8

6. 软换行

// 行末的 = 表示软换行
encoded := "This is a very long line that exceeds 76 characters =\r\nand continues here."

// 解码时软换行会被移除
reader := quotedprintable.NewReader(strings.NewReader(encoded))
decoded, _ := io.ReadAll(reader)
// 输出:This is a very long line that exceeds 76 characters and continues here.

7. 空格处理

// 行末的空格必须编码
text := "Hello   " // 行末有 3 个空格
// 编码:Hello=20=20=20

// 行中的空格可以保持不变
text := "Hello World"
// 编码:Hello World(或 Hello=20World)

8. 性能考虑

// ✓ 推荐:使用缓冲
var buf bytes.Buffer
writer := quotedprintable.NewWriter(&buf)
writer.Write(largeData)
writer.Close()

// ✓ 推荐:流式处理
reader := quotedprintable.NewReader(largeFile)
io.Copy(output, reader)

// ✗ 避免:小数据多次写入
for i := 0; i < 1000; i++ {
    writer.Write([]byte("x")) // 效率低
}

9. 与 Base64 对比

// Quoted-Printable:
// - 适合 mostly ASCII 的文本
// - 可读性较好
// - 编码后大小略增

// Base64:
// - 适合二进制数据
// - 可读性差
// - 编码后增加 33%

// 选择依据:
// - 文本内容:优先 Quoted-Printable
// - 二进制内容:使用 Base64

最后更新: 2026-04-05
Go 版本: Go 1.5+
包文档: https://pkg.go.dev/mime/quotedprintable
相关 RFC: RFC 2045 (Quoted-Printable), RFC 2047 (Encoded Words)