forked from Silverfish/proton-bridge
170 lines
4.8 KiB
Go
170 lines
4.8 KiB
Go
// Copyright (c) 2021 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 (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"io"
|
|
"io/ioutil"
|
|
"mime"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/textproto"
|
|
"strings"
|
|
|
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
|
pmmime "github.com/ProtonMail/proton-bridge/pkg/mime"
|
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
|
"github.com/emersion/go-message"
|
|
"github.com/emersion/go-textwrapper"
|
|
)
|
|
|
|
// BuildEncrypted is used for importing encrypted message.
|
|
func BuildEncrypted(m *pmapi.Message, readers []io.Reader, kr *crypto.KeyRing) ([]byte, error) { //nolint[funlen]
|
|
b := &bytes.Buffer{}
|
|
boundary := newBoundary(m.ID).gen()
|
|
|
|
// Overwrite content for main header for import.
|
|
// Even if message has just simple body we should upload as multipart/mixed.
|
|
// Each part has encrypted body and header reflects the original header.
|
|
mainHeader := convertGoMessageToTextprotoHeader(getMessageHeader(m, JobOptions{}))
|
|
mainHeader.Set("Content-Type", "multipart/mixed; boundary="+boundary)
|
|
mainHeader.Del("Content-Disposition")
|
|
mainHeader.Del("Content-Transfer-Encoding")
|
|
if err := WriteHeader(b, mainHeader); err != nil {
|
|
return nil, err
|
|
}
|
|
mw := multipart.NewWriter(b)
|
|
if err := mw.SetBoundary(boundary); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Write the body part.
|
|
bodyHeader := make(textproto.MIMEHeader)
|
|
bodyHeader.Set("Content-Type", m.MIMEType+"; charset=utf-8")
|
|
bodyHeader.Set("Content-Disposition", pmapi.DispositionInline)
|
|
bodyHeader.Set("Content-Transfer-Encoding", "7bit")
|
|
|
|
p, err := mw.CreatePart(bodyHeader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// First, encrypt the message body.
|
|
if err := m.Encrypt(kr, kr); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := io.WriteString(p, m.Body); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Write the attachments parts.
|
|
for i := 0; i < len(m.Attachments); i++ {
|
|
att := m.Attachments[i]
|
|
r := readers[i]
|
|
h := getAttachmentHeader(att, false)
|
|
p, err := mw.CreatePart(h)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
data, err := ioutil.ReadAll(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Create encrypted writer.
|
|
pgpMessage, err := kr.Encrypt(crypto.NewPlainMessage(data), nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ww := textwrapper.NewRFC822(p)
|
|
bw := base64.NewEncoder(base64.StdEncoding, ww)
|
|
if _, err := bw.Write(pgpMessage.GetBinary()); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := bw.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if err := mw.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
func convertGoMessageToTextprotoHeader(h message.Header) textproto.MIMEHeader {
|
|
out := make(textproto.MIMEHeader)
|
|
hf := h.Fields()
|
|
for hf.Next() {
|
|
// go-message fields are in the reverse order.
|
|
// textproto.MIMEHeader is not ordered except for the values of
|
|
// the same key which are ordered
|
|
key := textproto.CanonicalMIMEHeaderKey(hf.Key())
|
|
out[key] = append([]string{hf.Value()}, out[key]...)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func getAttachmentHeader(att *pmapi.Attachment, buildForIMAP bool) textproto.MIMEHeader {
|
|
mediaType := att.MIMEType
|
|
if mediaType == "application/pgp-encrypted" {
|
|
mediaType = "application/octet-stream"
|
|
}
|
|
|
|
transferEncoding := "base64"
|
|
if mediaType == rfc822Message && buildForIMAP {
|
|
transferEncoding = "8bit"
|
|
}
|
|
|
|
encodedName := pmmime.EncodeHeader(att.Name)
|
|
disposition := "attachment" //nolint[goconst]
|
|
if strings.Contains(att.Header.Get("Content-Disposition"), pmapi.DispositionInline) {
|
|
disposition = pmapi.DispositionInline
|
|
}
|
|
|
|
h := make(textproto.MIMEHeader)
|
|
h.Set("Content-Type", mime.FormatMediaType(mediaType, map[string]string{"name": encodedName}))
|
|
if transferEncoding != "" {
|
|
h.Set("Content-Transfer-Encoding", transferEncoding)
|
|
}
|
|
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
|
|
}
|
|
|
|
func WriteHeader(w io.Writer, h textproto.MIMEHeader) (err error) {
|
|
if err = http.Header(h).Write(w); err != nil {
|
|
return
|
|
}
|
|
_, err = io.WriteString(w, "\r\n")
|
|
return
|
|
}
|