diff --git a/imap/command/capability.go b/imap/command/capability.go new file mode 100644 index 00000000..d395421d --- /dev/null +++ b/imap/command/capability.go @@ -0,0 +1,22 @@ +package command + +import ( + "fmt" + "github.com/ProtonMail/gluon/imap/parser" +) + +type CapabilityCommand struct{} + +func (l CapabilityCommand) String() string { + return fmt.Sprintf("CAPABILITY") +} + +func (l CapabilityCommand) SanitizedString() string { + return l.String() +} + +type CapabilityCommandParser struct{} + +func (CapabilityCommandParser) FromParser(p *parser.Parser) (Payload, error) { + return &CapabilityCommand{}, nil +} diff --git a/imap/command/capability_test.go b/imap/command/capability_test.go new file mode 100644 index 00000000..223a4fb3 --- /dev/null +++ b/imap/command/capability_test.go @@ -0,0 +1,22 @@ +package command + +import ( + "bytes" + "github.com/ProtonMail/gluon/imap/parser" + "github.com/stretchr/testify/require" + "testing" +) + +func TestParser_CapabilityCommand(t *testing.T) { + input := toIMAPLine(`tag CAPABILITY`) + s := parser.NewScanner(bytes.NewReader(input)) + p := NewParser(s) + + expected := Command{Tag: "tag", Payload: &CapabilityCommand{}} + + cmd, err := p.Parse() + require.NoError(t, err) + require.Equal(t, expected, cmd) + require.Equal(t, "capability", p.LastParsedCommand()) + require.Equal(t, "tag", p.LastParsedTag()) +} diff --git a/imap/command/check.go b/imap/command/check.go new file mode 100644 index 00000000..585f7e3e --- /dev/null +++ b/imap/command/check.go @@ -0,0 +1,22 @@ +package command + +import ( + "fmt" + "github.com/ProtonMail/gluon/imap/parser" +) + +type CheckCommand struct{} + +func (l CheckCommand) String() string { + return fmt.Sprintf("CHECK") +} + +func (l CheckCommand) SanitizedString() string { + return l.String() +} + +type CheckCommandParser struct{} + +func (CheckCommandParser) FromParser(p *parser.Parser) (Payload, error) { + return &CheckCommand{}, nil +} diff --git a/imap/command/check_test.go b/imap/command/check_test.go new file mode 100644 index 00000000..903be79a --- /dev/null +++ b/imap/command/check_test.go @@ -0,0 +1,22 @@ +package command + +import ( + "bytes" + "github.com/ProtonMail/gluon/imap/parser" + "github.com/stretchr/testify/require" + "testing" +) + +func TestParser_CheckCommand(t *testing.T) { + input := toIMAPLine(`tag CHECK`) + s := parser.NewScanner(bytes.NewReader(input)) + p := NewParser(s) + + expected := Command{Tag: "tag", Payload: &CheckCommand{}} + + cmd, err := p.Parse() + require.NoError(t, err) + require.Equal(t, expected, cmd) + require.Equal(t, "check", p.LastParsedCommand()) + require.Equal(t, "tag", p.LastParsedTag()) +} diff --git a/imap/command/close.go b/imap/command/close.go new file mode 100644 index 00000000..859aeb7f --- /dev/null +++ b/imap/command/close.go @@ -0,0 +1,22 @@ +package command + +import ( + "fmt" + "github.com/ProtonMail/gluon/imap/parser" +) + +type CloseCommand struct{} + +func (l CloseCommand) String() string { + return fmt.Sprintf("CLOSE") +} + +func (l CloseCommand) SanitizedString() string { + return l.String() +} + +type CloseCommandParser struct{} + +func (CloseCommandParser) FromParser(p *parser.Parser) (Payload, error) { + return &CloseCommand{}, nil +} diff --git a/imap/command/close_test.go b/imap/command/close_test.go new file mode 100644 index 00000000..5996941a --- /dev/null +++ b/imap/command/close_test.go @@ -0,0 +1,22 @@ +package command + +import ( + "bytes" + "github.com/ProtonMail/gluon/imap/parser" + "github.com/stretchr/testify/require" + "testing" +) + +func TestParser_CloseCommand(t *testing.T) { + input := toIMAPLine(`tag CLOSE`) + s := parser.NewScanner(bytes.NewReader(input)) + p := NewParser(s) + + expected := Command{Tag: "tag", Payload: &CloseCommand{}} + + cmd, err := p.Parse() + require.NoError(t, err) + require.Equal(t, expected, cmd) + require.Equal(t, "close", p.LastParsedCommand()) + require.Equal(t, "tag", p.LastParsedTag()) +} diff --git a/imap/command/done.go b/imap/command/done.go new file mode 100644 index 00000000..30b62234 --- /dev/null +++ b/imap/command/done.go @@ -0,0 +1,15 @@ +package command + +import ( + "fmt" +) + +type DoneCommand struct{} + +func (l DoneCommand) String() string { + return fmt.Sprintf("DONE") +} + +func (l DoneCommand) SanitizedString() string { + return l.String() +} diff --git a/imap/command/done_test.go b/imap/command/done_test.go new file mode 100644 index 00000000..8a674e30 --- /dev/null +++ b/imap/command/done_test.go @@ -0,0 +1,31 @@ +package command + +import ( + "bytes" + "github.com/ProtonMail/gluon/imap/parser" + "github.com/stretchr/testify/require" + "testing" +) + +func TestParser_DoneCommand(t *testing.T) { + input := toIMAPLine(`DONE`) + s := parser.NewScanner(bytes.NewReader(input)) + p := NewParser(s) + + expected := Command{Tag: "", Payload: &DoneCommand{}} + + cmd, err := p.Parse() + require.NoError(t, err) + require.Equal(t, expected, cmd) + require.Equal(t, "done", p.LastParsedCommand()) + require.Empty(t, p.LastParsedTag()) +} + +func TestParser_DoneCommandAfterTagIsError(t *testing.T) { + input := toIMAPLine(`tag DONE`) + s := parser.NewScanner(bytes.NewReader(input)) + p := NewParser(s) + _, err := p.Parse() + require.Error(t, err) + require.Equal(t, "tag", p.LastParsedTag()) +} diff --git a/imap/command/expunge.go b/imap/command/expunge.go new file mode 100644 index 00000000..89054168 --- /dev/null +++ b/imap/command/expunge.go @@ -0,0 +1,22 @@ +package command + +import ( + "fmt" + "github.com/ProtonMail/gluon/imap/parser" +) + +type ExpungeCommand struct{} + +func (l ExpungeCommand) String() string { + return fmt.Sprintf("EXPUNGE") +} + +func (l ExpungeCommand) SanitizedString() string { + return l.String() +} + +type ExpungeCommandParser struct{} + +func (ExpungeCommandParser) FromParser(p *parser.Parser) (Payload, error) { + return &ExpungeCommand{}, nil +} diff --git a/imap/command/expunge_test.go b/imap/command/expunge_test.go new file mode 100644 index 00000000..b3d9b5df --- /dev/null +++ b/imap/command/expunge_test.go @@ -0,0 +1,22 @@ +package command + +import ( + "bytes" + "github.com/ProtonMail/gluon/imap/parser" + "github.com/stretchr/testify/require" + "testing" +) + +func TestParser_ExpungeCommand(t *testing.T) { + input := toIMAPLine(`tag EXPUNGE`) + s := parser.NewScanner(bytes.NewReader(input)) + p := NewParser(s) + + expected := Command{Tag: "tag", Payload: &ExpungeCommand{}} + + cmd, err := p.Parse() + require.NoError(t, err) + require.Equal(t, expected, cmd) + require.Equal(t, "expunge", p.LastParsedCommand()) + require.Equal(t, "tag", p.LastParsedTag()) +} diff --git a/imap/command/idle.go b/imap/command/idle.go new file mode 100644 index 00000000..e1709f7c --- /dev/null +++ b/imap/command/idle.go @@ -0,0 +1,22 @@ +package command + +import ( + "fmt" + "github.com/ProtonMail/gluon/imap/parser" +) + +type IdleCommand struct{} + +func (l IdleCommand) String() string { + return fmt.Sprintf("IDLE") +} + +func (l IdleCommand) SanitizedString() string { + return l.String() +} + +type IdleCommandParser struct{} + +func (IdleCommandParser) FromParser(p *parser.Parser) (Payload, error) { + return &IdleCommand{}, nil +} diff --git a/imap/command/idle_test.go b/imap/command/idle_test.go new file mode 100644 index 00000000..cd0e689e --- /dev/null +++ b/imap/command/idle_test.go @@ -0,0 +1,22 @@ +package command + +import ( + "bytes" + "github.com/ProtonMail/gluon/imap/parser" + "github.com/stretchr/testify/require" + "testing" +) + +func TestParser_IdleCommand(t *testing.T) { + input := toIMAPLine(`tag IDLE`) + s := parser.NewScanner(bytes.NewReader(input)) + p := NewParser(s) + + expected := Command{Tag: "tag", Payload: &IdleCommand{}} + + cmd, err := p.Parse() + require.NoError(t, err) + require.Equal(t, expected, cmd) + require.Equal(t, "idle", p.LastParsedCommand()) + require.Equal(t, "tag", p.LastParsedTag()) +} diff --git a/imap/command/logout.go b/imap/command/logout.go new file mode 100644 index 00000000..76e2cd75 --- /dev/null +++ b/imap/command/logout.go @@ -0,0 +1,22 @@ +package command + +import ( + "fmt" + "github.com/ProtonMail/gluon/imap/parser" +) + +type LogoutCommand struct{} + +func (l LogoutCommand) String() string { + return fmt.Sprintf("LOGOUT") +} + +func (l LogoutCommand) SanitizedString() string { + return l.String() +} + +type LogoutCommandParser struct{} + +func (LogoutCommandParser) FromParser(p *parser.Parser) (Payload, error) { + return &LogoutCommand{}, nil +} diff --git a/imap/command/logout_test.go b/imap/command/logout_test.go new file mode 100644 index 00000000..f8099f3c --- /dev/null +++ b/imap/command/logout_test.go @@ -0,0 +1,22 @@ +package command + +import ( + "bytes" + "github.com/ProtonMail/gluon/imap/parser" + "github.com/stretchr/testify/require" + "testing" +) + +func TestParser_LogoutCommand(t *testing.T) { + input := toIMAPLine(`tag LOGOUT`) + s := parser.NewScanner(bytes.NewReader(input)) + p := NewParser(s) + + expected := Command{Tag: "tag", Payload: &LogoutCommand{}} + + cmd, err := p.Parse() + require.NoError(t, err) + require.Equal(t, expected, cmd) + require.Equal(t, "logout", p.LastParsedCommand()) + require.Equal(t, "tag", p.LastParsedTag()) +} diff --git a/imap/command/noop.go b/imap/command/noop.go new file mode 100644 index 00000000..6dca9bca --- /dev/null +++ b/imap/command/noop.go @@ -0,0 +1,22 @@ +package command + +import ( + "fmt" + "github.com/ProtonMail/gluon/imap/parser" +) + +type NoopCommand struct{} + +func (l NoopCommand) String() string { + return fmt.Sprintf("NOOP") +} + +func (l NoopCommand) SanitizedString() string { + return l.String() +} + +type NoopCommandParser struct{} + +func (NoopCommandParser) FromParser(p *parser.Parser) (Payload, error) { + return &NoopCommand{}, nil +} diff --git a/imap/command/noop_test.go b/imap/command/noop_test.go new file mode 100644 index 00000000..a5809a16 --- /dev/null +++ b/imap/command/noop_test.go @@ -0,0 +1,22 @@ +package command + +import ( + "bytes" + "github.com/ProtonMail/gluon/imap/parser" + "github.com/stretchr/testify/require" + "testing" +) + +func TestParser_NoopCommand(t *testing.T) { + input := toIMAPLine(`tag NOOP`) + s := parser.NewScanner(bytes.NewReader(input)) + p := NewParser(s) + + expected := Command{Tag: "tag", Payload: &NoopCommand{}} + + cmd, err := p.Parse() + require.NoError(t, err) + require.Equal(t, expected, cmd) + require.Equal(t, "noop", p.LastParsedCommand()) + require.Equal(t, "tag", p.LastParsedTag()) +} diff --git a/imap/command/parser.go b/imap/command/parser.go index d01354df..e52c6fc0 100644 --- a/imap/command/parser.go +++ b/imap/command/parser.go @@ -3,6 +3,7 @@ package command import ( "fmt" "github.com/ProtonMail/gluon/imap/parser" + "strings" ) type Builder interface { @@ -25,10 +26,19 @@ 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{}, + "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{}, }, } } @@ -57,6 +67,16 @@ func (p *Parser) Parse() (Command, error) { return result, err } + // Done command does not have a tag. + if strings.ToLower(tag) == "done" { + p.lastCmd = "done" + + return Command{ + Tag: "", + Payload: &DoneCommand{}, + }, nil + } + result.Tag = tag p.lastTag = tag diff --git a/imap/command/starttls.go b/imap/command/starttls.go new file mode 100644 index 00000000..ea35e173 --- /dev/null +++ b/imap/command/starttls.go @@ -0,0 +1,22 @@ +package command + +import ( + "fmt" + "github.com/ProtonMail/gluon/imap/parser" +) + +type StartTLSCommand struct{} + +func (l StartTLSCommand) String() string { + return fmt.Sprintf("STARTTLS") +} + +func (l StartTLSCommand) SanitizedString() string { + return l.String() +} + +type StartTLSCommandParser struct{} + +func (StartTLSCommandParser) FromParser(p *parser.Parser) (Payload, error) { + return &StartTLSCommand{}, nil +} diff --git a/imap/command/starttls_test.go b/imap/command/starttls_test.go new file mode 100644 index 00000000..db8e88d9 --- /dev/null +++ b/imap/command/starttls_test.go @@ -0,0 +1,22 @@ +package command + +import ( + "bytes" + "github.com/ProtonMail/gluon/imap/parser" + "github.com/stretchr/testify/require" + "testing" +) + +func TestParser_StartTLSCommand(t *testing.T) { + input := toIMAPLine(`tag STARTTLS`) + s := parser.NewScanner(bytes.NewReader(input)) + p := NewParser(s) + + expected := Command{Tag: "tag", Payload: &StartTLSCommand{}} + + cmd, err := p.Parse() + require.NoError(t, err) + require.Equal(t, expected, cmd) + require.Equal(t, "starttls", p.LastParsedCommand()) + require.Equal(t, "tag", p.LastParsedTag()) +} diff --git a/imap/command/unselect.go b/imap/command/unselect.go new file mode 100644 index 00000000..39911c92 --- /dev/null +++ b/imap/command/unselect.go @@ -0,0 +1,22 @@ +package command + +import ( + "fmt" + "github.com/ProtonMail/gluon/imap/parser" +) + +type UnselectCommand struct{} + +func (l UnselectCommand) String() string { + return fmt.Sprintf("UNSELECT") +} + +func (l UnselectCommand) SanitizedString() string { + return l.String() +} + +type UnselectCommandParser struct{} + +func (UnselectCommandParser) FromParser(p *parser.Parser) (Payload, error) { + return &UnselectCommand{}, nil +} diff --git a/imap/command/unselect_test.go b/imap/command/unselect_test.go new file mode 100644 index 00000000..bedc3907 --- /dev/null +++ b/imap/command/unselect_test.go @@ -0,0 +1,22 @@ +package command + +import ( + "bytes" + "github.com/ProtonMail/gluon/imap/parser" + "github.com/stretchr/testify/require" + "testing" +) + +func TestParser_UnselectCommand(t *testing.T) { + input := toIMAPLine(`tag UNSELECT`) + s := parser.NewScanner(bytes.NewReader(input)) + p := NewParser(s) + + expected := Command{Tag: "tag", Payload: &UnselectCommand{}} + + cmd, err := p.Parse() + require.NoError(t, err) + require.Equal(t, expected, cmd) + require.Equal(t, "unselect", p.LastParsedCommand()) + require.Equal(t, "tag", p.LastParsedTag()) +}