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

1 回复

更多关于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函数会:

  1. 直接从Content-Disposition头部获取原始值
  2. 处理RFC 5987编码(filename*参数)
  3. 处理RFC 2047编码(MIME编码字)
  4. 返回完整的原始文件名

这样即使文件名包含"/"字符,也能保持完整,不会被filepath.Base()截断。

回到顶部