使用emersion/go-imap调用imap.FetchRFC822触发空指针引用Panic
问题分析与解决
1. 触发Panic的直接原因
你的代码中连接IMAP服务器后立即执行defer c.Logout(),但如果client.DialTLS返回错误(比如连接失败),此时c是nil,程序退出时执行c.Logout()就会触发nil指针解引用panic。这和你用imap.FetchRFC822还是imap.FetchEnvelope无关,只是用后者时刚好连接没出错,没暴露这个问题。
2. 修复代码的核心步骤
步骤1:调整连接后的错误检查顺序
必须先检查DialTLS的错误,确认c非nil后再执行defer c.Logout():
log.Println("Connecting to server...") c, err := client.DialTLS(server, nil) if err != nil { log.Println("In connection Error") return err } log.Println("Connected to " + server) defer c.Logout() // 确认c非nil后再延迟调用Logout
步骤2:正确处理imap.FetchRFC822的返回结果
你当前的代码只启动了Fetch的goroutine,但没有从messages通道读取消息,这会导致通道阻塞或者资源泄漏。要提取附件,需要遍历消息并解析邮件内容:
// 遍历messages通道处理邮件 for msg := range messages { if msg == nil { log.Println("Received nil message") continue } // 获取RFC822内容 rfc822 := msg.GetBody(&imap.BodySectionName{}) if rfc822 == nil { log.Println("Message has no RFC822 body") continue } // 解析邮件 msgReader, err := message.Read(rfc822) if err != nil { log.Printf("Failed to parse message: %v", err) continue } // 遍历邮件的各个部分,提取附件 if err := processMessageParts(msgReader); err != nil { log.Printf("Failed to process message parts: %v", err) } }
步骤3:添加附件提取的辅助函数
实现processMessageParts函数来递归解析邮件部分,筛选出.gz/.zip等附件:
func processMessageParts(msg *message.Entity) error { // 如果是多部分邮件,递归处理每个子部分 if msg.Multipart { for _, part := range msg.MultipartReader().Parts { if err := processMessageParts(part); err != nil { return err } } return nil } // 检查当前部分是否是附件 disp, _, err := msg.Header.ContentDisposition() if err != nil { return err } if disp == "attachment" { // 获取附件文件名 filename, err := msg.Header.Filename() if err != nil { return err } // 筛选.gz/.zip后缀的文件 lowerFilename := strings.ToLower(filename) if strings.HasSuffix(lowerFilename, ".gz") || strings.HasSuffix(lowerFilename, ".zip") { log.Printf("Found target attachment: %s", filename) // 读取附件内容并保存到文件 file, err := os.Create(filename) if err != nil { return err } defer file.Close() _, err = io.Copy(file, msg.Body) if err != nil { return err } } } return nil }
完整修复后的代码
package internal import ( "io" "log" "os" "strings" "github.com/emersion/go-imap" "github.com/emersion/go-imap/client" "github.com/emersion/go-message" ) func FetchEMail(server string, username string, password string) error { // Connect to Server log.Println("Connecting to server...") c, err := client.DialTLS(server, nil) if err != nil { log.Println("In connection Error") return err } log.Println("Connected to " + server) defer c.Logout() // Login log.Println("Logging in...") err = c.Login(username, password) if err != nil { log.Println("In login Error") return err } log.Println("Logged in as " + username) // Select INBOX log.Println("Selecting INBOX...") mbox, err := c.Select("INBOX", false) if err != nil { return err } log.Println("Selected INBOX") // Fetch all messages log.Println("Fetching all messages...") seqset := new(imap.SeqSet) seqset.AddRange(1, mbox.Messages) items := []imap.FetchItem{imap.FetchRFC822} messages := make(chan *imap.Message, 10) done := make(chan error, 1) go func() { done <- c.Fetch(seqset, items, messages) }() // Check if fetch successful if err := <-done; err != nil { log.Println("In fetch Error") return err } // Process each message for msg := range messages { if msg == nil { log.Println("Received nil message") continue } rfc822 := msg.GetBody(&imap.BodySectionName{}) if rfc822 == nil { log.Println("Message has no RFC822 body") continue } msgReader, err := message.Read(rfc822) if err != nil { log.Printf("Failed to parse message: %v", err) continue } if err := processMessageParts(msgReader); err != nil { log.Printf("Failed to process message parts: %v", err) } } log.Println("Run Successful - Terminating...") return nil } func processMessageParts(msg *message.Entity) error { if msg.Multipart { for _, part := range msg.MultipartReader().Parts { if err := processMessageParts(part); err != nil { return err } } return nil } disp, _, err := msg.Header.ContentDisposition() if err != nil { return err } if disp == "attachment" { filename, err := msg.Header.Filename() if err != nil { return err } lowerFilename := strings.ToLower(filename) if strings.HasSuffix(lowerFilename, ".gz") || strings.HasSuffix(lowerFilename, ".zip") { log.Printf("Found target attachment: %s", filename) file, err := os.Create(filename) if err != nil { return err } defer file.Close() _, err = io.Copy(file, msg.Body) if err != nil { return err } } } return nil }
3. 额外注意事项
- 确保导入
"os"包(代码中用到了os.Create) - 如果邮件数量很大,建议调整
messages通道的缓冲区大小,或者分批Fetch,避免内存占用过高 - 部分IMAP服务器可能对
FetchRFC822的支持有限,也可以尝试用imap.FetchBody替代,效果一致
内容的提问来源于stack exchange,提问作者Builderhummel
相关产品推荐
相关产品推荐

