├── 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 | [![Build Status](https://img.shields.io/travis/ProtonMail/libsieve-php.svg?style=flat-square)](https://travis-ci.org/ProtonMail/libsieve-php) 4 | [![Coverage](https://img.shields.io/codecov/c/github/ProtonMail/libsieve-php.svg?style=flat-square)](https://codecov.io/gh/ProtonMail/libsieve-php) 5 | [![License](https://img.shields.io/github/license/ProtonMail/libsieve-php.svg?style=flat-square)](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 | --------------------------------------------------------------------------------