2017-09-18 15:41:50 +03:00
|
|
|
package protonmail
|
|
|
|
|
|
|
|
import (
|
2017-09-20 11:09:31 +03:00
|
|
|
"bytes"
|
|
|
|
"io"
|
2017-09-18 15:41:50 +03:00
|
|
|
"net/http"
|
2017-09-20 11:09:31 +03:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
"golang.org/x/crypto/openpgp"
|
|
|
|
"golang.org/x/crypto/openpgp/armor"
|
2017-09-18 15:41:50 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
type MessageType int
|
|
|
|
|
|
|
|
const (
|
|
|
|
MessageInbox MessageType = iota
|
|
|
|
MessageDraft
|
|
|
|
MessageSent
|
|
|
|
MessageInboxAndSent
|
|
|
|
)
|
|
|
|
|
|
|
|
type MessageEncryption int
|
|
|
|
|
2017-09-20 11:09:31 +03:00
|
|
|
const (
|
|
|
|
MessageUnencrypted MessageEncryption = iota
|
|
|
|
MessageEncryptedInternal
|
|
|
|
MessageEncryptedExternal
|
|
|
|
MessageEncryptedOutside
|
|
|
|
_
|
|
|
|
_
|
|
|
|
_
|
|
|
|
MessageEncryptedInlinePGP
|
|
|
|
MessageEncryptedPGPMIME
|
|
|
|
_
|
|
|
|
_
|
|
|
|
)
|
|
|
|
|
2017-09-18 15:41:50 +03:00
|
|
|
type MessageAddress struct {
|
|
|
|
Address string
|
2017-09-19 15:54:47 +03:00
|
|
|
Name string
|
2017-09-18 15:41:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
type Message struct {
|
2017-09-19 15:54:47 +03:00
|
|
|
ID string
|
|
|
|
Order int64
|
|
|
|
Subject string
|
|
|
|
IsRead int
|
|
|
|
Type MessageType
|
|
|
|
Sender *MessageAddress
|
|
|
|
ReplyTo *MessageAddress
|
|
|
|
ToList []*MessageAddress
|
|
|
|
Time int64
|
|
|
|
Size int64
|
|
|
|
IsEncrypted MessageEncryption
|
2017-09-18 15:41:50 +03:00
|
|
|
ExpirationTime int64
|
2017-09-19 15:54:47 +03:00
|
|
|
IsReplied int
|
|
|
|
IsRepliedAll int
|
|
|
|
IsForwarded int
|
|
|
|
SpamScore int
|
|
|
|
AddressID string
|
|
|
|
Body string
|
|
|
|
MIMEType string
|
|
|
|
CCList []*MessageAddress
|
|
|
|
BCCList []*MessageAddress
|
|
|
|
Header string
|
|
|
|
Attachments []*Attachment
|
|
|
|
LabelIDs []string
|
|
|
|
ExternalID string
|
2017-09-18 15:41:50 +03:00
|
|
|
}
|
|
|
|
|
2017-09-20 11:09:31 +03:00
|
|
|
func (msg *Message) Read(keyring openpgp.KeyRing, prompt openpgp.PromptFunction) (*openpgp.MessageDetails, error) {
|
|
|
|
switch msg.IsEncrypted {
|
|
|
|
case MessageUnencrypted:
|
|
|
|
return &openpgp.MessageDetails{
|
|
|
|
IsEncrypted: false,
|
|
|
|
IsSigned: false,
|
|
|
|
UnverifiedBody: strings.NewReader(msg.Body),
|
|
|
|
}, nil
|
|
|
|
default:
|
|
|
|
block, err := armor.Decode(strings.NewReader(msg.Body))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return openpgp.ReadMessage(block.Body, keyring, prompt, nil)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type messageWriter struct {
|
|
|
|
plaintext io.WriteCloser
|
|
|
|
ciphertext io.WriteCloser
|
|
|
|
b *bytes.Buffer
|
|
|
|
msg *Message
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *messageWriter) Write(p []byte) (n int, err error) {
|
|
|
|
return w.plaintext.Write(p)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *messageWriter) Close() error {
|
|
|
|
if err := w.plaintext.Close(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := w.ciphertext.Close(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
w.msg.Body = w.b.String()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (msg *Message) Encrypt(to []*openpgp.Entity, signed *openpgp.Entity) (plaintext io.WriteCloser, err error) {
|
|
|
|
var b bytes.Buffer
|
|
|
|
ciphertext, err := armor.Encode(&b, "PGP MESSAGE", nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
plaintext, err = openpgp.Encrypt(ciphertext, to, signed, nil, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &messageWriter{
|
|
|
|
plaintext: plaintext,
|
|
|
|
ciphertext: ciphertext,
|
|
|
|
b: &b,
|
|
|
|
msg: msg,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2017-09-18 15:41:50 +03:00
|
|
|
func (c *Client) GetMessage(id string) (*Message, error) {
|
|
|
|
req, err := c.newRequest(http.MethodGet, "/messages/"+id, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var respData struct {
|
|
|
|
resp
|
|
|
|
Message *Message
|
|
|
|
}
|
|
|
|
if err := c.doJSON(req, &respData); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return respData.Message, nil
|
|
|
|
}
|
|
|
|
|
2017-09-20 11:09:31 +03:00
|
|
|
// CreateDraftMessage creates a new draft message. ToList, CCList, BCCList,
|
|
|
|
// Subject, Body and AddressID are required in msg.
|
2017-09-18 15:41:50 +03:00
|
|
|
func (c *Client) CreateDraftMessage(msg *Message, parentID string) (*Message, error) {
|
|
|
|
reqData := struct {
|
2017-09-19 15:54:47 +03:00
|
|
|
Message *Message
|
2017-09-18 15:41:50 +03:00
|
|
|
ParentID string `json:",omitempty"`
|
2017-09-19 15:54:47 +03:00
|
|
|
Action *int `json:",omitempty"`
|
2017-09-18 15:41:50 +03:00
|
|
|
}{msg, parentID, nil}
|
|
|
|
req, err := c.newJSONRequest(http.MethodPost, "/messages", &reqData)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var respData struct {
|
|
|
|
resp
|
|
|
|
Message *Message
|
|
|
|
}
|
|
|
|
if err := c.doJSON(req, &respData); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return respData.Message, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) UpdateDraftMessage(msg *Message) (*Message, error) {
|
|
|
|
reqData := struct {
|
|
|
|
Message *Message
|
|
|
|
}{msg}
|
|
|
|
req, err := c.newJSONRequest(http.MethodPut, "/messages/"+msg.ID, &reqData)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var respData struct {
|
|
|
|
resp
|
|
|
|
Message *Message
|
|
|
|
}
|
|
|
|
if err := c.doJSON(req, &respData); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return respData.Message, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type MessageKeyPacket struct {
|
2017-09-19 15:54:47 +03:00
|
|
|
ID string
|
2017-09-18 15:41:50 +03:00
|
|
|
KeyPackets string
|
|
|
|
}
|
|
|
|
|
|
|
|
type MessagePackageType int
|
|
|
|
|
|
|
|
type MessagePackage struct {
|
2017-09-19 15:54:47 +03:00
|
|
|
Address string
|
|
|
|
Type MessagePackageType
|
|
|
|
Body string
|
2017-09-18 15:41:50 +03:00
|
|
|
KeyPackets []*MessageKeyPacket
|
|
|
|
|
2017-09-19 15:54:47 +03:00
|
|
|
Token string
|
|
|
|
EncToken string
|
2017-09-18 15:41:50 +03:00
|
|
|
PasswordHint string `json:",omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type MessageOutgoing struct {
|
|
|
|
ID string
|
|
|
|
|
2017-09-19 15:54:47 +03:00
|
|
|
ClearBody string
|
2017-09-18 15:41:50 +03:00
|
|
|
ExpirationTime int64
|
|
|
|
AttachmentKeys []*AttachmentKey
|
2017-09-19 15:54:47 +03:00
|
|
|
Packages []*MessagePackage
|
2017-09-18 15:41:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) SendMessage(msg *MessageOutgoing) (sent, parent *Message, err error) {
|
|
|
|
req, err := c.newJSONRequest(http.MethodPut, "/messages/"+msg.ID, msg)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var respData struct {
|
|
|
|
resp
|
|
|
|
Sent, Parent *Message
|
|
|
|
}
|
|
|
|
if err := c.doJSON(req, &respData); err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return respData.Sent, respData.Parent, nil
|
|
|
|
}
|