├── tests
├── good
│ ├── empty.siv
│ ├── keep.siv
│ ├── stop.siv
│ ├── discard.siv
│ ├── if.siv
│ ├── false.siv
│ ├── true.siv
│ ├── anyof.siv
│ ├── not.siv
│ ├── redirect.siv
│ ├── case.siv
│ ├── exists.siv
│ ├── else.siv
│ ├── elsif.siv
│ ├── fileinto.siv
│ ├── reject.siv
│ ├── ereject.siv
│ ├── copy.siv
│ ├── require.siv
│ ├── environment.siv
│ ├── regex.siv
│ ├── utf_8.siv
│ ├── allof.siv
│ ├── notify.siv
│ ├── variables.siv
│ ├── subaddress.siv
│ ├── spamtest.siv
│ ├── imap4flags.siv
│ ├── index.siv
│ ├── spamtestplus.siv
│ ├── virustest.siv
│ ├── vacation.siv
│ ├── body.siv
│ ├── imapflags.siv
│ ├── size.siv
│ ├── extlists.siv
│ ├── comments.siv
│ ├── editheader.siv
│ ├── date.siv
│ ├── header.siv
│ ├── address.siv
│ ├── envelope.siv
│ ├── relational.siv
│ └── currentdate.siv
├── bad
│ ├── invalid.siv
│ ├── invalid_block.siv
│ ├── invalid_test.siv
│ ├── require.siv
│ ├── plain_test.siv
│ ├── invalid_string_list.siv
│ ├── invalid_else.siv
│ ├── invalid_test_list.siv
│ ├── invalid_variable_namespace.siv
│ ├── relational_count_no_comparator.siv
│ ├── spamtest.siv
│ └── spamtestplus.siv
├── bootstrap.php
├── customExtensions
│ ├── wrongTypeExtension.xml
│ ├── requireRequiresExtension.xml
│ ├── requireForbidsExtension.xml
│ └── requireMixedExtension.xml
├── SieveKeywordRegistryTest.php
└── SieveParserTest.php
├── .gitignore
├── lib
├── extensions
│ ├── subaddress.xml
│ ├── comparator-ascii-numeric.xml
│ ├── fileinto.xml
│ ├── ereject.xml
│ ├── reject.xml
│ ├── copy.xml
│ ├── mailbox.xml
│ ├── regex.xml
│ ├── virustest.xml
│ ├── spamtest.xml
│ ├── envelope.xml
│ ├── spamtestplus.xml
│ ├── environment.xml
│ ├── relational.xml
│ ├── extlists.xml
│ ├── body.xml
│ ├── imapflags.xml
│ ├── index.xml
│ ├── editheader.xml
│ ├── imap4flags.xml
│ ├── variables.xml
│ ├── notify.xml
│ ├── vacation.xml
│ └── date.xml
├── SieveDumpable.php
├── SieveException.php
├── keywords.xml
├── SieveToken.php
├── SieveTree.php
├── SieveScanner.php
├── SieveKeywordRegistry.php
└── SieveParser.php
├── .editorconfig
├── phpcs.xml
├── psalm.xml
├── .github
└── workflows
│ └── tests.yml
├── phpunit.xml
├── composer.json
├── README.md
└── rfcs
├── rfc3894-degener-sieve-copy.txt
├── rfc5233-murchison-sieve-subaddress.txt
├── draft-melnikov-sieve-imapflags-03.txt
├── rfc5231-segmuller-sieve-relational.txt
├── draft-ietf-sieve-notify-00.txt
├── rfc5173-freed-sieve-environment.txt
└── rfc5293-degener-sieve-editheader.txt
/tests/good/empty.siv:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/good/keep.siv:
--------------------------------------------------------------------------------
1 | keep;
--------------------------------------------------------------------------------
/tests/good/stop.siv:
--------------------------------------------------------------------------------
1 | stop;
--------------------------------------------------------------------------------
/tests/good/discard.siv:
--------------------------------------------------------------------------------
1 | discard;
--------------------------------------------------------------------------------
/tests/bad/invalid.siv:
--------------------------------------------------------------------------------
1 | ["imap4flags"]
2 |
--------------------------------------------------------------------------------
/tests/good/if.siv:
--------------------------------------------------------------------------------
1 | if true {
2 | stop;
3 | }
--------------------------------------------------------------------------------
/tests/bad/invalid_block.siv:
--------------------------------------------------------------------------------
1 | if true {
2 | discard;
--------------------------------------------------------------------------------
/tests/bad/invalid_test.siv:
--------------------------------------------------------------------------------
1 | if {
2 | discard;
3 | }
--------------------------------------------------------------------------------
/tests/good/false.siv:
--------------------------------------------------------------------------------
1 | if false {
2 | stop;
3 | }
4 |
--------------------------------------------------------------------------------
/tests/good/true.siv:
--------------------------------------------------------------------------------
1 | if true {
2 | stop;
3 | }
4 |
--------------------------------------------------------------------------------
/tests/bad/require.siv:
--------------------------------------------------------------------------------
1 | require "unknown extension";
2 |
--------------------------------------------------------------------------------
/tests/good/anyof.siv:
--------------------------------------------------------------------------------
1 | if anyof(true) {
2 | stop;
3 | }
4 |
--------------------------------------------------------------------------------
/tests/good/not.siv:
--------------------------------------------------------------------------------
1 | if not not true {
2 | stop;
3 | }
4 |
--------------------------------------------------------------------------------
/tests/good/redirect.siv:
--------------------------------------------------------------------------------
1 | redirect "me@dom.invalid";
2 |
--------------------------------------------------------------------------------
/tests/bad/plain_test.siv:
--------------------------------------------------------------------------------
1 | address :all "From" "him@example.com";
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | composer.lock
3 | reports
4 | vendor
5 | coverage.xml
6 |
--------------------------------------------------------------------------------
/tests/bad/invalid_string_list.siv:
--------------------------------------------------------------------------------
1 | if address ["from"] {
2 | discard;
3 | }
--------------------------------------------------------------------------------
/tests/bad/invalid_else.siv:
--------------------------------------------------------------------------------
1 | if {
2 | else {
3 | discard;
4 | }
5 | }
--------------------------------------------------------------------------------
/tests/bad/invalid_test_list.siv:
--------------------------------------------------------------------------------
1 |
2 | if allof(true, false {
3 | discard;
4 | }
--------------------------------------------------------------------------------
/tests/good/case.siv:
--------------------------------------------------------------------------------
1 | Require "vacation";
2 |
3 | VaCAtiON :MiMe :SUBJECT "foo" "bar";
--------------------------------------------------------------------------------
/tests/good/exists.siv:
--------------------------------------------------------------------------------
1 | if exists ["X-Foo", "X-Bar"]
2 | {
3 | stop;
4 | }
5 |
--------------------------------------------------------------------------------
/tests/good/else.siv:
--------------------------------------------------------------------------------
1 | if true {
2 | stop;
3 | }
4 | else
5 | {
6 | discard
7 | ;
8 | }
--------------------------------------------------------------------------------
/tests/good/elsif.siv:
--------------------------------------------------------------------------------
1 | if true {
2 | stop;
3 | }
4 | elsif true{stop ;
5 |
6 |
7 |
8 | }
--------------------------------------------------------------------------------
/tests/bad/invalid_variable_namespace.siv:
--------------------------------------------------------------------------------
1 | require "variables";
2 |
3 | set "name.space" "value";
4 | keep;
--------------------------------------------------------------------------------
/tests/good/fileinto.siv:
--------------------------------------------------------------------------------
1 | require ["fileinto","fileinto"];
2 | fileinto "INBOX.foo";
3 | fileinto "INBOX.bar";
4 |
5 |
--------------------------------------------------------------------------------
/tests/good/reject.siv:
--------------------------------------------------------------------------------
1 | require "reject";
2 | reject "Sorry, I can't take it anymore.";
3 | reject "Really.";
4 |
5 |
--------------------------------------------------------------------------------
/tests/good/ereject.siv:
--------------------------------------------------------------------------------
1 | require ["ereject"];
2 | ereject "Sorry, I can't take it anymore.";
3 | ereject "Really.";
4 |
5 |
--------------------------------------------------------------------------------
/tests/good/copy.siv:
--------------------------------------------------------------------------------
1 | require ["fileinto", "copy"];
2 |
3 | fileinto :copy "INBOX.foo";
4 | redirect :copy "me@there.invalid";
5 |
--------------------------------------------------------------------------------
/tests/good/require.siv:
--------------------------------------------------------------------------------
1 | require "fileinto";
2 | require "fileinto";
3 | require ["envelope"];
4 | require ["reject", "fileinto"];
5 |
--------------------------------------------------------------------------------
/tests/good/environment.siv:
--------------------------------------------------------------------------------
1 | require "environment";
2 |
3 | if environment :matches "remote-host" ["pharmacy", "casino"]
4 | { discard; }
5 |
--------------------------------------------------------------------------------
/tests/bad/relational_count_no_comparator.siv:
--------------------------------------------------------------------------------
1 | require ["relational","variables"];
2 |
3 | if string :count "eq" "..." "1" {
4 | set "ok" "1";
5 | }
6 |
--------------------------------------------------------------------------------
/tests/good/regex.siv:
--------------------------------------------------------------------------------
1 | require ["regex", "variables"];
2 |
3 | set :quoteregex "foo" "[bar]";
4 |
5 | if header :regex "a" ["[:digit:]]+"]
6 | { keep; }
7 |
--------------------------------------------------------------------------------
/tests/good/utf_8.siv:
--------------------------------------------------------------------------------
1 | require "fileinto";
2 |
3 | if address :localpart :comparator "i;unicode-casemap" "from" "Idéeßuccinte" {
4 | fileinto "Idées!😂";
5 | }
--------------------------------------------------------------------------------
/tests/good/allof.siv:
--------------------------------------------------------------------------------
1 | if allof(true) {
2 | stop;
3 | }
4 | if allof(true, true) {
5 | stop;
6 | }
7 | elsif allof(true, true, false) {
8 | stop;
9 | }
10 |
--------------------------------------------------------------------------------
/tests/good/notify.siv:
--------------------------------------------------------------------------------
1 | require "notify";
2 |
3 | notify :id "foobar" :method "proto:heiko@foobar.invalid" :high :message "MSG";
4 | notify :low;
5 | denotify :is "foobar" :high;
6 |
7 | keep;
--------------------------------------------------------------------------------
/tests/good/variables.siv:
--------------------------------------------------------------------------------
1 | require "variables";
2 | require [];
3 |
4 | set "foo" "bar";
5 | set :length :lower "foo_len" "${foo}";
6 |
7 | if string :matches ["${foo_len}"] "3" {
8 | keep;
9 | }
--------------------------------------------------------------------------------
/tests/good/subaddress.siv:
--------------------------------------------------------------------------------
1 | require "subaddress";
2 |
3 | if address :user :is [ "From", "To" ] "foo"
4 | {
5 | keep;
6 | }
7 | elsif address :detail "BCC" "spam"
8 | {
9 | keep;
10 | }
--------------------------------------------------------------------------------
/lib/extensions/subaddress.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/tests/good/spamtest.siv:
--------------------------------------------------------------------------------
1 | require ["spamtest", "relational"
2 | , "comparator-i;ascii-numeric"];
3 |
4 | if spamtest :value "lt" :comparator "i;ascii-numeric" "0" {
5 | discard;
6 | }
7 |
8 | keep;
--------------------------------------------------------------------------------
/lib/extensions/comparator-ascii-numeric.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/tests/bad/spamtest.siv:
--------------------------------------------------------------------------------
1 | require ["spamtest", "spamtestplus", "relational"
2 | , "comparator-i;ascii-numeric"];
3 |
4 | if spamtest :value "lt" :comparator "i;ascii-numeric" "0" {
5 | discard;
6 | }
7 |
8 | keep;
--------------------------------------------------------------------------------
/tests/good/imap4flags.siv:
--------------------------------------------------------------------------------
1 | require "imap4flags";
2 |
3 | setflag "$a";
4 | removeflag "$a";
5 | addflag ["\\Deleted \\foo"];
6 |
7 | if hasflag ["\\Deleted", "\\foo"]
8 | {
9 | keep :flags "\\New";
10 | }
11 |
--------------------------------------------------------------------------------
/tests/good/index.siv:
--------------------------------------------------------------------------------
1 | require ["date", "relational", "index"];
2 |
3 | if date :value "gt" :index 2 :zone "-0500" "received"
4 | "iso8601" "2007-02-26T09:00:00-05:00"
5 | { redirect "aftercutoff@example.org"; }
6 |
--------------------------------------------------------------------------------
/tests/good/spamtestplus.siv:
--------------------------------------------------------------------------------
1 | require ["spamtestplus", "relational"
2 | , "comparator-i;ascii-numeric"];
3 |
4 | if spamtest :percent :value "lt" :comparator "i;ascii-numeric" "0" {
5 | discard;
6 | }
7 |
8 | keep;
--------------------------------------------------------------------------------
/tests/good/virustest.siv:
--------------------------------------------------------------------------------
1 | require ["virustest"
2 | , "relational"
3 | , "comparator-i;ascii-numeric"
4 | ];
5 |
6 | if virustest :value "lt" :comparator "i;ascii-numeric" "0" {
7 | discard;
8 | }
9 |
10 | keep;
--------------------------------------------------------------------------------
/lib/SieveDumpable.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/lib/extensions/ereject.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/lib/extensions/reject.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/tests/good/body.siv:
--------------------------------------------------------------------------------
1 | require "body";
2 | require "comparator-i;ascii-numeric";
3 |
4 | if body :content "text" :contains "foo" { keep; }
5 | if body :raw ["foo", "bar"] { keep; }
6 | if body :comparator "i;ascii-numeric" :content "text" "foo" { keep; }
7 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tests/good/imapflags.siv:
--------------------------------------------------------------------------------
1 | require "imapflags";
2 | require "reject";
3 |
4 | if true {
5 | mark;
6 | unmark;unmark;
7 | mark; mark;
8 | reject "fooo";
9 | }
10 |
11 | setflag ["foo", "bar", "baz"];
12 | addflag "qaz";
13 | removeflag "pateng";
14 |
--------------------------------------------------------------------------------
/tests/good/size.siv:
--------------------------------------------------------------------------------
1 | if size :over 10M {
2 | keep;
3 | } elsif size :under 20K {
4 | keep;
5 | } elsif size :over 400G {
6 | keep;
7 | } elsif size :over 0 {
8 | keep;
9 | } elsif size :under 0 {
10 | keep;
11 | } elsif size :under 1 {
12 | keep;
13 | }
--------------------------------------------------------------------------------
/tests/customExtensions/requireRequiresExtension.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/lib/extensions/copy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/lib/extensions/mailbox.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tests/customExtensions/requireForbidsExtension.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | indent_style = space
8 | indent_size = 4
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [*.yml]
15 | indent_size = 2
16 |
--------------------------------------------------------------------------------
/tests/customExtensions/requireMixedExtension.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tests/good/extlists.siv:
--------------------------------------------------------------------------------
1 | require "extlists";
2 |
3 | # Submission from list members is sent to all members
4 | if allof(
5 | valid_ext_list "tag:example.com,2010-05-28:mylist",
6 | header :list "from" "tag:example.com,2010-05-28:mylist"
7 | ) {
8 | redirect :list "tag:example.com,2010-05-28:mylist";
9 | }
--------------------------------------------------------------------------------
/lib/extensions/regex.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/tests/good/comments.siv:
--------------------------------------------------------------------------------
1 | # comment
2 | require "vacation";
3 | /* comment */
4 | require "reject";
5 |
6 | if true {
7 | keep;
8 | # comment
9 | }
10 |
11 | if size :over 100K { /* this is a comment
12 | this is still a comment */ discard /* this is a comment
13 | */ ;
14 | }
15 |
16 | # comment
17 |
--------------------------------------------------------------------------------
/lib/extensions/virustest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/lib/extensions/spamtest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/tests/good/editheader.siv:
--------------------------------------------------------------------------------
1 | require ["editheader"];
2 |
3 | if not header :contains "X-Sieve-Filtered"
4 | ["", ""]
5 | {
6 | addheader :last "X-Sieve-Filtered" "";
7 | redirect "kim@home.example.com";
8 | }
9 |
10 | deleteheader :index 1 :last :contains "Delivered-To" "bob@example.com";
11 |
--------------------------------------------------------------------------------
/tests/good/date.siv:
--------------------------------------------------------------------------------
1 | require ["date", "relational", "fileinto"];
2 |
3 | if allof(header :is "from" "boss@example.com",
4 | date :value "ge" :originalzone "date" "hour" "09",
5 | date :value "lt" :originalzone "date" "HOUR" "17")
6 | { fileinto "urgent"; }
7 |
8 | if anyof(date :is "received" "Weekday" "0",
9 | date :is "received" "weekday" "6")
10 | { fileinto "weekend"; }
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | */vendor/*
11 |
12 |
--------------------------------------------------------------------------------
/tests/good/header.siv:
--------------------------------------------------------------------------------
1 | if header :is "To" ["a@b.c"]
2 | { keep; }
3 | elsif header :contains "From" ["me" ,"you"]
4 | { keep; }
5 | elsif header :contains ["To","From"] ["me" ,"you"]
6 | { keep; }
7 | elsif header :matches :comparator "i;ascii-casemap" "FROM" "a@b.c"
8 | { keep; }
9 | elsif header :comparator "i;octet" :is ["TO"] "a"
10 | { keep; }
11 | elsif header :contains :comparator "i;octet" ["FROM" , "tO"] "a@b.c"
12 | { keep; }
13 |
--------------------------------------------------------------------------------
/lib/extensions/envelope.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/lib/extensions/spamtestplus.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lib/extensions/environment.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/lib/extensions/relational.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/lib/extensions/extlists.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/lib/extensions/body.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/lib/extensions/imapflags.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/tests/good/address.siv:
--------------------------------------------------------------------------------
1 | require ["comparator-i;ascii-numeric"];
2 |
3 | if address :all :is "To" ["a@b.c"]
4 | { keep; }
5 | elsif address :contains :localpart "From" ["me" ,"you"]
6 | { keep; }
7 | elsif address :contains :localpart ["To","From"] ["me" ,"you"]
8 | { keep; }
9 | elsif address :domain :matches :comparator "i;ascii-casemap" "CC" "a@b.c"
10 | { keep; }
11 | elsif address :comparator "i;octet" :domain ["CC"] "a"
12 | { keep; }
13 | elsif address :is :comparator "i;ascii-numeric" ["CC" , "From"] "a@b.c"
14 | { keep; }
15 |
--------------------------------------------------------------------------------
/tests/good/envelope.siv:
--------------------------------------------------------------------------------
1 | require ["envelope"];
2 | require ["envelope"];
3 |
4 | if envelope :all :is "To" ["a@b.c"]
5 | { keep; }
6 | elsif envelope :contains :localpart "From" ["me" ,"you"]
7 | { keep; }
8 | elsif envelope :contains :localpart ["To","From"] ["me" ,"you"]
9 | { keep; }
10 | elsif envelope :domain :matches :comparator "i;ascii-casemap" "FROM" "a@b.c"
11 | { keep; }
12 | elsif envelope :comparator "i;octet" :domain ["TO"] "a"
13 | { keep; }
14 | elsif envelope :is :comparator "i;octet" ["FROM" , "tO"] "a@b.c"
15 | { keep; }
16 |
--------------------------------------------------------------------------------
/tests/good/relational.siv:
--------------------------------------------------------------------------------
1 | require ["envelope", "relational", "comparator-i;ascii-numeric"];
2 |
3 | if header :comparator "i;ascii-numeric" :count "lt" "a" ["3"]
4 | { keep; }
5 | elsif header :value "gt" ["a", "b"] ["3"]
6 | { keep; }
7 | elsif address :comparator "i;ascii-numeric" :count "eq" ["to"] "5"
8 | { keep; }
9 | elsif address :value "ne" ["to", "from"] "5"
10 | { keep; }
11 | elsif envelope :count "ge" :comparator "i;ascii-numeric" ["from"] "3"
12 | { keep; }
13 | elsif envelope :value "le" :all :comparator "i;ascii-numeric" "from" "M"
14 | { keep; }
15 |
--------------------------------------------------------------------------------
/lib/extensions/index.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/tests/good/currentdate.siv:
--------------------------------------------------------------------------------
1 | require ["date", "relational", "vacation"];
2 | require ["variables", "fileinto", "editheader"];
3 |
4 | if anyof(currentdate :is "weekday" "0",
5 | currentdate :is "weekday" "6",
6 | currentdate :value "lt" "hour" "09",
7 | currentdate :value "ge" "hour" "17")
8 | { redirect "pager@example.com"; }
9 |
10 | if allof(currentdate :value "ge" "date" "2007-06-30",
11 | currentdate :value "le" "date" "2007-07-07")
12 | { vacation :days 7 "I'm away during the first week in July."; }
13 |
14 | if currentdate :matches "month" "*" { set "month" "${1}"; }
15 | if currentdate :matches "year" "*" { set "year" "${1}"; }
16 | fileinto "${month}-${year}";
17 |
18 | if currentdate :matches "std11" "*"
19 | {addheader "Processing-date" "${0}";}
20 |
--------------------------------------------------------------------------------
/lib/extensions/editheader.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/lib/extensions/imap4flags.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/lib/extensions/variables.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: "Unit tests"
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | static-analysis-phpstan:
11 | name: Unit tests
12 | runs-on: ubuntu-latest
13 |
14 | strategy:
15 | matrix:
16 | php-version:
17 | - 8.0
18 | - 8.1
19 | - 8.2
20 |
21 | steps:
22 | - name: Checkout code
23 | uses: actions/checkout@v2
24 |
25 | - name: Install PHP
26 | uses: shivammathur/setup-php@v2
27 | with:
28 | coverage: none
29 | php-version: ${{ matrix.php-version }}
30 |
31 | - name: Install dependencies with composer
32 | run: composer i --no-interaction --no-progress --no-suggest
33 |
34 | - name: Run Psalm
35 | run: ./vendor/bin/psalm
36 |
37 | - name: Run PHPUnit
38 | run: ./vendor/bin/phpunit --testdox
39 |
--------------------------------------------------------------------------------
/lib/extensions/notify.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/lib/extensions/vacation.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/lib/SieveException.php:
--------------------------------------------------------------------------------
1 | type);
32 | $message = "$tokenType where $type expected near $token->text";
33 | }
34 |
35 | parent::__construct("line $token->line: $message");
36 | }
37 |
38 | public function getLineNo(): int
39 | {
40 | return $this->token->line;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/extensions/date.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 |
17 |
18 | lib
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | ./tests
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "protonlabs/libsieve-php",
3 | "description": "libsieve-php is a library to manage and modify sieve (RFC5228) scripts.",
4 | "keywords": ["sieve", "filters", "mail"],
5 | "type": "library",
6 | "license": "GPL-3.0-or-later",
7 | "homepage": "https://sourceforge.net/projects/libsieve-php/",
8 | "support": {
9 | "issues": "https://github.com/ProtonMail/libsieve-php/issues"
10 | },
11 | "authors": [
12 | {
13 | "name": "Heiko Hund",
14 | "homepage": "https://sourceforge.net/u/heikoh/profile/"
15 | },
16 | {
17 | "name": "Martin Zeman",
18 | "email": "martin@protonmail.com",
19 | "homepage": "https://protonmail.com"
20 | },
21 | {
22 | "name": "Kay Lukas",
23 | "email": "kay@pm.me",
24 | "homepage": "https://protonmail.com"
25 | },
26 | {
27 | "name": "Thomas Hareau",
28 | "email": "thomas.hareau@protonmail.com",
29 | "homepage": "https://thomas.hareau.eu"
30 | }
31 | ],
32 | "require": {
33 | "php": ">=8.0",
34 | "ext-mbstring": "*",
35 | "ext-simplexml": "*"
36 | },
37 | "autoload": {
38 | "psr-4": {
39 | "Sieve\\": "lib/"
40 | }
41 | },
42 | "require-dev": {
43 | "phpunit/phpunit": "^9",
44 | "squizlabs/php_codesniffer": "^3.5",
45 | "protonlabs/php-coding-standard": "^4.0",
46 | "vimeo/psalm": "^4.22"
47 | },
48 | "scripts": {
49 | "phpcs": "phpcs lib",
50 | "unit": "phpunit --testdox --colors=always"
51 | },
52 | "config": {
53 | "allow-plugins": {
54 | "dealerdirect/phpcodesniffer-composer-installer": true
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/lib/keywords.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/lib/SieveToken.php:
--------------------------------------------------------------------------------
1 | '\r', "\n" => '\n', "\t" => '\t'];
34 |
35 | protected const TYPE_STR = [
36 | SieveToken::IDENTIFIER => 'identifier',
37 | SieveToken::WHITESPACE => 'whitespace',
38 | SieveToken::QUOTED_STRING => 'quoted string',
39 | SieveToken::TAG => 'tag',
40 | SieveToken::SEMICOLON => 'semicolon',
41 | SieveToken::LEFT_BRACKET => 'left bracket',
42 | SieveToken::RIGHT_BRACKET => 'right bracket',
43 | SieveToken::BLOCK_START => 'block start',
44 | SieveToken::BLOCK_END => 'block end',
45 | SieveToken::LEFT_PARENTHESIS => 'left parenthesis',
46 | SieveToken::RIGHT_PARENTHESIS => 'right parenthesis',
47 | SieveToken::COMMA => 'comma',
48 | SieveToken::NUMBER => 'number',
49 | SieveToken::COMMENT => 'comment',
50 | SieveToken::MULTILINE_STRING => 'multiline string',
51 | SieveToken::SCRIPT_END => 'script end',
52 | SieveToken::STRING => 'string',
53 | SieveToken::STRING_LIST => 'string list',
54 | ];
55 |
56 | public function __construct(public int $type, public string $text, public int $line)
57 | {
58 | }
59 |
60 | /**
61 | * Dump the current token.
62 | */
63 | public function dump(): string
64 | {
65 | return '<' . static::escape($this->text) . '> type:' . static::typeString(
66 | $this->type
67 | ) . ' line:' . $this->line;
68 | }
69 |
70 | /**
71 | * Get the Sieve Text of the current token.
72 | */
73 | public function text(): string
74 | {
75 | return $this->text;
76 | }
77 |
78 | /**
79 | * Check if a token type is the given type.
80 | */
81 | public function is(int $type): bool
82 | {
83 | return (bool) ($this->type & $type);
84 | }
85 |
86 | /**
87 | * Get the string value of a Type.
88 | */
89 | public static function typeString(int $type): string
90 | {
91 | return static::TYPE_STR[$type] ?? 'unknown token';
92 | }
93 |
94 | /**
95 | * Escapes a value.
96 | */
97 | public static function escape(string $val): string
98 | {
99 | return strtr($val, self::$tr);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/lib/SieveTree.php:
--------------------------------------------------------------------------------
1 | children = [];
22 | $this->maxId = 0;
23 |
24 | $this->parents = [null];
25 | $this->nodes = [$name];
26 | }
27 |
28 | /**
29 | * Add child to last node.
30 | */
31 | public function addChild(SieveToken $child): ?int
32 | {
33 | return $this->addChildTo($this->maxId, $child);
34 | }
35 |
36 | /**
37 | * Add child to given parent.
38 | */
39 | public function addChildTo(int $parentId, SieveToken $child): ?int
40 | {
41 | if (!isset($this->nodes[$parentId])) {
42 | return null;
43 | }
44 |
45 | if (!isset($this->children[$parentId])) {
46 | $this->children[$parentId] = [];
47 | }
48 |
49 | $childId = ++$this->maxId;
50 | $this->nodes[$childId] = $child;
51 | $this->parents[$childId] = $parentId;
52 | $this->children[$parentId][] = $childId;
53 |
54 | return $childId;
55 | }
56 |
57 | /**
58 | * Get root Id.
59 | */
60 | public function getRoot(): int
61 | {
62 | return 0;
63 | }
64 |
65 | /**
66 | * Get children of a specific node.
67 | *
68 | * @return int[]|null the child ids or null, if parent node not found
69 | */
70 | public function getChildren(int $nodeId): ?array
71 | {
72 | if (!isset($this->nodes[$nodeId])) {
73 | return null;
74 | }
75 |
76 | return $this->children[$nodeId] ?? [];
77 | }
78 |
79 | /**
80 | * Get node from Id.
81 | */
82 | public function getNode(int $nodeId): ?SieveToken
83 | {
84 | if ($nodeId === 0 || !isset($this->nodes[$nodeId])) {
85 | return null;
86 | }
87 |
88 | return $this->nodes[$nodeId] ?? null;
89 | }
90 |
91 | /**
92 | * Get parent from child id.
93 | */
94 | public function getParent(int $nodeId): ?int
95 | {
96 | return $this->parents[$nodeId] ?? null;
97 | }
98 |
99 | /**
100 | * Get last id.
101 | */
102 | public function getLastId(): int
103 | {
104 | return $this->maxId;
105 | }
106 |
107 | /**
108 | * Dump the tree.
109 | */
110 | public function dump(): string
111 | {
112 | return $this->nodes[$this->getRoot()] . "\n" . $this->dumpChildren($this->getRoot(), ' ');
113 | }
114 |
115 | /**
116 | * Dump children of given node.
117 | */
118 | protected function dumpChildren(int $parentId, string $prefix): string
119 | {
120 | $children = $this->children[$parentId] ?? [];
121 | $lastChild = count($children);
122 | $dump = '';
123 | for ($i = 1; $i <= $lastChild; ++$i) {
124 | $childNode = $this->nodes[$children[$i - 1]];
125 | $infix = ($i === $lastChild ? '`--- ' : '|--- ');
126 | $dump .= $prefix . $infix . $childNode->dump() . ' (id:' . $children[$i - 1] . ")\n";
127 |
128 | $nextPrefix = $prefix . ($i === $lastChild ? ' ' : '| ');
129 | $dump .= $this->dumpChildren($children[$i - 1], $nextPrefix);
130 | }
131 | return $dump;
132 | }
133 |
134 | /**
135 | * Get text.
136 | */
137 | public function getText(): string
138 | {
139 | return $this->childrenText($this->getRoot());
140 | }
141 |
142 | /**
143 | * Get child text.
144 | */
145 | protected function childrenText(int $parentId): string
146 | {
147 | $children = $this->children[$parentId] ?? [];
148 |
149 | $dump = '';
150 | foreach ($children as $iValue) {
151 | $childNode = $this->nodes[$iValue];
152 | $dump .= $childNode->text();
153 | $dump .= $this->childrenText($iValue);
154 | }
155 | return $dump;
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/tests/SieveKeywordRegistryTest.php:
--------------------------------------------------------------------------------
1 | parse($sieve);
23 | } catch (\Sieve\SieveException $e) {
24 | $this->assertSame(1, $e->getLineNo());
25 |
26 | $this->expectException(\Sieve\SieveException::class);
27 | throw $e;
28 | }
29 | }
30 |
31 | /**
32 | * Checks extension that require another extension.
33 | *
34 | * @throws \Sieve\SieveException
35 | */
36 | public function testRequiresExtension(): void
37 | {
38 | $path = __DIR__ . "/customExtensions/requireRequiresExtension.xml";
39 | $parser = new SieveParser(null, [$path]);
40 | $sieve = <<parse($sieve);
45 | static::assertTrue(true);
46 |
47 | $parser = new SieveParser(null, [$path]);
48 | $sieve = <<parse($sieve);
54 | static::assertTrue(true);
55 | }
56 |
57 | /**
58 | * Checks extensions fails if required extension is not loaded before.
59 | *
60 | * @throws \Sieve\SieveException
61 | */
62 | public function testRequiresExtensionFailsIfNotLoaded(): void
63 | {
64 | $path = __DIR__ . "/customExtensions/requireRequiresExtension.xml";
65 | $parser = new SieveParser(null, [$path]);
66 | $sieve = <<expectException(\Sieve\SieveException::class);
71 |
72 | $parser->parse($sieve);
73 | }
74 |
75 | /**
76 | * Checks the behavior when several extensions are required.
77 | * @throws \Sieve\SieveException
78 | * @dataProvider mixExtensionProvider
79 | */
80 | public function testMixExtension(string $sieveExtensions, ?string $exception = null): void
81 | {
82 | $path = __DIR__ . "/customExtensions/requireMixedExtension.xml";
83 | $parser = new SieveParser(null, [$path]);
84 |
85 | $sieve = <<parse($sieve);
94 | static::assertTrue(true);
95 | }
96 |
97 | /**
98 | * Provides various extensions requirement configuration.
99 | */
100 | public function mixExtensionProvider(): array
101 | {
102 | return [
103 | "correct" => [
104 | '"relational", "mixed"',
105 | ],
106 | "correct inversed" => [
107 | '"mixed", "relational"',
108 | ],
109 | "missing required" => [
110 | '"mixed"',
111 | \Sieve\SieveException::class,
112 | ],
113 | "one forbidden extension" => [
114 | '"mixed", "spamtest"',
115 | \Sieve\SieveException::class,
116 | ],
117 | "several forbidden extensions" => [
118 | '"vacation", "mixed", "spamtest"',
119 | \Sieve\SieveException::class,
120 | ],
121 | ];
122 | }
123 |
124 | public function testWrongExtensionType(): void
125 | {
126 | $path = __DIR__ . "/customExtensions/wrongTypeExtension.xml";
127 | $parser = new SieveParser(null, [$path]);
128 | $sieve = <<expectException(\Sieve\SieveException::class);
133 | $this->expectExceptionMessage('Unsupported extension type \'silly\' in extension \'wrong\'');
134 |
135 | $parser->parse($sieve);
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/tests/SieveParserTest.php:
--------------------------------------------------------------------------------
1 | parse($sieve);
40 |
41 | self::assertEquals($sieve, $parser->getParseTree()->getText());
42 | }
43 |
44 | /**
45 | * @dataProvider goodProvider
46 | * @throws \Sieve\SieveException
47 | */
48 | public function testGood(string $sieve): void
49 | {
50 | $parser = new SieveParser();
51 | $parser->parse($sieve);
52 | // it should not raise an exception
53 | self::assertEquals($sieve, $parser->getScriptText());
54 | }
55 |
56 | /**
57 | * @dataProvider badProvider
58 | * @param string $sieve
59 | * @throws \Sieve\SieveException
60 | */
61 | public function testBad(string $sieve): void
62 | {
63 | $parser = new SieveParser();
64 |
65 | $this->expectException(\Sieve\SieveException::class);
66 | $parser->parse($sieve);
67 | }
68 |
69 | private function provider(string $dirName)
70 | {
71 | $directoryIterator = new DirectoryIterator(__DIR__ . '/' . $dirName);
72 | $iterator = new RegexIterator($directoryIterator, "/.*.siv/", RegexIterator::MATCH);
73 | foreach ($iterator as $sieveFile) {
74 | yield $sieveFile->getBasename('.siv') => [file_get_contents($sieveFile->getPathname())];
75 | }
76 | }
77 |
78 | public function goodProvider()
79 | {
80 | return $this->provider("good");
81 | }
82 |
83 | public function badProvider()
84 | {
85 | return $this->provider("bad");
86 | }
87 |
88 | /**
89 | * @dataProvider dataDumpProvider
90 | * @param string $sieve
91 | * @param string $dumpExpected
92 | * @throws \Sieve\SieveException
93 | */
94 | public function testDump(string $sieve, string $dumpExpected): void
95 | {
96 | $parser = new SieveParser();
97 | $parser->parse($sieve);
98 | $dump = $parser->dumpParseTree();
99 |
100 | $this->assertStringStartsWith(
101 | str_replace("\r", '', trim($dumpExpected)),
102 | str_replace("\r", '', trim($dump))
103 | );
104 | }
105 |
106 | public function dataDumpProvider(): iterable
107 | {
108 | $dumpExpected0 = <<<'DUMP'
109 | tree
110 | `--- type:identifier line:1 (id:1)
111 | |--- < > type:whitespace line:1 (id:2)
112 | |--- type:identifier line:1 (id:3)
113 | | |--- <(> type:left parenthesis line:1 (id:4)
114 | | |--- type:identifier line:1 (id:5)
115 | | `--- <)> type:right parenthesis line:1 (id:6)
116 | | `--- < > type:whitespace line:1 (id:7)
117 | |--- <{> type:block start line:1 (id:8)
118 | `--- <}> type:block end line:1 (id:9)
119 | DUMP;
120 |
121 | $dumpExpected1 = <<<'DUMP'
122 | tree
123 | `--- type:identifier line:1 (id:1)
124 | |--- < > type:whitespace line:1 (id:2)
125 | |--- type:identifier line:1 (id:3)
126 | | |--- < > type:whitespace line:1 (id:4)
127 | | |--- <:is> type:tag line:1 (id:5)
128 | | | `--- < > type:whitespace line:1 (id:6)
129 | | |--- <"To"> type:quoted string line:1 (id:7)
130 | | | `--- < > type:whitespace line:1 (id:8)
131 | | |--- <[> type:left bracket line:1 (id:9)
132 | | |--- <"a@b.c"> type:quoted string line:1 (id:10)
133 | | `--- <]> type:right bracket line:1 (id:11)
134 | | `--- < > type:whitespace line:1 (id:12)
135 | |--- <{> type:block start line:1 (id:13)
136 | `--- <}> type:block end line:1 (id:14)
137 | DUMP;
138 |
139 | $dumpExpected2 = <<<'DUMP'
140 | tree
141 | `--- type:identifier line:1 (id:1)
142 | |--- < > type:whitespace line:1 (id:2)
143 | |--- <[> type:left bracket line:1 (id:3)
144 | |--- <"virustest"> type:quoted string line:1 (id:4)
145 | |--- <,> type:comma line:1 (id:5)
146 | | `--- < > type:whitespace line:1 (id:6)
147 | |--- <"comparator-i;ascii-numeric"> type:quoted string line:1 (id:7)
148 | |--- <]> type:right bracket line:1 (id:8)
149 | `--- <;> type:semicolon line:1 (id:9)
150 | DUMP;
151 |
152 | $dumpExpected3 = <<<'DUMP'
153 | tree
154 | |--- <# C\n> type:comment line:1 (id:1)
155 | |--- type:identifier line:2 (id:2)
156 | | |--- < > type:whitespace line:2 (id:3)
157 | | |--- type:identifier line:2 (id:4)
158 | | | |--- < > type:whitespace line:2 (id:5)
159 | | | |--- <:under> type:tag line:2 (id:6)
160 | | | | `--- < > type:whitespace line:2 (id:7)
161 | | | `--- <1> type:number line:2 (id:8)
162 | | | `--- < > type:whitespace line:2 (id:9)
163 | | |--- <{> type:block start line:2 (id:10)
164 | | `--- <}> type:block end line:2 (id:11)
165 | | `--- <\n> type:whitespace line:2 (id:12)
166 | `--- type:identifier line:3 (id:13)
167 | |--- < > type:whitespace line:3 (id:14)
168 | |--- type:identifier line:3 (id:15)
169 | | |--- < > type:whitespace line:3 (id:16)
170 | | |--- <:is> type:tag line:3 (id:17)
171 | | | `--- < > type:whitespace line:3 (id:18)
172 | | |--- <"b"> type:quoted string line:3 (id:19)
173 | | | `--- < > type:whitespace line:3 (id:20)
174 | | `--- type:multiline string line:3 (id:21)
175 | |--- <{> type:block start line:6 (id:22)
176 | `--- <}> type:block end line:6 (id:23)
177 | DUMP;
178 |
179 | yield ['if allof(true) {}', $dumpExpected0];
180 | yield ['if header :is "To" ["a@b.c"] {}', $dumpExpected1];
181 | yield [
182 | 'require ["virustest", "comparator-i;ascii-numeric"];',
183 | $dumpExpected2,
184 | ];
185 | yield [
186 | '# C
187 | if size :under 1 {}
188 | if address :is "b" text:
189 | t
190 | .
191 | {}',
192 | $dumpExpected3,
193 | ];
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/lib/SieveScanner.php:
--------------------------------------------------------------------------------
1 | '\[',
16 | SieveToken::RIGHT_BRACKET => '\]',
17 | SieveToken::BLOCK_START => '\{',
18 | SieveToken::BLOCK_END => '\}',
19 | SieveToken::LEFT_PARENTHESIS => '\(',
20 | SieveToken::RIGHT_PARENTHESIS => '\)',
21 | SieveToken::COMMA => ',',
22 | SieveToken::SEMICOLON => ';',
23 | SieveToken::WHITESPACE => '[ \r\n\t]+',
24 | SieveToken::TAG => ':[[:alpha:]_][[:alnum:]_]*(?=\b)',
25 | /*
26 | " # match a quotation mark
27 | ( # start matching parts that include an escaped quotation mark
28 | ([^"]*[^"\\\\]) # match a string without quotation marks and not ending with a backlash
29 | ? # this also includes the empty string
30 | (\\\\\\\\)* # match any groups of even number of backslashes
31 | # (thus the character after these groups are not escaped)
32 | \\\\" # match an escaped quotation mark
33 | )* # accept any number of strings that end with an escaped quotation mark
34 | [^"]* # accept any trailing part that does not contain any quotation marks
35 | " # end of the quoted string
36 | */
37 | SieveToken::QUOTED_STRING => '"(([^"]*[^"\\\\])?(\\\\\\\\)*\\\\")*[^"]*"',
38 | SieveToken::NUMBER => '[[:digit:]]+(?:[KMG])?(?=\b)',
39 | SieveToken::COMMENT => '(?:\/\*(?:[^\*]|\*(?=[^\/]))*\*\/|#[^\r\n]*\r?(\n|$))',
40 | SieveToken::MULTILINE_STRING =>
41 | 'text:[ \t]*(?:#[^\r\n]*)?\r?\n(\.[^\r\n]+\r?\n|[^\.][^\r\n]*\r?\n)*\.\r?(\n|$)',
42 | SieveToken::IDENTIFIER => '[[:alpha:]_][[:alnum:]_]*(?=\b)',
43 | SieveToken::UNKNOWN => '[^ \r\n\t]+',
44 | ];
45 |
46 | /**
47 | * SieveScanner constructor.
48 | */
49 | public function __construct(?string $script)
50 | {
51 | if ($script === null) {
52 | return;
53 | }
54 |
55 | $this->tokenize($script);
56 | }
57 |
58 | /**
59 | * Set passthrough func.
60 | */
61 | public function setPassthroughFunc(callable $callback): void
62 | {
63 | $this->ptFn = Closure::fromCallable($callback);
64 | }
65 |
66 | /**
67 | * Tokenizes a script.
68 | */
69 | public function tokenize(string $script): void
70 | {
71 | $pos = 0;
72 | $line = 1;
73 |
74 | $scriptLength = strlen($script);
75 |
76 | $unprocessedScript = $script;
77 |
78 | //create one regex to find the right match
79 | //avoids looping over all possible tokens: increases performance
80 | $nameToType = [];
81 | $regex = [];
82 | // chr(65) === 'A'
83 | $i = 65;
84 |
85 | foreach ($this->tokenMatch as $type => $subregex) {
86 | $nameToType[chr($i)] = $type;
87 | $regex[] = '(?P<' . chr($i) . ">\G$subregex)";
88 | $i++;
89 | }
90 |
91 | $regex = '/' . implode('|', $regex) . '/';
92 |
93 | while ($pos < $scriptLength) {
94 | if (preg_match($regex, $unprocessedScript, $match, 0, $pos)) {
95 | // only keep the group that match and we only want matches with group names
96 | // we can use the group name to find the token type using nameToType
97 | $filterMatch = array_filter(
98 | $match,
99 | function ($value, $key) {
100 | return is_string($key) && isset($value) && $value !== '';
101 | },
102 | ARRAY_FILTER_USE_BOTH
103 | );
104 |
105 | // the first element in filterMatch will contain the matched group and the key will be the name
106 | $type = $nameToType[key($filterMatch)];
107 | $currentMatch = current($filterMatch);
108 |
109 | //create the token
110 | $token = new SieveToken($type, $currentMatch, $line);
111 | $this->tokens[] = $token;
112 |
113 | if ($type === SieveToken::UNKNOWN) {
114 | return;
115 | }
116 |
117 | // just remove the part that we parsed: don't extract the new substring using script length
118 | // as mb_strlen is \theta(pos) (it's linear in the position)
119 | $pos += strlen($currentMatch);
120 | $line += mb_substr_count($currentMatch, "\n");
121 | } else {
122 | $this->tokens[] = new SieveToken(SieveToken::UNKNOWN, '', $line);
123 |
124 | return;
125 | }
126 | }
127 |
128 | $this->tokens[] = new SieveToken(SieveToken::SCRIPT_END, '', $line);
129 | }
130 |
131 | /**
132 | * Get current token.
133 | */
134 | public function getCurrentToken(): ?SieveToken
135 | {
136 | return $this->tokens[$this->tokenPos - 1] ?? null;
137 | }
138 |
139 | /**
140 | * Check if next token is of given type.
141 | */
142 | public function nextTokenIs(int $type): bool
143 | {
144 | return $this->peekNextToken()->is($type);
145 | }
146 |
147 | /**
148 | * Check is current token is of type.
149 | */
150 | public function currentTokenIs(int $type): bool
151 | {
152 | $currentToken = $this->getCurrentToken();
153 |
154 | return isset($currentToken) && $currentToken->is($type);
155 | }
156 |
157 | /**
158 | * Peek next token. (not moving the position)
159 | */
160 | public function peekNextToken(): SieveToken
161 | {
162 | $offset = 0;
163 | do {
164 | $next = $this->tokens[$this->tokenPos + $offset++];
165 | } while ($next->is(SieveToken::COMMENT | SieveToken::WHITESPACE));
166 |
167 | return $next;
168 | }
169 |
170 | /**
171 | * Get next token. (and moving the position)
172 | */
173 | public function nextToken(): SieveToken
174 | {
175 | $token = $this->tokens[$this->tokenPos++];
176 |
177 | while ($token->is(SieveToken::COMMENT | SieveToken::WHITESPACE)) {
178 | if (isset($this->ptFn)) {
179 | ($this->ptFn)($token);
180 | }
181 |
182 | $token = $this->tokens[$this->tokenPos++];
183 | }
184 |
185 | return $token;
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # libsieve-php
2 |
3 | [](https://travis-ci.org/ProtonMail/libsieve-php)
4 | [](https://codecov.io/gh/ProtonMail/libsieve-php)
5 | [](https://github.com/ProtonMail/libsieve-php/blob/master/LICENSE)
6 |
7 | libsieve-php is a library to manage and modify sieve (RFC5228) scripts. It contains a parser for the sieve language (including extensions).
8 |
9 | This project is adopted from the discontinued PHP sieve library available at https://sourceforge.net/projects/libsieve-php.
10 |
11 | ## Changes from the RFC
12 |
13 | - The `date` and the `currentdate` both allow for `zone` parameter any string to be passed.
14 | This allows the user to enter zone names like `Europe/Zurich` instead of `+0100`.
15 | The reason we allow this is because offsets like `+0100` don't encode information about the
16 | daylight saving time, which is often needed.
17 |
18 | ## Usage examples
19 |
20 | The libsieve parses a sieve script into a tree. This tree can then be used to interpret its meaning.
21 |
22 | Two example will be provided: one basic and one more complex.
23 | ### Basic Example: Check if an extension is loaded
24 | In this first example, we will check if a specific extension was loaded through a require node:
25 |
26 | ```php
27 | parse($sieve);
45 | } catch (\Sieve\SieveException $se) {
46 | throw new \Exception("The provided sieve script is invalid!");
47 | }
48 |
49 | // we store the tree, because it contains all the information.
50 | $this->tree = $parser->GetParseTree();
51 | }
52 |
53 | /**
54 | * Checks if an extension is loaded.
55 | *
56 | * @param string $extension
57 | * @return bool
58 | */
59 | public function isLoaded(string $extension)
60 | {
61 | /** @var int $root_node_id */
62 | $root_node_id = $this->tree->getRoot();
63 | // The root is not a node, we can only access its children
64 | $children = $this->tree->getChildren($root_node_id);
65 | foreach ($children as $child) {
66 | // The child is an id to a node, which can be access using the following:
67 | $node = $this->tree->getNode($child);
68 |
69 | // is can be used to check the type of node.
70 | if ($node->is(\Sieve\SieveToken::IDENTIFIER) && $node->text === "require") {
71 | if ($this->checkChildren($this->tree->getChildren($child), $extension)) {
72 | return true;
73 | }
74 | }
75 | }
76 | return false;
77 | }
78 |
79 | /**
80 | * Checks the arguments given to a require node, to know if it includes
81 | *
82 | * @param $children
83 | * @param string $extension
84 | * @return bool
85 | */
86 | private function checkChildren($children, string $extension): bool
87 | {
88 | if (is_array($children)) {
89 | // it's a string list, let's loop over them.
90 | foreach ($children as $child) {
91 | if ($this->checkChildren($child, $extension)) {
92 | return true;
93 | }
94 | }
95 | return false;
96 | }
97 |
98 | $node = $this->tree->getNode($children);
99 | return $node->is(\Sieve\SieveToken::QUOTED_STRING) && $extension === trim($node->text, '"');
100 | }
101 | }
102 |
103 | // load a script, from the tests folder.
104 | $sieve = file_get_contents(__DIR__ . './tests/good/currentdate.siv');
105 |
106 | $runner = new ExtensionCheckExample($sieve);
107 | var_dump($runner->isLoaded("variables"));
108 | ```
109 |
110 | ### Complex Example: Dumping the tree
111 |
112 | This second example will print back the sieve script from a parsed tree (note this could simply be done through
113 | the method `$tree->dump()`).
114 |
115 | ```php
116 | parse($sieve);
135 | } catch (\Sieve\SieveException $se) {
136 | throw new \Exception("The provided sieve script is invalid!");
137 | }
138 |
139 | // we store the tree, because it contains all the information.
140 | $this->tree = $parser->GetParseTree();
141 | }
142 |
143 | /**
144 | * Displays the tree
145 | */
146 | public function display()
147 | {
148 | /** @var int $root_node_id */
149 | $root_node_id = $this->tree->getRoot();
150 | // The root is not a node, we can only access its children
151 | $children = $this->tree->getChildren($root_node_id);
152 | $this->displayNodeList($children);
153 | }
154 |
155 | /**
156 | * Loop over a list of nodes, and display them.
157 | *
158 | * @param int[] $nodes a list of node ids.
159 | * @param string $indent
160 | */
161 | private function displayNodeList(array $nodes, string $indent = '')
162 | {
163 | foreach ($nodes as $node) {
164 | $this->displayNode($node, $indent);
165 | }
166 | }
167 |
168 | /**
169 | * Display a node and its children.
170 | *
171 | * @param int $node_id the current node id.
172 | * @param string $indent
173 | */
174 | private function displayNode(int $node_id, string $indent)
175 | {
176 | /**
177 | * @var \Sieve\SieveToken $node can be used to get info about a specific node.
178 | */
179 | $node = $this->tree->getNode($node_id);
180 |
181 | // All the possible node types are listed as constants in the class SieveToken...
182 | switch ($node->type) {
183 | case \Sieve\SieveToken::SCRIPT_END:
184 | printf($indent . "EOS");
185 | break;
186 | case Sieve\SieveToken::WHITESPACE:
187 | case Sieve\SieveToken::COMMENT:
188 | break;
189 | default:
190 | // the $node->type is a integer. It can be turned into an explicit string this way...
191 | $type = \Sieve\SieveToken::typeString($node->type);
192 |
193 | $open_color = '';
194 | $end_color = '';
195 |
196 | // The type of a node can be checked with the is method. Mask can be used to match several types.
197 | if ($node->is(\Sieve\SieveToken::QUOTED_STRING | Sieve\SieveToken::MULTILINE_STRING)) {
198 | // we want to put a specific color arround strings...
199 | $open_color = "\e[1;32;47m";
200 | $end_color = "\e[0m";
201 | }
202 |
203 | // The children of a node can be obtain through this method:
204 | $children = $this->tree->getChildren($node_id);
205 |
206 | // do whatever you want with a node and its children :) Here we are going to display them.
207 | printf("[%4d, %-10.10s (%5d) ]%s ${open_color}%s$end_color" . PHP_EOL, $node->line, $type, $node->type, $indent, $node->text);
208 | $this->displayNodeList($children, $indent . "\t");
209 | }
210 | }
211 | }
212 |
213 | $sieve = file_get_contents(__DIR__ . '/tests/good/currentdate.siv');
214 |
215 | $parser = new UsageExample($sieve);
216 | $parser->display();
217 | ```
218 |
--------------------------------------------------------------------------------
/rfcs/rfc3894-degener-sieve-copy.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Network Working Group J. Degener
8 | Request for Comments: 3894 Sendmail, Inc.
9 | Category: Standards Track October 2004
10 |
11 |
12 | Sieve Extension: Copying Without Side Effects
13 |
14 | Status of this Memo
15 |
16 | This document specifies an Internet standards track protocol for the
17 | Internet community, and requests discussion and suggestions for
18 | improvements. Please refer to the current edition of the "Internet
19 | Official Protocol Standards" (STD 1) for the standardization state
20 | and status of this protocol. Distribution of this memo is unlimited.
21 |
22 | Copyright Notice
23 |
24 | Copyright (C) The Internet Society (2004).
25 |
26 | Abstract
27 |
28 | The Sieve scripting language allows users to control handling and
29 | disposal of their incoming e-mail. By default, an e-mail message
30 | that is processed by a Sieve script is saved in the owner's "inbox".
31 | Actions such as "fileinto" and "redirect" cancel this default
32 | behavior.
33 |
34 | This document defines a new keyword parameter, ":copy", to be used
35 | with the Sieve "fileinto" and "redirect" actions. Adding ":copy" to
36 | an action suppresses cancellation of the default "inbox" save. It
37 | allows users to add commands to an existing script without changing
38 | the meaning of the rest of the script.
39 |
40 | 1. Introduction
41 |
42 | The Sieve scripting language [SIEVE] allows users to control handling
43 | and disposal of their incoming e-mail. Two frequently used Sieve
44 | commands are "fileinto" (saving into a local message store, such as
45 | an IMAP server) and "redirect" (forwarding to another e-mail
46 | address). Both of these cancel the Sieve default behavior of saving
47 | into the user's "inbox".
48 |
49 | But some users have the notion of forwarding an extra copy of a
50 | message for safekeeping to another e-mail address, or of saving a
51 | copy in a folder - in addition to the regular message delivery, which
52 | shouldn't be affected by the copy.
53 |
54 |
55 |
56 |
57 |
58 | Degener Standards Track [Page 1]
59 |
60 | RFC 3894 Sieve Extension - Copy Without Side Effects October 2004
61 |
62 |
63 | If saving an extra copy is all the user wanted to do,
64 |
65 | fileinto "unfiltered";
66 | keep;
67 |
68 | would do the job. The "keep" command does explicitly what the
69 | cancelled default behavior did. But the explicit "keep" is a poor
70 | substitute for the implicit "keep" when more processing follows:
71 |
72 | fileinto "unfiltered";
73 | keep;
74 |
75 | if header "Subject" "MAKE MONEY FAST!!!"
76 | {
77 | discard;
78 | }
79 |
80 | In this example, the "discard" is ineffective against the explicit
81 | "keep"; the discarded message still ends up in the user's inbox.
82 |
83 | It is possible to generate Sieve code that perfectly expresses a
84 | user's wishes, but such code quickly grows unwieldy because it needs
85 | to keep track of the state that the implicit "keep" would have had
86 | without the "fileinto" or "redirect" command.
87 |
88 | This extension tries to make life easier for user interface designers
89 | and script writers by allowing them to express the "copy" semantics
90 | directly.
91 |
92 | 2. Conventions used
93 |
94 | Conventions for notations are as in [SIEVE] section 1.1, including
95 | use of [KEYWORDS] and "Syntax:" label for the definition of action
96 | and tagged arguments syntax.
97 |
98 | The capability string associated with extension defined in this
99 | document is "copy".
100 |
101 | 3. ":copy" extension to the "fileinto" and "redirect" commands
102 |
103 | Syntax:
104 | "fileinto" [":copy"]
105 | "redirect" [":copy"]
106 |
107 | If the optional ":copy" keyword is specified with "fileinto" or
108 | "redirect", the tagged command does not cancel the implicit "keep".
109 | Instead, it merely files or redirects a copy in addition to whatever
110 | else is happening to the message.
111 |
112 |
113 |
114 | Degener Standards Track [Page 2]
115 |
116 | RFC 3894 Sieve Extension - Copy Without Side Effects October 2004
117 |
118 |
119 | Example:
120 |
121 | require ["copy", "fileinto"];
122 | fileinto :copy "incoming";
123 |
124 | # ... more processing follows ...
125 |
126 | 4. Security Considerations
127 |
128 | The "copy" extension makes it easier to eavesdrop on a user's message
129 | stream without the user noticing. This was technically possible
130 | before if an attacker gained read/write access to a user's Sieve
131 | scripts, but now an attacker no longer needs to parse a script in
132 | order to modify it. Write access to Sieve scripts must be protected
133 | as strongly as read/write access to e-mail, for example by using
134 | secure directory protocols such as correctly parameterized LDAP over
135 | TLS [LDAP].
136 |
137 | Organizations that wish to monitor their users' e-mail traffic must
138 | familiarize themselves with local data protection laws before
139 | creating stores of old e-mail traffic without control, or perhaps
140 | even knowledge, of the sender or intended recipients.
141 |
142 | Organizations that legally use "redirect :copy" to eavesdrop on
143 | correspondence (for example, by keeping a log to answer questions
144 | about insider trading at a later time) can avoid future problems by
145 | setting users' privacy expectations correctly.
146 |
147 | 5. IANA Considerations
148 |
149 | The following template specifies the IANA registration of the "copy"
150 | Sieve extension specified in this document.
151 |
152 | To: iana@iana.org
153 | Subject: Registration of new Sieve extension
154 |
155 | Capability name: copy
156 | Capability keyword: copy
157 | Capability arguments: N/A
158 | Standards Track: RFC 3894
159 | Person and email address to contact for further information:
160 |
161 | Jutta Degener
162 | Sendmail, Inc.
163 | 6425 Christie Ave, 4th Floor
164 | Emeryville, CA 94608
165 |
166 | Email: jutta@sendmail.com
167 |
168 |
169 |
170 | Degener Standards Track [Page 3]
171 |
172 | RFC 3894 Sieve Extension - Copy Without Side Effects October 2004
173 |
174 |
175 | This information has been added to the list of Sieve extensions given
176 | on http://www.iana.org/assignments/sieve-extensions.
177 |
178 | 6. Acknowledgments
179 |
180 | Thanks to Eric Allman, Ned Freed, Will Lee, Nigel Swinson, and Rand
181 | Wacker for corrections and comments.
182 |
183 | 7. References
184 |
185 | 7.1. Normative References
186 |
187 | [KEYWORDS] Bradner, S., "Key words for use in RFCs to Indicate
188 | Requirement Levels", BCP 14, RFC 2119, March 1997.
189 |
190 | [SIEVE] Showalter, T., "Sieve: A Mail Filtering Language", RFC
191 | 3028, January 2001.
192 |
193 | 7.2. Informative References
194 |
195 | [LDAP] Wahl, M., Alvestrand, H., Hodges, J., and R. Morgan,
196 | "Authentication Methods for LDAP", RFC 2829, May 2000.
197 |
198 | Author's Address
199 |
200 | Jutta Degener
201 | Sendmail, Inc.
202 | 6425 Christie Ave, 4th Floor
203 | Emeryville, CA 94608
204 |
205 | EMail: jutta@sendmail.com
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 | Degener Standards Track [Page 4]
227 |
228 | RFC 3894 Sieve Extension - Copy Without Side Effects October 2004
229 |
230 |
231 | Full Copyright Statement
232 |
233 | Copyright (C) The Internet Society (2004).
234 |
235 | This document is subject to the rights, licenses and restrictions
236 | contained in BCP 78, and except as set forth therein, the authors
237 | retain all their rights.
238 |
239 | This document and the information contained herein are provided on an
240 | "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/S HE
241 | REPRESENTS OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE
242 | INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR
243 | IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF
244 | THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED
245 | WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
246 |
247 | Intellectual Property
248 |
249 | The IETF takes no position regarding the validity or scope of any
250 | Intellectual Property Rights or other rights that might be claimed to
251 | pertain to the implementation or use of the technology described in
252 | this document or the extent to which any license under such rights
253 | might or might not be available; nor does it represent that it has
254 | made any independent effort to identify any such rights. Information
255 | on the IETF's procedures with respect to rights in IETF Documents can
256 | be found in BCP 78 and BCP 79.
257 |
258 | Copies of IPR disclosures made to the IETF Secretariat and any
259 | assurances of licenses to be made available, or the result of an
260 | attempt made to obtain a general license or permission for the use of
261 | such proprietary rights by implementers or users of this
262 | specification can be obtained from the IETF on-line IPR repository at
263 | http://www.ietf.org/ipr.
264 |
265 | The IETF invites any interested party to bring to its attention any
266 | copyrights, patents or patent applications, or other proprietary
267 | rights that may cover technology that may be required to implement
268 | this standard. Please address the information to the IETF at ietf-
269 | ipr@ietf.org.
270 |
271 | Acknowledgement
272 |
273 | Funding for the RFC Editor function is currently provided by the
274 | Internet Society.
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 | Degener Standards Track [Page 5]
283 |
284 |
--------------------------------------------------------------------------------
/lib/SieveKeywordRegistry.php:
--------------------------------------------------------------------------------
1 | children() as $keyword) {
49 | switch ($keyword->getName()) {
50 | case 'matchtype':
51 | $type = &$this->matchTypes;
52 | break;
53 | case 'comparator':
54 | $type = &$this->comparators;
55 | break;
56 | case 'addresspart':
57 | $type = &$this->addressParts;
58 | break;
59 | case 'test':
60 | $type = &$this->tests;
61 | break;
62 | case 'command':
63 | $type = &$this->commands;
64 | break;
65 | default:
66 | trigger_error("Unsupported keyword type \"{$keyword->getName()}\" in file \"keywords.xml\"");
67 | return;
68 | }
69 |
70 | $name = (string) $keyword['name'];
71 | if (array_key_exists($name, $type)) {
72 | trigger_error("redefinition of array $name - skipping");
73 | } else {
74 | $type[$name] = $keyword->children();
75 | }
76 | }
77 |
78 | foreach (glob(__DIR__ . '/extensions/*.xml') as $file) {
79 | $extension = simplexml_load_file($file);
80 | $name = (string) $extension['name'];
81 |
82 | if ($extensionsEnabled !== null && !in_array($name, $extensionsEnabled, true)) {
83 | continue;
84 | }
85 |
86 | if (array_key_exists($name, $this->registry)) {
87 | trigger_error("overwriting extension \"$name\"");
88 | }
89 | $this->registry[$name] = $extension;
90 | }
91 |
92 | foreach ($customExtensions as $customExtension) {
93 | $extension = simplexml_load_file($customExtension);
94 | $name = (string) $extension['name'];
95 |
96 | if (array_key_exists($name, $this->registry)) {
97 | trigger_error("overwriting extension \"$name\"");
98 | }
99 | $this->registry[$name] = $extension;
100 | }
101 | }
102 |
103 | /**
104 | * Activates an extension.
105 | */
106 | public function activate(string $extension): void
107 | {
108 | if (!isset($this->registry[$extension]) // extension unknown
109 | || isset($this->loadedExtensions[$extension]) // already loaded
110 | ) {
111 | return;
112 | }
113 |
114 | $this->loadedExtensions[$extension] = true;
115 |
116 | // we can safely unset the required extension
117 | unset($this->requiredExtensions[$extension]);
118 |
119 | $xml = $this->registry[$extension];
120 |
121 | if (isset($xml['require'])) {
122 | $requireExtensions = explode(',', (string) $xml['require']);
123 | foreach ($requireExtensions as $require) {
124 | if ($require[0] !== '!') {
125 | if (!isset($this->loadedExtensions[$require])) {
126 | // $require is needed, but not yet loaded.
127 | $this->requiredExtensions[$require] = $this->requiredExtensions[$require] ?? [];
128 | $this->requiredExtensions[$require][] = $extension;
129 | }
130 | } else {
131 | $forbidden = ltrim($require, '!');
132 | $this->forbiddenExtensions[$forbidden] = $this->forbiddenExtensions[$forbidden] ?? [];
133 | $this->forbiddenExtensions[$forbidden][] = $extension;
134 | }
135 | }
136 | }
137 |
138 | foreach ($xml->children() as $e) {
139 | switch ($e->getName()) {
140 | case 'matchtype':
141 | $type = &$this->matchTypes;
142 | break;
143 | case 'comparator':
144 | $type = &$this->comparators;
145 | break;
146 | case 'addresspart':
147 | $type = &$this->addressParts;
148 | break;
149 | case 'test':
150 | $type = &$this->tests;
151 | break;
152 | case 'command':
153 | $type = &$this->commands;
154 | break;
155 | case 'tagged-argument':
156 | $xml = $e->parameter[0];
157 | $this->arguments[(string) $xml['name']] = [
158 | 'extends' => (string) $e['extends'],
159 | 'rules' => $xml,
160 | ];
161 | break;
162 | default:
163 | trigger_error('Unsupported extension type \'' .
164 | $e->getName() . "' in extension '$extension'");
165 | }
166 |
167 | $name = (string) $e['name'];
168 | if (!isset($type[$name]) || (string) $e['overrides'] === 'true') {
169 | $type[$name] = $e->children();
170 | }
171 | }
172 | }
173 |
174 | /**
175 | * Is test.
176 | */
177 | public function isTest(string $name): bool
178 | {
179 | return isset($this->tests[$name]);
180 | }
181 |
182 | /**
183 | * Is command.
184 | */
185 | public function isCommand(string $name): bool
186 | {
187 | return isset($this->commands[$name]);
188 | }
189 |
190 | public function matchType(string $name): mixed
191 | {
192 | return $this->matchTypes[$name] ?? null;
193 | }
194 |
195 | public function addressPart(string $name): mixed
196 | {
197 | return $this->addressParts[$name] ?? null;
198 | }
199 |
200 | /**
201 | * Get comparator.
202 | */
203 | public function comparator(string $name): mixed
204 | {
205 | return $this->comparators[$name] ?? null;
206 | }
207 |
208 | /**
209 | * Get test.
210 | */
211 | public function test($name): mixed
212 | {
213 | return $this->tests[$name] ?? null;
214 | }
215 |
216 | /**
217 | * Get command.
218 | */
219 | public function command($name): mixed
220 | {
221 | return $this->commands[$name] ?? null;
222 | }
223 |
224 | /**
225 | * Get arguments.
226 | *
227 | * @return SimpleXMLElement[]
228 | */
229 | public function arguments(string $command): array
230 | {
231 | $res = [];
232 | foreach ($this->arguments as $arg) {
233 | if (preg_match('/' . $arg['extends'] . '/', $command)) {
234 | $res[] = $arg['rules'];
235 | }
236 | }
237 |
238 | return $res;
239 | }
240 |
241 | /**
242 | * Get (single) argument.
243 | */
244 | public function argument(string $name): mixed
245 | {
246 | return $this->arguments[$name]['rules'] ?? null;
247 | }
248 |
249 | /**
250 | * Get require strings.
251 | *
252 | * @return string[]
253 | */
254 | public function requireStrings(): array
255 | {
256 | return array_keys($this->registry);
257 | }
258 |
259 | /**
260 | * Get match types.
261 | *
262 | * @return string[]
263 | */
264 | public function matchTypes(): array
265 | {
266 | return array_keys($this->matchTypes);
267 | }
268 |
269 | /**
270 | * Get comparators.
271 | *
272 | * @return string[]
273 | */
274 | public function comparators(): array
275 | {
276 | return array_keys($this->comparators);
277 | }
278 |
279 | /**
280 | * Get address parts.
281 | *
282 | * @return string[]
283 | */
284 | public function addressParts(): array
285 | {
286 | return array_keys($this->addressParts);
287 | }
288 |
289 | /**
290 | * Get tests.
291 | *
292 | * @return string[]
293 | */
294 | public function tests(): array
295 | {
296 | return array_keys($this->tests);
297 | }
298 |
299 | /**
300 | * Get commands.
301 | *
302 | * @return string[]
303 | */
304 | public function commands(): array
305 | {
306 | return array_keys($this->commands);
307 | }
308 |
309 | /**
310 | * Validate requires.
311 | *
312 | * @throws SieveException if invalid
313 | */
314 | public function validateRequires(SieveToken $sieveToken): void
315 | {
316 | $message = "Extensions requirement are not fulfilled: \n";
317 | $error = false;
318 | if (count($this->requiredExtensions)) {
319 | $error = true;
320 | foreach ($this->requiredExtensions as $required => $by) {
321 | $message .= "Extension `$required` is required by `" . implode(', ', $by) . "`.\n";
322 | }
323 | }
324 |
325 | foreach ($this->forbiddenExtensions as $forbiddenExtension => $by) {
326 | if (!empty($this->loadedExtensions[$forbiddenExtension])) {
327 | $error = true;
328 | $message .= "Extension $forbiddenExtension cannot be loaded together with " .
329 | implode(', ', $by) . ".\n";
330 | }
331 | }
332 |
333 | if ($error) {
334 | throw new SieveException($sieveToken, $message);
335 | }
336 | }
337 | }
338 |
--------------------------------------------------------------------------------
/lib/SieveParser.php:
--------------------------------------------------------------------------------
1 | registry = new SieveKeywordRegistry($extensionsEnabled, $customExtensions);
23 | $this->extensionsEnabled = $extensionsEnabled;
24 | $this->customExtensions = $customExtensions;
25 | }
26 |
27 | /**
28 | * Get parsed tree.
29 | *
30 | * @throws Exception
31 | */
32 | public function getParseTree(): SieveTree
33 | {
34 | if ($this->tree === null) {
35 | throw new Exception('Tree not initialized');
36 | }
37 |
38 | return $this->tree;
39 | }
40 |
41 | /**
42 | * Dump parse tree.
43 | */
44 | public function dumpParseTree(): string
45 | {
46 | return $this->tree->dump();
47 | }
48 |
49 | /**
50 | * Get script text.
51 | */
52 | public function getScriptText(): string
53 | {
54 | return $this->tree->getText();
55 | }
56 |
57 | /**
58 | * Get previous token.
59 | */
60 | protected function getPrevToken(int $parentId): ?SieveToken
61 | {
62 | $children = $this->tree->getChildren($parentId);
63 |
64 | for ($i = count($children); $i > 0; --$i) {
65 | $prev = $this->tree->getNode($children[$i - 1]);
66 | if ($prev->is(SieveToken::COMMENT | SieveToken::WHITESPACE)) {
67 | continue;
68 | }
69 |
70 | // use command owning a block or list instead of previous
71 | if ($prev->is(SieveToken::BLOCK_START | SieveToken::COMMA | SieveToken::LEFT_PARENTHESIS)) {
72 | $prev = $this->tree->getNode($parentId);
73 | }
74 |
75 | return $prev;
76 | }
77 |
78 | return $this->tree->getNode($parentId);
79 | }
80 |
81 | /*******************************************************************************
82 | * methods for recursive descent start below
83 | */
84 |
85 | /**
86 | * PassThrough whitespace comment.
87 | */
88 | public function passThroughWhitespaceComment(SieveToken $token): void
89 | {
90 | if ($token->is(SieveToken::WHITESPACE)) {
91 | $this->tree->addChild($token);
92 | } elseif ($token->is(SieveToken::COMMENT)) {
93 | /** @var ?SieveToken $parent */
94 |
95 | $parentId = $this->tree->getLastId();
96 | do {
97 | $parentId = $this->tree->getParent($parentId) ?? 0;
98 | $parent = $this->tree->getNode($parentId);
99 | } while (isset($parent) &&
100 | $parent->is(
101 | SieveToken::WHITESPACE | SieveToken::COMMENT | SieveToken::BLOCK_END | SieveToken::SEMICOLON
102 | )
103 | );
104 |
105 | if (isset($parent)) {
106 | $this->tree->addChildTo($parentId, $token);
107 | } else {
108 | $this->tree->addChild($token);
109 | }
110 | }
111 | }
112 |
113 | /**
114 | * Passthrough function.
115 | */
116 | public function passThroughFunction(SieveToken $token): void
117 | {
118 | $this->tree->addChild($token);
119 | }
120 |
121 | /**
122 | * Parse the given script.
123 | *
124 | * @throws SieveException
125 | */
126 | public function parse(string $script): void
127 | {
128 | // we reset the registry
129 | $this->registry = new SieveKeywordRegistry($this->extensionsEnabled, $this->customExtensions);
130 |
131 | $this->script = $script;
132 |
133 | $this->scanner = new SieveScanner($this->script);
134 |
135 | // Define what happens with passthrough tokens like whitespaces and comments
136 | $this->scanner->setPassthroughFunc([$this, 'passThroughWhitespaceComment']);
137 |
138 | $this->tree = new SieveTree('tree');
139 | $this->commands($this->tree->getRoot());
140 |
141 | if (!$this->scanner->currentTokenIs(SieveToken::SCRIPT_END)) {
142 | $token = $this->scanner->nextToken();
143 | throw new SieveException($token, SieveToken::SCRIPT_END);
144 | }
145 | }
146 |
147 | /**
148 | * Get and check command token.
149 | *
150 | * @throws SieveException
151 | */
152 | protected function commands(int $parentId): void
153 | {
154 | while (true) {
155 | if (!$this->scanner->nextTokenIs(SieveToken::IDENTIFIER)) {
156 | break;
157 | }
158 |
159 | // Get and check a command token
160 | $token = $this->scanner->nextToken();
161 | $semantics = new SieveSemantics($this->registry, $token, $this->getPrevToken($parentId));
162 |
163 | // Process eventual arguments
164 | $thisNode = $this->tree->addChildTo($parentId, $token);
165 | $this->arguments($thisNode, $semantics);
166 |
167 | $token = $this->scanner->nextToken();
168 | if (!$token->is(SieveToken::SEMICOLON)) {
169 | // TODO: check if/when semcheck is needed here
170 | $semantics->validateToken($token);
171 |
172 | if ($token->is(SieveToken::BLOCK_START)) {
173 | $this->tree->addChildTo($thisNode, $token);
174 | $this->block($thisNode);
175 | continue;
176 | }
177 |
178 | throw new SieveException($token, SieveToken::SEMICOLON);
179 | }
180 |
181 | $semantics->done($token);
182 | $this->tree->addChildTo($thisNode, $token);
183 | }
184 | if ($this->scanner->nextTokenIs(SieveToken::SCRIPT_END)) {
185 | $this->scanner->nextToken(); // attach comment to ScriptEnd
186 | $this->done();
187 | }
188 | }
189 |
190 | /**
191 | * Process arguments.
192 | *
193 | * @throws SieveException
194 | */
195 | protected function arguments(int $parentId, SieveSemantics $semantics): void
196 | {
197 | while (true) {
198 | if ($this->scanner->nextTokenIs(SieveToken::NUMBER | SieveToken::TAG)) {
199 | // Check if semantics allow a number or tag
200 | $token = $this->scanner->nextToken();
201 | $semantics->validateToken($token);
202 | $this->tree->addChildTo($parentId, $token);
203 | } elseif ($this->scanner->nextTokenIs(SieveToken::STRING_LIST)) {
204 | $this->stringList($parentId, $semantics);
205 | } else {
206 | break;
207 | }
208 | }
209 |
210 | if ($this->scanner->nextTokenIs(SieveToken::TEST_LIST)) {
211 | $this->testList($parentId, $semantics);
212 | }
213 | }
214 |
215 | /**
216 | * Parses a string list.
217 | *
218 | * @throws SieveException
219 | */
220 | protected function stringList(int $parentId, SieveSemantics $semantics): void
221 | {
222 | if (!$this->scanner->nextTokenIs(SieveToken::LEFT_BRACKET)) {
223 | $this->string($parentId, $semantics);
224 |
225 | return;
226 | }
227 |
228 | $token = $this->scanner->nextToken();
229 | $semantics->startStringList($token);
230 | $this->tree->addChildTo($parentId, $token);
231 |
232 | if ($this->scanner->nextTokenIs(SieveToken::RIGHT_BRACKET)) {
233 | //allow empty lists
234 | $token = $this->scanner->nextToken();
235 | $this->tree->addChildTo($parentId, $token);
236 | $semantics->endStringList();
237 |
238 | return;
239 | }
240 |
241 | do {
242 | $this->string($parentId, $semantics);
243 | $token = $this->scanner->nextToken();
244 |
245 | if (!$token->is(SieveToken::COMMA | SieveToken::RIGHT_BRACKET)) {
246 | throw new SieveException($token, [SieveToken::COMMA, SieveToken::RIGHT_BRACKET]);
247 | }
248 | if ($token->is(SieveToken::COMMA)) {
249 | $semantics->continueStringList();
250 | }
251 |
252 | $this->tree->addChildTo($parentId, $token);
253 | } while (!$token->is(SieveToken::RIGHT_BRACKET));
254 |
255 | $semantics->endStringList();
256 | }
257 |
258 | /**
259 | * Processes a string.
260 | *
261 | * @throws SieveException
262 | */
263 | protected function string(int $parentId, SieveSemantics $semantics): void
264 | {
265 | $token = $this->scanner->nextToken();
266 | $semantics->validateToken($token);
267 | $this->tree->addChildTo($parentId, $token);
268 | }
269 |
270 | /**
271 | * Process a test list.
272 | *
273 | * @throws SieveException
274 | */
275 | protected function testList(int $parentId, SieveSemantics $semantics): void
276 | {
277 | if (!$this->scanner->nextTokenIs(SieveToken::LEFT_PARENTHESIS)) {
278 | $this->test($parentId, $semantics);
279 |
280 | return;
281 | }
282 |
283 | $token = $this->scanner->nextToken();
284 | $semantics->validateToken($token);
285 | $this->tree->addChildTo($parentId, $token);
286 |
287 | do {
288 | $this->test($parentId, $semantics);
289 |
290 | $token = $this->scanner->nextToken();
291 | if (!$token->is(SieveToken::COMMA | SieveToken::RIGHT_PARENTHESIS)) {
292 | throw new SieveException($token, [SieveToken::COMMA, SieveToken::RIGHT_PARENTHESIS]);
293 | }
294 | $this->tree->addChildTo($parentId, $token);
295 | } while (!$token->is(SieveToken::RIGHT_PARENTHESIS));
296 | }
297 |
298 | /**
299 | * Process a test.
300 | *
301 | * @throws SieveException
302 | */
303 | protected function test(int $parentId, SieveSemantics $semantics): void
304 | {
305 | // Check if semantics allow an identifier
306 | $token = $this->scanner->nextToken();
307 | $semantics->validateToken($token);
308 |
309 | // Get semantics for this test command
310 | $thisSemantics = new SieveSemantics($this->registry, $token, $this->getPrevToken($parentId));
311 | $thisNode = $this->tree->addChildTo($parentId, $token);
312 |
313 | // Consume eventual argument tokens
314 | $this->arguments($thisNode, $thisSemantics);
315 |
316 | // Check that all required arguments were there
317 | $token = $this->scanner->peekNextToken();
318 | $thisSemantics->done($token);
319 | }
320 |
321 | /**
322 | * Process a block.
323 | *
324 | * @throws SieveException
325 | */
326 | protected function block(int $parentId): void
327 | {
328 | $this->commands($parentId);
329 |
330 | if ($this->scanner->currentTokenIs(SieveToken::SCRIPT_END)) {
331 | throw new SieveException($this->scanner->getCurrentToken(), SieveToken::BLOCK_END);
332 | }
333 | $token = $this->scanner->nextToken();
334 | if (!$token->is(SieveToken::BLOCK_END)) {
335 | throw new SieveException($token, SieveToken::BLOCK_END);
336 | }
337 | $this->tree->addChildTo($parentId, $token);
338 | }
339 |
340 | /**
341 | * Process the last block.
342 | *
343 | * @throws SieveException
344 | */
345 | protected function done(): void
346 | {
347 | $this->registry->validateRequires($this->scanner->getCurrentToken());
348 | }
349 | }
350 |
--------------------------------------------------------------------------------
/rfcs/rfc5233-murchison-sieve-subaddress.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Network Working Group K. Murchison
8 | Request for Comments: 5233 Carnegie Mellon University
9 | Obsoletes: 3598 January 2008
10 | Category: Standards Track
11 |
12 |
13 | Sieve Email Filtering: Subaddress Extension
14 |
15 | Status of This Memo
16 |
17 | This document specifies an Internet standards track protocol for the
18 | Internet community, and requests discussion and suggestions for
19 | improvements. Please refer to the current edition of the "Internet
20 | Official Protocol Standards" (STD 1) for the standardization state
21 | and status of this protocol. Distribution of this memo is unlimited.
22 |
23 | Abstract
24 |
25 | On email systems that allow for 'subaddressing' or 'detailed
26 | addressing' (e.g., "ken+sieve@example.org"), it is sometimes
27 | desirable to make comparisons against these sub-parts of addresses.
28 | This document defines an extension to the Sieve Email Filtering
29 | Language that allows users to compare against the user and detail
30 | sub-parts of an address.
31 |
32 | Table of Contents
33 |
34 | 1. Introduction ....................................................2
35 | 2. Conventions Used in This Document ...............................2
36 | 3. Capability Identifier ...........................................2
37 | 4. Subaddress Comparisons ..........................................2
38 | 5. IANA Considerations .............................................5
39 | 6. Security Considerations .........................................5
40 | 7. Normative References ............................................5
41 | Appendix A. Acknowledgments ........................................6
42 | Appendix B. Changes since RFC 3598 .................................6
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | Murchison Standards Track [Page 1]
59 |
60 | RFC 5233 Sieve: Subaddress Extension January 2008
61 |
62 |
63 | 1. Introduction
64 |
65 | Subaddressing is the practice of augmenting the local-part of an
66 | [RFC2822] address with some 'detail' information in order to give
67 | some extra meaning to that address. One common way of encoding
68 | 'detail' information into the local-part is to add a 'separator
69 | character sequence', such as "+", to form a boundary between the
70 | 'user' (original local-part) and 'detail' sub-parts of the address,
71 | much like the "@" character forms the boundary between the local-part
72 | and domain.
73 |
74 | Typical uses of subaddressing might be:
75 |
76 | o A message addressed to "ken+sieve@example.org" is delivered into a
77 | mailbox called "sieve" belonging to the user "ken".
78 |
79 | o A message addressed to "5551212#123@example.com" is delivered to
80 | the voice mailbox number "123" at phone number "5551212".
81 |
82 | This document describes an extension to the Sieve language defined by
83 | [RFC5228] for comparing against the 'user' and 'detail' sub-parts of
84 | an address.
85 |
86 | 2. Conventions Used in This Document
87 |
88 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
89 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
90 | document are to be interpreted as described in [RFC2119].
91 |
92 | 3. Capability Identifier
93 |
94 | The capability string associated with the extension defined in this
95 | document is "subaddress".
96 |
97 | 4. Subaddress Comparisons
98 |
99 | Test commands that act exclusively on addresses may take the optional
100 | tagged arguments ":user" and ":detail" to specify what sub-part of
101 | the local-part of the address will be acted upon.
102 |
103 | NOTE: In most cases, the envelope "to" address is the preferred
104 | address to examine for subaddress information when the desire is
105 | to sort messages based on how they were addressed so as to get to
106 | a specific recipient. The envelope address is, after all, the
107 | reason a given message is being processed by a given sieve script
108 | for a given user. This is particularly true when mailing lists,
109 |
110 |
111 |
112 |
113 |
114 | Murchison Standards Track [Page 2]
115 |
116 | RFC 5233 Sieve: Subaddress Extension January 2008
117 |
118 |
119 | aliases, and 'virtual domains' are involved since the envelope may
120 | be the only source of detail information for the specific
121 | recipient.
122 |
123 | NOTE: Because the encoding of detailed addresses are site and/or
124 | implementation specific, using the subaddress extension on foreign
125 | addresses (such as the envelope "from" address or originator
126 | header fields) may lead to inconsistent or incorrect results.
127 |
128 | The ":user" argument specifies the user sub-part of the local-part of
129 | an address. If the address is not encoded to contain a detail sub-
130 | part, then ":user" specifies the entire left side of the address
131 | (equivalent to ":localpart").
132 |
133 | The ":detail" argument specifies the detail sub-part of the local-
134 | part of an address. If the address is not encoded to contain a
135 | detail sub-part, then the address fails to match any of the specified
136 | keys. If a zero-length string is encoded as the detail sub-part,
137 | then ":detail" resolves to the empty value ("").
138 |
139 | NOTE: If the encoding method used for detailed addresses utilizes
140 | a separator character sequence, and the separator character
141 | sequence occurs more than once in the local-part, then the logic
142 | used to split the address is implementation-defined and is usually
143 | dependent on the format used by the encompassing mail system.
144 |
145 | Implementations MUST make sure that the encoding method used for
146 | detailed addresses matches that which is used and/or allowed by the
147 | encompassing mail system, otherwise unexpected results might occur.
148 | Note that the mechanisms used to define and/or query the encoding
149 | method used by the mail system are outside the scope of this
150 | document.
151 |
152 | The ":user" and ":detail" address parts are subject to the same rules
153 | and restrictions as the standard address parts defined in [RFC5228],
154 | Section 2.7.4.
155 |
156 | For convenience, the "ADDRESS-PART" syntax element defined in
157 | [RFC5228], Section 2.7.4, is augmented here as follows:
158 |
159 | ADDRESS-PART =/ ":user" / ":detail"
160 |
161 | A diagram showing the ADDRESS-PARTs of an email address where the
162 | detail information follows a separator character sequence of "+" is
163 | shown below:
164 |
165 |
166 |
167 |
168 |
169 |
170 | Murchison Standards Track [Page 3]
171 |
172 | RFC 5233 Sieve: Subaddress Extension January 2008
173 |
174 |
175 | :user "+" :detail "@" :domain
176 | \-----------------/
177 | :local-part
178 |
179 | A diagram showing the ADDRESS-PARTs of a email address where the
180 | detail information precedes a separator character sequence of "--" is
181 | shown below:
182 |
183 | :detail "--" :user "@" :domain
184 | \------------------/
185 | :local-part
186 |
187 | Example (where the detail information follows "+"):
188 |
189 | require ["envelope", "subaddress", "fileinto"];
190 |
191 | # In this example the same user account receives mail for both
192 | # "ken@example.com" and "postmaster@example.com"
193 |
194 | # File all messages to postmaster into a single mailbox,
195 | # ignoring the :detail part.
196 | if envelope :user "to" "postmaster" {
197 | fileinto "inbox.postmaster";
198 | stop;
199 | }
200 |
201 | # File mailing list messages (subscribed as "ken+mta-filters").
202 | if envelope :detail "to" "mta-filters" {
203 | fileinto "inbox.ietf-mta-filters";
204 | }
205 |
206 | # Redirect all mail sent to "ken+foo".
207 | if envelope :detail "to" "foo" {
208 | redirect "ken@example.net";
209 | }
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 | Murchison Standards Track [Page 4]
227 |
228 | RFC 5233 Sieve: Subaddress Extension January 2008
229 |
230 |
231 | 5. IANA Considerations
232 |
233 | The following template specifies the IANA registration of the
234 | subaddress Sieve extension specified in this document. This
235 | registration replaces that from RFC 3598:
236 |
237 | To: iana@iana.org
238 | Subject: Registration of new Sieve extension
239 |
240 | Capability name: subaddress
241 | Description: Adds the ':user' and ':detail' address parts
242 | for use with the address and envelope tests
243 | RFC number: RFC 5233
244 | Contact address: The Sieve discussion list
245 |
246 | This information has been added to the list of Sieve extensions given
247 | on http://www.iana.org/assignments/sieve-extensions.
248 |
249 | 6. Security Considerations
250 |
251 | Security considerations are discussed in [RFC5228]. It is believed
252 | that this extension does not introduce any additional security
253 | concerns.
254 |
255 | 7. Normative References
256 |
257 | [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
258 | Requirement Levels", BCP 14, RFC 2119, March 1997.
259 |
260 | [RFC2822] Resnick, P., "Internet Message Format", RFC 2822, April
261 | 2001.
262 |
263 | [RFC5228] Guenther, P., Ed., and T. Showalter, Ed., "Sieve: An Email
264 | Filtering Language", RFC 5228, January 2008.
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 | Murchison Standards Track [Page 5]
283 |
284 | RFC 5233 Sieve: Subaddress Extension January 2008
285 |
286 |
287 | Appendix A. Acknowledgments
288 |
289 | Thanks to Tim Showalter, Alexey Melnikov, Michael Salmon, Randall
290 | Gellens, Philip Guenther, Jutta Degener, Michael Haardt, Ned Freed,
291 | Mark Mallett, and Barry Leiba for their help with this document.
292 |
293 | Appendix B. Changes since RFC 3598
294 |
295 | o Discussion of how the user and detail information is encoded now
296 | uses generic language.
297 |
298 | o Added note detailing that this extension is most useful when used
299 | on the envelope "to" address.
300 |
301 | o Added note detailing that this extension isn't very useful on
302 | foreign addresses (envelope "from" or originator header fields).
303 |
304 | o Fixed envelope test example to only use "to" address.
305 |
306 | o Replaced ":user" example with one that doesn't produce unexpected
307 | behavior.
308 |
309 | o Refer to the zero-length string ("") as "empty" instead of "null"
310 | (per RFC 5228).
311 |
312 | o Use only RFC 2606 domains in examples.
313 |
314 | o Miscellaneous editorial changes.
315 |
316 | Author's Address
317 |
318 | Kenneth Murchison
319 | Carnegie Mellon University
320 | 5000 Forbes Avenue
321 | Cyert Hall 285
322 | Pittsburgh, PA 15213
323 | USA
324 |
325 | Phone: +1 412 268 2638
326 | EMail: murch@andrew.cmu.edu
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 | Murchison Standards Track [Page 6]
339 |
340 | RFC 5233 Sieve: Subaddress Extension January 2008
341 |
342 |
343 | Full Copyright Statement
344 |
345 | Copyright (C) The IETF Trust (2008).
346 |
347 | This document is subject to the rights, licenses and restrictions
348 | contained in BCP 78, and except as set forth therein, the authors
349 | retain all their rights.
350 |
351 | This document and the information contained herein are provided on an
352 | "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS
353 | OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY, THE IETF TRUST AND
354 | THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS
355 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF
356 | THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED
357 | WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
358 |
359 | Intellectual Property
360 |
361 | The IETF takes no position regarding the validity or scope of any
362 | Intellectual Property Rights or other rights that might be claimed to
363 | pertain to the implementation or use of the technology described in
364 | this document or the extent to which any license under such rights
365 | might or might not be available; nor does it represent that it has
366 | made any independent effort to identify any such rights. Information
367 | on the procedures with respect to rights in RFC documents can be
368 | found in BCP 78 and BCP 79.
369 |
370 | Copies of IPR disclosures made to the IETF Secretariat and any
371 | assurances of licenses to be made available, or the result of an
372 | attempt made to obtain a general license or permission for the use of
373 | such proprietary rights by implementers or users of this
374 | specification can be obtained from the IETF on-line IPR repository at
375 | http://www.ietf.org/ipr.
376 |
377 | The IETF invites any interested party to bring to its attention any
378 | copyrights, patents or patent applications, or other proprietary
379 | rights that may cover technology that may be required to implement
380 | this standard. Please address the information to the IETF at
381 | ietf-ipr@ietf.org.
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 | Murchison Standards Track [Page 7]
395 |
396 |
--------------------------------------------------------------------------------
/rfcs/draft-melnikov-sieve-imapflags-03.txt:
--------------------------------------------------------------------------------
1 | Network Working Group
2 | Internet Draft: Sieve -- IMAP flag Extension A. Melnikov
3 | Document: draft-melnikov-sieve-imapflags-03.txt Messaging Direct, Ltd.
4 | Expires: January 2001 July 2000
5 |
6 |
7 | Sieve -- IMAP flag Extension
8 |
9 |
10 | Status of this memo
11 |
12 | This document is an Internet-Draft and is in full conformance with
13 | all provisions of Section 10 of RFC2026. Internet-Drafts are
14 | working documents of the Internet Engineering Task Force (IETF), its
15 | areas, and its working groups. Note that other groups may also
16 | distribute working documents as Internet-Drafts.
17 |
18 | Internet-Drafts are draft documents valid for a maximum of six
19 | months and may be updated, replaced, or obsoleted by other documents
20 | at any time. It is inappropriate to use Internet- Drafts as
21 | reference material or to cite them other than as "work in progress."
22 |
23 |
24 | The list of current Internet-Drafts can be accessed at
25 | http://www.ietf.org/ietf/1id-abstracts.txt
26 |
27 | The list of Internet-Draft Shadow Directories can be accessed at
28 | http://www.ietf.org/shadow.html.
29 |
30 | The protocol discussed in this document is experimental and subject
31 | to change. Persons planning on either implementing or using this
32 | protocol are STRONGLY URGED to get in touch with the author before
33 | embarking on such a project.
34 |
35 | Copyright
36 |
37 | Copyright (C) The Internet Society 2000. All Rights Reserved.
38 |
39 | Abstract
40 |
41 | Recent discussions have shown that it is desirable to set
42 | different [IMAP] flags on message delivery. This can be done, for
43 | example, by a SIEVE interpreter that works as a part of a Mail Delivery
44 | Agent.
45 |
46 | This document describes an extension to the Sieve mail filtering
47 | language for setting [IMAP] flags. The extension allows to set both
48 | [IMAP] system flags and [IMAP] keywords.
49 |
50 |
51 | 0. Meta-information on this draft
52 |
53 | This information is intended to facilitate discussion. It will be
54 | removed when this document leaves the Internet-Draft stage.
55 |
56 |
57 | 0.1. Discussion
58 |
59 | This draft is intended to be compared with the Sieve mail filtering
60 | language, an Internet-Draft being discussed on the MTA Filters
61 | mailing list at . Subscription requests
62 | can be sent to (send an email
63 | message with the word "subscribe" in the body). More information on
64 | the mailing list along with a WWW archive of back messages is
65 | available at .
66 |
67 |
68 | 0.2. Changes from the version submitted to the SIEVE mailing list
69 |
70 | 1. Added addflag and removeflag actions
71 |
72 | 2. Changed the semantics of setflag (setflag is not additive any more)
73 |
74 | 3. Corrected section "Interaction with Other Sieve Actions".
75 | Removed incorrect reference to the forward action as to an
76 | action that prohibits setflag.
77 |
78 | 4. Added paragraph about the mutual order of fileinto/keep and
79 | setflag/addflag/removeflag actions.
80 |
81 |
82 | 0.3. Changes from the revision 00
83 |
84 | 1. Corrected Capability Identifier section (Section 2)
85 |
86 | 2. Corrected "Interaction with Other Sieve Actions" section (Section 4)
87 |
88 | 3. Examples were updated to be compatible with Sieve-07 draft
89 |
90 | 4. Added "mark" and "unmark" actions
91 |
92 |
93 | 0.4. Changes from the revision 01
94 |
95 | 1. Some language fixes based on Tony Hansen comments
96 |
97 | 2. Clarified that the extension allows to set both IMAP System Flags and Keywords
98 |
99 |
100 | 0.5. Changes from the revision 02
101 |
102 | 1. BugFix: all backslashes must be escaped
103 |
104 | 2. Added extended example and more detailed description of addflag/removeflag additivity.
105 |
106 | 3. Minor example bugfixes
107 |
108 |
109 | 1. Introduction
110 |
111 | This is an extension to the Sieve language defined by [SIEVE] for
112 | setting [IMAP] flags. It defines several new actions "setflag",
113 | "addflag", "removeflag", "mark" and "unmark".
114 |
115 | This document doesn't dictate how the SIEVE interpreter will set the [IMAP]
116 | flags. In particular, the SIEVE interpreter may work as an IMAP client,
117 | or may have direct access to the mailstore.
118 |
119 | SIEVE interpreters that don't support integration with IMAP
120 | SHOULD ignore this extension.
121 |
122 | Conventions for notations are as in [SIEVE] section 1.1, including
123 | use of [KEYWORDS].
124 |
125 |
126 | 2. Capability Identifier
127 |
128 | The capability string associated with extension defined in this document
129 | is "imapflags".
130 |
131 |
132 | 3. Actions
133 |
134 | All actions described in this specification (setflag, addflag, removeflag,
135 | mark, unmark) operate on an internal variable that contains the set of [IMAP] flags
136 | associated with the message being delivered. When the interpreter starts executing
137 | a script this variable contains an empty set. The 'addflag' action adds flags
138 | to the existing set. The 'removeflag' action removes flags from the existing set.
139 | The 'setflag' action replaces the existing set of flags with a new set.
140 | Whenever the interpreter encounters a 'fileinto' or 'keep' action it files
141 | the message with the current set of flags.
142 |
143 |
144 | 3.1. Setflag Action
145 |
146 | Syntax: setflag
147 |
148 | Setflag is used for setting [IMAP] system flags or keywords. Setflag
149 | replaces any previously set flags. It should be used together with keep
150 | or fileinto. It MUST be ignored if mailstore or target mailbox doesn't
151 | support the storing of any flags.
152 |
153 | Flags can be set only for the message that is currently being processed by
154 | SIEVE. When called with keep, setflag sets flags in the user's main
155 | mailbox. When called with fileinto, setflag sets flags in the
156 | mailbox indicated by the parameter.
157 |
158 | The order of setflag/fileinto or setflag/keep is important in the
159 | script. Any setflag action applies only to subsequent fileinto/keep
160 | actions in a script till next occurence of setflag/addflag/removeflag/mark/unmark.
161 |
162 | Server MUST ignore all flags that it can't store permanently. This
163 | means, in particular, that if the user's main mailbox can't store any
164 | flags, then the following SIEVE script produces no actions
165 |
166 | Example: if size :over 500K {
167 | setflag "\\Deleted";
168 | }
169 |
170 | A more substantial example is:
171 |
172 | Example:
173 | if header :contains "from" "boss@frobnitzm.edu" {
174 | setflag "\\Flagged";
175 | fileinto "INBOX.From Boss";
176 | }
177 |
178 |
179 | 3.2. Addflag action
180 |
181 | Syntax: addflag
182 |
183 | Addflag is used for setting [IMAP] flags. However unlike setflag it
184 | doesn't replace any previously set flags. This means that multiple
185 | occurrences of addflag are treated additively.
186 |
187 | For example, the following two actions
188 |
189 | addflag "\\Deleted";
190 | addflag "\\Answered";
191 |
192 | produce the same result as the single action
193 |
194 | addflag ["\\Deleted", "\\Answered"];
195 |
196 | In all other respects addflag behaves the same way as
197 | setflag.
198 |
199 |
200 | 3.3. Removeflag Action
201 |
202 | Syntax: removeflag
203 |
204 | Removeflag is used for setting [IMAP] flags. Removeflag clears
205 | flags previously set by setflag/addflag. Calling removeflag with a
206 | flag that wasn't set before is not an error and is ignored.
207 | Multiple occurrences of removeflag are treated additively.
208 |
209 | In all other respects removeflag behaves the same way as
210 | setflag.
211 |
212 | Example:
213 | if header :contains "Disposition-Notification-To" "mel@example.com" {
214 | addflag "$MDNRequired";
215 | }
216 | if header :contains "from" "imap@cac.washington.edu" {
217 | removeflag "$MDNRequired";
218 | fileinto "INBOX.imap-list";
219 | }
220 |
221 |
222 | 3.4. Mark and Unmark Actions
223 |
224 | Syntax: mark
225 |
226 | Syntax: unmark
227 |
228 | The mark action allows a message to be marked as urgent. Implementers are free
229 | to choose any flag or any combination of [IMAP] flags, however it is
230 | RECOMMENDED that the [IMAP] \Flagged flag be used. The mark action is
231 | semantically equivalent to 'addflag "\\Flagged"'.
232 |
233 | The unmark action allows the flag previously set by the Mark
234 | action to be unset. Unmark SHOULD at least clear the [IMAP] \Flagged flag
235 | and MUST clear all flags that could be added with mark.
236 | Unmark MAY clear other flags as well. The unmark action is semantically
237 | equivalent to 'removeflag "\\Flagged"'.
238 |
239 |
240 | 3.5 Extended example
241 |
242 | #
243 | # Example Sieve Filter
244 | # Declare any optional features or extension used by the script
245 | #
246 | require ["fileinto", "reject", "imapflags"];
247 |
248 | #
249 | # Reject any large messages
250 | #
251 | if size :over 1M
252 | {
253 | if header :is "From" "boss@company.com"
254 | {
255 | addflag "\\Flagged $Big";
256 | # The message will be marked as "\Flagged $Big" when filed into mailbox "Big messages"
257 | }
258 | fileinto "Big messages";
259 | }
260 |
261 | if header :is "From" "grandma@example.net"
262 | {
263 | addflag ["\\Answered", "$MDNSent"];
264 | # If the message is bigger than 1Mb it will be marked as "\Flagged $Big \Answered $MDNSent"
265 | # when filed into mailbox "grandma". If the message is shorter than 1Mb it will be marked as
266 | # "\Answered $MDNSent"
267 | fileinto "GrandMa"; # move to "GrandMa" folder
268 | }
269 |
270 | #
271 | # Handle messages from known mailing lists
272 | # Move messages from IETF filter discussion list to filter folder
273 | #
274 | if header :is "Sender" "owner-ietf-mta-filters@imc.org"
275 | {
276 | setflag "\\Flagged";
277 | # Message will always have just "\Flagged" flag
278 | keep;
279 | }
280 |
281 | #
282 | # Keep all messages to or from people in my company
283 | #
284 | elsif anyof address :domain :is ["From", "To"] "company.com"
285 | {
286 | keep; # keep in "In" folder
287 | }
288 | #
289 | # Try and catch unsolicited email. If a message is not to me,
290 | # or it contains a subject known to be spam, file it away.
291 | #
292 | elsif anyof (not address :all :contains
293 | ["To", "Cc", "Bcc"] "me@company.com",
294 | header :matches "subject"
295 | ["*make*money*fast*", "*university*dipl*mas*"])
296 | {
297 | removeflag "\\Flagged";
298 | # If message header does not contain my address,
299 | # it's from a list.
300 | fileinto "spam"; # move to "spam" folder
301 | }
302 | else
303 | {
304 | # Move all other (non-company) mail to "personal"
305 | # folder.
306 | fileinto "personal";
307 | }
308 |
309 |
310 |
311 | 4. Interaction with Other Sieve Actions
312 |
313 | Sieve actions sometimes prohibit each other in order to make
314 | filtering scripts less likely to cause serious problems.
315 |
316 | It is strongly discouraged to use setflag/addflag/removeflag/mark/unmark
317 | actions together with reject, because that action doesn't allow keeping a
318 | received message.
319 |
320 | The SIEVE interpreter MUST ignore any setflag/addflag/removeflag/mark/unmark
321 | commands when they are used with reject. The SIEVE interpreter MUST ignore these
322 | commands when no keep (implicit or explicit) or fileinto actions will be taken.
323 |
324 | A SIEVE verifier SHOULD reject a script that contains a
325 | setflag/addflag/removeflag/mark/unmark action together with reject.
326 |
327 |
328 | 5. Other Considerations
329 |
330 | This extension intentionally doesn't allow setting [IMAP] flags on an
331 | arbitrary message in the [IMAP] message store.
332 |
333 |
334 | 6. Security Considerations
335 |
336 | Security considerations are discussed in the [IMAP] and [SIEVE].
337 | It is belived that this extension doesn't introduce any
338 | additional security concerns.
339 |
340 |
341 | 7. Formal Grammar
342 |
343 | The grammar used in this section is the same as the ABNF described in
344 | [ABNF].
345 |
346 | action =/ setflag / addflag / removeflag / mark / unmark
347 |
348 | setflag = "setflag" WSP string-list
349 | ;; a list of [IMAP] flags
350 |
351 | addflag = "addflag" WSP string-list
352 | ;; a list of [IMAP] flags
353 |
354 | removeflag = "removeflag" WSP string-list
355 | ;; a list of [IMAP] flags
356 |
357 | mark = "mark"
358 |
359 | unmark = "unmark"
360 |
361 |
362 | 8. Acknowledgments
363 |
364 | This document has been revised in part based on comments and
365 | discussions which took place on and off the SIEVE mailing list.
366 | The help of those who took the time to review the draft and make
367 | suggestions is appreciated, especially that of Tim Showalter,
368 | Barry Leiba, and Randall Gellens. Special thanks to Tony Hansen,
369 | David Lamb and Roman Migal for helping me explain better the concept.
370 |
371 |
372 | 9. Author's Address
373 |
374 | Alexey Melnikov
375 | Messaging Direct, Ltd.
376 |
377 | Address : #900, 10117 Jasper Avenue, Edmonton, Alberta, Canada,
378 | T5J1W8
379 |
380 | Email: mel@messagingdirect.com
381 |
382 |
383 | Appendices
384 |
385 | Appendix A. References
386 |
387 | [SIEVE] Showalter, T., "Sieve: A Mail Filtering Language", Mirapoint,
388 | Work in Progress, draft-showalter-sieve-XX.txt
389 |
390 | [ABNF] Crocker, D., "Augmented BNF for Syntax Specifications: ABNF",
391 | Internet Mail Consortium, RFC 2234, November, 1997.
392 |
393 | [KEYWORDS] Bradner, S., "Key words for use in RFCs to Indicate
394 | Requirement Levels", Harvard University, RFC 2119, March 1997.
395 |
396 | [IMAP] Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1",
397 | University of Washington, RFC 2060, December 1996.
398 |
399 |
400 | Appendix B. Full Copyright Statement
401 |
402 | Copyright (C) The Internet Society 2000. All Rights Reserved.
403 |
404 | This document and translations of it may be copied and furnished to
405 | others, and derivative works that comment on or otherwise explain it
406 | or assist in its implementation may be prepared, copied, published
407 | and distributed, in whole or in part, without restriction of any
408 | kind, provided that the above copyright notice and this paragraph
409 | are included on all such copies and derivative works. However, this
410 | document itself may not be modified in any way, such as by removing
411 | the copyright notice or references to the Internet Society or other
412 | Internet organizations, except as needed for the purpose of
413 | developing Internet standards in which case the procedures for
414 | copyrights defined in the Internet Standards process must be
415 | followed, or as required to translate it into languages other than
416 | English.
417 |
418 | The limited permissions granted above are perpetual and will not be
419 | revoked by the Internet Society or its successors or assigns.
420 |
421 | This document and the information contained herein is provided on an
422 | "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING
423 | TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
424 | BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION
425 | HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF
426 | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
427 |
--------------------------------------------------------------------------------
/rfcs/rfc5231-segmuller-sieve-relational.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Network Working Group W. Segmuller
8 | Request for Comments: 5231 B. Leiba
9 | Obsoletes: 3431 IBM T.J. Watson Research Center
10 | Category: Standards Track January 2008
11 |
12 |
13 | Sieve Email Filtering: Relational Extension
14 |
15 | Status of This Memo
16 |
17 | This document specifies an Internet standards track protocol for the
18 | Internet community, and requests discussion and suggestions for
19 | improvements. Please refer to the current edition of the "Internet
20 | Official Protocol Standards" (STD 1) for the standardization state
21 | and status of this protocol. Distribution of this memo is unlimited.
22 |
23 | Abstract
24 |
25 | This document describes the RELATIONAL extension to the Sieve mail
26 | filtering language defined in RFC 3028. This extension extends
27 | existing conditional tests in Sieve to allow relational operators.
28 | In addition to testing their content, it also allows for testing of
29 | the number of entities in header and envelope fields.
30 |
31 | This document obsoletes RFC 3431.
32 |
33 | Table of Contents
34 |
35 | 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 2
36 | 2. Conventions Used in This Document . . . . . . . . . . . . . . . 2
37 | 3. Comparators . . . . . . . . . . . . . . . . . . . . . . . . . . 2
38 | 4. Match Types . . . . . . . . . . . . . . . . . . . . . . . . . . 3
39 | 4.1. Match Type VALUE . . . . . . . . . . . . . . . . . . . . . 3
40 | 4.2. Match Type COUNT . . . . . . . . . . . . . . . . . . . . . 3
41 | 5. Interaction with Other Sieve Actions . . . . . . . . . . . . . 4
42 | 6. Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
43 | 7. Extended Example . . . . . . . . . . . . . . . . . . . . . . . 6
44 | 8. Changes since RFC 3431 . . . . . . . . . . . . . . . . . . . . 6
45 | 9. IANA Considerations . . . . . . . . . . . . . . . . . . . . . . 7
46 | 10. Security Considerations . . . . . . . . . . . . . . . . . . . . 7
47 | 11. Normative References . . . . . . . . . . . . . . . . . . . . . 7
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | Segmuller & Leiba Standards Track [Page 1]
59 |
60 | RFC 5231 Sieve: Relational Extension January 2008
61 |
62 |
63 | 1. Introduction
64 |
65 | The RELATIONAL extension to the Sieve mail filtering language [Sieve]
66 | provides relational operators on the address, envelope, and header
67 | tests. This extension also provides a way of counting the entities
68 | in a message header or address field.
69 |
70 | With this extension, the Sieve script may now determine if a field is
71 | greater than or less than a value instead of just equivalent. One
72 | use is for the x-priority field: move messages with a priority
73 | greater than 3 to the "work on later" folder. Mail could also be
74 | sorted by the from address. Those userids that start with 'a'-'m' go
75 | to one folder, and the rest go to another folder.
76 |
77 | The Sieve script can also determine the number of fields in the
78 | header, or the number of addresses in a recipient field, for example,
79 | whether there are more than 5 addresses in the to and cc fields.
80 |
81 | The capability string associated with the extension defined in this
82 | document is "relational".
83 |
84 | 2. Conventions Used in This Document
85 |
86 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
87 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
88 | document are to be interpreted as described in BCP 14, RFC 2119.
89 |
90 | Conventions for notations are as in [Sieve] section 1.1, including
91 | the use of [Kwds] and the use of [ABNF].
92 |
93 | 3. Comparators
94 |
95 | This document does not define any comparators or exempt any
96 | comparators from the require clause. Any comparator used must be
97 | treated as defined in [Sieve].
98 |
99 | The "i;ascii-numeric" comparator, as defined in [RFC4790], MUST be
100 | supported for any implementation of this extension. The comparator
101 | "i;ascii-numeric" MUST support at least 32-bit unsigned integers.
102 |
103 | Larger integers MAY be supported. Note: the "i;ascii-numeric"
104 | comparator does not support negative numbers.
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | Segmuller & Leiba Standards Track [Page 2]
115 |
116 | RFC 5231 Sieve: Relational Extension January 2008
117 |
118 |
119 | 4. Match Types
120 |
121 | This document defines two new match types. They are the VALUE match
122 | type and the COUNT match type.
123 |
124 | The syntax is:
125 |
126 | MATCH-TYPE =/ COUNT / VALUE
127 |
128 | COUNT = ":count" relational-match
129 |
130 | VALUE = ":value" relational-match
131 |
132 | relational-match = DQUOTE
133 | ("gt" / "ge" / "lt" / "le" / "eq" / "ne") DQUOTE
134 | ; "gt" means "greater than", the C operator ">".
135 | ; "ge" means "greater than or equal", the C operator ">=".
136 | ; "lt" means "less than", the C operator "<".
137 | ; "le" means "less than or equal", the C operator "<=".
138 | ; "eq" means "equal to", the C operator "==".
139 | ; "ne" means "not equal to", the C operator "!=".
140 |
141 | 4.1. Match Type VALUE
142 |
143 | The VALUE match type does a relational comparison between strings.
144 |
145 | The VALUE match type may be used with any comparator that returns
146 | sort information.
147 |
148 | A value from the message is considered the left side of the relation.
149 | A value from the test expression, the key-list for address, envelope,
150 | and header tests, is the right side of the relation.
151 |
152 | If there are multiple values on either side or both sides, the test
153 | is considered true if any pair is true.
154 |
155 | 4.2. Match Type COUNT
156 |
157 | The COUNT match type first determines the number of the specified
158 | entities in the message and does a relational comparison of the
159 | number of entities, as defined below, to the values specified in the
160 | test expression.
161 |
162 | The COUNT match type SHOULD only be used with numeric comparators.
163 |
164 | The Address Test counts the number of addresses (the number of
165 | "mailbox" elements, as defined in [RFC2822]) in the specified fields.
166 | Group names are ignored, but the contained mailboxes are counted.
167 |
168 |
169 |
170 | Segmuller & Leiba Standards Track [Page 3]
171 |
172 | RFC 5231 Sieve: Relational Extension January 2008
173 |
174 |
175 | The Envelope Test counts the number of addresses in the specified
176 | envelope parts. The envelope "to" will always have only one entry,
177 | which is the address of the user for whom the Sieve script is
178 | running. Using this test, there is no way a Sieve script can
179 | determine if the message was actually sent to someone else. The
180 | envelope "from" will be 0 if the MAIL FROM is empty, or 1 if MAIL
181 | FROM is not empty.
182 |
183 | The Header Test counts the total number of instances of the specified
184 | fields. This does not count individual addresses in the "to", "cc",
185 | and other recipient fields.
186 |
187 | In all cases, if more than one field name is specified, the counts
188 | for all specified fields are added together to obtain the number for
189 | comparison. Thus, specifying ["to", "cc"] in an address COUNT test
190 | compares the total number of "to" and "cc" addresses; if separate
191 | counts are desired, they must be done in two comparisons, perhaps
192 | joined by "allof" or "anyof".
193 |
194 | 5. Interaction with Other Sieve Actions
195 |
196 | This specification adds two match types. The VALUE match type only
197 | works with comparators that return sort information. The COUNT match
198 | type only makes sense with numeric comparators.
199 |
200 | There is no interaction with any other Sieve operations, nor with any
201 | known extensions. In particular, this specification has no effect on
202 | implicit KEEP, nor on any explicit message actions.
203 |
204 | 6. Example
205 |
206 | Using the message:
207 |
208 | received: ...
209 | received: ...
210 | subject: example
211 | to: foo@example.com, baz@example.com
212 | cc: qux@example.com
213 |
214 | The test:
215 |
216 | address :count "ge" :comparator "i;ascii-numeric"
217 | ["to", "cc"] ["3"]
218 |
219 | would evaluate to true, and the test
220 |
221 |
222 |
223 |
224 |
225 |
226 | Segmuller & Leiba Standards Track [Page 4]
227 |
228 | RFC 5231 Sieve: Relational Extension January 2008
229 |
230 |
231 | anyof ( address :count "ge" :comparator "i;ascii-numeric"
232 | ["to"] ["3"],
233 | address :count "ge" :comparator "i;ascii-numeric"
234 | ["cc"] ["3"] )
235 |
236 | would evaluate to false.
237 |
238 | To check the number of received fields in the header, the following
239 | test may be used:
240 |
241 | header :count "ge" :comparator "i;ascii-numeric"
242 | ["received"] ["3"]
243 |
244 | This would evaluate to false. But
245 |
246 | header :count "ge" :comparator "i;ascii-numeric"
247 | ["received", "subject"] ["3"]
248 |
249 | would evaluate to true.
250 |
251 | The test:
252 |
253 | header :count "ge" :comparator "i;ascii-numeric"
254 | ["to", "cc"] ["3"]
255 |
256 | will always evaluate to false on an RFC 2822 compliant message
257 | [RFC2822], since a message can have at most one "to" field and at
258 | most one "cc" field. This test counts the number of fields, not the
259 | number of addresses.
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 | Segmuller & Leiba Standards Track [Page 5]
283 |
284 | RFC 5231 Sieve: Relational Extension January 2008
285 |
286 |
287 | 7. Extended Example
288 |
289 | require ["relational", "comparator-i;ascii-numeric", "fileinto"];
290 |
291 | if header :value "lt" :comparator "i;ascii-numeric"
292 | ["x-priority"] ["3"]
293 | {
294 | fileinto "Priority";
295 | }
296 |
297 | elsif address :count "gt" :comparator "i;ascii-numeric"
298 | ["to"] ["5"]
299 | {
300 | # everything with more than 5 recipients in the "to" field
301 | # is considered SPAM
302 | fileinto "SPAM";
303 | }
304 |
305 | elsif address :value "gt" :all :comparator "i;ascii-casemap"
306 | ["from"] ["M"]
307 | {
308 | fileinto "From N-Z";
309 | } else {
310 | fileinto "From A-M";
311 | }
312 |
313 | if allof ( address :count "eq" :comparator "i;ascii-numeric"
314 | ["to", "cc"] ["1"] ,
315 | address :all :comparator "i;ascii-casemap"
316 | ["to", "cc"] ["me@foo.example.com"] )
317 | {
318 | fileinto "Only me";
319 | }
320 |
321 | 8. Changes since RFC 3431
322 |
323 | Apart from several minor editorial/wording changes, the following
324 | list describes the notable changes to this specification since RFC
325 | 3431.
326 |
327 | o Updated references, including changing the comparator reference
328 | from the Application Configuration Access Protocol (ACAP) to the
329 | "Internet Application Protocol Collation Registry" document
330 | [RFC4790].
331 |
332 | o Updated and corrected the examples.
333 |
334 |
335 |
336 |
337 |
338 | Segmuller & Leiba Standards Track [Page 6]
339 |
340 | RFC 5231 Sieve: Relational Extension January 2008
341 |
342 |
343 | o Added definition comments to ABNF for "gt", "lt", etc.
344 |
345 | o Clarified what RFC 2822 elements are counted in the COUNT test.
346 |
347 | o Removed the requirement to strip white space from header fields
348 | before comparing; a more general version of this requirement has
349 | been added to the Sieve base spec.
350 |
351 | 9. IANA Considerations
352 |
353 | The following template specifies the IANA registration of the
354 | relational Sieve extension specified in this document:
355 |
356 | To: iana@iana.org
357 | Subject: Registration of new Sieve extension
358 |
359 | Capability name: relational
360 | Description: Extends existing conditional tests in Sieve language
361 | to allow relational operators
362 | RFC number: RFC 5231
363 | Contact address: The Sieve discussion list
364 |
365 | 10. Security Considerations
366 |
367 | An implementation MUST ensure that the test for envelope "to" only
368 | reflects the delivery to the current user. Using this test, it MUST
369 | not be possible for a user to determine if this message was delivered
370 | to someone else.
371 |
372 | Additional security considerations are discussed in [Sieve].
373 |
374 | 11. Normative References
375 |
376 | [ABNF] Crocker, D., Ed. and P. Overell, "Augmented BNF for Syntax
377 | Specifications: ABNF", RFC 4234, October 2005.
378 |
379 | [Kwds] Bradner, S., "Key words for use in RFCs to Indicate
380 | Requirement Levels", RFC 2119, March 1997.
381 |
382 | [RFC2822] Resnick, P., "Internet Message Format", RFC 2822,
383 | April 2001.
384 |
385 | [RFC4790] Newman, C., Duerst, M., and A. Gulbrandsen, "Internet
386 | Application Protocol Collation Registry", RFC 4790,
387 | March 2007.
388 |
389 | [Sieve] Guenther, P., Ed. and T. Showalter, Ed., "Sieve: An Email
390 | Filtering Language", RFC 5228, January 2008.
391 |
392 |
393 |
394 | Segmuller & Leiba Standards Track [Page 7]
395 |
396 | RFC 5231 Sieve: Relational Extension January 2008
397 |
398 |
399 | Authors' Addresses
400 |
401 | Wolfgang Segmuller
402 | IBM T.J. Watson Research Center
403 | 19 Skyline Drive
404 | Hawthorne, NY 10532
405 | US
406 |
407 | Phone: +1 914 784 7408
408 | EMail: werewolf@us.ibm.com
409 |
410 |
411 | Barry Leiba
412 | IBM T.J. Watson Research Center
413 | 19 Skyline Drive
414 | Hawthorne, NY 10532
415 | US
416 |
417 | Phone: +1 914 784 7941
418 | EMail: leiba@watson.ibm.com
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 | Segmuller & Leiba Standards Track [Page 8]
451 |
452 | RFC 5231 Sieve: Relational Extension January 2008
453 |
454 |
455 | Full Copyright Statement
456 |
457 | Copyright (C) The IETF Trust (2008).
458 |
459 | This document is subject to the rights, licenses and restrictions
460 | contained in BCP 78, and except as set forth therein, the authors
461 | retain all their rights.
462 |
463 | This document and the information contained herein are provided on an
464 | "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS
465 | OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY, THE IETF TRUST AND
466 | THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS
467 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF
468 | THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED
469 | WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
470 |
471 | Intellectual Property
472 |
473 | The IETF takes no position regarding the validity or scope of any
474 | Intellectual Property Rights or other rights that might be claimed to
475 | pertain to the implementation or use of the technology described in
476 | this document or the extent to which any license under such rights
477 | might or might not be available; nor does it represent that it has
478 | made any independent effort to identify any such rights. Information
479 | on the procedures with respect to rights in RFC documents can be
480 | found in BCP 78 and BCP 79.
481 |
482 | Copies of IPR disclosures made to the IETF Secretariat and any
483 | assurances of licenses to be made available, or the result of an
484 | attempt made to obtain a general license or permission for the use of
485 | such proprietary rights by implementers or users of this
486 | specification can be obtained from the IETF on-line IPR repository at
487 | http://www.ietf.org/ipr.
488 |
489 | The IETF invites any interested party to bring to its attention any
490 | copyrights, patents or patent applications, or other proprietary
491 | rights that may cover technology that may be required to implement
492 | this standard. Please address the information to the IETF at
493 | ietf-ipr@ietf.org.
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 | Segmuller & Leiba Standards Track [Page 9]
507 |
508 |
--------------------------------------------------------------------------------
/rfcs/draft-ietf-sieve-notify-00.txt:
--------------------------------------------------------------------------------
1 | Network Working Group Alexey Melnikov
2 | Document: draft-ietf-sieve-notify-00.txt Editor
3 | Expires March 2006 September 2005
4 |
5 |
6 | Sieve -- An extension for providing instant notifications
7 |
8 | Status of this Memo
9 |
10 | By submitting this Internet-Draft, each author represents that any
11 | applicable patent or other IPR claims of which he or she is aware
12 | have been or will be disclosed, and any of which he or she becomes
13 | aware will be disclosed, in accordance with Section 6 of BCP 79.
14 |
15 | Internet-Drafts are working documents of the Internet Engineering
16 | Task Force (IETF), its areas, and its working groups. Note that
17 | other groups may also distribute working documents as Internet-
18 | Drafts.
19 |
20 | Internet-Drafts are draft documents valid for a maximum of six
21 | months and may be updated, replaced, or obsoleted by other
22 | documents at any time. It is inappropriate to use Internet-Drafts as
23 | reference material or to cite them other than as "work in
24 | progress".
25 |
26 | The list of current Internet-Drafts can be accessed at
27 | http://www.ietf.org/ietf/1id-abstracts.txt
28 |
29 | The list of Internet-Draft Shadow Directories can be accessed at
30 | http://www.ietf.org/shadow.html.
31 |
32 | Distribution of this memo is unlimited.
33 |
34 | Copyright Notice
35 |
36 | Copyright (C) The Internet Society (2005).
37 |
38 |
39 | Abstract
40 |
41 | Users go to great lengths to be notified as quickly as possible that
42 | they have received new mail. Most of these methods involve polling
43 | to check for new messages periodically. A push method handled by the
44 | final delivery agent gives users quicker notifications and saves
45 | server resources. This document does not specify the notification
46 | method but is expected that using existing instant messaging
47 | infrastructure such as Zephyr, Jabber, or SMS messages will be popular.
48 | This draft describes an extension to the Sieve mail filtering
49 | language that allows users to give specific preferences for
50 | notification of Sieve actions.
51 |
52 |
53 | 1. Introduction
54 |
55 | This is an extension to the Sieve language defined by [SIEVE] for
56 | providing instant notifications of sieve actions that have been
57 | preformed. It defines the new action "notify".
58 |
59 | This document does not dictate the notification method used.
60 | Examples of possible notification methods are Zephyr and Jabber. The
61 | method shall be site-defined.
62 |
63 | Sieve interpreters for which notifications are impractical or is not
64 | possible SHOULD ignore this extension.
65 |
66 | Conventions for notations are as in [SIEVE] section 1.1, including
67 | use of [KEYWORDS].
68 |
69 |
70 | 1.1. Conventions Used in This Document
71 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
72 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
73 | document are to be interpreted as described in [KEYWORDS].
74 |
75 |
76 | 2. Capability Identifier
77 |
78 | The capability string associated with the extension defined in this
79 | document is "notify".
80 |
81 |
82 | 3. Actions
83 |
84 |
85 | 3.1. Notify action
86 |
87 | Usage: notify [":method" string]
88 | [":id" string]
89 | [<":low" / ":normal" / ":high">]
90 | [":message" string]
91 |
92 | The Notify action specifies that a notification should be sent to
93 | the user upon successful handling of this message.
94 |
95 | The format of the notification is implementation-defined. However,
96 | all content specified in the notify action, including Sieve actions
97 | taken on the message, SHOULD be included. If errors occurred in
98 | another action they SHOULD be reported in the notification. In
99 | addition, if the notification method does not provide a timestamp,
100 | one SHOULD be appended to the notification. Implementations SHOULD
101 | NOT include extraneous information.
102 |
103 | The :method tag identifies the notification method that will be
104 | used, it is an URI. For examples, the notification method can
105 | be an SMS URI [SMS-URI] containing a phone number, or an XMPP [XMPP]
106 | URI containing Jabber identifier [XMPP-URI].
107 | If the :method tag is not specified, the default
108 | implementation defined notification method is used. The
109 | possible values of this will be site-specific. If an URI schema is
110 | specified that the implementation does not support, the notification
111 | MUST be ignored. An implementation treats this as a warning
112 | condition and execution of the sieve script MUST continue.
113 |
114 | The :id tag can be used to give the notify action an unique
115 | identifier. This identifier can be used later in the script to
116 | cancel the specific notify. The string may have any value and SHOULD
117 | NOT be included in the notification.
118 |
119 | The priority parameter specifies the importance of the notification.
120 | The priority parameter has the following values: ":high" (very
121 | important), ":normal", and ":low" (not very important). If no
122 | priority is given, a default priority of ":normal" SHOULD be
123 | assumed. Some notification methods allow users to specify their
124 | state of activity (for example "busy" or "away from keyboard"). If
125 | the notification method provides this information it SHOULD be used
126 | to selectively send notifications. If, for example, the user marks
127 | herself as "busy", an implementation SHOULD NOT send a notification
128 | for a new mailing list message with a priority of :low, however the
129 | user should be notified of a high priority action. If the
130 | notification method allows users to filter messages based upon
131 | certain parameters in the message, users should be able to filter
132 | based upon priority. If the notification method does not support
133 | priority, then this parameter MUST be ignored.
134 |
135 | The :message tag specifies the message data to be included in the
136 | notification. The entirety of the string SHOULD be sent but
137 | implementations MAY shorten the message for technical or aesthetic
138 | reasons. If the message parameter is absent, a default message
139 | containing the value of the From header field and the value of the
140 | Subject header field will be used. Note that the notification
141 | method (the ":method" tag) may affect how this information is
142 | formatted.
143 | In order to construct more complex messages
144 | the notify extension can be used together with the Sieve variables
145 | extension [VARIABLES], as shown at the end of this section.
146 |
147 | <>
154 |
155 | If there are errors sending the notification, the Sieve interpreter
156 | SHOULD ignore the notification and not retry indefinitely.
157 |
158 | This action MUST NOT cancel the implicit keep.
159 |
160 | Example:
161 | require ["notify", "fileinto", "variables"];
162 |
163 | if header :contains "from" "boss@example.org" {
164 | notify :high :message "This is probably very important";
165 | # Don't send any further notifications
166 | stop;
167 | }
168 |
169 | if header :contains "to" "sievemailinglist@example.org" {
170 | # :matches is used to get the value of the Subject header
171 | if header :matches "Subject" "*" {
172 | set "subject" "${1}";
173 | }
174 |
175 | # :matches is used to get the value of the From header
176 | if header :matches "From" "*" {
177 | set "from" "${1}";
178 | }
179 |
180 | notify :low :message "[SIEVE] ${from}: ${subject}";
181 | fileinto "INBOX.sieve";
182 | }
183 |
184 | Example:
185 | require ["notify", "fileinto", "variables", "envelope"];
186 |
187 | if header :matches "from" "*@*.example.org" {
188 | # :matches is used to get the MAIL FROM address
189 | if envelope :all :matches "from" "*" {
190 | set "env_from" " [really: ${1}]";
191 | }
192 |
193 | # :matches is used to get the value of the Subject header
194 | if header :matches "Subject" "*" {
195 | set "subject" "${1}";
196 | }
197 |
198 | # :matches is used to get the address from the From header
199 | if address :matches :all "from" "*" {
200 | set "from_addr" "${1}";
201 | }
202 |
203 | notify :message "${from_addr}${env_from}: ${subject}";
204 | }
205 |
206 |
207 | 3.2. Denotify Action
208 |
209 | Usage: denotify [MATCH-TYPE string] [<":low" / ":normal" / ":high">]
210 |
211 | The denotify action can be used to cancel a previous notification.
212 | If the priority, ":low" / ":normal" / ":high", is specified, then
213 | only cancel those notifications with the specified priority. If a
214 | MATCH-TYPE with a string is specified, then only those notifications
215 | whose :id tag matches the specified string using the match-type
216 | operator are canceled. The ascii-casemap comparator MUST be used.
217 |
218 | If no notifications exist that match the search criteria, then the
219 | denotify has no effect. A denotify only cancels notifications that
220 | have already been requested. It is not possible to preemptively
221 | cancel a notification.
222 |
223 | The sequence:
224 |
225 | denotify;
226 | notify;
227 |
228 | will still generate a notification. The denotify does not cancel
229 | the notify.
230 |
231 | The following table shows which notifies would get cancelled:
232 |
233 | # what is cancelled
234 | denotify # all notifications
235 | denotify :matches "*" # all notifications with :id tag
236 | denotify :high # all high priority notifications
237 | denotify :is "foobar" # all notifications with id "foobar"
238 | denotify :matches "foo*" :normal # all normal priority notifications
239 | # with id that starts with "foo"
240 |
241 | Example:
242 |
243 | require ["notify", "variables"];
244 |
245 | notify :method "xmpp:tim@example.com?You%20got%20mail&subject=SIEVE"
246 | :id "foobar";
247 |
248 | if header :contains "from" "boss@example.org" {
249 | # :matches is used to get the value of the Subject header
250 | if header :matches "Subject" "*" {
251 | set "subject" "${1}";
252 | }
253 |
254 | notify :method "sms:+14085551212" :id "foobar"
255 | :high :message "BOSS: ${subject}";
256 | }
257 |
258 | if header :contains "to" "sievemailinglist@example.org" {
259 | denotify :is "foobar";
260 | }
261 |
262 | if header :contains "subject" "FYI:" {
263 | # don't need high priority notification for
264 | # a 'for your information'
265 | denotify :is "foobar" :high;
266 | }
267 |
268 |
269 | 4. Interaction with Other Sieve Actions
270 |
271 | Notifications MUST be sent in all cases, unless a reject action is
272 | also executed. Users may wish to be notified of a message being
273 | discarded, for example. <>
277 |
278 | The notify action MUST NOT cancel the implicit keep.
279 |
280 | The notify action is compatible with itself.
281 |
282 | The denotify action MUST NOT affect any actions other than the
283 | notify action.
284 |
285 | Failures of other actions MAY be reported in the notification.
286 |
287 |
288 | 5. Security Considerations
289 |
290 | Security considerations are discussed in [SIEVE]. Additionally
291 | implementations must be careful to follow the security
292 | considerations of the specific notification methods. It is believed
293 | that this extension does not introduce any additional security
294 | concerns.
295 |
296 | The notify action is potentially very dangerous. The path the
297 | notification takes through the network may not be secure. An error
298 | in the options string may cause the message to be transmitted to
299 | someone it was not intended for.
300 |
301 | Just because a notification is received doesn't mean it was sent by
302 | the sieve implementation. It might be possible to forge
303 | notifications with some notification methods.
304 |
305 |
306 | 6. IANA Considerations
307 |
308 | The following template specifies the IANA registration of the
309 | variables Sieve extension specified in this document:
310 |
311 | To: iana@iana.org
312 | Subject: Registration of new Sieve extension
313 | Capability name: notify
314 | Capability keyword: notify
315 | Capability arguments: N/A
316 | Standards Track/IESG-approved experimental RFC number:
317 | this RFC
318 | Person and email address to contact for further information:
319 | Alexey Melnikov
320 |
321 | This information should be added to the list of sieve extensions
322 | given on http://www.iana.org/assignments/sieve-extensions.
323 |
324 |
325 | 7. Acknowledgments
326 |
327 | Thanks to Larry Greenfield, Sarah Robeson, Tim Showalter, Barry
328 | Leiba, and Cyrus Daboo for help with this document.
329 |
330 |
331 | 8. References
332 |
333 | 8.1. Normative References
334 |
335 | [KEYWORDS] Bradner, S., "Key words for use in RFCs to Indicate
336 | Requirement Levels", BCP 14, RFC 2119, March 1997.
337 |
338 | [ABNF] Crocker, Overell, "Augmented BNF for Syntax Specifications:
339 | ABNF", RFC 2234, Internet Mail Consortium, Demon Internet Ltd,
340 | November 1997. <>
341 |
342 | [SIEVE] Showalter, T. and P. Guenther, "Sieve: An Email Filtering
343 | Language", work in progress, draft-ietf-sieve-3028bis-XX.txt.
344 |
345 |
346 | 8.2. Informative References
347 |
348 | [VARIABLES] Homme, K., "Sieve Extension: Variables", work in
349 | progress, draft-ietf-sieve-variables-XX.txt.
350 |
351 | [XMPP]
352 |
353 | [XMPP-URI] Saint-Andre, P., "A Uniform Resource Identifier (URI)
354 | Scheme for the Extensible Messaging and Presence Protocol (XMPP)",
355 | work in progress, draft-saintandre-xmpp-uri-XX.txt.
356 |
357 | [SMS-URI] Wilde, E. and A. Vaha-Sipila, "URI scheme for GSM Short
358 | Message Service", work in progress, draft-wilde-sms-uri-XX.txt.
359 |
360 |
361 | 9. Author's and Editor's Addresses
362 |
363 | Tim Martin
364 | Mirapoint Inc.
365 | 909 Hermosa Court
366 | Sunnyvale, CA 94085
367 |
368 | Phone: (408) 720-3835
369 | EMail: tmartin@mirapoint.com
370 |
371 | Wolfgang Segmuller
372 | IBM T.J. Watson Research Center
373 | 30 Saw Mill River Rd
374 | Hawthorne, NY 10532
375 |
376 | Phone: (914) 784-7408
377 | Email: whs@watson.ibm.com
378 |
379 |
380 | Alexey Melnikov (Editor)
381 | Isode Limited
382 | 5 Castle Business Village
383 | 36 Station Road
384 | Hampton, Middlesex
385 | TW12 2BX, UK
386 |
387 | Email: Alexey.Melnikov@isode.com
388 |
389 |
390 | Intellectual Property Statement
391 |
392 | The IETF takes no position regarding the validity or scope of any
393 | Intellectual Property Rights or other rights that might be claimed to
394 | pertain to the implementation or use of the technology described in
395 | this document or the extent to which any license under such rights
396 | might or might not be available; nor does it represent that it has
397 | made any independent effort to identify any such rights. Information
398 | on the procedures with respect to rights in RFC documents can be
399 | found in BCP 78 and BCP 79.
400 |
401 | Copies of IPR disclosures made to the IETF Secretariat and any
402 | assurances of licenses to be made available, or the result of an
403 | attempt made to obtain a general license or permission for the use of
404 | such proprietary rights by implementers or users of this
405 | specification can be obtained from the IETF on-line IPR repository at
406 | http://www.ietf.org/ipr.
407 |
408 | The IETF invites any interested party to bring to its attention any
409 | copyrights, patents or patent applications, or other proprietary
410 | rights that may cover technology that may be required to implement
411 | this standard. Please address the information to the IETF at
412 | ietf-ipr@ietf.org.
413 |
414 |
415 | Disclaimer of Validity
416 |
417 | This document and the information contained herein are provided on an
418 | "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS
419 | OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET
420 | ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED,
421 | INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE
422 | INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED
423 | WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
424 |
425 |
426 | Copyright Statement
427 |
428 | Copyright (C) The Internet Society (2005). This document is subject
429 | to the rights, licenses and restrictions contained in BCP 78, and
430 | except as set forth therein, the authors retain all their rights.
431 |
432 |
433 | Acknowledgment
434 |
435 | Funding for the RFC Editor function is currently provided by the
436 | Internet Society.
437 |
--------------------------------------------------------------------------------
/rfcs/rfc5173-freed-sieve-environment.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Network Working Group N. Freed
8 | Request for Comments: 5183 Sun Microsystems
9 | Category: Standards Track May 2008
10 |
11 |
12 | Sieve Email Filtering: Environment Extension
13 |
14 | Status of This Memo
15 |
16 | This document specifies an Internet standards track protocol for the
17 | Internet community, and requests discussion and suggestions for
18 | improvements. Please refer to the current edition of the "Internet
19 | Official Protocol Standards" (STD 1) for the standardization state
20 | and status of this protocol. Distribution of this memo is unlimited.
21 |
22 | Abstract
23 |
24 | This document describes the "environment" extension to the Sieve
25 | email filtering language. The "environment" extension gives a Sieve
26 | script access to information about the Sieve interpreter itself,
27 | where it is running, and about any transport connection currently
28 | involved in transferring the message.
29 |
30 | 1. Introduction
31 |
32 | Sieve [RFC5228] is a language for filtering email messages at or
33 | around the time of final delivery. It is designed to be
34 | implementable on either a mail client or mail server. It is suitable
35 | for running on a mail server where users may not be allowed to
36 | execute arbitrary programs, such as on black box Internet Message
37 | Access Protocol [RFC3501] servers, as it has no user-controlled loops
38 | or the ability to run external programs.
39 |
40 | Although Sieve is intended to be independent of access protocol, mail
41 | architecture, and operating system, in some cases it is useful to
42 | allow scripts to access information about their execution context.
43 | The "environment" extension provides a new environment test that can
44 | be used to implement scripts that behave differently when moved from
45 | one system to another, when messages arrive from different remote
46 | sources or when otherwise operated in different contexts.
47 |
48 | 2. Conventions Used in This Document
49 |
50 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
51 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
52 | document are to be interpreted as described in [RFC2119].
53 |
54 |
55 |
56 |
57 |
58 | Freed Standards Track [Page 1]
59 |
60 | RFC 5183 Sieve Environment Extension May 2008
61 |
62 |
63 | The terms used to describe the various components of the Sieve
64 | language are taken from Section 1.1 of [RFC5228].
65 |
66 | This document refers to the ABNF productions IPv4-address-literal,
67 | IPv6-address-literal, and General-address-literal defined in Section
68 | 4.1.3 of [RFC2821].
69 |
70 | The location item makes use of standard terms for email service
71 | components. Additional information and background on these terms can
72 | be found in [EMAIL-ARCH].
73 |
74 | 3. Capability Identifiers
75 |
76 | The capability string associated with the extension defined in this
77 | document is "environment".
78 |
79 | 4. Environment Test
80 |
81 | Usage: environment [COMPARATOR] [MATCH-TYPE]
82 |
83 |
84 |
85 | The environment test retrieves the item of environment information
86 | specified by the name string and matches it to the values specified
87 | in the key-list argument. The test succeeds if a match occurs. The
88 | type of match defaults to ":is" and the default comparator is
89 | "i;ascii-casemap".
90 |
91 | The current message is not a direct source of information for the
92 | environment test; the item of information specified by the name
93 | string is extracted from the script's operating environment and the
94 | key-list argument comes from the script.
95 |
96 | The environment test MUST fail unconditionally if the specified
97 | information item does not exist. A script MUST NOT fail with an
98 | error if the item does not exist. This allows scripts to be written
99 | that handle nonexistent items gracefully. In particular, the test:
100 |
101 | if environment :contains "item" "" { ... }
102 |
103 | only succeeds if "item" is known to the implementation, and always
104 | succeeds if it is.
105 |
106 | The "relational" extension [RFC5231] adds a match type called
107 | ":count". The count of an environment test is 0 if the environment
108 | information returned is the empty string, or 1 otherwise.
109 |
110 |
111 |
112 |
113 |
114 | Freed Standards Track [Page 2]
115 |
116 | RFC 5183 Sieve Environment Extension May 2008
117 |
118 |
119 | Environment items can be standardized or vendor-defined. An IANA
120 | registry is defined for both types of items. Extensions designed for
121 | interoperable use SHOULD be defined in standards track or
122 | experimental RFCs.
123 |
124 | 4.1. Initial Standard Environment Items
125 |
126 | The initial set of standardized environment items is as follows:
127 |
128 | "domain" => The primary DNS domain associated with the Sieve
129 | execution context, usually but not always a proper
130 | suffix of the host name.
131 |
132 | "host" => The fully-qualified domain name of the host where
133 | the Sieve script is executing.
134 |
135 | "location"
136 | => Sieve evaluation can be performed at various
137 | different points as messages are processed. This item
138 | provides additional information about the type of
139 | service that is evaluating the script. Possible values
140 | are "MTA", meaning the Sieve is being evaluated by a
141 | Message Transfer Agent, "MDA", meaning evaluation is
142 | being performed by a Mail Delivery Agent, "MUA",
143 | meaning evaluation is being performed by a Mail User
144 | Agent, and "MS", meaning evaluation is being performed
145 | by a Message Store. Additional information and
146 | background on these terms can be found in
147 | [EMAIL-ARCH].
148 |
149 | "name" => The product name associated with the Sieve interpreter.
150 |
151 | "phase" => The point relative to final delivery where the
152 | Sieve script is being evaluated. Possible values are
153 | "pre", "during", and "post", referring respectively to
154 | processing before, during, and after final delivery
155 | has taken place.
156 |
157 | "remote-host"
158 | => Host name of remote SMTP/LMTP/Submission client
159 | expressed as a Fully Qualified Domain Name (FQDN),
160 | if applicable and available. The empty string will be
161 | returned if for some reason this information cannot be
162 | obtained for the current client.
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 | Freed Standards Track [Page 3]
171 |
172 | RFC 5183 Sieve Environment Extension May 2008
173 |
174 |
175 | "remote-ip"
176 | => IP address of remote SMTP/LMTP/Submission client, if
177 | applicable and available. IPv4, IPv6, and other types
178 | of addresses are respectively represented in the
179 | formats defined by the IPv4-address-literal,
180 | IPv6-address-literal, and General-address-literal
181 | productions defined in Section 4.1.3 of [RFC2821].
182 |
183 | "version" => The product version associated with the Sieve
184 | interpreter. The meaning of the product version string
185 | is product-specific and should always be considered
186 | in the context of the product name given by the
187 | "name" item.
188 |
189 | Implementations SHOULD support as many of the items on this initial
190 | list as possible. Additional standardized items can only be defined
191 | in standards-track or experimental RFCs.
192 |
193 | 4.2. Vendor-defined Environment Items
194 |
195 | Environment item names beginning with "vnd." represent vendor-defined
196 | extensions. Such extensions are not defined by Internet standards or
197 | RFCs, but are still registered with IANA in order to prevent
198 | conflicts.
199 |
200 | 4.3. IANA Registration of Environment Items
201 |
202 | A registry of environment items is provided by IANA. Item names may
203 | be registered on a first-come, first-served basis.
204 |
205 | Groups of items defined in a standards track or experimental RFC MAY
206 | choose to use a common name prefix of the form "name.", where "name"
207 | is a string that identifies the group of related items.
208 |
209 | Items not defined in a standards track or experimental RFC MUST have
210 | a name that begins with the "vnd." prefix, and this prefix is
211 | followed by the name of the vendor or product, such as
212 | "vnd.acme.rocket-sled-status".
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 | Freed Standards Track [Page 4]
227 |
228 | RFC 5183 Sieve Environment Extension May 2008
229 |
230 |
231 | 4.3.1. Template for Environment Registrations
232 |
233 | The following template is to be used for registering new Sieve
234 | environment item names with IANA.
235 |
236 | To: iana@iana.org
237 | Subject: Registration of new Sieve environment item
238 |
239 | Item name: [the string for use in the 'environment' test]
240 | Description: [a brief description of the semantics of the
241 | value the item returns]
242 |
243 | RFC number: [for extensions published as RFCs]
244 | Contact address: [email and/or physical address to contact for
245 | additional information]
246 |
247 | Multiple items and descriptions MAY be specified in a single
248 | registration request. Both standardized and vendor-defined items use
249 | this form.
250 |
251 | 5. Security Considerations
252 |
253 | The environment extension may be used to obtain information about the
254 | system the Sieve implementation is running on. This information in
255 | turn may reveal details about service provider or enterprise
256 | infrastructure.
257 |
258 | An implementation can use any technique to determine the remote-host
259 | environment item defined in this specification, and the
260 | trustworthiness of the result will vary. One common method will be
261 | to perform a PTR DNS lookup on the client IP address. This
262 | information may come from an untrusted source. For example, the
263 | test:
264 |
265 | if environment :matches "remote-host" "*.example.com" { ... }
266 |
267 | is not a good way to test whether the message came from "outside"
268 | because anyone who can create a PTR record can create one that refers
269 | to whatever domain they choose.
270 |
271 | All of the security considerations given in the base Sieve
272 | specification also apply to this extension.
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 | Freed Standards Track [Page 5]
283 |
284 | RFC 5183 Sieve Environment Extension May 2008
285 |
286 |
287 | 6. IANA Considerations
288 |
289 | The following template specifies the IANA registration of the Sieve
290 | extension specified in this document:
291 |
292 | To: iana@iana.org
293 | Subject: Registration of new Sieve extension
294 |
295 | Capability name: environment
296 | Description: The "environment" extension provides a new
297 | environment test that can be used to implement
298 | scripts that behave differently when moved
299 | from one system to another or otherwise
300 | operated in different contexts.
301 | RFC number: RFC 5183
302 | Contact address: Sieve discussion list
303 |
304 | This specification also defines a new IANA registry for Sieve
305 | environment item names. The specifics of this registry are given in
306 | Section 4.3. The initial contents of the registry are given in the
307 | following section.
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 | Freed Standards Track [Page 6]
339 |
340 | RFC 5183 Sieve Environment Extension May 2008
341 |
342 |
343 | 6.1. Initial Environment Item Registrations
344 |
345 | The following template specifies the initial IANA registrations for
346 | the environment items defined in this document:
347 |
348 | To: iana@iana.org
349 | Subject: Registration of new Sieve environment items
350 |
351 | Capability name: domain
352 | Description: The primary DNS domain associated with the Sieve
353 | execution context, usually but not always a
354 | proper suffix of the host name.
355 |
356 | Capability name: host
357 | Description: The fully-qualified domain name of the host
358 | where the Sieve script is executing.
359 |
360 | Capability name: location
361 | Description: Type of service executing the Sieve script.
362 |
363 | Capability name: name
364 | Description: The product name associated with the Sieve
365 | interpreter.
366 |
367 | Capability name: phase
368 | Description: Point relative to final delivery at which the
369 | Sieve script is being evaluated.
370 |
371 | Capability name: remote-host
372 | Description: Host name of remote SMTP client, if applicable
373 | and available.
374 |
375 | Capability name: remote-ip
376 | Description: IP address of remote SMTP client, if applicable
377 | and available.
378 |
379 | Capability name: version
380 | Description: The product version associated with the Sieve
381 | interpreter.
382 |
383 | RFC number: RFC 5183
384 | Contact address: Sieve discussion list
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 | Freed Standards Track [Page 7]
395 |
396 | RFC 5183 Sieve Environment Extension May 2008
397 |
398 |
399 | 7. References
400 |
401 | 7.1. Normative references
402 |
403 | [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
404 | Requirement Levels", BCP 14, RFC 2119, March 1997.
405 |
406 | [RFC2821] Klensin, J., "Simple Mail Transfer Protocol", RFC 2821,
407 | April 2001.
408 |
409 | [RFC5228] Guenther, P. and T. Showalter, "Sieve: An Email
410 | Filtering Language", RFC 5228, January 2008.
411 |
412 | [RFC5231] Segmuller, W. and B. Leiba, "Sieve Email Filtering:
413 | Relational Extension", RFC 5231, January 2008.
414 |
415 | 7.2. Informative references
416 |
417 | [EMAIL-ARCH] Crocker, D., "Internet Mail Architecture", Work
418 | in Progress, February 2008.
419 |
420 | [RFC3501] Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL -
421 | VERSION 4rev1", RFC 3501, March 2003.
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 | Freed Standards Track [Page 8]
451 |
452 | RFC 5183 Sieve Environment Extension May 2008
453 |
454 |
455 | Appendix A. Acknowledgements
456 |
457 | Brian Carpenter, Dave Crocker, Cyrus Daboo, Philip Guenther, Kjetil
458 | Torgrim Homme, John Klensin, Mark Mallett, Alexey Melnikov, and
459 | Dilyan Palauzo provided helpful suggestions and corrections.
460 |
461 | Author's Address
462 |
463 | Ned Freed
464 | Sun Microsystems
465 | 3401 Centrelake Drive, Suite 410
466 | Ontario, CA 92761-1205
467 | USA
468 |
469 | Phone: +1 909 457 4293
470 | EMail: ned.freed@mrochek.com
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 | Freed Standards Track [Page 9]
507 |
508 | RFC 5183 Sieve Environment Extension May 2008
509 |
510 |
511 | Full Copyright Statement
512 |
513 | Copyright (C) The IETF Trust (2008).
514 |
515 | This document is subject to the rights, licenses and restrictions
516 | contained in BCP 78, and except as set forth therein, the authors
517 | retain all their rights.
518 |
519 | This document and the information contained herein are provided on an
520 | "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS
521 | OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY, THE IETF TRUST AND
522 | THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS
523 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF
524 | THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED
525 | WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
526 |
527 | Intellectual Property
528 |
529 | The IETF takes no position regarding the validity or scope of any
530 | Intellectual Property Rights or other rights that might be claimed to
531 | pertain to the implementation or use of the technology described in
532 | this document or the extent to which any license under such rights
533 | might or might not be available; nor does it represent that it has
534 | made any independent effort to identify any such rights. Information
535 | on the procedures with respect to rights in RFC documents can be
536 | found in BCP 78 and BCP 79.
537 |
538 | Copies of IPR disclosures made to the IETF Secretariat and any
539 | assurances of licenses to be made available, or the result of an
540 | attempt made to obtain a general license or permission for the use of
541 | such proprietary rights by implementers or users of this
542 | specification can be obtained from the IETF on-line IPR repository at
543 | http://www.ietf.org/ipr.
544 |
545 | The IETF invites any interested party to bring to its attention any
546 | copyrights, patents or patent applications, or other proprietary
547 | rights that may cover technology that may be required to implement
548 | this standard. Please address the information to the IETF at
549 | ietf-ipr@ietf.org.
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 | Freed Standards Track [Page 10]
563 |
564 |
--------------------------------------------------------------------------------
/rfcs/rfc5293-degener-sieve-editheader.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Network Working Group J. Degener
8 | Request for Comments: 5293 P. Guenther
9 | Category: Standards Track Sendmail, Inc.
10 | August 2008
11 |
12 |
13 | Sieve Email Filtering: Editheader Extension
14 |
15 | Status of This Memo
16 |
17 | This document specifies an Internet standards track protocol for the
18 | Internet community, and requests discussion and suggestions for
19 | improvements. Please refer to the current edition of the "Internet
20 | Official Protocol Standards" (STD 1) for the standardization state
21 | and status of this protocol. Distribution of this memo is unlimited.
22 |
23 | Abstract
24 |
25 | This document defines two new actions for the "Sieve" email filtering
26 | language that add and delete email header fields.
27 |
28 | 1. Introduction
29 |
30 | Email header fields are a flexible and easy-to-understand means of
31 | communication between email processors. This extension enables sieve
32 | scripts to interact with other components that consume or produce
33 | header fields by allowing the script to delete and add header fields.
34 |
35 | 2. Conventions Used in This Document
36 |
37 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
38 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
39 | document are to be interpreted as described in [KEYWORDS].
40 |
41 | Conventions for notations are as in Section 1.1 of [SIEVE], including
42 | use of the "Usage:" label for the definition of action and tagged
43 | arguments syntax.
44 |
45 | The term "header field" is used here as in [IMAIL] to mean a logical
46 | line of an email message header.
47 |
48 | 3. Capability Identifier
49 |
50 | The capability string associated with the extension defined in this
51 | document is "editheader".
52 |
53 |
54 |
55 |
56 |
57 |
58 | Degener & Guenther Standards Track [Page 1]
59 |
60 | RFC 5293 Sieve Email Filtering: Editheader Extension August 2008
61 |
62 |
63 | 4. Action addheader
64 |
65 | Usage: "addheader" [":last"]
66 |
67 | The addheader action adds a header field to the existing message
68 | header. If the field-name is not a valid 7-bit US-ASCII header field
69 | name, as described by the [IMAIL] "field-name" nonterminal syntax
70 | element, the implementation MUST flag an error. The addheader action
71 | does not affect Sieve's implicit keep.
72 |
73 | If the specified field value does not match the [IMAIL]
74 | "unstructured" nonterminal syntax element or exceeds a length limit
75 | set by the implementation, the implementation MUST either flag an
76 | error or encode the field using folding white space and the encodings
77 | described in [MIME3] or [MIMEPARAM] to be compliant with [IMAIL].
78 |
79 | An implementation MAY impose a length limit onto the size of the
80 | encoded header field; such a limit MUST NOT be less than 998
81 | characters, not including the terminating CRLF supplied by the
82 | implementation.
83 |
84 | By default, the header field is inserted at the beginning of the
85 | existing message header. If the optional flag ":last" is specified,
86 | it is appended at the end.
87 |
88 | Example:
89 |
90 | /* Don't redirect if we already redirected */
91 | if not header :contains "X-Sieve-Filtered"
92 | ["", ""]
93 | {
94 | addheader "X-Sieve-Filtered" "";
95 | redirect "kim@home.example.com";
96 | }
97 |
98 | 5. Action deleteheader
99 |
100 | Usage: "deleteheader" [":index" [":last"]]
101 | [COMPARATOR] [MATCH-TYPE]
102 |
103 | []
104 |
105 | By default, the deleteheader action deletes all occurrences of the
106 | named header field. The deleteheader action does not affect Sieve's
107 | implicit keep.
108 |
109 |
110 |
111 |
112 |
113 |
114 | Degener & Guenther Standards Track [Page 2]
115 |
116 | RFC 5293 Sieve Email Filtering: Editheader Extension August 2008
117 |
118 |
119 | The field-name is mandatory and always matched as a case-insensitive
120 | US-ASCII string. If the field-name is not a valid 7-bit header field
121 | name as described by the [IMAIL] "field-name" nonterminal syntax
122 | element, the implementation MUST flag an error.
123 |
124 | The value-patterns, if specified, restrict which occurrences of the
125 | header field are deleted to those whose values match any of the
126 | specified value-patterns, the matching being according to the match-
127 | type and comparator and performed as if by the "header" test. In
128 | particular, leading and trailing whitespace in the field values is
129 | ignored. If no value-patterns are specified, then the comparator and
130 | match-type options are silently ignored.
131 |
132 | If :index is specified, the attempts to match a value are
133 | limited to the occurrence of the named header field,
134 | beginning at 1, the first named header field. If :last is specified,
135 | the count is backwards; 1 denotes the last named header field, 2 the
136 | second to last, and so on. The counting happens before the match, if any. For example:
138 |
139 | deleteheader :index 1 :contains "Delivered-To"
140 | "bob@example.com";
141 |
142 | deletes the first "Delivered-To" header field if it contains the
143 | string "bob@example.com" (not the first "Delivered-To" field that
144 | contains "bob@example.com").
145 |
146 | It is not an error if no header fields match the conditions in the
147 | deleteheader action or if the :index argument is greater than the
148 | number of named header fields.
149 |
150 | The implementation MUST flag an error if :last is specified without
151 | also specifying :index.
152 |
153 | 6. Implementation Limitations on Changes
154 |
155 | As a matter of local policy, implementations MAY limit which header
156 | fields may be deleted and which header fields may be added. However,
157 | implementations MUST NOT permit attempts to delete "Received" and
158 | "Auto-Submitted" header fields and MUST permit both addition and
159 | deletion of the "Subject" header field.
160 |
161 | If a script tries to make a change that isn't permitted, the attempt
162 | MUST be silently ignored.
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 | Degener & Guenther Standards Track [Page 3]
171 |
172 | RFC 5293 Sieve Email Filtering: Editheader Extension August 2008
173 |
174 |
175 | 7. Interaction with Other Sieve Extensions
176 |
177 | Actions that generate [MDN], [DSN], or similar disposition messages
178 | MUST do so using the original, unmodified message header. Similarly,
179 | if an error terminates processing of the script, the original message
180 | header MUST be used when doing the implicit keep required by Section
181 | 2.10.6 of [SIEVE].
182 |
183 | All other actions that store, send, or alter the message MUST do so
184 | with the current set of header fields. This includes the addheader
185 | and deleteheader actions themselves. For example, the following
186 | leaves the message unchanged:
187 |
188 | addheader "X-Hello" "World";
189 | deleteheader :index 1 "X-Hello";
190 |
191 | Similarly, given a message with three or more "X-Hello" header
192 | fields, the following example deletes the first and third of them,
193 | not the first and second:
194 |
195 | deleteheader :index 1 "X-Hello";
196 | deleteheader :index 2 "X-Hello";
197 |
198 | Tests and actions such as "exists", "header", or "vacation"
199 | [VACATION] that examine header fields MUST examine the current state
200 | of a header as modified by any actions that have taken place so far.
201 |
202 | As an example, the "header" test in the following fragment will
203 | always evaluate to true, regardless of whether or not the incoming
204 | message contained an "X-Hello" header field:
205 |
206 | addheader "X-Hello" "World";
207 | if header :contains "X-Hello" "World"
208 | {
209 | fileinto "international";
210 | }
211 |
212 | However, if the presence or value of a header field affects how the
213 | implementation parses or decodes other parts of the message, then,
214 | for the purposes of that parsing or decoding, the implementation MAY
215 | ignore some or all changes made to those header fields. For example,
216 | in an implementation that supports the [BODY] extension, "body" tests
217 | may be unaffected by deleting or adding "Content-Type" or "Content-
218 | Transfer-Encoding" header fields. This does not rescind the
219 | requirement that changes to those header fields affect direct tests;
220 | only the semantic side effects of changes to the fields may be
221 | ignored.
222 |
223 |
224 |
225 |
226 | Degener & Guenther Standards Track [Page 4]
227 |
228 | RFC 5293 Sieve Email Filtering: Editheader Extension August 2008
229 |
230 |
231 | For the purpose of weeding out duplicates, a message modified by
232 | addheader or deleteheader MUST be considered the same as the original
233 | message. For example, in an implementation that obeys the constraint
234 | in Section 2.10.3 of [SIEVE] and does not deliver the same message to
235 | a folder more than once, the following code fragment
236 |
237 | keep;
238 | addheader "X-Flavor" "vanilla";
239 | keep;
240 |
241 | MUST only file one message. It is up to the implementation to pick
242 | which of the redundant "fileinto" or "keep" actions is executed, and
243 | which ones are ignored.
244 |
245 | The "implicit keep" is thought to be executed at the end of the
246 | script, after the headers have been modified. (However, a canceled
247 | "implicit keep" remains canceled.)
248 |
249 | 8. IANA Considerations
250 |
251 | The following template specifies the IANA registration of the Sieve
252 | extension specified in this document:
253 |
254 | To: iana@iana.org
255 | Subject: Registration of new Sieve extension
256 |
257 | Capability name: editheader
258 | Description: Adds actions 'addheader' and 'deleteheader' that
259 | modify the header of the message being processed
260 | RFC number: RFC 5293
261 | Contact Address: The Sieve discussion list
262 |
263 | 9. Security Considerations
264 |
265 | Someone with write access to a user's script storage may use this
266 | extension to generate headers that a user would otherwise be shielded
267 | from (e.g., by a gateway Mail Transport Agent (MTA) that removes
268 | them).
269 |
270 | This is the first Sieve extension to be standardized that allows
271 | alteration of messages being processed by Sieve engines. A Sieve
272 | script that uses Sieve tests defined in [SIEVE], the editheader
273 | extension, and the redirect action back to the same user can keep
274 | some state between different invocations of the same script for the
275 | same message. But note that it would not be possible to introduce an
276 | infinite loop using any such script, because each iteration adds a
277 | new Received header field, so email loop prevention described in
278 | [SMTP] will eventually non deliver the message, and because the
279 |
280 |
281 |
282 | Degener & Guenther Standards Track [Page 5]
283 |
284 | RFC 5293 Sieve Email Filtering: Editheader Extension August 2008
285 |
286 |
287 | editheader extension is explicitly prohibited to alter or delete
288 | Received header fields (i.e., it can't interfere with loop
289 | prevention).
290 |
291 | A sieve filter that removes header fields may unwisely destroy
292 | evidence about the path a message has taken.
293 |
294 | Any change in message content may interfere with digital signature
295 | mechanisms that include the header in the signed material. For
296 | example, changes to (or deletion/addition of) header fields included
297 | in the "SHOULD be included in the signature" list in Section 5.5 of
298 | [DKIM] can invalidate DKIM signatures. This also includes DKIM
299 | signatures that guarantee a header field absence.
300 |
301 | The editheader extension doesn't directly affect [IMAIL] header field
302 | signatures generated using [SMIME] or [OPENPGP], because these
303 | signature schemes include a separate copy of the header fields inside
304 | the signed message/rfc822 body part. However, software written to
305 | detect differences between the inner (signed) copy of header fields
306 | and the outer (modified by editheader) header fields might be
307 | affected by changes made by editheader.
308 |
309 | Since normal message delivery adds "Received" header fields and other
310 | trace fields to the beginning of a message, many such digital
311 | signature mechanisms are impervious to headers prefixed to a message,
312 | and will work with "addheader" unless :last is used.
313 |
314 | Any decision mechanism in a user's filter that is based on headers is
315 | vulnerable to header spoofing. For example, if the user adds an
316 | APPROVED header or tag, a malicious sender may add that tag or header
317 | themselves. One way to guard against this is to delete or rename any
318 | such headers or stamps prior to processing the message.
319 |
320 | 10. Acknowledgments
321 |
322 | Thanks to Eric Allman, Cyrus Daboo, Matthew Elvey, Ned Freed, Arnt
323 | Gulbrandsen, Kjetil Torgrim Homme, Simon Josefsson, Will Lee, William
324 | Leibzon, Mark E. Mallett, Chris Markle, Alexey Melnikov, Randall
325 | Schwartz, Aaron Stone, Nigel Swinson, and Rand Wacker for extensive
326 | corrections and suggestions.
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 | Degener & Guenther Standards Track [Page 6]
339 |
340 | RFC 5293 Sieve Email Filtering: Editheader Extension August 2008
341 |
342 |
343 | 11. References
344 |
345 | 11.1. Normative References
346 |
347 | [IMAIL] Resnick, P., Ed., "Internet Message Format", RFC 2822,
348 | April 2001.
349 |
350 | [KEYWORDS] Bradner, S., "Key words for use in RFCs to Indicate
351 | Requirement Levels", BCP 14, RFC 2119, March 1997.
352 |
353 | [MIME3] Moore, K., "MIME (Multipurpose Internet Mail Extensions)
354 | Part Three: Message Header Extensions for Non-ASCII
355 | Text", RFC 2047, November 1996.
356 |
357 | [MIMEPARAM] Freed, N. and K. Moore, "MIME Parameter Value and
358 | Encoded Word Extensions: Character Sets, Languages, and
359 | Continuations", RFC 2231, November 1997.
360 |
361 | [SIEVE] Guenther, P., Ed., and T. Showalter, Ed., "Sieve: An
362 | Email Filtering Language", RFC 5228, January 2008.
363 |
364 | 11.2. Informative References
365 |
366 | [BODY] Degener, J. and P. Guenther, "Sieve Email Filtering:
367 | Body Extension", RFC 5173, April 2008.
368 |
369 | [DKIM] Allman, E., Callas, J., Delany, M., Libbey, M., Fenton,
370 | J., and M. Thomas, "DomainKeys Identified Mail (DKIM)
371 | Signatures", RFC 4871, May 2007.
372 |
373 | [DSN] Moore, K. and G. Vaudreuil, "An Extensible Message
374 | Format for Delivery Status Notifications", RFC 3464,
375 | January 2003.
376 |
377 | [MDN] Hansen, T., Ed., and G. Vaudreuil, Ed., "Message
378 | Disposition Notification", RFC 3798, May 2004.
379 |
380 | [OPENPGP] Elkins, M., Del Torto, D., Levien, R., and T. Roessler,
381 | "MIME Security with OpenPGP", RFC 3156, August 2001.
382 |
383 | [SMIME] Ramsdell, B., Ed., "Secure/Multipurpose Internet Mail
384 | Extensions (S/MIME) Version 3.1 Message Specification",
385 | RFC 3851, July 2004.
386 |
387 | [SMTP] Klensin, J., Ed., "Simple Mail Transfer Protocol", RFC
388 | 2821, April 2001.
389 |
390 |
391 |
392 |
393 |
394 | Degener & Guenther Standards Track [Page 7]
395 |
396 | RFC 5293 Sieve Email Filtering: Editheader Extension August 2008
397 |
398 |
399 | [VACATION] Showalter, T. and N. Freed, Ed., "Sieve Email Filtering:
400 | Vacation Extension", RFC 5230, January 2008.
401 |
402 | Authors' Addresses
403 |
404 | Jutta Degener
405 | 5245 College Ave, Suite #127
406 | Oakland, CA 94618
407 |
408 | EMail: jutta@pobox.com
409 |
410 |
411 | Philip Guenther
412 | Sendmail, Inc.
413 | 6475 Christie Ave., Ste 350
414 | Emeryville, CA 94608
415 |
416 | EMail: guenther@sendmail.com
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 | Degener & Guenther Standards Track [Page 8]
451 |
452 | RFC 5293 Sieve Email Filtering: Editheader Extension August 2008
453 |
454 |
455 | Full Copyright Statement
456 |
457 | Copyright (C) The IETF Trust (2008).
458 |
459 | This document is subject to the rights, licenses and restrictions
460 | contained in BCP 78, and except as set forth therein, the authors
461 | retain all their rights.
462 |
463 | This document and the information contained herein are provided on an
464 | "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS
465 | OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY, THE IETF TRUST AND
466 | THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS
467 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF
468 | THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED
469 | WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
470 |
471 | Intellectual Property
472 |
473 | The IETF takes no position regarding the validity or scope of any
474 | Intellectual Property Rights or other rights that might be claimed to
475 | pertain to the implementation or use of the technology described in
476 | this document or the extent to which any license under such rights
477 | might or might not be available; nor does it represent that it has
478 | made any independent effort to identify any such rights. Information
479 | on the procedures with respect to rights in RFC documents can be
480 | found in BCP 78 and BCP 79.
481 |
482 | Copies of IPR disclosures made to the IETF Secretariat and any
483 | assurances of licenses to be made available, or the result of an
484 | attempt made to obtain a general license or permission for the use of
485 | such proprietary rights by implementers or users of this
486 | specification can be obtained from the IETF on-line IPR repository at
487 | http://www.ietf.org/ipr.
488 |
489 | The IETF invites any interested party to bring to its attention any
490 | copyrights, patents or patent applications, or other proprietary
491 | rights that may cover technology that may be required to implement
492 | this standard. Please address the information to the IETF at
493 | ietf-ipr@ietf.org.
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 | Degener & Guenther Standards Track [Page 9]
507 |
508 |
--------------------------------------------------------------------------------