mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 04:36:43 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 27889b8085 | |||
| 2cd7735468 | |||
| 8990f2d1d6 | |||
| 7bc608ce6c | |||
| 01c7daaba7 | |||
| 8408a5fdc0 | |||
| 828fe0e86e | |||
| 5c3179df48 | |||
| 618cb27ac1 | |||
| 83a569b366 | |||
| 70244071ea |
18
Changelog.md
18
Changelog.md
@ -2,6 +2,24 @@
|
||||
|
||||
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
||||
|
||||
## [Bridge 3.0.4] Perth Narrows
|
||||
|
||||
### Changed
|
||||
* Other: Do not list \Deleted flag for All Mail.
|
||||
* Other: Disable perma-delete for expunge on Spam folder.
|
||||
|
||||
### Fixed
|
||||
* Other: Ensure expunge feature test pushes to error stack.
|
||||
* GODT-2170: Use client-side draft update in integration tests.
|
||||
* GODT-2170: Improving test server behaviour.
|
||||
* GODT-2170: Update draft event means delete old and create new message.
|
||||
* GODT-2170: User create draft route: first steps.
|
||||
|
||||
## [Bridge 3.0.3] Perth Narrows
|
||||
|
||||
### Fixed
|
||||
* GPA v0.1.4: fix token expiration mechanism.
|
||||
|
||||
## [Bridge 3.0.2] Perth Narrows
|
||||
|
||||
### Changed
|
||||
|
||||
2
Makefile
2
Makefile
@ -11,7 +11,7 @@ ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
|
||||
.PHONY: build build-gui build-nogui build-launcher versioner hasher
|
||||
|
||||
# Keep version hardcoded so app build works also without Git repository.
|
||||
BRIDGE_APP_VERSION?=3.0.1+git
|
||||
BRIDGE_APP_VERSION?=3.0.4+git
|
||||
APP_VERSION:=${BRIDGE_APP_VERSION}
|
||||
APP_FULL_NAME:=Proton Mail Bridge
|
||||
APP_VENDOR:=Proton AG
|
||||
|
||||
4
go.mod
4
go.mod
@ -5,9 +5,9 @@ go 1.18
|
||||
require (
|
||||
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
||||
github.com/Masterminds/semver/v3 v3.1.1
|
||||
github.com/ProtonMail/gluon v0.14.2-0.20221129150032-c663738a6cee
|
||||
github.com/ProtonMail/gluon v0.14.2-0.20221202093012-ad1570c49c8c
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||
github.com/ProtonMail/go-proton-api v0.1.2
|
||||
github.com/ProtonMail/go-proton-api v0.1.5
|
||||
github.com/ProtonMail/go-rfc5322 v0.11.0
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.4.10
|
||||
github.com/PuerkitoBio/goquery v1.8.0
|
||||
|
||||
8
go.sum
8
go.sum
@ -28,8 +28,8 @@ github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs
|
||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
|
||||
github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk=
|
||||
github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g=
|
||||
github.com/ProtonMail/gluon v0.14.2-0.20221129150032-c663738a6cee h1:rDGqVa4CepqpJF8TDjqnBITqD8OzrLzeg66ibVDCPSc=
|
||||
github.com/ProtonMail/gluon v0.14.2-0.20221129150032-c663738a6cee/go.mod h1:z2AxLIiBCT1K+0OBHyaDI7AEaO5qI6/BEC2TE42vs4Q=
|
||||
github.com/ProtonMail/gluon v0.14.2-0.20221202093012-ad1570c49c8c h1:DzVlJERHOHDQjYz/P12VlORS4rF2Ii83cWcYHsXGdng=
|
||||
github.com/ProtonMail/gluon v0.14.2-0.20221202093012-ad1570c49c8c/go.mod h1:z2AxLIiBCT1K+0OBHyaDI7AEaO5qI6/BEC2TE42vs4Q=
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||
@ -43,8 +43,8 @@ github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753/go.mod h1:NB
|
||||
github.com/ProtonMail/go-mime v0.0.0-20220302105931-303f85f7fe0f/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20220429130430-2192574d760f h1:4IWzKjHzZxdrW9k4zl/qCwenOVHDbVDADPPHFLjs0Oc=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20220429130430-2192574d760f/go.mod h1:qRZgbeASl2a9OwmsV85aWwRqic0NHPh+9ewGAzb4cgM=
|
||||
github.com/ProtonMail/go-proton-api v0.1.2 h1:MD0lbo8ohU1O+1mbMU6EkDmVj4BAq5e5cCPkIZgDF9Q=
|
||||
github.com/ProtonMail/go-proton-api v0.1.2/go.mod h1:jqvJ2HqLHqiPJoEb+BTIB1IF7wvr6p+8ZfA6PO2NRNk=
|
||||
github.com/ProtonMail/go-proton-api v0.1.5 h1:6RJO3jXP3opFfGqrXNvFTefdD4MlfxKjIebT8r5ROf8=
|
||||
github.com/ProtonMail/go-proton-api v0.1.5/go.mod h1:jqvJ2HqLHqiPJoEb+BTIB1IF7wvr6p+8ZfA6PO2NRNk=
|
||||
github.com/ProtonMail/go-rfc5322 v0.11.0 h1:o5Obrm4DpmQEffvgsVqG6S4BKwC1Wat+hYwjIp2YcCY=
|
||||
github.com/ProtonMail/go-rfc5322 v0.11.0/go.mod h1:6oOKr0jXvpoE6pwTx/HukigQpX2J9WUf6h0auplrFTw=
|
||||
github.com/ProtonMail/go-srp v0.0.5 h1:xhUioxZgDbCnpo9JehyFhwwsn9JLWkUGfB0oiKXgiGg=
|
||||
|
||||
@ -389,6 +389,18 @@ func (user *User) handleMessageEvents(ctx context.Context, messageEvents []proto
|
||||
}
|
||||
|
||||
case proton.EventUpdate, proton.EventUpdateFlags:
|
||||
// Draft update means to completely remove old message and upload the new data again.
|
||||
if event.Message.IsDraft() {
|
||||
if err := user.handleUpdateDraftEvent(
|
||||
logging.WithLogrusField(ctx, "action", "update draft"),
|
||||
event,
|
||||
); err != nil {
|
||||
return fmt.Errorf("failed to handle update draft event: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GODT-2028 - Use better events here. It should be possible to have 3 separate events that refrain to
|
||||
// whether the flags, labels or read only data (header+body) has been changed. This requires fixing proton
|
||||
// first so that it correctly reports those cases.
|
||||
@ -400,16 +412,6 @@ func (user *User) handleMessageEvents(ctx context.Context, messageEvents []proto
|
||||
return fmt.Errorf("failed to handle update message event: %w", err)
|
||||
}
|
||||
|
||||
// Only issue body updates if the message is a draft.
|
||||
if event.Message.IsDraft() {
|
||||
if err := user.handleUpdateDraftEvent(
|
||||
logging.WithLogrusField(ctx, "action", "update draft"),
|
||||
event,
|
||||
); err != nil {
|
||||
return fmt.Errorf("failed to handle update draft event: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
case proton.EventDelete:
|
||||
if err := user.handleDeleteMessageEvent(
|
||||
logging.WithLogrusField(ctx, "action", "delete message"),
|
||||
|
||||
@ -18,8 +18,10 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/mail"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
@ -31,6 +33,7 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||
"github.com/ProtonMail/proton-bridge/v3/pkg/message"
|
||||
"github.com/ProtonMail/proton-bridge/v3/pkg/message/parser"
|
||||
"github.com/bradenaw/juniper/stream"
|
||||
"github.com/bradenaw/juniper/xslices"
|
||||
"golang.org/x/exp/slices"
|
||||
@ -326,7 +329,7 @@ func (conn *imapConnector) RemoveMessagesFromMailbox(ctx context.Context, messag
|
||||
return err
|
||||
}
|
||||
|
||||
if mailboxID == proton.SpamLabel || mailboxID == proton.TrashLabel || mailboxID == proton.DraftsLabel {
|
||||
if mailboxID == proton.TrashLabel || mailboxID == proton.DraftsLabel {
|
||||
var metadata []proton.MessageMetadata
|
||||
|
||||
// There's currently no limit on how many IDs we can filter on,
|
||||
@ -437,20 +440,37 @@ func (conn *imapConnector) importMessage(
|
||||
|
||||
if err := safe.RLockRet(func() error {
|
||||
return withAddrKR(conn.apiUser, conn.apiAddrs[conn.addrID], conn.vault.KeyPass(), func(_, addrKR *crypto.KeyRing) error {
|
||||
res, err := stream.Collect(ctx, conn.client.ImportMessages(ctx, addrKR, 1, 1, []proton.ImportReq{{
|
||||
Metadata: proton.ImportMetadata{
|
||||
AddressID: conn.addrID,
|
||||
LabelIDs: labelIDs,
|
||||
Unread: proton.Bool(unread),
|
||||
Flags: flags,
|
||||
},
|
||||
Message: literal,
|
||||
}}...))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to import message: %w", err)
|
||||
messageID := ""
|
||||
|
||||
if slices.Contains(labelIDs, proton.DraftsLabel) {
|
||||
msg, err := conn.createDraft(ctx, literal, addrKR, conn.apiAddrs[conn.addrID])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create draft: %w", err)
|
||||
}
|
||||
|
||||
// apply labels
|
||||
|
||||
messageID = msg.ID
|
||||
} else {
|
||||
res, err := stream.Collect(ctx, conn.client.ImportMessages(ctx, addrKR, 1, 1, []proton.ImportReq{{
|
||||
Metadata: proton.ImportMetadata{
|
||||
AddressID: conn.addrID,
|
||||
LabelIDs: labelIDs,
|
||||
Unread: proton.Bool(unread),
|
||||
Flags: flags,
|
||||
},
|
||||
Message: literal,
|
||||
}}...))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to import message: %w", err)
|
||||
}
|
||||
|
||||
messageID = res[0].MessageID
|
||||
}
|
||||
|
||||
if full, err = conn.client.GetFullMessage(ctx, res[0].MessageID); err != nil {
|
||||
var err error
|
||||
|
||||
if full, err = conn.client.GetFullMessage(ctx, messageID); err != nil {
|
||||
return fmt.Errorf("failed to fetch message: %w", err)
|
||||
}
|
||||
|
||||
@ -497,6 +517,73 @@ func toIMAPMessage(message proton.MessageMetadata) imap.Message {
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *imapConnector) createDraft(ctx context.Context, literal []byte, addrKR *crypto.KeyRing, sender proton.Address) (proton.Message, error) { //nolint:funlen
|
||||
// Create a new message parser from the reader.
|
||||
parser, err := parser.New(bytes.NewReader(literal))
|
||||
if err != nil {
|
||||
return proton.Message{}, fmt.Errorf("failed to create parser: %w", err)
|
||||
}
|
||||
|
||||
message, err := message.ParseWithParser(parser)
|
||||
if err != nil {
|
||||
return proton.Message{}, fmt.Errorf("failed to parse message: %w", err)
|
||||
}
|
||||
|
||||
decBody := string(message.PlainBody)
|
||||
if message.RichBody != "" {
|
||||
decBody = string(message.RichBody)
|
||||
}
|
||||
|
||||
encBody, err := addrKR.Encrypt(crypto.NewPlainMessageFromString(decBody), nil)
|
||||
if err != nil {
|
||||
return proton.Message{}, fmt.Errorf("failed to encrypt message body: %w", err)
|
||||
}
|
||||
|
||||
armBody, err := encBody.GetArmored()
|
||||
if err != nil {
|
||||
return proton.Message{}, fmt.Errorf("failed to get armored message body: %w", err)
|
||||
}
|
||||
|
||||
draft, err := conn.client.CreateDraft(ctx, proton.CreateDraftReq{
|
||||
Message: proton.DraftTemplate{
|
||||
Subject: message.Subject,
|
||||
Body: armBody,
|
||||
MIMEType: message.MIMEType,
|
||||
|
||||
Sender: &mail.Address{Name: sender.DisplayName, Address: sender.Email},
|
||||
ToList: message.ToList,
|
||||
CCList: message.CCList,
|
||||
BCCList: message.BCCList,
|
||||
|
||||
ExternalID: message.ExternalID,
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return proton.Message{}, fmt.Errorf("failed to create draft: %w", err)
|
||||
}
|
||||
|
||||
for _, att := range message.Attachments {
|
||||
disposition := proton.AttachmentDisposition
|
||||
if att.Disposition == "inline" && att.ContentID != "" {
|
||||
disposition = proton.InlineDisposition
|
||||
}
|
||||
|
||||
if _, err := conn.client.UploadAttachment(ctx, addrKR, proton.CreateAttachmentReq{
|
||||
MessageID: draft.ID,
|
||||
Filename: att.Name,
|
||||
MIMEType: rfc822.MIMEType(att.MIMEType),
|
||||
Disposition: disposition,
|
||||
ContentID: att.ContentID,
|
||||
Body: att.Data,
|
||||
}); err != nil {
|
||||
return proton.Message{}, fmt.Errorf("failed to add attachment to draft: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return draft, nil
|
||||
}
|
||||
|
||||
func toIMAPMailbox(label proton.Label, flags, permFlags, attrs imap.FlagSet) imap.Mailbox {
|
||||
if label.Type == proton.LabelTypeLabel {
|
||||
label.Path = append([]string{labelPrefix}, label.Path...)
|
||||
|
||||
@ -333,6 +333,8 @@ func newSystemMailboxCreatedUpdate(labelID imap.MailboxID, labelName string) *im
|
||||
}
|
||||
|
||||
attrs := imap.NewFlagSet(imap.AttrNoInferiors)
|
||||
permanentFlags := defaultPermanentFlags
|
||||
flags := defaultFlags
|
||||
|
||||
switch labelID {
|
||||
case proton.TrashLabel:
|
||||
@ -343,6 +345,8 @@ func newSystemMailboxCreatedUpdate(labelID imap.MailboxID, labelName string) *im
|
||||
|
||||
case proton.AllMailLabel:
|
||||
attrs = attrs.Add(imap.AttrAll)
|
||||
flags = imap.NewFlagSet(imap.FlagSeen, imap.FlagFlagged)
|
||||
permanentFlags = imap.NewFlagSet(imap.FlagSeen, imap.FlagFlagged)
|
||||
|
||||
case proton.ArchiveLabel:
|
||||
attrs = attrs.Add(imap.AttrArchive)
|
||||
@ -360,8 +364,8 @@ func newSystemMailboxCreatedUpdate(labelID imap.MailboxID, labelName string) *im
|
||||
return imap.NewMailboxCreated(imap.Mailbox{
|
||||
ID: labelID,
|
||||
Name: []string{labelName},
|
||||
Flags: defaultFlags,
|
||||
PermanentFlags: defaultPermanentFlags,
|
||||
Flags: flags,
|
||||
PermanentFlags: permanentFlags,
|
||||
Attributes: attrs,
|
||||
})
|
||||
}
|
||||
|
||||
@ -19,7 +19,6 @@ package tests
|
||||
|
||||
import (
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
"github.com/ProtonMail/go-proton-api/server"
|
||||
)
|
||||
|
||||
@ -36,8 +35,6 @@ type API interface {
|
||||
RemoveAddress(userID, addrID string) error
|
||||
RemoveAddressKey(userID, addrID, keyID string) error
|
||||
|
||||
UpdateDraft(userID, draftID string, changes proton.DraftTemplate) error
|
||||
|
||||
Close()
|
||||
}
|
||||
|
||||
|
||||
@ -98,7 +98,7 @@ func TestFeatures(testingT *testing.T) {
|
||||
ctx.Step(`^the address "([^"]*)" of account "([^"]*)" has the following messages in "([^"]*)":$`, s.theAddressOfAccountHasTheFollowingMessagesInMailbox)
|
||||
ctx.Step(`^the address "([^"]*)" of account "([^"]*)" has (\d+) messages in "([^"]*)"$`, s.theAddressOfAccountHasMessagesInMailbox)
|
||||
ctx.Step(`^the address "([^"]*)" of account "([^"]*)" has no keys$`, s.theAddressOfAccountHasNoKeys)
|
||||
ctx.Step(`^the following fields where changed in draft (\d+) for address "([^"]*)" of account "([^"]*)":$`, s.addressDraftChanged)
|
||||
ctx.Step(`^the following fields were changed in draft (\d+) for address "([^"]*)" of account "([^"]*)":$`, s.theFollowingFieldsWereChangedInDraftForAddressOfAccount)
|
||||
|
||||
// ==== BRIDGE ====
|
||||
ctx.Step(`^bridge starts$`, s.bridgeStarts)
|
||||
|
||||
@ -230,36 +230,28 @@ func (t *testCtx) getMBoxID(userID string, name string) string {
|
||||
// getDraftID will return the API ID of draft message with draftIndex, where
|
||||
// draftIndex is similar to sequential ID i.e. 1 represents the first message
|
||||
// of draft folder sorted by API creation time.
|
||||
func (t *testCtx) getDraftID(username string, draftIndex int) string {
|
||||
if draftIndex < 1 {
|
||||
panic(fmt.Sprintf("draft index suppose to be non-zero positive integer, but have %d", draftIndex))
|
||||
}
|
||||
|
||||
func (t *testCtx) getDraftID(username string, draftIndex int) (string, error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
var draftID string
|
||||
|
||||
if err := t.withClient(ctx, username, func(ctx context.Context, client *proton.Client) error {
|
||||
messages, err := client.GetMessageMetadata(
|
||||
ctx, proton.MessageFilter{LabelID: proton.DraftsLabel},
|
||||
)
|
||||
messages, err := client.GetMessageMetadata(ctx, proton.MessageFilter{LabelID: proton.DraftsLabel})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(messages) < draftIndex {
|
||||
panic("draft index too high")
|
||||
return fmt.Errorf("failed to get message metadata: %w", err)
|
||||
} else if len(messages) < draftIndex {
|
||||
return fmt.Errorf("draft index %d is out of range", draftIndex)
|
||||
}
|
||||
|
||||
draftID = messages[draftIndex-1].ID
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return draftID
|
||||
return draftID, nil
|
||||
}
|
||||
|
||||
func (t *testCtx) getLastCall(method, pathExp string) (server.Call, error) {
|
||||
|
||||
@ -27,6 +27,7 @@ Feature: IMAP create messages
|
||||
And IMAP client "1" eventually sees the following messages in "Drafts":
|
||||
| from | to | subject | body |
|
||||
| user@pm.me | john.doe@email.com | foo | bar |
|
||||
# This fails now
|
||||
And IMAP client "1" eventually sees the following messages in "All Mail":
|
||||
| from | to | subject | body |
|
||||
| user@pm.me | john.doe@email.com | foo | bar |
|
||||
|
||||
@ -16,12 +16,14 @@ Feature: IMAP remove messages from mailbox
|
||||
And IMAP client "1" marks message 2 as deleted
|
||||
Then IMAP client "1" sees that message 2 has the flag "\Deleted"
|
||||
When IMAP client "1" expunges
|
||||
And it succeeds
|
||||
Then IMAP client "1" sees 9 messages in "Folders/mbox"
|
||||
|
||||
Scenario: Mark all messages as deleted and EXPUNGE
|
||||
When IMAP client "1" selects "Folders/mbox"
|
||||
And IMAP client "1" marks all messages as deleted
|
||||
And IMAP client "1" expunges
|
||||
And it succeeds
|
||||
Then IMAP client "1" sees 0 messages in "Folders/mbox"
|
||||
|
||||
Scenario: Mark messages as undeleted and EXPUNGE
|
||||
@ -30,11 +32,11 @@ Feature: IMAP remove messages from mailbox
|
||||
But IMAP client "1" marks message 2 as not deleted
|
||||
And IMAP client "1" marks message 3 as not deleted
|
||||
When IMAP client "1" expunges
|
||||
And it succeeds
|
||||
Then IMAP client "1" sees 2 messages in "Folders/mbox"
|
||||
|
||||
# TODO(GODT-1989): Re-enable!
|
||||
# Scenario: Not possible to delete from All Mail and expunge does nothing
|
||||
# When IMAP client "1" selects "All Mail"
|
||||
# And IMAP client "1" marks message 2 as deleted
|
||||
# And IMAP client "1" expunges
|
||||
# Then IMAP client "1" eventually sees 10 messages in "All Mail"
|
||||
Scenario: Not possible to delete from All Mail and expunge does nothing
|
||||
When IMAP client "1" selects "All Mail"
|
||||
And IMAP client "1" marks message 2 as deleted
|
||||
And IMAP client "1" expunges
|
||||
Then it fails
|
||||
@ -6,8 +6,8 @@ Feature: IMAP remove messages from Trash
|
||||
| mbox | folder |
|
||||
| label | label |
|
||||
|
||||
Scenario Outline: Message in Trash or Spam and some other label is not permanently deleted
|
||||
Given the address "user@pm.me" of account "user@pm.me" has the following messages in "<mailbox>":
|
||||
Scenario Outline: Message in Trash and some other label is not permanently deleted
|
||||
Given the address "user@pm.me" of account "user@pm.me" has the following messages in "Trash":
|
||||
| from | to | subject | body |
|
||||
| john.doe@mail.com | user@pm.me | foo | hello |
|
||||
| jane.doe@mail.com | name@pm.me | bar | world |
|
||||
@ -15,27 +15,22 @@ Feature: IMAP remove messages from Trash
|
||||
And the user logs in with username "user@pm.me" and password "password"
|
||||
And user "user@pm.me" finishes syncing
|
||||
And user "user@pm.me" connects and authenticates IMAP client "1"
|
||||
And IMAP client "1" selects "<mailbox>"
|
||||
When IMAP client "1" copies the message with subject "foo" from "<mailbox>" to "Labels/label"
|
||||
And IMAP client "1" selects "Trash"
|
||||
When IMAP client "1" copies the message with subject "foo" from "Trash" to "Labels/label"
|
||||
Then it succeeds
|
||||
When IMAP client "1" marks the message with subject "foo" as deleted
|
||||
Then it succeeds
|
||||
And IMAP client "1" sees 2 messages in "<mailbox>"
|
||||
And IMAP client "1" sees 2 messages in "Trash"
|
||||
And IMAP client "1" sees 2 messages in "All Mail"
|
||||
And IMAP client "1" sees 1 messages in "Labels/label"
|
||||
When IMAP client "1" expunges
|
||||
Then it succeeds
|
||||
And IMAP client "1" sees 1 messages in "<mailbox>"
|
||||
And IMAP client "1" sees 1 messages in "Trash"
|
||||
And IMAP client "1" sees 2 messages in "All Mail"
|
||||
And IMAP client "1" sees 1 messages in "Labels/label"
|
||||
|
||||
Examples:
|
||||
| mailbox |
|
||||
| Spam |
|
||||
| Trash |
|
||||
|
||||
Scenario Outline: Message in Trash or Spam only is permanently deleted
|
||||
Given the address "user@pm.me" of account "user@pm.me" has the following messages in "<mailbox>":
|
||||
Scenario Outline: Message in Trash only is permanently deleted
|
||||
Given the address "user@pm.me" of account "user@pm.me" has the following messages in "Trash":
|
||||
| from | to | subject | body |
|
||||
| john.doe@mail.com | user@pm.me | foo | hello |
|
||||
| jane.doe@mail.com | name@pm.me | bar | world |
|
||||
@ -43,17 +38,12 @@ Feature: IMAP remove messages from Trash
|
||||
And the user logs in with username "user@pm.me" and password "password"
|
||||
And user "user@pm.me" finishes syncing
|
||||
And user "user@pm.me" connects and authenticates IMAP client "1"
|
||||
And IMAP client "1" selects "<mailbox>"
|
||||
And IMAP client "1" selects "Trash"
|
||||
When IMAP client "1" marks the message with subject "foo" as deleted
|
||||
Then it succeeds
|
||||
And IMAP client "1" sees 2 messages in "<mailbox>"
|
||||
And IMAP client "1" sees 2 messages in "Trash"
|
||||
And IMAP client "1" sees 2 messages in "All Mail"
|
||||
When IMAP client "1" expunges
|
||||
Then it succeeds
|
||||
And IMAP client "1" sees 1 messages in "<mailbox>"
|
||||
And IMAP client "1" eventually sees 1 messages in "All Mail"
|
||||
|
||||
Examples:
|
||||
| mailbox |
|
||||
| Spam |
|
||||
| Trash |
|
||||
And IMAP client "1" sees 1 messages in "Trash"
|
||||
And IMAP client "1" eventually sees 1 messages in "All Mail"
|
||||
@ -11,10 +11,15 @@ Feature: IMAP Draft messages
|
||||
|
||||
This is a dra
|
||||
"""
|
||||
Then IMAP client "1" eventually sees the following messages in "Drafts":
|
||||
| body |
|
||||
| This is a dra |
|
||||
And IMAP client "1" sees 1 messages in "Drafts"
|
||||
|
||||
Scenario: Draft edited locally
|
||||
When IMAP client "1" marks message 1 as deleted
|
||||
And IMAP client "1" expunges
|
||||
And it succeeds
|
||||
And IMAP client "1" appends the following message to "Drafts":
|
||||
"""
|
||||
Subject: Basic Draft
|
||||
@ -30,7 +35,7 @@ Feature: IMAP Draft messages
|
||||
And IMAP client "1" sees 1 messages in "Drafts"
|
||||
|
||||
Scenario: Draft edited remotely
|
||||
When the following fields where changed in draft 1 for address "user@pm.me" of account "user@pm.me":
|
||||
When the following fields were changed in draft 1 for address "user@pm.me" of account "user@pm.me":
|
||||
| to | subject | body |
|
||||
| someone@proton.me | Basic Draft | This is a draft body, but longer |
|
||||
Then IMAP client "1" eventually sees the following messages in "Drafts":
|
||||
|
||||
@ -33,6 +33,7 @@ import (
|
||||
"github.com/emersion/go-imap"
|
||||
id "github.com/emersion/go-imap-id"
|
||||
"github.com/emersion/go-imap/client"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
@ -280,7 +281,9 @@ func (s *scenario) imapClientSeesTheFollowingMessagesInMailbox(clientID, mailbox
|
||||
|
||||
func (s *scenario) imapClientEventuallySeesTheFollowingMessagesInMailbox(clientID, mailbox string, table *godog.Table) error {
|
||||
return eventually(func() error {
|
||||
return s.imapClientSeesTheFollowingMessagesInMailbox(clientID, mailbox, table)
|
||||
err := s.imapClientSeesTheFollowingMessagesInMailbox(clientID, mailbox, table)
|
||||
logrus.WithError(err).Trace("Matching eventually")
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
@ -374,7 +377,9 @@ func (s *scenario) imapClientSeesThatMessageHasTheFlag(clientID string, seq int,
|
||||
func (s *scenario) imapClientExpunges(clientID string) error {
|
||||
_, client := s.t.getIMAPClient(clientID)
|
||||
|
||||
return client.Expunge(nil)
|
||||
s.t.pushError(client.Expunge(nil))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scenario) imapClientAppendsTheFollowingMessageToMailbox(clientID string, mailbox string, docString *godog.DocString) error {
|
||||
|
||||
@ -144,7 +144,7 @@ func matchMessages(have, want []Message) error {
|
||||
})
|
||||
|
||||
if !IsSub(ToAny(have), ToAny(want)) {
|
||||
return fmt.Errorf("missing messages: have %+v, want %+v", have, want)
|
||||
return fmt.Errorf("missing messages: have %#v, want %#v", have, want)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@ -226,7 +226,7 @@ func (s *scenario) theAddressOfAccountHasNoKeys(address, username string) error
|
||||
// accountDraftChanged changes the draft attributes, where draftIndex is
|
||||
// similar to sequential ID i.e. 1 represents the first message of draft folder
|
||||
// sorted by API creation time.
|
||||
func (s *scenario) addressDraftChanged(draftIndex int, address, username string, table *godog.Table) error {
|
||||
func (s *scenario) theFollowingFieldsWereChangedInDraftForAddressOfAccount(draftIndex int, address, username string, table *godog.Table) error {
|
||||
wantMessages, err := unmarshalTable[Message](table)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -236,35 +236,59 @@ func (s *scenario) addressDraftChanged(draftIndex int, address, username string,
|
||||
return fmt.Errorf("expected to have one row in table but got %d instead", len(wantMessages))
|
||||
}
|
||||
|
||||
draftID := s.t.getDraftID(username, draftIndex)
|
||||
|
||||
encBody := []byte{}
|
||||
|
||||
if wantMessages[0].Body != "" {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
if err := s.t.withClient(ctx, username, func(ctx context.Context, c *proton.Client) error {
|
||||
return s.t.withAddrKR(ctx, c, username, s.t.getUserAddrID(s.t.getUserID(username), address),
|
||||
func(ctx context.Context, addrKR *crypto.KeyRing) error {
|
||||
var err error
|
||||
encBody, err = proton.EncryptRFC822(addrKR, wantMessages[0].Build())
|
||||
return err
|
||||
})
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
draftID, err := s.t.getDraftID(username, draftIndex)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get draft ID: %w", err)
|
||||
}
|
||||
|
||||
changes := proton.DraftTemplate{
|
||||
Subject: wantMessages[0].Subject,
|
||||
Body: string(encBody),
|
||||
}
|
||||
if wantMessages[0].To != "" {
|
||||
changes.ToList = []*mail.Address{{Address: wantMessages[0].To}}
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
return s.t.api.UpdateDraft(s.t.getUserID(username), draftID, changes)
|
||||
return s.t.withClient(ctx, username, func(ctx context.Context, c *proton.Client) error {
|
||||
return s.t.withAddrKR(ctx, c, username, s.t.getUserAddrID(s.t.getUserID(username), address), func(_ context.Context, addrKR *crypto.KeyRing) error {
|
||||
var changes proton.DraftTemplate
|
||||
|
||||
if wantMessages[0].From != "" {
|
||||
return fmt.Errorf("changing from address is not supported")
|
||||
}
|
||||
|
||||
if wantMessages[0].To != "" {
|
||||
changes.ToList = []*mail.Address{{Address: wantMessages[0].To}}
|
||||
}
|
||||
|
||||
if wantMessages[0].CC != "" {
|
||||
changes.CCList = []*mail.Address{{Address: wantMessages[0].CC}}
|
||||
}
|
||||
|
||||
if wantMessages[0].BCC != "" {
|
||||
changes.BCCList = []*mail.Address{{Address: wantMessages[0].BCC}}
|
||||
}
|
||||
|
||||
if wantMessages[0].Subject != "" {
|
||||
changes.Subject = wantMessages[0].Subject
|
||||
}
|
||||
|
||||
if wantMessages[0].Body != "" {
|
||||
enc, err := addrKR.Encrypt(crypto.NewPlainMessageFromString(wantMessages[0].Body), addrKR)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encrypt message body: %w", err)
|
||||
}
|
||||
|
||||
arm, err := enc.GetArmored()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get armored message: %w", err)
|
||||
}
|
||||
|
||||
changes.Body = arm
|
||||
}
|
||||
|
||||
if _, err := c.UpdateDraft(ctx, draftID, proton.UpdateDraftReq{Message: changes}); err != nil {
|
||||
return fmt.Errorf("failed to update draft: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *scenario) userLogsInWithUsernameAndPassword(username, password string) error {
|
||||
|
||||
Reference in New Issue
Block a user