At this node *.pm vs. *::Simple and this recent node CGI made me bloated the merits or otherwise of the CGI.pm module have been debated yet again. I am sick of hearing that CGI.pm is bloated and that by rights should be several smaller more focused modules. That's it.

While there are light weight options available the are not IMHO of the same standard as CGI.pm. Here is an attempt to address some of the recurrent issues with CGI.pm -> CGI::Simple.

This module rather faithfully recreates the OO API from CGI.pm. It does not handle multipart/form-data/file uploads, header generation, or HTML generation. It simply parses aplication/xxx url encoded blah/form params. It is OO only and will not polute your namespace one iota.

So put up or shut up. Either be happy with CGI.pm or help test/break/fix/etc this code. There is quite an extensive test suite in the tarball. You can get a .tar.gz from http://tachyon.perlmonk.org/scripts/cgi-simple0.01.tar.gz Did someone say merlyn?

Here is the module code:

package CGI::Simple; require 5.004; use strict; # use warnings; # during testing use SelfLoader; use vars qw ( $DISABLE_UPLOADS $POST_MAX $VERSION ); $VERSION = "0.01"; $DISABLE_UPLOADS = 1; $POST_MAX = 102400; # use a post max of 100K, set to -1 for no limits =head1 NAME CGI::Simple - A Simple totally OO CGI interface that is CGI.pm complia +nt =head1 SYNOPSIS use CGI::Simple; $CGI::Simple::POST_MAX = 1024; $q = new CGI::Simple; $q = new CGI::Simple( { 'foo'=>'1', 'bar'=>[2,3,4] } ); $q = new CGI::Simple( 'foo=1&bar=2&bar=3&bar=4' ); $q = new CGI::Simple( \*FILEHANDLE ); $q->save( \*FILEHANDLE ); @params = $q->param; $value = $q->param('foo'); @values = $q->param('foo'); %fields = $q->Vars; @keywords = $q->keywords; $q->param( 'foo', 'some', 'new', 'values' ); $q->param( -name=>'foo', -value=>'bar' ); $q->param( -name=>'foo', -value=>['bar','baz'] ); $q->append( -name=>'foo', -value=>'bar' ); $q->append( -name=>'foo', -value=>['some', 'new', 'values'] ); $q->delete('foo'); $q->delete_all; $decoded = $q->url_decode($encoded); $encoded = $q->url_encode($unencoded); $param = $q->escapeHTML(' < >&"'."\n".'<>&"'); dienice( $q->cgi_error ) if $q->cgi_error; =head1 DESCRIPTION CGI::Simple provides a lightweight drop in replacement for CGI.pm. It +has an identical OO interface to CGI.pm for parameter parsing. Multipart/f +orm-data encoding is not supported. It has no HTML output functionality outside + of an escapeHTML() method. There is no header method either. A list of the supported methods is given above. For full specifics see + the extensive CGI.pm pod. Unlike CGI.pm this module has a default $POST_MA +X setting of 100K This should be more than adequate for most purposes. If you wi +sh to change this you must set it before you call the new() constructor, +just as with CGI.pm =head2 EXPORT Nothing. $POST_MAX and $DISABLE_UPLOADS globals from CGI.pm used =head1 BUGS As this is 0.01 there are almost bound to be some. Unlike CGI.pm which + tries to accept all filehandle like objects only \*FH and $fh are accepted b +y CGI::Simple as file accessors. =head1 AUTHOR Dr James Freeman E<lt>jfreeman@tassie.net.auE<gt> This package is free software and is provided ``as is'' without expres +s or implied warranty. It may be used, redistributed and/or modified under +the terms of the Perl Artistic License (see http://www.perl.com/perl/misc/Artist +ic.html) =head1 CREDITS The entire interface and key sections of code come from CGI.pm by Linc +oln Stein. Hopefully Randall Schwartz will go over the code with his eagle eye to + iron out the bugs. Then again he may just flame me :-) I enjoyed writing this a +nyway. Jeff Pinyan picked the first bug - the gauntlet is laid down: Cristians: 0 Lions: 13 Japhy:1 Rest: 0 =head1 SEE ALSO CGI.pm by Lincoln Stein =cut sub new { my ( $class, $init ) = @_; $class = ref($class) || $class; my $self = {}; bless $self, $class; $self->init($init); return $self; } sub init { my ( $self, $init ) = @_; if ( ! defined $init ) { $self->initialise; } elsif ( (ref $init) =~ m/HASH/i ) { for my $param( keys %{$init} ) { $self->{$param} = (ref $init->{$param}) ? $init->{$param} +: [$init->{$param}]; } } elsif ( (ref $init) =~ m/GLOB/i ) { $self->init_from_file($init); } else { $self->parse_params($init); } } sub initialise { my $self = shift; my $data; my ( $type, $length, $method ) = @ENV{'CONTENT_TYPE','CONTENT_LENG +TH','REQUEST_METHOD'}; if ( defined $type and $type =~ m|^multipart/form-data| ) { $self->cgi_error( "Can't process multipart/form-data encoding" + ); return undef; } if ( $POST_MAX != -1 and $length > $POST_MAX ) { $self->cgi_error( "$length bytes on STDIN exceeds POST_MAX!" ) +; return undef; } if ( $method eq 'POST') { read( STDIN, $data, $length ); unless ( $length == length $data ) { $self->cgi_error( "Bad read! wanted $length, got ".(length + $data) ); return undef; } # uncomment this if you want to use query string and POST at t +he same time (not recommended) # $data .= $ENV{'QUERY_STRING'}; } elsif ( $method =~/^(GET|HEAD)$/ ) { $data = $ENV{'QUERY_STRING'}; $data ||= $ENV{'REDIRECT_QUERY_STRING'} || ''; } else { $self->cgi_error("Unknown method $method"); return undef; } unless ( $data ) { $self->cgi_error("No data recieved via $method"); return undef; } $data =~ s/%00//g; # prevent null byte hacks $data =~ tr/\000//d; $self->parse_params($data); } sub parse_params { my ( $self, $data ) = @_; unless ($data =~ /[&=;]/) { $self->{'keywords'} = [$self->parse_keywordlist($data)]; return; } my @pairs = split /[&;]/, $data; for my $pair(@pairs) { my ( $param, $value ) = split '=', $pair, 2; next unless $param; $value = '' unless defined $value; $param = url_decode( $self, $param ); $value = url_decode( $self, $value ); push @{$self->{$param}}, $value; } } sub parse_keywordlist { my( $self, $keywords ) = @_; $keywords = $self->url_decode($keywords); my @keywords = split /\s+/, $keywords; return @keywords; } sub param { my ( $self, $param, @values ) = @_; # set values using -name=>'foo',-value=>'bar' syntax if ( defined $param and $param eq '-name' ) { my ( $param, undef, $value ) = @values; $self->{$param} = (ref $value) ? $value : [$value]; return; } # if multiple values passed we also assume set param if ( @values ) { $self->{$param} = [@values]; return; } unless ( defined $param ) { my @params = grep { ! /^\.cgi_error/ } keys %{$self}; return sort @params; } if (defined $self->{$param}) { my @values = @{$self->{$param}}; return wantarray ? @values : $values[0]; } return undef; } sub url_decode { my ( $self, $decode ) = @_; return undef unless defined $decode; $decode =~ tr/+/ /; $decode =~ s/%([a-fA-F0-9]{2})/ pack "C", hex $1 /eg; return $decode; } sub cgi_error { my ( $self, $err ) = @_; $self->{'.cgi_error'} = $err if defined $err; return $self->{'.cgi_error'}; } sub init_from_file { my ( $self, $fh ) = @_; local $/ = "\n"; while (my $pair = <$fh>) { chomp $pair; return if $pair eq '='; $self->parse_params($pair); } } # these next subs will only be loaded on demand by Selfloader # they can be safely deleted if you do not call them __DATA__ sub url_encode { my ( $self, $encode ) = @_; return undef unless defined $encode; $encode =~ s/([^A-Za-z0-9\-_.!~*'() ])/ uc sprintf "%%%02x",ord $1 + /eg; $encode =~ tr/ /+/; return $encode; } sub Vars { my $self = shift; my @hash_list = (); my @params = $self->param; for my $param(@params) { my @values = $self->param($param); push @hash_list, ($param, (join "\0", @values)); } return @hash_list; } sub append { my ( $self, undef, $param, undef, $value ) = @_; my @values = ref $value ? @{$value} : ($value); push @{$self->{$param}}, $_ for @values; } sub keywords { my $self = shift; return @{$self->{'keywords'}} if defined $self->{'keywords'}; return undef; } sub delete { my ( $self, $param ) = @_; return undef unless defined $self->{$param}; delete $self->{$param}; } sub delete_all { my $self = shift; undef %{$self}; } sub escapeHTML { my ( $self, $escape ) = @_; return undef unless defined $escape ; # make the required escapes $escape =~ s/&/&amp/g; $escape =~ s/"/&quot;/g; $escape =~ s/</&lt;/g; $escape =~ s/>/&gt;/g; # change tabs to 4 spaces $escape =~ s/\t/ /g; # make the whitespace escapes $escape =~ s/( {2,})/"&nbsp;" x length $1/eg; # make the brower bugfix escapes as seen on source of # CGI.pm. Doing this globally should do no harm $escape =~ s/\x8b/&#139;/g; $escape =~ s/\x9b/&#155;/g; # uncomment to change newlines to <br> if desired # $escape =~ s/\n/<br>\n/g; return $escape; } sub save { my ( $self, $fh ) = @_; local ( $,, $\ ) = ( '', '' ); unless ( $fh and fileno $fh ) { $self->cgi_error('Invalid filehandle'); return undef; } for my $param($self->param) { for my $value ( $self->param($param) ) {; print $fh $self->url_encode($param), '=', $self->url_encod +e($value), "\n"; } } print $fh "=\n"; } __END__

Patches

See the POD Japhy rocks so what's new. .tar.gz is not atomic with PM. Priorities. PM....->....->....CPAN

cheers

tachyon

s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

Replies are listed 'Best First'.
Re: CGI.pm - put up or shut up
by japhy (Canon) on Nov 01, 2001 at 11:48 UTC
    This is wrong:
    sub Vars { my $self = shift; my @hash_list = (); my @params = $self->param; for my $param(@params) { my @values = $self->param($param); push @hash_list, ($param, (join /\000/, @values)); } return @hash_list; }
    join() takes a string, not a regex. The patch:
    sub Vars { my $self = shift; my @hash_list = (); my @params = $self->param; for my $param(@params) { my @values = $self->param($param); push @hash_list, ($param, (join "\0", @values)); } return @hash_list; }

    _____________________________________________________
    Jeff[japhy]Pinyan: Perl, regex, and perl hacker.
    s++=END;++y(;-P)}y js++=;shajsj<++y(p-q)}?print:??;

      Wow that's some serious nit picking with the code, I see it has bene patched. ++ Japhy for picking the faults.

      doc

      print "almost was a could have been";

      Thanks Jeff

(Ovid - minor nits) Re: CGI.pm - put up or shut up
by Ovid (Cardinal) on Nov 01, 2001 at 22:11 UTC

    tachyon, love the idea, though there are a few minor nits I would pick.

    sub new { my ( $class, $init ) = @_; $class = ref($class) || $class; my $self = {}; bless $self, $class; $self->init($init); return $self; }

    Why is the ref( $class ) in there? new, to many OO programmers is a class method but the ref( $class ) is usually there to help create a clone of the object, thus making this an instance method. Which is this? It's confusing to many programmers. I'd take that out.

    # uncomment this if you want to use query string and POST at the same +time (not recommended) # $data .= $ENV{'QUERY_STRING'};

    That's good, but I don't see a url_param() method. Unfortunately, sometimes Web pages get coded POST data and URL parameters. Rather than forcing the programmer to a make a permanent hack to your code or reset the request method and create a new object, there should be a convenient method for getting those params.

    As for not handling uploads, I would add that in. If you do that, you've provided, in one module, all of the functionality that 99% of Web programmers use. If you did that and threw in cookie support, this might be a module I could use here at work. Barring that, it's just a novelty for me in terms of serious work. I could use if for small projects, but I don't want to have more than one CGI module being used here.

    On the off chance that you do add support for multipart/form-data, you'll need to take out this:

    $data =~ s/%00//g; # prevent null byte hacks $data =~ tr/\000//d;

    No sense in destroying most uploads :)

    Cheers,
    Ovid

    Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

