OSDN Git Service

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