Skip to content

Commit

Permalink
feat(GODT-2201): Add mailbox related IMAP commands
Browse files Browse the repository at this point in the history
This patch implements the following IMAP commands:

* Create
* Delete
* Examine
* LSUB
* Rename
* Select
* Subscribe
* Unsubscribe
  • Loading branch information
LBeernaertProton committed Feb 14, 2023
1 parent c9c7806 commit 3c0f769
Show file tree
Hide file tree
Showing 17 changed files with 560 additions and 14 deletions.
36 changes: 36 additions & 0 deletions imap/command/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package command

import (
"fmt"
"github.com/ProtonMail/gluon/imap/parser"
)

type CreateCommand struct {
Mailbox string
}

func (l CreateCommand) String() string {
return fmt.Sprintf("CREATE '%v'", l.Mailbox)
}

func (l CreateCommand) SanitizedString() string {
return fmt.Sprintf("CREATE '%v'", sanitizeString(l.Mailbox))
}

type CreateCommandParser struct{}

func (CreateCommandParser) FromParser(p *parser.Parser) (Payload, error) {
// create = "CREATE" SP mailbox
if err := p.Consume(parser.TokenTypeSP, "expected space after command"); err != nil {
return nil, err
}

mailbox, err := p.ParseMailbox()
if err != nil {
return nil, err
}

return &CreateCommand{
Mailbox: mailbox,
}, nil
}
24 changes: 24 additions & 0 deletions imap/command/create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package command

import (
"bytes"
"github.com/ProtonMail/gluon/imap/parser"
"github.com/stretchr/testify/require"
"testing"
)

func TestParser_CreateCommand(t *testing.T) {
input := toIMAPLine(`tag CREATE INBOX`)
s := parser.NewScanner(bytes.NewReader(input))
p := NewParser(s)

expected := Command{Tag: "tag", Payload: &CreateCommand{
Mailbox: "INBOX",
}}

cmd, err := p.Parse()
require.NoError(t, err)
require.Equal(t, expected, cmd)
require.Equal(t, "create", p.LastParsedCommand())
require.Equal(t, "tag", p.LastParsedTag())
}
36 changes: 36 additions & 0 deletions imap/command/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package command

import (
"fmt"
"github.com/ProtonMail/gluon/imap/parser"
)

type DeleteCommand struct {
Mailbox string
}

func (l DeleteCommand) String() string {
return fmt.Sprintf("DELETE '%v'", l.Mailbox)
}

func (l DeleteCommand) SanitizedString() string {
return fmt.Sprintf("DELETE '%v'", sanitizeString(l.Mailbox))
}

type DeleteCommandParser struct{}

func (DeleteCommandParser) FromParser(p *parser.Parser) (Payload, error) {
// delete = "DELETE" SP mailbox
if err := p.Consume(parser.TokenTypeSP, "expected space after command"); err != nil {
return nil, err
}

mailbox, err := p.ParseMailbox()
if err != nil {
return nil, err
}

return &DeleteCommand{
Mailbox: mailbox,
}, nil
}
24 changes: 24 additions & 0 deletions imap/command/delete_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package command

import (
"bytes"
"github.com/ProtonMail/gluon/imap/parser"
"github.com/stretchr/testify/require"
"testing"
)

func TestParser_DeleteCommand(t *testing.T) {
input := toIMAPLine(`tag DELETE INBOX`)
s := parser.NewScanner(bytes.NewReader(input))
p := NewParser(s)

expected := Command{Tag: "tag", Payload: &DeleteCommand{
Mailbox: "INBOX",
}}

cmd, err := p.Parse()
require.NoError(t, err)
require.Equal(t, expected, cmd)
require.Equal(t, "delete", p.LastParsedCommand())
require.Equal(t, "tag", p.LastParsedTag())
}
36 changes: 36 additions & 0 deletions imap/command/examine.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package command

import (
"fmt"
"github.com/ProtonMail/gluon/imap/parser"
)

type ExamineCommand struct {
Mailbox string
}

func (l ExamineCommand) String() string {
return fmt.Sprintf("EXAMINE '%v'", l.Mailbox)
}

func (l ExamineCommand) SanitizedString() string {
return fmt.Sprintf("EXAMINE '%v'", sanitizeString(l.Mailbox))
}

type ExamineCommandParser struct{}

func (ExamineCommandParser) FromParser(p *parser.Parser) (Payload, error) {
// examine = "EXAMINE" SP mailbox
if err := p.Consume(parser.TokenTypeSP, "expected space after command"); err != nil {
return nil, err
}

mailbox, err := p.ParseMailbox()
if err != nil {
return nil, err
}

return &ExamineCommand{
Mailbox: mailbox,
}, nil
}
24 changes: 24 additions & 0 deletions imap/command/examine_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package command

import (
"bytes"
"github.com/ProtonMail/gluon/imap/parser"
"github.com/stretchr/testify/require"
"testing"
)

func TestParser_ExamineCommand(t *testing.T) {
input := toIMAPLine(`tag EXAMINE INBOX`)
s := parser.NewScanner(bytes.NewReader(input))
p := NewParser(s)

expected := Command{Tag: "tag", Payload: &ExamineCommand{
Mailbox: "INBOX",
}}

cmd, err := p.Parse()
require.NoError(t, err)
require.Equal(t, expected, cmd)
require.Equal(t, "examine", p.LastParsedCommand())
require.Equal(t, "tag", p.LastParsedTag())
}
47 changes: 47 additions & 0 deletions imap/command/lsub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package command

import (
"fmt"
"github.com/ProtonMail/gluon/imap/parser"
)

type LSubCommand struct {
Mailbox string
LSubMailbox string
}

func (l LSubCommand) String() string {
return fmt.Sprintf("LSUB '%v' '%v'", l.Mailbox, l.LSubMailbox)
}

func (l LSubCommand) SanitizedString() string {
return l.String()
}

type LSubCommandParser struct{}

func (LSubCommandParser) FromParser(p *parser.Parser) (Payload, error) {
// lsub = "LSUB" SP mailbox SP list-mailbox
if err := p.Consume(parser.TokenTypeSP, "expected space after command"); err != nil {
return nil, err
}

mailbox, err := p.ParseMailbox()
if err != nil {
return nil, err
}

if err := p.Consume(parser.TokenTypeSP, "expected space after mailbox"); err != nil {
return nil, err
}

listMailbox, err := parseListMailbox(p)
if err != nil {
return nil, err
}

return &LSubCommand{
Mailbox: mailbox,
LSubMailbox: listMailbox,
}, nil
}
59 changes: 59 additions & 0 deletions imap/command/lsub_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package command

import (
"bytes"
"github.com/ProtonMail/gluon/imap/parser"
"github.com/stretchr/testify/require"
"testing"
)

func TestParser_LSubCommand(t *testing.T) {
input := toIMAPLine(`tag LSUB "" "*"`)
s := parser.NewScanner(bytes.NewReader(input))
p := NewParser(s)

expected := Command{Tag: "tag", Payload: &LSubCommand{
Mailbox: "",
LSubMailbox: "*",
}}

cmd, err := p.Parse()
require.NoError(t, err)
require.Equal(t, expected, cmd)
require.Equal(t, "lsub", p.LastParsedCommand())
require.Equal(t, "tag", p.LastParsedTag())
}

func TestParser_LSubCommandSpecialAsterisk(t *testing.T) {
input := toIMAPLine(`tag LSUB "foo" *`)
s := parser.NewScanner(bytes.NewReader(input))
p := NewParser(s)

expected := Command{Tag: "tag", Payload: &LSubCommand{
Mailbox: "foo",
LSubMailbox: "*",
}}

cmd, err := p.Parse()
require.NoError(t, err)
require.Equal(t, expected, cmd)
require.Equal(t, "lsub", p.LastParsedCommand())
require.Equal(t, "tag", p.LastParsedTag())
}

