pathologic.test 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. <?php
  2. /**
  3. * @file
  4. * Pathologic behavior testing.
  5. */
  6. /**
  7. * Tests that Pathologic ain't broke.
  8. *
  9. * We extend FilterUnitTestCase because it has some nice methods that we also
  10. * want to be able to use.
  11. *
  12. * Note to self: The method to pass bits of text through are fail() or pass().
  13. */
  14. class PathologicTestCase extends DrupalWebTestCase {
  15. public static function getInfo() {
  16. return array(
  17. 'name' => 'Pathologic path filtering',
  18. 'description' => 'Test Pathologic&rsquo;s path translation and conversion.',
  19. 'group' => 'Filter',
  20. );
  21. }
  22. function setUp() {
  23. parent::setUp('pathologic');
  24. }
  25. function testPathologic() {
  26. // Start by testing our function to build protocol-relative URLs
  27. $this->assertEqual(
  28. _pathologic_url_to_protocol_relative('http://example.com/foo/bar'),
  29. '//example.com/foo/bar',
  30. t('Protocol-relative URL creation with http:// URL')
  31. );
  32. $this->assertEqual(
  33. _pathologic_url_to_protocol_relative('https://example.org/baz'),
  34. '//example.org/baz',
  35. t('Protocol-relative URL creation with https:// URL')
  36. );
  37. // Build a phony filter
  38. $filter = new stdClass;
  39. $filter->callback = '_pathologic';
  40. $filter->settings = array(
  41. 'protocol_style' => 'full',
  42. 'local_paths' => '',
  43. );
  44. $filter->format = 0;
  45. // Build some paths to check against
  46. $test_paths = array(
  47. 'foo' => array(
  48. 'path' => 'foo',
  49. 'opts' => array()
  50. ),
  51. 'foo/bar' => array(
  52. 'path' => 'foo/bar',
  53. 'opts' => array()
  54. ),
  55. 'foo/bar?baz' => array(
  56. 'path' => 'foo/bar',
  57. 'opts' => array('query' => array('baz' => NULL))
  58. ),
  59. 'foo/bar?baz=qux' => array(
  60. 'path' => 'foo/bar',
  61. 'opts' => array('query' => array('baz' => 'qux'))
  62. ),
  63. 'foo/bar#baz' => array(
  64. 'path' => 'foo/bar',
  65. 'opts' => array('fragment' => 'baz'),
  66. ),
  67. 'foo/bar?baz=qux&amp;quux=quuux#quuuux' => array(
  68. 'path' => 'foo/bar',
  69. 'opts' => array(
  70. 'query' => array('baz' => 'qux', 'quux' => 'quuux'),
  71. 'fragment' => 'quuuux',
  72. ),
  73. ),
  74. 'foo%20bar?baz=qux%26quux' => array(
  75. 'path' => 'foo bar',
  76. 'opts' => array(
  77. 'query' => array('baz' => 'qux&quux'),
  78. ),
  79. ),
  80. '/' => array(
  81. 'path' => '<front>',
  82. 'opts' => array(),
  83. ),
  84. );
  85. // Run tests with clean URLs both enabled and disabled
  86. foreach (array(TRUE, FALSE) as $clean_url) {
  87. variable_set('clean_url', $clean_url);
  88. // Run tests with absoulte filtering enabled and disabled
  89. foreach (array('full', 'proto-rel', 'path') as $protocol_style) {
  90. $filter->settings['protocol_style'] = $protocol_style;
  91. $filter->format++;
  92. $paths = array();
  93. foreach ($test_paths as $path => $args) {
  94. $args['opts']['absolute'] = $protocol_style !== 'path';
  95. $paths[$path] = _pathologic_content_url($args['path'], $args['opts']);
  96. if ($protocol_style === 'proto-rel') {
  97. $paths[$path] = _pathologic_url_to_protocol_relative($paths[$path]);
  98. }
  99. }
  100. $t10ns = array(
  101. '!clean' => $clean_url ? t('Yes') : t('No'),
  102. '!ps' => $protocol_style,
  103. );
  104. $this->assertEqual(
  105. _pathologic_filter('<a href="foo"><img src="foo/bar" /></a>', $filter, NULL, LANGUAGE_NONE, NULL, NULL),
  106. '<a href="' . $paths['foo'] . '"><img src="' . $paths['foo/bar'] . '" /></a>',
  107. t('Simple paths. Clean URLs: !clean; protocol style: !ps.', $t10ns)
  108. );
  109. $this->assertEqual(
  110. _pathologic_filter('<form action="foo/bar?baz"><IMG LONGDESC="foo/bar?baz=qux" /></a>', $filter, NULL, LANGUAGE_NONE, NULL, NULL),
  111. '<form action="' . $paths['foo/bar?baz'] . '"><IMG LONGDESC="' . $paths['foo/bar?baz=qux'] . '" /></a>',
  112. t('Paths with query string. Clean URLs: !clean; protocol style: !ps.', $t10ns)
  113. );
  114. $this->assertEqual(
  115. _pathologic_filter('<a href="foo/bar#baz">', $filter, NULL, LANGUAGE_NONE, NULL, NULL),
  116. '<a href="' . $paths['foo/bar#baz'] . '">',
  117. t('Path with fragment. Clean URLs: !clean; protocol style: !ps.', $t10ns)
  118. );
  119. $this->assertEqual(
  120. _pathologic_filter('<a href="#foo">', $filter, NULL, LANGUAGE_NONE, NULL, NULL),
  121. '<a href="#foo">',
  122. t('Fragment-only links. Clean URLs: !clean; protocol style: !ps.', $t10ns)
  123. );
  124. $this->assertEqual(
  125. _pathologic_filter('<a href="foo/bar?baz=qux&amp;quux=quuux#quuuux">', $filter, NULL, LANGUAGE_NONE, NULL, NULL),
  126. '<a href="' . $paths['foo/bar?baz=qux&amp;quux=quuux#quuuux'] . '">',
  127. t('Path with query string and fragment. Clean URLs: !clean; protocol style: !ps.', $t10ns)
  128. );
  129. $this->assertEqual(
  130. _pathologic_filter('<a href="foo%20bar?baz=qux%26quux">', $filter, NULL, LANGUAGE_NONE, NULL, NULL),
  131. '<a href="' . $paths['foo%20bar?baz=qux%26quux'] . '">',
  132. t('Path with URL encoded parts')
  133. );
  134. $this->assertEqual(
  135. _pathologic_filter('<a href="/"></a>', $filter, NULL, LANGUAGE_NONE, NULL, NULL),
  136. '<a href="' . $paths['/'] . '"></a>',
  137. t('Path with just slash. Clean URLs: !clean; protocol style: !ps', $t10ns)
  138. );
  139. }
  140. }
  141. global $base_path;
  142. $this->assertEqual(
  143. _pathologic_filter('<a href="' . $base_path . 'foo">bar</a>', $filter, NULL, LANGUAGE_NONE, NULL, NULL),
  144. '<a href="' . _pathologic_content_url('foo', array('absolute' => FALSE)) .'">bar</a>',
  145. t('Paths beginning with $base_path (like WYSIWYG editors like to make)')
  146. );
  147. global $base_url;
  148. $this->assertEqual(
  149. _pathologic_filter('<a href="' . $base_url . '/foo">bar</a>', $filter, NULL, LANGUAGE_NONE, NULL, NULL),
  150. '<a href="' . _pathologic_content_url('foo', array('absolute' => FALSE)) .'">bar</a>',
  151. t('Paths beginning with $base_url')
  152. );
  153. // @see http://drupal.org/node/1617944
  154. $this->assertEqual(
  155. _pathologic_filter('<a href="//example.com/foo">bar</a>', $filter, NULL, LANGUAGE_NONE, NULL, NULL),
  156. '<a href="//example.com/foo">bar</a>',
  157. t('Off-site schemeless URLs (//example.com/foo) ignored')
  158. );
  159. // Test internal: and all base paths
  160. $filter->settings = array(
  161. 'protocol_style' => 'full',
  162. 'local_paths' => "http://example.com/qux\nhttp://example.org\n/bananas",
  163. );
  164. $filter->format++;
  165. // @see https://drupal.org/node/2030789
  166. $this->assertEqual(
  167. _pathologic_filter('<a href="//example.org/foo">bar</a>', $filter, NULL, LANGUAGE_NONE, NULL, NULL),
  168. '<a href="' . _pathologic_content_url('foo', array('absolute' => TRUE)) . '">bar</a>',
  169. t('On-site schemeless URLs processed')
  170. );
  171. $this->assertEqual(
  172. _pathologic_filter('<a href="internal:foo">', $filter, NULL, LANGUAGE_NONE, NULL, NULL),
  173. '<a href="' . _pathologic_content_url('foo', array('absolute' => TRUE)) . '">',
  174. t('Path Filter compatibility (internal:)')
  175. );
  176. $this->assertEqual(
  177. _pathologic_filter('<a href="files:image.jpeg">', $filter, NULL, LANGUAGE_NONE, NULL, NULL),
  178. '<a href="' . _pathologic_content_url(file_create_url('public://image.jpeg'), array('absolute' => TRUE, 'is_file' => TRUE)) . '">',
  179. t('Path Filter compatibility (files:)')
  180. );
  181. $this->assertEqual(
  182. _pathologic_filter('<a href="http://example.com/qux/foo"><img src="http://example.org/bar.jpeg" longdesc="/bananas/baz" /></a>', $filter, NULL, LANGUAGE_NONE, NULL, NULL),
  183. '<a href="' . _pathologic_content_url('foo', array('absolute' => TRUE)) . '"><img src="' . _pathologic_content_url('bar.jpeg', array('absolute' => TRUE)) . '" longdesc="' . _pathologic_content_url('baz', array('absolute' => TRUE)) . '" /></a>',
  184. t('"All base paths for this site" functionality')
  185. );
  186. $this->assertEqual(
  187. _pathologic_filter('<a href="webcal:foo">bar</a>', $filter, NULL, LANGUAGE_NONE, NULL, NULL),
  188. '<a href="webcal:foo">bar</a>',
  189. t('URLs with likely protocols are ignored')
  190. );
  191. // Test hook_pathologic_alter() implementation.
  192. $this->assertEqual(
  193. _pathologic_filter('<a href="foo?test=add_foo_qpart">', $filter, NULL, LANGUAGE_NONE, NULL, NULL),
  194. '<a href="' . _pathologic_content_url('foo', array('absolute' => TRUE, 'query' => array('test' => 'add_foo_qpart', 'foo' => 'bar'))) . '">',
  195. t('hook_pathologic_alter(): Alter $url_params')
  196. );
  197. $this->assertEqual(
  198. _pathologic_filter('<a href="bar?test=use_original">', $filter, NULL, LANGUAGE_NONE, NULL, NULL),
  199. '<a href="bar?test=use_original">',
  200. t('hook_pathologic_alter(): Passthrough with use_original option')
  201. );
  202. // Test paths to existing files when clean URLs are disabled.
  203. // @see http://drupal.org/node/1672430
  204. variable_set('clean_url', FALSE);
  205. $filtered_tag = _pathologic_filter('<img src="misc/druplicon.png" />', $filter, NULL, LANGUAGE_NONE, NULL, NULL);
  206. $this->assertTrue(
  207. strpos($filtered_tag, 'q=') === FALSE,
  208. t('Paths to files don\'t have ?q= when clean URLs are off')
  209. );
  210. }
  211. }
  212. /**
  213. * Wrapper around url() which does HTML entity decoding and encoding.
  214. *
  215. * Since Pathologic works with paths in content, it needs to decode paths which
  216. * have been HTML-encoded, and re-encode them when done. This is a wrapper
  217. * around url() which does the same thing so that we can expect the results
  218. * from it and from Pathologic to still match in our tests.
  219. *
  220. * @see url()
  221. * @see http://drupal.org/node/1672932
  222. * @see http://www.w3.org/TR/xhtml1/guidelines.html#C_12
  223. */
  224. function _pathologic_content_url($path, $options) {
  225. // If we should pretend this is a path to a file, temporarily enable clean
  226. // URLs if necessary.
  227. // @see _pathologic_replace()
  228. // @see http://drupal.org/node/1672430
  229. if (!empty($options['is_file'])) {
  230. $options['orig_clean_url'] = !empty($GLOBALS['conf']['clean_url']);
  231. if (!$options['orig_clean_url']) {
  232. $GLOBALS['conf']['clean_url'] = TRUE;
  233. }
  234. }
  235. $url = check_plain(url(htmlspecialchars_decode($path), $options));
  236. if (!empty($options['is_file']) && !$options['orig_clean_url']) {
  237. $GLOBALS['conf']['clean_url'] = FALSE;
  238. }
  239. return $url;
  240. }
  241. /**
  242. * Implements hook_pathologic_alter(), for testing that functionality.
  243. */
  244. function pathologic_pathologic_alter(&$url_params, $parts, $settings) {
  245. if (is_array($parts['qparts']) && isset($parts['qparts']['test'])) {
  246. if ($parts['qparts']['test'] === 'add_foo_qpart') {
  247. // Add a "foo" query part
  248. if (empty($url_params['options']['query'])) {
  249. $url_params['options']['query'] = array();
  250. }
  251. $url_params['options']['query']['foo'] = 'bar';
  252. }
  253. elseif ($parts['qparts']['test'] === 'use_original') {
  254. $url_params['options']['use_original'] = TRUE;
  255. }
  256. }
  257. }