Commit | Line | Data |
---|---|---|
b63f5679 TO |
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 | } |