forked from Silverfish/proton-bridge
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6cbc11a75d | |||
| a21bb130e1 | |||
| 12403785af | |||
| b4892855d4 | |||
| 7ff67f2217 |
20
Changelog.md
20
Changelog.md
@ -4,12 +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
|
## [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 +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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
21
test/features/bridge/imap/message/update_spam.feature
Normal file
21
test/features/bridge/imap/message/update_spam.feature
Normal 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 |
|
||||||
Reference in New Issue
Block a user