'drupal_alter() tests', 'description' => 'Confirm that alteration of arguments passed to drupal_alter() works correctly.', 'group' => 'System', ); } function setUp() { parent::setUp('common_test'); } function testDrupalAlter() { // This test depends on Bartik, so make sure that it is always the current // active theme. global $theme, $base_theme_info; $theme = 'bartik'; $base_theme_info = array(); $array = array('foo' => 'bar'); $entity = new stdClass(); $entity->foo = 'bar'; // Verify alteration of a single argument. $array_copy = $array; $array_expected = array('foo' => 'Drupal theme'); drupal_alter('drupal_alter', $array_copy); $this->assertEqual($array_copy, $array_expected, 'Single array was altered.'); $entity_copy = clone $entity; $entity_expected = clone $entity; $entity_expected->foo = 'Drupal theme'; drupal_alter('drupal_alter', $entity_copy); $this->assertEqual($entity_copy, $entity_expected, 'Single object was altered.'); // Verify alteration of multiple arguments. $array_copy = $array; $array_expected = array('foo' => 'Drupal theme'); $entity_copy = clone $entity; $entity_expected = clone $entity; $entity_expected->foo = 'Drupal theme'; $array2_copy = $array; $array2_expected = array('foo' => 'Drupal theme'); drupal_alter('drupal_alter', $array_copy, $entity_copy, $array2_copy); $this->assertEqual($array_copy, $array_expected, 'First argument to drupal_alter() was altered.'); $this->assertEqual($entity_copy, $entity_expected, 'Second argument to drupal_alter() was altered.'); $this->assertEqual($array2_copy, $array2_expected, 'Third argument to drupal_alter() was altered.'); // Verify alteration order when passing an array of types to drupal_alter(). // common_test_module_implements_alter() places 'block' implementation after // other modules. $array_copy = $array; $array_expected = array('foo' => 'Drupal block theme'); drupal_alter(array('drupal_alter', 'drupal_alter_foo'), $array_copy); $this->assertEqual($array_copy, $array_expected, 'hook_TYPE_alter() implementations ran in correct order.'); } } /** * Tests for URL generation functions. * * url() calls module_implements(), which may issue a db query, which requires * inheriting from a web test case rather than a unit test case. */ class CommonURLUnitTest extends DrupalWebTestCase { public static function getInfo() { return array( 'name' => 'URL generation tests', 'description' => 'Confirm that url(), drupal_get_query_parameters(), drupal_http_build_query(), and l() work correctly with various input.', 'group' => 'System', ); } /** * Confirm that invalid text given as $path is filtered. */ function testLXSS() { $text = $this->randomName(); $path = ""; $link = l($text, $path); $sanitized_path = check_url(url($path)); $this->assertTrue(strpos($link, $sanitized_path) !== FALSE, format_string('XSS attack @path was filtered', array('@path' => $path))); } /* * Tests for active class in l() function. */ function testLActiveClass() { $link = l($this->randomName(), $_GET['q']); $this->assertTrue($this->hasClass($link, 'active'), format_string('Class @class is present on link to the current page', array('@class' => 'active'))); } /** * Tests for custom class in l() function. */ function testLCustomClass() { $class = $this->randomName(); $link = l($this->randomName(), $_GET['q'], array('attributes' => array('class' => array($class)))); $this->assertTrue($this->hasClass($link, $class), format_string('Custom class @class is present on link when requested', array('@class' => $class))); $this->assertTrue($this->hasClass($link, 'active'), format_string('Class @class is present on link to the current page', array('@class' => 'active'))); } private function hasClass($link, $class) { return preg_match('|class="([^\"\s]+\s+)*' . $class . '|', $link); } /** * Test drupal_get_query_parameters(). */ function testDrupalGetQueryParameters() { $original = array( 'a' => 1, 'b' => array( 'd' => 4, 'e' => array( 'f' => 5, ), ), 'c' => 3, 'q' => 'foo/bar', ); // Default arguments. $result = $_GET; unset($result['q']); $this->assertEqual(drupal_get_query_parameters(), $result, "\$_GET['q'] was removed."); // Default exclusion. $result = $original; unset($result['q']); $this->assertEqual(drupal_get_query_parameters($original), $result, "'q' was removed."); // First-level exclusion. $result = $original; unset($result['b']); $this->assertEqual(drupal_get_query_parameters($original, array('b')), $result, "'b' was removed."); // Second-level exclusion. $result = $original; unset($result['b']['d']); $this->assertEqual(drupal_get_query_parameters($original, array('b[d]')), $result, "'b[d]' was removed."); // Third-level exclusion. $result = $original; unset($result['b']['e']['f']); $this->assertEqual(drupal_get_query_parameters($original, array('b[e][f]')), $result, "'b[e][f]' was removed."); // Multiple exclusions. $result = $original; unset($result['a'], $result['b']['e'], $result['c']); $this->assertEqual(drupal_get_query_parameters($original, array('a', 'b[e]', 'c')), $result, "'a', 'b[e]', 'c' were removed."); } /** * Test drupal_http_build_query(). */ function testDrupalHttpBuildQuery() { $this->assertEqual(drupal_http_build_query(array('a' => ' &#//+%20@۞')), 'a=%20%26%23//%2B%2520%40%DB%9E', 'Value was properly encoded.'); $this->assertEqual(drupal_http_build_query(array(' &#//+%20@۞' => 'a')), '%20%26%23%2F%2F%2B%2520%40%DB%9E=a', 'Key was properly encoded.'); $this->assertEqual(drupal_http_build_query(array('a' => '1', 'b' => '2', 'c' => '3')), 'a=1&b=2&c=3', 'Multiple values were properly concatenated.'); $this->assertEqual(drupal_http_build_query(array('a' => array('b' => '2', 'c' => '3'), 'd' => 'foo')), 'a[b]=2&a[c]=3&d=foo', 'Nested array was properly encoded.'); } /** * Test drupal_parse_url(). */ function testDrupalParseUrl() { // Relative URL. $url = 'foo/bar?foo=bar&bar=baz&baz#foo'; $result = array( 'path' => 'foo/bar', 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), 'fragment' => 'foo', ); $this->assertEqual(drupal_parse_url($url), $result, 'Relative URL parsed correctly.'); // Relative URL that is known to confuse parse_url(). $url = 'foo/bar:1'; $result = array( 'path' => 'foo/bar:1', 'query' => array(), 'fragment' => '', ); $this->assertEqual(drupal_parse_url($url), $result, 'Relative URL parsed correctly.'); // Absolute URL. $url = '/foo/bar?foo=bar&bar=baz&baz#foo'; $result = array( 'path' => '/foo/bar', 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), 'fragment' => 'foo', ); $this->assertEqual(drupal_parse_url($url), $result, 'Absolute URL parsed correctly.'); // External URL testing. $url = 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo'; // Test that drupal can recognize an absolute URL. Used to prevent attack vectors. $this->assertTrue(url_is_external($url), 'Correctly identified an external URL.'); // External URL without an explicit protocol. $url = '//drupal.org/foo/bar?foo=bar&bar=baz&baz#foo'; $this->assertTrue(url_is_external($url), 'Correctly identified an external URL without a protocol part.'); // Internal URL starting with a slash. $url = '/drupal.org'; $this->assertFalse(url_is_external($url), 'Correctly identified an internal URL with a leading slash.'); // Test the parsing of absolute URLs. $url = 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo'; $result = array( 'path' => 'http://drupal.org/foo/bar', 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), 'fragment' => 'foo', ); $this->assertEqual(drupal_parse_url($url), $result, 'External URL parsed correctly.'); // Verify proper parsing of URLs when clean URLs are disabled. $result = array( 'path' => 'foo/bar', 'query' => array('bar' => 'baz'), 'fragment' => 'foo', ); // Non-clean URLs #1: Absolute URL generated by url(). $url = $GLOBALS['base_url'] . '/?q=foo/bar&bar=baz#foo'; $this->assertEqual(drupal_parse_url($url), $result, 'Absolute URL with clean URLs disabled parsed correctly.'); // Non-clean URLs #2: Relative URL generated by url(). $url = '?q=foo/bar&bar=baz#foo'; $this->assertEqual(drupal_parse_url($url), $result, 'Relative URL with clean URLs disabled parsed correctly.'); // Non-clean URLs #3: URL generated by url() on non-Apache webserver. $url = 'index.php?q=foo/bar&bar=baz#foo'; $this->assertEqual(drupal_parse_url($url), $result, 'Relative URL on non-Apache webserver with clean URLs disabled parsed correctly.'); // Test that drupal_parse_url() does not allow spoofing a URL to force a malicious redirect. $parts = drupal_parse_url('forged:http://cwe.mitre.org/data/definitions/601.html'); $this->assertFalse(valid_url($parts['path'], TRUE), 'drupal_parse_url() correctly parsed a forged URL.'); } /** * Test url() with/without query, with/without fragment, absolute on/off and * assert all that works when clean URLs are on and off. */ function testUrl() { global $base_url; foreach (array(FALSE, TRUE) as $absolute) { // Get the expected start of the path string. $base = $absolute ? $base_url . '/' : base_path(); $absolute_string = $absolute ? 'absolute' : NULL; // Disable Clean URLs. $GLOBALS['conf']['clean_url'] = 0; $url = $base . '?q=node/123'; $result = url('node/123', array('absolute' => $absolute)); $this->assertEqual($url, $result, "$url == $result"); $url = $base . '?q=node/123#foo'; $result = url('node/123', array('fragment' => 'foo', 'absolute' => $absolute)); $this->assertEqual($url, $result, "$url == $result"); $url = $base . '?q=node/123&foo'; $result = url('node/123', array('query' => array('foo' => NULL), 'absolute' => $absolute)); $this->assertEqual($url, $result, "$url == $result"); $url = $base . '?q=node/123&foo=bar&bar=baz'; $result = url('node/123', array('query' => array('foo' => 'bar', 'bar' => 'baz'), 'absolute' => $absolute)); $this->assertEqual($url, $result, "$url == $result"); $url = $base . '?q=node/123&foo#bar'; $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute)); $this->assertEqual($url, $result, "$url == $result"); $url = $base . '?q=node/123&foo#0'; $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => '0', 'absolute' => $absolute)); $this->assertEqual($url, $result, "$url == $result"); $url = $base . '?q=node/123&foo'; $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => '', 'absolute' => $absolute)); $this->assertEqual($url, $result, "$url == $result"); $url = $base; $result = url('', array('absolute' => $absolute)); $this->assertEqual($url, $result, "$url == $result"); // Enable Clean URLs. $GLOBALS['conf']['clean_url'] = 1; $url = $base . 'node/123'; $result = url('node/123', array('absolute' => $absolute)); $this->assertEqual($url, $result, "$url == $result"); $url = $base . 'node/123#foo'; $result = url('node/123', array('fragment' => 'foo', 'absolute' => $absolute)); $this->assertEqual($url, $result, "$url == $result"); $url = $base . 'node/123?foo'; $result = url('node/123', array('query' => array('foo' => NULL), 'absolute' => $absolute)); $this->assertEqual($url, $result, "$url == $result"); $url = $base . 'node/123?foo=bar&bar=baz'; $result = url('node/123', array('query' => array('foo' => 'bar', 'bar' => 'baz'), 'absolute' => $absolute)); $this->assertEqual($url, $result, "$url == $result"); $url = $base . 'node/123?foo#bar'; $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute)); $this->assertEqual($url, $result, "$url == $result"); $url = $base; $result = url('', array('absolute' => $absolute)); $this->assertEqual($url, $result, "$url == $result"); } } /** * Test external URL handling. */ function testExternalUrls() { $test_url = 'http://drupal.org/'; // Verify external URL can contain a fragment. $url = $test_url . '#drupal'; $result = url($url); $this->assertEqual($url, $result, 'External URL with fragment works without a fragment in $options.'); // Verify fragment can be overidden in an external URL. $url = $test_url . '#drupal'; $fragment = $this->randomName(10); $result = url($url, array('fragment' => $fragment)); $this->assertEqual($test_url . '#' . $fragment, $result, 'External URL fragment is overidden with a custom fragment in $options.'); // Verify external URL can contain a query string. $url = $test_url . '?drupal=awesome'; $result = url($url); $this->assertEqual($url, $result, 'External URL with query string works without a query string in $options.'); // Verify external URL can be extended with a query string. $url = $test_url; $query = array($this->randomName(5) => $this->randomName(5)); $result = url($url, array('query' => $query)); $this->assertEqual($url . '?' . http_build_query($query, '', '&'), $result, 'External URL can be extended with a query string in $options.'); // Verify query string can be extended in an external URL. $url = $test_url . '?drupal=awesome'; $query = array($this->randomName(5) => $this->randomName(5)); $result = url($url, array('query' => $query)); $this->assertEqual($url . '&' . http_build_query($query, '', '&'), $result, 'External URL query string can be extended with a custom query string in $options.'); // Verify that an internal URL does not result in an external URL without // protocol part. $url = '/drupal.org'; $result = url($url); $this->assertTrue(strpos($result, '//') === FALSE, 'Internal URL does not turn into an external URL.'); // Verify that an external URL without protocol part is recognized as such. $url = '//drupal.org'; $result = url($url); $this->assertEqual($url, $result, 'External URL without protocol is not altered.'); } } /** * Tests url_is_external(). */ class UrlIsExternalUnitTest extends DrupalUnitTestCase { public static function getInfo() { return array( 'name' => 'External URL checking', 'description' => 'Performs tests on url_is_external().', 'group' => 'System', ); } /** * Tests if each URL is external or not. */ function testUrlIsExternal() { foreach ($this->examples() as $path => $expected) { $this->assertIdentical(url_is_external($path), $expected, $path); } } /** * Provides data for testUrlIsExternal(). * * @return array * An array of test data, keyed by a path, with the expected value where * TRUE is external, and FALSE is not external. */ protected function examples() { return array( // Simple external URLs. 'http://example.com' => TRUE, 'https://example.com' => TRUE, 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo' => TRUE, '//drupal.org' => TRUE, // Some browsers ignore or strip leading control characters. "\x00//www.example.com" => TRUE, "\x08//www.example.com" => TRUE, "\x1F//www.example.com" => TRUE, "\n//www.example.com" => TRUE, // JSON supports decoding directly from UTF-8 code points. json_decode('"\u00AD"') . "//www.example.com" => TRUE, json_decode('"\u200E"') . "//www.example.com" => TRUE, json_decode('"\uE0020"') . "//www.example.com" => TRUE, json_decode('"\uE000"') . "//www.example.com" => TRUE, // Backslashes should be normalized to forward. '\\\\example.com' => TRUE, // Local URLs. 'node' => FALSE, '/system/ajax' => FALSE, '?q=foo:bar' => FALSE, 'node/edit:me' => FALSE, '/drupal.org' => FALSE, '' => FALSE, ); } } /** * Tests for check_plain(), filter_xss(), format_string(), and check_url(). */ class CommonXssUnitTest extends DrupalUnitTestCase { public static function getInfo() { return array( 'name' => 'String filtering tests', 'description' => 'Confirm that check_plain(), filter_xss(), format_string() and check_url() work correctly, including invalid multi-byte sequences.', 'group' => 'System', ); } /** * Check that invalid multi-byte sequences are rejected. */ function testInvalidMultiByte() { // Ignore PHP 5.3+ invalid multibyte sequence warning. $text = @check_plain("Foo\xC0barbaz"); $this->assertEqual($text, '', 'check_plain() rejects invalid sequence "Foo\xC0barbaz"'); // Ignore PHP 5.3+ invalid multibyte sequence warning. $text = @check_plain("\xc2\""); $this->assertEqual($text, '', 'check_plain() rejects invalid sequence "\xc2\""'); $text = check_plain("Fooÿñ"); $this->assertEqual($text, "Fooÿñ", 'check_plain() accepts valid sequence "Fooÿñ"'); $text = filter_xss("Foo\xC0barbaz"); $this->assertEqual($text, '', 'filter_xss() rejects invalid sequence "Foo\xC0barbaz"'); $text = filter_xss("Fooÿñ"); $this->assertEqual($text, "Fooÿñ", 'filter_xss() accepts valid sequence Fooÿñ'); } /** * Check that special characters are escaped. */ function testEscaping() { $text = check_plain("