summaryrefslogtreecommitdiffstats
path: root/pigeonhole/tests/extensions/mime
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
commit0441d265f2bb9da249c7abf333f0f771fadb4ab5 (patch)
tree3f3789daa2f6db22da6e55e92bee0062a7d613fe /pigeonhole/tests/extensions/mime
parentInitial commit. (diff)
downloaddovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.tar.xz
dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.zip
Adding upstream version 1:2.3.21+dfsg1.upstream/1%2.3.21+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'pigeonhole/tests/extensions/mime')
-rw-r--r--pigeonhole/tests/extensions/mime/address.svtest281
-rw-r--r--pigeonhole/tests/extensions/mime/calendar-example.svtest129
-rw-r--r--pigeonhole/tests/extensions/mime/content-header.svtest161
-rw-r--r--pigeonhole/tests/extensions/mime/errors.svtest162
-rw-r--r--pigeonhole/tests/extensions/mime/errors/address-mime-tag.sieve38
-rw-r--r--pigeonhole/tests/extensions/mime/errors/break.sieve157
-rw-r--r--pigeonhole/tests/extensions/mime/errors/exists-mime-tag.sieve43
-rw-r--r--pigeonhole/tests/extensions/mime/errors/extracttext-nofep.sieve4
-rw-r--r--pigeonhole/tests/extensions/mime/errors/extracttext-novar.sieve6
-rw-r--r--pigeonhole/tests/extensions/mime/errors/extracttext.sieve42
-rw-r--r--pigeonhole/tests/extensions/mime/errors/foreverypart.sieve45
-rw-r--r--pigeonhole/tests/extensions/mime/errors/header-mime-tag.sieve100
-rw-r--r--pigeonhole/tests/extensions/mime/errors/limits-include.sieve6
-rw-r--r--pigeonhole/tests/extensions/mime/errors/limits.sieve13
-rw-r--r--pigeonhole/tests/extensions/mime/execute.svtest82
-rw-r--r--pigeonhole/tests/extensions/mime/execute/foreverypart.sieve14
-rw-r--r--pigeonhole/tests/extensions/mime/execute/mime.sieve69
-rw-r--r--pigeonhole/tests/extensions/mime/exists.svtest237
-rw-r--r--pigeonhole/tests/extensions/mime/extracttext.svtest143
-rw-r--r--pigeonhole/tests/extensions/mime/foreverypart.svtest178
-rw-r--r--pigeonhole/tests/extensions/mime/header.svtest444
-rw-r--r--pigeonhole/tests/extensions/mime/included/include-foreverypart.sieve44
-rw-r--r--pigeonhole/tests/extensions/mime/included/include-loop-2.sieve6
-rw-r--r--pigeonhole/tests/extensions/mime/included/include-loop-3.sieve6
-rw-r--r--pigeonhole/tests/extensions/mime/included/include-loop-4.sieve6
-rw-r--r--pigeonhole/tests/extensions/mime/included/include-loop-5.sieve9
26 files changed, 2425 insertions, 0 deletions
diff --git a/pigeonhole/tests/extensions/mime/address.svtest b/pigeonhole/tests/extensions/mime/address.svtest
new file mode 100644
index 0000000..1607450
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/address.svtest
@@ -0,0 +1,281 @@
+require "vnd.dovecot.testsuite";
+require "mime";
+require "foreverypart";
+
+/*
+ * Basic functionionality
+ */
+
+test_set "message" text:
+From: stephan@example.com
+To: nico@nl.example.com, harry@de.example.com
+cc: Timo <tss(no spam)@fi.iki>
+Subject: Frobnitzm
+
+Test.
+.
+;
+
+test "Basic functionality" {
+ /* Must match */
+ if not address :mime :anychild :contains ["to", "from"] "harry" {
+ test_fail "failed to match address (1)";
+ }
+
+ if not address :mime :anychild :contains ["to", "from"] "de.example" {
+ test_fail "failed to match address (2)";
+ }
+
+ if not address :mime :anychild :matches "to" "*@*.example.com" {
+ test_fail "failed to match address (3)";
+ }
+
+ if not address :mime :anychild :is "to" "harry@de.example.com" {
+ test_fail "failed to match address (4)";
+ }
+
+ /* Must not match */
+ if address :mime :anychild :is ["to", "from"] "nonsense@example.com" {
+ test_fail "matches erroneous address";
+ }
+
+ /* Match first key */
+ if not address :mime :anychild :contains ["to"] ["nico", "fred", "henk"] {
+ test_fail "failed to match first key";
+ }
+
+ /* Match second key */
+ if not address :mime :anychild :contains ["to"] ["fred", "nico", "henk"] {
+ test_fail "failed to match second key";
+ }
+
+ /* Match last key */
+ if not address :mime :anychild :contains ["to"] ["fred", "henk", "nico"] {
+ test_fail "failed to match last key";
+ }
+
+ /* First header */
+ if not address :mime :anychild :contains
+ ["to", "from"] ["fred", "nico", "henk"] {
+ test_fail "failed to match first header";
+ }
+
+ /* Second header */
+ if not address :mime :anychild :contains
+ ["from", "to"] ["fred", "nico", "henk"] {
+ test_fail "failed to match second header";
+ }
+
+ /* Comment */
+ if not address :mime :anychild :is "cc" "tss@fi.iki" {
+ test_fail "failed to ignore comment in address";
+ }
+}
+
+/*
+ * Basic functionionality - foreverypart
+ */
+
+test "Basic functionality - foreverypart" {
+ foreverypart {
+ /* Must match */
+ if not address :mime :anychild :contains ["to", "from"] "harry" {
+ test_fail "failed to match address (1)";
+ }
+
+ if not address :mime :anychild :contains ["to", "from"] "de.example" {
+ test_fail "failed to match address (2)";
+ }
+
+ if not address :mime :anychild :matches "to" "*@*.example.com" {
+ test_fail "failed to match address (3)";
+ }
+
+ if not address :mime :anychild :is "to" "harry@de.example.com" {
+ test_fail "failed to match address (4)";
+ }
+
+ /* Must not match */
+ if address :mime :anychild :is ["to", "from"] "nonsense@example.com" {
+ test_fail "matches erroneous address";
+ }
+
+ /* Match first key */
+ if not address :mime :anychild :contains ["to"] ["nico", "fred", "henk"] {
+ test_fail "failed to match first key";
+ }
+
+ /* Match second key */
+ if not address :mime :anychild :contains ["to"] ["fred", "nico", "henk"] {
+ test_fail "failed to match second key";
+ }
+
+ /* Match last key */
+ if not address :mime :anychild :contains ["to"] ["fred", "henk", "nico"] {
+ test_fail "failed to match last key";
+ }
+
+ /* First header */
+ if not address :mime :anychild :contains
+ ["to", "from"] ["fred", "nico", "henk"] {
+ test_fail "failed to match first header";
+ }
+
+ /* Second header */
+ if not address :mime :anychild :contains
+ ["from", "to"] ["fred", "nico", "henk"] {
+ test_fail "failed to match second header";
+ }
+
+ /* Comment */
+ if not address :mime :anychild :is "cc" "tss@fi.iki" {
+ test_fail "failed to ignore comment in address";
+ }
+ }
+}
+
+/*
+ * Address headers
+ */
+
+test_set "message" text:
+From: stephan@friep.frop
+To: henk@tukkerland.ex
+CC: ivo@boer.ex
+Bcc: joop@hooibaal.ex
+Sender: s.bosch@friep.frop
+Resent-From: ivo@boer.ex
+Resent-To: idioot@dombo.ex
+Subject: Berichtje
+
+Test.
+.
+;
+
+test "Address headers" {
+ if not address :mime :anychild "from" "stephan@friep.frop" {
+ test_fail "from header not recognized";
+ }
+
+ if not address :mime :anychild "to" "henk@tukkerland.ex" {
+ test_fail "to header not recognized";
+ }
+
+ if not address :mime :anychild "cc" "ivo@boer.ex" {
+ test_fail "cc header not recognized";
+ }
+
+ if not address :mime :anychild "bcc" "joop@hooibaal.ex" {
+ test_fail "bcc header not recognized";
+ }
+
+ if not address :mime :anychild "sender" "s.bosch@friep.frop" {
+ test_fail "sender header not recognized";
+ }
+
+ if not address :mime :anychild "resent-from" "ivo@boer.ex" {
+ test_fail "resent-from header not recognized";
+ }
+
+ if not address :mime :anychild "resent-to" "idioot@dombo.ex" {
+ test_fail "resent-to header not recognized";
+ }
+}
+
+/*
+ * Address headers - foreverypart
+ */
+
+test "Address headers - foreverypart" {
+ foreverypart {
+ if not address :mime :anychild "from" "stephan@friep.frop" {
+ test_fail "from header not recognized";
+ }
+
+ if not address :mime :anychild "to" "henk@tukkerland.ex" {
+ test_fail "to header not recognized";
+ }
+
+ if not address :mime :anychild "cc" "ivo@boer.ex" {
+ test_fail "cc header not recognized";
+ }
+
+ if not address :mime :anychild "bcc" "joop@hooibaal.ex" {
+ test_fail "bcc header not recognized";
+ }
+
+ if not address :mime :anychild "sender" "s.bosch@friep.frop" {
+ test_fail "sender header not recognized";
+ }
+
+ if not address :mime :anychild "resent-from" "ivo@boer.ex" {
+ test_fail "resent-from header not recognized";
+ }
+
+ if not address :mime :anychild "resent-to" "idioot@dombo.ex" {
+ test_fail "resent-to header not recognized";
+ }
+ }
+}
+
+/*
+ * Multipart anychild
+ */
+
+test_set "message" text:
+From: Hendrik <hendrik@example.com>
+To: Harrie <harrie@example.com>
+Date: Sat, 11 Oct 2010 00:31:44 +0200
+Subject: Harrie is een prutser
+Content-Type: multipart/mixed; boundary=AA
+CC: AA@example.com
+
+This is a multi-part message in MIME format.
+--AA
+Content-Type: multipart/mixed; boundary=BB
+CC: BB@example.com
+
+This is a multi-part message in MIME format.
+--BB
+Content-Type: text/plain; charset="us-ascii"
+CC: CC@example.com
+
+Hello
+
+--BB
+Content-Type: text/plain; charset="us-ascii"
+CC: DD@example.com
+
+Hello again
+
+--BB--
+This is the end of MIME multipart.
+
+--AA
+Content-Type: text/plain; charset="us-ascii"
+CC: EE@example.com
+
+And again
+
+--AA--
+This is the end of MIME multipart.
+.
+;
+
+test "Multipart anychild" {
+ if not address :mime :anychild :localpart "Cc" "AA" {
+ test_fail "AA Cc repient does not exist";
+ }
+ if not address :mime :anychild :localpart "Cc" "BB" {
+ test_fail "BB Cc repient does not exist";
+ }
+ if not address :mime :anychild :localpart "Cc" "CC" {
+ test_fail "CC Cc repient does not exist";
+ }
+ if not address :mime :anychild :localpart "Cc" "DD" {
+ test_fail "DD Cc repient does not exist";
+ }
+ if not address :mime :anychild :localpart "Cc" "EE" {
+ test_fail "EE Cc repient does not exist";
+ }
+}
diff --git a/pigeonhole/tests/extensions/mime/calendar-example.svtest b/pigeonhole/tests/extensions/mime/calendar-example.svtest
new file mode 100644
index 0000000..745e6e6
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/calendar-example.svtest
@@ -0,0 +1,129 @@
+require "vnd.dovecot.testsuite";
+require "mime";
+require "foreverypart";
+require "editheader";
+require "relational";
+require "variables";
+
+# Example from RFC 6047, Section 2.5:
+test_set "message" text:
+From: user1@example.com
+To: user2@example.com
+Subject: Phone Conference
+Mime-Version: 1.0
+Date: Wed, 07 May 2008 21:30:25 +0400
+Message-ID: <4821E731.5040506@laptop1.example.com>
+Content-Type: text/calendar; method=REQUEST; charset=UTF-8
+Content-Transfer-Encoding: quoted-printable
+
+BEGIN:VCALENDAR
+PRODID:-//Example/ExampleCalendarClient//EN
+METHOD:REQUEST
+VERSION:2.0
+BEGIN:VEVENT
+ORGANIZER:mailto:user1@example.com
+ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:mailto:user1@example.com
+ATTENDEE;RSVP=YES;CUTYPE=INDIVIDUAL:mailto:user2@example.com
+DTSTAMP:20080507T170000Z
+DTSTART:20080701T160000Z
+DTEND:20080701T163000Z
+SUMMARY:Phone call to discuss your last visit
+DESCRIPTION:=D1=82=D1=8B =D0=BA=D0=B0=D0=BA - =D0=B4=D0=BE=D0=
+ =B2=D0=BE=D0=BB=D0=B5=D0=BD =D0=BF=D0=BE=D0=B5=D0=B7=D0=B4=D0=BA=D0
+ =BE=D0=B9?
+UID:calsvr.example.com-8739701987387998
+SEQUENCE:0
+STATUS:TENTATIVE
+END:VEVENT
+END:VCALENDAR
+.
+;
+
+test "Calendar only" {
+ foreverypart {
+ if allof(
+ header :mime :count "eq" "Content-Type" "1",
+ header :mime :contenttype "Content-Type" "text/calendar",
+ header :mime :param "method" :matches "Content-Type" "*",
+ header :mime :param "charset" :is "Content-Type" "UTF-8" ) {
+ addheader "X-ICAL" "${1}";
+ break;
+ }
+ }
+
+ if not header "x-ical" "request" {
+ test_fail "Failed to parse message correctly";
+ }
+}
+
+# Modified example
+test_set "message" text:
+From: user1@example.com
+To: user2@example.com
+Subject: Phone Conference
+Mime-Version: 1.0
+Date: Wed, 07 May 2008 21:30:25 +0400
+Message-ID: <4821E731.5040506@laptop1.example.com>
+Content-Type: multipart/mixed; boundary=AA
+
+This is a multi-part message in MIME format.
+
+--AA
+Content-Type: text/plain
+
+Hello,
+
+I'd like to discuss your last visit. A tentative meeting schedule is
+attached.
+
+Regards,
+
+User1
+
+--AA
+Content-Type: text/calendar; method=REQUEST; charset=UTF-8
+Content-Transfer-Encoding: quoted-printable
+
+BEGIN:VCALENDAR
+PRODID:-//Example/ExampleCalendarClient//EN
+METHOD:REQUEST
+VERSION:2.0
+BEGIN:VEVENT
+ORGANIZER:mailto:user1@example.com
+ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:mailto:user1@example.com
+ATTENDEE;RSVP=YES;CUTYPE=INDIVIDUAL:mailto:user2@example.com
+DTSTAMP:20080507T170000Z
+DTSTART:20080701T160000Z
+DTEND:20080701T163000Z
+SUMMARY:Phone call to discuss your last visit
+DESCRIPTION:=D1=82=D1=8B =D0=BA=D0=B0=D0=BA - =D0=B4=D0=BE=D0=
+ =B2=D0=BE=D0=BB=D0=B5=D0=BD =D0=BF=D0=BE=D0=B5=D0=B7=D0=B4=D0=BA=D0
+ =BE=D0=B9?
+UID:calsvr.example.com-8739701987387998
+SEQUENCE:0
+STATUS:TENTATIVE
+END:VEVENT
+END:VCALENDAR
+
+--AA--
+.
+;
+
+test "Multipart message" {
+ foreverypart {
+ if allof(
+ header :mime :count "eq" "Content-Type" "1",
+ header :mime :contenttype "Content-Type" "text/calendar",
+ header :mime :param "method" :matches "Content-Type" "*",
+ header :mime :param "charset" :is "Content-Type" "UTF-8" ) {
+ addheader "X-ICAL" "${1}";
+ break;
+ }
+ }
+
+ if not header "x-ical" "request" {
+ test_fail "Failed to parse message correctly";
+ }
+}
+
+
diff --git a/pigeonhole/tests/extensions/mime/content-header.svtest b/pigeonhole/tests/extensions/mime/content-header.svtest
new file mode 100644
index 0000000..9686e35
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/content-header.svtest
@@ -0,0 +1,161 @@
+require "vnd.dovecot.testsuite";
+require "relational";
+require "mime";
+
+test_set "message" text:
+From: stephan@example.com
+To: timo@example.com
+Subject: Frop
+Content-Type: text/plain
+
+Frop
+.
+;
+
+test "Simple Content-Type :type" {
+ if not header :mime :type "content-type" "text" {
+ test_fail "wrong type extracted";
+ }
+}
+
+test "Simple Content-Type :subype" {
+ if not header :mime :subtype "content-type" "plain" {
+ test_fail "wrong subtype extracted";
+ }
+}
+
+test "Simple Content-Type :contenttype" {
+ if not header :mime :contenttype "content-type" "text/plain" {
+ test_fail "wrong contenttype extracted";
+ }
+}
+
+test_set "message" text:
+From: stephan@example.com
+To: timo@example.com
+Subject: Frop
+Content-Type: text/calendar; method=request; charset=UTF-8;
+
+Frop
+.
+;
+
+test "Advanced Content-Type :type" {
+ if not header :mime :type "content-type" "text" {
+ test_fail "wrong type extracted";
+ }
+}
+
+test "Advanced Content-Type :subype" {
+ if not header :mime :subtype "content-type" "calendar" {
+ test_fail "wrong subtype extracted";
+ }
+}
+
+test "Advanced Content-Type :contenttype" {
+ if not header :mime :contenttype "content-type" "text/calendar" {
+ test_fail "wrong contenttype extracted";
+ }
+}
+
+test "Advanced Content-Type :param" {
+ if not header :mime :param "method" "content-type" "request" {
+ test_fail "wrong method param extracted";
+ }
+
+ if not header :mime :param "charset" "content-type" "UTF-8" {
+ test_fail "wrong charset param extracted";
+ }
+
+ if not header :mime :param ["method", "charset"]
+ "content-type" "request" {
+ test_fail "wrong method param extracted";
+ }
+
+ if not header :mime :param ["method", "charset"]
+ "content-type" "UTF-8" {
+ test_fail "wrong charset param extracted";
+ }
+
+ if not header :count "eq" :mime :param ["method", "charset"]
+ "content-type" "2" {
+ test_fail "wrong number of parameters";
+ }
+}
+
+test_set "message" text:
+From: stephan@example.com
+To: timo@example.com
+Subject: Frop
+Content-Type: application/x-stuff;
+ title*0*=us-ascii'en'This%20is%20even%20more%20;
+ title*1*=%2A%2A%2Afun%2A%2A%2A%20;
+ title*2="isn't it!"
+
+Frop
+.
+;
+
+test "Encoded Content-Type :param" {
+ if not header :mime :param "title" "content-type"
+ "This is even more ***fun*** isn't it!" {
+ test_fail "wrong method param extracted";
+ }
+}
+
+test_set "message" text:
+From: stephan@example.com
+To: timo@example.com
+Subject: Frop
+Content-Type: image/png
+Content-Disposition: inline; filename="frop.exe"; title="Frop!"
+
+Frop
+.
+;
+
+test "Content-Disposition :type" {
+ if not header :mime :type "content-disposition" "inline" {
+ test_fail "wrong type extracted";
+ }
+}
+
+test "Content-Disposition :subype" {
+ if not header :mime :subtype "content-disposition" "" {
+ test_fail "wrong subtype extracted";
+ }
+}
+
+test "Content-Disposition :contenttype" {
+ if not header :mime :contenttype "content-disposition" "inline" {
+ test_fail "wrong contenttype extracted";
+ }
+}
+
+test "Content-Disposition :param" {
+ if not header :mime :param "filename" "content-disposition" "frop.exe" {
+ test_fail "wrong filename param extracted";
+ }
+
+ if not header :mime :param "title" "content-disposition" "Frop!" {
+ test_fail "wrong title param extracted";
+ }
+
+ if not header :mime :param ["filename", "title"]
+ "content-disposition" "frop.exe" {
+ test_fail "wrong filename param extracted";
+ }
+
+ if not header :mime :param ["filename", "title"]
+ "content-disposition" "Frop!" {
+ test_fail "wrong title param extracted";
+ }
+
+ if not header :count "eq" :mime :param ["filename", "title"]
+ "content-disposition" "2" {
+ test_fail "wrong number of parameters";
+ }
+
+}
+
+
diff --git a/pigeonhole/tests/extensions/mime/errors.svtest b/pigeonhole/tests/extensions/mime/errors.svtest
new file mode 100644
index 0000000..b3b858e
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/errors.svtest
@@ -0,0 +1,162 @@
+require "vnd.dovecot.testsuite";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+
+test "Foreverypart command" {
+ if test_script_compile "errors/foreverypart.sieve" {
+ test_fail "compile should have failed";
+ }
+
+ if test_error :count "ne" :comparator "i;ascii-numeric" "12" {
+ test_fail "incorrect number of compile errors reported";
+ }
+}
+
+test "Break command" {
+ if test_script_compile "errors/break.sieve" {
+ test_fail "compile should have failed";
+ }
+
+ if test_error :count "ne" :comparator "i;ascii-numeric" "21" {
+ test_fail "incorrect number of compile errors reported";
+ }
+}
+
+test "Header test with :mime tag" {
+ if test_script_compile "errors/header-mime-tag.sieve" {
+ test_fail "compile should have failed";
+ }
+
+ if test_error :count "ne" :comparator "i;ascii-numeric" "10" {
+ test_fail "incorrect number of compile errors reported";
+ }
+}
+
+test "Address test with :mime tag" {
+ if test_script_compile "errors/address-mime-tag.sieve" {
+ test_fail "compile should have failed";
+ }
+
+ if test_error :count "ne" :comparator "i;ascii-numeric" "6" {
+ test_fail "incorrect number of compile errors reported";
+ }
+}
+
+test "Exists test with :mime tag" {
+ if test_script_compile "errors/exists-mime-tag.sieve" {
+ test_fail "compile should have failed";
+ }
+
+ if test_error :count "ne" :comparator "i;ascii-numeric" "6" {
+ test_fail "incorrect number of compile errors reported";
+ }
+}
+
+test "Limits" {
+ if test_script_compile "errors/limits.sieve" {
+ test_fail "compile should have failed";
+ }
+
+ if test_error :count "ne" :comparator "i;ascii-numeric" "2" {
+ test_fail "incorrect number of compile errors reported";
+ }
+}
+
+test_set "message" text:
+From: Whomever <whoever@example.com>
+To: Someone <someone@example.com>
+Date: Sat, 10 Oct 2009 00:30:04 +0200
+Subject: whatever
+Content-Type: multipart/mixed; boundary=AA
+
+This is a multi-part message in MIME format.
+
+--AA
+Content-Type: multipart/alternative; boundary=BB
+
+This is a multi-part message in MIME format.
+
+--BB
+Content-Type: multipart/alternative; boundary=CC
+
+This is a multi-part message in MIME format.
+
+--CC
+Content-Type: multipart/alternative; boundary=DD
+
+This is a multi-part message in MIME format.
+
+--DD
+Content-Type: multipart/alternative; boundary=EE
+
+This is a nested multi-part message in MIME format.
+
+--EE
+Content-Type: text/plain; charset="us-ascii"
+
+Hello
+
+--EE--
+
+This is the end of the inner MIME multipart.
+
+--DD--
+
+This is the end of the MIME multipart.
+
+--CC--
+
+This is the end of the MIME multipart.
+
+--BB--
+
+This is the end of the MIME multipart.
+
+--AA--
+
+This is the end of the MIME multipart.
+.
+;
+
+test "Limits - include" {
+ if not test_script_compile "errors/limits-include.sieve" {
+ test_fail "script compile failed";
+ }
+
+ if test_script_run {
+ test_fail "script run should have failed";
+ }
+}
+
+test "Extracttext" {
+ if test_script_compile "errors/extracttext.sieve" {
+ test_fail "compile should have failed";
+ }
+
+ if test_error :count "ne" :comparator "i;ascii-numeric" "11" {
+ test_fail "incorrect number of compile errors reported";
+ }
+}
+
+test "Extracttext - without variables" {
+ if test_script_compile "errors/extracttext-novar.sieve" {
+ test_fail "compile should have failed";
+ }
+
+ if test_error :count "ne" :comparator "i;ascii-numeric" "2" {
+ test_fail "incorrect number of compile errors reported";
+ }
+}
+
+test "Extracttext - without foreverypart" {
+ if test_script_compile "errors/extracttext-nofep.sieve" {
+ test_fail "compile should have failed";
+ }
+
+ if test_error :count "ne" :comparator "i;ascii-numeric" "2" {
+ test_fail "incorrect number of compile errors reported";
+ }
+}
+
+
diff --git a/pigeonhole/tests/extensions/mime/errors/address-mime-tag.sieve b/pigeonhole/tests/extensions/mime/errors/address-mime-tag.sieve
new file mode 100644
index 0000000..7adb7bc
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/errors/address-mime-tag.sieve
@@ -0,0 +1,38 @@
+require "mime";
+
+## Address
+
+# No error
+if address :contains :mime "To" "frop@example.com" {
+ discard;
+}
+
+# No error
+if address :anychild :contains :mime "To" "frop@example.com" {
+ discard;
+}
+
+# 1: Bare anychild option
+if address :anychild "To" "frop@example.com" {
+ discard;
+}
+
+# 2: Inappropriate option
+if address :mime :anychild :type "To" "frop@example.com" {
+ discard;
+}
+
+# 3: Inappropriate option
+if address :mime :anychild :subtype "To" "frop@example.com" {
+ discard;
+}
+
+# 4: Inappropriate option
+if address :mime :anychild :contenttype "To" "frop@example.com" {
+ discard;
+}
+
+# 5: Inappropriate option
+if address :mime :anychild :param "frop" "To" "frop@example.com" {
+ discard;
+}
diff --git a/pigeonhole/tests/extensions/mime/errors/break.sieve b/pigeonhole/tests/extensions/mime/errors/break.sieve
new file mode 100644
index 0000000..1858673
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/errors/break.sieve
@@ -0,0 +1,157 @@
+require "foreverypart";
+
+foreverypart :name "frop" {
+ # 1: Spurious tag
+ break :tag;
+
+ # 2: Spurious tests
+ break true;
+
+ # 3: Spurious tests
+ break anyof(true, false);
+
+ # 4: Bare string
+ break "frop";
+
+ # 5: Bare string-list
+ break ["frop", "friep"];
+
+ # 6: Several bad arguments
+ break 13 ["frop", "friep"];
+
+ # 7: Spurious additional tag
+ break :name "frop" :friep;
+
+ # 8: Spurious additional string
+ break :name "frop" "friep";
+
+ # 9: Bad name
+ break :name 13;
+
+ # 10: Bad name
+ break :name ["frop", "friep"];
+
+ # No error
+ break;
+
+ # No error
+ break :name "frop";
+
+ # No error
+ if exists "frop" {
+ break;
+ }
+
+ # No error
+ if exists "frop" {
+ break :name "frop";
+ }
+
+ # No error
+ foreverypart {
+ break :name "frop";
+ }
+
+ # No error
+ foreverypart :name "friep" {
+ break :name "frop";
+ }
+
+ # No error
+ foreverypart :name "friep" {
+ break :name "friep";
+ }
+
+ # No error
+ foreverypart :name "friep" {
+ break;
+ }
+
+ # No error
+ foreverypart {
+ if exists "frop" {
+ break :name "frop";
+ }
+ }
+
+ # No error
+ foreverypart :name "friep" {
+ if exists "frop" {
+ break :name "frop";
+ }
+ }
+
+ # No error
+ foreverypart :name "friep" {
+ if exists "frop" {
+ break :name "friep";
+ }
+ }
+
+ # No error
+ foreverypart :name "friep" {
+ if exists "frop" {
+ break;
+ }
+ }
+}
+
+# 11: Outside loop
+break;
+
+# 12: Outside loop
+if exists "frop" {
+ break;
+}
+
+# 13: Outside loop
+break :name "frop";
+
+# 14: Outside loop
+if exists "frop" {
+ break :name "frop";
+}
+
+# 15: Bad name
+foreverypart {
+ break :name "frop";
+}
+
+# 16: Bad name
+foreverypart {
+ if exists "frop" {
+ break :name "frop";
+ }
+}
+
+# 17: Bad name
+foreverypart :name "friep" {
+ break :name "frop";
+}
+
+# 18: Bad name
+foreverypart :name "friep" {
+ if exists "frop" {
+ break :name "frop";
+ }
+}
+
+# 19: Bad name
+foreverypart :name "friep" {
+ foreverypart :name "frop" {
+ break :name "frml";
+ }
+}
+
+# 20: Bad name
+foreverypart :name "friep" {
+ foreverypart :name "frop" {
+ if exists "frop" {
+ break :name "frml";
+ }
+ }
+}
+
+
+
+
diff --git a/pigeonhole/tests/extensions/mime/errors/exists-mime-tag.sieve b/pigeonhole/tests/extensions/mime/errors/exists-mime-tag.sieve
new file mode 100644
index 0000000..84c86a7
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/errors/exists-mime-tag.sieve
@@ -0,0 +1,43 @@
+require "mime";
+
+## Exists
+
+# No error
+if exists :mime "To" {
+ discard;
+}
+
+# No error
+if exists :anychild :mime "To" {
+ discard;
+}
+
+# 1: Inappropriate option
+if exists :anychild "To" {
+ discard;
+}
+
+# 2: Inappropriate option
+if exists :mime :type "To" {
+ discard;
+}
+
+# 3: Inappropriate option
+if exists :mime :subtype "To" {
+ discard;
+}
+
+# 4: Inappropriate option
+if exists :mime :contenttype "To" {
+ discard;
+}
+
+# 5: Inappropriate option
+if exists :mime :param ["frop", "friep"] "To" {
+ discard;
+}
+
+
+
+
+
diff --git a/pigeonhole/tests/extensions/mime/errors/extracttext-nofep.sieve b/pigeonhole/tests/extensions/mime/errors/extracttext-nofep.sieve
new file mode 100644
index 0000000..c38b228
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/errors/extracttext-nofep.sieve
@@ -0,0 +1,4 @@
+require "extracttext";
+require "variables";
+
+keep;
diff --git a/pigeonhole/tests/extensions/mime/errors/extracttext-novar.sieve b/pigeonhole/tests/extensions/mime/errors/extracttext-novar.sieve
new file mode 100644
index 0000000..8e2a378
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/errors/extracttext-novar.sieve
@@ -0,0 +1,6 @@
+require "extracttext";
+require "foreverypart";
+
+foreverypart {
+ extracttext "frop";
+}
diff --git a/pigeonhole/tests/extensions/mime/errors/extracttext.sieve b/pigeonhole/tests/extensions/mime/errors/extracttext.sieve
new file mode 100644
index 0000000..f8af1c9
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/errors/extracttext.sieve
@@ -0,0 +1,42 @@
+require "extracttext";
+require "variables";
+require "foreverypart";
+
+# 1: Used outside foreverypart
+extracttext :first 10 "data";
+
+foreverypart {
+ # 2: Missing arguments
+ extracttext;
+
+ # 3: Bad arguments
+ extracttext 1;
+
+ # 4: Bad arguments
+ extracttext ["frop", "friep"];
+
+ # 5: Unknown tag
+ extracttext :frop "frop";
+
+ # 6: Invalid variable name
+ extracttext "${frop}";
+
+ # Not an error
+ extracttext "\n\a\m\e";
+
+ # 7: Trying to assign match variable
+ extracttext "0";
+
+ # Not an error
+ extracttext :lower "frop";
+
+ # 8: Bad ":first" tag
+ extracttext :first "frop";
+
+ # 9: Bad ":first" tag
+ extracttext :first "frop" "friep";
+
+ # 10: Bad ":first" tag
+ extracttext :first ["frop", "friep"] "frml";
+}
+
diff --git a/pigeonhole/tests/extensions/mime/errors/foreverypart.sieve b/pigeonhole/tests/extensions/mime/errors/foreverypart.sieve
new file mode 100644
index 0000000..38a28d4
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/errors/foreverypart.sieve
@@ -0,0 +1,45 @@
+require "foreverypart";
+
+# 1: No block
+foreverypart;
+
+# 2: Spurious tag
+foreverypart :tag { }
+
+# 3: Spurious tests
+foreverypart true { }
+
+# 4: Spurious tests
+foreverypart anyof(true, false) { }
+
+# 5: Bare string
+foreverypart "frop" { }
+
+# 6: Bare string-list
+foreverypart ["frop", "friep"] { }
+
+# 7: Several bad arguments
+foreverypart 13 ["frop", "friep"] { }
+
+# 8: Spurious additional tag
+foreverypart :name "frop" :friep { }
+
+# 9: Spurious additional string
+foreverypart :name "frop" "friep" { }
+
+# 10: Bad name
+foreverypart :name 13 { }
+
+# 11: Bad name
+foreverypart :name ["frop", "friep"] { }
+
+# No error
+foreverypart { keep; }
+
+# No error
+foreverypart :name "frop" { keep; }
+
+# No error
+foreverypart :name "frop" { foreverypart { keep; } }
+
+
diff --git a/pigeonhole/tests/extensions/mime/errors/header-mime-tag.sieve b/pigeonhole/tests/extensions/mime/errors/header-mime-tag.sieve
new file mode 100644
index 0000000..85782af
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/errors/header-mime-tag.sieve
@@ -0,0 +1,100 @@
+require "mime";
+
+## Header
+
+# No error
+if header :contains :mime "Content-Type" "text/plain" {
+ discard;
+}
+
+# No error
+if header :mime :type "Content-Type" "text" {
+ discard;
+}
+
+# No error
+if header :mime :subtype "Content-Type" "plain" {
+ discard;
+}
+
+# No error
+if header :mime :contenttype "Content-Type" "text/plain" {
+ discard;
+}
+
+# No error
+if header :mime :param ["frop", "friep"] "Content-Type" "frml" {
+ discard;
+}
+
+# No error
+if header :anychild :contains :mime "Content-Type" "text/plain" {
+ discard;
+}
+
+# No error
+if header :mime :anychild :type "Content-Type" "text" {
+ discard;
+}
+
+# No error
+if header :mime :subtype :anychild "Content-Type" "plain" {
+ discard;
+}
+
+# No error
+if header :anychild :mime :contenttype "Content-Type" "text/plain" {
+ discard;
+}
+
+# No error
+if header :mime :param ["frop", "friep"] :anychild "Content-Type" "frml" {
+ discard;
+}
+
+# 1: Bare anychild option
+if header :anychild "Content-Type" "frml" {
+ discard;
+}
+
+# 2: Bare mime option
+if header :type "Content-Type" "frml" {
+ discard;
+}
+
+# 3: Bare mime option
+if header :subtype "Content-Type" "frml" {
+ discard;
+}
+
+# 4: Bare mime option
+if header :contenttype "Content-Type" "frml" {
+ discard;
+}
+
+# 5: Bare mime option
+if header :param "frop" "Content-Type" "frml" {
+ discard;
+}
+
+# 6: Multiple option tags
+if header :mime :type :subtype "Content-Type" "frml" {
+ discard;
+}
+
+# 7: Bad param argument
+if header :mime :param 13 "Content-Type" "frml" {
+ discard;
+}
+
+# 8: Missing param argument
+if header :mime :param :anychild "Content-Type" "frml" {
+ discard;
+}
+
+# 9: Missing param argument
+if header :mime :param :frop "Content-Type" "frml" {
+ discard;
+}
+
+
diff --git a/pigeonhole/tests/extensions/mime/errors/limits-include.sieve b/pigeonhole/tests/extensions/mime/errors/limits-include.sieve
new file mode 100644
index 0000000..ef92456
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/errors/limits-include.sieve
@@ -0,0 +1,6 @@
+require "foreverypart";
+require "include";
+
+foreverypart :name "frop" {
+ include "include-loop-2";
+}
diff --git a/pigeonhole/tests/extensions/mime/errors/limits.sieve b/pigeonhole/tests/extensions/mime/errors/limits.sieve
new file mode 100644
index 0000000..0add1c3
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/errors/limits.sieve
@@ -0,0 +1,13 @@
+require "foreverypart";
+
+foreverypart :name "frop" {
+ foreverypart :name "friep" {
+ foreverypart :name "frml" {
+ foreverypart {
+ foreverypart {
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/pigeonhole/tests/extensions/mime/execute.svtest b/pigeonhole/tests/extensions/mime/execute.svtest
new file mode 100644
index 0000000..2ced83b
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/execute.svtest
@@ -0,0 +1,82 @@
+require "vnd.dovecot.testsuite";
+
+/*
+ * Execution testing (currently just meant to trigger any segfaults)
+ */
+
+test_set "message" text:
+From: Whomever <whoever@example.com>
+To: Someone <someone@example.com>
+Date: Sat, 10 Oct 2009 00:30:04 +0200
+Subject: whatever
+Content-Type: multipart/mixed; boundary=outer
+
+This is a multi-part message in MIME format.
+
+--outer
+Content-Type: multipart/alternative; boundary=inner
+
+This is a nested multi-part message in MIME format.
+
+--inner
+Content-Type: text/plain; charset="us-ascii"
+
+Hello
+
+--inner
+Content-Type: text/html; charset="us-ascii"
+
+<html><body>Hello</body></html>
+
+--inner--
+
+This is the end of the inner MIME multipart.
+
+--outer
+Content-Type: message/rfc822
+
+From: Someone Else
+Subject: Hello, this is an elaborate request for you to finally say hello
+ already!
+
+Please say Hello
+
+--outer--
+
+This is the end of the outer MIME multipart.
+.
+;
+
+test "Basic - foreverypart" {
+ if not test_script_compile "execute/foreverypart.sieve" {
+ test_fail "script compile failed";
+ }
+
+ if not test_script_run {
+ test_fail "script run failed";
+ }
+
+ if not test_result_execute {
+ test_fail "result execute failed";
+ }
+
+ test_binary_save "ihave-basic";
+ test_binary_load "ihave-basic";
+}
+
+test "Basic - mime" {
+ if not test_script_compile "execute/mime.sieve" {
+ test_fail "script compile failed";
+ }
+
+ if not test_script_run {
+ test_fail "script run failed";
+ }
+
+ if not test_result_execute {
+ test_fail "result execute failed";
+ }
+
+ test_binary_save "ihave-basic";
+ test_binary_load "ihave-basic";
+}
diff --git a/pigeonhole/tests/extensions/mime/execute/foreverypart.sieve b/pigeonhole/tests/extensions/mime/execute/foreverypart.sieve
new file mode 100644
index 0000000..9ae1fba
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/execute/foreverypart.sieve
@@ -0,0 +1,14 @@
+require "foreverypart";
+require "variables";
+
+foreverypart {
+ foreverypart {
+ foreverypart {
+ foreverypart {
+ set "a" "a${a}";
+ }
+ }
+ }
+}
+
+
diff --git a/pigeonhole/tests/extensions/mime/execute/mime.sieve b/pigeonhole/tests/extensions/mime/execute/mime.sieve
new file mode 100644
index 0000000..dd7fedc
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/execute/mime.sieve
@@ -0,0 +1,69 @@
+require "mime";
+require "foreverypart";
+require "variables";
+
+if header :contains :mime "Content-Type" "text/plain" {
+ discard;
+}
+if header :mime :type "Content-Type" "text" {
+ discard;
+}
+if header :mime :subtype "Content-Type" "plain" {
+ discard;
+}
+if header :mime :contenttype "Content-Type" "text/plain" {
+ discard;
+}
+if header :mime :param ["frop", "friep"] "Content-Type" "frml" {
+ discard;
+}
+if header :anychild :contains :mime "Content-Type" "text/plain" {
+ discard;
+}
+if header :mime :anychild :type "Content-Type" "text" {
+ discard;
+}
+if header :mime :subtype :anychild "Content-Type" "plain" {
+ discard;
+}
+if header :anychild :mime :contenttype "Content-Type" "text/plain" {
+ discard;
+}
+if header :mime :param ["frop", "friep"] :anychild "Content-Type" "frml" {
+ discard;
+}
+
+foreverypart {
+ foreverypart {
+ if header :contains :mime "Content-Type" "text/plain" {
+ discard;
+ }
+ if header :mime :type "Content-Type" "text" {
+ discard;
+ }
+ if header :mime :subtype "Content-Type" "plain" {
+ discard;
+ }
+ if header :mime :contenttype "Content-Type" "text/plain" {
+ discard;
+ }
+ if header :mime :param ["frop", "friep"] "Content-Type" "frml" {
+ discard;
+ }
+ if header :anychild :contains :mime "Content-Type" "text/plain" {
+ discard;
+ }
+ if header :mime :anychild :type "Content-Type" "text" {
+ discard;
+ }
+ if header :mime :subtype :anychild "Content-Type" "plain" {
+ discard;
+ }
+ if header :anychild :mime :contenttype "Content-Type" "text/plain" {
+ discard;
+ }
+ if header :mime :param ["frop", "friep"] :anychild "Content-Type" "frml" {
+ discard;
+ }
+ }
+}
diff --git a/pigeonhole/tests/extensions/mime/exists.svtest b/pigeonhole/tests/extensions/mime/exists.svtest
new file mode 100644
index 0000000..517deeb
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/exists.svtest
@@ -0,0 +1,237 @@
+require "vnd.dovecot.testsuite";
+require "mime";
+require "foreverypart";
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@vestingbar.bl
+Subject: Test message
+Date: Wed, 29 Jul 2009 18:21:44 +0300
+X-Spam-Status: Not Spam
+Resent-To: nico@frop.example.com
+
+Test!
+.
+;
+
+/*
+ * One header
+ */
+
+test "One header" {
+ if not exists :mime :anychild "from" {
+ test_fail "exists test missed from header";
+ }
+
+ if exists :mime :anychild "x-nonsense" {
+ test_fail "exists test found non-existent header";
+ }
+}
+
+/*
+ * One header - foreverypart
+ */
+
+test "One header - foreverypart" {
+ foreverypart {
+ if not exists :mime :anychild "from" {
+ test_fail "exists test missed from header";
+ }
+
+ if exists :mime :anychild "x-nonsense" {
+ test_fail "exists test found non-existent header";
+ }
+ }
+}
+
+/*
+ * Two headers
+ */
+
+test "Two headers" {
+ if not exists :mime :anychild ["from","to"] {
+ test_fail "exists test missed from or to header";
+ }
+
+ if exists :mime :anychild ["from","x-nonsense"] {
+ test_fail "exists test found non-existent header (1)";
+ }
+
+ if exists :mime :anychild ["x-nonsense","to"] {
+ test_fail "exists test found non-existent header (2)";
+ }
+
+ if exists :mime :anychild ["x-nonsense","x-nonsense2"] {
+ test_fail "exists test found non-existent header (3)";
+ }
+}
+
+/*
+ * Two headers - foreverypart
+ */
+
+test "Two headers - foreverypart" {
+ foreverypart {
+ if not exists :mime :anychild ["from","to"] {
+ test_fail "exists test missed from or to header";
+ }
+
+ if exists :mime :anychild ["from","x-nonsense"] {
+ test_fail "exists test found non-existent header (1)";
+ }
+
+ if exists :mime :anychild ["x-nonsense","to"] {
+ test_fail "exists test found non-existent header (2)";
+ }
+
+ if exists :mime :anychild ["x-nonsense","x-nonsense2"] {
+ test_fail "exists test found non-existent header (3)";
+ }
+ }
+}
+
+/*
+ * Three headers
+ */
+
+test "Three headers" {
+ if not exists :mime :anychild ["Subject","date","resent-to"] {
+ test_fail "exists test missed subject, date or resent-to header";
+ }
+
+ if exists :mime :anychild ["x-nonsense","date","resent-to"] {
+ test_fail "exists test found non-existent header (1)";
+ }
+
+ if exists :mime :anychild ["subject", "x-nonsense","resent-to"] {
+ test_fail "exists test found non-existent header (2)";
+ }
+
+ if exists :mime :anychild ["subject","date","x-nonsense"] {
+ test_fail "exists test found non-existent header (3)";
+ }
+
+ if exists :mime :anychild ["subject", "x-nonsense","x-nonsense2"] {
+ test_fail "exists test found non-existent header (4)";
+ }
+
+ if exists :mime :anychild ["x-nonsense","date","x-nonsense2"] {
+ test_fail "exists test found non-existent header (5)";
+ }
+
+ if exists :mime :anychild ["x-nonsense","x-nonsense2","resent-to"] {
+ test_fail "exists test found non-existent header (6)";
+ }
+
+ if exists :mime :anychild ["x-nonsense","x-nonsense2","x-nonsense3"] {
+ test_fail "exists test found non-existent header (7)";
+ }
+}
+
+/*
+ * Three headers - foreverypart
+ */
+
+test "Three headers - foreverypart " {
+ foreverypart {
+ if not exists :mime :anychild ["Subject","date","resent-to"] {
+ test_fail "exists test missed subject, date or resent-to header";
+ }
+
+ if exists :mime :anychild ["x-nonsense","date","resent-to"] {
+ test_fail "exists test found non-existent header (1)";
+ }
+
+ if exists :mime :anychild ["subject", "x-nonsense","resent-to"] {
+ test_fail "exists test found non-existent header (2)";
+ }
+
+ if exists :mime :anychild ["subject","date","x-nonsense"] {
+ test_fail "exists test found non-existent header (3)";
+ }
+
+ if exists :mime :anychild ["subject", "x-nonsense","x-nonsense2"] {
+ test_fail "exists test found non-existent header (4)";
+ }
+
+ if exists :mime :anychild ["x-nonsense","date","x-nonsense2"] {
+ test_fail "exists test found non-existent header (5)";
+ }
+
+ if exists :mime :anychild ["x-nonsense","x-nonsense2","resent-to"] {
+ test_fail "exists test found non-existent header (6)";
+ }
+
+ if exists :mime :anychild ["x-nonsense","x-nonsense2","x-nonsense3"] {
+ test_fail "exists test found non-existent header (7)";
+ }
+ }
+}
+
+/*
+ * Multipart anychild
+ */
+
+test_set "message" text:
+From: Hendrik <hendrik@example.com>
+To: Harrie <harrie@example.com>
+Date: Sat, 11 Oct 2010 00:31:44 +0200
+Subject: Harrie is een prutser
+Content-Type: multipart/mixed; boundary=AA
+X-Test1: AA
+
+This is a multi-part message in MIME format.
+--AA
+Content-Type: multipart/mixed; boundary=BB
+X-Test2: BB
+
+This is a multi-part message in MIME format.
+--BB
+Content-Type: text/plain; charset="us-ascii"
+X-Test3: CC
+
+Hello
+
+--BB
+Content-Type: text/plain; charset="us-ascii"
+X-Test4: DD
+
+Hello again
+
+--BB--
+This is the end of MIME multipart.
+
+--AA
+Content-Type: text/plain; charset="us-ascii"
+X-Test5: EE
+
+And again
+
+--AA--
+This is the end of MIME multipart.
+.
+;
+
+test "Multipart anychild" {
+ if not exists :mime :anychild "X-Test1" {
+ test_fail "X-Test1 header does exist";
+ }
+ if not exists :mime :anychild "X-Test2" {
+ test_fail "X-Test2 header does exist";
+ }
+ if not exists :mime :anychild "X-Test3" {
+ test_fail "X-Test3 header does exist";
+ }
+ if not exists :mime :anychild "X-Test4" {
+ test_fail "X-Test4 header does exist";
+ }
+ if not exists :mime :anychild "X-Test5" {
+ test_fail "X-Test5 header does exist";
+ }
+ if not exists :mime :anychild
+ ["X-Test1", "X-Test2", "X-Test3", "X-Test4", "X-Test5"] {
+ test_fail "Not all headers exist";
+ }
+}
+
+
diff --git a/pigeonhole/tests/extensions/mime/extracttext.svtest b/pigeonhole/tests/extensions/mime/extracttext.svtest
new file mode 100644
index 0000000..510a52b
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/extracttext.svtest
@@ -0,0 +1,143 @@
+require "vnd.dovecot.testsuite";
+require "foreverypart";
+require "variables";
+require "extracttext";
+
+test_set "message" text:
+From: Hendrik <hendrik@example.com>
+To: Harrie <harrie@example.com>
+Date: Sat, 11 Oct 2010 00:31:44 +0200
+Subject: Harrie is een prutser
+Content-Type: multipart/mixed; boundary=AA
+
+This is a multi-part message in MIME format.
+--AA
+Content-Type: multipart/mixed; boundary=BB
+
+This is a multi-part message in MIME format.
+--BB
+Content-Type: text/plain; charset="us-ascii"
+
+This is the first message part containing
+plain text.
+
+--BB
+Content-Type: text/plain; charset="us-ascii"
+
+This is another plain text message part.
+
+--BB--
+This is the end of MIME multipart.
+
+--AA
+Content-Type: text/html; charset="us-ascii"
+
+<html>
+<body>This is a piece of HTML text.</body>
+</html>
+
+--AA--
+This is the end of MIME multipart.
+.
+;
+
+test "Basic" {
+ set "a" "a";
+ foreverypart {
+ extracttext "b";
+ if string "${a}" "aaa" {
+ if not string :contains "${b}" "first" {
+ test_fail "bad content extracted: ${b}";
+ }
+ } elsif string "${a}" "aaaa" {
+ if not string :contains "${b}" "another" {
+ test_fail "bad content extracted: ${b}";
+ }
+ } elsif string "${a}" "aaaaa" {
+ if not string :contains "${b}" "HTML text" {
+ test_fail "bad content extracted: ${b}";
+ }
+ if string :contains "${b}" "<html>" {
+ test_fail "content extracted html: ${b}";
+ }
+ }
+ set "a" "a${a}";
+ }
+ if not string "${a}" "aaaaaa" {
+ set :length "parts" "${a}";
+ test_fail "bad number of parts parsed: ${parts}";
+ }
+}
+
+test_set "message" text:
+From: <stephan@example.com>
+To: <frop@example.com>
+Subject: Frop!
+
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+.
+;
+
+test "First - less" {
+ foreverypart {
+ extracttext :first 20 "data";
+ if not string "${data}" "FROP! FROP! FROP! FR" {
+ test_fail "Bad data extracted";
+ }
+
+ extracttext :length :first 100 "data_len";
+ if not string "${data_len}" "100" {
+ test_fail "Bad number of bytes extracted";
+ }
+ }
+}
+
+test_set "message" text:
+From: <stephan@example.com>
+To: <frop@example.com>
+Subject: Frop!
+
+FROP! FROP! FROP! FROP!
+.
+;
+
+test "First - more" {
+ foreverypart {
+ extracttext :first 100 "data";
+ if not string :matches "${data}" "FROP! FROP! FROP! FROP!*" {
+ test_fail "Bad data extracted";
+ }
+ }
+}
+
+test_set "message" text:
+From: <stephan@example.com>
+To: <frop@example.com>
+Subject: Frop!
+
+FROP! FROP! FROP! FROP!
+.
+;
+
+test "Modifier" {
+ foreverypart {
+ extracttext :lower :upperfirst "data";
+ if not string :matches "${data}" "Frop! frop! frop! frop!*" {
+ test_fail "Bad data extracted";
+ }
+ }
+}
+
+
+
diff --git a/pigeonhole/tests/extensions/mime/foreverypart.svtest b/pigeonhole/tests/extensions/mime/foreverypart.svtest
new file mode 100644
index 0000000..08907c9
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/foreverypart.svtest
@@ -0,0 +1,178 @@
+require "vnd.dovecot.testsuite";
+require "relational";
+require "foreverypart";
+require "mime";
+require "variables";
+require "include";
+
+test_set "message" text:
+From: Hendrik <hendrik@example.com>
+To: Harrie <harrie@example.com>
+Date: Sat, 11 Oct 2010 00:31:44 +0200
+Subject: Harrie is een prutser
+Content-Type: multipart/mixed; boundary=AA
+X-Test: AA
+
+This is a multi-part message in MIME format.
+--AA
+Content-Type: multipart/mixed; boundary=BB
+X-Test: BB
+
+This is a multi-part message in MIME format.
+--BB
+Content-Type: text/plain; charset="us-ascii"
+X-Test: CC
+
+Hello
+
+--BB
+Content-Type: text/plain; charset="us-ascii"
+X-Test: DD
+
+Hello again
+
+--BB--
+This is the end of MIME multipart.
+
+--AA
+Content-Type: text/plain; charset="us-ascii"
+X-Test: EE
+
+And again
+
+--AA--
+This is the end of MIME multipart.
+.
+;
+
+test "Single loop" {
+ set "a" "a";
+ foreverypart {
+ set :length "la" "${a}";
+
+ if string "${a}" "a" {
+ if not header :mime "X-Test" "AA" {
+ test_fail "wrong header extracted (${la})";
+ }
+ } elsif string "${a}" "aa" {
+ if not header :mime "X-Test" "BB" {
+ test_fail "wrong header extracted (${la})";
+ }
+ } elsif string "${a}" "aaa" {
+ if not header :mime "X-Test" "CC" {
+ test_fail "wrong header extracted (${la})";
+ }
+ } elsif string "${a}" "aaaa" {
+ if not header :mime "X-Test" "DD" {
+ test_fail "wrong header extracted (${la})";
+ }
+ } elsif string "${a}" "aaaaa" {
+ if not header :mime "X-Test" "EE" {
+ test_fail "wrong header extracted (${la})";
+ }
+ }
+ set "a" "a${a}";
+ }
+}
+
+test "Double loop" {
+ set "a" "a";
+ foreverypart {
+ set :length "la" "${a}";
+
+ if string "${a}" "a" {
+ if not header :mime "X-Test" "AA" {
+ test_fail "wrong header extracted (${la})";
+ }
+ } elsif string "${a}" "aaaaaa" {
+ if not header :mime "X-Test" "BB" {
+ test_fail "wrong header extracted (${la})";
+ }
+ } elsif string "${a}" "aaaaaaaaa" {
+ if not header :mime "X-Test" "CC" {
+ test_fail "wrong header extracted (${la})";
+ }
+ } elsif string "${a}" "aaaaaaaaaa" {
+ if not header :mime "X-Test" "DD" {
+ test_fail "wrong header extracted (${la})";
+ }
+ } elsif string "${a}" "aaaaaaaaaaa" {
+ if not header :mime "X-Test" "EE" {
+ test_fail "wrong header extracted (${la})";
+ }
+ }
+
+ set "a" "a${a}";
+
+ foreverypart {
+ set :length "la" "${a}";
+
+ if string "${a}" "aa" {
+ if not header :mime "X-Test" "BB" {
+ test_fail "wrong header extracted (${la})";
+ }
+ } elsif string "${a}" "aaa" {
+ if not header :mime "X-Test" "CC" {
+ test_fail "wrong header extracted (${la})";
+ }
+ } elsif string "${a}" "aaaa" {
+ if not header :mime "X-Test" "DD" {
+ test_fail "wrong header extracted (${la})";
+ }
+ } elsif string "${a}" "aaaaa" {
+ if not header :mime "X-Test" "EE" {
+ test_fail "wrong header extracted (${la})";
+ }
+ } elsif string "${a}" "aaaaaaa" {
+ if not header :mime "X-Test" "CC" {
+ test_fail "wrong header extracted (${la})";
+ }
+ } elsif string "${a}" "aaaaaaaa" {
+ if not header :mime "X-Test" "DD" {
+ test_fail "wrong header extracted (${la})";
+ }
+ }
+ set "a" "a${a}";
+ }
+ }
+}
+
+test "Double loop - include" {
+ global "in";
+ global "error";
+ set "in" "a";
+ foreverypart {
+ set :length "la" "${in}";
+
+ if string "${in}" "in" {
+ if not header :mime "X-Test" "AA" {
+ test_fail "wrong header extracted (${la})";
+ }
+ } elsif string "${in}" "aaaaaa" {
+ if not header :mime "X-Test" "BB" {
+ test_fail "wrong header extracted (${la})";
+ }
+ } elsif string "${in}" "aaaaaaaaa" {
+ if not header :mime "X-Test" "CC" {
+ test_fail "wrong header extracted (${la})";
+ }
+ } elsif string "${in}" "aaaaaaaaaa" {
+ if not header :mime "X-Test" "DD" {
+ test_fail "wrong header extracted (${la})";
+ }
+ } elsif string "${in}" "aaaaaaaaaaa" {
+ if not header :mime "X-Test" "EE" {
+ test_fail "wrong header extracted (${la})";
+ }
+ }
+
+ set "in" "a${in}";
+
+ include "include-foreverypart";
+
+ if not string "${error}" "" {
+ test_fail "INCLUDED: ${error}";
+ }
+ }
+}
+
diff --git a/pigeonhole/tests/extensions/mime/header.svtest b/pigeonhole/tests/extensions/mime/header.svtest
new file mode 100644
index 0000000..48cd9e4
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/header.svtest
@@ -0,0 +1,444 @@
+require "vnd.dovecot.testsuite";
+require "variables";
+require "foreverypart";
+require "mime";
+
+/*
+ * Basic functionality
+ */
+
+test_set "message" text:
+From: stephan@example.com
+To: nico@nl.example.com, harry@de.example.com
+Subject: Frobnitzm
+Comments: This is nonsense.
+Keywords: nonsense, strange, testing
+X-Spam: Yes
+
+Test.
+.
+;
+
+test "Basic functionality" {
+ /* Must match */
+ if not header :mime :anychild :contains ["Subject", "Comments"] "Frobnitzm" {
+ test_fail "failed to match header (1)";
+ }
+
+ if not header :mime :anychild :contains ["Subject", "Comments"] "nonsense" {
+ test_fail "failed to match header(2)";
+ }
+
+ if not header :mime :anychild :matches "Keywords" "*, strange, *" {
+ test_fail "failed to match header (3)";
+ }
+
+ if not header :mime :anychild :is "Comments" "This is nonsense." {
+ test_fail "failed to match header (4)";
+ }
+
+ /* Must not match */
+ if header :mime :anychild ["subject", "comments", "keywords"] "idiotic" {
+ test_fail "matched nonsense";
+ }
+
+ /* Match first key */
+ if not header :mime :anychild :contains ["keywords"] ["strange", "snot", "vreemd"] {
+ test_fail "failed to match first key";
+ }
+
+ /* Match second key */
+ if not header :mime :anychild :contains ["keywords"] ["raar", "strange", "vreemd"] {
+ test_fail "failed to match second key";
+ }
+
+ /* Match last key */
+ if not header :mime :anychild :contains ["keywords"] ["raar", "snot", "strange"] {
+ test_fail "failed to match last key";
+ }
+
+ /* First header */
+ if not header :mime :anychild :contains ["keywords", "subject"]
+ ["raar", "strange", "vreemd"] {
+ test_fail "failed to match first header";
+ }
+
+ /* Second header */
+ if not header :mime :anychild :contains ["subject", "keywords"]
+ ["raar", "strange", "vreemd"] {
+ test_fail "failed to match second header";
+ }
+}
+
+/*
+ * Basic functionality - foreverypart
+ */
+
+test "Basic functionality - foreverypart" {
+ foreverypart {
+ /* Must match */
+ if not header :mime :anychild :contains ["Subject", "Comments"] "Frobnitzm" {
+ test_fail "failed to match header (1)";
+ }
+
+ if not header :mime :anychild :contains ["Subject", "Comments"] "nonsense" {
+ test_fail "failed to match header(2)";
+ }
+
+ if not header :mime :anychild :matches "Keywords" "*, strange, *" {
+ test_fail "failed to match header (3)";
+ }
+
+ if not header :mime :anychild :is "Comments" "This is nonsense." {
+ test_fail "failed to match header (4)";
+ }
+
+ /* Must not match */
+ if header :mime :anychild ["subject", "comments", "keywords"] "idiotic" {
+ test_fail "matched nonsense";
+ }
+
+ /* Match first key */
+ if not header :mime :anychild :contains ["keywords"] ["strange", "snot", "vreemd"] {
+ test_fail "failed to match first key";
+ }
+
+ /* Match second key */
+ if not header :mime :anychild :contains ["keywords"] ["raar", "strange", "vreemd"] {
+ test_fail "failed to match second key";
+ }
+
+ /* Match last key */
+ if not header :mime :anychild :contains ["keywords"] ["raar", "snot", "strange"] {
+ test_fail "failed to match last key";
+ }
+
+ /* First header */
+ if not header :mime :anychild :contains ["keywords", "subject"]
+ ["raar", "strange", "vreemd"] {
+ test_fail "failed to match first header";
+ }
+
+ /* Second header */
+ if not header :mime :anychild :contains ["subject", "keywords"]
+ ["raar", "strange", "vreemd"] {
+ test_fail "failed to match second header";
+ }
+ }
+}
+
+/*
+ * Matching empty key
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.com
+X-Caffeine: C8H10N4O2
+Subject: I need coffee!
+Comments:
+
+Text
+.
+;
+
+test "Matching empty key" {
+ if header :mime :anychild :is "X-Caffeine" "" {
+ test_fail ":is-matched non-empty header with empty string";
+ }
+
+ if not header :mime :anychild :contains "X-Caffeine" "" {
+ test_fail "failed to match existing header with empty string";
+ }
+
+ if not header :mime :anychild :is "comments" "" {
+ test_fail "failed to match empty header :mime :anychild with empty string";
+ }
+
+ if header :mime :anychild :contains "X-Nonsense" "" {
+ test_fail ":contains-matched non-existent header with empty string";
+ }
+}
+
+/*
+ * Matching empty key - foreverypart
+ */
+
+test "Matching empty key - foreverypart" {
+ foreverypart {
+ if header :mime :anychild :is "X-Caffeine" "" {
+ test_fail ":is-matched non-empty header with empty string";
+ }
+
+ if not header :mime :anychild :contains "X-Caffeine" "" {
+ test_fail "failed to match existing header with empty string";
+ }
+
+ if not header :mime :anychild :is "comments" "" {
+ test_fail "failed to match empty header :mime :anychild with empty string";
+ }
+
+ if header :mime :anychild :contains "X-Nonsense" "" {
+ test_fail ":contains-matched non-existent header with empty string";
+ }
+ }
+}
+
+/*
+ * Ignoring whitespace
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.com
+Subject: Help
+X-A: Text
+X-B: Text
+
+Text
+.
+;
+
+test "Ignoring whitespace" {
+ if not header :mime :anychild :is "x-a" "Text" {
+ if header :mime :anychild :matches "x-a" "*" {
+ set "header" "${1}";
+ }
+ test_fail "header :mime :anychild test does not strip leading whitespace (header=`${header}`)";
+ }
+
+ if not header :mime :anychild :is "x-b" "Text" {
+ if header :mime :anychild :matches "x-b" "*" {
+ set "header" "${1}";
+ }
+ test_fail "header :mime :anychild test does not strip trailing whitespace (header=`${header}`)";
+ }
+
+ if not header :mime :anychild :is "subject" "Help" {
+ if header :mime :anychild :matches "subject" "*" {
+ set "header" "${1}";
+ }
+ test_fail "header :mime :anychild test does not strip both leading and trailing whitespace (header=`${header}`)";
+ }
+}
+
+/*
+ * Ignoring whitespace - foreverypart
+ */
+
+test "Ignoring whitespace - foreverypart" {
+ foreverypart {
+ if not header :mime :anychild :is "x-a" "Text" {
+ if header :mime :anychild :matches "x-a" "*" {
+ set "header" "${1}";
+ }
+ test_fail "header :mime :anychild test does not strip leading whitespace (header=`${header}`)";
+ }
+
+ if not header :mime :anychild :is "x-b" "Text" {
+ if header :mime :anychild :matches "x-b" "*" {
+ set "header" "${1}";
+ }
+ test_fail "header :mime :anychild test does not strip trailing whitespace (header=`${header}`)";
+ }
+
+ if not header :mime :anychild :is "subject" "Help" {
+ if header :mime :anychild :matches "subject" "*" {
+ set "header" "${1}";
+ }
+ test_fail "header :mime :anychild test does not strip both leading and trailing whitespace (header=`${header}`)";
+ }
+ }
+}
+
+/*
+ * Absent or empty header
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.com
+CC: harry@nonsense.ex
+Subject:
+Comments:
+
+Text
+.
+;
+
+test "Absent or empty header" {
+ if not header :mime :anychild :matches "Cc" "?*" {
+ test_fail "CC header is not absent or empty";
+ }
+
+ if header :mime :anychild :matches "Subject" "?*" {
+ test_fail "Subject header is empty, but matched otherwise";
+ }
+
+ if header :mime :anychild :matches "Comment" "?*" {
+ test_fail "Comment header is empty, but matched otherwise";
+ }
+}
+
+/*
+ * Absent or empty header - foreverypart
+ */
+
+test "Absent or empty header - foreverypart" {
+ foreverypart {
+ if not header :mime :anychild :matches "Cc" "?*" {
+ test_fail "CC header is not absent or empty";
+ }
+
+ if header :mime :anychild :matches "Subject" "?*" {
+ test_fail "Subject header is empty, but matched otherwise";
+ }
+
+ if header :mime :anychild :matches "Comment" "?*" {
+ test_fail "Comment header is empty, but matched otherwise";
+ }
+ }
+}
+
+
+/*
+ * Invalid header name
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.com
+Subject: Valid message
+X-Multiline: This is a multi-line
+ header body, which should be
+ unfolded correctly.
+
+Text
+.
+;
+
+test "Invalid header name" {
+ if header :mime :anychild :contains "subject:" "" {
+ test_fail "matched invalid header name";
+ }
+
+ if header :mime :anychild :contains "to!" "" {
+ test_fail "matched invalid header name";
+ }
+}
+
+/*
+ * Invalid header name - foreverypart
+ */
+
+test "Invalid header name - foreverypart" {
+ foreverypart {
+ if header :mime :anychild :contains "subject:" "" {
+ test_fail "matched invalid header name";
+ }
+
+ if header :mime :anychild :contains "to!" "" {
+ test_fail "matched invalid header name";
+ }
+ }
+}
+
+/*
+ * Folded headers
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.com
+Subject: Not enough space on a line!
+X-Multiline: This is a multi-line
+ header body, which should be
+ unfolded correctly.
+
+Text
+.
+;
+
+test "Folded headers" {
+ if not header :mime :anychild :is "x-multiline"
+ "This is a multi-line header body, which should be unfolded correctly." {
+ test_fail "failed to properly unfold folded header.";
+ }
+}
+
+/*
+ * Folded headers - foreverypart
+ */
+
+test "Folded headers - foreverypart" {
+ foreverypart {
+ if not header :mime :anychild :is "x-multiline"
+ "This is a multi-line header body, which should be unfolded correctly." {
+ test_fail "failed to properly unfold folded header.";
+ }
+ }
+}
+
+/*
+ * Multipart anychild
+ */
+
+test_set "message" text:
+From: Hendrik <hendrik@example.com>
+To: Harrie <harrie@example.com>
+Date: Sat, 11 Oct 2010 00:31:44 +0200
+Subject: Harrie is een prutser
+Content-Type: multipart/mixed; boundary=AA
+X-Test: AA
+
+This is a multi-part message in MIME format.
+--AA
+Content-Type: multipart/mixed; boundary=BB
+X-Test: BB
+
+This is a multi-part message in MIME format.
+--BB
+Content-Type: text/plain; charset="us-ascii"
+X-Test: CC
+
+Hello
+
+--BB
+Content-Type: text/plain; charset="us-ascii"
+X-Test: DD
+
+Hello again
+
+--BB--
+This is the end of MIME multipart.
+
+--AA
+Content-Type: text/plain; charset="us-ascii"
+X-Test: EE
+
+And again
+
+--AA--
+This is the end of MIME multipart.
+.
+;
+
+test "Multipart anychild" {
+ if not header :mime :anychild "X-Test" "AA" {
+ test_fail "No AA";
+ }
+ if not header :mime :anychild "X-Test" "BB" {
+ test_fail "No BB";
+ }
+ if not header :mime :anychild "X-Test" "CC" {
+ test_fail "No CC";
+ }
+ if not header :mime :anychild "X-Test" "DD" {
+ test_fail "No DD";
+ }
+ if not header :mime :anychild "X-Test" "EE" {
+ test_fail "No EE";
+ }
+}
+
+
diff --git a/pigeonhole/tests/extensions/mime/included/include-foreverypart.sieve b/pigeonhole/tests/extensions/mime/included/include-foreverypart.sieve
new file mode 100644
index 0000000..f1b1b16
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/included/include-foreverypart.sieve
@@ -0,0 +1,44 @@
+require "include";
+require "foreverypart";
+require "mime";
+require "variables";
+
+global "in";
+global "error";
+
+foreverypart {
+ set :length "la" "${in}";
+
+ if string "${in}" "aa" {
+ if not header :mime "X-Test" "BB" {
+ set "error" "wrong header extracted (${la})";
+ return;
+ }
+ } elsif string "${in}" "aaa" {
+ if not header :mime "X-Test" "CC" {
+ set "error" "wrong header extracted (${la})";
+ return;
+ }
+ } elsif string "${in}" "aaaa" {
+ if not header :mime "X-Test" "DD" {
+ set "error" "wrong header extracted (${la})";
+ return;
+ }
+ } elsif string "${in}" "aaaaa" {
+ if not header :mime "X-Test" "EE" {
+ set "error" "wrong header extracted (${la})";
+ return;
+ }
+ } elsif string "${in}" "aaaaaaa" {
+ if not header :mime "X-Test" "CC" {
+ set "error" "wrong header extracted (${la})";
+ return;
+ }
+ } elsif string "${in}" "aaaaaaaa" {
+ if not header :mime "X-Test" "DD" {
+ set "error" "wrong header extracted (${la})";
+ return;
+ }
+ }
+ set "in" "a${in}";
+}
diff --git a/pigeonhole/tests/extensions/mime/included/include-loop-2.sieve b/pigeonhole/tests/extensions/mime/included/include-loop-2.sieve
new file mode 100644
index 0000000..80c5884
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/included/include-loop-2.sieve
@@ -0,0 +1,6 @@
+require "foreverypart";
+require "include";
+
+foreverypart :name "friep" {
+ include "include-loop-3";
+}
diff --git a/pigeonhole/tests/extensions/mime/included/include-loop-3.sieve b/pigeonhole/tests/extensions/mime/included/include-loop-3.sieve
new file mode 100644
index 0000000..228a8bc
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/included/include-loop-3.sieve
@@ -0,0 +1,6 @@
+require "foreverypart";
+require "include";
+
+foreverypart :name "frml" {
+ include "include-loop-4";
+}
diff --git a/pigeonhole/tests/extensions/mime/included/include-loop-4.sieve b/pigeonhole/tests/extensions/mime/included/include-loop-4.sieve
new file mode 100644
index 0000000..00dad84
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/included/include-loop-4.sieve
@@ -0,0 +1,6 @@
+require "foreverypart";
+require "include";
+
+foreverypart {
+ include "include-loop-5";
+}
diff --git a/pigeonhole/tests/extensions/mime/included/include-loop-5.sieve b/pigeonhole/tests/extensions/mime/included/include-loop-5.sieve
new file mode 100644
index 0000000..e22b21c
--- /dev/null
+++ b/pigeonhole/tests/extensions/mime/included/include-loop-5.sieve
@@ -0,0 +1,9 @@
+require "foreverypart";
+require "include";
+require "mime";
+
+foreverypart {
+ if header :mime :subtype "content-type" "plain" {
+ break;
+ }
+}