9 eval 'use Digest::MD5 qw(md5)';
13 eval 'use Encode
qw(decode)';
14 $has_encode=1 unless $@;
17 use constant MAX_UNICODE => 1114111;
23 my $protocol_re=qr{(?:http://|https://|ftp://|mailto:|news:|irc:)};
24 my $url_re=qr{(${protocol_re}[^\s<>()"]*?(?:\([^\s<>()"]*?\)[^\s<>()"]*?)*)((?:\s|<|>|"|\.||\]|!|\?|,|,|")*(?:[\s<>()"]|$))};
26 sub protocol_regexp() { return $protocol_re }
28 sub url_regexp() { return $url_re }
30 sub abbreviate_html($$$)
32 my ($html,$max_lines,$approx_len)=@_;
33 my ($lines,$chars,@stack);
35 return undef unless($max_lines);
37 while($html=~m!(?:([^<]+)|<(/?)(\w+).*?(/?)>)!g)
39 my ($text,$closing,$tag,$implicit)=($1,$2,lc($3),$4);
41 if($text) { $chars+=length $text; }
44 push @stack,$tag if(!$closing and !$implicit);
45 pop @stack if($closing);
47 if(($closing or $implicit) and ($tag eq "p" or $tag eq "blockquote" or $tag eq "pre"
48 or $tag eq "li" or $tag eq "ol" or $tag eq "ul" or $tag eq "br"))
50 $lines+=int($chars/$approx_len)+1;
51 $lines++ if($tag eq "p" or $tag eq "blockquote");
55 if($lines>=$max_lines)
57 # check if there's anything left other than end
-tags
58 return undef if (substr $html,pos $html)=~m!^(?:\s*</\w+>)*\s*$!s;
60 my $abbrev=substr $html,0,pos $html;
61 while(my $tag=pop @stack) { $abbrev.="</$tag>" }
75 my $entity_re=qr/&(?!\#[0-9]+;|\#x[0-9a-fA-F]+;|amp;|lt;|gt;)/;
77 while($html=~/(?:([^<]+)|<([^<>]*)>|(<))/sg)
79 my ($text,$tag,$lt)=($1,$2,$3);
87 $text=~s/$entity_re/&/g;
93 if($tag=~m!^\s*(/?)\s*([a-z0-9_:\-\.]+)(?:\s+(.*?)|)\s*(/?)\s*$!si)
95 my ($closing,$name,$args,$implicit)=($1,lc($2),$3,$4);
101 if(grep { $_ eq $name } @stack)
108 } until $entry eq $name;
117 while($args=~/([a-z0-9_:\-\.]+)(?:\s*=\s*(?:'([^']*?)'|"([^"]*?)"|['"]?([^'" ]*))|)/gi)
119 my ($arg,$value)=(lc($1),defined($2)?
$2:defined($3)?
$3:$4);
120 $value=$arg unless defined($value);
122 my $type=$tags{$name}{args
}{$arg};
128 if($type=~/url/i) { $passes=0 unless $value=~/(?:^${protocol_re}|^[^:]+$)/ }
129 if($type=~/number/i) { $passes=0 unless $value=~/^[0-9]+$/ }
133 $value=~s/$entity_re/&/g;
139 $args{$_}=$tags{$name}{forced
}{$_} for (keys %{$tags{$name}{forced
}}); # override forced arguments
141 my $cleanargs=join " ",map {
147 $implicit="/" if($tags{$name}{empty
});
149 push @stack,$name unless $implicit;
152 $clean.=" $cleanargs" if $cleanargs;
153 #$clean.=" $implicit" if $implicit;
155 $clean.="</$name>" if $implicit;
163 while($entry=pop @stack) { $clean.="</$entry>" }
168 sub describe_allowed
(%)
172 return join ", ",map { $_.($tags{$_}{args
}?
" (".(join ", ",sort keys %{$tags{$_}{args
}}).")":"") } sort keys %tags;
175 sub do_wakabamark
($;$$)
177 my ($text,$handler,$simplify)=@_;
180 my @lines=split /(?:\r\n|\n|\r)/,$text;
182 while(defined($_=$lines[0]))
184 if(/^\s*$/) { shift @lines; } # skip empty lines
185 elsif(/^(1\.|[\*\+\-]) /) # lists
187 my ($tag,$re,$skip,$html);
189 if($1 eq "1.") { $tag="ol"; $re=qr/[0-9]+\./; $skip=1; }
190 else { $tag="ul"; $re=qr/\Q$1\E/; $skip=0; }
192 while($lines[0]=~/^($re)(?: |\t)(.*)/)
194 my $spaces=(length $1)+1;
198 while($lines[0]=~/^(?: {1,$spaces}|\t)(.*)/) { $item.="$1\n"; shift @lines }
199 $html.="<li>".do_wakabamark
($item,$handler,1)."</li>";
201 if($skip) { while(@lines and $lines[0]=~/^\s*$/) { shift @lines; } } # skip empty lines
203 $res.="<$tag>$html</$tag>";
205 elsif(/^(?: |\t)/) # code sections
208 while($lines[0]=~/^(?: |\t)(.*)/) { push @code,$1; shift @lines; }
209 $res.="<pre><code>".(join "<br />",@code)."</code></pre>";
211 elsif(/^>/) # quoted sections
214 while($lines[0]=~/^(>.*)/) { push @quote,$1; shift @lines; }
215 $res.="<blockquote>".do_spans
($handler,@quote)."</blockquote>";
217 #while($lines[0]=~/^>(.*)/) { push @quote,$1; shift @lines; }
218 #$res.="<blockquote>".do_blocks($handler,@quote)."</blockquote>";
220 else # normal paragraph
223 while($lines[0]!~/^(?:\s*$|1\. |[\*\+\-] |>| |\t)/) { push @text,shift @lines; }
224 if(!defined($lines[0]) and $simplify) { $res.=do_spans
($handler,@text) }
225 else { $res.="<p>".do_spans
($handler,@text)."</p>" }
236 return join "<br />",map
241 # hide <code> sections
242 $line=~s{ (?<![\x80-\x9f\xe0-\xfc]) (`+) ([^<>]+?) (?<![\x80-\x9f\xe0-\xfc]) \1}{push @hidden,"<code>$2</code>"; "<!--$#hidden-->"}sgex;
244 # make URLs into links and hide them
245 $line=~s{$url_re}{push @hidden,"<a href=\"$1\" rel=\"nofollow\">$1\</a>"; "<!--$#hidden-->$2"}sge;
248 $line=~s{ (?<![0-9a-zA-Z\*_\x80-\x9f\xe0-\xfc]) (\*\*|__) (?![<>\s\*_]) ([^<>]+?) (?<![<>\s\*_\x80-\x9f\xe0-\xfc]) \1 (?![0-9a-zA-Z\*_]) }{<strong>$2</strong>}gx;
251 $line=~s{ (?<![0-9a-zA-Z\*_\x80-\x9f\xe0-\xfc]) (\*|_) (?![<>\s\*_]) ([^<>]+?) (?<![<>\s\*_\x80-\x9f\xe0-\xfc]) \1 (?![0-9a-zA-Z\*_]) }{<em>$2</em>}gx;
257 $regexp=qr/(?:&#?[0-9a-zA-Z]+;|[^&<>])(?<!\^H)(??{$regexp})?\^H/;
258 $line=~s{($regexp)}{"<del>".(substr $1,0,(length $1)/3)."</del>"}gex;
261 $line=$handler->($line) if($handler);
263 # fix up hidden sections
264 $line=~s{<!--([0-9]+)-->}{$hidden[$1]}ge;
270 sub compile_template
($;$)
272 my ($str,$nostrip)=@_;
282 while($str=~m!(.*?)(<(/?)(var|const|if|loop)(?:|\s+(.*?[^\\]))>|$)!sg)
284 my ($html,$tag,$closing,$name,$args)=($1,$2,$3,$4,$5);
286 $html=~s/(['\\])/\\$1/g;
287 $code.="\$res.='$html';" if(length $html);
294 if($name eq 'if') { $code.='}' }
295 elsif($name eq 'loop') { $code.='$$_=$__ov{$_} for(keys %__ov);}}' }
299 if($name eq 'var') { $code.='$res.=eval{'.$args.'};' }
300 elsif($name eq 'const') { my $const=eval $args; $const=~s/(['\\])/\\$1/g; $code.='$res.=\''.$const.'\';' }
301 elsif($name eq 'if') { $code.='if(eval{'.$args.'}){' }
302 elsif($name eq 'loop')
303 { $code.='my $__a=eval{'.$args.'};if($__a){for(@$__a){my %__v=%{$_};my %__ov;for(keys %__v){$__ov{$_}=$$_;$$_=$__v{$_};}' }
310 'my $port=$ENV{SERVER_PORT}==80?"":":$ENV{SERVER_PORT}";'.
311 'my $self=$ENV{SCRIPT_NAME};'.
312 'my $absolute_self="http://$ENV{SERVER_NAME}$port$ENV{SCRIPT_NAME}";'.
313 'my ($path)=$ENV{SCRIPT_NAME}=~m!^(.*/)[^/]+$!;'.
314 'my $absolute_path="http://$ENV{SERVER_NAME}$port$path";'.
315 'my %__v=@_;my %__ov;for(keys %__v){$__ov{$_}=$$_;$$_=$__v{$_};}'.
318 '$$_=$__ov{$_} for(keys %__ov);'.
321 die "Template format error" unless $sub;
326 sub template_for
($$$)
328 my ($var,$start,$end)=@_;
329 return [map +{$var=>$_},($start..$end)];
336 open FILE
,$filename or return '';
337 my $file=do { local $/; <FILE
> };
347 sub forbidden_unicode
($;$)
350 return 1 if length($dec)>7 or length($hex)>7; # too long numbers
351 my $ord=($dec or hex $hex);
353 return 1 if $ord>MAX_UNICODE
; # outside unicode range
354 return 1 if $ord<32; # control chars
355 return 1 if $ord>=0x7f and $ord<=0x84; # control chars
356 return 1 if $ord>=0xd800 and $ord<=0xdfff; # surrogate code points
357 return 1 if $ord>=0x202a and $ord<=0x202e; # text direction
358 return 1 if $ord>=0xfdd0 and $ord<=0xfdef; # non-characters
359 return 1 if $ord % 0x10000 >= 0xfffe; # non-characters
363 sub clean_string
($;$)
365 my ($str,$cleanentities)=@_;
367 if($cleanentities) { $str=~s/&/&/g } # clean up &
370 $str=~s
/&(#([0-9]+);|#x([0-9a-fA-F]+);|)/
371 if($1 eq "") { '&' } # change simple ampersands
372 elsif(forbidden_unicode
($2,$3)) { "" } # strip forbidden unicode chars
373 else { "&$1" } # and leave the rest as-is.
374 /ge # clean up &, excluding numerical entities
377 $str=~s/\</</g; # clean up brackets for HTML tags
379 $str=~s/"/"/g; # clean up quotes for HTML attributes
381 $str=~s/,/,/g; # clean up commas for some reason I forgot
383 $str=~s/[\x00-\x08\x0b\x0c\x0e-\x1f]//g; # remove control chars
388 sub decode_string
($;$$)
390 my ($str,$charset,$noentities)=@_;
391 my $use_unicode=$has_encode && $charset;
393 $str=decode
($charset,$str) if $use_unicode;
395 $str=~s
{(&#([0-9]*)([;&])|&#([x&])([0-9a-f]*)([;&]))}{
396 my $ord=($2 or hex $5);
397 if($3 eq '&' or $4 eq '&' or $5 eq '&') { $1 } # nested entities, leave as-is.
398 elsif(forbidden_unicode
($2,$5)) { "" } # strip forbidden unicode chars
399 elsif($ord==35 or $ord==38) { $1 } # don't convert & or #
400 elsif($use_unicode) { chr $ord } # if we have unicode support, convert all entities
401 elsif($ord<128) { chr $ord } # otherwise just convert ASCII-range entities
402 else { $1 } # and leave the rest as-is.
403 }gei
unless $noentities;
405 $str=~s/[\x00-\x08\x0b\x0c\x0e-\x1f]//g; # remove control chars
420 $str=~s/([^\w ])/"%".sprintf("%02x",ord $1)/sge;
428 $str=~s!([^\w/._\-])!"%".sprintf("%02x",ord $1)!sge;
435 # Javascript utilities
442 $str=~s/&/\\x26/g;
443 $str=~s/</\\x3c/g;
444 $str=~s/>/\\x3e/g;
445 $str=~s/"/\\x22/g; #"
446 $str=~s/('|')/\\x27/g;
448 $str=~s/&#[0-9]+;/sprintf "\\u%04x",$1/ge;
449 $str=~s/&#x[0-9a-f]+;/sprintf "\\u%04x",hex($1)/gie;
450 $str=~s/(\r\n|\r|\n)/\\n/g;
461 $str=~s/([\x00-\x1f\x80-\xff<>&])/sprintf "\\x%02x",ord($1)/ge;
462 eval '$str=~s/([\x{100}-\x{ffff}])/sprintf "\\u%04x",ord($1)/ge';
463 $str=~s/(\r\n|\r|\n)/\\n/g;
470 return "[".(join ",",@_)."]";
476 return "{".(join ",",map "'$_':$hash{$_}",keys %hash)."}";
484 # LIGHTWEIGHT HTTP/1.1 CLIENT
485 # by fatalM4/coda, modified by WAHa.06x36
487 use constant CACHEFILE_PREFIX
=> 'cache-'; # you can make this a directory (e.g. 'cachedir/cache-' ) if you'd like
488 use constant FORCETIME
=> '0.04'; # If the cache is less than (FORCETIME) days old, don't even attempt to refresh.
489 # Saves everyone some bandwidth. 0.04 days is ~ 1 hour. 0.0007 days is ~ 1 min.
490 eval 'use IO::Socket::INET'; # Will fail on old Perl versions!
494 my ($url,$maxsize,$referer,$cacheprefix)=@_;
495 my ($host,$port,$doc)=$url=~m!^(?:http://|)([^/]+)(:[0-9]+|)(.*)$!;
496 $port=80 unless($port);
498 my $hash=encode_base64
(rc4
(null_string
(6),"$host:$port$doc",0),"");
499 $hash=~tr!/+!_-!; # remove / and +
500 my $cachefile=($cacheprefix or CACHEFILE_PREFIX
).($doc=~m!([^/]{0,15})$!)[0]."-$hash"; # up to 15 chars of filename
501 my ($modified,$cache);
503 if(open CACHE
,"<",$cachefile) # get modified date and cache contents
506 $cache=join "",<CACHE
>;
510 return $cache if((-M
$cachefile)<FORCETIME
);
513 my $sock=IO
::Socket
::INET
->new("$host:$port") or return $cache;
514 print $sock "GET $doc HTTP/1.1\r\nHost: $host\r\nConnection: close\r\n";
515 print $sock "If-Modified-Since: $modified\r\n" if $modified;
516 print $sock "Referer: $referer\r\n" if $referer;
517 print $sock "\r\n"; #finished!
520 my ($line,$statuscode,$lastmod);
523 $statuscode=$1 if($line=~/^HTTP\/1\
.1 (\d
+)/);
524 $lastmod=$1 if($line=~/^Last-Modified: (.*)/);
525 } until ($line=~/^\r?\n/);
532 last if $maxsize and $output>=$maxsize;
536 if($statuscode=="200")
538 #navbar changed, update cache
539 if(open CACHE
,">$cachefile")
541 print CACHE
"$lastmod\n";
543 close CACHE
or die "close cache: $!";
547 else # touch and return cache, or nothing if no cache
549 utime(time,time,$cachefile);
554 sub make_http_forward
($;$)
556 my ($location,$alternate_method)=@_;
558 if($alternate_method)
560 print "Content-Type: text/html\n";
562 print "<html><head>";
563 print '<meta http-equiv="refresh" content="0; url='.$location.'" />';
564 print '<script type="text/javascript">document.location="'.$location.'";</script>';
565 print '</head><body><a href="'.$location.'">'.$location.'</a></body></html>';
569 print "Status: 303 Go West\n";
570 print "Location: $location\n";
571 print "Content-Type: text/html\n";
573 print '<html><body><a href="'.$location.'">'.$location.'</a></body></html>';
581 my $charset=$cookies{'-charset'};
582 my $expires=($cookies{'-expires'} or time+14*24*3600);
583 my $autopath=$cookies{'-autopath'};
584 my $path=$cookies{'-path'};
586 my $date=make_date
($expires,"cookie");
590 if($autopath eq 'current') { ($path)=$ENV{SCRIPT_NAME
}=~m!^(.*/)[^/]+$! }
591 elsif($autopath eq 'parent') { ($path)=$ENV{SCRIPT_NAME
}=~m!^(.*?/)(?:[^/]+/)?[^/]+$! }
595 foreach my $name (keys %cookies)
597 next if($name=~/^-/); # skip entries that start with a dash
599 my $value=$cookies{$name};
600 $value="" unless(defined $value);
602 $value=cookie_encode
($value,$charset);
604 print "Set-Cookie: $name=$value; path=$path; expires=$date;\n";
608 sub cookie_encode
($;$)
610 my ($str,$charset)=@_;
612 if($]>5.007) # new perl, use Encode.pm
617 $str=Encode
::decode
($charset,$str);
618 $str=~s/&\#([0-9]+);/chr $1/ge;
619 $str=~s/&\#x([0-9a-f]+);/chr hex $1/gei;
622 $str=~s
/([^0-9a-zA-Z])/
624 sprintf($c>255?
'%%u%04x':'%%%02x',$c);
627 else # do the hard work ourselves
629 if($charset=~/\butf-?8$/i)
631 $str=~s
{([\xe0-\xef][\x80-\xBF][\x80-\xBF]|[\xc0-\xdf][\x80-\xBF]|&#([0-9]+);|&#[xX]([0-9a-fA-F]+);|[^0-9a-zA-Z])}{ # convert UTF-8 to URL encoding - only handles up to U-FFFF
634 elsif($3) { $c=hex $3 }
635 elsif(length $1==1) { $c=ord $1 }
638 my @b=map { ord $_ } split //,$1;
639 $c=(($b[0]-0xc0)<<6)+($b[1]-0x80);
643 my @b=map { ord $_ } split //,$1;
644 $c=(($b[0]-0xe0)<<12)+(($b[1]-0x80)<<6)+($b[2]-0x80);
646 sprintf($c>255?
'%%u%04x':'%%%02x',$c);
649 elsif($charset=~/\b(?:shift.*jis|sjis)$/i) # old perl, using shift_jis
652 my $sjis_table=get_sjis_table
();
654 $str=~s
{([\x80-\x9f\xe0-\xfc].|&#([0-9]+);|&#[xX]([0-9a-fA-F]+);|[^0-9a-zA-Z])}{ # convert Shift_JIS to URL encoding
655 my $c=($2 or ($3 and hex $3) or $$sjis_table{$1});
656 sprintf($c>255?
'%%u%04x':'%%%02x',$c);
661 $str=~s/([^0-9a-zA-Z])/sprintf('%%%02x',ord $1)/sge;
668 sub get_xhtml_content_type
(;$$)
670 my ($charset,$usexhtml)=@_;
673 if($usexhtml and $ENV{HTTP_ACCEPT
}=~/application\/xhtml\
+xml
/) { $type="application/xhtml
+xml
"; }
674 else { $type="text
/html
"; }
676 $type.="; charset
=$charset" if($charset);
681 sub expand_filename($)
684 return $filename if($filename=~m!^/!);
685 return $filename if($filename=~m!^\w+:!);
687 my ($self_path)=$ENV{SCRIPT_NAME}=~m!^(.*/)[^/]+$!;
688 return $self_path.$filename;
698 return (gethostbyaddr inet_aton($ip),AF_INET or $ip);
706 sub process_tripcode($;$$$$)
708 my ($name,$tripkey,$secret,$charset,$nonamedecoding)=@_;
709 $tripkey="!" unless($tripkey);
711 if($name=~/^(.*?)((?<!&)#|\Q$tripkey\E)(.*)$/)
713 my ($namepart,$marker,$trippart)=($1,$2,$3);
716 $namepart=decode_string($namepart,$charset) unless $nonamedecoding;
717 $namepart=clean_string($namepart);
719 if($secret and $trippart=~s/(?:\Q$marker\E)(?<!&#)(?:\Q$marker\E)*(.*)$//) # do we want secure trips, and is there one?
722 my $maxlen=255-length($secret);
723 $str=substr $str,0,$maxlen if(length($str)>$maxlen);
724 # $trip=$tripkey.$tripkey.encode_base64(rc4(null_string(6),"t
".$str.$secret),"");
725 $trip=$tripkey.$tripkey.hide_data($1,6,"trip
",$secret,1);
726 return ($namepart,$trip) unless($trippart); # return directly if there's no normal tripcode
729 # 2ch trips are processed as Shift_JIS whenever possible
730 eval 'use Encode qw(decode encode)';
733 $trippart=decode_string($trippart,$charset);
734 $trippart=encode("Shift_JIS",$trippart,0x0200);
737 $trippart=clean_string($trippart);
738 my $salt=substr $trippart."H..",1,2;
739 $salt=~s/[^\.-z]/./g;
740 $salt=~tr/:;<=>?@[\\]^_`/ABCDEFGabcdef/;
741 $trip=$tripkey.(substr crypt($trippart,$salt),-10).$trip;
743 return ($namepart,$trip);
746 return clean_string($name) if $nonamedecoding;
747 return (clean_string(decode_string($name,$charset)),"");
752 my ($time,$style,@locdays)=@_;
753 my @days=qw(Sun Mon Tue Wed Thu Fri Sat);
754 my @months=qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
755 @locdays=@days unless(@locdays);
759 my @ltime=localtime($time);
761 return sprintf("%04d-%02d-%02d %02d:%02d",
762 $ltime[5]+1900,$ltime[4]+1,$ltime[3],$ltime[2],$ltime[1]);
764 elsif($style eq "futaba" or $style eq "0")
766 my @ltime=localtime($time);
768 return sprintf("%02d/%02d/%02d(%s)%02d:%02d",
769 $ltime[5]-100,$ltime[4]+1,$ltime[3],$locdays[$ltime[6]],$ltime[2],$ltime[1]);
771 elsif($style eq "localtime")
773 return scalar(localtime($time));
775 elsif($style eq "tiny")
777 my @ltime=localtime($time);
779 return sprintf("%02d/%02d %02d:%02d",
780 $ltime[4]+1,$ltime[3],$ltime[2],$ltime[1]);
782 elsif($style eq "http")
784 my ($sec,$min,$hour,$mday,$mon,$year,$wday)=gmtime($time);
785 return sprintf("%s, %02d %s %04d %02d:%02d:%02d GMT",
786 $days[$wday],$mday,$months[$mon],$year+1900,$hour,$min,$sec);
788 elsif($style eq "cookie")
790 my ($sec,$min,$hour,$mday,$mon,$year,$wday)=gmtime($time);
791 return sprintf("%s, %02d-%s-%04d %02d:%02d:%02d GMT",
792 $days[$wday],$mday,$months[$mon],$year+1900,$hour,$min,$sec);
794 elsif($style eq "month")
796 my ($sec,$min,$hour,$mday,$mon,$year,$wday)=gmtime($time);
797 return sprintf("%s %d",
798 $months[$mon],$year+1900);
800 elsif($style eq "2ch-sep93")
802 my $sep93=timelocal
(0,0,0,1,8,93);
803 return make_date
($time,"2ch") if($time<$sep93);
805 my @ltime=localtime($time);
807 return sprintf("%04d-%02d-%02d %02d:%02d",
808 1993,9,int ($time-$sep93)/86400+1,$ltime[2],$ltime[1]);
812 sub parse_http_date
($)
815 my %months=(Jan
=>0,Feb
=>1,Mar
=>2,Apr
=>3,May
=>4,Jun
=>5,Jul
=>6,Aug
=>7,Sep
=>8,Oct
=>9,Nov
=>10,Dec
=>11);
817 if($date=~/^[SMTWF][a-z][a-z], (\d\d) ([JFMASOND][a-z][a-z]) (\d\d\d\d) (\d\d):(\d\d):(\d\d) GMT$/)
818 { return eval { timegm
($6,$5,$4,$1,$months{$2},$3-1900) } }
825 my ($str,%grammar)=@_;
827 my @expansions=@
{$grammar{$1}};
828 cfg_expand
($expansions[rand @expansions],%grammar);
833 sub encode_base64
($;$) # stolen from MIME::Base64::Perl
836 $eol="\n" unless(defined $eol);
838 my $res=pack "u",$data;
839 $res=~s/^.//mg; # remove length counts
840 $res=~s/\n//g; # remove newlines
841 $res=~tr
|` -_|AA-Za-z0-9+/|; # translate to base64
843 my $padding=(3-length($data)%3)%3; # fix padding at the end
844 $res=~s/.{$padding}$/'='x$padding/e if($padding);
846 $res=~s/(.{1,76})/$1$eol/g if(length $eol); # break encoded string into lines of no more than 76 characters each
851 sub decode_base64($) # stolen from MIME::Base64::Perl
855 $str=~tr|A-Za-z0-9+=/||cd; # remove non-base64 characters
856 $str=~s/=+$//; # remove padding
857 $str=~tr|A-Za-z0-9+/| -_|; # translate to uuencode
858 return "" unless(length $str);
859 return unpack "u",join '',map { chr(32+length($_)*3/4).$_ } $str=~/(.{1,60})/gs;
864 return unpack('N',pack('C4',split(/\./, $_[0]))); # wow, magic.
869 return join('.',unpack('C4',pack('N',$_[0])));
874 my ($ip,$key,$algorithm)=@_;
876 $ip=dot_to_dec($ip) if $ip=~/\./;
878 my ($block,$stir)=setup_masking($key,$algorithm);
883 my $bit=$ip&$mask?"1":"0";
884 $block=$stir->($block);
885 $ip^=$mask if(ord($block)&0x80);
890 return sprintf "%08x",$ip;
895 my ($id,$key,$algorithm)=@_;
899 my ($block,$stir)=setup_masking($key,$algorithm);
904 $block=$stir->($block);
905 $id^=$mask if(ord($block)&0x80);
906 my $bit=$id&$mask?"1":"0";
911 return dec_to_dot($id);
914 sub setup_masking($$)
916 my ($key,$algorithm)=@_;
918 $algorithm=$has_md5?"md5":"rc6" unless $algorithm;
922 if($algorithm eq "md5")
924 return (md5($key),sub { md5(shift) })
929 return (null_string(16),sub { encrypt_rc6(shift) })
933 sub make_random_string($)
936 my $chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
939 $str.=substr $chars,rand length $chars,1 for(1..$num);
944 sub null_string($) { "\0"x(shift) }
948 my ($key,$secret,$length)=@_;
949 return rc4(null_string($length),$key.$secret);
952 sub hide_data($$$$;$)
954 my ($data,$bytes,$key,$secret,$base64)=@_;
956 my $crypt=rc4(null_string($bytes),make_key($key,$secret,32).$data);
958 return encode_base64($crypt,"") if $base64;
972 if(ref $file eq "GLOB")
974 return map { s/\r?\n?$//; $_ } <$file>;
978 open FILE,$file or return ();
980 my @array=map { s/\r?\n?$//; $_ } <FILE>;
988 my ($file,@array)=@_;
990 if(ref $file eq "GLOB")
992 print $file join "\n",@array;
994 else # super-paranoid atomic write
996 my $rndname1="__".make_random_string(12).".dat";
997 my $rndname2="__".make_random_string(12).".dat";
998 if(open FILE,">$rndname1")
1001 if(print FILE join "\n",@array)
1004 rename $file,$rndname2 if -e $file;
1005 if(rename $rndname1,$file)
1007 unlink $rndname2 if -e $rndname2;
1013 die "Couldn't write to file \"$file\"";
1023 sub spam_check($$) # Deprecated function
1025 my ($text,$spamfile)=@_;
1026 return compile_spam_checker($spamfile)->($text);
1029 sub compile_spam_checker(@)
1032 s{(\\?\\?&\\?#([0-9]+)\\?;|\\?&\\?#x([0-9a-f]+)\\?;)}{
1033 sprintf("\\x{%x}",($2 or hex $3));
1034 }gei if $has_encode;
1037 s/(^|\s+)#.*//; s/^\s+//; s/\s+$//; # strip perl-style comments and whitespace
1038 if(!length) { () } # nothing left, skip
1039 elsif(m!^/(.*)/$!) { $1 } # a regular expression
1040 elsif(m!^/(.*)/([xism]+)$!) { "(?$2)$1" } # a regular expression with xism modifiers
1041 else { quotemeta } # a normal string
1042 } map read_array($_),@_;
1046 # study; # causes a strange bug - moved to spam_engine()
1047 return '.(join "||",map "/$_/mo",(@re)).';
1054 my @spam_files=@{$args{spam_files}||[]};
1055 my @trap_fields=@{$args{trap_fields}||[]};
1056 my @included_fields=@{$args{included_fields}||[]};
1057 my %excluded_fields=map ($_=>1),@{$args{excluded_fields}||[]};
1058 my $query=$args{query}||new CGI;
1059 my $charset=$args{charset};
1061 for(@trap_fields) { spam_screen($query) if $query->param($_) }
1063 my $spam_checker=compile_spam_checker(@spam_files);
1064 my @fields=@included_fields?@included_fields:$query->param;
1065 @fields=grep !$excluded_fields{$_},@fields if %excluded_fields;
1066 # my $fulltext=join "\n",map decode_string($query->param($_),$charset),@fields;
1067 my $fulltext=join "\n",map $query->param($_),@fields;
1070 spam_screen($query) if $spam_checker->($fulltext);
1077 print "Content-Type: text/html\n\n";
1078 print "<html><body>";
1079 print "<h1>Anti-spam filters triggered.</h1>";
1080 print "<p>If you are not a spammer, you are probably accidentially ";
1081 print "trying to use an URL that is listed in the spam file. Try ";
1082 print "editing your post to remove it. Sorry for any inconvenience.</p>";
1083 print "<small style='color:white'><small>";
1084 print "$_<br>" for(map $query->param($_),$query->param);
1085 print "</small></small>";
1095 sub analyze_image($$)
1097 my ($file,$name)=@_;
1100 safety_check($file);
1102 return ("jpg",@res) if(@res=analyze_jpeg($file));
1103 return ("png",@res) if(@res=analyze_png($file));
1104 return ("gif",@res) if(@res=analyze_gif($file));
1106 # find file extension for unknown files
1107 my ($ext)=$name=~/\.([^\.]+)$/;
1108 return (lc($ext),0,0);
1111 sub safety_check($file)
1115 # Check for IE MIME sniffing XSS exploit - thanks, MS, totally appreciating this
1116 read $file,my $buffer,256;
1118 die "Possible IE XSS exploit in file" if $buffer=~/<(?:body|head|html|img|plaintext|pre|script|table|title|a href|channel|scriptlet)/;
1126 read($file,$buffer,2);
1128 if($buffer eq "\xff\xd8")
1135 last OUTER unless(read($file,$buffer,1));
1136 last if($buffer eq "\xff");
1139 last unless(read($file,$buffer,3)==3);
1140 my ($mark,$size)=unpack("Cn",$buffer);
1141 last if($mark==0xda or $mark==0xd9); # SOS/EOI
1142 die "Possible virus in image" if($size<2); # MS GDI+ JPEG exploit uses short chunks
1144 if($mark>=0xc0 and $mark<=0xc2) # SOF0..SOF2 - what the hell are the rest?
1146 last unless(read($file,$buffer,5)==5);
1147 my ($bits,$height,$width)=unpack("Cnn",$buffer);
1150 return($width,$height);
1153 seek($file,$size-2,1);
1165 my ($bytes,$buffer);
1167 $bytes=read($file,$buffer,24);
1169 return () unless($bytes==24);
1171 my ($magic1,$magic2,$length,$ihdr,$width,$height)=unpack("NNNNNN",$buffer);
1173 return () unless($magic1==0x89504e47 and $magic2==0x0d0a1a0a and $ihdr==0x49484452);
1175 return ($width,$height);
1181 my ($bytes,$buffer);
1183 $bytes=read($file,$buffer,10);
1185 return () unless($bytes==10);
1187 my ($magic,$width,$height)=unpack("A6 vv",$buffer);
1189 return () unless($magic eq "GIF87a" or $magic eq "GIF89a");
1191 return ($width,$height);
1194 sub make_thumbnail($$$$$;$)
1196 my ($filename,$thumbnail,$width,$height,$quality,$convert)=@_;
1198 # first try ImageMagick
1200 my $magickname=$filename;
1201 $magickname.="[0]" if($magickname=~/\.gif$/);
1203 $convert="convert" unless($convert);
1204 `$convert -background white
-flatten
-size
${width
}x
${height
} -geometry
${width
}x
${height
}! -quality
$quality $magickname $thumbnail`;
1206 return 1 unless($?);
1208 # if that fails, try pnmtools instead
1210 if($filename=~/\.jpg$/)
1212 `djpeg
$filename | pnmscale
-width
$width -height
$height | cjpeg
-quality
$quality > $thumbnail`;
1213 # could use -scale 1/n
1214 return 1 unless($?);
1216 elsif($filename=~/\.png$/)
1218 `pngtopnm
$filename | pnmscale
-width
$width -height
$height | cjpeg
-quality
$quality > $thumbnail`;
1219 return 1 unless($?);
1221 elsif($filename=~/\.gif$/)
1223 `giftopnm
$filename | pnmscale
-width
$width -height
$height | cjpeg
-quality
$quality > $thumbnail`;
1224 return 1 unless($?);
1227 # try Mac OS X's sips
1229 `sips
-z
$height $width -s formatOptions normal
-s format jpeg
$filename --out
$thumbnail >/dev/null
`; # quality setting doesn't seem to work
1230 return 1 unless($?);
1232 # try PerlMagick (it sucks)
1234 eval 'use Image::Magick';
1239 $magick=Image::Magick->new;
1241 $res=$magick->Read($magickname);
1243 $res=$magick->Scale(width=>$width, height=>$height);
1244 #return 0 if "$res";
1245 $res=$magick->Write(filename=>$thumbnail, quality=>$quality);
1246 #return 0 if "$res";
1251 # try GD lib (also sucks, and untested)
1256 if($filename=~/\.jpg$/i) { $src=GD::Image->newFromJpeg($filename) }
1257 elsif($filename=~/\.png$/i) { $src=GD::Image->newFromPng($filename) }
1258 elsif($filename=~/\.gif$/i)
1260 if(defined &GD::Image->newFromGif) { $src=GD::Image->newFromGif($filename) }
1263 `gif2png
$filename`; # gif2png taken from futallaby
1264 $filename=~s/\.gif/\.png/;
1265 $src=GD::Image->newFromPng($filename);
1270 my ($img_w,$img_h)=$src->getBounds();
1271 my $thumb=GD::Image->new($width,$height);
1272 $thumb->copyResized($src,0,0,0,0,$width,$height,$img_w,$img_h);
1273 my $jpg=$thumb->jpeg($quality);
1274 open THUMBNAIL,">$thumbnail";
1276 print THUMBNAIL $jpg;
1278 return 1 unless($!);
1291 my ($message,$key,$skip)=@_;
1293 my @k=unpack 'C*',$key;
1294 my @message=unpack 'C*',$message;
1296 $skip=256 unless(defined $skip);
1301 $y=($y+$s[$x]+$k[$x%@k])%256;
1302 @s[$x,$y]=@s[$y,$x];
1310 @s[$x,$y]=@s[$y,$x];
1317 @s[$x,$y]=@s[$y,$x];
1318 $_^=$s[($s[$x]+$s[$y])%256];
1321 return pack 'C*',@message;
1330 $key.="\0"x(4-(length $key)&3); # pad key
1332 my @L=unpack "V*",$key;
1335 $S[$_]=add($S[$_-1],0x9e3779b9) for(1..43);
1337 my $v=@L>44 ? @L*3 : 132;
1338 my ($A,$B,$i,$j)=(0,0,0,0);
1342 $A=$S[$i]=rol(add($S[$i],$A,$B),3);
1343 $B=$L[$j]=rol(add($L[$j]+$A+$B),add($A+$B));
1352 my ($A,$B,$C,$D)=unpack "V4",$block."\0"x16;
1357 for(my $i=1;$i<=20;$i++)
1359 my $t=rol(mul($B,rol($B,1)|1),5);
1360 my $u=rol(mul($D,rol($D,1)|1),5);
1361 $A=add(rol($A^$t,$u),$S[2*$i]);
1362 $C=add(rol($C^$u,$t),$S[2*$i+1]);
1364 ($A,$B,$C,$D)=($B,$C,$D,$A);
1370 return pack "V4",$A,$B,$C,$D;
1376 my ($A,$B,$C,$D)=unpack "V4",$block."\0"x16;
1381 for(my $i=20;$i>=1;$i--)
1383 ($A,$B,$C,$D)=($D,$A,$B,$C);
1384 my $u=rol(mul($D,add(rol($D,1)|1)),5);
1385 my $t=rol(mul($B,add(rol($B,1)|1)),5);
1386 $C=ror(add($C,-$S[2*$i+1]),$t)^$u;
1387 $A=ror(add($A,-$S[2*$i]),$u)^$t;
1391 $D=add32($D,-$S[1]);
1392 $B=add32($B,-$S[0]);
1394 return pack "V4",$A,$B,$C,$D;
1409 sub add(@) { my ($sum,$term); while(defined ($term=shift)) { $sum+=$term } return $sum%4294967296 }
1410 sub rol($$) { my ($x,$n); ( $x = shift ) << ( $n = 31 & shift ) | 2**$n - 1 & $x >> 32 - $n; }
1411 sub ror($$) { rol(shift,32-(31&shift)); } # rorororor
1412 sub mul($$) { my ($a,$b)=@_; return ( (($a>>16)*($b&65535)+($b>>16)*($a&65535))*65536+($a&65535)*($b&65535) )%4294967296 }