Re: CGI.pm - put up or shut up
by Zecho (Hermit) on Nov 02, 2001 at 01:12 UTC
    tachyon No offense but, I think maybe you should re-read my question at *.pm vs. *::Simple. Mainly the first line:
    (substitute CGI for any module with a ::Simple counterpart)

    I will summarize the rest for you as:
    "What's the difference?"

    Why not read my nodes before including them in your criticism?



    Downvote this as you like, but if you want to include my nodes in your rants, please read them first and make sure they apply, and if you're going to vote on my nodes, read them first as well.

    Update: I upvoted this because I do understand and agree to the point about testing modules and about not bitching about a module you don't use correctly.

Re: CGI.pm - put up or shut up
by hackmare (Pilgrim) on Nov 01, 2001 at 22:54 UTC

    I like your module and I think I'm going to give it a try.

    Nevertheless, I think that the CGI bloating issue is not that relevant any more, given current best-practice CGI coding standards.

    Please correct me if I am wrong, but here is what I understand to be the mechanism:

    Is it not basically bad coding practice to pull in a perl module with all the dependant methods rather than declaring the desired ones?

    use CGI qw(:header :h1 :h2 :p :param);

    As well, is the impact of the size of a module impact not decreased when using mod_perl or fastcgi? Who in their right mind is using perl without oen of these in a CGI context?

    Hackmare.

Re: CGI.pm - put up or shut up
by Anonymous Monk on Nov 02, 2001 at 00:48 UTC