Compare commits

...

6 Commits

10 changed files with 83 additions and 40 deletions

View File

@ -4,12 +4,33 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
## Unreleased ## Unreleased
### Fixed
* GODT-829 Remove `NoInferior` to display sub-folders in apple mail.
## [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 ## [Bridge 1.4.2] Forth
### Changed ### Changed
* GODT-761 Use label.Path instead of Name to partially support subfolders for webapp beta release. * 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. * 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
@ -17,12 +38,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

@ -80,7 +80,10 @@ func (im *imapMailbox) Info() (*imap.MailboxInfo, error) {
} }
func (im *imapMailbox) getFlags() []string { func (im *imapMailbox) getFlags() []string {
flags := []string{imap.NoInferiorsAttr} // Subfolders are not yet supported by API. flags := []string{}
if !im.storeMailbox.IsFolder() || im.storeMailbox.IsSystem() {
flags = append(flags, imap.NoInferiorsAttr) // Subfolders are not supported for System or Label
}
switch im.storeMailbox.LabelID() { switch im.storeMailbox.LabelID() {
case pmapi.SentLabel: case pmapi.SentLabel:
flags = append(flags, specialuse.Sent) flags = append(flags, specialuse.Sent)

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

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

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

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

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

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