CGI-Plus
view release on metacpan or search on metacpan
lib/CGI/Plus.pm view on Meta::CPAN
package CGI::Plus;
use strict;
use Carp;
use CGI::Safe 'taint';
use base 'CGI::Safe';
use String::Util ':all';
use CGI::Cookie;
# version
our $VERSION = '0.15';
# Debug::ShowStuff
# use Debug::ShowStuff ':all';
# use Debug::ShowStuff::ShowVar;
# enable file uploads
$CGI::DISABLE_UPLOADS = 0;
# maximum upload: 5 mb
$CGI::POST_MAX = 5 * 1024 * 1024;
# set path to empty string
$ENV{'PATH'} = '';
=head1 NAME
CGI::Plus -- Extra utilities for CGI
=head1 Description
This module adds a few enhancements to
L<CGI::Safe|http://search.cpan.org/~ovid/CGI-Safe/lib/CGI/Safe.pm>,
which itself adds a few security-based enancements to
L<CGI.pm|http://perldoc.perl.org/CGI.html>. The enhancement are almost
entirely additions - the only method that is overridden is new(), and
the changes there are only addition. The enhancements in this module entirely
use the object-oriented interface.
=head1 SYNOPSIS
use CGI::Plus;
my ($cgi, $cookie, $url, $param);
# new CGI::Plus object
$cgi = CGI::Plus->new();
# turn on checks for cross-site request forgeries (CSRF)
$cgi->csrf(1);
# get a cookie and look at its values
$cookie = $cgi->incoming_cookies->{'mycookie'};
print $cookie->{'values'}->{'x'}, "\n";
print $cookie->{'values'}->{'y'}, "\n";
# more concise way to get an incoming cookie
$cookie = $cgi->ic->{'mycookie'};
# resend a cookie, but change one of its values
$cookie = $cgi->resend_cookie('mycookie');
$cookie->{'values'}->{'x'} = 2;
# add an outgoing cookie, set some values
$cookie = $cgi->new_send_cookie('newcookie');
$cookie->{'values'}->{'val1'} = '1';
$cookie->{'values'}->{'val2'} = '2';
# output HTTP header with outgoing cookies, including CSRF
# check cookie, automatically added
print $cgi->header_plus;
# output header again if it hasn't already been sent, but if it
# has then output an empty string
print $cgi->header_plus;
# output the URL of the current page but set a new value
# for the "t" param and remove the "j" param
$url = $cgi->self_link(params=>{t=>2, j=>undef});
# check if the submitted form includes the value of the CSRF
# cookie that was sent
if (! $cgi->csrf_check)
{ die 'security error' }
# output the randomly generated value of the CSRF cookie,
# output: KTFnGgpkZ4
print $cgi->csrf_value, "\n";
# output the hidden input form field that uses the same
# value as the CSRF cookie
# output: <input type="hidden" name="csrf" value="KTFnGgpkZ4">
print $cgi->csrf_field, "\n";
# get the CSRF check param for use in a URL
# output: csrf=KTFnGgpkZ4
print $cgi->csrf_param;
# set a custom header
$cgi->set_header('myheader', 'whatever');
# change content type
$cgi->set_content_type('text/json');
# output HTTP headers, including added cookies, the CSRF cookie,
# and the new header
print $cgi->header_plus;
# outputs something like this:
# Set-Cookie: newcookie=val2&2&val1&1; path=/
# Set-Cookie: mycookie=y&2&x&2; path=/
# Set-Cookie: csrf=v&KTFnGgpkZ4; path=/
# Date: Sun, 29 Jul 2012 04:08:06 GMT
# Myheader: whatever
# Content-Type: text/json; charset=ISO-8859-1
=head1 INSTALLATION
CGI::Plus can be installed with the usual routine:
perl Makefile.PL
make
make test
make install
=head1 METHODS
=cut
#------------------------------------------------------------------------------
## new
#
=head2 CGI::Plus->new()
Creates and returns a CGI::Plus object. New calls the super-class' new()
method, so all params sent to this method will be passed through to CGI
and CGI::Safe.
=cut
sub new {
my $class = shift;
my $cgi = $class->SUPER::new(@_);
# set cookies
$cgi->initialize_cookies();
# call super method
return $cgi;
}
#
# new
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# ic, oc
# quick accesors to incoming and outgoing cookies
#
=head2 $cgi->ic, $cgi->oc
=cut
sub incoming_cookies { return $_[0]->{'cookies'}->{'incoming'} }
sub ic { return shift->incoming_cookies(@_) }
sub outgoing_cookies { return $_[0]->{'cookies'}->{'outgoing'} }
sub oc { return shift->outgoing_cookies(@_) }
#
# ic, oc
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# initialize_cookies
# private method
#
sub initialize_cookies {
my ($cgi) = @_;
my ($got, %cookies);
# cookie hashes
$cgi->{'cookies'} = {};
$got = $cgi->{'cookies'}->{'incoming'} = {};
$cgi->{'cookies'}->{'outgoing'} = {};
# get hash of cookies that were sent
%cookies = CGI::Cookie->fetch();
# showhash \%cookies, title=>'%cookies';
# populate cookie values
foreach my $name (keys %cookies) {
my ($cookie, $element, @value);
$cookie = $cookies{$name};
$element = {};
# name of cookie
$element->{'name'} = $name;
# original cookie object
$element->{'org'} = $cookie;
# expires
if (defined $cookie->expires())
{ $element->{'expires'} = $cookie->expires() }
# get parsed values
@value = $cookie->value();
# if more than one element in @value, assume it's a hash
if (@value > 1)
{ $element->{'values'} = {@value} }
# else it's a single string value
else
{ $element->{'value'} = $cookie->value() }
# hold on to cookie element
$got->{$name} = $element;
}
}
#
# initialize_cookies
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# cookie_resend
#
sub resend_cookie {
my $cgi = shift;
return $cgi->cookie_resend(@_);
}
sub cookie_resend {
my ($cgi, $name, %opts) = @_;
my ($got, $send);
# default %opts
%opts = (ensure=>1, %opts);
# if not ensuring existence of cookie, and cookie doesn't
# exist, return undef
unless ( $cgi->ic->{$name} || $opts{'ensure'}) {
return undef;
}
# get sent cookie
$got = $cgi->ic->{$name} || {'name'=>$name};
# create cookie that gets sent back out
$send = {};
# clone $got cookie
foreach my $key (keys %$got) {
my $value = $got->{$key};
# original cookie
if (UNIVERSAL::isa $value, 'CGI::Cookie') {
$send->{$key} = $value;
}
# hashref
elsif (UNIVERSAL::isa $value, 'HASH') {
$send->{$key} = {%$value};
}
# else just copy
else {
$send->{$key} = $value;
}
}
# set cookie
$cgi->oc->{$name} = $send;
# return new cookie
return $send;
}
#
# cookie_resend
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# new_send_cookie
#
sub new_send_cookie {
my ($cgi, $name) = @_;
my ($cookie);
# create oject
$cookie = {};
$cookie->{'name'} = $name;
$cookie->{'values'} = {};
# add to hash of outgoing cookies
$cgi->oc->{$name} = $cookie;
# return new cookie
return $cookie;
}
#
# new_send_cookie
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# self_link
#
=head2 $cgi->self_link(%options)
Returns a url that is a relative link to the current page. The local path of
the URL is sent, but not the protocol or host. So, for example, if the URL
of the current page is
http://www.example.com/cgi-plus/?y=1&x=2&t=2&y=2
lib/CGI/Plus.pm view on Meta::CPAN
# return empty string
if ($cgi->{'header_sent'})
{ return '' }
# set content type
if ( (! $opts{'-type'}) && $cgi->{'content_type'} ) {
$opts{'-type'} = $cgi->{'content_type'};
}
# add headers
if ($cgi->{'headers'}) {
while ( my($name, $value) = each(%{$cgi->{'headers'}}) ) {
$name =~ s|^\-*|-|s;
$opts{$name} ||= $value;
}
}
# add cookies
foreach my $name ( keys %{$cgi->oc} ) {
my (%element, %params, $cookie);
%element = %{$cgi->oc->{$name}};
# 'values' takes precedence over 'value'
if ($element{'values'})
{ delete $element{'value'} }
# else if no 'value' either, set it to empty string
elsif (! defined $element{'value'})
{ $element{'value'} = '' }
# loop through values
foreach my $key (keys %element) {
# special case: values
if ($key eq 'values') {
$params{'-value'} = $element{$key};
}
# else just copy value
else {
my $send_key = $key;
$send_key =~ s|^\-*|-|;
$params{$send_key} = $element{$key};
}
}
# set domain
if ($element{'domain'})
{ $params{'-domain'} = $element{'domain'} }
# set expires: default to one year
if (exists $element{'expires'}) {
if (defined $element{'expires'})
{ $params{'-expires'} = $element{'expires'} }
}
else {
$params{'-expires'} = '+1y';
}
# create cookie object
$cookie = CGI::Cookie->new(%params);
if (! defined $cookie) {
# showhash \%params, title=>'error generating cookie';
die 'cookie error';
}
# add to cookie array
push @cookies, $cookie;
}
# add cookies to header options
if (@cookies)
{ $opts{'-cookie'} = \@cookies }
# note that header has been sent
$cgi->{'header_sent'} = 1;
# call super method
return $cgi->SUPER::header(%opts);
}
#
# header_plus
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# CSRF info
#
=head1 Cross-site request forgery (CSRF) defenses
A L<Cross-site request forgery|http://en.wikipedia.org/wiki/Cross-site_request_forgery>
(CSRF) is a technique for breaching a web site's security. CSRF is one of the
most common web-site vulnerabilities. CGI::Plus provides a technique for
protecting
=cut
#
# CSRF info
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# csrf
#
sub csrf {
my $cgi = shift;
# set csrf value if sent
if (@_) {
$cgi->{'csrf'} = $_[0];
# set csrf cookie
if ($cgi->{'csrf'}) {
my ($cookie);
( run in 0.789 second using v1.01-cache-2.11-cpan-22024b96cdf )