| 1 | #!/usr/bin/env php |
| 2 | <?php |
| 3 | |
| 4 | // FIXME: Make this a proper app with unit-tests |
| 5 | |
| 6 | $civi_pkgs_dir = dirname( dirname( dirname( __FILE__ ) ) ) . DIRECTORY_SEPARATOR . 'packages'; |
| 7 | require_once $civi_pkgs_dir . DIRECTORY_SEPARATOR . 'simple_html_dom.php'; |
| 8 | exit(main($argv)); |
| 9 | |
| 10 | /** |
| 11 | * Scan a series of files for suspicious code |
| 12 | * |
| 13 | * example: find templates -name '*.tpl' | xargs php tools/scripts/tpl-lint |
| 14 | * |
| 15 | * @param array $argv |
| 16 | * @return int |
| 17 | */ |
| 18 | function main($argv) { |
| 19 | $files = $argv; |
| 20 | array_shift($files); // skip program name |
| 21 | foreach ($files as $file) { |
| 22 | check_tpl($file, function($code, $message) use ($file) { |
| 23 | printf("[%s] %s\n", $file, $message); |
| 24 | }); |
| 25 | } |
| 26 | return 0; |
| 27 | } |
| 28 | |
| 29 | /** |
| 30 | * Scan a file for suspicious code |
| 31 | * |
| 32 | * @param string $file |
| 33 | * @param callable $reporter |
| 34 | * @return void |
| 35 | */ |
| 36 | function check_tpl($file, $reporter) { |
| 37 | $html = file_get_html($file); |
| 38 | foreach ($html->find('a') as $a) { |
| 39 | check_a($a, $reporter); |
| 40 | } |
| 41 | } |
| 42 | |
| 43 | /** |
| 44 | * Scan an <A> tag |
| 45 | * |
| 46 | * @param object $a |
| 47 | * @param callable $reporter |
| 48 | * @return void |
| 49 | */ |
| 50 | function check_a($a, $reporter) { |
| 51 | if (!$a->hasAttribute('href')) { |
| 52 | // anchor, don't care |
| 53 | return; |
| 54 | } |
| 55 | |
| 56 | $href = trim($a->getAttribute('href')); |
| 57 | if (preg_match('/javascript:/', $href)) { |
| 58 | $reporter('javascript-url', "<a> has javascript url: $href"); |
| 59 | return; |
| 60 | } |
| 61 | if ($href == '#' && $a->hasAttribute('onclick')) { |
| 62 | $onclick = $a->getAttribute('onclick'); |
| 63 | if (!js_returns_false($onclick) && !js_returns_func($onclick)) { |
| 64 | $reporter('a-no-return', "<a> has href=# but handler fails to return false: $onclick"); |
| 65 | return; |
| 66 | } |
| 67 | } |
| 68 | if ($href != '#' && $a->hasAttribute('onclick')) { |
| 69 | $onclick = $a->getAttribute('onclick'); |
| 70 | $reporter('a-double-action', "<a> has both URL ($href) and onclick ($onclick)"); |
| 71 | return; |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | /** |
| 76 | * Determine if snippet of JS returns strictly false |
| 77 | */ |
| 78 | function js_returns_false($js) { |
| 79 | return |
| 80 | // last in a series of statements |
| 81 | preg_match('/; *return +false *; *$/', $js) |
| 82 | || |
| 83 | // only statement |
| 84 | preg_match('/^ *return +false *;? *$/', $js); |
| 85 | } |
| 86 | |
| 87 | /** |
| 88 | * Determine if snippet of JS returns a function call |
| 89 | */ |
| 90 | function js_returns_func($js) { |
| 91 | return preg_match('/^ *return +[a-zA-Z0-9\._$]+\(/', $js); |
| 92 | } |