Skip to content

Commit 040758c

Browse files
committed
Finalize lexer,parser, interpreter implementations
1 parent b467e78 commit 040758c

27 files changed

+1788
-60
lines changed

cmd/sieve-run/main.go

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"context"
6+
"flag"
7+
"log"
8+
"net/textproto"
9+
"os"
10+
"time"
11+
12+
"github.com/foxcpp/go-sieve"
13+
"github.com/foxcpp/go-sieve/interp"
14+
)
15+
16+
func main() {
17+
msgPath := flag.String("eml", "", "msgPath message to process")
18+
scriptPath := flag.String("scriptPath", "", "scriptPath to run")
19+
envFrom := flag.String("from", "", "envelope from")
20+
envTo := flag.String("to", "", "envelope to")
21+
flag.Parse()
22+
23+
msg, err := os.Open(*msgPath)
24+
if err != nil {
25+
log.Fatalln(err)
26+
}
27+
defer msg.Close()
28+
fileInfo, err := msg.Stat()
29+
if err != nil {
30+
log.Fatalln(err)
31+
}
32+
msgHdr, err := textproto.NewReader(bufio.NewReader(msg)).ReadMIMEHeader()
33+
if err != nil {
34+
log.Fatalln(err)
35+
}
36+
37+
script, err := os.Open(*scriptPath)
38+
if err != nil {
39+
log.Fatalln(err)
40+
}
41+
defer script.Close()
42+
43+
start := time.Now()
44+
loadedScript, err := sieve.Load(script, sieve.DefaultOptions())
45+
end := time.Now()
46+
if err != nil {
47+
log.Fatalln(err)
48+
}
49+
log.Println("script loaded in", end.Sub(start))
50+
51+
data := interp.NewRuntimeData(loadedScript, interp.Callback{
52+
RedirectAllowed: func(ctx context.Context, d *interp.RuntimeData, addr string) (bool, error) {
53+
return true, nil
54+
},
55+
HeaderGet: func(key string) (string, bool, error) {
56+
vals, ok := msgHdr[key]
57+
if !ok {
58+
return "", false, nil
59+
}
60+
return vals[0], true, nil
61+
},
62+
})
63+
data.MessageSize = int(fileInfo.Size())
64+
data.SMTP.From = *envFrom
65+
data.SMTP.To = *envTo
66+
67+
ctx := context.Background()
68+
if err := loadedScript.Execute(ctx, data); err != nil {
69+
log.Fatalln(err)
70+
}
71+
72+
log.Println("redirect:", data.RedirectAddr)
73+
log.Println("fileinfo:", data.Mailboxes)
74+
log.Println("keep:", data.ImplicitKeep || data.Keep)
75+
}

cmd/sieve-run/msg.eml

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Date: Tue, 1 Apr 1997 09:06:31 -0800 (PST)
2+
3+
4+
Subject: I have a present for you
5+
6+
Look, I'm sorry about the whole anvil thing, and I really
7+
didn't mean to try and drop it on you from the top of the
8+
cliff. I want to try to make it up to you. I've got some
9+
great birdseed over here at my place--top of the line
10+
stuff--and if you come by, I'll have it all wrapped up
11+
for you. I'm really sorry for all the problems I've caused
12+
for you over the years, but I know we can work this out.
13+
--
14+
Wile E. Coyote "Super Genius" [email protected]

cmd/sieve-run/msgB.eml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
3+
4+
Date: Mon, 31 Mar 1997 18:26:10 -0800
5+
Subject: $$$ YOU, TOO, CAN BE A MILLIONAIRE! $$$
6+
7+
YOU MAY HAVE ALREADY WON TEN MILLION DOLLARS, BUT I DOUBT
8+
IT! SO JUST POST THIS TO SIX HUNDRED NEWSGROUPS! IT WILL
9+
GUARANTEE THAT YOU GET AT LEAST FIVE RESPONSES WITH MONEY!
10+
MONEY! MONEY! COLD HARD CASH! YOU WILL RECEIVE OVER
11+
$20,000 IN LESS THAN TWO MONTHS! AND IT'S LEGAL!!!!!!!!!
12+
!!!!!!!!!!!!!!!!!!111111111!!!!!!!11111111111!!1 JUST
13+
SEND $5 IN SMALL, UNMARKED BILLS TO THE ADDRESSES BELOW!

