From e824925290a0fdf3852f4562dc459a4cbd4e5768 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Thu, 16 Aug 2012 13:44:28 -0400
Subject: [PATCH] Rewritten test scripts for PHPUnit

---
 CHANGELOG            |    1 
 /dev/null            |   65 -------
 tests/HtmlToText.php |   32 +-
 tests/MailFunc.php   |  172 +++++++++++++++++++
 tests/VCards.php     |   59 ++++++
 tests/phpunit.xml    |   13 +
 tests/MailDecode.php |  123 +++++++++++++
 tests/bootstrap.php  |   33 +++
 tests/ModCss.php     |   39 ++++
 9 files changed, 455 insertions(+), 82 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 192ecce..3edbdd4 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Rewritten test scripts for PHPUnit
 - Fix lower-casing email address on replies (#1488598)
 - Fix line separator in exported messages (#1488603)
 - Fix XSS issue where plain signatures wasn't secured in HTML mode (#1488613)
diff --git a/tests/html_to_text.php b/tests/HtmlToText.php
similarity index 75%
rename from tests/html_to_text.php
rename to tests/HtmlToText.php
index aabc1a8..34e2d1a 100644
--- a/tests/html_to_text.php
+++ b/tests/HtmlToText.php
@@ -5,18 +5,12 @@
  *
  * @package Tests
  */
-class rcube_test_html2text extends UnitTestCase
+class HtmlToText extends PHPUnit_Framework_TestCase
 {
 
-    function __construct()
+    function data()
     {
-        $this->UnitTestCase("HTML-to-Text conversion tests");
-
-    }
-
-    function test_html2text()
-    {
-        $data = array(
+        return array(
             0 => array(
                 'title' => 'Test entry',
                 'in'    => '',
@@ -48,14 +42,18 @@
                 'out'   => 'Ś',
             ),
         );
-
-        $ht = new html2text(null, false, false);
-
-        foreach ($data as $idx => $item) {
-            $ht->set_html($item['in']);
-            $res = $ht->get_text();
-            $this->assertEqual($item['out'], $res, $item['title'] . "($idx)");
-        }
     }
 
+    /**
+     * @dataProvider data
+     */
+    function test_html2text($title, $in, $out)
+    {
+        $ht = new html2text(null, false, false);
+
+        $ht->set_html($in);
+        $res = $ht->get_text();
+
+        $this->assertEquals($out, $res, $title);
+    }
 }
diff --git a/tests/MailDecode.php b/tests/MailDecode.php
new file mode 100644
index 0000000..7969603
--- /dev/null
+++ b/tests/MailDecode.php
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * Test class to test messages decoding functions
+ *
+ * @package Tests
+ */
+class MailDecode extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Test decoding of single e-mail address strings
+     * Uses rcube_mime::decode_address_list()
+     */
+    function test_decode_single_address()
+    {
+        $headers = array(
+            0  => 'test@domain.tld',
+            1  => '<test@domain.tld>',
+            2  => 'Test <test@domain.tld>',
+            3  => 'Test Test <test@domain.tld>',
+            4  => 'Test Test<test@domain.tld>',
+            5  => '"Test Test" <test@domain.tld>',
+            6  => '"Test Test"<test@domain.tld>',
+            7  => '"Test \\" Test" <test@domain.tld>',
+            8  => '"Test<Test" <test@domain.tld>',
+            9  => '=?ISO-8859-1?B?VGVzdAo=?= <test@domain.tld>',
+            10 => '=?ISO-8859-1?B?VGVzdAo=?=<test@domain.tld>', // #1487068
+            // comments in address (#1487673)
+            11 => 'Test (comment) <test@domain.tld>',
+            12 => '"Test" (comment) <test@domain.tld>',
+            13 => '"Test (comment)" (comment) <test@domain.tld>',
+            14 => '(comment) <test@domain.tld>',
+            15 => 'Test <test@(comment)domain.tld>',
+            16 => 'Test Test ((comment)) <test@domain.tld>',
+            17 => 'test@domain.tld (comment)',
+            18 => '"Test,Test" <test@domain.tld>',
+            // 1487939
+            19 => 'Test <"test test"@domain.tld>',
+            20 => '<"test test"@domain.tld>',
+            21 => '"test test"@domain.tld',
+        );
+
+        $results = array(
+            0  => array(1, '', 'test@domain.tld'),
+            1  => array(1, '', 'test@domain.tld'),
+            2  => array(1, 'Test', 'test@domain.tld'),
+            3  => array(1, 'Test Test', 'test@domain.tld'),
+            4  => array(1, 'Test Test', 'test@domain.tld'),
+            5  => array(1, 'Test Test', 'test@domain.tld'),
+            6  => array(1, 'Test Test', 'test@domain.tld'),
+            7  => array(1, 'Test " Test', 'test@domain.tld'),
+            8  => array(1, 'Test<Test', 'test@domain.tld'),
+            9  => array(1, 'Test', 'test@domain.tld'),
+            10 => array(1, 'Test', 'test@domain.tld'),
+            11 => array(1, 'Test', 'test@domain.tld'),
+            12 => array(1, 'Test', 'test@domain.tld'),
+            13 => array(1, 'Test (comment)', 'test@domain.tld'),
+            14 => array(1, '', 'test@domain.tld'),
+            15 => array(1, 'Test', 'test@domain.tld'),
+            16 => array(1, 'Test Test', 'test@domain.tld'),
+            17 => array(1, '', 'test@domain.tld'),
+            18 => array(1, 'Test,Test', 'test@domain.tld'),
+            19 => array(1, 'Test', '"test test"@domain.tld'),
+            20 => array(1, '', '"test test"@domain.tld'),
+            21 => array(1, '', '"test test"@domain.tld'),
+        );
+
+        foreach ($headers as $idx => $header) {
+            $res = rcube_mime::decode_address_list($header);
+
+            $this->assertEquals($results[$idx][0], count($res), "Rows number in result for header: " . $header);
+            $this->assertEquals($results[$idx][1], $res[1]['name'], "Name part decoding for header: " . $header);
+            $this->assertEquals($results[$idx][2], $res[1]['mailto'], "Email part decoding for header: " . $header);
+        }
+    }
+
+    /**
+     * Test decoding of header values
+     * Uses rcube_mime::decode_mime_string()
+     */
+    function test_header_decode_qp()
+    {
+        $test = array(
+            // #1488232: invalid character "?"
+            'quoted-printable (1)' => array(
+                'in'  => '=?utf-8?Q?Certifica=C3=A7=C3=A3??=',
+                'out' => 'Certifica=C3=A7=C3=A3?',
+            ),
+            'quoted-printable (2)' => array(
+                'in'  => '=?utf-8?Q?Certifica=?= =?utf-8?Q?C3=A7=C3=A3?=',
+                'out' => 'Certifica=C3=A7=C3=A3',
+            ),
+            'quoted-printable (3)' => array(
+                'in'  => '=?utf-8?Q??= =?utf-8?Q??=',
+                'out' => '',
+            ),
+            'quoted-printable (4)' => array(
+                'in'  => '=?utf-8?Q??= a =?utf-8?Q??=',
+                'out' => ' a ',
+            ),
+            'quoted-printable (5)' => array(
+                'in'  => '=?utf-8?Q?a?= =?utf-8?Q?b?=',
+                'out' => 'ab',
+            ),
+            'quoted-printable (6)' => array(
+                'in'  => '=?utf-8?Q?   ?= =?utf-8?Q?a?=',
+                'out' => '   a',
+            ),
+            'quoted-printable (7)' => array(
+                'in'  => '=?utf-8?Q?___?= =?utf-8?Q?a?=',
+                'out' => '   a',
+            ),
+        );
+
+        foreach ($test as $idx => $item) {
+            $res = rcube_mime::decode_mime_string($item['in'], 'UTF-8');
+            $res = quoted_printable_encode($res);
+
+            $this->assertEquals($item['out'], $res, "Header decoding for: " . $idx);
+        }
+    }
+}
diff --git a/tests/MailFunc.php b/tests/MailFunc.php
new file mode 100644
index 0000000..57a6b9d
--- /dev/null
+++ b/tests/MailFunc.php
@@ -0,0 +1,172 @@
+<?php
+
+/**
+ * Test class to test steps/mail/func.inc functions
+ *
+ * @package Tests
+ */
+class MailFunc extends PHPUnit_Framework_TestCase
+{
+
+    function __construct()
+    {
+        // simulate environment to successfully include func.inc
+        $GLOBALS['RCMAIL'] = $RCMAIL = rcmail::get_instance();
+        $GLOBALS['OUTPUT'] = $OUTPUT = $RCMAIL->load_gui();
+        $RCMAIL->action = 'autocomplete';
+        $RCMAIL->storage_init(false);
+
+        require_once INSTALL_PATH . 'program/steps/mail/func.inc';
+
+        $GLOBALS['EMAIL_ADDRESS_PATTERN'] = $EMAIL_ADDRESS_PATTERN;
+    }
+
+    /**
+     * Helper method to create a HTML message part object
+     */
+    function get_html_part($body)
+    {
+        $part = new rcube_message_part;
+        $part->ctype_primary = 'text';
+        $part->ctype_secondary = 'html';
+        $part->body = file_get_contents(TESTS_DIR . $body);
+        $part->replaces = array();
+        return $part;
+    }
+
+
+    /**
+     * Test sanitization of a "normal" html message
+     */
+    function test_html()
+    {
+        $part = $this->get_html_part('src/htmlbody.txt');
+        $part->replaces = array('ex1.jpg' => 'part_1.2.jpg', 'ex2.jpg' => 'part_1.2.jpg');
+
+        // render HTML in normal mode
+        $html = rcmail_html4inline(rcmail_print_body($part, array('safe' => false)), 'foo');
+
+        $this->assertRegExp('/src="'.$part->replaces['ex1.jpg'].'"/', $html, "Replace reference to inline image");
+        $this->assertRegExp('#background="./program/resources/blocked.gif"#', $html, "Replace external background image");
+        $this->assertNotRegExp('/ex3.jpg/', $html, "No references to external images");
+        $this->assertNotRegExp('/<meta [^>]+>/', $html, "No meta tags allowed");
+        //$this->assertNoPattern('/<style [^>]+>/', $html, "No style tags allowed");
+        $this->assertNotRegExp('/<form [^>]+>/', $html, "No form tags allowed");
+        $this->assertRegExp('/Subscription form/', $html, "Include <form> contents");
+        $this->assertRegExp('/<!-- link ignored -->/', $html, "No external links allowed");
+        $this->assertRegExp('/<a[^>]+ target="_blank">/', $html, "Set target to _blank");
+        $this->assertTrue($GLOBALS['REMOTE_OBJECTS'], "Remote object detected");
+
+        // render HTML in safe mode
+        $html2 = rcmail_html4inline(rcmail_print_body($part, array('safe' => true)), 'foo');
+
+        $this->assertRegExp('/<style [^>]+>/', $html2, "Allow styles in safe mode");
+        $this->assertRegExp('#src="http://evilsite.net/mailings/ex3.jpg"#', $html2, "Allow external images in HTML (safe mode)");
+        $this->assertRegExp("#url\('?http://evilsite.net/newsletter/image/bg/bg-64.jpg'?\)#", $html2, "Allow external images in CSS (safe mode)");
+        $css = '<link rel="stylesheet" .+_u=tmp-[a-z0-9]+\.css.+_action=modcss';
+        $this->assertRegExp('#'.$css.'#Ui', $html2, "Filter (anonymized) external styleseehts with utils/modcss.inc");
+    }
+
+    /**
+     * Test the elimination of some trivial XSS vulnerabilities
+     */
+    function test_html_xss()
+    {
+        $part = $this->get_html_part('src/htmlxss.txt');
+        $washed = rcmail_print_body($part, array('safe' => true));
+
+        $this->assertNotRegExp('/src="skins/', $washed, "Remove local references");
+        $this->assertNotRegExp('/\son[a-z]+/', $washed, "Remove on* attributes");
+
+        $html = rcmail_html4inline($washed, 'foo');
+        $this->assertNotRegExp('/onclick="return rcmail.command(\'compose\',\'xss@somehost.net\',this)"/', $html, "Clean mailto links");
+        $this->assertNotRegExp('/alert/', $html, "Remove alerts");
+    }
+
+    /**
+     * Test HTML sanitization to fix the CSS Expression Input Validation Vulnerability
+     * reported at http://www.securityfocus.com/bid/26800/
+     */
+    function test_html_xss2()
+    {
+        $part = $this->get_html_part('src/BID-26800.txt');
+        $washed = rcmail_html4inline(rcmail_print_body($part, array('safe' => true)), 'dabody', '', $attr, true);
+
+        $this->assertNotRegExp('/alert|expression|javascript|xss/', $washed, "Remove evil style blocks");
+        $this->assertNotRegExp('/font-style:italic/', $washed, "Allow valid styles");
+    }
+
+    /**
+     * Test washtml class on non-unicode characters (#1487813)
+     */
+    function test_washtml_utf8()
+    {
+        $part = $this->get_html_part('src/invalidchars.html');
+        $washed = rcmail_print_body($part);
+
+        $this->assertRegExp('/<p>символ<\/p>/', $washed, "Remove non-unicode characters from HTML message body");
+    }
+
+    /**
+     * Test links pattern replacements in plaintext messages
+     */
+    function test_plaintext()
+    {
+        $part = new rcube_message_part;
+        $part->ctype_primary = 'text';
+        $part->ctype_secondary = 'plain';
+        $part->body = quoted_printable_decode(file_get_contents(TESTS_DIR . 'src/plainbody.txt'));
+        $html = rcmail_print_body($part, array('safe' => true));
+
+        $this->assertRegExp('/<a href="mailto:nobody@roundcube.net" onclick="return rcmail.command\(\'compose\',\'nobody@roundcube.net\',this\)">nobody@roundcube.net<\/a>/', $html, "Mailto links with onclick");
+        $this->assertRegExp('#<a href="http://www.apple.com/legal/privacy" target="_blank">http://www.apple.com/legal/privacy</a>#', $html, "Links with target=_blank");
+        $this->assertRegExp('#\\[<a href="http://example.com/\\?tx\\[a\\]=5" target="_blank">http://example.com/\\?tx\\[a\\]=5</a>\\]#', $html, "Links with square brackets");
+    }
+
+    /**
+     * Test mailto links in html messages
+     */
+    function test_mailto()
+    {
+        $part = $this->get_html_part('src/mailto.txt');
+
+        // render HTML in normal mode
+        $html = rcmail_html4inline(rcmail_print_body($part, array('safe' => false)), 'foo');
+
+        $mailto = '<a href="mailto:me@me.com?subject=this is the subject&amp;body=this is the body"'
+            .' onclick="return rcmail.command(\'compose\',\'me@me.com?subject=this is the subject&amp;body=this is the body\',this)">e-mail</a>';
+
+        $this->assertRegExp('|'.preg_quote($mailto, '|').'|', $html, "Extended mailto links");
+    }
+
+    /**
+     * Test the elimination of HTML comments
+     */
+    function test_html_comments()
+    {
+        $part = $this->get_html_part('src/htmlcom.txt');
+        $washed = rcmail_print_body($part, array('safe' => true));
+
+        // #1487759
+        $this->assertRegExp('|<p>test1</p>|', $washed, "Buggy HTML comments");
+        // but conditional comments (<!--[if ...) should be removed
+        $this->assertNotRegExp('|<p>test2</p>|', $washed, "Conditional HTML comments");
+    }
+
+    /**
+     * Test URI base resolving in HTML messages
+     */
+    function test_resolve_base()
+    {
+        $html = file_get_contents(TESTS_DIR . 'src/htmlbase.txt');
+        $html = rcmail_resolve_base($html);
+
+        $this->assertRegExp('|src="http://alec\.pl/dir/img1\.gif"|', $html, "URI base resolving [1]");
+        $this->assertRegExp('|src="http://alec\.pl/dir/img2\.gif"|', $html, "URI base resolving [2]");
+        $this->assertRegExp('|src="http://alec\.pl/img3\.gif"|', $html, "URI base resolving [3]");
+
+        // base resolving exceptions
+        $this->assertRegExp('|src="cid:theCID"|', $html, "URI base resolving exception [1]");
+        $this->assertRegExp('|src="http://other\.domain\.tld/img3\.gif"|', $html, "URI base resolving exception [2]");
+    }
+}
diff --git a/tests/ModCss.php b/tests/ModCss.php
new file mode 100644
index 0000000..38cf84c
--- /dev/null
+++ b/tests/ModCss.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * Test class to test rcmail_mod_css_styles and XSS vulnerabilites
+ *
+ * @package Tests
+ */
+class ModCss extends PHPUnit_Framework_TestCase
+{
+
+    function test_modcss()
+    {
+        $css = file_get_contents(TESTS_DIR . 'src/valid.css');
+        $mod = rcmail_mod_css_styles($css, 'rcmbody');
+
+        $this->assertRegExp('/#rcmbody\s+\{/', $mod, "Replace body style definition");
+        $this->assertRegExp('/#rcmbody h1\s\{/', $mod, "Prefix tag styles (single)");
+        $this->assertRegExp('/#rcmbody h1, #rcmbody h2, #rcmbody h3, #rcmbody textarea\s+\{/', $mod, "Prefix tag styles (multiple)");
+        $this->assertRegExp('/#rcmbody \.noscript\s+\{/', $mod, "Prefix class styles");
+    }
+
+    function test_xss()
+    {
+        $mod = rcmail_mod_css_styles("body.main2cols { background-image: url('../images/leftcol.png'); }", 'rcmbody');
+        $this->assertEquals("/* evil! */", $mod, "No url() values allowed");
+
+        $mod = rcmail_mod_css_styles("@import url('http://localhost/somestuff/css/master.css');", 'rcmbody');
+        $this->assertEquals("/* evil! */", $mod, "No import statements");
+
+        $mod = rcmail_mod_css_styles("left:expression(document.body.offsetWidth-20)", 'rcmbody');
+        $this->assertEquals("/* evil! */", $mod, "No expression properties");
+
+        $mod = rcmail_mod_css_styles("left:exp/*  */ression( alert(&#039;xss3&#039;) )", 'rcmbody');
+        $this->assertEquals("/* evil! */", $mod, "Don't allow encoding quirks");
+
+        $mod = rcmail_mod_css_styles("background:\\0075\\0072\\006c( javascript:alert(&#039;xss&#039;) )", 'rcmbody');
+        $this->assertEquals("/* evil! */", $mod, "Don't allow encoding quirks (2)");
+    }
+}
diff --git a/tests/VCards.php b/tests/VCards.php
new file mode 100644
index 0000000..e61dca9
--- /dev/null
+++ b/tests/VCards.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * Unit tests for class rcube_vcard
+ *
+ * @package Tests
+ */
+class VCards extends PHPUnit_Framework_TestCase
+{
+
+    function _srcpath($fn)
+    {
+        return realpath(dirname(__FILE__) . '/src/' . $fn);
+    }
+
+    function test_parse_one()
+    {
+        $vcard = new rcube_vcard(file_get_contents($this->_srcpath('apple.vcf')));
+
+        $this->assertTrue($vcard->business, "Identify as business record");
+        $this->assertEquals("Apple Computer AG", $vcard->displayname, "FN => displayname");
+        $this->assertEquals("", $vcard->firstname, "No person name set");
+    }
+
+    function test_parse_two()
+    {
+        $vcard = new rcube_vcard(file_get_contents($this->_srcpath('johndoe.vcf')), null);
+
+        $this->assertFalse($vcard->business, "Identify as private record");
+        $this->assertEquals("John Doë", $vcard->displayname, "Decode according to charset attribute");
+        $this->assertEquals("roundcube.net", $vcard->organization, "Test organization field");
+        $this->assertCount(2, $vcard->email, "List two e-mail addresses");
+        $this->assertEquals("roundcube@gmail.com", $vcard->email[0], "Use PREF e-mail as primary");
+    }
+
+    function test_import()
+    {
+        $input = file_get_contents($this->_srcpath('apple.vcf'));
+        $input .= file_get_contents($this->_srcpath('johndoe.vcf'));
+
+        $vcards = rcube_vcard::import($input);
+
+        $this->assertCount(2, $vcards, "Detected 2 vcards");
+        $this->assertEquals("Apple Computer AG", $vcards[0]->displayname, "FN => displayname");
+        $this->assertEquals("John Doë", $vcards[1]->displayname, "Displayname with correct charset");
+
+        // http://trac.roundcube.net/ticket/1485542
+        $vcards2 = rcube_vcard::import(file_get_contents($this->_srcpath('thebat.vcf')));
+        $this->assertEquals("Iksiñski", $vcards2[0]->surname, "Detect charset in encoded values");
+    }
+
+    function test_encodings()
+    {
+        $input = file_get_contents($this->_srcpath('utf-16_sample.vcf'));
+
+        $vcards = rcube_vcard::import($input);
+        $this->assertEquals("Ǽgean ĽdaMonté", $vcards[0]->displayname, "Decoded from UTF-16");
+    }
+}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 0000000..b321125
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | tests/bootstrap.php                                                   |
+ |                                                                       |
+ | This file is part of the Roundcube Webmail client                     |
+ | Copyright (C) 2009-2012, The Roundcube Dev Team                       |
+ |                                                                       |
+ | Licensed under the GNU General Public License version 3 or            |
+ | any later version with exceptions for skins & plugins.                |
+ | See the README file for a full license statement.                     |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Environment initialization script for unit tests                    |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ | Author: Aleksander Machniak <alec@alec.pl>                            |
+ +-----------------------------------------------------------------------+
+*/
+
+if (php_sapi_name() != 'cli')
+  die("Not in shell mode (php-cli)");
+
+if (!defined('INSTALL_PATH')) define('INSTALL_PATH', realpath(dirname(__FILE__) . '/..') . '/' );
+
+define('TESTS_DIR', dirname(__FILE__) . '/');
+
+if (@is_dir(TESTS_DIR . 'config')) {
+    define('RCMAIL_CONFIG_DIR', TESTS_DIR . 'config');
+}
+
+require_once(INSTALL_PATH . 'program/include/iniset.php');
diff --git a/tests/maildecode.php b/tests/maildecode.php
deleted file mode 100644
index 4ac4993..0000000
--- a/tests/maildecode.php
+++ /dev/null
@@ -1,130 +0,0 @@
-<?php
-
-/**
- * Test class to test messages decoding functions
- *
- * @package Tests
- */
-class rcube_test_maildecode extends UnitTestCase
-{
-  private $app;
-
-  function __construct()
-  {
-    $this->UnitTestCase('Mail headers decoding tests');
-  }
-
-  /**
-   * Test decoding of single e-mail address strings
-   * Uses rcube_mime::decode_address_list()
-   */
-  function test_decode_single_address()
-  {
-    $headers = array(
-        0  => 'test@domain.tld',
-        1  => '<test@domain.tld>',
-        2  => 'Test <test@domain.tld>',
-        3  => 'Test Test <test@domain.tld>',
-        4  => 'Test Test<test@domain.tld>',
-        5  => '"Test Test" <test@domain.tld>',
-        6  => '"Test Test"<test@domain.tld>',
-        7  => '"Test \\" Test" <test@domain.tld>',
-        8  => '"Test<Test" <test@domain.tld>',
-        9  => '=?ISO-8859-1?B?VGVzdAo=?= <test@domain.tld>',
-        10 => '=?ISO-8859-1?B?VGVzdAo=?=<test@domain.tld>', // #1487068
-        // comments in address (#1487673)
-        11 => 'Test (comment) <test@domain.tld>',
-        12 => '"Test" (comment) <test@domain.tld>',
-        13 => '"Test (comment)" (comment) <test@domain.tld>',
-        14 => '(comment) <test@domain.tld>',
-        15 => 'Test <test@(comment)domain.tld>',
-        16 => 'Test Test ((comment)) <test@domain.tld>',
-        17 => 'test@domain.tld (comment)',
-        18 => '"Test,Test" <test@domain.tld>',
-        // 1487939
-        19 => 'Test <"test test"@domain.tld>',
-        20 => '<"test test"@domain.tld>',
-        21 => '"test test"@domain.tld',
-    );
-
-    $results = array(
-        0  => array(1, '', 'test@domain.tld'),
-        1  => array(1, '', 'test@domain.tld'),
-        2  => array(1, 'Test', 'test@domain.tld'),
-        3  => array(1, 'Test Test', 'test@domain.tld'),
-        4  => array(1, 'Test Test', 'test@domain.tld'),
-        5  => array(1, 'Test Test', 'test@domain.tld'),
-        6  => array(1, 'Test Test', 'test@domain.tld'),
-        7  => array(1, 'Test " Test', 'test@domain.tld'),
-        8  => array(1, 'Test<Test', 'test@domain.tld'),
-        9  => array(1, 'Test', 'test@domain.tld'),
-        10 => array(1, 'Test', 'test@domain.tld'),
-        11 => array(1, 'Test', 'test@domain.tld'),
-        12 => array(1, 'Test', 'test@domain.tld'),
-        13 => array(1, 'Test (comment)', 'test@domain.tld'),
-        14 => array(1, '', 'test@domain.tld'),
-        15 => array(1, 'Test', 'test@domain.tld'),
-        16 => array(1, 'Test Test', 'test@domain.tld'),
-        17 => array(1, '', 'test@domain.tld'),
-        18 => array(1, 'Test,Test', 'test@domain.tld'),
-        19 => array(1, 'Test', '"test test"@domain.tld'),
-        20 => array(1, '', '"test test"@domain.tld'),
-        21 => array(1, '', '"test test"@domain.tld'),
-    );
-
-    foreach ($headers as $idx => $header) {
-      $res = rcube_mime::decode_address_list($header);
-
-      $this->assertEqual($results[$idx][0], count($res), "Rows number in result for header: " . $header);
-      $this->assertEqual($results[$idx][1], $res[1]['name'], "Name part decoding for header: " . $header);
-      $this->assertEqual($results[$idx][2], $res[1]['mailto'], "Email part decoding for header: " . $header);
-    }
-  }
-
-  /**
-   * Test decoding of header values
-   * Uses rcube_mime::decode_mime_string()
-   */
-  function test_header_decode_qp()
-  {
-    $test = array(
-      // #1488232: invalid character "?"
-      'quoted-printable (1)' => array(
-        'in'  => '=?utf-8?Q?Certifica=C3=A7=C3=A3??=',
-        'out' => 'Certifica=C3=A7=C3=A3?',
-      ),
-      'quoted-printable (2)' => array(
-        'in'  => '=?utf-8?Q?Certifica=?= =?utf-8?Q?C3=A7=C3=A3?=',
-        'out' => 'Certifica=C3=A7=C3=A3',
-      ),
-      'quoted-printable (3)' => array(
-        'in'  => '=?utf-8?Q??= =?utf-8?Q??=',
-        'out' => '',
-      ),
-      'quoted-printable (4)' => array(
-        'in'  => '=?utf-8?Q??= a =?utf-8?Q??=',
-        'out' => ' a ',
-      ),
-      'quoted-printable (5)' => array(
-        'in'  => '=?utf-8?Q?a?= =?utf-8?Q?b?=',
-        'out' => 'ab',
-      ),
-      'quoted-printable (6)' => array(
-        'in'  => '=?utf-8?Q?   ?= =?utf-8?Q?a?=',
-        'out' => '   a',
-      ),
-      'quoted-printable (7)' => array(
-        'in'  => '=?utf-8?Q?___?= =?utf-8?Q?a?=',
-        'out' => '   a',
-      ),
-    );
-
-    foreach ($test as $idx => $item) {
-      $res = rcube_mime::decode_mime_string($item['in'], 'UTF-8');
-      $res = quoted_printable_encode($res);
-
-      $this->assertEqual($item['out'], $res, "Header decoding for: " . $idx);
-    }
-
-  }
-}
diff --git a/tests/mailfunc.php b/tests/mailfunc.php
deleted file mode 100644
index 493ce94..0000000
--- a/tests/mailfunc.php
+++ /dev/null
@@ -1,173 +0,0 @@
-<?php
-
-/**
- * Test class to test steps/mail/func.inc functions
- *
- * @package Tests
- */
-class rcube_test_mailfunc extends UnitTestCase
-{
-
-  function __construct()
-  {
-    $this->UnitTestCase('Mail body rendering tests');
-    
-    // simulate environment to successfully include func.inc
-    $GLOBALS['RCMAIL'] = $RCMAIL = rcmail::get_instance();
-    $GLOBALS['OUTPUT'] = $OUTPUT = $RCMAIL->load_gui();
-    $RCMAIL->action = 'autocomplete';
-    $RCMAIL->storage_init(false);
-    
-    require_once INSTALL_PATH . 'program/steps/mail/func.inc';
-    
-    $GLOBALS['EMAIL_ADDRESS_PATTERN'] = $EMAIL_ADDRESS_PATTERN;
-  }
-
-  /**
-   * Helper method to create a HTML message part object
-   */
-  function get_html_part($body)
-  {
-    $part = new rcube_message_part;
-    $part->ctype_primary = 'text';
-    $part->ctype_secondary = 'html';
-    $part->body = file_get_contents(TESTS_DIR . $body);
-    $part->replaces = array();
-    return $part;
-  }
-
-  /**
-   * Test sanitization of a "normal" html message
-   */
-  function test_html()
-  {
-    $part = $this->get_html_part('src/htmlbody.txt');
-    $part->replaces = array('ex1.jpg' => 'part_1.2.jpg', 'ex2.jpg' => 'part_1.2.jpg');
-    
-    // render HTML in normal mode
-    $html = rcmail_html4inline(rcmail_print_body($part, array('safe' => false)), 'foo');
-
-    $this->assertPattern('/src="'.$part->replaces['ex1.jpg'].'"/', $html, "Replace reference to inline image");
-    $this->assertPattern('#background="./program/resources/blocked.gif"#', $html, "Replace external background image");
-    $this->assertNoPattern('/ex3.jpg/', $html, "No references to external images");
-    $this->assertNoPattern('/<meta [^>]+>/', $html, "No meta tags allowed");
-    //$this->assertNoPattern('/<style [^>]+>/', $html, "No style tags allowed");
-    $this->assertNoPattern('/<form [^>]+>/', $html, "No form tags allowed");
-    $this->assertPattern('/Subscription form/', $html, "Include <form> contents");
-    $this->assertPattern('/<!-- link ignored -->/', $html, "No external links allowed");
-    $this->assertPattern('/<a[^>]+ target="_blank">/', $html, "Set target to _blank");
-    $this->assertTrue($GLOBALS['REMOTE_OBJECTS'], "Remote object detected");
-    
-    // render HTML in safe mode
-    $html2 = rcmail_html4inline(rcmail_print_body($part, array('safe' => true)), 'foo');
-    
-    $this->assertPattern('/<style [^>]+>/', $html2, "Allow styles in safe mode");
-    $this->assertPattern('#src="http://evilsite.net/mailings/ex3.jpg"#', $html2, "Allow external images in HTML (safe mode)");
-    $this->assertPattern("#url\('?http://evilsite.net/newsletter/image/bg/bg-64.jpg'?\)#", $html2, "Allow external images in CSS (safe mode)");
-    $css = '<link rel="stylesheet" .+_u=tmp-[a-z0-9]+\.css.+_action=modcss';
-    $this->assertPattern('#'.$css.'#Ui', $html2, "Filter (anonymized) external styleseehts with utils/modcss.inc");
-  }
-
-  /**
-   * Test the elimination of some trivial XSS vulnerabilities
-   */
-  function test_html_xss()
-  {
-    $part = $this->get_html_part('src/htmlxss.txt');
-    $washed = rcmail_print_body($part, array('safe' => true));
-    
-    $this->assertNoPattern('/src="skins/', $washed, "Remove local references");
-    $this->assertNoPattern('/\son[a-z]+/', $washed, "Remove on* attributes");
-    
-    $html = rcmail_html4inline($washed, 'foo');
-    $this->assertNoPattern('/onclick="return rcmail.command(\'compose\',\'xss@somehost.net\',this)"/', $html, "Clean mailto links");
-    $this->assertNoPattern('/alert/', $html, "Remove alerts");
-  }
-
-  /**
-   * Test HTML sanitization to fix the CSS Expression Input Validation Vulnerability
-   * reported at http://www.securityfocus.com/bid/26800/
-   */
-  function test_html_xss2()
-  {
-    $part = $this->get_html_part('src/BID-26800.txt');
-    $washed = rcmail_html4inline(rcmail_print_body($part, array('safe' => true)), 'dabody', '', $attr, true);
-
-    $this->assertNoPattern('/alert|expression|javascript|xss/', $washed, "Remove evil style blocks");
-    $this->assertNoPattern('/font-style:italic/', $washed, "Allow valid styles");
-  }
-
-  /**
-   * Test washtml class on non-unicode characters (#1487813)
-   */
-  function test_washtml_utf8()
-  {
-    $part = $this->get_html_part('src/invalidchars.html');
-    $washed = rcmail_print_body($part);
-
-    $this->assertPattern('/<p>символ<\/p>/', $washed, "Remove non-unicode characters from HTML message body");
-  }
-
-  /**
-   * Test links pattern replacements in plaintext messages
-   */
-  function test_plaintext()
-  {
-    $part = new rcube_message_part;
-    $part->ctype_primary = 'text';
-    $part->ctype_secondary = 'plain';
-    $part->body = quoted_printable_decode(file_get_contents(TESTS_DIR . 'src/plainbody.txt'));
-    $html = rcmail_print_body($part, array('safe' => true));
-
-    $this->assertPattern('/<a href="mailto:nobody@roundcube.net" onclick="return rcmail.command\(\'compose\',\'nobody@roundcube.net\',this\)">nobody@roundcube.net<\/a>/', $html, "Mailto links with onclick");
-    $this->assertPattern('#<a href="http://www.apple.com/legal/privacy" target="_blank">http://www.apple.com/legal/privacy</a>#', $html, "Links with target=_blank");
-    $this->assertPattern('#\\[<a href="http://example.com/\\?tx\\[a\\]=5" target="_blank">http://example.com/\\?tx\\[a\\]=5</a>\\]#', $html, "Links with square brackets");
-  }
-
-  /**
-   * Test mailto links in html messages
-   */
-  function test_mailto()
-  {
-    $part = $this->get_html_part('src/mailto.txt');
-
-    // render HTML in normal mode
-    $html = rcmail_html4inline(rcmail_print_body($part, array('safe' => false)), 'foo');
-
-    $mailto = '<a href="mailto:me@me.com?subject=this is the subject&amp;body=this is the body"'
-      .' onclick="return rcmail.command(\'compose\',\'me@me.com?subject=this is the subject&amp;body=this is the body\',this)">e-mail</a>';
-
-    $this->assertPattern('|'.preg_quote($mailto, '|').'|', $html, "Extended mailto links");
-  }
-
-  /**
-   * Test the elimination of HTML comments
-   */
-  function test_html_comments()
-  {
-    $part = $this->get_html_part('src/htmlcom.txt');
-    $washed = rcmail_print_body($part, array('safe' => true));
-
-    // #1487759
-    $this->assertPattern('|<p>test1</p>|', $washed, "Buggy HTML comments");
-    // but conditional comments (<!--[if ...) should be removed
-    $this->assertNoPattern('|<p>test2</p>|', $washed, "Conditional HTML comments");
-  }
-
-  /**
-   * Test URI base resolving in HTML messages
-   */
-  function test_resolve_base()
-  {
-    $html = file_get_contents(TESTS_DIR . 'src/htmlbase.txt');
-    $html = rcmail_resolve_base($html);
-
-    $this->assertPattern('|src="http://alec\.pl/dir/img1\.gif"|', $html, "URI base resolving [1]");
-    $this->assertPattern('|src="http://alec\.pl/dir/img2\.gif"|', $html, "URI base resolving [2]");
-    $this->assertPattern('|src="http://alec\.pl/img3\.gif"|', $html, "URI base resolving [3]");
-
-    // base resolving exceptions
-    $this->assertPattern('|src="cid:theCID"|', $html, "URI base resolving exception [1]");
-    $this->assertPattern('|src="http://other\.domain\.tld/img3\.gif"|', $html, "URI base resolving exception [2]");
-  }
-}
diff --git a/tests/modcss.php b/tests/modcss.php
deleted file mode 100644
index 945cac3..0000000
--- a/tests/modcss.php
+++ /dev/null
@@ -1,45 +0,0 @@
-<?php
-
-/**
- * Test class to test rcmail_mod_css_styles and XSS vulnerabilites
- *
- * @package Tests
- */
-class rcube_test_modcss extends UnitTestCase
-{
-
-  function __construct()
-  {
-    $this->UnitTestCase('CSS modification and vulnerability tests');
-  }
-  
-  function test_modcss()
-  {
-    $css = file_get_contents(TESTS_DIR . 'src/valid.css');
-    $mod = rcmail_mod_css_styles($css, 'rcmbody');
-
-    $this->assertPattern('/#rcmbody\s+\{/', $mod, "Replace body style definition");
-    $this->assertPattern('/#rcmbody h1\s\{/', $mod, "Prefix tag styles (single)");
-    $this->assertPattern('/#rcmbody h1, #rcmbody h2, #rcmbody h3, #rcmbody textarea\s+\{/', $mod, "Prefix tag styles (multiple)");
-    $this->assertPattern('/#rcmbody \.noscript\s+\{/', $mod, "Prefix class styles");
-  }
-  
-  function test_xss()
-  {
-    $mod = rcmail_mod_css_styles("body.main2cols { background-image: url('../images/leftcol.png'); }", 'rcmbody');
-    $this->assertEqual("/* evil! */", $mod, "No url() values allowed");
-    
-    $mod = rcmail_mod_css_styles("@import url('http://localhost/somestuff/css/master.css');", 'rcmbody');
-    $this->assertEqual("/* evil! */", $mod, "No import statements");
-    
-    $mod = rcmail_mod_css_styles("left:expression(document.body.offsetWidth-20)", 'rcmbody');
-    $this->assertEqual("/* evil! */", $mod, "No expression properties");
-    
-    $mod = rcmail_mod_css_styles("left:exp/*  */ression( alert(&#039;xss3&#039;) )", 'rcmbody');
-    $this->assertEqual("/* evil! */", $mod, "Don't allow encoding quirks");
-    
-    $mod = rcmail_mod_css_styles("background:\\0075\\0072\\006c( javascript:alert(&#039;xss&#039;) )", 'rcmbody');
-    $this->assertEqual("/* evil! */", $mod, "Don't allow encoding quirks (2)");
-  }
-  
-}
diff --git a/tests/phpunit.xml b/tests/phpunit.xml
new file mode 100644
index 0000000..4a3b883
--- /dev/null
+++ b/tests/phpunit.xml
@@ -0,0 +1,13 @@
+<phpunit backupGlobals="false"
+    bootstrap="bootstrap.php"
+    colors="true">
+    <testsuites>
+        <testsuite name="All Tests">
+            <file>HtmlToText.php</file>
+            <file>MailDecode.php</file>
+            <file>MailFunc.php</file>
+            <file>ModCss.php</file>
+            <file>VCards.php</file>
+        </testsuite>
+    </testsuites>
+</phpunit>
diff --git a/tests/runtests.sh b/tests/runtests.sh
deleted file mode 100755
index 9cfeb0a..0000000
--- a/tests/runtests.sh
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/usr/bin/env php
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | tests/runtests.sh                                                     |
- |                                                                       |
- | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2009, The Roundcube Dev Team                            |
- |                                                                       |
- | Licensed under the GNU General Public License version 3 or            |
- | any later version with exceptions for skins & plugins.                |
- | See the README file for a full license statement.                     |
- |                                                                       |
- | PURPOSE:                                                              |
- |   Run-script for unit tests based on http://simpletest.org            |
- |   All .php files in this folder will be treated as tests              |
- +-----------------------------------------------------------------------+
- | Author: Thomas Bruederli <roundcube@gmail.com>                        |
- +-----------------------------------------------------------------------+
-*/
-
-if (php_sapi_name() != 'cli')
-  die("Not in shell mode (php-cli)");
-
-if (!defined('SIMPLETEST'))   define('SIMPLETEST', '/www/simpletest/');
-if (!defined('INSTALL_PATH')) define('INSTALL_PATH', realpath(dirname(__FILE__) . '/..') . '/' );
-
-define('TESTS_DIR', dirname(__FILE__) . '/');
-define('RCMAIL_CONFIG_DIR', TESTS_DIR . 'config');
-
-require_once(SIMPLETEST . 'unit_tester.php');
-require_once(SIMPLETEST . 'reporter.php');
-require_once(INSTALL_PATH . 'program/include/iniset.php');
-
-if (count($_SERVER['argv']) > 1) {
-  $testfiles = array();
-  for ($i=1; $i < count($_SERVER['argv']); $i++)
-    $testfiles[] = realpath('./' . $_SERVER['argv'][$i]);
-}
-else {
-  $testfiles = glob(TESTS_DIR . '*.php');
-}
-
-$test = new TestSuite('Roundcube unit tests');
-$reporter = new TextReporter();
-
-foreach ($testfiles as $fn) {
-  $test->addTestFile($fn);
-}
-
-$test->run($reporter);
-
-?>
diff --git a/tests/vcards.php b/tests/vcards.php
deleted file mode 100644
index 22f7cdd..0000000
--- a/tests/vcards.php
+++ /dev/null
@@ -1,65 +0,0 @@
-<?php
-
-/**
- * Unit tests for class rcube_vcard
- *
- * @package Tests
- */
-class rcube_test_vcards extends UnitTestCase
-{
-
-  function __construct()
-  {
-    $this->UnitTestCase('Vcard encoding/decoding tests');
-  }
-  
-  function _srcpath($fn)
-  {
-    return realpath(dirname(__FILE__) . '/src/' . $fn);
-  }
-  
-  function test_parse_one()
-  {
-    $vcard = new rcube_vcard(file_get_contents($this->_srcpath('apple.vcf')));
-    
-    $this->assertEqual(true, $vcard->business, "Identify as business record");
-    $this->assertEqual("Apple Computer AG", $vcard->displayname, "FN => displayname");
-    $this->assertEqual("", $vcard->firstname, "No person name set");
-  }
-
-  function test_parse_two()
-  {
-    $vcard = new rcube_vcard(file_get_contents($this->_srcpath('johndoe.vcf')), null);
-    
-    $this->assertEqual(false, $vcard->business, "Identify as private record");
-    $this->assertEqual("John Doë", $vcard->displayname, "Decode according to charset attribute");
-    $this->assertEqual("roundcube.net", $vcard->organization, "Test organization field");
-    $this->assertEqual(2, count($vcard->email), "List two e-mail addresses");
-    $this->assertEqual("roundcube@gmail.com", $vcard->email[0], "Use PREF e-mail as primary");
-  }
-  
-  function test_import()
-  {
-    $input = file_get_contents($this->_srcpath('apple.vcf'));
-    $input .= file_get_contents($this->_srcpath('johndoe.vcf'));
-    
-    $vcards = rcube_vcard::import($input);
-
-    $this->assertEqual(2, count($vcards), "Detected 2 vcards");
-    $this->assertEqual("Apple Computer AG", $vcards[0]->displayname, "FN => displayname");
-    $this->assertEqual("John Doë", $vcards[1]->displayname, "Displayname with correct charset");
-    
-    // http://trac.roundcube.net/ticket/1485542
-    $vcards2 = rcube_vcard::import(file_get_contents($this->_srcpath('thebat.vcf')));
-    $this->assertEqual("Iksiñski", $vcards2[0]->surname, "Detect charset in encoded values");
-  }
-  
-  function test_encodings()
-  {
-      $input = file_get_contents($this->_srcpath('utf-16_sample.vcf'));
-      
-      $vcards = rcube_vcard::import($input);
-      $this->assertEqual("Ǽgean ĽdaMonté", $vcards[0]->displayname, "Decoded from UTF-16");
-  }
-  
-}

--
Gitblit v1.9.1