Sync all IMAP folders

This commit is contained in:
DonVoo 2026-07-05 23:03:52 +02:00
parent bb2bcb0576
commit 569b274b2c
4 changed files with 228 additions and 32 deletions

View file

@ -66,7 +66,21 @@ type imapSource struct {
}
func (s *imapSource) Folders() ([]Folder, error) {
return []Folder{{Name: "INBOX", Delim: "/"}}, nil
boxes, err := s.c.listMailboxes()
if err != nil {
return nil, err
}
folders := make([]Folder, 0, len(boxes))
for _, box := range boxes {
if hasIMAPAttr(box.Attrs, `\Noselect`) {
continue
}
folders = append(folders, Folder{Name: box.Name, Attrs: box.Attrs, Delim: box.Delim})
}
if len(folders) == 0 {
folders = append(folders, Folder{Name: "INBOX", Delim: "/"})
}
return folders, nil
}
func (s *imapSource) Fetch(folder string) ([]RawMessage, error) {
@ -198,6 +212,110 @@ func (c *simpleIMAP) fetchAll() ([]RawMessage, error) {
}
}
type imapListMailbox struct {
Name string
Attrs []string
Delim string
}
func (c *simpleIMAP) listMailboxes() ([]imapListMailbox, error) {
tag := c.nextTag()
if _, err := fmt.Fprintf(c.w, "%s LIST \"\" \"*\"\r\n", tag); err != nil {
return nil, err
}
if err := c.w.Flush(); err != nil {
return nil, err
}
var out []imapListMailbox
for {
line, err := c.r.ReadString('\n')
if err != nil {
return nil, err
}
if strings.HasPrefix(line, tag+" ") {
if strings.Contains(line, " OK") {
return out, nil
}
return out, fmt.Errorf("imap list failed: %s", strings.TrimSpace(line))
}
if !strings.HasPrefix(line, "* LIST ") {
continue
}
if box, ok := parseListMailbox(strings.TrimSpace(line)); ok {
out = append(out, box)
}
}
}
func parseListMailbox(line string) (imapListMailbox, bool) {
rest := strings.TrimSpace(strings.TrimPrefix(line, "* LIST "))
if !strings.HasPrefix(rest, "(") {
return imapListMailbox{}, false
}
endAttrs := strings.Index(rest, ")")
if endAttrs < 0 {
return imapListMailbox{}, false
}
attrs := strings.Fields(rest[1:endAttrs])
rest = strings.TrimSpace(rest[endAttrs+1:])
delim, next, ok := parseIMAPAtomOrQuoted(rest)
if !ok {
return imapListMailbox{}, false
}
if strings.EqualFold(delim, "NIL") {
delim = "/"
}
rest = strings.TrimSpace(rest[next:])
name, _, ok := parseIMAPAtomOrQuoted(rest)
if !ok || name == "" {
return imapListMailbox{}, false
}
return imapListMailbox{Name: name, Attrs: attrs, Delim: delim}, true
}
func parseIMAPAtomOrQuoted(s string) (string, int, bool) {
s = strings.TrimLeft(s, " \t")
trimmed := len(s)
if s == "" {
return "", 0, false
}
if s[0] != '"' {
end := strings.IndexAny(s, " \t\r\n")
if end < 0 {
end = len(s)
}
return s[:end], trimmed - len(s) + end, true
}
var b strings.Builder
escaped := false
for i := 1; i < len(s); i++ {
ch := s[i]
if escaped {
b.WriteByte(ch)
escaped = false
continue
}
if ch == '\\' {
escaped = true
continue
}
if ch == '"' {
return b.String(), trimmed - len(s) + i + 1, true
}
b.WriteByte(ch)
}
return "", 0, false
}
func hasIMAPAttr(attrs []string, want string) bool {
for _, attr := range attrs {
if strings.EqualFold(attr, want) {
return true
}
}
return false
}
func (c *simpleIMAP) appendMessage(folder string, m RawMessage) error {
tag := c.nextTag()
flags := ""