cmd/sieve-run/sieve-run

2.66 MB
Binary file not shown.

cmd/sieve-run/test.sieve

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#
2+
# Example Sieve Filter
3+
# Declare any optional features or extension used by the script
4+
#
5+
require ["fileinto"];
6+
7+
#
8+
# Handle messages from known mailing lists
9+
# Move messages from IETF filter discussion list to filter mailbox
10+
#
11+
if header :is "Sender" "[email protected]"
12+
{
13+
fileinto "filter"; # move to "filter" mailbox
14+
}
15+
#
16+
# Keep all messages to or from people in my company
17+
#
18+
elsif address :DOMAIN :is ["From", "To"] "example.com"
19+
{
20+
keep; # keep in "In" mailbox
21+
}
22+
23+
#
24+
# Try and catch unsolicited email. If a message is not to me,
25+
# or it contains a subject known to be spam, file it away.
26+
#
27+
elsif anyof (NOT address :all :contains
28+
["To", "Cc", "Bcc"] "[email protected]",
29+
header :matches "subject"
30+
["*make*money*fast*", "*university*dipl*mas*"])
31+
{
32+
fileinto "spam"; # move to "spam" mailbox
33+
}
34+
else
35+
{
36+
# Move all other (non-company) mail to "personal"
37+
# mailbox.
38+
fileinto "personal";
39+
}

go.mod

+5-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
module github.com/foxcpp/go-sieve
1+
module github.com/foxcpp/go-sieve
2+
3+
go 1.18
4+
5+
require github.com/davecgh/go-spew v1.1.1

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

interp/action.go

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package interp
2+
3+
import (
4+
"context"
5+
"fmt"
6+
)
7+
8+
type CmdStop struct{}
9+
10+
func (c CmdStop) Execute(ctx context.Context, d *RuntimeData) error {
11+
return ErrStop
12+
}
13+
14+
type CmdFileInto struct {
15+
Mailbox string
16+
}
17+
18+
func (c CmdFileInto) Execute(ctx context.Context, d *RuntimeData) error {
19+
found := false
20+
for _, m := range d.Mailboxes {
21+
if m == c.Mailbox {
22+
found = true
23+
}
24+
}
25+
if found {
26+
return nil
27+
}
28+
d.Mailboxes = append(d.Mailboxes, c.Mailbox)
29+
d.ImplicitKeep = false
30+
return nil
31+
}
32+
33+
type CmdRedirect struct {
34+
Addr string
35+
}
36+
37+
func (c CmdRedirect) Execute(ctx context.Context, d *RuntimeData) error {
38+
if d.Callback.RedirectAllowed != nil {
39+
ok, err := d.Callback.RedirectAllowed(ctx, d, c.Addr)
40+
if err != nil {
41+
return err
42+
}
43+
if !ok {
44+
return nil
45+
}
46+
}
47+
d.RedirectAddr = append(d.RedirectAddr, c.Addr)
48+
d.ImplicitKeep = false
49+
50+
if len(d.RedirectAddr) > d.Script.opts.MaxRedirects {
51+
return fmt.Errorf("too many actions")
52+
}
53+
return nil
54+
}
55+
56+
type CmdKeep struct{}
57+
58+
func (c CmdKeep) Execute(_ context.Context, d *RuntimeData) error {
59+
d.Keep = true
60+
return nil
61+
}
62+
63+
type CmdDiscard struct{}
64+
65+
func (c CmdDiscard) Execute(_ context.Context, d *RuntimeData) error {
66+
d.ImplicitKeep = false
67+
return nil
68+
}