func TestParser_LSubCommandSpecialPercentage(t *testing.T) {
input := toIMAPLine(`tag LSUB "bar" %`)
s := parser.NewScanner(bytes.NewReader(input))
p := NewParser(s)

expected := Command{Tag: "tag", Payload: &LSubCommand{
Mailbox: "bar",
LSubMailbox: "%",
}}

cmd, err := p.Parse()
require.NoError(t, err)
require.Equal(t, expected, cmd)
require.Equal(t, "lsub", p.LastParsedCommand())
require.Equal(t, "tag", p.LastParsedTag())
}
36 changes: 22 additions & 14 deletions imap/command/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,28 @@ func NewParserWithLiteralContinuationCb(s *parser.Scanner, cb func() error) *Par
return &Parser{
parser: parser.NewParserWithLiteralContinuationCb(s, cb),
commands: map[string]Builder{
"list": &ListCommandParser{},
"append": &AppendCommandParser{},
"search": &SearchCommandParser{},
"fetch": &FetchCommandParser{},
"capability": &CapabilityCommandParser{},
"idle": &IdleCommandParser{},
"noop": &NoopCommandParser{},
"logout": &LogoutCommandParser{},
"check": &CheckCommandParser{},
"close": &CloseCommandParser{},
"expunge": &ExpungeCommandParser{},
"unselect": &UnselectCommandParser{},
"starttls": &StartTLSCommandParser{},
"status": &StatusCommandParser{},
"list": &ListCommandParser{},
"append": &AppendCommandParser{},
"search": &SearchCommandParser{},
"fetch": &FetchCommandParser{},
"capability": &CapabilityCommandParser{},
"idle": &IdleCommandParser{},
"noop": &NoopCommandParser{},
"logout": &LogoutCommandParser{},
"check": &CheckCommandParser{},
"close": &CloseCommandParser{},
"expunge": &ExpungeCommandParser{},
"unselect": &UnselectCommandParser{},
"starttls": &StartTLSCommandParser{},
"status": &StatusCommandParser{},
"select": &SelectCommandParser{},
"examine": &ExamineCommandParser{},
"create": &CreateCommandParser{},
"delete": &DeleteCommandParser{},
"subscribe": &SubscribeCommandParser{},
"unsubscribe": &UnsubscribeCommandParser{},
"rename": &RenameCommandParser{},
"lsub": &LSubCommandParser{},
},
}
}
Expand Down
47 changes: 47 additions & 0 deletions imap/command/rename.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package command

import (
"fmt"
"github.com/ProtonMail/gluon/imap/parser"
)

type RenameCommand struct {
From string
To string
}

func (l RenameCommand) String() string {
return fmt.Sprintf("RENAME '%v' '%v'", l.From, l.To)
}

func (l RenameCommand) SanitizedString() string {
return fmt.Sprintf("RENAME '%v' '%v'", sanitizeString(l.From), sanitizeString(l.To))
}

type RenameCommandParser struct{}

func (RenameCommandParser) FromParser(p *parser.Parser) (Payload, error) {
// rename = "RENAME" SP mailbox SP mailbox
if err := p.Consume(parser.TokenTypeSP, "expected space after command"); err != nil {
return nil, err
}

mailboxFrom, err := p.ParseMailbox()
if err != nil {
return nil, err
}

if err := p.Consume(parser.TokenTypeSP, "expected space after mailbox"); err != nil {
return nil, err
}

mailboxTo, err := p.ParseMailbox()
if err != nil {
return nil, err
}

return &RenameCommand{
From: mailboxFrom,
To: mailboxTo,
}, nil
}
Loading

0 comments on commit 3c0f769

Please sign in to comment.