-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(GODT-2201): Native GO IMAP Parser Foundations
Lay the foundations for native parsers in Go so we can move away from the C++ libraries. This patch sets up the foundations to convert the remaining commands to this new format. At the moment the List and Append command have been ported and should serve as an example for the remaining commands. More commands will follow in future patches.
- Loading branch information
1 parent
9f98ae4
commit b62de94
Showing
12 changed files
with
1,732 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package command | ||
|
||
import ( | ||
"fmt" | ||
"github.com/ProtonMail/gluon/imap/parser" | ||
"time" | ||
) | ||
|
||
type AppendCommand struct { | ||
Mailbox string | ||
Flags []string | ||
DateTime time.Time | ||
Literal []byte | ||
} | ||
|
||
func (l AppendCommand) String() string { | ||
return fmt.Sprintf("APPEND '%v' Flags='%v' DateTime='%v' Literal=%v", | ||
l.Mailbox, | ||
l.Flags, | ||
l.DateTime, | ||
l.Literal, | ||
) | ||
} | ||
|
||
func (l AppendCommand) SanitizedString() string { | ||
return fmt.Sprintf("APPEND '%v' Flags='%v' DateTime='%v'", | ||
sanitizeString(l.Mailbox), | ||
l.Flags, | ||
l.DateTime, | ||
) | ||
} | ||
|
||
func (l AppendCommand) HasDateTime() bool { | ||
return l.DateTime != time.Time{} | ||
} | ||
|
||
type AppendCommandParser struct{} | ||
|
||
func (AppendCommandParser) FromParser(p *parser.Parser) (Payload, error) { | ||
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 | ||
} | ||
|
||
var appendFlags []string | ||
|
||
// check if we have flags. | ||
flagList, hasFlagList, err := p.TryParseFlagList() | ||
if err != nil { | ||
return nil, err | ||
} else if hasFlagList { | ||
appendFlags = flagList | ||
} | ||
|
||
if hasFlagList { | ||
if err := p.Consume(parser.TokenTypeSP, "expected space after flag list"); err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
var dateTime time.Time | ||
// check date time. | ||
if !p.Check(parser.TokenTypeLCurly) { | ||
dt, err := ParseDateTime(p) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
dateTime = dt | ||
|
||
if err := p.Consume(parser.TokenTypeSP, "expected space after flag list"); err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
// read literal. | ||
literal, err := p.ParseLiteral() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &AppendCommand{ | ||
Mailbox: mailbox, | ||
Literal: literal, | ||
Flags: appendFlags, | ||
DateTime: dateTime, | ||
}, 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,138 @@ | ||
package command | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"github.com/ProtonMail/gluon/imap/parser" | ||
cppParser "github.com/ProtonMail/gluon/internal/parser" | ||
"github.com/stretchr/testify/require" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func buildAppendDateTime(year int, month time.Month, day int, hour int, min int, sec int, zoneHour int, zoneMinutes int, negativeZone bool) time.Time { | ||
zoneMultiplier := 1 | ||
if negativeZone { | ||
zoneMultiplier = -1 | ||
} | ||
|
||
zone := (zoneHour*3600 + zoneMinutes*60) * zoneMultiplier | ||
|
||
location := time.FixedZone("zone", zone) | ||
|
||
return time.Date(year, month, day, hour, min, sec, 0, location) | ||
} | ||
|
||
func TestParser_AppendCommandWithAllFields(t *testing.T) { | ||
input := toIMAPLine(`A003 APPEND saved-messages (\Seen) "15-Nov-1984 13:37:01 +0730" {23}`, `My message body is here`) | ||
s := parser.NewScanner(bytes.NewReader(input)) | ||
p := NewParser(s) | ||
|
||
expected := Command{Tag: "A003", Payload: &AppendCommand{ | ||
Mailbox: "saved-messages", | ||
Flags: []string{`\Seen`}, | ||
Literal: []byte("My message body is here"), | ||
DateTime: buildAppendDateTime(1984, time.November, 15, 13, 37, 1, 07, 30, false), | ||
}} | ||
|
||
cmd, err := p.Parse() | ||
require.NoError(t, err) | ||
require.Equal(t, expected, cmd) | ||
require.Equal(t, "append", p.LastParsedCommand()) | ||
require.Equal(t, "A003", p.LastParsedTag()) | ||
} | ||
|
||
func TestParser_AppendCommandWithLiteralOnly(t *testing.T) { | ||
input := toIMAPLine(`A003 APPEND saved-messages {23}`, `My message body is here`) | ||
s := parser.NewScanner(bytes.NewReader(input)) | ||
p := NewParser(s) | ||
|
||
expected := Command{Tag: "A003", Payload: &AppendCommand{ | ||
Mailbox: "saved-messages", | ||
Literal: []byte("My message body is here"), | ||
}} | ||
|
||
cmd, err := p.Parse() | ||
require.NoError(t, err) | ||
require.Equal(t, expected, cmd) | ||
require.Equal(t, "append", p.LastParsedCommand()) | ||
require.Equal(t, "A003", p.LastParsedTag()) | ||
} | ||
|
||
func TestParser_AppendCommandWithFlagAndLiteral(t *testing.T) { | ||
input := toIMAPLine(`A003 APPEND saved-messages (\Seen) {23}`, `My message body is here`) | ||
s := parser.NewScanner(bytes.NewReader(input)) | ||
p := NewParser(s) | ||
|
||
expected := Command{Tag: "A003", Payload: &AppendCommand{ | ||
Mailbox: "saved-messages", | ||
Flags: []string{`\Seen`}, | ||
Literal: []byte("My message body is here"), | ||
}} | ||
|
||
cmd, err := p.Parse() | ||
require.NoError(t, err) | ||
require.Equal(t, expected, cmd) | ||
require.Equal(t, "append", p.LastParsedCommand()) | ||
require.Equal(t, "A003", p.LastParsedTag()) | ||
} | ||
|
||
func TestParser_AppendCommandWithDateTimeAndLiteral(t *testing.T) { | ||
input := toIMAPLine(`A003 APPEND saved-messages "15-Nov-1984 13:37:01 +0730" {23}`, `My message body is here`) | ||
s := parser.NewScanner(bytes.NewReader(input)) | ||
p := NewParser(s) | ||
|
||
expected := Command{Tag: "A003", Payload: &AppendCommand{ | ||
Mailbox: "saved-messages", | ||
Literal: []byte("My message body is here"), | ||
DateTime: buildAppendDateTime(1984, time.November, 15, 13, 37, 1, 07, 30, false), | ||
}} | ||
|
||
cmd, err := p.Parse() | ||
require.NoError(t, err) | ||
require.Equal(t, expected, cmd) | ||
require.Equal(t, "append", p.LastParsedCommand()) | ||
require.Equal(t, "A003", p.LastParsedTag()) | ||
} | ||
|
||
func TestParser_AppendWithUTF8Literal(t *testing.T) { | ||
const literal = `割ゃちとた紀別チノホコ隠面ノネシ披畑つ筋型菊ラ済百チユネ報能げほべえ一王ユサ禁未シムカ学康ほル退今ずはぞゃ宿理古えべにあ。民さぱをだ意宇りう医6通海ヘクヲ点71丈2社つぴげわ中知多ッ場親られ見要クラ著喜栄潟ぼゆラけ。著災ンう三育府能に汁裁ラヤユ哉能ルサレ開30被ちゃ英死オイ教禁能ふてっせ社化選市へす。` | ||
input := toIMAPLine(fmt.Sprintf("A003 APPEND saved-messages (\\Seen) {%v}", len(literal)), literal) | ||
|
||
s := parser.NewScanner(bytes.NewReader(input)) | ||
p := NewParser(s) | ||
|
||
expected := Command{Tag: "A003", Payload: &AppendCommand{ | ||
Mailbox: "saved-messages", | ||
Flags: []string{`\Seen`}, | ||
Literal: []byte(literal), | ||
}} | ||
|
||
cmd, err := p.Parse() | ||
require.NoError(t, err) | ||
require.Equal(t, expected, cmd) | ||
} | ||
|
||
func BenchmarkParser_Append(b *testing.B) { | ||
input := toIMAPLine(`A003 APPEND saved-messages (\Seen) {23}`, `My message body is here`) | ||
|
||
for i := 0; i < b.N; i++ { | ||
s := parser.NewScanner(bytes.NewReader(input)) | ||
p := NewParser(s) | ||
|
||
_, err := p.Parse() | ||
require.NoError(b, err) | ||
} | ||
} | ||
|
||
func BenchmarkParser_AppendCPP(b *testing.B) { | ||
input := string(toIMAPLine(`A003 APPEND saved-messages (\Seen) {23}`, `04269a34-ad29-472c-9d5d-042a02b7fc0d`)) | ||
|
||
literalMap := cppParser.NewStringMap() | ||
|
||
literalMap.Set("04269a34-ad29-472c-9d5d-042a02b7fc0d", "My message body is here") | ||
|
||
for i := 0; i < b.N; i++ { | ||
cppParser.Parse(input, literalMap, "/") | ||
} | ||
} |
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,22 @@ | ||
package command | ||
|
||
import ( | ||
"encoding/base64" | ||
"github.com/ProtonMail/gluon/internal/hash" | ||
) | ||
|
||
type Payload interface { | ||
String() string | ||
|
||
// SanitizedString should return the command payload with all the sensitive information stripped out. | ||
SanitizedString() string | ||
} | ||
|
||
func sanitizeString(s string) string { | ||
return base64.StdEncoding.EncodeToString(hash.SHA256([]byte(s))) | ||
} | ||
|
||
type Command struct { | ||
Tag string | ||
Payload Payload | ||
} |
Oops, something went wrong.