#!/usr/local/bin/perl -Tw
# Pre-live checklist:
# [X] add some file locking for Godsakes!!!
# [X] link to this thing and do auth ONLY through SSL!!
# [X] protect /admin dir with .htaccess
# [X] use $ENV{ REMOTE_USER } once .htaccess is in place
# [X] make sure to untaint $ENV{ REMOTE_USER }
# [X] write a file-storing routine ... just did it inline instead
# [X] bonus points: use XML :)
# [X] app tempfiles?
# [X] move to "live" data dir automatically?
# [X] add CSV validation to _validate_upload
# [X] test CSV validation to _validate_upload
# [X] count fields in a cool way for $num_fields
use lib '/dw02/d37/dawgpolo/myperl';
use lib '/dw02/d37/dawgpolo/myperl/lib';
use CGI;
use CGI::Carp qw( fatalsToBrowser ); # I think this slows us down
use Data::Dumper;
use Date::Format;
use Dawg qw( %CONFIG %CSV );
use Dawg::DB;
use Dawg::Draw;
use Fcntl qw( :flock );
use strict;
$ENV{ HTTPS } or die "Insecure! Change URL to https://$ENV{ HTTP_HOST }".
"$ENV{ SCRIPT_NAME }\n";
$ENV{ HTTPS } =~ tr/A-Za-z//cd;
$ENV{ SERVER_ADMIN } = $CONFIG{ EMAIL };
$ENV{ PATH } = '/usr/local/bin:/usr/bin:/bin';
my $cgi = CGI->new;
my $draw = Dawg::Draw->new;
my $title = 'CSV Update Tool';
my $self = $ENV{ SCRIPT_NAME };
my $user = $ENV{ REMOTE_USER };
$user =~ tr/A-Za-z//cd;
my $userIP = $ENV{ REMOTE_ADDR };
sub main {
my %dispatch = (
upload => \&upload,
debug => \&debug,
DEFAULT => \&upload,
);
$draw->headers();
$draw->html_start( { title => $title } );
my $action = $cgi->param('action') || 'DEFAULT';
my $do_sub = $dispatch{ $action };
if ( $do_sub ) {
&$do_sub();
} else {
warn "[warn] Invalid action: [$action]";
die "[die] You can't make me do that!\n";
}
$draw->html_end();
}
sub upload {
my @errors = (); # array of errors found during execution
my @lines = (); # input lines of uploaded file
my $page = ''; # page that uploaded file corresponds to
my $ucpage = ''; # uppercased version of the same thang
my $mode = $cgi->param( 'mode' );
$mode =~ tr#a-z_##cd; # untaint that param!
my $in = $cgi->upload( 'datafile_in' ); # returns a filehandle
my $logfile = "$CONFIG{ ADMIN }/transactions.log"; # log entire transaction
my $qwiklog = "$CONFIG{ ADMIN }/quicklog.csv"; # abbreviated log
open( LOG, ">> $logfile" ) or die "Couldn't open $logfile";
open( QWK, ">> $qwiklog" ) or die "Couldn't open $qwiklog";
if ( $mode ) {
# They're trying to upload a file
push @errors, "Invalid filename" unless ( ref $in eq 'Fh' );
# retrieve the uploaded file and check for data integrity
unless ( @errors ) {
my $rv = Dawg::DB->slurp_csv_safe( $in );
if ( $rv ) {
@lines = @$rv;
} else {
die "No lines returned while slurping CSV";
}
if ( $mode =~ /^([a-z_]+)_go$/ ) {
$page = $1;
$ucpage = uc($page);
die "unknown page [$ucpage]" unless ( exists $CSV{$ucpage} );
my $result = Dawg::DB->validate_csv( $ucpage, \@lines );
@errors = @$result unless $result == 1;
}
}
if ( @errors ) {
print qq[ ERRORS OCCURRED DURING FILE UPLOAD:\n ];
print qq[
\n ];
foreach my $error ( @errors ) {
print "
$error\n";
}
print qq[
\n ];
} else {
# upload was good.
my $time = time2str( "%C", time() );
my $outfile = "$CONFIG{DATA}/$page.csv"
or die "cannot draft datafile filename for [$page]";
open( CSV, "> $outfile" ) or die "Couldn't open $outfile";
print qq[ $ucpage UPLOAD SUCCESSFUL \n ];
# - 1 - open and lock semaphores
my $csv_sem = "$CONFIG{ADMIN}/.csv.lock";
my $log_sem = "$CONFIG{ADMIN}/.transactions.lock";
my $qwk_sem = "$CONFIG{ADMIN}/.quicklog.lock";
open( CSV_SEM, "> $csv_sem" ) or die "$csv_sem: $!";
open( LOG_SEM, "> $log_sem" ) or die "$log_sem: $!";
open( QWK_SEM, "> $qwk_sem" ) or die "$qwk_sem: $!";
flock( CSV_SEM, LOCK_EX ) or die "Can't lock CSV";
flock( LOG_SEM, LOCK_EX ) or die "Can't lock LOG";
flock( QWK_SEM, LOCK_EX ) or die "Can't lock QWK";
# - 2 - write the datafile itself
for (@lines) { print CSV "$_\n" } # write csv to datafile
# - 3 - write the entire transaction
print LOG "[ $time ] $ucpage UPDATED by $user [$userIP]\n";
for (@lines) { print LOG "$_\n" } # log the transaction
# - 4 - write a "qwiklog" entry
print QWK "[ $time ] $ucpage UPDATED by $user [$userIP]\n";
# - 5 - close all filehandles, drop locks
close( CSV ) or die "Couldn't close file $outfile"; # file locks
close( LOG ) or die "Couldn't close file $logfile"; # are removed
close( QWK ) or die "Couldn't close file $qwiklog"; # automatically
close( CSV_SEM ) or warn "close error on $csv_sem: $!";
close( LOG_SEM ) or warn "close error on $log_sem: $!";
close( QWK_SEM ) or warn "close error on $qwk_sem: $!";
}
} else {
# They're not trying to upload a file YET, so give them tips
print <
Make sure the uploaded file is TRUE CSV, eg. blah,1,2,"blow, joe"
Click the respective Upload button once, then wait
You can only upload one file at a time
End_HTML
}
foreach my $table ( sort keys %CSV ) {
my $name = lc($table);
print <
Download $name datafile
UploadForms
}
}
sub debug {
print "