#!/usr/bin/env php find('a') as $a) { check_a($a, $reporter); } } /** * Scan an tag * * @param object $a * @param callable $reporter * @return void */ function check_a($a, $reporter) { if (!$a->hasAttribute('href')) { // anchor, don't care return; } $href = trim($a->getAttribute('href')); if (preg_match('/javascript:/', $href)) { $reporter('javascript-url', " has javascript url: $href"); return; } if ($href == '#' && $a->hasAttribute('onclick')) { $onclick = $a->getAttribute('onclick'); if (!js_returns_false($onclick) && !js_returns_func($onclick)) { $reporter('a-no-return', " has href=# but handler fails to return false: $onclick"); return; } } if ($href != '#' && $a->hasAttribute('onclick')) { $onclick = $a->getAttribute('onclick'); $reporter('a-double-action', " has both URL ($href) and onclick ($onclick)"); return; } } /** * Determine if snippet of JS returns strictly false * @param $js * @return bool */ function js_returns_false($js) { return // last in a series of statements preg_match('/; *return +false *; *$/', $js) || // only statement preg_match('/^ *return +false *;? *$/', $js); } /** * Determine if snippet of JS returns a function call * @param $js * @return int */ function js_returns_func($js) { return preg_match('/^ *return +[a-zA-Z0-9\._$]+\(/', $js); }