Documentation
¶
Overview ¶
Package sieve parses and executes scripts written in the Sieve mail-filtering language (RFC 5228) — the language mail servers use to let users sort, file, redirect, and auto-reply to incoming mail.
Parsing and evaluation are decoupled from any mailbox model. A caller [Parse]s a script once into a Script, adapts its own email into the neutral Message type, and calls Script.Evaluate with an Executor it implements. The evaluator walks the script, evaluates each test against the message, and calls the Executor's methods to apply the actions the script selected (fileinto, redirect, imap4flags, vacation, notify, keep). The two terminal dispositions, discard and reject, are reported through the returned Outcome rather than the Executor. How each action maps onto a real mailbox — folder names, flag storage, vacation de-duplication, notification transport — is entirely the Executor's concern: this package decides what to do, the host decides how. It depends only on the Go standard library.
Evaluating a script ¶
script, err := sieve.Parse(src)
if err != nil {
// syntax error
}
outcome := script.Evaluate(msg, exec) // exec implements sieve.Executor
switch outcome.Disposition {
case sieve.Discard:
// silently drop the message
case sieve.Reject:
// refuse it, citing outcome.RejectReason
default:
// deliver, honouring the actions exec recorded
}
Use Validate to check a script's syntax without evaluating it.
Supported language ¶
Control commands: if / elsif / else, require, stop.
Tests: address, header, envelope, exists, size (:over / :under), body, allof, anyof, not, true / false.
Actions: keep, discard, fileinto (with :create), redirect, reject, imap4flags (setflag / addflag / removeflag), vacation, notify.
Match types: :is, :contains, :matches (glob with * and ?), and a non-standard :regex extension. Comparators: i;ascii-casemap (the default), i;octet, and i;ascii-numeric. Address parts: :all, :localpart, :domain.
require and extensions ¶
Parsing enforces RFC 5228 "require" semantics. Every extension a script uses must be declared with require before use; requiring an extension this package does not implement is an error; require must precede every other command; and an unknown command or test (including a typo) is a parse error rather than a silent no-op. Parse and Validate therefore reject a script that uses an unsupported or undeclared extension instead of running it partially. Script.Requires reports the extensions the script declared via "require".
Example ¶
Example parses a small Sieve script, runs it against a message, and inspects the actions the script selected.
package main
import (
"fmt"
sieve "github.com/rest-mail/go-sieve"
)
// mailbox is a minimal sieve.Executor that records where the script decided to
// deliver the message and which flags it set. A real implementation would move
// the message into an actual folder, persist IMAP flags, send the vacation
// reply, and so on — the package decides what to do, this type decides how.
type mailbox struct {
folder string
flags []string
}
func (m *mailbox) Keep() {}
func (m *mailbox) FileInto(folder string, create bool) { m.folder = folder }
func (m *mailbox) Redirect(addr string) {}
func (m *mailbox) Flag(op string, flags []string) { m.flags = flags }
func (m *mailbox) Vacation(v sieve.Vacation) {}
func (m *mailbox) Notify(method, message string) {}
// Example parses a small Sieve script, runs it against a message, and inspects
// the actions the script selected.
func main() {
script, err := sieve.Parse(`require ["fileinto", "imap4flags", "mailbox"];
if header :contains "Subject" "invoice" {
setflag "\\Flagged";
fileinto :create "Invoices";
}`)
if err != nil {
panic(err)
}
msg := &sieve.Message{
Headers: sieve.Headers{Subject: "Your March invoice is ready"},
}
mb := &mailbox{}
outcome := script.Evaluate(msg, mb)
switch outcome.Disposition {
case sieve.Discard:
fmt.Println("discard")
case sieve.Reject:
fmt.Println("reject:", outcome.RejectReason)
default:
fmt.Printf("deliver to %q with flags %v\n", mb.folder, mb.flags)
}
}
Output: deliver to "Invoices" with flags [\Flagged]
Index ¶
Examples ¶
Constants ¶
const DefaultMaxDepth = 64
DefaultMaxDepth is the default limit on how deeply tests and control blocks may nest before Parse rejects a script. The parser and evaluator both recurse over this nesting, so an unbounded script would otherwise exhaust the goroutine stack and crash the host process (a denial-of-service vector when script content is user-supplied). RFC 5228 §2.10.7 explicitly sanctions a finite limit and requires implementations to support only 15 levels; 64 leaves generous headroom for real scripts while capping the recursion a crafted script can drive. Override it per-call with WithMaxDepth.
Variables ¶
This section is empty.
Functions ¶
func Validate ¶
Validate reports whether a Sieve script is syntactically valid. It accepts the same [Option]s as Parse; nesting beyond DefaultMaxDepth (or the limit set with WithMaxDepth) is reported as invalid.
Types ¶
type Attachment ¶
type Attachment struct {
Size int64
}
Attachment contributes its octet Size to the size test.
type Disposition ¶
type Disposition int
Disposition is the terminal delivery decision an evaluation reached.
const ( // Continue means no terminal action fired; deliver honouring whatever // actions the Executor recorded. Continue Disposition = iota // Discard means the script asked to silently drop the message. Discard // Reject means the script asked to refuse the message (see Outcome.RejectReason). Reject )
type Envelope ¶
Envelope holds the SMTP envelope identities used by the envelope test. The host resolves any override (e.g. a gateway-supplied sender) before populating these fields.
type Executor ¶
type Executor interface {
// Keep requests explicit delivery to the default mailbox.
Keep()
// FileInto delivers into the named folder, creating it when create is set.
FileInto(folder string, create bool)
// Redirect forwards the message to addr.
Redirect(addr string)
// Flag applies an imap4flags operation ("setflag", "addflag", "removeflag").
Flag(op string, flags []string)
// Vacation records a vacation auto-reply request.
Vacation(v Vacation)
// Notify records a notification request.
Notify(method, message string)
}
Executor applies the (non-terminal) actions a Sieve script selects. The host implements it to map each action onto its mailbox/delivery model. Methods are invoked in script order; the terminal actions discard and reject are not Executor methods but are reported via the Outcome returned by Script.Evaluate.
type Headers ¶
type Headers struct {
Subject string
From []Address
To []Address
Cc []Address
Bcc []Address
MessageID string
InReplyTo string
Date string
References []string
Raw map[string][]string
}
Headers holds the structured and raw headers used by header/address/exists tests. Raw is consulted (case-insensitively) for any header not covered by a structured field, so custom headers such as X-Priority are testable.
type Message ¶
type Message struct {
Headers Headers
Envelope Envelope
Body Body
Attachments []Attachment
}
Message is the neutral view of an email the evaluator tests against. A host maps its own representation onto this type before evaluating; the evaluator never sees the host's message model.
type Option ¶ added in v0.2.0
type Option func(*parseOptions)
Option configures optional Parse / Validate behaviour.
func WithMaxDepth ¶ added in v0.2.0
WithMaxDepth overrides DefaultMaxDepth, the maximum nesting depth of tests (not / allof / anyof) and control blocks (if / elsif / else) the parser will accept before returning an error. A non-positive value restores the default.
type Outcome ¶
type Outcome struct {
Disposition Disposition
RejectReason string // set only when Disposition == Reject
}
Outcome is the result of evaluating a script.
type Script ¶
type Script struct {
// contains filtered or unexported fields
}
Script is a fully parsed Sieve script. Parse it with Parse; evaluate it with Script.Evaluate.
func Parse ¶
Parse parses a Sieve script into an evaluable Script. It is strict about the constructs it understands and lenient about unknown extensions (see the package overview). Nesting of tests and control blocks is capped at DefaultMaxDepth (override with WithMaxDepth); a script that exceeds the cap is rejected rather than allowed to exhaust the stack at parse or evaluation time.
func (*Script) Empty ¶
Empty reports whether the script has no executable commands (a nil script, or one consisting only of require/comments).