diff --git a/Changelog.md b/Changelog.md index b343c05d..cda6f4ad 100644 --- a/Changelog.md +++ b/Changelog.md @@ -11,6 +11,30 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/) * Bump crypto version to v0.0.0-20200818122824-ed5d25e28db8 +## [Bridge 1.4.4] Forth + +### Fixed +* GODT-798 Replace, don't add, transfer encoding when making body 7-bit clean. +* Move/Copy duplicate for emails with References in Outlook +* CSB-247 Cannot update from 1.4.0 + + +## [Bridge 1.4.3] Forth + +### Changed +* Reverted sending IMAP updates to be not blocking again. + +### Fixed +* GODT-783 Settings flags by FLAGS (not using +/-FLAGS) do not change spam state. + + +## [Bridge 1.4.2] Forth + +### Changed +* GODT-761 Use label.Path instead of Name to partially support subfolders for webapp beta release. +* GODT-765 Improve speed of checking whether message is deleted. + + ## [IE 1.1.1] Danube (beta 2020-09-xx) [Bridge 1.4.1] Forth (beta 2020-09-xx) ### Fixed @@ -18,12 +42,14 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/) * GODT-752 Parsing non-utf8 multipart/alternative message. * GODT-752 Parsing message with duplicate charset parameter. + ## [IE 1.1.0] Danube ### Fixed * GODT-703 Import-Export showed always at least one total message. * GODT-738 Fix for mbox files with long lines. + ## [Bridge 1.4.0] Forth ### Added diff --git a/internal/bridge/credits.go b/internal/bridge/credits.go index 15d6e6a2..12f61bc5 100644 --- a/internal/bridge/credits.go +++ b/internal/bridge/credits.go @@ -15,8 +15,8 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -// Code generated by ./credits.sh at Wed Sep 16 16:48:58 CEST 2020. DO NOT EDIT. +// Code generated by ./credits.sh at Tue Sep 29 14:56:25 CEST 2020. DO NOT EDIT. package bridge -const Credits = "github.com/0xAX/notificator;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/go-smtp;github.com/ProtonMail/go-vcard;github.com/ProtonMail/gopenpgp/v2;github.com/PuerkitoBio/goquery;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/certifi/gocertifi;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-specialuse;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-message;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/raven-go;github.com/go-resty/resty/v2;github.com/golang/mock;github.com/google/go-cmp;github.com/google/uuid;github.com/gopherjs/gopherjs;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/kardianos/osext;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/mattn/go-runewidth;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/olekukonko/tablewriter;github.com/pkg/errors;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/ssor/bom;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;" +const Credits = "github.com/0xAX/notificator;github.com/Masterminds/semver/v3;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/go-smtp;github.com/ProtonMail/go-vcard;github.com/ProtonMail/gopenpgp/v2;github.com/ProtonMail/mbox;github.com/PuerkitoBio/goquery;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/certifi/gocertifi;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-specialuse;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-message;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/raven-go;github.com/go-resty/resty/v2;github.com/golang/mock;github.com/google/go-cmp;github.com/google/uuid;github.com/gopherjs/gopherjs;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/kardianos/osext;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/mattn/go-runewidth;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/olekukonko/tablewriter;github.com/pkg/errors;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/ssor/bom;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;" diff --git a/internal/imap/mailbox_message.go b/internal/imap/mailbox_message.go index 5d25a2ae..5ae84d9c 100644 --- a/internal/imap/mailbox_message.go +++ b/internal/imap/mailbox_message.go @@ -24,7 +24,6 @@ import ( "mime/multipart" "net/mail" "net/textproto" - "regexp" "sort" "strings" "time" @@ -141,18 +140,19 @@ func (im *imapMailbox) CreateMessage(flags []string, date time.Time, body imap.L references := m.Header.Get("References") referenceList := strings.Fields(references) - if len(referenceList) > 0 { + // In case there is a mail client which corrupts headers, try + // "References" too. + if internalID == "" && len(referenceList) > 0 { lastReference := referenceList[len(referenceList)-1] - // In case we are using a mail client which corrupts headers, try "References" too. - re := regexp.MustCompile(pmapi.InternalReferenceFormat) - match := re.FindStringSubmatch(lastReference) - if len(match) > 0 { - internalID = match[0] + match := pmapi.RxInternalReferenceFormat.FindStringSubmatch(lastReference) + if len(match) == 2 { + internalID = match[1] } } - // Avoid appending a message which is already on the server. Apply the new - // label instead. This sometimes happens which Outlook (it uses APPEND instead of COPY). + // Avoid appending a message which is already on the server. Apply the + // new label instead. This always happens with Outlook (it uses APPEND + // instead of COPY). if internalID != "" { // Check to see if this belongs to a different address in split mode or another ProtonMail account. msg, err := im.storeMailbox.GetMessage(internalID) diff --git a/internal/imap/mailbox_messages.go b/internal/imap/mailbox_messages.go index f45a7062..95419dfd 100644 --- a/internal/imap/mailbox_messages.go +++ b/internal/imap/mailbox_messages.go @@ -57,6 +57,10 @@ func (im *imapMailbox) UpdateMessagesFlags(uid bool, seqSet *imap.SeqSet, operat return im.addOrRemoveFlags(operation, messageIDs, flags) } +// setFlags is used for FLAGS command (not +FLAGS or -FLAGS), which means +// to set flags passed as an argument and unset the rest. For example, +// if message is not read, is flagged and is not deleted, call FLAGS \Seen +// should flag message as read, unflagged and keep undeleted. func (im *imapMailbox) setFlags(messageIDs, flags []string) error { //nolint seen := false flagged := false @@ -106,16 +110,17 @@ func (im *imapMailbox) setFlags(messageIDs, flags []string) error { //nolint } } - spamMailbox, err := im.storeAddress.GetMailbox("Spam") - if err != nil { - return err - } + // Spam should not be taken into action here as Outlook is using FLAGS + // without preserving junk flag. Probably it's because junk is not standard + // in the rfc3501 and thus Outlook expects calling FLAGS \Seen will not + // change the state of junk or other non-standard flags. + // Still, its safe to label as spam once any client sends the request. if spam { - if err := spamMailbox.LabelMessages(messageIDs); err != nil { + spamMailbox, err := im.storeAddress.GetMailbox("Spam") + if err != nil { return err } - } else { - if err := spamMailbox.UnlabelMessages(messageIDs); err != nil { + if err := spamMailbox.LabelMessages(messageIDs); err != nil { return err } } diff --git a/internal/importexport/credits.go b/internal/importexport/credits.go index a8d79c8d..1b05aead 100644 --- a/internal/importexport/credits.go +++ b/internal/importexport/credits.go @@ -15,8 +15,8 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -// Code generated by ./credits.sh at Wed Sep 23 01:34:10 PM CEST 2020. DO NOT EDIT. +// Code generated by ./credits.sh at Tue Sep 29 14:56:25 CEST 2020. DO NOT EDIT. package importexport -const Credits = "github.com/0xAX/notificator;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/certifi/gocertifi;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-specialuse;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-message;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/raven-go;github.com/golang/mock;github.com/google/go-cmp;github.com/google/uuid;github.com/gopherjs/gopherjs;github.com/go-resty/resty/v2;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/kardianos/osext;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/Masterminds/semver/v3;github.com/mattn/go-runewidth;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/olekukonko/tablewriter;github.com/pkg/errors;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/gopenpgp/v2;github.com/ProtonMail/go-smtp;github.com/ProtonMail/go-vcard;github.com/ProtonMail/mbox;github.com/PuerkitoBio/goquery;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/ssor/bom;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;" +const Credits = "github.com/0xAX/notificator;github.com/Masterminds/semver/v3;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/go-smtp;github.com/ProtonMail/go-vcard;github.com/ProtonMail/gopenpgp/v2;github.com/ProtonMail/mbox;github.com/PuerkitoBio/goquery;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/certifi/gocertifi;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-specialuse;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-message;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/raven-go;github.com/go-resty/resty/v2;github.com/golang/mock;github.com/google/go-cmp;github.com/google/uuid;github.com/gopherjs/gopherjs;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/kardianos/osext;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/mattn/go-runewidth;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/olekukonko/tablewriter;github.com/pkg/errors;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/ssor/bom;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;" diff --git a/internal/smtp/user.go b/internal/smtp/user.go index 079c920f..37957249 100644 --- a/internal/smtp/user.go +++ b/internal/smtp/user.go @@ -25,7 +25,6 @@ import ( "io" "mime" "net/mail" - "regexp" "strings" "time" @@ -408,9 +407,9 @@ func (su *smtpUser) handleReferencesHeader(m *pmapi.Message) (draftID, parentID if !strings.Contains(reference, "@"+pmapi.InternalIDDomain) { newReferences = append(newReferences, reference) } else { // internalid is the parentID. - idMatch := regexp.MustCompile(pmapi.InternalReferenceFormat).FindStringSubmatch(reference) - if len(idMatch) > 0 { - lastID := strings.TrimSuffix(strings.Trim(idMatch[0], "<>"), "@protonmail.internalid") + idMatch := pmapi.RxInternalReferenceFormat.FindStringSubmatch(reference) + if len(idMatch) == 2 { + lastID := idMatch[1] filter := &pmapi.MessagesFilter{ID: []string{lastID}} if su.addressID != "" { filter.AddressID = su.addressID diff --git a/internal/store/address.go b/internal/store/address.go index d890742b..65e1fb07 100644 --- a/internal/store/address.go +++ b/internal/store/address.go @@ -69,7 +69,7 @@ func (storeAddress *Address) init(foldersAndLabels []*pmapi.Label) (err error) { prefix := getLabelPrefix(label) var mailbox *Mailbox - if mailbox, err = txNewMailbox(tx, storeAddress, label.ID, prefix, label.Name, label.Color); err != nil { + if mailbox, err = txNewMailbox(tx, storeAddress, label.ID, prefix, label.Path, label.Color); err != nil { storeAddress.log. WithError(err). WithField("labelID", label.ID). diff --git a/internal/store/address_mailbox.go b/internal/store/address_mailbox.go index dfa4a78e..33efcdf7 100644 --- a/internal/store/address_mailbox.go +++ b/internal/store/address_mailbox.go @@ -73,14 +73,14 @@ func (storeAddress *Address) createOrUpdateMailboxEvent(label *pmapi.Label) erro prefix := getLabelPrefix(label) mailbox, ok := storeAddress.mailboxes[label.ID] if !ok { - mailbox, err := newMailbox(storeAddress, label.ID, prefix, label.Name, label.Color) + mailbox, err := newMailbox(storeAddress, label.ID, prefix, label.Path, label.Color) if err != nil { return err } storeAddress.mailboxes[label.ID] = mailbox mailbox.store.imapMailboxCreated(storeAddress.address, mailbox.labelName) } else { - mailbox.labelName = prefix + label.Name + mailbox.labelName = prefix + label.Path mailbox.color = label.Color } return nil diff --git a/internal/store/change.go b/internal/store/change.go index 96c1c061..39946b3b 100644 --- a/internal/store/change.go +++ b/internal/store/change.go @@ -122,22 +122,10 @@ func (store *Store) imapSendUpdate(update imapBackend.Update) { return } - done := update.Done() - go func() { - // This timeout is to not keep running many blocked goroutines. - // In case nothing listens to this channel, this thread should stop. - select { - case store.imapUpdates <- update: - case <-time.After(1 * time.Second): - store.log.Warn("IMAP update could not be sent (timeout).") - } - }() - - // This timeout is to not block IMAP backend by wait for IMAP client. select { - case <-done: case <-time.After(1 * time.Second): - store.log.Warn("IMAP update could not be delivered (timeout).") + store.log.Warn("IMAP update could not be sent (timeout)") return + case store.imapUpdates <- update: } } diff --git a/internal/store/mailbox.go b/internal/store/mailbox.go index e711093c..89857af3 100644 --- a/internal/store/mailbox.go +++ b/internal/store/mailbox.go @@ -142,6 +142,9 @@ func initMailboxBucket(tx *bolt.Tx, bucketName []byte) error { if _, err := bucket.CreateBucketIfNotExists(apiIDsBucket); err != nil { return err } + if _, err := bucket.CreateBucketIfNotExists(deletedIDsBucket); err != nil { + return err + } return nil } @@ -240,13 +243,7 @@ func (storeMailbox *Mailbox) txGetAPIIDsBucket(tx *bolt.Tx) *bolt.Bucket { // txGetDeletedIDsBucket returns the bucket with messagesID marked as deleted func (storeMailbox *Mailbox) txGetDeletedIDsBucket(tx *bolt.Tx) *bolt.Bucket { - // There should be no error since it _...returns an error if the bucket - // name is blank, or if the bucket name is too long._ - bucket, err := storeMailbox.txGetBucket(tx).CreateBucketIfNotExists(deletedIDsBucket) - if err != nil || bucket == nil { - storeMailbox.log.WithError(err).Error("Cannot create or get bucket with deleted IDs.") - } - return bucket + return storeMailbox.txGetBucket(tx).Bucket(deletedIDsBucket) } // txGetBucket returns the bucket of mailbox containing mapping buckets. diff --git a/internal/store/mailbox_counts.go b/internal/store/mailbox_counts.go index 3674a1d8..4fd3a636 100644 --- a/internal/store/mailbox_counts.go +++ b/internal/store/mailbox_counts.go @@ -125,6 +125,7 @@ func (mc *mailboxCounts) getPMLabel() *pmapi.Label { return &pmapi.Label{ ID: mc.LabelID, Name: mc.LabelName, + Path: mc.LabelName, Color: mc.Color, Order: mc.Order, Type: pmapi.LabelTypeMailbox, @@ -158,7 +159,7 @@ func (store *Store) createOrUpdateMailboxCountsBuckets(labels []*pmapi.Label) er } // Update mailbox info, but dont change on-API-counts. - mailbox.LabelName = label.Name + mailbox.LabelName = label.Path mailbox.Color = label.Color mailbox.Order = label.Order mailbox.IsFolder = label.Exclusive == 1 diff --git a/internal/store/message.go b/internal/store/message.go index b88698c1..38b230a6 100644 --- a/internal/store/message.go +++ b/internal/store/message.go @@ -66,7 +66,7 @@ func (message *Message) Message() *pmapi.Message { // mailbox func (message *Message) IsMarkedDeleted() bool { isMarkedAsDeleted := false - err := message.storeMailbox.db().Update(func(tx *bolt.Tx) error { + err := message.storeMailbox.db().View(func(tx *bolt.Tx) error { isMarkedAsDeleted = message.storeMailbox.txGetDeletedIDsBucket(tx).Get([]byte(message.msg.ID)) != nil return nil }) diff --git a/internal/updates/updates.go b/internal/updates/updates.go index 538c9d84..cdd11fe6 100644 --- a/internal/updates/updates.go +++ b/internal/updates/updates.go @@ -310,7 +310,9 @@ func (u *Updates) StartUpgrade(currentStatus chan<- Progress) { // nolint[funlen status.UpdateDescription(InfoUpgrading) switch runtime.GOOS { case "windows": //nolint[goconst] - installerFile := strings.Split(u.winInstallerFile, "/")[1] + // Cannot use filepath.Base on windows it has different delimiter + split := strings.Split(u.winInstallerFile, "/") + installerFile := split[len(split)-1] cmd := exec.Command("./" + installerFile) // nolint[gosec] cmd.Dir = u.updateTempDir status.Err = cmd.Start() diff --git a/pkg/message/parser/writer.go b/pkg/message/parser/writer.go index e1e4e3d5..b4cbaefc 100644 --- a/pkg/message/parser/writer.go +++ b/pkg/message/parser/writer.go @@ -35,7 +35,7 @@ func newWriter(root *Part) *Writer { func (w *Writer) Write(ww io.Writer) error { if !w.root.is7BitClean() { - w.root.Header.Add("Content-Transfer-Encoding", "base64") + w.root.Header.Set("Content-Transfer-Encoding", "base64") } msgWriter, err := message.CreateWriter(ww, w.root.Header) @@ -68,7 +68,7 @@ func (w *Writer) write(writer *message.Writer, p *Part) error { func (w *Writer) writeAsChild(writer *message.Writer, p *Part) error { if !p.is7BitClean() { - p.Header.Add("Content-Transfer-Encoding", "base64") + p.Header.Set("Content-Transfer-Encoding", "base64") } childWriter, err := writer.CreatePart(p.Header) diff --git a/pkg/pmapi/labels.go b/pkg/pmapi/labels.go index cc970740..9e0c7808 100644 --- a/pkg/pmapi/labels.go +++ b/pkg/pmapi/labels.go @@ -80,6 +80,7 @@ const ( type Label struct { ID string Name string + Path string Color string Order int `json:",omitempty"` Display int // Not used for now, leave it empty. diff --git a/pkg/pmapi/messages.go b/pkg/pmapi/messages.go index ed5d38b5..623bd07b 100644 --- a/pkg/pmapi/messages.go +++ b/pkg/pmapi/messages.go @@ -29,6 +29,7 @@ import ( "net/http" "net/mail" "net/url" + "regexp" "strconv" "strings" @@ -149,8 +150,9 @@ const ConversationIDDomain = `protonmail.conversationid` // InternalIDDomain is used as a placeholder for reference/message ID headers to improve compatibility with various clients. const InternalIDDomain = `protonmail.internalid` -// InternalReferenceFormat describes format of the message ID (as regex) used for parsing reference headers. -const InternalReferenceFormat = `(?U)<.*@` + InternalIDDomain + `>` +// RxInternalReferenceFormat is compiled regexp which describes the match for +// a message ID used in reference headers. +var RxInternalReferenceFormat = regexp.MustCompile(`(?U)<(.+)@` + regexp.QuoteMeta(InternalIDDomain) + `>`) //nolint[gochecknoglobals] // Message structure. type Message struct { diff --git a/test/fakeapi/controller_control.go b/test/fakeapi/controller_control.go index 0bf2cd21..c8d1db40 100644 --- a/test/fakeapi/controller_control.go +++ b/test/fakeapi/controller_control.go @@ -83,6 +83,9 @@ func (ctl *Controller) AddUserLabel(username string, label *pmapi.Label) error { } label.ID = ctl.labelIDGenerator.next(prefix) label.Name = labelName + if label.Path == "" { + label.Path = label.Name + } ctl.labelsByUsername[username] = append(ctl.labelsByUsername[username], label) ctl.resetUsers() return nil diff --git a/test/fakeapi/labels.go b/test/fakeapi/labels.go index 24be7e5a..8d88c009 100644 --- a/test/fakeapi/labels.go +++ b/test/fakeapi/labels.go @@ -53,6 +53,9 @@ func (api *FakePMAPI) CreateLabel(label *pmapi.Label) (*pmapi.Label, error) { prefix = "folder" } label.ID = api.controller.labelIDGenerator.next(prefix) + if label.Path == "" { + label.Path = label.Name + } api.labels = append(api.labels, label) api.addEventLabel(pmapi.EventCreate, label) return label, nil @@ -67,6 +70,9 @@ func (api *FakePMAPI) UpdateLabel(label *pmapi.Label) (*pmapi.Label, error) { // Request doesn't have to include all properties and these have to stay the same. label.Type = existingLabel.Type label.Exclusive = existingLabel.Exclusive + if label.Path == "" { + label.Path = label.Name + } api.labels[idx] = label api.addEventLabel(pmapi.EventUpdate, label) return label, nil diff --git a/test/features/bridge/imap/mailbox/list.feature b/test/features/bridge/imap/mailbox/list.feature index 0fa0989e..e8848f1f 100644 --- a/test/features/bridge/imap/mailbox/list.feature +++ b/test/features/bridge/imap/mailbox/list.feature @@ -1,11 +1,11 @@ Feature: IMAP list mailboxes Background: Given there is connected user "user" - And there is "user" with mailbox "Folders/mbox1" - And there is "user" with mailbox "Labels/mbox2" - And there is IMAP client logged in as "user" Scenario: List mailboxes + Given there is "user" with mailbox "Folders/mbox1" + And there is "user" with mailbox "Labels/mbox2" + And there is IMAP client logged in as "user" When IMAP client lists mailboxes Then IMAP response contains "INBOX" Then IMAP response contains "Sent" @@ -14,3 +14,16 @@ Feature: IMAP list mailboxes Then IMAP response contains "All Mail" Then IMAP response contains "Folders/mbox1" Then IMAP response contains "Labels/mbox2" + + @ignore-live + Scenario: List mailboxes with subfolders + # Escaped slash in the name contains slash in the name. + # Not-escaped slash in the name means tree structure. + # We keep escaping in an IMAP communication so each mailbox is unique and + # both mailboxes are accessible. The slash is visible in the IMAP client. + Given there is "user" with mailbox "Folders/a\/b" + And there is "user" with mailbox "Folders/a/b" + And there is IMAP client logged in as "user" + When IMAP client lists mailboxes + Then IMAP response contains "Folders/a\/b" + Then IMAP response contains "Folders/a/b" diff --git a/test/features/imap/message/delete_from_trash.feature b/test/features/bridge/imap/message/delete_from_trash.feature similarity index 100% rename from test/features/imap/message/delete_from_trash.feature rename to test/features/bridge/imap/message/delete_from_trash.feature diff --git a/test/features/imap/message/move_without_support.feature b/test/features/bridge/imap/message/move_without_support.feature similarity index 100% rename from test/features/imap/message/move_without_support.feature rename to test/features/bridge/imap/message/move_without_support.feature diff --git a/test/features/bridge/imap/message/update_spam.feature b/test/features/bridge/imap/message/update_spam.feature new file mode 100644 index 00000000..f2c127da --- /dev/null +++ b/test/features/bridge/imap/message/update_spam.feature @@ -0,0 +1,21 @@ +Feature: IMAP update messages in Spam folder + Background: + Given there is connected user "user" + # Messages are inserted in opposite way to keep increasing ID. + # Sequence numbers are then opposite than listed above. + And there are messages in mailbox "Spam" for "user" + | from | to | subject | body | read | starred | deleted | + | john.doe@mail.com | user@pm.me | foo | hello | false | false | false | + | jane.doe@mail.com | name@pm.me | bar | world | true | true | false | + And there is IMAP client logged in as "user" + And there is IMAP client selected in "Spam" + + Scenario: Mark message as read only + When IMAP client marks message "2" with "\Seen" + Then IMAP response is "OK" + And message "1" in "Spam" for "user" is marked as read + And message "1" in "Spam" for "user" is marked as unstarred + And API mailbox "Spam" for "user" has messages + | from | to | subject | + | john.doe@mail.com | user@pm.me | foo | + | jane.doe@mail.com | name@pm.me | bar |