Files
Proton-API-Bridge/mail.go
Mark Ferrell 04a533aec9 feat: leverage go's mod replace sanely
The Proton-API-Bridge should not be attempting to take over, nor
re-define, the existing proton-go-api interfaces. This is known has an
"unfriendly fork" and increases the overall complexity of maintaining
the Proton-API-Bridge.

All changes to the proton-go-api should be done as "friendly" as
possible, and should be submitted back upstream for inclusion. This
improves the functionality of the proton-go-api for more than just 1
project, and it reduces the long-term maintenance required for
Proton-API-Bridge.

To this end, Proton-API-Bridge should be written to always assume it is
using github.com/ProtonMail/proton-go-api and leverage the `replace`
operation in go.mod in order to leverage our "friendly fork" of
proton-go-api. I.e. the long-term goal should be to _not_ maintain a
fork of proton-go-api.

> Anything else would be uncivilized.
2025-01-16 05:38:50 -08:00

199 lines
5.0 KiB
Go

package proton_api_bridge
import (
"context"
"encoding/base64"
"io/ioutil"
"log"
"net/mail"
"path/filepath"
"github.com/ProtonMail/gluon/rfc822"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/go-proton-api"
)
type MailSendingParameters struct {
TemplateFile string
EmailSubject string
RecipientEmailAddress string
EmailAttachments []string
EmailContentIDs []string
}
func (protonDrive *ProtonDrive) SendEmail(ctx context.Context, i int, errChan chan error, config *MailSendingParameters) {
log.Println("SendEmail in", i)
defer log.Println("SendEmail out", i)
createDraftResp, err := protonDrive.createDraft(ctx, config)
if err != nil {
errChan <- err
}
attachments, err := protonDrive.uploadAttachments(ctx, createDraftResp, config)
if err != nil {
errChan <- err
}
err = protonDrive.sendDraft(ctx, createDraftResp.ID, attachments, config)
if err != nil {
errChan <- err
}
errChan <- nil
}
func (protonDrive *ProtonDrive) getHTMLBody(config *MailSendingParameters) ([]byte, error) {
htmlTemplate, err := ioutil.ReadFile(config.TemplateFile)
if err != nil {
return nil, err
}
return htmlTemplate, nil
}
func (protonDrive *ProtonDrive) createDraft(ctx context.Context, config *MailSendingParameters) (*proton.Message, error) {
htmlTemplate, err := protonDrive.getHTMLBody(config)
if err != nil {
return nil, err
}
createDraftReq := proton.CreateDraftReq{
Message: proton.DraftTemplate{
Subject: config.EmailSubject,
Sender: &mail.Address{
Address: protonDrive.signatureAddress,
},
ToList: []*mail.Address{
{
Address: config.RecipientEmailAddress,
},
},
CCList: []*mail.Address{},
BCCList: []*mail.Address{},
Body: string(htmlTemplate), // NOTE: the body here is for yourself to view it! No sender encryption is done yet
MIMEType: rfc822.TextHTML,
// Unread: false,
// ExternalID: "", // FIXME: what's this
},
}
createDraftResp, err := protonDrive.c.CreateDraft(ctx, protonDrive.DefaultAddrKR, createDraftReq)
if err != nil {
return nil, err
}
return &createDraftResp, nil
}
func (protonDrive *ProtonDrive) getAttachmentSessionKeyMap(attachments []*proton.Attachment) (map[string]*crypto.SessionKey, error) {
ret := make(map[string]*crypto.SessionKey)
for i := range attachments {
keyPacket, err := base64.StdEncoding.DecodeString(attachments[i].KeyPackets)
if err != nil {
return nil, err
}
key, err := protonDrive.DefaultAddrKR.DecryptSessionKey(keyPacket)
if err != nil {
return nil, err
}
ret[attachments[i].ID] = key
}
return ret, nil
}
func (protonDrive *ProtonDrive) uploadAttachments(ctx context.Context, createDraftResp *proton.Message, config *MailSendingParameters) ([]*proton.Attachment, error) {
attachments := make([]*proton.Attachment, 0)
for i := range config.EmailAttachments {
// read out attachment file
fileByteArray, err := ioutil.ReadFile(config.EmailAttachments[i])
if err != nil {
return nil, err
}
req := proton.CreateAttachmentReq{
MessageID: createDraftResp.ID,
Filename: filepath.Base(config.EmailAttachments[i]),
MIMEType: rfc822.MultipartMixed, // FIXME: what is this?
Disposition: proton.InlineDisposition,
ContentID: config.EmailContentIDs[i],
Body: fileByteArray,
}
uploadAttachmentResp, err := protonDrive.c.UploadAttachment(ctx, protonDrive.DefaultAddrKR, req)
if err != nil {
return nil, err
}
// log.Printf("uploadAttachmentResp %#v", uploadAttachmentResp)
attachments = append(attachments, &uploadAttachmentResp)
}
return attachments, nil
}
func (protonDrive *ProtonDrive) sendDraft(ctx context.Context, messageID string, attachents []*proton.Attachment, config *MailSendingParameters) error {
// FIXME: repect all sendPrefs
// FIXME: respect PGPMIMEScheme, etc.
recipientPublicKeys, recipientType, err := protonDrive.c.GetPublicKeys(ctx, config.RecipientEmailAddress)
if err != nil {
return err
}
if recipientType != proton.RecipientTypeInternal {
log.Fatalln("Currently only support internal email sending")
}
recipientKR, err := recipientPublicKeys.GetKeyRing()
if err != nil {
return err
}
htmlTemplate, err := protonDrive.getHTMLBody(config)
if err != nil {
return err
}
atts, err := protonDrive.getAttachmentSessionKeyMap(attachents)
if err != nil {
return err
}
// send email
sendReq := proton.SendDraftReq{
// Packages: []*proton.MessagePackage{},
}
// for each of the recipient, we encrypt body for them
if err = sendReq.AddTextPackage(protonDrive.DefaultAddrKR,
string(htmlTemplate),
rfc822.TextHTML,
map[string]proton.SendPreferences{config.RecipientEmailAddress: {
Encrypt: true,
PubKey: recipientKR,
SignatureType: proton.DetachedSignature,
EncryptionScheme: proton.InternalScheme,
MIMEType: rfc822.TextHTML, // FIXME
}},
atts,
); err != nil {
return err
}
/* msg */
_, err = protonDrive.c.SendDraft(ctx, messageID, sendReq)
if err != nil {
return err
}
// log.Println(msg)
return nil
}