Go mime/quotedprintable 包详解
概述
mime/quotedprintable 包实现了 RFC 2045 定义的 quoted-printable(可引用打印)编码。Quoted-printable 是一种编码方式,用于将 8 位数据编码为 7 位 ASCII 字符,主要用于电子邮件传输。该包提供了 Reader 和 Writer 两种类型,分别用于解码和编码 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)
// 读取解码后的正文...
}
五、快速参考
类型
| 类型 | 功能 | 接口 |
|---|---|---|
Reader | quoted-printable 解码器 | io.Reader |
Writer | quoted-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 字段
| 字段 | 类型 | 说明 |
|---|---|---|
Binary | bool | 二进制模式(默认 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)