OSDN Git Service

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