Compare commits

...

21 Commits

Author SHA1 Message Date
1d9855a190 Other: Bridge James 1.8.11 2021-11-12 14:06:04 +01:00
cea33bebe2 GODT-1415: Only messages which are in Spam should be moved to INBOX once they are marked as not-a-spam.
This is regression of GODT-963
2021-11-09 10:32:58 +00:00
9d405a1549 GODT-1397: Update bbolt to v1.3.6 2021-11-09 10:01:08 +00:00
47f468e4b7 GODT-1410: Remove event ID from sentry report description 2021-11-08 16:05:34 +00:00
b9c6c00709 GODT-1395: CI should fail on go.sum changed. 2021-11-05 06:12:38 +01:00
5ce9cb8eec GODT-1405: Integration test fix: Prevent unilateral update in FETCH when copying message by append. 2021-11-01 16:32:28 +01:00
bc7133e401 Merge branch 'master' into devel 2021-10-21 13:26:29 +02:00
a219ecf3cb Other: fix go.sum 2021-10-21 13:25:44 +02:00
8061b1e6fa GODT-1392: added unit test for get message 2021-10-15 14:01:04 +02:00
6509df523f GODT-1360: added extra check.
This should help diagnostic test failure next time it happens.
2021-10-08 09:01:02 +00:00
0d1abaec0d Other: fixed new copy test feature. 2021-10-07 11:31:56 +00:00
4d1ace5de7 Other: Copy from All Mail allowed again.
Creates duplicate.
Added test scenario.
2021-10-07 11:31:56 +00:00
1250621a4d GODT-963 STORE removing junk or adding nojunk should move message to inbox 2021-10-07 11:31:56 +00:00
8d6e55ba54 GODT-965 MOVE command should end with error for All Mail 2021-10-07 11:31:56 +00:00
a4a29cbf82 Other Improved tests in move_without_support 2021-10-07 11:31:56 +00:00
39bccc2747 GODT-968 Messages in All Mail should not be able to mark as deleted
Feature already implemented.
A test already existed, but lacked the final expunge check.
2021-10-07 11:31:56 +00:00
0cf1b38c2b GODT-967 Append external message to All Mail should be APPEND to Archive instead 2021-10-07 11:31:56 +00:00
6b7e706100 GODT-966 Do nothing if message APPEND to All Mail is already in Archive. 2021-10-07 11:31:56 +00:00
bb90ed5446 GODT-966: Fixed comment. 2021-10-07 11:31:56 +00:00
107843d58f GODT-966: return correct UID in response to APPEND to All Mail. 2021-10-07 11:31:56 +00:00
63f089540e GODT-966 Append internal message to AllMail should be no action
GODT-966 Fixed CI issues (WIP)

WIP GODT-966 modified behavior of APPEND.

WIP GODT-966 implemented test for possible order of operations.

WIP GODT-966: code cleanup and refactoring.

WIP GODT-966 fix for linter.

Other: Minor refactoring.
2021-10-07 11:31:56 +00:00
24 changed files with 448 additions and 104 deletions

View File

@ -85,11 +85,17 @@ dependency-updates:
stage: build
only:
- branches
before_script:
- mkdir -p .cache/bin
- export PATH=$(pwd)/.cache/bin:$PATH
- export GOPATH="$CI_PROJECT_DIR/.cache"
script:
- make build
- git diff && git diff-index --quiet HEAD
artifacts:
# Note: The latest artifacts for refs are locked against deletion, and kept regardless of the expiry time.
# Introduced in GitLab 13.0 behind a disabled feature flag, and made the default behavior in GitLab 13.4.
# Note: The latest artifacts for refs are locked against deletion, and kept
# regardless of the expiry time. Introduced in GitLab 13.0 behind a
# disabled feature flag, and made the default behavior in GitLab 13.4.
expire_in: 1 day
tags:
- large

View File

