RFC 5784

Sieve Email Filtering: Sieves and Display Directives in XML

๐Ÿ“… published March 2010, from ietf

RFC 5784: Sieve Email Filtering: Sieves and Display Directives in XML ๐Ÿ’–

Internet Engineering Task Force (IETF) ๐ŸŒธ Authors: N. Freed and S. Vedam, both at Sun Microsystems ๐ŸŽ€ Category: Standards Track, so this is an official Internet Standards Track document โœจ ISSN: 2070-1721 Date: March 2010 ๐Ÿ“…

Okay, the header has no "obsoletes" or "updates" entries, so this RFC doesn't replace or change any earlier RFC, and it's a brand new standard. Main character debut ๐Ÿ’…

Abstract โœจ

Okay so this doc is about turning Sieve email filtering scripts into XML. ๐Ÿงš The XML version is NOT meant to be a new way to store Sieve. It's there so you can poke at scripts with XML tools, which is so useful, obsessed ๐Ÿ˜ญ

The XML version also adds extra elements that don't exist in regular Sieve. These are for graphical UIs, so you can label or group chunks of a script and display them more nicely. In the normal Sieve format, those elements show up as specially structured comments. Iconic ๐Ÿ‘‘

Status of This Memo

This is an Internet Standards Track document. ๐Ÿ’œ

It's a product of the Internet Engineering Task Force (IETF), and it reflects what the IETF community agreed on. It got public review and was approved for publication by the Internet Engineering Steering Group (IESG). If you want more on Internet Standards, check Section 2 of RFC 5741. ๐Ÿ“š

For the current status of this document, any errata, and how to send feedback, go to http://www.rfc-editor.org/info/rfc5784. ๐Ÿ”—

Copyright (c) 2010 IETF Trust and the persons identified as the document authors. All rights reserved. ๐ŸŒˆ

This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents, which you can find at http://trustee.ietf.org/license-info, as they were in effect on the date this document was published. Please read those carefully, because they spell out your rights and restrictions for this document. Code Components pulled out of this document must include the Simplified BSD License text, as described in Section 4.e of the Trust Legal Provisions, and they come with no warranty, as described in the Simplified BSD License itself. โš–๏ธ

Table of Contents ๐Ÿ“–

   1.  Introduction . . . . . . . . . . . . . . . . . . . . . . . . .  3
   2.  Conventions Used in This Document  . . . . . . . . . . . . . .  4
   3.  Grammatical Structure of Sieve . . . . . . . . . . . . . . . .  4
   4.  XML Representation of Sieve  . . . . . . . . . . . . . . . . .  6
     4.1.  XML Display Directives . . . . . . . . . . . . . . . . . .  9
     4.2.  Structured Comments  . . . . . . . . . . . . . . . . . . . 10
     4.3.  Validation . . . . . . . . . . . . . . . . . . . . . . . . 11
   5.  Security Considerations  . . . . . . . . . . . . . . . . . . . 11
   6.  IANA Considerations  . . . . . . . . . . . . . . . . . . . . . 11
   7.  References . . . . . . . . . . . . . . . . . . . . . . . . . . 12
     7.1.  Normative References . . . . . . . . . . . . . . . . . . . 12
     7.2.  Informative References . . . . . . . . . . . . . . . . . . 13
   Appendix A.  Extended Example  . . . . . . . . . . . . . . . . . . 14
   Appendix B.  XML Schema for Sieves in XML  . . . . . . . . . . . . 21
   Appendix C.  Relax NG Schema for Sieves in XML . . . . . . . . . . 24
   Appendix D.  Stylesheet for Conversion from XML  . . . . . . . . . 26
   Appendix E.  Acknowledgements  . . . . . . . . . . . . . . . . . . 32

1. Introduction โœจ

Okay bestie, so [RFC5228] defines Sieve, which is a language for filtering email at or around the moment it gets delivered. ๐Ÿ’Œ It's built to run on either a mail client or a mail server. It's supposed to be extensible and simple, and it doesn't care about the access protocol, the mail setup, or the operating system. It's also meant to be driven by all kinds of different user interfaces. ๐Ÿฆ‹

Lots of UI environments already have solid tools for handling XML [XML]. You could add support for other data syntaxes in most of them, but it's often not very convenient. The obvious workaround is to map Sieve scripts into XML, maybe on a separate back-end system, edit the XML, and convert it back into normal Sieve. ๐Ÿ”„

Converting to and from XML can happen as its own step on a totally different machine, which is a strong reason to have one shared XML representation for Sieve. That way, different front-end UIs can work with different back-end mapping and storage setups. ๐Ÿ“

Here's the catch though: just sharing the same mapping and storage doesn't mean the extra XML info that different front ends make will work together. ๐Ÿ™ˆ

Sieve extensions have already been defined, like [RFC5183], [RFC5229], [RFC5230], [RFC5231], [RFC5232], [RFC5233], [RFC5235], and [RFC5293], and more are on the way. Which extensions you get depends on the implementation, and it can even change depending on configuration. So the XML representation has to handle Sieve extensions without needing schema changes. It's also nice if extensions don't force changes to the code that does the XML conversion. ๐Ÿ’…

This spec defines an XML representation for Sieve scripts and explains how converting to and from XML works. The XML representation can handle any future Sieve extension as long as the underlying Sieve grammar stays the same. Also, the code that converts XML back to normal Sieve needs no changes for extensions. The code that goes the other way, from normal Sieve to XML, only needs changes when new control commands get added, and that's a rare event. โœจ The appendices also include an XML Schema, a Relax NG Schema, and a sample stylesheet for converting from the XML format. ๐ŸŽ€

2. Conventions Used in This Document ๐Ÿ“š

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 [RFC2119]. ๐Ÿซถ

"Processor" is the word used throughout this document for agents that convert Sieve to and from the XML representation. "Editor" refers to agents that work on Sieves in XML format, which can mean creating or modifying them. ๐Ÿ–Š๏ธ

