From ee7d9bb2c08603a60fa4b4c29dad4d11f849c050 Mon Sep 17 00:00:00 2001
From: Angie Byron
Date: Sun, 28 Jun 2009 12:01:26 +0000
Subject: #276597 by wrwrwr and jhedstrom: Add a ton of totally awesome filter
tests.
---
modules/filter/filter.test | 435 ++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 433 insertions(+), 2 deletions(-)
diff --git a/modules/filter/filter.test b/modules/filter/filter.test
index 53fc98518..09f5b759c 100644
--- a/modules/filter/filter.test
+++ b/modules/filter/filter.test
@@ -197,9 +197,19 @@ class FilterTestCase extends DrupalWebTestCase {
}
/**
- * Test the line break filter
+ * Test the line break filter.
*/
function testLineBreakFilter() {
+
+ // Single line breaks should be changed to tags, while paragraphs
+ // separated with double line breaks should be enclosed with tags.
+ $f = _filter_autop("aaa\nbbb\n\nccc");
+ $this->assertEqual(str_replace("\n", '', $f), "
aaa bbb
ccc
", t('Line breaking basic case.'));
+
+ // Text within some contexts should not be processed.
+ $f = _filter_autop("");
+ $this->assertEqual($f, "", t('Line breaking -- do not break scripts.'));
+
$f = _filter_autop('
');
$this->assertEqual(substr_count($f, '
'), substr_count($f, '
'), t('Make sure line breaking produces matching paragraph tags.'));
@@ -215,10 +225,381 @@ class FilterTestCase extends DrupalWebTestCase {
}
/**
- * Test the HTML filter
+ * Test limiting allowed tags, XSS prevention and adding 'nofollow' to links.
+ * XSS tests assume that script is dissallowed on default and src is allowed on default, but on* and style are dissallowed.
+ *
+ * Script injection vectors mostly adopted from http://ha.ckers.org/xss.html.
+ *
+ * Relevant CVEs:
+ * CVE-2002-1806, ~CVE-2005-0682, ~CVE-2005-2106, CVE-2005-3973,
+ * CVE-2006-1226 (= rev. 1.112?), CVE-2008-0273, CVE-2008-3740.
+ *
*/
function testHtmlFilter() {
+ // Tag stripping, different ways to work around removal of HTML tags.
+ $f = filter_xss('');
+ $this->assertNoNormalized($f, 'script', t('HTML tag stripping -- simple script without special characters.'));
+
+ $f = filter_xss('');
+ $this->assertNoNormalized($f, 'script', t('HTML tag stripping -- empty script with source.'));
+
+ $f = filter_xss('');
+ $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- non whitespace character after tag name.'));
+
+ $f = filter_xss('');
+ $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no space between tag and attribute.'));
+
+ // Null between < and tag name works at least with IE6.
+ $f = filter_xss("<\0scr\0ipt>alert(0)");
+ $this->assertNoNormalized($f, 'ipt', t('HTML tag stripping evasion -- breaking HTML with nulls.'));
+
+ $f = filter_xss("");
+ $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- filter just removing "script".'));
+
+ $f = filter_xss('<');
+ $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- double opening brackets.'));
+
+ $f = filter_xss('', array('img'));
+ $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- a malformed image tag.'));
+
+ $f = filter_xss('', array('blockquote'));
+ $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- script in a blockqoute.'));
+
+ $f = filter_xss("");
+ $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- script within a comment.'));
+
+ // Dangerous attributes removal.
+ $f = filter_xss('
", array('p')); // DRUPAL-SA-2008-006
+ $this->assertNoNormalized($f, 'style', t('HTML filter -- invalid UTF-8.'));
+
+ $f = filter_xss("\xc0aaa");
+ $this->assertEqual($f, '', t('HTML filter -- overlong UTF-8 sequences.'));
+ }
+
+ /**
+ * Test filter settings, defaults, access restrictions and similar.
+ *
+ * TODO: This is for functions like filter_filter and check_markup, whose
+ * functionality is not completely focused on filtering. Some ideas:
+ * restricting formats according to user permissions, proper cache
+ * handling, defaults -- allowed tags/attributes/protocols.
+ *
+ * TODO: It is possible to add script, iframe etc. to allowed tags, but
+ * this makes HTML filter completely ineffective.
+ *
+ * TODO: Class, id, name and xmlns should be added to disallowed attributes,
+ * or better a whitelist approach should be used for that too.
+ */
+ function testFilter() {
+ // Check that access restriction really works.
+
+ // HTML filter is not able to secure some tags, these should never be
+ // allowed.
+ $f = filter_filter('process', 0, 'no_such_format', '');
+ $this->assertNoNormalized($f, 'script', t('HTML filter should always remove script tags.'));
+
+ $f = filter_filter('process', 0, 'no_such_format', '');
+ $this->assertNoNormalized($f, 'iframe', t('HTML filter should always remove iframe tags.'));
+
+ $f = filter_filter('process', 0, 'no_such_format', '');
+ $this->assertNoNormalized($f, 'object', t('HTML filter should always remove object tags.'));
+
+ $f = filter_filter('process', 0, 'no_such_format', '');
+ $this->assertNoNormalized($f, 'style', t('HTML filter should always remove style tags.'));
+
+ // Some tags make CSRF attacks easier, let the user take the risk herself.
+ $f = filter_filter('process', 0, 'no_such_format', '');
+ $this->assertNoNormalized($f, 'img', t('HTML filter should remove img tags on default.'));
+
+ $f = filter_filter('process', 0, 'no_such_format', '');
+ $this->assertNoNormalized($f, 'img', t('HTML filter should remove input tags on default.'));
+ // Filtering content of some attributes is infeasible, these shouldn't be
+ // allowed too.
+ $f = filter_filter('process', 0, 'no_such_format', '
');
+ $this->assertNoNormalized($f, 'style', t('HTML filter should remove style attribute on default.'));
+
+ $f = filter_filter('process', 0, 'no_such_format', '');
+ $this->assertNoNormalized($f, 'onerror', t('HTML filter should remove on* attributes on default.'));
+ }
+
+ /**
+ * Test the spam deterrent.
+ */
+ function testNoFollowFilter() {
+ variable_set('filter_html_nofollow_f', TRUE);
+
+ // Test if the rel="nofollow" attribute is added, even if we try to prevent
+ // it.
+ $f = _filter_html('text', 'f');
+ $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent -- no evasion.'));
+
+ $f = _filter_html('text', 'f');
+ $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- capital A.'));
+
+ $f = _filter_html("text", 'f');
+ $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- non whitespace character after tag name.'));
+
+ $f = _filter_html("<\0a\0 href=\"http://www.example.com/\">text", 'f');
+ $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- some nulls.'));
+
+ $f = _filter_html('', 'f');
+ $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- link within a comment.'));
+ }
+
+ /**
+ * Test the loose, admin HTML filter.
+ */
+ function testAdminHtmlFilter() {
+ // DRUPAL-SA-2008-044
+ $f = filter_xss_admin('');
+ $this->assertNoNormalized($f, 'object', t('Admin HTML filter -- should not allow object tag.'));
+
+ $f = filter_xss_admin('');
+ $this->assertNoNormalized($f, 'script', t('Admin HTML filter -- should not allow script tag.'));
+
+ $f = filter_xss_admin('');
+ $this->assertEqual($f, '', t('Admin HTML filter -- should never allow some tags.'));
+ }
+
+ /**
+ * Test the HTML escaping filter. Here we test only whether check_plain()
+ * does what it should.
+ */
+ function testNoHtmlFilter() {
+ // Test that characters that have special meaning in XML are changed into
+ // entities.
+ $f = check_plain('<>&"');
+ $this->assertEqual($f, '<>&"', t('No HTML filter basic test.'));
+
+ // A single quote can also be used for evil things in some contexts.
+ $f = check_plain('\'');
+ $this->assertEqual($f, ''', t('No HTML filter -- single quote.'));
+
+ // Test that the filter is not fooled by different evasion techniques.
+ $f = check_plain("\xc2\"");
+ $this->assertEqual($f, '', t('No HTML filter -- invalid UTF-8.'));
+ }
+
+ /**
+ * Test the URL filter.
+ */
+ function testUrlFilter() {
+ variable_set('filter_url_length_f', 496);
+
+ // Converting URLs.
+ $f = _filter_url('http://www.example.com/', 'f');
+ $this->assertEqual($f, 'http://www.example.com/', t('Converting URLs.'));
+
+ $f = _filter_url('http://www.example.com/?a=1&b=2', 'f');
+ $this->assertEqual($f, 'http://www.example.com/?a=1&b=2', t('Converting URLs -- ampersands.'));
+
+ $f = _filter_url('ftp://user:pass@ftp.example.com/dir1/dir2', 'f');
+ $this->assertEqual($f, 'ftp://user:pass@ftp.example.com/dir1/dir2', t('Converting URLs -- FTP scheme.'));
+
+ // Converting domain names.
+ $f = _filter_url('www.example.com', 'f');
+ $this->assertEqual($f, 'www.example.com', t('Converting domain names.'));
+
+ $f = _filter_url('
', t('Converting domain names -- domain in a list.'));
+
+ $f = _filter_url('(www.example.com/dir?a=1&b=2#a)', 'f');
+ $this->assertEqual($f, '(www.example.com/dir?a=1&b=2#a)', t('Converting domain names -- domain in parentheses.'));
+
+ // Converting e-mail addresses.
+ $f = _filter_url('johndoe@example.com', 'f');
+ $this->assertEqual($f, 'johndoe@example.com', t('Converting e-mail addresses.'));
+
+ $f = _filter_url('aaa@sub.tv', 'f');
+ $this->assertEqual($f, 'aaa@sub.tv', t('Converting e-mail addresses -- a short e-mail from Tuvalu.'));
+
+ // URL trimming.
+ variable_set('filter_url_length_f', 28);
+
+ $f = _filter_url('http://www.example.com/d/ff.ext?a=1&b=2#a1', 'f');
+ $this->assertNormalized($f, 'http://www.example.com/d/ff....', t('URL trimming.'));
+
+ // Not breaking existing links.
+ $f = _filter_url('www.example.com', 'f');
+ $this->assertEqual($f, 'www.example.com', t('Converting URLs -- do not break existing links.'));
+
+ $f = _filter_url('http://www.example.com', 'f');
+ $this->assertEqual($f, 'http://www.example.com', t('Converting URLs -- do not break existing, relative links.'));
+
+ // Addresses within some tags such as code or script should not be converted.
+ $f = _filter_url('http://www.example.com', 'f');
+ $this->assertEqual($f, 'http://www.example.com', t('Converting URLs -- skip code contents.'));
+
+ $f = _filter_url('http://www.example.com', 'f');
+ $this->assertEqual($f, 'http://www.example.com', t('Converting URLs -- really skip code contents.'));
+
+ $f = _filter_url('', 'f');
+ $this->assertEqual($f, '', t('Converting URLs -- do not process scripts.'));
+
+ // Addresses in attributes should not be converted.
+ $f = _filter_url('', 'f');
+ $this->assertEqual($f, '', t('Converting URLs -- do not convert addresses in attributes.'));
+
+ $f = _filter_url('text', 'f');
+ $this->assertEqual($f, 'text', t('Converting URLs -- do not break existing links with custom title attribute.'));
+
+ // Even though a dot at the end of a URL can indicate a fully qualified
+ // domain name, such usage is rare compared to using a link at the end
+ // of a sentence, so remove the dot from the link.
+ // name. It can also be used at the end of a filename or a query string
+ $f = _filter_url('www.example.com.', 'f');
+ $this->assertEqual($f, 'www.example.com.', t('Converting URLs -- do not recognize a dot at the end of a domain name (FQDNs).'));
+
+ $f = _filter_url('http://www.example.com.', 'f');
+ $this->assertEqual($f, 'http://www.example.com.', t('Converting URLs -- do not recognize a dot at the end of an URL (FQDNs).'));
+
+ $f = _filter_url('www.example.com/index.php?a=.', 'f');
+ $this->assertEqual($f, 'www.example.com/index.php?a=.', t('Converting URLs -- do forget about a dot at the end of a query string.'));
+ }
+
+ /**
+ * Test the HTML corrector.
+ *
+ * TODO: This test could really use some validity checking function.
+ */
+ function testHtmlCorrector() {
+ // Tag closing.
+ $f = _filter_htmlcorrector('
text');
+ $this->assertEqual($f, '
text
', t('HTML corrector -- tag closing at the end of input.'));
+
+ $f = _filter_htmlcorrector('
', t('HTML corrector -- unclosed tag with attribute.'));
+
+ // XHTML slash for empty elements.
+ $f = _filter_htmlcorrector(' ');
+ $this->assertEqual($f, ' ', t('HTML corrector -- XHTML closing slash.'));
}
function createFormat($filter) {
@@ -237,4 +618,54 @@ class FilterTestCase extends DrupalWebTestCase {
$this->drupalPost('admin/settings/formats/delete/' . $format->format, array(), t('Delete'));
}
}
+
+ /**
+ * Asserts that a text transformed to lowercase with HTML entities decoded
+ * does contains a given string.
+ *
+ * Otherwise fails the test with a given message, similar to all the
+ * SimpleTest assert* functions.
+ *
+ * Note that this does not remove nulls, new lines and other characters that
+ * could be used to obscure a tag or an attribute name.
+ *
+ * @param $haystack
+ * Text to look in.
+ * @param $needle
+ * Lowercase, plain text to look for.
+ * @param $message
+ * Message to display if failed.
+ * @param $group
+ * The group this message belongs to, defaults to 'Other'.
+ * @return
+ * TRUE on pass, FALSE on fail.
+ */
+ function assertNormalized($haystack, $needle, $message = '', $group = 'Other') {
+ return $this->assertTrue(strpos(strtolower(decode_entities($haystack)), $needle) !== FALSE, $message, $group);
+ }
+
+ /**
+ * Asserts that text transformed to lowercase with HTML entities decoded does
+ * not contain a given string.
+ *
+ * Otherwise fails the test with a given message, similar to all the
+ * SimpleTest assert* functions.
+ *
+ * Note that this does not remove nulls, new lines, and other character that
+ * could be used to obscure a tag or an attribute name.
+ *
+ * @param $haystack
+ * Text to look in.
+ * @param $needle
+ * Lowercase, plain text to look for.
+ * @param $message
+ * Message to display if failed.
+ * @param $group
+ * The group this message belongs to, defaults to 'Other'.
+ * @return
+ * TRUE on pass, FALSE on fail.
+ */
+ function assertNoNormalized($haystack, $needle, $message = '', $group = 'Other') {
+ return $this->assertTrue(strpos(strtolower(decode_entities($haystack)), $needle) === FALSE, $message, $group);
+ }
}
--
cgit v1.2.3