mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 04:36:43 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d9855a190 | |||
| cea33bebe2 | |||
| 9d405a1549 | |||
| 47f468e4b7 | |||
| b9c6c00709 | |||
| 5ce9cb8eec | |||
| bc7133e401 | |||
| a219ecf3cb | |||
| 8061b1e6fa | |||
| 6509df523f | |||
| 0d1abaec0d | |||
| 4d1ace5de7 | |||
| 1250621a4d | |||
| 8d6e55ba54 | |||
| a4a29cbf82 | |||
| 39bccc2747 | |||
| 0cf1b38c2b | |||
| 6b7e706100 | |||
| bb90ed5446 | |||
| 107843d58f | |||
| 63f089540e |
@ -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
|
||||
|
||||
19
Changelog.md
19
Changelog.md
@ -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
|
||||
|
||||
2
Makefile
2
Makefile
@ -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
2
go.mod
@ -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
6
go.sum
@ -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=
|
||||
|
||||
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,23 +181,37 @@ 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 {
|
||||
return err
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
})
|
||||
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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": [
|
||||
@ -52,4 +54,4 @@
|
||||
"0"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
| 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
|
||||
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 | 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
|
||||
| from | to | subject |
|
||||
| john.doe@mail.com | user@pm.me | foo |
|
||||
And mailbox "<mailbox>" for "user" has messages
|
||||
| from | to | subject |
|
||||
| jane.doe@mail.com | name@pm.me | bar |
|
||||
|
||||
And mailbox "<dstMailbox>" for "user" has 1 messages
|
||||
And mailbox "<dstMailbox>" for "user" has messages
|
||||
| from | to | subject |
|
||||
| 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 |
|
||||
| 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 |
|
||||
|
||||
@ -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 |
|
||||
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 := ""
|
||||
|
||||
Reference in New Issue
Block a user