Compare commits

...

7 Commits

22 changed files with 122 additions and 58 deletions

View File

@ -4,6 +4,30 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
## Unreleased ## Unreleased
## [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) ## [IE 1.1.1] Danube (beta 2020-09-xx) [Bridge 1.4.1] Forth (beta 2020-09-xx)
### Fixed ### Fixed
@ -11,12 +35,14 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
* GODT-752 Parsing non-utf8 multipart/alternative message. * GODT-752 Parsing non-utf8 multipart/alternative message.
* GODT-752 Parsing message with duplicate charset parameter. * GODT-752 Parsing message with duplicate charset parameter.
## [IE 1.1.0] Danube ## [IE 1.1.0] Danube
### Fixed ### Fixed
* GODT-703 Import-Export showed always at least one total message. * GODT-703 Import-Export showed always at least one total message.
* GODT-738 Fix for mbox files with long lines. * GODT-738 Fix for mbox files with long lines.
## [Bridge 1.4.0] Forth ## [Bridge 1.4.0] Forth
### Added ### Added

View File

@ -15,8 +15,8 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// 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 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;"

View File

@ -24,7 +24,6 @@ import (
"mime/multipart" "mime/multipart"
"net/mail" "net/mail"
"net/textproto" "net/textproto"
"regexp"
"sort" "sort"
"strings" "strings"
"time" "time"
@ -141,18 +140,19 @@ func (im *imapMailbox) CreateMessage(flags []string, date time.Time, body imap.L
references := m.Header.Get("References") references := m.Header.Get("References")
referenceList := strings.Fields(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] lastReference := referenceList[len(referenceList)-1]
// In case we are using a mail client which corrupts headers, try "References" too. match := pmapi.RxInternalReferenceFormat.FindStringSubmatch(lastReference)
re := regexp.MustCompile(pmapi.InternalReferenceFormat) if len(match) == 2 {
match := re.FindStringSubmatch(lastReference) internalID = match[1]
if len(match) > 0 {
internalID = match[0]
} }
} }
// Avoid appending a message which is already on the server. Apply the new // Avoid appending a message which is already on the server. Apply the
// label instead. This sometimes happens which Outlook (it uses APPEND instead of COPY). // new label instead. This always happens with Outlook (it uses APPEND
// instead of COPY).
if internalID != "" { if internalID != "" {
// Check to see if this belongs to a different address in split mode or another ProtonMail account. // Check to see if this belongs to a different address in split mode or another ProtonMail account.
msg, err := im.storeMailbox.GetMessage(internalID) msg, err := im.storeMailbox.GetMessage(internalID)

View File

@ -57,6 +57,10 @@ func (im *imapMailbox) UpdateMessagesFlags(uid bool, seqSet *imap.SeqSet, operat
return im.addOrRemoveFlags(operation, messageIDs, flags) 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 func (im *imapMailbox) setFlags(messageIDs, flags []string) error { //nolint
seen := false seen := false
flagged := false flagged := false
@ -106,16 +110,17 @@ func (im *imapMailbox) setFlags(messageIDs, flags []string) error { //nolint
} }
} }
spamMailbox, err := im.storeAddress.GetMailbox("Spam") // Spam should not be taken into action here as Outlook is using FLAGS
if err != nil { // without preserving junk flag. Probably it's because junk is not standard
return err // 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 spam {
if err := spamMailbox.LabelMessages(messageIDs); err != nil { spamMailbox, err := im.storeAddress.GetMailbox("Spam")
if err != nil {
return err return err
} }
} else { if err := spamMailbox.LabelMessages(messageIDs); err != nil {
if err := spamMailbox.UnlabelMessages(messageIDs); err != nil {
return err return err
} }
} }

View File

@ -15,8 +15,8 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// 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 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;"

View File