3. Grammatical Structure of Sieve ๐Ÿงฉ

The Sieve language was designed to be super extensible without changing its basic syntax. ๐ŸŒธ So the syntax of Sieve, defined in Section 8 of [RFC5228], is entirely structural and has no reserved words at all. ๐Ÿ™Œ

Structurally, a Sieve script is a series of commands. Each command has an identifier, zero or more arguments, an optional test or test-list, and finally an optional block that holds another series of commands. Commands split further into controls and actions, but you can't tell that difference from the grammar alone. ๐Ÿคทโ€โ™€๏ธ

Here are some example Sieve controls:

stop;               <-- No arguments, test, or command block
require "fileinto"; <-- Control with a single argument
if true {stop;}     <-- Control with test and command block

And here are some example Sieve actions:

discard;            <-- Action with no arguments, test, or command block
fileinto "folder";  <-- Action with an argument

As of writing this, no controls are defined that take both arguments and a test. Similarly, no defined actions allow either a test or a command block. Still, the Sieve grammar allows those constructs to be defined by some future extension. ๐Ÿ”ฎ

A test is an identifier followed by zero or more arguments, then another test or test-list. Unlike commands, tests can't be followed by a command block. ๐Ÿšซ

Here are some Sieve test examples. Keep in mind that tests have to show up as part of a command to be syntactically valid. ๐Ÿ’–

true                <-- Test with no argument or subordinate test
envelope "to" "me@example.com" <-- Test with several arguments
header :is "from" "you@example.com" <-- Test with tagged argument

Command or test arguments can be string lists, whole numbers, or tags. (Tags are just identifiers with a colon in front.) The Sieve grammar treats a single string as a degenerate case of a string list, but some tests or actions only take individual strings, not lists, so watch out ๐Ÿ‘€

Here's an example that uses both a test-list and a string list:

if anyof (not exists ["From", "Date"],
          header :contains "from" "fool@example.edu") {
  discard;
}

Extensions can add new controls, actions, tests, or arguments to existing controls or actions. Extensions have also changed how string content gets interpreted, though that doesn't matter for this spec. The big thing to notice is that, so far, only one of the many Sieve extensions, the foreverypart extension [RFC5703], has added new controls to the language. Because of their nature, it's pretty safe to assume new controls will be pretty rare in the future too. ๐Ÿงš

Last but not least, comments are allowed between lexical elements in a Sieve script. One important use for comments is storing metadata about the script, and Sieve doesn't have any other way to do that. So comments need to be kept in the XML representation. ๐Ÿ’œโœจ

4. XML Representation of Sieve โœจ

okay so bestie, Sieve controls and actions get written in XML as "control" or "action" elements ๐ŸŽ€. The command's identifier lives as a name attribute right on the element itself. That's literally the only attribute allowed on controls and actions ๐Ÿ’…. Arguments, tests, test-lists, and nested command blocks all show up as nested elements instead. You might think naming the element after the control or action is the cuter move, but nope, that would make extensions need frequent schema changes ๐Ÿ™ˆ

The example Sieve controls from the last section would look like this in XML:

<control name="stop"/>

<control name="require"><str>fileinto</str></control>

<control name="if">
 <test name="true"/><control name="stop"/>
</control>

And the example Sieve actions from above would show up as:

<action name="discard"/>
<action name="fileinto"><str>folder</str></action>

So controls and actions are separated in XML, which means the converter from normal Sieve format to XML has to be able to tell them apart ๐Ÿงš. The easy trick is keeping a list of every known control, since new controls don't get added very often. As of writing, the defined controls are:

  1. if [RFC5228],
  2. stop [RFC5228],
  3. require [RFC5228],
  4. foreverypart [RFC5703], and
  5. break [RFC5703].

Heads up: with this approach, unknown controls just get treated as actions, and they can still go back and forth between the two representations ๐Ÿฆ‹. Treating a control as an action is unlikely to cause other problems, since you almost always need to know a control's language semantics to make use of it anyway.

Tests are represented the same basic way as controls and actions, as a "test" element with a name attribute holding the test identifier ๐Ÿ’œ. Here are a few different kinds of tests:

<test name="true"/>

<test name="envelope"/>
  <str>to</str><str>me@example.com</str>
</test>

<test name="header"/>
  <tag>is</tag><str>from</str><str>you@example.com</str>
</test>

String, number, and tag arguments are represented as "str", "num", and "tag" elements, respectively. The actual string, number, or tag identifier sits inside the element as text. None of these elements have any defined attributes ๐Ÿ“. You've already seen several arguments in the control, action, and test examples above. Any whitespace in the str body content MUST be preserved by the processor ๐Ÿซถ. Also, since strings and tags are just element text, any quotes or other syntax needed in regular Sieve gets dropped rather than carried over into the XML ๐Ÿ’€.

String list arguments are a "list" element that holds one or more str elements. This keeps the difference between a single string and a string list containing one string ๐ŸŒธ. It isn't essential, since a list with one string could just be mapped to a plain string, but it seems smart to keep the distinction when going to and from XML.

Nested command blocks show up as a series of control or action elements inside an outer control or action element. No block element is needed, because an inner command block can only appear once, and only after any arguments, tests, or test-lists ๐ŸŽ€. For example:

<control name="if">
  <test name="anyof">
    <test name="not">
      <test name="exists">
        <list><str>From</str><str>Date</str></list>
      </test>
    </test>
    <test name="header">
      <tag>contains</tag>
      <str>from</str>
      <str>fool@example.edu</str>
    </test>
  </test>
  <action name="discard"/>
</control>

Sieve comments get mapped to a special "comment" element in XML โœจ. Hash comments and bracketed comments both map to the same construct, so the difference between them is lost in XML. XML comments aren't used, because some XML tools make it annoying to get at comment nodes ๐Ÿ™ˆ.

