OSDN Git Service

uri_pickup_normalize_composite_path():
[pukiwiki/pukiwiki_sandbox.git] / spam / spam_pickup.php
1 <?php
2 // $Id: spam_pickup.php,v 1.66 2009/01/02 09:30:50 henoheno Exp $
3 // Copyright (C) 2006-2007 PukiWiki Developers Team
4 // License: GPL v2 or (at your option) any later version
5 //
6 // Functions for Concept-work of spam-uri metrics
7 //
8
9 // ---------------------
10 // URI pickup
11
12 // Return an array of URIs in the $string
13 // [OK] http://nasty.example.org#nasty_string
14 // [OK] http://nasty.example.org:80/foo/xxx#nasty_string/bar
15 // [OK] ftp://nasty.example.org:80/dfsdfs
16 // [OK] ftp://cnn.example.com&story=breaking_news@10.0.0.1/top_story.htm (from RFC3986)
17 function uri_pickup($string = '')
18 {
19         if (! is_string($string)) return array();
20
21         // Not available for: IDN(ignored)
22         $array = array();
23         preg_match_all(
24                 // scheme://userinfo@host:port/path/or/pathinfo/maybefile.and?query=string#fragment
25                 // Refer RFC3986 (Regex below is not strict)
26                 '#(\b[a-z][a-z0-9.+-]{1,8}):[/\\\]+' .          // 1: Scheme
27                 '(?:' .
28                         '([^\s<>"\'\[\]/\#?@]*)' .              // 2: Userinfo (Username)
29                 '@)?' .
30                 '(' .
31                         // 3: Host
32                         '\[[0-9a-f:.]+\]' . '|' .                               // IPv6([colon-hex and dot]): RFC2732
33                         '(?:[0-9]{1,3}\.){3}[0-9]{1,3}' . '|' . // IPv4(dot-decimal): 001.22.3.44
34                         '[a-z0-9_-][a-z0-9_.-]+[a-z0-9_-]' .    // hostname(FQDN) : foo.example.org
35                 ')' .
36                 '(?::([0-9]*))?' .                                      // 4: Port
37                 '((?:/+[^\s<>"\'\[\]/\#]+)*/+)?' .      // 5: Directory path
38                 '([^\s<>"\'\[\]\#?]+)?' .                       // 6: File?
39                 '(?:\?([^\s<>"\'\[\]\#]+))?' .          // 7: Query string
40                 '(?:\#([a-z0-9._~%!$&\'()*+,;=:@-]*))?' .       // 8: Fragment
41                 '#i',
42                  $string, $array, PREG_SET_ORDER | PREG_OFFSET_CAPTURE
43         );
44
45         // Reformat the $array
46         static $parts = array(
47                 1 => 'scheme', 2 => 'userinfo', 3 => 'host', 4 => 'port',
48                 5 => 'path', 6 => 'file', 7 => 'query', 8 => 'fragment'
49         );
50         $default = array(0 => '', 1 => -1);
51         foreach(array_keys($array) as $uri) {
52                 $_uri = & $array[$uri];
53                 array_rename_keys($_uri, $parts, TRUE, $default);
54                 $offset = $_uri['scheme'][1]; // Scheme's offset = URI's offset
55                 foreach(array_keys($_uri) as $part) {
56                         $_uri[$part] = $_uri[$part][0]; // Remove offsets
57                 }
58         }
59
60         foreach(array_keys($array) as $uri) {
61                 $_uri = & $array[$uri];
62                 if ($_uri['scheme'] === '') {
63                         unset($array[$uri]);    // Considererd harmless
64                         continue;
65                 }
66                 unset($_uri[0]); // Matched string itself
67                 $_uri['area']['offset'] = $offset;      // Area offset for area_measure()
68         }
69
70         return $array;
71 }
72
73 // Pickupped URI array => An URI (See uri_pickup())
74 // USAGE:
75 //      $pickups = uri_pickup('a string include some URIs');
76 //      $uris = array();
77 //      foreach (array_keys($pickups) as $key) {
78 //              $uris[$key] = uri_pickup_implode($pickups[$key]);
79 //      }
80 function uri_pickup_implode($uri = array())
81 {
82         if (empty($uri) || ! is_array($uri)) return NULL;
83
84         $tmp = array();
85         if (isset($uri['scheme']) && $uri['scheme'] !== '') {
86                 $tmp[] = & $uri['scheme'];
87                 $tmp[] = '://';
88         }
89         if (isset($uri['userinfo']) && $uri['userinfo'] !== '') {
90                 $tmp[] = & $uri['userinfo'];
91                 $tmp[] = '@';
92         }
93         if (isset($uri['host']) && $uri['host'] !== '') {
94                 $tmp[] = & $uri['host'];
95         }
96         if (isset($uri['port']) && $uri['port'] !== '') {
97                 $tmp[] = ':';
98                 $tmp[] = & $uri['port'];
99         }
100         if (isset($uri['path']) && $uri['path'] !== '') {
101                 $tmp[] = & $uri['path'];
102         }
103         if (isset($uri['file']) && $uri['file'] !== '') {
104                 $tmp[] = & $uri['file'];
105         }
106         if (isset($uri['query']) && $uri['query'] !== '') {
107                 $tmp[] = '?';
108                 $tmp[] = & $uri['query'];
109         }
110         if (isset($uri['fragment']) && $uri['fragment'] !== '') {
111                 $tmp[] = '#';
112                 $tmp[] = & $uri['fragment'];
113         }
114
115         return implode('', $tmp);
116 }
117
118 // ---------------------
119 // URI normalization
120
121 // Normalize an array of URI arrays
122 // NOTE: Give me the uri_pickup() results
123 function uri_pickup_normalize(& $pickups, $destructive = TRUE, $pathfile = FALSE)
124 {
125         if (! is_array($pickups)) return $pickups;
126
127         if ($destructive) {
128                 foreach (array_keys($pickups) as $key) {
129                         $_key = & $pickups[$key];
130                         $_key['scheme']   = isset($_key['scheme'])   ? scheme_normalize($_key['scheme']) : '';
131                         $_key['host']     = isset($_key['host'])     ? host_normalize($_key['host'])     : '';
132                         $_key['port']     = isset($_key['port'])     ? port_normalize($_key['port'], $_key['scheme'], FALSE) : '';
133                         $_key['path']     = isset($_key['path'])     ? strtolower(path_normalize($_key['path'])) : '';
134                         $_key['file']     = isset($_key['file'])     ? file_normalize($_key['file'])   : '';
135                         $_key['query']    = isset($_key['query'])    ? query_normalize($_key['query']) : '';
136                         $_key['fragment'] = isset($_key['fragment']) ? strtolower($_key['fragment'])   : '';
137                 }
138         } else {
139                 foreach (array_keys($pickups) as $key) {
140                         $_key = & $pickups[$key];
141                         $_key['scheme']   = isset($_key['scheme'])   ? scheme_normalize($_key['scheme']) : '';
142                         $_key['host']     = isset($_key['host'])     ? strtolower($_key['host'])         : '';
143                         $_key['port']     = isset($_key['port'])     ? port_normalize($_key['port'], $_key['scheme'], FALSE) : '';
144                         $_key['path']     = isset($_key['path'])     ? path_normalize($_key['path']) : '';
145                 }
146         }
147
148         if ($pathfile) {
149                 return uri_pickup_normalize_pathfile($pickups);
150         } else {
151                 return $pickups;
152         }
153 }
154
155 // Normalize: 'path' + 'file' = 'path' (Similar structure using PHP's "parse_url()" function)
156 // NOTE: In some case, 'file' DOES NOT mean _filename_.
157 // [EXAMPLE] http://example.com/path/to/directory-accidentally-not-ended-with-slash
158 function uri_pickup_normalize_pathfile(& $pickups)
159 {
160         if (! is_array($pickups)) return $pickups;
161
162         foreach (array_keys($pickups) as $key) {
163                 $_key = & $pickups[$key];
164                 if (isset($_key['path'], $_key['file'])) {
165                         $_key['path'] = $_key['path'] . $_key['file'];
166                         unset($_key['file']);
167                 }
168         }
169
170         return $pickups;
171 }
172
173 // Scheme normalization: Renaming the schemes
174 // snntp://example.org =>  nntps://example.org
175 // NOTE: Keep the static lists simple. See also port_normalize().
176 function scheme_normalize($scheme = '', $abbrevs_harmfull = TRUE)
177 {
178         // Abbreviations they have no intention of link
179         static $abbrevs = array(
180                 'ttp'   => 'http',
181                 'ttps'  => 'https',
182         );
183
184         // Aliases => normalized ones
185         static $aliases = array(
186                 'pop'   => 'pop3',
187                 'news'  => 'nntp',
188                 'imap4' => 'imap',
189                 'snntp' => 'nntps',
190                 'snews' => 'nntps',
191                 'spop3' => 'pop3s',
192                 'pops'  => 'pop3s',
193         );
194
195         if (! is_string($scheme)) return '';
196
197         $scheme = strtolower($scheme);
198         if (isset($abbrevs[$scheme])) {
199                 $scheme = $abbrevs_harmfull ? $abbrevs[$scheme] : '';
200         }
201         if (isset($aliases[$scheme])) {
202                 $scheme = $aliases[$scheme];
203         }
204
205         return $scheme;
206 }
207
208 // Hostname normlization (Destructive)
209 // www.foo     => www.foo   ('foo' seems TLD)
210 // www.foo.bar => foo.bar
211 // www.10.20   => www.10.20 (Invalid hostname)
212 // NOTE:
213 //   'www' is basically traditional hostname for WWW server.
214 //   In these case, 'www.foo.bar' MAY be identical with 'foo.bar'.
215 function host_normalize($host = '')
216 {
217         if (! is_string($host)) return '';
218
219         $host = strtolower($host);
220
221         $matches = array();
222         if (preg_match('/^www\.(.+\.[a-z]+)$/', $host, $matches)) {
223                 return $matches[1];
224         } else {
225                 return $host;
226         }
227 }
228
229 // Port normalization: Suppress the (redundant) default port
230 // HTTP://example.org:80/ => http://example.org/
231 // HTTP://example.org:8080/ => http://example.org:8080/
232 // HTTPS://example.org:443/ => https://example.org/
233 function port_normalize($port, $scheme, $scheme_normalize = FALSE)
234 {
235         // Schemes that users _maybe_ want to add protocol-handlers
236         // to their web browsers. (and attackers _maybe_ want to use ...)
237         // Reference: http://www.iana.org/assignments/port-numbers
238         static $array = array(
239                 // scheme => default port
240                 'ftp'     =>    21,
241                 'ssh'     =>    22,
242                 'telnet'  =>    23,
243                 'smtp'    =>    25,
244                 'tftp'    =>    69,
245                 'gopher'  =>    70,
246                 'finger'  =>    79,
247                 'http'    =>    80,
248                 'pop3'    =>   110,
249                 'sftp'    =>   115,
250                 'nntp'    =>   119,
251                 'imap'    =>   143,
252                 'irc'     =>   194,
253                 'wais'    =>   210,
254                 'https'   =>   443,
255                 'nntps'   =>   563,
256                 'rsync'   =>   873,
257                 'ftps'    =>   990,
258                 'telnets' =>   992,
259                 'imaps'   =>   993,
260                 'ircs'    =>   994,
261                 'pop3s'   =>   995,
262                 'mysql'   =>  3306,
263         );
264
265         // intval() converts '0-1' to '0', so preg_match() rejects these invalid ones
266         if (! is_numeric($port) || $port < 0 || preg_match('/[^0-9]/i', $port))
267                 return '';
268
269         $port = intval($port);
270         if ($scheme_normalize) $scheme = scheme_normalize($scheme);
271         if (isset($array[$scheme]) && $port == $array[$scheme])
272                 $port = ''; // Ignore the defaults
273
274         return $port;
275 }
276
277 // Path normalization
278 // http://example.org => http://example.org/
279 // http://example.org#hoge => http://example.org/#hoge
280 // http://example.org/path/a/b/./c////./d => http://example.org/path/a/b/c/d
281 // http://example.org/path/../../a/../back => http://example.org/back
282 function path_normalize($path = '', $divider = '/', $add_root = TRUE)
283 {
284         if (! is_string($divider)) return is_string($path) ? $path : '';
285
286         if ($add_root) {
287                 $first_div = & $divider;
288         } else {
289                 $first_div = '';
290         }
291         if (! is_string($path) || $path == '') return $first_div;
292
293         if (strpos($path, $divider, strlen($path) - strlen($divider)) === FALSE) {
294                 $last_div = '';
295         } else {
296                 $last_div = & $divider;
297         }
298
299         $array = explode($divider, $path);
300
301         // Remove paddings ('//' and '/./')
302         foreach(array_keys($array) as $key) {
303                 if ($array[$key] == '' || $array[$key] == '.') {
304                          unset($array[$key]);
305                 }
306         }
307
308         // Remove back-tracks ('/../')
309         $tmp = array();
310         foreach($array as $value) {
311                 if ($value == '..') {
312                         array_pop($tmp);
313                 } else {
314                         array_push($tmp, $value);
315                 }
316         }
317         $array = & $tmp;
318
319         if (empty($array)) {
320                 return $first_div;
321         } else {
322                 return $first_div . implode($divider, $array) . $last_div;
323         }
324 }
325
326 // DirectoryIndex normalize (Destructive and rough)
327 // TODO: sample.en.ja.html.gz => sample.html
328 function file_normalize($file = 'index.html.en')
329 {
330         static $simple_defaults = array(
331                 'default.htm'   => TRUE,
332                 'default.html'  => TRUE,
333                 'default.asp'   => TRUE,
334                 'default.aspx'  => TRUE,
335                 'index'                 => TRUE,        // Some system can omit the suffix
336         );
337
338         static $content_suffix = array(
339                 // index.xxx, sample.xxx
340                 'htm'   => TRUE,
341                 'html'  => TRUE,
342                 'shtml' => TRUE,
343                 'jsp'   => TRUE,
344                 'php'   => TRUE,
345                 'php3'  => TRUE,
346                 'php4'  => TRUE,
347                 'pl'    => TRUE,
348                 'py'    => TRUE,
349                 'rb'    => TRUE,
350                 'cgi'   => TRUE,
351                 'xml'   => TRUE,
352         );
353
354         static $language_suffix = array(
355                 // Reference: Apache 2.0.59 'AddLanguage' default
356                 'ca'    => TRUE,
357                 'cs'    => TRUE,        // cs
358                 'cz'    => TRUE,        // cs
359                 'de'    => TRUE,
360                 'dk'    => TRUE,        // da
361                 'el'    => TRUE,
362                 'en'    => TRUE,
363                 'eo'    => TRUE,
364                 'es'    => TRUE,
365                 'et'    => TRUE,
366                 'fr'    => TRUE,
367                 'he'    => TRUE,
368                 'hr'    => TRUE,
369                 'it'    => TRUE,
370                 'ja'    => TRUE,
371                 'ko'    => TRUE,
372                 'ltz'   => TRUE,
373                 'nl'    => TRUE,
374                 'nn'    => TRUE,
375                 'no'    => TRUE,
376                 'po'    => TRUE,
377                 'pt'    => TRUE,
378                 'pt-br' => TRUE,
379                 'ru'    => TRUE,
380                 'sv'    => TRUE,
381                 'zh-cn' => TRUE,
382                 'zh-tw' => TRUE,
383
384                 // Reference: Apache 2.0.59 default 'index.html' variants
385                 'ee'    => TRUE,
386                 'lb'    => TRUE,
387                 'var'   => TRUE,
388         );
389
390         static $charset_suffix = array(
391                 // Reference: Apache 2.0.59 'AddCharset' default
392                 'iso8859-1'     => TRUE, // ISO-8859-1
393                 'latin1'        => TRUE, // ISO-8859-1
394                 'iso8859-2'     => TRUE, // ISO-8859-2
395                 'latin2'        => TRUE, // ISO-8859-2
396                 'cen'           => TRUE, // ISO-8859-2
397                 'iso8859-3'     => TRUE, // ISO-8859-3
398                 'latin3'        => TRUE, // ISO-8859-3
399                 'iso8859-4'     => TRUE, // ISO-8859-4
400                 'latin4'        => TRUE, // ISO-8859-4
401                 'iso8859-5'     => TRUE, // ISO-8859-5
402                 'latin5'        => TRUE, // ISO-8859-5
403                 'cyr'           => TRUE, // ISO-8859-5
404                 'iso-ru'        => TRUE, // ISO-8859-5
405                 'iso8859-6'     => TRUE, // ISO-8859-6
406                 'latin6'        => TRUE, // ISO-8859-6
407                 'arb'           => TRUE, // ISO-8859-6
408                 'iso8859-7'     => TRUE, // ISO-8859-7
409                 'latin7'        => TRUE, // ISO-8859-7
410                 'grk'           => TRUE, // ISO-8859-7
411                 'iso8859-8'     => TRUE, // ISO-8859-8
412                 'latin8'        => TRUE, // ISO-8859-8
413                 'heb'           => TRUE, // ISO-8859-8
414                 'iso8859-9'     => TRUE, // ISO-8859-9
415                 'latin9'        => TRUE, // ISO-8859-9
416                 'trk'           => TRUE, // ISO-8859-9
417                 'iso2022-jp'=> TRUE, // ISO-2022-JP
418                 'jis'           => TRUE, // ISO-2022-JP
419                 'iso2022-kr'=> TRUE, // ISO-2022-KR
420                 'kis'           => TRUE, // ISO-2022-KR
421                 'iso2022-cn'=> TRUE, // ISO-2022-CN
422                 'cis'           => TRUE, // ISO-2022-CN
423                 'big5'          => TRUE,
424                 'cp-1251'       => TRUE, // ru, WINDOWS-1251
425                 'win-1251'      => TRUE, // ru, WINDOWS-1251
426                 'cp866'         => TRUE, // ru
427                 'koi8-r'        => TRUE, // ru, KOI8-r
428                 'koi8-ru'       => TRUE, // ru, KOI8-r
429                 'koi8-uk'       => TRUE, // ru, KOI8-ru
430                 'ua'            => TRUE, // ru, KOI8-ru
431                 'ucs2'          => TRUE, // ru, ISO-10646-UCS-2
432                 'ucs4'          => TRUE, // ru, ISO-10646-UCS-4
433                 'utf8'          => TRUE,
434
435                 // Reference: Apache 2.0.59 default 'index.html' variants
436                 'euc-kr'        => TRUE,
437                 'gb2312'        => TRUE,
438         );
439
440         // May uncompress by web browsers on the fly
441         // Must be at the last of the filename
442         // Reference: Apache 2.0.59 'AddEncoding'
443         static $encoding_suffix = array(
444                 'z'             => TRUE,
445                 'gz'    => TRUE,
446         );
447
448         if (! is_string($file)) return '';
449         $_file = strtolower($file);
450         if (isset($simple_defaults[$_file])) return '';
451
452         // Roughly removing language/character-set/encoding suffixes
453         // References:
454         //  * Apache 2 document about 'Content-negotiaton', 'mod_mime' and 'mod_negotiation'
455         //    http://httpd.apache.org/docs/2.0/content-negotiation.html
456         //    http://httpd.apache.org/docs/2.0/mod/mod_mime.html
457         //    http://httpd.apache.org/docs/2.0/mod/mod_negotiation.html
458         //  * http://www.iana.org/assignments/character-sets
459         //  * RFC3066: Tags for the Identification of Languages
460         //    http://www.ietf.org/rfc/rfc3066.txt
461         //  * ISO 639: codes of 'language names'
462         $suffixes = explode('.', $_file);
463         $body = array_shift($suffixes);
464         if ($suffixes) {
465                 // Remove the last .gz/.z
466                 $last_key = end(array_keys($suffixes));
467                 if (isset($encoding_suffix[$suffixes[$last_key]])) {
468                         unset($suffixes[$last_key]);
469                 }
470         }
471         // Cut language and charset suffixes
472         foreach($suffixes as $key => $value){
473                 if (isset($language_suffix[$value]) || isset($charset_suffix[$value])) {
474                         unset($suffixes[$key]);
475                 }
476         }
477         if (empty($suffixes)) return $body;
478
479         // Index.xxx
480         $count = count($suffixes);
481         reset($suffixes);
482         $current = current($suffixes);
483         if ($body == 'index' && $count == 1 && isset($content_suffix[$current])) return '';
484
485         return $file;
486 }
487
488 // Sort query-strings if possible (Destructive and rough)
489 // [OK] &&&&f=d&b&d&c&a=0dd  =>  a=0dd&b&c&d&f=d
490 // [OK] nothing==&eg=dummy&eg=padding&eg=foobar  =>  eg=foobar
491 function query_normalize($string = '', $equal = TRUE, $equal_cutempty = TRUE, $stortolower = TRUE)
492 {
493         if (! is_string($string)) return '';
494         if ($stortolower) $string = strtolower($string);
495
496         $array = explode('&', $string);
497
498         // Remove '&' paddings
499         foreach(array_keys($array) as $key) {
500                 if ($array[$key] == '') {
501                          unset($array[$key]);
502                 }
503         }
504
505         // Consider '='-sepalated input and paddings
506         if ($equal) {
507                 $equals = $not_equals = array();
508                 foreach ($array as $part) {
509                         if (strpos($part, '=') === FALSE) {
510                                  $not_equals[] = $part;
511                         } else {
512                                 list($key, $value) = explode('=', $part, 2);
513                                 $value = ltrim($value, '=');
514                                 if (! $equal_cutempty || $value != '') {
515                                         $equals[$key] = $value;
516                                 }
517                         }
518                 }
519
520                 $array = & $not_equals;
521                 foreach ($equals as $key => $value) {
522                         $array[] = $key . '=' . $value;
523                 }
524                 unset($equals);
525         }
526
527         natsort($array);
528         return implode('&', $array);
529 }
530
531 // ---------------------
532 // Area pickup
533
534 // Pickup all of markup areas
535 function area_pickup($string = '', $method = array())
536 {
537         $area = array();
538         if (empty($method)) return $area;
539
540         // Anchor tag pair by preg_match and preg_match_all()
541         // [OK] <a href></a>
542         // [OK] <a href=  >Good site!</a>
543         // [OK] <a href= "#" >test</a>
544         // [OK] <a href="http://nasty.example.com">visit http://nasty.example.com/</a>
545         // [OK] <a href=\'http://nasty.example.com/\' >discount foobar</a> 
546         // [NG] <a href="http://ng.example.com">visit http://ng.example.com _not_ended_
547         $regex = '#<a\b[^>]*\bhref\b[^>]*>.*?</a\b[^>]*(>)#is';
548         if (isset($method['area_anchor'])) {
549                 $areas = array();
550                 $count = isset($method['asap']) ?
551                         preg_match($regex, $string) :
552                         preg_match_all($regex, $string, $areas);
553                 if (! empty($count)) $area['area_anchor'] = $count;
554         }
555         if (isset($method['uri_anchor'])) {
556                 $areas = array();
557                 preg_match_all($regex, $string, $areas, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
558                 foreach(array_keys($areas) as $_area) {
559                         $areas[$_area] =  array(
560                                 $areas[$_area][0][1], // Area start (<a href>)
561                                 $areas[$_area][1][1], // Area end   (</a>)
562                         );
563                 }
564                 if (! empty($areas)) $area['uri_anchor'] = $areas;
565         }
566
567         // phpBB's "BBCode" pair by preg_match and preg_match_all()
568         // [OK] [url][/url]
569         // [OK] [url]http://nasty.example.com/[/url]
570         // [OK] [link]http://nasty.example.com/[/link]
571         // [OK] [url=http://nasty.example.com]visit http://nasty.example.com/[/url]
572         // [OK] [link http://nasty.example.com/]buy something[/link]
573         $regex = '#\[(url|link|img|email)\b[^\]]*\].*?\[/\1\b[^\]]*(\])#is';
574         if (isset($method['area_bbcode'])) {
575                 $areas = array();
576                 $count = isset($method['asap']) ?
577                         preg_match($regex, $string) :
578                         preg_match_all($regex, $string, $areas, PREG_SET_ORDER);
579                 if (! empty($count)) $area['area_bbcode'] = $count;
580         }
581         if (isset($method['uri_bbcode'])) {
582                 $areas = array();
583                 preg_match_all($regex, $string, $areas, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
584                 foreach(array_keys($areas) as $_area) {
585                         $areas[$_area] = array(
586                                 $areas[$_area][0][1], // Area start ([url])
587                                 $areas[$_area][2][1], // Area end   ([/url])
588                         );
589                 }
590                 if (! empty($areas)) $area['uri_bbcode'] = $areas;
591         }
592
593         // Various Wiki syntax
594         // [text_or_uri>text_or_uri]
595         // [text_or_uri:text_or_uri]
596         // [text_or_uri|text_or_uri]
597         // [text_or_uri->text_or_uri]
598         // [text_or_uri text_or_uri] // MediaWiki
599         // MediaWiki: [http://nasty.example.com/ visit http://nasty.example.com/]
600
601         return $area;
602 }
603
604 // If in doubt, it's a little doubtful
605 // if (Area => inside <= Area) $brief += -1
606 function area_measure($areas, & $array, $belief = -1, $a_key = 'area', $o_key = 'offset')
607 {
608         if (! is_array($areas) || ! is_array($array)) return;
609
610         $areas_keys = array_keys($areas);
611         foreach(array_keys($array) as $u_index) {
612                 $offset = isset($array[$u_index][$o_key]) ?
613                         intval($array[$u_index][$o_key]) : 0;
614                 foreach($areas_keys as $a_index) {
615                         if (isset($array[$u_index][$a_key])) {
616                                 $offset_s = intval($areas[$a_index][0]);
617                                 $offset_e = intval($areas[$a_index][1]);
618                                 // [Area => inside <= Area]
619                                 if ($offset_s < $offset && $offset < $offset_e) {
620                                         $array[$u_index][$a_key] += $belief;
621                                 }
622                         }
623                 }
624         }
625 }
626
627
628 // ---------------------
629 // Spam-uri pickup
630
631 // Preprocess: Removing/Modifying uninterest part for URI detection
632 function spam_uri_removing_hocus_pocus($binary = '', $method = array())
633 {
634         $from = $to = array();
635
636         // Remove sequential spaces and too short lines
637         $length = 4 ; // 'http'(1) and '://'(2) and 'fqdn'(1)
638         if (is_array($method)) {
639                 // '<a'(2) or 'href='(5) or '>'(1) or '</a>'(4)
640                 // '[uri'(4) or ']'(1) or '[/uri]'(6) 
641                 if (isset($method['area_anchor']) || isset($method['uri_anchor']) ||
642                     isset($method['area_bbcode']) || isset($method['uri_bbcode']))
643                                 $length = 1;    // Seems not effective
644         }
645         $binary = strings($binary, $length, TRUE, FALSE); // Multibyte NOT needed
646
647         // Remove/Replace quoted-spaces within tags
648         $from[] = '#(<\w+ [^<>]*?\w ?= ?")([^"<>]*? [^"<>]*)("[^<>]*?>)#ie';
649         $to[]   = "'$1' . str_replace(' ' , '%20' , trim('$2')) . '$3'";
650
651         // Remove words (has no '<>[]:') between spaces
652         $from[] = '/[ \t][\w.,()\ \t]+[ \t]/';
653         $to[]   = ' ';
654
655         return preg_replace($from, $to, $binary);
656 }
657
658 // Preprocess: Domain exposure callback (See spam_uri_pickup_preprocess())
659 // http://victim.example.org/?foo+site:nasty.example.com+bar
660 // => http://nasty.example.com/?refer=victim.example.org
661 // NOTE: 'refer=' is not so good for (at this time).
662 // Consider about using IP address of the victim, try to avoid that.
663 function _preg_replace_callback_domain_exposure($matches = array())
664 {
665         $result = '';
666
667         // Preserve the victim URI as a complicity or ...
668         if (isset($matches[5])) {
669                 $result =
670                         $matches[1] . '://' .   // scheme
671                         $matches[2] . '/' .             // victim.example.org
672                         $matches[3];                    // The rest of all (before victim)
673         }
674
675         // Flipped URI
676         if (isset($matches[4])) {
677                 $result = 
678                         $matches[1] . '://' .   // scheme
679                         $matches[4] .                   // nasty.example.com
680                         '/?refer=' . strtolower($matches[2]) .  // victim.example.org
681                         ' ' . $result;
682         }
683
684         return $result;
685 }
686
687 // Preprocess: minor-rawurldecode() and adding space(s) and something
688 // to detect/count some URIs _if possible_
689 // NOTE: It's maybe danger to var_dump(result). [e.g. 'javascript:']
690 // [OK] http://victim.example.org/?site:nasty.example.org
691 // [OK] http://victim.example.org/nasty.example.org
692 // [OK] http://victim.example.org/go?http%3A%2F%2Fnasty.example.org
693 // [OK] http://victim.example.org/http://nasty.example.org
694 function spam_uri_pickup_preprocess($string = '', $method = array())
695 {
696         if (! is_string($string)) return '';
697
698         // rawurldecode(), just to catch encoded 'http://path/to/file', not to change '%20' to ' '
699         $string = strtr(
700                 $string,
701                 array(
702                         '%3A' => ':',
703                         '%3a' => ':',
704                         '%2F' => '/',
705                         '%2f' => '/',
706                         '%5C' => '\\',
707                         '%5c' => '\\',
708                 )
709         );
710
711         $string = spam_uri_removing_hocus_pocus($string, $method);
712
713         // Domain exposure (simple)
714         // http://victim.example.org/nasty.example.org/path#frag
715         // => http://nasty.example.org/?refer=victim.example.org and original
716         $string = preg_replace(
717                 '#h?ttp://' .
718                 '(' .
719                         'a9\.com/' . '|' .
720                         'aboutus\.org/' . '|' .
721                         'alexa\.com/data/details\?url='  . '|' .
722                         'ime\.(?:nu|st)/' . '|' .       // 2ch.net
723                         'link\.toolbot\.com/' . '|' .
724                         'urlx\.org/' . '|' .
725                         'big5.51job.com/gate/big5/'      . '|' .
726                         'big5.china.com/gate/big5/'      . '|' .
727                         'big5.shippingchina.com:8080/' . '|' .
728                         'big5.xinhuanet.com/gate/big5/' . '|' .
729                         'bhomiyo.com/en.xliterate/' . '|' .
730                         'google.com/translate_c\?u=(?:http://)?' . '|' .
731                         'web.archive.org/web/2[^/]*/(?:http://)?' . '|' .
732                         'technorati.com/blogs/' .
733                 ')' .
734                 '([a-z0-9.%_-]+\.[a-z0-9.%_-]+)' .      // nasty.example.org
735                 '#i',
736                 'http://$2/?refer=$1 $0',                       // Preserve $0 or remove?
737                 $string
738         );
739
740         // Domain exposure (site:) See _preg_replace_callback_domain_exposure()
741         $string = preg_replace_callback(
742                 array(
743                         '#(h?ttp)://' . // 1:Scheme
744                         // 2:Host
745                         '(' .
746                                 '(?:[a-z0-9_.-]+\.)?[a-z0-9_-]+\.[a-z0-9_-]+' .
747                                 // Something Google: http://www.google.com/supported_domains
748                                 // AltaVista: http://es.altavista.com/web/results?q=site%3Anasty.example.org+foobar
749                                 // Live Search: search.live.com
750                                 // MySpace: http://sads.myspace.com/Modules/Search/Pages/Search.aspx?_snip_&searchString=site:nasty.example.org
751                                 // (also searchresults.myspace.com)
752                                 // alltheweb.com
753                                 // search.bbc.co.uk
754                                 // search.orange.co.uk
755                                 // ...
756                         ')' .
757                         '/' .
758                         //TODO: Specify URL-enable characters
759                         '([a-z0-9?=&.,%_/\'\\\+-]+)' .                          // 3:path/?query=foo+bar+
760                         '(?:\b|%20)site:([a-z0-9.%_-]+\.[a-z0-9.%_-]+)' .       // 4:site:nasty.example.com
761                         '()' .                                                                          // 5:Preserve or remove?
762                         '#i',
763                 ),
764                 '_preg_replace_callback_domain_exposure',
765                 $string
766         );
767
768         // URI exposure (uriuri => uri uri)
769         $string = preg_replace(
770                 array(
771                         '#(?<! )(?:https?|ftp):/#i',
772                 //      '#[a-z][a-z0-9.+-]{1,8}://#i',
773                 //      '#[a-z][a-z0-9.+-]{1,8}://#i'
774                 ),
775                 ' $0',
776                 $string
777         );
778
779         return $string;
780 }
781
782 // Main function of spam-uri pickup,
783 // A wrapper function of uri_pickup()
784 function spam_uri_pickup($string = '', $method = array())
785 {
786         if (! is_array($method) || empty($method)) {
787                 $method = check_uri_spam_method();
788         }
789
790         $string = spam_uri_pickup_preprocess($string, $method);
791
792         $array  = uri_pickup($string);
793
794         // Area elevation of URIs, for '(especially external)link' intension
795         if (! empty($array)) {
796                 $_method = array();
797                 if (isset($method['uri_anchor'])) $_method['uri_anchor'] = & $method['uri_anchor'];
798                 if (isset($method['uri_bbcode'])) $_method['uri_bbcode'] = & $method['uri_bbcode'];
799                 $areas = area_pickup($string, $_method, TRUE);
800                 if (! empty($areas)) {
801                         $area_shadow = array();
802                         foreach (array_keys($array) as $key) {
803                                 $area_shadow[$key] = & $array[$key]['area'];
804                                 foreach (array_keys($_method) as $_key) {
805                                         $area_shadow[$key][$_key] = 0;
806                                 }
807                         }
808                         foreach (array_keys($_method) as $_key) {
809                                 if (isset($areas[$_key])) {
810                                         area_measure($areas[$_key], $area_shadow, 1, $_key);
811                                 }
812                         }
813                 }
814         }
815
816         // Remove 'offset's for area_measure()
817         foreach(array_keys($array) as $key) {
818                 unset($array[$key]['area']['offset']);
819         }
820
821         return $array;
822 }
823
824 ?>