mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2026-02-14 04:48:32 +00:00
We build too many walls and not enough bridges
This commit is contained in:
214
pkg/message/header.go
Normal file
214
pkg/message/header.go
Normal file
@ -0,0 +1,214 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package message
|
||||
|
||||
import (
|
||||
"mime"
|
||||
"net/mail"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
pmmime "github.com/ProtonMail/proton-bridge/pkg/mime"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
// GetHeader builds the header for the message.
|
||||
func GetHeader(msg *pmapi.Message) textproto.MIMEHeader { //nolint[funlen]
|
||||
h := make(textproto.MIMEHeader)
|
||||
|
||||
// Copy the custom header fields if there are some.
|
||||
if msg.Header != nil {
|
||||
h = textproto.MIMEHeader(msg.Header)
|
||||
}
|
||||
|
||||
// Add or rewrite fields.
|
||||
h.Set("Subject", pmmime.EncodeHeader(msg.Subject))
|
||||
if msg.Sender != nil {
|
||||
h.Set("From", pmmime.EncodeHeader(msg.Sender.String()))
|
||||
}
|
||||
if len(msg.ReplyTos) > 0 {
|
||||
h.Set("Reply-To", pmmime.EncodeHeader(formatAddressList(msg.ReplyTos)))
|
||||
}
|
||||
if len(msg.ToList) > 0 {
|
||||
h.Set("To", pmmime.EncodeHeader(formatAddressList(msg.ToList)))
|
||||
}
|
||||
if len(msg.CCList) > 0 {
|
||||
h.Set("Cc", pmmime.EncodeHeader(formatAddressList(msg.CCList)))
|
||||
}
|
||||
if len(msg.BCCList) > 0 {
|
||||
h.Set("Bcc", pmmime.EncodeHeader(formatAddressList(msg.BCCList)))
|
||||
}
|
||||
|
||||
// Add or rewrite date related fields.
|
||||
if msg.Time > 0 {
|
||||
h.Set("X-Pm-Date", time.Unix(msg.Time, 0).Format(time.RFC1123Z))
|
||||
if d, err := msg.Header.Date(); err != nil || d.IsZero() { // Fix date if needed.
|
||||
h.Set("Date", time.Unix(msg.Time, 0).Format(time.RFC1123Z))
|
||||
}
|
||||
}
|
||||
|
||||
// Use External-Id if available to ensure email clients:
|
||||
// * build the conversations threads correctly (Thunderbird, Mac Outlook, Apple Mail)
|
||||
// * do not think the message is lost (Apple Mail)
|
||||
if msg.ExternalID != "" {
|
||||
h.Set("X-Pm-External-Id", "<"+msg.ExternalID+">")
|
||||
if h.Get("Message-Id") == "" {
|
||||
h.Set("Message-Id", "<"+msg.ExternalID+">")
|
||||
}
|
||||
}
|
||||
if msg.ID != "" {
|
||||
if h.Get("Message-Id") == "" {
|
||||
h.Set("Message-Id", "<"+msg.ID+"@protonmail.internalid>")
|
||||
}
|
||||
h.Set("X-Pm-Internal-Id", msg.ID)
|
||||
// Forward References, and include the message ID here (to improve outlook support).
|
||||
if references := h.Get("References"); !strings.Contains(references, msg.ID) {
|
||||
references += " <" + msg.ID + "@protonmail.internalid>"
|
||||
h.Set("References", references)
|
||||
}
|
||||
}
|
||||
if msg.ConversationID != "" {
|
||||
h.Set("X-Pm-ConversationID-Id", msg.ConversationID)
|
||||
if references := h.Get("References"); !strings.Contains(references, msg.ConversationID) {
|
||||
references += " <" + msg.ConversationID + "@protonmail.conversationid>"
|
||||
h.Set("References", references)
|
||||
}
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func SetBodyContentFields(h *textproto.MIMEHeader, m *pmapi.Message) {
|
||||
h.Set("Content-Type", m.MIMEType+"; charset=utf-8")
|
||||
h.Set("Content-Disposition", "inline")
|
||||
h.Set("Content-Transfer-Encoding", "quoted-printable")
|
||||
}
|
||||
|
||||
func GetBodyHeader(m *pmapi.Message) textproto.MIMEHeader {
|
||||
h := make(textproto.MIMEHeader)
|
||||
SetBodyContentFields(&h, m)
|
||||
return h
|
||||
}
|
||||
|
||||
func GetRelatedHeader(m *pmapi.Message) textproto.MIMEHeader {
|
||||
h := make(textproto.MIMEHeader)
|
||||
h.Set("Content-Type", "multipart/related; boundary="+GetRelatedBoundary(m))
|
||||
return h
|
||||
}
|
||||
|
||||
func GetAttachmentHeader(att *pmapi.Attachment) textproto.MIMEHeader {
|
||||
mediaType := att.MIMEType
|
||||
if mediaType == "application/pgp-encrypted" {
|
||||
mediaType = "application/octet-stream"
|
||||
}
|
||||
|
||||
encodedName := pmmime.EncodeHeader(att.Name)
|
||||
disposition := "attachment" //nolint[goconst]
|
||||
if strings.Contains(att.Header.Get("Content-Disposition"), "inline") {
|
||||
disposition = "inline"
|
||||
}
|
||||
|
||||
h := make(textproto.MIMEHeader)
|
||||
h.Set("Content-Type", mime.FormatMediaType(mediaType, map[string]string{"name": encodedName}))
|
||||
h.Set("Content-Transfer-Encoding", "base64")
|
||||
h.Set("Content-Disposition", mime.FormatMediaType(disposition, map[string]string{"filename": encodedName}))
|
||||
|
||||
// Forward some original header lines.
|
||||
forward := []string{"Content-Id", "Content-Description", "Content-Location"}
|
||||
for _, k := range forward {
|
||||
v := att.Header.Get(k)
|
||||
if v != "" {
|
||||
h.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// ========= Header parsing and sanitizing functions =========
|
||||
|
||||
func parseHeader(h mail.Header) (m *pmapi.Message, err error) { //nolint[unparam]
|
||||
m = pmapi.NewMessage()
|
||||
|
||||
if subject, err := pmmime.DecodeHeader(h.Get("Subject")); err == nil {
|
||||
m.Subject = subject
|
||||
}
|
||||
if addrs, err := sanitizeAddressList(h, "From"); err == nil && len(addrs) > 0 {
|
||||
m.Sender = addrs[0]
|
||||
}
|
||||
if addrs, err := sanitizeAddressList(h, "Reply-To"); err == nil && len(addrs) > 0 {
|
||||
m.ReplyTos = addrs
|
||||
}
|
||||
if addrs, err := sanitizeAddressList(h, "To"); err == nil {
|
||||
m.ToList = addrs
|
||||
}
|
||||
if addrs, err := sanitizeAddressList(h, "Cc"); err == nil {
|
||||
m.CCList = addrs
|
||||
}
|
||||
if addrs, err := sanitizeAddressList(h, "Bcc"); err == nil {
|
||||
m.BCCList = addrs
|
||||
}
|
||||
m.Time = 0
|
||||
if t, err := h.Date(); err == nil && !t.IsZero() {
|
||||
m.Time = t.Unix()
|
||||
}
|
||||
|
||||
m.Header = h
|
||||
return
|
||||
}
|
||||
|
||||
func sanitizeAddressList(h mail.Header, field string) (addrs []*mail.Address, err error) {
|
||||
raw := h.Get(field)
|
||||
if raw == "" {
|
||||
err = mail.ErrHeaderNotPresent
|
||||
return
|
||||
}
|
||||
var decoded string
|
||||
decoded, err = pmmime.DecodeHeader(raw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
addrs, err = mail.ParseAddressList(parseAddressComment(decoded))
|
||||
if err == nil {
|
||||
if addrs == nil {
|
||||
addrs = []*mail.Address{}
|
||||
}
|
||||
return
|
||||
}
|
||||
// Probably missing encoding error -- try to at least parse addresses in brackets.
|
||||
addrStr := h.Get(field)
|
||||
first := strings.Index(addrStr, "<")
|
||||
last := strings.LastIndex(addrStr, ">")
|
||||
if first < 0 || last < 0 || first >= last {
|
||||
return
|
||||
}
|
||||
var addrList []string
|
||||
open := first
|
||||
for open < last && 0 <= open {
|
||||
addrStr = addrStr[open:]
|
||||
close := strings.Index(addrStr, ">")
|
||||
addrList = append(addrList, addrStr[:close+1])
|
||||
addrStr = addrStr[close:]
|
||||
open = strings.Index(addrStr, "<")
|
||||
last = strings.LastIndex(addrStr, ">")
|
||||
}
|
||||
addrStr = strings.Join(addrList, ", ")
|
||||
//
|
||||
return mail.ParseAddressList(addrStr)
|
||||
}
|
||||
Reference in New Issue
Block a user