interp/control.go

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package interp
2+
3+
import (
4+
"context"
5+
)
6+
7+
type CmdIf struct {
8+
Test Test
9+
Block []Cmd
10+
}
11+
12+
func (c CmdIf) Execute(ctx context.Context, d *RuntimeData) error {
13+
res, err := c.Test.Check(ctx, d)
14+
if err != nil {
15+
return err
16+
}
17+
if res {
18+
for _, c := range c.Block {
19+
if err := c.Execute(ctx, d); err != nil {
20+
return err
21+
}
22+
}
23+
}
24+
d.ifResult = res
25+
return nil
26+
}
27+
28+
type CmdElsif struct {
29+
Test Test
30+
Block []Cmd
31+
}
32+
33+
func (c CmdElsif) Execute(ctx context.Context, d *RuntimeData) error {
34+
if d.ifResult {
35+
return nil
36+
}
37+
res, err := c.Test.Check(ctx, d)
38+
if err != nil {
39+
return err
40+
}
41+
if res {
42+
for _, c := range c.Block {
43+
if err := c.Execute(ctx, d); err != nil {
44+
return err
45+
}
46+
}
47+
}
48+
d.ifResult = res
49+
return nil
50+
}
51+
52+
type CmdElse struct {
53+
Block []Cmd
54+
}
55+
56+
func (c CmdElse) Execute(ctx context.Context, d *RuntimeData) error {
57+
if d.ifResult {
58+
return nil
59+
}
60+
for _, c := range c.Block {
61+
if err := c.Execute(ctx, d); err != nil {
62+
return err
63+
}
64+
}
65+
return nil
66+
}

interp/load.go

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package interp
2+
3+
import (
4+
"strings"
5+
6+
"github.com/foxcpp/go-sieve/lexer"
7+
"github.com/foxcpp/go-sieve/parser"
8+
)
9+
10+
var supportedExtensions = map[string]struct{}{
11+
"fileinto": {},
12+
"envelope": {},
13+
}
14+
15+
var (
16+
commands map[string]func(*Script, parser.Cmd) (Cmd, error)
17+
tests map[string]func(*Script, parser.Test) (Test, error)
18+
)
19+
20+
func init() {
21+
// break initialization loop
22+
23+
commands = map[string]func(*Script, parser.Cmd) (Cmd, error){
24+
// RFC 5228 Actions
25+
"require": loadRequire,
26+
"if": loadIf,
27+
"elsif": loadElsif,
28+
"else": loadElse,
29+
"stop": loadStop,
30+
// RFC 5228 Actions
31+
"fileinto": loadFileInto, // fileinto extension
32+
"redirect": loadRedirect,
33+
"keep": loadKeep,
34+
"discard": loadDiscard,
35+
}
36+
tests = map[string]func(*Script, parser.Test) (Test, error){
37+
// RFC 5228 Tests
38+
"address": loadAddressTest,
39+
"allof": loadAllOfTest,
40+
"anyof": loadAnyOfTest,
41+
"envelope": loadEnvelopeTest, // envelope extension
42+
"exists": loadExistsTest,
43+
"false": loadFalseTest,
44+
"true": loadTrueTest,
45+
"header": loadHeaderTest,
46+
"not": loadNotTest,
47+
"size": loadSizeTest,
48+
}
49+
}
50+
51+
func LoadScript(cmdStream []parser.Cmd, opts *Options) (*Script, error) {
52+
s := &Script{
53+
opts: opts,
54+
}
55+
56+
loadedCmds, err := LoadBlock(s, cmdStream)
57+
if err != nil {
58+
return nil, err
59+
}
60+
s.cmd = loadedCmds
61+
62+
return s, nil
63+
}
64+
65+
func LoadBlock(s *Script, cmds []parser.Cmd) ([]Cmd, error) {
66+
loaded := make([]Cmd, 0, len(cmds))
67+
for _, c := range cmds {
68+
cmd, err := LoadCmd(s, c)
69+
if err != nil {
70+
return nil, err
71+
}
72+
if cmd == nil {
73+
continue
74+
}
75+
loaded = append(loaded, cmd)
76+
}
77+
return loaded, nil
78+
}
79+
80+
func LoadCmd(s *Script, cmd parser.Cmd) (Cmd, error) {
81+
cmdName := strings.ToLower(cmd.Id)
82+
factory := commands[cmdName]
83+
if factory == nil {
84+
return nil, lexer.ErrorAt(cmd, "LoadBlock: unsupported command: %v", cmdName)
85+
}
86+
return factory(s, cmd)
87+
88+
}
89+
90+
func LoadTest(s *Script, t parser.Test) (Test, error) {
91+
testName := strings.ToLower(t.Id)
92+
factory := tests[testName]
93+
if factory == nil {
94+
return nil, lexer.ErrorAt(t, "LoadTest: unsupported test: %v", testName)
95+
}
96+
return factory(s, t)
97+
}

0 commit comments

Comments
 (0)