Golang《基於 MIME 協議的郵件資訊解析》部分實現
Golang 郵件
golang 中郵件相關的包
net/mail
net/smtp
net/textproto
mime
mime/multipart
mime/quotedprintable
基於 smtp 包的郵件傳送示例,基於官方文件:
// Set up authentication information.
auth := smtp.PlainAuth("", "xxxx@qq.com", "xxxxx", "smtp.qq.com")
// Connect to the server, authenticate, set the sender and recipient,
// and send the email all in one step.
to := []string{"xxxx@xx.com"} //收件地址
msg := []byte("To: xxx@xxx.com\r\n" +
"Subject: discount Gophers!\r\n" +
"\r\n" +
"This is the email body.\r\n")
err := smtp.SendMail("smtp.qq.com:25", auth, "xxx@xx.com", to, msg)
if err != nil {
log.Fatal(err)
}
程式碼解析:
func PlainAuth(identity, username, password, host string) Auth
// identity 引數一般為空
// username 一般為傳送者郵件地址
// password 通過郵箱賬號得到的登入口令,在通過代理髮送郵件時候需要。
// host 設定使用的那個平臺的郵箱
func SendMail(addr string, a Auth, from string, to []string, msg []byte) error
// addr 引數是平臺地址引數,各大平臺都有自己的地址,不同的平臺去官網查詢即可,該地址引數要帶一個埠,預設一般是25
// a 是上面生成的 安全認證(也可以說是一個賬戶資訊,如同使用郵件之前需要登陸郵箱一樣)
// from 引數是傳送者地址,此引數要保持和auth 中的引數使用者名稱一致
// to 存放一個地址收件郵箱資訊。
// msg 是一個訊息體,要滿足標準協議
SMTP 協議分析:
https://www.rfc-editor.org/rfc/rfc821.html#section-3.1
傳送完整郵件的訊息體主要包括一下幾個部分:
是 \r\n 是 ascii 轉移字元,用於標識每個部分結束的資訊
發件人 (在開發者角度上面可以理解為發件人,該資訊主要是讓 SMTP 伺服器知道我們要用哪個賬號傳送郵件)
MAIL FROM:
收件人:
RCPT TO:
資料:
DATA
以上三點是最基礎的 3 個部分,在郵件中還可以指定其他內容資訊,比如時間,附件,訊息格式等等。
有關協議中的一小部分部分內容整理如下以便理解大概:
SMTP 郵件事務分為三個步驟。交易以 MAIL 命令開始,該命令為發件人提供鑑別。一系列一個或多個 RCPT 命令如下給接收者資訊。然後一個 DATA 命令給出郵件資料。最後,郵件資料指示器的結尾確認交易。
該過程的第一步是 MAIL 命令。這<reverse-path> 包含源郵箱。
MAIL <SP> FROM:<reverse-path> <CRLF>
此命令告訴 SMTP 接收器新郵件事務正在開始並重置其所有狀態表和緩衝區,包括任何收件人或郵件資料。它給出了可用於報告錯誤的反向路徑。如果接受,接收方 SMTP 返回 250 OK 回覆。
<reverse-path> 可以包含的不僅僅是郵箱。這<reverse-path> 是主機的反向源路由列表,源郵箱。 <reverse-path> 中的第一個主機應該是傳送此命令的主機。
該過程的第二步是 RCPT 命令。
RCPT <SP> TO:<forward-path> <CRLF>
該命令給出了識別一個接收者的前向路徑。如果接受,接收方 SMTP 將返回 250 OK 回覆,並且儲存前向路徑。如果收件人未知接收者 SMTP 返回 550 失敗回覆。這第二步該過程可以重複任意次。
該過程的第三步是 DATA 命令。
DATA <CRLF>
如果接受,接收方-SMTP 將返回 354 中間回覆並將所有後續行視為訊息文字。當文字的結尾被接收並儲存時,SMTP 接收器傳送 250 OK 回覆。
有關訊息型別頭的標準協議文件:
https://www.rfc-editor.org/rfc/rfc1049.html
郵件擴充套件標準
Internet 郵件擴充套件 (MIME)第一部分: Internet 訊息體的格式
https://www.rfc-editor.org/rfc/rfc2045
Internet 郵件擴充套件 (MIME) 第二部分: 媒體型別
https://www.rfc-editor.org/rfc/rfc2046
該部分主要講述了,有關在構建郵件訊息體以及,如何混合不同型別的訊息格式。
MIME(多用途 Internet 郵件擴充套件)第三部分: 非 ASCII 文字的訊息頭擴充套件
https://www.rfc-editor.org/rfc/rfc2047
第四部分
https://www.rfc-editor.org/rfc/rfc4289.html#page-5
Internet 郵件擴充套件 (MIME) 第五部分: 一致性標準和示例
https://www.rfc-editor.org/rfc/rfc2049#page-15
該部分,有著複雜的混合訊息組合例子
通過上面的 5 協議,簡單的實現了一個傳送郵件的內容分裝。
設計訊息訊息:
根據第二部分的協議,需要支援多媒體型別混合訊息傳送,需要設計一個用於遞迴解析的結構體來生成最終傳送的內容,如下所示
// Message 郵件中的訊息部分
type Message struct {
boundaryStart string //邊界分割
header []*Header //訊息頭
body string //正文,一般為字串,HTML,或者編碼後的字串
msg []*Message //巢狀訊息
boundaryEed string //邊界結尾
}
完成訊息體的設計,接下來只需要把訊息體,解析成為我們需要的格式即可,程式碼實現如下:
// 對訊息進行解析,生成最終傳輸的訊息體,該解析實現了 rfc2046 的多部份訊息混合解析
func parseMessage(message *Message) []byte {
buf := &bytes.Buffer{}
//解析郵件邊界分割
if message.boundaryStart != "" && message.body != "" { //如果當前 message 沒有任何訊息正文,則不新增分割符
buf.WriteString(CRLF + message.boundaryStart + CRLF)
}
//解析郵件頭 檢驗本身是否屬於一個多部份混合訊息,如果是多部份混合訊息 則自己本身也要使用自己的訊息分割符
if message.header != nil {
for i := 0; i < len(message.header); i++ {
if message.header[i].Name == ContentType { //檢索是否有內容型別頭
for _, value := range message.header[i].Value { //檢索是否需要分段
if strings.HasPrefix(value, "boundary") {
//如果有該屬性,那麼後續的 msg []*Message //巢狀訊息 都需要使用這個作為分割
//message.boundaryStart = "--" + value[9:]
message.boundaryEed = "--" + value[9:] + "--"
if message.msg != nil {
for j := 0; j < len(message.msg); j++ {
message.msg[j].boundaryStart = "--" + value[9:] //此處初始化被巢狀的訊息起始分割符號
}
}
}
}
}
buf.Write(message.header[i].Encoding())
}
}
if message.body != "" {
buf.WriteString(message.body)
}
if message.msg != nil {
// 開始遞迴解析巢狀訊息
for i := 0; i < len(message.msg); i++ {
s := parseMessage(message.msg[i])
buf.Write(s)
}
}
if message.boundaryEed != "" {
buf.WriteString(CRLF + message.boundaryEed + CRLF)
}
return buf.Bytes()
}
核心程式碼基本上已經結束了。
設計附件資訊:
// File 郵件附件資訊
type File struct {
Filename string //檔名稱
Type []string //內容型別
Disposition []string //檔案描述
TransferEncoding string //傳輸編碼
Encoding string //編碼後的字串
data []byte //檔案位元組
}
欄位解析:
Filmename:指定的檔名
Type:封裝訊息體的內容型別資訊,一般有兩個資訊在裡面:application/octet-stream,name="xxx"
Disposition: 檔案描述資訊,一般為:attachment,filename="xxxx",(此處的屬性是基於其他郵件訊息格式得出,Type 和 Disposition 此處決定了郵件附件的檔名)
TransferEncoding : 標識以什麼方式編碼傳輸,本文統一以 Base64 編碼
郵件客戶端設計:
type client struct {
host string
username string
password string
auth smtp.Auth
boundary string //標識該郵件是否為混合訊息體,如果是混合訊息,則需要用此引數去初始化第一個message的邊界符號
from string //發件人資訊
//一下為郵件內容設定
subject string //郵件主題
main *Message //用於初始化構建訊息
html string //html訊息
text string //文字訊息
file map[string]*File //檔案資訊
}
重點看一下檔案儲存上面
// File 設定傳送郵件的附件資訊
// 檔案預設的傳輸格式採用Base64
func (c *client) File(name string, data []byte) {
if c.file == nil {
c.file = make(map[string]*File)
}
encodeToString := base64.StdEncoding.EncodeToString(data)
file := &File{
Filename: name,
Type: []string{"application/octet-stream", "name=" + name},
Disposition: []string{"attachment", "filename=" + name + CRLF},
TransferEncoding: Base64,
data: data,
Encoding: encodeToString,
}
c.file[name] = file
}
讓我們對官方 demo 稍加包裝一下即可:
func NewEmail(user, password, host string) *client {
c := &client{host: host, username: user, password: password, from: user}
c.auth = smtp.PlainAuth("", user, password, host)
return c
}
// 傳送郵件
func (c *client) SendEmail(addr ...string) (bool, error) {
c.build()
if c.main == nil {
return false, errors.New("email content is empty")
}
c.main.header = append(c.main.header[:1], append([]*Header{NewHeader(To, addr...)}, c.main.header[1:]...)...)
message := parseMessage(c.main)
if message == nil {
return false, errors.New("email content is empty")
}
err := smtp.SendMail(c.host+":25", c.auth, c.from, addr, message)
if err != nil {
return false, err
}
return true, nil
}
在傳送郵件之前對內容設定好即可。部分解析的 api 過於繁瑣,有興趣的可以去看一下原始碼.
https://github.com/awensir/aurora-email
測試 demo
func TestText(t *testing.T) {
email := NewEmail("xxxx@qq.com", "xxxxxxxxxxxxxx", "smtp.qq.com")
email.Subject("test")
//email.Text("test 普通文字訊息")
email.Html(`<!DOCTYPE html>
<html>
<body>
<p style="font-size:30px;color:orange">測試HTML 資訊</p>
</body>
</html>`)
file, err := os.ReadFile("E:\\space\\src\\project\\email\\1111.pdf")
if err != nil {
fmt.Println(err.Error())
return
}
email.File("test.pdf", file)
_, err = email.SendEmail("xxxxxxx@qq.com")
if err != nil {
fmt.Println(err.Error())
return
}
}
注意:本次實現的郵件傳送,目前僅支援 html 和附件 ,對於 html 和文字,和其他 資訊的複雜解析,還有待改進支援。
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- 郵件協議之MIME協議
- 基於Netty實現自定義訊息通訊協議(協議設計及解析應用實戰)Netty協議
- 如何在Outlook安裝使用S/MIME郵件證書實現郵件簽名加密加密
- 應對FDA檢查,S/MIME郵件證書如何使藥企實現郵件通訊合規化
- python實現基於smtp傳送郵件Python
- STOMP協議——基於Websocket實現協議Web
- 使用java語言基於SMTP協議手寫郵件客戶端Java協議客戶端
- Python 基於 TCP 傳輸協議的網路通訊實現PythonTCP協議
- 電子郵件協議及GO傳送QQ郵件協議Go
- 【.NET6+Modbus】Modbus TCP協議解析、模擬環境以及基於.NET實現基礎通訊TCP協議
- 基於 IJKPlayer-concat 協議的視訊無縫拼接技術實現協議
- 基於事件匯流排EventBus實現郵件推送功能事件
- SpringBoot 實戰 (十六) | 整合 WebSocket 基於 STOMP 協議實現廣播訊息Spring BootWeb協議
- 郵件協議之IMAP指令講解協議
- POP3協議(電子郵件郵局協議)中UIDL和TOP命令在實際使用中的作用協議UI
- 如何在Mac郵件客戶端配置使用S/MIME郵件證書Mac客戶端
- 郵件協議之POP3的講解協議
- 如何選購S/MIME郵件安全證書
- 音視訊同步!RTCP 協議解析及程式碼實現TCP協議
- 基於SpringBoot+STOMP協議實現的web聊天室Spring Boot協議Web
- 基於Netty實現Redis協議的編碼解碼器NettyRedis協議
- Java:基於TCP協議網路socket程式設計(實現C/S通訊)JavaTCP協議程式設計
- 基於Python的郵件檢測工具Python
- SMTP協議解讀以及如何使用SMTP協議傳送電子郵件協議
- golang傳送郵件(qq郵箱)Golang
- 通訊協議protobuf的原理與實現協議
- 如何驗證獲取S/MIME郵件安全證書
- consul 原始碼解析(一)raft 協議實現原始碼Raft協議
- okhttp 原始碼解析 – http 協議的實現 – 重定向HTTP原始碼協議
- 網路協議之:基於UDP的高速資料傳輸協議UDT協議UDP
- TCP/IP學習筆記之協議和郵件TCP筆記協議
- 基於 Electron 做視訊會議的兩種實現方式
- python實現郵件的傳送Python
- 抖音協議直播間彈幕資訊解析協議
- QT使用 http 協議通訊的實現示例QTHTTP協議
- zmq通訊協議的實現,又稱zeromqMQ協議
- Golang 實現客戶端與伺服器端UDP協議連線通訊Golang客戶端伺服器UDP協議
- Qt 基於QTcpSocket的ModbusTCP協議QTTCP協議