summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Framework/BaseReplacer.php34
-rw-r--r--tests/Framework/Bootstrap.php218
-rw-r--r--tests/Framework/Browser.php253
-rw-r--r--tests/Framework/Cache.php20
-rw-r--r--tests/Framework/Charset.php180
-rw-r--r--tests/Framework/ContentFilter.php20
-rw-r--r--tests/Framework/Csv2vcard.php58
-rw-r--r--tests/Framework/Enriched.php74
-rw-r--r--tests/Framework/Html.php45
-rw-r--r--tests/Framework/Html2text.php106
-rw-r--r--tests/Framework/Image.php20
-rw-r--r--tests/Framework/Imap.php20
-rw-r--r--tests/Framework/ImapGeneric.php61
-rw-r--r--tests/Framework/MessageHeader.php20
-rw-r--r--tests/Framework/MessagePart.php20
-rw-r--r--tests/Framework/Mime.php213
-rw-r--r--tests/Framework/Rcube.php20
-rw-r--r--tests/Framework/ResultIndex.php67
-rw-r--r--tests/Framework/ResultSet.php20
-rw-r--r--tests/Framework/ResultThread.php59
-rw-r--r--tests/Framework/Smtp.php20
-rw-r--r--tests/Framework/Spellchecker.php20
-rw-r--r--tests/Framework/StringReplacer.php75
-rw-r--r--tests/Framework/User.php20
-rw-r--r--tests/Framework/Utils.php337
-rw-r--r--tests/Framework/VCard.php119
-rw-r--r--tests/Framework/Washtml.php127
-rw-r--r--tests/MailFunc.php268
-rw-r--r--tests/Selenium/Addressbook/Addressbook.php21
-rw-r--r--tests/Selenium/Addressbook/Import.php29
-rw-r--r--tests/Selenium/Login.php21
-rw-r--r--tests/Selenium/Logout.php20
-rw-r--r--tests/Selenium/Mail/CheckRecent.php14
-rw-r--r--tests/Selenium/Mail/Compose.php25
-rw-r--r--tests/Selenium/Mail/Getunread.php13
-rw-r--r--tests/Selenium/Mail/List.php25
-rw-r--r--tests/Selenium/Mail/Mail.php23
-rw-r--r--tests/Selenium/Settings/About.php14
-rw-r--r--tests/Selenium/Settings/Folders.php20
-rw-r--r--tests/Selenium/Settings/Identities.php19
-rw-r--r--tests/Selenium/Settings/Settings.php17
-rw-r--r--tests/Selenium/bootstrap.php185
-rw-r--r--tests/Selenium/index.html8
-rw-r--r--tests/Selenium/phpunit.xml21
-rw-r--r--tests/bootstrap.php41
-rw-r--r--tests/phpunit.xml70
-rw-r--r--tests/src/BID-26800.txt53
-rw-r--r--tests/src/Csv2vcard/email.csv5
-rw-r--r--tests/src/Csv2vcard/email.vcf20
-rw-r--r--tests/src/Csv2vcard/tb_plain.csv2
-rw-r--r--tests/src/Csv2vcard/tb_plain.vcf20
-rw-r--r--tests/src/apple.vcf49
-rw-r--r--tests/src/format-flowed-unfolded.txt19
-rw-r--r--tests/src/format-flowed.txt21
-rw-r--r--tests/src/htmlbase.txt12
-rw-r--r--tests/src/htmlbody.txt51
-rw-r--r--tests/src/htmlcom.txt16
-rw-r--r--tests/src/htmlxss.txt22
-rw-r--r--tests/src/imap_thread.txt1
-rw-r--r--tests/src/invalidchars.html1
-rw-r--r--tests/src/johndoe.vcf12
-rw-r--r--tests/src/mailto.txt8
-rw-r--r--tests/src/media.css22
-rw-r--r--tests/src/photo.vcf45
-rw-r--r--tests/src/plainbody.txt38
-rw-r--r--tests/src/thebat.vcf8
-rwxr-xr-xtests/src/utf-16_sample.vcfbin0 -> 460 bytes
-rw-r--r--tests/src/valid.css30
68 files changed, 3555 insertions, 0 deletions
diff --git a/tests/Framework/BaseReplacer.php b/tests/Framework/BaseReplacer.php
new file mode 100644
index 000000000..44a9604ac
--- /dev/null
+++ b/tests/Framework/BaseReplacer.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * Test class to test rcube_base_replacer class
+ *
+ * @package Tests
+ */
+class Framework_BaseReplacer extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_base_replacer('test');
+
+ $this->assertInstanceOf('rcube_base_replacer', $object, "Class constructor");
+ }
+
+ /**
+ * Test replace()
+ */
+ function test_replace()
+ {
+ $base = 'http://thisshouldntbetheurl.bob.com/';
+ $html = '<A href=http://shouldbethislink.com>Test URL</A>';
+
+ $replacer = new rcube_base_replacer($base);
+ $response = $replacer->replace($html);
+
+ $this->assertSame('<A href="http://shouldbethislink.com">Test URL</A>', $response);
+ }
+}
diff --git a/tests/Framework/Bootstrap.php b/tests/Framework/Bootstrap.php
new file mode 100644
index 000000000..904be7e3b
--- /dev/null
+++ b/tests/Framework/Bootstrap.php
@@ -0,0 +1,218 @@
+<?php
+
+/**
+ * Test class to test rcube_shared functions
+ *
+ * @package Tests
+ */
+class Framework_Bootstrap extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * bootstrap.php: in_array_nocase()
+ */
+ function test_in_array_nocase()
+ {
+ $haystack = array('Test');
+ $needle = 'test';
+ $result = in_array_nocase($needle, $haystack);
+
+ $this->assertTrue($result, $title);
+
+ $result = in_array_nocase($needle, null);
+
+ $this->assertFalse($result, $title);
+ }
+
+ /**
+ * bootstrap.php: parse_bytes()
+ */
+ function test_parse_bytes()
+ {
+ $data = array(
+ '1' => 1,
+ '1024' => 1024,
+ '2k' => 2 * 1024,
+ '2 k' => 2 * 1024,
+ '2kb' => 2 * 1024,
+ '2kB' => 2 * 1024,
+ '2m' => 2 * 1048576,
+ '2 m' => 2 * 1048576,
+ '2mb' => 2 * 1048576,
+ '2mB' => 2 * 1048576,
+ '2g' => 2 * 1024 * 1048576,
+ '2 g' => 2 * 1024 * 1048576,
+ '2gb' => 2 * 1024 * 1048576,
+ '2gB' => 2 * 1024 * 1048576,
+ );
+
+ foreach ($data as $value => $expected) {
+ $result = parse_bytes($value);
+ $this->assertEquals($expected, $result, "Invalid parse_bytes() result for $value");
+ }
+ }
+
+ /**
+ * bootstrap.php: slashify()
+ */
+ function test_slashify()
+ {
+ $data = array(
+ 'test' => 'test/',
+ 'test/' => 'test/',
+ '' => '/',
+ "\\" => "\\/",
+ );
+
+ foreach ($data as $value => $expected) {
+ $result = slashify($value);
+ $this->assertEquals($expected, $result, "Invalid slashify() result for $value");
+ }
+
+ }
+
+ /**
+ * bootstrap.php: unslashify()
+ */
+ function test_unslashify()
+ {
+ $data = array(
+ 'test' => 'test',
+ 'test/' => 'test',
+ '/' => '',
+ "\\/" => "\\",
+ 'test/test' => 'test/test',
+ 'test//' => 'test',
+ );
+
+ foreach ($data as $value => $expected) {
+ $result = unslashify($value);
+ $this->assertEquals($expected, $result, "Invalid unslashify() result for $value");
+ }
+
+ }
+
+ /**
+ * bootstrap.php: get_offset_sec()
+ */
+ function test_get_offset_sec()
+ {
+ $data = array(
+ '1s' => 1,
+ '1m' => 1 * 60,
+ '1h' => 1 * 60 * 60,
+ '1d' => 1 * 60 * 60 * 24,
+ '1w' => 1 * 60 * 60 * 24 * 7,
+ '1y' => (int) '1y',
+ 100 => 100,
+ '100' => 100,
+ );
+
+ foreach ($data as $value => $expected) {
+ $result = get_offset_sec($value);
+ $this->assertEquals($expected, $result, "Invalid get_offset_sec() result for $value");
+ }
+
+ }
+
+ /**
+ * bootstrap.php: array_keys_recursive()
+ */
+ function test_array_keys_recursive()
+ {
+ $input = array(
+ 'one' => array(
+ 'two' => array(
+ 'three' => array(),
+ 'four' => 'something',
+ ),
+ ),
+ 'five' => 'test',
+ );
+
+ $result = array_keys_recursive($input);
+ $input_str = 'one,two,three,four,five';
+ $result_str = implode(',', $result);
+
+ $this->assertEquals($input_str, $result_str, "Invalid array_keys_recursive() result");
+ }
+
+ /**
+ * bootstrap.php: format_email()
+ */
+ function test_format_email()
+ {
+ $data = array(
+ '' => '',
+ 'test' => 'test',
+ 'test@test.tld' => 'test@test.tld',
+ 'test@[127.0.0.1]' => 'test@[127.0.0.1]',
+ 'TEST@TEST.TLD' => 'TEST@test.tld',
+ );
+
+ foreach ($data as $value => $expected) {
+ $result = format_email($value);
+ $this->assertEquals($expected, $result, "Invalid format_email() result for $value");
+ }
+
+ }
+
+ /**
+ * bootstrap.php: format_email_recipient()
+ */
+ function test_format_email_recipient()
+ {
+ $data = array(
+ '' => array(''),
+ 'test' => array('test'),
+ 'test@test.tld' => array('test@test.tld'),
+ 'test@[127.0.0.1]' => array('test@[127.0.0.1]'),
+ 'TEST@TEST.TLD' => array('TEST@TEST.TLD'),
+ 'TEST <test@test.tld>' => array('test@test.tld', 'TEST'),
+ '"TEST\"" <test@test.tld>' => array('test@test.tld', 'TEST"'),
+ );
+
+ foreach ($data as $expected => $value) {
+ $result = format_email_recipient($value[0], $value[1]);
+ $this->assertEquals($expected, $result, "Invalid format_email_recipient()");
+ }
+
+ }
+
+ /**
+ * bootstrap.php: is_ascii()
+ */
+ function test_is_ascii()
+ {
+ $result = is_ascii("0123456789");
+ $this->assertTrue($result, "Valid ASCII (numbers)");
+
+ $result = is_ascii("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
+ $this->assertTrue($result, "Valid ASCII (letters)");
+
+ $result = is_ascii(" !\"#\$%&'()*+,-./:;<=>?@[\\^_`{|}~");
+ $this->assertTrue($result, "Valid ASCII (special characters)");
+
+ $result = is_ascii("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"
+ ."\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F");
+ $this->assertTrue($result, "Valid ASCII (control characters)");
+
+ $result = is_ascii("\n", false);
+ $this->assertFalse($result, "Valid ASCII (control characters)");
+
+ $result = is_ascii("ż");
+ $this->assertFalse($result, "Invalid ASCII (UTF-8 character)");
+
+ $result = is_ascii("ż", false);
+ $this->assertFalse($result, "Invalid ASCII (UTF-8 character [2])");
+ }
+
+ /**
+ * bootstrap.php: version_parse()
+ */
+ function test_version_parse()
+ {
+ $this->assertEquals('0.9.0', version_parse('0.9-stable'));
+ $this->assertEquals('0.9.99', version_parse('0.9-git'));
+ }
+}
diff --git a/tests/Framework/Browser.php b/tests/Framework/Browser.php
new file mode 100644
index 000000000..6afd2144c
--- /dev/null
+++ b/tests/Framework/Browser.php
@@ -0,0 +1,253 @@
+<?php
+
+/**
+ * Test class to test rcube_browser class
+ *
+ * @package Tests
+ */
+class Framework_Browser extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_browser();
+
+ $this->assertInstanceOf('rcube_browser', $object, "Class constructor");
+ }
+
+ /**
+ * @dataProvider browsers
+ */
+ function test_browser($useragent, $opera, $chrome, $ie, $ns, $safari, $mz)
+ {
+
+ $object = $this->getBrowser($useragent);
+
+ $this->assertEquals($opera, $object->opera, 'Check for Opera failed');
+ $this->assertEquals($chrome, $object->chrome, 'Check for Chrome failed');
+ $this->assertEquals($ie, $object->ie, 'Check for IE failed');
+ $this->assertEquals($ns, $object->ns, 'Check for NS failed');
+ $this->assertEquals($safari, $object->safari, 'Check for Safari failed');
+ $this->assertEquals($mz, $object->mz, 'Check for MZ failed');
+ }
+
+ /**
+ * @dataProvider os
+ */
+ function test_os($useragent, $windows, $linux, $unix, $mac)
+ {
+ $object = $this->getBrowser($useragent);
+
+ $this->assertEquals($windows, $object->win, 'Check Result of Windows');
+ $this->assertEquals($linux, $object->linux, 'Check Result of Linux');
+ $this->assertEquals($mac, $object->mac, 'Check Result of Mac');
+ $this->assertEquals($unix, $object->unix, 'Check Result of Unix');
+
+ }
+
+ /**
+ * @dataProvider versions
+ */
+ function test_version($useragent, $version)
+ {
+ $object = $this->getBrowser($useragent);
+ $this->assertEquals($version, $object->ver);
+ }
+
+ /**
+ * @dataProvider dom
+ */
+ function test_dom($useragent, $dom)
+ {
+ $object = $this->getBrowser($useragent);
+ $this->assertEquals($dom, $object->dom);
+
+ }
+
+ /**
+ * @dataProvider pngalpha
+ */
+ function test_pngalpha($useragent, $pngalpha)
+ {
+ $object = $this->getBrowser($useragent);
+ $this->assertEquals($pngalpha, $object->pngalpha);
+ }
+
+ /**
+ * @dataProvider imgdata
+ */
+ function test_imgdata($useragent, $imgdata)
+ {
+ $object = $this->getBrowser($useragent);
+ $this->assertEquals($imgdata, $object->imgdata);
+ }
+
+ function versions()
+ {
+ return $this->extractDataSet(array('version'));
+ }
+
+ function pngalpha()
+ {
+ return $this->extractDataSet(array('canPNGALPHA'));
+ }
+
+ function imgdata()
+ {
+ return $this->extractDataSet(array('canIMGDATA'));
+ }
+
+ private function extractDataSet($keys)
+ {
+ $keys = array_merge(array('useragent'), $keys);
+
+ $browser = $this->useragents();
+
+ $extracted = array();
+
+ foreach ($browser as $label => $data) {
+ foreach($keys as $key) {
+ $extracted[$data['useragent']][] = $data[$key];
+ }
+
+ }
+
+ return $extracted;
+ }
+
+ function lang()
+ {
+ return $this->extractDataSet(array('lang'));
+ }
+
+ function dom()
+ {
+ return $this->extractDataSet(array('hasDOM'));
+ }
+
+ function browsers()
+ {
+ return $this->extractDataSet(array('isOpera','isChrome','isIE','isNS','isSafari','isMZ'));
+ }
+
+ function useragents()
+ {
+ return array(
+ 'WIN: Mozilla Firefox ' => array(
+ 'useragent' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.1) Gecko/20060111 Firefox/1.5.0.1',
+ 'version' => '1.8', //Version
+ 'isWin' => true, //isWindows
+ 'isLinux' => false,
+ 'isMac' => false, //isMac
+ 'isUnix' => false, //isUnix
+ 'isOpera' => false, //isOpera
+ 'isChrome' => false, //isChrome
+ 'isIE' => false, //isIE
+ 'isNS' => false, //isNS
+ 'isSafari' => false, //isSafari
+ 'isMZ' => true, //isMZ
+ 'lang' => 'en-US', //lang
+ 'hasDOM' => true, //hasDOM
+ 'canPNGALPHA' => true, //canPNGALPHA
+ 'canIMGDATA' => true, //canIMGDATA
+ ),
+ 'LINUX: Bon Echo ' => array(
+ 'useragent' => 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.1) Gecko/20070222 BonEcho/2.0.0.1',
+ 'version' => '1.8', //Version
+ 'isWin' => false, //isWindows
+ 'isLinux' => true,
+ 'isMac' => false, //isMac
+ 'isUnix' => false, //isUnix
+ 'isOpera' => false, //isOpera
+ 'isChrome' => false, //isChrome
+ 'isIE' => false, //isIE
+ 'isNS' => false, //isNS
+ 'isSafari' => false, //isSafari
+ 'isMZ' => true, //isMZ
+ 'lang' => 'en-US', //lang
+ 'hasDOM' => true, //hasDOM
+ 'canPNGALPHA' => true, //canPNGALPHA
+ 'canIMGDATA' => true, //canIMGDATA
+ ),
+
+ 'Chrome Mac' => array(
+ 'useragent' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.461.0 Safari/534.3',
+ 'version' => '6', //Version
+ 'isWin' => false, //isWindows
+ 'isLinux' => false,
+ 'isMac' => true, //isMac
+ 'isUnix' => false, //isUnix
+ 'isOpera' => false, //isOpera
+ 'isChrome' => true, //isChrome
+ 'isIE' => false, //isIE
+ 'isNS' => false, //isNS
+ 'isSafari' => false, //isSafari
+ 'isMZ' => false, //isMZ
+ 'lang' => 'en-US', //lang
+ 'hasDOM' => false, //hasDOM
+ 'canPNGALPHA' => false, //canPNGALPHA
+ 'canIMGDATA' => true, //canIMGDATA
+ ),
+
+ 'IE 11' => array(
+ 'useragent' => 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; .NET4.0E; .NET4.0C; rv:11.0) like Gecko',
+ 'version' => '11.0', //Version
+ 'isWin' => true, //isWindows
+ 'isLinux' => false,
+ 'isMac' => false, //isMac
+ 'isUnix' => false, //isUnix
+ 'isOpera' => false, //isOpera
+ 'isChrome' => false, //isChrome
+ 'isIE' => true, //isIE
+ 'isNS' => false, //isNS
+ 'isSafari' => false, //isSafari
+ 'isMZ' => false, //isMZ
+ 'lang' => '', //lang
+ 'hasDOM' => true, //hasDOM
+ 'canPNGALPHA' => true, //canPNGALPHA
+ 'canIMGDATA' => false, //canIMGDATA
+ ),
+
+ 'Opera 15' => array(
+ 'useragent' => 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.29 Safari/537.36 OPR/15.0.1147.24',
+ 'version' => '15.0', //Version
+ 'isWin' => true, //isWindows
+ 'isLinux' => false,
+ 'isMac' => false, //isMac
+ 'isUnix' => false, //isUnix
+ 'isOpera' => true, //isOpera
+ 'isChrome' => false, //isChrome
+ 'isIE' => false, //isIE
+ 'isNS' => false, //isNS
+ 'isSafari' => false, //isSafari
+ 'isMZ' => false, //isMZ
+ 'lang' => '', //lang
+ 'hasDOM' => true, //hasDOM
+ 'canPNGALPHA' => true, //canPNGALPHA
+ 'canIMGDATA' => true, //canIMGDATA
+ ),
+ );
+ }
+
+ function os()
+ {
+ return $this->extractDataSet(array('isWin','isLinux','isUnix','isMac'));
+ }
+
+ /**
+ * @param string $useragent
+ * @return rcube_browser
+ */
+ private function getBrowser($useragent)
+ {
+ /** @var $object rcube_browser */
+ $_SERVER['HTTP_USER_AGENT'] = $useragent;
+
+ $object = new rcube_browser();
+
+ return $object;
+ }
+}
diff --git a/tests/Framework/Cache.php b/tests/Framework/Cache.php
new file mode 100644
index 000000000..dc026a634
--- /dev/null
+++ b/tests/Framework/Cache.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_cache class
+ *
+ * @package Tests
+ */
+class Framework_Cache extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_cache('db', 1);
+
+ $this->assertInstanceOf('rcube_cache', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/Charset.php b/tests/Framework/Charset.php
new file mode 100644
index 000000000..d3d3e88dd
--- /dev/null
+++ b/tests/Framework/Charset.php
@@ -0,0 +1,180 @@
+<?php
+
+/**
+ * Test class to test rcube_charset class
+ *
+ * @package Tests
+ */
+class Framework_Charset extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Data for test_clean()
+ */
+ function data_clean()
+ {
+ return array(
+ array('', ''),
+ array("\xC1", ''),
+ );
+ }
+
+ /**
+ * @dataProvider data_clean
+ */
+ function test_clean($input, $output)
+ {
+ $this->assertEquals($output, rcube_charset::clean($input));
+ }
+
+ /**
+ * Data for test_parse_charset()
+ */
+ function data_parse_charset()
+ {
+ return array(
+ array('UTF8', 'UTF-8'),
+ array('WIN1250', 'WINDOWS-1250'),
+ );
+ }
+
+ /**
+ * @dataProvider data_parse_charset
+ */
+ function test_parse_charset($input, $output)
+ {
+ $this->assertEquals($output, rcube_charset::parse_charset($input));
+ }
+
+ /**
+ * Data for test_convert()
+ */
+ function data_convert()
+ {
+ return array(
+ array('ö', 'ö', 'UTF-8', 'UTF-8'),
+ array('ö', '', 'UTF-8', 'US-ASCII'),
+ array('aż', 'a', 'UTF-8', 'US-ASCII'),
+ array('&BCAEMARBBEEESwQ7BDoEOA-', 'Рассылки', 'UTF7-IMAP', 'UTF-8'),
+ array('Рассылки', '&BCAEMARBBEEESwQ7BDoEOA-', 'UTF-8', 'UTF7-IMAP'),
+ );
+ }
+
+ /**
+ * @dataProvider data_convert
+ */
+ function test_convert($input, $output, $from, $to)
+ {
+ $this->assertEquals($output, rcube_charset::convert($input, $from, $to));
+ }
+
+ /**
+ * Data for test_utf7_to_utf8()
+ */
+ function data_utf7_to_utf8()
+ {
+ return array(
+ array('+BCAEMARBBEEESwQ7BDoEOA-', 'Рассылки'),
+ );
+ }
+
+ /**
+ * @dataProvider data_utf7_to_utf8
+ */
+ function test_utf7_to_utf8($input, $output)
+ {
+ $this->assertEquals($output, rcube_charset::utf7_to_utf8($input));
+ }
+
+ /**
+ * Data for test_utf7imap_to_utf8()
+ */
+ function data_utf7imap_to_utf8()
+ {
+ return array(
+ array('&BCAEMARBBEEESwQ7BDoEOA-', 'Рассылки'),
+ );
+ }
+
+ /**
+ * @dataProvider data_utf7imap_to_utf8
+ */
+ function test_utf7imap_to_utf8($input, $output)
+ {
+ $this->assertEquals($output, rcube_charset::utf7imap_to_utf8($input));
+ }
+
+ /**
+ * Data for test_utf8_to_utf7imap()
+ */
+ function data_utf8_to_utf7imap()
+ {
+ return array(
+ array('Рассылки', '&BCAEMARBBEEESwQ7BDoEOA-'),
+ );
+ }
+
+ /**
+ * @dataProvider data_utf8_to_utf7imap
+ */
+ function test_utf8_to_utf7imap($input, $output)
+ {
+ $this->assertEquals($output, rcube_charset::utf8_to_utf7imap($input));
+ }
+
+ /**
+ * Data for test_utf16_to_utf8()
+ */
+ function data_utf16_to_utf8()
+ {
+ return array(
+ array(base64_decode('BCAEMARBBEEESwQ7BDoEOA=='), 'Рассылки'),
+ );
+ }
+
+ /**
+ * @dataProvider data_utf16_to_utf8
+ */
+ function test_utf16_to_utf8($input, $output)
+ {
+ $this->assertEquals($output, rcube_charset::utf16_to_utf8($input));
+ }
+
+ /**
+ * Data for test_detect()
+ */
+ function data_detect()
+ {
+ return array(
+ array('', '', 'UTF-8'),
+ array('a', 'UTF-8', 'UTF-8'),
+ );
+ }
+
+ /**
+ * @dataProvider data_detect
+ */
+ function test_detect($input, $fallback, $output)
+ {
+ $this->assertEquals($output, rcube_charset::detect($input, $fallback));
+ }
+
+ /**
+ * Data for test_detect()
+ */
+ function data_detect_with_lang()
+ {
+ return array(
+ array('ܦW,Dn', 'zh_TW', 'BIG-5'),
+ );
+ }
+
+ /**
+ * @dataProvider data_detect_with_lang
+ */
+ function test_detect_with_lang($input, $lang, $output)
+ {
+ $this->assertEquals($output, rcube_charset::detect($input, $output, $lang));
+ }
+
+}
diff --git a/tests/Framework/ContentFilter.php b/tests/Framework/ContentFilter.php
new file mode 100644
index 000000000..9bee9368b
--- /dev/null
+++ b/tests/Framework/ContentFilter.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_content_filter class
+ *
+ * @package Tests
+ */
+class Framework_ContentFilter extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_content_filter();
+
+ $this->assertInstanceOf('rcube_content_filter', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/Csv2vcard.php b/tests/Framework/Csv2vcard.php
new file mode 100644
index 000000000..5d52efc58
--- /dev/null
+++ b/tests/Framework/Csv2vcard.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * Test class to test rcube_csv2vcard class
+ *
+ * @package Tests
+ */
+class Framework_Csv2vcard extends PHPUnit_Framework_TestCase
+{
+
+ function test_import_generic()
+ {
+ $csv = new rcube_csv2vcard;
+
+ // empty input
+ $csv->import('');
+ $this->assertSame(array(), $csv->export());
+ }
+
+ function test_import_tb_plain()
+ {
+ $csv_text = file_get_contents(TESTS_DIR . '/src/Csv2vcard/tb_plain.csv');
+ $vcf_text = file_get_contents(TESTS_DIR . '/src/Csv2vcard/tb_plain.vcf');
+
+ $csv = new rcube_csv2vcard;
+ $csv->import($csv_text);
+ $result = $csv->export();
+ $vcard = $result[0]->export(false);
+
+ $this->assertCount(1, $result);
+
+ $vcf_text = trim(str_replace("\r\n", "\n", $vcf_text));
+ $vcard = trim(str_replace("\r\n", "\n", $vcard));
+
+ $this->assertEquals($vcf_text, $vcard);
+ }
+
+ function test_import_email()
+ {
+ $csv_text = file_get_contents(TESTS_DIR . '/src/Csv2vcard/email.csv');
+ $vcf_text = file_get_contents(TESTS_DIR . '/src/Csv2vcard/email.vcf');
+
+ $csv = new rcube_csv2vcard;
+ $csv->import($csv_text);
+ $result = $csv->export();
+
+ $this->assertCount(4, $result);
+
+ $vcard = '';
+ foreach ($result as $vcf) {
+ $vcard .= $vcf->export(false) . "\n";
+ }
+
+ $vcf_text = trim(str_replace("\r\n", "\n", $vcf_text));
+ $vcard = trim(str_replace("\r\n", "\n", $vcard));
+ $this->assertEquals($vcf_text, $vcard);
+ }
+}
diff --git a/tests/Framework/Enriched.php b/tests/Framework/Enriched.php
new file mode 100644
index 000000000..26bbc3b4e
--- /dev/null
+++ b/tests/Framework/Enriched.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * Test class to test rcube_enriched class
+ *
+ * @package Tests
+ */
+class Framework_Enriched extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_enriched();
+
+ $this->assertInstanceOf('rcube_enriched', $object, "Class constructor");
+ }
+
+ /**
+ * Test to_html()
+ */
+ function test_to_html()
+ {
+ $enriched = '<bold><italic>the-text</italic></bold>';
+ $expected = '<b><i>the-text</i></b>';
+ $result = rcube_enriched::to_html($enriched);
+
+ $this->assertSame($expected, $result);
+ }
+
+ /**
+ * Data for test_formatting()
+ */
+ function data_formatting()
+ {
+ return array(
+ array('<bold>', '<b>'),
+ array('</bold>', '</b>'),
+ array('<italic>', '<i>'),
+ array('</italic>', '</i>'),
+ array('<fixed>', '<tt>'),
+ array('</fixed>', '</tt>'),
+ array('<smaller>', '<font size=-1>'),
+ array('</smaller>', '</font>'),
+ array('<bigger>', '<font size=+1>'),
+ array('</bigger>', '</font>'),
+ array('<underline>', '<span style="text-decoration: underline">'),
+ array('</underline>', '</span>'),
+ array('<flushleft>', '<span style="text-align: left">'),
+ array('</flushleft>', '</span>'),
+ array('<flushright>', '<span style="text-align: right">'),
+ array('</flushright>', '</span>'),
+ array('<flushboth>', '<span style="text-align: justified">'),
+ array('</flushboth>', '</span>'),
+ array('<indent>', '<span style="padding-left: 20px">'),
+ array('</indent>', '</span>'),
+ array('<indentright>', '<span style="padding-right: 20px">'),
+ array('</indentright>', '</span>'),
+ );
+ }
+
+ /**
+ * Test formatting conversion
+ * @dataProvider data_formatting
+ */
+ function test_formatting($enriched, $expected)
+ {
+ $result = rcube_enriched::to_html($enriched);
+
+ $this->assertSame($expected, $result);
+ }
+}
diff --git a/tests/Framework/Html.php b/tests/Framework/Html.php
new file mode 100644
index 000000000..60284deef
--- /dev/null
+++ b/tests/Framework/Html.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * Test class to test rcube_html class
+ *
+ * @package Tests
+ */
+class Framework_Html extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new html;
+
+ $this->assertInstanceOf('html', $object, "Class constructor");
+ }
+
+ /**
+ * Data for test_quote()
+ */
+ function data_quote()
+ {
+ return array(
+ array('abc', 'abc'),
+ array('?', '?'),
+ array('"', '&quot;'),
+ array('<', '&lt;'),
+ array('>', '&gt;'),
+ array('&', '&amp;'),
+ array('&amp;', '&amp;amp;'),
+ );
+ }
+
+ /**
+ * Test for quote()
+ * @dataProvider data_quote
+ */
+ function test_quote($str, $result)
+ {
+ $this->assertEquals(html::quote($str), $result);
+ }
+}
diff --git a/tests/Framework/Html2text.php b/tests/Framework/Html2text.php
new file mode 100644
index 000000000..2c7759f7d
--- /dev/null
+++ b/tests/Framework/Html2text.php
@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * Test class to test rcube_html2text class
+ *
+ * @package Tests
+ */
+class rc_html2text extends PHPUnit_Framework_TestCase
+{
+
+ function data_html2text()
+ {
+ return array(
+ 0 => array(
+ 'title' => 'Test entry',
+ 'in' => '',
+ 'out' => '',
+ ),
+ 1 => array(
+ 'title' => 'Basic HTML entities',
+ 'in' => '&quot;&amp;',
+ 'out' => '"&',
+ ),
+ 2 => array(
+ 'title' => 'HTML entity string',
+ 'in' => '&amp;quot;',
+ 'out' => '&quot;',
+ ),
+ 3 => array(
+ 'title' => 'HTML entity in STRONG tag',
+ 'in' => '<strong>&#347;</strong>', // ś
+ 'out' => 'Ś', // upper ś
+ ),
+ 4 => array(
+ 'title' => 'STRONG tag to upper-case conversion',
+ 'in' => '<strong>ś</strong>',
+ 'out' => 'Ś',
+ ),
+ 5 => array(
+ 'title' => 'STRONG inside B tag',
+ 'in' => '<b><strong>&#347;</strong></b>',
+ 'out' => 'Ś',
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider data_html2text
+ */
+ function test_html2text($title, $in, $out)
+ {
+ $ht = new rcube_html2text(null, false, false);
+
+ $ht->set_html($in);
+ $res = $ht->get_text();
+
+ $this->assertEquals($out, $res, $title);
+ }
+
+ /**
+ *
+ */
+ function test_multiple_blockquotes()
+ {
+ $html = <<<EOF
+<br>Begin<br><blockquote>OUTER BEGIN<blockquote>INNER 1<br></blockquote><div><br></div><div>Par 1</div>
+<blockquote>INNER 2</blockquote><div><br></div><div>Par 2</div>
+<div><br></div><div>Par 3</div><div><br></div>
+<blockquote>INNER 3</blockquote>OUTER END</blockquote>
+EOF;
+ $ht = new rcube_html2text($html, false, false);
+ $res = $ht->get_text();
+
+ $this->assertContains('>> INNER 1', $res, 'Quote inner');
+ $this->assertContains('>> INNER 3', $res, 'Quote inner');
+ $this->assertContains('> OUTER END', $res, 'Quote outer');
+ }
+
+ function test_broken_blockquotes()
+ {
+ // no end tag
+ $html = <<<EOF
+Begin<br>
+<blockquote>QUOTED TEXT
+<blockquote>
+NO END TAG FOUND
+EOF;
+ $ht = new rcube_html2text($html, false, false);
+ $res = $ht->get_text();
+
+ $this->assertContains('QUOTED TEXT NO END TAG FOUND', $res, 'No quoating on invalid html');
+
+ // with some (nested) end tags
+ $html = <<<EOF
+Begin<br>
+<blockquote>QUOTED TEXT
+<blockquote>INNER 1</blockquote>
+<blockquote>INNER 2</blockquote>
+NO END TAG FOUND
+EOF;
+ $ht = new rcube_html2text($html, false, false);
+ $res = $ht->get_text();
+
+ $this->assertContains('QUOTED TEXT INNER 1 INNER 2 NO END', $res, 'No quoating on invalid html');
+ }
+}
diff --git a/tests/Framework/Image.php b/tests/Framework/Image.php
new file mode 100644
index 000000000..31e852042
--- /dev/null
+++ b/tests/Framework/Image.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_image class
+ *
+ * @package Tests
+ */
+class Framework_Image extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_image('test');
+
+ $this->assertInstanceOf('rcube_image', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/Imap.php b/tests/Framework/Imap.php
new file mode 100644
index 000000000..3f52e07be
--- /dev/null
+++ b/tests/Framework/Imap.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_imap class
+ *
+ * @package Tests
+ */
+class Framework_Imap extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_imap;
+
+ $this->assertInstanceOf('rcube_imap', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/ImapGeneric.php b/tests/Framework/ImapGeneric.php
new file mode 100644
index 000000000..af73158e5
--- /dev/null
+++ b/tests/Framework/ImapGeneric.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * Test class to test rcube_imap_generic class
+ *
+ * @package Tests
+ */
+class Framework_ImapGeneric extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_imap_generic;
+
+ $this->assertInstanceOf('rcube_imap_generic', $object, "Class constructor");
+ }
+
+ /**
+ * Test for uncompressMessageSet
+ */
+ function test_uncompressMessageSet()
+ {
+ $result = rcube_imap_generic::uncompressMessageSet(null);
+ $this->assertSame(array(), $result);
+ $this->assertCount(0, $result);
+
+ $result = rcube_imap_generic::uncompressMessageSet('1');
+ $this->assertSame(array(1), $result);
+ $this->assertCount(1, $result);
+
+ $result = rcube_imap_generic::uncompressMessageSet('1:3');
+ $this->assertSame(array(1, 2, 3), $result);
+ $this->assertCount(3, $result);
+ }
+
+ /**
+ * Test for tokenizeResponse
+ */
+ function test_tokenizeResponse()
+ {
+ $response = "test brack[et] {1}\r\na {0}\r\n (item1 item2)";
+
+ $result = rcube_imap_generic::tokenizeResponse($response, 1);
+ $this->assertSame("test", $result);
+
+ $result = rcube_imap_generic::tokenizeResponse($response, 1);
+ $this->assertSame("brack[et]", $result);
+
+ $result = rcube_imap_generic::tokenizeResponse($response, 1);
+ $this->assertSame("a", $result);
+
+ $result = rcube_imap_generic::tokenizeResponse($response, 1);
+ $this->assertSame("", $result);
+
+ $result = rcube_imap_generic::tokenizeResponse($response, 1);
+ $this->assertSame(array('item1', 'item2'), $result);
+ }
+}
diff --git a/tests/Framework/MessageHeader.php b/tests/Framework/MessageHeader.php
new file mode 100644
index 000000000..e5bc1752f
--- /dev/null
+++ b/tests/Framework/MessageHeader.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_message_header class
+ *
+ * @package Tests
+ */
+class Framework_MessageHeader extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_message_header;
+
+ $this->assertInstanceOf('rcube_message_header', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/MessagePart.php b/tests/Framework/MessagePart.php
new file mode 100644
index 000000000..deb426024
--- /dev/null
+++ b/tests/Framework/MessagePart.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_message_part class
+ *
+ * @package Tests
+ */
+class Framework_MessagePart extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_message_part;
+
+ $this->assertInstanceOf('rcube_message_part', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/Mime.php b/tests/Framework/Mime.php
new file mode 100644
index 000000000..d767905e3
--- /dev/null
+++ b/tests/Framework/Mime.php
@@ -0,0 +1,213 @@
+<?php
+
+/**
+ * Test class to test rcube_mime class
+ *
+ * @package Tests
+ */
+class Framework_Mime 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',
+ // invalid (#1489092)
+ 22 => '"John Doe @ SomeBusinessName" <MAILER-DAEMON>',
+ 23 => '=?UTF-8?B?IlRlc3QsVGVzdCI=?= <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'),
+ // invalid (#1489092)
+ 22 => array(1, 'John Doe @ SomeBusinessName', 'MAILER-DAEMON'),
+ 23 => array(1, 'Test,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);
+ }
+ }
+
+ /**
+ * Test format=flowed unfolding
+ */
+ function test_format_flowed()
+ {
+ $raw = file_get_contents(TESTS_DIR . 'src/format-flowed-unfolded.txt');
+ $flowed = file_get_contents(TESTS_DIR . 'src/format-flowed.txt');
+
+ $this->assertEquals($flowed, rcube_mime::format_flowed($raw, 80), "Test correct folding and space-stuffing");
+ }
+
+ /**
+ * Test format=flowed unfolding
+ */
+ function test_unfold_flowed()
+ {
+ $flowed = file_get_contents(TESTS_DIR . 'src/format-flowed.txt');
+ $unfolded = file_get_contents(TESTS_DIR . 'src/format-flowed-unfolded.txt');
+
+ $this->assertEquals($unfolded, rcube_mime::unfold_flowed($flowed), "Test correct unfolding of quoted lines");
+ }
+
+ /**
+ * Test wordwrap()
+ */
+ function test_wordwrap()
+ {
+ $samples = array(
+ array(
+ array("aaaa aaaa\n aaaa"),
+ "aaaa aaaa\n aaaa",
+ ),
+ array(
+ array("123456789 123456789 123456789 123", 29),
+ "123456789 123456789 123456789\n123",
+ ),
+ array(
+ array("123456789 3456789 123456789", 29),
+ "123456789 3456789 123456789",
+ ),
+ array(
+ array("123456789 123456789 123456789 123", 29),
+ "123456789 123456789 123456789\n 123",
+ ),
+ array(
+ array("abc", 1, "\n", true),
+ "a\nb\nc",
+ ),
+ array(
+ array("ąść", 1, "\n", true, 'UTF-8'),
+ "ą\nś\nć",
+ ),
+ array(
+ array(">abc\n>def", 2, "\n", true),
+ ">abc\n>def",
+ ),
+ array(
+ array("abc def", 3, "-"),
+ "abc-def",
+ ),
+ array(
+ array("----------------------------------------------------------------------------------------\nabc def123456789012345", 76),
+ "----------------------------------------------------------------------------------------\nabc def123456789012345",
+ ),
+ array(
+ array("-------\nabc def", 5),
+ "-------\nabc\ndef",
+ ),
+ array(
+ array("http://xx.xxx.xx.xxx:8080/addressbooks/roundcubexxxxx%40xxxxxxxxxxxxxxxxxxxxxxx.xx.xx/testing/", 70),
+ "http://xx.xxx.xx.xxx:8080/addressbooks/roundcubexxxxx%40xxxxxxxxxxxxxxxxxxxxxxx.xx.xx/testing/",
+ ),
+ array(
+ array("this-is-just-some-blabla-to-make-this-more-than-seventy-five-characters-in-a-row -- this line should be wrapped", 20, "\n"),
+ "this-is-just-some-blabla-to-make-this-more-than-seventy-five-characters-in-a-row\n-- this line should\nbe wrapped",
+ ),
+ );
+
+ foreach ($samples as $sample) {
+ $this->assertEquals($sample[1], call_user_func_array(array('rcube_mime', 'wordwrap'), $sample[0]), "Test text wrapping");
+ }
+ }
+
+}
diff --git a/tests/Framework/Rcube.php b/tests/Framework/Rcube.php
new file mode 100644
index 000000000..637558dc9
--- /dev/null
+++ b/tests/Framework/Rcube.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube class
+ *
+ * @package Tests
+ */
+class Framework_Rcube extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = rcube::get_instance();
+
+ $this->assertInstanceOf('rcube', $object, "Class singleton");
+ }
+}
diff --git a/tests/Framework/ResultIndex.php b/tests/Framework/ResultIndex.php
new file mode 100644
index 000000000..da1cc628f
--- /dev/null
+++ b/tests/Framework/ResultIndex.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * Test class to test rcube_result_index class
+ *
+ * @package Tests
+ */
+class Framework_ResultIndex extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_result_index;
+
+ $this->assertInstanceOf('rcube_result_index', $object, "Class constructor");
+ }
+
+ /**
+ * thread parser test
+ */
+ function test_parse()
+ {
+ $text = "* SORT 2001 2002 2035 2036 2037 2038 2044 2046 2043 2045 2226 2225 2224 2223";
+ $object = new rcube_result_index('INBOX', $text);
+
+ $this->assertSame(false, $object->is_empty(), "Object is empty");
+ $this->assertSame(false, $object->is_error(), "Object is error");
+ $this->assertSame(2226, $object->max(), "Max message UID");
+ $this->assertSame(2001, $object->min(), "Min message UID");
+ $this->assertSame(14, $object->count_messages(), "Messages count");
+ $this->assertSame(14, $object->count(), "Messages count");
+ $this->assertSame(1, $object->exists(2002, true), "Message exists");
+ $this->assertSame(true, $object->exists(2002), "Message exists (bool)");
+ $this->assertSame(2001, $object->get_element('FIRST'), "Get first element");
+ $this->assertSame(2223, $object->get_element('LAST'), "Get last element");
+ $this->assertSame(2035, (int) $object->get_element(2), "Get specified element");
+ $this->assertSame("2001:2002,2035:2038,2043:2046,2223:2226", $object->get_compressed(), "Get compressed index");
+ $this->assertSame('INBOX', $object->get_parameters('MAILBOX'), "Get parameter");
+
+ $clone = clone $object;
+ $clone->filter(array(2035, 2002));
+
+ $this->assertSame(2, $clone->count(), "Messages count (filtered)");
+ $this->assertSame(2002, $clone->get_element('FIRST'), "Get first element (filtered)");
+
+ $clone = clone $object;
+ $clone->revert();
+
+ $this->assertSame(14, $clone->count(), "Messages count (reverted)");
+ $this->assertSame(12, $clone->exists(2002, true), "Message exists (reverted)");
+ $this->assertSame(true, $clone->exists(2002), "Message exists (bool) (reverted)");
+ $this->assertSame(2223, $clone->get_element('FIRST'), "Get first element (reverted)");
+ $this->assertSame(2001, $clone->get_element('LAST'), "Get last element (reverted)");
+ $this->assertSame(2225, (int) $clone->get_element(2), "Get specified element (reverted)");
+
+ $clone = clone $object;
+ $clone->slice(2, 3);
+
+ $this->assertSame(3, $clone->count(), "Messages count (sliced)");
+ $this->assertSame(2035, $clone->get_element('FIRST'), "Get first element (sliced)");
+ $this->assertSame(2037, $clone->get_element('LAST'), "Get last element (sliced)");
+ }
+
+}
diff --git a/tests/Framework/ResultSet.php b/tests/Framework/ResultSet.php
new file mode 100644
index 000000000..2d04e53c0
--- /dev/null
+++ b/tests/Framework/ResultSet.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_result_set class
+ *
+ * @package Tests
+ */
+class Framework_ResultSet extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_result_set;
+
+ $this->assertInstanceOf('rcube_result_set', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/ResultThread.php b/tests/Framework/ResultThread.php
new file mode 100644
index 000000000..55fca4c6a
--- /dev/null
+++ b/tests/Framework/ResultThread.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * Test class to test rcube_result_thread class
+ *
+ * @package Tests
+ */
+class Framework_ResultThread extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_result_thread;
+
+ $this->assertInstanceOf('rcube_result_thread', $object, "Class constructor");
+ }
+
+ /**
+ * thread parser test
+ */
+ function test_parse_thread()
+ {
+ $text = file_get_contents(__DIR__ . '/../src/imap_thread.txt');
+ $object = new rcube_result_thread('INBOX', $text);
+
+ $this->assertSame(false, $object->is_empty(), "Object is empty");
+ $this->assertSame(false, $object->is_error(), "Object is error");
+ $this->assertSame(1721, $object->max(), "Max message UID");
+ $this->assertSame(1, $object->min(), "Min message UID");
+ $this->assertSame(1721, $object->count_messages(), "Messages count");
+ $this->assertSame(1691, $object->exists(1720, true), "Message exists");
+ $this->assertSame(true, $object->exists(1720), "Message exists (bool)");
+ $this->assertSame(1, $object->get_element('FIRST'), "Get first element");
+ $this->assertSame(1719, $object->get_element('LAST'), "Get last element");
+ $this->assertSame(14, (int) $object->get_element(2), "Get specified element");
+
+ $clone = clone $object;
+ $clone->filter(array(7));
+ $clone = $clone->get_tree();
+
+ $this->assertSame(1, count($clone), "Structure check");
+ $this->assertSame(3, count($clone[7]), "Structure check");
+ $this->assertSame(0, count($clone[7][12]), "Structure check");
+ $this->assertSame(1, count($clone[7][167]), "Structure check");
+ $this->assertSame(0, count($clone[7][167][197]), "Structure check");
+ $this->assertSame(2, count($clone[7][458]), "Structure check");
+ $this->assertSame(1, count($clone[7][458][460]), "Structure check");
+ $this->assertSame(0, count($clone[7][458][460][463]), "Structure check");
+ $this->assertSame(1, count($clone[7][458][464]), "Structure check");
+ $this->assertSame(0, count($clone[7][458][464][471]), "Structure check");
+
+ $object->filter(array(784));
+ $this->assertSame(118, $object->count_messages(), "Messages filter");
+ $this->assertSame(1, $object->count(), "Messages filter (count)");
+ }
+}
diff --git a/tests/Framework/Smtp.php b/tests/Framework/Smtp.php
new file mode 100644
index 000000000..4bd78d097
--- /dev/null
+++ b/tests/Framework/Smtp.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_smtp class
+ *
+ * @package Tests
+ */
+class Framework_Smtp extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_smtp;
+
+ $this->assertInstanceOf('rcube_smtp', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/Spellchecker.php b/tests/Framework/Spellchecker.php
new file mode 100644
index 000000000..9c3e92ffd
--- /dev/null
+++ b/tests/Framework/Spellchecker.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_spellchecker class
+ *
+ * @package Tests
+ */
+class Framework_Spellchecker extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_spellchecker;
+
+ $this->assertInstanceOf('rcube_spellchecker', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/StringReplacer.php b/tests/Framework/StringReplacer.php
new file mode 100644
index 000000000..0fa7fae34
--- /dev/null
+++ b/tests/Framework/StringReplacer.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * Test class to test rcube_string_replacer class
+ *
+ * @package Tests
+ */
+class Framework_StringReplacer extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $sr = new rcube_string_replacer;
+
+ $this->assertInstanceOf('rcube_string_replacer', $sr, "Class constructor");
+ }
+
+ /**
+ * Data for test_replace()
+ */
+ function data_replace()
+ {
+ return array(
+ array('http://domain.tld/path*path2', '<a href="http://domain.tld/path*path2">http://domain.tld/path*path2</a>'),
+ array("Click this link:\nhttps://mail.xn--brderli-o2a.ch/rc/ EOF", "Click this link:\n<a href=\"https://mail.xn--brderli-o2a.ch/rc/\">https://mail.xn--brderli-o2a.ch/rc/</a> EOF"),
+ array('Start http://localhost/?foo End', 'Start <a href="http://localhost/?foo">http://localhost/?foo</a> End'),
+ array('http://localhost/?foo=bar. Period', '<a href="http://localhost/?foo=bar">http://localhost/?foo=bar</a>. Period'),
+ array('www.domain.tld', '<a href="http://www.domain.tld">www.domain.tld</a>'),
+ array('WWW.DOMAIN.TLD', '<a href="http://WWW.DOMAIN.TLD">WWW.DOMAIN.TLD</a>'),
+ array('[http://link.com]', '[<a href="http://link.com">http://link.com</a>]'),
+ array('http://link.com?a[]=1', '<a href="http://link.com?a[]=1">http://link.com?a[]=1</a>'),
+ array('http://link.com?a[]', '<a href="http://link.com?a[]">http://link.com?a[]</a>'),
+ array('(http://link.com)', '(<a href="http://link.com">http://link.com</a>)'),
+ array('http://link.com?a(b)c', '<a href="http://link.com?a(b)c">http://link.com?a(b)c</a>'),
+ array('http://link.com?(link)', '<a href="http://link.com?(link)">http://link.com?(link)</a>'),
+ array('https://github.com/a/b/compare/3a0f82...1f4b2a after', '<a href="https://github.com/a/b/compare/3a0f82...1f4b2a">https://github.com/a/b/compare/3a0f82...1f4b2a</a> after'),
+ array('http://<test>', 'http://<test>'),
+ array('http://', 'http://'),
+ array('1@1.com www.domain.tld', '<a href="mailto:1@1.com">1@1.com</a> <a href="http://www.domain.tld">www.domain.tld</a>'),
+ array(' www.domain.tld ', ' <a href="http://www.domain.tld">www.domain.tld</a> '),
+ array(' www.domain.tld/#!download|856p1|2 ', ' <a href="http://www.domain.tld/#!download|856p1|2">www.domain.tld/#!download|856p1|2</a> '),
+ );
+ }
+
+ /**
+ * @dataProvider data_replace
+ */
+ function test_replace($input, $output)
+ {
+ $replacer = new rcube_string_replacer;
+ $result = $replacer->replace($input);
+ $result = $replacer->resolve($result);
+
+ $this->assertEquals($output, $result);
+ }
+
+ function test_linkrefs()
+ {
+ $input = "This is a sample message [1] to test the new linkref [ref0] replacement feature of [Roundcube].\n";
+ $input.= "\n";
+ $input.= "[1] http://en.wikipedia.org/wiki/Email\n";
+ $input.= "[ref0] www.link-ref.com\n";
+
+ $replacer = new rcube_string_replacer;
+ $result = $replacer->replace($input);
+ $result = $replacer->resolve($result);
+
+ $this->assertContains('[<a href="http://en.wikipedia.org/wiki/Email">1</a>] to', $result, "Numeric linkref replacements");
+ $this->assertContains('[<a href="http://www.link-ref.com">ref0</a>] repl', $result, "Alphanum linkref replacements");
+ $this->assertContains('of [Roundcube].', $result, "Don't touch strings wihtout an index entry");
+ }
+}
diff --git a/tests/Framework/User.php b/tests/Framework/User.php
new file mode 100644
index 000000000..3b1983c58
--- /dev/null
+++ b/tests/Framework/User.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_user class
+ *
+ * @package Tests
+ */
+class Framework_User extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $user = new rcube_user;
+
+ $this->assertInstanceOf('rcube_user', $user, "Class constructor");
+ }
+}
diff --git a/tests/Framework/Utils.php b/tests/Framework/Utils.php
new file mode 100644
index 000000000..1f1e57b0e
--- /dev/null
+++ b/tests/Framework/Utils.php
@@ -0,0 +1,337 @@
+<?php
+
+/**
+ * Test class to test rcube_utils class
+ *
+ * @package Tests
+ */
+class Framework_Utils extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Valid email addresses for test_valid_email()
+ */
+ function data_valid_email()
+ {
+ return array(
+ array('email@domain.com', 'Valid email'),
+ array('firstname.lastname@domain.com', 'Email contains dot in the address field'),
+ array('email@subdomain.domain.com', 'Email contains dot with subdomain'),
+ array('firstname+lastname@domain.com', 'Plus sign is considered valid character'),
+ array('email@[123.123.123.123]', 'Square bracket around IP address'),
+ array('email@[IPv6:::1]', 'Square bracket around IPv6 address (1)'),
+ array('email@[IPv6:::1.2.3.4]', 'Square bracket around IPv6 address (2)'),
+ array('email@[IPv6:2001:2d12:c4fe:5afe::1]', 'Square bracket around IPv6 address (3)'),
+ array('"email"@domain.com', 'Quotes around email is considered valid'),
+ array('1234567890@domain.com', 'Digits in address are valid'),
+ array('email@domain-one.com', 'Dash in domain name is valid'),
+ array('_______@domain.com', 'Underscore in the address field is valid'),
+ array('email@domain.name', '.name is valid Top Level Domain name'),
+ array('email@domain.co.jp', 'Dot in Top Level Domain name also considered valid (use co.jp as example here)'),
+ array('firstname-lastname@domain.com', 'Dash in address field is valid'),
+ );
+ }
+
+ /**
+ * Invalid email addresses for test_invalid_email()
+ */
+ function data_invalid_email()
+ {
+ return array(
+ array('plainaddress', 'Missing @ sign and domain'),
+ array('#@%^%#$@#$@#.com', 'Garbage'),
+ array('@domain.com', 'Missing username'),
+ array('Joe Smith <email@domain.com>', 'Encoded html within email is invalid'),
+ array('email.domain.com', 'Missing @'),
+ array('email@domain@domain.com', 'Two @ sign'),
+ array('.email@domain.com', 'Leading dot in address is not allowed'),
+ array('email.@domain.com', 'Trailing dot in address is not allowed'),
+ array('email..email@domain.com', 'Multiple dots'),
+ array('あいうえお@domain.com', 'Unicode char as address'),
+ array('email@domain.com (Joe Smith)', 'Text followed email is not allowed'),
+ array('email@domain', 'Missing top level domain (.com/.net/.org/etc)'),
+ array('email@-domain.com', 'Leading dash in front of domain is invalid'),
+// array('email@domain.web', '.web is not a valid top level domain'),
+ array('email@123.123.123.123', 'IP address without brackets'),
+ array('email@2001:2d12:c4fe:5afe::1', 'IPv6 address without brackets'),
+ array('email@IPv6:2001:2d12:c4fe:5afe::1', 'IPv6 address without brackets (2)'),
+ array('email@[111.222.333.44444]', 'Invalid IP format'),
+ array('email@[111.222.255.257]', 'Invalid IP format (2)'),
+ array('email@[.222.255.257]', 'Invalid IP format (3)'),
+ array('email@[::1]', 'Invalid IPv6 format (1)'),
+ array('email@[IPv6:2001:23x2:1]', 'Invalid IPv6 format (2)'),
+ array('email@[IPv6:1111:2222:33333::4444:5555]', 'Invalid IPv6 format (3)'),
+ array('email@[IPv6:1111::3333::4444:5555]', 'Invalid IPv6 format (4)'),
+ array('email@domain..com', 'Multiple dot in the domain portion is invalid'),
+ );
+ }
+
+ /**
+ * @dataProvider data_valid_email
+ */
+ function test_valid_email($email, $title)
+ {
+ $this->assertTrue(rcube_utils::check_email($email, false), $title);
+ }
+
+ /**
+ * @dataProvider data_invalid_email
+ */
+ function test_invalid_email($email, $title)
+ {
+ $this->assertFalse(rcube_utils::check_email($email, false), $title);
+ }
+
+ /**
+ * Valid IP addresses for test_valid_ip()
+ */
+ function data_valid_ip()
+ {
+ return array(
+ array('0.0.0.0'),
+ array('123.123.123.123'),
+ array('::'),
+ array('::1'),
+ array('::1.2.3.4'),
+ array('2001:2d12:c4fe:5afe::1'),
+ );
+ }
+
+ /**
+ * Valid IP addresses for test_invalid_ip()
+ */
+ function data_invalid_ip()
+ {
+ return array(
+ array(''),
+ array(0),
+ array('123.123.123.1234'),
+ array('1.1.1.1.1'),
+ array('::1.2.3.260'),
+ array('::1.0'),
+ array('2001::c4fe:5afe::1'),
+ );
+ }
+
+ /**
+ * @dataProvider data_valid_ip
+ */
+ function test_valid_ip($ip)
+ {
+ $this->assertTrue(rcube_utils::check_ip($ip));
+ }
+
+ /**
+ * @dataProvider data_invalid_ip
+ */
+ function test_invalid_ip($ip)
+ {
+ $this->assertFalse(rcube_utils::check_ip($ip));
+ }
+
+ /**
+ * Data for test_rep_specialchars_output()
+ */
+ function data_rep_specialchars_output()
+ {
+ return array(
+ array('', '', 'abc', 'abc'),
+ array('', '', '?', '?'),
+ array('', '', '"', '&quot;'),
+ array('', '', '<', '&lt;'),
+ array('', '', '>', '&gt;'),
+ array('', '', '&', '&amp;'),
+ array('', '', '&amp;', '&amp;amp;'),
+ array('', '', '<a>', '&lt;a&gt;'),
+ array('', 'remove', '<a>', ''),
+ );
+ }
+
+ /**
+ * Test for rep_specialchars_output
+ * @dataProvider data_rep_specialchars_output
+ */
+ function test_rep_specialchars_output($type, $mode, $str, $res)
+ {
+ $result = rcube_utils::rep_specialchars_output(
+ $str, $type ? $type : 'html', $mode ? $mode : 'strict');
+
+ $this->assertEquals($result, $res);
+ }
+
+ /**
+ * rcube_utils::mod_css_styles()
+ */
+ function test_mod_css_styles()
+ {
+ $css = file_get_contents(TESTS_DIR . 'src/valid.css');
+ $mod = rcube_utils::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");
+
+ $css = file_get_contents(TESTS_DIR . 'src/media.css');
+ $mod = rcube_utils::mod_css_styles($css, 'rcmbody');
+
+ $this->assertContains('#rcmbody table[class=w600]', $mod, 'Replace styles nested in @media block');
+ $this->assertContains('#rcmbody {width:600px', $mod, 'Replace body selector nested in @media block');
+ }
+
+ /**
+ * rcube_utils::mod_css_styles()
+ */
+ function test_mod_css_styles_xss()
+ {
+ $mod = rcube_utils::mod_css_styles("body.main2cols { background-image: url('../images/leftcol.png'); }", 'rcmbody');
+ $this->assertEquals("/* evil! */", $mod, "No url() values allowed");
+
+ $mod = rcube_utils::mod_css_styles("@import url('http://localhost/somestuff/css/master.css');", 'rcmbody');
+ $this->assertEquals("/* evil! */", $mod, "No import statements");
+
+ $mod = rcube_utils::mod_css_styles("left:expression(document.body.offsetWidth-20)", 'rcmbody');
+ $this->assertEquals("/* evil! */", $mod, "No expression properties");
+
+ $mod = rcube_utils::mod_css_styles("left:exp/* */ression( alert(&#039;xss3&#039;) )", 'rcmbody');
+ $this->assertEquals("/* evil! */", $mod, "Don't allow encoding quirks");
+
+ $mod = rcube_utils::mod_css_styles("background:\\0075\\0072\\006c( javascript:alert(&#039;xss&#039;) )", 'rcmbody');
+ $this->assertEquals("/* evil! */", $mod, "Don't allow encoding quirks (2)");
+ }
+
+ /**
+ * Check rcube_utils::explode_quoted_string()
+ */
+ function test_explode_quoted_string()
+ {
+ $data = array(
+ '"a,b"' => array('"a,b"'),
+ '"a,b","c,d"' => array('"a,b"','"c,d"'),
+ '"a,\\"b",d' => array('"a,\\"b"', 'd'),
+ );
+
+ foreach ($data as $text => $res) {
+ $result = rcube_utils::explode_quoted_string(',', $text);
+ $this->assertSame($res, $result);
+ }
+ }
+
+ /**
+ * Check rcube_utils::explode_quoted_string() compat. with explode()
+ */
+ function test_explode_quoted_string_compat()
+ {
+ $data = array('', 'a,b,c', 'a', ',', ',a');
+
+ foreach ($data as $text) {
+ $result = rcube_utils::explode_quoted_string(',', $text);
+ $this->assertSame(explode(',', $text), $result);
+ }
+ }
+
+ /**
+ * rcube_utils::get_boolean()
+ */
+ function test_get_boolean()
+ {
+ $input = array(
+ false, 'false', '0', 'no', 'off', 'nein', 'FALSE', '', null,
+ );
+
+ foreach ($input as $idx => $value) {
+ $this->assertFalse(get_boolean($value), "Invalid result for $idx test item");
+ }
+
+ $input = array(
+ true, 'true', '1', 1, 'yes', 'anything', 1000,
+ );
+
+ foreach ($input as $idx => $value) {
+ $this->assertTrue(get_boolean($value), "Invalid result for $idx test item");
+ }
+ }
+
+ /**
+ * rcube:utils::file2class()
+ */
+ function test_file2class()
+ {
+ $test = array(
+ array('', '', 'unknown'),
+ array('text', 'text', 'text'),
+ array('image/png', 'image.png', 'image png'),
+ );
+
+ foreach ($test as $v) {
+ $result = rcube_utils::file2class($v[0], $v[1]);
+ $this->assertSame($v[2], $result);
+ }
+ }
+
+ /**
+ * rcube:utils::strtotime()
+ */
+ function test_strtotime()
+ {
+ $test = array(
+ '1' => 1,
+ '' => 0,
+ '2013-04-22' => 1366581600,
+ '2013/04/22' => 1366581600,
+ '2013.04.22' => 1366581600,
+ '22-04-2013' => 1366581600,
+ '22/04/2013' => 1366581600,
+ '22.04.2013' => 1366581600,
+ '22.4.2013' => 1366581600,
+ '20130422' => 1366581600,
+ );
+
+ foreach ($test as $datetime => $ts) {
+ $result = rcube_utils::strtotime($datetime);
+ $this->assertSame($ts, $result, "Error parsing date: $datetime");
+ }
+ }
+
+ /**
+ * rcube:utils::anytodatetime()
+ */
+ function test_anytodatetime()
+ {
+ $test = array(
+ '2013-04-22' => '2013-04-22',
+ '2013/04/22' => '2013-04-22',
+ '2013.04.22' => '2013-04-22',
+ '22-04-2013' => '2013-04-22',
+ '22/04/2013' => '2013-04-22',
+ '22.04.2013' => '2013-04-22',
+ '04/22/2013' => '2013-04-22',
+ '22.4.2013' => '2013-04-22',
+ '20130422' => '2013-04-22',
+ '1900-10-10' => '1900-10-10',
+ '01-01-1900' => '1900-01-01',
+ '01/30/1960' => '1960-01-30'
+ );
+
+ foreach ($test as $datetime => $ts) {
+ $result = rcube_utils::anytodatetime($datetime);
+ $this->assertSame($ts, $result ? $result->format('Y-m-d') : '', "Error parsing date: $datetime");
+ }
+ }
+
+ /**
+ * rcube:utils::normalize _string()
+ */
+ function test_normalize_string()
+ {
+ $test = array(
+ '' => '',
+ 'abc def' => 'abc def',
+ );
+
+ foreach ($test as $input => $output) {
+ $result = rcube_utils::normalize_string($input);
+ $this->assertSame($output, $result);
+ }
+ }
+}
diff --git a/tests/Framework/VCard.php b/tests/Framework/VCard.php
new file mode 100644
index 000000000..3353b5b13
--- /dev/null
+++ b/tests/Framework/VCard.php
@@ -0,0 +1,119 @@
+<?php
+
+/**
+ * Unit tests for class rcube_vcard
+ *
+ * @package Tests
+ */
+class Framework_VCard 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");
+ }
+
+ /**
+ * Make sure MOBILE phone is returned as CELL (as specified in standard)
+ */
+ function test_parse_three()
+ {
+ $vcard = new rcube_vcard(file_get_contents($this->_srcpath('johndoe.vcf')), null);
+
+ $vcf = $vcard->export();
+ $this->assertRegExp('/TEL;CELL:\+987654321/', $vcf, "Return CELL instead of MOBILE (import)");
+
+ $vcard = new rcube_vcard();
+ $vcard->set('phone', '+987654321', 'MOBILE');
+
+ $vcf = $vcard->export();
+ $this->assertRegExp('/TEL;TYPE=CELL:\+987654321/', $vcf, "Return CELL instead of MOBILE (set)");
+ }
+
+ /**
+ * Backslash escaping test (#1488896)
+ */
+ function test_parse_four()
+ {
+ $vcard = "BEGIN:VCARD\nVERSION:3.0\nN:last\\;;first\\\\;middle\\\\\\;\\\\;prefix;\nFN:test\nEND:VCARD";
+ $vcard = new rcube_vcard($vcard, null);
+ $vcard = $vcard->get_assoc();
+
+ $this->assertEquals("last;", $vcard['surname'], "Decode backslash character");
+ $this->assertEquals("first\\", $vcard['firstname'], "Decode backslash character");
+ $this->assertEquals("middle\\;\\", $vcard['middlename'], "Decode backslash character");
+ $this->assertEquals("prefix", $vcard['prefix'], "Decode backslash character");
+ }
+
+ /**
+ * Backslash parsing test (#1489085)
+ */
+ function test_parse_five()
+ {
+ $vcard = "BEGIN:VCARD\nVERSION:3.0\nN:last\\\\\\a;fir\\nst\nURL:http\\://domain.tld\nEND:VCARD";
+ $vcard = new rcube_vcard($vcard, null);
+ $vcard = $vcard->get_assoc();
+
+ $this->assertEquals("last\\a", $vcard['surname'], "Decode dummy backslash character");
+ $this->assertEquals("fir\nst", $vcard['firstname'], "Decode backslash character");
+ $this->assertEquals("http://domain.tld", $vcard['website:other'][0], "Decode dummy backslash character");
+ }
+
+ 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_import_photo_encoding()
+ {
+ $input = file_get_contents($this->_srcpath('photo.vcf'));
+
+ $vcards = rcube_vcard::import($input);
+ $vcard = $vcards[0]->get_assoc();
+
+ $this->assertCount(1, $vcards, "Detected 1 vcard");
+
+ // ENCODING=b case (#1488683)
+ $this->assertEquals("/9j/4AAQSkZJRgABAQA", substr(base64_encode($vcard['photo']), 0, 19), "Photo decoding");
+ $this->assertEquals("Müller", $vcard['surname'], "Unicode characters");
+ }
+
+ 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/Framework/Washtml.php b/tests/Framework/Washtml.php
new file mode 100644
index 000000000..7485d4383
--- /dev/null
+++ b/tests/Framework/Washtml.php
@@ -0,0 +1,127 @@
+<?php
+
+/**
+ * Test class to test rcube_washtml class
+ *
+ * @package Tests
+ */
+class Framework_Washtml extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Test the elimination of some XSS vulnerabilities
+ */
+ function test_html_xss3()
+ {
+ // #1488850
+ $html = '<p><a href="data:text/html,&lt;script&gt;alert(document.cookie)&lt;/script&gt;">Firefox</a>'
+ .'<a href="vbscript:alert(document.cookie)">Internet Explorer</a></p>';
+
+ $washer = new rcube_washtml;
+ $washed = $washer->wash($html);
+
+ $this->assertNotRegExp('/data:text/', $washed, "Remove data:text/html links");
+ $this->assertNotRegExp('/vbscript:/', $washed, "Remove vbscript: links");
+ }
+
+ /**
+ * Test fixing of invalid href (#1488940)
+ */
+ function test_href()
+ {
+ $html = "<p><a href=\"\nhttp://test.com\n\">Firefox</a>";
+
+ $washer = new rcube_washtml;
+ $washed = $washer->wash($html);
+
+ $this->assertRegExp('|href="http://test.com">|', $washed, "Link href with newlines (#1488940)");
+ }
+
+ /**
+ * Test handling HTML comments
+ */
+ function test_comments()
+ {
+ $washer = new rcube_washtml;
+
+ $html = "<!--[if gte mso 10]><p>p1</p><!--><p>p2</p>";
+ $washed = $washer->wash($html);
+
+ $this->assertEquals('<!-- node type 8 --><!-- html ignored --><!-- body ignored --><p>p2</p>', $washed, "HTML conditional comments (#1489004)");
+
+ $html = "<!--TestCommentInvalid><p>test</p>";
+ $washed = $washer->wash($html);
+
+ $this->assertEquals('<!-- html ignored --><!-- body ignored --><p>test</p>', $washed, "HTML invalid comments (#1487759)");
+ }
+
+ /**
+ * Test fixing of invalid self-closing elements (#1489137)
+ */
+ function test_self_closing()
+ {
+ $html = "<textarea>test";
+
+ $washer = new rcube_washtml;
+ $washed = $washer->wash($html);
+
+ $this->assertRegExp('|<textarea>test</textarea>|', $washed, "Self-closing textarea (#1489137)");
+ }
+
+ /**
+ * Test fixing of invalid closing tags (#1489446)
+ */
+ function test_closing_tag_attrs()
+ {
+ $html = "<a href=\"http://test.com\">test</a href>";
+
+ $washer = new rcube_washtml;
+ $washed = $washer->wash($html);
+
+ $this->assertRegExp('|</a>|', $washed, "Invalid closing tag (#1489446)");
+ }
+
+ /**
+ * Test fixing of invalid lists nesting (#1488768)
+ */
+ function test_lists()
+ {
+ $data = array(
+ array(
+ "<ol><li>First</li><li>Second</li><ul><li>First sub</li></ul><li>Third</li></ol>",
+ "<ol><li>First</li><li>Second<ul><li>First sub</li></ul></li><li>Third</li></ol>"
+ ),
+ array(
+ "<ol><li>First<ul><li>First sub</li></ul></li></ol>",
+ "<ol><li>First<ul><li>First sub</li></ul></li></ol>",
+ ),
+ array(
+ "<ol><li>First<ol><li>First sub</li></ol></li></ol>",
+ "<ol><li>First<ol><li>First sub</li></ol></li></ol>",
+ ),
+ array(
+ "<ul><li>First</li><ul><li>First sub</li><ul><li>sub sub</li></ul></ul><li></li></ul>",
+ "<ul><li>First<ul><li>First sub<ul><li>sub sub</li></ul></li></ul></li><li></li></ul>",
+ ),
+ array(
+ "<ul><li>First</li><li>second</li><ul><ul><li>sub sub</li></ul></ul></ul>",
+ "<ul><li>First</li><li>second<ul><ul><li>sub sub</li></ul></ul></li></ul>",
+ ),
+ array(
+ "<ol><ol><ol></ol></ol></ol>",
+ "<ol><ol><ol></ol></ol></ol>",
+ ),
+ array(
+ "<div><ol><ol><ol></ol></ol></ol></div>",
+ "<div><ol><ol><ol></ol></ol></ol></div>",
+ ),
+ );
+
+ foreach ($data as $element) {
+ rcube_washtml::fix_broken_lists($element[0]);
+
+ $this->assertSame($element[1], $element[0], "Broken nested lists (#1488768)");
+ }
+ }
+
+}
diff --git a/tests/MailFunc.php b/tests/MailFunc.php
new file mode 100644
index 000000000..ab0074ef2
--- /dev/null
+++ b/tests/MailFunc.php
@@ -0,0 +1,268 @@
+<?php
+
+/**
+ * Test class to test steps/mail/func.inc functions
+ *
+ * @package Tests
+ */
+class MailFunc extends PHPUnit_Framework_TestCase
+{
+
+ function setUp()
+ {
+ // 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';
+ }
+
+ /**
+ * 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 the elimination of some XSS vulnerabilities
+ */
+ function test_html_xss3()
+ {
+ // #1488850
+ $html = '<p><a href="data:text/html,&lt;script&gt;alert(document.cookie)&lt;/script&gt;">Firefox</a>'
+ .'<a href="vbscript:alert(document.cookie)">Internet Explorer</a></p>';
+ $washed = rcmail_wash_html($html, array('safe' => true), array());
+
+ $this->assertNotRegExp('/data:text/', $washed, "Remove data:text/html links");
+ $this->assertNotRegExp('/vbscript:/', $washed, "Remove vbscript: links");
+ }
+
+ /**
+ * 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 rel="noreferrer" target="_blank" href="http://www.apple.com/legal/privacy">http://www.apple.com/legal/privacy</a>#', $html, "Links with target=_blank");
+ $this->assertRegExp('#\\[<a rel="noreferrer" target="_blank" href="http://example.com/\\?tx\\[a\\]=5">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"'
+ .' onclick="return rcmail.command(\'compose\',\'me@me.com?subject=this is the subject&amp;body=this is the body\',this)" rel="noreferrer">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 = rcube_washtml::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]");
+ }
+
+ /**
+ * Test identities selection using Return-Path header
+ */
+ function test_rcmail_identity_select()
+ {
+ $identities = array(
+ array(
+ 'name' => 'Test',
+ 'email_ascii' => 'addr@domain.tld',
+ 'ident' => 'Test <addr@domain.tld>',
+ ),
+ array(
+ 'name' => 'Test',
+ 'email_ascii' => 'thing@domain.tld',
+ 'ident' => 'Test <thing@domain.tld>',
+ ),
+ array(
+ 'name' => 'Test',
+ 'email_ascii' => 'other@domain.tld',
+ 'ident' => 'Test <other@domain.tld>',
+ ),
+ );
+
+ $message = new stdClass;
+ $message->headers = new rcube_message_header;
+ $message->headers->set('Return-Path', '<some_thing@domain.tld>');
+ $res = rcmail_identity_select($message, $identities);
+
+ $this->assertSame($identities[0], $res);
+
+ $message->headers->set('Return-Path', '<thing@domain.tld>');
+ $res = rcmail_identity_select($message, $identities);
+
+ $this->assertSame($identities[1], $res);
+ }
+
+ /**
+ * Test identities selection (#1489378)
+ */
+ function test_rcmail_identity_select2()
+ {
+ $identities = array(
+ array(
+ 'name' => 'Test 1',
+ 'email_ascii' => 'addr1@domain.tld',
+ 'ident' => 'Test 1 <addr1@domain.tld>',
+ ),
+ array(
+ 'name' => 'Test 2',
+ 'email_ascii' => 'addr2@domain.tld',
+ 'ident' => 'Test 2 <addr2@domain.tld>',
+ ),
+ array(
+ 'name' => 'Test 3',
+ 'email_ascii' => 'addr3@domain.tld',
+ 'ident' => 'Test 3 <addr3@domain.tld>',
+ ),
+ array(
+ 'name' => 'Test 4',
+ 'email_ascii' => 'addr2@domain.tld',
+ 'ident' => 'Test 4 <addr2@domain.tld>',
+ ),
+ );
+
+ $message = new stdClass;
+ $message->headers = new rcube_message_header;
+
+ $message->headers->set('From', '<addr2@domain.tld>');
+ $res = rcmail_identity_select($message, $identities);
+ $this->assertSame($identities[1], $res);
+
+ $message->headers->set('From', 'Test 2 <addr2@domain.tld>');
+ $res = rcmail_identity_select($message, $identities);
+ $this->assertSame($identities[1], $res);
+
+ $message->headers->set('From', 'Other <addr2@domain.tld>');
+ $res = rcmail_identity_select($message, $identities);
+ $this->assertSame($identities[1], $res);
+
+ $message->headers->set('From', 'Test 4 <addr2@domain.tld>');
+ $res = rcmail_identity_select($message, $identities);
+ $this->assertSame($identities[3], $res);
+ }
+}
diff --git a/tests/Selenium/Addressbook/Addressbook.php b/tests/Selenium/Addressbook/Addressbook.php
new file mode 100644
index 000000000..9a22b6e13
--- /dev/null
+++ b/tests/Selenium/Addressbook/Addressbook.php
@@ -0,0 +1,21 @@
+<?php
+
+class Selenium_Addressbook_Addressbook extends Selenium_Test
+{
+ public function testAddressbook()
+ {
+ $this->go('addressbook');
+
+ // check task
+ $env = $this->get_env();
+ $this->assertEquals('addressbook', $env['task']);
+
+ $objects = $this->get_objects();
+
+ // these objects should be there always
+ $this->assertContains('qsearchbox', $objects);
+ $this->assertContains('folderlist', $objects);
+ $this->assertContains('contactslist', $objects);
+ $this->assertContains('countdisplay', $objects);
+ }
+}
diff --git a/tests/Selenium/Addressbook/Import.php b/tests/Selenium/Addressbook/Import.php
new file mode 100644
index 000000000..13d81740f
--- /dev/null
+++ b/tests/Selenium/Addressbook/Import.php
@@ -0,0 +1,29 @@
+<?php
+
+class Selenium_Addressbook_Import extends Selenium_Test
+{
+ public function testImport()
+ {
+ $this->go('addressbook', 'import');
+
+ // check task and action
+ $env = $this->get_env();
+ $this->assertEquals('addressbook', $env['task']);
+ $this->assertEquals('import', $env['action']);
+
+ $objects = $this->get_objects();
+
+ // these objects should be there always
+ $this->assertContains('importform', $objects);
+ }
+
+ public function testImport2()
+ {
+ $this->go('addressbook', 'import');
+
+ $objects = $this->get_objects();
+
+ // these objects should be there always
+ $this->assertContains('importform', $objects);
+ }
+}
diff --git a/tests/Selenium/Login.php b/tests/Selenium/Login.php
new file mode 100644
index 000000000..a3f0ab6b4
--- /dev/null
+++ b/tests/Selenium/Login.php
@@ -0,0 +1,21 @@
+<?php
+
+class Selenium_Login extends Selenium_Test
+{
+ public function testLogin()
+ {
+ // first test, we're already on the login page
+ $this->url(TESTS_URL);
+
+ // task should be set to 'login'
+ $env = $this->get_env();
+ $this->assertEquals('login', $env['task']);
+
+ // test valid login
+ $this->login();
+
+ // task should be set to 'mail' now
+ $env = $this->get_env();
+ $this->assertEquals('mail', $env['task']);
+ }
+}
diff --git a/tests/Selenium/Logout.php b/tests/Selenium/Logout.php
new file mode 100644
index 000000000..95eeda57c
--- /dev/null
+++ b/tests/Selenium/Logout.php
@@ -0,0 +1,20 @@
+<?php
+
+class Selenium_Logout extends Selenium_Test
+{
+ public function testLogout()
+ {
+ $this->go('mail');
+
+ $this->click_button('logout');
+
+ sleep(TESTS_SLEEP);
+
+ // task should be set to 'login'
+ $env = $this->get_env();
+ $this->assertEquals('login', $env['task']);
+
+ // form should exist
+ $user_input = $this->byCssSelector('form input[name="_user"]');
+ }
+}
diff --git a/tests/Selenium/Mail/CheckRecent.php b/tests/Selenium/Mail/CheckRecent.php
new file mode 100644
index 000000000..865421c2d
--- /dev/null
+++ b/tests/Selenium/Mail/CheckRecent.php
@@ -0,0 +1,14 @@
+<?php
+
+class Selenium_Mail_CheckRecent extends Selenium_Test
+{
+ public function testCheckRecent()
+ {
+ $this->go('mail');
+
+ $res = $this->ajaxResponse('check-recent', "rcmail.command('checkmail')");
+
+ $this->assertEquals('check-recent', $res['action']);
+ $this->assertRegExp('/this\.set_unread_count/', $res['exec']);
+ }
+}
diff --git a/tests/Selenium/Mail/Compose.php b/tests/Selenium/Mail/Compose.php
new file mode 100644
index 000000000..e707ef17d
--- /dev/null
+++ b/tests/Selenium/Mail/Compose.php
@@ -0,0 +1,25 @@
+<?php
+
+class Selenium_Mail_Compose extends Selenium_Test
+{
+ public function testCompose()
+ {
+ $this->go('mail', 'compose');
+
+ // check task and action
+ $env = $this->get_env();
+ $this->assertEquals('mail', $env['task']);
+ $this->assertEquals('compose', $env['action']);
+
+ $objects = $this->get_objects();
+
+ // these objects should be there always
+ $this->assertContains('qsearchbox', $objects);
+ $this->assertContains('addressbookslist', $objects);
+ $this->assertContains('contactslist', $objects);
+ $this->assertContains('messageform', $objects);
+ $this->assertContains('attachmentlist', $objects);
+ $this->assertContains('filedrop', $objects);
+ $this->assertContains('uploadform', $objects);
+ }
+}
diff --git a/tests/Selenium/Mail/Getunread.php b/tests/Selenium/Mail/Getunread.php
new file mode 100644
index 000000000..d6362f2f4
--- /dev/null
+++ b/tests/Selenium/Mail/Getunread.php
@@ -0,0 +1,13 @@
+<?php
+
+class Selenium_Mail_Getunread extends Selenium_Test
+{
+ public function testGetunread()
+ {
+ $this->go('mail');
+
+ $res = $this->ajaxResponse('getunread', "rcmail.http_request('getunread')");
+
+ $this->assertEquals('getunread', $res['action']);
+ }
+}
diff --git a/tests/Selenium/Mail/List.php b/tests/Selenium/Mail/List.php
new file mode 100644
index 000000000..7574c1801
--- /dev/null
+++ b/tests/Selenium/Mail/List.php
@@ -0,0 +1,25 @@
+<?php
+
+class Selenium_Mail_List extends Selenium_Test
+{
+ public function testCheckRecent()
+ {
+ $this->go('mail');
+
+ $res = $this->ajaxResponse('list', "rcmail.command('list')");
+
+ $this->assertEquals('list', $res['action']);
+ $this->assertRegExp('/this\.set_pagetitle/', $res['exec']);
+ $this->assertRegExp('/this\.set_unread_count/', $res['exec']);
+ $this->assertRegExp('/this\.set_rowcount/', $res['exec']);
+ $this->assertRegExp('/this\.set_message_coltypes/', $res['exec']);
+// $this->assertRegExp('/this\.add_message_row/', $res['exec']);
+
+ $this->assertContains('current_page', $res['env']);
+ $this->assertContains('exists', $res['env']);
+ $this->assertContains('pagecount', $res['env']);
+ $this->assertContains('pagesize', $res['env']);
+ $this->assertContains('messagecount', $res['env']);
+ $this->assertContains('mailbox', $res['env']);
+ }
+}
diff --git a/tests/Selenium/Mail/Mail.php b/tests/Selenium/Mail/Mail.php
new file mode 100644
index 000000000..98413787b
--- /dev/null
+++ b/tests/Selenium/Mail/Mail.php
@@ -0,0 +1,23 @@
+<?php
+
+class Selenium_Mail_Mail extends Selenium_Test
+{
+ public function testMail()
+ {
+ $this->go('mail');
+
+ // check task
+ $env = $this->get_env();
+ $this->assertEquals('mail', $env['task']);
+
+ $objects = $this->get_objects();
+
+ // these objects should be there always
+ $this->assertContains('qsearchbox', $objects);
+ $this->assertContains('mailboxlist', $objects);
+ $this->assertContains('messagelist', $objects);
+ $this->assertContains('quotadisplay', $objects);
+ $this->assertContains('search_filter', $objects);
+ $this->assertContains('countdisplay', $objects);
+ }
+}
diff --git a/tests/Selenium/Settings/About.php b/tests/Selenium/Settings/About.php
new file mode 100644
index 000000000..9a6c31d4b
--- /dev/null
+++ b/tests/Selenium/Settings/About.php
@@ -0,0 +1,14 @@
+<?php
+
+class Selenium_Settings_About extends Selenium_Test
+{
+ public function testAbout()
+ {
+ $this->url(TESTS_URL . '/?_task=settings&_action=about');
+
+ // check task and action
+ $env = $this->get_env();
+ $this->assertEquals('settings', $env['task']);
+ $this->assertEquals('about', $env['action']);
+ }
+}
diff --git a/tests/Selenium/Settings/Folders.php b/tests/Selenium/Settings/Folders.php
new file mode 100644
index 000000000..fa64e45d6
--- /dev/null
+++ b/tests/Selenium/Settings/Folders.php
@@ -0,0 +1,20 @@
+<?php
+
+class Selenium_Settings_Folders extends Selenium_Test
+{
+ public function testFolders()
+ {
+ $this->go('settings', 'folders');
+
+ // task should be set to 'settings' and action to 'folders'
+ $env = $this->get_env();
+ $this->assertEquals('settings', $env['task']);
+ $this->assertEquals('folders', $env['action']);
+
+ $objects = $this->get_objects();
+
+ // these objects should be there always
+ $this->assertContains('quotadisplay', $objects);
+ $this->assertContains('subscriptionlist', $objects);
+ }
+}
diff --git a/tests/Selenium/Settings/Identities.php b/tests/Selenium/Settings/Identities.php
new file mode 100644
index 000000000..869018b09
--- /dev/null
+++ b/tests/Selenium/Settings/Identities.php
@@ -0,0 +1,19 @@
+<?php
+
+class Selenium_Settings_Identities extends Selenium_Test
+{
+ public function testIdentities()
+ {
+ $this->go('settings', 'identities');
+
+ // check task and action
+ $env = $this->get_env();
+ $this->assertEquals('settings', $env['task']);
+ $this->assertEquals('identities', $env['action']);
+
+ $objects = $this->get_objects();
+
+ // these objects should be there always
+ $this->assertContains('identitieslist', $objects);
+ }
+}
diff --git a/tests/Selenium/Settings/Settings.php b/tests/Selenium/Settings/Settings.php
new file mode 100644
index 000000000..08d8339f1
--- /dev/null
+++ b/tests/Selenium/Settings/Settings.php
@@ -0,0 +1,17 @@
+<?php
+
+class Selenium_Settings_Settings extends Selenium_Test
+{
+ public function testSettings()
+ {
+ $this->go('settings');
+
+ // task should be set to 'settings'
+ $env = $this->get_env();
+ $this->assertEquals('settings', $env['task']);
+
+ $objects = $this->get_objects();
+
+ $this->assertContains('sectionslist', $objects);
+ }
+}
diff --git a/tests/Selenium/bootstrap.php b/tests/Selenium/bootstrap.php
new file mode 100644
index 000000000..e8b186a1e
--- /dev/null
+++ b/tests/Selenium/bootstrap.php
@@ -0,0 +1,185 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | tests/Selenium/bootstrap.php |
+ | |
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 2009-2013, 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('RCUBE_CONFIG_DIR', TESTS_DIR . 'config');
+}
+
+require_once(INSTALL_PATH . 'program/include/iniset.php');
+
+// Extend include path so some plugin test won't fail
+$include_path = ini_get('include_path') . PATH_SEPARATOR . TESTS_DIR . '..';
+if (set_include_path($include_path) === false) {
+ die("Fatal error: ini_set/set_include_path does not work.");
+}
+
+$rcmail = rcube::get_instance('test');
+
+define('TESTS_URL', $rcmail->config->get('tests_url'));
+define('TESTS_BROWSER', $rcmail->config->get('tests_browser', 'firefox'));
+define('TESTS_USER', $rcmail->config->get('tests_username'));
+define('TESTS_PASS', $rcmail->config->get('tests_password'));
+define('TESTS_SLEEP', $rcmail->config->get('tests_sleep', 5));
+
+PHPUnit_Extensions_Selenium2TestCase::shareSession(true);
+
+// @TODO: remove user record from DB before running tests
+// @TODO: make sure mailbox has some content (always the same) or is empty
+// @TODO: plugins: enable all?
+
+/**
+ * Base class for all tests in this directory
+ */
+class Selenium_Test extends PHPUnit_Extensions_Selenium2TestCase
+{
+ protected function setUp()
+ {
+// $this->rc = rcube::get_instance();
+ $this->setBrowser(TESTS_BROWSER);
+
+ // Set root to our index.html, for better performance
+ // See https://github.com/sebastianbergmann/phpunit-selenium/issues/217
+ $this->setBrowserUrl(TESTS_URL . '/tests/Selenium');
+ }
+
+ protected function login()
+ {
+ $this->go('mail');
+
+ $user_input = $this->byCssSelector('form input[name="_user"]');
+ $pass_input = $this->byCssSelector('form input[name="_pass"]');
+ $submit = $this->byCssSelector('form input[type="submit"]');
+
+ $user_input->value(TESTS_USER);
+ $pass_input->value(TESTS_PASS);
+
+ // submit login form
+ $submit->click();
+
+ // wait after successful login
+ sleep(TESTS_SLEEP);
+ }
+
+ protected function go($task = 'mail', $action = null)
+ {
+ $this->url(TESTS_URL . '/?_task=' . $task);
+
+ // wait for interface load (initial ajax requests, etc.)
+ sleep(TESTS_SLEEP);
+
+ if ($action) {
+ $this->click_button($action);
+
+ sleep(TESTS_SLEEP);
+ }
+ }
+
+ protected function get_env()
+ {
+ return $this->execute(array(
+ 'script' => 'return rcmail.env;',
+ 'args' => array(),
+ ));
+ }
+
+ protected function get_buttons($action)
+ {
+ $buttons = $this->execute(array(
+ 'script' => "return rcmail.buttons['$action'];",
+ 'args' => array(),
+ ));
+
+ if (is_array($buttons)) {
+ foreach ($buttons as $idx => $button) {
+ $buttons[$idx] = $button['id'];
+ }
+ }
+
+ return (array) $buttons;
+ }
+
+ protected function get_objects()
+ {
+ return $this->execute(array(
+ 'script' => "var i,r = []; for (i in rcmail.gui_objects) r.push(i); return r;",
+ 'args' => array(),
+ ));
+ }
+
+ protected function click_button($action)
+ {
+ $buttons = $this->get_buttons($action);
+ $id = array_shift($buttons);
+
+ // this doesn't work for me
+ $this->byId($id)->click();
+ }
+
+ protected function ajaxResponse($action, $script = '', $button = false)
+ {
+ if (!$script && !$button) {
+ $script = "rcmail.command('$action')";
+ }
+
+ $script =
+ "if (!window.test_ajax_response) {
+ window.test_ajax_response_object = {};
+ function test_ajax_response(response)
+ {
+ if (response.response && response.response.action) {
+ window.test_ajax_response_object[response.response.action] = response.response;
+ }
+ }
+ rcmail.addEventListener('responsebefore', test_ajax_response);
+ }
+ window.test_ajax_response_object['$action'] = null;
+ $script;
+ ";
+
+ // run request
+ $this->execute(array(
+ 'script' => $script,
+ 'args' => array(),
+ ));
+
+ if ($button) {
+ $this->click_button($action);
+ }
+
+ // wait
+ sleep(TESTS_SLEEP);
+
+ // get response
+ $response = $this->execute(array(
+ 'script' => "return window.test_ajax_response_object['$action'];",
+ 'args' => array(),
+ ));
+
+ return $response;
+ }
+}
diff --git a/tests/Selenium/index.html b/tests/Selenium/index.html
new file mode 100644
index 000000000..7aa65f829
--- /dev/null
+++ b/tests/Selenium/index.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+ <title>Roundcube Webmail Tests</title>
+</head>
+<body>
+Testing...
+</body>
+</html>
diff --git a/tests/Selenium/phpunit.xml b/tests/Selenium/phpunit.xml
new file mode 100644
index 000000000..b5835cf74
--- /dev/null
+++ b/tests/Selenium/phpunit.xml
@@ -0,0 +1,21 @@
+<phpunit backupGlobals="false"
+ bootstrap="bootstrap.php"
+ colors="true">
+ <testsuites>
+ <testsuite name="All Tests">
+ <file>Login.php</file><!-- Login.php test must be first -->
+ <file>Addressbook/Addressbook.php</file>
+ <file>Addressbook/Import.php</file>
+ <file>Mail/Mail.php</file>
+ <file>Mail/CheckRecent.php</file>
+ <file>Mail/Compose.php</file>
+ <file>Mail/Getunread.php</file>
+ <file>Mail/List.php</file>
+ <file>Settings/About.php</file>
+ <file>Settings/Folders.php</file>
+ <file>Settings/Identities.php</file>
+ <file>Settings/Settings.php</file>
+ <file>Logout.php</file><!-- Logout.php test must be last -->
+ </testsuite>
+ </testsuites>
+</phpunit>
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 000000000..192997d0d
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,41 @@
+<?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('RCUBE_CONFIG_DIR', TESTS_DIR . 'config');
+}
+
+require_once(INSTALL_PATH . 'program/include/iniset.php');
+
+rcmail::get_instance('test')->config->set('devel_mode', false);
+
+// Extend include path so some plugin test won't fail
+$include_path = ini_get('include_path') . PATH_SEPARATOR . TESTS_DIR . '..';
+if (set_include_path($include_path) === false) {
+ die("Fatal error: ini_set/set_include_path does not work.");
+}
diff --git a/tests/phpunit.xml b/tests/phpunit.xml
new file mode 100644
index 000000000..a5942c433
--- /dev/null
+++ b/tests/phpunit.xml
@@ -0,0 +1,70 @@
+<phpunit backupGlobals="false"
+ bootstrap="bootstrap.php"
+ colors="true">
+ <testsuites>
+ <testsuite name="All Tests">
+ <file>Framework/BaseReplacer.php</file>
+ <file>Framework/Bootstrap.php</file>
+ <file>Framework/Browser.php</file>
+ <file>Framework/Cache.php</file>
+ <file>Framework/Charset.php</file>
+ <file>Framework/ContentFilter.php</file>
+ <file>Framework/Csv2vcard.php</file>
+ <file>Framework/Enriched.php</file>
+ <file>Framework/Html.php</file>
+ <file>Framework/Html2text.php</file>
+ <file>Framework/Imap.php</file>
+ <file>Framework/ImapGeneric.php</file>
+ <file>Framework/Image.php</file>
+ <file>Framework/MessageHeader.php</file>
+ <file>Framework/MessagePart.php</file>
+ <file>Framework/Mime.php</file>
+ <file>Framework/Rcube.php</file>
+ <file>Framework/ResultIndex.php</file>
+ <file>Framework/ResultSet.php</file>
+ <file>Framework/ResultThread.php</file>
+ <file>Framework/Smtp.php</file>
+ <file>Framework/Spellchecker.php</file>
+ <file>Framework/StringReplacer.php</file>
+ <file>Framework/User.php</file>
+ <file>Framework/Utils.php</file>
+ <file>Framework/VCard.php</file>
+ <file>Framework/Washtml.php</file>
+ <file>MailFunc.php</file>
+ </testsuite>
+ <testsuite name="Plugins Tests">
+ <file>./../plugins/acl/tests/Acl.php</file>
+ <file>./../plugins/additional_message_headers/tests/AdditionalMessageHeaders.php</file>
+ <file>./../plugins/archive/tests/Archive.php</file>
+ <file>./../plugins/autologon/tests/Autologon.php</file>
+ <file>./../plugins/database_attachments/tests/DatabaseAttachments.php</file>
+ <file>./../plugins/debug_logger/tests/DebugLogger.php</file>
+ <file>./../plugins/emoticons/tests/Emoticons.php</file>
+ <file>./../plugins/enigma/tests/Enigma.php</file>
+ <file>./../plugins/example_addressbook/tests/ExampleAddressbook.php</file>
+ <file>./../plugins/filesystem_attachments/tests/FilesystemAttachments.php</file>
+ <file>./../plugins/help/tests/Help.php</file>
+ <file>./../plugins/hide_blockquote/tests/HideBlockquote.php</file>
+ <file>./../plugins/http_authentication/tests/HttpAuthentication.php</file>
+ <file>./../plugins/identity_select/tests/IdentitySelect.php</file>
+ <file>./../plugins/jqueryui/tests/Jqueryui.php</file>
+ <file>./../plugins/managesieve/tests/Managesieve.php</file>
+ <file>./../plugins/managesieve/tests/Parser.php</file>
+ <file>./../plugins/managesieve/tests/Tokenizer.php</file>
+ <file>./../plugins/markasjunk/tests/Markasjunk.php</file>
+ <file>./../plugins/new_user_dialog/tests/NewUserDialog.php</file>
+ <file>./../plugins/new_user_identity/tests/NewUserIdentity.php</file>
+ <file>./../plugins/newmail_notifier/tests/NewmailNotifier.php</file>
+ <file>./../plugins/password/tests/Password.php</file>
+ <file>./../plugins/redundant_attachments/tests/RedundantAttachments.php</file>
+ <file>./../plugins/show_additional_headers/tests/ShowAdditionalHeaders.php</file>
+ <file>./../plugins/squirrelmail_usercopy/tests/Squirrelmail_usercopy.php</file>
+ <file>./../plugins/subscriptions_option/tests/SubscriptionsOption.php</file>
+ <file>./../plugins/userinfo/tests/Userinfo.php</file>
+ <file>./../plugins/vcard_attachments/tests/VcardAttachments.php</file>
+ <file>./../plugins/virtuser_file/tests/VirtuserFile.php</file>
+ <file>./../plugins/virtuser_query/tests/VirtuserQuery.php</file>
+ <file>./../plugins/zipdownload/tests/Zipdownload.php</file>
+ </testsuite>
+ </testsuites>
+</phpunit>
diff --git a/tests/src/BID-26800.txt b/tests/src/BID-26800.txt
new file mode 100644
index 000000000..e4e2fe795
--- /dev/null
+++ b/tests/src/BID-26800.txt
@@ -0,0 +1,53 @@
+<html>
+<head>
+</head>
+<body>
+<h1>1 test</h1>
+<p>&lt;style&gt; block</p>
+<style>input { left:expression( alert(&#039;expression!&#039;) ) }</style>
+<style>div { background:url(alert(&#039;URL!&#039;) ) }</style>
+
+<h1>2 test</h1>
+<p>&lt;div&gt; block</p>
+<div style="font-style:italic">valid css</div>
+<div style="color:red; background:url('//somedomain.com/somepath/somefile.png')">
+<div style="{ left:expression( alert(&#039;expression!&#039;) ) }">
+<div style="{ background:url( alert(&#039;URL!&#039;) ) }">
+
+<h1>3 test</h1>
+<p>Inject comment text</p>
+<div style="{ left:exp/* */ression( alert(&#039;xss3&#039;) ) }">
+<div style=" background:u/* */rl( alert(&#039;xssurl3&#039;) ) ">
+
+<h1>4 test</h1>
+<p>Using reverse solid to directe the codepoint</p>
+<div style="{ left:\0065\0078pression( alert(&#039;xss4&#039;) ) }">
+<div style="{ background:\0075rl( alert(&#039;xssurl4&#039;) ) }">
+
+<h1>5 test</h1>
+<p>Character entity references</p>
+<p>Character entity references is acceptable in "inline styles"</p>
+<div style="{ left:&#x0065;xpression( alert(&#039;xss&#039;) ) }">
+<div style="{ left:&#101;xpression( alert(&#039;xss&#039;) ) }">
+<div style="{ background:&#x0075;rl( alert(&#039;URL!&#039;) ) }">
+<div style="{ background:&#117;rl( alert(&#039;URL!&#039;) ) }">
+<div style="{ left:&#x0065xpression( alert(&#039;xss&#039;) ) }">
+
+<div style="{ left:..p.....o.( alert(&#039;xss&#039;) ) }">
+<div style="{ left:..&#x2f;**/pression( alert(&#039;xss&#039;) ) }">
+<div style="{ left:exp&#x0280;essio&#x0274;( alert(&#039;xss&#039;) ) }">
+<div style="{ left:&#x5c;0065&#x5c;0078pression( alert(&#039;xss&#039;) ) }">
+<div style="{ left:ex p ression( alert(&#039;xss&#039;) ) }">
+
+<div style="{ background:...( javascript:alert(&#039;xss&#039;) ) }">
+<div style="{ background:&#x0075;/**/rl( javascript:alert(&#039;xss&#039;) ) }">
+<div style="{ background:\0075\0072\006c( javascript:alert(&#039;xss&#039;) ) }">
+<div style="{ background:u&#x0280;&#x029F;( javascript:alert(&#039;xss&#039;) )
+}">
+<div style="{ background:&#x5c;0075&#x5c;0280l( javascript:alert(&#039;xss&#039;)
+) }">
+<div style="{ background:u r l( javascript:alert(&#039;xss&#039;) ) }">
+
+</body>
+</html>
+
diff --git a/tests/src/Csv2vcard/email.csv b/tests/src/Csv2vcard/email.csv
new file mode 100644
index 000000000..1556d9142
--- /dev/null
+++ b/tests/src/Csv2vcard/email.csv
@@ -0,0 +1,5 @@
+Primary Email
+test1@domain.tld
+test2@domain.tld
+test3@domain.tld
+test4@domain.tld
diff --git a/tests/src/Csv2vcard/email.vcf b/tests/src/Csv2vcard/email.vcf
new file mode 100644
index 000000000..69912a639
--- /dev/null
+++ b/tests/src/Csv2vcard/email.vcf
@@ -0,0 +1,20 @@
+BEGIN:VCARD
+VERSION:3.0
+FN:test1@domain.tld
+EMAIL;TYPE=INTERNET;TYPE=PREF:test1@domain.tld
+END:VCARD
+BEGIN:VCARD
+VERSION:3.0
+FN:test2@domain.tld
+EMAIL;TYPE=INTERNET;TYPE=PREF:test2@domain.tld
+END:VCARD
+BEGIN:VCARD
+VERSION:3.0
+FN:test3@domain.tld
+EMAIL;TYPE=INTERNET;TYPE=PREF:test3@domain.tld
+END:VCARD
+BEGIN:VCARD
+VERSION:3.0
+FN:test4@domain.tld
+EMAIL;TYPE=INTERNET;TYPE=PREF:test4@domain.tld
+END:VCARD
diff --git a/tests/src/Csv2vcard/tb_plain.csv b/tests/src/Csv2vcard/tb_plain.csv
new file mode 100644
index 000000000..4c4af14ca
--- /dev/null
+++ b/tests/src/Csv2vcard/tb_plain.csv
@@ -0,0 +1,2 @@
+First Name,Last Name,Display Name,Nickname,Primary Email,Secondary Email,Screen Name,Work Phone,Home Phone,Fax Number,Pager Number,Mobile Number,Home Address,Home Address 2,Home City,Home State,Home ZipCode,Home Country,Work Address,Work Address 2,Work City,Work State,Work ZipCode,Work Country,Job Title,Department,Organization,Web Page 1,Web Page 2,Birth Year,Birth Month,Birth Day,Custom 1,Custom 2,Custom 3,Custom 4,Notes,
+Firstname,Lastname,Displayname,Nick,test@domain.tld,next@domain.tld,,phone work,phone home,fax,pager,mobile,Priv address,,City,region,xx-xxx,USA,Addr work,,Wcity,Wstate,33-333,Poland,title,department,Organization,http://page.com,http://webpage.tld,1970,11,15,,,,,,
diff --git a/tests/src/Csv2vcard/tb_plain.vcf b/tests/src/Csv2vcard/tb_plain.vcf
new file mode 100644
index 000000000..2aa91adf8
--- /dev/null
+++ b/tests/src/Csv2vcard/tb_plain.vcf
@@ -0,0 +1,20 @@
+BEGIN:VCARD
+VERSION:3.0
+FN:Displayname
+N:Lastname;Firstname;;;
+NICKNAME:Nick
+EMAIL;TYPE=INTERNET;TYPE=PREF:test@domain.tld
+EMAIL;TYPE=INTERNET;TYPE=OTHER:next@domain.tld
+TEL;TYPE=work:phone work
+TEL;TYPE=home:phone home
+TEL;TYPE=fax:fax
+TEL;TYPE=cell:mobile
+TITLE:title
+X-DEPARTMENT:department
+ORG:Organization
+URL;TYPE=homepage:http://page.com
+URL;TYPE=other:http://webpage.tld
+BDAY;VALUE=date:1970-11-15
+ADR;TYPE=home:;;Priv address;City;region;xx-xxx;USA
+ADR;TYPE=work:;;Addr work;Wcity;Wstate;33-333;Poland
+END:VCARD
diff --git a/tests/src/apple.vcf b/tests/src/apple.vcf
new file mode 100644
index 000000000..856eaf328
--- /dev/null
+++ b/tests/src/apple.vcf
@@ -0,0 +1,49 @@
+BEGIN:VCARD
+VERSION:3.0
+N:;;;;
+FN:Apple Computer AG
+ORG:Apple Computer AG;
+item1.ADR;type=WORK;type=pref:;;Birgistrasse 4a;Wallisellen-Zürich;;8304;Switzerland
+item1.X-ABADR:ch
+item2.URL;type=pref:http\://www.apple.ch
+item2.X-ABLabel:_$!<HomePage>!$_
+PHOTO;BASE64:
+ /9j/4AAQSkZJRgABAQAAAQABAAD/7QAcUGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAD/2wBDAAEB
+ AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB
+ AQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB
+ AQEBAQEBAQEBAQEBAQEBAQH/wAARCAAwADADAREAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAA
+ AAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEI
+ I0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlq
+ c3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW
+ 19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL
+ /8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLR
+ ChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOE
+ hYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn
+ 6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD+/igAoAKAPmH43ftT+CfgzqNt4bNjeeLvGV2IHXw7
+ pVxDbLZx3LBbdtU1GVLhbN7jIMFvHa3VzIpWRoY4mWQ9dDCTrrmuoQ/mavfvZXV7dW2jkr4ynQfL
+ Zzn1inZL1lZ6+ST87H0lp1zLe6fY3k9s1nNd2dtczWjv5j2ss8KSyWzybU3tA7mJn2JuKk7Vzgcr
+ Vm1e9m1fvrv8zqi+aKbVm0nbtdXt8i5SGFAHHeOfH3hH4b6DP4k8Z61a6JpUBCCW4LPNdTsCUtbK
+ 1iD3F5dSYO2C3jd8AuwVFZhdOnOrLlhFyf4Lzb6IzqVYUo81SSiundvslu3/AEz5i0n9u74Fanqo
+ 064m8UaNbvII49X1TRUGnnORvmFje3t5BGTjDtatwcyCPBrreArpX9xvqlJ3/FJP7zlWYUHKzVRL
+ +ZxVvnaTa+4+QLvVPgJ4U+LutfFXx78QJfi3q954iuvEOieHvBmkyzaVbO1x5ulPruq6pLawTvp1
+ uLdIrK08xFmgXzl2oI67LV50o0qdP2K5VGUptKXnyxi29XfVtb6HDehGtKpUm6zcnNKCfK9brnlK
+ 3pZJ37pH3/8ACj9qb4T/ABd1FdD0TUr3SPETozwaJ4ht47C5vQgLONOnjnuLS8dVBYwJOt1tDMIN
+ oLV51XCVqK5pJSj1cW3b1uk1vvqvM9Kji6VZ8qbjJ7KVlf0abTf4n0dXMdQUAfhf+2J8UNW8ffFz
+ W9Cnlki0XwDqOp+GdNsFdhB9qsr6a31DUDHu2tcXbwojSEbhFCka4Uc+9hKUadGMl8VRKcn11V0v
+ RJng4urKpWkntTlKCXTRtN+re58n11HKFAH6PfsGfBvQfEl3rHxR8Q2y38vhrUrfT/DNpIT5FvqY
+ iF1carIgI8ye2R4Y7RXykbySTbS6xlfOx9aUVGlF2503N+W1vnrc9HAUYzlKrJX5GuVf3t7+dvu3
+ vc/WKvIPXCgD8FP2s/BF/wCC/jd4wlu0It/Fmp6h4usJf4JINZ1G7ndVbu0UpZZBztY446V9BhZq
+ dCnZ/DFQfrFJHz+Kg4V6l/tSc15qTb/rzPmqug5woA++v2J/j74d+HN9rHgLxndrpmjeJr62vtJ1
+ mbP2Sw1dY/s0ltfOM+Rb30Yh8u5ZTHFPEFlZEk3Dgx2HlVUZw1lBNOPVp9vNa6X22137sFiI0ZSh
+ N2jNpqXSLSe/k+/R+p+w1eMe0FAHxz+2P8DLr4r+CIPEPhy2Nx4y8FJdXVnaxrmfWdGlAk1DSosK
+ WkuozEt5p8fHmTLNADuuQR24KuqU3GTtCpbXtLo32T2b9GcWNw7qwU4q84X06yi9Wl531XfXyPxK
+ ME4nNsYZRciUwG3MbicTh/LMJix5glD/ACGPbv3/AC4zxXtniFvUNJ1XSmjXVNM1DTWmUvCuoWVz
+ ZtKgxloxcRxmRRkZZcgZGTzSTT2afo7hqtz6w/ZB+BV78UPHtl4n1eykHgfwdewajfXE0bCDVtVt
+ mE+n6PAzAJOBOkdxqAUssdsnlSDNwoPLi66pU3FP95NNJdk95P06d35XOvCUHWqJtfu4NOT7vdR8
+ 7vfsvVH7gV4R7oUAFAHKReA/A8Gsy+IofBnhSHxBO7ST67F4d0iPWZnb7zy6mlmL2R2/iZ5yT3NX
+ 7Spbl9pPl/l55W+69iPZUubm9nDmvfm5I81+97XuX9a8MeGvEtsLLxF4e0PX7MMGFprWk2Gq2wZe
+ jCC+t54tw7HZkdqUZzg7wlKL7xk4v700OUIT0lCMl2lFS/NMuaXpOlaHZQ6Zoumafo+nW4It9P0u
+ yttPsoATkiG1tI4oIgTyQkagnmk5Sk7ybk+7bb+96jjGMVaMVFdopJfcjQpDP//Z
+X-ABShowAs:COMPANY
+X-ABUID:2E4CB084-4767-4C85-BBCA-805B1DCB1C8E\:ABPerson
+END:VCARD
diff --git a/tests/src/format-flowed-unfolded.txt b/tests/src/format-flowed-unfolded.txt
new file mode 100644
index 000000000..0af9b7130
--- /dev/null
+++ b/tests/src/format-flowed-unfolded.txt
@@ -0,0 +1,19 @@
+I'm replying on this with a very long line which is then wrapped and space-stuffed because the draft is saved as format=flowed.
+From what's specified in RFC 2646 some lines need to be space-stuffed to avoid muning during transport.
+
+X
+
+On XX.YY.YYYY Y:YY, Somebody wrote:
+> This part is a reply wihtout any flowing lines. rcube_mime::unfold_flowed()
+>> has to be careful with empty quoted lines because they might end with a
+> space but still shouldn't be considered as flowed!
+>
+> The above empty line should persist after unfolding.
+> xxxxxxxxxx. xxxx xxxxx xxxxx xxxx xx xx.xx. xxxxxx xxxxxxxxxxxx, xxxx xx
+>
+> ... and this one as well.
+
+> > text
+
+--
+Sig
diff --git a/tests/src/format-flowed.txt b/tests/src/format-flowed.txt
new file mode 100644
index 000000000..da36064e0
--- /dev/null
+++ b/tests/src/format-flowed.txt
@@ -0,0 +1,21 @@
+I'm replying on this with a very long line which is then wrapped and
+space-stuffed because the draft is saved as format=flowed.
+ From what's specified in RFC 2646 some lines need to be space-stuffed to avoid
+muning during transport.
+
+X
+
+On XX.YY.YYYY Y:YY, Somebody wrote:
+> This part is a reply wihtout any flowing lines. rcube_mime::unfold_flowed()
+>> has to be careful with empty quoted lines because they might end with a
+> space but still shouldn't be considered as flowed!
+>
+> The above empty line should persist after unfolding.
+> xxxxxxxxxx. xxxx xxxxx xxxxx xxxx xx xx.xx. xxxxxx xxxxxxxxxxxx, xxxx xx
+>
+> ... and this one as well.
+
+> > text
+
+--
+Sig
diff --git a/tests/src/htmlbase.txt b/tests/src/htmlbase.txt
new file mode 100644
index 000000000..eb590748b
--- /dev/null
+++ b/tests/src/htmlbase.txt
@@ -0,0 +1,12 @@
+<html>
+<head>
+<base href="http://alec.pl/dir/" />
+</head>
+<body>
+<img src="img1.gif" />
+<img src="./img2.gif" />
+<img src="../img3.gif" />
+<img src="cid:theCID" />
+<img src="http://other.domain.tld/img3.gif" />
+</body>
+</html>
diff --git a/tests/src/htmlbody.txt b/tests/src/htmlbody.txt
new file mode 100644
index 000000000..66286e61d
--- /dev/null
+++ b/tests/src/htmlbody.txt
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
+<title>Roundcube Test Message</title>
+<link rel="stylesheet" type="text/css" href="http://anysite.net/styles/mail.css">
+<style type="text/css">
+
+p, a {
+ font-family: Arial, 'Bitstream Vera Sans', Helvetica;
+ margin-top: 0px;
+ margin-bottom: 0px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+}
+
+</style>
+</head>
+<body style="margin: 0 0 0 0;">
+
+<table width="100%" cellpadding="0" cellspacing="20" style="background-image:url(http://evilsite.net/newsletter/image/bg/bg-64.jpg);background-attachment:fixed;" background="http://evilsite.net/newsletter/image/bg/bg-64.jpg" border="0">
+<tr>
+<td>
+
+<h1>This is a HTML message</h1>
+
+<p>See nice pictures like the following:</p>
+
+<div>
+ <img src="ex1.jpg" width="320" height="320" alt="Example 1">
+ <img src="ex2.jpg" width="320" height="320" alt="Example 2">
+ <img src="http://evilsite.net/mailings/ex3.jpg" width="320" height="320" alt="Example 3">
+</div>
+
+<form action="http://evilsite.net/subscribe.php">
+ <p>Subscription form</p>
+
+ E-Mail: <input type="text" name="mail" value=""><br/>
+ <input type="submit" value="Subscribe">
+
+</form>
+
+<p>To unsubscribe click here <a href="http://evilsite.net/unsubscribe.php?mail=foo@bar.com"> or
+ send a mail to <a href="mailto:unsubscribe@evilsite.net">unsubscribe@evilsite.net</a></p>
+
+</td>
+</tr>
+</table>
+
+</body>
+</html> \ No newline at end of file
diff --git a/tests/src/htmlcom.txt b/tests/src/htmlcom.txt
new file mode 100644
index 000000000..b3eb1de79
--- /dev/null
+++ b/tests/src/htmlcom.txt
@@ -0,0 +1,16 @@
+<html>
+<head>
+<title>Roundcube Test Message</title>
+</head>
+<body>
+
+<!--REF>
+<p>test1</p>
+<!--DEREF>
+
+<!--[if gte mso 9]><xml>
+ <p>test2</p>
+</xml><![endif]-->
+
+</body>
+</html> \ No newline at end of file
diff --git a/tests/src/htmlxss.txt b/tests/src/htmlxss.txt
new file mode 100644
index 000000000..f6c43e353
--- /dev/null
+++ b/tests/src/htmlxss.txt
@@ -0,0 +1,22 @@
+<html>
+<body>
+
+<p><img onLoad.="alert(document.cookie)" src="skins/default/images/roundcube_logo.png" /></p>
+
+<p><a href="mailto:xss@somehost.net') && alert(document.cookie) || ignore('">mail me!</a>
+<a href="http://roundcube.net" target="_self">roundcube.net</a>
+<a href="http://roundcube.net" \onmouseover="alert('XSS')">roundcube.net (2)</a>
+
+</p>
+
+<div>Brilliant!</div>
+
+<table><tbody><tr><td background="javascript:alert('XSS')">BBBBBB</td></tr></tbody></table>
+
+<p>
+Have a nice Christmas time.<br />
+Thomas
+</p>
+
+</body>
+</html>
diff --git a/tests/src/imap_thread.txt b/tests/src/imap_thread.txt
new file mode 100644
index 000000000..88e0a8a66
--- /dev/null
+++ b/tests/src/imap_thread.txt
@@ -0,0 +1 @@
+* THREAD (1)(2)(14)(3)(4 18 39 100)((5)(6)(8 (11)(13 15)(465))(209))(7 (12)(167 197)(458 (460 463)(464 471)))((9)(10 1057))((16 32)(33))(17)(48)(22 23)((19)(314))(20 21)(26)((24 27 1598)(1578)(1580)(1591)(1592)(1593)(1596))((25)(160)(329)(334)(355)(383)(847)(910))(28 30 31)(29)(34 35 36)(37)(38 (42 (72)(96))(107)(106))(40)(41)((43)(330 (336 (366)(367)(380))(1116)(1150)))(45)(44 49 52 55 56 60 61 62 65 66 68 69 70 71 90 91 93 94 95 103 108 109 111 120 123 125 126 129 130 132 134 136 138)(46)((47)(1117)(1122))(50 58 (77)(110 113 117 122 145))(51)(53)(54 57 64 84 87 88 89 92)(59 99)(63)(67)((73)(133)(141))(74 (98)(205))(75 (101)(200)(202))(76 (82)(201)(203))((78)(504)(534)(535))(79)(80)(81)(83)(85)(86)(97)(114)(115)(102 318 (319 320)(332))(104 (466)(468))(105)(112 118 135 144 146)(116)(119)(147)(121)(124)(127)(128)(131)(137)(139)(148)((140)(384)(682)(713)(881)(967)(991)(996)(1010)(1028)(1080)(1088)(1634))(149)(142 143)(150 151)(152)(153 155 161)(154)(168)(156 157)((158)(159)(163)(164))(162)((165)(361)(362))(166 (229)(232)(240 242 247 292))(169 (172)(177)(178)(179 1058))(170)(171)(173)(174 425 431 476 510 511)(188)(175 176)(180 (182 183)(185)(184 1068))((181)(419)(418)(705)(992)(1123)(1374)(1594))(186 (204)(226)(225))((187)(422)(708))(189)(190)(191)(192)(193 215 246)(194 199 218 245 290)(195)(196)(198)(206)(207)(208)(210 211)((212)(213))(214)(216)(217 (219 (220)(221)(227))(223)(224)(222 (1054)(1055)))(234)(228)(230 235 236)(231)(233 772 1566)(237)(238 (244)(251)(257)(258 1061))(239)(241 (590)(873)(874)(900))(243)(248)(249)(250)(252)((253)(255)(338)(339)(340)(341)(436)(437)(438)(439)(441)(442)(447)(450)(512)(513)(514)(597)(598)(601)(604)(605)(635)(646)(649)(652))(254 313)(256)(296)((259)(260)(359)(358))(261)(262)(263)((264)(803)(804))(265)(266 (268)(283))(267)(269)(270)((271)(272))((273)(842)(844))(274 289 303)(275)(277)(276)(278)(279)(280)(281)(282)((284)(433))((285)(595))(286)(295)(287)(288)(291 310)(293)(294)(297 299)((298)(574)(777)(1083)(1161)(1440)(1711))(300)(301 348 356 357 360 365 392)(302 (304)(305)(323)(324 1059))(306)(308)(307)(309 364)(311)((312)(573))(315)((317)(316))((344)(326))(321)(322)(342)(325)(345)(353 354 (698)(707))(343)(327 328 331)(333)((335)(400))(337)(346 (350)(370)(371)(372 1060))(347)(349 388 524 566 577 690 1491)(351)(352)(363)(368)(369)(390)(373)((374)(376))(375)(377)(378 589)(379)(381)((382)(588)(592)(1496))(385)(386)(387 399 426)(389 397 427)(391)(393)(394)(407)(395 396)(398 408)(401 (402 403)(404 (405 (410)(415))(406 (416)(443)(638)(1291)(1522))(413 414 (448 (449)(451)(478))(484)(485 (477)(515 (527)(554))(1521)))))(409)(411)(412)(428)((417)(480)(482))(420 (421)(564)(565))((423)(808))(424)(429)(430 446)(432)(434)(435)(440 445 (454 470)(456))((444)(555 (763)(764)))(452 (453)(459)(461)(462)(473)(475 1063))(455 457)(518)(467)(469 474 (483)(539)(540))(472)(479 486 525 528 530)(481 (487)(489)(506)(507 1062))(488)(490 (491)(492)(496)(498)(500)(502)(509)(521)(946)(1404)(1681)(1682)(1683)(1685)(1686)(1693)(1721))(494)(493)((495)(1079)(1401)(1582))(497)(499)(501)(503)((505)(517)(520))(508 523)(516)(519)(522)(526 529)(531)(532 538 582)(533)(536)(537)(541 614)((542)(543)(546)(547)(572))(544 545)(548)(549 (550)(551)(553)(552 1056))(556 (559)(562)(563))(557 (558)(561)(560))((567)(1502))(569)(568)(570)((571)(749)(751)(752)(757)(760)(776)(775)(1034)(1035)(1076))(575)(576 579)(578)(580)(581)(583)(584)(585)(586)(587)(591)(593)(594)(624)(596)(625)(628)(599 (600)(603 617)(619)(620)(621 1064))(602 (613 (615)(618))(616 (622)(675)))(606 607 608 609 (610 612)(629))(611)(623)(627)(626)(630)(631)(632)(633)((634)(650)(651))(636 (641 677)(848)(1595 1597))(637)(642 644 645)(639 640)(643)(669)(647)(648)(653)(654)(655)(656 (657)(659)(660 1065))(658 (661 665)(662))(663 664 666)(667)(668)((670)(1008))(671 686)(674)(672)(673)(688)(676)(678)(679 681)(680)(683)(684 689)(685)(687)(691)(692)(693)(699)((694)(704))(695)(696 715 853 899)(697)(700 (701)(710)(720)(721 1066))(702)(703)(709)(706)(711)(712)(714)(716 734)(717)(718 724 (727)(728 730 732 733 735))(719 723 731)(722 729)(725)(726)(736)(738)((737)(1620))(754)(739)(740 (741)(742 (743)(745)(746)(761))(744)(765 (766)(767)))(747)(748)(753)(750)(755)(756)(758)(759)(762)(768)(769)(770)(774)(771)(773)(779)(778)(780)(781)(782)(783)(784 786 787 788 790 791 793 795 796 798 802 805 809 815 (816)(817 818 821 822 824 825 827 829 830 831 832 833 834 836 838 839 840 841 854 856 862 863 879 882 884 885 886 888 890 892 894 895 896 897 898 908 912 924 926 941 943 947 948 952 956 957 958 959 960 961 962 963 964 965 970 971 973 974 975 976 978 979 980 981 982 984 985 986 987 989 993 994 995 997 1000 1002 1003 1007 1009 1011 1013 1014 1015 1016 1018 1020 1021 1022 1023 1027 1029 1031 1033 1040 1041 1043 1044 1045 1047 1048 1049 1052 1053))(785)(789 792)(811)(794)(812)(797)(799)(800)(801)(806)(807)(810 813)(823)(814)(872)(819 (820)(826)(843 845)(916 (921)(923)(922 1067)))(828)(866)(837)(835)((868)(867)(869))(846)(871)(870)(849)(850 (883)(903 (938)(1515)))(851)(852)((855)(857))(858)(859 (928)(933)(935))(860 (929)(931)(934))(861 (930)(932)(936))(864 1469)(865 889 940)(875)(876 (877)(878)(901)(966))(880 939)(905)(887)(891)(893)(902)(904)(907)(906 1102)(909 969)(911)(913 (914 915)(917 918)(919 937)(920 927 942 944 945 1051 (1073 1081)(1103 1106 1125)))(925)(949)(950)(951)(953)(954)(955)(972)(968 (1113)(1147 (1114)(1115)(1148)))(977)(983)(998)(988 1104)(990)(1036)(999 1019)(1004)(1001)(1005 1006 (1279)(1285 1290))(1012)(1017)(1038)(1037)(1024 (1025)(1096)(1097)(1403))(1026)(1030)(1032)(1039)(1042)(1046)(1050)(1084)((1069)(1070))(1071)(1072)(1074)(1075 1078)(1077)(1082)(1085)((1086)(1091))(1087)(1089)(1090)(1093)(1092)(1094)(1095)(1098)(1099)(1100)((1107)(1204))(1101)(1108)(1105 (1152 1395)(1605))((1112 1215 1228)(1231 1233)(1230))(1109)(1110)(1111)(1118)(1119)(1149 (1124 1146 1151)(1218 1219)(1220)(1474))(1120)(1121)(1126)(1127)(1128 1136)(1129)(1130)(1140)(1131)(1132)(1133)(1134 1135)(1137 1138 1203)(1139)(1141)(1142)(1143)(1144)(1145)(1153)(1154)(1155)(1156)(1157)(1158)(1205)((1159)(1160))(1207)((1162)(1481))(1206)(1163)(1164)(1165)(1211 (1241 1366)(1367)(1368 1369))(1166 (1183)(1238 (1239)(1303)(1300)))(1167 (1184)(1334)(1310))(1168 (1185)(1296)(1341))(1169 (1186)(1308)(1337))(1170 (1187)(1240 (1242)(1306)(1298)))(1171 (1188)(1295)(1336))(1172 (1189)(1343)(1339))(1173 (1190)(1299)(1333))(1174 (1191)(1309)(1312))(1175 (1198)(1307)(1332))(1176 (1192)(1335)(1311))(1177 (1193)(1301)(1342))(1178 (1194)(1305)(1314))(1179 (1195)(1302)(1313))(1180 (1196)(1340)(1297))(1181 (1182)(1197)(1304)(1338))(1199)(1200 1201)(1202 1225 1365)(1208)(1209)(1210)(1212 (1216 1236)(1217 1280))(1213)(1221)(1214)(1222)(1223)(1224)(1226)(1227)(1229)(1232 1234 1235)(1282)(1237)(1243 (1259)(1315))(1244 (1260)(1316))(1245 (1261)(1317))(1246 (1262)(1318))(1247 (1263)(1319))(1248 (1264)(1320))(1249 (1265)(1321))(1250 (1266)(1322))(1251 (1268)(1323))(1252 (1269)(1324))(1253 (1270)(1325))(1254 (1271)(1326))(1255 (1272)(1327))(1256 (1273)(1328))(1257 (1274)(1329))(1258 (1267 1276 1277 1278 1331)(1275)(1330))(1281)(1283)(1284)(1370)(1286)((1287)(1288)(1289)(1375)(1377)(1376)(1378)(1380)(1386)(1387))((1292)(1293)(1294)(1381)(1382)(1383)(1384)(1385))(1344 (1354)(1453)(1461))(1346 (1355)(1455)(1472))(1345 (1356)(1392 (1393 1424)(1458)(1459)))(1347 (1357)(1468)(1473))(1348 (1358)(1426 (1431)(1467)(1462)))(1349 (1359)(1465)(1470))(1350 (1360 1362)(1428 (1432)(1456)(1460)))(1351 (1361)(1466)(1463))(1352 (1363)(1430 (1433)(1454)(1464)))(1353 (1364)(1457)(1471))(1371 (1425 1483)(1450 1484 1489))(1372)(1373)(1379)(1388)(1389)(1390)(1391)((1394 1397 1398)(1421))(1396 1400)(1399)(1402)(1405)(1406)(1407 1410)(1408 (1579)(1583 1584 (1585)(1586)))(1409)(1411)(1412)(1413)(1416)(1414)(1415)(1417)(1418)(1419)(1420)(1422)((1442)(1427))(1423)(1429)(1434 (1435)(1451)(1452))(1436)(1439)(1441)(1437)(1438 1495 (1559)(1561))(1443)(1444)(1445)(1446)(1507)(1447)(1448)(1449)(1506)(1475)(1476)(1505)((1477)(1478))((1479)(1480))(1482)(1485)(1486)(1487)(1488)(1490)(1492)(1493)(1494)(1497 (1498)(1644)(1645))(1499)(1500)(1501 1704)(1503)(1504)(1508)(1509)(1510)(1519 (1520)(1523))(1511 1512 1513 1516)(1562)(1514)(1517)(1518)(1524 (1540)(1647)(1666))(1525 (1541)(1656)(1646))(1526 (1542 1643)(1573))(1527 (1543)(1574 1641))(1528 (1544)(1650)(1663))(1529 (1545)(1654)(1649))(1530 1546 1575)(1531 (1547)(1651)(1653))(1532 (1548)(1658)(1672))(1533 (1549)(1665)(1662))(1534 (1550 1603)(1576))(1535 (1551)(1648)(1667))(1536 (1552)(1652)(1669))(1537 (1553)(1655)(1668))(1538 (1554 1604)(1577 (1640)(1657)(1670)))(1539 (1555)(1661)(1671))(1556)(1557)(1558)(1560 1567)(1563)(1564)(1565)(1587)(1568 (1569)(1570)(1581))(1571)(1572 (1589 (1612)(1613)(1617))(1599 1600)(1601)(1602))(1588)(1590)(1606)(1607 1608 (1610)(1629))(1609 (1614)(1618)(1619))(1611)(1615)(1616)(1621 (1622)(1624)(1623 1627)(1628 1632 (1637 1673 1675 1678)(1664)))(1625 1626)(1630)((1631 1635)(1633))(1636 1638)(1639)(1642 (1659)(1660 1679)(1674 1680))(1676)(1677 1684)(1687)(1720)(1689)(1688)(1690 1697)(1691 1694)(1692)(1695)(1696)(1713)(1698)(1699)(1700)(1701 1707)(1702)(1703)(1705 1706)(1708)(1709)(1710)(1712)(1714)(1715)(1716)(1717)(1718)(1719) \ No newline at end of file
diff --git a/tests/src/invalidchars.html b/tests/src/invalidchars.html
new file mode 100644
index 000000000..bd460eab1
--- /dev/null
+++ b/tests/src/invalidchars.html
@@ -0,0 +1 @@
+<p>символ</p>
diff --git a/tests/src/johndoe.vcf b/tests/src/johndoe.vcf
new file mode 100644
index 000000000..386afaf57
--- /dev/null
+++ b/tests/src/johndoe.vcf
@@ -0,0 +1,12 @@
+BEGIN:VCARD
+VERSION:2.1
+N;CHARSET=windows-1252:Do;John;;;
+FN;CHARSET=windows-1252:John Do
+ORG:roundcube.net;
+EMAIL;INTERNET;WORK:inbox@roundcube.net
+EMAIL;INTERNET;HOME;TYPE=pref:roundcube@gmail.com
+TEL;WORK:+123456789
+TEL;CELL:+987654321
+ADR;WORK:;;The street;Hometown;;5555;Cayman Islands
+NOTE:The notes...
+END:VCARD
diff --git a/tests/src/mailto.txt b/tests/src/mailto.txt
new file mode 100644
index 000000000..e70b12de8
--- /dev/null
+++ b/tests/src/mailto.txt
@@ -0,0 +1,8 @@
+<html>
+<head></head>
+<body>
+
+<a href="mailto:me@me.com?subject=this is the subject&body=this is the body">e-mail</a>
+
+</body>
+</html> \ No newline at end of file
diff --git a/tests/src/media.css b/tests/src/media.css
new file mode 100644
index 000000000..24eacc8a1
--- /dev/null
+++ b/tests/src/media.css
@@ -0,0 +1,22 @@
+.ReadMsgBody{width: 100%;}
+.ExternalClass{width: 100%;}
+div, p, a, li, td { -webkit-text-size-adjust:none; }
+@media (max-width: 450px){
+ table[class=w600], td[class=w600], table[class=w540], td[class=w540], img[class=w600]{ width:100% !important; }
+ table[class=w30], td[class=w30]{ width:20px !important; }
+ .pict img {max-width:260px; height:auto !important;}
+}
+@media (min-width: 450px) and (max-width: 600px){
+ table[class=w600], td[class=w600], table[class=w540], td[class=w540], img[class=w600]{ width:100% !important; }
+ table[class=w30], td[class=w30]{ width:20px !important; }
+ .pict img {max-width:410px; height:auto !important;}
+}
+@media (min-width:600px){
+ body {width:600px !important; margin:auto !important;}
+ .pict img {max-width:540px !important; height:auto !important;}
+}
+h1{ font-weight:bold; font-size:14px;color:#3c3c3c ;margin:0px; }
+h2{ color:#8DB048 ; font-size:14px; font-weight:bold; margin-top:20px; border-bottom:1px solid #d6d6d6; padding-bottom:4px; }
+h3{ color:#7e7e7e ; font-size:14px; font-weight:bold; margin:20px 0px 0px 0px; border-bottom:1px solid #d6d6d6; padding-bottom:0px 0px 4px 0px; }
+h4{ color:#8DB048 ; font-size:12px; font-weight:bold; margin:0px; padding:0px; }
+a:visited{cursor:pointer; color:#8DB048; text-decoration:none; border:none;}
diff --git a/tests/src/photo.vcf b/tests/src/photo.vcf
new file mode 100644
index 000000000..c3a805009
--- /dev/null
+++ b/tests/src/photo.vcf
@@ -0,0 +1,45 @@
+BEGIN:VCARD
+VERSION:3.0
+N:Müller;Jörg;;;
+FN:Apple Computer AG
+ORG:Apple Computer AG;
+PHOTO;ENCODING=b:
+ /9j/4AAQSkZJRgABAQAAAQABAAD/7QAcUGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAD/2wBDAAEB
+ AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB
+ AQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB
+ AQEBAQEBAQEBAQEBAQEBAQH/wAARCAAwADADAREAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAA
+ AAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEI
+ I0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlq
+ c3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW
+ 19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL
+ /8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLR
+ ChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOE
+ hYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn
+ 6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD+/igAoAKAPmH43ftT+CfgzqNt4bNjeeLvGV2IHXw7
+ pVxDbLZx3LBbdtU1GVLhbN7jIMFvHa3VzIpWRoY4mWQ9dDCTrrmuoQ/mavfvZXV7dW2jkr4ynQfL
+ Zzn1inZL1lZ6+ST87H0lp1zLe6fY3k9s1nNd2dtczWjv5j2ss8KSyWzybU3tA7mJn2JuKk7Vzgcr
+ Vm1e9m1fvrv8zqi+aKbVm0nbtdXt8i5SGFAHHeOfH3hH4b6DP4k8Z61a6JpUBCCW4LPNdTsCUtbK
+ 1iD3F5dSYO2C3jd8AuwVFZhdOnOrLlhFyf4Lzb6IzqVYUo81SSiundvslu3/AEz5i0n9u74Fanqo
+ 064m8UaNbvII49X1TRUGnnORvmFje3t5BGTjDtatwcyCPBrreArpX9xvqlJ3/FJP7zlWYUHKzVRL
+ +ZxVvnaTa+4+QLvVPgJ4U+LutfFXx78QJfi3q954iuvEOieHvBmkyzaVbO1x5ulPruq6pLawTvp1
+ uLdIrK08xFmgXzl2oI67LV50o0qdP2K5VGUptKXnyxi29XfVtb6HDehGtKpUm6zcnNKCfK9brnlK
+ 3pZJ37pH3/8ACj9qb4T/ABd1FdD0TUr3SPETozwaJ4ht47C5vQgLONOnjnuLS8dVBYwJOt1tDMIN
+ oLV51XCVqK5pJSj1cW3b1uk1vvqvM9Kji6VZ8qbjJ7KVlf0abTf4n0dXMdQUAfhf+2J8UNW8ffFz
+ W9Cnlki0XwDqOp+GdNsFdhB9qsr6a31DUDHu2tcXbwojSEbhFCka4Uc+9hKUadGMl8VRKcn11V0v
+ RJng4urKpWkntTlKCXTRtN+re58n11HKFAH6PfsGfBvQfEl3rHxR8Q2y38vhrUrfT/DNpIT5FvqY
+ iF1carIgI8ye2R4Y7RXykbySTbS6xlfOx9aUVGlF2503N+W1vnrc9HAUYzlKrJX5GuVf3t7+dvu3
+ vc/WKvIPXCgD8FP2s/BF/wCC/jd4wlu0It/Fmp6h4usJf4JINZ1G7ndVbu0UpZZBztY446V9BhZq
+ dCnZ/DFQfrFJHz+Kg4V6l/tSc15qTb/rzPmqug5woA++v2J/j74d+HN9rHgLxndrpmjeJr62vtJ1
+ mbP2Sw1dY/s0ltfOM+Rb30Yh8u5ZTHFPEFlZEk3Dgx2HlVUZw1lBNOPVp9vNa6X22137sFiI0ZSh
+ N2jNpqXSLSe/k+/R+p+w1eMe0FAHxz+2P8DLr4r+CIPEPhy2Nx4y8FJdXVnaxrmfWdGlAk1DSosK
+ WkuozEt5p8fHmTLNADuuQR24KuqU3GTtCpbXtLo32T2b9GcWNw7qwU4q84X06yi9Wl531XfXyPxK
+ ME4nNsYZRciUwG3MbicTh/LMJix5glD/ACGPbv3/AC4zxXtniFvUNJ1XSmjXVNM1DTWmUvCuoWVz
+ ZtKgxloxcRxmRRkZZcgZGTzSTT2afo7hqtz6w/ZB+BV78UPHtl4n1eykHgfwdewajfXE0bCDVtVt
+ mE+n6PAzAJOBOkdxqAUssdsnlSDNwoPLi66pU3FP95NNJdk95P06d35XOvCUHWqJtfu4NOT7vdR8
+ 7vfsvVH7gV4R7oUAFAHKReA/A8Gsy+IofBnhSHxBO7ST67F4d0iPWZnb7zy6mlmL2R2/iZ5yT3NX
+ 7Spbl9pPl/l55W+69iPZUubm9nDmvfm5I81+97XuX9a8MeGvEtsLLxF4e0PX7MMGFprWk2Gq2wZe
+ jCC+t54tw7HZkdqUZzg7wlKL7xk4v700OUIT0lCMl2lFS/NMuaXpOlaHZQ6Zoumafo+nW4It9P0u
+ yttPsoATkiG1tI4oIgTyQkagnmk5Sk7ybk+7bb+96jjGMVaMVFdopJfcjQpDP//Z
+X-ABShowAs:COMPANY
+X-ABUID:2E4CB084-4767-4C85-BBCA-805B1DCB1C8E\:ABPerson
+END:VCARD
diff --git a/tests/src/plainbody.txt b/tests/src/plainbody.txt
new file mode 100644
index 000000000..7fba94f86
--- /dev/null
+++ b/tests/src/plainbody.txt
@@ -0,0 +1,38 @@
+From: iPhone Developer Program <noreply-iphonedev@apple.com>
+To: nobody@roundcube.net
+
+*iPhone Developer Program*
+
+-----------------------------------
+iPhone SDK 2.2.1 is now available
+https://daw.apple.com/cgi-bin/WebObjects/DSAuthWeb.woa/wa/login?appIdKey=3D=
+D635F5C417E087A3B9864DAC5D25920C4E9442C9339FA9277951628F0291F620&path=3D//i=
+phone/login.action
+
+Log in to the iPhone Dev Center to download iPhone SDK for iPhone OS 2.2.1.=
+ Installation of iPhone SDK 2.2.1 is required for development with devices =
+updated to iPhone OS 2.2.1. Please view the Read Me before installing the n=
+ew version of the iPhone SDK.
+
+Log in now
+https://daw.apple.com/cgi-bin/WebObjects/DSAuthWeb.woa/wa/login?appIdKey=3D=
+D635F5C417E087A3B9864DAC5D25920C4E9442C9339FA9277951628F0291F620&path=3D//i=
+phone/login.action
+
+-----------------------------------
+Copyright (c) 2009 Apple Inc. 1 Infinite Loop, MS 303-3DM, Cupertino, CA 95=
+014.
+
+All Rights Reserved
+http://www.apple.com/legal/default.html
+
+Keep Informed
+http://www.apple.com/enews/subscribe/
+
+Privacy Policy
+http://www.apple.com/legal/privacy.
+
+My Info
+https://myinfo.apple.com/cgi-bin/WebObjects/MyInfo
+
+-[http://example.com/?tx[a]=5]-
diff --git a/tests/src/thebat.vcf b/tests/src/thebat.vcf
new file mode 100644
index 000000000..8179f788d
--- /dev/null
+++ b/tests/src/thebat.vcf
@@ -0,0 +1,8 @@
+BEGIN:VCARD
+VERSION:2.1
+N;ENCODING=QUOTED-PRINTABLE:Iksi=F1ski;Piotr
+FN;ENCODING=QUOTED-PRINTABLE:Piotr Iksi=F1ski
+EMAIL;PREF;INTERNET:piotr.iksinski@somedomain.com
+X-GENDER:Male
+REV:20080716T203548Z
+END:VCARD
diff --git a/tests/src/utf-16_sample.vcf b/tests/src/utf-16_sample.vcf
new file mode 100755
index 000000000..22f54618a
--- /dev/null
+++ b/tests/src/utf-16_sample.vcf
Binary files differ
diff --git a/tests/src/valid.css b/tests/src/valid.css
new file mode 100644
index 000000000..340fa9a87
--- /dev/null
+++ b/tests/src/valid.css
@@ -0,0 +1,30 @@
+/** Master style definitions **/
+
+body, p, div, h1, h2, h3, textarea {
+ font-family: "Lucida Grande", Helvetica, sans-serif;
+ font-size: 8.8pt;
+ color: #333;
+}
+
+body {
+ background-color: white;
+ margin: 0;
+}
+
+h1 {
+ color: #1F519A;
+ font-size: 1.7em;
+ font-weight: normal;
+ margin-top: 0;
+ margin-bottom: 1em;
+}
+
+.noscript {
+ display: none;
+}
+
+.hint, .username {
+ color: #999;
+}
+
+