forked from Silverfish/proton-bridge
refactor smtp sending
* [x] move package creation logic to `pmapi.SendMessageReq`
* [ ] write test of package creation logic
* [x] internal
* [x] plain
* [x] external encrypted
* [ ] signature ???
* [x] attachments
This commit is contained in:
@ -187,7 +187,7 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
|
|||||||
log.WithError(err).Error("Failed to parse message")
|
log.WithError(err).Error("Failed to parse message")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
clearBody := message.Body
|
richBody := message.Body
|
||||||
|
|
||||||
externalID := message.Header.Get("Message-Id")
|
externalID := message.Header.Get("Message-Id")
|
||||||
externalID = strings.Trim(externalID, "<>")
|
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...)
|
atts = append(atts, message.Attachments...)
|
||||||
// Decrypt attachment keys, because we will need to re-encrypt them with the recipients' public keys.
|
// Decrypt attachment keys, because we will need to re-encrypt them with the recipients' public keys.
|
||||||
attkeys := make(map[string]*crypto.SessionKey)
|
attkeys := make(map[string]*crypto.SessionKey)
|
||||||
attkeysEncoded := make(map[string]pmapi.AlgoKey)
|
|
||||||
|
|
||||||
for _, att := range atts {
|
for _, att := range atts {
|
||||||
var keyPackets []byte
|
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 {
|
if attkeys[att.ID], err = kr.DecryptSessionKey(keyPackets); err != nil {
|
||||||
return errors.Wrap(err, "decrypting attachment session key")
|
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
|
req := pmapi.NewSendMessageReq(kr, mimeBody, plainBody, richBody, attkeys)
|
||||||
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
|
|
||||||
|
|
||||||
containsUnencryptedRecipients := false
|
containsUnencryptedRecipients := false
|
||||||
|
|
||||||
for _, email := range to {
|
for _, email := range to {
|
||||||
@ -300,59 +285,13 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
|
|||||||
|
|
||||||
var signature int
|
var signature int
|
||||||
if sendPreferences.Sign {
|
if sendPreferences.Sign {
|
||||||
signature = pmapi.YesSignature
|
signature = pmapi.SignatureDetached
|
||||||
} else {
|
} else {
|
||||||
signature = pmapi.NoSignature
|
signature = pmapi.SignatureNone
|
||||||
}
|
}
|
||||||
if sendPreferences.Scheme == pmapi.PGPMIMEPackage || sendPreferences.Scheme == pmapi.ClearMIMEPackage {
|
|
||||||
if mimeKey == nil {
|
if err := req.AddRecipient(email, sendPreferences.Scheme, sendPreferences.PublicKey, signature, sendPreferences.MIMEType, sendPreferences.Encrypt); err != nil {
|
||||||
if mimeKey, mimeData, err = encryptSymmetric(kr, mimeBody, true); err != nil {
|
return errors.Wrap(err, "failed to add recipient")
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,31 +309,7 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
req := &pmapi.SendMessageReq{}
|
req.PreparePackages()
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
return su.storeUser.SendMessage(message.ID, req)
|
return su.storeUser.SendMessage(message.ID, req)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,11 +18,7 @@
|
|||||||
package smtp
|
package smtp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:gochecknoglobals // Used like a constant
|
//nolint:gochecknoglobals // Used like a constant
|
||||||
@ -35,85 +31,3 @@ var mailFormat = regexp.MustCompile(`.+@.+\..+`)
|
|||||||
func looksLikeEmail(e string) bool {
|
func looksLikeEmail(e string) bool {
|
||||||
return mailFormat.MatchString(e)
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@ -254,7 +254,7 @@ func (c *client) doBuffered(req *http.Request, bodyBuffer []byte, retryUnauthori
|
|||||||
head += "\n"
|
head += "\n"
|
||||||
}
|
}
|
||||||
c.log.Tracef("REQHEAD \n%s", head)
|
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
|
hasBody := len(bodyBuffer) > 0
|
||||||
|
|||||||
43
pkg/pmapi/debug.go
Normal file
43
pkg/pmapi/debug.go
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
@ -19,6 +19,7 @@ package pmapi
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -289,3 +290,57 @@ func signAttachment(encrypter *crypto.KeyRing, data io.Reader) (signature io.Rea
|
|||||||
}
|
}
|
||||||
return bytes.NewReader(sig.GetBinary()), nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
331
pkg/pmapi/message_send.go
Normal file
331
pkg/pmapi/message_send.go
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
807
pkg/pmapi/message_send_test.go
Normal file
807
pkg/pmapi/message_send_test.go
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -569,114 +569,6 @@ func (c *client) GetMessage(id string) (msg *Message, err error) {
|
|||||||
return res.Message, res.Err()
|
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 {
|
type MessagesActionReq struct {
|
||||||
IDs []string
|
IDs []string
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user