summaryrefslogtreecommitdiff
path: root/t
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2015-06-16 06:44:29 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2015-06-16 06:44:29 +0000
commitf9f3ab3056d94292adb4ab2e1451645bee989769 (patch)
treecc5a62954d359d5aad449420bc7ec259b3edb79e /t
downloadCGI-tarball-master.tar.gz
Diffstat (limited to 't')
-rw-r--r--t/Dump.t5
-rw-r--r--t/arbitrary_handles.t30
-rw-r--r--t/autoescape.t200
-rw-r--r--t/can.t7
-rw-r--r--t/carp.t440
-rw-r--r--t/cgi.t73
-rw-r--r--t/changes.t12
-rw-r--r--t/charset.t27
-rw-r--r--t/checkbox_group.t21
-rw-r--r--t/compiles_pod.t42
-rw-r--r--t/cookie.t441
-rw-r--r--t/delete.t59
-rw-r--r--t/end_form.t9
-rw-r--r--t/form.t235
-rw-r--r--t/function.t110
-rw-r--r--t/gh-155.t23
-rw-r--r--t/headers.t54
-rw-r--r--t/headers/attachment.t23
-rw-r--r--t/headers/charset.t20
-rw-r--r--t/headers/cookie.t34
-rw-r--r--t/headers/default.t13
-rw-r--r--t/headers/nph.t24
-rw-r--r--t/headers/p3p.t33
-rw-r--r--t/headers/target.t22
-rw-r--r--t/headers/type.t101
-rw-r--r--t/hidden.t38
-rw-r--r--t/html.t220
-rw-r--r--t/html_functions.t53
-rw-r--r--t/http.t44
-rw-r--r--t/init.t13
-rw-r--r--t/init_test.txt3
-rw-r--r--t/multipart_init.t25
-rw-r--r--t/multipart_start.t34
-rw-r--r--t/no_tabindex.t122
-rw-r--r--t/param_fetch.t26
-rw-r--r--t/param_list_context.t57
-rw-r--r--t/popup_menu.t33
-rw-r--r--t/postdata.t121
-rw-r--r--t/pretty.t13
-rw-r--r--t/push.t68
-rw-r--r--t/query_string.t15
-rw-r--r--t/redirect_query_string.t72
-rw-r--r--t/request.t130
-rw-r--r--t/rt-31107.t43
-rw-r--r--t/rt-52469.t19
-rw-r--r--t/rt-57524.t19
-rw-r--r--t/rt-75628.t27
-rw-r--r--t/rt-84767.t25
-rw-r--r--t/rt_31107.txt31
-rw-r--r--t/rt_75628.txt17
-rw-r--r--t/save_read_roundtrip.t26
-rw-r--r--t/sorted.t30
-rw-r--r--t/start_end_asterisk.t72
-rw-r--r--t/start_end_end.t72
-rw-r--r--t/start_end_start.t72
-rw-r--r--t/unescapeHTML.t19
-rw-r--r--t/upload.t185
-rw-r--r--t/uploadInfo.t114
-rw-r--r--t/upload_post_text.txtbin0 -> 3284 bytes
-rw-r--r--t/url.t100
-rw-r--r--t/user_agent.t14
-rw-r--r--t/utf8.t34
-rw-r--r--t/util-58.t29
-rw-r--r--t/util.t90
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&lt;a&gt;/, 'param names are HTML escaped by Dump()');
+like($cgi->Dump, qr/\Q&lt;b&gt;/, '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&lt;" value="test&lt;" />', "autoEscape defaults to On");
+
+my $before = escapeHTML("test<");
+autoEscape(undef);
+my $after = escapeHTML("test<");
+
+
+is($before, "test&lt;", "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&lt;" value="test&lt;" />', "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'
+);
diff --git a/t/can.t b/t/can.t
new file mode 100644
index 0000000..c4dfd4f
--- /dev/null
+++ b/t/can.t
@@ -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' );
diff --git a/t/cgi.t b/t/cgi.t
new file mode 100644
index 0000000..19360b2
--- /dev/null
+++ b/t/cgi.t
@@ -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 &lt;not&gt; &#139;right&#155;</h1>';
+
+undef( $CGI::ENCODE_ENTITIES );
+
+is h1( escapeHTML("this is <not> \x8bright\x9b") ),
+ '<h1>this is &lt;not&gt; &#139;right&#155;</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&egrave;" }, 'hello &aacute;' ),
+ '<p title="hello world&amp;egrave;">hello &aacute;</p>';
+
+$q->autoEscape(0);
+
+is $q->p( { title => "hello world&egrave;" }, 'hello &aacute;' ),
+ '<p title="hello world&egrave;">hello &aacute;</p>';
+
+is p( { title => "hello world&egrave;" }, 'hello &aacute;' ),
+ '<p title="hello world&amp;egrave;">hello &aacute;</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( '&amp;'), '&', 'unescapeHTML: &');
+is( unescapeHTML( '&quot;'), '"', 'unescapeHTML: "');
+is( unescapeHTML( '&#60;'), '<', '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_&lt;entities&gt;'),
+ '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
new file mode 100644
index 0000000..10d6238
--- /dev/null
+++ b/t/upload_post_text.txt
Binary files differ
diff --git a/t/url.t b/t/url.t
new file mode 100644
index 0000000..1e46ef0
--- /dev/null
+++ b/t/url.t
@@ -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' );