request_sanitizer.test 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. <?php
  2. /**
  3. * @file
  4. * Tests for the RequestSanitizer class.
  5. */
  6. /**
  7. * Tests DrupalRequestSanitizer class.
  8. */
  9. class RequestSanitizerTest extends DrupalUnitTestCase {
  10. /**
  11. * Log of errors triggered during sanitization.
  12. *
  13. * @var array
  14. */
  15. protected $errors;
  16. /**
  17. * {@inheritdoc}
  18. */
  19. public static function getInfo() {
  20. return array(
  21. 'name' => 'DrupalRequestSanitizer',
  22. 'description' => 'Test the DrupalRequestSanitizer class',
  23. 'group' => 'System',
  24. );
  25. }
  26. /**
  27. * {@inheritdoc}
  28. */
  29. protected function setUp() {
  30. require_once DRUPAL_ROOT . '/includes/request-sanitizer.inc';
  31. parent::setUp();
  32. set_error_handler(array($this, "sanitizerTestErrorHandler"));
  33. }
  34. /**
  35. * Iterate through all the RequestSanitizerTests.
  36. */
  37. public function testRequestSanitization() {
  38. foreach ($this->requestSanitizerTests() as $label => $data) {
  39. $this->errors = array();
  40. // Normalize the test parameters.
  41. $test = array(
  42. 'request' => $data[0],
  43. 'expected' => isset($data[1]) ? $data[1] : array(),
  44. 'expected_errors' => isset($data[2]) ? $data[2] : NULL,
  45. 'whitelist' => isset($data[3]) ? $data[3] : array(),
  46. );
  47. $this->requestSanitizationTest($test['request'], $test['expected'], $test['expected_errors'], $test['whitelist'], $label);
  48. }
  49. }
  50. /**
  51. * Tests RequestSanitizer class.
  52. *
  53. * @param \SanitizerTestRequest $request
  54. * The request to sanitize.
  55. * @param array $expected
  56. * An array of expected request parameters after sanitization.
  57. * @param array|null $expected_errors
  58. * An array of expected errors. If set to NULL then error logging is
  59. * disabled.
  60. * @param array $whitelist
  61. * An array of keys to whitelist and not sanitize.
  62. * @param string $label
  63. * A descriptive name for each test / group of assertions.
  64. *
  65. * @throws \ReflectionException
  66. */
  67. public function requestSanitizationTest(SanitizerTestRequest $request, array $expected = array(), array $expected_errors = NULL, array $whitelist = array(), $label = NULL) {
  68. // Set up globals.
  69. $_GET = $request->getQuery();
  70. $_POST = $request->getRequest();
  71. $_COOKIE = $request->getCookies();
  72. $_REQUEST = array_merge($request->getQuery(), $request->getRequest());
  73. $GLOBALS['conf']['sanitize_input_whitelist'] = $whitelist;
  74. $GLOBALS['conf']['sanitize_input_logging'] = is_null($expected_errors) ? FALSE : TRUE;
  75. if ($label !== 'already sanitized request') {
  76. $reflection = new \ReflectionProperty('DrupalRequestSanitizer', 'sanitized');
  77. $reflection->setAccessible(TRUE);
  78. $reflection->setValue(NULL, FALSE);
  79. }
  80. DrupalRequestSanitizer::sanitize();
  81. if (isset($_GET['destination'])) {
  82. DrupalRequestSanitizer::cleanDestination();
  83. }
  84. // Normalise the expected data.
  85. $expected += array(
  86. 'cookies' => array(),
  87. 'query' => array(),
  88. 'request' => array(),
  89. );
  90. // Test PHP globals.
  91. $this->assertEqualLabelled($expected['cookies'], $_COOKIE, NULL, 'Other', $label . ' (COOKIE)');
  92. $this->assertEqualLabelled($expected['query'], $_GET, NULL, 'Other', $label . ' (GET)');
  93. $this->assertEqualLabelled($expected['request'], $_POST, NULL, 'Other', $label . ' (POST)');
  94. $expected_request = array_merge($expected['query'], $expected['request']);
  95. $this->assertEqualLabelled($expected_request, $_REQUEST, NULL, 'Other', $label . ' (REQUEST)');
  96. // Ensure any expected errors have been triggered.
  97. if (!empty($expected_errors)) {
  98. foreach ($expected_errors as $expected_error) {
  99. $this->assertError($expected_error, E_USER_NOTICE, $label . ' (errors)');
  100. }
  101. }
  102. else {
  103. $this->assertEqualLabelled(array(), $this->errors, NULL, 'Other', $label . ' (errors)');
  104. }
  105. }
  106. /**
  107. * Data provider for testRequestSanitization.
  108. *
  109. * @return array
  110. * A list of tests to carry out.
  111. */
  112. public function requestSanitizerTests() {
  113. $tests = array();
  114. $request = new SanitizerTestRequest(array('q' => 'index.php'));
  115. $tests['no sanitization GET'] = array($request, array('query' => array('q' => 'index.php')));
  116. $request = new SanitizerTestRequest(array(), array('field' => 'value'));
  117. $tests['no sanitization POST'] = array($request, array('request' => array('field' => 'value')));
  118. $request = new SanitizerTestRequest(array(), array(), array(), array('key' => 'value'));
  119. $tests['no sanitization COOKIE'] = array($request, array('cookies' => array('key' => 'value')));
  120. $request = new SanitizerTestRequest(array('q' => 'index.php'), array('field' => 'value'), array(), array('key' => 'value'));
  121. $tests['no sanitization GET, POST, COOKIE'] = array($request, array('query' => array('q' => 'index.php'), 'request' => array('field' => 'value'), 'cookies' => array('key' => 'value')));
  122. $request = new SanitizerTestRequest(array('q' => 'index.php'));
  123. $tests['no sanitization GET log'] = array($request, array('query' => array('q' => 'index.php')), array());
  124. $request = new SanitizerTestRequest(array(), array('field' => 'value'));
  125. $tests['no sanitization POST log'] = array($request, array('request' => array('field' => 'value')), array());
  126. $request = new SanitizerTestRequest(array(), array(), array(), array('key' => 'value'));
  127. $tests['no sanitization COOKIE log'] = array($request, array('cookies' => array('key' => 'value')), array());
  128. $request = new SanitizerTestRequest(array('#q' => 'index.php'));
  129. $tests['sanitization GET'] = array($request);
  130. $request = new SanitizerTestRequest(array(), array('#field' => 'value'));
  131. $tests['sanitization POST'] = array($request);
  132. $request = new SanitizerTestRequest(array(), array(), array(), array('#key' => 'value'));
  133. $tests['sanitization COOKIE'] = array($request);
  134. $request = new SanitizerTestRequest(array('#q' => 'index.php'), array('#field' => 'value'), array(), array('#key' => 'value'));
  135. $tests['sanitization GET, POST, COOKIE'] = array($request);
  136. $request = new SanitizerTestRequest(array('#q' => 'index.php'));
  137. $tests['sanitization GET log'] = array($request, array(), array('Potentially unsafe keys removed from query string parameters (GET): #q'));
  138. $request = new SanitizerTestRequest(array(), array('#field' => 'value'));
  139. $tests['sanitization POST log'] = array($request, array(), array('Potentially unsafe keys removed from request body parameters (POST): #field'));
  140. $request = new SanitizerTestRequest(array(), array(), array(), array('#key' => 'value'));
  141. $tests['sanitization COOKIE log'] = array($request, array(), array('Potentially unsafe keys removed from cookie parameters (COOKIE): #key'));
  142. $request = new SanitizerTestRequest(array('#q' => 'index.php'), array('#field' => 'value'), array(), array('#key' => 'value'));
  143. $tests['sanitization GET, POST, COOKIE log'] = array($request, array(), array('Potentially unsafe keys removed from query string parameters (GET): #q', 'Potentially unsafe keys removed from request body parameters (POST): #field', 'Potentially unsafe keys removed from cookie parameters (COOKIE): #key'));
  144. $request = new SanitizerTestRequest(array('q' => 'index.php', 'foo' => array('#bar' => 'foo')));
  145. $tests['recursive sanitization log'] = array($request, array('query' => array('q' => 'index.php', 'foo' => array())), array('Potentially unsafe keys removed from query string parameters (GET): #bar'));
  146. $request = new SanitizerTestRequest(array('q' => 'index.php', 'foo' => array('#bar' => 'foo')));
  147. $tests['recursive no sanitization whitelist'] = array($request, array('query' => array('q' => 'index.php', 'foo' => array('#bar' => 'foo'))), array(), array('#bar'));
  148. $request = new SanitizerTestRequest(array(), array('#field' => 'value'));
  149. $tests['no sanitization POST whitelist'] = array($request, array('request' => array('#field' => 'value')), array(), array('#field'));
  150. $request = new SanitizerTestRequest(array('q' => 'index.php', 'foo' => array('#bar' => 'foo', '#foo' => 'bar')));
  151. $tests['recursive multiple sanitization log'] = array($request, array('query' => array('q' => 'index.php', 'foo' => array())), array('Potentially unsafe keys removed from query string parameters (GET): #bar, #foo'));
  152. $request = new SanitizerTestRequest(array('#q' => 'index.php'));
  153. $tests['already sanitized request'] = array($request, array('query' => array('#q' => 'index.php')));
  154. $request = new SanitizerTestRequest(array('destination' => 'whatever?%23test=value'));
  155. $tests['destination removal GET'] = array($request);
  156. $request = new SanitizerTestRequest(array('destination' => 'whatever?%23test=value'));
  157. $tests['destination removal GET log'] = array($request, array(), array('Potentially unsafe destination removed from query string parameters (GET) because it contained the following keys: #test'));
  158. $request = new SanitizerTestRequest(array('destination' => 'whatever?q[%23test]=value'));
  159. $tests['destination removal subkey'] = array($request);
  160. $request = new SanitizerTestRequest(array('destination' => 'whatever?q[%23test]=value'));
  161. $tests['destination whitelist'] = array($request, array('query' => array('destination' => 'whatever?q[%23test]=value')), array(), array('#test'));
  162. $request = new SanitizerTestRequest(array('destination' => "whatever?\x00bar=base&%23test=value"));
  163. $tests['destination removal zero byte'] = array($request);
  164. $request = new SanitizerTestRequest(array('destination' => 'whatever?q=value'));
  165. $tests['destination kept'] = array($request, array('query' => array('destination' => 'whatever?q=value')));
  166. $request = new SanitizerTestRequest(array('destination' => 'whatever'));
  167. $tests['destination no query'] = array($request, array('query' => array('destination' => 'whatever')));
  168. return $tests;
  169. }
  170. /**
  171. * Catches and logs errors to $this->errors.
  172. *
  173. * @param int $errno
  174. * The severity level of the error.
  175. * @param string $errstr
  176. * The error message.
  177. */
  178. public function sanitizerTestErrorHandler($errno, $errstr) {
  179. $this->errors[] = compact('errno', 'errstr');
  180. }
  181. /**
  182. * Asserts that the expected error has been logged.
  183. *
  184. * @param string $errstr
  185. * The error message.
  186. * @param int $errno
  187. * The severity level of the error.
  188. * @param string $label
  189. * The label to include with the message.
  190. *
  191. * @return bool
  192. * TRUE if the assertion succeeded, FALSE otherwise.
  193. */
  194. protected function assertError($errstr, $errno, $label) {
  195. $label = (empty($label)) ? '' : $label . ': ';
  196. foreach ($this->errors as $error) {
  197. if ($error['errstr'] === $errstr && $error['errno'] === $errno) {
  198. return $this->pass($label . "Error with level $errno and message '$errstr' found");
  199. }
  200. }
  201. return $this->fail($label . "Error with level $errno and message '$errstr' not found in " . var_export($this->errors, TRUE));
  202. }
  203. /**
  204. * Asserts two values are equal, includes a label.
  205. *
  206. * @param mixed $first
  207. * The first value to check.
  208. * @param mixed $second
  209. * The second value to check.
  210. * @param string $message
  211. * The message to display along with the assertion.
  212. * @param string $group
  213. * The type of assertion - examples are "Browser", "PHP".
  214. * @param string $label
  215. * The label to include with the message.
  216. *
  217. * @return bool
  218. * TRUE if the assertion succeeded, FALSE otherwise.
  219. */
  220. protected function assertEqualLabelled($first, $second, $message = '', $group = 'Other', $label = '') {
  221. $label = (empty($label)) ? '' : $label . ': ';
  222. $message = $message ? $message : t('Value @first is equal to value @second.', array(
  223. '@first' => var_export($first, TRUE),
  224. '@second' => var_export($second, TRUE),
  225. ));
  226. return $this->assert($first == $second, $label . $message, $group);
  227. }
  228. }
  229. /**
  230. * Basic HTTP Request class.
  231. */
  232. class SanitizerTestRequest {
  233. /**
  234. * The query (GET).
  235. *
  236. * @var array
  237. */
  238. protected $query;
  239. /**
  240. * The request (POST).
  241. *
  242. * @var array
  243. */
  244. protected $request;
  245. /**
  246. * The request attributes.
  247. *
  248. * @var array
  249. */
  250. protected $attributes;
  251. /**
  252. * The request cookies.
  253. *
  254. * @var array
  255. */
  256. protected $cookies;
  257. /**
  258. * Constructor.
  259. *
  260. * @param array $query
  261. * The GET parameters.
  262. * @param array $request
  263. * The POST parameters.
  264. * @param array $attributes
  265. * The request attributes.
  266. * @param array $cookies
  267. * The COOKIE parameters.
  268. */
  269. public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array()) {
  270. $this->query = $query;
  271. $this->request = $request;
  272. $this->attributes = $attributes;
  273. $this->cookies = $cookies;
  274. }
  275. /**
  276. * Getter for $query.
  277. */
  278. public function getQuery() {
  279. return $this->query;
  280. }
  281. /**
  282. * Getter for $request.
  283. */
  284. public function getRequest() {
  285. return $this->request;
  286. }
  287. /**
  288. * Getter for $attributes.
  289. */
  290. public function getAttributes() {
  291. return $this->attributes;
  292. }
  293. /**
  294. * Getter for $cookies.
  295. */
  296. public function getCookies() {
  297. return $this->cookies;
  298. }
  299. }