Files
proton-bridge/internal/services/imapservice/helpers.go
Leander Beernaert a187747c7c feat(GODT-2802): IMAP Serivce
Handles all IMAP related tasks. Unlike the previous iteration, this new
service automatically adds and removes users from Gluon by interfacing
with server manager.
2023-07-31 11:06:47 +02:00

192 lines
4.1 KiB
Go

// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package imapservice
import (
"context"
"fmt"
"net/mail"
"time"
"github.com/ProtonMail/gluon/imap"
"github.com/ProtonMail/gluon/rfc5322"
"github.com/ProtonMail/gluon/rfc822"
"github.com/ProtonMail/go-proton-api"
"github.com/bradenaw/juniper/xslices"
)
func toIMAPMailbox(label proton.Label, flags, permFlags, attrs imap.FlagSet) imap.Mailbox {
if label.Type == proton.LabelTypeLabel {
label.Path = append([]string{labelPrefix}, label.Path...)
} else if label.Type == proton.LabelTypeFolder {
label.Path = append([]string{folderPrefix}, label.Path...)
}
return imap.Mailbox{
ID: imap.MailboxID(label.ID),
Name: label.Path,
Flags: flags,
PermanentFlags: permFlags,
Attributes: attrs,
}
}
func isAllMailOrScheduled(mailboxID imap.MailboxID) bool {
return (mailboxID == proton.AllMailLabel) || (mailboxID == proton.AllScheduledLabel)
}
func BuildFlagSetFromMessageMetadata(message proton.MessageMetadata) imap.FlagSet {
flags := imap.NewFlagSet()
if message.Seen() {
flags.AddToSelf(imap.FlagSeen)
}
if message.Starred() {
flags.AddToSelf(imap.FlagFlagged)
}
if message.IsDraft() {
flags.AddToSelf(imap.FlagDraft)
}
if message.IsRepliedAll == true || message.IsReplied == true { //nolint: gosimple
flags.AddToSelf(imap.FlagAnswered)
}
return flags
}
func getLiteralToList(literal []byte) ([]string, error) {
headerLiteral, _ := rfc822.Split(literal)
header, err := rfc822.NewHeader(headerLiteral)
if err != nil {
return nil, err
}
var result []string
parseAddress := func(field string) error {
if fieldAddr, ok := header.GetChecked(field); ok {
addr, err := rfc5322.ParseAddressList(fieldAddr)
if err != nil {
return fmt.Errorf("failed to parse addresses for '%v': %w", field, err)
}
result = append(result, xslices.Map(addr, func(addr *mail.Address) string {
return addr.Address
})...)
return nil
}
return nil
}
if err := parseAddress("To"); err != nil {
return nil, err
}
if err := parseAddress("Cc"); err != nil {
return nil, err
}
if err := parseAddress("Bcc"); err != nil {
return nil, err
}
return result, nil
}
func toIMAPMessage(message proton.MessageMetadata) imap.Message {
flags := BuildFlagSetFromMessageMetadata(message)
var date time.Time
if message.Time > 0 {
date = time.Unix(message.Time, 0)
} else {
date = time.Now()
}
return imap.Message{
ID: imap.MessageID(message.ID),
Flags: flags,
Date: date,
}
}
func WantLabel(label proton.Label) bool {
if label.Type != proton.LabelTypeSystem {
return true
}
// nolint:exhaustive
switch label.ID {
case proton.InboxLabel:
return true
case proton.TrashLabel:
return true
case proton.SpamLabel:
return true
case proton.AllMailLabel:
return true
case proton.ArchiveLabel:
return true
case proton.SentLabel:
return true
case proton.DraftsLabel:
return true
case proton.StarredLabel:
return true
case proton.AllScheduledLabel:
return true
default:
return false
}
}
func wantLabels(apiLabels map[string]proton.Label, labelIDs []string) []string {
return xslices.Filter(labelIDs, func(labelID string) bool {
apiLabel, ok := apiLabels[labelID]
if !ok {
return false
}
return WantLabel(apiLabel)
})
}
// sleepCtx sleeps for the given duration, or until the context is canceled.
func sleepCtx(ctx context.Context, d time.Duration) {
select {
case <-ctx.Done():
case <-time.After(d):
}
}