-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2238d80
commit 6aac246
Showing
4 changed files
with
297 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
package command | ||
|
||
import ( | ||
"fmt" | ||
"github.com/ProtonMail/gluon/imap/parser" | ||
) | ||
|
||
type StoreAction int | ||
|
||
const ( | ||
StoreActionAddFlags StoreAction = iota | ||
StoreActionRemFlags | ||
StoreActionSetFlags | ||
) | ||
|
||
func (s StoreAction) String() string { | ||
switch s { | ||
case StoreActionAddFlags: | ||
return "+FLAGS" | ||
case StoreActionRemFlags: | ||
return "+FLAGS" | ||
case StoreActionSetFlags: | ||
return "FLAGS" | ||
default: | ||
return "UNKNOWN" | ||
} | ||
} | ||
|
||
type StoreCommand struct { | ||
SeqSet []SeqRange | ||
Action StoreAction | ||
Flags []string | ||
Silent bool | ||
} | ||
|
||
func (s StoreCommand) String() string { | ||
silentStr := "" | ||
if s.Silent { | ||
silentStr = ".SILENT" | ||
} | ||
|
||
return fmt.Sprintf("STORE %v%v %v", s.Action.String(), silentStr, s.Flags) | ||
} | ||
|
||
func (s StoreCommand) SanitizedString() string { | ||
return s.String() | ||
} | ||
|
||
type StoreCommandParser struct{} | ||
|
||
func (StoreCommandParser) FromParser(p *parser.Parser) (Payload, error) { | ||
//nolint:dupword | ||
// store = "STORE" SP sequence-set SP store-att-flags | ||
// store-att-flags = (["+" / "-"] "FLAGS" [".SILENT"]) SP | ||
// (flag-list / (flag *(SP flag))) | ||
if err := p.Consume(parser.TokenTypeSP, "expected space after command"); err != nil { | ||
return nil, err | ||
} | ||
|
||
seqSet, err := ParseSeqSet(p) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if err := p.Consume(parser.TokenTypeSP, "expected space after sequence set"); err != nil { | ||
return nil, err | ||
} | ||
|
||
var action StoreAction | ||
|
||
if ok, err := p.Matches(parser.TokenTypePlus); err != nil { | ||
return nil, err | ||
} else if !ok { | ||
if ok, err := p.Matches(parser.TokenTypeMinus); err != nil { | ||
return nil, err | ||
} else if ok { | ||
action = StoreActionRemFlags | ||
} else { | ||
action = StoreActionSetFlags | ||
} | ||
} else { | ||
action = StoreActionAddFlags | ||
} | ||
|
||
if err := p.ConsumeBytesFold([]byte{'F', 'L', 'A', 'G', 'S'}); err != nil { | ||
return nil, err | ||
} | ||
|
||
var silent bool | ||
|
||
if ok, err := p.Matches(parser.TokenTypePeriod); err != nil { | ||
return nil, err | ||
} else if ok { | ||
if err := p.ConsumeBytesFold([]byte{'S', 'I', 'L', 'E', 'N', 'T'}); err != nil { | ||
return nil, err | ||
} | ||
|
||
silent = true | ||
} | ||
|
||
if err := p.Consume(parser.TokenTypeSP, "expected space after FLAGS"); err != nil { | ||
return nil, err | ||
} | ||
|
||
flags, err := parseStoreFlags(p) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &StoreCommand{ | ||
SeqSet: seqSet, | ||
Action: action, | ||
Flags: flags, | ||
Silent: silent, | ||
}, nil | ||
} | ||
|
||
func parseStoreFlags(p *parser.Parser) ([]string, error) { | ||
// (flag-list / (flag *(SP flag))) | ||
fl, ok, err := p.TryParseFlagList() | ||
if err != nil { | ||
return nil, err | ||
} else if ok { | ||
return fl, nil | ||
} | ||
|
||
var flags []string | ||
|
||
// first flag. | ||
{ | ||
f, err := p.ParseFlag() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
flags = append(flags, f) | ||
} | ||
|
||
// remaining. | ||
for { | ||
if ok, err := p.Matches(parser.TokenTypeSP); err != nil { | ||
return nil, err | ||
} else if !ok { | ||
break | ||
} | ||
|
||
f, err := p.ParseFlag() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
flags = append(flags, f) | ||
} | ||
|
||
return flags, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package command | ||
|
||
import ( | ||
"bytes" | ||
"github.com/ProtonMail/gluon/imap/parser" | ||
"github.com/stretchr/testify/require" | ||
"testing" | ||
) | ||
|
||
func TestParser_StoreCommandSetFlags(t *testing.T) { | ||
input := toIMAPLine(`tag STORE 1 FLAGS Foo`) | ||
s := parser.NewScanner(bytes.NewReader(input)) | ||
p := NewParser(s) | ||
|
||
expected := Command{Tag: "tag", Payload: &StoreCommand{ | ||
SeqSet: []SeqRange{{ | ||
Begin: 1, | ||
End: 1, | ||
}}, | ||
Action: StoreActionSetFlags, | ||
Flags: []string{"Foo"}, | ||
Silent: false, | ||
}} | ||
|
||
cmd, err := p.Parse() | ||
require.NoError(t, err) | ||
require.Equal(t, expected, cmd) | ||
require.Equal(t, "store", p.LastParsedCommand()) | ||
require.Equal(t, "tag", p.LastParsedTag()) | ||
} | ||
|
||
func TestParser_StoreCommandAddFlags(t *testing.T) { | ||
expected := Command{Tag: "tag", Payload: &StoreCommand{ | ||
SeqSet: []SeqRange{{ | ||
Begin: 1, | ||
End: 1, | ||
}}, | ||
Action: StoreActionAddFlags, | ||
Flags: []string{"Foo"}, | ||
Silent: false, | ||
}} | ||
|
||
cmd, err := testParseCommand(`tag STORE 1 +FLAGS Foo`) | ||
require.NoError(t, err) | ||
require.Equal(t, expected, cmd) | ||
} | ||
|
||
func TestParser_StoreCommandRemoveFlags(t *testing.T) { | ||
expected := Command{Tag: "tag", Payload: &StoreCommand{ | ||
SeqSet: []SeqRange{{ | ||
Begin: 1, | ||
End: 1, | ||
}}, | ||
Action: StoreActionRemFlags, | ||
Flags: []string{"Foo"}, | ||
Silent: false, | ||
}} | ||
|
||
cmd, err := testParseCommand(`tag STORE 1 -FLAGS Foo`) | ||
require.NoError(t, err) | ||
require.Equal(t, expected, cmd) | ||
} | ||
|
||
func TestParser_StoreCommandSilent(t *testing.T) { | ||
expected := Command{Tag: "tag", Payload: &StoreCommand{ | ||
SeqSet: []SeqRange{{ | ||
Begin: 1, | ||
End: 1, | ||
}}, | ||
Action: StoreActionAddFlags, | ||
Flags: []string{"Foo"}, | ||
Silent: true, | ||
}} | ||
|
||
cmd, err := testParseCommand(`tag STORE 1 +FLAGS.SILENT Foo`) | ||
require.NoError(t, err) | ||
require.Equal(t, expected, cmd) | ||
} | ||
|
||
func TestParser_StoreCommandMultipleFlags(t *testing.T) { | ||
expected := Command{Tag: "tag", Payload: &StoreCommand{ | ||
SeqSet: []SeqRange{{ | ||
Begin: 1, | ||
End: 1, | ||
}}, | ||
Action: StoreActionAddFlags, | ||
Flags: []string{"Foo", "Bar"}, | ||
Silent: true, | ||
}} | ||
|
||
cmd, err := testParseCommand(`tag STORE 1 +FLAGS.SILENT Foo Bar`) | ||
require.NoError(t, err) | ||
require.Equal(t, expected, cmd) | ||
} | ||
|
||
func TestParser_StoreCommandMultipleFlagsWithParen(t *testing.T) { | ||
expected := Command{Tag: "tag", Payload: &StoreCommand{ | ||
SeqSet: []SeqRange{{ | ||
Begin: 1, | ||
End: 1, | ||
}}, | ||
Action: StoreActionAddFlags, | ||
Flags: []string{"Foo", "Bar"}, | ||
Silent: true, | ||
}} | ||
|
||
cmd, err := testParseCommand(`tag STORE 1 +FLAGS.SILENT (Foo Bar)`) | ||
require.NoError(t, err) | ||
require.Equal(t, expected, cmd) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters