Golang中imapclient获取附件文件名时遇到问题
Golang中imapclient获取附件文件名时遇到问题 在 Go 1.21.6 的 mime/multipart/multipart.go 文件的第 88 行
// FileName returns the filename parameter of the Part's Content-Disposition
// header. If not empty, the filename is passed through filepath.Base (which is
// platform dependent) before being returned.
func (p *Part) FileName() string {
if p.dispositionParams == nil {
p.parseContentDisposition()
}
filename := p.dispositionParams["filename"]
if filename == "" {
return ""
}
// RFC 7578, Section 4.2 requires that if a filename is provided, the
// directory path information must not be used.
return filepath.Base(filename)
}
如果 RFC2047 编码的文件名包含“/”,文件名可能会在“return filepath.Base(filename)”处被截断并返回。 是否有其他方法可以获取正常的文件名?
更多关于Golang中imapclient获取附件文件名时遇到问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于Golang中imapclient获取附件文件名时遇到问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Go的mime/multipart包中,FileName()方法确实会使用filepath.Base()处理文件名,这会移除路径信息。要获取完整的原始文件名(包括处理RFC2047编码),你可以直接解析Content-Disposition头部。
以下是一个替代方案,通过直接访问邮件部分的头部来获取原始文件名:
package main
import (
"fmt"
"mime"
"net/mail"
"strings"
)
// 获取原始附件文件名(不经过filepath.Base处理)
func GetAttachmentFilename(part *mail.Part) (string, error) {
// 获取Content-Disposition头部
disposition := part.Header.Get("Content-Disposition")
if disposition == "" {
return "", fmt.Errorf("no Content-Disposition header")
}
// 解析Content-Disposition
_, params, err := mime.ParseMediaType(disposition)
if err != nil {
return "", err
}
// 获取filename参数
filename := params["filename"]
if filename == "" {
// 尝试filename*参数(RFC 5987编码)
filename = params["filename*"]
if filename == "" {
return "", fmt.Errorf("no filename in Content-Disposition")
}
}
// 处理RFC 5987编码(如UTF-8''格式)
if strings.Contains(filename, "UTF-8''") || strings.Contains(filename, "utf-8''") {
decoded, err := decodeRFC5987(filename)
if err == nil {
filename = decoded
}
}
// 处理RFC 2047编码(如=?UTF-8?B?...?=格式)
decoded, err := decodeRFC2047(filename)
if err == nil {
filename = decoded
}
return filename, nil
}
// 解码RFC 5987格式(如UTF-8''%E6%96%87%E4%BB%B6.txt)
func decodeRFC5987(s string) (string, error) {
if idx := strings.Index(s, "''"); idx != -1 {
// 跳过字符集部分(如UTF-8'')
encoded := s[idx+2:]
// 这里需要实现URL解码
// 可以使用url.QueryUnescape或自定义解码
return encoded, nil
}
return s, nil
}
// 解码RFC 2047格式(MIME编码字)
func decodeRFC2047(s string) (string, error) {
dec := new(mime.WordDecoder)
return dec.DecodeHeader(s)
}
// 示例:在IMAP客户端中使用
func processIMAPAttachment(part *mail.Part) {
// 使用标准方法(可能被截断)
stdFilename := part.FileName()
fmt.Printf("标准文件名(可能被截断): %s\n", stdFilename)
// 使用自定义方法获取完整文件名
origFilename, err := GetAttachmentFilename(part)
if err != nil {
fmt.Printf("获取原始文件名失败: %v\n", err)
return
}
fmt.Printf("原始文件名: %s\n", origFilename)
}
如果你使用的是第三方IMAP库(如emersion/go-imap),可以这样处理:
import (
"github.com/emersion/go-imap"
"github.com/emersion/go-message/mail"
)
func processIMAPMessage(msg *imap.Message) {
// 解析邮件
mr, err := mail.CreateReader(msg.GetBody(&imap.BodySectionName{}))
if err != nil {
panic(err)
}
defer mr.Close()
// 遍历邮件部分
for {
p, err := mr.NextPart()
if err != nil {
break
}
switch h := p.Header.(type) {
case *mail.AttachmentHeader:
// 获取附件信息
filename, _ := h.Filename()
fmt.Printf("附件文件名: %s\n", filename)
// 如果需要原始文件名,可以访问原始头部
disposition := h.Get("Content-Disposition")
// 使用上面的GetAttachmentFilename逻辑解析
}
}
}
对于包含特殊字符(如"/")的RFC2047编码文件名,关键是要在调用filepath.Base()之前先解码。上面的GetAttachmentFilename函数会:
- 直接从Content-Disposition头部获取原始值
- 处理RFC 5987编码(
filename*参数) - 处理RFC 2047编码(MIME编码字)
- 返回完整的原始文件名
这样即使文件名包含"/"字符,也能保持完整,不会被filepath.Base()截断。

