diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2015-06-16 06:44:29 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2015-06-16 06:44:29 +0000 |
commit | f9f3ab3056d94292adb4ab2e1451645bee989769 (patch) | |
tree | cc5a62954d359d5aad449420bc7ec259b3edb79e /t | |
download | CGI-tarball-master.tar.gz |
Diffstat (limited to 't')
64 files changed, 4183 insertions, 0 deletions
diff --git a/t/Dump.t b/t/Dump.t new file mode 100644 index 0000000..fafb5b2 --- /dev/null +++ b/t/Dump.t @@ -0,0 +1,5 @@ +use Test::More 'no_plan'; +use CGI; +my $cgi = CGI->new('<a>=<b>'); +like($cgi->Dump, qr/\Q<a>/, 'param names are HTML escaped by Dump()'); +like($cgi->Dump, qr/\Q<b>/, 'param values are HTML escaped by Dump()'); diff --git a/t/arbitrary_handles.t b/t/arbitrary_handles.t new file mode 100644 index 0000000..eaaea0c --- /dev/null +++ b/t/arbitrary_handles.t @@ -0,0 +1,30 @@ +#!perl + +use strict; +use warnings; + +use Test::More tests => 4; +use IO::File; +use CGI; + +my $test_string = 'game=soccer&game=baseball&weather=nice'; +my $handle = IO::File->new_tmpfile; +$handle->write( $test_string ); +$handle->flush; +$handle->seek( 0,0 ); + +{ + local $ENV{REQUEST_METHOD} = 'POST'; + + ok( my $q = CGI->new( $handle ),"CGI->new from POST" ); + is( $q->param( 'weather' ),'nice', "param() from POST with IO::File" ); +} + +$handle->seek( 0,0 ); + +{ + local $ENV{REQUEST_METHOD} = 'GET'; + + ok( my $q = CGI->new( $handle ),"CGI->new from GET" ); + is( $q->param( 'weather' ),'nice', "param() from GET with IO::File" ); +} diff --git a/t/autoescape.t b/t/autoescape.t new file mode 100644 index 0000000..3a25c2d --- /dev/null +++ b/t/autoescape.t @@ -0,0 +1,200 @@ +#!perl + +use strict; +use warnings; + +use Test::More tests => 18; + +use CGI qw/ autoEscape escapeHTML button textfield password_field textarea popup_menu scrolling_list checkbox_group optgroup checkbox radio_group submit image_button button /; +$CGI::Util::SORT_ATTRIBUTES = 1; + +is (button(-name => 'test<'), '<input type="button" name="test<" value="test<" />', "autoEscape defaults to On"); + +my $before = escapeHTML("test<"); +autoEscape(undef); +my $after = escapeHTML("test<"); + + +is($before, "test<", "reality check escapeHTML"); + +is ($before, $after, "passing undef to autoEscape doesn't break escapeHTML"); +is (button(-name => 'test<'), '<input type="button" name="test<" value="test<" />', "turning off autoescape actually works"); +autoEscape(1); +is (button(-name => 'test<'), '<input type="button" name="test<" value="test<" />', "autoescape turns back on"); +$before = escapeHTML("test<"); +autoEscape(0); +$after = escapeHTML("test<"); + +is ($before, $after, "passing 0 to autoEscape doesn't break escapeHTML"); + +# RT #25485: Needs Tests: autoEscape() bypassed for Javascript handlers, except in button() +autoEscape(undef); + +is(textfield( +{ +default => 'text field', +onclick => 'alert("===> text field")', +}, +), +qq{<input type="text" name="" value="text field" onclick="alert("===> text field")" />}, +'autoescape javascript turns off for textfield' +); + +is(password_field( +{ +default => 'password field', +onclick => 'alert("===> password +field")', +}, +), +qq{<input type="password" name="" value="password field" onclick="alert("===> password +field")" />}, +'autoescape javascript turns off for password field' +); + +is(textarea( +{ +name => 'foo', +default => 'text area', +rows => 10, +columns => 50, +onclick => 'alert("===> text area")', +}, +), +qq{<textarea name="foo" rows="10" cols="50" onclick="alert("===> text area")">text area</textarea>}, +'autoescape javascript turns off for textarea' +); + +is(popup_menu( +{ +name => 'menu_name', +values => ['eenie','meenie','minie'], +default => 'meenie', +onclick => 'alert("===> popup menu")', +} +), +qq{<select name="menu_name" onclick="alert("===> popup menu")"> +<option value="eenie">eenie</option> +<option selected="selected" value="meenie">meenie</option> +<option value="minie">minie</option> +</select>}, +'autoescape javascript turns off for popup_menu' +); + +is(popup_menu( +-name=>'menu_name', +onclick => 'alert("===> menu group")', +-values=>[ +qw/eenie meenie minie/, +optgroup( +-name=>'optgroup_name', +onclick => +'alert("===> menu group option")', +-values => ['moe','catch'], +-attributes=>{'catch'=>{'class'=>'red'}} +) +], +-labels=>{ +'eenie'=>'one', +'meenie'=>'two', +'minie'=>'three' +}, +-default=>'meenie' +), +qq{<select name="menu_name" onclick="alert("===> menu group")"> +<option value="eenie">one</option> +<option selected="selected" value="meenie">two</option> +<option value="minie">three</option> +<optgroup label="optgroup_name" onclick="alert("===> menu group option")"> +<option value="moe">moe</option> +<option class="red" value="catch">catch</option> +</optgroup> +</select>}, +'autoescape javascript turns off for popup_menu #2' +); + +is(scrolling_list( +-name=>'list_name', +onclick => 'alert("===> scrolling +list")', +-values=>['eenie','meenie','minie','moe'], +-default=>['eenie','moe'], +-size=>5, +-multiple=>'true', +), +qq{<select name="list_name" size="5" multiple="multiple" onclick="alert("===> scrolling +list")"> +<option selected="selected" value="eenie">eenie</option> +<option value="meenie">meenie</option> +<option value="minie">minie</option> +<option selected="selected" value="moe">moe</option> +</select>}, +'autoescape javascript turns off for scrolling list' +); + +is(checkbox_group( +-name=>'group_name', +onclick => 'alert("===> checkbox group")', +-values=>['eenie','meenie','minie','moe'], +-default=>['eenie','moe'], +-linebreak=>'true', +), +qq{<label><input type="checkbox" name="group_name" value="eenie" checked="checked" onclick="alert("===> checkbox group")" />eenie</label><br /> <label><input type="checkbox" name="group_name" value="meenie" onclick="alert("===> checkbox group")" />meenie</label><br /> <label><input type="checkbox" name="group_name" value="minie" onclick="alert("===> checkbox group")" />minie</label><br /> <label><input type="checkbox" name="group_name" value="moe" checked="checked" onclick="alert("===> checkbox group")" />moe</label><br />}, +'autoescape javascript turns off for checkbox group' +); + +is(checkbox( +-name=>'checkbox_name', +onclick => 'alert("===> single checkbox")', +onchange => 'alert("===> single checkbox +changed")', +-checked=>1, +-value=>'ON', +-label=>'CLICK ME' +), +qq{<label><input type="checkbox" name="checkbox_name" value="ON" checked="checked" onchange="alert("===> single checkbox +changed")" onclick="alert("===> single checkbox")" />CLICK ME</label>}, +'autoescape javascript turns off for checkbox' +); + +is(radio_group( +{ +name=>'group_name', +onclick => 'alert("===> radio group")', +values=>['eenie','meenie','minie','moe'], +rows=>2, +columns=>2, +} +), +qq{<table><tr><td><label><input type="radio" name="group_name" value="eenie" checked="checked" onclick="alert("===> radio group")" />eenie</label></td><td><label><input type="radio" name="group_name" value="minie" onclick="alert("===> radio group")" />minie</label></td></tr><tr><td><label><input type="radio" name="group_name" value="meenie" onclick="alert("===> radio group")" />meenie</label></td><td><label><input type="radio" name="group_name" value="moe" onclick="alert("===> radio group")" />moe</label></td></tr></table>}, +'autoescape javascript turns off for radio group' +); + +is(submit( +-name=>'button_name', +onclick => 'alert("===> submit button")', +-value=>'value' +), +qq{<input type="submit" name="button_name" value="value" onclick="alert("===> submit button")" />}, +'autoescape javascript turns off for submit' +); + +is(image_button( +-name=>'button_name', +onclick => 'alert("===> image button")', +-src=>'/source/URL', +-align=>'MIDDLE' +), +qq{<input type="image" name="button_name" src="/source/URL" align="middle" onclick="alert("===> image button")" />}, +'autoescape javascript turns off for image_button' +); + +is(button( +{ +onclick => 'alert("===> Button")', +title => 'Button', +}, +), +qq{<input type="button" onclick="alert("===> Button")" title="Button" />}, +'autoescape javascript turns off for button' +); @@ -0,0 +1,7 @@ +#!/usr/local/bin/perl -w + +use Test::More tests => 2; + +BEGIN{ use_ok('CGI'); } + +can_ok('CGI', qw/cookie param/); diff --git a/t/carp.t b/t/carp.t new file mode 100644 index 0000000..307fc61 --- /dev/null +++ b/t/carp.t @@ -0,0 +1,440 @@ +# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 2 -*- +#!perl -w + +use strict; + +use Test::More tests => 71; +use IO::Handle; + +use CGI::Carp; +use Cwd; + +#----------------------------------------------------------------------------- +# Test id +#----------------------------------------------------------------------------- + +# directly invoked +my $expect_f = __FILE__; +my $expect_l = __LINE__ + 1; +my ($file, $line, $id) = CGI::Carp::id(0); +is($file, $expect_f, "file"); +is($line, $expect_l, "line"); +is($id, "carp.t", "id"); + +# one level of indirection +sub id1 { my $level = shift; return CGI::Carp::id($level); }; + +$expect_l = __LINE__ + 1; +($file, $line, $id) = id1(1); +is($file, $expect_f, "file"); +is($line, $expect_l, "line"); +is($id, "carp.t", "id"); + +# two levels of indirection +sub id2 { my $level = shift; return id1($level); }; + +$expect_l = __LINE__ + 1; +($file, $line, $id) = id2(2); +is($file, $expect_f, "file"); +is($line, $expect_l, "line"); +is($id, "carp.t", "id"); + +#----------------------------------------------------------------------------- +# Test stamp +#----------------------------------------------------------------------------- + +my $stamp = "/^\\[ + ([a-z]{3}\\s){2}\\s? + [\\s\\d:]+ + \\]\\s$id:/ix"; + +like(CGI::Carp::stamp(), + $stamp, + "Time in correct format"); + +sub stamp1 {return CGI::Carp::stamp()}; +sub stamp2 {return stamp1()}; + +like(stamp2(), $stamp, "Time in correct format"); + +$CGI::Carp::FULL_PATH = 1; +# really should test the full path here, but platform differnces +# will make the regexp hideous. this may well fail if anything +# using it chdirs into t/ so using Cwd to dry to catch this +my $cwd = getcwd; +if ( $cwd !~ /t$/ ) { + unlike(stamp2(), $stamp, "Time in correct format (FULL_PATH)"); +} else { + pass( "Can't run FULL_PATH test when cwd is t/" ); +} +$CGI::Carp::FULL_PATH = 0; + +#----------------------------------------------------------------------------- +# Test warn and _warn +#----------------------------------------------------------------------------- + +# set some variables to control what's going on. +$CGI::Carp::WARN = 0; +$CGI::Carp::EMIT_WARNINGS = 0; +my $q_file = quotemeta($file); + + +# Test that realwarn is called +{ + local $^W = 0; + eval "sub CGI::Carp::realwarn {return 'Called realwarn'};"; +} + +$expect_l = __LINE__ + 1; +is(CGI::Carp::warn("There is a problem"), + "Called realwarn", + "CGI::Carp::warn calls CORE::warn"); + +# Test that message is constructed correctly +eval 'sub CGI::Carp::realwarn {my $mess = shift; return $mess};'; + +$expect_l = __LINE__ + 1; +like(CGI::Carp::warn("There is a problem"), + "/] $id: There is a problem at $q_file line $expect_l.".'$/', + "CGI::Carp::warn builds correct message"); + +# Test that _warn is called at the correct time +$CGI::Carp::WARN = 1; + +my $warn_expect_l = $expect_l = __LINE__ + 1; +like(CGI::Carp::warn("There is a problem"), + "/] $id: There is a problem at $q_file line $expect_l.".'$/', + "CGI::Carp::warn builds correct message"); + +# Test $NO_TIMESTAMP +{ + local $CGI::Carp::NO_TIMESTAMP = 1; + $expect_l = __LINE__ + 1; + like(CGI::Carp::warn("There is a problem"), + qr/\A\Q$id: There is a problem at $file line $expect_l.\E\s*\z/, + "noTimestamp"); + + local $CGI::Carp::NO_TIMESTAMP = 0; + $expect_l = __LINE__ + 2; + import CGI::Carp 'noTimestamp'; + like(CGI::Carp::warn("There is a problem"), + qr/\A\Q$id: There is a problem at $file line $expect_l.\E\s*\z/, + "noTimestamp"); +} + +#----------------------------------------------------------------------------- +# Test ineval +#----------------------------------------------------------------------------- + +ok(!CGI::Carp::ineval, 'ineval returns false when not in eval'); +eval {ok(CGI::Carp::ineval, 'ineval returns true when in eval');}; + +#----------------------------------------------------------------------------- +# Test die +#----------------------------------------------------------------------------- + +# set some variables to control what's going on. +$CGI::Carp::WRAP = 0; + +$expect_l = __LINE__ + 1; +eval { CGI::Carp::die('There is a problem'); }; +like($@, + '/^There is a problem/', + 'CGI::Carp::die calls CORE::die without altering argument in eval'); + +# Test that realwarn is called +{ + local $^W = 0; + local *CGI::Carp::realdie = sub { my $mess = shift; return $mess }; + + like(CGI::Carp::die('There is a problem'), + $stamp, + 'CGI::Carp::die calls CORE::die, but adds stamp'); + +} + +#----------------------------------------------------------------------------- +# Test set_message +#----------------------------------------------------------------------------- + +is(CGI::Carp::set_message('My new Message'), + 'My new Message', + 'CGI::Carp::set_message returns new message'); + +is($CGI::Carp::CUSTOM_MSG, + 'My new Message', + 'CGI::Carp::set_message message set correctly'); + +# set the message back to the empty string so that the tests later +# work properly. +CGI::Carp::set_message(''), + +#----------------------------------------------------------------------------- +# Test set_progname +#----------------------------------------------------------------------------- + +import CGI::Carp qw(name=new_progname); +is($CGI::Carp::PROGNAME, + 'new_progname', + 'CGI::Carp::import set program name correctly'); + +is(CGI::Carp::set_progname('newer_progname'), + 'newer_progname', + 'CGI::Carp::set_progname returns new program name'); + +is($CGI::Carp::PROGNAME, + 'newer_progname', + 'CGI::Carp::set_progname program name set correctly'); + +# set the message back to the empty string so that the tests later +# work properly. +is (CGI::Carp::set_progname(undef),undef,"CGI::Carp::set_progname returns unset name correctly"); +is ($CGI::Carp::PROGNAME,undef,"CGI::Carp::set_progname program name unset correctly"); + +#----------------------------------------------------------------------------- +# Test warnings_to_browser +#----------------------------------------------------------------------------- + +CGI::Carp::warningsToBrowser(0); +is($CGI::Carp::EMIT_WARNINGS, 0, "Warnings turned off"); + +# turn off STDOUT (prevents spurious warnings to screen +tie *STDOUT, 'StoreStuff' or die "Can't tie STDOUT"; +CGI::Carp::warningsToBrowser(1); +my $fake_out = join '', <STDOUT>; +untie *STDOUT; + +open(STDOUT, ">&REAL_STDOUT"); +my $fname = $0; +$fname =~ tr/<>-/\253\273\255/; # _warn does this so we have to also +is( $fake_out, "<!-- warning: There is a problem at $fname line $warn_expect_l. -->\n", + 'warningsToBrowser() on' ); + +is($CGI::Carp::EMIT_WARNINGS, 1, "Warnings turned off"); + +#----------------------------------------------------------------------------- +# Test fatals_to_browser +#----------------------------------------------------------------------------- + +package StoreStuff; + +sub TIEHANDLE { + my $class = shift; + bless [], $class; +} + +sub PRINT { + my $self = shift; + push @$self, @_; +} + +sub READLINE { + my $self = shift; + shift @$self; +} + +package main; + +tie *STDOUT, "StoreStuff"; + +# do tests +my @result; + +CGI::Carp::fatalsToBrowser(); +$result[0] .= $_ while (<STDOUT>); + +CGI::Carp::fatalsToBrowser('Message to the world'); +$result[1] .= $_ while (<STDOUT>); + +$ENV{SERVER_ADMIN} = 'foo@bar.com'; +CGI::Carp::fatalsToBrowser(); +$result[2] .= $_ while (<STDOUT>); + +CGI::Carp::set_message('Override the message passed in'), + +CGI::Carp::fatalsToBrowser('Message to the world'); +$result[3] .= $_ while (<STDOUT>); +CGI::Carp::set_message(''), +delete $ENV{SERVER_ADMIN}; + +# now restore STDOUT +untie *STDOUT; + + +like($result[0], + '/Content-type: text/html/', + "Default string has header"); + +ok($result[0] !~ /Message to the world/, "Custom message not in default string"); + +like($result[1], + '/Message to the world/', + "Custom Message appears in output"); + +ok($result[0] !~ /foo\@bar.com/, "Server Admin does not appear in default message"); + +like($result[2], + '/foo@bar.com/', + "Server Admin appears in output"); + +like($result[3], + '/Message to the world/', + "Custom message not in result"); + +like($result[3], + '/Override the message passed in/', + "Correct message in string"); + +#----------------------------------------------------------------------------- +# Test to_filehandle +#----------------------------------------------------------------------------- + +sub buffer { + CGI::Carp::to_filehandle (@_); +} + +tie *STORE, "StoreStuff"; + +require FileHandle; +my $fh = FileHandle->new; + +ok( defined buffer(\*STORE), '\*STORE returns proper filehandle'); +ok( defined buffer( $fh ), '$fh returns proper filehandle'); +ok( defined buffer('::STDOUT'), 'STDIN returns proper filehandle'); +ok( defined buffer(*main::STDOUT), 'STDIN returns proper filehandle'); +ok(!defined buffer("WIBBLE"), '"WIBBLE" doesn\'t returns proper filehandle'); + +# Calling die with code refs with no WRAP +{ + local $CGI::Carp::WRAP = 0; + + eval { CGI::Carp::die( 'regular string' ) }; + like $@ => qr/regular string/, 'die with string'; + + eval { CGI::Carp::die( [ 1..10 ] ) }; + like $@ => qr/ARRAY\(0x[\da-f]+\)/, 'die with array ref'; + + eval { CGI::Carp::die( { a => 1 } ) }; + like $@ => qr/HASH\(0x[\da-f]+\)/, 'die with hash ref'; + + eval { CGI::Carp::die( sub { 'Farewell' } ) }; + like $@ => qr/CODE\(0x[\da-f]+\)/, 'die with code ref'; + + eval { CGI::Carp::die( My::Plain::Object->new ) }; + isa_ok $@, 'My::Plain::Object'; + + eval { CGI::Carp::die( My::Plain::Object->new, ' and another argument' ) }; + like $@ => qr/My::Plain::Object/, 'object is stringified'; + like $@ => qr/and another argument/, 'second argument is present'; + + eval { CGI::Carp::die( My::Stringified::Object->new ) }; + isa_ok $@, 'My::Stringified::Object'; + + eval { CGI::Carp::die( My::Stringified::Object->new, ' and another argument' ) }; + like $@ => qr/stringified/, 'object is stringified'; + like $@ => qr/and another argument/, 'second argument is present'; + + eval { CGI::Carp::die() }; + like $@ => qr/Died at/, 'die with no argument'; +} + +# Calling die with code refs when WRAPped +{ + local $CGI::Carp::WRAP = 1; + local *CGI::Carp::realdie = sub { return @_ }; + local *STDOUT; + + tie *STDOUT, 'StoreStuff'; + + my %result; # store results because stdout is kidnapped + + CGI::Carp::die( 'regular string' ); + $result{string} .= $_ while <STDOUT>; + + CGI::Carp::die( [ 1..10 ] ); + $result{array_ref} .= $_ while <STDOUT>; + + CGI::Carp::die( { a => 1 } ); + $result{hash_ref} .= $_ while <STDOUT>; + + CGI::Carp::die( sub { 'Farewell' } ); + $result{code_ref} .= $_ while <STDOUT>; + + CGI::Carp::die( My::Plain::Object->new ); + $result{plain_object} .= $_ while <STDOUT>; + + CGI::Carp::die( My::Stringified::Object->new ); + $result{string_object} .= $_ while <STDOUT>; + + undef $@; + CGI::Carp::die(); + $result{no_args} .= $_ while <STDOUT>; + + $@ = "I think I caught a virus"; + CGI::Carp::die(); + $result{propagated} .= $_ while <STDOUT>; + + untie *STDOUT; + + like $result{string} => qr/regular string/, 'regular string, wrapped'; + like $result{array_ref} => qr/ARRAY\(\w+?\)/, 'array ref, wrapped'; + like $result{hash_ref} => qr/HASH\(\w+?\)/, 'hash ref, wrapped'; + like $result{code_ref} => qr/CODE\(\w+?\)/, 'code ref, wrapped'; + like $result{plain_object} => qr/My::Plain::Object/, + 'plain object, wrapped'; + like $result{string_object} => qr/stringified/, + 'stringified object, wrapped'; + like $result{no_args} => qr/Died at/, 'no args, wrapped'; + + like $result{propagated} => qr/I think I caught a virus\t\.{3}propagated/, + 'propagating $@ if no argument'; + +} + +{ + package My::Plain::Object; + + sub new { + return bless {}, shift; + } +} + +{ + package My::Stringified::Object; + + use overload '""' => sub { 'stringified' }; + + sub new { + return bless {}, shift; + } +} + + +@result = (); +tie *STDOUT, 'StoreStuff' or die "Can't tie STDOUT"; + { + eval { + $CGI::Carp::TO_BROWSER = 0; + die 'Message ToBrowser = 0'; + }; + $result[0] = $@; + $result[1] .= $_ while (<STDOUT>); + } +untie *STDOUT; + + like $result[0] => qr/Message ToBrowser/, 'die message for ToBrowser = 0 is OK'; + ok !$result[1], 'No output for ToBrowser = 0'; + +*CGI::Carp::die = sub { &$CGI::Carp::DIE_HANDLER; return 1 }; +*CGI::Carp::warn = sub { return 1 }; + +CGI::Carp::set_die_handler( sub { pass( "die handler" ); return 1 } ); +ok( CGI::Carp::confess(),'confess' ); +ok( CGI::Carp::croak(),'croak' ); +ok( CGI::Carp::carp(),'carp' ); +ok( CGI::Carp::cluck(),'cluck' ); + +use File::Temp; +my $fh = File::Temp->new; + +ok( CGI::Carp::carpout( $fh ),'carpout' ); @@ -0,0 +1,73 @@ +#!/usr/local/bin/perl + +# coverage for testing that doesn't sit elsewhere + +use strict; +use warnings; + +use Test::More tests => 25; +use Test::Deep; +use Test::Warn; + +use CGI (); + +# Set up a CGI environment +$ENV{REQUEST_METHOD} = 'GET'; +$ENV{QUERY_STRING} = 'game=chess&game=checkers&weather=dull'; + +isa_ok( my $q = CGI->new,'CGI' ); + +# undocumented ->r method, seems to be a temp store? +$q->r( 'foo' ); +is( $q->r,'foo','r' ); + +diag( "cgi-lib.pl routines" ); + +ok( $q->ReadParse,'ReadParse' ); +is( $q->PrintHeader,$q->header,'PrintHeader' ); +is( $q->HtmlTop,$q->start_html,'HtmlTop' ); +is( $q->HtmlBot,$q->end_html,'HtmlBot' ); + +cmp_deeply( + [ my @params = CGI::SplitParam( "foo\0bar" ) ], + [ qw/ foo bar /], + 'SplitParam' +); + +ok( $q->MethGet,'MethGet' ); +ok( ! $q->MethPost,'MethPost' ); +ok( ! $q->MethPut,'MethPut' ); + +note( "TIE methods" ); +ok( ! $q->FIRSTKEY,'FIRSTKEY' ); +ok( ! $q->NEXTKEY,'NEXTKEY' ); +ok( ! $q->CLEAR,'CLEAR' ); + +is( $q->version,$CGI::VERSION,'version' ); +is( $q->as_string,'<ul></ul>','as_string' ); + +is( ( $q->_style )[0],'<link rel="stylesheet" type="text/css" href="" />','_style' ); +is( $q->state,'http://localhost','state' ); + +$CGI::NOSTICKY = 0; +ok( $q->nosticky( 1 ),'nosticky' ); +is( $CGI::NOSTICKY,1,' ... sets $CGI::NOSTICKY' ); + +$CGI::NPH = 0; +ok( $q->nph( 1 ),'nph' ); +is( $CGI::NPH,1,' ... sets $CGI::NPH' ); + +$CGI::CLOSE_UPLOAD_FILES = 0; +ok( $q->close_upload_files( 1 ),'close_upload_files' ); +is( $CGI::CLOSE_UPLOAD_FILES,1,' ... sets $CGI::CLOSE_UPLOAD_FILES' ); + +cmp_deeply( + $q->default_dtd, + [ + '-//W3C//DTD HTML 4.01 Transitional//EN', + 'http://www.w3.org/TR/html4/loose.dtd' + ], + 'default_dtd' +); + +ok( ! $q->private_tempfiles,'private_tempfiles' ); diff --git a/t/changes.t b/t/changes.t new file mode 100644 index 0000000..1f40250 --- /dev/null +++ b/t/changes.t @@ -0,0 +1,12 @@ +#!perl + +use strict; +use warnings; + +use Test::More; + +eval 'use Test::CPAN::Changes'; + +plan skip_all => 'Test::CPAN::Changes required for this test' if $@; + +changes_ok(); diff --git a/t/charset.t b/t/charset.t new file mode 100644 index 0000000..7459797 --- /dev/null +++ b/t/charset.t @@ -0,0 +1,27 @@ +#!perl + +use strict; +use warnings; + +use Test::More 'no_plan'; + +use CGI; + +my $q = CGI->new; + +like( $q->header + , qr/charset=ISO-8859-1/, "charset ISO-8859-1 is set by default for default content-type"); +like( $q->header('application/json') + , qr/charset=ISO-8859-1/, "charset ISO-8859-1 is set by default for application/json content-type"); + +{ + $q->charset('UTF-8'); + my $out = $q->header('text/plain'); + like($out, qr{Content-Type: text/plain; charset=UTF-8}, "setting charset alters header of text/plain"); +} +{ + $q->charset('UTF-8'); + my $out = $q->header('application/json'); + like($out, qr{Content-Type: application/json; charset=UTF-8}, "setting charset alters header of application/json"); +} + diff --git a/t/checkbox_group.t b/t/checkbox_group.t new file mode 100644 index 0000000..ea5ad08 --- /dev/null +++ b/t/checkbox_group.t @@ -0,0 +1,21 @@ +#!/usr/local/bin/perl -w + +use Test::More tests => 3; + +BEGIN { use_ok('CGI'); }; +use CGI (':standard','-no_debug','-no_xhtml'); + +# no_xhtml test on checkbox_group() +is(checkbox_group(-name => 'game', + '-values' => [qw/checkers chess cribbage/], + '-defaults' => ['cribbage']), + qq(<input type="checkbox" name="game" value="checkers" >checkers <input type="checkbox" name="game" value="chess" >chess <input type="checkbox" name="game" value="cribbage" checked >cribbage), + 'checkbox_group()'); + +# xhtml test on checkbox_group() +$CGI::XHTML = 1; +is(checkbox_group(-name => 'game', + '-values' => [qw/checkers chess cribbage/], + '-defaults' => ['cribbage']), + qq(<label><input type="checkbox" name="game" value="checkers" />checkers</label> <label><input type="checkbox" name="game" value="chess" />chess</label> <label><input type="checkbox" name="game" value="cribbage" checked="checked" />cribbage</label>), + 'checkbox_group()'); diff --git a/t/compiles_pod.t b/t/compiles_pod.t new file mode 100644 index 0000000..076d016 --- /dev/null +++ b/t/compiles_pod.t @@ -0,0 +1,42 @@ +#!perl + +use strict; +use warnings; + +use Test::More; +use File::Find; + +if(($ENV{HARNESS_PERL_SWITCHES} || '') =~ /Devel::Cover/) { + plan skip_all => 'HARNESS_PERL_SWITCHES =~ /Devel::Cover/'; +} +if(!eval 'use Test::Pod; 1') { + *Test::Pod::pod_file_ok = sub { SKIP: { skip "pod_file_ok(@_) (Test::Pod is required)", 1 } }; +} +if(!eval 'use Test::Pod::Coverage; 1') { + *Test::Pod::Coverage::pod_coverage_ok = sub { SKIP: { skip "pod_coverage_ok(@_) (Test::Pod::Coverage is required)", 1 } }; +} + +my @files; + +find( + { + wanted => sub { /\.pm$/ and push @files, $File::Find::name }, + no_chdir => 1 + }, + -e 'blib' ? 'blib' : 'lib', +); + +plan tests => @files * 3; + +for my $file (@files) { + my $module = $file; $module =~ s,\.pm$,,; $module =~ s,.*/?lib/,,; $module =~ s,/,::,g; + ok eval "use $module; 1", "use $module" or diag $@; + Test::Pod::pod_file_ok($file); + TODO: { + # not enough POD coverage yet by a long way, also the nature + # of CGI.pm at present (most subs eval'd as strings) means + # this test isn't that much use - so mark as TODO for now + local $TODO = 'POD coverage'; + Test::Pod::Coverage::pod_coverage_ok($module); + } +} diff --git a/t/cookie.t b/t/cookie.t new file mode 100644 index 0000000..dda2f82 --- /dev/null +++ b/t/cookie.t @@ -0,0 +1,441 @@ +#!perl -w + +use strict; + +# to have a consistent baseline, we nail the current time +# to 100 seconds after the epoch +BEGIN { + *CORE::GLOBAL::time = sub { 100 }; +} + +use Test::More 'no_plan'; +use CGI::Util qw(escape unescape); +use POSIX qw(strftime); +use CGI::Cookie; + +#----------------------------------------------------------------------------- +# make sure module loaded +#----------------------------------------------------------------------------- + +my @test_cookie = ( + # including leading and trailing whitespace in first cookie + ' foo=123 ; bar=qwerty; baz=wibble; qux=a1', + 'foo=123; bar=qwerty; baz=wibble;', + 'foo=vixen; bar=cow; baz=bitch; qux=politician', + 'foo=a%20phrase; bar=yes%2C%20a%20phrase; baz=%5Ewibble; qux=%27', + ); + +#----------------------------------------------------------------------------- +# Test parse +#----------------------------------------------------------------------------- + +{ + my $result = CGI::Cookie->parse($test_cookie[0]); + is(ref($result), 'HASH', "Hash ref returned in scalar context"); + + my @result = CGI::Cookie->parse($test_cookie[0]); + is(@result, 8, "returns correct number of fields"); + + @result = CGI::Cookie->parse($test_cookie[1]); + is(@result, 6, "returns correct number of fields"); + + my %result = CGI::Cookie->parse($test_cookie[0]); + is($result{foo}->value, '123', "cookie foo is correct"); + is($result{bar}->value, 'qwerty', "cookie bar is correct"); + is($result{baz}->value, 'wibble', "cookie baz is correct"); + is($result{qux}->value, 'a1', "cookie qux is correct"); + + my @array = CGI::Cookie->parse(''); + my $scalar = CGI::Cookie->parse(''); + is_deeply(\@array, [], " parse('') returns an empty array in list context (undocumented)"); + is_deeply($scalar, {}, " parse('') returns an empty hashref in scalar context (undocumented)"); + + @array = CGI::Cookie->parse(undef); + $scalar = CGI::Cookie->parse(undef); + is_deeply(\@array, [], " parse(undef) returns an empty array in list context (undocumented)"); + is_deeply($scalar, {}, " parse(undef) returns an empty hashref in scalar context (undocumented)"); +} + +#----------------------------------------------------------------------------- +# Test fetch +#----------------------------------------------------------------------------- + +{ + # make sure there are no cookies in the environment + delete $ENV{HTTP_COOKIE}; + delete $ENV{COOKIE}; + + my %result = CGI::Cookie->fetch(); + ok(keys %result == 0, "No cookies in environment, returns empty list"); + + # now set a cookie in the environment and try again + $ENV{HTTP_COOKIE} = $test_cookie[2]; + %result = CGI::Cookie->fetch(); + ok(eq_set([keys %result], [qw(foo bar baz qux)]), + "expected cookies extracted"); + + is(ref($result{foo}), 'CGI::Cookie', 'Type of objects returned is correct'); + is($result{foo}->value, 'vixen', "cookie foo is correct"); + is($result{bar}->value, 'cow', "cookie bar is correct"); + is($result{baz}->value, 'bitch', "cookie baz is correct"); + is($result{qux}->value, 'politician', "cookie qux is correct"); + + # Delete that and make sure it goes away + delete $ENV{HTTP_COOKIE}; + %result = CGI::Cookie->fetch(); + ok(keys %result == 0, "No cookies in environment, returns empty list"); + + # try another cookie in the other environment variable thats supposed to work + $ENV{COOKIE} = $test_cookie[3]; + %result = CGI::Cookie->fetch(); + ok(eq_set([keys %result], [qw(foo bar baz qux)]), + "expected cookies extracted"); + + is(ref($result{foo}), 'CGI::Cookie', 'Type of objects returned is correct'); + is($result{foo}->value, 'a phrase', "cookie foo is correct"); + is($result{bar}->value, 'yes, a phrase', "cookie bar is correct"); + is($result{baz}->value, '^wibble', "cookie baz is correct"); + is($result{qux}->value, "'", "cookie qux is correct"); +} + +#----------------------------------------------------------------------------- +# Test raw_fetch +#----------------------------------------------------------------------------- + +{ + # make sure there are no cookies in the environment + delete $ENV{HTTP_COOKIE}; + delete $ENV{COOKIE}; + + my %result = CGI::Cookie->raw_fetch(); + ok(keys %result == 0, "No cookies in environment, returns empty list"); + + # now set a cookie in the environment and try again + $ENV{HTTP_COOKIE} = $test_cookie[2]; + %result = CGI::Cookie->raw_fetch(); + ok(eq_set([keys %result], [qw(foo bar baz qux)]), + "expected cookies extracted"); + + is(ref($result{foo}), '', 'Plain scalar returned'); + is($result{foo}, 'vixen', "cookie foo is correct"); + is($result{bar}, 'cow', "cookie bar is correct"); + is($result{baz}, 'bitch', "cookie baz is correct"); + is($result{qux}, 'politician', "cookie qux is correct"); + + # Delete that and make sure it goes away + delete $ENV{HTTP_COOKIE}; + %result = CGI::Cookie->raw_fetch(); + ok(keys %result == 0, "No cookies in environment, returns empty list"); + + # try another cookie in the other environment variable thats supposed to work + $ENV{COOKIE} = $test_cookie[3]; + %result = CGI::Cookie->raw_fetch(); + ok(eq_set([keys %result], [qw(foo bar baz qux)]), + "expected cookies extracted"); + + is(ref($result{foo}), '', 'Plain scalar returned'); + is($result{foo}, 'a%20phrase', "cookie foo is correct"); + is($result{bar}, 'yes%2C%20a%20phrase', "cookie bar is correct"); + is($result{baz}, '%5Ewibble', "cookie baz is correct"); + is($result{qux}, '%27', "cookie qux is correct"); + + $ENV{COOKIE} = '$Version=1; foo; $Path="/test"'; + %result = CGI::Cookie->raw_fetch(); + is($result{foo}, '', 'no value translates to empty string'); +} + +#----------------------------------------------------------------------------- +# Test new +#----------------------------------------------------------------------------- + +{ + # Try new with full information provided + my $c = CGI::Cookie->new(-name => 'foo', + -value => 'bar', + -expires => '+3M', + -domain => '.capricorn.com', + -path => '/cgi-bin/database', + -secure => 1, + -httponly=> 1 + ); + is(ref($c), 'CGI::Cookie', 'new returns objects of correct type'); + is($c->name , 'foo', 'name is correct'); + is($c->value , 'bar', 'value is correct'); + like($c->expires, '/^[a-z]{3},\s*\d{2}-[a-z]{3}-\d{4}/i', 'expires in correct format'); + is($c->domain , '.capricorn.com', 'domain is correct'); + is($c->path , '/cgi-bin/database', 'path is correct'); + ok($c->secure , 'secure attribute is set'); + ok( $c->httponly, 'httponly attribute is set' ); + + # now try it with the only two manditory values (should also set the default path) + $c = CGI::Cookie->new(-name => 'baz', + -value => 'qux', + ); + is(ref($c), 'CGI::Cookie', 'new returns objects of correct type'); + is($c->name , 'baz', 'name is correct'); + is($c->value , 'qux', 'value is correct'); + ok(!defined $c->expires, 'expires is not set'); + ok(!defined $c->max_age, 'max_age is not set'); + ok(!defined $c->domain , 'domain attributeis not set'); + is($c->path, '/', 'path atribute is set to default'); + ok(!defined $c->secure , 'secure attribute is set'); + ok( !defined $c->httponly, 'httponly attribute is not set' ); + +# I'm really not happy about the restults of this section. You pass +# the new method invalid arguments and it just merilly creates a +# broken object :-) +# I've commented them out because they currently pass but I don't +# think they should. I think this is testing broken behaviour :-( + +# # This shouldn't work +# $c = CGI::Cookie->new(-name => 'baz' ); +# +# is(ref($c), 'CGI::Cookie', 'new returns objects of correct type'); +# is($c->name , 'baz', 'name is correct'); +# ok(!defined $c->value, "Value is undefined "); +# ok(!defined $c->expires, 'expires is not set'); +# ok(!defined $c->domain , 'domain attributeis not set'); +# is($c->path , '/', 'path atribute is set to default'); +# ok(!defined $c->secure , 'secure attribute is set'); + +} + +#----------------------------------------------------------------------------- +# Test as_string +#----------------------------------------------------------------------------- + +{ + my $c = CGI::Cookie->new(-name => 'Jam', + -value => 'Hamster', + -expires => '+3M', + '-max-age' => '+3M', + -domain => '.pie-shop.com', + -path => '/', + -secure => 1, + -httponly=> 1 + ); + + my $name = $c->name; + like($c->as_string, "/$name/", "Stringified cookie contains name"); + + my $value = $c->value; + like($c->as_string, "/$value/", "Stringified cookie contains value"); + + my $expires = $c->expires; + like($c->as_string, "/$expires/", "Stringified cookie contains expires"); + + my $max_age = $c->max_age; + like($c->as_string, "/$max_age/", "Stringified cookie contains max_age"); + + my $domain = $c->domain; + like($c->as_string, "/$domain/", "Stringified cookie contains domain"); + + my $path = $c->path; + like($c->as_string, "/$path/", "Stringified cookie contains path"); + + like($c->as_string, '/secure/', "Stringified cookie contains secure"); + + like( $c->as_string, '/HttpOnly/', + "Stringified cookie contains HttpOnly" ); + + $c = CGI::Cookie->new(-name => 'Hamster-Jam', + -value => 'Tulip', + ); + + $name = $c->name; + like($c->as_string, "/$name/", "Stringified cookie contains name"); + + $value = $c->value; + like($c->as_string, "/$value/", "Stringified cookie contains value"); + + ok($c->as_string !~ /expires/, "Stringified cookie has no expires field"); + + ok($c->as_string !~ /max-age/, "Stringified cookie has no max-age field"); + + ok($c->as_string !~ /domain/, "Stringified cookie has no domain field"); + + $path = $c->path; + like($c->as_string, "/$path/", "Stringified cookie contains path"); + + ok($c->as_string !~ /secure/, "Stringified cookie does not contain secure"); + + ok( $c->as_string !~ /HttpOnly/, + "Stringified cookie does not contain HttpOnly" ); +} + +#----------------------------------------------------------------------------- +# Test compare +#----------------------------------------------------------------------------- + +{ + my $c1 = CGI::Cookie->new(-name => 'Jam', + -value => 'Hamster', + -expires => '+3M', + -domain => '.pie-shop.com', + -path => '/', + -secure => 1 + ); + + # have to use $c1->expires because the time will occasionally be + # different between the two creates causing spurious failures. + my $c2 = CGI::Cookie->new(-name => 'Jam', + -value => 'Hamster', + -expires => $c1->expires, + -domain => '.pie-shop.com', + -path => '/', + -secure => 1 + ); + + # This looks titally whacked, but it does the -1, 0, 1 comparison + # thing so 0 means they match + is($c1->compare("$c1"), 0, "Cookies are identical"); + is( "$c1", "$c2", "Cookies are identical"); + + $c1 = CGI::Cookie->new(-name => 'Jam', + -value => 'Hamster', + -domain => '.foo.bar.com' + ); + + # have to use $c1->expires because the time will occasionally be + # different between the two creates causing spurious failures. + $c2 = CGI::Cookie->new(-name => 'Jam', + -value => 'Hamster', + ); + + # This looks titally whacked, but it does the -1, 0, 1 comparison + # thing so 0 (i.e. false) means they match + is($c1->compare("$c1"), 0, "Cookies are identical"); + ok($c1->compare("$c2"), "Cookies are not identical"); + + $c2->domain('.foo.bar.com'); + is($c1->compare("$c2"), 0, "Cookies are identical"); +} + +#----------------------------------------------------------------------------- +# Test name, value, domain, secure, expires and path +#----------------------------------------------------------------------------- + +{ + my $c = CGI::Cookie->new(-name => 'Jam', + -value => 'Hamster', + -expires => '+3M', + -domain => '.pie-shop.com', + -path => '/', + -secure => 1 + ); + + is($c->name, 'Jam', 'name is correct'); + is($c->name('Clash'), 'Clash', 'name is set correctly'); + is($c->name, 'Clash', 'name now returns updated value'); + + # this is insane! it returns a simple scalar but can't accept one as + # an argument, you have to give it an arrary ref. It's totally + # inconsitent with these other methods :-( + is($c->value, 'Hamster', 'value is correct'); + is($c->value(['Gerbil']), 'Gerbil', 'value is set correctly'); + is($c->value, 'Gerbil', 'value now returns updated value'); + + my $exp = $c->expires; + like($c->expires, '/^[a-z]{3},\s*\d{2}-[a-z]{3}-\d{4}/i', 'expires is correct'); + like($c->expires('+12h'), '/^[a-z]{3},\s*\d{2}-[a-z]{3}-\d{4}/i', 'expires is set correctly'); + like($c->expires, '/^[a-z]{3},\s*\d{2}-[a-z]{3}-\d{4}/i', 'expires now returns updated value'); + isnt($c->expires, $exp, "Expiry time has changed"); + + is($c->domain, '.pie-shop.com', 'domain is correct'); + is($c->domain('.wibble.co.uk'), '.wibble.co.uk', 'domain is set correctly'); + is($c->domain, '.wibble.co.uk', 'domain now returns updated value'); + + is($c->path, '/', 'path is correct'); + is($c->path('/basket/'), '/basket/', 'path is set correctly'); + is($c->path, '/basket/', 'path now returns updated value'); + + ok($c->secure, 'secure attribute is set'); + ok(!$c->secure(0), 'secure attribute is cleared'); + ok(!$c->secure, 'secure attribute is cleared'); +} + +#---------------------------------------------------------------------------- +# Max-age +#---------------------------------------------------------------------------- + +MAX_AGE: { + my $cookie = CGI::Cookie->new( -name=>'a', value=>'b', '-expires' => 'now',); + is $cookie->expires, 'Thu, 01-Jan-1970 00:01:40 GMT'; + is $cookie->max_age => undef, 'max-age is undefined when setting expires'; + + $cookie = CGI::Cookie->new( -name=>'a', 'value'=>'b' ); + $cookie->max_age( '+4d' ); + + is $cookie->expires, undef, 'expires is undef when setting max_age'; + is $cookie->max_age => 4*24*60*60, 'setting via max-age'; + + $cookie->max_age( '113' ); + is $cookie->max_age => 13, 'max_age(num) as delta'; + + $cookie = CGI::Cookie->new( -name=>'a', value=>'b', '-max-age' => '+3d'); + is( $cookie->max_age,3*24*60*60,'-max-age in constructor' ); + ok( !$cookie->expires,' ... lack of expires' ); + + $cookie = CGI::Cookie->new( -name=>'a', value=>'b', '-expires' => 'now', '-max-age' => '+3d'); + is( $cookie->max_age,3*24*60*60,'-max-age in constructor' ); + ok( $cookie->expires,'-expires in constructor' ); +} + + +#---------------------------------------------------------------------------- +# bake +#---------------------------------------------------------------------------- + +BAKE: { + my $cookie = CGI::Cookie->new( -name=>'a', value=>'b', '-expires' => 'now',); + eval { $cookie->bake }; + is($@,'', "calling bake() without mod_perl should survive"); +} + +#----------------------------------------------------------------------------- +# Apache2?::Cookie compatibility. +#----------------------------------------------------------------------------- +APACHEREQ: { + my $r = Apache::Faker->new; + isa_ok $r, 'Apache'; + ok my $c = CGI::Cookie->new( + $r, + -name => 'Foo', + -value => 'Bar', + ), 'Pass an Apache object to the CGI::Cookie constructor'; + isa_ok $c, 'CGI::Cookie'; + ok $c->bake($r), 'Bake the cookie'; + ok eq_array( $r->{check}, [ 'Set-Cookie', $c->as_string ]), + 'bake() should call headers_out->set()'; + + $r = Apache2::Faker->new; + isa_ok $r, 'Apache2::RequestReq'; + ok $c = CGI::Cookie->new( + $r, + -name => 'Foo', + -value => 'Bar', + ), 'Pass an Apache::RequestReq object to the CGI::Cookie constructor'; + isa_ok $c, 'CGI::Cookie'; + ok $c->bake($r), 'Bake the cookie'; + ok eq_array( $r->{check}, [ 'Set-Cookie', $c->as_string ]), + 'bake() should call headers_out->set()'; +} + + +package Apache::Faker; +sub new { bless {}, shift } +sub isa { + my ($self, $pkg) = @_; + return $pkg eq 'Apache'; +} +sub headers_out { shift } +sub add { shift->{check} = \@_; } + +package Apache2::Faker; +sub new { bless {}, shift } +sub isa { + my ($self, $pkg) = @_; + return $pkg eq 'Apache2::RequestReq'; +} +sub headers_out { shift } +sub add { shift->{check} = \@_; } diff --git a/t/delete.t b/t/delete.t new file mode 100644 index 0000000..0fabad7 --- /dev/null +++ b/t/delete.t @@ -0,0 +1,59 @@ +#!/usr/local/bin/perl + +use strict; +use warnings; + +use Test::More; + +use CGI (); +use Config; + +my $loaded = 1; + +$| = 1; + +$CGI::LIST_CONTEXT_WARN = 0; + +######################### End of black magic. + +# Set up a CGI environment +$ENV{REQUEST_METHOD} = 'DELETE'; +$ENV{QUERY_STRING} = 'game=chess&game=checkers&weather=dull'; +$ENV{PATH_INFO} = '/somewhere/else'; +$ENV{PATH_TRANSLATED} = '/usr/local/somewhere/else'; +$ENV{SCRIPT_NAME} = '/cgi-bin/foo.cgi'; +$ENV{SERVER_PROTOCOL} = 'HTTP/1.0'; +$ENV{SERVER_PORT} = 8080; +$ENV{SERVER_NAME} = 'the.good.ship.lollypop.com'; +$ENV{REQUEST_URI} = "$ENV{SCRIPT_NAME}$ENV{PATH_INFO}?$ENV{QUERY_STRING}"; +$ENV{HTTP_LOVE} = 'true'; + +my $q = CGI->new; +ok $q,"CGI::new()"; +is $q->request_method => 'DELETE',"CGI::request_method()"; +is $q->query_string => 'game=chess;game=checkers;weather=dull',"CGI::query_string()"; +is $q->param(), 2,"CGI::param()"; +is join(' ',sort $q->param()), 'game weather',"CGI::param()"; +is $q->param('game'), 'chess',"CGI::param()"; +is $q->param('weather'), 'dull',"CGI::param()"; +is join(' ',$q->param('game')), 'chess checkers',"CGI::param()"; +ok $q->param(-name=>'foo',-value=>'bar'),'CGI::param() put'; +is $q->param(-name=>'foo'), 'bar','CGI::param() get'; +is $q->query_string, 'game=chess;game=checkers;weather=dull;foo=bar',"CGI::query_string() redux"; +is $q->http('love'), 'true',"CGI::http()"; +is $q->script_name, '/cgi-bin/foo.cgi',"CGI::script_name()"; +is $q->url, 'http://the.good.ship.lollypop.com:8080/cgi-bin/foo.cgi',"CGI::url()"; +is $q->self_url, + 'http://the.good.ship.lollypop.com:8080/cgi-bin/foo.cgi/somewhere/else?game=chess;game=checkers;weather=dull;foo=bar', + "CGI::url()"; +is $q->url(-absolute=>1), '/cgi-bin/foo.cgi','CGI::url(-absolute=>1)'; +is $q->url(-relative=>1), 'foo.cgi','CGI::url(-relative=>1)'; +is $q->url(-relative=>1,-path=>1), 'foo.cgi/somewhere/else','CGI::url(-relative=>1,-path=>1)'; +is $q->url(-relative=>1,-path=>1,-query=>1), + 'foo.cgi/somewhere/else?game=chess;game=checkers;weather=dull;foo=bar', + 'CGI::url(-relative=>1,-path=>1,-query=>1)'; +$q->delete('foo'); +ok !$q->param('foo'),'CGI::delete()'; + + +done_testing(); diff --git a/t/end_form.t b/t/end_form.t new file mode 100644 index 0000000..6a13e0b --- /dev/null +++ b/t/end_form.t @@ -0,0 +1,9 @@ + +use strict; +use warnings; + +use Test::More tests => 2; + +BEGIN { use_ok 'CGI', qw/ :form / }; + +is end_form() => '</form>', 'end_form()'; diff --git a/t/form.t b/t/form.t new file mode 100644 index 0000000..0a90b9c --- /dev/null +++ b/t/form.t @@ -0,0 +1,235 @@ +#!perl -w + +# Form-related tests for CGI.pm +# If you are adding or updated tests, please put tests for each methods in +# their own file, rather than growing this file any larger. + +use Test::More 'no_plan'; +use CGI (':standard','-no_debug','-tabindex'); + +my $CRLF = "\015\012"; +if ($^O eq 'VMS') { + $CRLF = "\n"; # via web server carriage is inserted automatically +} +if (ord("\t") != 9) { # EBCDIC? + $CRLF = "\r\n"; +} + + +# Set up a CGI environment +$ENV{REQUEST_METHOD} = 'GET'; +$ENV{QUERY_STRING} = 'game=chess&game=checkers&weather=dull'; +$ENV{PATH_INFO} = '/somewhere/else'; +$ENV{PATH_TRANSLATED} = '/usr/local/somewhere/else'; +$ENV{SCRIPT_NAME} ='/cgi-bin/foo.cgi'; +$ENV{SERVER_PROTOCOL} = 'HTTP/1.0'; +$ENV{SERVER_PORT} = 8080; +$ENV{SERVER_NAME} = 'the.good.ship.lollypop.com'; + +is(start_form(-action=>'foobar',-method=>'get'), + qq(<form method="get" action="foobar" enctype="multipart/form-data">), + "start_form()"); + +is(submit(), + qq(<input type="submit" tabindex="1" name=".submit" />), + "submit()"); + +is(submit(-name => 'foo', + -value => 'bar'), + qq(<input type="submit" tabindex="2" name="foo" value="bar" />), + "submit(-name,-value)"); + +is(submit({-name => 'foo', + -value => 'bar'}), + qq(<input type="submit" tabindex="3" name="foo" value="bar" />), + "submit({-name,-value})"); + +is(textfield(-name => 'weather'), + qq(<input type="text" name="weather" tabindex="4" value="dull" />), + "textfield({-name})"); + +is(textfield(-name => 'weather', + -value => 'nice'), + qq(<input type="text" name="weather" tabindex="5" value="dull" />), + "textfield({-name,-value})"); + +is(textfield(-name => 'weather', + -value => 'nice', + -override => 1), + qq(<input type="text" name="weather" tabindex="6" value="nice" />), + "textfield({-name,-value,-override})"); + +is(checkbox(-name => 'weather', + -value => 'nice'), + qq(<label><input type="checkbox" name="weather" value="nice" tabindex="7" />weather</label>), + "checkbox()"); + +is(checkbox(-name => 'weather', + -value => 'nice', + -label => 'forecast'), + qq(<label><input type="checkbox" name="weather" value="nice" tabindex="8" />forecast</label>), + "checkbox()"); + +is(checkbox(-name => 'weather', + -value => 'nice', + -label => 'forecast', + -checked => 1, + -override => 1), + qq(<label><input type="checkbox" name="weather" value="nice" tabindex="9" checked="checked" />forecast</label>), + "checkbox()"); + +is(checkbox(-name => 'weather', + -value => 'dull', + -label => 'forecast'), + qq(<label><input type="checkbox" name="weather" value="dull" tabindex="10" checked="checked" />forecast</label>), + "checkbox()"); + +is(radio_group(-name => 'game'), + qq(<label><input type="radio" name="game" value="chess" checked="checked" tabindex="11" />chess</label> <label><input type="radio" name="game" value="checkers" tabindex="12" />checkers</label>), + 'radio_group()'); + +is(radio_group(-name => 'game', + -labels => {'chess' => 'ping pong'}), + qq(<label><input type="radio" name="game" value="chess" checked="checked" tabindex="13" />ping pong</label> <label><input type="radio" name="game" value="checkers" tabindex="14" />checkers</label>), + 'radio_group()'); + +is(checkbox_group(-name => 'game', + -Values => [qw/checkers chess cribbage/]), + qq(<label><input type="checkbox" name="game" value="checkers" checked="checked" tabindex="15" />checkers</label> <label><input type="checkbox" name="game" value="chess" checked="checked" tabindex="16" />chess</label> <label><input type="checkbox" name="game" value="cribbage" tabindex="17" />cribbage</label>), + 'checkbox_group()'); + +is(checkbox_group(-name => 'game', + '-values' => [qw/checkers chess cribbage/], + '-defaults' => ['cribbage'], + -override=>1), + qq(<label><input type="checkbox" name="game" value="checkers" tabindex="18" />checkers</label> <label><input type="checkbox" name="game" value="chess" tabindex="19" />chess</label> <label><input type="checkbox" name="game" value="cribbage" checked="checked" tabindex="20" />cribbage</label>), + 'checkbox_group()'); + +is(popup_menu(-name => 'game', + '-values' => [qw/checkers chess cribbage/], + -default => 'cribbage', + -override => 1), + '<select name="game" tabindex="21" > +<option value="checkers">checkers</option> +<option value="chess">chess</option> +<option selected="selected" value="cribbage">cribbage</option> +</select>', + 'popup_menu()'); +is(scrolling_list(-name => 'game', + '-values' => [qw/checkers chess cribbage/], + -default => 'cribbage', + -override=>1), + '<select name="game" tabindex="22" size="3"> +<option value="checkers">checkers</option> +<option value="chess">chess</option> +<option selected="selected" value="cribbage">cribbage</option> +</select>', + 'scrolling_list()'); + +is(checkbox_group(-name => 'game', + -Values => [qw/checkers chess cribbage/], + -disabled => ['checkers']), + qq(<label><input type="checkbox" name="game" value="checkers" checked="checked" tabindex="23" disabled='1'/><span style="color:gray">checkers</span></label> <label><input type="checkbox" name="game" value="chess" checked="checked" tabindex="24" />chess</label> <label><input type="checkbox" name="game" value="cribbage" tabindex="25" />cribbage</label>), + 'checkbox_group()'); + +my $optgroup = optgroup(-name=>'optgroup_name', + -Values => ['moe','catch'], + -attributes=>{'catch'=>{'class'=>'red'}}); + +is($optgroup, + qq(<optgroup label="optgroup_name"> +<option value="moe">moe</option> +<option class="red" value="catch">catch</option> +</optgroup>), + 'optgroup()'); + +is(popup_menu(-name=>'menu_name', + -Values=>[qw/eenie meenie minie/, $optgroup], + -labels=>{'eenie'=>'one', + 'meenie'=>'two', + 'minie'=>'three'}, + -default=>'meenie'), + qq(<select name="menu_name" tabindex="26" > +<option value="eenie">one</option> +<option selected="selected" value="meenie">two</option> +<option value="minie">three</option> +<optgroup label="optgroup_name"> +<option value="moe">moe</option> +<option class="red" value="catch">catch</option> +</optgroup> +</select>), + 'popup_menu() + optgroup()'); + +is(scrolling_list(-name=>'menu_name', + -Values=>[qw/eenie meenie minie/, $optgroup], + -labels=>{'eenie'=>'one', + 'meenie'=>'two', + 'minie'=>'three'}, + -default=>'meenie'), + qq(<select name="menu_name" tabindex="27" size="4"> +<option value="eenie">one</option> +<option selected="selected" value="meenie">two</option> +<option value="minie">three</option> +<optgroup label="optgroup_name"> +<option value="moe">moe</option> +<option class="red" value="catch">catch</option> +</optgroup> +</select>), + 'scrolling_list() + optgroup()'); + +# ---------- START 22046 ---------- +# The following tests were added for +# https://rt.cpan.org/Public/Bug/Display.html?id=22046 +# SHCOREY at cpan.org +# Saved whether working with XHTML because need to test both +# with it and without. +my $saved_XHTML = $CGI::XHTML; + +# set XHTML +$CGI::XHTML = 1; + +is(start_form("GET","/foobar"), + qq{<form method="get" action="/foobar" enctype="multipart/form-data">}, + 'start_form() + XHTML'); + +is(start_form("GET", "/foobar",&CGI::URL_ENCODED), + qq{<form method="get" action="/foobar" enctype="application/x-www-form-urlencoded">}, + 'start_form() + XHTML + URL_ENCODED'); + +is(start_form("GET", "/foobar",&CGI::MULTIPART), + qq{<form method="get" action="/foobar" enctype="multipart/form-data">}, + 'start_form() + XHTML + MULTIPART'); + +is(start_multipart_form("GET", "/foobar"), + qq{<form method="get" action="/foobar" enctype="multipart/form-data">}, + 'start_multipart_form() + XHTML'); + +is(start_multipart_form("GET", "/foobar","name=\"foobar\""), + qq{<form method="get" action="/foobar" enctype="multipart/form-data" name="foobar">}, + 'start_multipart_form() + XHTML + additional args'); + +# set no XHTML +$CGI::XHTML = 0; + +is(start_form("GET","/foobar"), + qq{<form method="get" action="/foobar" enctype="application/x-www-form-urlencoded">}, + 'start_form() + NO_XHTML'); + +is(start_form("GET", "/foobar",&CGI::URL_ENCODED), + qq{<form method="get" action="/foobar" enctype="application/x-www-form-urlencoded">}, + 'start_form() + NO_XHTML + URL_ENCODED'); + +is(start_form("GET", "/foobar",&CGI::MULTIPART), + qq{<form method="get" action="/foobar" enctype="multipart/form-data">}, + 'start_form() + NO_XHTML + MULTIPART'); + +is(start_multipart_form("GET", "/foobar"), + qq{<form method="get" action="/foobar" enctype="multipart/form-data">}, + 'start_multipart_form() + NO_XHTML'); + +is(start_multipart_form("GET", "/foobar","name=\"foobar\""), + qq{<form method="get" action="/foobar" enctype="multipart/form-data" name="foobar">}, + 'start_multipart_form() + NO_XHTML + additional args'); + +# restoring value +$CGI::XHTML = $saved_XHTML; diff --git a/t/function.t b/t/function.t new file mode 100644 index 0000000..56fa0c1 --- /dev/null +++ b/t/function.t @@ -0,0 +1,110 @@ +#!/usr/local/bin/perl -w + +BEGIN {$| = 1; print "1..33\n"; } +END {print "not ok 1\n" unless $loaded;} +use Config; +use CGI (':standard','keywords'); +$loaded = 1; +$CGI::Util::SORT_ATTRIBUTES = 1; +$CGI::LIST_CONTEXT_WARN = 0; +print "ok 1\n"; + +######################### End of black magic. + +# util +sub test { + local($^W) = 0; + my($num, $true,$msg) = @_; + print($true ? "ok $num\n" : "not ok $num $msg\n"); +} + +my $CRLF = "\015\012"; + +# A peculiarity of sending "\n" through MBX|Socket|web-server on VMS +# is that a CR character gets inserted automatically in the web server +# case but not internal to perl's double quoted strings "\n". This +# test would need to be modified to use the "\015\012" on VMS if it +# were actually run through a web server. +# Thanks to Peter Prymmer for this + +if ($^O eq 'VMS') { $CRLF = "\n"; } + +# Web servers on EBCDIC hosts are typically set up to do an EBCDIC -> ASCII +# translation hence CRLF is used as \r\n within CGI.pm on such machines. + +if (ord("\t") != 9) { $CRLF = "\r\n"; } + +# Set up a CGI environment +$ENV{REQUEST_METHOD}='GET'; +$ENV{QUERY_STRING} ='game=chess&game=checkers&weather=dull'; +$ENV{PATH_INFO} ='/somewhere/else'; +$ENV{PATH_TRANSLATED} ='/usr/local/somewhere/else'; +$ENV{SCRIPT_NAME} ='/cgi-bin/foo.cgi'; +$ENV{SERVER_PROTOCOL} = 'HTTP/1.0'; +$ENV{SERVER_PORT} = 8080; +$ENV{SERVER_NAME} = 'the.good.ship.lollypop.com'; +$ENV{HTTP_LOVE} = 'true'; + +test(2,request_method() eq 'GET',"CGI::request_method()"); +test(3,query_string() eq 'game=chess;game=checkers;weather=dull',"CGI::query_string()"); +test(4,param() == 2,"CGI::param()"); +test(5,join(' ',sort {$a cmp $b} param()) eq 'game weather',"CGI::param()"); +test(6,param('game') eq 'chess',"CGI::param()"); +test(7,param('weather') eq 'dull',"CGI::param()"); +test(8,join(' ',param('game')) eq 'chess checkers',"CGI::param()"); +test(9,param(-name=>'foo',-value=>'bar'),'CGI::param() put'); +test(10,param(-name=>'foo') eq 'bar','CGI::param() get'); +test(11,query_string() eq 'game=chess;game=checkers;weather=dull;foo=bar',"CGI::query_string() redux"); +test(12,http('love') eq 'true',"CGI::http()"); +test(13,script_name() eq '/cgi-bin/foo.cgi',"CGI::script_name()"); +test(14,url() eq 'http://the.good.ship.lollypop.com:8080/cgi-bin/foo.cgi',"CGI::url()"); +test(15,self_url() eq + 'http://the.good.ship.lollypop.com:8080/cgi-bin/foo.cgi/somewhere/else?game=chess;game=checkers;weather=dull;foo=bar', + "CGI::url()"); +test(16,url(-absolute=>1) eq '/cgi-bin/foo.cgi','CGI::url(-absolute=>1)'); +test(17,url(-relative=>1) eq 'foo.cgi','CGI::url(-relative=>1)'); +test(18,url(-relative=>1,-path=>1) eq 'foo.cgi/somewhere/else','CGI::url(-relative=>1,-path=>1)'); +test(19,url(-relative=>1,-path=>1,-query=>1) eq + 'foo.cgi/somewhere/else?game=chess;game=checkers;weather=dull;foo=bar', + 'CGI::url(-relative=>1,-path=>1,-query=>1)'); +Delete('foo'); +test(20,!param('foo'),'CGI::delete()'); + +CGI::_reset_globals(); +$ENV{QUERY_STRING}='mary+had+a+little+lamb'; +test(21,join(' ',keywords()) eq 'mary had a little lamb','CGI::keywords'); +test(22,join(' ',param('keywords')) eq 'mary had a little lamb','CGI::keywords'); + +CGI::_reset_globals; +if ($Config{d_fork}) { + $test_string = 'game=soccer&game=baseball&weather=nice'; + $ENV{REQUEST_METHOD}='POST'; + $ENV{CONTENT_LENGTH}=length($test_string); + $ENV{QUERY_STRING}='big_balls=basketball&small_balls=golf'; + if (open(CHILD,"|-")) { # cparent + print CHILD $test_string; + close CHILD; + exit 0; + } + # at this point, we're in a new (child) process + test(23,param('weather') eq 'nice',"CGI::param() from POST"); + test(24,(url_param('big_balls') eq 'basketball'),"CGI::url_param()"); +} else { + print "ok 23 # Skip\n"; + print "ok 24 # Skip\n"; +} +test(25,redirect('http://somewhere.else') eq "Status: 302 Found${CRLF}Location: http://somewhere.else${CRLF}${CRLF}","CGI::redirect() 1"); +my $h = redirect(-Location=>'http://somewhere.else',-Type=>'text/html'); +test(26,$h eq "Status: 302 Found${CRLF}Location: http://somewhere.else${CRLF}Content-Type: text/html; charset=ISO-8859-1${CRLF}${CRLF}","CGI::redirect() 2"); +test(27,redirect(-Location=>'http://somewhere.else/bin/foo&bar',-Type=>'text/html') eq "Status: 302 Found${CRLF}Location: http://somewhere.else/bin/foo&bar${CRLF}Content-Type: text/html; charset=ISO-8859-1${CRLF}${CRLF}","CGI::redirect() 2"); + +test(28,escapeHTML('CGI') eq 'CGI','escapeHTML(CGI) failing again'); + +test(29, charset("UTF-8") && header() eq "Content-Type: text/html; charset=UTF-8${CRLF}${CRLF}", "UTF-8 charset"); +test(30, !charset("") && header() eq "Content-Type: text/html${CRLF}${CRLF}", "Empty charset"); + +test(31, header(-foo=>'bar') eq "Foo: bar${CRLF}Content-Type: text/html${CRLF}${CRLF}", "Custom header"); + +test(32, start_form(-action=>'one',name=>'two',onsubmit=>'three') eq qq(<form method="post" action="one" enctype="multipart/form-data" name="two" onsubmit="three">), "initial dash followed by undashed arguments"); +$ENV{QUERY_STRING} ='game=chess&game=checkers&weather=dull'; +test(33,env_query_string() eq $ENV{QUERY_STRING},"CGI::env_query_string()"); diff --git a/t/gh-155.t b/t/gh-155.t new file mode 100644 index 0000000..0c198b0 --- /dev/null +++ b/t/gh-155.t @@ -0,0 +1,23 @@ +use strict; +use warnings; +use Test::More; + +use CGI; + +for (1 .. 20) { + my $q = CGI->new; + + my %args = ( + '-charset' => 'UTF-8', + '-type' => 'text/html', + '-content-type' => 'text/html; charset=iso-8859-1', + ); + + like( + $q->header(%args), + qr!Content-Type: text/html; charset=iso-8859-1!, + 'favour content type over charset/type' + ); +} + +done_testing(); diff --git a/t/headers.t b/t/headers.t new file mode 100644 index 0000000..a062f47 --- /dev/null +++ b/t/headers.t @@ -0,0 +1,54 @@ + +# Test that header generation is spec compliant. +# References: +# http://www.w3.org/Protocols/rfc2616/rfc2616.html +# http://www.w3.org/Protocols/rfc822/3_Lexical.html + +use strict; +use warnings; + +use Test::More 'no_plan'; + +use CGI; + +my $cgi = CGI->new; + +like $cgi->header( -type => "text/html" ), + qr#Type: text/html#, 'known header, basic case: type => "text/html"'; + +eval { $cgi->header( -type => "text/html".$CGI::CRLF."evil: stuff" ) }; +like($@,qr/contains a newline/,'invalid header blows up'); + +like $cgi->header( -type => "text/html".$CGI::CRLF." evil: stuff " ), + qr#Content-Type: text/html evil: stuff#, 'known header, with leading and trailing whitespace on the continuation line'; + +eval { $cgi->header( -p3p => ["foo".$CGI::CRLF."bar"] ) }; +like($@,qr/contains a newline/,'P3P header with CRLF embedded blows up'); + +eval { $cgi->header( -cookie => ["foo".$CGI::CRLF."bar"] ) }; +like($@,qr/contains a newline/,'Set-Cookie header with CRLF embedded blows up'); + +eval { $cgi->header( -foobar => "text/html".$CGI::CRLF."evil: stuff" ) }; +like($@,qr/contains a newline/,'unknown header with CRLF embedded blows up'); + +eval { $cgi->header( -foobar => $CGI::CRLF."Content-type: evil/header" ) }; +like($@,qr/contains a newline/, 'unknown header with leading newlines blows up'); + +eval { $cgi->redirect( -type => "text/html".$CGI::CRLF."evil: stuff" ) }; +like($@,qr/contains a newline/,'redirect with known header with CRLF embedded blows up'); + +eval { $cgi->redirect( -foobar => "text/html".$CGI::CRLF."evil: stuff" ) }; +like($@,qr/contains a newline/,'redirect with unknown header with CRLF embedded blows up'); + +eval { $cgi->redirect( $CGI::CRLF.$CGI::CRLF."Content-Type: text/html") }; +like($@,qr/contains a newline/,'redirect with leading newlines blows up'); + +{ + my $cgi = CGI->new('t=bogus%0A%0A<html>'); + my $out; + $CGI::LIST_CONTEXT_WARN = 0; + eval { $out = $cgi->redirect( $cgi->param('t') ) }; + like($@,qr/contains a newline/, "redirect does not allow double-newline injection"); +} + + diff --git a/t/headers/attachment.t b/t/headers/attachment.t new file mode 100644 index 0000000..967e9b8 --- /dev/null +++ b/t/headers/attachment.t @@ -0,0 +1,23 @@ +use strict; +use CGI; +use Test::More; + +{ + my $cgi = CGI->new; + my $got = $cgi->header( -attachment => 'foo.png' ); + my $expected = 'Content-Disposition: attachment; filename="foo.png"' + . $CGI::CRLF + . 'Content-Type: text/html; charset=ISO-8859-1' + . $CGI::CRLF x 2; + is $got, $expected, 'attachment'; +} + +{ + my $cgi = CGI->new; + my $got = $cgi->header( -attachment => q{} ); + my $expected = "Content-Type: text/html; charset=ISO-8859-1" + . $CGI::CRLF x 2; + is $got, $expected, 'attachment empty string'; +} + +done_testing; diff --git a/t/headers/charset.t b/t/headers/charset.t new file mode 100644 index 0000000..500bd9b --- /dev/null +++ b/t/headers/charset.t @@ -0,0 +1,20 @@ +use strict; +use CGI; +use Test::More; + +{ + my $cgi = CGI->new; + my $got = $cgi->header( -charset => 'utf-8' ); + my $expected = 'Content-Type: text/html; charset=utf-8' + . $CGI::CRLF x 2; + is $got, $expected, 'charset'; +} + +{ + my $cgi = CGI->new; + my $got = $cgi->header( -charset => q{} ); + my $expected = 'Content-Type: text/html' . $CGI::CRLF x 2; + is $got, $expected, 'charset empty string'; +} + +done_testing; diff --git a/t/headers/cookie.t b/t/headers/cookie.t new file mode 100644 index 0000000..a62f6fd --- /dev/null +++ b/t/headers/cookie.t @@ -0,0 +1,34 @@ +use strict; +use CGI; +use Test::More; + +{ + my $cgi = CGI->new; + my $got = $cgi->header( -cookie => 'foo' ); + my $expected = "^Set-Cookie: foo$CGI::CRLF" + . "Date: [^$CGI::CRLF]+$CGI::CRLF" + . 'Content-Type: text/html; charset=ISO-8859-1' + . $CGI::CRLF x 2; + like $got, qr($expected), 'cookie'; +} + +{ + my $cgi = CGI->new; + my $got = $cgi->header( -cookie => [ 'foo', 'bar' ] ); + my $expected = "^Set-Cookie: foo$CGI::CRLF" + . "Set-Cookie: bar$CGI::CRLF" + . "Date: [^$CGI::CRLF]+$CGI::CRLF" + . 'Content-Type: text/html; charset=ISO-8859-1' + . $CGI::CRLF x 2; + like $got, qr($expected), 'cookie arrayref'; +} + +{ + my $cgi = CGI->new; + my $got = $cgi->header( -cookie => q{} ); + my $expected = 'Content-Type: text/html; charset=ISO-8859-1' + . $CGI::CRLF x 2; + is $got, $expected, 'cookie empty string'; +} + +done_testing; diff --git a/t/headers/default.t b/t/headers/default.t new file mode 100644 index 0000000..007c6ea --- /dev/null +++ b/t/headers/default.t @@ -0,0 +1,13 @@ +use strict; +use CGI; +use Test::More; + +{ + my $cgi = CGI->new; + my $got = $cgi->header(); + my $expected = 'Content-Type: text/html; charset=ISO-8859-1' + . $CGI::CRLF x 2; + is $got, $expected, 'default'; +} + +done_testing; diff --git a/t/headers/nph.t b/t/headers/nph.t new file mode 100644 index 0000000..5d0e5e7 --- /dev/null +++ b/t/headers/nph.t @@ -0,0 +1,24 @@ +use strict; +use CGI; +use Test::More; + +{ + my $cgi = CGI->new; + my $got = $cgi->header( -nph => 1 ); + my $expected = "^HTTP/1.0 200 OK$CGI::CRLF" + . "Server: cmdline$CGI::CRLF" + . "Date: [^$CGI::CRLF]+$CGI::CRLF" + . 'Content-Type: text/html; charset=ISO-8859-1' + . $CGI::CRLF x 2; + like $got, qr($expected), 'nph'; +} + +{ + my $cgi = CGI->new; + my $got = $cgi->header( -nph => 0 ); + my $expected = 'Content-Type: text/html; charset=ISO-8859-1' + . $CGI::CRLF x 2; + is $got, $expected, 'nph'; +} + +done_testing; diff --git a/t/headers/p3p.t b/t/headers/p3p.t new file mode 100644 index 0000000..e10c073 --- /dev/null +++ b/t/headers/p3p.t @@ -0,0 +1,33 @@ +use strict; +use CGI; +use Test::More; + +{ + my $cgi = CGI->new; + my $got = $cgi->header( -p3p => "CAO DSP LAW CURa" ); + my $expected = 'P3P: policyref="/w3c/p3p.xml", CP="CAO DSP LAW CURa"' + . $CGI::CRLF + . 'Content-Type: text/html; charset=ISO-8859-1' + . $CGI::CRLF x 2; + is $got, $expected, 'p3p'; +} + +{ + my $cgi = CGI->new; + my $got = $cgi->header( -p3p => [ qw/CAO DSP LAW CURa/ ] ); + my $expected = 'P3P: policyref="/w3c/p3p.xml", CP="CAO DSP LAW CURa"' + . $CGI::CRLF + . 'Content-Type: text/html; charset=ISO-8859-1' + . $CGI::CRLF x 2; + is $got, $expected, 'p3p arrayref'; +} + +{ + my $cgi = CGI->new; + my $got = $cgi->header( -p3p => q{} ); + my $expected = 'Content-Type: text/html; charset=ISO-8859-1' + . $CGI::CRLF x 2; + is $got, $expected, 'p3p empty string'; +} + +done_testing; diff --git a/t/headers/target.t b/t/headers/target.t new file mode 100644 index 0000000..96c95d1 --- /dev/null +++ b/t/headers/target.t @@ -0,0 +1,22 @@ +use strict; +use CGI; +use Test::More; + +{ + my $cgi = CGI->new; + my $got = $cgi->header( -target => 'ResultsWindow' ); + my $expected = "Window-Target: ResultsWindow$CGI::CRLF" + . 'Content-Type: text/html; charset=ISO-8859-1' + . $CGI::CRLF x 2; + is $got, $expected, 'target'; +} + +{ + my $cgi = CGI->new; + my $got = $cgi->header( -target => q{} ); + my $expected = 'Content-Type: text/html; charset=ISO-8859-1' + . $CGI::CRLF x 2; + is $got, $expected, 'target empty string'; +} + +done_testing; diff --git a/t/headers/type.t b/t/headers/type.t new file mode 100644 index 0000000..536a8b7 --- /dev/null +++ b/t/headers/type.t @@ -0,0 +1,101 @@ +use strict; +use CGI; +use Test::More; + +{ + my $cgi = CGI->new; + my $got = $cgi->header( -type => 'text/plain' ); + my $expected = 'Content-Type: text/plain; charset=ISO-8859-1' + . $CGI::CRLF x 2; + is $got, $expected, 'type'; +} + +{ + my $cgi = CGI->new; + my $got = $cgi->header( -type => q{} ); + my $expected = $CGI::CRLF x 2; + is $got, $expected, 'type empty string'; +} + +{ + my $cgi = CGI->new; + my $got = $cgi->header( -type => 'text/plain; charset=utf-8' ); + my $expected = 'Content-Type: text/plain; charset=utf-8' + . $CGI::CRLF x 2; + is $got, $expected, 'type defines charset'; +} + +{ + my $cgi = CGI->new; + my $got = $cgi->header( + '-type' => 'text/plain', + '-charset' => 'utf-8', + ); + my $expected = 'Content-Type: text/plain; charset=utf-8' + . $CGI::CRLF x 2; + is $got, $expected, 'type and charset'; +} + +{ + my $cgi = CGI->new; + my $got = $cgi->header( + '-type' => q{}, + '-charset' => 'utf-8', + ); + my $expected = $CGI::CRLF x 2; + is $got, $expected, 'type and charset, type is empty string'; +} + +{ + my $cgi = CGI->new; + my $got = $cgi->header( + '-type' => 'text/plain; charset=utf-8', + '-charset' => q{}, + ); + my $expected = 'Content-Type: text/plain; charset=utf-8' + . $CGI::CRLF x 2; + is $got, $expected, 'type and charset, charset is empty string'; +} + +{ + my $cgi = CGI->new; + my $got = $cgi->header( + '-type' => 'text/plain; charset=utf-8', + '-charset' => 'EUC-JP', + ); + my $expected = 'Content-Type: text/plain; charset=utf-8' + . $CGI::CRLF x 2; + is $got, $expected, 'type and charset, type defines charset'; +} + +{ + my $cgi = CGI->new; + my $got = $cgi->header( -type => 'image/gif' ); + my $expected = 'Content-Type: image/gif; charset=ISO-8859-1' + . $CGI::CRLF x 2; + is $got, $expected, 'image type, no charset'; +} + +{ + my $cgi = CGI->new; + my $got = $cgi->header( + -type => 'image/gif', + -charset => '', + ); + my $expected = 'Content-Type: image/gif' + . $CGI::CRLF x 2; + is $got, $expected, 'image type, no charset'; +} + +{ + my $cgi = CGI->new; + my $got = $cgi->header( + -type => 'image/gif', + -charset => 'utf-8', + ); + my $expected = 'Content-Type: image/gif; charset=utf-8' + . $CGI::CRLF x 2; + is $got, $expected, 'image type, forced charset'; +} + +done_testing; diff --git a/t/hidden.t b/t/hidden.t new file mode 100644 index 0000000..e8291d7 --- /dev/null +++ b/t/hidden.t @@ -0,0 +1,38 @@ +#!perl -w + +use Test::More 'no_plan'; +use CGI; + +my $q = CGI->new; + +is( $q->hidden( 'hidden_name', 'foo' ), + qq(<input type="hidden" name="hidden_name" value="foo" />), + 'hidden() with single default value, positional'); + +is( $q->hidden( -name => 'hidden_name', -default =>'foo' ), + qq(<input type="hidden" name="hidden_name" value="foo" />), + 'hidden() with single default value, named'); + +is( $q->hidden( 'hidden_name', qw(foo bar baz fie) ), + qq(<input type="hidden" name="hidden_name" value="foo" /><input type="hidden" name="hidden_name" value="bar" /><input type="hidden" name="hidden_name" value="baz" /><input type="hidden" name="hidden_name" value="fie" />), + 'hidden() with default array, positional'); + +is( $q->hidden( -name=>'hidden_name', + -Values =>[qw/foo bar baz fie/], + -Title => "hidden_field"), + qq(<input type="hidden" name="hidden_name" value="foo" title="hidden_field" /><input type="hidden" name="hidden_name" value="bar" title="hidden_field" /><input type="hidden" name="hidden_name" value="baz" title="hidden_field" /><input type="hidden" name="hidden_name" value="fie" title="hidden_field" />), + 'hidden() default array, named as "Values"'); + +is( $q->hidden( -name=>'hidden_name', + -default =>[qw/foo bar baz fie/], + -Title => "hidden_field"), + qq(<input type="hidden" name="hidden_name" value="foo" title="hidden_field" /><input type="hidden" name="hidden_name" value="bar" title="hidden_field" /><input type="hidden" name="hidden_name" value="baz" title="hidden_field" /><input type="hidden" name="hidden_name" value="fie" title="hidden_field" />), + 'hidden() default array, named as "default"'); + +is( $q->hidden( -name=>'hidden_name', + '-value' =>[qw/foo bar baz fie/], + -Title => "hidden_field"), + qq(<input type="hidden" name="hidden_name" value="foo" title="hidden_field" /><input type="hidden" name="hidden_name" value="bar" title="hidden_field" /><input type="hidden" name="hidden_name" value="baz" title="hidden_field" /><input type="hidden" name="hidden_name" value="fie" title="hidden_field" />), + 'hidden() default array, named as "value"'); + + diff --git a/t/html.t b/t/html.t new file mode 100644 index 0000000..4d3904f --- /dev/null +++ b/t/html.t @@ -0,0 +1,220 @@ +#!/usr/local/bin/perl -w + +use Test::More tests => 40; + +END { ok $loaded; } +use CGI ( ':standard', '-no_debug', '*h3', 'start_table' ); +$loaded = 1; +$CGI::Util::SORT_ATTRIBUTES= 1; +ok 1; + +BEGIN { + $| = 1; + if ( $] > 5.006 ) { + + # no utf8 + require utf8; # we contain Latin-1 + utf8->unimport; + } +} + +######################### End of black magic. + +my $CRLF = "\015\012"; +if ( $^O eq 'VMS' ) { + $CRLF = "\n"; # via web server carriage is inserted automatically +} +if ( ord("\t") != 9 ) { # EBCDIC? + $CRLF = "\r\n"; +} + +# util +sub test { + local ($^W) = 0; + my ( undef, $true, $msg ) = @_; + ok $true => $msg; +} + +# all the automatic tags +is h1(), '<h1 />', "single tag"; + +is h1('fred'), '<h1>fred</h1>', "open/close tag"; + +is h1( 'fred', 'agnes', 'maura' ), '<h1>fred agnes maura</h1>', + "open/close tag multiple"; + +is h1( { -align => 'CENTER' }, 'fred' ), '<h1 align="CENTER">fred</h1>', + "open/close tag with attribute"; + +is h1( { -align => undef }, 'fred' ), '<h1 align>fred</h1>', + "open/close tag with orphan attribute"; + +is h1( { -align => 'CENTER' }, [ 'fred', 'agnes' ] ), + '<h1 align="CENTER">fred</h1> <h1 align="CENTER">agnes</h1>', + "distributive tag with attribute"; + +{ + local $" = '-'; + + is h1( 'fred', 'agnes', 'maura' ), '<h1>fred-agnes-maura</h1>', + "open/close tag \$\" interpolation"; + +} + +is header(), "Content-Type: text/html; charset=ISO-8859-1${CRLF}${CRLF}", + "header()"; + +is header( -type => 'image/gif', -charset => '' ), "Content-Type: image/gif${CRLF}${CRLF}", + "header()"; + +is header( -type => 'image/gif', -status => '500 Sucks' ), + "Status: 500 Sucks${CRLF}Content-Type: image/gif${CRLF}${CRLF}", "header()"; + +# return to normal +charset( 'ISO-8859-1' ); + +like header( -nph => 1 ), + qr!HTTP/1.0 200 OK${CRLF}Server: cmdline${CRLF}Date:.+${CRLF}Content-Type: text/html; charset=ISO-8859-1${CRLF}${CRLF}!, + "header()"; + +is start_html(), <<END, "start_html()"; +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US"> +<head> +<title>Untitled Document</title> +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> +</head> +<body> +END + +is start_html( + -Title => 'The world of foo' , + -Script => [ {-src=> 'foo.js', -charset=>'utf-8'} ], + ), <<END, "start_html()"; +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US"> +<head> +<title>The world of foo</title> +<script charset="utf-8" src="foo.js" type="text/javascript"></script> +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> +</head> +<body> +END + +for my $v (qw/ 2.0 3.2 4.0 4.01 /) { + local $CGI::XHTML = 1; + is + start_html( -dtd => "-//IETF//DTD HTML $v//FR", -lang => 'fr' ), + <<"END", 'start_html()'; +<!DOCTYPE html + PUBLIC "-//IETF//DTD HTML $v//FR"> +<html lang="fr"><head><title>Untitled Document</title> +</head> +<body> +END +} + +is + start_html( -dtd => "-//IETF//DTD HTML 9.99//FR", -lang => 'fr' ), + <<"END", 'start_html()'; +<!DOCTYPE html + PUBLIC "-//IETF//DTD HTML 9.99//FR"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="fr" xml:lang="fr"> +<head> +<title>Untitled Document</title> +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> +</head> +<body> +END + +my $cookie = + cookie( -name => 'fred', -value => [ 'chocolate', 'chip' ], -path => '/' ); + +is $cookie, 'fred=chocolate&chip; path=/', "cookie()"; + +my $h = header( -Cookie => $cookie ); + +like $h, + qr!^Set-Cookie: fred=chocolate&chip\; path=/${CRLF}Date:.*${CRLF}Content-Type: text/html; charset=ISO-8859-1${CRLF}${CRLF}!s, + "header(-cookie)"; + +$h = header( '-set-cookie' => $cookie ); +like $h, + qr!^Set-[Cc]ookie: fred=chocolate&chip\; path=/${CRLF}(Date:.*${CRLF})?Content-Type: text/html; charset=ISO-8859-1${CRLF}${CRLF}!s, + "header(-set-cookie)"; + +my $cookie2 = + cookie( -name => 'ginger', -value => 'snap' , -path => '/' ); +is $cookie2, 'ginger=snap; path=/', "cookie2()"; + +$h = header( -cookie => [ $cookie, $cookie2 ] ); +like $h, + qr!^Set-Cookie: fred=chocolate&chip\; path=/${CRLF}Set-Cookie: ginger=snap\; path=/${CRLF}(Date:.*${CRLF})?Content-Type: text/html; charset=ISO-8859-1${CRLF}${CRLF}!s, + "header(-cookie=>[cookies])"; + +$h = header( '-set-cookie' => [ $cookie, $cookie2 ] ); +like $h, + qr!^Set-Cookie: fred=chocolate&chip\; path=/${CRLF}Set-Cookie: ginger=snap\; path=/${CRLF}(Date:.*${CRLF})?Content-Type: text/html; charset=ISO-8859-1${CRLF}${CRLF}!s, + "header(-set-cookie=>[cookies])"; + +$h = redirect('http://elsewhere.org/'); +like $h, + qr!Status: 302 Found${CRLF}Location: http://elsewhere.org/!s, + "redirect"; + +$h = redirect(-url=>'http://elsewhere.org/', -cookie=>[$cookie,$cookie2]); +like $h, + qr!Status: 302 Found${CRLF}Set-[Cc]ookie: \Q$cookie\E${CRLF}Set-[Cc]ookie: \Q$cookie2\E${CRLF}(Date:.*${CRLF})?Location: http://elsewhere.org/!s, + "redirect with cookies"; + +$h = redirect(-url=>'http://elsewhere.org/', '-set-cookie'=>[$cookie,$cookie2]); +like $h, + qr!Status: 302 Found${CRLF}Set-[Cc]ookie: \Q$cookie\E${CRLF}Set-[Cc]ookie: \Q$cookie2\E${CRLF}(Date:.*${CRLF})?Location: http://elsewhere.org/!s, + "redirect with set-cookies"; + +is start_h3, '<h3>'; + +is end_h3, '</h3>'; + +is start_table( { -border => undef } ), '<table border>'; + +charset('utf-8'); + +my $old_encode = $CGI::ENCODE_ENTITIES; +$CGI::ENCODE_ENTITIES = '<'; + +isnt h1( escapeHTML("this is <not> \x8bright\x9b") ), + '<h1>this is <not> ‹right›</h1>'; + +undef( $CGI::ENCODE_ENTITIES ); + +is h1( escapeHTML("this is <not> \x8bright\x9b") ), + '<h1>this is <not> ‹right›</h1>'; + + +$CGI::ENCODE_ENTITIES = $old_encode; + +is i( p('hello there') ), '<i><p>hello there</p></i>'; + +my $q = CGI->new; +is $q->h1('hi'), '<h1>hi</h1>'; + +$q->autoEscape(1); + +is $q->p( { title => "hello worldè" }, 'hello á' ), + '<p title="hello world&egrave;">hello á</p>'; + +$q->autoEscape(0); + +is $q->p( { title => "hello worldè" }, 'hello á' ), + '<p title="hello worldè">hello á</p>'; + +is p( { title => "hello worldè" }, 'hello á' ), + '<p title="hello world&egrave;">hello á</p>'; + +is header( -type => 'image/gif', -charset => 'UTF-8' ), + "Content-Type: image/gif; charset=UTF-8${CRLF}${CRLF}", "header()"; diff --git a/t/html_functions.t b/t/html_functions.t new file mode 100644 index 0000000..e5fcbeb --- /dev/null +++ b/t/html_functions.t @@ -0,0 +1,53 @@ +#!perl + +use strict; +use warnings; + +use Test::More 'no_plan'; + +use CGI qw/ -compile :all /; + +# check html functions are imported into this namespace +# with the -compile pragma +is( a({ bar => "boz" }),"<a bar=\"boz\" />","-compile" ); + +my $q = CGI->new; + +foreach my $tag ( $q->_all_html_tags ) { + + my $expected_tag = lc( $tag ); + + is( + $q->$tag(), + "<$expected_tag />", + "$tag function (no args)" + ); + + is( + $q->$tag( 'some','contents' ), + "<$expected_tag>some contents</$expected_tag>", + "$tag function (content)" + ); + + is( + $q->$tag( { bar => 'boz', biz => 'baz' } ), + "<$expected_tag bar=\"boz\" biz=\"baz\" />", + "$tag function (attributes)" + ); + + is( + $q->$tag( { bar => 'boz' },'some','contents' ), + "<$expected_tag bar=\"boz\">some contents</$expected_tag>", + "$tag function (attributes and content)" + ); + + next if ($tag eq 'html'); + + my $start = "start_$tag"; + is( $q->$start( 'foo' ),"<$expected_tag>","$start function" ); + + my $end = "end_$tag"; + is( $q->$end( 'foo' ),"</$expected_tag>","$end function" ); +} + +ok( $q->compile,'compile' ); diff --git a/t/http.t b/t/http.t new file mode 100644 index 0000000..2ed3863 --- /dev/null +++ b/t/http.t @@ -0,0 +1,44 @@ +#!./perl -w + +# Fixes RT 12909 + +use lib qw(t/lib); + +use Test::More tests => 7; +use CGI; + +my $cgi = CGI->new(); + +{ + # http() without arguments should not cause warnings + local $SIG{__WARN__} = sub { die @_ }; + ok eval { $cgi->http(); 1 }, "http() without arguments doesn't warn"; + ok eval { $cgi->https(); 1 }, "https() without arguments doesn't warn"; +} + +{ + # Capitalization and the use of hyphens versus underscores are not significant. + local $ENV{'HTTP_HOST'} = 'foo'; + is $cgi->http('Host'), 'foo', 'http("Host") returns $ENV{HTTP_HOST}'; + is $cgi->http('http-host'), 'foo', 'http("http-host") returns $ENV{HTTP_HOST}'; +} + +{ + # Called with no arguments returns the list of HTTP environment variables + local $ENV{'HTTPS_FOO'} = 'bar'; + my @http = $cgi->http(); + is scalar( grep /^HTTPS/, @http), 0, "http() doesn't return HTTPS variables"; +} + +{ + # https() + # The same as http(), but operates on the HTTPS environment variables present when the SSL protocol is in + # effect. Can be used to determine whether SSL is turned on. + my @expect = grep /^HTTPS/, keys %ENV; + push @expect, 'HTTPS' if not exists $ENV{HTTPS}; + push @expect, 'HTTPS_KEYSIZE' if not exists $ENV{HTTPS_KEYSIZE}; + local $ENV{'HTTPS'} = 'ON'; + local $ENV{'HTTPS_KEYSIZE'} = 512; + is $cgi->https(), 'ON', 'scalar context to check SSL is on'; + ok eq_set( [$cgi->https()], \@expect), 'list context returns https keys'; +} diff --git a/t/init.t b/t/init.t new file mode 100644 index 0000000..532a277 --- /dev/null +++ b/t/init.t @@ -0,0 +1,13 @@ +#!/usr/bin perl -w + +use strict; +use Test::More tests => 1; + +use CGI; + + +$_ = "abcdefghijklmnopq"; +my $IN; +open ($IN, "t/init_test.txt"); +my $q = CGI->new($IN); +is($_, 'abcdefghijklmnopq', 'make sure not to clobber $_ on init'); diff --git a/t/init_test.txt b/t/init_test.txt new file mode 100644 index 0000000..3101583 --- /dev/null +++ b/t/init_test.txt @@ -0,0 +1,3 @@ +A=B +D=F +G=H diff --git a/t/multipart_init.t b/t/multipart_init.t new file mode 100644 index 0000000..20cd3f2 --- /dev/null +++ b/t/multipart_init.t @@ -0,0 +1,25 @@ +use Test::More 'no_plan'; + +use CGI; + +my $q = CGI->new; + +my $sv = $q->multipart_init; +like( $sv, qr|Content-Type: multipart/x-mixed-replace;boundary="------- =.*?; charset=ISO-8859-1|, 'multipart_init(), basic'); + +$sv = $q->multipart_init(-charset=>'utf-8'); +like( $sv, qr|Content-Type: multipart/x-mixed-replace;boundary="------- =.*?; charset=utf-8|, 'multipart_init(), -charset'); + +like( $sv, qr/$CGI::CRLF$/, 'multipart_init(), ends in CRLF' ); + +$sv = $q->multipart_init( 'this_is_the_boundary' ); +like( $sv, qr/boundary="this_is_the_boundary"/, 'multipart_init("simple_boundary")' ); +$sv = $q->multipart_init( -boundary => 'this_is_another_boundary' ); +like($sv, + qr/boundary="this_is_another_boundary"/, "multipart_init( -boundary => 'this_is_another_boundary')"); + +{ + my $sv = $q->multipart_init; + my $sv2 = $q->multipart_init; + isnt($sv,$sv2,"due to random boundaries, multiple calls produce different results"); +} diff --git a/t/multipart_start.t b/t/multipart_start.t new file mode 100644 index 0000000..42ade75 --- /dev/null +++ b/t/multipart_start.t @@ -0,0 +1,34 @@ +#!perl + +use strict; +use warnings; +use Test::More 'no_plan'; + +use CGI; + +my $q = CGI->new; +my $CRLF = $MultipartBuffer::CRLF; + +like( + $q->multipart_start, + qr!^Content-Type: text/html$CRLF$CRLF$!, + 'multipart_start with no args' +); + +like( + $q->multipart_start( -type => 'text/plain' ), + qr!^Content-Type: text/plain$CRLF$CRLF$!, + 'multipart_start with type' +); + +like( + $q->multipart_start( -charset => 'utf-8' ), + qr!^Content-Type: text/html; charset=utf-8$CRLF$CRLF$!, + 'multipart_start with charset' +); + +like( + $q->multipart_start( -type => 'text/plain', -charset => 'utf-8' ), + qr!^Content-Type: text/plain; charset=utf-8$CRLF$CRLF$!, + 'multipart_start with type and charset' +); diff --git a/t/no_tabindex.t b/t/no_tabindex.t new file mode 100644 index 0000000..66ea21c --- /dev/null +++ b/t/no_tabindex.t @@ -0,0 +1,122 @@ +#!/usr/local/bin/perl -w + +use Test::More tests => 18; + +BEGIN { use_ok('CGI'); }; +use CGI (':standard','-no_debug'); + +my $CRLF = "\015\012"; +if ($^O eq 'VMS') { + $CRLF = "\n"; # via web server carriage is inserted automatically +} +if (ord("\t") != 9) { # EBCDIC? + $CRLF = "\r\n"; +} + + +# Set up a CGI environment +$ENV{REQUEST_METHOD} = 'GET'; +$ENV{QUERY_STRING} = 'game=chess&game=checkers&weather=dull'; +$ENV{PATH_INFO} = '/somewhere/else'; +$ENV{PATH_TRANSLATED} = '/usr/local/somewhere/else'; +$ENV{SCRIPT_NAME} ='/cgi-bin/foo.cgi'; +$ENV{SERVER_PROTOCOL} = 'HTTP/1.0'; +$ENV{SERVER_PORT} = 8080; +$ENV{SERVER_NAME} = 'the.good.ship.lollypop.com'; + +ok( (not $CGI::TABINDEX), "Tab index turned off."); + +is(submit(), + qq(<input type="submit" name=".submit" />), + "submit()"); + +is(submit(-name => 'foo', + -value => 'bar'), + qq(<input type="submit" name="foo" value="bar" />), + "submit(-name,-value)"); + +is(submit({-name => 'foo', + -value => 'bar'}), + qq(<input type="submit" name="foo" value="bar" />), + "submit({-name,-value})"); + +is(textfield(-name => 'weather'), + qq(<input type="text" name="weather" value="dull" />), + "textfield({-name})"); + +is(textfield(-name => 'weather', + -value => 'nice'), + qq(<input type="text" name="weather" value="dull" />), + "textfield({-name,-value})"); + +is(textfield(-name => 'weather', + -value => 'nice', + -override => 1), + qq(<input type="text" name="weather" value="nice" />), + "textfield({-name,-value,-override})"); + +is(checkbox(-name => 'weather', + -value => 'nice'), + qq(<label><input type="checkbox" name="weather" value="nice" />weather</label>), + "checkbox()"); + +is(checkbox(-name => 'weather', + -value => 'nice', + -label => 'forecast'), + qq(<label><input type="checkbox" name="weather" value="nice" />forecast</label>), + "checkbox()"); + +is(checkbox(-name => 'weather', + -value => 'nice', + -label => 'forecast', + -checked => 1, + -override => 1), + qq(<label><input type="checkbox" name="weather" value="nice" checked="checked" />forecast</label>), + "checkbox()"); + +is(checkbox(-name => 'weather', + -value => 'dull', + -label => 'forecast'), + qq(<label><input type="checkbox" name="weather" value="dull" checked="checked" />forecast</label>), + "checkbox()"); + +is(radio_group(-name => 'game'), + qq(<label><input type="radio" name="game" value="chess" checked="checked" />chess</label> <label><input type="radio" name="game" value="checkers" />checkers</label>), + 'radio_group()'); + +is(radio_group(-name => 'game', + -labels => {'chess' => 'ping pong'}), + qq(<label><input type="radio" name="game" value="chess" checked="checked" />ping pong</label> <label><input type="radio" name="game" value="checkers" />checkers</label>), + 'radio_group()'); + +is(checkbox_group(-name => 'game', + -Values => [qw/checkers chess cribbage/]), + qq(<label><input type="checkbox" name="game" value="checkers" checked="checked" />checkers</label> <label><input type="checkbox" name="game" value="chess" checked="checked" />chess</label> <label><input type="checkbox" name="game" value="cribbage" />cribbage</label>), + 'checkbox_group()'); + +is(checkbox_group(-name => 'game', + '-values' => [qw/checkers chess cribbage/], + '-defaults' => ['cribbage'], + -override=>1), + qq(<label><input type="checkbox" name="game" value="checkers" />checkers</label> <label><input type="checkbox" name="game" value="chess" />chess</label> <label><input type="checkbox" name="game" value="cribbage" checked="checked" />cribbage</label>), + 'checkbox_group()'); + +is(popup_menu(-name => 'game', + '-values' => [qw/checkers chess cribbage/], + -default => 'cribbage', + -override => 1), + '<select name="game" > +<option value="checkers">checkers</option> +<option value="chess">chess</option> +<option selected="selected" value="cribbage">cribbage</option> +</select>', + 'popup_menu()'); + + +is(textarea(-name=>'foo', + -default=>'starting value', + -rows=>10, + -columns=>50), + '<textarea name="foo" rows="10" cols="50">starting value</textarea>', + 'textarea()'); + diff --git a/t/param_fetch.t b/t/param_fetch.t new file mode 100644 index 0000000..a3756cd --- /dev/null +++ b/t/param_fetch.t @@ -0,0 +1,26 @@ +#!perl + +# Tests for the param_fetch() method. + +use Test::More 'no_plan'; +use CGI; + +{ + my $q = CGI->new('b=baz;a=foo;a=bar'); + + is $q->param_fetch('a')->[0] => 'foo', 'first "a" is "foo"'; + is $q->param_fetch( -name => 'a' )->[0] => 'foo', + 'first "a" is "foo", with -name'; + is $q->param_fetch('a')->[1] => 'bar', 'second "a" is "bar"'; + is_deeply $q->param_fetch('a') => [qw/ foo bar /], 'a is array ref'; + is_deeply $q->param_fetch( -name => 'a' ) => [qw/ foo bar /], + 'a is array ref, w/ name'; + + is $q->param_fetch('b')->[0] => 'baz', '"b" is "baz"'; + is_deeply $q->param_fetch('b') => [qw/ baz /], 'b is array ref too'; + + is_deeply $q->param_fetch, [], "param_fetch without parameters"; + + is_deeply $q->param_fetch( 'a', 'b' ), [qw/ foo bar /], + "param_fetch only take first argument"; +} diff --git a/t/param_list_context.t b/t/param_list_context.t new file mode 100644 index 0000000..04f2dd6 --- /dev/null +++ b/t/param_list_context.t @@ -0,0 +1,57 @@ +#!/usr/local/bin/perl + +use strict; +use warnings; + +use Test::More tests => 7; +use Test::Deep; +use Test::Warn; + +use CGI (); + +# Set up a CGI environment +$ENV{REQUEST_METHOD} = 'GET'; +$ENV{QUERY_STRING} = 'game=chess&game=checkers&weather=dull'; + +my $q = CGI->new; +ok $q,"CGI::new()"; + +my @params; + +warnings_are + { @params = $q->param } + [], + "calling ->param with no args in list does not warn" +; + +warning_like + { @params = $q->param('game') } + qr/CGI::param called in list context from .+param_list_context\.t line 28, this can lead to vulnerabilities/, + "calling ->param with args in list context warns" +; + +cmp_deeply( + [ sort @params ], + [ qw/ checkers chess / ], + 'CGI::param()', +); + +warnings_are + { @params = $q->multi_param('game') } + [], + "no warnings calling multi_param" +; + +cmp_deeply( + [ sort @params ], + [ qw/ checkers chess / ], + 'CGI::multi_param' +); + +$CGI::LIST_CONTEXT_WARN = 0; + +warnings_are + { @params = $q->param } + [], + "no warnings when LIST_CONTEXT_WARN set to 0" +; diff --git a/t/popup_menu.t b/t/popup_menu.t new file mode 100644 index 0000000..bffba64 --- /dev/null +++ b/t/popup_menu.t @@ -0,0 +1,33 @@ +#!perl +# Tests for popup_menu(); +use Test::More 'no_plan'; +use CGI; + +my $q = CGI->new; + +is ( $q->popup_menu(-name=>"foo", - values=>[0,1], -default=>0), +'<select name="foo" > +<option selected="selected" value="0">0</option> +<option value="1">1</option> +</select>' +, 'popup_menu(): basic test, including 0 as a default value'); + +is( + CGI::popup_menu(-values=>[CGI::optgroup(-values=>["b+"])],-default=>"b+"), + '<select name="" > +<optgroup label=""> +<option selected="selected" value="b+">b+</option> +</optgroup> +</select>' + , "<optgroup> selections work when the default values contain regex characters (RT#49606)"); + +unlike( + $q->popup_menu( + -name =>"foo", + -values =>[0,1], + -multiple => 'true', + -MULTIPLE => 'true', + ), + qr/multiple/, + 'popup_menu ignores -multiple option', +); diff --git a/t/postdata.t b/t/postdata.t new file mode 100644 index 0000000..bd6263d --- /dev/null +++ b/t/postdata.t @@ -0,0 +1,121 @@ +#!/usr/local/bin/perl -w + +################################################################# +# Emanuele Zeppieri, Mark Stosberg # +# Shamelessly stolen from Data::FormValidator and CGI::Upload # +# Anonymous Monk says me too # +################################################################# + +use strict; +use Test::More tests => 28; + +use CGI; +$CGI::DEBUG=1; + +#----------------------------------------------------------------------------- +# %ENV setup. +#----------------------------------------------------------------------------- + +my %myenv; + +BEGIN { + %myenv = ( + 'SCRIPT_NAME' => '/test.cgi', + 'SERVER_NAME' => 'perl.org', + 'HTTP_CONNECTION' => 'TE, close', + 'REQUEST_METHOD' => 'POST', + 'SCRIPT_URI' => 'http://www.perl.org/test.cgi', + 'CONTENT_LENGTH' => 35, + 'SCRIPT_FILENAME' => '/home/usr/test.cgi', + 'SERVER_SOFTWARE' => 'Apache/1.3.27 (Unix) ', + 'HTTP_TE' => 'deflate,gzip;q=0.3', + 'QUERY_STRING' => '', + 'REMOTE_PORT' => '1855', + 'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; Konqueror/2.1.1; X11)', + 'SERVER_PORT' => '80', + 'REMOTE_ADDR' => '127.0.0.1', + 'CONTENT_TYPE' => 'application/octet-stream', ##dd + 'X_File_Name' => 'tiny.gif', ##dd + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'PATH' => '/usr/local/bin:/usr/bin:/bin', + 'REQUEST_URI' => '/test.cgi', + 'GATEWAY_INTERFACE' => 'CGI/1.1', + 'SCRIPT_URL' => '/test.cgi', + 'SERVER_ADDR' => '127.0.0.1', + 'DOCUMENT_ROOT' => '/home/develop', + 'HTTP_HOST' => 'www.perl.org' + ); + + for my $key (keys %myenv) { + $ENV{$key} = $myenv{$key}; + } +} + +END { + for my $key (keys %myenv) { + delete $ENV{$key}; + } +} + + + +for my $pdata ( qw' POST PUT ' ){ + local $ENV{REQUEST_METHOD} = $pdata; + my $pdata = $pdata.'DATA'; + CGI::initialize_globals(); #### IMPORTANT + ok( ! $CGI::PUTDATA_UPLOAD , "-\L$pdata\E_upload default is off"); + local *STDIN; + open STDIN, "<", \"GIF89a\1\0\1\0\x90\0\0\xFF\0\0\0\0\0,\0\0\0\0\1\0\1\0\0\2\2\4\1\0;" + or die "In-memory filehandle failed\n"; + binmode STDIN; + my $q = CGI->new; + ok( scalar $q->param( $pdata ), "we have $pdata param" ); + ok( ! ref $q->param( $pdata ), 'and it is not filehandle'); + ok( "GIF89a\1\0\1\0\x90\0\0\xFF\0\0\0\0\0,\0\0\0\0\1\0\1\0\0\2\2\4\1\0;" eq $q->param( $pdata ), "and the value isn't corrupted" ); +} + +for my $pdata ( qw' POST PUT ' ){ + local $ENV{REQUEST_METHOD} = $pdata; + my $pdata = $pdata.'DATA'; + local *STDIN; + open STDIN, "<", \"GIF89a\1\0\1\0\x90\0\0\xFF\0\0\0\0\0,\0\0\0\0\1\0\1\0\0\2\2\4\1\0;" + or die "In-memory filehandle failed\n"; + binmode STDIN; + + CGI::initialize_globals(); #### IMPORTANT + local $CGI::PUTDATA_UPLOAD; + CGI->import( lc "-$pdata\_upload" ); + ok( !!$CGI::PUTDATA_UPLOAD, "-\L$pdata\E_upload default is on"); + + my $q = CGI->new; + foreach my $class ( 'File::Temp','CGI::File::Temp','Fh' ) { + isa_ok( $q->param( $pdata ),$class,"$pdata param" ); + } + + my $filename = $q->param($pdata); + my $tmpfilename = $q->tmpFileName( $filename ); + ok( $tmpfilename , "and tmpFileName returns the filename" ); +} + + +for my $pdata ( qw' POST PUT ' ){ + local $ENV{REQUEST_METHOD} = $pdata; + my $pdata = $pdata.'DATA'; + local *STDIN; + open STDIN, "<", \"GIF89a\1\0\1\0\x90\0\0\xFF\0\0\0\0\0,\0\0\0\0\1\0\1\0\0\2\2\4\1\0;" + or die "In-memory filehandle failed\n"; + binmode STDIN; + + CGI::initialize_globals(); #### IMPORTANT + + my $yourang = 0; + my $callback = sub { + $yourang++; + }; + my $q = CGI->new( $callback ); + ok( ref $q, "got query"); + foreach my $class ( 'File::Temp','CGI::File::Temp','Fh' ) { + isa_ok( $q->param( $pdata ),$class,"$pdata param" ); + } + ok( $yourang, "and callback invoked"); +} diff --git a/t/pretty.t b/t/pretty.t new file mode 100644 index 0000000..b57baed --- /dev/null +++ b/t/pretty.t @@ -0,0 +1,13 @@ +#!/bin/perl -w + +use strict; +use Test::More tests => 6; +use CGI::Pretty ':all'; + +is(h1(), '<h1 />',"single tag (pretty turned off)"); +is(h1('fred'), '<h1>fred</h1>',"open/close tag (pretty turned off)"); +is(h1('fred','agnes','maura'), '<h1>fred agnes maura</h1>',"open/close tag multiple (pretty turned off)"); +is(h1({-align=>'CENTER'},'fred'), '<h1 align="CENTER">fred</h1>',"open/close tag with attribute (pretty turned off)"); +is(h1({-align=>undef},'fred'), '<h1 align>fred</h1>',"open/close tag with orphan attribute (pretty turned off)"); +is(h1({-align=>'CENTER'},['fred','agnes']), '<h1 align="CENTER">fred</h1> <h1 align="CENTER">agnes</h1>', + "distributive tag with attribute (pretty turned off)"); diff --git a/t/push.t b/t/push.t new file mode 100644 index 0000000..0274aa9 --- /dev/null +++ b/t/push.t @@ -0,0 +1,68 @@ +#!./perl -wT + +use Test::More tests => 12; + +use_ok( 'CGI::Push' ); + +ok( my $q = CGI::Push->new(), 'create a new CGI::Push object' ); + +# test the simple_counter() method +like( join('', $q->simple_counter(10)) , '/updated.+?10.+?times./', 'counter' ); + +ok( CGI::Push::do_sleep(0.01),'do_sleep' ); + +# test push_delay() +ok( ! defined $q->push_delay(), 'no initial delay' ); +is( $q->push_delay(.5), .5, 'set a delay' ); + +my $out = tie *STDOUT, 'TieOut'; + +# next_page() to be called twice, last_page() once, no delay +my %vars = ( + -next_page => sub { return if $_[1] > 2; 'next page' }, + -last_page => sub { 'last page' }, + -delay => 0, +); + +$q->do_push(%vars); + +# this seems to appear on every page +like( $$out, '/WARNING: YOUR BROWSER/', 'unsupported browser warning' ); + +# these should appear correctly +is( ($$out =~ s/next page//g), 2, 'next_page callback called appropriately' ); +is( ($$out =~ s/last page//g), 1, 'last_page callback called appropriately' ); + +# send a fake content type (header capitalization varies in CGI, CGI::Push) +$$out = ''; +$q->do_push(%vars, -type => 'fake' ); +like( $$out, '/Content-[Tt]ype: fake/', 'set custom Content-type' ); + +# use our own counter, as $COUNTER in CGI::Push is now off +my $i; +$$out = ''; + +# no delay, custom headers from callback, only call callback once +$q->do_push( + -delay => 0, + -type => 'dynamic', + -next_page => sub { + return if $i++; + return $_[0]->header('text/plain'), 'arduk'; + }, +); + +# header capitalization again, our word should appear only once +like( $$out, '/ype: text\/plain/', 'set custom Content-type in next_page()' ); +is( $$out =~ s/arduk//g, 1, 'found text from next_page()' ); + +package TieOut; + +sub TIEHANDLE { + bless( \(my $text), $_[0] ); +} + +sub PRINT { + my $self = shift; + $$self .= join( $/, @_ ); +} diff --git a/t/query_string.t b/t/query_string.t new file mode 100644 index 0000000..a7efbe9 --- /dev/null +++ b/t/query_string.t @@ -0,0 +1,15 @@ +#!perl + +# Tests for the query_string() method. + +use Test::More 'no_plan'; +use CGI; + +{ + my $q1 = CGI->new('b=2;a=1;a=1'); + my $q2 = CGI->new('b=2&a=1&a=1'); + + is($q1->query_string + ,$q2->query_string + , "query string format is returned with the same delimiter regardless of input."); +} diff --git a/t/redirect_query_string.t b/t/redirect_query_string.t new file mode 100644 index 0000000..28cc521 --- /dev/null +++ b/t/redirect_query_string.t @@ -0,0 +1,72 @@ +#!perl + +use strict; +use warnings; + +use Test::More 'no_plan'; +use CGI; + +# monkey patching to make testing easier +no warnings 'once'; +no warnings 'redefine'; +*CGI::read_multipart_related = sub {}; +*CGI::save_request = sub {}; + +my $q_string = 'foo=bar'; + +$ENV{REQUEST_METHOD} = 'POST'; +$ENV{CONTENT_TYPE} = 'multipart/related;boundary="------- =A; start=X'; + +{ + $ENV{QUERY_STRING} = $q_string; + my $q = CGI->new; + is( $q->query_string,$q_string,'query_string' ); +} + +{ + $ENV{REDIRECT_QUERY_STRING} + = delete( $ENV{QUERY_STRING} ); + + my $q = CGI->new; + is( $q->query_string,$q_string,'query_string (redirect)' ); +} + +{ + $ENV{REDIRECT_REDIRECT_QUERY_STRING} + = delete( $ENV{REDIRECT_QUERY_STRING} ); + + my $q = CGI->new; + is( $q->query_string,$q_string,'query_string (redirect x 2)' ); +} + +{ + $ENV{REDIRECT_REDIRECT_REDIRECT_QUERY_STRING} + = delete( $ENV{REDIRECT_REDIRECT_QUERY_STRING} ); + + my $q = CGI->new; + is( $q->query_string,$q_string,'query_string (redirect x 3)' ); +} + +{ + $ENV{REDIRECT_REDIRECT_REDIRECT_REDIRECT_QUERY_STRING} + = delete( $ENV{REDIRECT_REDIRECT_REDIRECT_QUERY_STRING} ); + + my $q = CGI->new; + is( $q->query_string,$q_string,'query_string (redirect x 4)' ); +} + +{ + $ENV{REDIRECT_REDIRECT_REDIRECT_REDIRECT_REDIRECT_QUERY_STRING} + = delete( $ENV{REDIRECT_REDIRECT_REDIRECT_REDIRECT_QUERY_STRING} ); + + my $q = CGI->new; + is( $q->query_string,$q_string,'query_string (redirect x 5)' ); +} + +{ + $ENV{REDIRECT_REDIRECT_REDIRECT_REDIRECT_REDIRECT_REDIRECT_QUERY_STRING} + = delete( $ENV{REDIRECT_REDIRECT_REDIRECT_REDIRECT_REDIRECT_QUERY_STRING} ); + + my $q = CGI->new; + is( $q->query_string,'','no more than 5 redirects supported' ); +} diff --git a/t/request.t b/t/request.t new file mode 100644 index 0000000..2c5974d --- /dev/null +++ b/t/request.t @@ -0,0 +1,130 @@ +#!/usr/local/bin/perl + +use strict; +use warnings; + +use Test::More tests => 45; +use Test::Deep; +use Test::NoWarnings; + +use CGI (); +use Config; + +my $loaded = 1; + +$| = 1; + +$CGI::LIST_CONTEXT_WARN = 0; + +######################### End of black magic. + +# Set up a CGI environment +$ENV{REQUEST_METHOD} = 'GET'; +$ENV{QUERY_STRING} = 'game=chess&game=checkers&weather=dull'; +$ENV{PATH_INFO} = '/somewhere/else'; +$ENV{PATH_TRANSLATED} = '/usr/local/somewhere/else'; +$ENV{SCRIPT_NAME} = '/cgi-bin/foo.cgi'; +$ENV{SERVER_PROTOCOL} = 'HTTP/1.0'; +$ENV{SERVER_PORT} = 8080; +$ENV{SERVER_NAME} = 'the.good.ship.lollypop.com'; +$ENV{REQUEST_URI} = "$ENV{SCRIPT_NAME}$ENV{PATH_INFO}?$ENV{QUERY_STRING}"; +$ENV{HTTP_LOVE} = 'true'; + +my $q = CGI->new; +ok $q,"CGI::new()"; +is $q->request_method => 'GET',"CGI::request_method()"; +is $q->query_string => 'game=chess;game=checkers;weather=dull',"CGI::query_string()"; +is $q->param(), 2,"CGI::param()"; +is join(' ',sort $q->param()), 'game weather',"CGI::param()"; +is $q->param('game'), 'chess',"CGI::param()"; +is $q->param('weather'), 'dull',"CGI::param()"; +is join(' ',$q->param('game')), 'chess checkers',"CGI::param()"; +ok $q->param(-name=>'foo',-value=>'bar'),'CGI::param() put'; +is $q->param(-name=>'foo'), 'bar','CGI::param() get'; +is $q->query_string, 'game=chess;game=checkers;weather=dull;foo=bar',"CGI::query_string() redux"; +is $q->http('love'), 'true',"CGI::http()"; +is $q->script_name, '/cgi-bin/foo.cgi',"CGI::script_name()"; +is $q->url, 'http://the.good.ship.lollypop.com:8080/cgi-bin/foo.cgi',"CGI::url()"; +is $q->self_url, + 'http://the.good.ship.lollypop.com:8080/cgi-bin/foo.cgi/somewhere/else?game=chess;game=checkers;weather=dull;foo=bar', + "CGI::url()"; +is $q->url(-absolute=>1), '/cgi-bin/foo.cgi','CGI::url(-absolute=>1)'; +is $q->url(-relative=>1), 'foo.cgi','CGI::url(-relative=>1)'; +is $q->url(-relative=>1,-path=>1), 'foo.cgi/somewhere/else','CGI::url(-relative=>1,-path=>1)'; +is $q->url(-relative=>1,-path=>1,-query=>1), + 'foo.cgi/somewhere/else?game=chess;game=checkers;weather=dull;foo=bar', + 'CGI::url(-relative=>1,-path=>1,-query=>1)'; +$q->delete('foo'); +ok !$q->param('foo'),'CGI::delete()'; + +$q->_reset_globals; +$ENV{QUERY_STRING}='mary+had+a+little+lamb'; +ok $q=CGI->new,"CGI::new() redux"; +is join(' ',$q->keywords), 'mary had a little lamb','CGI::keywords'; +is join(' ',$q->param('keywords')), 'mary had a little lamb','CGI::keywords'; +ok $q=CGI->new('foo=bar&foo=baz'),"CGI::new() redux"; +is $q->param('foo'), 'bar','CGI::param() redux'; +ok $q=CGI->new({'foo'=>'bar','bar'=>'froz'}),"CGI::new() redux 2"; +is $q->param('bar'), 'froz',"CGI::param() redux 2"; + +# test tied interface +my $p = $q->Vars; +is $p->{bar}, 'froz',"tied interface fetch"; +$p->{bar} = join("\0",qw(foo bar baz)); +is join(' ',$q->param('bar')), 'foo bar baz','tied interface store'; +ok exists $p->{bar}; +is delete $p->{bar}, "foo\0bar\0baz",'tied interface delete'; + +# test posting +$q->_reset_globals; +{ + my $test_string = 'game=soccer&game=baseball&weather=nice'; + local $ENV{REQUEST_METHOD}='POST'; + local $ENV{CONTENT_LENGTH}=length($test_string); + local $ENV{QUERY_STRING}='big_balls=basketball&small_balls=golf'; + + local *STDIN; + open STDIN, '<', \$test_string; + + ok $q=CGI->new,"CGI::new() from POST"; + is $q->param('weather'), 'nice',"CGI::param() from POST"; + is $q->url_param('big_balls'), 'basketball',"CGI::url_param()"; +} + +# test url_param +{ + local $ENV{QUERY_STRING} = 'game=chess&game=checkers&weather=dull'; + + CGI::_reset_globals; + my $q = CGI->new; + # params present, param and url_param should return true + ok $q->param, 'param() is true if parameters'; + ok $q->url_param, 'url_param() is true if parameters'; + + $ENV{QUERY_STRING} = ''; + + CGI::_reset_globals; + $q = CGI->new; + ok !$q->param, 'param() is false if no parameters'; + ok !$q->url_param, 'url_param() is false if no parameters'; + + $ENV{QUERY_STRING} = 'tiger dragon'; + CGI::_reset_globals; + $q = CGI->new; + + is_deeply [$q->$_] => [ 'keywords' ], "$_ with QS='$ENV{QUERY_STRING}'" + for qw/ param url_param /; + + is_deeply [ sort $q->$_( 'keywords' ) ], [ qw/ dragon tiger / ], + "$_ keywords" for qw/ param url_param /; + + { + $^W++; + + CGI::_reset_globals; + $q = CGI->new; + $ENV{QUERY_STRING} = 'p1=1&&&;;&;&&;;p2;p3;p4=4&=p5'; + ok $q->url_param, 'url_param() is true if parameters'; + cmp_deeply( [ $q->url_param ],bag( qw/p1 p2 p3 p4/,'' ),'url_param' ); + } +} diff --git a/t/rt-31107.t b/t/rt-31107.t new file mode 100644 index 0000000..e09c24e --- /dev/null +++ b/t/rt-31107.t @@ -0,0 +1,43 @@ +#!/usr/local/bin/perl -w + +use strict; + +use Test::More 'no_plan'; + +use CGI; + +$ENV{REQUEST_METHOD} = 'POST'; +$ENV{CONTENT_TYPE} = 'multipart/related;boundary="----=_Part_0.7772611529786723.1196412625897" type="text/xml"; start="cid:mm7-submit"'; + +my $q; + +{ + local *STDIN; + open STDIN, '<t/rt_31107.txt' + or die 'missing test file t/rt_31107.txt'; + binmode STDIN; + $q = CGI->new; +} + +foreach my $class ( 'File::Temp','CGI::File::Temp','Fh' ) { + isa_ok( $q->param( 'capabilities.zip' ),$class,'capabilities.zip' ); + isa_ok( $q->param( 'mm7-submit' ),$class,'mm7-submit' ); +} + +my $fh = $q->param( 'mm7-submit' ); + +my @content = $fh->getlines; +like( + $content[9], + qr!<CapRequestId>4401196412625869430</CapRequestId>!, + 'multipart data read' +); + +# test back compatibility handle method +seek( $fh,0,0 ); +@content = $fh->handle->getlines; +like( + $content[9], + qr!<CapRequestId>4401196412625869430</CapRequestId>!, + 'multipart data read' +); diff --git a/t/rt-52469.t b/t/rt-52469.t new file mode 100644 index 0000000..740012d --- /dev/null +++ b/t/rt-52469.t @@ -0,0 +1,19 @@ +use strict; +use warnings; + +use Test::More tests => 1; # last test to print + +use CGI; + +$ENV{REQUEST_METHOD} = 'PUT'; + +eval { + local $SIG{ALRM} = sub { die "timeout!" }; + alarm 10; + my $cgi = CGI->new; + alarm 0; + pass( 'new() returned' ); +}; +$@ && do { + fail( "CGI->new did not return" ); +}; diff --git a/t/rt-57524.t b/t/rt-57524.t new file mode 100644 index 0000000..784d23f --- /dev/null +++ b/t/rt-57524.t @@ -0,0 +1,19 @@ +use strict; +use warnings; + +use Test::More tests => 6; + +use CGI; + +foreach my $fh ( \*STDOUT,\*STDIN,\*STDERR ) { + binmode( STDOUT,':utf8' ); + my %layers = map { $_ => 1 } PerlIO::get_layers( \*STDOUT ); + ok( $layers{utf8},'set utf8 on STDOUT' ); +} + +CGI::_set_binmode(); + +foreach my $fh ( \*STDOUT,\*STDIN,\*STDERR ) { + my %layers = map { $_ => 1 } PerlIO::get_layers( \*STDOUT ); + ok( $layers{utf8},'layers were not lost in call to _set_binmode' ); +} diff --git a/t/rt-75628.t b/t/rt-75628.t new file mode 100644 index 0000000..c0611d6 --- /dev/null +++ b/t/rt-75628.t @@ -0,0 +1,27 @@ +#!/usr/local/bin/perl -w + +use strict; + +use Test::More 'no_plan'; + +use CGI; + +$ENV{REQUEST_METHOD} = 'POST'; +$ENV{CONTENT_TYPE} = 'application/xml'; +$ENV{CONTENT_LENGTH} = 792; + +my $q; + +{ + local *STDIN; + open STDIN, '<t/rt_75628.txt' + or die 'missing test file t/rt_75628.txt'; + binmode STDIN; + $q = CGI->new; +} + +like( + $q->param( 'POSTDATA' ), + qr!<MM7Version>5.3.0</MM7Version>!, + 'POSTDATA access to XForms:Model' +); diff --git a/t/rt-84767.t b/t/rt-84767.t new file mode 100644 index 0000000..e1ed361 --- /dev/null +++ b/t/rt-84767.t @@ -0,0 +1,25 @@ +#!perl + +use strict; +use warnings; + +use Test::More; +use FindBin qw/$Bin $Script/; + +plan tests => 1; + +use CGI::Carp; + +chdir( $Bin ); + +open( my $fh,"<","$Script" ) + || die "Can't open $Script for read: $!"; + +while ( <$fh> ) { + eval { die("error") if /error/; }; + $@ && do { + like( $@,qr!at \Q$0\E line 19!,'die with input line number' ); + last; + } +} +close( $fh ); diff --git a/t/rt_31107.txt b/t/rt_31107.txt new file mode 100644 index 0000000..d99f15f --- /dev/null +++ b/t/rt_31107.txt @@ -0,0 +1,31 @@ +------=_Part_0.7772611529786723.1196412625897
+Content-Type: text/xml
+Content-Transfer-Encoding: 7bit
+Content-ID: <mm7-submit>
+
+<?xml version="1.0" encoding="UTF-8" ?> +<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"> + <env:Header> + <mm7:TransactionID env:mustUnderstand="1" xmlns:mm7="http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-5-MM7-1-0">4401196412625869430</mm7:TransactionID> + </env:Header> + <env:Body> + <mm7:CapabilityASReplyReq xmlns:mm7="http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-5-MM7-1-0"> + <MM7Version>5.3.0</MM7Version> + <SenderAddress>XXXXX</SenderAddress> + <CapRequestId>4401196412625869430</CapRequestId> + <TimeStamp>2007-11-30 09:50:25</TimeStamp> + <StatusCode>1000</StatusCode> + <StatusText>Request Received</StatusText> + <Content href="cid:generic_content_id"/> + </mm7:CapabilityASReplyReq> + </env:Body> +</env:Envelope> +
+------=_Part_0.7772611529786723.1196412625897
+Content-Type: application/x-zip; name=capabilities.zip
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename=capabilities.zip
+Content-ID: <capabilities.zip>
+
+UEsDBBQACAAIA
+------=_Part_0.7772611529786723.1196412625897--
diff --git a/t/rt_75628.txt b/t/rt_75628.txt new file mode 100644 index 0000000..3634e52 --- /dev/null +++ b/t/rt_75628.txt @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"> + <env:Header> + <mm7:TransactionID env:mustUnderstand="1" xmlns:mm7="http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-5-MM7-1-0">4401196412625869430</mm7:TransactionID> + </env:Header> + <env:Body> + <mm7:CapabilityASReplyReq xmlns:mm7="http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-5-MM7-1-0"> + <MM7Version>5.3.0</MM7Version> + <SenderAddress>XXXXX</SenderAddress> + <CapRequestId>4401196412625869430</CapRequestId> + <TimeStamp>2007-11-30 09:50:25</TimeStamp> + <StatusCode>1000</StatusCode> + <StatusText>Request Received</StatusText> + <Content href="cid:generic_content_id"/> + </mm7:CapabilityASReplyReq> + </env:Body> +</env:Envelope> diff --git a/t/save_read_roundtrip.t b/t/save_read_roundtrip.t new file mode 100644 index 0000000..a329b8e --- /dev/null +++ b/t/save_read_roundtrip.t @@ -0,0 +1,26 @@ + +use strict; +use warnings; + +# Reference: RT#13158: Needs test: empty name/value, when saved, prevents proper restore from filehandle. +# https://rt.cpan.org/Ticket/Display.html?id=13158 + +use Test::More tests => 3; + +use IO::File; +use CGI; + +$CGI::LIST_CONTEXT_WARN = 0; + +my $cgi = CGI->new('a=1;=;b=2;=3'); +ok eq_set (['a', '', 'b'], [$cgi->param]); + +# not File::Temp, since that wasn't in core at 5.6.0 +my $tmp = IO::File->new_tmpfile; +$cgi->save($tmp); +$tmp->seek(0,0); + +$cgi = CGI->new($tmp); +ok eq_set (['a', '', 'b'], [$cgi->param]); +is $cgi->param(''), 3; # '=' is lost, '=3' is retained + diff --git a/t/sorted.t b/t/sorted.t new file mode 100644 index 0000000..805a07b --- /dev/null +++ b/t/sorted.t @@ -0,0 +1,30 @@ +#!/bin/perl -w + +use strict; +use Test::More tests => 5; +use CGI qw /a start_html/; + +# Test that constructs fed from hashes generate unchanging HTML output + +# HTML Attributes within tags +is(a({-href=>'frog',-alt => 'Frog'},'frog'),'<a alt="Frog" href="frog">frog</a>',"sorted attributes 1"); +is(a({-href=>'frog',-alt => 'Frog', -frog => 'green'},'frog'),'<a alt="Frog" frog="green" href="frog">frog</a>',"sorted attributes 2"); +is(a({-href=>'frog',-alt => 'Frog', -frog => 'green', -type => 'water'},'frog'),'<a alt="Frog" frog="green" href="frog" type="water">frog</a>',"sorted attributes 3"); + +# List of meta attributes in the HTML header +my %meta = ( + 'frog1' => 'frog1', + 'frog2' => 'frog2', + 'frog3' => 'frog3', + 'frog4' => 'frog4', + 'frog5' => 'frog5', +); + +is(join("",grep (/frog\d/,split("\n",start_html( -meta => \%meta )))), +'<meta name="frog1" content="frog1" /><meta name="frog2" content="frog2" /><meta name="frog3" content="frog3" /><meta name="frog4" content="frog4" /><meta name="frog5" content="frog5" />', +"meta tags are sorted alphabetically by name 1"); + +$meta{'frog6'} = 'frog6'; +is(join("",grep (/frog\d/,split("\n",start_html( -meta => \%meta )))), +'<meta name="frog1" content="frog1" /><meta name="frog2" content="frog2" /><meta name="frog3" content="frog3" /><meta name="frog4" content="frog4" /><meta name="frog5" content="frog5" /><meta name="frog6" content="frog6" />', +"meta tags are sorted alphabetically by name 2"); diff --git a/t/start_end_asterisk.t b/t/start_end_asterisk.t new file mode 100644 index 0000000..0d67c9d --- /dev/null +++ b/t/start_end_asterisk.t @@ -0,0 +1,72 @@ +#!/usr/local/bin/perl -w + +use lib qw(t/lib); +use strict; + +# Due to a bug in older versions of MakeMaker & Test::Harness, we must +# ensure the blib's are in @INC, else we might use the core CGI.pm +use lib qw(blib/lib blib/arch); +use Test::More tests => 45; + +use CGI qw(:standard *h1 *h2 *h3 *h4 *h5 *h6 *table *ul *li *ol *td *b *i *u *div); + +is(start_h1(), "<h1>", "start_h1"); # TEST +is(start_h1({class => 'hello'}), "<h1 class=\"hello\">", "start_h1 with param"); # TEST +is(end_h1(), "</h1>", "end_h1"); # TEST + +is(start_h2(), "<h2>", "start_h2"); # TEST +is(start_h2({class => 'hello'}), "<h2 class=\"hello\">", "start_h2 with param"); # TEST +is(end_h2(), "</h2>", "end_h2"); # TEST + +is(start_h3(), "<h3>", "start_h3"); # TEST +is(start_h3({class => 'hello'}), "<h3 class=\"hello\">", "start_h3 with param"); # TEST +is(end_h3(), "</h3>", "end_h3"); # TEST + +is(start_h4(), "<h4>", "start_h4"); # TEST +is(start_h4({class => 'hello'}), "<h4 class=\"hello\">", "start_h4 with param"); # TEST +is(end_h4(), "</h4>", "end_h4"); # TEST + +is(start_h5(), "<h5>", "start_h5"); # TEST +is(start_h5({class => 'hello'}), "<h5 class=\"hello\">", "start_h5 with param"); # TEST +is(end_h5(), "</h5>", "end_h5"); # TEST + +is(start_h6(), "<h6>", "start_h6"); # TEST +is(start_h6({class => 'hello'}), "<h6 class=\"hello\">", "start_h6 with param"); # TEST +is(end_h6(), "</h6>", "end_h6"); # TEST + +is(start_table(), "<table>", "start_table"); # TEST +is(start_table({class => 'hello'}), "<table class=\"hello\">", "start_table with param"); # TEST +is(end_table(), "</table>", "end_table"); # TEST + +is(start_ul(), "<ul>", "start_ul"); # TEST +is(start_ul({class => 'hello'}), "<ul class=\"hello\">", "start_ul with param"); # TEST +is(end_ul(), "</ul>", "end_ul"); # TEST + +is(start_li(), "<li>", "start_li"); # TEST +is(start_li({class => 'hello'}), "<li class=\"hello\">", "start_li with param"); # TEST +is(end_li(), "</li>", "end_li"); # TEST + +is(start_ol(), "<ol>", "start_ol"); # TEST +is(start_ol({class => 'hello'}), "<ol class=\"hello\">", "start_ol with param"); # TEST +is(end_ol(), "</ol>", "end_ol"); # TEST + +is(start_td(), "<td>", "start_td"); # TEST +is(start_td({class => 'hello'}), "<td class=\"hello\">", "start_td with param"); # TEST +is(end_td(), "</td>", "end_td"); # TEST + +is(start_b(), "<b>", "start_b"); # TEST +is(start_b({class => 'hello'}), "<b class=\"hello\">", "start_b with param"); # TEST +is(end_b(), "</b>", "end_b"); # TEST + +is(start_i(), "<i>", "start_i"); # TEST +is(start_i({class => 'hello'}), "<i class=\"hello\">", "start_i with param"); # TEST +is(end_i(), "</i>", "end_i"); # TEST + +is(start_u(), "<u>", "start_u"); # TEST +is(start_u({class => 'hello'}), "<u class=\"hello\">", "start_u with param"); # TEST +is(end_u(), "</u>", "end_u"); # TEST + +is(start_div(), "<div>", "start_div"); # TEST +is(start_div({class => 'hello'}), "<div class=\"hello\">", "start_div with param"); # TEST +is(end_div(), "</div>", "end_div"); # TEST + diff --git a/t/start_end_end.t b/t/start_end_end.t new file mode 100644 index 0000000..2eeed60 --- /dev/null +++ b/t/start_end_end.t @@ -0,0 +1,72 @@ +#!/usr/local/bin/perl -w + +use lib qw(t/lib); +use strict; + +# Due to a bug in older versions of MakeMaker & Test::Harness, we must +# ensure the blib's are in @INC, else we might use the core CGI.pm +use lib qw(blib/lib blib/arch); +use Test::More tests => 45; + +use CGI qw(:standard end_h1 end_h2 end_h3 end_h4 end_h5 end_h6 end_table end_ul end_li end_ol end_td end_b end_i end_u end_div); + +is(start_h1(), "<h1>", "start_h1"); # TEST +is(start_h1({class => 'hello'}), "<h1 class=\"hello\">", "start_h1 with param"); # TEST +is(end_h1(), "</h1>", "end_h1"); # TEST + +is(start_h2(), "<h2>", "start_h2"); # TEST +is(start_h2({class => 'hello'}), "<h2 class=\"hello\">", "start_h2 with param"); # TEST +is(end_h2(), "</h2>", "end_h2"); # TEST + +is(start_h3(), "<h3>", "start_h3"); # TEST +is(start_h3({class => 'hello'}), "<h3 class=\"hello\">", "start_h3 with param"); # TEST +is(end_h3(), "</h3>", "end_h3"); # TEST + +is(start_h4(), "<h4>", "start_h4"); # TEST +is(start_h4({class => 'hello'}), "<h4 class=\"hello\">", "start_h4 with param"); # TEST +is(end_h4(), "</h4>", "end_h4"); # TEST + +is(start_h5(), "<h5>", "start_h5"); # TEST +is(start_h5({class => 'hello'}), "<h5 class=\"hello\">", "start_h5 with param"); # TEST +is(end_h5(), "</h5>", "end_h5"); # TEST + +is(start_h6(), "<h6>", "start_h6"); # TEST +is(start_h6({class => 'hello'}), "<h6 class=\"hello\">", "start_h6 with param"); # TEST +is(end_h6(), "</h6>", "end_h6"); # TEST + +is(start_table(), "<table>", "start_table"); # TEST +is(start_table({class => 'hello'}), "<table class=\"hello\">", "start_table with param"); # TEST +is(end_table(), "</table>", "end_table"); # TEST + +is(start_ul(), "<ul>", "start_ul"); # TEST +is(start_ul({class => 'hello'}), "<ul class=\"hello\">", "start_ul with param"); # TEST +is(end_ul(), "</ul>", "end_ul"); # TEST + +is(start_li(), "<li>", "start_li"); # TEST +is(start_li({class => 'hello'}), "<li class=\"hello\">", "start_li with param"); # TEST +is(end_li(), "</li>", "end_li"); # TEST + +is(start_ol(), "<ol>", "start_ol"); # TEST +is(start_ol({class => 'hello'}), "<ol class=\"hello\">", "start_ol with param"); # TEST +is(end_ol(), "</ol>", "end_ol"); # TEST + +is(start_td(), "<td>", "start_td"); # TEST +is(start_td({class => 'hello'}), "<td class=\"hello\">", "start_td with param"); # TEST +is(end_td(), "</td>", "end_td"); # TEST + +is(start_b(), "<b>", "start_b"); # TEST +is(start_b({class => 'hello'}), "<b class=\"hello\">", "start_b with param"); # TEST +is(end_b(), "</b>", "end_b"); # TEST + +is(start_i(), "<i>", "start_i"); # TEST +is(start_i({class => 'hello'}), "<i class=\"hello\">", "start_i with param"); # TEST +is(end_i(), "</i>", "end_i"); # TEST + +is(start_u(), "<u>", "start_u"); # TEST +is(start_u({class => 'hello'}), "<u class=\"hello\">", "start_u with param"); # TEST +is(end_u(), "</u>", "end_u"); # TEST + +is(start_div(), "<div>", "start_div"); # TEST +is(start_div({class => 'hello'}), "<div class=\"hello\">", "start_div with param"); # TEST +is(end_div(), "</div>", "end_div"); # TEST + diff --git a/t/start_end_start.t b/t/start_end_start.t new file mode 100644 index 0000000..94768c1 --- /dev/null +++ b/t/start_end_start.t @@ -0,0 +1,72 @@ +#!/usr/local/bin/perl -w + +use lib qw(t/lib); +use strict; + +# Due to a bug in older versions of MakeMaker & Test::Harness, we must +# ensure the blib's are in @INC, else we might use the core CGI.pm +use lib qw(blib/lib blib/arch); +use Test::More tests => 45; + +use CGI qw(:standard start_h1 start_h2 start_h3 start_h4 start_h5 start_h6 start_table start_ul start_li start_ol start_td start_b start_i start_u start_div); + +is(start_h1(), "<h1>", "start_h1"); # TEST +is(start_h1({class => 'hello'}), "<h1 class=\"hello\">", "start_h1 with param"); # TEST +is(end_h1(), "</h1>", "end_h1"); # TEST + +is(start_h2(), "<h2>", "start_h2"); # TEST +is(start_h2({class => 'hello'}), "<h2 class=\"hello\">", "start_h2 with param"); # TEST +is(end_h2(), "</h2>", "end_h2"); # TEST + +is(start_h3(), "<h3>", "start_h3"); # TEST +is(start_h3({class => 'hello'}), "<h3 class=\"hello\">", "start_h3 with param"); # TEST +is(end_h3(), "</h3>", "end_h3"); # TEST + +is(start_h4(), "<h4>", "start_h4"); # TEST +is(start_h4({class => 'hello'}), "<h4 class=\"hello\">", "start_h4 with param"); # TEST +is(end_h4(), "</h4>", "end_h4"); # TEST + +is(start_h5(), "<h5>", "start_h5"); # TEST +is(start_h5({class => 'hello'}), "<h5 class=\"hello\">", "start_h5 with param"); # TEST +is(end_h5(), "</h5>", "end_h5"); # TEST + +is(start_h6(), "<h6>", "start_h6"); # TEST +is(start_h6({class => 'hello'}), "<h6 class=\"hello\">", "start_h6 with param"); # TEST +is(end_h6(), "</h6>", "end_h6"); # TEST + +is(start_table(), "<table>", "start_table"); # TEST +is(start_table({class => 'hello'}), "<table class=\"hello\">", "start_table with param"); # TEST +is(end_table(), "</table>", "end_table"); # TEST + +is(start_ul(), "<ul>", "start_ul"); # TEST +is(start_ul({class => 'hello'}), "<ul class=\"hello\">", "start_ul with param"); # TEST +is(end_ul(), "</ul>", "end_ul"); # TEST + +is(start_li(), "<li>", "start_li"); # TEST +is(start_li({class => 'hello'}), "<li class=\"hello\">", "start_li with param"); # TEST +is(end_li(), "</li>", "end_li"); # TEST + +is(start_ol(), "<ol>", "start_ol"); # TEST +is(start_ol({class => 'hello'}), "<ol class=\"hello\">", "start_ol with param"); # TEST +is(end_ol(), "</ol>", "end_ol"); # TEST + +is(start_td(), "<td>", "start_td"); # TEST +is(start_td({class => 'hello'}), "<td class=\"hello\">", "start_td with param"); # TEST +is(end_td(), "</td>", "end_td"); # TEST + +is(start_b(), "<b>", "start_b"); # TEST +is(start_b({class => 'hello'}), "<b class=\"hello\">", "start_b with param"); # TEST +is(end_b(), "</b>", "end_b"); # TEST + +is(start_i(), "<i>", "start_i"); # TEST +is(start_i({class => 'hello'}), "<i class=\"hello\">", "start_i with param"); # TEST +is(end_i(), "</i>", "end_i"); # TEST + +is(start_u(), "<u>", "start_u"); # TEST +is(start_u({class => 'hello'}), "<u class=\"hello\">", "start_u with param"); # TEST +is(end_u(), "</u>", "end_u"); # TEST + +is(start_div(), "<div>", "start_div"); # TEST +is(start_div({class => 'hello'}), "<div class=\"hello\">", "start_div with param"); # TEST +is(end_div(), "</div>", "end_div"); # TEST + diff --git a/t/unescapeHTML.t b/t/unescapeHTML.t new file mode 100644 index 0000000..952cce8 --- /dev/null +++ b/t/unescapeHTML.t @@ -0,0 +1,19 @@ +use Test::More tests => 7; +use CGI 'unescapeHTML'; + +is( unescapeHTML( '&'), '&', 'unescapeHTML: &'); +is( unescapeHTML( '"'), '"', 'unescapeHTML: "'); +is( unescapeHTML( '<'), '<', 'unescapeHTML: < (using a numbered sequence)'); +is( unescapeHTML( 'Bob & Tom went to the store; Where did you go?'), + 'Bob & Tom went to the store; Where did you go?', 'unescapeHTML: a case where &...; should not be escaped.'); +is( unescapeHTML( 'This_string_contains_both_escaped_&_unescaped_<entities>'), + 'This_string_contains_both_escaped_&_unescaped_<entities>', 'unescapeHTML: partially-escaped string.'); +is( unescapeHTML( 'This escaped string kind of looks like it has an escaped entity &x; it does not'), + 'This escaped string kind of looks like it has an escaped entity &x; it does not', 'unescapeHTML: Another case where &...; should not be escaped.'); + +# rt #61120 +is( + unescapeHTML( 'ies_detection:&any_non_whitespace;results_in' ), + 'ies_detection:&any_non_whitespace;results_in', + "none white space doesn't cause unescape" +); diff --git a/t/upload.t b/t/upload.t new file mode 100644 index 0000000..ee926f3 --- /dev/null +++ b/t/upload.t @@ -0,0 +1,185 @@ +#!/usr/local/bin/perl -w + +################################################################# +# Emanuele Zeppieri, Mark Stosberg # +# Shamelessly stolen from Data::FormValidator and CGI::Upload # +################################################################# + +use strict; + +use Test::More 'no_plan'; + +use CGI qw/ :cgi /; +$CGI::LIST_CONTEXT_WARN = 0; + +#----------------------------------------------------------------------------- +# %ENV setup. +#----------------------------------------------------------------------------- + +my %myenv; + +BEGIN { + %myenv = ( + 'SCRIPT_NAME' => '/test.cgi', + 'SERVER_NAME' => 'perl.org', + 'HTTP_CONNECTION' => 'TE, close', + 'REQUEST_METHOD' => 'POST', + 'SCRIPT_URI' => 'http://www.perl.org/test.cgi', + 'CONTENT_LENGTH' => 3285, + 'SCRIPT_FILENAME' => '/home/usr/test.cgi', + 'SERVER_SOFTWARE' => 'Apache/1.3.27 (Unix) ', + 'HTTP_TE' => 'deflate,gzip;q=0.3', + 'QUERY_STRING' => '', + 'REMOTE_PORT' => '1855', + 'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; Konqueror/2.1.1; X11)', + 'SERVER_PORT' => '80', + 'REMOTE_ADDR' => '127.0.0.1', + 'CONTENT_TYPE' => 'multipart/form-data; boundary=xYzZY', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'PATH' => '/usr/local/bin:/usr/bin:/bin', + 'REQUEST_URI' => '/test.cgi', + 'GATEWAY_INTERFACE' => 'CGI/1.1', + 'SCRIPT_URL' => '/test.cgi', + 'SERVER_ADDR' => '127.0.0.1', + 'DOCUMENT_ROOT' => '/home/develop', + 'HTTP_HOST' => 'www.perl.org' + ); + + for my $key (keys %myenv) { + $ENV{$key} = $myenv{$key}; + } +} + +END { + for my $key (keys %myenv) { + delete $ENV{$key}; + } +} + +#----------------------------------------------------------------------------- +# Simulate the upload (really, multiple uploads contained in a single stream). +#----------------------------------------------------------------------------- + +my $q; + +{ + local *STDIN; + open STDIN, '<t/upload_post_text.txt' + or die 'missing test file t/upload_post_text.txt'; + binmode STDIN; + $q = CGI->new; +} + +#----------------------------------------------------------------------------- +# Check that the file names retrieved by CGI are correct. +#----------------------------------------------------------------------------- + +is( $q->param('does_not_exist_gif'), 'does_not_exist.gif', 'filename_2' ); +is( $q->param('100;100_gif') , '100;100.gif' , 'filename_3' ); +is( $q->param('300x300_gif') , '300x300.gif' , 'filename_4' ); + +{ + my $test = "multiple file names are handled right with same-named upload fields"; + my @hello_names = $q->param('hello_world'); + is ($hello_names[0],'goodbye_world.txt',$test. "...first file"); + is ($hello_names[1],'hello_world.txt',$test. "...second file"); +} + +#----------------------------------------------------------------------------- +# Now check that the upload method works. +#----------------------------------------------------------------------------- + +isa_ok( upload('does_not_exist_gif'),'File::Temp','upload_basic_2 (no object)' ); +isa_ok( upload('does_not_exist_gif'),'Fh','upload_basic_2 (no object)' ); +ok( defined $q->upload('does_not_exist_gif'), 'upload_basic_2' ); +ok( defined $q->upload('100;100_gif') , 'upload_basic_3' ); +ok( defined $q->upload('300x300_gif') , 'upload_basic_4' ); + +{ + my $test = "file handles have expected length for multi-valued field. "; + my ($goodbye_fh,$hello_fh) = $q->upload('hello_world'); + + # Go to end of file; + seek($goodbye_fh,0,2); + # How long is the file? + is(tell($goodbye_fh), 15, "$test..first file"); + + # Go to end of file; + seek($hello_fh,0,2); + # How long is the file? + is(tell($hello_fh), 13, "$test..second file"); + +} + + + +{ + my $test = "300x300_gif has expected length"; + my $fh1 = $q->upload('300x300_gif'); + is(tell($fh1), 0, "First object: filehandle starts with position set at zero"); + + # Go to end of file; + seek($fh1,0,2); + # How long is the file? + is(tell($fh1), 1656, $test); +} + +{ # test handle() method + my $fh1 = $q->upload("300x300_gif"); + my $rawhandle = $fh1->handle; + ok($rawhandle, "check handle()"); + isnt($rawhandle, "300x300_gif", "no string overload"); + # check it acts like a handle + seek($rawhandle, 0, 2); + is(tell($rawhandle), 1656, "check it acts like a handle"); + ok(eval { $rawhandle->seek(0, 2); 1 }, "can call seek() on handle result"); +} + +# param returns a blessed reference, so this always worked +{ + ok($q->tmpFileName($q->param("300x300_gif")), 'tmpFileName(param(field)) works'); + my $fn = $q->tmpFileName($q->param("300x300_gif")); + ok(-s $fn == 1656, 'tmpFileName(param(field)) result has desired size'); +} +# upload returns a blessed reference, so this always worked +{ + ok($q->tmpFileName($q->upload("300x300_gif")), 'tmpFileName(upload(field)) works'); + my $fn = $q->tmpFileName($q->upload("300x300_gif")); + ok(-s $fn == 1656, 'tmpFileName result has desired size'); +} +# the API and documentation make it look as though this ought to work, and +# it did in some versions, but is non-optimal; using the ref is better +{ + ok($q->tmpFileName($q->param("300x300_gif").""), 'tmpFileName(stringified param) works'); + my $fn = $q->tmpFileName($q->param("300x300_gif").""); + ok(-s $fn == 1656, 'tmpFileName(stringified param) result has desired size'); + # equivalent to the above + ok($q->tmpFileName("300x300.gif"), 'tmpFileName(string) works'); + $fn = $q->tmpFileName("300x300.gif"); + ok(-s $fn == 1656, 'tmpFileName(string) result has desired size'); +} + +my $q2 = CGI->new; + +{ + my $test = "Upload filehandles still work after calling CGI->new a second time"; + $q->param('new','zoo'); + + is($q2->param('new'),undef, + "Reality Check: params set in one object instance don't appear in another instance"); + + my $fh2 = $q2->upload('300x300_gif'); + is(tell($fh2), 0, "...so the state of a file handle shouldn't be carried to a new object instance, either."); + # Go to end of file; + seek($fh2,0,2); + # How long is the file? + is(tell($fh2), 1656, $test); +} + +{ + my $test = "multi-valued uploads are reset properly"; + my ($dont_care, $hello_fh2) = $q2->upload('hello_world'); + is(tell($hello_fh2), 0, $test); +} + +# vim: nospell diff --git a/t/uploadInfo.t b/t/uploadInfo.t new file mode 100644 index 0000000..f486447 --- /dev/null +++ b/t/uploadInfo.t @@ -0,0 +1,114 @@ +#!/usr/local/bin/perl -w + +################################################################# +# Emanuele Zeppieri, Mark Stosberg # +# Shamelessly stolen from Data::FormValidator and CGI::Upload # +################################################################# + +use strict; +use Test::More 'no_plan'; + +use CGI qw/ :form /; + +#----------------------------------------------------------------------------- +# %ENV setup. +#----------------------------------------------------------------------------- + +my %myenv; + +BEGIN { + %myenv = ( + 'SCRIPT_NAME' => '/test.cgi', + 'SERVER_NAME' => 'perl.org', + 'HTTP_CONNECTION' => 'TE, close', + 'REQUEST_METHOD' => 'POST', + 'SCRIPT_URI' => 'http://www.perl.org/test.cgi', + 'CONTENT_LENGTH' => 3285, + 'SCRIPT_FILENAME' => '/home/usr/test.cgi', + 'SERVER_SOFTWARE' => 'Apache/1.3.27 (Unix) ', + 'HTTP_TE' => 'deflate,gzip;q=0.3', + 'QUERY_STRING' => '', + 'REMOTE_PORT' => '1855', + 'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; Konqueror/2.1.1; X11)', + 'SERVER_PORT' => '80', + 'REMOTE_ADDR' => '127.0.0.1', + 'CONTENT_TYPE' => 'multipart/form-data; boundary=xYzZY', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'PATH' => '/usr/local/bin:/usr/bin:/bin', + 'REQUEST_URI' => '/test.cgi', + 'GATEWAY_INTERFACE' => 'CGI/1.1', + 'SCRIPT_URL' => '/test.cgi', + 'SERVER_ADDR' => '127.0.0.1', + 'DOCUMENT_ROOT' => '/home/develop', + 'HTTP_HOST' => 'www.perl.org' + ); + + for my $key (keys %myenv) { + $ENV{$key} = $myenv{$key}; + } +} + +END { + for my $key (keys %myenv) { + delete $ENV{$key}; + } +} + + +#----------------------------------------------------------------------------- +# Simulate the upload (really, multiple uploads contained in a single stream). +#----------------------------------------------------------------------------- + +my $q; + +{ + local *STDIN; + open STDIN, '<t/upload_post_text.txt' + or die 'missing test file t/upload_post_text.txt'; + binmode STDIN; + $q = CGI->new; +} + +{ + # That's cheating! We shouldn't do that! + my $test = "All temp files are present"; + is( scalar(keys %{$q->{'.tmpfiles'}}), 5, $test); +} + +my %uploadinfo_for = ( + 'does_not_exist_gif' => {type => 'application/octet-stream', size => undef, }, + '100;100_gif' => {type => 'image/gif', size => 896, }, + '300x300_gif' => {type => 'image/gif', size => 1656, }, +); + + +foreach my $param_name (sort keys %uploadinfo_for) { + my $f_type = $uploadinfo_for{$param_name}->{type}; + my $f_size = $uploadinfo_for{$param_name}->{size}; + my $test = "uploadInfo: $param_name"; + + my $fh = $q->upload($param_name); + is( uploadInfo($fh)->{'Content-Type'}, $f_type, $test); + is( $q->uploadInfo($fh)->{'Content-Type'}, $f_type, $test); + is( $q->uploadInfo($fh)->{'Content-Length'}, $f_size, $test); + + # access using param + my $param_value = $q->param($param_name); + ok( ref( $param_value ),'param returns filehandle' ); + is( $q->uploadInfo( $param_value )->{'Content-Type'}, $f_type, $test . ' via param'); + is( $q->uploadInfo( $param_value )->{'Content-Length'}, $f_size, $test . ' via param'); + + # access using Vars (is not possible) + my $vars = $q->Vars; + ok( ! ref( $vars->{$param_name} ),'Vars does not return filehandle' ); + ok( ! $q->uploadInfo( $vars->{$param_name} ), $test . ' via Vars'); +} + +my $q2 = CGI->new; + +{ + my $test = "uploadInfo: works with second object instance"; + my $fh = $q2->upload('300x300_gif'); + is( $q2->uploadInfo($fh)->{'Content-Type'}, "image/gif", $test); +} + diff --git a/t/upload_post_text.txt b/t/upload_post_text.txt Binary files differnew file mode 100644 index 0000000..10d6238 --- /dev/null +++ b/t/upload_post_text.txt @@ -0,0 +1,100 @@ +use strict; +use warnings; + +use Test::More; + +use CGI ':all'; + +delete( $ENV{SCRIPT_NAME} ); # Win32 fix, see RT 89992 +$ENV{HTTP_X_FORWARDED_HOST} = 'proxy:8484'; +$ENV{SERVER_PROTOCOL} = 'HTTP/1.0'; +$ENV{SERVER_PORT} = 8080; +$ENV{SERVER_NAME} = 'the.good.ship.lollypop.com'; + +is virtual_port() => 8484, 'virtual_port()'; +is server_port() => 8080, 'server_port()'; + +is url() => 'http://proxy:8484', 'url()'; + +$ENV{HTTP_X_FORWARDED_HOST} = '192.169.1.1, proxy1:80, 127.0.0.1, proxy2:8484'; + +is url() => 'http://proxy2:8484', 'url() with multiple proxies'; + +# let's see if we do the defaults right + +$ENV{HTTP_X_FORWARDED_HOST} = 'proxy:80'; + +is url() => 'http://proxy', 'url() with default port'; + +subtest 'rewrite_interactions' => sub { + # Reference: RT#45019 + + local $ENV{HTTP_X_FORWARDED_HOST} = undef; + local $ENV{SERVER_PROTOCOL} = undef; + local $ENV{SERVER_PORT} = undef; + local $ENV{SERVER_NAME} = undef; + + # These two are always set + local $ENV{'SCRIPT_NAME'} = '/real/cgi-bin/dispatch.cgi'; + local $ENV{'SCRIPT_FILENAME'} = '/home/mark/real/path/cgi-bin/dispatch.cgi'; + + # These two are added by mod_rewrite Ref: http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html + + local $ENV{'SCRIPT_URL'} = '/real/path/info'; + local $ENV{'SCRIPT_URI'} = 'http://example.com/real/path/info'; + + local $ENV{'PATH_INFO'} = '/path/info'; + local $ENV{'REQUEST_URI'} = '/real/path/info'; + local $ENV{'HTTP_HOST'} = 'example.com'; + + my $q = CGI->new; + + is( + $q->url( -absolute => 1, -query => 1, -path_info => 1 ), + '/real/path/info', + '$q->url( -absolute => 1, -query => 1, -path_info => 1 ) should return complete path, even when mod_rewrite is detected.' + ); + is( $q->url(), 'http://example.com/real', '$q->url(), with rewriting detected' ); + is( $q->url(-full=>1), 'http://example.com/real', '$q->url(-full=>1), with rewriting detected' ); + is( $q->url(-path=>1), 'http://example.com/real/path/info', '$q->url(-path=>1), with rewriting detected' ); + is( $q->url(-path=>0), 'http://example.com/real', '$q->url(-path=>0), with rewriting detected' ); + is( $q->url(-full=>1,-path=>1), 'http://example.com/real/path/info', '$q->url(-full=>1,-path=>1), with rewriting detected' ); + is( $q->url(-rewrite=>1,-path=>0), 'http://example.com/real', '$q->url(-rewrite=>1,-path=>0), with rewriting detected' ); + is( $q->url(-rewrite=>1), 'http://example.com/real', + '$q->url(-rewrite=>1), with rewriting detected' ); + is( $q->url(-rewrite=>0), 'http://example.com/real/cgi-bin/dispatch.cgi', + '$q->url(-rewrite=>0), with rewriting detected' ); + is( $q->url(-rewrite=>0,-path=>1), 'http://example.com/real/cgi-bin/dispatch.cgi/path/info', + '$q->url(-rewrite=>0,-path=>1), with rewriting detected' ); + is( $q->url(-rewrite=>1,-path=>1), 'http://example.com/real/path/info', + '$q->url(-rewrite=>1,-path=>1), with rewriting detected' ); + is( $q->url(-rewrite=>0,-path=>0), 'http://example.com/real/cgi-bin/dispatch.cgi', + '$q->url(-rewrite=>0,-path=>1), with rewriting detected' ); +}; + +subtest 'RT#58377: + in PATH_INFO' => sub { + local $ENV{PATH_INFO} = '/hello+world'; + local $ENV{HTTP_X_FORWARDED_HOST} = undef; + local $ENV{'HTTP_HOST'} = 'example.com'; + local $ENV{'SCRIPT_NAME'} = '/script/plus+name.cgi'; + local $ENV{'SCRIPT_FILENAME'} = '/script/plus+filename.cgi'; + + my $q = CGI->new; + is($q->url(), 'http://example.com/script/plus+name.cgi', 'a plus sign in a script name is preserved when calling url()'); + is($q->path_info(), '/hello+world', 'a plus sign in a script name is preserved when calling path_info()'); +}; + +subtest 'IIS PATH_INFO eq SCRIPT_NAME' => sub { + $CGI::IIS++; + local $ENV{PATH_INFO} = '/hello+world'; + local $ENV{HTTP_X_FORWARDED_HOST} = undef; + local $ENV{HTTP_HOST} = 'example.com'; + local $ENV{SCRIPT_NAME} = '/hello+world'; + + my $q = CGI->new; + is( $q->url,'http://example.com/hello+world','PATH_INFO being the same as SCRIPT_NAME'); +}; + +done_testing(); + + diff --git a/t/user_agent.t b/t/user_agent.t new file mode 100644 index 0000000..b861afb --- /dev/null +++ b/t/user_agent.t @@ -0,0 +1,14 @@ +# Test the user_agent method. +use Test::More 'no_plan'; +use CGI; + +my $q = CGI->new; + +is($q->user_agent, undef, 'user_agent: undef test'); + +$ENV{HTTP_USER_AGENT} = 'mark'; +is($q->user_agent, 'mark', 'user_agent: basic test'); +ok($q->user_agent('ma.*'), 'user_agent: positive regex test'); +ok(!$q->user_agent('BOOM.*'), 'user_agent: negative regex test'); + + diff --git a/t/utf8.t b/t/utf8.t new file mode 100644 index 0000000..016dc3b --- /dev/null +++ b/t/utf8.t @@ -0,0 +1,34 @@ +#!perl -T + +use strict; +use warnings; + +use utf8; + +use Test::More tests => 7; +use Encode; + +use_ok( 'CGI' ); + +ok( my $q = CGI->new, 'create a new CGI object' ); + +{ + no warnings qw/ once /; + $CGI::PARAM_UTF8 = 1; +} + +my $data = 'áéíóúµ'; +ok Encode::is_utf8($data), "created UTF-8 encoded data string"; + +# now set the param. +$q->param(data => $data); + +# if param() runs the data through Encode::decode(), this will fail. +is $q->param('data'), $data; + +# make sure setting bytes decodes properly +my $bytes = Encode::encode(utf8 => $data); +ok !Encode::is_utf8($bytes), "converted UTF-8 to bytes"; +$q->param(data => $bytes); +is $q->param('data'), $data; +ok Encode::is_utf8($q->param('data')), 'param() decoded UTF-8'; diff --git a/t/util-58.t b/t/util-58.t new file mode 100644 index 0000000..c478d5d --- /dev/null +++ b/t/util-58.t @@ -0,0 +1,29 @@ +# test CGI::Util::escape +use Test::More tests => 4; +use_ok("CGI::Util"); + +# Byte strings should be escaped byte by byte: +# 1) not a valid utf-8 sequence: +my $uri = "pe\x{f8}\x{ed}\x{e8}ko.ogg"; +is(CGI::Util::escape($uri), "pe%F8%ED%E8ko.ogg", "Escape a Latin-2 string"); + +# 2) is a valid utf-8 sequence, but not an UTF-8-flagged string +# This happens often: people write utf-8 strings to source, but forget +# to tell perl about it by "use utf8;"--this is obviously wrong, but we +# have to handle it gracefully, for compatibility with CGI.pm under +# perl-5.8.x +# +$uri = "pe\x{c5}\x{99}\x{c3}\x{ad}\x{c4}\x{8d}ko.ogg"; +is(CGI::Util::escape($uri), "pe%C5%99%C3%AD%C4%8Dko.ogg", + "Escape an utf-8 byte string"); + +SKIP: +{ + # This tests CGI::Util::escape() when fed with UTF-8-flagged string + # -- dankogai + skip("Unicode strings not available in $]", 1) if ($] < 5.008); + $uri = "\x{5c0f}\x{98fc} \x{5f3e}.txt"; # KOGAI, Dan, in Kanji + is(CGI::Util::escape($uri), "%E5%B0%8F%E9%A3%BC%20%E5%BC%BE.txt", + "Escape string with UTF-8 flag"); +} +__END__ diff --git a/t/util.t b/t/util.t new file mode 100644 index 0000000..a0791ee --- /dev/null +++ b/t/util.t @@ -0,0 +1,90 @@ +#!/usr/local/bin/perl -w + +# Test ability to escape() and unescape() punctuation characters +# except for qw(- . _). + +$| = 1; + +use Test::More tests => 80; +use Test::Deep; +use Config; +use_ok ( 'CGI::Util', qw( + escape + unescape + rearrange + ebcdic2ascii + ascii2ebcdic +) ); + +# ASCII order, ASCII codepoints, ASCII repertoire + +my %punct = ( + ' ' => '20', '!' => '21', '"' => '22', '#' => '23', + '$' => '24', '%' => '25', '&' => '26', '\'' => '27', + '(' => '28', ')' => '29', '*' => '2A', '+' => '2B', + ',' => '2C', '/' => '2F', # '-' => '2D', '.' => '2E' + ':' => '3A', ';' => '3B', '<' => '3C', '=' => '3D', + '>' => '3E', '?' => '3F', '[' => '5B', '\\' => '5C', + ']' => '5D', '^' => '5E', '`' => '60', # '_' => '5F', + '{' => '7B', '|' => '7C', '}' => '7D', # '~' => '7E', + ); + +# The sort order may not be ASCII on EBCDIC machines: + +my $i = 1; + +foreach(sort(keys(%punct))) { + $i++; + my $escape = "AbC\%$punct{$_}dEF"; + my $cgi_escape = escape("AbC$_" . "dEF"); + is($escape, $cgi_escape , "# $escape ne $cgi_escape"); + $i++; + my $unescape = "AbC$_" . "dEF"; + my $cgi_unescape = unescape("AbC\%$punct{$_}dEF"); + is($unescape, $cgi_unescape , "# $unescape ne $cgi_unescape"); +} + +# rearrange should return things in a consistent order, so when we pass through +# a hash reference it should sort the keys +for ( 1 .. 20 ) { + my %args = ( + '-charset' => 'UTF-8', + '-type' => 'text/html', + '-content-type' => 'text/html; charset=iso-8859-1', + ); + + my @ordered = rearrange( + [ + [ 'TYPE','CONTENT_TYPE','CONTENT-TYPE' ], + 'STATUS', + [ 'COOKIE','COOKIES','SET-COOKIE' ], + 'TARGET', + 'EXPIRES', + 'NPH', + 'CHARSET', + 'ATTACHMENT', + 'P3P' + ], + %args, + ); + + cmp_deeply( + [ @ordered ], + [ + 'text/html; charset=iso-8859-1', + undef, + undef, + undef, + undef, + undef, + 'UTF-8', + undef, + undef + ], + 'rearrange not sensitive to hash key ordering' + ); +} + +ok( CGI::Util::utf8_chr( "1",1 ),'utf8_chr' ); +ok( my $ebcdic = ascii2ebcdic( "A" ),'ascii2ebcdic' ); +is( ebcdic2ascii( $ebcdic ),'A','ebcdic2ascii' ); |