@ -25,7 +25,6 @@ import (
"io" "io"
"mime" "mime"
"net/mail" "net/mail"
"regexp"
"strings" "strings"
"time" "time"
@ -408,9 +407,9 @@ func (su *smtpUser) handleReferencesHeader(m *pmapi.Message) (draftID, parentID
if !strings.Contains(reference, "@"+pmapi.InternalIDDomain) { if !strings.Contains(reference, "@"+pmapi.InternalIDDomain) {
newReferences = append(newReferences, reference) newReferences = append(newReferences, reference)
} else { // internalid is the parentID. } else { // internalid is the parentID.
idMatch := regexp.MustCompile(pmapi.InternalReferenceFormat).FindStringSubmatch(reference) idMatch := pmapi.RxInternalReferenceFormat.FindStringSubmatch(reference)
if len(idMatch) > 0 { if len(idMatch) == 2 {
lastID := strings.TrimSuffix(strings.Trim(idMatch[0], "<>"), "@protonmail.internalid") lastID := idMatch[1]
filter := &pmapi.MessagesFilter{ID: []string{lastID}} filter := &pmapi.MessagesFilter{ID: []string{lastID}}
if su.addressID != "" { if su.addressID != "" {
filter.AddressID = su.addressID filter.AddressID = su.addressID

View File

@ -69,7 +69,7 @@ func (storeAddress *Address) init(foldersAndLabels []*pmapi.Label) (err error) {
prefix := getLabelPrefix(label) prefix := getLabelPrefix(label)
var mailbox *Mailbox 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. storeAddress.log.
WithError(err). WithError(err).
WithField("labelID", label.ID). WithField("labelID", label.ID).

View File

@ -73,14 +73,14 @@ func (storeAddress *Address) createOrUpdateMailboxEvent(label *pmapi.Label) erro
prefix := getLabelPrefix(label) prefix := getLabelPrefix(label)
mailbox, ok := storeAddress.mailboxes[label.ID] mailbox, ok := storeAddress.mailboxes[label.ID]
if !ok { 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 { if err != nil {
return err return err
} }
storeAddress.mailboxes[label.ID] = mailbox storeAddress.mailboxes[label.ID] = mailbox
mailbox.store.imapMailboxCreated(storeAddress.address, mailbox.labelName) mailbox.store.imapMailboxCreated(storeAddress.address, mailbox.labelName)
} else { } else {
mailbox.labelName = prefix + label.Name mailbox.labelName = prefix + label.Path
mailbox.color = label.Color mailbox.color = label.Color
} }
return nil return nil

View File

@ -122,22 +122,10 @@ func (store *Store) imapSendUpdate(update imapBackend.Update) {
return 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 { select {
case <-done:
case <-time.After(1 * time.Second): 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 return
case store.imapUpdates <- update:
} }
} }

View File

@ -142,6 +142,9 @@ func initMailboxBucket(tx *bolt.Tx, bucketName []byte) error {
if _, err := bucket.CreateBucketIfNotExists(apiIDsBucket); err != nil { if _, err := bucket.CreateBucketIfNotExists(apiIDsBucket); err != nil {
return err return err
} }
if _, err := bucket.CreateBucketIfNotExists(deletedIDsBucket); err != nil {
return err
}
return nil return nil
} }
@ -240,13 +243,7 @@ func (storeMailbox *Mailbox) txGetAPIIDsBucket(tx *bolt.Tx) *bolt.Bucket {
// txGetDeletedIDsBucket returns the bucket with messagesID marked as deleted // txGetDeletedIDsBucket returns the bucket with messagesID marked as deleted
func (storeMailbox *Mailbox) txGetDeletedIDsBucket(tx *bolt.Tx) *bolt.Bucket { func (storeMailbox *Mailbox) txGetDeletedIDsBucket(tx *bolt.Tx) *bolt.Bucket {
// There should be no error since it _...returns an error if the bucket return storeMailbox.txGetBucket(tx).Bucket(deletedIDsBucket)
// 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
} }
// txGetBucket returns the bucket of mailbox containing mapping buckets. // txGetBucket returns the bucket of mailbox containing mapping buckets.

View File

@ -125,6 +125,7 @@ func (mc *mailboxCounts) getPMLabel() *pmapi.Label {
return &pmapi.Label{ return &pmapi.Label{
ID: mc.LabelID, ID: mc.LabelID,
Name: mc.LabelName, Name: mc.LabelName,
Path: mc.LabelName,
Color: mc.Color, Color: mc.Color,
Order: mc.Order, Order: mc.Order,
Type: pmapi.LabelTypeMailbox, Type: pmapi.LabelTypeMailbox,
@ -158,7 +159,7 @@ func (store *Store) createOrUpdateMailboxCountsBuckets(labels []*pmapi.Label) er
} }
// Update mailbox info, but dont change on-API-counts. // Update mailbox info, but dont change on-API-counts.
mailbox.LabelName = label.Name mailbox.LabelName = label.Path
mailbox.Color = label.Color mailbox.Color = label.Color
mailbox.Order = label.Order mailbox.Order = label.Order
mailbox.IsFolder = label.Exclusive == 1 mailbox.IsFolder = label.Exclusive == 1

View File

@ -66,7 +66,7 @@ func (message *Message) Message() *pmapi.Message {
// mailbox // mailbox
func (message *Message) IsMarkedDeleted() bool { func (message *Message) IsMarkedDeleted() bool {
isMarkedAsDeleted := false 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 isMarkedAsDeleted = message.storeMailbox.txGetDeletedIDsBucket(tx).Get([]byte(message.msg.ID)) != nil
return nil return nil
}) })

View File

@ -310,7 +310,9 @@ func (u *Updates) StartUpgrade(currentStatus chan<- Progress) { // nolint[funlen
status.UpdateDescription(InfoUpgrading) status.UpdateDescription(InfoUpgrading)
switch runtime.GOOS { switch runtime.GOOS {
case "windows": //nolint[goconst] 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 := exec.Command("./" + installerFile) // nolint[gosec]
cmd.Dir = u.updateTempDir cmd.Dir = u.updateTempDir
status.Err = cmd.Start() status.Err = cmd.Start()

View File

@ -35,7 +35,7 @@ func newWriter(root *Part) *Writer {
func (w *Writer) Write(ww io.Writer) error { func (w *Writer) Write(ww io.Writer) error {
if !w.root.is7BitClean() { 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) 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 { func (w *Writer) writeAsChild(writer *message.Writer, p *Part) error {
if !p.is7BitClean() { if !p.is7BitClean() {
p.Header.Add("Content-Transfer-Encoding", "base64") p.Header.Set("Content-Transfer-Encoding", "base64")
} }
childWriter, err := writer.CreatePart(p.Header) childWriter, err := writer.CreatePart(p.Header)

View File

@ -80,6 +80,7 @@ const (
type Label struct { type Label struct {
ID string ID string
Name string Name string
Path string
Color string Color string
Order int `json:",omitempty"` Order int `json:",omitempty"`
Display int // Not used for now, leave it empty. Display int // Not used for now, leave it empty.

View File

@ -29,6 +29,7 @@ import (
"net/http" "net/http"
"net/mail" "net/mail"
"net/url" "net/url"
"regexp"
"strconv" "strconv"
"strings" "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. // InternalIDDomain is used as a placeholder for reference/message ID headers to improve compatibility with various clients.
const InternalIDDomain = `protonmail.internalid` const InternalIDDomain = `protonmail.internalid`
// InternalReferenceFormat describes format of the message ID (as regex) used for parsing reference headers. // RxInternalReferenceFormat is compiled regexp which describes the match for
const InternalReferenceFormat = `(?U)<.*@` + InternalIDDomain + `>` // a message ID used in reference headers.
var RxInternalReferenceFormat = regexp.MustCompile(`(?U)<(.+)@` + regexp.QuoteMeta(InternalIDDomain) + `>`) //nolint[gochecknoglobals]
// Message structure. // Message structure.
type Message struct { type Message struct {

View File

@ -83,6 +83,9 @@ func (ctl *Controller) AddUserLabel(username string, label *pmapi.Label) error {
} }
label.ID = ctl.labelIDGenerator.next(prefix) label.ID = ctl.labelIDGenerator.next(prefix)
label.Name = labelName label.Name = labelName
if label.Path == "" {
label.Path = label.Name
}
ctl.labelsByUsername[username] = append(ctl.labelsByUsername[username], label) ctl.labelsByUsername[username] = append(ctl.labelsByUsername[username], label)
ctl.resetUsers() ctl.resetUsers()
return nil return nil

View File

@ -53,6 +53,9 @@ func (api *FakePMAPI) CreateLabel(label *pmapi.Label) (*pmapi.Label, error) {
prefix = "folder" prefix = "folder"
} }
label.ID = api.controller.labelIDGenerator.next(prefix) label.ID = api.controller.labelIDGenerator.next(prefix)
if label.Path == "" {
label.Path = label.Name
}
api.labels = append(api.labels, label) api.labels = append(api.labels, label)
api.addEventLabel(pmapi.EventCreate, label) api.addEventLabel(pmapi.EventCreate, label)
return label, nil 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. // Request doesn't have to include all properties and these have to stay the same.
label.Type = existingLabel.Type label.Type = existingLabel.Type
label.Exclusive = existingLabel.Exclusive label.Exclusive = existingLabel.Exclusive
if label.Path == "" {
label.Path = label.Name
}
api.labels[idx] = label api.labels[idx] = label
api.addEventLabel(pmapi.EventUpdate, label) api.addEventLabel(pmapi.EventUpdate, label)
return label, nil return label, nil

View File

@ -1,11 +1,11 @@
Feature: IMAP list mailboxes Feature: IMAP list mailboxes
Background: Background:
Given there is connected user "user" 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 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 When IMAP client lists mailboxes
Then IMAP response contains "INBOX" Then IMAP response contains "INBOX"
Then IMAP response contains "Sent" Then IMAP response contains "Sent"
@ -14,3 +14,16 @@ Feature: IMAP list mailboxes
Then IMAP response contains "All Mail" Then IMAP response contains "All Mail"
Then IMAP response contains "Folders/mbox1" Then IMAP response contains "Folders/mbox1"
Then IMAP response contains "Labels/mbox2" 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"

View File

@ -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 |