@ -2,6 +2,24 @@
Changelog [format](http://keepachangelog.com/en/1.0.0/)
## [Bridge 1.8.11] James
### Fixed
* GODT-1415: Only messages which are in Spam should be moved to INBOX once they are marked as not-a-spam.
* GODT-1405: Integration test fix: Prevent unilateral update in FETCH when copying message by append.
* GODT-1392: Fix broken header fields for attachments.
* GODT-1360: Fix live integration test.
* GODT-968: Messages in All Mail should not be able to mark as deleted.
* GODT-967: Append external message to All Mail should be APPEND to Archive instead.
* GODT-966: Append internal message to AllMail should be no action.
* GODT-965: MOVE command should end with error for All Mail.
* GODT-963: STORE removing junk or adding nojunk should move message to inbox.
### Changed
* GODT-1397: Update bbolt to v1.3.6.
* GODT-1410: Remove event ID from sentry report description.
* GODT-1395: CI should fail on go.sum changed.
## [Bridge 1.8.10] James
### Fixed
@ -13,6 +31,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
* GODT-1205: "RCPT TO" does not contain all addressed from "CC".
* GODT-1103: Cleanup on windows when uninstalling Bridge.
## [Bridge 1.8.9] James
### Fixed

View File

@ -10,7 +10,7 @@ TARGET_OS?=${GOOS}
.PHONY: build build-ie build-nogui build-ie-nogui build-launcher build-launcher-ie versioner hasher
# Keep version hardcoded so app build works also without Git repository.
BRIDGE_APP_VERSION?=1.8.10+git
BRIDGE_APP_VERSION?=1.8.11+git
IE_APP_VERSION?=1.3.3+git
APP_VERSION:=${BRIDGE_APP_VERSION}
SRC_ICO:=logo.ico

2
go.mod
View File

@ -64,7 +64,7 @@ require (
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d // indirect
github.com/urfave/cli/v2 v2.2.0
github.com/vmihailenco/msgpack/v5 v5.1.3
go.etcd.io/bbolt v1.3.5
go.etcd.io/bbolt v1.3.6
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec

6
go.sum
View File

@ -433,8 +433,8 @@ github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FB
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@ -533,6 +533,7 @@ golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c=
@ -567,6 +568,7 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -20,6 +20,7 @@ package imap
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"net/mail"
"strings"
@ -116,11 +117,10 @@ func (im *imapMailbox) createMessage(imapFlags []string, date time.Time, r imap.
if internalID != "" {
if msg, err := im.storeMailbox.GetMessage(internalID); err == nil {
if im.user.user.IsCombinedAddressMode() || im.storeAddress.AddressID() == msg.Message().AddressID {
return im.labelExistingMessage(msg.ID(), msg.IsMarkedDeleted())
return im.labelExistingMessage(msg)
}
}
}
return im.importMessage(kr, hdr, body, imapFlags, date)
}
@ -146,7 +146,17 @@ func (im *imapMailbox) createDraftMessage(kr *crypto.KeyRing, email string, body
return uidplus.AppendResponse(im.storeMailbox.UIDValidity(), im.storeMailbox.GetUIDList([]string{draft.ID}))
}
func (im *imapMailbox) labelExistingMessage(messageID string, isDeleted bool) error {
func findMailboxForAddress(address storeAddressProvider, labelID string) (storeMailboxProvider, error) {
for _, mailBox := range address.ListMailboxes() {
if mailBox.LabelID() == labelID {
return mailBox, nil
}
}
return nil, fmt.Errorf("could not find %v label in mailbox for user %v", labelID,
address.AddressString())
}
func (im *imapMailbox) labelExistingMessage(msg storeMessageProvider) error { //nolint[funlen]
im.log.Info("Labelling existing message")
// IMAP clients can move message to local folder (setting \Deleted flag)
@ -156,20 +166,37 @@ func (im *imapMailbox) labelExistingMessage(messageID string, isDeleted bool) er
// not delete the message (EXPUNGE would delete the original message and
// the new duplicate one would stay). API detects duplicates; therefore
// we need to remove \Deleted flag if IMAP client re-imports.
if isDeleted {
if err := im.storeMailbox.MarkMessagesUndeleted([]string{messageID}); err != nil {
if msg.IsMarkedDeleted() {
if err := im.storeMailbox.MarkMessagesUndeleted([]string{msg.ID()}); err != nil {
log.WithError(err).Error("Failed to undelete re-imported message")
}
}
if err := im.storeMailbox.LabelMessages([]string{messageID}); err != nil {
// Outlook Uses APPEND instead of COPY. There is no need to copy to All Mail because messages are already there.
// If the message is copied from Spam or Trash, it must be moved otherwise we will have data loss.
// If the message is moved from any folder, the moment when expunge happens on source we will move message trash unless we move it to archive.
// If the message is already in Archive we should not call API at all.
// Otherwise the message is already in All mail, Return OK.
var storeMBox = im.storeMailbox
if pmapi.AllMailLabel == storeMBox.LabelID() {
if msg.Message().HasLabelID(pmapi.ArchiveLabel) {
return uidplus.AppendResponse(storeMBox.UIDValidity(), storeMBox.GetUIDList([]string{msg.ID()}))
}
var err error
storeMBox, err = findMailboxForAddress(im.storeAddress, pmapi.ArchiveLabel)
if err != nil {
return err
}
}
if err := storeMBox.LabelMessages([]string{msg.ID()}); err != nil {
return err
}
return uidplus.AppendResponse(im.storeMailbox.UIDValidity(), im.storeMailbox.GetUIDList([]string{messageID}))
return uidplus.AppendResponse(im.storeMailbox.UIDValidity(), im.storeMailbox.GetUIDList([]string{msg.ID()}))
}
func (im *imapMailbox) importMessage(kr *crypto.KeyRing, hdr textproto.Header, body []byte, imapFlags []string, date time.Time) error {
func (im *imapMailbox) importMessage(kr *crypto.KeyRing, hdr textproto.Header, body []byte, imapFlags []string, date time.Time) error { //nolint[funlen]
im.log.Info("Importing external message")
var (
@ -211,18 +238,29 @@ func (im *imapMailbox) importMessage(kr *crypto.KeyRing, hdr textproto.Header, b
return err
}
messageID, err := im.storeMailbox.ImportMessage(enc, seen, labelIDs, flags, time)
var targetMailbox = im.storeMailbox
if targetMailbox.LabelID() == pmapi.AllMailLabel {
// Importing mail in directly into All Mail is not allowed. Instead we redirect the import to Archive
// The mail will automatically appear in All mail. The appends response still reports that the mail was
// successfully APPEND to All Mail.
targetMailbox, err = findMailboxForAddress(im.storeAddress, pmapi.ArchiveLabel)
if err != nil {
return err
}
}
messageID, err := targetMailbox.ImportMessage(enc, seen, labelIDs, flags, time)
if err != nil {
return err
}
msg, err := im.storeMailbox.GetMessage(messageID)
msg, err := targetMailbox.GetMessage(messageID)
if err != nil {
return err
}
if msg.IsMarkedDeleted() {
if err := im.storeMailbox.MarkMessagesUndeleted([]string{messageID}); err != nil {
if err := targetMailbox.MarkMessagesUndeleted([]string{messageID}); err != nil {
log.WithError(err).Error("Failed to undelete re-imported message")
}
}

View File

@ -137,8 +137,14 @@ func (im *imapMailbox) setFlags(messageIDs, flags []string) error { //nolint
return nil
}
func (im *imapMailbox) addOrRemoveFlags(operation imap.FlagsOp, messageIDs, flags []string) error {
func (im *imapMailbox) addOrRemoveFlags(operation imap.FlagsOp, messageIDs, flags []string) error { //nolint[funlen]
for _, f := range flags {
// Adding flag 'nojunk' is equivalent to removing flag 'junk'
if (operation == imap.AddFlags) && (f == "nojunk") {
operation = imap.RemoveFlags
f = "junk"
}
switch f {
case imap.SeenFlag:
switch operation { //nolint[exhaustive] imap.SetFlags is processed by im.setFlags
@ -175,24 +181,38 @@ func (im *imapMailbox) addOrRemoveFlags(operation imap.FlagsOp, messageIDs, flag
}
case imap.AnsweredFlag, imap.DraftFlag, imap.RecentFlag:
// Not supported.
case message.AppleMailJunkFlag, message.ThunderbirdJunkFlag:
storeMailbox, err := im.storeAddress.GetMailbox("Spam")
case strings.ToLower(message.AppleMailJunkFlag), strings.ToLower(message.ThunderbirdJunkFlag):
spamMailbox, err := im.storeAddress.GetMailbox("Spam")
if err != nil {
return err
}
// Handle custom junk flags for Apple Mail and Thunderbird.
switch operation { //nolint[exhaustive] imap.SetFlag is processed by im.setFlags
// No label removal is necessary because Spam and Inbox are both exclusive labels so the backend
// will automatically take care of label removal.
case imap.AddFlags:
if err := storeMailbox.LabelMessages(messageIDs); err != nil {
if err := spamMailbox.LabelMessages(messageIDs); err != nil {
return err
}
case imap.RemoveFlags:
if err := storeMailbox.UnlabelMessages(messageIDs); err != nil {
// During spam flag removal only messages which
// are in Spam folder should be moved to Inbox.
// For other messages it is NOOP.
messagesInSpam := []string{}
for _, mID := range messageIDs {
if uid := spamMailbox.GetUIDList([]string{mID}); len(*uid) != 0 {
messagesInSpam = append(messagesInSpam, mID)
}
}
if len(messagesInSpam) != 0 {
inboxMailbox, err := im.storeAddress.GetMailbox("INBOX")
if err != nil {
return err
}
if err := inboxMailbox.LabelMessages(messagesInSpam); err != nil {
return err
}
}
}
}
}
@ -230,6 +250,10 @@ func (im *imapMailbox) moveMessages(uid bool, seqSet *imap.SeqSet, targetLabel s
// Called from go-imap in goroutines - we need to handle panics for each function.
defer im.panicHandler.HandlePanic()
// Moving from All Mail is not allowed.
if im.storeMailbox.LabelID() == pmapi.AllMailLabel {
return errors.New("move from All Mail is not allowed")
}
return im.labelMessages(uid, seqSet, targetLabel, true)
}

View File

@ -61,9 +61,17 @@ func NewReporter(appName, appVersion string, userAgent fmt.Stringer) *Reporter {
}
func (r *Reporter) ReportException(i interface{}) error {
return r.ReportExceptionWithContext(i, make(map[string]interface{}))
}
func (r *Reporter) ReportMessage(msg string) error {
return r.ReportMessageWithContext(msg, make(map[string]interface{}))
}
func (r *Reporter) ReportExceptionWithContext(i interface{}, context map[string]interface{}) error {
err := fmt.Errorf("recover: %v", i)
return r.scopedReport(func() {
return r.scopedReport(context, func() {
if eventID := sentry.CaptureException(err); eventID != nil {
logrus.WithError(err).
WithField("reportID", *eventID).
@ -72,8 +80,8 @@ func (r *Reporter) ReportException(i interface{}) error {
})
}
func (r *Reporter) ReportMessage(msg string) error {
return r.scopedReport(func() {
func (r *Reporter) ReportMessageWithContext(msg string, context map[string]interface{}) error {
return r.scopedReport(context, func() {
if eventID := sentry.CaptureMessage(msg); eventID != nil {
logrus.WithField("message", msg).
WithField("reportID", *eventID).
@ -83,7 +91,7 @@ func (r *Reporter) ReportMessage(msg string) error {
}
// Report reports a sentry crash with stacktrace from all goroutines.
func (r *Reporter) scopedReport(doReport func()) error {
func (r *Reporter) scopedReport(context map[string]interface{}, doReport func()) error {
SkipDuringUnwind()
if os.Getenv("PROTONMAIL_ENV") == "dev" {
@ -101,6 +109,7 @@ func (r *Reporter) scopedReport(doReport func()) error {
sentry.WithScope(func(scope *sentry.Scope) {
SkipDuringUnwind()
scope.SetTags(tags)
scope.SetContexts(context)
doReport()
})

View File

@ -247,7 +247,12 @@ func (loop *eventLoop) processNextEvent() (more bool, err error) { // nolint[fun
l.WithError(err).WithField("errors", loop.errCounter).Error("Error skipped")
loop.errCounter++
if loop.errCounter == errMaxSentry {
if sentryErr := loop.store.sentryReporter.ReportMessage("Warning: event loop issues: " + err.Error() + ", " + loop.currentEventID); sentryErr != nil {
context := map[string]interface{}{
"EventLoop": map[string]interface{}{
"EventID": loop.currentEventID,
},
}
if sentryErr := loop.store.sentryReporter.ReportMessageWithContext("Warning: event loop issues: "+err.Error(), context); sentryErr != nil {
l.WithError(sentryErr).Error("Failed to report error to sentry")
}
}
@ -302,7 +307,12 @@ func (loop *eventLoop) processEvent(event *pmapi.Event) (err error) {
eventLog.Info("Processing refresh event")
loop.store.triggerSync()
if sentryErr := loop.store.sentryReporter.ReportMessage("Warning: refresh occurred, " + loop.currentEventID); sentryErr != nil {
context := map[string]interface{}{
"EventLoop": map[string]interface{}{
"EventID": loop.currentEventID,
},
}
if sentryErr := loop.store.sentryReporter.ReportMessageWithContext("Warning: refresh occurred", context); sentryErr != nil {
loop.log.WithError(sentryErr).Error("Failed to report refresh to sentry")
}

View File

@ -24,8 +24,7 @@ import (
"testing"
"github.com/ProtonMail/gopenpgp/v2/crypto"
a "github.com/stretchr/testify/assert"
r "github.com/stretchr/testify/require"
"github.com/stretchr/testify/require"
)
const testMessageCleartext = `<div>jeej saas<br></div><div><br></div><div class="protonmail_signature_block"><div>Sent from <a href="https://protonmail.ch">ProtonMail</a>, encrypted email based in Switzerland.<br></div><div><br></div></div>`
@ -127,70 +126,78 @@ ClW54lp9eeOfYTsdTSbn9VaSO0E6m2/Q4Tk=
-----END PGP PUBLIC KEY BLOCK-----`
func TestMessage_IsBodyEncrypted(t *testing.T) {
r := require.New(t)
msg := &Message{Body: testMessageEncrypted}
r.True(t, msg.IsBodyEncrypted(), "the body should be encrypted")
r.True(msg.IsBodyEncrypted(), "the body should be encrypted")
msg.Body = testMessageCleartext
r.True(t, !msg.IsBodyEncrypted(), "the body should not be encrypted")
r.True(!msg.IsBodyEncrypted(), "the body should not be encrypted")
}
func TestMessage_Decrypt(t *testing.T) {
r := require.New(t)
msg := &Message{Body: testMessageEncrypted}
dec, err := msg.Decrypt(testPrivateKeyRing)
r.NoError(t, err)
r.Equal(t, testMessageCleartext, string(dec))
r.NoError(err)
r.Equal(testMessageCleartext, string(dec))
}
func TestMessage_Decrypt_Legacy(t *testing.T) {
r := require.New(t)
testPrivateKeyLegacy := readTestFile("testPrivateKeyLegacy", false)
key, err := crypto.NewKeyFromArmored(testPrivateKeyLegacy)
r.NoError(t, err)
r.NoError(err)
unlockedKey, err := key.Unlock([]byte(testMailboxPasswordLegacy))
r.NoError(t, err)
r.NoError(err)
testPrivateKeyRingLegacy, err := crypto.NewKeyRing(unlockedKey)
r.NoError(t, err)
r.NoError(err)
msg := &Message{Body: testMessageEncryptedLegacy}
dec, err := msg.Decrypt(testPrivateKeyRingLegacy)
r.NoError(t, err)
r.NoError(err)
r.Equal(t, testMessageCleartextLegacy, string(dec))
r.Equal(testMessageCleartextLegacy, string(dec))
}
func TestMessage_Decrypt_signed(t *testing.T) {
r := require.New(t)
msg := &Message{Body: testMessageSigned}
dec, err := msg.Decrypt(testPrivateKeyRing)
r.NoError(t, err)
r.Equal(t, testMessageCleartext, string(dec))
r.NoError(err)
r.Equal(testMessageCleartext, string(dec))
}
func TestMessage_Encrypt(t *testing.T) {
r := require.New(t)
key, err := crypto.NewKeyFromArmored(testMessageSigner)
r.NoError(t, err)
r.NoError(err)
signer, err := crypto.NewKeyRing(key)
r.NoError(t, err)
r.NoError(err)
msg := &Message{Body: testMessageCleartext}
r.NoError(t, msg.Encrypt(testPrivateKeyRing, testPrivateKeyRing))
r.NoError(msg.Encrypt(testPrivateKeyRing, testPrivateKeyRing))
dec, err := msg.Decrypt(testPrivateKeyRing)
r.NoError(t, err)
r.NoError(err)
r.Equal(t, testMessageCleartext, string(dec))
r.Equal(t, testIdentity, signer.GetIdentities()[0])
r.Equal(testMessageCleartext, string(dec))
r.Equal(testIdentity, signer.GetIdentities()[0])
}
func routeLabelMessages(tb testing.TB, w http.ResponseWriter, req *http.Request) string {
r.NoError(tb, checkMethodAndPath(req, "PUT", "/mail/v4/messages/label"))
require.NoError(tb, checkMethodAndPath(req, "PUT", "/mail/v4/messages/label"))
return "messages/label/put_response.json"
}
func TestMessage_LabelMessages_NoPaging(t *testing.T) {
r := require.New(t)
// This should be only enough IDs to produce one page.
testIDs := []string{}
for i := 0; i < messageIDPageSize-1; i++ {
@ -203,10 +210,12 @@ func TestMessage_LabelMessages_NoPaging(t *testing.T) {
)
defer finish()
a.NoError(t, c.LabelMessages(context.Background(), testIDs, "mylabel"))
r.NoError(c.LabelMessages(context.Background(), testIDs, "mylabel"))
}
func TestMessage_LabelMessages_Paging(t *testing.T) {
r := require.New(t)
// This should be enough IDs to produce three pages.
testIDs := []string{}
for i := 0; i < 3*messageIDPageSize; i++ {
@ -221,5 +230,26 @@ func TestMessage_LabelMessages_Paging(t *testing.T) {
)
defer finish()
a.NoError(t, c.LabelMessages(context.Background(), testIDs, "mylabel"))
r.NoError(c.LabelMessages(context.Background(), testIDs, "mylabel"))
}
// TestClient_GetMessage might look like no actual functionality is tested
// here. But there was case when API was responding with bad payload and it was
// useful to have this to quickly test it.
func TestClient_GetMessage(t *testing.T) {
r := require.New(t)
testID := "AeUizgtA3H44qRgcr-HdBApwLiUhlQg5kB81mg_QalWotmQJIHep9OScWIo7Wu9pnYxM4RqQxJnr3BE4kh4y_Q=="
finish, c := newTestClientCallbacks(t,
func(tb testing.TB, w http.ResponseWriter, req *http.Request) string {
r.NoError(checkMethodAndPath(req, "GET", "/mail/v4/messages/"+testID))
return "/messages/get_response.json"
},
)
defer finish()
msg, err := c.GetMessage(context.Background(), testID)
r.NoError(err)
r.Equal(testID, msg.ID)
}

View File

@ -42,9 +42,11 @@
"MIMEType": "text/plain",
"KeyPackets": "wcBMA0fcZ7XLgmf2AQgAiRsOlnm1kSB4/lr7tYe6pBsRGn10GqwUhrwU5PMKOHdCgnO12jO3y3CzP0Yl/jGhAYja9wLDqH8X0sk3tY32u4Sb1Qe5IuzggAiCa4dwOJj5gEFMTHMzjIMPHR7A70XqUxMhmILye8V4KRm/j4c1sxbzA1rM3lYBumQuB5l/ck0Kgt4ZqxHVXHK5Q1l65FHhSXRj8qnunasHa30TYNzP8nmBA8BinnJxpiQ7FGc2umnUhgkFtjm5ixu9vyjr9ukwDTbwAXXfmY+o7tK7kqIXJcmTL6k2UeC6Mz1AagQtRCRtU+bv/3zGojq/trZo9lom3naIeQYa36Ketmcpj2Qwjg==",
"Headers": {
"content-description": "You'll never believe what's in this text file"
"content-description": "attachment",
"x-pm-incorporated":"1",
"x-pm-notes": ["You'll never believe", "what's in this text file"]
},
"MessageID": "h3CD-DT7rLoAw1vmpcajvIPAl-wwDfXR2MHtWID3wuQURDBKTiGUAwd6E2WBbS44QQKeXImW-axm6X0hAfcVCA=="
"MessageID": "AeUizgtA3H44qRgcr-HdBApwLiUhlQg5kB81mg_QalWotmQJIHep9OScWIo7Wu9pnYxM4RqQxJnr3BE4kh4y_Q=="
}
],
"LabelIDs": [

View File

@ -1,7 +1,7 @@
.PHONY: check-go check-godog install-godog test test-bridge test-ie test-live test-live-bridge test-live-ie test-stage test-debug test-live-debug bench
export GO111MODULE=on
export BRIDGE_VERSION:=1.8.10+integrationtests
export BRIDGE_VERSION:=1.8.11+integrationtests
export VERBOSITY?=fatal
export TEST_DATA=testdata
export TEST_APP?=bridge

View File

@ -30,7 +30,16 @@ func (api *FakePMAPI) isLabelFolder(labelID string) bool {
return bool(label.Exclusive)
}
}
return labelID == pmapi.InboxLabel || labelID == pmapi.ArchiveLabel || labelID == pmapi.SentLabel
switch labelID {
case pmapi.InboxLabel,
pmapi.TrashLabel,
pmapi.SpamLabel,
pmapi.ArchiveLabel,
pmapi.SentLabel,
pmapi.DraftLabel:
return true
}
return false
}
func (api *FakePMAPI) ListLabels(context.Context) ([]*pmapi.Label, error) {

View File

@ -48,6 +48,26 @@ Feature: IMAP copy messages
| from | to | subject | body | read | deleted |
| john.doe@mail.com | user@pm.me | foo | hello | true | false |
Scenario: Copy message from All mail moves from the original location
Given there is IMAP client selected in "All Mail"
When IMAP client copies message seq "1" to "Folders/mbox"
Then IMAP response is "OK"
And mailbox "INBOX" for "user" has 2 messages
And mailbox "INBOX" for "user" has messages
| from | to | subject | body | read | deleted |
| jane.doe@mail.com | name@pm.me | bar | world | false | true |
| john.doe@mail.com | user@pm.me | response | hello | true | false |
And mailbox "All Mail" for "user" has 3 messages
And mailbox "All Mail" for "user" has messages
| from | to | subject | body | read | deleted |
| john.doe@mail.com | user@pm.me | foo | hello | true | false |
| jane.doe@mail.com | name@pm.me | bar | world | false | false |
| john.doe@mail.com | user@pm.me | response | hello | true | false |
And mailbox "Folders/mbox" for "user" has 1 messages
And mailbox "Folders/mbox" for "user" has messages
| from | to | subject | body | read | deleted |
| john.doe@mail.com | user@pm.me | foo | hello | true | false |
Scenario: Copy all messages to folder does move
Given there is IMAP client selected in "INBOX"
When IMAP client copies message seq "1:*" to "Folders/mbox"

View File

@ -65,6 +65,7 @@ Feature: IMAP create messages
| from | to | subject | body |
| [primary] | chosen@one.com | Meet the Twins | Hello, Mr. Anderson |
And there is IMAP client selected in "Sent"
Then mailbox "Sent" for "userMoreAddresses" has 1 messages
When IMAP client creates message "Meet the Twins" from address "primary" of "userMoreAddresses" to "chosen@one.com" with body "Hello, Mr. Anderson" in "Sent"
Then IMAP response is "OK"
And mailbox "Sent" for "userMoreAddresses" has 2 messages

View File

@ -96,12 +96,22 @@ Feature: IMAP remove messages from mailbox
| LOGOUT | 9 |
| UNSELECT | 10 |
Scenario: Not possible to delete from All Mail
Given there are 1 messages in mailbox "INBOX" for "user"
Scenario: Not possible to delete from All Mail and expunge does nothing
Given there are messages in mailbox "INBOX" for "user"
| id | from | to | subject | body |
| 1 | john.doe@mail.com | user@pm.me | subj1 | body1 |
And there is IMAP client logged in as "user"
And there is IMAP client selected in "All Mail"
When IMAP client marks message seq "1" as deleted
Then IMAP response is "IMAP error: NO operation not allowed for 'All Mail' folder"
And mailbox "All Mail" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | subj1 |
When IMAP client sends expunge
Then IMAP response is "OK"
And mailbox "All Mail" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | subj1 |
Scenario: Expunge specific message only
Given there are 5 messages in mailbox "INBOX" for "user"

View File

@ -198,3 +198,23 @@ Feature: IMAP import messages
"""
Then IMAP response is "OK \[APPENDUID \d 1\] APPEND completed"
Scenario: Import message to All Mail
When IMAP client imports message to "All Mail"
"""
From: Foo <from1@pm.me>
To: Bridge Test <to1@pm.me>
Subject: subj1
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
body1
"""
Then IMAP response is "OK \[APPENDUID \d 1\] APPEND completed"
Then mailbox "Archive" for "user" has messages
| from | to | subject | body
| from1@pm.me | to1@pm.me | subj1 | body1
And API mailbox "Archive" for "user" has 1 message
And mailbox "All Mail" for "user" has messages
| from | to | subject | body
| from1@pm.me | to1@pm.me | subj1 | body1
And API mailbox "All Mail" for "user" has 1 message

View File

@ -80,16 +80,12 @@ Feature: IMAP move messages
Scenario: Move message from All Mail is not possible
Given there is IMAP client selected in "All Mail"
When IMAP client moves message seq "1" to "Folders/folder"
Then IMAP response is "OK"
Then IMAP response is "NO move from All Mail is not allowed"
And mailbox "All Mail" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | foo |
| jane.doe@mail.com | name@pm.me | bar |
And mailbox "Folders/folder" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | baz |
And API endpoint "PUT /mail/v4/messages/label" is called
And API endpoint "PUT /mail/v4/messages/unlabel" is not called
And mailbox "Folders/folder" for "user" has 0 messages
Scenario: Move message from Inbox to Sent is not possible
Given there is IMAP client selected in "INBOX"

View File

@ -5,52 +5,73 @@ Feature: IMAP move messages by append and delete (without MOVE support, e.g., Ou
And there is IMAP client "source" logged in as "user"
And there is IMAP client "target" logged in as "user"
Scenario Outline: Move message from INBOX to mailbox by append and delete
Given there are messages in mailbox "INBOX" for "user"
Scenario Outline: Move message from <srcMailbox> to <dstMailbox> by <order>
Given there are messages in mailbox "<srcMailbox>" for "user"
| id | from | to | subject | body |
| 1 | john.doe@mail.com | user@pm.me | foo | hello |
| 2 | jane.doe@mail.com | name@pm.me | bar | world |
And there is IMAP client "source" selected in "INBOX"
And there is IMAP client "target" selected in "<mailbox>"
When IMAP clients "source" and "target" move message seq "2" of "user" from "INBOX" to "<mailbox>" by append and delete
| 1 | sndr1@pm.me | rcvr1@pm.me | subj1 | body1 |
| 2 | sndr2@pm.me | rcvr2@pm.me | subj2 | body2 |
And there is IMAP client "source" selected in "<srcMailbox>"
And there is IMAP client "target" selected in "<dstMailbox>"
When IMAP clients "source" and "target" move message seq "2" of "user" to "<dstMailbox>" by <order>
Then IMAP response to "source" is "OK"
Then IMAP response to "target" is "OK"
When IMAP client "source" sends expunge
Then IMAP response to "source" is "OK"
And mailbox "INBOX" for "user" has messages
And mailbox "<dstMailbox>" for "user" has 1 messages
And mailbox "<dstMailbox>" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | foo |
And mailbox "<mailbox>" for "user" has messages
| sndr2@pm.me | rcvr2@pm.me | subj2 |
And mailbox "<srcMailbox>" for "user" has 1 messages
And mailbox "<srcMailbox>" for "user" has messages
| from | to | subject |
| jane.doe@mail.com | name@pm.me | bar |
| sndr1@pm.me | rcvr1@pm.me | subj1 |
Examples:
| mailbox |
| Archive |
| Folders/mbox |
| Spam |
| Trash |
| srcMailbox | dstMailbox | order |
| Trash | INBOX | APPEND DELETE EXPUNGE |
| Spam | INBOX | APPEND DELETE EXPUNGE |
| INBOX | Archive | APPEND DELETE EXPUNGE |
| INBOX | Folders/mbox | APPEND DELETE EXPUNGE |
| INBOX | Spam | APPEND DELETE EXPUNGE |
| INBOX | Trash | APPEND DELETE EXPUNGE |
| Trash | INBOX | DELETE APPEND EXPUNGE |
| Spam | INBOX | DELETE APPEND EXPUNGE |
| INBOX | Archive | DELETE APPEND EXPUNGE |
| INBOX | Folders/mbox | DELETE APPEND EXPUNGE |
| INBOX | Spam | DELETE APPEND EXPUNGE |
| INBOX | Trash | DELETE APPEND EXPUNGE |
| Trash | INBOX | DELETE EXPUNGE APPEND |
| Spam | INBOX | DELETE EXPUNGE APPEND |
| INBOX | Archive | DELETE EXPUNGE APPEND |
| INBOX | Folders/mbox | DELETE EXPUNGE APPEND |
| INBOX | Spam | DELETE EXPUNGE APPEND |
| INBOX | Trash | DELETE EXPUNGE APPEND |
Scenario Outline: Move message from Trash/Spam to INBOX by append and delete
Scenario Outline: Move message from <mailbox> to All Mail by <order>
Given there are messages in mailbox "<mailbox>" for "user"
| id | from | to | subject | body |
| 1 | john.doe@mail.com | user@pm.me | foo | hello |
| 2 | jane.doe@mail.com | name@pm.me | bar | world |
| 1 | john.doe@mail.com | user@pm.me | subj1 | body1 |
| 2 | john.doe@mail.com | name@pm.me | subj2 | body2 |
And there is IMAP client "source" selected in "<mailbox>"
And there is IMAP client "target" selected in "INBOX"
When IMAP clients "source" and "target" move message seq "2" of "user" from "<mailbox>" to "INBOX" by append and delete
And there is IMAP client "target" selected in "All Mail"
When IMAP clients "source" and "target" move message seq "2" of "user" to "All Mail" by <order>
Then IMAP response to "source" is "OK"
Then IMAP response to "target" is "OK"
When IMAP client "source" sends expunge
Then IMAP response to "source" is "OK"
And mailbox "INBOX" for "user" has messages
| from | to | subject |
| jane.doe@mail.com | name@pm.me | bar |
And mailbox "<mailbox>" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | foo |
| john.doe@mail.com | user@pm.me | subj1 |
And mailbox "All Mail" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | subj1 |
| john.doe@mail.com | name@pm.me | subj2 |
Examples:
| mailbox |
| Spam |
| Trash |
| mailbox | order |
| INBOX | APPEND DELETE EXPUNGE |
| Archive | APPEND DELETE EXPUNGE |
| Trash | APPEND DELETE EXPUNGE |
| Spam | APPEND DELETE EXPUNGE |
| INBOX | DELETE APPEND EXPUNGE |
| Archive | DELETE APPEND EXPUNGE |
| Trash | DELETE APPEND EXPUNGE |
| Spam | DELETE APPEND EXPUNGE |
| INBOX | DELETE EXPUNGE APPEND |
| Archive | DELETE EXPUNGE APPEND |
| Trash | DELETE EXPUNGE APPEND |
| Spam | DELETE EXPUNGE APPEND |

View File

@ -19,3 +19,44 @@ Feature: IMAP update messages in Spam folder
| from | to | subject |
| john.doe@mail.com | user@pm.me | foo |
| jane.doe@mail.com | name@pm.me | bar |
Scenario Outline: Move from Spam to INBOX when client <operation> <flag>
When IMAP client <operation> flags "<flag>" <suffix> message seq "1"
Then IMAP response is "OK"
And mailbox "INBOX" for "user" has 1 messages
And mailbox "INBOX" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | foo |
And mailbox "Spam" for "user" has 1 messages
And mailbox "Spam" for "user" has messages
| from | to | subject |
| jane.doe@mail.com | name@pm.me | bar |
Examples:
| operation | suffix | flag |
| adds | to | nojunk |
| adds | to | NoJunk |
| removes | from | junk |
| removes | from | Junk |
| removes | from | $Junk |
Scenario Outline: Do not move from Archive to INBOX when client <operation> <flag>
Given there are messages in mailbox "Archive" for "user"
| id | from | to | subject | body | read | starred | deleted |
| 1 | john.doe@mail.com | user@pm.me | Archived | hello | false | false | false |
And there is IMAP client selected in "Archive"
When IMAP client <operation> flags "<flag>" <suffix> message seq "1"
Then IMAP response is "OK"
And mailbox "INBOX" for "user" has 0 messages
And mailbox "Archive" for "user" has 1 messages
And mailbox "Archive" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | Archived |
Examples:
| operation | suffix | flag |
| adds | to | nojunk |
| adds | to | NoJunk |
| removes | from | junk |
| removes | from | Junk |
| removes | from | $Junk |

View File

@ -18,10 +18,14 @@
package tests
import (
"errors"
"fmt"
"strconv"
"strings"
"sync"
"time"
"github.com/ProtonMail/proton-bridge/test/mocks"
"github.com/cucumber/godog"
"golang.org/x/net/html/charset"
)
@ -35,7 +39,8 @@ func IMAPActionsMessagesFeatureContext(s *godog.ScenarioContext) {
s.Step(`^IMAP client searches for "([^"]*)"$`, imapClientSearchesFor)
s.Step(`^IMAP client copies message seq "([^"]*)" to "([^"]*)"$`, imapClientCopiesMessagesTo)
s.Step(`^IMAP client moves message seq "([^"]*)" to "([^"]*)"$`, imapClientMovesMessagesTo)
s.Step(`^IMAP clients "([^"]*)" and "([^"]*)" move message seq "([^"]*)" of "([^"]*)" from "([^"]*)" to "([^"]*)" by append and delete$`, imapClientsMoveMessageSeqOfUserFromToByAppendAndDelete)
s.Step(`^IMAP clients "([^"]*)" and "([^"]*)" move message seq "([^"]*)" of "([^"]*)" from "([^"]*)" to "([^"]*)"$`, imapClientsMoveMessageSeqOfUserFromTo)
s.Step(`^IMAP clients "([^"]*)" and "([^"]*)" move message seq "([^"]*)" of "([^"]*)" to "([^"]*)" by ([^"]*) ([^"]*) ([^"]*)`, imapClientsMoveMessageSeqOfUserFromToByOrederedOperations)
s.Step(`^IMAP client imports message to "([^"]*)"$`, imapClientCreatesMessage)
s.Step(`^IMAP client imports message to "([^"]*)" with encoding "([^"]*)"$`, imapClientCreatesMessageWithEncoding)
s.Step(`^IMAP client creates message "([^"]*)" from "([^"]*)" to "([^"]*)" with body "([^"]*)" in "([^"]*)"$`, imapClientCreatesMessageFromToWithBody)
@ -43,6 +48,10 @@ func IMAPActionsMessagesFeatureContext(s *godog.ScenarioContext) {
s.Step(`^IMAP client creates message "([^"]*)" from address "([^"]*)" of "([^"]*)" to "([^"]*)" with body "([^"]*)" in "([^"]*)"$`, imapClientCreatesMessageFromAddressOfUserToWithBody)
s.Step(`^IMAP client marks message seq "([^"]*)" with "([^"]*)"$`, imapClientMarksMessageSeqWithFlags)
s.Step(`^IMAP client "([^"]*)" marks message seq "([^"]*)" with "([^"]*)"$`, imapClientNamedMarksMessageSeqWithFlags)
s.Step(`^IMAP client adds flags "([^"]*)" to message seq "([^"]*)"$`, imapClientAddsFlagsToMessageSeq)
s.Step(`^IMAP client "([^"]*)" adds flags "([^"]*)" to message seq "([^"]*)"$`, imapClientNamedAddsFlagsToMessageSeq)
s.Step(`^IMAP client removes flags "([^"]*)" from message seq "([^"]*)"$`, imapClientRemovesFlagsFromMessageSeq)
s.Step(`^IMAP client "([^"]*)" removes flags "([^"]*)" from message seq "([^"]*)"$`, imapClientNamedRemovesFlagsFromMessageSeq)
s.Step(`^IMAP client marks message seq "([^"]*)" as read$`, imapClientMarksMessageSeqAsRead)
s.Step(`^IMAP client "([^"]*)" marks message seq "([^"]*)" as read$`, imapClientNamedMarksMessageSeqAsRead)
s.Step(`^IMAP client marks message seq "([^"]*)" as unread$`, imapClientMarksMessageSeqAsUnread)
@ -113,7 +122,7 @@ func imapClientMovesMessagesTo(messageSeq, newMailboxName string) error {
return nil
}
func imapClientsMoveMessageSeqOfUserFromToByAppendAndDelete(sourceIMAPClient, targetIMAPClient, messageSeq, bddUserID, sourceMailboxName, targetMailboxName string) error {
func imapClientsMoveMessageSeqOfUserFromTo(sourceIMAPClient, targetIMAPClient, messageSeq, bddUserID, sourceMailboxName, targetMailboxName string) error {
account := ctx.GetTestAccount(bddUserID)
if account == nil {
return godog.ErrPending
@ -160,6 +169,49 @@ func imapClientsMoveMessageSeqOfUserFromToByAppendAndDelete(sourceIMAPClient, ta
return nil
}
func extractMessageBodyFromImapResponse(response *mocks.IMAPResponse) (string, error) {
sections := response.Sections()
if len(sections) != 1 {
return "", internalError(errors.New("unexpected result from FETCH"), "retrieving message body using FETCH")
}
sections = strings.Split(sections[0], "\n")
if len(sections) < 2 {
return "", internalError(errors.New("failed to parse FETCH result"), "extraction body from FETCH reply")
}
return strings.Join(sections[1:], "\n"), nil
}
func imapClientsMoveMessageSeqOfUserFromToByOrederedOperations(sourceIMAPClient, targetIMAPClient, messageSeq, bddUserID, targetMailboxName, op1, op2, op3 string) error {
account := ctx.GetTestAccount(bddUserID)
if account == nil {
return godog.ErrPending
}
// call NOOP to prevent unilateral updates in following FETCH
ctx.GetIMAPClient(sourceIMAPClient).Noop().AssertOK()
msgStr, err := extractMessageBodyFromImapResponse(ctx.GetIMAPClient(sourceIMAPClient).Fetch(messageSeq, "BODY.PEEK[]").AssertOK())
if err != nil {
return err
}
for _, op := range []string{op1, op2, op3} {
switch op {
case "APPEND":
res := ctx.GetIMAPClient(targetIMAPClient).Append(targetMailboxName, msgStr)
ctx.SetIMAPLastResponse(targetIMAPClient, res)
case "DELETE":
_ = imapClientNamedMarksMessageSeqAsDeleted(sourceIMAPClient, messageSeq)
case "EXPUNGE":
_ = imapClientNamedExpunge(sourceIMAPClient)
default:
return errors.New("unknow IMAP operation " + op)
}
time.Sleep(100 * time.Millisecond)
}
return nil
}
func imapClientCreatesMessage(mailboxName string, message *godog.DocString) error {
return imapClientCreatesMessageWithEncoding(mailboxName, "utf8", message)
}
@ -222,6 +274,26 @@ func imapClientNamedMarksMessageSeqWithFlags(imapClient, messageSeq, flags strin
return nil
}
func imapClientAddsFlagsToMessageSeq(flags, messageSeq string) error {
return imapClientNamedAddsFlagsToMessageSeq("imap", flags, messageSeq)
}
func imapClientNamedAddsFlagsToMessageSeq(imapClient, flags, messageSeq string) error {
res := ctx.GetIMAPClient(imapClient).AddFlags(messageSeq, flags)
ctx.SetIMAPLastResponse(imapClient, res)
return nil
}
func imapClientRemovesFlagsFromMessageSeq(flags, messageSeq string) error {
return imapClientNamedRemovesFlagsFromMessageSeq("imap", flags, messageSeq)
}
func imapClientNamedRemovesFlagsFromMessageSeq(imapClient, flags, messageSeq string) error {
res := ctx.GetIMAPClient(imapClient).RemoveFlags(messageSeq, flags)
ctx.SetIMAPLastResponse(imapClient, res)
return nil
}
func imapClientMarksMessageSeqAsRead(messageSeq string) error {
return imapClientNamedMarksMessageSeqAsRead("imap", messageSeq)
}

View File

@ -253,6 +253,10 @@ func (c *IMAPClient) ExpungeUID(ids string) *IMAPResponse {
return c.SendCommand(fmt.Sprintf("UID EXPUNGE %s", ids))
}
func (c *IMAPClient) Noop() *IMAPResponse {
return c.SendCommand("NOOP")
}
// Extennsions
// Extennsions: IDLE

View File

@ -107,6 +107,11 @@ func (ir *IMAPResponse) AssertOK() *IMAPResponse {
return ir
}
func (ir *IMAPResponse) Sections() []string {
ir.wait()
return ir.sections
}
func (ir *IMAPResponse) AssertResult(wantResult string) *IMAPResponse {
ir.wait()
a.NoError(ir.t, ir.err)

View File

@ -101,6 +101,11 @@ func thereAreMessagesInMailboxesForAddressOfUser(mailboxNames, bddAddressID, bdd
if message.HasLabelID(pmapi.SentLabel) {
message.Flags |= pmapi.FlagSent
} else {
// some tests (Outlook move by DELETE EXPUNGE APPEND) imply creating hard copies of emails,
// and the importMessage() function flags the email as Sent if the 'Received' key in not present in the
// header.
header.Add("Received", "from dummy.protonmail.com")
}
bddMessageID := ""