Comments are allowed between commands and inside "test" elements. But they aren't allowed directly inside commands. Because of XML Schema limitations, you need special enclosing "preamble" and "postamble" elements ๐Ÿ˜ญ:

<control name="if">
  <preamble>
    <comment>Check to see if message should be discarded</comment>
  </preamble>
  <test name="anyof">
    <test name="not">
      <test name="exists">
        <list><str>From</str><str>Date</str></list>
      </test>
    </test>
    <test name="header">
      <tag>contains</tag>
      <str>from</str>
      <str>fool@example.edu</str>
    </test>
  </test>
  <action name="discard"/>
  <postamble>
    <comment>End of discard check</comment>
  </postamble>
</control>

4.1. XML Display Directives

Sometimes graphical user interfaces are the easiest way to give users Sieve management tools ๐Ÿ’–. These interfaces usually summarize, annotate, group, and display Sieve scripts in a way that makes sense to end users.

To do that well, the GUI might need extra information about the Sieve script itself. That "meta-data" could include, but isn't limited to, a Sieve name (identifying the current Sieve), whether the Sieve is enabled or disabled, and the order the parts of the Sieve get presented to the user โญ. The GUI can also offer ways for the user to change the script.

GUIs also love grouping related script elements so they can be shown separately and managed as one object ๐ŸŒˆ. Examples include Sieve statements that together make up vacation responders, blacklists and whitelists, and other filtering controls.

Some advanced GUIs even show a natural language version of a Sieve script, and/or an advanced interface that presents Sieve statements directly ๐Ÿงš.

A GUI might only support a subset of action commands in the Sieve language (and its extensions). So a way to show how much is supported, and to describe how the supported actions relate to the test and its arguments, is super useful and probably required for clients that don't fully know Sieve grammar and semantics ๐Ÿ‘‘.

Here's the tea: the Sieve language has no way to say how a script should be shown in a user interface ๐Ÿ™ˆ. It also doesn't have any specific mechanisms for other meta-data about the script. Adding that kind of support to a Sieve script is totally implementation specific right now, and people usually do it by imposing some structure on comments.

Still, that information can be represented in XML really easily, so it makes sense to define a framework for it as part of the XML format ๐Ÿ’…. Implementations MAY choose to use structured comments to keep this information when the script is converted to normal Sieve format.

The sample schemata for the XML representation of Sieve let XML from foreign namespaces get inserted anywhere "comment" elements can appear in Sieve scripts. That's the preferred way to add extra info ๐Ÿซถ. The schema also defines two display directives, displayblock and displaydata, as containers for the meta-data GUIs need.

Editors MAY use displayblock, displaydata, and foreign namespaces to associate meta-data. Some editors find it inconvenient to keep this extra data during an editing session. Editors MAY preserve this data during an editing session so they stay compatible with other editors ๐Ÿฆ‹.

The displayblock element can enclose any number of Sieve statements at any level. It means nothing to the Sieve script itself, and it allows an arbitrary set of attributes. Implementations MAY use it to provide lots of simple, display-related meta-data for the Sieve, like a Sieve identifier, group identifier, order of processing, and so on ๐ŸŽ€.

The displaydata element supports any number of arbitrary child elements. Implementations MAY use it for complex data about that Sieve, like a natural language representation of the Sieve or a way to provide the Sieve script directly ๐ŸŒธ.


4.2. Structured Comments

The XML representation isn't meant to be a storage format, so there needs to be a way to keep the extra info from XML inside normal Sieve syntax ๐Ÿ’œ. This happens through three structured comment conventions:

  1. XML content in other namespaces goes inside Sieve bracketed comments that begin with the string /* [/ and end with the string /] */.
  2. The content of displaydata elements goes inside Sieve bracketed comments that begin with the string /* [| and end with the string |] */.
  3. The start of a displayblock element maps to a bracketed Sieve comment beginning with the string /* [*, which then lists any displayblock attribute names and values in XML format. The end of a displayblock element maps to a comment of the form *] */.

Processors MUST preserve the additional information allowed in the XML format and SHOULD use the structured comment format shown above โœจ.

Note: If */ is found in the XML content, it would prematurely end the comment once mapped into one. Escaping that sequence would often be inconvenient for processors ๐Ÿ˜ญ. Editors SHALL NOT include */ within displayblock, displaydata, or foreign markup. Processors MAY regard documents containing */ in foreign markup, displayblock, or displaydata as invalid.

4.3. Validation

A processor MAY validate documents against a schema, and it MAY reject any that don't conform ๐Ÿ”ฅ. For any document a processor doesn't reject as invalid, any markup the processor can't understand by reference to this specification MAY be discarded ๐Ÿ’….

Example Relax NG and XML Schema are given in the appendices below ๐Ÿงš.

5. Security Considerations

Any syntactically valid Sieve script can be represented in XML ๐Ÿšจ. So every security consideration that applies to Sieve and any extensions used also applies to the XML representation. Don't skip this part, bestie!

The use of XML carries its own security risks ๐Ÿ’€. Section 7 of RFC 3470 [RFC3470] goes through these risks.

It's axiomatic that a Sieve editor has to be trusted to do what the user specifies ๐Ÿซถ. If XML formats are used, that trust has to extend to every component involved in converting to and from XML format.

Arbitrary data can be included using other namespaces, or placed in the extensible displayblock and displaydata constructs defined in this specification. That could include entire scripts and other executable content in languages other than Sieve ๐Ÿ˜ญ. Material like that will have its own security considerations, which are beyond the scope of this document.

6. IANA Considerations โœจ

okay so this section registers a brand new XML namespace, and it follows the rules in [RFC3688] ๐ŸŽ€ Here's the registration info, bestie:

URI:  urn:ietf:params:xml:ns:sieve

Registrant Contact:  IETF Sieve working group
                     <ietf-mta-filters@imc.org>

And here's the XML registration itself, which is basically the namespace's little calling card ๐Ÿ’…

