OSDN Git Service

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