Skip to content

Instantly share code, notes, and snippets.

@kimarx
Created February 17, 2012 13:37
Show Gist options
  • Save kimarx/1853485 to your computer and use it in GitHub Desktop.
Save kimarx/1853485 to your computer and use it in GitHub Desktop.
4chan clone bbs.cgi
#!/usr/bin/perl
use strict;
use warnings;
use utf8;
package CGI::Four::Register;
use Mouse;
use CGI::Carp qw( fatalsToBrowser );
use CGI::Minimal;
use FindBin;
use DBI;
use SQL::Abstract::Limit;
use Image::Magick;
use Encode;
use Time::Piece;
# -----------
has 'db_name' => ( is => 'rw', isa => 'Str', );
has 'db_port' => ( is => 'rw', isa => 'Int', );
has 'db_user' => ( is => 'rw', isa => 'Str', );
has 'log_table' => ( is => 'rw', isa => 'Str', );
# -----------
has 'max_thread' => ( is => 'rw',
isa => 'Int',
default => 1000, );
has 'max_comment' => ( is => 'rw',
isa => 'Int',
default => 50, );
has 'base_uri' => ( is => 'rw', isa => 'Str', );
has 'master_key' => ( is => 'rw', isa => 'Str', );
has 'cgi_name' => ( is => 'rw',
isa => 'Str',
default => 'read.cgi', );
has 'image_dir' => ( is => 'rw',
isa => 'Str',
default => 'image', );
has 'thumb_dir' => ( is => 'rw',
isa => 'Str',
default => 'thumb', );
has 'max_post_size' => ( is => 'rw',
isa => 'Int',
default => 5 * 1024 * 1024, );
has 'max_message_length' => ( is => 'rw',
isa => 'Int',
default => 1024, );
has 'interval_limit' => ( is => 'rw',
isa => 'Int',
default => 3 * 60, );
has 'thumb_geometry' => ( is => 'rw',
isa => 'Int',
default => 126, );
sub get_query {
my $self = shift;
CGI::Minimal::reset_globals();
CGI::Minimal::max_read_size( $self->max_post_size );
my $q = CGI::Minimal->new;
if ( $q->truncated ) {
my $too_large =qq| This file is too large. |;
$self->error_message( $too_large );
exit;
}
if( $q->param( 'request' ) eq 'DEL' ) {
my $id = $q->param( 'id' );
my $delete_key = $q->param( 'del_pass' );
unless ( $delete_key =~ /[0-9a-zA-Z]+/ ) {
my $no_multibyte = "This password could not be received. $delete_key";
$self->error_message( $no_multibyte );
} else {
$delete_key = decode( 'utf8', $q->htmlize( $delete_key ) );
$self->delete_message( $id, $delete_key );
my $location = $self->base_uri . $self->cgi_name;
print "Location: " . $location . "\n\n";
}
} elsif ( ( $q->param( 'MESSAGE' ) ne '' ) &&
( $q->param( 'MESSAGE' ) =~ /\S/ ) ) {
my @values = ( $q->param( 'FROM' ),
$q->param( 'mail' ),
$q->param( 'subject' ),
$q->param( 'MESSAGE' ),
$q->param( 'pwd' ), );
foreach my $value ( @values ) {
my $tmp_value = decode( 'utf8', $value );
$value = $q->htmlize( $tmp_value );
}
my ( $name, $mail, $subject, $message, $password ) = @values;
my $length = length( $message );
if ( $length <= $self->max_message_length ) {
if ( $name eq '' ) {
$name = 'Anonymous';
} elsif ( $name =~ /(#.{4,})/ ) {
my $tripkey = $1;
my $tripcode = $self->tripcode( $tripkey );
$name =~ s/$tripkey/!$tripcode/;
}
$message = $self->link_check( $message );
# get hostname from ip.
my $ip = $ENV{'REMOTE_ADDR'};
my $hostname = $self->get_host_by_addr( $ip );
# interval check
if ( ( $hostname eq '' ) ||
( $self->check_interval( $hostname ) ) ) {
my $short = 'There is no hurry!';
$self->error_message( $short );
exit;
}
# file upload check
my ( $img_path, $thumb_path, $img_size, $img_geometry );
if ( $q->param_filename( 'upfile' ) ) {
( $img_path, $thumb_path, $img_size, $img_geometry )
= $self->upload_process( $q );
}
my $key = $q->param( 'key' );
$self->register( $key, $name, $mail, $subject, $message, $password,
$img_path, $thumb_path, $img_size, $img_geometry,
$ip, $hostname );
$self->update_com_number( $key ) if $key ne '';
my $location = $self->base_uri . $self->cgi_name;
print "Location: $location\n\n";
exit;
} else {
my $too_long = "Perhaps your message's length is too long.";
$self->error_message( $too_long );
exit;
}
} else {
my $location = $self->base_uri . $self->cgi_name;
print "Location: $location\n\n";
exit;
}
}
sub check_interval {
my ( $self, $hostname ) = @_;
my $dbh = $self->connect_sql;
my $table_name = $self->log_table;
my $req = qq| SELECT date FROM $table_name |;
$req .= qq| WHERE hostname = ? ORDER BY id DESC LIMIT 1 |;
if ( my $date_arry_ref = $dbh->selectrow_arrayref( $req, undef, $hostname ) ) {
my ( $date ) = @$date_arry_ref;
$date = encode( 'utf8', $date );
my $t = Time::Piece->strptime( decode( 'utf8', $date ), "%FT%T" );
my $unix_time = $t->strftime( '%s' );
my $current_unix_time = time;
my $diff = $current_unix_time - $unix_time;
if ( $diff > $self->interval_limit ) {
return 0;
} else {
return 1;
}
} else {
return 0;
}
}
sub update_com_number {
my ( $self, $key ) = @_;
my $id = int( $key );
my $unix_time = time();
my $table_name = $self->log_table;
my $req = qq| UPDATE $table_name SET com_num = com_num +1, |;
$req .= qq| update_utime = ? WHERE id = ? |;
my $dbh = $self->connect_sql;
my $sth = $dbh->prepare( $req );
$sth->execute( $unix_time, $id );
$dbh->disconnect;
return 0;
}
sub tripcode {
my ($self, $tripkey) = @_;
# Wikipedia http://is.gd/BxNSsL
$tripkey = substr( $tripkey, 1 );
my $salt = substr( $tripkey.'H.', 1, 2 );
$salt =~ s/[^\.-z]/\./go;
$salt =~ tr/:;<=>?@[\\]^_`/ABCDEFGabcdef/;
my $trip = crypt( $tripkey, $salt );
$trip = substr( $trip, -10 );
return $trip;
}
sub delete_message {
my ( $self, $id, $delete_key ) = @_;
my $dbh = $self->connect_sql;
my $table_name = $self->log_table;
my $req = qq| SELECT delete_key FROM $table_name WHERE id = ? LIMIT 1 |;
my $del_key_ref = $dbh->selectrow_arrayref( $req, undef, $id );
my ( $del_key ) = @$del_key_ref;
if ( ( $delete_key eq $del_key )
|| ( $delete_key eq $self->master_key ) ) {
my $sqla = SQL::Abstract->new;
my %where = ( -or => { id => $id, reply_to => $id, }, );
my @fields = qw/ img_path thumb_path /;
my ( $stmt, @bind ) = $sqla->select( $self->log_table, \@fields, \%where );
my $sth = $dbh->prepare( $stmt );
$sth->execute( @bind );
while ( my $arry_ref = $sth->fetchrow_arrayref ) {
my ( $image_path, $thumb_path ) = @$arry_ref;
my $full_path = $FindBin::Bin . '/' . $image_path;
if ( $image_path ne '' ) {
unless ( unlink $full_path ) {
my $failure = "Could not delete this post. : $image_path ";
$self->error_message( $failure );
exit;
} else {
my $full_thumb_path = $FindBin::Bin . '/' . $thumb_path;
unlink $full_thumb_path;
}
}
}
my ( $stmt2, @bind2 ) = $sqla->delete( $self->log_table, \%where );
$sth = $dbh->prepare( $stmt2 );
$sth->execute( @bind2 );
$dbh->disconnect;
return 0;
} else {
my $incorrect = qq| Your password would be incorrect. : $delete_key |;
$self->error_message( $incorrect );
exit;
}
}
sub upload_process {
my ( $self, $q ) = @_;
my $file_name = $q->param( 'upfile' );
my $mime = $q->param_mime( 'upfile' );
my $extension;
if ( $mime =~ /((gif|jpeg|png))$/ ) {
$extension = $1;
$extension =~ s/jpeg/jpg/;
} else {
my $not_image = 'This would not be an image file.';
$self->error_message( $not_image );
exit;
}
my $unix_time = time();
my $upfile_name = $unix_time . '.' . $extension;
my $save_image_path =
$FindBin::Bin . '/' . $self->image_dir . '/' . $upfile_name;
my $save_thumb_path =
$FindBin::Bin . '/' . $self->thumb_dir . '/' . $upfile_name;
# upload
my $ok = $self->upload( $file_name, $save_image_path );
if ( $ok ) {
my $size;
$size = -s $save_image_path;
my $thread_image = 0;
$thread_image = 1 if $q->param( 'key' ) eq '';
my ( $convert, $img_geometry )
= $self->make_thumb( $save_image_path, $save_thumb_path, $thread_image );
my $i_path = $self->image_dir . '/' . $upfile_name;
my $t_path = $self->thumb_dir . '/' . $upfile_name;
$t_path = $i_path if $convert == 0;
return ( $i_path, $t_path, $size, $img_geometry );
} else {
my $failure = 'Failed at uploading.';
$self->error_message( $failure );
}
}
sub upload {
my ( $self, $file_name, $save_path ) = @_;
if ( open UPLOAD, "+> $save_path" ) {
binmode( UPLOAD );
print UPLOAD $file_name;
close UPLOAD;
chmod( 0666, $save_path );
return 1;
} else {
return 0;
}
}
sub make_thumb {
my ( $self, $save_image_path, $save_thumb_path, $thread_image ) = @_;
my $image = Image::Magick->new;
$image->Read( $save_image_path );
# my $width = $image->Get( 'width' );
# my $height = $image->GetPixels( 'height' );
my ( $width, $height ) = $image->Get('width', 'height');
my $thumb_geometry = $self->thumb_geometry;
$thumb_geometry = $thumb_geometry * 2
if $thread_image == 1;
my ( $thumb_width, $thumb_height, $convert );
if ( ( $width > $height ) && ( $width > $thumb_geometry ) ) {
my $ratio = $thumb_geometry / $width;
$thumb_width = $thumb_geometry;
$thumb_height = int( $height * $ratio );
$convert = 1;
} elsif ( ( $width < $height ) && ( $height > $thumb_geometry ) ) {
my $ratio = $thumb_geometry / $height;
$thumb_height = $thumb_geometry;
$thumb_width = int( $width * $ratio );
$convert = 1;
} elsif ( ( $width == $height ) && ( $width > $thumb_geometry ) ) {
$thumb_width = $thumb_geometry;
$thumb_height = $thumb_geometry;
$convert = 1;
} else {
$thumb_width = $width;
$thumb_height = $height;
$convert = 0;
}
my $geometry = $width . 'x' . $height;
if ( $convert == 1 ) {
$image->Resize( width => $thumb_width, height => $thumb_height );
$image->Comment( "" );
$image->Write( $save_thumb_path );
return ( $convert, $geometry );
} else {
return ( $convert, $geometry );
}
}
sub get_host_by_addr {
my ( $self, $ip ) = @_;
my @addr = split(/\./, $ip);
my $packed_ip = pack("C4", $addr[0], $addr[1], $addr[2], $addr[3]);
my $hostname = gethostbyaddr($packed_ip, 2);
return $hostname;
}
sub link_check {
my ( $self, $message ) = @_;
if ( $message =~ /(?<!\]\()https?:\/\/.+/i ) {
my $caution = 'This cgi does not permit any link.';
$self->error_message( $caution );
exit;
} else {
# ttp to http
$message =~ s/(?<!h)(ttps?:\/\/[0-9A-Za-z\/\.\?=_-]+)/<a href="h$1">h$1<\/a>/g;
return $message;
}
}
sub error_message {
my ( $self, $text ) = @_;
my $location = $self->base_uri . $self->cgi_name;
$text = qq| <html><body><div style="text-align: center"><h1> $text </h1> |;
$text .= qq| <p><a href="$location">Back</a></p></div></body></html> |;
print "Content-Type: text/html; charset=UTF-8\n\n";
print encode( 'utf8', $text );
}
sub connect_sql {
my $self = shift;
my $db_name = $self->db_name;
my $db_port = $self->db_port;
my $pg_user = $self->db_user;
my $dbh = DBI->connect( "dbi:Pg:dbname=$db_name;port=$db_port",
$pg_user,
'',
{ RaiseError => 1,
pg_enable_utf8 => 1, } )
|| die "Database Connection Error: $!";
return $dbh;
}
sub register {
my ( $self, @values ) = @_;
my ( $key, $name, $mail, $subject, $message, $password,
$img_path, $thumb_path, $img_size, $img_geometry,
$ip, $hostname ) = @values;
my $date = $self->get_date;
my $unix_time = time();
$key = 0 if $key eq '';
my %fieldvals = ( date => $date,
name => $name,
mail => $mail,
subject => $subject,
message => $message,
img_path => $img_path,
thumb_path => $thumb_path,
img_size => $img_size,
img_geometry => $img_geometry,
update_utime => $unix_time,
delete_key => $password,
com_num => 0,
reply_to => int( $key ),
ip => $ip,
hostname => $hostname, );
my $dbh = $self->connect_sql;
my $sqla = SQL::Abstract->new;
my ( $stmt, @bind ) = $sqla->insert( $self->log_table, \%fieldvals );
my $sth = $dbh->prepare( $stmt );
$sth->execute( @bind );
$dbh->disconnect;
}
sub get_date {
my $self = shift;
my $t = Time::Piece::localtime();
my $date = $t->strftime( '%FT%T' );
return $date;
}
# ------------
package main;
my $conf_file = './.conf';
my $conf = do $conf_file
or die qq/Can't load config file "$conf_file": $!$@/;
my $register = CGI::Four::Register->new( db_name => $conf->{ db_name },
db_port => $conf->{ db_port },
db_user => $conf->{ db_user },
cgi_name => $conf->{ cgi_name },
log_table => $conf->{ log_table },
master_key => $conf->{ master_key },
base_uri => $conf->{ base_uri }, );
$register->get_query;
exit;
# last modified: 2012-01-03 01:42+09:00.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment