OSDN Git Service

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