.$DB_EXT in the $DB_DIR directory, and try to save values to it, but only if it's a extant, valid sqlite database that's writable by the CGI script - so it's not so bad. Paranoid people can hardcode the database name, and turn this off.
my $ALLOW_FILE = 1;
############################################
# initialize
my $DB_PATH;
if (my $f = param('_file') && $ALLOW_FILE) {
$f .= '.' . $DB_EXT;
$f = $DB_DIR ? "$DB_DIR/$f" : $f;
$DB_PATH = $f if -s $f;
if (!$DB_PATH) {
fail("Can't open $f, file does not exist");
}
} else {
$DB_PATH = $DB_DIR ? "$DB_DIR/$DB_NAME.$DB_EXT" : "$DB_NAME.$DB_EXT";
}
# setup debug info
my $DEBUG = param('_debug');
my $DTMP = '';
debug("DB=$DB_PATH");
# connect to DB
my $DB = DBI->connect("dbi:SQLite:dbname=$DB_PATH","","");
fail("Can't open DB $DB_PATH: " . DBI->errstr) if (!$DB);
my $CONF;
configure();
my $ok_url = param_url('_ok', conf('ok'));
my $err_url = param_url('_err', conf('err'));
my $fields = conf('fields'); # comma delimited list of allowed fields;
my $row_id;
my $err_str;
fail('DEBUG') if $DEBUG;
my %fields;
if ($fields) {
for (split(/\s*,\s*/, $fields)) {
$fields{lc($_)} = 1;
}
}
#### time to validate
my @fds;
my $flds;
my $vals;
for my $f (param()) {
next if $f =~ /^_/;
next unless $f =~ /^[a-z0-9_.+-]+$/i;
if (%fields && !$fields{lc($f)}) {
$err_str .= "Invalid field '$f'
";
next;
}
my $v = param($f);
next if length($v) > $MAX_LEN_VALUE;
my $val = $1 if $f =~ s/\+(int|word|name|(list\+\w+))$//i;
my $list = $1 if $val =~ s/^list\+(.*)/list/;
$list = param("_" . $list) if $list;
next if length($f) > $MAX_LEN_NAME;
if ($list) {
$v =~ s/^\s+//; $v =~ s/\s+$//;
my %list = map {lc($_), 1} split(/\s*,\s*/, $list);
$err_str .= "Invalid value '$v' for field '$f'
" if !$list{lc($v)};
}
if ($val eq 'int') {
$v =~ s/^\s+//; $v =~ s/\s+$//;
$err_str .= "Invalid value '$v' for field '$f', need integer
" if $v !~ /^\d+$/;
} elsif ($val eq 'word') {
$v =~ s/^\s+//; $v =~ s/\s+$//;
$err_str .= "Invalid value '$v' for field '$f', need word
" if $v !~ /^\w+$/;
} elsif ($val eq 'name') {
$v =~ s/^\s+//; $v =~ s/\s+$//;
$err_str .= "Invalid value '$v' for field '$f', need name
" if $v !~ /^[\w ]+$/;
}
$v =~ s/\\/\\\\/g;
$v =~ s/'/''/g;
push @fds, $f;
$flds .= "$f,";
$vals .= "'$v',";
}
$flds =~ s/,$//;
$vals =~ s/,$//;
if (!$vals) {
$err_str .= "No values specified
";
}
if ($err_str) {
save_err();
}
#### time to save, we could blob into 1 field - but that would be boring
local $DB->{PrintError} = 0;
my $s = $DB->prepare("select * from data limit 0");
if (!$s) {
# easy, make a table to match
my $qry = "create table data (_id integer primary key autoincrement, _ip text, _time int, " . $flds . ")";
if (!$DB->do($qry)) {
save_err($DB->errstr);
}
} else {
# fields grew
my %f;
my $c;
for (@{$s->{NAME}}) {
$f{lc($_)} = 1;
++$c;
}
my $qry;
my $a;
for ((@fds , '_id', '_ip', '_time')) {
$qry .= "alter table data add $_ text;" if ! $f{lc($_)};
++$a;
}
if (($c + $a) > $MAX_FIELDS) {
save_err("Too many fields.");
}
if ($qry && !$DB->do($qry)) {
save_err($DB->errstr);
}
$s->execute();
$s->finish();
}
my $ip = remote_host();
my $time = time();
my $qry = "insert into data (_ip, _time, $flds) values ('$ip', '$time', $vals)";
if (!$DB->do($qry)) {
$err_str .= $DB->errstr;
save_err();
}
save_ok();
sub debug {
if ($DEBUG) {
$DTMP .= "$_[0]\n";
}
}
sub fail {
$DTMP .= "$_[0]\n";
$DTMP=pre($DTMP) if $DTMP;
print header, start_html('Formlite : Error'), h1('Error'), $DTMP, end_html();
exit(0);
}
sub save_ok {
if ($ok_url) {
print redirect($ok_url . ($ok_url =~ /\?/ ? '&' : '?') . "row_id=$row_id");
} else {
$DTMP=pre($DTMP) if $DTMP;
print header, start_html('Formlite : OK'), h1('OK'), p, "Information was saved.", $DTMP, end_html();
}
exit(0);
}
sub save_err {
my ($str) = @_;
$err_str .= $str if $str;
if ($err_url) {
print redirect($err_url . ($err_url =~ /\?/ ? '&' : '?') . "err_str=$err_str");
} else {
$DTMP=pre($DTMP) if $DTMP;
print header, start_html('Formlite : Error'), h1('Error'), p, "Error saving information: $err_str.", $DTMP, end_html();
}
exit(0);
}
sub configure {
#load config
loadconf();
#create config
if (!$CONF) {
$DB->do("create table conf (name text not null primary key, value text)");
loadconf();
}
if (!$CONF) {
fail("Can't create/load config table");
}
if (!(conf('pwd') eq param('_pwd'))) {
if (conf('pwd') && (!param() || param('_pwd'))) {
print header, start_html('Formlite : Login'), h1('Login');
print start_form(),
"
Password: ",
password_field('_pwd'),
submit('Go');
end_form;
end();
}
return;
}
# check password
# lightweight admin page
if (param('_save')) {
setconf('ok', param('ok'));
setconf('err', param('err'));
if (param('newpwd')) {
param('_pwd',param('newpwd'));
setconf('pwd', param('newpwd'));
param('newpwd','');
}
}
if (param('_view') || param('_export')) {
my ($where, $limit);
$where = "where _id > " . (conf('last') + 0) if param('since');
$limit = "limit " . param('num') if param('num');
my $qry = "select * from data $where order by _id desc $limit";
my $s = $DB->prepare($qry);
if (!$s || !$s->execute()) {
fail(!$s ? $DB->errstr : $s->errstr);
}
my $idx = -1;
{
my $i=0;
grep {$idx=$i && last if $_ eq '_id'; ++$i} @{$s->{NAME}};
}
fail("Can't find _id field") unless $idx >= 0;
my $l;
if (param('_export')) {
print header('application/vnd.ms-excel');
my @d;
my $idx;
for (@{$s->{NAME}}) {
push @d, $_;
}
print join(',',@d) . "\r\n";
while (my $r = $s->fetchrow_arrayref()) {
my @d = ();
for (@{$r}) {
if ($_ =~ /,/) {
s/\"/\"\"/g;
$_ = '"' . $_ . '"';
}
push @d, $_;
}
print join(',',@d) . "\r\n";
$l = $r->[$idx] if $r->[$idx] > $l;
}
} elsif (param('_view')) {
print header;
print start_html;
my @d;
print "
";
for (@{$s->{NAME}}) {
print "$_ | ";
}
print "
";
while (my $r = $s->fetchrow_arrayref()) {
print "";
for (@{$r}) {
print "$_ | ";
}
print "
";
$l = $r->[$idx] if $r->[$idx] > $l;
}
print end_html;
}
setconf('last', $l);
exit(0);
}
print header,
start_html(
-title=>'Formlite : Admin',
-style=>{-code=>'
body {
font-familiy: sans;
}
.aligned input {
color: blue;
position: absolute;
left: 20%;
clear: both;
}
.aligned p {
vertical-align: top;
}
'}
), h1('Administration');
if (conf('pwd')) {
print "";
print start_form(),
hidden('_pwd',param('_pwd')),
checkbox(-name=>'since', -label=>"Only show data since last view/export");
print '
Limit Number of Rows: ',
textfield(-name=>'num', -size=>4);
print "
";
print p
submit('_view','View Data'),
submit('_export','Export Data');
} else {
print "Configuring $DB_NAME for first use by formlite."
}
print "
";
print start_form(),
hidden('_pwd',param('_pwd')),
"
Redirect on OK: ",
textfield('ok',conf('ok')),
"
Redirect on ERROR: ",
textfield('err',conf('err')),
"
Allowed fields: ",
textfield('fields',conf('fields'),40),
"
Change Password: ",
password_field('newpwd'), p;
print "
";
print "Leave the field list blank, or fill with a comma-delimited list of fields with optional plus-suffixes. If present, this will cause the form to fail if other unmatched fields are present.";
print
p, submit('_save','Save Settings'),
end_form;
end();
}
sub loadconf {
undef $CONF;
local $DB->{PrintError} = 0;
my $r = $DB->selectall_arrayref("select name, value from conf");
if ($r) {
$CONF = {};
}
for (@{$r}) {
$CONF->{$_->[0]} =$_->[1] if $_->[0];
}
}
sub end {
print pre($DTMP) if $DTMP;
print end_html();
exit(0);
}
sub conf {
my ($name, $default) = @_;
if ($default) {
return $CONF->{$name} ? $CONF->{$name} : $default;
} else {
return $CONF->{$name};
}
}
sub setconf {
my ($name, $value) = @_;
$value =~ s/\\/\\\\/g;
$value =~ s/'/''/g;
my ($exists) = $DB->selectrow_array("select name from conf where name='$name'");
my $res;
if ($exists) {
$res = $DB->do("update conf set value='$value' where name ='$name'");
} else {
$res = $DB->do("insert into conf (name, value) values ('$name', '$value')");
}
$CONF->{$name} = $value;
return $res;
}
sub param_url {
my ($name, $def) = @_;
return $def if !param($name);
if (param($name) =~ /^[a-z0-9:@\/&%,;=_#-]+$/) {
return param($name);
} else {
debug("$name is not a valid url");
}
return undef;
}
sub param_word {
my ($name, $def) = @_;
return $def if !param($name);
if (param($name) =~ /^\w+$/) {
return param($name);
} else {
debug("$name is not a simple word");
}
return undef;
}
=head1 COPYRIGHT
Copyright 2008 Erik Aronesty
Free software distributed under the Perl Artistic License.
See L
=head1 WARRANTY
The program is free. IT COMES WITHOUT WARRANTY OF ANY KIND.
=cut