diff --git a/internal/smtp/user.go b/internal/smtp/user.go
index 37957249..3f4bf5de 100644
--- a/internal/smtp/user.go
+++ b/internal/smtp/user.go
@@ -187,7 +187,7 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
log.WithError(err).Error("Failed to parse message")
return
}
- clearBody := message.Body
+ richBody := message.Body
externalID := message.Header.Get("Message-Id")
externalID = strings.Trim(externalID, "<>")
@@ -256,7 +256,6 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
atts = append(atts, message.Attachments...)
// Decrypt attachment keys, because we will need to re-encrypt them with the recipients' public keys.
attkeys := make(map[string]*crypto.SessionKey)
- attkeysEncoded := make(map[string]pmapi.AlgoKey)
for _, att := range atts {
var keyPackets []byte
@@ -266,23 +265,9 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
if attkeys[att.ID], err = kr.DecryptSessionKey(keyPackets); err != nil {
return errors.Wrap(err, "decrypting attachment session key")
}
- attkeysEncoded[att.ID] = pmapi.AlgoKey{
- Key: attkeys[att.ID].GetBase64Key(),
- Algorithm: attkeys[att.ID].Algo,
- }
}
- plainSharedScheme := 0
- htmlSharedScheme := 0
- mimeSharedType := 0
-
- plainAddressMap := make(map[string]*pmapi.MessageAddress)
- htmlAddressMap := make(map[string]*pmapi.MessageAddress)
- mimeAddressMap := make(map[string]*pmapi.MessageAddress)
-
- var plainKey, htmlKey, mimeKey *crypto.SessionKey
- var plainData, htmlData, mimeData []byte
-
+ req := pmapi.NewSendMessageReq(kr, mimeBody, plainBody, richBody, attkeys)
containsUnencryptedRecipients := false
for _, email := range to {
@@ -300,59 +285,13 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
var signature int
if sendPreferences.Sign {
- signature = pmapi.YesSignature
+ signature = pmapi.SignatureDetached
} else {
- signature = pmapi.NoSignature
+ signature = pmapi.SignatureNone
}
- if sendPreferences.Scheme == pmapi.PGPMIMEPackage || sendPreferences.Scheme == pmapi.ClearMIMEPackage {
- if mimeKey == nil {
- if mimeKey, mimeData, err = encryptSymmetric(kr, mimeBody, true); err != nil {
- return err
- }
- }
- if sendPreferences.Scheme == pmapi.PGPMIMEPackage {
- mimeBodyPacket, _, err := createPackets(sendPreferences.PublicKey, mimeKey, map[string]*crypto.SessionKey{})
- if err != nil {
- return err
- }
- mimeAddressMap[email] = &pmapi.MessageAddress{Type: sendPreferences.Scheme, BodyKeyPacket: mimeBodyPacket, Signature: signature}
- } else {
- mimeAddressMap[email] = &pmapi.MessageAddress{Type: sendPreferences.Scheme, Signature: signature}
- }
- mimeSharedType |= sendPreferences.Scheme
- } else {
- switch sendPreferences.MIMEType {
- case pmapi.ContentTypePlainText:
- if plainKey == nil {
- if plainKey, plainData, err = encryptSymmetric(kr, plainBody, true); err != nil {
- return err
- }
- }
- newAddress := &pmapi.MessageAddress{Type: sendPreferences.Scheme, Signature: signature}
- if sendPreferences.Encrypt && sendPreferences.PublicKey != nil {
- newAddress.BodyKeyPacket, newAddress.AttachmentKeyPackets, err = createPackets(sendPreferences.PublicKey, plainKey, attkeys)
- if err != nil {
- return err
- }
- }
- plainAddressMap[email] = newAddress
- plainSharedScheme |= sendPreferences.Scheme
- case pmapi.ContentTypeHTML:
- if htmlKey == nil {
- if htmlKey, htmlData, err = encryptSymmetric(kr, clearBody, true); err != nil {
- return err
- }
- }
- newAddress := &pmapi.MessageAddress{Type: sendPreferences.Scheme, Signature: signature}
- if sendPreferences.Encrypt && sendPreferences.PublicKey != nil {
- newAddress.BodyKeyPacket, newAddress.AttachmentKeyPackets, err = createPackets(sendPreferences.PublicKey, htmlKey, attkeys)
- if err != nil {
- return err
- }
- }
- htmlAddressMap[email] = newAddress
- htmlSharedScheme |= sendPreferences.Scheme
- }
+
+ if err := req.AddRecipient(email, sendPreferences.Scheme, sendPreferences.PublicKey, signature, sendPreferences.MIMEType, sendPreferences.Encrypt); err != nil {
+ return errors.Wrap(err, "failed to add recipient")
}
}
@@ -370,31 +309,7 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
}
}
- req := &pmapi.SendMessageReq{}
-
- plainPkg := buildPackage(plainAddressMap, plainSharedScheme, pmapi.ContentTypePlainText, plainData, plainKey, attkeysEncoded)
- if plainPkg != nil {
- req.Packages = append(req.Packages, plainPkg)
- }
-
- htmlPkg := buildPackage(htmlAddressMap, htmlSharedScheme, pmapi.ContentTypeHTML, htmlData, htmlKey, attkeysEncoded)
- if htmlPkg != nil {
- req.Packages = append(req.Packages, htmlPkg)
- }
-
- if len(mimeAddressMap) > 0 {
- pkg := &pmapi.MessagePackage{
- Body: base64.StdEncoding.EncodeToString(mimeData),
- Addresses: mimeAddressMap,
- MIMEType: pmapi.ContentTypeMultipartMixed,
- Type: mimeSharedType,
- BodyKey: pmapi.AlgoKey{
- Key: mimeKey.GetBase64Key(),
- Algorithm: mimeKey.Algo,
- },
- }
- req.Packages = append(req.Packages, pkg)
- }
+ req.PreparePackages()
return su.storeUser.SendMessage(message.ID, req)
}
diff --git a/internal/smtp/utils.go b/internal/smtp/utils.go
index 36b98171..745ff2dc 100644
--- a/internal/smtp/utils.go
+++ b/internal/smtp/utils.go
@@ -18,11 +18,7 @@
package smtp
import (
- "encoding/base64"
"regexp"
-
- "github.com/ProtonMail/gopenpgp/v2/crypto"
- "github.com/ProtonMail/proton-bridge/pkg/pmapi"
)
//nolint:gochecknoglobals // Used like a constant
@@ -35,85 +31,3 @@ var mailFormat = regexp.MustCompile(`.+@.+\..+`)
func looksLikeEmail(e string) bool {
return mailFormat.MatchString(e)
}
-
-func createPackets(
- pubkey *crypto.KeyRing,
- bodyKey *crypto.SessionKey,
- attkeys map[string]*crypto.SessionKey,
-) (bodyPacket string, attachmentPackets map[string]string, err error) {
- // Encrypt message body keys.
- packetBytes, err := pubkey.EncryptSessionKey(bodyKey)
- if err != nil {
- return
- }
- bodyPacket = base64.StdEncoding.EncodeToString(packetBytes)
-
- // Encrypt attachment keys.
- attachmentPackets = make(map[string]string)
- for id, attkey := range attkeys {
- var packets []byte
- if packets, err = pubkey.EncryptSessionKey(attkey); err != nil {
- return
- }
- attachmentPackets[id] = base64.StdEncoding.EncodeToString(packets)
- }
- return
-}
-
-func encryptSymmetric(
- kr *crypto.KeyRing,
- textToEncrypt string,
- canonicalizeText bool, // nolint[unparam]
-) (key *crypto.SessionKey, symEncryptedData []byte, err error) {
- // We use only primary key to encrypt the message. Our keyring contains all keys (primary, old and deacivated ones).
- firstKey, err := kr.FirstKey()
- if err != nil {
- return
- }
-
- pgpMessage, err := firstKey.Encrypt(crypto.NewPlainMessageFromString(textToEncrypt), kr)
- if err != nil {
- return
- }
-
- pgpSplitMessage, err := pgpMessage.SeparateKeyAndData(len(textToEncrypt), 0)
- if err != nil {
- return
- }
-
- key, err = kr.DecryptSessionKey(pgpSplitMessage.GetBinaryKeyPacket())
- if err != nil {
- return
- }
-
- symEncryptedData = pgpSplitMessage.GetBinaryDataPacket()
-
- return
-}
-
-func buildPackage(
- addressMap map[string]*pmapi.MessageAddress,
- sharedScheme int,
- mimeType string,
- bodyData []byte,
- bodyKey *crypto.SessionKey,
- attKeys map[string]pmapi.AlgoKey,
-) (pkg *pmapi.MessagePackage) {
- if len(addressMap) == 0 {
- return nil
- }
-
- pkg = &pmapi.MessagePackage{
- Body: base64.StdEncoding.EncodeToString(bodyData),
- Addresses: addressMap,
- MIMEType: mimeType,
- Type: sharedScheme,
- }
-
- if sharedScheme|pmapi.ClearPackage > 0 {
- pkg.BodyKey.Key = bodyKey.GetBase64Key()
- pkg.BodyKey.Algorithm = bodyKey.Algo
- pkg.AttachmentKeys = attKeys
- }
- return pkg
-}
diff --git a/pkg/pmapi/client.go b/pkg/pmapi/client.go
index 3e1c9cea..3f513b08 100644
--- a/pkg/pmapi/client.go
+++ b/pkg/pmapi/client.go
@@ -254,7 +254,7 @@ func (c *client) doBuffered(req *http.Request, bodyBuffer []byte, retryUnauthori
head += "\n"
}
c.log.Tracef("REQHEAD \n%s", head)
- c.log.Tracef("REQBODY '%s'", string(bodyBuffer))
+ c.log.Tracef("REQBODY '%s'", printBytes(bodyBuffer))
}
hasBody := len(bodyBuffer) > 0
diff --git a/pkg/pmapi/debug.go b/pkg/pmapi/debug.go
new file mode 100644
index 00000000..6f74bc59
--- /dev/null
+++ b/pkg/pmapi/debug.go
@@ -0,0 +1,43 @@
+// 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 .
+
+package pmapi
+
+import "unicode/utf8"
+
+func printBytes(body []byte) string {
+ if utf8.Valid(body) {
+ return string(body)
+ }
+ enc := []rune{}
+ for _, b := range body {
+ switch {
+ case b == 9:
+ enc = append(enc, rune('⟼'))
+ case b == 13:
+ enc = append(enc, rune('↵'))
+ case b < 32, b == 127:
+ enc = append(enc, '◡')
+ case b > 31 && b < 127, b == 10:
+ enc = append(enc, rune(b))
+ default:
+ enc = append(enc, 9728+rune(b))
+ }
+ }
+
+ return string(enc)
+}
diff --git a/pkg/pmapi/keyring.go b/pkg/pmapi/keyring.go
index 978ac6e7..23169b7e 100644
--- a/pkg/pmapi/keyring.go
+++ b/pkg/pmapi/keyring.go
@@ -19,6 +19,7 @@ package pmapi
import (
"bytes"
+ "encoding/base64"
"encoding/json"
"io"
"io/ioutil"
@@ -289,3 +290,57 @@ func signAttachment(encrypter *crypto.KeyRing, data io.Reader) (signature io.Rea
}
return bytes.NewReader(sig.GetBinary()), nil
}
+
+func createPackets(
+ pubkey *crypto.KeyRing,
+ bodyKey *crypto.SessionKey,
+ attkeys map[string]*crypto.SessionKey,
+) (bodyPacket string, attachmentPackets map[string]string, err error) {
+ // Encrypt message body keys.
+ packetBytes, err := pubkey.EncryptSessionKey(bodyKey)
+ if err != nil {
+ return
+ }
+ bodyPacket = base64.StdEncoding.EncodeToString(packetBytes)
+
+ // Encrypt attachment keys.
+ attachmentPackets = make(map[string]string)
+ for id, attkey := range attkeys {
+ var packets []byte
+ if packets, err = pubkey.EncryptSessionKey(attkey); err != nil {
+ return
+ }
+ attachmentPackets[id] = base64.StdEncoding.EncodeToString(packets)
+ }
+ return
+}
+
+func encryptSymmetric(
+ kr *crypto.KeyRing,
+ textToEncrypt string,
+) (decryptedKey *crypto.SessionKey, symEncryptedData []byte, err error) {
+ // We use only primary key to encrypt the message. Our keyring contains all keys (primary, old and deacivated ones).
+ firstKey, err := kr.FirstKey()
+ if err != nil {
+ return
+ }
+
+ pgpMessage, err := firstKey.Encrypt(crypto.NewPlainMessageFromString(textToEncrypt), kr)
+ if err != nil {
+ return
+ }
+
+ pgpSplitMessage, err := pgpMessage.SeparateKeyAndData(len(textToEncrypt), 0)
+ if err != nil {
+ return
+ }
+
+ decryptedKey, err = kr.DecryptSessionKey(pgpSplitMessage.GetBinaryKeyPacket())
+ if err != nil {
+ return
+ }
+
+ symEncryptedData = pgpSplitMessage.GetBinaryDataPacket()
+
+ return
+}
diff --git a/pkg/pmapi/message_send.go b/pkg/pmapi/message_send.go
new file mode 100644
index 00000000..f3fa6413
--- /dev/null
+++ b/pkg/pmapi/message_send.go
@@ -0,0 +1,331 @@
+// 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 .
+
+package pmapi
+
+import (
+ "encoding/base64"
+ "errors"
+
+ "github.com/ProtonMail/gopenpgp/v2/crypto"
+)
+
+const (
+ DraftActionReply = 0
+ DraftActionReplyAll = 1
+ DraftActionForward = 2
+)
+
+// Message package types.
+const (
+ InternalPackage = 1
+ EncryptedOutsidePackage = 2
+ ClearPackage = 4
+ PGPInlinePackage = 8
+ PGPMIMEPackage = 16
+ ClearMIMEPackage = 32
+)
+
+// Signature types.
+const (
+ SignatureNone = 0
+ SignatureDetached = 1
+ SignatureAttachedArmored = 2
+)
+
+type DraftReq struct {
+ Message *Message
+ ParentID string `json:",omitempty"`
+ Action int
+ AttachmentKeyPackets []string
+}
+
+func (c *client) CreateDraft(m *Message, parent string, action int) (created *Message, err error) {
+ createReq := &DraftReq{Message: m, ParentID: parent, Action: action, AttachmentKeyPackets: []string{}}
+
+ req, err := c.NewJSONRequest("POST", "/mail/v4/messages", createReq)
+ if err != nil {
+ return
+ }
+
+ var res MessageRes
+ if err = c.DoJSON(req, &res); err != nil {
+ return
+ }
+
+ created, err = res.Message, res.Err()
+ return
+}
+
+type AlgoKey struct {
+ Key string
+ Algorithm string
+}
+
+type MessageAddress struct {
+ Type int
+ BodyKeyPacket string // base64-encoded key packet.
+ Signature int // 0 = None, 1 = Detached, 2 = Attached/Armored
+ AttachmentKeyPackets map[string]string
+}
+
+type MessagePackage struct {
+ Addresses map[string]*MessageAddress
+ Type int
+ MIMEType string
+ Body string // base64-encoded encrypted data packet.
+ BodyKey AlgoKey // base64-encoded session key (only if cleartext recipients).
+ AttachmentKeys map[string]AlgoKey // Only include if cleartext & attachments.
+}
+
+func newMessagePackage(
+ send sendData,
+ attKeys map[string]AlgoKey,
+) (pkg *MessagePackage) {
+ pkg = &MessagePackage{
+ Body: base64.StdEncoding.EncodeToString(send.data),
+ Addresses: send.addressMap,
+ MIMEType: send.contentType,
+ Type: send.sharedScheme,
+ }
+
+ if send.sharedScheme&ClearPackage == ClearPackage ||
+ send.sharedScheme&ClearMIMEPackage == ClearMIMEPackage {
+ pkg.BodyKey.Key = send.key.GetBase64Key()
+ pkg.BodyKey.Algorithm = send.key.Algo
+ }
+
+ if attKeys != nil && send.sharedScheme&ClearPackage == ClearPackage {
+ pkg.AttachmentKeys = attKeys
+ }
+
+ return pkg
+}
+
+type sendData struct {
+ key *crypto.SessionKey //body session key
+ addressMap map[string]*MessageAddress
+ sharedScheme int
+ data []byte // ciphertext
+ body string // cleartext
+ contentType string
+}
+
+type SendMessageReq struct {
+ ExpirationTime int64 `json:",omitempty"`
+ // AutoSaveContacts int `json:",omitempty"`
+
+ // Data for encrypted recipients.
+ Packages []*MessagePackage
+
+ mime, plain, rich sendData
+ attKeys map[string]*crypto.SessionKey
+ kr *crypto.KeyRing
+}
+
+func NewSendMessageReq(
+ kr *crypto.KeyRing,
+ mimeBody, plainBody, richBody string,
+ attKeys map[string]*crypto.SessionKey,
+) *SendMessageReq {
+ req := &SendMessageReq{}
+
+ req.mime.addressMap = make(map[string]*MessageAddress)
+ req.plain.addressMap = make(map[string]*MessageAddress)
+ req.rich.addressMap = make(map[string]*MessageAddress)
+
+ req.mime.body = mimeBody
+ req.plain.body = plainBody
+ req.rich.body = richBody
+
+ req.attKeys = attKeys
+ req.kr = kr
+
+ return req
+}
+
+var (
+ errMultipartInNonMIME = errors.New("multipart mixed not allowed in this scheme")
+ errAttSignNotSupported = errors.New("attached signature not supported")
+ errEncryptMustSign = errors.New("encrypted package must be signed")
+ errEONotSupported = errors.New("encrypted outside is not supported")
+ errWrongSendScheme = errors.New("wrong send scheme")
+ errInternalMustEncrypt = errors.New("internal package must be encrypted")
+ errInlinelMustEncrypt = errors.New("PGP Inline package must be encrypted")
+ errMisingPubkey = errors.New("cannot encrypt body key packet: missing pubkey")
+ errSignMustBeMultipart = errors.New("clear singed packet must be multipart")
+ errMIMEMustBeMultipart = errors.New("MIME packet must be multipart")
+)
+
+func (req *SendMessageReq) AddRecipient(
+ email string, sendScheme int,
+ pubkey *crypto.KeyRing, signature int,
+ contentType string, doEncrypt bool,
+) (err error) {
+ if signature == SignatureAttachedArmored {
+ return errAttSignNotSupported
+ }
+
+ if doEncrypt && signature != SignatureDetached {
+ return errEncryptMustSign
+ }
+
+ switch sendScheme {
+ case PGPMIMEPackage, ClearMIMEPackage:
+ if contentType != ContentTypeMultipartMixed {
+ return errMIMEMustBeMultipart
+ }
+ return req.addMIMERecipient(email, sendScheme, pubkey, signature)
+ case InternalPackage, ClearPackage, PGPInlinePackage:
+ return req.addNonMIMERecipient(email, sendScheme, pubkey, signature, contentType, doEncrypt)
+ case EncryptedOutsidePackage:
+ return errEONotSupported
+ }
+ return errWrongSendScheme
+}
+
+func (req *SendMessageReq) addNonMIMERecipient(
+ email string, sendScheme int,
+ pubkey *crypto.KeyRing, signature int,
+ contentType string, doEncrypt bool,
+) (err error) {
+ if sendScheme == ClearPackage && signature == SignatureDetached {
+ return errSignMustBeMultipart
+ }
+
+ var send *sendData
+ switch contentType {
+ case ContentTypePlainText:
+ send = &req.plain
+ send.contentType = contentType
+ case ContentTypeHTML:
+ send = &req.rich
+ send.contentType = contentType
+ case ContentTypeMultipartMixed:
+ return errMultipartInNonMIME
+ }
+
+ if send.key == nil {
+ if send.key, send.data, err = encryptSymmetric(req.kr, send.body); err != nil {
+ return err
+ }
+ }
+ newAddress := &MessageAddress{Type: sendScheme, Signature: signature}
+
+ if sendScheme == PGPInlinePackage && !doEncrypt {
+ return errInlinelMustEncrypt
+ }
+ if sendScheme == InternalPackage && !doEncrypt {
+ return errInternalMustEncrypt
+ }
+ if doEncrypt && pubkey == nil {
+ return errMisingPubkey
+ }
+
+ if doEncrypt {
+ newAddress.BodyKeyPacket, newAddress.AttachmentKeyPackets, err = createPackets(pubkey, send.key, req.attKeys)
+ if err != nil {
+ return err
+ }
+ }
+ send.addressMap[email] = newAddress
+ send.sharedScheme |= sendScheme
+
+ return nil
+}
+
+func (req *SendMessageReq) addMIMERecipient(
+ email string, sendScheme int,
+ pubkey *crypto.KeyRing, signature int,
+) (err error) {
+
+ req.mime.contentType = ContentTypeMultipartMixed
+ if req.mime.key == nil {
+ if req.mime.key, req.mime.data, err = encryptSymmetric(req.kr, req.mime.body); err != nil {
+ return err
+ }
+ }
+
+ if sendScheme == PGPMIMEPackage {
+ if pubkey == nil {
+ return errMisingPubkey
+ }
+ // Attachment keys are not needed because attachments are part
+ // of MIME body and therefore attachments are encrypted with
+ // body session key.
+ mimeBodyPacket, _, err := createPackets(pubkey, req.mime.key, map[string]*crypto.SessionKey{})
+ if err != nil {
+ return err
+ }
+ req.mime.addressMap[email] = &MessageAddress{Type: sendScheme, BodyKeyPacket: mimeBodyPacket, Signature: signature}
+ } else {
+ req.mime.addressMap[email] = &MessageAddress{Type: sendScheme, Signature: signature}
+ }
+ req.mime.sharedScheme |= sendScheme
+
+ return nil
+}
+
+func (req *SendMessageReq) PreparePackages() {
+ attkeysEncoded := make(map[string]AlgoKey)
+ for attID, attkey := range req.attKeys {
+ attkeysEncoded[attID] = AlgoKey{
+ Key: attkey.GetBase64Key(),
+ Algorithm: attkey.Algo,
+ }
+ }
+
+ for _, send := range []sendData{req.mime, req.plain, req.rich} {
+ if len(send.addressMap) == 0 {
+ continue
+ }
+ req.Packages = append(req.Packages, newMessagePackage(send, attkeysEncoded))
+ }
+}
+
+type SendMessageRes struct {
+ Res
+
+ Sent *Message
+
+ // Parent is only present if the sent message has a parent (reply/reply all/forward).
+ Parent *Message
+}
+
+func (c *client) SendMessage(id string, sendReq *SendMessageReq) (sent, parent *Message, err error) {
+ if id == "" {
+ err = errors.New("pmapi: cannot send message with an empty id")
+ return
+ }
+
+ if sendReq.Packages == nil {
+ sendReq.Packages = []*MessagePackage{}
+ }
+
+ req, err := c.NewJSONRequest("POST", "/mail/v4/messages/"+id, sendReq)
+ if err != nil {
+ return
+ }
+
+ var res SendMessageRes
+ if err = c.DoJSON(req, &res); err != nil {
+ return
+ }
+
+ sent, parent, err = res.Sent, res.Parent, res.Err()
+ return
+}
diff --git a/pkg/pmapi/message_send_test.go b/pkg/pmapi/message_send_test.go
new file mode 100644
index 00000000..feed8630
--- /dev/null
+++ b/pkg/pmapi/message_send_test.go
@@ -0,0 +1,807 @@
+// 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 .
+
+package pmapi
+
+import (
+ "encoding/base64"
+ "testing"
+
+ "github.com/ProtonMail/gopenpgp/v2/crypto"
+ "github.com/stretchr/testify/require"
+)
+
+type recipient struct {
+ email string
+ sendScheme int
+ pubkey *crypto.KeyRing
+ signature int
+ contentType string
+ doEncrypt bool
+ wantError error
+}
+
+type testData struct {
+ recipients []recipient
+ wantPackages []*MessagePackage
+
+ attKeys map[string]*crypto.SessionKey
+ mimeBody, plainBody, richBody string
+}
+
+func (td *testData) prepareAndCheck(t *testing.T) {
+ r := require.New(t)
+
+ shouldBeEmpty := func(want string) require.ValueAssertionFunc {
+ if len(want) == 0 {
+ return require.Empty
+ }
+ return require.NotEmpty
+ }
+
+ have := NewSendMessageReq(testPrivateKeyRing, td.mimeBody, td.plainBody, td.richBody, td.attKeys)
+ for _, rec := range td.recipients {
+ err := have.AddRecipient(rec.email, rec.sendScheme, rec.pubkey, rec.signature, rec.contentType, rec.doEncrypt)
+
+ if rec.wantError == nil {
+ r.NoError(err, "email %s", rec.email)
+ } else {
+ r.EqualError(err, rec.wantError.Error(), "email %s", rec.email)
+ }
+ }
+ have.PreparePackages()
+
+ r.Equal(len(td.wantPackages), len(have.Packages))
+
+ for i, wantPackage := range td.wantPackages {
+ havePackage := have.Packages[i]
+
+ r.Equal(len(havePackage.Addresses), len(wantPackage.Addresses))
+ for email, wantAddress := range wantPackage.Addresses {
+ haveAddress, ok := havePackage.Addresses[email]
+ r.True(ok, "pkg %d email %s", i, email)
+
+ r.Equal(wantAddress.Type, haveAddress.Type, "pkg %d email %s", i, email)
+ shouldBeEmpty(wantAddress.BodyKeyPacket)(t, haveAddress.BodyKeyPacket, "pkg %d email %s", i, email)
+ r.Equal(wantAddress.Signature, haveAddress.Signature, "pkg %d email %s", i, email)
+
+ if len(td.attKeys) == 0 {
+ r.Len(haveAddress.AttachmentKeyPackets, 0)
+ } else {
+ r.Equal(
+ len(wantAddress.AttachmentKeyPackets),
+ len(haveAddress.AttachmentKeyPackets),
+ "pkg %d email %s", i, email,
+ )
+ for attID, wantAttKey := range wantAddress.AttachmentKeyPackets {
+ haveAttKey, ok := haveAddress.AttachmentKeyPackets[attID]
+ r.True(ok, "pkg %d email %s att %s", i, email, attID)
+ shouldBeEmpty(wantAttKey)(t, haveAttKey, "pkg %d email %s att %s", i, email, attID)
+ }
+ }
+ }
+
+ r.Equal(wantPackage.Type, havePackage.Type, "pkg %d", i)
+ r.Equal(wantPackage.MIMEType, havePackage.MIMEType, "pkg %d", i)
+
+ shouldBeEmpty(wantPackage.Body)(t, havePackage.Body, "pkg %d", i)
+
+ wantBodyKey := wantPackage.BodyKey
+ haveBodyKey := havePackage.BodyKey
+
+ shouldBeEmpty(wantBodyKey.Algorithm)(t, haveBodyKey.Algorithm, "pkg %d", i)
+ shouldBeEmpty(wantBodyKey.Key)(t, haveBodyKey.Key, "pkg %d", i)
+
+ if len(td.attKeys) == 0 {
+ r.Len(havePackage.AttachmentKeys, 0)
+ } else {
+ r.Equal(
+ len(wantPackage.AttachmentKeys),
+ len(havePackage.AttachmentKeys),
+ "pkg %d", i,
+ )
+ for attID, wantAttKey := range wantPackage.AttachmentKeys {
+ haveAttKey, ok := havePackage.AttachmentKeys[attID]
+ r.True(ok, "pkg %d att %s", i, attID)
+ shouldBeEmpty(wantAttKey.Key)(t, haveAttKey.Key, "pkg %d att %s", i, attID)
+ shouldBeEmpty(wantAttKey.Algorithm)(t, haveAttKey.Algorithm, "pkg %d att %s", i, attID)
+ }
+ }
+ }
+}
+
+func TestSendReq(t *testing.T) {
+ attKeyB64 := "EvjO/2RIJNn6HdoU6ACqFdZglzJhpjQ/PpjsvL3mB5Q="
+ token, err := base64.StdEncoding.DecodeString(attKeyB64)
+ require.NoError(t, err)
+
+ attKey := crypto.NewSessionKeyFromToken(token, "aes256")
+ attKeyPackets := map[string]string{"attID": "not-empty"}
+ attAlgoKeys := map[string]AlgoKey{"attID": {"not-empty", "not-empty"}}
+
+ // NOTE naming
+ // Single: there should be one packet
+ // Multiple: there should be more than one packet
+ // Internal: there should be internal package
+ // Clear: there should be non-encrypted package
+ // Encrypted: there should be encrypted package
+ // NotAllowed: combination of inputs which are not allowed
+ tests := map[string]testData{
+ "SingleInternalHTML": {
+ recipients: []recipient{
+ {"html@pm.me", InternalPackage, testPublicKeyRing, SignatureDetached, ContentTypeHTML, true, nil},
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "html@pm.me": {
+ Type: InternalPackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "not-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ },
+ Type: InternalPackage,
+ MIMEType: ContentTypeHTML,
+ Body: "non-empty",
+ },
+ },
+ },
+ "SingleInternalPlain": {
+ recipients: []recipient{
+ {"plain@pm.me", InternalPackage, testPublicKeyRing, SignatureDetached, ContentTypePlainText, true, nil},
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "plain@pm.me": {
+ Type: InternalPackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "not-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ },
+ Type: InternalPackage,
+ MIMEType: ContentTypePlainText,
+ Body: "non-empty",
+ },
+ },
+ },
+ "InternalNotAllowed": {
+ recipients: []recipient{
+ {"multipart@pm.me", InternalPackage, testPublicKeyRing, SignatureDetached, ContentTypeMultipartMixed, true, errMultipartInNonMIME},
+ {"noencrypt@pm.me", InternalPackage, testPublicKeyRing, SignatureDetached, ContentTypeHTML, false, errInternalMustEncrypt},
+ {"no-pubkey@pm.me", InternalPackage, nil, SignatureDetached, ContentTypeHTML, true, errMisingPubkey},
+ {"nosigning@pm.me", InternalPackage, testPublicKeyRing, SignatureNone, ContentTypeHTML, true, errEncryptMustSign},
+ },
+ },
+ "MultipleInternal": {
+ recipients: []recipient{
+ {"internal1@pm.me", InternalPackage, testPublicKeyRing, SignatureDetached, ContentTypePlainText, true, nil},
+ {"internal2@pm.me", InternalPackage, testPublicKeyRing, SignatureDetached, ContentTypeHTML, true, nil},
+ {"internal3@pm.me", InternalPackage, testPublicKeyRing, SignatureDetached, ContentTypePlainText, true, nil},
+ {"internal4@pm.me", InternalPackage, testPublicKeyRing, SignatureDetached, ContentTypeHTML, true, nil},
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "internal1@pm.me": {
+ Type: InternalPackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "not-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ "internal3@pm.me": {
+ Type: InternalPackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "not-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ },
+ Type: InternalPackage,
+ MIMEType: ContentTypePlainText,
+ Body: "non-empty",
+ },
+ {
+ Addresses: map[string]*MessageAddress{
+ "internal2@pm.me": {
+ Type: InternalPackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "not-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ "internal4@pm.me": {
+ Type: InternalPackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "not-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ },
+ Type: InternalPackage,
+ MIMEType: ContentTypeHTML,
+ Body: "non-empty",
+ },
+ },
+ },
+
+ "SingleClearHTML": {
+ recipients: []recipient{
+ {"html@email.com", ClearPackage, nil, SignatureNone, ContentTypeHTML, false, nil},
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "html@email.com": {
+ Type: ClearPackage,
+ Signature: SignatureNone,
+ },
+ },
+ Type: ClearPackage,
+ MIMEType: ContentTypeHTML,
+ Body: "non-empty",
+ BodyKey: AlgoKey{"non-empty", "non-empty"},
+ AttachmentKeys: attAlgoKeys,
+ },
+ },
+ },
+ "SingleClearPlain": {
+ recipients: []recipient{
+ {"plain@email.com", ClearPackage, nil, SignatureNone, ContentTypePlainText, false, nil},
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "plain@email.com": {
+ Type: ClearPackage,
+ Signature: SignatureNone,
+ },
+ },
+ Type: ClearPackage,
+ MIMEType: ContentTypePlainText,
+ Body: "non-empty",
+ BodyKey: AlgoKey{"non-empty", "non-empty"},
+ AttachmentKeys: attAlgoKeys,
+ },
+ },
+ },
+ "SingleClearMIME": {
+ recipients: []recipient{
+ {"mime@email.com", ClearMIMEPackage, nil, SignatureNone, ContentTypeMultipartMixed, false, nil},
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "mime@email.com": {
+ Type: ClearMIMEPackage,
+ Signature: SignatureNone,
+ },
+ },
+ Type: ClearMIMEPackage,
+ MIMEType: ContentTypeMultipartMixed,
+ Body: "non-empty",
+ BodyKey: AlgoKey{"non-empty", "non-empty"},
+ },
+ },
+ },
+ "SingleClearSign": {
+ recipients: []recipient{
+ {"signed@email.com", ClearMIMEPackage, nil, SignatureDetached, ContentTypeMultipartMixed, false, nil},
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "signed@email.com": {
+ Type: ClearMIMEPackage,
+ Signature: SignatureDetached,
+ },
+ },
+ Type: ClearMIMEPackage,
+ MIMEType: ContentTypeMultipartMixed,
+ Body: "non-empty",
+ BodyKey: AlgoKey{"non-empty", "non-empty"},
+ },
+ },
+ },
+ "ClearNotAllowed": {
+ recipients: []recipient{
+ {"plain@email.com", ClearPackage, nil, SignatureDetached, ContentTypePlainText, false, errSignMustBeMultipart},
+ {"html-1@email.com", ClearPackage, nil, SignatureDetached, ContentTypeHTML, false, errSignMustBeMultipart},
+ {"plain@email.com", ClearMIMEPackage, nil, SignatureDetached, ContentTypePlainText, false, errMIMEMustBeMultipart},
+ {"html-@email.com", ClearMIMEPackage, nil, SignatureDetached, ContentTypeHTML, false, errMIMEMustBeMultipart},
+ },
+ },
+ "MultipleClear": {
+ recipients: []recipient{
+ {"html@email.com", ClearPackage, nil, SignatureNone, ContentTypeHTML, false, nil},
+ {"sign@email.com", ClearMIMEPackage, nil, SignatureDetached, ContentTypeMultipartMixed, false, nil},
+ {"mime@email.com", ClearMIMEPackage, nil, SignatureNone, ContentTypeMultipartMixed, false, nil},
+ {"plain@email.com", ClearPackage, nil, SignatureNone, ContentTypePlainText, false, nil},
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{ // TODO can this two be combined
+ "sign@email.com": {
+ Type: ClearMIMEPackage,
+ Signature: SignatureDetached,
+ },
+ "mime@email.com": {
+ Type: ClearMIMEPackage,
+ Signature: SignatureNone,
+ },
+ },
+ Type: ClearMIMEPackage,
+ MIMEType: ContentTypeMultipartMixed,
+ Body: "non-empty",
+ BodyKey: AlgoKey{"non-empty", "non-empty"},
+ },
+ {
+ Addresses: map[string]*MessageAddress{
+ "plain@email.com": {
+ Type: ClearPackage,
+ Signature: SignatureNone,
+ },
+ },
+ Type: ClearPackage,
+ MIMEType: ContentTypePlainText,
+ Body: "non-empty",
+ BodyKey: AlgoKey{"non-empty", "non-empty"},
+ AttachmentKeys: attAlgoKeys,
+ },
+ {
+ Addresses: map[string]*MessageAddress{
+ "html@email.com": {
+ Type: ClearPackage,
+ Signature: SignatureNone,
+ },
+ },
+ Type: ClearPackage,
+ MIMEType: ContentTypeHTML,
+ Body: "non-empty",
+ BodyKey: AlgoKey{"non-empty", "non-empty"},
+ AttachmentKeys: attAlgoKeys,
+ },
+ },
+ },
+
+ "SingleEncryptedMIME": {
+ recipients: []recipient{
+ {"mime@gpg.com", PGPMIMEPackage, testPublicKeyRing, SignatureDetached, ContentTypeMultipartMixed, true, nil},
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "mime@gpg.com": {
+ Type: PGPMIMEPackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "non-empty",
+ },
+ },
+ Type: PGPMIMEPackage,
+ MIMEType: ContentTypeMultipartMixed,
+ Body: "non-empty",
+ },
+ },
+ },
+ "SingleEncryptedInlinePlain": {
+ recipients: []recipient{
+ {"inline-plain@gpg.com", PGPInlinePackage, testPublicKeyRing, SignatureDetached, ContentTypePlainText, true, nil},
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "inline-plain@gpg.com": {
+ Type: PGPInlinePackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "non-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ },
+ Type: PGPInlinePackage,
+ MIMEType: ContentTypePlainText,
+ Body: "non-empty",
+ },
+ },
+ },
+ "SingleEncryptedInlineHTML": {
+ recipients: []recipient{
+ {"inline-html@gpg.com", PGPInlinePackage, testPublicKeyRing, SignatureDetached, ContentTypeHTML, true, nil},
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "inline-html@gpg.com": {
+ Type: PGPInlinePackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "non-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ },
+ Type: PGPInlinePackage,
+ MIMEType: ContentTypeHTML,
+ Body: "non-empty",
+ },
+ },
+ },
+ "EncryptedNotAllowed": {
+ recipients: []recipient{
+ {"inline-mixed@gpg.com", PGPInlinePackage, testPublicKeyRing, SignatureDetached, ContentTypeMultipartMixed, true, errMultipartInNonMIME},
+ {"inline-clear@gpg.com", PGPInlinePackage, nil, SignatureDetached, ContentTypePlainText, false, errInlinelMustEncrypt},
+ {"mime-plain@gpg.com", PGPMIMEPackage, nil, SignatureDetached, ContentTypePlainText, true, errMIMEMustBeMultipart},
+ {"mime-html@gpg.com", PGPMIMEPackage, nil, SignatureDetached, ContentTypeHTML, true, errMIMEMustBeMultipart},
+ {"no-pubkey@gpg.com", PGPMIMEPackage, nil, SignatureDetached, ContentTypeMultipartMixed, true, errMisingPubkey},
+ {"not-signed@gpg.com", PGPMIMEPackage, testPublicKeyRing, SignatureNone, ContentTypeMultipartMixed, true, errEncryptMustSign},
+ },
+ },
+ "MultipleEncrypted": {
+ recipients: []recipient{
+ {"mime@gpg.com", PGPMIMEPackage, testPublicKeyRing, SignatureDetached, ContentTypeMultipartMixed, true, nil},
+ {"inline-plain@gpg.com", PGPInlinePackage, testPublicKeyRing, SignatureDetached, ContentTypePlainText, true, nil},
+ {"inline-html@gpg.com", PGPInlinePackage, testPublicKeyRing, SignatureDetached, ContentTypeHTML, true, nil},
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "mime@gpg.com": {
+ Type: PGPMIMEPackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "non-empty",
+ },
+ },
+ Type: PGPMIMEPackage,
+ MIMEType: ContentTypeMultipartMixed,
+ Body: "non-empty",
+ },
+ {
+ Addresses: map[string]*MessageAddress{
+ "inline-plain@gpg.com": {
+ Type: PGPInlinePackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "non-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ },
+ Type: PGPInlinePackage,
+ MIMEType: ContentTypePlainText,
+ Body: "non-empty",
+ },
+ {
+ Addresses: map[string]*MessageAddress{
+ "inline-html@gpg.com": {
+ Type: PGPInlinePackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "non-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ },
+ Type: PGPInlinePackage,
+ MIMEType: ContentTypeHTML,
+ Body: "non-empty",
+ },
+ },
+ },
+
+ "SingleInternalEncryptedHTML": {
+ recipients: []recipient{
+ {"inline-html@gpg.com", PGPInlinePackage, testPublicKeyRing, SignatureDetached, ContentTypeHTML, true, nil},
+ {"internal@pm.me", InternalPackage, testPublicKeyRing, SignatureDetached, ContentTypeHTML, true, nil},
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "inline-html@gpg.com": {
+ Type: PGPInlinePackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "non-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ "internal@pm.me": {
+ Type: InternalPackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "non-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ },
+ Type: PGPInlinePackage | InternalPackage,
+ MIMEType: ContentTypeHTML,
+ Body: "non-empty",
+ },
+ },
+ },
+ "SingleInternalEncryptedPlain": {
+ recipients: []recipient{
+ {"inline-plain@gpg.com", PGPInlinePackage, testPublicKeyRing, SignatureDetached, ContentTypePlainText, true, nil},
+ {"internal@pm.me", InternalPackage, testPublicKeyRing, SignatureDetached, ContentTypePlainText, true, nil},
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "inline-plain@gpg.com": {
+ Type: PGPInlinePackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "non-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ "internal@pm.me": {
+ Type: InternalPackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "non-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ },
+ Type: PGPInlinePackage | InternalPackage,
+ MIMEType: ContentTypePlainText,
+ Body: "non-empty",
+ },
+ },
+ },
+ "SingleInternalClearHTML": {
+ recipients: []recipient{
+ {"internal@pm.me", InternalPackage, testPublicKeyRing, SignatureDetached, ContentTypeHTML, true, nil},
+ {"html@email.com", ClearPackage, nil, SignatureNone, ContentTypeHTML, false, nil},
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "internal@pm.me": {
+ Type: InternalPackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "not-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ "html@email.com": {
+ Type: ClearPackage,
+ Signature: SignatureNone,
+ },
+ },
+ Type: InternalPackage | ClearPackage,
+ MIMEType: ContentTypeHTML,
+ Body: "non-empty",
+ BodyKey: AlgoKey{"non-empty", "non-empty"},
+ AttachmentKeys: attAlgoKeys,
+ },
+ },
+ },
+ "SingleInternalClearPlain": {
+ recipients: []recipient{
+ {"internal@pm.me", InternalPackage, testPublicKeyRing, SignatureDetached, ContentTypeHTML, true, nil},
+ {"html@email.com", ClearPackage, nil, SignatureNone, ContentTypeHTML, false, nil},
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "internal@pm.me": {
+ Type: InternalPackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "not-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ "html@email.com": {
+ Type: ClearPackage,
+ Signature: SignatureNone,
+ },
+ },
+ Type: InternalPackage | ClearPackage,
+ MIMEType: ContentTypeHTML,
+ Body: "non-empty",
+ BodyKey: AlgoKey{"non-empty", "non-empty"},
+ AttachmentKeys: attAlgoKeys,
+ },
+ },
+ },
+ "SingleClearEncryptedHTML": {
+ recipients: []recipient{
+ {"html@email.com", ClearPackage, nil, SignatureNone, ContentTypeHTML, false, nil},
+ {"inline-html@gpg.com", PGPInlinePackage, testPublicKeyRing, SignatureDetached, ContentTypeHTML, true, nil},
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "inline-html@gpg.com": {
+ Type: PGPInlinePackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "non-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ "html@email.com": {
+ Type: ClearPackage,
+ Signature: SignatureNone,
+ },
+ },
+ Type: PGPInlinePackage | ClearPackage,
+ MIMEType: ContentTypeHTML,
+ Body: "non-empty",
+ BodyKey: AlgoKey{"non-empty", "non-empty"},
+ AttachmentKeys: attAlgoKeys,
+ },
+ },
+ },
+ "SingleClearEncryptedPlain": {
+ recipients: []recipient{
+ {"plain@email.com", ClearPackage, nil, SignatureNone, ContentTypePlainText, false, nil},
+ {"inline-plain@gpg.com", PGPInlinePackage, testPublicKeyRing, SignatureDetached, ContentTypePlainText, true, nil},
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "plain@email.com": {
+ Type: ClearPackage,
+ Signature: SignatureNone,
+ },
+ "inline-plain@gpg.com": {
+ Type: PGPInlinePackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "non-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ },
+ Type: PGPInlinePackage | ClearPackage,
+ MIMEType: ContentTypePlainText,
+ Body: "non-empty",
+ BodyKey: AlgoKey{"non-empty", "non-empty"},
+ AttachmentKeys: attAlgoKeys,
+ },
+ },
+ },
+ "SingleClearEncryptedMIME": {
+ recipients: []recipient{
+ {"signed@email.com", ClearMIMEPackage, nil, SignatureDetached, ContentTypeMultipartMixed, false, nil},
+ {"mime@gpg.com", PGPMIMEPackage, testPublicKeyRing, SignatureDetached, ContentTypeMultipartMixed, true, nil},
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "mime@gpg.com": {
+ Type: PGPMIMEPackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "non-empty",
+ },
+ "signed@email.com": {
+ Type: ClearMIMEPackage,
+ Signature: SignatureDetached,
+ },
+ },
+ Type: ClearMIMEPackage | PGPMIMEPackage,
+ MIMEType: ContentTypeMultipartMixed,
+ Body: "non-empty",
+ BodyKey: AlgoKey{"non-empty", "non-empty"},
+ },
+ },
+ },
+ "SingleClearEncryptedMIMENoSign": {
+ recipients: []recipient{
+ {"mime@email.com", ClearMIMEPackage, nil, SignatureNone, ContentTypeMultipartMixed, false, nil},
+ {"mime@gpg.com", PGPMIMEPackage, testPublicKeyRing, SignatureDetached, ContentTypeMultipartMixed, true, nil},
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "mime@gpg.com": {
+ Type: PGPMIMEPackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "non-empty",
+ },
+ "mime@email.com": { // can this be combined ?
+ Type: ClearMIMEPackage,
+ Signature: SignatureNone,
+ },
+ },
+ Type: ClearMIMEPackage | PGPMIMEPackage,
+ MIMEType: ContentTypeMultipartMixed,
+ Body: "non-empty",
+ BodyKey: AlgoKey{"non-empty", "non-empty"},
+ },
+ },
+ },
+ "MultipleCombo": {
+ recipients: []recipient{
+ {"mime@email.com", ClearMIMEPackage, nil, SignatureNone, ContentTypeMultipartMixed, false, nil},
+ {"signed@email.com", ClearMIMEPackage, nil, SignatureDetached, ContentTypeMultipartMixed, false, nil},
+ {"mime@gpg.com", PGPMIMEPackage, testPublicKeyRing, SignatureDetached, ContentTypeMultipartMixed, true, nil},
+
+ {"plain@pm.me", InternalPackage, testPublicKeyRing, SignatureDetached, ContentTypePlainText, true, nil},
+ {"plain@email.com", ClearPackage, nil, SignatureNone, ContentTypePlainText, false, nil},
+ {"inline-plain@gpg.com", PGPInlinePackage, testPublicKeyRing, SignatureDetached, ContentTypePlainText, true, nil},
+
+ {"html@pm.me", InternalPackage, testPublicKeyRing, SignatureDetached, ContentTypeHTML, true, nil},
+ {"html@email.com", ClearPackage, nil, SignatureNone, ContentTypeHTML, false, nil},
+ {"inline-html@gpg.com", PGPInlinePackage, testPublicKeyRing, SignatureDetached, ContentTypeHTML, true, nil},
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{ // TODO can this three be combined
+ "mime@gpg.com": {
+ Type: PGPMIMEPackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "non-empty",
+ },
+ "mime@email.com": {
+ Type: ClearMIMEPackage,
+ Signature: SignatureNone,
+ },
+ "signed@email.com": {
+ Type: ClearMIMEPackage,
+ Signature: SignatureDetached,
+ },
+ },
+ Type: ClearMIMEPackage | PGPMIMEPackage,
+ MIMEType: ContentTypeMultipartMixed,
+ Body: "non-empty",
+ BodyKey: AlgoKey{"non-empty", "non-empty"},
+ },
+ {
+ Addresses: map[string]*MessageAddress{
+ "plain@pm.me": {
+ Type: InternalPackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "not-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ "plain@email.com": {
+ Type: ClearPackage,
+ Signature: SignatureNone,
+ },
+ "inline-plain@gpg.com": {
+ Type: PGPInlinePackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "non-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ },
+ Type: InternalPackage | ClearPackage | PGPInlinePackage,
+ MIMEType: ContentTypePlainText,
+ Body: "non-empty",
+ BodyKey: AlgoKey{"non-empty", "non-empty"},
+ AttachmentKeys: attAlgoKeys,
+ },
+ {
+ Addresses: map[string]*MessageAddress{
+ "html@pm.me": {
+ Type: InternalPackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "not-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ "html@email.com": {
+ Type: ClearPackage,
+ Signature: SignatureNone,
+ },
+ "inline-html@gpg.com": {
+ Type: PGPInlinePackage,
+ Signature: SignatureDetached,
+ BodyKeyPacket: "non-empty",
+ AttachmentKeyPackets: attKeyPackets,
+ },
+ },
+ Type: InternalPackage | ClearPackage | PGPInlinePackage,
+ MIMEType: ContentTypeHTML,
+ Body: "non-empty",
+ BodyKey: AlgoKey{"non-empty", "non-empty"},
+ AttachmentKeys: attAlgoKeys,
+ },
+ },
+ },
+ }
+
+ for name, test := range tests {
+ test.mimeBody = "Mime body"
+ test.plainBody = "Plain body"
+ test.richBody = "HTML body"
+ t.Run("NoAtt"+name, test.prepareAndCheck)
+ test.attKeys = map[string]*crypto.SessionKey{"attID": attKey}
+ t.Run("Att"+name, test.prepareAndCheck)
+ }
+}
diff --git a/pkg/pmapi/messages.go b/pkg/pmapi/messages.go
index 623bd07b..32ee60d2 100644
--- a/pkg/pmapi/messages.go
+++ b/pkg/pmapi/messages.go
@@ -569,114 +569,6 @@ func (c *client) GetMessage(id string) (msg *Message, err error) {
return res.Message, res.Err()
}
-type SendMessageReq struct {
- ExpirationTime int64 `json:",omitempty"`
- // AutoSaveContacts int `json:",omitempty"`
-
- // Data for encrypted recipients.
- Packages []*MessagePackage
-}
-
-// Message package types.
-const (
- InternalPackage = 1
- EncryptedOutsidePackage = 2
- ClearPackage = 4
- PGPInlinePackage = 8
- PGPMIMEPackage = 16
- ClearMIMEPackage = 32
-)
-
-// Signature types.
-const (
- NoSignature = 0
- YesSignature = 1
-)
-
-type MessagePackage struct {
- Addresses map[string]*MessageAddress
- Type int
- MIMEType string
- Body string // base64-encoded encrypted data packet.
- BodyKey AlgoKey // base64-encoded session key (only if cleartext recipients).
- AttachmentKeys map[string]AlgoKey // Only include if cleartext & attachments.
-}
-
-type MessageAddress struct {
- Type int
- BodyKeyPacket string // base64-encoded key packet.
- Signature int // 0 = None, 1 = Detached, 2 = Attached/Armored
- AttachmentKeyPackets map[string]string
-}
-
-type AlgoKey struct {
- Key string
- Algorithm string
-}
-
-type SendMessageRes struct {
- Res
-
- Sent *Message
-
- // Parent is only present if the sent message has a parent (reply/reply all/forward).
- Parent *Message
-}
-
-func (c *client) SendMessage(id string, sendReq *SendMessageReq) (sent, parent *Message, err error) {
- if id == "" {
- err = errors.New("pmapi: cannot send message with an empty id")
- return
- }
-
- if sendReq.Packages == nil {
- sendReq.Packages = []*MessagePackage{}
- }
-
- req, err := c.NewJSONRequest("POST", "/mail/v4/messages/"+id, sendReq)
- if err != nil {
- return
- }
-
- var res SendMessageRes
- if err = c.DoJSON(req, &res); err != nil {
- return
- }
-
- sent, parent, err = res.Sent, res.Parent, res.Err()
- return
-}
-
-const (
- DraftActionReply = 0
- DraftActionReplyAll = 1
- DraftActionForward = 2
-)
-
-type DraftReq struct {
- Message *Message
- ParentID string `json:",omitempty"`
- Action int
- AttachmentKeyPackets []string
-}
-
-func (c *client) CreateDraft(m *Message, parent string, action int) (created *Message, err error) {
- createReq := &DraftReq{Message: m, ParentID: parent, Action: action, AttachmentKeyPackets: []string{}}
-
- req, err := c.NewJSONRequest("POST", "/mail/v4/messages", createReq)
- if err != nil {
- return
- }
-
- var res MessageRes
- if err = c.DoJSON(req, &res); err != nil {
- return
- }
-
- created, err = res.Message, res.Err()
- return
-}
-
type MessagesActionReq struct {
IDs []string
}