XML:

  BEGIN
  <?xml version="1.0"?>
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.0//EN"
    "http://www.w3.org/TR/xhtml-basic/xhtml-basic10.dtd">
  <html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="content-type"
          content="text/html;charset=iso-8859-1"/>
    <title>Sieve Namespace</title>
  </head>
  <body>
    <h1>Namespace for Sieve Language objects expressed in XML</h1>
    <h2>urn:ietf:params:xml:ns:sieve</h2>
    <p>See <a href="http://www.rfc-editor.org/rfc/rfc5784.txt">
     RFC 5784</a>.
    </p>
  </body>
  </html>
  END

7. References ๐Ÿ“š

7.1. Normative References ๐Ÿง 

these are the must-read sources the spec leans on, periodt:

  • [OASISRNC] Clark, J., "RELAX NG Compact Syntax", OASIS Committee Specification rnc, November 2002.
  • [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997.
  • [RFC3470] Hollenbeck, S., Rose, M., and L. Masinter, "Guidelines for the Use of Extensible Markup Language (XML) within IETF Protocols", BCP 70, RFC 3470, January 2003.
  • [RFC3688] Mealling, M., "The IETF XML Registry", BCP 81, RFC 3688, January 2004.
  • [RFC5228] Guenther, P. and T. Showalter, "Sieve: An Email Filtering Language", RFC 5228, January 2008.
  • [XML] Bray, T., Paoli, J., Sperberg-McQueen, C., Maler, E., and F. Yergeau, "Extensible Markup Language (XML) 1.0 (Fifth Edition)", W3C REC-xml-20081126, November 2008, <http://www.w3.org/TR/2008/REC-xml-20081126/>.

7.2. Informative References ๐Ÿฆ‹

these ones are extra reading, lowkey a whole vibe:

  • [RFC5183] Freed, N., "Sieve Email Filtering: Environment Extension", RFC 5183, May 2008.
  • [RFC5229] Homme, K., "Sieve Email Filtering: Variables Extension", RFC 5229, January 2008.
  • [RFC5230] Showalter, T. and N. Freed, "Sieve Email Filtering: Vacation Extension", RFC 5230, January 2008.
  • [RFC5231] Segmuller, W. and B. Leiba, "Sieve Email Filtering: Relational Extension", RFC 5231, January 2008.
  • [RFC5232] Melnikov, A., "Sieve Email Filtering: Imap4flags Extension", RFC 5232, January 2008.
  • [RFC5233] Murchison, K., "Sieve Email Filtering: Subaddress Extension", RFC 5233, January 2008.
  • [RFC5235] Daboo, C., "Sieve Email Filtering: Spamtest and Virustest Extensions", RFC 5235, January 2008.
  • [RFC5293] Degener, J. and P. Guenther, "Sieve Email Filtering: Editheader Extension", RFC 5293, August 2008.
  • [RFC5703] Hansen, T. and C. Daboo, "Sieve Email Filtering: MIME Part Tests, Iteration, Extraction, Replacement, and Enclosure", RFC 5703, October 2009.

Appendix A. Extended Example ๐ŸŽ€

okay this is the fun part, the example! we're taking the Sieve script from Section 9 of [RFC5228] and dressing it up as XML ๐Ÿงš here's the glow-up:

<sieve xmlns="urn:ietf:params:xml:ns:sieve">
  <comment>
    Example Sieve Filter
    Declare any optional features or extensions used by the script
  </comment>

  <control name="require">
    <str>fileinto</str>
  </control>
  <comment>
    Handle messages from known mailing lists.
    Move messages from IETF filter discussion list to filter mailbox.
  </comment>
  <control name="if">
    <test name="header">
      <tag>is</tag>
      <str>Sender</str>
      <str>owner-ietf-mta-filters@imc.org</str>
    </test>
    <action name="fileinto">
      <str>filter</str>
    </action>
    <postamble>
      <comment>move to "filter" mailbox</comment>
    </postamble>
  </control>

  <comment>
    Keep all messages to or from people in my company.
  </comment>
  <control name="elsif">
    <test name="address">
      <tag>domain</tag>
      <tag>is</tag>
      <list>
        <str>From</str>
        <str>To</str>
      </list>
      <str>example.com</str>
    </test>
    <action name="keep"/>
  </control>
  <comment>
    Try and catch unsolicited email.  If a message is not to me,
    or it contains a subject known to be spam, file it away.
  </comment>
  <control name="elsif">
    <test name="anyof">
      <test name="not">
        <test name="address">
          <tag>all</tag>
          <tag>contains</tag>
          <list>
            <str>To</str>
            <str>Cc</str>
            <str>Bcc</str>
          </list>
          <str>me@example.com</str>
        </test>
      </test>
      <test name="header">
        <tag>matches</tag>
        <str>subject</str>
        <list>
          <str>*make*money*fast*</str>
          <str>*university*dipl*mas*</str>
        </list>
      </test>
    </test>
    <action name="fileinto">
      <str>spam</str>
    </action>
  </control>
  <control name="else">
    <preamble>
      <comment>
        Move all other (non-company) mail to "personal"
        mailbox.
      </comment>
    </preamble>
    <action name="fileinto">
      <str>personal</str>
    </action>
  </control>

</sieve>

the same script can be dressed up with graphical display hints in a bunch of different ways ๐Ÿ’– three possible code components that do this are:

<sieve xmlns="urn:ietf:params:xml:ns:sieve">

  <control name="require">
    <str>fileinto</str>
  </control>

  <displayblock name="File filter list mail" order="1"
                group="FILE_TO_FOLDER" enable="true">
    <control name="if">
      <test name="header">
        <tag>is</tag>
        <str>Sender</str>
        <str>owner-ietf-mta-filters@imc.org</str>
      </test>
      <action name="fileinto">
        <str>filter</str>
      </action>
    </control>
  </displayblock>

  <displayblock name="Keep all company mail" order="2"
                group="KEEP_MESSAGE" enable="true">
    <control name="elsif">
      <test name="address">
        <tag>domain</tag>
        <tag>is</tag>
        <list>
          <str>From</str>
          <str>To</str>
        </list>
        <str>example.com</str>
      </test>
      <action name="keep"/>
   </control>
  </displayblock>

  <displayblock name="File suspected spam" order="3"
                group="FILE_TO_FOLDER" enable="true">
     <control name="elsif">
       <test name="anyof">
         <test name="not">
           <test name="address">
             <tag>all</tag>
             <tag>contains</tag>
             <list>

               <str>To</str>
               <str>Cc</str>
               <str>Bcc</str>
             </list>
             <str>me@example.com</str>
           </test>
         </test>
         <test name="header">
           <tag>matches</tag>
           <str>subject</str>
           <list>
             <str>*make*money*fast*</str>
             <str>*university*dipl*mas*</str>
           </list>
         </test>
       </test>
       <action name="fileinto">
         <str>spam</str>
       </action>
     </control>
   </displayblock>

  <displayblock name="File noncompany mail as personal" order="4"
                group="FILE_TO_FOLDER" enable="true">
    <control name="else">
      <action name="fileinto">
        <str>personal</str>
      </action>
    </control>
  </displayblock>

</sieve>

psst, note this! since displayblock elements are semantically null as far as the script itself is concerned, they can be used to group structures like elsif and else that are tied to statements in other groups ๐Ÿซถ

the representation of this script in regular Sieve syntax uses structured comments ๐ŸŒธ

require "fileinto";
/* [* name="File filter list mail" order="1"
      group="FILE_TO_FOLDER" enable="true" */
if header :is "Sender" "owner-ietf-mta-filters@imc.org"
{
  fileinto "filter";
}
/* *] */
/* [* name="Keep all company mail" order="2"
      group="KEEP_MESSAGE" enable="true" */
elsif address :domain :is [ "From", "To" ] "example.com"
{
  keep;
}
/* *] */
/* [* name="File suspected spam" order="3"
      group="FILE_TO_FOLDER" enable="true" */
elsif anyof ( not ( address :all :contains [ "To", "Cc", "Bcc" ]
                    "me@example.com" ),
              header :matches "subject" [ "*make*money*fast*",
                                          "*university*dipl*mas*" ] )
{
  fileinto "spam";
}
/* *] */
/* [* name="File noncompany mail as personal" order="4"
      group="FILE_TO_FOLDER" enable="true" */
else
{
  fileinto "personal";
}
/* *] */

a separate namespace can be used to embed text or structured information ๐Ÿฆ‹

<sieve xmlns="urn:ietf:params:xml:ns:sieve"
       xmlns:nls="http://example.com/nls">

  <nls:interpretation>
    If the email header "Sender" is owner-ietf-mta-filters@imc.org,
    then file it into the "filter" folder.

    Otherwise, if the address in the "From" or "To" has a domain
    that is "example.com", then keep it.

    Otherwise, messages meeting with any of these conditions:

    (1) None of the addresses in "To", "Cc", or "Bcc" contain
        the domain "example.com".

    (2) The "Subject" field matches the pattern *make*money*fast*
        or *university*dipl*mas*, then file it into the "spam"
        folder.

    If all else fails, then file the message in the "personal"
    folder.
  </nls:interpretation>

  ... the actual Sieve script ...

</sieve>

alternately, displaydata elements can get the same job done ๐Ÿ’…

<sieve xmlns="urn:ietf:params:xml:ns:sieve">

  <displaydata>
    <nls-interpretation>
      If the email header "Sender" is owner-ietf-mta-filters@imc.org,
      then file it into the "filter" folder.

      Otherwise, if the address in the "From" or "To" has a domain
      that is "example.com", then keep it.

      Otherwise, messages meeting with any of these conditions:

      (1) None of the addresses in "To","Cc", or "Bcc" contain
          the domain "example.com".

      (2) The "Subject" field matches the pattern *make*money*fast*
          or *university*dipl*mas*, then file it into the "spam"
          folder.

      If all else fails, then file the message in the "personal"
      folder.
    </nls-interpretation>
  </displaydata>

  ... the actual Sieve script ...

</sieve>

again, structured comments are how you show this in regular Sieve syntax, iykyk ๐Ÿ™ˆ

  /* [|
    <nls-interpretation>
      If the email header "Sender" is owner-ietf-mta-filters@imc.org,
      then file it into the "filter" folder.

      Otherwise, if the address in the "From" or "To" has a domain
      that is "example.com", then keep it.

      Otherwise, messages meeting with any of these conditions:

      (1) None of the addresses in "To","Cc", or "Bcc" contain
          the domain "example.com".

      (2) The "Subject" field matches the pattern *make*money*fast*
          or *university*dipl*mas*, then file it into the "spam"
          folder.

      If all else fails, then file the message in the "personal"
      folder.
    </nls-interpretation>
  |] */

  ... the actual Sieve script ...

Appendix B. XML Schema for Sieves in XML โœจ

okay so this appendix is informative ๐Ÿซถ It's a little XML Schema for the XML representation of Sieve scripts. Most of the elements with a complex content model are happy to take elements from other namespaces, but with lax XML Schema validation rules ๐Ÿงš Also, displaydata elements can wrap arbitrary XML content ๐ŸŽ€ And displayblock elements are the general-purpose grouping thing, where arbitrary attributes are allowed on them ๐Ÿ’…

<?xml version="1.0" encoding="UTF-8"?>

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns="urn:ietf:params:xml:ns:sieve"
            targetNamespace="urn:ietf:params:xml:ns:sieve">

  <xsd:element name="sieve">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:choice maxOccurs="unbounded" minOccurs="0">

          <xsd:element ref="control"/>
          <xsd:element ref="action"/>
          <xsd:element ref="displayblock"/>
          <xsd:element ref="displaydata"/>
          <xsd:element ref="comment"/>
          <xsd:any namespace="##other" processContents="lax"/>
        </xsd:choice>
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>

  <xsd:element name="comment" type="xsd:string"/>

  <xsd:complexType name="ambles">
    <xsd:choice minOccurs="0" maxOccurs="unbounded">
      <xsd:element ref="displaydata"/>
      <xsd:element ref="comment"/>
      <xsd:any namespace="##other" processContents="lax"/>
   </xsd:choice>
  </xsd:complexType>

  <xsd:element name="preamble" type="ambles"/>
  <xsd:element name="postamble" type="ambles"/>

  <xsd:complexType name="command">
    <xsd:sequence>
      <xsd:element ref="preamble" minOccurs="0" maxOccurs="1"/>
      <xsd:choice minOccurs="0" maxOccurs="unbounded">
        <xsd:element ref="str"/>
        <xsd:element ref="num"/>
        <xsd:element ref="list"/>
        <xsd:element ref="tag"/>
      </xsd:choice>
      <xsd:element ref="test" minOccurs="0" maxOccurs="1"/>
      <xsd:choice minOccurs="0" maxOccurs="unbounded">
        <xsd:element ref="control"/>
        <xsd:element ref="action"/>
        <xsd:element ref="displayblock"/>
      </xsd:choice>
      <xsd:element ref="postamble" minOccurs="0" maxOccurs="1"/>
    </xsd:sequence>
    <xsd:attribute use="required" name="name" type="identifier"/>
  </xsd:complexType>

  <xsd:element name="control" type="command"/>
  <xsd:element name="action" type="command"/>

  <xsd:element name="test">

    <xsd:complexType>
      <xsd:sequence>
        <xsd:choice minOccurs="0" maxOccurs="unbounded">
          <xsd:element ref="str"/>
          <xsd:element ref="num"/>
          <xsd:element ref="list"/>
          <xsd:element ref="tag"/>
          <xsd:element ref="displaydata"/>
          <xsd:element ref="comment"/>
          <xsd:any namespace="##other" processContents="lax"/>
        </xsd:choice>
        <xsd:element ref="test" minOccurs="0"
                     maxOccurs="unbounded"/>
      </xsd:sequence>
      <xsd:attribute use="required" name="name" type="identifier"/>
    </xsd:complexType>
  </xsd:element>

  <xsd:element name="list">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element ref="str" minOccurs="1"
                     maxOccurs="unbounded"/>
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>

  <xsd:element name="tag" type="identifier"/>

  <xsd:element name="str" type="xsd:string"/>

  <xsd:element name="num" type="xsd:nonNegativeInteger"/>

  <xsd:simpleType name="identifier">
    <xsd:restriction base="xsd:token">
      <xsd:pattern value="[A-Za-z_][A-Za-z0-9_]*"/>
    </xsd:restriction>
  </xsd:simpleType>

  <xsd:element name="displayblock">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:choice minOccurs="0" maxOccurs="unbounded">
          <xsd:element ref="control"/>
          <xsd:element ref="action"/>
          <xsd:element ref="displayblock"/>
          <xsd:element ref="displaydata"/>
          <xsd:element ref="comment"/>
          <xsd:any namespace="##other" processContents="lax"/>
        </xsd:choice>
      </xsd:sequence>
      <xsd:anyAttribute processContents="skip"/>
    </xsd:complexType>
  </xsd:element>

  <xsd:element name="displaydata">
    <xsd:complexType>
      <xsd:sequence minOccurs="0" maxOccurs="unbounded">
        <xsd:any processContents="skip"/>
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>

</xsd:schema>

Appendix C. Relax NG Schema for Sieves in XML ๐ŸŒˆ

this one is informative too ๐Ÿฆ‹ It's a Relax NG Schema written in the compact notation from OASISRNC [OASISRNC], and it describes the same XML representation of Sieve scripts. Same vibe as before: most of the complex-content elements allow unrestricted use of elements in other namespaces ๐Ÿ‘‘ displaydata can encapsulate arbitrary XML content, and displayblock is the general-purpose grouping element where arbitrary attributes are allowed ๐Ÿ“

namespace sieve = "urn:ietf:params:xml:ns:sieve"

start = element sieve:sieve { ( control | action | displayblock |
                                displaydata | comment | ext )* }

comment = element sieve:comment { xsd:string }

ambles =
   (
      ( displaydata | comment | ext )*
   ),
   empty

preamble = element sieve:preamble { ambles }
postamble = element sieve:postamble { ambles }

command =
   (
      attribute name {
        xsd:token {

          pattern = "[A-Za-z_][A-Za-z0-9_]*" } },
      preamble?,
      ( str | num | \list | tag )*,
      test?,
      ( control | action | displayblock)*,
      postamble?
   ),
   empty

control = element sieve:control { command }
action = element sieve:action { command }

test =
   element sieve:test
   {
      attribute name {
        xsd:token {
          pattern = "[A-Za-z_][A-Za-z0-9_]*" } },
      ( str | num | \list | tag | comment | ext )*,
      test*
   }

\list = element sieve:list { str+ }

tag = element sieve:tag {
        xsd:token {
          pattern = "[A-Za-z_][A-Za-z0-9_]*" } }

str = element sieve:str { xsd:string }

num = element sieve:num { xsd:nonNegativeInteger }

any = ( element * { any } | attribute * { text } | text )*

ext = element * - sieve:* { any }*

displayblock =
   element sieve:displayblock
   {
      ( control | action | displayblock |
        displaydata | comment | ext )*,
      attribute * { text }*
   }

displaydata = element sieve:displaydata { any* }

Appendix D. Stylesheet for Conversion from XML โœจ

okay so this appendix is informative ๐ŸŽ€, which just means it's here to help, not to boss anyone around ๐Ÿ’…. The code below is a stylesheet that can be used to convert the Sieve in XML representation to regular Sieve format ๐Ÿฆ‹. Content in other namespaces, displaydata, and displayblock elements gets converted to structured comments as appropriate ๐Ÿ’–

   <?xml version="1.0" encoding="UTF-8"?>

   <!-- Convert Sieve in XML to standard Sieve syntax -->

   <xsl:stylesheet version="1.0"
                   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                   xmlns:sieve="urn:ietf:params:xml:ns:sieve">

     <xsl:output method="text" encoding="UTF-8"
                 media-type="application/sieve"/>

     <!-- Only preserve whitespace in str elements -->
     <xsl:strip-space elements="*"/>
     <xsl:preserve-space elements="sieve:str"/>

     <!-- Match top level sieve node,
          start processing in sieve mode -->

     <xsl:template match="sieve:sieve">
       <xsl:apply-templates select="*" mode="sieve">
         <xsl:with-param name="prefix" select="''"/>
       </xsl:apply-templates>
     </xsl:template>

     <!-- Routine to properly literalize quotes in Sieve strings -->

     <xsl:template name="quote-string">
       <xsl:param name="str"/>
       <xsl:choose>
         <xsl:when test="not($str)"/>
         <xsl:when test="contains($str, '&quot;')">
           <xsl:call-template name="quote-string">
             <xsl:with-param name="str"
                select="substring-before($str, '&quot;')"/>
           </xsl:call-template>
           <xsl:text>\&quot;</xsl:text>
           <xsl:call-template name="quote-string">
             <xsl:with-param name="str"
                select="substring-after($str, '&quot;')"/>
           </xsl:call-template>

         </xsl:when>
         <xsl:when test="contains($str, '\')">
           <xsl:call-template name="quote-string">
             <xsl:with-param name="str"
                select="substring-before($str, '\')"/>
           </xsl:call-template>
           <xsl:text>\\</xsl:text>
           <xsl:call-template name="quote-string">
             <xsl:with-param name="str"
                select="substring-after($str, '\')"/>
           </xsl:call-template>
         </xsl:when>
         <xsl:otherwise>
           <xsl:value-of select="$str"/>
         </xsl:otherwise>
       </xsl:choose>
     </xsl:template>

     <!-- Sieve mode processing templates -->

     <xsl:template match="sieve:control|sieve:action" mode="sieve">
       <xsl:param name="prefix"/>
       <xsl:text xml:space="preserve">
   </xsl:text>
       <xsl:value-of select="$prefix"/>
       <xsl:value-of select="@name"/>
       <xsl:variable name="blockbegin"
               select="generate-id(sieve:control|sieve:action)"/>
       <xsl:for-each select="*">
         <xsl:choose>
           <xsl:when test="self::sieve:str|self::sieve:num|
                           self::sieve:list|self::sieve:tag|
                           self::sieve:test">
             <xsl:apply-templates select="." mode="sieve"/>
           </xsl:when>
           <xsl:when test="generate-id(.) = $blockbegin">
             <xsl:text xml:space="preserve">
   </xsl:text>
             <xsl:value-of select="$prefix"/>
             <xsl:text>{</xsl:text>
             <xsl:apply-templates select="." mode="sieve">
               <xsl:with-param name="prefix"
                  select="concat($prefix, '  ')"/>
             </xsl:apply-templates>
           </xsl:when>
           <xsl:otherwise>
             <xsl:apply-templates select="." mode="sieve">
               <xsl:with-param name="prefix"

                  select="concat($prefix, '  ')"/>
             </xsl:apply-templates>
           </xsl:otherwise>
         </xsl:choose>
       </xsl:for-each>
       <xsl:choose>
         <xsl:when test="count(sieve:control|sieve:action) &gt; 0">
           <xsl:text xml:space="preserve">
   </xsl:text>
           <xsl:value-of select="$prefix"/>
           <xsl:text>}</xsl:text>
         </xsl:when>
         <xsl:otherwise>
           <xsl:text>;</xsl:text>
         </xsl:otherwise>
       </xsl:choose>
     </xsl:template>

     <xsl:template match="sieve:preamble|sieve:postamble" mode="sieve">
       <xsl:param name="prefix"/>
       <xsl:apply-templates mode="sieve">
         <xsl:with-param name="prefix" select="$prefix"/>
       </xsl:apply-templates>
     </xsl:template>

     <xsl:template match="sieve:test" mode="sieve">
       <xsl:text xml:space="preserve"> </xsl:text>
       <xsl:value-of select="@name"/>
       <xsl:apply-templates select="*[not(self::sieve:test)]"
                            mode="sieve"/>
       <xsl:if test="count(descendant::sieve:test) &gt; 0">
         <xsl:text> (</xsl:text>
         <xsl:for-each select="sieve:test">
           <xsl:apply-templates select="." mode="sieve"/>
           <xsl:if test="count(following-sibling::sieve:test) &gt; 0">
             <xsl:text>,</xsl:text>
           </xsl:if>
         </xsl:for-each>
         <xsl:text> )</xsl:text>
       </xsl:if>
     </xsl:template>

     <xsl:template match="sieve:str" mode="sieve">
       <xsl:text> &quot;</xsl:text>
       <xsl:call-template name="quote-string">
         <xsl:with-param name="str" select="text()"/>
       </xsl:call-template>
       <xsl:text>&quot;</xsl:text>

     </xsl:template>

     <xsl:template match="sieve:num" mode="sieve">
       <xsl:text xml:space="preserve"> </xsl:text>
       <!-- Use numeric suffixes when possible -->
       <xsl:choose>
         <xsl:when test="(number(text()) mod 1073741824) = 0">
           <xsl:value-of select="number(text()) div 1073741824"/>
           <xsl:text>G</xsl:text>
         </xsl:when>
         <xsl:when test="(number(text()) mod 1048576) = 0">
           <xsl:value-of select="number(text()) div 1048576"/>
           <xsl:text>M</xsl:text>
         </xsl:when>
         <xsl:when test="(number(text()) mod 1024) = 0">
           <xsl:value-of select="number(text()) div 1024"/>
           <xsl:text>K</xsl:text>
         </xsl:when>
         <xsl:otherwise>
           <xsl:value-of select="text()"/>
         </xsl:otherwise>
       </xsl:choose>
     </xsl:template>

     <xsl:template match="sieve:list" mode="sieve">
       <xsl:text> [</xsl:text>
       <xsl:for-each select="sieve:str">
         <xsl:apply-templates select="." mode="sieve"/>
         <xsl:if test="count(following-sibling::sieve:str) &gt; 0">
           <xsl:text>,</xsl:text>
         </xsl:if>
       </xsl:for-each>
       <xsl:text> ]</xsl:text>
     </xsl:template>

     <xsl:template match="sieve:tag" mode="sieve">
       <xsl:text> :</xsl:text>
       <xsl:value-of select="text()"/>
     </xsl:template>

     <xsl:template match="sieve:comment" mode="sieve">
       <xsl:param name="prefix"/>
       <xsl:text xml:space="preserve">
   </xsl:text>
       <xsl:value-of select="$prefix"/>
       <xsl:text>/*</xsl:text>
       <xsl:value-of select="."/>
       <xsl:value-of select="$prefix"/>
       <xsl:text>*/</xsl:text>
     </xsl:template>

     <!-- Convert display information into structured comments -->

     <xsl:template match="sieve:displayblock" mode="sieve">
       <xsl:param name="prefix"/>
       <xsl:text xml:space="preserve">
   </xsl:text>
       <xsl:value-of select="$prefix"/>
       <xsl:text>/* [*</xsl:text>
       <xsl:apply-templates select="@*" mode="copy"/>
       <xsl:text> */</xsl:text>
       <xsl:apply-templates select="*" mode="sieve">
         <xsl:with-param name="prefix" select="$prefix"/>
       </xsl:apply-templates>
       <xsl:text xml:space="preserve">
   </xsl:text>
       <xsl:value-of select="$prefix"/>
       <xsl:text>/* *] */</xsl:text>
     </xsl:template>

     <xsl:template match="sieve:displaydata" mode="sieve">
       <xsl:param name="prefix"/>
       <xsl:text xml:space="preserve">
   </xsl:text>
       <xsl:value-of select="$prefix"/>
       <xsl:text>/* [|</xsl:text>
       <xsl:apply-templates mode="copy">
         <xsl:with-param name="prefix"
                         select="concat($prefix, '  ')"/>
       </xsl:apply-templates>
       <xsl:text xml:space="preserve">
   </xsl:text>
       <xsl:value-of select="$prefix"/>
       <xsl:text>   |] */</xsl:text>
     </xsl:template>

     <!-- Copy unrecnognized nodes and their descendants -->

     <xsl:template match="*" mode="sieve">
       <xsl:param name="prefix"/>
       <xsl:text xml:space="preserve">
   </xsl:text>
       <xsl:value-of select="$prefix"/>
       <xsl:text>/* [/</xsl:text>
       <xsl:apply-templates select="." mode="copy">
         <xsl:with-param name="prefix"

                         select="concat($prefix, '  ')"/>
       </xsl:apply-templates>
       <xsl:text xml:space="preserve">
   </xsl:text>
       <xsl:value-of select="$prefix"/>
       <xsl:text>   /] */</xsl:text>
     </xsl:template>

     <!-- Copy mode processing templates -->

     <xsl:template match="*[not(node())]" mode="copy">
       <xsl:param name="prefix"/>
       <xsl:text xml:space="preserve">
   </xsl:text>
       <xsl:value-of select="$prefix"/>
       <xsl:text>&lt;</xsl:text>
       <xsl:value-of select="name()"/>
       <xsl:apply-templates select="@*" mode="copy"/>
       <xsl:text>/&gt;</xsl:text>
     </xsl:template>

     <xsl:template match="*[node()]" mode="copy">
       <xsl:param name="prefix"/>
       <xsl:text xml:space="preserve">
   </xsl:text>
       <xsl:value-of select="$prefix"/>
       <xsl:text>&lt;</xsl:text>
       <xsl:value-of select="name()"/>
       <xsl:apply-templates select="@*" mode="copy"/>
       <xsl:text>&gt;</xsl:text>
       <xsl:apply-templates mode="copy">
         <xsl:with-param name="prefix"
                         select="concat($prefix, '  ')"/>
       </xsl:apply-templates>
       <xsl:if test="*[last()][not(text())]">
         <xsl:text xml:space="preserve">
   </xsl:text>
         <xsl:value-of select="$prefix"/>
       </xsl:if>
       <xsl:text>&lt;/</xsl:text>
       <xsl:value-of select="name()"/>
       <xsl:text>&gt;</xsl:text>
     </xsl:template>

     <xsl:template match="@*" mode="copy">
       <xsl:text> </xsl:text>
       <xsl:value-of select="name()"/>
       <xsl:text>="</xsl:text>
       <xsl:value-of select="."/>
       <xsl:text>"</xsl:text>
     </xsl:template>

   </xsl:stylesheet>

Appendix E. Acknowledgements ๐Ÿซถ

the stylesheet copy mode code is loosely based on a sample code posted to the xsl-list list by Americo Albuquerque ๐Ÿ’– and a huge thank you to Jari Arkko, Robert Burrell Donkin, Andrew McKeon, Alexey Melnikov, and Aaron Stone for providing useful comments on the document ๐ŸŒธ the whole squad, no notes ๐Ÿ‘‘

Authors' Addresses ๐Ÿ’œ

   Ned Freed
   Sun Microsystems
   800 Royal Oaks
   Monrovia, CA  91016-6347
   USA

   Phone: +1 909 457 4293
   EMail: ned.freed@mrochek.com


   Srinivas Saisatish Vedam
   Sun Microsystems

   Phone: +91 80669 27577
   EMail: Srinivas.Sv@Sun.COM
๐Ÿ“ฆ where this page comes from