summaryrefslogtreecommitdiff
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
downloadCGI-tarball-master.tar.gz
-rw-r--r--Changes2132
-rw-r--r--MANIFEST88
-rw-r--r--META.json64
-rw-r--r--META.yml40
-rw-r--r--Makefile.PL69
-rw-r--r--README.md1781
-rwxr-xr-xexamples/clickable_image.cgi56
-rwxr-xr-xexamples/cookie.cgi111
-rwxr-xr-xexamples/crash.cgi9
-rwxr-xr-xexamples/file_upload.cgi74
-rw-r--r--examples/mojo_proxy.pl36
-rwxr-xr-xexamples/wikipedia_example.cgi40
-rw-r--r--examples/wilogo.gifbin0 -> 458 bytes
-rw-r--r--lib/CGI.pm3856
-rw-r--r--lib/CGI.pod1843
-rw-r--r--lib/CGI/Carp.pm615
-rw-r--r--lib/CGI/Cookie.pm537
-rw-r--r--lib/CGI/File/Temp.pm39
-rw-r--r--lib/CGI/HTML/Functions.pm8
-rw-r--r--lib/CGI/HTML/Functions.pod1927
-rw-r--r--lib/CGI/Pretty.pm85
-rw-r--r--lib/CGI/Push.pm306
-rw-r--r--lib/CGI/Util.pm354
-rw-r--r--lib/Fh.pm7
-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
88 files changed, 18260 insertions, 0 deletions
diff --git a/Changes b/Changes
new file mode 100644
index 0000000..8301d6d
--- /dev/null
+++ b/Changes
@@ -0,0 +1,2132 @@
+4.21 2015-06-16
+
+ [ RELEASE NOTES ]
+ - CGI.pm is now considered "done". See also "mature" and "legacy"
+ Features requests and none critical issues will be outright rejected.
+ The module is now in maintenance mode for critical issues only.
+
+ - This release removes the AUTOLOAD and compile optimisations from CGI.pm
+ that were introduced into CGI.pm twenty (20) years ago as a response to
+ its large size, which meant there was a significant compile time penalty.
+
+ - This optimisation is no longer relevant and makes the code difficult to
+ deal with as well as making test coverage metrics incorrect. Benchmarks
+ show that advantages of AUTOLOAD / lazy loading / deferred compile are
+ less than 0.05s, which will be dwarfed by just about any meaningful code
+ in a cgi script. If this is an issue for you then you should look at
+ running CGI.pm in a persistent environment (FCGI, etc)
+
+ - To offset some of the time added by removing the AUTOLOAD functionality
+ the dependencies have been made runtime rather than compile time. The
+ POD has also been split into its own file. CGI.pm now contains around
+ 4000 lines of code, which compared to some modules on CPAN isn't really
+ that much
+
+ - This essentially deprecates the -compile pragma and ->compile method. The
+ -compile pragma will no longer do anything, whereas the ->compile method
+ will raise a deprecation warning. More importantly this also REMOVES the
+ -any pragma because as per the documentation this pragma needed to be
+ "used with care or not at all" and allowing arbitrary HTML tags is almost
+ certainly a bad idea. If you are using the -any pragma and using arbitrary
+ tags (or have typo's in your code) your code will *BREAK*
+
+ - Although this release should be back compatible (with the exception of any
+ code using the -any pragma) you are encouraged to test it throughly as if
+ you are doing anything out of the ordinary with CGI.pm (i.e. have bugs
+ that may have been masked by the AUTOLOAD feature) you may see some issues.
+
+ - References: GH #162, GH #137, GH #164
+
+ [ SPEC / BUG FIXES ]
+ - make the list context warning in param show the filename rather than
+ the package so we have more information on exactly where the warning
+ has been raised from (GH #171)
+ - correct self_url when PATH_INFO and SCRIPT_NAME are the same but we
+ are not running under IIS (GH #176)
+ - Add the multi_param method to :cgi export (thanks to xblitz for the patch
+ and tests. GH #167)
+ - Fix warning for lack of HTTP_USER_AGENT in CGI::Carp (GH #168)
+ - Fix imports when called from CGI::Fast, restores the import of CGI functions
+ into the callers namespace for users of CGI::Fast (GH leejo/cgi-fast#11 and
+ GH leejo/cgi-fast#12)
+ - Fix regression of tmpFileName when calling with a plain string (GH #178,
+ thanks to Simon McVittie for the report and fix)
+
+ [ FEATURES ]
+ - CGI::Carp now has $CGI::Carp::FULL_PATH for displaying the full path to the
+ offending script in error messages
+ - CGI now has env_query_string() for getting the value of QUERY_STRING from
+ the environment and not that fiddled with by CGI.pm (which is what
+ query_string() does) (GH #161)
+ - CGI::ENCODE_ENTITIES var added to control which chracters are encoded by
+ the call to the HTML::Entities module - defaults to &<>"' (GH #157 - the
+ \x8b and \x9b chars have been removed from this list as we are concerned
+ more about unicode compat these days than old browser support.)
+
+ [ DOCUMENTATION ]
+ - Fix some typos (GH #173, GH #174)
+ - All *documentation* for HTML functionality in CGI has been moved into
+ its own namespace: CGI::HTML::Functions - although the functionality
+ continues to exist within CGI.pm so there are no code changes required
+ (GH #142)
+ - Add missing documentation for env variable fetching routines (GH #163)
+
+ [ TESTING ]
+ - Increase test coverage (GH #3)
+
+ [ INTERNALS ]
+ - Cwd made a TEST_REQUIRES rather than a BUILD_REQUIRES in Makefile.PL
+ (GH #170)
+ - AutoloadClass variables have been removed as AUTOLOAD was removed in
+ v4.14 so these are no longer necessary (GH #172 thanks to alexmv)
+ - Remove dependency on constant - internal DEBUG, XHTML_DTD and EBCDIC
+ constants changes to $_DEBUG, $_XHTML_DTD, and $_EBCDIC
+
+4.13 2014-12-18
+
+ [ RELEASE NOTES ]
+ - CGI::Pretty is now DEPRECATED and will be removed in a future release.
+ Please see GH #162 (https://github.com/leejo/CGI.pm/issues/162) for more
+ information and discussion (also GH #140 for HTML function deprecation
+ discussion: https://github.com/leejo/CGI.pm/issues/140)
+
+ [ TESTING ]
+ - fix t\rt-84767.t for failures on Win32 platforms related to file paths
+
+4.11 2014-12-02
+
+ [ SPEC / BUG FIXES ]
+ - more hash key ordering bugs fixed in HTML attribute output (GH #158,
+ thanks to Marcus Meissner for the patch and test case)
+
+ [ REFACTORING ]
+ - escapeHTML (and unescapeHTML) have been refactored to use the functions
+ exported by the HTML::Entities module (GH #157)
+ - change BUILD_REQUIRES to TEST_REQUIRES in Makefile.PL as these are test
+ dependencies not build dependencies (GH #159)
+
+ [ DOCUMENTATION ]
+ - replace any remaining uses of indirect object notation (new Object) with
+ the safer Object->new syntax (GH #156)
+
+4.10 2014-11-27
+
+ [ SPEC / BUG FIXES ]
+ - favour -content-type arg in header if -type and -charset options are also
+ passed in (GH #155, thanks to kaoru for the test case). this change also
+ sorts the hash keys in the rearrange method in CGI::Util meaning the order
+ of the arrangement will always be the same for params that have multiple
+ aliases. really you shouldn't be passing in multiple aliases, but this will
+ make it consistent should you do that
+
+ [ DOCUMENTATION ]
+ - fix some typos
+
+4.09 2014-10-21
+
+ [ RELEASE NOTES ]
+ - with this release the large backlog of issues against CGI.pm has been
+ cleared. All fixes have been made in the versions 4.00 and above so if
+ you are upgrading from 3.* you should thoroughly test your code against
+ recent versions of CGI.pm
+ - an effort has been made to retain back compatibility against previous
+ versions of CGI.pm for any fixes made, however some changes related to
+ the handling of temporary files may have consequences for your code
+ - please refer to the RELEASE NOTES for version 4.00 and above for all
+ recent changes and file an issue on github if there has been a regression.
+ - please do *NOT* file issues regarding HTML generating functions, these
+ are no longer being maintained (see perldoc for rationale)
+
+ [ SPEC / BUG FIXES ]
+ - tweak url to DTRT when the web server is IIS (RT #89827 / GH #152)
+ - fix temporary file handling when dealing with multiple files in MIME uploads
+ (GH #154, thanks to GeJ for the test case)
+
+4.08 2014-10-18
+
+ [ DOCUMENTATION ]
+ - note that calling headers without a -charset may lead to a nonsensical
+ charset being added to certain content types due to the default and the
+ workaround
+ - remove documentation stating that calls to escapeHTML with a changed
+ charset force numeric encoding of all characters, because that does not
+ happen
+ - documentation tweaks for calling param() in list context and the addition
+ of multi_param()
+
+ [ SPEC / BUG FIXES ]
+ - don't sub out PATH_INFO in url if PATH_INFO is the same as SCRIPT_NAME
+ (RT #89827)
+ - add multi_param() method to allow calling of param() in list context
+ without having to disable the $LIST_CONTEXT_WARN flag (see RELEASE NOTES
+ for version 4.05 on why calling param() in list context could be a bad
+ thing)
+
+4.07 2014-10-12
+
+ [ RELEASE NOTES ]
+ - please see changes for v4.05
+
+ [ TESTING ]
+ - typo and POD fixes, add test to check POD and compiles
+
+4.06 2014-10-10
+
+ [ RELEASE NOTES ]
+ - please see changes for v4.05
+
+ [ DOCUMENTATION ]
+ - make warning on list context call of ->param more lenient and don't
+ warn if called with no arguments
+
+4.05 2014-10-08
+
+ [ RELEASE NOTES ]
+ - this release includes *significant* refactoring of temporary file
+ handling in CGI.pm. See "Changes in temporary file handling" in perldoc
+
+ - this release adds a warning for when the param method is called
+ in list context, see the Warning in the perldoc for the section
+ "Fetching the value or values of a single named parameter" for why
+ this has been added and how to disable this warning
+
+ [ DOCUMENTATION ]
+ - change AUTHOR INFORMATION to LICENSE to please Kwalitee
+
+ [ TESTING ]
+ - t/arbitrary_handles.t to check need for patch in RT #54055, it
+ turns out there is no need - the first argument to CGI->new can
+ be an arbitrary handle
+ - add test case for incorrect unescaping of redirect headers
+ (RT #61120)
+ - add tests for the handle method (RT #85074, thanks to TONYC@cpan.org)
+
+ [ SPEC / BUG FIXES ]
+ - don't set binmode on STDOUT/STDERR/STDIN if a none standard layer
+ is already set on them on none UNIX platforms (RT #57524)
+ - make XForms:Model data accesible through POSTDATA/PUTDATA param
+ (RT #75628)
+ - prevent corruption of POSTDATA/PUTDATA when -utf8 flag is used and use
+ tempfiles to handle this data (RT #79102, thanks anonymous)
+ - unescape request URI *after* having removed the query string to prevent
+ removal of ? chars that are part of the original URI (and were encoded)
+ (RT #83265)
+ - fix q( to qq( in CGI::Carp so $@ is correct interpolated (RT #83360)
+ - don't call ->query_string in url unless -query is passed (RT #87790)
+ (optimisation and fits the current documented behaviour)
+
+4.04 2014-09-04
+
+ [ RELEASE NOTES ]
+ - this release removes some long deprecated modules/functions and
+ includes refactoring to the temporary file handling in CGI.pm. if
+ you are doing anything out of the ordinary with regards to temp
+ files you should test your code before deploying this update as
+ temp files may no longer be stored in previously used locations
+
+ [ REMOVED / DEPRECATIONS ]
+ - startform and endform methods removed (previously deprecated, you
+ should be using the start_form and end_form methods)
+ - both CGI::Apache and CGI::Switch have been removed as these modules
+ 1) have been deprecated for *years*, and 2) do nothing whatsoever
+
+ [ SPEC / BUG FIXES ]
+ - handle multiple values in X-Forwarded-Host header, we follow the
+ logic in most other frameworks and take the last value from the list
+ (RT #54487)
+ - reverse the order of TEMP dir placement for WINDOWS: TEMP > TMP > WINDIR
+ (RT #71799, thanks to jeff@math.tntech.edu), this returns the behaviour
+ to pre e24d04e9bc5fda7722444b02fec135d8cc2ff488 but with the undefined
+ fix still in place
+ - refactor CGITempFile::find_tempdir to use File::Spec->tmpdir
+ (related: RT #71799)
+ - fix warnings when QUERY_STRING has empty key=value pairs (RT #54511)
+ - pad custom 500 status response messages to > 512 for MSIE (RT #81946)
+ - make Vars tied hash delete method return the value deleted from the hash
+ making it act like perl's delete (RT #51020)
+
+ [ TESTING ]
+ - add .travis.yml (https://travis-ci.org)
+ - test case for RT #53966 - disallow filenames with ~ char
+ - test case for RT #55166 - calling Vars to get the filename does not return
+ a filehandle, so this cannot be used in the call to uploadinfo, also
+ update documentation for the uploadInfo to show that ->Vars should not be
+ used to get the filename for this method
+ - fix t/url.t to pass on Win32 platforms that have the SCRIPT_NAME env
+ variable set (RT #89992)
+ - add procedural call tests for upload and uploadInfo to confirm these work
+ as should (RT #91136)
+
+ [ DOCUMENTATION ]
+ - tweak perldoc for -utf8 option (RT #54341, thanks to Helmut Richter)
+ - explain the HTML generation functions should no longer be used and that
+ they may be deprecated in a future release
+
+4.03 2014-07-02
+
+ [ REMOVED / DEPRECATIONS ]
+ - the -multiple option to popup_menu is now IGNORED as this did not
+ function correctly. If you require a menu with multiple selections
+ use the scrolling_list method. (RT #30057)
+
+ [ SPEC / BUG FIXES ]
+ - support redirects in mod_perl2, or fall back to using env variable
+ for up to 5 redirects, when getting the query string (RT #36312)
+ - CGI::Cookie now correctly supports the -max-age argument, previously
+ if this was passed the value of the -expires argument would be used
+ meaning there was no way to supply *only* this argument (RT #50576)
+ - make :all actually import all methods, except for :cgi-lib, and add
+ :ssl to the :standard import (RT #70337)
+
+ [ DOCUMENTATION ]
+ - clarify documentation regarding query_string method (RT #48370)
+ - links fixed in some perldoc (Thanks to Michiel Beijen)
+
+ [ TESTING ]
+ - add t/changes.t for testing this Changes file
+ - test case for RT #31107 confirming multipart parsing is to spec
+ - improve t/rt-52469.t by adding a timeout check
+
+4.02 2014-06-09
+
+ [ NEW FEATURES ]
+ - CGI::Carp learns noTimestamp / $CGI::Carp::NO_TIMESTAMP to prevent
+ timestamp in messages (RT #82364, EDAVIS@cpan.org)
+ - multipart_init and multipart_start learn -charset option (RT #22737)
+
+ [ SPEC / BUG FIXES ]
+ - Support multiple cookies when passing an ARRAY ref with -set-cookie
+ (RT #15065, JWILLIAMS@cpan.org)
+
+ [ DOCUMENTATION ]
+ - Made licencing information consistent and remove duplicate comments
+ about licence details, corrected location to report bugs (RT #38285)
+
+4.01 2014-05-27
+
+ [ DOCUMENTATION ]
+ - CGI.pm hasn't been removed from core *just* yet, but will be soon:
+ http://perl5.git.perl.org/perl.git/commitdiff/e9fa5a80
+
+4.00 2014-05-22
+
+ [ INTERNALS ]
+ - CGI::Fast split out into its own distribution, related files and tests removed
+ - developer test added for building with perlbrew
+
+ [ DOCUMENTATION ]
+ - Update perldoc to explain that CGI.pm has been removed from perl core
+ - Make =head2 perldoc less shouty (RT #91140)
+ - Tickets migrated from RT to github issues (both CGI and CGI.pm distributions)
+ - Repointing bugtracker at newly forked github repo and note that Lee Johnson
+ is the current maintainer.
+ - Bump version to 4.00 for clear boundary of above changes
+
+Version 3.65 Feb 11, 2014
+
+ [INTERNALS]
+ - Update Makefile to refine where CGI.pm gets installed
+ (Thanks to bingo, rjbs: https://github.com/markstos/CGI.pm/pull/30)
+
+Version 3.64 Nov 23, 2013
+
+ [BUG FIXES]
+ - Avoid warning about "undefined variable in user_agent in some cases (RT#72882)
+
+ [INTERNALS]
+ - Avoiding warning about "unitialized value" in when calling user_agent() in some cases. (RT#72882, perl@max-maurer.de)
+ - Update minimum required version in Makefile.PL to 5.8.1. It had already been
+ updated to 5.8.1 in the CGI.pm module in 3.53.
+ - Fix POD errors reported by newer pod2man (Thanks to jmdh)
+ - Typo fixes, (dsteinbrunner).
+ - use deprecate.pm on perls 5.19.0 and later. (rjbs).
+
+ [DOCUMENTATION]
+ - Update CGI::Cookie docs to reflect that HttpOnly is widely supported now.
+
+
+Version 3.63 Nov 12, 2012
+
+ [SECURITY]
+ - CR escaping for Set-Cookie and P3P headers was improved. There was potential
+ for newline injection in these headers.
+ (Thanks to anazawa, https://github.com/markstos/CGI.pm/pull/23)
+
+Version 3.62, Nov 9th, 2012
+
+ [INTERNALS]
+ - Changed how the deprecated endform function was defined for compatibility
+ with the development version of Perl.
+ - Fix failures in t/tmpdir.t when run as root
+ https://github.com/markstos/CGI.pm/issues/22, RT#80659)
+
+ - Made it possible to force a sorted order for things like hash
+ attributes so that tests are not dependent on a particular hash
+ ordering. This will be required in modern perls which will
+ change the ordering per process. (Yves, RT#80659)
+
+Version 3.61 Nov 2nd, 2012
+
+ (No code changes)
+
+ [INTERNALS]
+ - formatting of CGI::Carp documentation was improved. Thanks to benkasminbullock.
+ - un-TODO some tests in t/tmpdir.t that were passing in most cases.
+ More on this:
+ https://github.com/markstos/CGI.pm/issues/19#
+ https://github.com/markstos/CGI.pm/commit/cc73dc9807b0fabb56b3cdf1a9726588b2eda0f7
+
+Version 3.60 Aug 15th, 2012
+
+ [BUG FIXES]
+ - In some caes, When unescapeHTML() hit something it didn't recognize with an ampersand and
+ and semicolon, it would throw away the semicolon and ampersand. It now does a better job.
+ of preserving content it doesn't recognize. Thanks to CEBJYRE@cpan.org (RT#75595)
+ - Remove trailing newline after <form> tag inserted by startform and start_form. It can
+ cause rendering problems in some cases. Thanks to SJOHNSTON@cpan.org (RT#67719)
+ - Workaround "Insecure Dependency" warning generated by some versions of Perl (RT#53733).
+ Thanks to degatcpan@ntlworld.com, klchu@lbl.gov and Anonymous Monk
+
+ [DOCUMENTATION]
+ - Clarify that when -status is used, the human-readable phase should be included, per RFC 2616.
+ Thanks to SREZIC@cpan.org (RT#76691).
+
+ [INTERNALS]
+ - More tests for header(), thanks to Ryo Anazawa.
+ - t/url.t has been fixed on VMS. Thanks to cberry@cpan.org (RT#72380)
+ - MANIFEST patched so that t/multipart_init.t is included again. Thanks to shay@cpan.org (RT#76189)
+
+Version 3.59 Dec 29th, 2011
+
+ [BUG FIXES]
+ - We no longer read from STDIN when the Content-Length is not set, preventing
+ requests with no Content-Length from freezing in some cases. This is consistent
+ with the CGI RFC 3875, and is also consistent with CGI::Simple. However, the old
+ behavior may have been expected by some command-line uses of CGI.pm.
+ Thanks to Philip Potter and Yanick Champoux. See RT#52469 for details:
+ https://rt.cpan.org/Public/Bug/Display.html?id=52469
+
+ [INTERNALS]
+ - remove tmpdirs more aggressively. Thanks to rjbs (RT#73288)
+ - use Text::ParseWords instead of ancient shellwords.pl. Thanks to AlexBio.
+ - remove use of define(@arr). Thanks to rjbs.
+ - spelling fixes. Thanks to Gregor Herrmann and Alessandro Ghedini.
+ - fix test count and warning in t/fast.t. Thanks to Yanick.
+
+Version 3.58 Nov 11th, 2011
+
+ [DOCUMENTATION]
+ - Clarify that using query_string() only has defined behavior when using the GET method. (RT#60813)
+
+Version 3.57 Nov 9th, 2011
+ [INTERNALS]
+ - test failure in t/fast.t introduced in 3.56 is fixed. (Thanks to zefram and chansen).
+ - Test::More requirement has been bumped to 0.98
+
+Version 3.56 Nov 8th, 2011
+
+ [SECURITY]
+ Use public and documented FCGI.pm API in CGI::Fast
+ CGI::Fast was using an FCGI API that was deprecated and removed from
+ documentation more than ten years ago. Usage of this deprecated API with
+ FCGI >= 0.70 or FCGI <= 0.73 introduces a security issue.
+ <https://rt.cpan.org/Public/Bug/Display.html?id=68380>
+ <http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2011-2766>
+ (Thanks to chansen)
+
+ [INTERNALS]
+ - tmp files are now cleaned up on VMS ( RT#69210, thanks to cberry@cpan.org )
+ - Fixed test failure: done_testing() added to url.t (Thanks to Ryan Jendoubi)
+ - Clarify preferred bug submission location in docs, and note that Mark Stosberg
+ is the current maintainer.
+
+Version 3.55 June 3rd, 2011
+
+ [THINGS THAT MAY BREAK YOUR CODE]
+ url() was fixed to return "PATH_INFO" when it is explicitly requested
+ with either the path=>1 or path_info=>1 flag.
+
+ If your code is running under mod_rewrite (or compatible) and you are calling self_url() or
+ you are calling url() and passing path_info=>1, These methods will actually be
+ returning PATH_INFO now, as you have explicitly requested, or has self_url()
+ has requested on your behalf.
+
+ The PATH_INFO has been omitted in such URLs since the issue was introduced
+ in the 3.12 release in December, 2005.
+
+ This bug is so old your application may have come to depend on it or
+ workaround it. Check for application before upgrading to this release.
+
+ Examples of affected method calls:
+
+ $q->url(-absolute => 1, -query => 1, -path_info => 1 )
+ $q->url(-path=>1)
+ $q->url(-full=>1,-path=>1)
+ $q->url(-rewrite=>1,-path=>1)
+ $q->self_url();
+
+Version 3.54, Apr 28, 2011
+ No code changes
+
+ [INTERNALS]
+ - Address test failures in t/tmpdir.t, thanks to Niko Tyni.
+ Some tests here are failing on some platforms and have been marked as TODO.
+
+Version 3.53, Apr 25, 2011
+
+ [NEW FEATURES]
+ - The DELETE HTTP verb is now supported.
+ (RT#52614, James Robson, Eduardo Ari�o de la Rubia)
+
+ [INTERNALS]
+ - Correct t/tmpdir.t MANIFEST entry. (RT#64949)
+ - Update minimum required Perl version to be Perl 5.8.1, which
+ has been out since 2003. This allows us to drop some hacks
+ and exceptions (Mark Stosberg)
+
+Version 3.52, Jan 24, 2011
+
+ [DOCUMENTATION]
+ - The documentation for multi-line header handling was been updated to reflect
+ the changes in 3.51. (Mark Stosberg, ntyni@iki.fi)
+
+ [INTERNALS]
+ - Add missing t/tmpfile.t file. (RT#64949)
+ - Fix warning in t/cookie.t (RT#64570, Chris Williams, Rainer Tammer, Mark Stosberg)
+ - Fixed logic bug in t/multipart_init.t (RT#64261, Niko Tyni)
+
+Version 3.51, Jan 5, 2011
+
+ [NEW FEATURES]
+ - A new option to set $CGI::Carp::TO_BROWSER = 0, allows you to explicitly
+ exclude a particular scope from triggering printing to the browser when
+ fatatlsToBrowser is set. (RT#62783, Thanks to papowell)
+ - The <script> tag now supports the "charset" attribute.
+ (RT#62907, Thanks to Fabrice Metge)
+ - In CGI::Cookie, "Max-Age" is now supported for better spec compliance.
+ (Mark Stosberg)
+
+ [BUG FIXES]
+ - Setting charset() now works for all content types, not just "text/*".
+ (RT#57945, Thanks to Yanick and Gerv.)
+ - support for user temporary directories ($HOME/tmp) was commented out
+ in 2.61 but the documentation wasn't updated (Peter Gervai, Niko Tyni)
+ - setting $CGITempFile::TMPDIRECTORY before loading CGI.pm has been
+ working but undocumented since 3.12 (which listed it in Changes as
+ $CGI::TMPDIRECTORY) (Peter Gervai, Niko Tyni)
+ - unfortunately the previous change broke the runtime check for looking
+ for a new temporary directory if the current one suddenly became
+ unwritable (Peter Gervai, Niko Tyni)
+ - A bug was fixed in CGI::Carp triggered by certain death cases in
+ the BEGIN phase of parent classes.
+ (RT#57224, Thanks to UNERA, Yanick Champoux, Mark Stosberg)
+ - CGI::Cookie->new() now follows the documentation and returns undef
+ if the -name and -value args aren't provided. This new behavior is also
+ consistent with the docs and code of CGI::Simple::Cookie. (Mark Stosberg)
+ - CGI::Cookie->parse() now trims leading and trailing whitespace from cookie
+ elements as intended. The change also makes this part of the parsing
+ identical to CGI::Simple::Cookie (Mark Stosberg)
+ - Temp file handling was improved (RT#62762)
+
+ [SECURITY]
+ - Further improvements have been made to guard against newline injections
+ in headers. (Thanks to Max Kanat-Alexander, Yanick Champoux, Mark Stosberg)
+
+ [PERFORMANCE]
+ - Make EBCDIC a compile-time constant so there's zero overhead (and less
+ compiled code) in subroutines that test for it. (Tim Bunce)
+ - If you just want to use CGI::Cookie, CGI.pm will no longer be loaded
+ unless you call the bake() method, which requires it. (Mark Stosberg)
+
+ [DOCUMENTATION]
+ - quit referring to the <link> tag as being "rarely used". (Victor Sanders)
+ - typo and whitespace fixes (RT#62785, thanks to scop@cpan.org)
+ - The -dtd argument to start_html() is now documented
+ (RT#60473, Thanks to giecrilj and steve@fisharerojo.org)
+ - CGI::Carp doc are updated to reflect that it can work with mod_perl 2.0.
+ - when creating a temporary file in the directory fails, the error message
+ could indicate the root of the problem better (Peter Gervai, Niko Tyni)
+
+ [INTERNALS]
+ - Re-fixing https test in http.t. (RT#54768, thanks to SPROUT)
+ - param_fetch no longer triggers a warning when called with no arguments (ysth, Mark Stosberg)
+
+Version 3.50, Nov 8, 2010
+
+ [SECURITY]
+ 1. The MIME boundary in multipart_init is now random.
+ Thanks to Byron Jones, Masahiro Yamada, Reed Loden, and Mark Stosberg
+ 2. Further improvements to handling of newlines embedded in header values.
+ An exception is thrown if header values contain invalid newlines.
+ Thanks to Michal Zalewski, Max Kanat-Alexander, Yanick Champoux,
+ Lincoln Stein, Fr�d�ric Buclin and Mark Stosberg
+
+ [DOCUMENTATION]
+ 1. Correcting/clarifying documentation for param_fetch(). Thanks to
+ Ren�e B�cker. (RT#59132)
+
+ [INTERNALS]
+ 1. Fixing https test in http.t. (RT#54768)
+ 2. Tests were added for multipart_init(). Thanks to Mark Stosberg and CGI::Simple.
+
+Version 3.49, Feb 5th, 2010
+
+ [BUG FIXES]
+ 1. Fix a regression since 3.44 involving a case when the header includes "Content-Length: 0".
+ Thanks to Alex Vandiver (RT#51109)
+ 2. Suppress uninitialized warnings under -w. Thanks to burak. (RT#50301)
+ 3. url() now uses virtual_port() instead of server_port(). Thanks to MKANAT and Yanick Champoux. (RT#51562)
+ 4. CGI::Carp now properly handles stringifiable objects, like Exception::Class throws (RT#39904)
+
+ [SECURITY]
+ 1. embedded newlines are now filtered out of header values in header().
+ Thanks to Mark Stosberg and Yanick Champoux.
+
+ [DOCUMENTATION]
+ 1. README was updated to reflect that CGI.pm was moved under ./lib.
+ Thanks to Alex Vandiver.
+
+ [INTERNALS]
+ 1. More tests were added for autoescape, thanks to Bob Kuo. (RT#25485)
+ 2. Attempt to avoid test failures with t/fast, thanks to Steve Hay. (RT#49599)
+
+Version 3.48, Sep 25, 2009
+
+ [BUG FIXES]
+ 1. <optgroup> default values are now properly escaped.
+ Thanks to #raleigh.pm and Mark Stosberg. (RT#49606)
+ 2. The change to exception handling in CGI::Carp introduced in 3.47 has been
+ reverted for now. It caused regressions reported in RT#49630.
+ Thanks to mkanat for the report.
+
+ [DOCUMENTATION]
+ 1. Documentation for upload() has been overhauled, thanks to Mark Stosberg.
+ 2. Documentation for tmpFileName has been added. Thanks to Mark Stosberg and Nathaniel K. Smith.
+ 3. URLS were updated, thanks to Leon Brocard and Yanick Champoux. (RT#49770)
+
+ [INTERNALS]
+ 1. More tests were added for autoescape, thanks to Bob Kuo. (RT#25485)
+
+Version 3.47, Sep 9, 2009
+
+ No code changes.
+
+ [INTERNALS]
+ Re-release of 3.46, which did not contain a proper MANIFEST
+
+Version 3.46
+
+ [BUG FIXES]
+ 1. In CGI::Pretty, we no longer add line breaks after tags we claim not to format. Thanks to rrt, Bob Kuo and
+ and Mark Stosberg. (RT#42114).
+ 2. unescapeHTML() no longer falsely recognizes certain text as entities. Thanks to Pete Gamanche, Mark Stosberg
+ and Bob Kuo. (RT#39122)
+ 3. checkbox_group() now correctly includes a space before the "checked" attribute.
+ Thanks to Andrew Speer and Bob Kuo. (RT#36583)
+ 4. Fix case-sensitivity in http() and https() according to docs. Make https()
+ return list of keys in list context. Thanks to riQyRoe and Rhesa Rozendaal. (RT#12909)
+ 5. XHTML is now automatically disabled for HTML 4, as well as HTML 2 and HTML 3. Thanks to
+ Dan Harkless and Yanick Champoux. (RT#27907)
+ 6. Pre-compiling 'end_form' with ':form' switch now works. Thanks to ryochin and Yanick Champoux. (RT#41530)
+ 7. Empty name/values pairs are now properly saved and restored from filehandles. Thanks to rlucas and
+ Rhesa Rozendaal (RT#13158)
+ 8. Some differences between startform() and start_form() have been fixed. Thanks to Slaven Rezic and
+ Shawn Corey. (RT#22046)
+ 9. url_param() has been updated to be more consistent with the documentation and param().
+ Thanks to Britton Kerin and Yanick Campoux. (RT#43587)
+ 10.hidden() now correctly supports multiple default values.
+ Thanks to david@dierauer.net and Russell Jenkins. (RT#20436)
+ 11.Calling CGI->new() no longer clobbers the value of $_ in the current scope.
+ Thanks to Alexey Tourbin, Bob Kuo and Mark Stosberg. (RT#25131)
+ 12.UTF-8 params should not get double-decoded now.
+ Thanks to Yves, Bodo, Burak G�rsoy, and Michael Schout. (RT#19913)
+ 13.We now give objects passed to CGI::Carp::die a chance to be stringified.
+ Thanks to teek and Yanick Champoux (RT#41530)
+ 14.Turning off autoEscape() now only affects the behavior of built-in HTML
+ generation fuctions. Explicit calls to escapeHTML() always escape HTML regardless
+ of the setting. Thanks to vindex, Bob Kuo and Mark Stosberg (RT#40748)
+ 15.In CGI::Fast, preferences set via pragmas are now preserved.
+ Thanks to heinst and Mark Stosberg (RT#32119)
+
+ [DOCUMENTATION]
+ 1. remote_addr() is now documented. Thanks to Yanick Champoux. (RT#38884)
+ 2. In CGI::Pretty in the list of tags left unformatted was updated to match the code. Thanks to Mark Stosberg. (RT#42114)
+ 3. In CGI::Pretty, performance concerns are now documented. Thanks to Jochen, Rhesa Rozendaal and Mark Stosberg (RT#13223)
+ 4. A number of outdated Netscape references have been removed. Thanks to Mark Stosberg.
+ 5. The documentation has been purged of examples of using indirect object notation. Thanks to Mark Stosberg.
+ 6. Some POD formatting was fixed. Thanks to Dave Mitchell (RT#48935).
+ 7. Docs and examples were updated to highlight start_form instead of startform.
+ Thanks to Slaven Rezic.
+ 8. Note that CGI::Carp::carpout() doesn't work with in-memory filehandles.
+ Thanks to rhubbell and Mark Stosberg.
+ 9. The documentation for the -newstyle_urls is now less confusing.
+ Thanks to Ryan Tate and Mark Stosberg (RT#49454)
+
+ [INTERNALS]
+ 1. Quit bundling an ancient copy of Test::More and and using a custom 'lib' path for the tests. Instead, Test::More
+ is now a dependency. Thanks to Ansgar and Mark Stosberg (RT#48811)
+ 2. Automated tests for hidden() have been added, thanks to Russel Jenkins and Mark Stosberg (RT#20436)
+ 3. t/util.t has been updated to use Test::More instead of a home-grown test function. Thanks to Bob Kuo.
+
+Version 3.45, Aug 14, 2009
+
+ [BUG FIXES]
+ 1. Prevent warnings about "uninitialized values" for REQUEST_URI, HTTP_USER_AGENT and other environment variables.
+ Patches by Callum Gibson, heiko and Mark Stosberg. (RT#24684, RT#29065)
+ 2. Avoid death in some cases when running under Taint mode on Windows.
+ Patch by Peter Hancock (RT#43796)
+ 3. Allow 0 to be used as a default value in popup_menu(). This was broken starting in 3.37.
+ Thanks to Haze, who was the first to report this and supply a patch, and pfschill, who pinpointed
+ when the bug was introduced. A regression test for this was also added. (RT#37908)
+ 4. Allow "+" as a valid character in file names, which fixes temp file creation on OS X Leopard.
+ Thanks to Andy Armstrong, and alech for patches. (RT#30504)
+ 5. Set binmode() on the Netware platform, thanks to Guenter Knauf (RT#27455)
+ 6. Don't allow a CGI::Carp error handler to die recursively. Print a warning and exit instead.
+ Thanks to Marc Chantreux. (RT#45956)
+ 7. The Dump() method now is fixed to escape HTML properly. Thanks to Mark Stosberg (RT#21341)
+ 8. Support for <optgroup> with scrolling_list() now works the same way as it does for popup_menu().
+ Thanks to Stuart Johnston (RT#30097)
+ 9. CGI::Pretty now works properly when $" is set to ''. Thanks to Jim Keenan (RT#12401)
+ 10. Fix crash when used in combination with PerlEx::DBI. Thanks to Burak G�rsoy (RT#19902)
+
+ [DOCUMENTATION]
+ 1. Several typos were fixed, Thanks to ambs. (RT#41105)
+ 2. A typo related to the nosticky pragma was fixed, thanks to Britton Kerin. (RT#43220)
+ 3. examples/nph-clock.cgi is now more portable, by calling localtime() rather than `/bin/date`,
+ thanks to Guenter Knauf. (RT#27456).
+ 4. In CGI::Carp, the SEE ALSO section was cleaned up, thanks to Slaven Rezic. (RT#32769)
+ 5. The docs for redirect() were updated to reflect that most headers are
+ ignored during redirection. Thanks to Mark Stosberg (RT#44911)
+
+ [INTERNALS]
+ 1. New t/unescapeHTML.t test script has been added. It includes a TODO test for a pre-existing
+ bug which could use a patch. Thanks to Pete Gamache and Mark Stosberg (RT#39122)
+ 2. New test scripts have been added for user_agent(), popup_menu() and query_string(), scrolling_list() and Dump()
+ Thanks to Mark Stosberg and Stuart Johnston. (RT#37908, RT#43006, RT#21341, RT#30097)
+ 3. CGI::Carp and CGI::Util have been updated to have non-developer version numbers.
+ Thanks to Slaven Rezic. (RT#48425)
+ 4. CGI::Switch and CGI::Apache now properly set their VERSION in their own name space.
+ Thanks to Alexey Tourbin (RT#11941,RT#11942)
+
+Version 3.44, Jul 30, 2009
+
+ 1. Patch from Kurt Jaeger to allow HTTP PUT even if the content length is unknown.
+ 2. Patch from Pavel merdin to fix a problem for one of the FireFox addons.
+ 3. Fixed issue in mod_perl & fastCGI environment of cookies returned from
+ CGI->cookie() leaking from one session to another.
+
+Version 3.43, Apr 06, 2009
+
+ 1. Documentation patch from MARKSTOS@cpan.org to replace all occurrences of
+ "new CGI" with CGI->new()" to reflect best perl practices.
+ 2. Patch from Stepan Kasal to fix utf-8 related problems in perl 5.10
+
+Version 3.42, Sep 08, 2008
+
+ 1. Added patch from Renee Baecker that makes it possible to subclass
+ CGI::Pretty.
+ 2. Added patch from Nicholas Clark to allow ~ characters in temporary directories.
+ 3. Added patch from Renee Baecker that fixes the inappropriate escaping of fields
+ in multipart headers.
+
+Version 3.41, Aug 25, 2008
+
+ 1. Fix url() returning incorrect path when query string contains escaped newline.
+ 2. Added additional windows temporary directories and environment variables, courtesy patch from Renee Baecker
+ 3. Added a handle() method to the lightweight upload
+ filehandles. This method returns a real IO::Handle object.
+ 4. Added patch from Tony Vanlingen to fix deep recursion warnings in CGI::Pretty.
+
+Version 3.40, Aug 06, 2008
+
+ 1. Fixed CGI::Fast docs to eliminate references to a "special"
+ version of Perl.
+ 2. Makefile.PL now depends on FCGI so that CGI::Fast installs properly.
+ 3. Fix script_name() call from Stephane Chazelas.
+
+Version 3.39, Jun 29, 2008
+
+ 1. Fixed regression in "exists" function when using tied interface to CGI via $q->Vars.
+
+Version 3.38, Jun 25, 2008
+
+ 1. Fix annoying warning in http://rt.cpan.org/Ticket/Display.html?id=34551
+ 2. Added nobr() function http://rt.cpan.org/Ticket/Display.html?id=35377
+ 3. popup_menu() allows multiple items to be selected by default, satisfying
+ http://rt.cpan.org/Ticket/Display.html?id=35376
+ 4. Patch from Renee Backer to avoid doubled <http-equiv> headers.
+ 5. Fixed documentation bug that describes what happens when a
+ parameter is empty (e.g. "?test1=").
+ 6. Fixed minor warning described at http://rt.cpan.org/Public/Bug/Display.html?id=36435
+ 7. Fixed overlap of attribute and parameter space described in http://rt.perl.org/rt3//Ticket/Display.html?id=24294
+
+Version 3.37, Apr 22, 2008
+
+ 1. Fix pragmas so that they persist over modperl invocations (e.g. RT 34761)
+ 2. Fixed handling of chunked multipart uploads; thanks to Michael Bernhardt
+ who reported and fixed the problem.
+
+Version 3.36
+
+ 1. Fix CGI::Cookie to support cookies that are separated by "," instead of ";".
+
+Version 3.35, Mar 27, 2008
+
+ 1. Resync with bleadperl, primarily fixing a bug in parsing semicolons in uploaded filenames.
+
+Version 3.34, Mar 18, 2008
+
+ 1. Handle Unicode %uXXXX escapes properly -- patch from DANKOGAI@cpan.org
+ 2. Fix url() method to not choke on path names that contain regex characters.
+
+Version 3.33, Jan 02, 2008
+
+ 1. Remove uninit variable warning when calling url(-relative=>1)
+ 2. Fix uninit variable warnings for two lc calls
+ 3. Fixed failure of tempfile upload due to sprintf() taint failure in perl 5.10
+
+Version 3.32, Dec 27, 2007
+
+ 1. Patch from Miguel Santinho to prevent sending premature headers under mod_perl 2.0
+
+Version 3.31, Nov 30, 2007
+
+ 1. Patch from Xavier Robin so that CGI::Carp issues a 500 Status code rather than a 200 status code.
+ 2. Patch from Alexander Klink to select correct temporary directory in OSX Leopard so that upload works.
+ 3. Possibly fixed "wrapped pack" error on 5.10 and higher.
+
+Version 3.30
+
+ 1. Patch from Mike Barry to handle POSTDATA in the same way as PUT.
+ 2. Patch from Rafael Garcia-Suarez to correctly reencode unicode values as byte values.
+
+Version 3.29, Apr 16, 2007
+
+ 1. The position of file handles is now reset to zero when CGI->new is called.
+ (Mark Stosberg)
+ 2. uploadInfo() now works across multiple object instances. Also, the first
+ tests for uploadInfo() were added as part of the fix. (CPAN bug 11895, with
+ contributions from drfrench and Mark Stosberg).
+
+Version 3.28, Mar 29, 2007
+
+ 1. Applied patch from Allen Day that makes Cookie parsing RFC2109 compliant
+ (attribute/values can be separated by commas as well as semicolons).
+ 2. Applied patch from Stephan Struckmann that allows script_name() to be set correctly.
+ 3. Fixed problem with url(-full) in which port number appears twice.
+
+Version 3.27, Feb 27, 2007
+
+ 1. Applied patch from Steve Taylor that allows checkbox_groups to be
+ disabled with a new -disabled=> option.
+
+Version 3.26
+
+ 1. Fixed alternate stylesheet behavior so that it is insensitive to order of declarations.
+ 2. Patch from John Binns to allow users to provide a callback to CGI::Carp.
+ 3. Added "~" as an unreserved character in escape().
+ 4. Patch from Chris Fedde to prevent HTTP_HOST from inhibiting SERVER_PORT in url() generation.
+ 5. Fixed outdated documentation (and behavior) of -language in start_html -script option.
+ 6. Fixed bug in seconds calculation in CGI::Util::expire_calc.
+
+Version 3.25, Sep 28, 2006
+
+ 1. Fixed the link to the Netscape frames page.
+ 2. Added ability to specify an alternate stylesheet.
+ 3. Add support for XForms POST submssion both as application/xml or as multipart/related
+
+Version 3.24
+
+ 1. In startform(), if request_uri() returns undef, then falls back
+ to self_url(). This should rarely happen except when run outside of
+ the CGI environment.
+ 2. image button alignment options were mistakenly being capitalized, causing xhtml validation to fail.
+
+Version 3.23, Aug 23, 2006
+
+ 1. Typo in upload() persisted, now fixed for real. Thanks to
+ Emanuele Zeppieri for correct patch and regression test.
+
+Version 3.22, Aug 23, 2006
+
+ 1. Typo in upload() function broke uploads. Now fixed (CPAN bug 21126).
+
+Version 3.21, Aug 21, 2006
+
+ 1. Don't try to read data at all when POST > $POST_MAX.
+ 2. Fixed bug that caused $cgi->param('name',undef,'value') to unset param('name') entirely.
+ 3. Fixed bug in which upload() sometimes returns empty. (CPAN bug #12694).
+ 4. Incorporated patch from BURAK@cpan.org to support HTTPcookies (CPAN bug 21019).
+
+Version 3.20
+
+ 1. Patch from David Wheeler for CGI::Cookie->bake(). Uses mod_perl headers_out->add()
+ rather than headers_out->set().
+ 2. Fixed problem identified by Andrei Voronkov in which start_form() output was screwed
+ up when initial argument begins with a dash and subsequent arguments do not.
+ 3. Quashed uninitialized variable warnings coming from script_name(), url() and other
+ functions that require access to the PATH_INFO environment variable.
+
+Version 3.19
+
+ 1. Added patch from Stephen Frost that allows one to suppress use of the temp file that is
+ created during uploads.
+ 2. Fixed problem noted by Martin Foster in which regular expression meta-character terms
+ in the path information were not quoted, causing URL parsing
+ to fail on URLs that contained metacharacters (such as +).
+ 3. More fixes to the url() method.
+ 4. Removed "hack to fix broken PATH_INFO in MSII".
+
+Version 3.18
+
+ 1. Doc typo fixes.
+ 2. Patch from Steve Peters to default the document type to match the charset.
+ 3. Fixed param() so that param(-name=>'foo',-values=>[]) sets the parameter to empty list.
+
+Version 3.17, Feb 24, 2006
+
+ 1. Added patch from Mike Hanafey which caused 0 arguments to CGI::Cookie->new() to
+ be treated as empty.
+ 2. Patch to CGI::Carp from Peter Whaite to fix the unfixable problem of CGI::Carp
+ not behaving correctly in an eval() context.
+ 3. CGI::Fast->new() calls CGI->_reset_globals to avoid contamination of one session
+ with another's variables.
+ 4. Fixed upload failure on files that contain semicolons in their names.
+
+Version 3.16, Feb 8, 2006
+
+ 1. header() -charset option now works even when the MIME type is not "text".
+ 2. Fixed documentation for cookie() function and fastCGI.
+ 3. Upload filehandles now only closed automatically on Windows systems.
+ 4. Apache::Cookie compatibility fix from David Wheeler
+ 5. CGI::Carp->fatalsToBrowser() does not work correctly with
+ mod_perl 2. No workaround is known.
+ 6. Fixed text status code associated with 302 redirects. Should be "Found"
+ but was "Moved".
+ 7. Fixed charset in start_html() and header() to be in synch.
+
+Version 3.15, Dec 7, 2005
+
+ 1. Remove extraneous "?" from self_url() when URI contains a ? but no query string.
+
+Version 3.14, Dec 6, 2005
+
+ 1. Fixed broken scrolling_list() select attribute.
+
+Version 3.13, Dec 4, 2005
+
+ 1. Removed extraneous empty "?" from end of self_url().
+
+Version 3.12, Dec 4, 2005
+
+ 1. Fixed virtual_port so that it works properly with https protocol.
+ 2. Fixed documentation for upload_hook().
+ 3. Added POSTDATA documentation.
+ 4. Made upload_hook() work in function-oriented mode.
+ 5. Fixed POST_MAX behavior so that it doesn't cause client to hang.
+ 6. Disabled automatic tab indexes and added new -tabindex pragma to
+ turn automatic indexes back on.
+ 7. The url() and self_url() methods now work better in the context of Apache
+ mod_rewrite. Be advised that path_info() may give you confusing results
+ when mod_rewrite is active because Apache calculates the path info *after*
+ rewriting. This is mostly worked around in url() and self_url(), but you
+ may notice some anomalies.
+ 8. Removed empty (and non-validating) <div> from code emitted by end_form().
+ 9. Fixed CGI::Carp to work correctly with Mod_perl 1.29 in an Apache 2 environment.
+ 10. Setting $CGI::TMPDIRECTORY should now be effective.
+
+Version 3.11, Aug 3, 2005
+
+ 1. Killed warning in CGI::Cookie about MOD_PERL_API_VERSION
+ 2. Fixed append() so that it works in function mode.
+ 3. Workaround for a bug that appears in Apache2 versions through 2.0.54
+ in which SCRIPT_NAME and PATH_INFO are incorrect if the additional path_info
+ contains a double slash. This workaround will handle the common case of
+ http://mysite.com/cgi-bin/log.cgi/http://www.some.other.site/args, but will
+ not handle the uncommon case of a ScriptAlias directive that adds additional
+ path information to the end of the translated URI.
+
+Version 3.10, May 13, 2005
+
+ 1. Added Apache2::RequestIO, which is necessary for mp2 interoperability.
+
+Version 3.09, May 5, 2005
+
+ 1. Fixed tabindex="0" when using CGI to create forms without a prior start_html
+ 2. Removed warning about non-numeric MOD_PERL_API_VERSION.
+
+Version 3.08, Apr 20, 2005
+
+ 1. update support for mod_perl 2.0. versions prior to
+ mod_perl 1.999_22 (2.0.0-RC5) are no longer supported.
+
+Version 3.07, Mar 14, 2005
+
+ 1. Fixed typo in mod_perl detection.
+
+Version 3.06, Mar 09, 2005
+
+ 1. Fixed bare call to script() in start_html
+ 2. Moved Fh::DESTROY out of autoloaded functions so as to avoid
+ clobbering $@ when CGI functions are executed in an eval{}
+ context.
+ 3. mod_perl 2.0 version detection patch in CGI::Cookie provided by
+ Allen Day.
+ 4. autoEscape() flag is now respected when generating extra
+ attributes.
+ 5. Tests for *tag start/end generation from Shlomi Fish.
+ 6. Support for can() method provided by Ron Savage.
+ 7. Fix for lang='' when outputting XHTML.
+ 8. Added support for chunked transfer encoding, as suggested by
+ Hakan Ardo
+ 9. Fixed clobbering of row and column headers in tableized radio
+ and checkbox groups, as reported by Nicolas Thierry-Mieg.
+ 10. <Label> tags are now associated with form elements, as suggested
+ by accessibility guidelines.
+ 11. The <?xml> directive produced by start_html is now turned off by
+ default and the charset is specified in a <meta> directive. Apparently
+ IE6 (and maybe some versions of Opera) were getting confused by this.
+ 12. Support for tab indexes.
+ 13. Retired the HTML docs. The POD docs are now primary documentation.
+ 14. CGI::Carp now correctly detects and handles Apache::Dispatch.
+ 15. CGI::Util::utf8_chr now correctly sets the UTF8 flag on 5.006 or
+ higher perls (fix courtesy Slaven Rezic).
+
+
+Version 3.05, Apr 12, 2004
+
+ 1. Fixed uninitialized variable warning on start_form() when running
+ from command line.
+ 2. Fixed CGI::_set_attributes so that attributes with a - are handled
+ correctly.
+ 3. Fixed CGI::Carp::die() so as to avoid problems from _longmess()
+ clobbering @_.
+ 4. If HTTP_X_FORWARDED_HOST is defined (i.e. running under a proxy),
+ the various functions that return HOST will use that instead.
+ 5. Fix for undefined utf8() call in CGI::Util.
+ 6. Changed the call to warningsToBrowser() in
+ CGI::Carp::fatalsToBrowser to call only after HTTP header is sent
+ (thanks to Didier Lebrun for noticing).
+ 7. Patches from Dan Harkless to make CGI.pm validatable against HTML
+ 3.2.
+ 8. Fixed an extraneous "foo=bar" appearing when extra style
+ parameters passed to start_html;
+ 9. Fixed cross-site scripting bug in startform() pointed out by Dan
+ Harkless.
+ 10. Fixed documentation to discuss list context behavior of
+ form-element generators explicitly.
+ 11. Fixed incorrect results from end_form() when called in OO manner.
+ 12. Fixed query string stripping in order to handle URLs containing
+ escaped newlines.
+ 13. During server push, set NPH to 0 rather than 1. This is supposed
+ to fix problems with Apache.
+ 14. Fixed incorrect processing of multipart form fields that contain
+ embedded quotes. There's still the issue of how to handle ones
+ that contain embedded semicolons, but no one has complained (yet).
+ 15. Fixed documentation bug in -style argument to start_html()
+ 16. Added -status argument to redirect().
+
+Version 3.04, Jan 18, 2004
+
+ 1. Fixed the problem with mod_perl crashing when "defaults" button
+ pressed.
+
+Version 3.03, Jan 13, 2004
+
+ 1. Fix upload hook functionality
+ 2. Workaround for CGI->unescape_html()
+ 3. Bumped version numbers in CGI::Fast and CGI::Util for 5.8.3-tobe
+
+Version 3.02
+
+ 1. Bring in Apache::Response just in case.
+ 2. File upload on EBCDIC systems now works.
+
+Version 3.01, Dec 10, 2003
+
+ 1. No fix yet for upload failures when running on EBCDIC server.
+ 2. Fixed uninitialized glob warnings that appeared when file
+ uploading under perl 5.8.2.
+ 3. Added patch from Schlomi Fish to allow debugging of PATH_INFO from
+ command line.
+ 4. Added patch from Steve Hay to correctly unlink tmp files under
+ mod_perl/windows
+ 5. Added upload_hook functionality from Jamie LeTaul
+ 6. Workarounds for mod_perl 2 IO issues. Check that file upload and
+ state saving still working.
+ 7. Added code for underreads.
+ 8. Fixed misleading description of redirect() and relative URLs in
+ the POD docs.
+ 9. Workaround for weird interaction of CGI::Carp with Safe module
+ reported by William McKee.
+ 10. Added patches from Ilmari Karonen to improve behavior of
+ CGI::Carp.
+ 11. Fixed documentation error in -style argument.
+ 12. Added virtual_port() method for finding out what port server is
+ listening on in a virtual-host aware fashion.
+
+Version 3.00, Aug 18, 2003
+
+ 1. Patch from Randal Schwartz to fix bug introduced by cross-site
+ scripting vulnerability "fix."
+ 2. Patch from JFreeman to replace UTF-8 escape constant of 0xfe with
+ 0xfc. Hope this is right!
+
+ Version 2.99
+
+ 1. Patch from Steve Hay to fix extra Content-type: appearing on
+ browser screen when FatalsToBrowser invoked.
+ 2. Patch from Ewann Corvellec to fix cross-site scripting
+ vulnerability.
+ 3. Fixed tmpdir routine for file uploading to solve problem that
+ occurs under mod_perl when tmpdir is writable at startup time, but
+ not at session time.
+
+ Version 2.98
+
+ 1. Fixed crash in Dump() function.
+
+ Version 2.97
+
+ 1. Sigh. Uploaded wrong 2.96 to CPAN.
+
+ Version 2.96
+
+ 1. More bugfixes to the -style argument.
+
+ Version 2.95
+
+ 1. Fixed bugs in start_html(-style=>...) support introduced in 2.94.
+
+ Version 2.94
+
+ 1. Removed warning from reset() method.
+ 2. Moved
+
+ and tags into the :html3 group. Hope this removes undefined CGI::Area
+ errors.
+
+ Changed CGI::Carp to play with mod_perl2 and to (hopefully) restore
+ reporting of compile-time errors.
+
+ Fixed potential deadlock between web server and CGI.pm when aborting
+ a read due to POST_MAX (reported by Antti Lankila).
+
+ Fixed issue with tag-generating function not incorporating content
+ when first variable undef.
+
+ Fixed cross-site scripting bug reported by obscure.
+
+ Fixed Dump() function to return correctly formed XHTML - bug
+ reported by Ralph Siemsen.
+
+ Version 2.93
+
+ 1. Fixed embarassing bug in mp1 support.
+
+ Version 2.92
+
+ 1. Fix to be P3P compliant submitted from MPREWITT.
+ 2. Added CGI->r() API for mod_perl1/mod_perl2.
+ 3. Fixed bug in redirect() that was corrupting cookies.
+ 4. Minor fix to behavior of reset() button to make it consistent with
+ submit() button (first time this has been changed in 9 years).
+ 5. Patch from Dan Kogai to handle UTF-8 correctly in 5.8 and higher.
+ 6. Patch from Steve Hay to make CGI::Carp's error messages appear on
+ MSIE browsers.
+ 7. Added Yair Lenga's patch for non-urlencoded postings.
+ 8. Added Stas Bekman's patches for mod_perl 2 compatibility.
+ 9. Fixed uninitialized escape behavior submitted by William Campbell.
+ 10. Fixed tied behavior so that you can pass arguments to tie()
+ 11. Fixed incorrect generation of URLs when the path_info contains +
+ and other odd characters.
+ 12. Fixed redirect(-cookies=>$cookie) problem.
+ 13. Fixed tag generation bug that affects -javascript passed to
+ start_html().
+
+ Version 2.91
+
+ 1. Attribute generation now correctly respects the value of
+ autoEscape().
+ 2. Fixed endofrm() syntax error introduced by Ben Edgington's patch.
+
+ Version 2.90
+
+ 1. Fixed bug in redirect header handling.
+ 2. Added P3P option to header().
+ 3. Patches from Alexey Mahotkin to make CGI::Carp work correctly with
+ object-oriented exceptions.
+ 4. Removed inaccurate description of how to set multiple cookies from
+ CGI::Cookie pod file.
+ 5. Patch from Kevin Mahony to prevent running out of filehandles when
+ uploading lots of files.
+ 6. Documentation enhancement from Mark Fisher to note that the
+ import_names() method transforms the parameter names into valid
+ Perl names.
+ 7. Patch from Dan Harkless to suppress lang attribute in <html> tag
+ if specified as a null string.
+ 8. Patch from Ben Edgington to fix broken XHTML-transitional 1.0
+ validation on endform().
+ 9. Custom html header fix from Steffen Beyer (first letter correctly
+ upcased now)
+ 10. Added a -verbatim option to stylesheet generation from Michael
+ Dickson
+ 11. Faster delete() method from Neelam Gupta
+ 12. Fixed broken Cygwin support.
+ 13. Added empty charset support from Bradley Baetz
+ 14. Patches from Doug Perham and Kevin Mahoney to fix file upload
+ failures when uploaded file is a multiple of 4096.
+
+ Version 2.89
+
+ 1. Fixed behavior of ACTION tag when POSTING to a URL that has a
+ query string.
+ 2. Added Patch from Michael Rommel to handle multipart/mixed uploads
+ from Opera
+
+ Version 2.88
+
+ 1. Fixed problem with uploads being refused under Perl 5.8 when under
+ Taint mode.
+ 2. Fixed uninitialized variable warnings under Perl 5.8.
+ 3. Fixed CGI::Pretty regression test failures.
+
+ Version 2.87
+
+ 1. Security hole patched: when processing multipart/form-data
+ postings, most arguments were being untainted silently. Returned
+ arguments are now tainted correctly. This may cause some scripts
+ to fail that used to work (thanks to Nick Cleaton for pointing
+ this out and persisting until it was fixed).
+ 2. Update for mod_perl 2.0.
+ 3. Pragmas such as -no_xhtml are now respected in mod_perl
+ environment.
+
+ Version 2.86
+
+ 1. Fixes for broken CGI::Cookie expiration dates introduced in 2.84.
+
+ Version 2.85
+
+ 1. Fix for broken autoEscape function introduced in 2.84.
+
+ Version 2.84
+
+ 1. Fix for failed file uploads on Cygwin platforms.
+ 2. HTML escaping code now replaced 0x8b and 0x9b with unicode
+ references < and *#8250;
+
+ Version 2.83
+
+ 1. Fixed autoEscape() documentation inconsistencies.
+ 2. Patch from Ville Skytt� to fix a number of XHTML inconsistencies.
+ 3. Added Max-Age to list of CGI::Cookie headers.
+
+ Version 2.82
+
+ 1. Patch from Rudolf Troller to add attribute setting and option
+ groups to form fields.
+ 2. Patch from Simon Perreault for silent crashes when using CGI::Carp
+ under mod_perl.
+ 3. Patch from Scott Gifford allows you to set the program name for
+ CGI::Carp.
+
+ Version 2.81
+
+ 1. Removed extraneous slash from end of stylesheet tags generated by
+ start_html in non-XHTML mode.
+ 2. Changed behavior of CGI::Carp with respect to eval{} contexts so
+ that output behaves properly in mod_perl environments.
+ 3. Fixed default DTD so that it validates with W3C validator.
+
+ Version 2.80
+
+ 1. Fixed broken messages in CGI::Carp.
+ 2. Changed checked="1" to checked="checked" for real XHTML
+ compatibility.
+ 3. Resurrected REQUEST_URI code so that url() works correctly with
+ multiviews.
+
+ Version 2.79
+
+ 1. Changes to CGI::Carp to avoid "subroutine redefined" error
+ messages.
+ 2. Default DTD is now XHTML 1.0 Transitional
+ 3. Patches to support all HTML4 tags.
+
+ Version 2.78
+
+ 1. Added ability to change encoding in <?xml> assertion.
+ 2. Fixed the old escapeHTML('CGI') ne "CGI" bug
+ 3. In accordance with XHTML requirements, there are no longer any
+ minimized attributes, such as "checked".
+ 4. Patched bug which caused file uploads of exactly 4096 bytes to be
+ truncated to 4094 (thanks to Kevin Mahony)
+ 5. New tests and fixes to CGI::Pretty (thanks to Michael Schwern).
+
+ Version 2.77
+
+ 1. No new features, but released in order to fix an apparent CPAN
+ bug.
+
+ Version 2.76
+
+ 1. New esc.t regression test for EBCDIC translations courtesy Peter
+ Prymmer.
+ 2. Patches from James Jurach to make compatible with FCGI-ProcManager
+ 3. Additional fields passed to header() (like -Content_disposition)
+ now honor initial capitalization.
+ 4. Patch from Andrew McNaughton to handle utf-8 escapes (%uXXXX
+ codes) in URLs.
+
+ Version 2.752
+
+ 1. Syntax error in the autoloaded Fh::new() subroutine.
+ 2. Better error reporting in autoloaded functions.
+
+ Version 2.751
+
+ 1. Tiny tweak to filename regular expression function on line 3355.
+
+ Version 2.75
+
+ 1. Fixed bug in server push boundary strings (CGI.pm and CGI::Push).
+ 2. Fixed bug that occurs when uploading files with funny characters
+ in the name
+ 3. Fixed non-XHTML-compliant attributes produced by textfield()
+ 4. Added EPOC support, courtesy Olaf Flebbe
+ 5. Fixed minor XHTML bugs.
+ 6. Made escape() and unescape() symmetric with respect to EBCDIC,
+ courtesy Roca, Ignasi <ignasi.roca@fujitsu.siemens.es>
+ 7. Removed uninitialized variable warning from CGI::Cookie, provided
+ by Atipat Rojnuckarin <rojnuca@yahoo.com>
+ 8. Fixed bug in CGI::Pretty that causes it to print partial end tags
+ when the $INDENT global is changed.
+ 9. Single quotes are changed to character entity ' for compatibility
+ with URLs.
+
+ Version 2.74
+
+ September 13, 2000
+ 1. Quashed one-character bug that caused CGI.pm to fail on file
+ uploads.
+
+ Version 2.73
+
+ September 12, 2000
+ 1. Added -base to the list of arguments accepted by url().
+ 2. Fixes to XHTML support.
+ 3. POST parameters no longer show up in the Location box.
+
+ Version 2.72
+
+ August 19, 2000
+ 1. Fixed the defaults button so that it works again
+ 2. Charset is now correctly saved and restored when saving to files
+ 3. url() now works correctly when given scripts with %20 and other
+ escapes in the additional path info. This undoes a patch
+ introduced in version 2.47 that I no longer understand the
+ rationale for.
+
+ Version 2.71
+
+ August 13, 2000
+ 1. Newlines in the value attributes of hidden fields and other form
+ elements are now escaped when using ISO-Latin.
+ 2. Inline script and style sections are now protected as CDATA
+ sections when XHTML mode is on (the default).
+
+ Version 2.70
+
+ August 4, 2000
+ 1. Fixed bug in scrolling_list() which omitted a space in front of
+ the "multiple" attribute.
+ 2. Squashed the "useless use of string in void context" message from
+ redirects.
+
+ Version 2.69
+
+ 1. startform() now creates default ACTION for POSTs as well as GETs.
+ This may break some browsers, but it no longer violates the HTML
+ spec.
+ 2. CGI.pm now emits XHTML by default. Disable with -no_xhtml.
+ 3. We no longer interpret &#ddd sequences in non-latin character
+ sets.
+
+ Version 2.68
+
+ 1. No longer attempts to escape characters when dealing with non
+ ISO-8861 character sets.
+ 2. checkbox() function now defaults to using -value as its label,
+ rather than -name. The current behavior is what has been
+ documented from the beginning.
+ 3. -style accepts array reference to incorporate multiple stylesheets
+ into document.
+
+ 1. Fixed two bugs that caused the -compile pragma to fail with a
+ syntax error.
+
+ Version 2.67
+
+ 1. Added XHTML support (incomplete; tags need to be lowercased).
+ 2. Fixed CGI/Carp when running under mod_perl. Probably broke in
+ other contexts.
+ 3. Fixed problems when passing multiple cookies.
+ 4. Suppress warnings from _tableize() that were appearing when using
+ -w switch with radio_group() and checkbox_group().
+ 5. Support for the header() -attachment argument, which can give
+ pages a default file name when saving to disk.
+
+ Version 2.66
+
+ 1. 2.65 changes in make_attributes() broke HTTP header functions
+ (including redirect), so made it context sensitive.
+
+ Version 2.65
+
+ 1. Fixed regression tests to skip tests that require implicit fork on
+ machines without fork().
+ 2. Changed make_attributes() to automatically escape any HTML
+ reserved characters.
+ 3. Minor documentation fix in javascript example.
+
+ Version 2.64
+
+ 1. Changes introduced in 2.63 broke param() when retrieving parameter
+ lists containing only a single argument. This is now fixed.
+ 2. self_url() now defaults to returning parameters delimited with
+ semicolon. Use the pragma -oldstyle_urls to get the old "&"
+ delimiter.
+
+ Version 2.63
+
+ 1. Fixed CGI::Push to pull out parameters correctly.
+ 2. Fixed redirect() so that it works with default character set
+ 3. Changed param() so as to returned empty string '' when referring
+ to variables passed in query strings like 'name1=&name2'
+
+ Version 2.62
+
+ 1. Fixed broken ReadParse() function, and added regression tests
+ 2. Fixed broken CGI::Pretty, and added regression tests
+
+ Version 2.61
+
+ 1. Moved more functions from CGI.pm proper into CGI/Util.pm.
+ CGI/Cookie should now be standalone.
+ 2. Disabled per-user temporary directories, which were causing grief.
+
+ Version 2.60
+
+ 1. Fixed junk appearing in autogenerated HTML functions when using
+ object-oriented mode.
+
+ Version 2.59
+
+ 1. autoescape functionality breaks too much existing code, removed
+ it.
+ 2. use escapeHTML() manually
+
+ Version 2.58
+
+ This is the release version of 2.57.
+
+ Version 2.57
+
+ 1. Added -debug pragma and turned off auto reading of STDIN.
+ 2. Default DTD updated to HTML 4.01 transitional.
+ 3. Added charset() method and the -charset argument to header().
+ 4. Fixed behavior of escapeHTML() to respect charset() and to escape
+ nasty Windows characters (thanks to Tom Christiansen).
+ 5. Handle REDIRECT_QUERY_STRING correctly.
+ 6. Removed use_named_parameters() because of dependency problems and
+ general lameness.
+ 7. Fixed problems with bad HREF links generated by url(-relative=>1)
+ when the url is like /people/.
+ 8. Silenced a warning on upload (patch provided by Jonas Liljegren)
+ 9. Fixed race condition in CGI::Carp when errors occur during parsing
+ (patch provided by Maurice Aubrey).
+ 10. Fixed failure of url(-path_info=>1) when path contains % signs.
+ 11. Fixed warning from CGI::Cookie when receiving foreign cookies that
+ don't use name=value format.
+ 12. Fixed incompatibilities with file uploading on VMS systems.
+
+ Version 2.56
+
+ 1. Fixed bugs in file upload introduced in version 2.55
+ 2. Fixed long-standing bug that prevented two files with identical
+ names from being uploaded.
+
+ Version 2.55
+
+ 1. Fixed cookie regression test so as not to produce an error.
+ 2. Fixed path_info() and self_url() to work correctly together when
+ path_info() modified.
+ 3. Removed manify warnings from CGI::{Switch,Apache}.
+
+ Version 2.54
+
+ 1. This will be the last release of the monolithic CGI.pm module.
+ Later versions will be modularized and optimized.
+ 2. DOMAIN tag no longer added to cookies by default. This will break
+ some versions of Internet Explorer, but will avoid breaking
+ networks which use host tables without fully qualified domain
+ names. For compatibility, please always add the -domain tag when
+ creating cookies.
+ 3. Fixed escape() method so that +'s are treated correctly.
+ 4. Updated CGI::Pretty module.
+
+ Version 2.53
+
+ 1. Forgot to upgrade regression tests before releasing 2.52. NOTHING
+ ELSE HAS CHANGED IN LIBRARY
+
+ Version 2.52
+
+ 1. Spurious newline in checkbox() routine removed. (courtesy John
+ Essen)
+ 2. TEXTAREA linebreaks now respected in dump() routine. (courtesy
+ John Essen)
+ 3. Patches for DOS ports (courtesy Robert Davies)
+ 4. Patches for VMS
+ 5. More fixes for cookie problems
+ 6. Fix CGI::Carp so that it doesn't affect eval{} blocks (courtesy
+ Byron Brummer)
+
+ Version 2.51
+
+ 1. Fixed problems with cookies not being remembered when sent to IE
+ 5.0 (and Netscape 5.0 too?)
+ 2. Numerous HTML compliance problems in cgi_docs.html; fixed thanks
+ to Michael Leahy
+
+ Version 2.50
+
+ 1. Added a new Vars() method to retrieve all parameters as a tied
+ hash.
+ 2. Untainted tainted tempfile name so that script doesn't fail on
+ terminal unlink.
+ 3. Made picking of upload tempfile name more intelligent so that
+ doesn't fail in case of name collision.
+ 4. Fixed handling of expire times when passed an absolute timestamp.
+ 5. Changed dump() to Dump() to avoid name clashes.
+
+ Version 2.49
+
+ 1. Fixes for FastCGI (globals not getting reset)
+ 2. Fixed url() to correctly handle query string and path under
+ MOD_PERL
+
+ Version 2.48
+
+ 1. Reverted detection of MOD_PERL to avoid breaking PerlEX.
+
+ Version 2.47
+
+ 1. Patch to fix file upload bug appearing in IE 3.01 for
+ Macintosh/PowerPC.
+ 2. Replaced use of $ENV{SCRIPT_NAME} with $ENV{REQUEST_URI} when
+ running under Apache, to fix self-referencing URIs.
+ 3. Fixed bug in escapeHTML() which caused certain constructs, such as
+ CGI->image_button(), to fail.
+ 4. Fixed bug which caused strong('CGI') to fail. Be careful to use
+ CGI::strong('CGI') and not CGI->strong('CGI'). The latter will
+ produce confusing results.
+ 5. Added upload() function, as a preferred replacement for the
+ "filehandle as string" feature.
+ 6. Added cgi_error() function.
+ 7. Rewrote file upload handling to return undef rather than dieing
+ when an error is encountered. Be sure to call cgi_error() to find
+ out what went wrong.
+
+ Version 2.46
+
+ 1. Fix for failure of the "include" tests under mod_perl
+ 2. Added end_multipart_form to prevent failures during qw(-compile
+ :all)
+
+ Version 2.45
+
+ 1. Multiple small documentation fixes
+ 2. CGI::Pretty didn't get into 2.44. Fixed now.
+
+ Version 2.44
+
+ 1. Fixed file descriptor leak in upload function.
+ 2. Fixed bug in header() that prevented fields from containing double
+ quotes.
+ 3. Added Brian Paulsen's CGI::Pretty package for pretty-printing
+ output HTML.
+ 4. Removed CGI::Apache and CGI::Switch from the distribution.
+ 5. Generated start_* shortcuts so that start_table(), end_table(),
+ start_ol(), end_ol(), and so forth now work (see the docs on how
+ to enable this feature).
+ 6. Changed accept() to Accept(), sub() to Sub(). There's still a
+ conflict with reset(), but this will break too many existing
+ scripts!
+
+ Version 2.43
+
+ 1. Fixed problem with "use strict" and file uploads (thanks to Peter
+ Haworth)
+ 2. Fixed problem with not MSIE 3.01 for the power_mac not doing file
+ uploads right.
+ 3. Fixed problem with file upload on IIS 4.0 when authorization in
+ use.
+ 4. -content_type and '-content-type' can now be provided to header()
+ as synonyms for -type.
+ 5. CGI::Carp now escapes the ampersand BEFORE escaping the > and <
+ signs.
+ 6. Fixed "not an array reference" error when passing a hash reference
+ to radio_group().
+ 7. Fixed non-removal of uploaded TMP files on NT platforms which
+ occurs when server runs on non-C drive (thanks to Steve Kilbane
+ for finding this one).
+
+ Version 2.42
+
+ 1. Too many screams of anguish at changed behavior of url(). Is now
+ back to its old behavior by default, with options to generate all
+ the variants.
+ 2. Added regression tests. "make test" now works.
+ 3. Documentation fixes.
+ 4. Fixes for Macintosh uploads, but uploads STILL do not work pending
+ changes to MacPerl.
+
+ Version 2.41
+
+ 1. url() method now includes the path info. Use script_name() to get
+ it without path info().
+ 2. Changed handling of empty attributes in HTML tag generation. Be
+ warned! Use table({-border=>undef}) rather than
+ table({-border=>''}).
+ 3. Changes to allow uploaded filenames to be compared to other
+ strings with "eq", "cmp" and "ne".
+ 4. Changes to allow CGI.pm to coexist more peacefully with
+ ActiveState PerlEX.
+ 5. Changes to prevent exported variables from clashing when importing
+ ":all" set in combination with cookies.
+
+ Version 2.40
+
+ 1. CGI::Carp patched to work better with mod_perl (thanks to Chris
+ Dean).
+ 2. Uploads of files whose names begin with numbers or the Windows
+ \\UNC\shared\file nomenclature should no longer fail.
+ 3. The <STYLE> tag (for cascading style sheets) now generates the
+ required TYPE attribute.
+ 4. Server push primitives added, thanks to Ed Jordan.
+ 5. Table and other HTML3 functions are now part of the :standard set.
+ 6. Small documentation fixes.
+
+ TO DO:
+ 1. Do something about the DTD mess. The module should generate
+ correct DTDs, or at least offer the programmer a way to specify
+ the correct one.
+ 2. Split CGI.pm into CGI processing and HTML-generating modules.
+ 3. More robust file upload (?still not working on the Macintosh?).
+ 4. Bring in all the HTML4 functionality, particular the accessibility
+ features.
+
+ Version 2.39
+
+ 1. file uploads failing because of VMS patch; fixed.
+ 2. -dtd parameter was not being properly processed.
+
+ Version 2.38
+
+ I finally got tired of all the 2.37 betas and released 2.38. The main
+ difference between this version and the last 2.37 beta (2.37b30) are
+ some fixes for VMS. This should allow file upload to work properly on
+ all VMS Web servers.
+
+ Version 2.37, various beta versions
+
+ 1. Added a CGI::Cookie::parse() method for lucky mod_perl users.
+ 2. No longer need separate -values and -labels arguments for
+ multi-valued form elements.
+ 3. Added better interface to raw cookies (fix courtesy Ken Fox,
+ kfox@ford.com)
+ 4. Added param_fetch() function for direct access to parameter list.
+ 5. Fix to checkbox() to allow for multi-valued single checkboxes
+ (weird problem).
+ 6. Added a compile() method for those who want to compile without
+ importing.
+ 7. Documented the import pragmas a little better.
+ 8. Added a -compile switch to the use clause for the long-suffering
+ mod_perl and Perl compiler users.
+ 9. Fixed initialization routines so that FileHandle and type globs
+ work correctly (and hash initialization doesn't fail!).
+ 10. Better deletion of temporary files on NT systems.
+ 11. Added documentation on escape(), unescape(), unescapeHTML() and
+ unescapeHTML() subroutines.
+ 12. Added documentation on creating subclasses.
+ 13. Fixed problem when calling $self->SUPER::foo() from inheriting
+ subclasses.
+ 14. Fixed problem using filehandles from within subroutines.
+ 15. Fixed inability to use the string "CGI" as a parameter.
+ 16. Fixed exponentially growing $FILLUNIT bug
+ 17. Check for undef filehandle in read_from_client()
+ 18. Now requires the UNIVERSAL.pm module, present in Perl 5.003_7 or
+ higher.
+ 19. Fixed problem with uppercase-only parameters being ignored.
+ 20. Fixed vanishing cookie problem.
+ 21. Fixed warning in initialize_globals() under mod_perl.
+ 22. File uploads from Macintosh versions of MSIE should now work.
+ 23. Pragmas now preceded by dashes (-nph) rather than colons (:nph).
+ Old style is supported for backward compatibility.
+ 24. Can now pass arguments to all functions using {} brackets,
+ resolving historical inconsistencies.
+ 25. Removed autoloader warnings about absent MultipartBuffer::DESTROY.
+ 26. Fixed non-sticky checkbox() when -name used without -value.
+ 27. Hack to fix path_info() in IIS 2.0. Doesn't help with IIS 3.0.
+ 28. Parameter syntax for debugging from command line now more
+ straightforward.
+ 29. Added $DISABLE_UPLOAD to disable file uploads.
+ 30. Added $POST_MAX to error out if POSTings exceed some ceiling.
+ 31. Fixed url_param(), which wasn't working at all.
+ 32. Fixed variable suicide problem in s///e expressions, where the
+ autoloader was needed during evaluation.
+ 33. Removed excess spaces between elements of checkbox and radio
+ groups
+ 34. Can now create "valueless" submit buttons
+ 35. Can now set path_info as well as read it.
+ 36. ReadParse() now returns a useful function result.
+ 37. import_names() now allows you to optionally clear out the
+ namespace before importing (for mod_perl users)
+ 38. Made it possible to have a popup menu or radio button with a value
+ of "0".
+ 39. link() changed to Link() to avoid overriding native link function.
+ 40. Takes advantage of mod_perl's register_cleanup() function to clear
+ globals.
+ 41. <LAYER> and <ILAYER> added to :html3 functions.
+ 42. Fixed problems with private tempfiles and NT/IIS systems.
+ 43. No longer prints the DTD by default (I bet no one will complain).
+ 44. Allow underscores to replace internal hyphens in parameter names.
+ 45. CGI::Push supports heterogeneous MIME types and adjustable delays
+ between pages.
+ 46. url_param() method added for retrieving URL parameters even when a
+ fill-out form is POSTed.
+ 47. Got rid of warnings when radio_group() is called.
+ 48. Cookies now moved to their very own module.
+ 49. Fixed documentation bug in CGI::Fast.
+ 50. Added a :no_debug pragma to the import list.
+
+ Version 2.36
+
+ 1. Expanded JavaScript functionality
+ 2. Preliminary support for cascading stylesheets
+ 3. Security fixes for file uploads:
+ + Module will bail out if its temporary file already exists
+ + Temporary files can now be made completely private to avoid
+ peeking by other users or CGI scripts.
+ 4. use CGI qw/:nph/ wasn't working correctly. Now it is.
+ 5. Cookie and HTTP date formats didn't meet spec. Thanks to Mark
+ Fisher (fisherm@indy.tce.com) for catching and fixing this.
+
+ p
+
+ Version 2.35
+
+ 1. Robustified multipart file upload against incorrect syntax in
+ POST.
+ 2. Fixed more problems with mod_perl.
+ 3. Added -noScript parameter to start_html().
+ 4. Documentation fixes.
+
+ Version 2.34
+
+ 1. Stupid typo fix
+
+ Version 2.33
+
+ 1. Fixed a warning about an undefined environment variable.
+ 2. Doug's patch for redirect() under mod_perl
+ 3. Partial fix for busted inheritence from CGI::Apache
+ 4. Documentation fixes.
+
+ Version 2.32
+
+ 1. Improved support for Apache's mod_perl.
+ 2. Changes to better support inheritance.
+ 3. Support for OS/2.
+
+ Version 2.31
+
+ 1. New uploadInfo() method to obtain header information from uploaded
+ files.
+ 2. cookie() without any arguments returns all the cookies passed to a
+ script.
+ 3. Removed annoying warnings about $ENV{NPH} when running with the -w
+ switch.
+ 4. Removed operator overloading throughout to make compatible with
+ new versions of perl.
+ 5. -expires now implies the -date header, to avoid clock skew.
+ 6. WebSite passes cookies in $ENV{COOKIE} rather than
+ $ENV{HTTP_COOKIE}. We now handle this, even though it's O'Reilly's
+ fault.
+ 7. Tested successfully against new sfio I/O layer.
+ 8. Documentation fixes.
+
+ Version 2.30
+
+ 1. Automatic detection of operating system at load time.
+ 2. Changed select() function to Select() in order to avoid conflict
+ with Perl built-in.
+ 3. Added Tr() as an alternative to TR(); some people think it looks
+ better that way.
+ 4. Fixed problem with autoloading of MultipartBuffer::DESTROY code.
+ 5. Added the following methods:
+ + virtual_host()
+ + server_software()
+ 6. Automatic NPH mode when running under Microsoft IIS server.
+
+ Version 2.29
+
+ 1. Fixed cookie bugs
+ 2. Fixed problems that cropped up when useNamedParameters was set to
+ 1.
+ 3. Prevent CGI::Carp::fatalsToBrowser() from crapping out when
+ encountering a die() within an eval().
+ 4. Fixed problems with filehandle initializers.
+
+ Version 2.28
+
+ 1. Added support for NPH scripts; also fixes problems with Microsoft
+ IIS.
+ 2. Fixed a problem with checkbox() values not being correctly saved
+ and restored.
+ 3. Fixed a bug in which CGI objects created with empty string
+ initializers took on default values from earlier CGI objects.
+ 4. Documentation fixes.
+
+ Version 2.27
+
+ 1. Small but important bug fix: the automatic capitalization of tag
+ attributes was accidentally capitalizing the VALUES as well as the
+ ATTRIBUTE names (oops).
+
+ Version 2.26
+
+ 1. Changed behavior of scrolling_list(), checkbox() and
+ checkbox_group() methods so that defaults are honored correctly.
+ The "fix" causes endform() to generate additional <INPUT
+ TYPE="HIDDEN"> tags -- don't be surpised.
+ 2. Fixed bug involving the detection of the SSL protocol.
+ 3. Fixed documentation error in position of the -meta argument in
+ start_html().
+ 4. HTML shortcuts now generate tags in ALL UPPERCASE.
+ 5. start_html() now generates correct SGML header:
+ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+
+ 6. CGI::Carp no longer fails "use strict refs" pragma.
+
+ Version 2.25
+
+ 1. Fixed bug that caused bad redirection on destination URLs with
+ arguments.
+ 2. Fixed bug involving use_named_parameters() followed by
+ start_multipart_form()
+ 3. Fixed bug that caused incorrect determination of binmode for
+ Macintosh.
+ 4. Spelling fixes on documentation.
+
+ Version 2.24
+
+ 1. Fixed bug that caused generation of lousy HTML for some form
+ elements
+ 2. Fixed uploading bug in Windows NT
+ 3. Some code cleanup (not enough)
+
+ Version 2.23
+
+ 1. Fixed an obscure bug that caused scripts to fail mysteriously.
+ 2. Fixed auto-caching bug.
+ 3. Fixed bug that prevented HTML shortcuts from passing taint checks.
+ 4. Fixed some -w warning problems.
+
+ Version 2.22
+
+ 1. New CGI::Fast module for use with FastCGI protocol. See pod
+ documentation for details.
+ 2. Fixed problems with inheritance and autoloading.
+ 3. Added TR() (<tr>) and PARAM() (<param>) methods to list of
+ exported HTML tag-generating functions.
+ 4. Moved all CGI-related I/O to a bottleneck method so that this can
+ be overridden more easily in mod_perl (thanks to Doug MacEachern).
+ 5. put() method as substitute for print() for use in mod_perl.
+ 6. Fixed crash in tmpFileName() method.
+ 7. Added tmpFileName(), startform() and endform() to export list.
+ 8. Fixed problems with attributes in HTML shortcuts.
+ 9. Functions that don't actually need access to the CGI object now no
+ longer generate a default one. May speed things up slightly.
+ 10. Aesthetic improvements in generated HTML.
+ 11. New examples.
+
+ Version 2.21
+
+ 1. Added the -meta argument to start_html().
+ 2. Fixed hidden fields (again).
+ 3. Radio_group() and checkbox_group() now return an appropriate
+ scalar value when called in a scalar context, rather than
+ returning a numeric value!
+ 4. Cleaned up the formatting of form elements to avoid unesthetic
+ extra spaces within the attributes.
+ 5. HTML elements now correctly include the closing tag when
+ parameters are present but null: em('')
+ 6. Added password_field() to the export list.
+
+ Version 2.20
+
+ 1. Dumped the SelfLoader because of problems with running with taint
+ checks and rolled my own. Performance is now significantly
+ improved.
+ 2. Added HTML shortcuts.
+ 3. import() now adheres to the Perl module conventions, allowing
+ CGI.pm to import any or all method names into the user's name
+ space.
+ 4. Added the ability to initialize CGI objects from strings and
+ associative arrays.
+ 5. Made it possible to initialize CGI objects with filehandle
+ references rather than filehandle strings.
+ 6. Added the delete_all() and append() methods.
+ 7. CGI objects correctly initialize from filehandles on NT/95 systems
+ now.
+ 8. Fixed the problem with binary file uploads on NT/95 systems.
+ 9. Fixed bug in redirect().
+ 10. Added '-Window-target' parameter to redirect().
+ 11. Fixed import_names() so that parameter names containing funny
+ characters work.
+ 12. Broke the unfortunate connection between cookie and CGI parameter
+ name space.
+ 13. Fixed problems with hidden fields whose values are 0.
+ 14. Cleaned up the documentation somewhat.
+
+ Version 2.19
+
+ 1. Added cookie() support routines.
+ 2. Added -expires parameter to header().
+ 3. Added cgi-lib.pl compatibility mode.
+ 4. Made the module more configurable for different operating systems.
+ 5. Fixed a dumb bug in JavaScript button() method.
+
+ Version 2.18
+
+ 1. Fixed a bug that corrects a hang that occurs on some platforms
+ when processing file uploads. Unfortunately this disables the
+ check for bad Netscape uploads.
+ 2. Fixed bizarre problem involving the inability to process uploaded
+ files that begin with a non alphabetic character in the file name.
+ 3. Fixed a bug in the hidden fields involving the -override directive
+ being ignored when scalar defaults were passed.
+ 4. Added documentation on how to disable the SelfLoader features.
+
+ Version 2.17
+
+ 1. Added support for the SelfLoader module.
+ 2. Added oodles of JavaScript support routines.
+ 3. Fixed bad bug in query_string() method that caused some parameters
+ to be silently dropped.
+ 4. Robustified file upload code to handle premature termination by
+ the client.
+ 5. Exported temporary file names on file upload.
+ 6. Removed spurious "uninitialized variable" warnings that appeared
+ when running under 5.002.
+ 7. Added the Carp.pm library to the standard distribution.
+ 8. Fixed a number of errors in this documentation, and probably added
+ a few more.
+ 9. Checkbox_group() and radio_group() now return the buttons as
+ arrays, so that you can incorporate the individual buttons into
+ specialized tables.
+ 10. Added the '-nolabels' option to checkbox_group() and
+ radio_group(). Probably should be added to all the other
+ HTML-generating routines.
+ 11. Added the url() method to recover the URL without the entire query
+ string appended.
+ 12. Added request_method() to list of environment variables available.
+ 13. Would you believe it? Fixed hidden fields again!
+
+ Version 2.16
+
+ 1. Fixed hidden fields yet again.
+ 2. Fixed subtle problems in the file upload method that caused
+ intermittent failures (thanks to Keven Hendrick for this one).
+ 3. Made file upload more robust in the face of bizarre behavior by
+ the Macintosh and Windows Netscape clients.
+ 4. Moved the POD documentation to the bottom of the module at the
+ request of Stephen Dahmen.
+ 5. Added the -xbase parameter to the start_html() method, also at the
+ request of Stephen Dahmen.
+ 6. Added JavaScript form buttons at Stephen's request. I'm not sure
+ how to use this Netscape extension correctly, however, so for now
+ the form() method is in the module as an undocumented feature. Use
+ at your own risk!
+
+ Version 2.15
+
+ 1. Added the -override parameter to all field-generating methods.
+ 2. Documented the user_name() and remote_user() methods.
+ 3. Fixed bugs that prevented empty strings from being recognized as
+ valid textfield contents.
+ 4. Documented the use of framesets and added a frameset example.
+
+ Version 2.14
+
+ This was an internal experimental version that was never released.
+
+ Version 2.13
+
+ 1. Fixed a bug that interfered with the value "0" being entered into
+ text fields.
+
+ Version 2.01
+
+ 1. Added -rows and -columns to the radio and checkbox groups. No
+ doubt this will cause much grief because it seems to promise a
+ level of meta-organization that it doesn't actually provide.
+ 2. Fixed a bug in the redirect() method -- it was not truly HTTP/1.0
+ compliant.
+
+ Version 2.0
+
+ The changes seemed to touch every line of code, so I decided to bump
+ up the major version number.
+ 1. Support for named parameter style method calls. This turns out
+ to be a big win for extending CGI.pm when Netscape adds new HTML
+ "features".
+ 2. Changed behavior of hidden fields back to the correct "sticky"
+ behavior. This is going to break some programs, but it is for
+ the best in the long run.
+ 3. Netscape 2.0b2 broke the file upload feature. CGI.pm now handles
+ both 2.0b1 and 2.0b2-style uploading. It will probably break again
+ in 2.0b3.
+ 4. There were still problems with library being unable to distinguish
+ between a form being loaded for the first time, and a subsequent
+ loading with all fields blank. We now forcibly create a default
+ name for the Submit button (if not provided) so that there's
+ always at least one parameter.
+ 5. More workarounds to prevent annoying spurious warning messages
+ when run under the -w switch. -w is seriously broken in perl
+ 5.001!
+
+ Version 1.57
+
+ 1. Support for the Netscape 2.0 "File upload" field.
+ 2. The handling of defaults for selected items in scrolling lists and
+ multiple checkboxes is now consistent.
+
+ Version 1.56
+
+ 1. Created true "pod" documentation for the module.
+ 2. Cleaned up the code to avoid many of the spurious "use of
+ uninitialized variable" warnings when running with the -w switch.
+ 3. Added the autoEscape() method. v
+ 4. Added string interpolation of the CGI object.
+ 5. Added the ability to pass additional parameters to the <BODY> tag.
+ 6. Added the ability to specify the status code in the HTTP header.
+
+ Bug fixes in version 1.55
+
+ 1. Every time self_url() was called, the parameter list would grow.
+ This was a bad "feature".
+ 2. Documented the fact that you can pass "-" to radio_group() in
+ order to prevent any button from being highlighted by default.
+
+ Bug fixes in version 1.54
+
+ 1. The user_agent() method is now documented;
+ 2. A potential security hole in import() is now plugged.
+ 3. Changed name of import() to import_names() for compatibility with
+ CGI:: modules.
+
+ Bug fixes in version 1.53
+
+ 1. Fixed several typos in the code that were causing the following
+ subroutines to fail in some circumstances
+ 1. checkbox()
+ 2. hidden()
+ 2. No features added
+
+ New features added in version 1.52
+
+ 1. Added backslashing, quotation marks, and other shell-style escape
+ sequences to the parameters passed in during debugging off-line.
+ 2. Changed the way that the hidden() method works so that the default
+ value always overrides the current one.
+ 3. Improved the handling of sticky values in forms. It's now less
+ likely that sticky values will get stuck.
+ 4. If you call server_name(), script_name() and several other methods
+ when running offline, the methods now create "dummy" values to
+ work with.
+
+ Bugs fixed in version 1.51
+
+ 1. param() when called without arguments was returning an array of
+ length 1 even when there were no parameters to be had. Bad bug!
+ Bad!
+ 2. The HTML code generated would break if input fields contained the
+ forbidden characters ">< or &. You can now use these characters
+ freely.
+
+ New features added in version 1.50
+
+ 1. import() method allows all the parameters to be imported into a
+ namespace in one fell swoop.
+ 2. Parameters are now returned in the same order in which they were
+ defined.
+
+ Bugs fixed in version 1.45
+
+ 1. delete() method didn't work correctly. This is now fixed.
+ 2. reset() method didn't allow you to set the name of the button.
+ Fixed.
+
+ Bugs fixed in version 1.44
+
+ 1. self_url() didn't include the path information. This is now fixed.
+
+ New features added in version 1.43
+
+ 1. Added the delete() method.
+
+ New features added in version 1.42
+
+ 1. The image_button() method to create clickable images.
+ 2. A few bug fixes involving forms embedded in <PRE> blocks.
+
+ New features added in version 1.4
+
+ 1. New header shortcut methods
+ + redirect() to create HTTP redirection messages.
+ + start_html() to create the HTML title, complete with the
+ recommended <LINK> tag that no one ever remembers to include.
+ + end_html() for completeness' sake.
+ 2. A new save() method that allows you to write out the state of an
+ script to a file or pipe.
+ 3. An improved version of the new() method that allows you to restore
+ the state of a script from a file or pipe. With (2) this gives you
+ dump and restore capabilities! (Wow, you can put a "121,931
+ customers served" banner at the bottom of your pages!)
+ 4. A self_url() method that allows you to create state-maintaining
+ hypertext links. In addition to allowing you to maintain the state
+ of your scripts between invocations, this lets you work around a
+ problem that some browsers have when jumping to internal links in
+ a document that contains a form -- the form information gets lost.
+ 5. The user-visible labels in checkboxes, radio buttons, popup menus
+ and scrolling lists have now been decoupled from the values sent
+ to your CGI script. Your script can know a checkbox by the name of
+ "cb1" while the user knows it by a more descriptive name. I've
+ also added some parameters that were missing from the text fields,
+ such as MAXLENGTH.
+ 6. A whole bunch of methods have been added to get at environment
+ variables involved in user verification and other obscure
+ features.
+
+ Bug fixes
+
+ 1. The problems with the hidden fields have (I hope at last) been
+ fixed.
+ 2. You can create multiple query objects and they will all be
+ initialized correctly. This simplifies the creation of multiple
+ forms on one page.
+ 3. The URL unescaping code works correctly now.
diff --git a/MANIFEST b/MANIFEST
new file mode 100644
index 0000000..f481e58
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,88 @@
+Changes
+examples/clickable_image.cgi
+examples/cookie.cgi
+examples/crash.cgi
+examples/file_upload.cgi
+examples/mojo_proxy.pl
+examples/wikipedia_example.cgi
+examples/wilogo.gif
+lib/CGI.pm
+lib/CGI.pod
+lib/Fh.pm
+lib/CGI/Carp.pm
+lib/CGI/Cookie.pm
+lib/CGI/Pretty.pm
+lib/CGI/Push.pm
+lib/CGI/Util.pm
+lib/CGI/File/Temp.pm
+lib/CGI/HTML/Functions.pm
+lib/CGI/HTML/Functions.pod
+Makefile.PL
+MANIFEST This list of files
+README.md
+t/Dump.t
+t/arbitrary_handles.t
+t/autoescape.t
+t/can.t
+t/carp.t
+t/cgi.t
+t/changes.t
+t/charset.t
+t/checkbox_group.t
+t/compiles_pod.t
+t/cookie.t
+t/delete.t
+t/end_form.t
+t/form.t
+t/function.t
+t/gh-155.t
+t/headers.t
+t/headers/attachment.t
+t/headers/charset.t
+t/headers/cookie.t
+t/headers/default.t
+t/headers/nph.t
+t/headers/p3p.t
+t/headers/target.t
+t/headers/type.t
+t/hidden.t
+t/html.t
+t/html_functions.t
+t/http.t
+t/init.t
+t/init_test.txt
+t/multipart_init.t
+t/multipart_start.t
+t/no_tabindex.t
+t/param_fetch.t
+t/param_list_context.t
+t/popup_menu.t
+t/postdata.t
+t/pretty.t
+t/push.t
+t/query_string.t
+t/redirect_query_string.t
+t/request.t
+t/rt-31107.t
+t/rt-52469.t
+t/rt-57524.t
+t/rt-75628.t
+t/rt-84767.t
+t/rt_31107.txt
+t/rt_75628.txt
+t/save_read_roundtrip.t
+t/sorted.t
+t/start_end_asterisk.t
+t/start_end_end.t
+t/start_end_start.t
+t/unescapeHTML.t
+t/upload.t
+t/uploadInfo.t
+t/upload_post_text.txt
+t/url.t
+t/user_agent.t
+t/utf8.t
+t/util-58.t
+t/util.t
+META.yml Module YAML meta-data (added by MakeMaker)
+META.json Module JSON meta-data (added by MakeMaker)
diff --git a/META.json b/META.json
new file mode 100644
index 0000000..304d045
--- /dev/null
+++ b/META.json
@@ -0,0 +1,64 @@
+{
+ "abstract" : "Handle Common Gateway Interface requests and responses",
+ "author" : [
+ "unknown"
+ ],
+ "dynamic_config" : 1,
+ "generated_by" : "ExtUtils::MakeMaker version 7.0401, CPAN::Meta::Converter version 2.150001",
+ "license" : [
+ "unknown"
+ ],
+ "meta-spec" : {
+ "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
+ "version" : "2"
+ },
+ "name" : "CGI",
+ "no_index" : {
+ "directory" : [
+ "t",
+ "inc",
+ "t"
+ ]
+ },
+ "prereqs" : {
+ "build" : {
+ "requires" : {}
+ },
+ "configure" : {
+ "requires" : {}
+ },
+ "runtime" : {
+ "requires" : {
+ "Carp" : "0",
+ "Config" : "0",
+ "Encode" : "0",
+ "Exporter" : "0",
+ "File::Spec" : "0.82",
+ "File::Temp" : "0",
+ "HTML::Entities" : "3.69",
+ "base" : "0",
+ "if" : "0",
+ "overload" : "0",
+ "parent" : "0.225",
+ "perl" : "5.008001",
+ "strict" : "0",
+ "utf8" : "0",
+ "warnings" : "0"
+ }
+ }
+ },
+ "release_status" : "stable",
+ "resources" : {
+ "bugtracker" : {
+ "web" : "https://github.com/leejo/CGI.pm/issues"
+ },
+ "homepage" : "https://metacpan.org/module/CGI",
+ "license" : [
+ "http://dev.perl.org/licenses/"
+ ],
+ "repository" : {
+ "url" : "https://github.com/leejo/CGI.pm"
+ }
+ },
+ "version" : "4.21"
+}
diff --git a/META.yml b/META.yml
new file mode 100644
index 0000000..2a51f1e
--- /dev/null
+++ b/META.yml
@@ -0,0 +1,40 @@
+---
+abstract: 'Handle Common Gateway Interface requests and responses'
+author:
+ - unknown
+build_requires: {}
+configure_requires: {}
+dynamic_config: 1
+generated_by: 'ExtUtils::MakeMaker version 7.0401, CPAN::Meta::Converter version 2.150001'
+license: unknown
+meta-spec:
+ url: http://module-build.sourceforge.net/META-spec-v1.4.html
+ version: '1.4'
+name: CGI
+no_index:
+ directory:
+ - t
+ - inc
+ - t
+requires:
+ Carp: '0'
+ Config: '0'
+ Encode: '0'
+ Exporter: '0'
+ File::Spec: '0.82'
+ File::Temp: '0'
+ HTML::Entities: '3.69'
+ base: '0'
+ if: '0'
+ overload: '0'
+ parent: '0.225'
+ perl: '5.008001'
+ strict: '0'
+ utf8: '0'
+ warnings: '0'
+resources:
+ bugtracker: https://github.com/leejo/CGI.pm/issues
+ homepage: https://metacpan.org/module/CGI
+ license: http://dev.perl.org/licenses/
+ repository: https://github.com/leejo/CGI.pm
+version: '4.21'
diff --git a/Makefile.PL b/Makefile.PL
new file mode 100644
index 0000000..b06ef59
--- /dev/null
+++ b/Makefile.PL
@@ -0,0 +1,69 @@
+use ExtUtils::MakeMaker;
+my $mm = $ExtUtils::MakeMaker::VERSION;
+
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+WriteMakefile(
+ INSTALLDIRS => ( $] >= 5.012 ? 'site' : 'perl' ),
+ ABSTRACT_FROM => 'lib/CGI.pod',
+ VERSION_FROM => 'lib/CGI.pm',
+ NAME => 'CGI',
+ DISTNAME => 'CGI',
+ VERSION_FROM => 'lib/CGI.pm',
+ MIN_PERL_VERSION => '5.8.1',
+ PREREQ_PM => {
+ 'Carp' => 0, # Carp was first released with perl 5
+ 'Exporter' => 0, # Exporter was first released with perl 5
+ 'base' => 0, # base was first released with perl 5.00405
+ 'overload' => 0, # overload was first released with perl 5.002
+ 'strict' => 0, # strict was first released with perl 5
+ 'utf8' => 0, # utf8 was first released with perl v5.6.0
+ 'warnings' => 0, # warnings was first released with perl v5.6.0
+ 'File::Spec' => 0.82,
+ 'if' => 0, # core in 5.6.2 and later, for deprecate.pm
+ 'parent' => 0.225, # parent was first released with perl v5.10.1
+ 'File::Temp' => 0, # was first released with perl v5.6.1'
+ 'HTML::Entities' => 3.69,
+ 'Encode' => 0, # Encode was first released with perl v5.7.3
+ 'Config' => 0, # Config was first released with perl 5.00307
+ },
+ TEST_REQUIRES => {
+ 'Cwd' => 0, # Cwd was first released with perl 5
+ 'POSIX' => 0, # POSIX was first released with perl 5
+ 'IO::File' => 0, # IO::File was first released with perl 5.00307
+ 'IO::Handle' => 0, # IO::Handle was first released with perl 5.00307
+ 'File::Find' => 0, # File::Find was first released with perl 5
+ 'Test::Deep' => 0.11,
+ 'Test::More' => 0.98,
+ 'Test::Warn' => 0.30,
+ 'Test::NoWarnings' => 1.04,
+ },
+ test => { TESTS => 't/*.t t/headers/*.t' },
+ linkext => { LINKTYPE => '' }, # no link needed
+ dist => {
+ COMPRESS => 'gzip -9f',
+ SUFFIX => 'gz',
+ ZIP => '/usr/bin/zip',
+ ZIPFLAGS => '-rl'
+ },
+ (
+ $mm < 6.46
+ ? ()
+ : (
+ META_MERGE => {
+ requires => { perl => '5.008001' },
+ resources => {
+ license => 'http://dev.perl.org/licenses/',
+ homepage => 'https://metacpan.org/module/CGI',
+ repository => 'https://github.com/leejo/CGI.pm',
+ bugtracker => 'https://github.com/leejo/CGI.pm/issues',
+ },
+ no_index => { directory => [qw/t/] },
+ },
+ META_ADD => {
+ build_requires => {},
+ configure_requires => {}
+ },
+ )
+ ),
+);
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..69bff30
--- /dev/null
+++ b/README.md
@@ -0,0 +1,1781 @@
+# NAME
+
+CGI - Handle Common Gateway Interface requests and responses
+
+<div>
+
+ <a href='https://travis-ci.org/leejo/CGI.pm?branch=master'><img src='https://travis-ci.org/leejo/CGI.pm.svg?branch=master' alt='Build Status' /></a>
+ <a href='https://coveralls.io/r/leejo/CGI.pm'><img src='https://coveralls.io/repos/leejo/CGI.pm/badge.png?branch=master' alt='Coverage Status' /></a>
+</div>
+
+# SYNOPSIS
+
+ use strict;
+ use warnings;
+
+ use CGI;
+
+ my $q = CGI->new;
+
+ # Process an HTTP request
+ my @values = $q->multi_param('form_field');
+ my $value = $q->param('param_name');
+
+ my $fh = $q->upload('file_field');
+
+ my $riddle = $query->cookie('riddle_name');
+ my %answers = $query->cookie('answers');
+
+ # Prepare various HTTP responses
+ print $q->header();
+ print $q->header('application/json');
+
+ my $cookie1 = $q->cookie(
+ -name => 'riddle_name',
+ -value => "The Sphynx's Question"
+ );
+
+ my $cookie2 = $q->cookie(
+ -name => 'answers',
+ -value => \%answers
+ );
+
+ print $q->header(
+ -type => 'image/gif',
+ -expires => '+3d',
+ -cookie => [ $cookie1,$cookie2 ]
+ );
+
+ print $q->redirect('http://somewhere.else/in/movie/land');
+
+# DESCRIPTION
+
+CGI.pm is a stable, complete and mature solution for processing and preparing
+HTTP requests and responses. Major features including processing form
+submissions, file uploads, reading and writing cookies, query string generation
+and manipulation, and processing and preparing HTTP headers.
+
+CGI.pm performs very well in a vanilla CGI.pm environment and also comes
+with built-in support for mod\_perl and mod\_perl2 as well as FastCGI.
+
+It has the benefit of having developed and refined over 20 years with input
+from dozens of contributors and being deployed on thousands of websites.
+CGI.pm was included in the perl distribution from perl v5.4 to v5.20, however
+is has now been removed from the perl core...
+
+# CGI.pm HAS BEEN REMOVED FROM THE PERL CORE
+
+[http://perl5.git.perl.org/perl.git/commitdiff/e9fa5a80](http://perl5.git.perl.org/perl.git/commitdiff/e9fa5a80)
+
+If you upgrade to a new version of perl or if you rely on a
+system or vendor perl and get an updated version of perl through a system
+update, then you will have to install CGI.pm yourself with cpan/cpanm/a vendor
+package/manually. To make this a little easier the [CGI::Fast](https://metacpan.org/pod/CGI::Fast) module has been
+split into its own distribution, meaning you do not need access to a compiler
+to install CGI.pm
+
+The rationale for this decision is that CGI.pm is no longer considered good
+practice for developing web applications, **including** quick prototyping and
+small web scripts. There are far better, cleaner, quicker, easier, safer,
+more scalable, more extensible, more modern alternatives available at this point
+in time. These will be documented with [CGI::Alternatives](https://metacpan.org/pod/CGI::Alternatives).
+
+For more discussion on the removal of CGI.pm from core please see:
+
+[http://www.nntp.perl.org/group/perl.perl5.porters/2013/05/msg202130.html](http://www.nntp.perl.org/group/perl.perl5.porters/2013/05/msg202130.html)
+
+Note that the v4 releases of CGI.pm will retain back compatibility **as much**
+**as possible**, however you may need to make some minor changes to your code
+if you are using deprecated methods or some of the more obscure features of the
+module. If you plan to upgrade to v4.00 and beyond you should read the Changes
+file for more information and **test your code** against CGI.pm before deploying
+it.
+
+# HTML Generation functions should no longer be used
+
+**All** HTML generation functions within CGI.pm are no longer being
+maintained. Any issues, bugs, or patches will be rejected unless
+they relate to fundamentally broken page rendering.
+
+The rationale for this is that the HTML generation functions of CGI.pm
+are an obfuscation at best and a maintenance nightmare at worst. You
+should be using a template engine for better separation of concerns.
+See [CGI::Alternatives](https://metacpan.org/pod/CGI::Alternatives) for an example of using CGI.pm with the
+[Template::Toolkit](https://metacpan.org/pod/Template::Toolkit) module.
+
+These functions, and perldoc for them, will continue to exist in the
+v4 releases of CGI.pm but may be deprecated (soft) in v5 and beyond.
+All documentation for these functions has been moved to [CGI::HTML::Functions](https://metacpan.org/pod/CGI::HTML::Functions).
+
+# Programming style
+
+There are two styles of programming with CGI.pm, an object-oriented (OO)
+style and a function-oriented style. You are recommended to use the OO
+style as CGI.pm will create an internal default object when the functions
+are called procedurally and you will not have to worry about method names
+clashing with perl builtins.
+
+In the object-oriented style you create one or more CGI objects and then
+use object methods to create the various elements of the page. Each CGI
+object starts out with the list of named parameters that were passed to
+your CGI script by the server. You can modify the objects, save them to a
+file or database and recreate them. Because each object corresponds to the
+"state" of the CGI script, and because each object's parameter list is
+independent of the others, this allows you to save the state of the
+script and restore it later.
+
+For example, using the object oriented style:
+
+ #!/usr/bin/env perl
+
+ use strict;
+ use warnings;
+
+ use CGI; # load CGI routines
+
+ my $q = CGI->new; # create new CGI object
+ print $q->header; # create the HTTP header
+
+ ...
+
+In the function-oriented style, there is one default CGI object that
+you rarely deal with directly. Instead you just call functions to
+retrieve CGI parameters, manage cookies, and so on. The following example
+is identical to above, in terms of output, but uses the function-oriented
+interface. The main differences are that we now need to import a set of
+functions into our name space (usually the "standard" functions), and we don't
+need to create the CGI object.
+
+ #!/usr/bin/env perl
+
+ use strict;
+ use warnings;
+
+ use CGI qw/:standard/; # load standard CGI routines
+ print header(); # create the HTTP header
+
+ ...
+
+The examples in this document mainly use the object-oriented style. See HOW
+TO IMPORT FUNCTIONS for important information on function-oriented programming
+in CGI.pm
+
+## Calling CGI.pm routines
+
+Most CGI.pm routines accept several arguments, sometimes as many as 20
+optional ones! To simplify this interface, all routines use a named
+argument calling style that looks like this:
+
+ print $q->header(
+ -type => 'image/gif',
+ -expires => '+3d',
+ );
+
+Each argument name is preceded by a dash. Neither case nor order matters in
+the argument list: -type, -Type, and -TYPE are all acceptable. In fact, only
+the first argument needs to begin with a dash. If a dash is present in the
+first argument CGI.pm assumes dashes for the subsequent ones.
+
+Several routines are commonly called with just one argument. In the case
+of these routines you can provide the single argument without an argument
+name. header() happens to be one of these routines. In this case, the single
+argument is the document type.
+
+ print $q->header('text/html');
+
+Other such routines are documented below.
+
+Sometimes named arguments expect a scalar, sometimes a reference to an array,
+and sometimes a reference to a hash. Often, you can pass any type of argument
+and the routine will do whatever is most appropriate. For example, the param()
+routine is used to set a CGI parameter to a single or a multi-valued value.
+The two cases are shown below:
+
+ $q->param(
+ -name => 'veggie',
+ -value => 'tomato',
+ );
+
+ $q->param(
+ -name => 'veggie',
+ -value => [ qw/tomato tomahto potato potahto/ ],
+ );
+
+Many routines will do something useful with a named argument that it doesn't
+recognize. For example, you can produce non-standard HTTP header fields by
+providing them as named arguments:
+
+ print $q->header(
+ -type => 'text/html',
+ -cost => 'Three smackers',
+ -annoyance_level => 'high',
+ -complaints_to => 'bit bucket',
+ );
+
+This will produce the following nonstandard HTTP header:
+
+ HTTP/1.0 200 OK
+ Cost: Three smackers
+ Annoyance-level: high
+ Complaints-to: bit bucket
+ Content-type: text/html
+
+Notice the way that underscores are translated automatically into hyphens.
+
+## Creating a new query object (object-oriented style)
+
+ my $query = CGI->new;
+
+This will parse the input (from POST, GET and DELETE methods) and store
+it into a perl5 object called $query. Note that because the input parsing
+happens at object instantiation you have to set any CGI package variables
+that control parsing **before** you call CGI->new.
+
+Any filehandles from file uploads will have their position reset to the
+beginning of the file.
+
+## Creating a new query object from an input file
+
+ my $query = CGI->new( $input_filehandle );
+
+If you provide a file handle to the new() method, it will read parameters
+from the file (or STDIN, or whatever). The file can be in any of the forms
+describing below under debugging (i.e. a series of newline delimited
+TAG=VALUE pairs will work). Conveniently, this type of file is created by
+the save() method (see below). Multiple records can be saved and restored.
+
+Perl purists will be pleased to know that this syntax accepts references to
+file handles, or even references to filehandle globs, which is the "official"
+way to pass a filehandle. You can also initialize the CGI object with a
+FileHandle or IO::File object.
+
+If you are using the function-oriented interface and want to initialize CGI
+state from a file handle, the way to do this is with **restore\_parameters()**.
+This will (re)initialize the default CGI object from the indicated file handle.
+
+ open( my $in_fh,'<',"test.in") || die "Couldn't open test.in for read: $!";
+ restore_parameters( $in_fh );
+ close( $in_fh );
+
+You can also initialize the query object from a hash reference:
+
+ my $query = CGI->new( {
+ 'dinosaur' => 'barney',
+ 'song' => 'I love you',
+ 'friends' => [ qw/ Jessica George Nancy / ]
+ } );
+
+or from a properly formatted, URL-escaped query string:
+
+ my $query = CGI->new('dinosaur=barney&color=purple');
+
+or from a previously existing CGI object (currently this clones the parameter
+list, but none of the other object-specific fields, such as autoescaping):
+
+ my $old_query = CGI->new;
+ my $new_query = CGI->new($old_query);
+
+To create an empty query, initialize it from an empty string or hash:
+
+ my $empty_query = CGI->new("");
+
+ -or-
+
+ my $empty_query = CGI->new({});
+
+## Fetching a list of keywords from the query
+
+ my @keywords = $query->keywords
+
+If the script was invoked as the result of an ISINDEX search, the parsed
+keywords can be obtained as an array using the keywords() method.
+
+## Fetching the names of all the parameters passed to your script
+
+ my @names = $query->multi_param
+
+ my @names = $query->param
+
+If the script was invoked with a parameter list
+(e.g. "name1=value1&name2=value2&name3=value3"), the param() / multi\_param()
+methods will return the parameter names as a list. If the script was invoked
+as an ISINDEX script and contains a string without ampersands
+(e.g. "value1+value2+value3"), there will be a single parameter named
+"keywords" containing the "+"-delimited keywords.
+
+The array of parameter names returned will be in the same order as they were
+submitted by the browser. Usually this order is the same as the order in which
+the parameters are defined in the form (however, this isn't part of the spec,
+and so isn't guaranteed).
+
+## Fetching the value or values of a single named parameter
+
+ my @values = $query->multi_param('foo');
+
+ -or-
+
+ my $value = $query->param('foo');
+
+Pass the param() / multi\_param() method a single argument to fetch the value
+of the named parameter. If the parameter is multivalued (e.g. from multiple
+selections in a scrolling list), you can ask to receive an array. Otherwise
+the method will return a single value.
+
+**Warning** - calling param() in list context can lead to vulnerabilities if
+you do not sanitise user input as it is possible to inject other param
+keys and values into your code. This is why the multi\_param() method exists,
+to make it clear that a list is being returned, note that param() can still
+be called in list context and will return a list for back compatibility.
+
+The following code is an example of a vulnerability as the call to param will
+be evaluated in list context and thus possibly inject extra keys and values
+into the hash:
+
+ my %user_info = (
+ id => 1,
+ name => $query->param('name'),
+ );
+
+The fix for the above is to force scalar context on the call to ->param by
+prefixing it with "scalar"
+
+ name => scalar $query->param('name'),
+
+If you call param() in list context with an argument a warning will be raised
+by CGI.pm, you can disable this warning by setting $CGI::LIST\_CONTEXT\_WARN to 0
+or by using the multi\_param() method instead
+
+If a value is not given in the query string, as in the queries "name1=&name2=",
+it will be returned as an empty string.
+
+If the parameter does not exist at all, then param() will return undef in scalar
+context, and the empty list in a list context.
+
+## Setting the value(s) of a named parameter
+
+ $query->param('foo','an','array','of','values');
+
+This sets the value for the named parameter 'foo' to an array of values. This
+is one way to change the value of a field AFTER the script has been invoked
+once before.
+
+param() also recognizes a named parameter style of calling described in more
+detail later:
+
+ $query->param(
+ -name => 'foo',
+ -values => ['an','array','of','values'],
+ );
+
+ -or-
+
+ $query->param(
+ -name => 'foo',
+ -value => 'the value',
+ );
+
+## Appending additional values to a named parameter
+
+ $query->append(
+ -name =>'foo',
+ -values =>['yet','more','values'],
+ );
+
+This adds a value or list of values to the named parameter. The values are
+appended to the end of the parameter if it already exists. Otherwise the
+parameter is created. Note that this method only recognizes the named argument
+calling syntax.
+
+## Importing all parameters into a namespace
+
+ $query->import_names('R');
+
+This creates a series of variables in the 'R' namespace. For example, $R::foo,
+@R:foo. For keyword lists, a variable @R::keywords will appear. If no namespace
+is given, this method will assume 'Q'. **WARNING**: don't import anything into
+'main'; this is a major security risk!
+
+NOTE 1: Variable names are transformed as necessary into legal perl variable
+names. All non-legal characters are transformed into underscores. If you need
+to keep the original names, you should use the param() method instead to access
+CGI variables by name.
+
+In fact, you should probably not use this method at all given the above caveats
+and security risks.
+
+## Deleting a parameter completely
+
+ $query->delete('foo','bar','baz');
+
+This completely clears a list of parameters. It sometimes useful for resetting
+parameters that you don't want passed down between script invocations.
+
+If you are using the function call interface, use "Delete()" instead to avoid
+conflicts with perl's built-in delete operator.
+
+## Deleting all parameters
+
+ $query->delete_all();
+
+This clears the CGI object completely. It might be useful to ensure that all
+the defaults are taken when you create a fill-out form.
+
+Use Delete\_all() instead if you are using the function call interface.
+
+## Handling non-urlencoded arguments
+
+If POSTed data is not of type application/x-www-form-urlencoded or
+multipart/form-data, then the POSTed data will not be processed, but instead
+be returned as-is in a parameter named POSTDATA. To retrieve it, use code like
+this:
+
+ my $data = $query->param('POSTDATA');
+
+Likewise if PUTed data can be retrieved with code like this:
+
+ my $data = $query->param('PUTDATA');
+
+(If you don't know what the preceding means, worry not. It only affects people
+trying to use CGI for XML processing and other specialized tasks)
+
+PUTDATA/POSTDATA are also available via
+[upload\_hook](#progress-bars-for-file-uploads-and-avoiding-temp-files),
+and as [file uploads](#processing-a-file-upload-field) via ["-putdata\_upload"](#putdata_upload)
+option.
+
+## Direct access to the parameter list
+
+ $q->param_fetch('address')->[1] = '1313 Mockingbird Lane';
+ unshift @{$q->param_fetch(-name=>'address')},'George Munster';
+
+If you need access to the parameter list in a way that isn't covered by the
+methods given in the previous sections, you can obtain a direct reference to
+it by calling the **param\_fetch()** method with the name of the parameter. This
+will return an array reference to the named parameter, which you then can
+manipulate in any way you like.
+
+You can also use a named argument style using the **-name** argument.
+
+## Fetching the parameter list as a hash
+
+ my $params = $q->Vars;
+ print $params->{'address'};
+ my @foo = split("\0",$params->{'foo'});
+ my %params = $q->Vars;
+
+ use CGI ':cgi-lib';
+ my $params = Vars();
+
+Many people want to fetch the entire parameter list as a hash in which the keys
+are the names of the CGI parameters, and the values are the parameters' values.
+The Vars() method does this. Called in a scalar context, it returns the
+parameter list as a tied hash reference. Changing a key changes the value of
+the parameter in the underlying CGI parameter list. Called in a list context,
+it returns the parameter list as an ordinary hash. This allows you to read the
+contents of the parameter list, but not to change it.
+
+When using this, the thing you must watch out for are multivalued CGI
+parameters. Because a hash cannot distinguish between scalar and list context,
+multivalued parameters will be returned as a packed string, separated by the
+"\\0" (null) character. You must split this packed string in order to get at the
+individual values. This is the convention introduced long ago by Steve Brenner
+in his cgi-lib.pl module for perl version 4, and may be replaced in future
+versions with array references.
+
+If you wish to use Vars() as a function, import the _:cgi-lib_ set of function
+calls (also see the section on CGI-LIB compatibility).
+
+## Saving the state of the script to a file
+
+ $query->save(\*FILEHANDLE)
+
+This will write the current state of the form to the provided filehandle. You
+can read it back in by providing a filehandle to the new() method. Note that
+the filehandle can be a file, a pipe, or whatever.
+
+The format of the saved file is:
+
+ NAME1=VALUE1
+ NAME1=VALUE1'
+ NAME2=VALUE2
+ NAME3=VALUE3
+ =
+
+Both name and value are URL escaped. Multi-valued CGI parameters are represented
+as repeated names. A session record is delimited by a single = symbol. You can
+write out multiple records and read them back in with several calls to **new**.
+You can do this across several sessions by opening the file in append mode,
+allowing you to create primitive guest books, or to keep a history of users'
+queries. Here's a short example of creating multiple session records:
+
+ use strict;
+ use warnings;
+ use CGI;
+
+ open (my $out_fh,'>>','test.out') || die "Can't open test.out: $!";
+ my $records = 5;
+ for ( 0 .. $records ) {
+ my $q = CGI->new;
+ $q->param( -name => 'counter',-value => $_ );
+ $q->save( $out_fh );
+ }
+ close( $out_fh );
+
+ # reopen for reading
+ open (my $in_fh,'<','test.out') || die "Can't open test.out: $!";
+ while (!eof($in_fh)) {
+ my $q = CGI->new($in_fh);
+ print $q->param('counter'),"\n";
+ }
+
+The file format used for save/restore is identical to that used by the Whitehead
+Genome Center's data exchange format "Boulderio", and can be manipulated and
+even databased using Boulderio utilities. See [Boulder](https://metacpan.org/pod/Boulder) for further details.
+
+If you wish to use this method from the function-oriented (non-OO) interface,
+the exported name for this method is **save\_parameters()**.
+
+## Retrieving cgi errors
+
+Errors can occur while processing user input, particularly when processing
+uploaded files. When these errors occur, CGI will stop processing and return
+an empty parameter list. You can test for the existence and nature of errors
+using the _cgi\_error()_ function. The error messages are formatted as HTTP
+status codes. You can either incorporate the error text into a page, or use
+it as the value of the HTTP status:
+
+ if ( my $error = $q->cgi_error ) {
+ print $q->header( -status => $error );
+ print "Error: $error";
+ exit 0;
+ }
+
+When using the function-oriented interface (see the next section), errors may
+only occur the first time you call _param()_. Be ready for this!
+
+## Using the function-oriented interface
+
+To use the function-oriented interface, you must specify which CGI.pm
+routines or sets of routines to import into your script's namespace.
+There is a small overhead associated with this importation, but it
+isn't much.
+
+ use strict;
+ use warnings;
+
+ use CGI qw/ list of methods /;
+
+The listed methods will be imported into the current package; you can
+call them directly without creating a CGI object first. This example
+shows how to import the **param()** and **header()**
+methods, and then use them directly:
+
+ use strict;
+ use warnings;
+
+ use CGI qw/ param header /;
+ print header('text/plain');
+ my $zipcode = param('zipcode');
+
+More frequently, you'll import common sets of functions by referring
+to the groups by name. All function sets are preceded with a ":"
+character as in ":cgi" (for CGI protocol handling methods).
+
+Here is a list of the function sets you can import:
+
+- **:cgi**
+
+ Import all CGI-handling methods, such as **param()**, **path\_info()**
+ and the like.
+
+- **:all**
+
+ Import all the available methods. For the full list, see the CGI.pm
+ code, where the variable %EXPORT\_TAGS is defined. (N.B. the :cgi-lib
+ imports will **not** be included in the :all import, you will have to
+ import :cgi-lib to get those)
+
+Note that in the interests of execution speed CGI.pm does **not** use
+the standard [Exporter](https://metacpan.org/pod/Exporter) syntax for specifying load symbols. This may
+change in the future.
+
+## Pragmas
+
+In addition to the function sets, there are a number of pragmas that you can
+import. Pragmas, which are always preceded by a hyphen, change the way that
+CGI.pm functions in various ways. Pragmas, function sets, and individual
+functions can all be imported in the same use() line. For example, the
+following use statement imports the cgi set of functions and enables
+debugging mode (pragma -debug):
+
+ use strict;
+ use warninigs;
+ use CGI qw/ :cgi -debug /;
+
+The current list of pragmas is as follows:
+
+- -no\_undef\_params
+
+ This keeps CGI.pm from including undef params in the parameter list.
+
+- -utf8
+
+ This makes CGI.pm treat all parameters as text strings rather than binary
+ strings (see [perlunitut](https://metacpan.org/pod/perlunitut) for the distinction), assuming UTF-8 for the
+ encoding.
+
+ CGI.pm does the decoding from the UTF-8 encoded input data, restricting this
+ decoding to input text as distinct from binary upload data which are left
+ untouched. Therefore, a ':utf8' layer must **not** be used on STDIN.
+
+ If you do not use this option you can manually select which fields are
+ expected to return utf-8 strings and convert them using code like this:
+
+ use strict;
+ use warnings;
+
+ use CGI;
+ use Encode qw/ decode /;
+
+ my $cgi = CGI->new;
+ my $param = $cgi->param('foo');
+ $param = decode( 'UTF-8',$param );
+
+- -putdata\_upload
+
+ Makes `$cgi->param('PUTDATA');` and `$cgi->param('POSTDATA');`
+ act like file uploads named PUTDATA and POSTDATA. See
+ ["Handling non-urlencoded arguments"](#handling-non-urlencoded-arguments) and ["Processing a file upload field"](#processing-a-file-upload-field)
+ PUTDATA/POSTDATA are also available via
+ [upload\_hook](#progress-bars-for-file-uploads-and-avoiding-temp-files).
+
+- -nph
+
+ This makes CGI.pm produce a header appropriate for an NPH (no parsed header)
+ script. You may need to do other things as well to tell the server that the
+ script is NPH. See the discussion of NPH scripts below.
+
+- -newstyle\_urls
+
+ Separate the name=value pairs in CGI parameter query strings with semicolons
+ rather than ampersands. For example:
+
+ ?name=fred;age=24;favorite_color=3
+
+ Semicolon-delimited query strings are always accepted, and will be emitted by
+ self\_url() and query\_string(). newstyle\_urls became the default in version
+ 2.64.
+
+- -oldstyle\_urls
+
+ Separate the name=value pairs in CGI parameter query strings with ampersands
+ rather than semicolons. This is no longer the default.
+
+- -no\_debug
+
+ This turns off the command-line processing features. If you want to run a CGI.pm
+ script from the command line, and you don't want it to read CGI parameters from
+ the command line or STDIN, then use this pragma:
+
+ use CGI qw/ -no_debug :standard /;
+
+- -debug
+
+ This turns on full debugging. In addition to reading CGI arguments from the
+ command-line processing, CGI.pm will pause and try to read arguments from STDIN,
+ producing the message "(offline mode: enter name=value pairs on standard input)"
+ features.
+
+ See the section on debugging for more details.
+
+# GENERATING DYNAMIC DOCUMENTS
+
+Most of CGI.pm's functions deal with creating documents on the fly. Generally
+you will produce the HTTP header first, followed by the document itself. CGI.pm
+provides functions for generating HTTP headers of various types.
+
+Each of these functions produces a fragment of HTTP which you can print out
+directly so that it is processed by the browser, appended to a string, or saved
+to a file for later use.
+
+## Creating a standard http header
+
+Normally the first thing you will do in any CGI script is print out an HTTP
+header. This tells the browser what type of document to expect, and gives other
+optional information, such as the language, expiration date, and whether to
+cache the document. The header can also be manipulated for special purposes,
+such as server push and pay per view pages.
+
+ use strict;
+ use warnings;
+
+ use CGI;
+
+ my $cgi = CGI->new;
+
+ print $cgi->header;
+
+ -or-
+
+ print $cgi->header('image/gif');
+
+ -or-
+
+ print $cgi->header('text/html','204 No response');
+
+ -or-
+
+ print $cgi->header(
+ -type => 'image/gif',
+ -nph => 1,
+ -status => '402 Payment required',
+ -expires => '+3d',
+ -cookie => $cookie,
+ -charset => 'utf-8',
+ -attachment => 'foo.gif',
+ -Cost => '$2.00'
+ );
+
+header() returns the Content-type: header. You can provide your own MIME type
+if you choose, otherwise it defaults to text/html. An optional second parameter
+specifies the status code and a human-readable message. For example, you can
+specify 204, "No response" to create a script that tells the browser to do
+nothing at all. Note that RFC 2616 expects the human-readable phase to be there
+as well as the numeric status code.
+
+The last example shows the named argument style for passing arguments to the CGI
+methods using named parameters. Recognized parameters are **-type**, **-status**,
+**-expires**, and **-cookie**. Any other named parameters will be stripped of
+their initial hyphens and turned into header fields, allowing you to specify
+any HTTP header you desire. Internal underscores will be turned into hyphens:
+
+ print $cgi->header( -Content_length => 3002 );
+
+Most browsers will not cache the output from CGI scripts. Every time the browser
+reloads the page, the script is invoked anew. You can change this behavior with
+the **-expires** parameter. When you specify an absolute or relative expiration
+interval with this parameter, some browsers and proxy servers will cache the
+script's output until the indicated expiration date. The following forms are all
+valid for the -expires field:
+
+ +30s 30 seconds from now
+ +10m ten minutes from now
+ +1h one hour from now
+ -1d yesterday (i.e. "ASAP!")
+ now immediately
+ +3M in three months
+ +10y in ten years time
+ Thursday, 25-Apr-2018 00:40:33 GMT at the indicated time & date
+
+The **-cookie** parameter generates a header that tells the browser to provide
+a "magic cookie" during all subsequent transactions with your script. Some
+cookies have a special format that includes interesting attributes such as
+expiration time. Use the cookie() method to create and retrieve session cookies.
+
+The **-nph** parameter, if set to a true value, will issue the correct headers
+to work with a NPH (no-parse-header) script. This is important to use with
+certain servers that expect all their scripts to be NPH.
+
+The **-charset** parameter can be used to control the character set sent to the
+browser. If not provided, defaults to ISO-8859-1. As a side effect, this sets
+the charset() method as well. **Note** that the default being ISO-8859-1 may not
+make sense for all content types, e.g.:
+
+ Content-Type: image/gif; charset=ISO-8859-1
+
+In the above case you need to pass -charset => '' to prevent the default being
+used.
+
+The **-attachment** parameter can be used to turn the page into an attachment.
+Instead of displaying the page, some browsers will prompt the user to save it
+to disk. The value of the argument is the suggested name for the saved file. In
+order for this to work, you may have to set the **-type** to
+"application/octet-stream".
+
+The **-p3p** parameter will add a P3P tag to the outgoing header. The parameter
+can be an arrayref or a space-delimited string of P3P tags. For example:
+
+ print $cgi->header( -p3p => [ qw/ CAO DSP LAW CURa / ] );
+ print $cgi->header( -p3p => 'CAO DSP LAW CURa' );
+
+In either case, the outgoing header will be formatted as:
+
+ P3P: policyref="/w3c/p3p.xml" cp="CAO DSP LAW CURa"
+
+CGI.pm will accept valid multi-line headers when each line is separated with a
+CRLF value ("\\r\\n" on most platforms) followed by at least one space. For
+example:
+
+ print $cgi->header( -ingredients => "ham\r\n\seggs\r\n\sbacon" );
+
+Invalid multi-line header input will trigger in an exception. When multi-line
+headers are received, CGI.pm will always output them back as a single line,
+according to the folding rules of RFC 2616: the newlines will be removed, while
+the white space remains.
+
+## Generating a redirection header
+
+ print $q->redirect( 'http://somewhere.else/in/movie/land' );
+
+Sometimes you don't want to produce a document yourself, but simply redirect
+the browser elsewhere, perhaps choosing a URL based on the time of day or the
+identity of the user.
+
+The redirect() method redirects the browser to a different URL. If you use
+redirection like this, you should **not** print out a header as well.
+
+You should always use full URLs (including the http: or ftp: part) in
+redirection requests. Relative URLs will not work correctly.
+
+You can also use named arguments:
+
+ print $q->redirect(
+ -uri => 'http://somewhere.else/in/movie/land',
+ -nph => 1,
+ -status => '301 Moved Permanently'
+ );
+
+All names arguments recognized by header() are also recognized by redirect().
+However, most HTTP headers, including those generated by -cookie and -target,
+are ignored by the browser.
+
+The **-nph** parameter, if set to a true value, will issue the correct headers
+to work with a NPH (no-parse-header) script. This is important to use with
+certain servers, such as Microsoft IIS, which expect all their scripts to be
+NPH.
+
+The **-status** parameter will set the status of the redirect. HTTP defines
+several different possible redirection status codes, and the default if not
+specified is 302, which means "moved temporarily." You may change the status
+to another status code if you wish.
+
+Note that the human-readable phrase is also expected to be present to conform
+with RFC 2616, section 6.1.
+
+## Creating a self-referencing url that preserves state information
+
+ my $myself = $q->self_url;
+ print qq(<a href="$myself">I'm talking to myself.</a>);
+
+self\_url() will return a URL, that, when selected, will re-invoke this script
+with all its state information intact. This is most useful when you want to
+jump around within the document using internal anchors but you don't want to
+disrupt the current contents of the form(s). Something like this will do the
+trick:
+
+ my $myself = $q->self_url;
+ print "<a href=\"$myself#table1\">See table 1</a>";
+ print "<a href=\"$myself#table2\">See table 2</a>";
+ print "<a href=\"$myself#yourself\">See for yourself</a>";
+
+If you want more control over what's returned, using the **url()** method
+instead.
+
+You can also retrieve a query string representation of the current object
+state with query\_string():
+
+ my $the_string = $q->query_string();
+
+The behavior of calling query\_string is currently undefined when the HTTP method
+is something other than GET.
+
+If you want to retrieved the query string as set in the webserver, namely the
+environment variable, you can call env\_query\_string()
+
+## Obtaining the script's url
+
+ my $full_url = url();
+ my $full_url = url( -full =>1 ); # alternative syntax
+ my $relative_url = url( -relative => 1 );
+ my $absolute_url = url( -absolute =>1 );
+ my $url_with_path = url( -path_info => 1 );
+ my $url_path_qry = url( -path_info => 1, -query =>1 );
+ my $netloc = url( -base => 1 );
+
+**url()** returns the script's URL in a variety of formats. Called without any
+arguments, it returns the full form of the URL, including host name and port
+number
+
+ http://your.host.com/path/to/script.cgi
+
+You can modify this format with the following named arguments:
+
+- **-absolute**
+
+ If true, produce an absolute URL, e.g.
+
+ /path/to/script.cgi
+
+- **-relative**
+
+ Produce a relative URL. This is useful if you want to re-invoke your
+ script with different parameters. For example:
+
+ script.cgi
+
+- **-full**
+
+ Produce the full URL, exactly as if called without any arguments. This overrides
+ the -relative and -absolute arguments.
+
+- **-path** (**-path\_info**)
+
+ Append the additional path information to the URL. This can be combined with
+ **-full**, **-absolute** or **-relative**. **-path\_info** is provided as a synonym.
+
+- **-query** (**-query\_string**)
+
+ Append the query string to the URL. This can be combined with **-full**,
+ **-absolute** or **-relative**. **-query\_string** is provided as a synonym.
+
+- **-base**
+
+ Generate just the protocol and net location, as in http://www.foo.com:8000
+
+- **-rewrite**
+
+ If Apache's mod\_rewrite is turned on, then the script name and path info
+ probably won't match the request that the user sent. Set -rewrite => 1 (default)
+ to return URLs that match what the user sent (the original request URI). Set
+ \-rewrite => 0 to return URLs that match the URL after the mod\_rewrite rules have
+ run.
+
+## Mixing post and url parameters
+
+ my $color = url_param('color');
+
+It is possible for a script to receive CGI parameters in the URL as well as in
+the fill-out form by creating a form that POSTs to a URL containing a query
+string (a "?" mark followed by arguments). The **param()** method will always
+return the contents of the POSTed fill-out form, ignoring the URL's query
+string. To retrieve URL parameters, call the **url\_param()** method. Use it in
+the same way as **param()**. The main difference is that it allows you to read
+the parameters, but not set them.
+
+Under no circumstances will the contents of the URL query string interfere with
+similarly-named CGI parameters in POSTed forms. If you try to mix a URL query
+string with a form submitted with the GET method, the results will not be what
+you expect.
+
+## Processing a file upload field
+
+### Basics
+
+When the form is processed, you can retrieve an [IO::File](https://metacpan.org/pod/IO::File) compatible handle
+for a file upload field like this:
+
+ use autodie;
+
+ # undef may be returned if it's not a valid file handle
+ if ( my $io_handle = $q->upload('field_name') ) {
+ open ( my $out_file,'>>','/usr/local/web/users/feedback' );
+ while ( my $bytesread = $io_handle->read($buffer,1024) ) {
+ print $out_file $buffer;
+ }
+ }
+
+In a list context, upload() will return an array of filehandles. This makes it
+possible to process forms that use the same name for multiple upload fields.
+
+If you want the entered file name for the file, you can just call param():
+
+ my $filename = $q->param('field_name');
+
+Different browsers will return slightly different things for the name. Some
+browsers return the filename only. Others return the full path to the file,
+using the path conventions of the user's machine. Regardless, the name returned
+is always the name of the file on the _user's_ machine, and is unrelated to
+the name of the temporary file that CGI.pm creates during upload spooling
+(see below).
+
+When a file is uploaded the browser usually sends along some information along
+with it in the format of headers. The information usually includes the MIME
+content type. To retrieve this information, call uploadInfo(). It returns a
+reference to a hash containing all the document headers.
+
+ my $filehandle = $q->upload( 'uploaded_file' );
+ my $type = $q->uploadInfo( $filehandle )->{'Content-Type'};
+ if ( $type ne 'text/html' ) {
+ die "HTML FILES ONLY!";
+ }
+
+Note that you must use ->upload or ->param to get the file-handle to pass into
+uploadInfo as internally this is represented as a File::Temp object (which is
+what will be returned by ->upload or ->param). When using ->Vars you will get
+the literal filename rather than the File::Temp object, which will not return
+anything when passed to uploadInfo. So don't use ->Vars.
+
+If you are using a machine that recognizes "text" and "binary" data modes, be
+sure to understand when and how to use them (see the Camel book). Otherwise
+you may find that binary files are corrupted during file uploads.
+
+### Accessing the temp files directly
+
+When processing an uploaded file, CGI.pm creates a temporary file on your hard
+disk and passes you a file handle to that file. After you are finished with the
+file handle, CGI.pm unlinks (deletes) the temporary file. If you need to you
+can access the temporary file directly. You can access the temp file for a file
+upload by passing the file name to the tmpFileName() method:
+
+ my $filehandle = $query->upload( 'uploaded_file' );
+ my $tmpfilename = $query->tmpFileName( $filehandle );
+
+As with ->uploadInfo, using the reference returned by ->upload or ->param is
+preferred, although unlike ->uploadInfo, plain filenames also work if possible
+for backwards compatibility.
+
+The temporary file will be deleted automatically when your program exits unless
+you manually rename it or set $CGI::UNLINK\_TMP\_FILES to 0. On some operating
+systems (such as Windows NT), you will need to close the temporary file's
+filehandle before your program exits. Otherwise the attempt to delete the
+temporary file will fail.
+
+### Changes in temporary file handling (v4.05+)
+
+CGI.pm had its temporary file handling significantly refactored, this logic is
+now all deferred to File::Temp (which is wrapped in a compatibility object,
+CGI::File::Temp - **DO NOT USE THIS PACKAGE DIRECTLY**). As a consequence the
+PRIVATE\_TEMPFILES variable has been removed along with deprecation of the
+private\_tempfiles routine and **complete** removal of the CGITempFile package.
+The $CGITempFile::TMPDIRECTORY is no longer used to set the temp directory,
+refer to the perldoc for File::Temp if you want to override the default
+settings in that package (the TMPDIR env variable is still available on some
+platforms). For Windows platforms the temporary directory order remains
+as before: TEMP > TMP > WINDIR ( > TMPDIR ) so if you have any of these in
+use in existing scripts they should still work.
+
+The Fh package still exists but does nothing, the CGI::File::Temp class is
+a subclass of both File::Temp and the empty Fh package, so if you have any
+code that checks that the filehandle isa Fh this should still work.
+
+When you get the internal file handle you will receive a File::Temp object,
+this should be transparent as File::Temp isa IO::Handle and isa IO::Seekable
+meaning it behaves as previously. If you are doing anything out of the ordinary
+with regards to temp files you should test your code before deploying this
+update and refer to the File::Temp documentation for more information.
+
+### Handling interrupted file uploads
+
+There are occasionally problems involving parsing the uploaded file. This
+usually happens when the user presses "Stop" before the upload is finished. In
+this case, CGI.pm will return undef for the name of the uploaded file and set
+_cgi\_error()_ to the string "400 Bad request (malformed multipart POST)". This
+error message is designed so that you can incorporate it into a status code to
+be sent to the browser. Example:
+
+ my $file = $q->upload( 'uploaded_file' );
+ if ( !$file && $q->cgi_error ) {
+ print $q->header( -status => $q->cgi_error );
+ exit 0;
+ }
+
+### Progress bars for file uploads and avoiding temp files
+
+CGI.pm gives you low-level access to file upload management through a file
+upload hook. You can use this feature to completely turn off the temp file
+storage of file uploads, or potentially write your own file upload progress
+meter.
+
+This is much like the UPLOAD\_HOOK facility available in [Apache::Request](https://metacpan.org/pod/Apache::Request),
+with the exception that the first argument to the callback is an
+[Apache::Upload](https://metacpan.org/pod/Apache::Upload) object, here it's the remote filename.
+
+ my $q = CGI->new( \&hook [,$data [,$use_tempfile]] );
+
+ sub hook {
+ my ( $filename, $buffer, $bytes_read, $data ) = @_;
+ print "Read $bytes_read bytes of $filename\n";
+ }
+
+The `$data` field is optional; it lets you pass configuration information
+(e.g. a database handle) to your hook callback.
+
+The `$use_tempfile` field is a flag that lets you turn on and off CGI.pm's
+use of a temporary disk-based file during file upload. If you set this to a
+FALSE value (default true) then $q->param('uploaded\_file') will no longer work,
+and the only way to get at the uploaded data is via the hook you provide.
+
+If using the function-oriented interface, call the CGI::upload\_hook() method
+before calling param() or any other CGI functions:
+
+ CGI::upload_hook( \&hook [,$data [,$use_tempfile]] );
+
+This method is not exported by default. You will have to import it explicitly
+if you wish to use it without the CGI:: prefix.
+
+### Troubleshooting file uploads on Windows
+
+If you are using CGI.pm on a Windows platform and find that binary files get
+slightly larger when uploaded but that text files remain the same, then you
+have forgotten to activate binary mode on the output filehandle. Be sure to call
+binmode() on any handle that you create to write the uploaded file to disk.
+
+### Older ways to process file uploads
+
+This section is here for completeness. if you are building a new application
+with CGI.pm, you can skip it.
+
+The original way to process file uploads with CGI.pm was to use param(). The
+value it returns has a dual nature as both a file name and a lightweight
+filehandle. This dual nature is problematic if you following the recommended
+practice of having `use strict` in your code. perl will complain when you try
+to use a string as a filehandle. More seriously, it is possible for the remote
+user to type garbage into the upload field, in which case what you get from
+param() is not a filehandle at all, but a string.
+
+To solve this problem the upload() method was added, which always returns a
+lightweight filehandle. This generally works well, but will have trouble
+interoperating with some other modules because the file handle is not derived
+from [IO::File](https://metacpan.org/pod/IO::File). So that brings us to current recommendation given above,
+which is to call the handle() method on the file handle returned by upload().
+That upgrades the handle to an IO::File. It's a big win for compatibility for
+a small penalty of loading IO::File the first time you call it.
+
+# HTTP COOKIES
+
+CGI.pm has several methods that support cookies.
+
+A cookie is a name=value pair much like the named parameters in a CGI query
+string. CGI scripts create one or more cookies and send them to the browser
+in the HTTP header. The browser maintains a list of cookies that belong to a
+particular Web server, and returns them to the CGI script during subsequent
+interactions.
+
+In addition to the required name=value pair, each cookie has several optional
+attributes:
+
+- 1. an expiration time
+
+ This is a time/date string (in a special GMT format) that indicates when a
+ cookie expires. The cookie will be saved and returned to your script until this
+ expiration date is reached if the user exits the browser and restarts it. If an
+ expiration date isn't specified, the cookie will remain active until the user
+ quits the browser.
+
+- 2. a domain
+
+ This is a partial or complete domain name for which the cookie is valid. The
+ browser will return the cookie to any host that matches the partial domain name.
+ For example, if you specify a domain name of ".capricorn.com", then the browser
+ will return the cookie to Web servers running on any of the machines
+ "www.capricorn.com", "www2.capricorn.com", "feckless.capricorn.com", etc. Domain
+ names must contain at least two periods to prevent attempts to match on top
+ level domains like ".edu". If no domain is specified, then the browser will
+ only return the cookie to servers on the host the cookie originated from.
+
+- 3. a path
+
+ If you provide a cookie path attribute, the browser will check it against your
+ script's URL before returning the cookie. For example, if you specify the path
+ "/cgi-bin", then the cookie will be returned to each of the scripts
+ "/cgi-bin/tally.pl", "/cgi-bin/order.pl", and
+ "/cgi-bin/customer\_service/complain.pl", but not to the script
+ "/cgi-private/site\_admin.pl". By default, path is set to "/", which causes the
+ cookie to be sent to any CGI script on your site.
+
+- 4. a "secure" flag
+
+ If the "secure" attribute is set, the cookie will only be sent to your script
+ if the CGI request is occurring on a secure channel, such as SSL.
+
+The interface to HTTP cookies is the **cookie()** method:
+
+ my $cookie = $q->cookie(
+ -name => 'sessionID',
+ -value => 'xyzzy',
+ -expires => '+1h',
+ -path => '/cgi-bin/database',
+ -domain => '.capricorn.org',
+ -secure => 1
+ );
+
+ print $q->header( -cookie => $cookie );
+
+**cookie()** creates a new cookie. Its parameters include:
+
+- **-name**
+
+ The name of the cookie (required). This can be any string at all. Although
+ browsers limit their cookie names to non-whitespace alphanumeric characters,
+ CGI.pm removes this restriction by escaping and unescaping cookies behind the
+ scenes.
+
+- **-value**
+
+ The value of the cookie. This can be any scalar value, array reference, or even
+ hash reference. For example, you can store an entire hash into a cookie this
+ way:
+
+ my $cookie = $q->cookie(
+ -name => 'family information',
+ -value => \%childrens_ages
+ );
+
+- **-path**
+
+ The optional partial path for which this cookie will be valid, as described
+ above.
+
+- **-domain**
+
+ The optional partial domain for which this cookie will be valid, as described
+ above.
+
+- **-expires**
+
+ The optional expiration date for this cookie. The format is as described in the
+ section on the **header()** method:
+
+ "+1h" one hour from now
+
+- **-secure**
+
+ If set to true, this cookie will only be used within a secure SSL session.
+
+The cookie created by cookie() must be incorporated into the HTTP header within
+the string returned by the header() method:
+
+ use strict;
+ use warnings;
+
+ use CGI;
+
+ my $q = CGI->new;
+ my $cookie = ...
+ print $q->header( -cookie => $cookie );
+
+To create multiple cookies, give header() an array reference:
+
+ my $cookie1 = $q->cookie(
+ -name => 'riddle_name',
+ -value => "The Sphynx's Question"
+ );
+
+ my $cookie2 = $q->cookie(
+ -name => 'answers',
+ -value => \%answers
+ );
+
+ print $q->header( -cookie => [ $cookie1,$cookie2 ] );
+
+To retrieve a cookie, request it by name by calling cookie() method without the
+**-value** parameter. This example uses the object-oriented form:
+
+ my $riddle = $q->cookie('riddle_name');
+ my %answers = $query->cookie('answers');
+
+Cookies created with a single scalar value, such as the "riddle\_name" cookie,
+will be returned in that form. Cookies with array and hash values can also be
+retrieved.
+
+The cookie and CGI namespaces are separate. If you have a parameter named
+'answers' and a cookie named 'answers', the values retrieved by param() and
+cookie() are independent of each other. However, it's simple to turn a CGI
+parameter into a cookie, and vice-versa:
+
+ # turn a CGI parameter into a cookie
+ my $c = cookie( -name => 'answers',-value => [$q->param('answers')] );
+ # vice-versa
+ $q->param( -name => 'answers',-value => [ $q->cookie('answers')] );
+
+If you call cookie() without any parameters, it will return a list of
+the names of all cookies passed to your script:
+
+ my @cookies = $q->cookie();
+
+See the **cookie.cgi** example script for some ideas on how to use cookies
+effectively.
+
+# DEBUGGING
+
+If you are running the script from the command line or in the perl debugger,
+you can pass the script a list of keywords or parameter=value pairs on the
+command line or from standard input (you don't have to worry about tricking
+your script into reading from environment variables). You can pass keywords
+like this:
+
+ your_script.pl keyword1 keyword2 keyword3
+
+or this:
+
+ your_script.pl keyword1+keyword2+keyword3
+
+or this:
+
+ your_script.pl name1=value1 name2=value2
+
+or this:
+
+ your_script.pl name1=value1&name2=value2
+
+To turn off this feature, use the -no\_debug pragma.
+
+To test the POST method, you may enable full debugging with the -debug pragma.
+This will allow you to feed newline-delimited name=value pairs to the script on
+standard input.
+
+When debugging, you can use quotes and backslashes to escape characters in the
+familiar shell manner, letting you place spaces and other funny characters in
+your parameter=value pairs:
+
+ your_script.pl "name1='I am a long value'" "name2=two\ words"
+
+Finally, you can set the path info for the script by prefixing the first
+name/value parameter with the path followed by a question mark (?):
+
+ your_script.pl /your/path/here?name1=value1&name2=value2
+
+# FETCHING ENVIRONMENT VARIABLES
+
+Some of the more useful environment variables can be fetched through this
+interface. The methods are as follows:
+
+- **Accept()**
+
+ Return a list of MIME types that the remote browser accepts. If you give this
+ method a single argument corresponding to a MIME type, as in
+ Accept('text/html'), it will return a floating point value corresponding to the
+ browser's preference for this type from 0.0 (don't want) to 1.0. Glob types
+ (e.g. text/\*) in the browser's accept list are handled correctly.
+
+ Note that the capitalization changed between version 2.43 and 2.44 in order to
+ avoid conflict with perl's accept() function.
+
+- **raw\_cookie()**
+
+ Returns the HTTP\_COOKIE variable. Cookies have a special format, and this
+ method call just returns the raw form (?cookie dough). See cookie() for ways
+ of setting and retrieving cooked cookies.
+
+ Called with no parameters, raw\_cookie() returns the packed cookie structure.
+ You can separate it into individual cookies by splitting on the character
+ sequence "; ". Called with the name of a cookie, retrieves the **unescaped**
+ form of the cookie. You can use the regular cookie() method to get the names,
+ or use the raw\_fetch() method from the CGI::Cookie module.
+
+- **env\_query\_string()**
+
+ Returns the QUERY\_STRING variable, note that this is the original value as set
+ in the environment by the webserver and (possibly) not the same value as
+ returned by query\_string(), which represents the object state
+
+- **user\_agent()**
+
+ Returns the HTTP\_USER\_AGENT variable. If you give this method a single
+ argument, it will attempt to pattern match on it, allowing you to do something
+ like user\_agent(Mozilla);
+
+- **path\_info()**
+
+ Returns additional path information from the script URL. E.G. fetching
+ /cgi-bin/your\_script/additional/stuff will result in path\_info() returning
+ "/additional/stuff".
+
+ NOTE: The Microsoft Internet Information Server is broken with respect to
+ additional path information. If you use the perl DLL library, the IIS server
+ will attempt to execute the additional path information as a perl script. If
+ you use the ordinary file associations mapping, the path information will be
+ present in the environment, but incorrect. The best thing to do is to avoid
+ using additional path information in CGI scripts destined for use with IIS. A
+ best attempt has been made to make CGI.pm do the right thing.
+
+- **path\_translated()**
+
+ As per path\_info() but returns the additional path information translated into
+ a physical path, e.g. "/usr/local/etc/httpd/htdocs/additional/stuff".
+
+ The Microsoft IIS is broken with respect to the translated path as well.
+
+- **remote\_host()**
+
+ Returns either the remote host name or IP address if the former is unavailable.
+
+- **remote\_ident()**
+
+ Returns the name of the remote user (as returned by identd) or undef if not set
+
+- **remote\_addr()**
+
+ Returns the remote host IP address, or 127.0.0.1 if the address is unavailable.
+
+- **request\_uri()**
+
+ Returns the interpreted pathname of the requested document or CGI (relative to
+ the document root). Or undef if not set.
+
+- **script\_name()**
+
+ Return the script name as a partial URL, for self-referring scripts.
+
+- **referer()**
+
+ Return the URL of the page the browser was viewing prior to fetching your
+ script.
+
+- **auth\_type()**
+
+ Return the authorization/verification method in use for this script, if any.
+
+- **server\_name()**
+
+ Returns the name of the server, usually the machine's host name.
+
+- **virtual\_host()**
+
+ When using virtual hosts, returns the name of the host that the browser
+ attempted to contact
+
+- **server\_port()**
+
+ Return the port that the server is listening on.
+
+- **server\_protocol()**
+
+ Returns the protocol and revision of the incoming request, or defaults to
+ HTTP/1.0 if this is not set
+
+- **virtual\_port()**
+
+ Like server\_port() except that it takes virtual hosts into account. Use this
+ when running with virtual hosts.
+
+- **server\_software()**
+
+ Returns the server software and version number.
+
+- **remote\_user()**
+
+ Return the authorization/verification name used for user verification, if this
+ script is protected.
+
+- **user\_name()**
+
+ Attempt to obtain the remote user's name, using a variety of different
+ techniques. May not work in all browsers.
+
+- **request\_method()**
+
+ Returns the method used to access your script, usually one of 'POST', 'GET'
+ or 'HEAD'.
+
+- **content\_type()**
+
+ Returns the content\_type of data submitted in a POST, generally
+ multipart/form-data or application/x-www-form-urlencoded
+
+- **http()**
+
+ Called with no arguments returns the list of HTTP environment variables,
+ including such things as HTTP\_USER\_AGENT, HTTP\_ACCEPT\_LANGUAGE, and
+ HTTP\_ACCEPT\_CHARSET, corresponding to the like-named HTTP header fields in the
+ request. Called with the name of an HTTP header field, returns its value.
+ Capitalization and the use of hyphens versus underscores are not significant.
+
+ For example, all three of these examples are equivalent:
+
+ my $requested_language = $q->http('Accept-language');
+
+ my $requested_language = $q->http('Accept_language');
+
+ my $requested_language = $q->http('HTTP_ACCEPT_LANGUAGE');
+
+- **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.
+
+# USING NPH SCRIPTS
+
+NPH, or "no-parsed-header", scripts bypass the server completely by sending the
+complete HTTP header directly to the browser. This has slight performance
+benefits, but is of most use for taking advantage of HTTP extensions that are
+not directly supported by your server, such as server push and PICS headers.
+
+Servers use a variety of conventions for designating CGI scripts as NPH. Many
+Unix servers look at the beginning of the script's name for the prefix "nph-".
+The Macintosh WebSTAR server and Microsoft's Internet Information Server, in
+contrast, try to decide whether a program is an NPH script by examining the
+first line of script output.
+
+CGI.pm supports NPH scripts with a special NPH mode. When in this mode, CGI.pm
+will output the necessary extra header information when the header() and
+redirect() methods are called.
+
+The Microsoft Internet Information Server requires NPH mode. As of version 2.30,
+CGI.pm will automatically detect when the script is running under IIS and put
+itself into this mode. You do not need to do this manually, although it won't
+hurt anything if you do.
+
+- In the **use** statement
+
+ Simply add the "-nph" pragma to the list of symbols to be imported into
+ your script:
+
+ use CGI qw(:standard -nph)
+
+- By calling the **nph()** method:
+
+ Call **nph()** with a non-zero parameter at any point after using CGI.pm in your
+ program.
+
+ CGI->nph(1)
+
+- By using **-nph** parameters
+
+ in the **header()** and **redirect()** statements:
+
+ print header(-nph=>1);
+
+# SERVER PUSH
+
+CGI.pm provides four simple functions for producing multipart documents of the
+type needed to implement server push. These functions were graciously provided
+by Ed Jordan <ed@fidalgo.net>. To import these into your namespace, you must
+import the ":push" set. You are also advised to put the script into NPH mode
+and to set $| to 1 to avoid buffering problems.
+
+Here is a simple script that demonstrates server push:
+
+ #!/usr/bin/env perl
+
+ use strict;
+ use warnings;
+
+ use CGI qw/:push -nph/;
+
+ $| = 1;
+ print multipart_init( -boundary=>'----here we go!' );
+ for (0 .. 4) {
+ print multipart_start( -type=>'text/plain' ),
+ "The current time is ",scalar( localtime ),"\n";
+ if ($_ < 4) {
+ print multipart_end();
+ } else {
+ print multipart_final();
+ }
+ sleep 1;
+ }
+
+This script initializes server push by calling **multipart\_init()**. It then
+enters a loop in which it begins a new multipart section by calling
+**multipart\_start()**, prints the current local time, and ends a multipart
+section with **multipart\_end()**. It then sleeps a second, and begins again.
+On the final iteration, it ends the multipart section with
+**multipart\_final()** rather than with **multipart\_end()**.
+
+- multipart\_init()
+
+ multipart_init( -boundary => $boundary, -charset => $charset );
+
+ Initialize the multipart system. The -boundary argument specifies what MIME
+ boundary string to use to separate parts of the document. If not provided,
+ CGI.pm chooses a reasonable boundary for you.
+
+ The -charset provides the character set, if not provided this will default to
+ ISO-8859-1
+
+- multipart\_start()
+
+ multipart_start( -type => $type, -charset => $charset );
+
+ Start a new part of the multipart document using the specified MIME type and
+ charset. If not specified, text/html ISO-8859-1 is assumed.
+
+- multipart\_end()
+
+ multipart_end()
+
+ End a part. You must remember to call multipart\_end() once for each
+ multipart\_start(), except at the end of the last part of the multipart document
+ when multipart\_final() should be called instead of multipart\_end().
+
+- multipart\_final()
+
+ multipart_final()
+
+ End all parts. You should call multipart\_final() rather than multipart\_end()
+ at the end of the last part of the multipart document.
+
+Users interested in server push applications should also have a look at the
+CGI::Push module.
+
+# AVOIDING DENIAL OF SERVICE ATTACKS
+
+A potential problem with CGI.pm is that, by default, it attempts to process
+form POSTings no matter how large they are. A wily hacker could attack your
+site by sending a CGI script a huge POST of many gigabytes. CGI.pm will attempt
+to read the entire POST into a variable, growing hugely in size until it runs
+out of memory. While the script attempts to allocate the memory the system may
+slow down dramatically. This is a form of denial of service attack.
+
+Another possible attack is for the remote user to force CGI.pm to accept a huge
+file upload. CGI.pm will accept the upload and store it in a temporary directory
+even if your script doesn't expect to receive an uploaded file. CGI.pm will
+delete the file automatically when it terminates, but in the meantime the remote
+user may have filled up the server's disk space, causing problems for other
+programs.
+
+The best way to avoid denial of service attacks is to limit the amount of
+memory, CPU time and disk space that CGI scripts can use. Some Web servers come
+with built-in facilities to accomplish this. In other cases, you can use the
+shell _limit_ or _ulimit_ commands to put ceilings on CGI resource usage.
+
+CGI.pm also has some simple built-in protections against denial of service
+attacks, but you must activate them before you can use them. These take the
+form of two global variables in the CGI name space:
+
+- **$CGI::POST\_MAX**
+
+ If set to a non-negative integer, this variable puts a ceiling on the size of
+ POSTings, in bytes. If CGI.pm detects a POST that is greater than the ceiling,
+ it will immediately exit with an error message. This value will affect both
+ ordinary POSTs and multipart POSTs, meaning that it limits the maximum size of
+ file uploads as well. You should set this to a reasonably high
+ value, such as 10 megabytes.
+
+- **$CGI::DISABLE\_UPLOADS**
+
+ If set to a non-zero value, this will disable file uploads completely. Other
+ fill-out form values will work as usual.
+
+To use these variables, set the variable at the top of the script, right after
+the "use" statement:
+
+ #!/usr/bin/env perl
+
+ use strict;
+ use warnings;
+
+ use CGI;
+
+ $CGI::POST_MAX = 1024 * 1024 * 10; # max 10MB posts
+ $CGI::DISABLE_UPLOADS = 1; # no uploads
+
+An attempt to send a POST larger than $POST\_MAX bytes will cause _param()_ to
+return an empty CGI parameter list. You can test for this event by checking
+_cgi\_error()_, either after you create the CGI object or, if you are using the
+function-oriented interface, call <param()> for the first time. If the POST was
+intercepted, then cgi\_error() will return the message "413 POST too large".
+
+This error message is actually defined by the HTTP protocol, and is designed to
+be returned to the browser as the CGI script's status code. For example:
+
+ my $uploaded_file = $q->param('upload');
+ if ( !$uploaded_file && $q->cgi_error() ) {
+ print $q->header( -status => $q->cgi_error() );
+ exit 0;
+ }
+
+However it isn't clear that any browser currently knows what to do with this
+status code. It might be better just to create a page that warns the user of
+the problem.
+
+# COMPATIBILITY WITH CGI-LIB.PL
+
+To make it easier to port existing programs that use cgi-lib.pl the
+compatibility routine "ReadParse" is provided. Porting is simple:
+
+OLD VERSION
+
+ require "cgi-lib.pl";
+ &ReadParse;
+ print "The value of the antique is $in{antique}.\n";
+
+NEW VERSION
+
+ use CGI;
+ CGI::ReadParse();
+ print "The value of the antique is $in{antique}.\n";
+
+CGI.pm's ReadParse() routine creates a tied variable named %in, which can be
+accessed to obtain the query variables. Like ReadParse, you can also provide
+your own variable. Infrequently used features of ReadParse, such as the creation
+of @in and $in variables, are not supported.
+
+Once you use ReadParse, you can retrieve the query object itself this way:
+
+ my $q = $in{CGI};
+
+This allows you to start using the more interesting features of CGI.pm without
+rewriting your old scripts from scratch.
+
+An even simpler way to mix cgi-lib calls with CGI.pm calls is to import both the
+`:cgi-lib` and `:standard` method:
+
+ use CGI qw(:cgi-lib :standard);
+ &ReadParse;
+ print "The price of your purchase is $in{price}.\n";
+ print textfield(-name=>'price', -default=>'$1.99');
+
+## Cgi-lib functions that are available in CGI.pm
+
+In compatibility mode, the following cgi-lib.pl functions are
+available for your use:
+
+ ReadParse()
+ PrintHeader()
+ SplitParam()
+ MethGet()
+ MethPost()
+
+# LICENSE
+
+The CGI.pm distribution is copyright 1995-2007, Lincoln D. Stein. It is
+distributed under GPL and the Artistic License 2.0. It is currently maintained
+by Lee Johnson (LEEJO) with help from many contributors.
+
+# CREDITS
+
+Thanks very much to:
+
+- Mark Stosberg (mark@stosberg.com)
+- Matt Heffron (heffron@falstaff.css.beckman.com)
+- James Taylor (james.taylor@srs.gov)
+- Scott Anguish <sanguish@digifix.com>
+- Mike Jewell (mlj3u@virginia.edu)
+- Timothy Shimmin (tes@kbs.citri.edu.au)
+- Joergen Haegg (jh@axis.se)
+- Laurent Delfosse (delfosse@delfosse.com)
+- Richard Resnick (applepi1@aol.com)
+- Craig Bishop (csb@barwonwater.vic.gov.au)
+- Tony Curtis (tc@vcpc.univie.ac.at)
+- Tim Bunce (Tim.Bunce@ig.co.uk)
+- Tom Christiansen (tchrist@convex.com)
+- Andreas Koenig (k@franz.ww.TU-Berlin.DE)
+- Tim MacKenzie (Tim.MacKenzie@fulcrum.com.au)
+- Kevin B. Hendricks (kbhend@dogwood.tyler.wm.edu)
+- Stephen Dahmen (joyfire@inxpress.net)
+- Ed Jordan (ed@fidalgo.net)
+- David Alan Pisoni (david@cnation.com)
+- Doug MacEachern (dougm@opengroup.org)
+- Robin Houston (robin@oneworld.org)
+- ...and many many more...
+
+ for suggestions and bug fixes.
+
+# BUGS
+
+Address bug reports and comments to: [https://github.com/leejo/CGI.pm/issues](https://github.com/leejo/CGI.pm/issues)
+
+The original bug tracker can be found at:
+[https://rt.cpan.org/Public/Dist/Display.html?Queue=CGI.pm](https://rt.cpan.org/Public/Dist/Display.html?Queue=CGI.pm)
+
+When sending bug reports, please provide the version of CGI.pm, the version of
+perl, the name and version of your Web server, and the name and version of the
+operating system you are using. If the problem is even remotely browser
+dependent, please provide information about the affected browsers as well.
+
+Failing tests cases are appreciated with issues, and if you submit a patch then
+it will \*not\* be accepted unless you provide a reasonable automated test case
+with it (please see the existing tests in t/ for examples).
+
+Please note the CGI.pm is now considered "done". See also "mature" and "legacy".
+Feature requests and none critical issues will be outright rejected. The module
+is now in maintenance mode for critical issues only.
+
+# SEE ALSO
+
+[CGI::Carp](https://metacpan.org/pod/CGI::Carp) - provides [Carp](https://metacpan.org/pod/Carp) implementation tailored to the CGI environment.
+
+[CGI::Fast](https://metacpan.org/pod/CGI::Fast) - supports running CGI applications under FastCGI
diff --git a/examples/clickable_image.cgi b/examples/clickable_image.cgi
new file mode 100755
index 0000000..78d874f
--- /dev/null
+++ b/examples/clickable_image.cgi
@@ -0,0 +1,56 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+use CGI;
+use Template;
+
+my $cgi = CGI->new;
+my $template_vars;
+
+if ( $cgi->param ) {
+ foreach my $var ( qw/ magnification letter x y / ) {
+ $template_vars->{$var} = $cgi->param(
+ $var =~ /^[xy]$/ ? "picture.$var" : $var
+ );
+ }
+}
+
+my $tt = Template->new;
+print $cgi->header(
+ -type => 'text/html',
+ -charset => 'utf-8',
+);
+
+$tt->process( \*DATA,$template_vars ) or warn $tt->error;
+
+__DATA__
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <title>A Clickable Image</title>
+ </head>
+ <body>
+ <h1>A Clickable Image</h1>
+ </a>
+ <p>Sorry, this isn't very exciting!</p>
+ <form method="post" action="/clickable_image/">
+ <input type="image" name="picture" src="/wilogo.gif" />
+ <p>Give me a:
+ <select name="letter" >[%- FOREACH letter_opt IN [ 'A','B','C','D','E','W' ] %]
+ <option value="[% letter_opt %]" [% IF letter == letter_opt %]selected[% END %]>[% letter_opt %]</option>
+ [%- END %]</select>
+ </p>
+ <p>Magnification:
+ [% FOREACH magnification_opt IN [ 1,2,4,20 ] -%]
+ [%- %]<label><input type="radio" name="magnification" value="[% magnification_opt %]"[%
+ IF magnification.defined AND magnification == magnification_opt %] checked="checked"[% END
+ %]/>[% magnification_opt %]X</label>
+ [% END -%]
+ [%- IF x.defined AND y.defined %]
+ <p>Selected Position <strong>([% x %],[% y %])</strong></p>
+ [% END %]
+ </body>
+</html>
diff --git a/examples/cookie.cgi b/examples/cookie.cgi
new file mode 100755
index 0000000..1908238
--- /dev/null
+++ b/examples/cookie.cgi
@@ -0,0 +1,111 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+use CGI;
+use Template;
+
+my $cgi = CGI->new;
+
+my $template_vars = {
+ animals => [
+ sort qw/lion tiger bear pig porcupine ferret zebra gnu ostrich
+ emu moa goat weasel yak chicken sheep hyena dodo lounge-lizard
+ squirrel rat mouse hedgehog racoon baboon kangaroo hippopotamus
+ giraffe
+ /
+ ],
+};
+
+# Recover the previous animals from the magic cookie.
+# The cookie has been formatted as an associative array
+# mapping animal name to the number of animals.
+my %zoo = $cgi->cookie( 'animals' );
+
+# Recover the new animal(s) from the parameter 'new_animal'
+if ( my @new = $cgi->multi_param( 'new_animals' ) ) {
+
+ # If the action is 'add', then add new animals to the zoo. Otherwise
+ # delete them.
+ foreach ( @new ) {
+ if ( $cgi->param('action') eq 'Add' ) {
+ $zoo{$_}++;
+ } elsif ( $cgi->param('action') eq 'Delete' ) {
+ $zoo{$_}-- if $zoo{$_};
+ delete $zoo{$_} unless $zoo{$_};
+ }
+ }
+
+ $template_vars->{zoo} = \%zoo if keys( %zoo );
+}
+
+# Add new animals to old, and put them in a cookie
+my $the_cookie = $cgi->cookie(
+ -name => 'animals',
+ -value => \%zoo,
+ -expires => '+1h'
+);
+
+my $tt = Template->new;
+
+# Print the header, incorporating the cookie and the expiration date...
+print $cgi->header(
+ -cookie => $the_cookie,
+ -type => 'text/html',
+ -charset => 'utf-8',
+);
+
+$tt->process( \*DATA,$template_vars ) or warn $tt->error;
+
+__DATA__
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <title>Animal crackers</title>
+ </head>
+ <body>
+ <h1>Animal Crackers</h1>
+ <p>
+ Choose the animals you want to add to the zoo, and click "add".
+ Come back to this page any time within the next hour and the list of
+ animals in the zoo will be resurrected. You can even quit the browser
+ completely!
+ </p>
+ <p>
+ Try adding the same animal several times to the list. Does this
+ remind you vaguely of a shopping cart?
+ </p>
+ <p>
+ <center>
+ <table border>
+ <tr><th>Add/Delete<th>Current Contents
+ <tr><td><form method="post" action="https://127.0.0.1:3333/cookie/" enctype="multipart/form-data">
+ <select name="new_animals" size="10" multiple="multiple">
+ [% FOREACH animal IN animals %]
+ <option value="[% animal %]">[% animal %]</option>
+ [% END %]
+ </select>
+ <br>
+ <input type="submit" name="action" value="Delete" />
+ <input type="submit" name="action" value="Add" />
+ <div>
+ <input type="hidden" name=".cgifields" value="new_animals" />
+ </div>
+ </form>
+ <td>
+ [% IF zoo.defined %]
+ <ul>
+ [% FOREACH animal IN zoo.keys.sort %]
+ <li>[% zoo.$animal %] [% animal %]</li>
+ [% END %]
+ </ul>
+ [% ELSE %]
+ <strong>The zoo is empty.</strong>
+ [% END %]
+ </table>
+ </center>
+ <hr>
+ </body>
+</html>
diff --git a/examples/crash.cgi b/examples/crash.cgi
new file mode 100755
index 0000000..9ae97b1
--- /dev/null
+++ b/examples/crash.cgi
@@ -0,0 +1,9 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+use CGI::Carp qw/ fatalsToBrowser /;
+
+# This line invokes a fatal error message at compile time.
+foo bar baz;
diff --git a/examples/file_upload.cgi b/examples/file_upload.cgi
new file mode 100755
index 0000000..b48d737
--- /dev/null
+++ b/examples/file_upload.cgi
@@ -0,0 +1,74 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+use CGI;
+use CGI::Carp qw/ fatalsToBrowser /;
+use Template;
+
+my $cgi = CGI->new;
+my $template_vars = {
+ cgi_version => $CGI::VERSION,
+};
+
+# Process the form if there is a file name entered
+if ( my $file = $cgi->param( 'filename' ) ) {
+
+ my $tmpfile = $cgi->tmpFileName( $file );
+ my $mimetype = $cgi->uploadInfo( $file )->{'Content-Type'} || '';
+
+ @{$template_vars}{qw/file temp_file mimetype/}
+ = ( $file,$tmpfile,$mimetype );
+
+ my %wanted = map { $_ => 1 } $cgi->multi_param( 'count' );
+
+ while ( <$file> ) {
+ $template_vars->{lines}++ if $wanted{"count lines"};
+ $template_vars->{words} += split(/\s+/) if $wanted{"count words"};
+ $template_vars->{chars} += length if $wanted{"count chars"};
+ }
+ close( $file );
+}
+
+print $cgi->header(
+ -type => 'text/html',
+ -charset => 'utf-8',
+);
+
+my $tt = Template->new;
+$tt->process( \*DATA,$template_vars ) or warn $tt->error;
+
+__DATA__
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <title>File Upload Example</title>
+ </head>
+ <body>
+ <b>Version</b> [% cgi_version %]
+ <h1>File Upload Example</h1>
+ <p>This example demonstrates how to prompt the remote user to select a remote file for uploading.</p>
+ <p>Select the <cite>browser</cite> button to choose a text file to upload.</p>
+ <p>When you press the submit button, this script will count the number of lines, words, and characters in the file.</p>
+ <form method="post" action="file_upload" enctype="multipart/form-data">Enter the file to process:
+ <input type="file" name="filename" size="45" /><br />
+ <label><input type="checkbox" name="count" value="count lines" checked="checked" />count lines</label>
+ <label><input type="checkbox" name="count" value="count words" checked="checked" />count words</label>
+ <label><input type="checkbox" name="count" value="count chars" checked="checked" />count characters</label>
+ <input type="reset" name=".reset" />
+ <input type="submit" name="submit" value="Process File" />
+ <input type="hidden" name=".cgifields" value="count" />
+ </form>
+ <hr />
+ [% IF file.defined %]
+ <h2>[% file %]</h2>
+ <h3>[% temp_file %]</h3>
+ <h4>MIME Type: <i>[% mime_type %]</i></h4>
+ <b>Lines: </b>[% lines %]<br />
+ <b>Words: </b>[% words %]<br />
+ <b>Characters: </b>[% chars %]<br />
+ [% END %]
+ </body>
+</html>
diff --git a/examples/mojo_proxy.pl b/examples/mojo_proxy.pl
new file mode 100644
index 0000000..f973f8a
--- /dev/null
+++ b/examples/mojo_proxy.pl
@@ -0,0 +1,36 @@
+#!/usr/bin/env perl
+
+use Mojolicious::Lite;
+use Mojolicious::Plugin::CGI;
+
+my %cgi_scripts = (
+ '/clickable_image' => "clickable_image.cgi",
+ '/cookie' => "cookie.cgi",
+ '/crash' => "crash.cgi",
+ '/file_upload' => "file_upload.cgi",
+ '/wikipedia_ex' => "wikipedia_example.cgi",
+);
+
+foreach my $route ( sort keys( %cgi_scripts ) ) {
+ plugin CGI => [ $route => $cgi_scripts{$route} ];
+}
+
+any '/' => sub {
+ my ( $c ) = @_;
+ $c->stash( { cgi_scripts => { %cgi_scripts } } );
+ $c->render( 'index' );
+};
+
+app->start;
+
+__DATA__
+@@ index.html.ep
+<!doctype html><html>
+ <head><title>CGI Examples</title></head>
+ <body>
+ <h3>CGI Examples</h3>
+ % for my $route ( sort keys( %{ $cgi_scripts } ) ) {
+ <a href="<%= $route %>"><%= $cgi_scripts->{$route} %></a><br />
+ % }
+ </body>
+</html>
diff --git a/examples/wikipedia_example.cgi b/examples/wikipedia_example.cgi
new file mode 100755
index 0000000..fe17a79
--- /dev/null
+++ b/examples/wikipedia_example.cgi
@@ -0,0 +1,40 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+use CGI;
+
+my $cgi = CGI->new;
+
+print $cgi->header('text/html');
+
+print << "EndOfHTML";
+<!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>A Simple CGI Page</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+ </head>
+ <body>
+ <h1>A Simple CGI Page</h1>
+ <form method="post" enctype="multipart/form-data">
+ Name: <input type="text" name="name" /><br />
+ Age: <input type="text" name="age" /><p />
+ <input type="submit" name="Submit!" value="Submit!" />
+ </form>
+ <hr />
+EndOfHTML
+
+if ( my $name = $cgi->param('name') ) {
+ print "Your name is $name.<br />";
+}
+
+if ( my $age = $cgi->param('age') ) {
+ print "You are $age years old.";
+}
+
+print '</body></html>';
diff --git a/examples/wilogo.gif b/examples/wilogo.gif
new file mode 100644
index 0000000..a7c309e
--- /dev/null
+++ b/examples/wilogo.gif
Binary files differ
diff --git a/lib/CGI.pm b/lib/CGI.pm
new file mode 100644
index 0000000..3ed0d0e
--- /dev/null
+++ b/lib/CGI.pm
@@ -0,0 +1,3856 @@
+package CGI;
+require 5.008001;
+use if $] >= 5.019, 'deprecate';
+use Carp 'croak';
+
+$CGI::VERSION='4.21';
+
+use CGI::Util qw(rearrange rearrange_header make_attributes unescape escape expires ebcdic2ascii ascii2ebcdic);
+
+$_XHTML_DTD = ['-//W3C//DTD XHTML 1.0 Transitional//EN',
+ 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'];
+
+{
+ local $^W = 0;
+ $TAINTED = substr("$0$^X",0,0);
+}
+
+$MOD_PERL = 0; # no mod_perl by default
+
+#global settings
+$POST_MAX = -1; # no limit to uploaded files
+$DISABLE_UPLOADS = 0;
+$UNLINK_TMP_FILES = 1;
+$LIST_CONTEXT_WARN = 1;
+$ENCODE_ENTITIES = q{&<>"'};
+
+@SAVED_SYMBOLS = ();
+
+# >>>>> Here are some globals that you might want to adjust <<<<<<
+sub initialize_globals {
+ # Set this to 1 to generate XTML-compatible output
+ $XHTML = 1;
+
+ # Change this to the preferred DTD to print in start_html()
+ # or use default_dtd('text of DTD to use');
+ $DEFAULT_DTD = [ '-//W3C//DTD HTML 4.01 Transitional//EN',
+ 'http://www.w3.org/TR/html4/loose.dtd' ] ;
+
+ # Set this to 1 to enable NOSTICKY scripts
+ # or:
+ # 1) use CGI '-nosticky';
+ # 2) $CGI::NOSTICKY = 1;
+ $NOSTICKY = 0;
+
+ # Set this to 1 to enable NPH scripts
+ # or:
+ # 1) use CGI qw(-nph)
+ # 2) CGI::nph(1)
+ # 3) print header(-nph=>1)
+ $NPH = 0;
+
+ # Set this to 1 to enable debugging from @ARGV
+ # Set to 2 to enable debugging from STDIN
+ $DEBUG = 1;
+
+ # Set this to 1 to generate automatic tab indexes
+ $TABINDEX = 0;
+
+ # Set this to 1 to cause files uploaded in multipart documents
+ # to be closed, instead of caching the file handle
+ # or:
+ # 1) use CGI qw(:close_upload_files)
+ # 2) $CGI::close_upload_files(1);
+ # Uploads with many files run out of file handles.
+ # Also, for performance, since the file is already on disk,
+ # it can just be renamed, instead of read and written.
+ $CLOSE_UPLOAD_FILES = 0;
+
+ # Automatically determined -- don't change
+ $EBCDIC = 0;
+
+ # Change this to 1 to suppress redundant HTTP headers
+ $HEADERS_ONCE = 0;
+
+ # separate the name=value pairs by semicolons rather than ampersands
+ $USE_PARAM_SEMICOLONS = 1;
+
+ # Do not include undefined params parsed from query string
+ # use CGI qw(-no_undef_params);
+ $NO_UNDEF_PARAMS = 0;
+
+ # return everything as utf-8
+ $PARAM_UTF8 = 0;
+
+ # make param('PUTDATA') act like file upload
+ $PUTDATA_UPLOAD = 0;
+
+ # Other globals that you shouldn't worry about.
+ undef $Q;
+ $BEEN_THERE = 0;
+ $DTD_PUBLIC_IDENTIFIER = "";
+ undef @QUERY_PARAM;
+ undef %EXPORT;
+ undef $QUERY_CHARSET;
+ undef %QUERY_FIELDNAMES;
+ undef %QUERY_TMPFILES;
+
+ # prevent complaints by mod_perl
+ 1;
+}
+
+# ------------------ START OF THE LIBRARY ------------
+
+# make mod_perlhappy
+initialize_globals();
+
+# FIGURE OUT THE OS WE'RE RUNNING UNDER
+# Some systems support the $^O variable. If not
+# available then require() the Config library
+unless ($OS) {
+ unless ($OS = $^O) {
+ require Config;
+ $OS = $Config::Config{'osname'};
+ }
+}
+if ($OS =~ /^MSWin/i) {
+ $OS = 'WINDOWS';
+} elsif ($OS =~ /^VMS/i) {
+ $OS = 'VMS';
+} elsif ($OS =~ /^dos/i) {
+ $OS = 'DOS';
+} elsif ($OS =~ /^MacOS/i) {
+ $OS = 'MACINTOSH';
+} elsif ($OS =~ /^os2/i) {
+ $OS = 'OS2';
+} elsif ($OS =~ /^epoc/i) {
+ $OS = 'EPOC';
+} elsif ($OS =~ /^cygwin/i) {
+ $OS = 'CYGWIN';
+} elsif ($OS =~ /^NetWare/i) {
+ $OS = 'NETWARE';
+} else {
+ $OS = 'UNIX';
+}
+
+# Some OS logic. Binary mode enabled on DOS, NT and VMS
+$needs_binmode = $OS=~/^(WINDOWS|DOS|OS2|MSWin|CYGWIN|NETWARE)/;
+
+# This is the default class for the CGI object to use when all else fails.
+$DefaultClass = 'CGI' unless defined $CGI::DefaultClass;
+
+# The path separator is a slash, backslash or semicolon, depending
+# on the platform.
+$SL = {
+ UNIX => '/', OS2 => '\\', EPOC => '/', CYGWIN => '/', NETWARE => '/',
+ WINDOWS => '\\', DOS => '\\', MACINTOSH => ':', VMS => '/'
+ }->{$OS};
+
+# This no longer seems to be necessary
+# Turn on NPH scripts by default when running under IIS server!
+# $NPH++ if defined($ENV{'SERVER_SOFTWARE'}) && $ENV{'SERVER_SOFTWARE'}=~/IIS/;
+$IIS++ if defined($ENV{'SERVER_SOFTWARE'}) && $ENV{'SERVER_SOFTWARE'}=~/IIS/;
+
+# Turn on special checking for ActiveState's PerlEx
+$PERLEX++ if defined($ENV{'GATEWAY_INTERFACE'}) && $ENV{'GATEWAY_INTERFACE'} =~ /^CGI-PerlEx/;
+
+# Turn on special checking for Doug MacEachern's modperl
+# PerlEx::DBI tries to fool DBI by setting MOD_PERL
+if (exists $ENV{MOD_PERL} && ! $PERLEX) {
+ # mod_perl handlers may run system() on scripts using CGI.pm;
+ # Make sure so we don't get fooled by inherited $ENV{MOD_PERL}
+ if (exists $ENV{MOD_PERL_API_VERSION} && $ENV{MOD_PERL_API_VERSION} == 2) {
+ $MOD_PERL = 2;
+ require Apache2::Response;
+ require Apache2::RequestRec;
+ require Apache2::RequestUtil;
+ require Apache2::RequestIO;
+ require APR::Pool;
+ } else {
+ $MOD_PERL = 1;
+ require Apache;
+ }
+}
+
+# Define the CRLF sequence. I can't use a simple "\r\n" because the meaning
+# of "\n" is different on different OS's (sometimes it generates CRLF, sometimes LF
+# and sometimes CR). The most popular VMS web server
+# doesn't accept CRLF -- instead it wants a LR. EBCDIC machines don't
+# use ASCII, so \015\012 means something different. I find this all
+# really annoying.
+$EBCDIC = "\t" ne "\011";
+if ($OS eq 'VMS') {
+ $CRLF = "\n";
+} elsif ($EBCDIC) {
+ $CRLF= "\r\n";
+} else {
+ $CRLF = "\015\012";
+}
+
+_set_binmode() if ($needs_binmode);
+
+sub _set_binmode {
+
+ # rt #57524 - don't set binmode on filehandles if there are
+ # already none default layers set on them
+ my %default_layers = (
+ unix => 1,
+ perlio => 1,
+ stdio => 1,
+ crlf => 1,
+ );
+
+ foreach my $fh (
+ \*main::STDOUT,
+ \*main::STDIN,
+ \*main::STDERR,
+ ) {
+ my @modes = grep { ! $default_layers{$_} }
+ PerlIO::get_layers( $fh );
+
+ if ( ! @modes ) {
+ $CGI::DefaultClass->binmode( $fh );
+ }
+ }
+}
+
+%EXPORT_TAGS = (
+ ':html2' => [ 'h1' .. 'h6', qw/
+ p br hr ol ul li dl dt dd menu code var strong em
+ tt u i b blockquote pre img a address cite samp dfn html head
+ base body Link nextid title meta kbd start_html end_html
+ input Select option comment charset escapeHTML
+ / ],
+ ':html3' => [ qw/
+ div table caption th td TR Tr sup Sub strike applet Param nobr
+ embed basefont style span layer ilayer font frameset frame script small big Area Map
+ / ],
+ ':html4' => [ qw/
+ abbr acronym bdo col colgroup del fieldset iframe
+ ins label legend noframes noscript object optgroup Q
+ thead tbody tfoot
+ / ],
+ ':form' => [ qw/
+ textfield textarea filefield password_field hidden checkbox checkbox_group
+ submit reset defaults radio_group popup_menu button autoEscape
+ scrolling_list image_button start_form end_form
+ start_multipart_form end_multipart_form isindex tmpFileName uploadInfo URL_ENCODED MULTIPART
+ / ],
+ ':cgi' => [ qw/
+ param multi_param upload path_info path_translated request_uri url self_url script_name
+ cookie Dump raw_cookie request_method query_string Accept user_agent remote_host content_type
+ remote_addr referer server_name server_software server_port server_protocol virtual_port
+ virtual_host remote_ident auth_type http append save_parameters restore_parameters param_fetch
+ remote_user user_name header redirect import_names put Delete Delete_all url_param cgi_error env_query_string
+ / ],
+ ':netscape' => [qw/blink fontsize center/],
+ ':ssl' => [qw/https/],
+ ':cgi-lib' => [qw/ReadParse PrintHeader HtmlTop HtmlBot SplitParam Vars/],
+ ':push' => [qw/multipart_init multipart_start multipart_end multipart_final/],
+
+ # bulk export/import
+ ':html' => [qw/:html2 :html3 :html4 :netscape/],
+ ':standard' => [qw/:html2 :html3 :html4 :form :cgi :ssl/],
+ ':all' => [qw/:html2 :html3 :html4 :netscape :form :cgi :ssl :push/]
+);
+
+# to import symbols into caller
+sub import {
+ my $self = shift;
+
+ # This causes modules to clash.
+ undef %EXPORT_OK;
+ undef %EXPORT;
+
+ $self->_setup_symbols(@_);
+ my ($callpack, $callfile, $callline) = caller;
+
+ if ( $callpack eq 'CGI::Fast' ) {
+ # fixes GH #11 (and GH #12 in CGI::Fast since
+ # sub import was added to CGI::Fast in 9537f90
+ # so we need to move up a level to export the
+ # routines to the namespace of whatever is using
+ # CGI::Fast
+ ($callpack, $callfile, $callline) = caller(1);
+ }
+
+ # To allow overriding, search through the packages
+ # Till we find one in which the correct subroutine is defined.
+ my @packages = ($self,@{"$self\:\:ISA"});
+ for $sym (keys %EXPORT) {
+ my $pck;
+ my $def = $DefaultClass;
+ for $pck (@packages) {
+ if (defined(&{"$pck\:\:$sym"})) {
+ $def = $pck;
+ last;
+ }
+ }
+ *{"${callpack}::$sym"} = \&{"$def\:\:$sym"};
+ }
+}
+
+sub expand_tags {
+ my($tag) = @_;
+ return ("start_$1","end_$1") if $tag=~/^(?:\*|start_|end_)(.+)/;
+ my(@r);
+ return ($tag) unless $EXPORT_TAGS{$tag};
+ for (@{$EXPORT_TAGS{$tag}}) {
+ push(@r,&expand_tags($_));
+ }
+ return @r;
+}
+
+#### Method: new
+# The new routine. This will check the current environment
+# for an existing query string, and initialize itself, if so.
+####
+sub new {
+ my($class,@initializer) = @_;
+ my $self = {};
+
+ bless $self,ref $class || $class || $DefaultClass;
+
+ # always use a tempfile
+ $self->{'use_tempfile'} = 1;
+
+ if (ref($initializer[0])
+ && (UNIVERSAL::isa($initializer[0],'Apache')
+ ||
+ UNIVERSAL::isa($initializer[0],'Apache2::RequestRec')
+ )) {
+ $self->r(shift @initializer);
+ }
+ if (ref($initializer[0])
+ && (UNIVERSAL::isa($initializer[0],'CODE'))) {
+ $self->upload_hook(shift @initializer, shift @initializer);
+ $self->{'use_tempfile'} = shift @initializer if (@initializer > 0);
+ }
+ if ($MOD_PERL) {
+ if ($MOD_PERL == 1) {
+ $self->r(Apache->request) unless $self->r;
+ my $r = $self->r;
+ $r->register_cleanup(\&CGI::_reset_globals);
+ $self->_setup_symbols(@SAVED_SYMBOLS) if @SAVED_SYMBOLS;
+ }
+ else {
+ # XXX: once we have the new API
+ # will do a real PerlOptions -SetupEnv check
+ $self->r(Apache2::RequestUtil->request) unless $self->r;
+ my $r = $self->r;
+ $r->subprocess_env unless exists $ENV{REQUEST_METHOD};
+ $r->pool->cleanup_register(\&CGI::_reset_globals);
+ $self->_setup_symbols(@SAVED_SYMBOLS) if @SAVED_SYMBOLS;
+ }
+ undef $NPH;
+ }
+ $self->_reset_globals if $PERLEX;
+ $self->init(@initializer);
+ return $self;
+}
+
+sub r {
+ my $self = shift;
+ my $r = $self->{'.r'};
+ $self->{'.r'} = shift if @_;
+ $r;
+}
+
+sub upload_hook {
+ my $self;
+ if (ref $_[0] eq 'CODE') {
+ $CGI::Q = $self = $CGI::DefaultClass->new(@_);
+ } else {
+ $self = shift;
+ }
+ my ($hook,$data,$use_tempfile) = @_;
+ $self->{'.upload_hook'} = $hook;
+ $self->{'.upload_data'} = $data;
+ $self->{'use_tempfile'} = $use_tempfile if defined $use_tempfile;
+}
+
+#### Method: param / multi_param
+# Returns the value(s)of a named parameter.
+# If invoked in a list context, returns the
+# entire list. Otherwise returns the first
+# member of the list.
+# If name is not provided, return a list of all
+# the known parameters names available.
+# If more than one argument is provided, the
+# second and subsequent arguments are used to
+# set the value of the parameter.
+#
+# note that calling param() in list context
+# will raise a warning about potential bad
+# things, hence the multi_param method
+####
+sub multi_param {
+ # we don't need to set $LIST_CONTEXT_WARN to 0 here
+ # because param() will check the caller before warning
+ my @list_of_params = param( @_ );
+ return @list_of_params;
+}
+
+sub param {
+ my($self,@p) = self_or_default(@_);
+
+ return $self->all_parameters unless @p;
+
+ # list context can be dangerous so warn:
+ # http://blog.gerv.net/2014.10/new-class-of-vulnerability-in-perl-web-applications
+ if ( wantarray && $LIST_CONTEXT_WARN ) {
+ my ( $package, $filename, $line ) = caller;
+ if ( $package ne 'CGI' ) {
+ warn "CGI::param called in list context from $filename line $line, this can lead to vulnerabilities. "
+ . 'See the warning in "Fetching the value or values of a single named parameter"';
+ }
+ }
+
+ my($name,$value,@other);
+
+ # For compatibility between old calling style and use_named_parameters() style,
+ # we have to special case for a single parameter present.
+ if (@p > 1) {
+ ($name,$value,@other) = rearrange([NAME,[DEFAULT,VALUE,VALUES]],@p);
+ my(@values);
+
+ if (substr($p[0],0,1) eq '-') {
+ @values = defined($value) ? (ref($value) && ref($value) eq 'ARRAY' ? @{$value} : $value) : ();
+ } else {
+ for ($value,@other) {
+ push(@values,$_) if defined($_);
+ }
+ }
+ # If values is provided, then we set it.
+ if (@values or defined $value) {
+ $self->add_parameter($name);
+ $self->{param}{$name}=[@values];
+ }
+ } else {
+ $name = $p[0];
+ }
+
+ return unless defined($name) && $self->{param}{$name};
+
+ my @result = @{$self->{param}{$name}};
+
+ if ($PARAM_UTF8 && $name ne 'PUTDATA' && $name ne 'POSTDATA') {
+ eval "require Encode; 1;" unless Encode->can('decode'); # bring in these functions
+ @result = map {ref $_ ? $_ : $self->_decode_utf8($_) } @result;
+ }
+
+ return wantarray ? @result : $result[0];
+}
+
+sub _decode_utf8 {
+ my ($self, $val) = @_;
+
+ if (Encode::is_utf8($val)) {
+ return $val;
+ }
+ else {
+ return Encode::decode(utf8 => $val);
+ }
+}
+
+sub self_or_default {
+ return @_ if defined($_[0]) && (!ref($_[0])) &&($_[0] eq 'CGI');
+ unless (defined($_[0]) &&
+ (ref($_[0]) eq 'CGI' || UNIVERSAL::isa($_[0],'CGI')) # slightly optimized for common case
+ ) {
+ $Q = $CGI::DefaultClass->new unless defined($Q);
+ unshift(@_,$Q);
+ }
+ return wantarray ? @_ : $Q;
+}
+
+sub self_or_CGI {
+ local $^W=0; # prevent a warning
+ if (defined($_[0]) &&
+ (substr(ref($_[0]),0,3) eq 'CGI'
+ || UNIVERSAL::isa($_[0],'CGI'))) {
+ return @_;
+ } else {
+ return ($DefaultClass,@_);
+ }
+}
+
+########################################
+# THESE METHODS ARE MORE OR LESS PRIVATE
+# GO TO THE __DATA__ SECTION TO SEE MORE
+# PUBLIC METHODS
+########################################
+
+# Initialize the query object from the environment.
+# If a parameter list is found, this object will be set
+# to a hash in which parameter names are keys
+# and the values are stored as lists
+# If a keyword list is found, this method creates a bogus
+# parameter list with the single parameter 'keywords'.
+
+sub init {
+ my $self = shift;
+ my($query_string,$meth,$content_length,$fh,@lines) = ('','','','');
+
+ my $is_xforms;
+
+ my $initializer = shift; # for backward compatibility
+ local($/) = "\n";
+
+ # set autoescaping on by default
+ $self->{'escape'} = 1;
+
+ # if we get called more than once, we want to initialize
+ # ourselves from the original query (which may be gone
+ # if it was read from STDIN originally.)
+ if (@QUERY_PARAM && !defined($initializer)) {
+ for my $name (@QUERY_PARAM) {
+ my $val = $QUERY_PARAM{$name}; # always an arrayref;
+ $self->param('-name'=>$name,'-value'=> $val);
+ if (defined $val and ref $val eq 'ARRAY') {
+ for my $fh (grep {defined($_) && ref($_) && defined(fileno($_))} @$val) {
+ seek($fh,0,0); # reset the filehandle.
+ }
+
+ }
+ }
+ $self->charset($QUERY_CHARSET);
+ $self->{'.fieldnames'} = {%QUERY_FIELDNAMES};
+ $self->{'.tmpfiles'} = {%QUERY_TMPFILES};
+ return;
+ }
+
+ $meth=$ENV{'REQUEST_METHOD'} if defined($ENV{'REQUEST_METHOD'});
+ $content_length = defined($ENV{'CONTENT_LENGTH'}) ? $ENV{'CONTENT_LENGTH'} : 0;
+
+ $fh = to_filehandle($initializer) if $initializer;
+
+ # set charset to the safe ISO-8859-1
+ $self->charset('ISO-8859-1');
+
+ METHOD: {
+
+ # avoid unreasonably large postings
+ if (($POST_MAX > 0) && ($content_length > $POST_MAX)) {
+ #discard the post, unread
+ $self->cgi_error("413 Request entity too large");
+ last METHOD;
+ }
+
+ # Process multipart postings, but only if the initializer is
+ # not defined.
+ if ($meth eq 'POST'
+ && defined($ENV{'CONTENT_TYPE'})
+ && $ENV{'CONTENT_TYPE'}=~m|^multipart/form-data|
+ && !defined($initializer)
+ ) {
+ my($boundary) = $ENV{'CONTENT_TYPE'} =~ /boundary=\"?([^\";,]+)\"?/;
+ $self->read_multipart($boundary,$content_length);
+ last METHOD;
+ }
+
+ # Process XForms postings. We know that we have XForms in the
+ # following cases:
+ # method eq 'POST' && content-type eq 'application/xml'
+ # method eq 'POST' && content-type =~ /multipart\/related.+start=/
+ # There are more cases, actually, but for now, we don't support other
+ # methods for XForm posts.
+ # In a XForm POST, the QUERY_STRING is parsed normally.
+ # If the content-type is 'application/xml', we just set the param
+ # XForms:Model (referring to the xml syntax) param containing the
+ # unparsed XML data.
+ # In the case of multipart/related we set XForms:Model as above, but
+ # the other parts are available as uploads with the Content-ID as the
+ # the key.
+ # See the URL below for XForms specs on this issue.
+ # http://www.w3.org/TR/2006/REC-xforms-20060314/slice11.html#submit-options
+ if ($meth eq 'POST' && defined($ENV{'CONTENT_TYPE'})) {
+ if ($ENV{'CONTENT_TYPE'} eq 'application/xml') {
+ my($param) = 'XForms:Model';
+ my($value) = '';
+ $self->add_parameter($param);
+ $self->read_from_client(\$value,$content_length,0)
+ if $content_length > 0;
+ push (@{$self->{param}{$param}},$value);
+ $is_xforms = 1;
+ } elsif ($ENV{'CONTENT_TYPE'} =~ /multipart\/related.+boundary=\"?([^\";,]+)\"?.+start=\"?\<?([^\"\>]+)\>?\"?/) {
+ my($boundary,$start) = ($1,$2);
+ my($param) = 'XForms:Model';
+ $self->add_parameter($param);
+ my($value) = $self->read_multipart_related($start,$boundary,$content_length,0);
+ push (@{$self->{param}{$param}},$value);
+ $query_string = $self->_get_query_string_from_env;
+ $is_xforms = 1;
+ }
+ }
+
+
+ # If initializer is defined, then read parameters
+ # from it.
+ if (!$is_xforms && defined($initializer)) {
+ if (UNIVERSAL::isa($initializer,'CGI')) {
+ $query_string = $initializer->query_string;
+ last METHOD;
+ }
+ if (ref($initializer) && ref($initializer) eq 'HASH') {
+ for (keys %$initializer) {
+ $self->param('-name'=>$_,'-value'=>$initializer->{$_});
+ }
+ last METHOD;
+ }
+
+ if (defined($fh) && ($fh ne '')) {
+ while (my $line = <$fh>) {
+ chomp $line;
+ last if $line =~ /^=$/;
+ push(@lines,$line);
+ }
+ # massage back into standard format
+ if ("@lines" =~ /=/) {
+ $query_string=join("&",@lines);
+ } else {
+ $query_string=join("+",@lines);
+ }
+ last METHOD;
+ }
+
+ # last chance -- treat it as a string
+ $initializer = $$initializer if ref($initializer) eq 'SCALAR';
+ $query_string = $initializer;
+
+ last METHOD;
+ }
+
+ # If method is GET, HEAD or DELETE, fetch the query from
+ # the environment.
+ if ($is_xforms || $meth=~/^(GET|HEAD|DELETE)$/) {
+ $query_string = $self->_get_query_string_from_env;
+ $self->param($meth . 'DATA', $self->param('XForms:Model'))
+ if $is_xforms;
+ last METHOD;
+ }
+
+ if ($meth eq 'POST' || $meth eq 'PUT') {
+ if ( $content_length > 0 ) {
+ if ( ( $PUTDATA_UPLOAD || $self->{'.upload_hook'} ) && !$is_xforms && ($meth eq 'POST' || $meth eq 'PUT')
+ && defined($ENV{'CONTENT_TYPE'})
+ && $ENV{'CONTENT_TYPE'} !~ m|^application/x-www-form-urlencoded|
+ && $ENV{'CONTENT_TYPE'} !~ m|^multipart/form-data| ){
+ my $postOrPut = $meth . 'DATA' ; # POSTDATA/PUTDATA
+ $self->read_postdata_putdata( $postOrPut, $content_length, $ENV{'CONTENT_TYPE'} );
+ $meth = ''; # to skip xform testing
+ undef $query_string ;
+ } else {
+ $self->read_from_client(\$query_string,$content_length,0);
+ }
+ }
+ # Some people want to have their cake and eat it too!
+ # Uncomment this line to have the contents of the query string
+ # APPENDED to the POST data.
+ # $query_string .= (length($query_string) ? '&' : '') . $ENV{'QUERY_STRING'} if defined $ENV{'QUERY_STRING'};
+ last METHOD;
+ }
+
+ # If $meth is not of GET, POST, PUT or HEAD, assume we're
+ # being debugged offline.
+ # Check the command line and then the standard input for data.
+ # We use the shellwords package in order to behave the way that
+ # UN*X programmers expect.
+ if ($DEBUG)
+ {
+ my $cmdline_ret = read_from_cmdline();
+ $query_string = $cmdline_ret->{'query_string'};
+ if (defined($cmdline_ret->{'subpath'}))
+ {
+ $self->path_info($cmdline_ret->{'subpath'});
+ }
+ }
+ }
+
+# YL: Begin Change for XML handler 10/19/2001
+ if (!$is_xforms && ($meth eq 'POST' || $meth eq 'PUT')
+ && defined($ENV{'CONTENT_TYPE'})
+ && $ENV{'CONTENT_TYPE'} !~ m|^application/x-www-form-urlencoded|
+ && $ENV{'CONTENT_TYPE'} !~ m|^multipart/form-data| ) {
+ my($param) = $meth . 'DATA' ;
+ $self->add_parameter($param) ;
+ push (@{$self->{param}{$param}},$query_string);
+ undef $query_string ;
+ }
+# YL: End Change for XML handler 10/19/2001
+
+ # We now have the query string in hand. We do slightly
+ # different things for keyword lists and parameter lists.
+ if (defined $query_string && length $query_string) {
+ if ($query_string =~ /[&=;]/) {
+ $self->parse_params($query_string);
+ } else {
+ $self->add_parameter('keywords');
+ $self->{param}{'keywords'} = [$self->parse_keywordlist($query_string)];
+ }
+ }
+
+ # Special case. Erase everything if there is a field named
+ # .defaults.
+ if ($self->param('.defaults')) {
+ $self->delete_all();
+ }
+
+ # hash containing our defined fieldnames
+ $self->{'.fieldnames'} = {};
+ for ($self->param('.cgifields')) {
+ $self->{'.fieldnames'}->{$_}++;
+ }
+
+ # Clear out our default submission button flag if present
+ $self->delete('.submit');
+ $self->delete('.cgifields');
+
+ $self->save_request unless defined $initializer;
+}
+
+sub _get_query_string_from_env {
+ my $self = shift;
+ my $query_string = '';
+
+ if ( $MOD_PERL ) {
+ $query_string = $self->r->args;
+ if ( ! $query_string && $MOD_PERL == 2 ) {
+ # possibly a redirect, inspect prev request
+ # (->prev only supported under mod_perl2)
+ if ( my $prev = $self->r->prev ) {
+ $query_string = $prev->args;
+ }
+ }
+ }
+
+ $query_string ||= $ENV{'QUERY_STRING'}
+ if defined $ENV{'QUERY_STRING'};
+
+ if ( ! $query_string ) {
+ # try to get from REDIRECT_ env variables, support
+ # 5 levels of redirect and no more (RT #36312)
+ REDIRECT: foreach my $r ( 1 .. 5 ) {
+ my $key = join( '',( 'REDIRECT_' x $r ) );
+ $query_string ||= $ENV{"${key}QUERY_STRING"}
+ if defined $ENV{"${key}QUERY_STRING"};
+ last REDIRECT if $query_string;
+ }
+ }
+
+ return $query_string;
+}
+
+# FUNCTIONS TO OVERRIDE:
+# Turn a string into a filehandle
+sub to_filehandle {
+ my $thingy = shift;
+ return undef unless $thingy;
+ return $thingy if UNIVERSAL::isa($thingy,'GLOB');
+ return $thingy if UNIVERSAL::isa($thingy,'FileHandle');
+ if (!ref($thingy)) {
+ my $caller = 1;
+ while (my $package = caller($caller++)) {
+ my($tmp) = $thingy=~/[\':]/ ? $thingy : "$package\:\:$thingy";
+ return $tmp if defined(fileno($tmp));
+ }
+ }
+ return undef;
+}
+
+# send output to the browser
+sub put {
+ my($self,@p) = self_or_default(@_);
+ $self->print(@p);
+}
+
+# print to standard output (for overriding in mod_perl)
+sub print {
+ shift;
+ CORE::print(@_);
+}
+
+# get/set last cgi_error
+sub cgi_error {
+ my ($self,$err) = self_or_default(@_);
+ $self->{'.cgi_error'} = $err if defined $err;
+ return $self->{'.cgi_error'};
+}
+
+sub save_request {
+ my($self) = @_;
+ # We're going to play with the package globals now so that if we get called
+ # again, we initialize ourselves in exactly the same way. This allows
+ # us to have several of these objects.
+ @QUERY_PARAM = $self->param; # save list of parameters
+ for (@QUERY_PARAM) {
+ next unless defined $_;
+ $QUERY_PARAM{$_}=$self->{param}{$_};
+ }
+ $QUERY_CHARSET = $self->charset;
+ %QUERY_FIELDNAMES = %{$self->{'.fieldnames'}};
+ %QUERY_TMPFILES = %{ $self->{'.tmpfiles'} || {} };
+}
+
+sub parse_params {
+ my($self,$tosplit) = @_;
+ my(@pairs) = split(/[&;]/,$tosplit);
+ my($param,$value);
+ for (@pairs) {
+ ($param,$value) = split('=',$_,2);
+ next unless defined $param;
+ next if $NO_UNDEF_PARAMS and not defined $value;
+ $value = '' unless defined $value;
+ $param = unescape($param);
+ $value = unescape($value);
+ $self->add_parameter($param);
+ push (@{$self->{param}{$param}},$value);
+ }
+}
+
+sub add_parameter {
+ my($self,$param)=@_;
+ return unless defined $param;
+ push (@{$self->{'.parameters'}},$param)
+ unless defined($self->{param}{$param});
+}
+
+sub all_parameters {
+ my $self = shift;
+ return () unless defined($self) && $self->{'.parameters'};
+ return () unless @{$self->{'.parameters'}};
+ return @{$self->{'.parameters'}};
+}
+
+# put a filehandle into binary mode (DOS)
+sub binmode {
+ return unless defined($_[1]) && ref ($_[1]) && defined fileno($_[1]);
+ CORE::binmode($_[1]);
+}
+
+# back compatibility html tag generation functions - noop
+# since this is now the default having removed AUTOLOAD
+sub compile { 1; }
+
+sub _all_html_tags {
+ return qw/
+ a abbr acronym address applet Area
+ b base basefont bdo big blink blockquote body br
+ caption center cite code col colgroup
+ dd del dfn div dl dt
+ em embed
+ fieldset font fontsize frame frameset
+ h1 h2 h3 h4 h5 h6 head hr html
+ i iframe ilayer img input ins
+ kbd
+ label layer legend li Link
+ Map menu meta
+ nextid nobr noframes noscript
+ object ol option
+ p Param pre
+ Q
+ samp script Select small span
+ strike strong style Sub sup
+ table tbody td tfoot th thead title Tr TR tt
+ u ul
+ var
+ /
+}
+
+foreach my $tag ( _all_html_tags() ) {
+ *$tag = sub { return _tag_func($tag,@_); };
+
+ # start_html and end_html already exist as custom functions
+ next if ($tag eq 'html');
+
+ foreach my $start_end ( qw/ start end / ) {
+ my $start_end_function = "${start_end}_${tag}";
+ *$start_end_function = sub { return _tag_func($start_end_function,@_); };
+ }
+}
+
+sub _tag_func {
+ my $tagname = shift;
+ my ($q,$a,@rest) = self_or_default(@_);
+
+ my($attr) = '';
+
+ if (ref($a) && ref($a) eq 'HASH') {
+ my(@attr) = make_attributes($a,$q->{'escape'});
+ $attr = " @attr" if @attr;
+ } else {
+ unshift @rest,$a if defined $a;
+ }
+
+ $tagname = lc( $tagname );
+
+ if ($tagname=~/start_(\w+)/i) {
+ return "<$1$attr>";
+ } elsif ($tagname=~/end_(\w+)/i) {
+ return "</$1>";
+ } else {
+ return $XHTML ? "<$tagname$attr />" : "<$tagname$attr>" unless @rest;
+ my($tag,$untag) = ("<$tagname$attr>","</$tagname>");
+ my @result = map { "$tag$_$untag" }
+ (ref($rest[0]) eq 'ARRAY') ? @{$rest[0]} : "@rest";
+ return "@result";
+ }
+}
+
+sub _selected {
+ my $self = shift;
+ my $value = shift;
+ return '' unless $value;
+ return $XHTML ? qq(selected="selected" ) : qq(selected );
+}
+
+sub _checked {
+ my $self = shift;
+ my $value = shift;
+ return '' unless $value;
+ return $XHTML ? qq(checked="checked" ) : qq(checked );
+}
+
+sub _reset_globals { initialize_globals(); }
+
+sub _setup_symbols {
+ my $self = shift;
+
+ # to avoid reexporting unwanted variables
+ undef %EXPORT;
+
+ for (@_) {
+
+ if ( /^[:-]any$/ ) {
+ warn "CGI -any pragma has been REMOVED. You should audit your code for any use "
+ . "of none supported / incorrectly spelled tags and remove them"
+ ;
+ next;
+ }
+ $HEADERS_ONCE++, next if /^[:-]unique_headers$/;
+ $NPH++, next if /^[:-]nph$/;
+ $NOSTICKY++, next if /^[:-]nosticky$/;
+ $DEBUG=0, next if /^[:-]no_?[Dd]ebug$/;
+ $DEBUG=2, next if /^[:-][Dd]ebug$/;
+ $USE_PARAM_SEMICOLONS++, next if /^[:-]newstyle_urls$/;
+ $PUTDATA_UPLOAD++, next if /^[:-](?:putdata_upload|postdata_upload)$/;
+ $PARAM_UTF8++, next if /^[:-]utf8$/;
+ $XHTML++, next if /^[:-]xhtml$/;
+ $XHTML=0, next if /^[:-]no_?xhtml$/;
+ $USE_PARAM_SEMICOLONS=0, next if /^[:-]oldstyle_urls$/;
+ $TABINDEX++, next if /^[:-]tabindex$/;
+ $CLOSE_UPLOAD_FILES++, next if /^[:-]close_upload_files$/;
+ $NO_UNDEF_PARAMS++, next if /^[:-]no_undef_params$/;
+
+ for (&expand_tags($_)) {
+ tr/a-zA-Z0-9_//cd; # don't allow weird function names
+ $EXPORT{$_}++;
+ }
+ }
+ @SAVED_SYMBOLS = @_;
+}
+
+sub charset {
+ my ($self,$charset) = self_or_default(@_);
+ $self->{'.charset'} = $charset if defined $charset;
+ $self->{'.charset'};
+}
+
+sub element_id {
+ my ($self,$new_value) = self_or_default(@_);
+ $self->{'.elid'} = $new_value if defined $new_value;
+ sprintf('%010d',$self->{'.elid'}++);
+}
+
+sub element_tab {
+ my ($self,$new_value) = self_or_default(@_);
+ $self->{'.etab'} ||= 1;
+ $self->{'.etab'} = $new_value if defined $new_value;
+ my $tab = $self->{'.etab'}++;
+ return '' unless $TABINDEX or defined $new_value;
+ return qq(tabindex="$tab" );
+}
+
+#####
+# subroutine: read_postdata_putdata
+#
+# Unless file uploads are disabled
+# Reads BODY of POST/PUT request and stuffs it into tempfile
+# accessible as param POSTDATA/PUTDATA
+#
+# Also respects upload_hook
+#
+# based on subroutine read_multipart_related
+#####
+sub read_postdata_putdata {
+ my ( $self, $postOrPut, $content_length, $content_type ) = @_;
+ my %header = (
+ "Content-Type" => $content_type,
+ );
+ my $param = $postOrPut;
+ # add this parameter to our list
+ $self->add_parameter($param);
+
+
+ UPLOADS: {
+
+ # If we get here, then we are dealing with a potentially large
+ # uploaded form. Save the data to a temporary file, then open
+ # the file for reading.
+
+ # skip the file if uploads disabled
+ if ($DISABLE_UPLOADS) {
+
+ # while (defined($data = $buffer->read)) { }
+ my $buff;
+ my $unit = $MultipartBuffer::INITIAL_FILLUNIT;
+ my $len = $content_length;
+ while ( $len > 0 ) {
+ my $read = $self->read_from_client( \$buf, $unit, 0 );
+ $len -= $read;
+ }
+ last UPLOADS;
+ }
+
+ # SHOULD PROBABLY SKIP THIS IF NOT $self->{'use_tempfile'}
+ # BUT THE REST OF CGI.PM DOESN'T, SO WHATEVER
+ my $tmp_dir = $CGI::OS eq 'WINDOWS'
+ ? ( $ENV{TEMP} || $ENV{TMP} || ( $ENV{WINDIR} ? ( $ENV{WINDIR} . $SL . 'TEMP' ) : undef ) )
+ : undef; # File::Temp defaults to TMPDIR
+
+ require CGI::File::Temp;
+ my $filehandle = CGI::File::Temp->new(
+ UNLINK => $UNLINK_TMP_FILES,
+ DIR => $tmp_dir,
+ );
+ $filehandle->_mp_filename( $postOrPut );
+
+ $CGI::DefaultClass->binmode($filehandle)
+ if $CGI::needs_binmode
+ && defined fileno($filehandle);
+
+ my ($data);
+ local ($\) = '';
+ my $totalbytes;
+ my $unit = $MultipartBuffer::INITIAL_FILLUNIT;
+ my $len = $content_length;
+ $unit = $len;
+ my $ZERO_LOOP_COUNTER =0;
+
+ while( $len > 0 )
+ {
+
+ my $bytesRead = $self->read_from_client( \$data, $unit, 0 );
+ $len -= $bytesRead ;
+
+ # An apparent bug in the Apache server causes the read()
+ # to return zero bytes repeatedly without blocking if the
+ # remote user aborts during a file transfer. I don't know how
+ # they manage this, but the workaround is to abort if we get
+ # more than SPIN_LOOP_MAX consecutive zero reads.
+ if ($bytesRead <= 0) {
+ die "CGI.pm: Server closed socket during read_postdata_putdata (client aborted?).\n" if $ZERO_LOOP_COUNTER++ >= $SPIN_LOOP_MAX;
+ } else {
+ $ZERO_LOOP_COUNTER = 0;
+ }
+
+ if ( defined $self->{'.upload_hook'} ) {
+ $totalbytes += length($data);
+ &{ $self->{'.upload_hook'} }( $param, $data, $totalbytes,
+ $self->{'.upload_data'} );
+ }
+ print $filehandle $data if ( $self->{'use_tempfile'} );
+ undef $data;
+ }
+
+ # back up to beginning of file
+ seek( $filehandle, 0, 0 );
+
+ ## Close the filehandle if requested this allows a multipart MIME
+ ## upload to contain many files, and we won't die due to too many
+ ## open file handles. The user can access the files using the hash
+ ## below.
+ close $filehandle if $CLOSE_UPLOAD_FILES;
+ $CGI::DefaultClass->binmode($filehandle) if $CGI::needs_binmode;
+
+ # Save some information about the uploaded file where we can get
+ # at it later.
+ # Use the typeglob + filename as the key, as this is guaranteed to be
+ # unique for each filehandle. Don't use the file descriptor as
+ # this will be re-used for each filehandle if the
+ # close_upload_files feature is used.
+ $self->{'.tmpfiles'}->{$$filehandle . $filehandle} = {
+ hndl => $filehandle,
+ name => $filehandle->filename,
+ info => {%header},
+ };
+ push( @{ $self->{param}{$param} }, $filehandle );
+ }
+ return;
+}
+
+sub URL_ENCODED { 'application/x-www-form-urlencoded'; }
+
+sub MULTIPART { 'multipart/form-data'; }
+
+sub SERVER_PUSH { 'multipart/x-mixed-replace;boundary="' . shift() . '"'; }
+
+# Create a new multipart buffer
+sub new_MultipartBuffer {
+ my($self,$boundary,$length) = @_;
+ return MultipartBuffer->new($self,$boundary,$length);
+}
+
+# Read data from a file handle
+sub read_from_client {
+ my($self, $buff, $len, $offset) = @_;
+ local $^W=0; # prevent a warning
+ return $MOD_PERL
+ ? $self->r->read($$buff, $len, $offset)
+ : read(\*STDIN, $$buff, $len, $offset);
+}
+
+#### Method: delete
+# Deletes the named parameter entirely.
+####
+sub delete {
+ my($self,@p) = self_or_default(@_);
+ my(@names) = rearrange([NAME],@p);
+ my @to_delete = ref($names[0]) eq 'ARRAY' ? @$names[0] : @names;
+ my %to_delete;
+ for my $name (@to_delete)
+ {
+ CORE::delete $self->{param}{$name};
+ CORE::delete $self->{'.fieldnames'}->{$name};
+ $to_delete{$name}++;
+ }
+ @{$self->{'.parameters'}}=grep { !exists($to_delete{$_}) } $self->param();
+ return;
+}
+
+#### Method: import_names
+# Import all parameters into the given namespace.
+# Assumes namespace 'Q' if not specified
+####
+sub import_names {
+ my($self,$namespace,$delete) = self_or_default(@_);
+ $namespace = 'Q' unless defined($namespace);
+ die "Can't import names into \"main\"\n" if \%{"${namespace}::"} == \%::;
+ if ($delete || $MOD_PERL || exists $ENV{'FCGI_ROLE'}) {
+ # can anyone find an easier way to do this?
+ for (keys %{"${namespace}::"}) {
+ local *symbol = "${namespace}::${_}";
+ undef $symbol;
+ undef @symbol;
+ undef %symbol;
+ }
+ }
+ my($param,@value,$var);
+ for $param ($self->param) {
+ # protect against silly names
+ ($var = $param)=~tr/a-zA-Z0-9_/_/c;
+ $var =~ s/^(?=\d)/_/;
+ local *symbol = "${namespace}::$var";
+ @value = $self->param($param);
+ @symbol = @value;
+ $symbol = $value[0];
+ }
+}
+
+#### Method: keywords
+# Keywords acts a bit differently. Calling it in a list context
+# returns the list of keywords.
+# Calling it in a scalar context gives you the size of the list.
+####
+sub keywords {
+ my($self,@values) = self_or_default(@_);
+ # If values is provided, then we set it.
+ $self->{param}{'keywords'}=[@values] if @values;
+ my(@result) = defined($self->{param}{'keywords'}) ? @{$self->{param}{'keywords'}} : ();
+ @result;
+}
+
+# These are some tie() interfaces for compatibility
+# with Steve Brenner's cgi-lib.pl routines
+sub Vars {
+ my $q = shift;
+ my %in;
+ tie(%in,CGI,$q);
+ return %in if wantarray;
+ return \%in;
+}
+
+# These are some tie() interfaces for compatibility
+# with Steve Brenner's cgi-lib.pl routines
+sub ReadParse {
+ local(*in);
+ if (@_) {
+ *in = $_[0];
+ } else {
+ my $pkg = caller();
+ *in=*{"${pkg}::in"};
+ }
+ tie(%in,CGI);
+ return scalar(keys %in);
+}
+
+sub PrintHeader {
+ my($self) = self_or_default(@_);
+ return $self->header();
+}
+
+sub HtmlTop {
+ my($self,@p) = self_or_default(@_);
+ return $self->start_html(@p);
+}
+
+sub HtmlBot {
+ my($self,@p) = self_or_default(@_);
+ return $self->end_html(@p);
+}
+
+sub SplitParam {
+ my ($param) = @_;
+ my (@params) = split ("\0", $param);
+ return (wantarray ? @params : $params[0]);
+}
+
+sub MethGet {
+ return request_method() eq 'GET';
+}
+
+sub MethPost {
+ return request_method() eq 'POST';
+}
+
+sub MethPut {
+ return request_method() eq 'PUT';
+}
+
+sub TIEHASH {
+ my $class = shift;
+ my $arg = $_[0];
+ if (ref($arg) && UNIVERSAL::isa($arg,'CGI')) {
+ return $arg;
+ }
+ return $Q ||= $class->new(@_);
+}
+
+sub STORE {
+ my $self = shift;
+ my $tag = shift;
+ my $vals = shift;
+ my @vals = index($vals,"\0")!=-1 ? split("\0",$vals) : $vals;
+ $self->param(-name=>$tag,-value=>\@vals);
+}
+
+sub FETCH {
+ return $_[0] if $_[1] eq 'CGI';
+ return undef unless defined $_[0]->param($_[1]);
+ return join("\0",$_[0]->param($_[1]));
+}
+
+sub FIRSTKEY {
+ $_[0]->{'.iterator'}=0;
+ $_[0]->{'.parameters'}->[$_[0]->{'.iterator'}++];
+}
+
+sub NEXTKEY {
+ $_[0]->{'.parameters'}->[$_[0]->{'.iterator'}++];
+}
+
+sub EXISTS {
+ exists $_[0]->{param}{$_[1]};
+}
+
+sub DELETE {
+ my ($self, $param) = @_;
+ my $value = $self->FETCH($param);
+ $self->delete($param);
+ return $value;
+}
+
+sub CLEAR {
+ %{$_[0]}=();
+}
+####
+
+####
+# Append a new value to an existing query
+####
+sub append {
+ my($self,@p) = self_or_default(@_);
+ my($name,$value) = rearrange([NAME,[VALUE,VALUES]],@p);
+ my(@values) = defined($value) ? (ref($value) ? @{$value} : $value) : ();
+ if (@values) {
+ $self->add_parameter($name);
+ push(@{$self->{param}{$name}},@values);
+ }
+ return $self->param($name);
+}
+
+#### Method: delete_all
+# Delete all parameters
+####
+sub delete_all {
+ my($self) = self_or_default(@_);
+ my @param = $self->param();
+ $self->delete(@param);
+}
+
+sub Delete {
+ my($self,@p) = self_or_default(@_);
+ $self->delete(@p);
+}
+
+sub Delete_all {
+ my($self,@p) = self_or_default(@_);
+ $self->delete_all(@p);
+}
+
+#### Method: autoescape
+# If you want to turn off the autoescaping features,
+# call this method with undef as the argument
+sub autoEscape {
+ my($self,$escape) = self_or_default(@_);
+ my $d = $self->{'escape'};
+ $self->{'escape'} = $escape;
+ $d;
+}
+
+#### Method: version
+# Return the current version
+####
+sub version {
+ return $VERSION;
+}
+
+#### Method: url_param
+# Return a parameter in the QUERY_STRING, regardless of
+# whether this was a POST or a GET
+####
+sub url_param {
+ my ($self,@p) = self_or_default(@_);
+ my $name = shift(@p);
+ return undef unless exists($ENV{QUERY_STRING});
+ unless (exists($self->{'.url_param'})) {
+ $self->{'.url_param'}={}; # empty hash
+ if ($ENV{QUERY_STRING} =~ /=/) {
+ my(@pairs) = split(/[&;]/,$ENV{QUERY_STRING});
+ my($param,$value);
+ for (@pairs) {
+ ($param,$value) = split('=',$_,2);
+ next if ! defined($param);
+ $param = unescape($param);
+ $value = unescape($value);
+ push(@{$self->{'.url_param'}->{$param}},$value);
+ }
+ } else {
+ my @keywords = $self->parse_keywordlist($ENV{QUERY_STRING});
+ $self->{'.url_param'}{'keywords'} = \@keywords if @keywords;
+ }
+ }
+ return keys %{$self->{'.url_param'}} unless defined($name);
+ return () unless $self->{'.url_param'}->{$name};
+ return wantarray ? @{$self->{'.url_param'}->{$name}}
+ : $self->{'.url_param'}->{$name}->[0];
+}
+
+#### Method: Dump
+# Returns a string in which all the known parameter/value
+# pairs are represented as nested lists, mainly for the purposes
+# of debugging.
+####
+sub Dump {
+ my($self) = self_or_default(@_);
+ my($param,$value,@result);
+ return '<ul></ul>' unless $self->param;
+ push(@result,"<ul>");
+ for $param ($self->param) {
+ my($name)=$self->_maybe_escapeHTML($param);
+ push(@result,"<li><strong>$name</strong></li>");
+ push(@result,"<ul>");
+ for $value ($self->param($param)) {
+ $value = $self->_maybe_escapeHTML($value);
+ $value =~ s/\n/<br \/>\n/g;
+ push(@result,"<li>$value</li>");
+ }
+ push(@result,"</ul>");
+ }
+ push(@result,"</ul>");
+ return join("\n",@result);
+}
+
+#### Method as_string
+#
+# synonym for "dump"
+####
+sub as_string {
+ &Dump(@_);
+}
+
+#### Method: save
+# Write values out to a filehandle in such a way that they can
+# be reinitialized by the filehandle form of the new() method
+####
+sub save {
+ my($self,$filehandle) = self_or_default(@_);
+ $filehandle = to_filehandle($filehandle);
+ my($param);
+ local($,) = ''; # set print field separator back to a sane value
+ local($\) = ''; # set output line separator to a sane value
+ for $param ($self->param) {
+ my($escaped_param) = escape($param);
+ my($value);
+ for $value ($self->param($param)) {
+ print $filehandle "$escaped_param=",escape("$value"),"\n"
+ if length($escaped_param) or length($value);
+ }
+ }
+ for (keys %{$self->{'.fieldnames'}}) {
+ print $filehandle ".cgifields=",escape("$_"),"\n";
+ }
+ print $filehandle "=\n"; # end of record
+}
+
+#### Method: save_parameters
+# An alias for save() that is a better name for exportation.
+# Only intended to be used with the function (non-OO) interface.
+####
+sub save_parameters {
+ my $fh = shift;
+ return save(to_filehandle($fh));
+}
+
+#### Method: restore_parameters
+# A way to restore CGI parameters from an initializer.
+# Only intended to be used with the function (non-OO) interface.
+####
+sub restore_parameters {
+ $Q = $CGI::DefaultClass->new(@_);
+}
+
+#### Method: multipart_init
+# Return a Content-Type: style header for server-push
+# This has to be NPH on most web servers, and it is advisable to set $| = 1
+#
+# Many thanks to Ed Jordan <ed@fidalgo.net> for this
+# contribution, updated by Andrew Benham (adsb@bigfoot.com)
+####
+sub multipart_init {
+ my($self,@p) = self_or_default(@_);
+ my($boundary,$charset,@other) = rearrange_header([BOUNDARY,CHARSET],@p);
+ if (!$boundary) {
+ $boundary = '------- =_';
+ my @chrs = ('0'..'9', 'A'..'Z', 'a'..'z');
+ for (1..17) {
+ $boundary .= $chrs[rand(scalar @chrs)];
+ }
+ }
+
+ $self->{'separator'} = "$CRLF--$boundary$CRLF";
+ $self->{'final_separator'} = "$CRLF--$boundary--$CRLF";
+ $type = SERVER_PUSH($boundary);
+ return $self->header(
+ -nph => 0,
+ -type => $type,
+ -charset => $charset,
+ (map { split "=", $_, 2 } @other),
+ ) . "WARNING: YOUR BROWSER DOESN'T SUPPORT THIS SERVER-PUSH TECHNOLOGY." . $self->multipart_end;
+}
+
+#### Method: multipart_start
+# Return a Content-Type: style header for server-push, start of section
+#
+# Many thanks to Ed Jordan <ed@fidalgo.net> for this
+# contribution, updated by Andrew Benham (adsb@bigfoot.com)
+####
+sub multipart_start {
+ my(@header);
+ my($self,@p) = self_or_default(@_);
+ my($type,$charset,@other) = rearrange([TYPE,CHARSET],@p);
+ $type = $type || 'text/html';
+ if ($charset) {
+ push(@header,"Content-Type: $type; charset=$charset");
+ } else {
+ push(@header,"Content-Type: $type");
+ }
+
+ # rearrange() was designed for the HTML portion, so we
+ # need to fix it up a little.
+ for (@other) {
+ # Don't use \s because of perl bug 21951
+ next unless my($header,$value) = /([^ \r\n\t=]+)=\"?(.+?)\"?$/;
+ ($_ = $header) =~ s/^(\w)(.*)/$1 . lc ($2) . ': '.$self->unescapeHTML($value)/e;
+ }
+ push(@header,@other);
+ my $header = join($CRLF,@header)."${CRLF}${CRLF}";
+ return $header;
+}
+
+#### Method: multipart_end
+# Return a MIME boundary separator for server-push, end of section
+#
+# Many thanks to Ed Jordan <ed@fidalgo.net> for this
+# contribution
+####
+sub multipart_end {
+ my($self,@p) = self_or_default(@_);
+ return $self->{'separator'};
+}
+
+#### Method: multipart_final
+# Return a MIME boundary separator for server-push, end of all sections
+#
+# Contributed by Andrew Benham (adsb@bigfoot.com)
+####
+sub multipart_final {
+ my($self,@p) = self_or_default(@_);
+ return $self->{'final_separator'} . "WARNING: YOUR BROWSER DOESN'T SUPPORT THIS SERVER-PUSH TECHNOLOGY." . $CRLF;
+}
+
+#### Method: header
+# Return a Content-Type: style header
+#
+####
+sub header {
+ my($self,@p) = self_or_default(@_);
+ my(@header);
+
+ return "" if $self->{'.header_printed'}++ and $HEADERS_ONCE;
+
+ my($type,$status,$cookie,$target,$expires,$nph,$charset,$attachment,$p3p,@other) =
+ rearrange([['TYPE','CONTENT_TYPE','CONTENT-TYPE'],
+ 'STATUS',['COOKIE','COOKIES','SET-COOKIE'],'TARGET',
+ 'EXPIRES','NPH','CHARSET',
+ 'ATTACHMENT','P3P'],@p);
+
+ # Since $cookie and $p3p may be array references,
+ # we must stringify them before CR escaping is done.
+ my @cookie;
+ for (ref($cookie) eq 'ARRAY' ? @{$cookie} : $cookie) {
+ my $cs = UNIVERSAL::isa($_,'CGI::Cookie') ? $_->as_string : $_;
+ push(@cookie,$cs) if defined $cs and $cs ne '';
+ }
+ $p3p = join ' ',@$p3p if ref($p3p) eq 'ARRAY';
+
+ # CR escaping for values, per RFC 822
+ for my $header ($type,$status,@cookie,$target,$expires,$nph,$charset,$attachment,$p3p,@other) {
+ if (defined $header) {
+ # From RFC 822:
+ # Unfolding is accomplished by regarding CRLF immediately
+ # followed by a LWSP-char as equivalent to the LWSP-char.
+ $header =~ s/$CRLF(\s)/$1/g;
+
+ # All other uses of newlines are invalid input.
+ if ($header =~ m/$CRLF|\015|\012/) {
+ # shorten very long values in the diagnostic
+ $header = substr($header,0,72).'...' if (length $header > 72);
+ die "Invalid header value contains a newline not followed by whitespace: $header";
+ }
+ }
+ }
+
+ $nph ||= $NPH;
+
+ $type ||= 'text/html' unless defined($type);
+
+ # sets if $charset is given, gets if not
+ $charset = $self->charset( $charset );
+
+ # rearrange() was designed for the HTML portion, so we
+ # need to fix it up a little.
+ for (@other) {
+ # Don't use \s because of perl bug 21951
+ next unless my($header,$value) = /([^ \r\n\t=]+)=\"?(.+?)\"?$/s;
+ ($_ = $header) =~ s/^(\w)(.*)/"\u$1\L$2" . ': '.$self->unescapeHTML($value)/e;
+ }
+
+ $type .= "; charset=$charset"
+ if $type ne ''
+ and $type !~ /\bcharset\b/
+ and defined $charset
+ and $charset ne '';
+
+ # Maybe future compatibility. Maybe not.
+ my $protocol = $ENV{SERVER_PROTOCOL} || 'HTTP/1.0';
+ push(@header,$protocol . ' ' . ($status || '200 OK')) if $nph;
+ push(@header,"Server: " . &server_software()) if $nph;
+
+ push(@header,"Status: $status") if $status;
+ push(@header,"Window-Target: $target") if $target;
+ push(@header,"P3P: policyref=\"/w3c/p3p.xml\", CP=\"$p3p\"") if $p3p;
+ # push all the cookies -- there may be several
+ push(@header,map {"Set-Cookie: $_"} @cookie);
+ # if the user indicates an expiration time, then we need
+ # both an Expires and a Date header (so that the browser is
+ # uses OUR clock)
+ push(@header,"Expires: " . expires($expires,'http'))
+ if $expires;
+ push(@header,"Date: " . expires(0,'http')) if $expires || $cookie || $nph;
+ push(@header,"Pragma: no-cache") if $self->cache();
+ push(@header,"Content-Disposition: attachment; filename=\"$attachment\"") if $attachment;
+ push(@header,map {ucfirst $_} @other);
+ push(@header,"Content-Type: $type") if $type ne '';
+ my $header = join($CRLF,@header)."${CRLF}${CRLF}";
+ if (($MOD_PERL >= 1) && !$nph) {
+ $self->r->send_cgi_header($header);
+ return '';
+ }
+ return $header;
+}
+
+#### Method: cache
+# Control whether header() will produce the no-cache
+# Pragma directive.
+####
+sub cache {
+ my($self,$new_value) = self_or_default(@_);
+ $new_value = '' unless $new_value;
+ if ($new_value ne '') {
+ $self->{'cache'} = $new_value;
+ }
+ return $self->{'cache'};
+}
+
+#### Method: redirect
+# Return a Location: style header
+#
+####
+sub redirect {
+ my($self,@p) = self_or_default(@_);
+ my($url,$target,$status,$cookie,$nph,@other) =
+ rearrange([[LOCATION,URI,URL],TARGET,STATUS,['COOKIE','COOKIES','SET-COOKIE'],NPH],@p);
+ $status = '302 Found' unless defined $status;
+ $url ||= $self->self_url;
+ my(@o);
+ for (@other) { tr/\"//d; push(@o,split("=",$_,2)); }
+ unshift(@o,
+ '-Status' => $status,
+ '-Location'=> $url,
+ '-nph' => $nph);
+ unshift(@o,'-Target'=>$target) if $target;
+ unshift(@o,'-Type'=>'');
+ my @unescaped;
+ unshift(@unescaped,'-Cookie'=>$cookie) if $cookie;
+ return $self->header((map {$self->unescapeHTML($_)} @o),@unescaped);
+}
+
+#### Method: start_html
+# Canned HTML header
+#
+# Parameters:
+# $title -> (optional) The title for this HTML document (-title)
+# $author -> (optional) e-mail address of the author (-author)
+# $base -> (optional) if set to true, will enter the BASE address of this document
+# for resolving relative references (-base)
+# $xbase -> (optional) alternative base at some remote location (-xbase)
+# $target -> (optional) target window to load all links into (-target)
+# $script -> (option) Javascript code (-script)
+# $no_script -> (option) Javascript <noscript> tag (-noscript)
+# $meta -> (optional) Meta information tags
+# $head -> (optional) any other elements you'd like to incorporate into the <head> tag
+# (a scalar or array ref)
+# $style -> (optional) reference to an external style sheet
+# @other -> (optional) any other named parameters you'd like to incorporate into
+# the <body> tag.
+####
+sub start_html {
+ my($self,@p) = &self_or_default(@_);
+ my($title,$author,$base,$xbase,$script,$noscript,
+ $target,$meta,$head,$style,$dtd,$lang,$encoding,$declare_xml,@other) =
+ rearrange([TITLE,AUTHOR,BASE,XBASE,SCRIPT,NOSCRIPT,TARGET,
+ META,HEAD,STYLE,DTD,LANG,ENCODING,DECLARE_XML],@p);
+
+ $self->element_id(0);
+ $self->element_tab(0);
+
+ $encoding = lc($self->charset) unless defined $encoding;
+
+ # Need to sort out the DTD before it's okay to call escapeHTML().
+ my(@result,$xml_dtd);
+ if ($dtd) {
+ if (defined(ref($dtd)) and (ref($dtd) eq 'ARRAY')) {
+ $dtd = $DEFAULT_DTD unless $dtd->[0] =~ m|^-//|;
+ } else {
+ $dtd = $DEFAULT_DTD unless $dtd =~ m|^-//|;
+ }
+ } else {
+ $dtd = $XHTML ? $_XHTML_DTD : $DEFAULT_DTD;
+ }
+
+ $xml_dtd++ if ref($dtd) eq 'ARRAY' && $dtd->[0] =~ /\bXHTML\b/i;
+ $xml_dtd++ if ref($dtd) eq '' && $dtd =~ /\bXHTML\b/i;
+ push @result,qq(<?xml version="1.0" encoding="$encoding"?>) if $xml_dtd && $declare_xml;
+
+ if (ref($dtd) && ref($dtd) eq 'ARRAY') {
+ push(@result,qq(<!DOCTYPE html\n\tPUBLIC "$dtd->[0]"\n\t "$dtd->[1]">));
+ $DTD_PUBLIC_IDENTIFIER = $dtd->[0];
+ } else {
+ push(@result,qq(<!DOCTYPE html\n\tPUBLIC "$dtd">));
+ $DTD_PUBLIC_IDENTIFIER = $dtd;
+ }
+
+ # Now that we know whether we're using the HTML 3.2 DTD or not, it's okay to
+ # call escapeHTML(). Strangely enough, the title needs to be escaped as
+ # HTML while the author needs to be escaped as a URL.
+ $title = $self->_maybe_escapeHTML($title || 'Untitled Document');
+ $author = $self->escape($author);
+
+ if ($DTD_PUBLIC_IDENTIFIER =~ /[^X]HTML (2\.0|3\.2|4\.01?)/i) {
+ $lang = "" unless defined $lang;
+ $XHTML = 0;
+ }
+ else {
+ $lang = 'en-US' unless defined $lang;
+ }
+
+ my $lang_bits = $lang ne '' ? qq( lang="$lang" xml:lang="$lang") : '';
+ my $meta_bits = qq(<meta http-equiv="Content-Type" content="text/html; charset=$encoding" />)
+ if $XHTML && $encoding && !$declare_xml;
+
+ push(@result,$XHTML ? qq(<html xmlns="http://www.w3.org/1999/xhtml"$lang_bits>\n<head>\n<title>$title</title>)
+ : ($lang ? qq(<html lang="$lang">) : "<html>")
+ . "<head><title>$title</title>");
+ if (defined $author) {
+ push(@result,$XHTML ? "<link rev=\"made\" href=\"mailto:$author\" />"
+ : "<link rev=\"made\" href=\"mailto:$author\">");
+ }
+
+ if ($base || $xbase || $target) {
+ my $href = $xbase || $self->url('-path'=>1);
+ my $t = $target ? qq/ target="$target"/ : '';
+ push(@result,$XHTML ? qq(<base href="$href"$t />) : qq(<base href="$href"$t>));
+ }
+
+ if ($meta && ref($meta) && (ref($meta) eq 'HASH')) {
+ for (sort keys %$meta) { push(@result,$XHTML ? qq(<meta name="$_" content="$meta->{$_}" />)
+ : qq(<meta name="$_" content="$meta->{$_}">)); }
+ }
+
+ my $meta_bits_set = 0;
+ if( $head ) {
+ if( ref $head ) {
+ push @result, @$head;
+ $meta_bits_set = 1 if grep { /http-equiv=["']Content-Type/i }@$head;
+ }
+ else {
+ push @result, $head;
+ $meta_bits_set = 1 if $head =~ /http-equiv=["']Content-Type/i;
+ }
+ }
+
+ # handle the infrequently-used -style and -script parameters
+ push(@result,$self->_style($style)) if defined $style;
+ push(@result,$self->_script($script)) if defined $script;
+ push(@result,$meta_bits) if defined $meta_bits and !$meta_bits_set;
+
+ # handle -noscript parameter
+ push(@result,<<END) if $noscript;
+<noscript>
+$noscript
+</noscript>
+END
+ ;
+ my($other) = @other ? " @other" : '';
+ push(@result,"</head>\n<body$other>\n");
+ return join("\n",@result);
+}
+
+### Method: _style
+# internal method for generating a CSS style section
+####
+sub _style {
+ my ($self,$style) = @_;
+ my (@result);
+
+ my $type = 'text/css';
+ my $rel = 'stylesheet';
+
+
+ my $cdata_start = $XHTML ? "\n<!--/* <![CDATA[ */" : "\n<!-- ";
+ my $cdata_end = $XHTML ? "\n/* ]]> */-->\n" : " -->\n";
+
+ my @s = ref($style) eq 'ARRAY' ? @$style : $style;
+ my $other = '';
+
+ for my $s (@s) {
+ if (ref($s)) {
+ my($src,$code,$verbatim,$stype,$alternate,$foo,@other) =
+ rearrange([qw(SRC CODE VERBATIM TYPE ALTERNATE FOO)],
+ ('-foo'=>'bar',
+ ref($s) eq 'ARRAY' ? @$s : %$s));
+ my $type = defined $stype ? $stype : 'text/css';
+ my $rel = $alternate ? 'alternate stylesheet' : 'stylesheet';
+ $other = "@other" if @other;
+
+ if (ref($src) eq "ARRAY") # Check to see if the $src variable is an array reference
+ { # If it is, push a LINK tag for each one
+ for $src (@$src)
+ {
+ push(@result,$XHTML ? qq(<link rel="$rel" type="$type" href="$src" $other/>)
+ : qq(<link rel="$rel" type="$type" href="$src"$other>)) if $src;
+ }
+ }
+ else
+ { # Otherwise, push the single -src, if it exists.
+ push(@result,$XHTML ? qq(<link rel="$rel" type="$type" href="$src" $other/>)
+ : qq(<link rel="$rel" type="$type" href="$src"$other>)
+ ) if $src;
+ }
+ if ($verbatim) {
+ my @v = ref($verbatim) eq 'ARRAY' ? @$verbatim : $verbatim;
+ push(@result, "<style type=\"text/css\">\n$_\n</style>") for @v;
+ }
+ if ($code) {
+ my @c = ref($code) eq 'ARRAY' ? @$code : $code;
+ push(@result,style({'type'=>$type},"$cdata_start\n$_\n$cdata_end")) for @c;
+ }
+
+ } else {
+ my $src = $s;
+ push(@result,$XHTML ? qq(<link rel="$rel" type="$type" href="$src" $other/>)
+ : qq(<link rel="$rel" type="$type" href="$src"$other>));
+ }
+ }
+ @result;
+}
+
+sub _script {
+ my ($self,$script) = @_;
+ my (@result);
+
+ my (@scripts) = ref($script) eq 'ARRAY' ? @$script : ($script);
+ for $script (@scripts) {
+ my($src,$code,$language,$charset);
+ if (ref($script)) { # script is a hash
+ ($src,$code,$type,$charset) =
+ rearrange(['SRC','CODE',['LANGUAGE','TYPE'],'CHARSET'],
+ '-foo'=>'bar', # a trick to allow the '-' to be omitted
+ ref($script) eq 'ARRAY' ? @$script : %$script);
+ $type ||= 'text/javascript';
+ unless ($type =~ m!\w+/\w+!) {
+ $type =~ s/[\d.]+$//;
+ $type = "text/$type";
+ }
+ } else {
+ ($src,$code,$type,$charset) = ('',$script, 'text/javascript', '');
+ }
+
+ my $comment = '//'; # javascript by default
+ $comment = '#' if $type=~/perl|tcl/i;
+ $comment = "'" if $type=~/vbscript/i;
+
+ my ($cdata_start,$cdata_end);
+ if ($XHTML) {
+ $cdata_start = "$comment<![CDATA[\n";
+ $cdata_end .= "\n$comment]]>";
+ } else {
+ $cdata_start = "\n<!-- Hide script\n";
+ $cdata_end = $comment;
+ $cdata_end .= " End script hiding -->\n";
+ }
+ my(@satts);
+ push(@satts,'src'=>$src) if $src;
+ push(@satts,'type'=>$type);
+ push(@satts,'charset'=>$charset) if ($src && $charset);
+ $code = $cdata_start . $code . $cdata_end if defined $code;
+ push(@result,$self->script({@satts},$code || ''));
+ }
+ @result;
+}
+
+#### Method: end_html
+# End an HTML document.
+# Trivial method for completeness. Just returns "</body>"
+####
+sub end_html {
+ return "\n</body>\n</html>";
+}
+
+################################
+# METHODS USED IN BUILDING FORMS
+################################
+
+#### Method: isindex
+# Just prints out the isindex tag.
+# Parameters:
+# $action -> optional URL of script to run
+# Returns:
+# A string containing a <isindex> tag
+sub isindex {
+ my($self,@p) = self_or_default(@_);
+ my($action,@other) = rearrange([ACTION],@p);
+ $action = qq/ action="$action"/ if $action;
+ my($other) = @other ? " @other" : '';
+ return $XHTML ? "<isindex$action$other />" : "<isindex$action$other>";
+}
+
+#### Method: start_form
+# Start a form
+# Parameters:
+# $method -> optional submission method to use (GET or POST)
+# $action -> optional URL of script to run
+# $enctype ->encoding to use (URL_ENCODED or MULTIPART)
+sub start_form {
+ my($self,@p) = self_or_default(@_);
+
+ my($method,$action,$enctype,@other) =
+ rearrange([METHOD,ACTION,ENCTYPE],@p);
+
+ $method = $self->_maybe_escapeHTML(lc($method || 'post'));
+
+ if( $XHTML ){
+ $enctype = $self->_maybe_escapeHTML($enctype || &MULTIPART);
+ }else{
+ $enctype = $self->_maybe_escapeHTML($enctype || &URL_ENCODED);
+ }
+
+ if (defined $action) {
+ $action = $self->_maybe_escapeHTML($action);
+ }
+ else {
+ $action = $self->_maybe_escapeHTML($self->request_uri || $self->self_url);
+ }
+ $action = qq(action="$action");
+ my($other) = @other ? " @other" : '';
+ $self->{'.parametersToAdd'}={};
+ return qq/<form method="$method" $action enctype="$enctype"$other>/;
+}
+
+#### Method: start_multipart_form
+sub start_multipart_form {
+ my($self,@p) = self_or_default(@_);
+ if (defined($p[0]) && substr($p[0],0,1) eq '-') {
+ return $self->start_form(-enctype=>&MULTIPART,@p);
+ } else {
+ my($method,$action,@other) =
+ rearrange([METHOD,ACTION],@p);
+ return $self->start_form($method,$action,&MULTIPART,@other);
+ }
+}
+
+#### Method: end_form
+# End a form
+# Note: This repeated below under the older name.
+sub end_form {
+ my($self,@p) = self_or_default(@_);
+ if ( $NOSTICKY ) {
+ return wantarray ? ("</form>") : "\n</form>";
+ } else {
+ if (my @fields = $self->get_fields) {
+ return wantarray ? ("<div>",@fields,"</div>","</form>")
+ : "<div>".(join '',@fields)."</div>\n</form>";
+ } else {
+ return "</form>";
+ }
+ }
+}
+
+#### Method: end_multipart_form
+# end a multipart form
+sub end_multipart_form {
+ &end_form;
+}
+
+sub _textfield {
+ my($self,$tag,@p) = self_or_default(@_);
+ my($name,$default,$size,$maxlength,$override,$tabindex,@other) =
+ rearrange([NAME,[DEFAULT,VALUE,VALUES],SIZE,MAXLENGTH,[OVERRIDE,FORCE],TABINDEX],@p);
+
+ my $current = $override ? $default :
+ (defined($self->param($name)) ? $self->param($name) : $default);
+
+ $current = defined($current) ? $self->_maybe_escapeHTML($current,1) : '';
+ $name = defined($name) ? $self->_maybe_escapeHTML($name) : '';
+ my($s) = defined($size) ? qq/ size="$size"/ : '';
+ my($m) = defined($maxlength) ? qq/ maxlength="$maxlength"/ : '';
+ my($other) = @other ? " @other" : '';
+ # this entered at cristy's request to fix problems with file upload fields
+ # and WebTV -- not sure it won't break stuff
+ my($value) = $current ne '' ? qq(value="$current") : '';
+ $tabindex = $self->element_tab($tabindex);
+ return $XHTML ? qq(<input type="$tag" name="$name" $tabindex$value$s$m$other />)
+ : qq(<input type="$tag" name="$name" $value$s$m$other>);
+}
+
+#### Method: textfield
+# Parameters:
+# $name -> Name of the text field
+# $default -> Optional default value of the field if not
+# already defined.
+# $size -> Optional width of field in characaters.
+# $maxlength -> Optional maximum number of characters.
+# Returns:
+# A string containing a <input type="text"> field
+#
+sub textfield {
+ my($self,@p) = self_or_default(@_);
+ $self->_textfield('text',@p);
+}
+
+#### Method: filefield
+# Parameters:
+# $name -> Name of the file upload field
+# $size -> Optional width of field in characaters.
+# $maxlength -> Optional maximum number of characters.
+# Returns:
+# A string containing a <input type="file"> field
+#
+sub filefield {
+ my($self,@p) = self_or_default(@_);
+ $self->_textfield('file',@p);
+}
+
+#### Method: password
+# Create a "secret password" entry field
+# Parameters:
+# $name -> Name of the field
+# $default -> Optional default value of the field if not
+# already defined.
+# $size -> Optional width of field in characters.
+# $maxlength -> Optional maximum characters that can be entered.
+# Returns:
+# A string containing a <input type="password"> field
+#
+sub password_field {
+ my ($self,@p) = self_or_default(@_);
+ $self->_textfield('password',@p);
+}
+
+#### Method: textarea
+# Parameters:
+# $name -> Name of the text field
+# $default -> Optional default value of the field if not
+# already defined.
+# $rows -> Optional number of rows in text area
+# $columns -> Optional number of columns in text area
+# Returns:
+# A string containing a <textarea></textarea> tag
+#
+sub textarea {
+ my($self,@p) = self_or_default(@_);
+ my($name,$default,$rows,$cols,$override,$tabindex,@other) =
+ rearrange([NAME,[DEFAULT,VALUE],ROWS,[COLS,COLUMNS],[OVERRIDE,FORCE],TABINDEX],@p);
+
+ my($current)= $override ? $default :
+ (defined($self->param($name)) ? $self->param($name) : $default);
+
+ $name = defined($name) ? $self->_maybe_escapeHTML($name) : '';
+ $current = defined($current) ? $self->_maybe_escapeHTML($current) : '';
+ my($r) = $rows ? qq/ rows="$rows"/ : '';
+ my($c) = $cols ? qq/ cols="$cols"/ : '';
+ my($other) = @other ? " @other" : '';
+ $tabindex = $self->element_tab($tabindex);
+ return qq{<textarea name="$name" $tabindex$r$c$other>$current</textarea>};
+}
+
+#### Method: button
+# Create a javascript button.
+# Parameters:
+# $name -> (optional) Name for the button. (-name)
+# $value -> (optional) Value of the button when selected (and visible name) (-value)
+# $onclick -> (optional) Text of the JavaScript to run when the button is
+# clicked.
+# Returns:
+# A string containing a <input type="button"> tag
+####
+sub button {
+ my($self,@p) = self_or_default(@_);
+
+ my($label,$value,$script,$tabindex,@other) = rearrange([NAME,[VALUE,LABEL],
+ [ONCLICK,SCRIPT],TABINDEX],@p);
+
+ $label=$self->_maybe_escapeHTML($label);
+ $value=$self->_maybe_escapeHTML($value,1);
+ $script=$self->_maybe_escapeHTML($script);
+
+ $script ||= '';
+
+ my($name) = '';
+ $name = qq/ name="$label"/ if $label;
+ $value = $value || $label;
+ my($val) = '';
+ $val = qq/ value="$value"/ if $value;
+ $script = qq/ onclick="$script"/ if $script;
+ my($other) = @other ? " @other" : '';
+ $tabindex = $self->element_tab($tabindex);
+ return $XHTML ? qq(<input type="button" $tabindex$name$val$script$other />)
+ : qq(<input type="button"$name$val$script$other>);
+}
+
+#### Method: submit
+# Create a "submit query" button.
+# Parameters:
+# $name -> (optional) Name for the button.
+# $value -> (optional) Value of the button when selected (also doubles as label).
+# $label -> (optional) Label printed on the button(also doubles as the value).
+# Returns:
+# A string containing a <input type="submit"> tag
+####
+sub submit {
+ my($self,@p) = self_or_default(@_);
+
+ my($label,$value,$tabindex,@other) = rearrange([NAME,[VALUE,LABEL],TABINDEX],@p);
+
+ $label=$self->_maybe_escapeHTML($label);
+ $value=$self->_maybe_escapeHTML($value,1);
+
+ my $name = $NOSTICKY ? '' : 'name=".submit" ';
+ $name = qq/name="$label" / if defined($label);
+ $value = defined($value) ? $value : $label;
+ my $val = '';
+ $val = qq/value="$value" / if defined($value);
+ $tabindex = $self->element_tab($tabindex);
+ my($other) = @other ? "@other " : '';
+ return $XHTML ? qq(<input type="submit" $tabindex$name$val$other/>)
+ : qq(<input type="submit" $name$val$other>);
+}
+
+#### Method: reset
+# Create a "reset" button.
+# Parameters:
+# $name -> (optional) Name for the button.
+# Returns:
+# A string containing a <input type="reset"> tag
+####
+sub reset {
+ my($self,@p) = self_or_default(@_);
+ my($label,$value,$tabindex,@other) = rearrange(['NAME',['VALUE','LABEL'],TABINDEX],@p);
+ $label=$self->_maybe_escapeHTML($label);
+ $value=$self->_maybe_escapeHTML($value,1);
+ my ($name) = ' name=".reset"';
+ $name = qq/ name="$label"/ if defined($label);
+ $value = defined($value) ? $value : $label;
+ my($val) = '';
+ $val = qq/ value="$value"/ if defined($value);
+ my($other) = @other ? " @other" : '';
+ $tabindex = $self->element_tab($tabindex);
+ return $XHTML ? qq(<input type="reset" $tabindex$name$val$other />)
+ : qq(<input type="reset"$name$val$other>);
+}
+
+#### Method: defaults
+# Create a "defaults" button.
+# Parameters:
+# $name -> (optional) Name for the button.
+# Returns:
+# A string containing a <input type="submit" name=".defaults"> tag
+#
+# Note: this button has a special meaning to the initialization script,
+# and tells it to ERASE the current query string so that your defaults
+# are used again!
+####
+sub defaults {
+ my($self,@p) = self_or_default(@_);
+
+ my($label,$tabindex,@other) = rearrange([[NAME,VALUE],TABINDEX],@p);
+
+ $label=$self->_maybe_escapeHTML($label,1);
+ $label = $label || "Defaults";
+ my($value) = qq/ value="$label"/;
+ my($other) = @other ? " @other" : '';
+ $tabindex = $self->element_tab($tabindex);
+ return $XHTML ? qq(<input type="submit" name=".defaults" $tabindex$value$other />)
+ : qq/<input type="submit" NAME=".defaults"$value$other>/;
+}
+
+#### Method: comment
+# Create an HTML <!-- comment -->
+# Parameters: a string
+sub comment {
+ my($self,@p) = self_or_CGI(@_);
+ return "<!-- @p -->";
+}
+
+#### Method: checkbox
+# Create a checkbox that is not logically linked to any others.
+# The field value is "on" when the button is checked.
+# Parameters:
+# $name -> Name of the checkbox
+# $checked -> (optional) turned on by default if true
+# $value -> (optional) value of the checkbox, 'on' by default
+# $label -> (optional) a user-readable label printed next to the box.
+# Otherwise the checkbox name is used.
+# Returns:
+# A string containing a <input type="checkbox"> field
+####
+sub checkbox {
+ my($self,@p) = self_or_default(@_);
+
+ my($name,$checked,$value,$label,$labelattributes,$override,$tabindex,@other) =
+ rearrange([NAME,[CHECKED,SELECTED,ON],VALUE,LABEL,LABELATTRIBUTES,
+ [OVERRIDE,FORCE],TABINDEX],@p);
+
+ $value = defined $value ? $value : 'on';
+
+ if (!$override && ($self->{'.fieldnames'}->{$name} ||
+ defined $self->param($name))) {
+ $checked = grep($_ eq $value,$self->param($name)) ? $self->_checked(1) : '';
+ } else {
+ $checked = $self->_checked($checked);
+ }
+ my($the_label) = defined $label ? $label : $name;
+ $name = $self->_maybe_escapeHTML($name);
+ $value = $self->_maybe_escapeHTML($value,1);
+ $the_label = $self->_maybe_escapeHTML($the_label);
+ my($other) = @other ? "@other " : '';
+ $tabindex = $self->element_tab($tabindex);
+ $self->register_parameter($name);
+ return $XHTML ? CGI::label($labelattributes,
+ qq{<input type="checkbox" name="$name" value="$value" $tabindex$checked$other/>$the_label})
+ : qq{<input type="checkbox" name="$name" value="$value"$checked$other>$the_label};
+}
+
+# Escape HTML
+sub escapeHTML {
+ require HTML::Entities;
+ # hack to work around earlier hacks
+ push @_,$_[0] if @_==1 && $_[0] eq 'CGI';
+ my ($self,$toencode,$newlinestoo) = CGI::self_or_default(@_);
+ return undef unless defined($toencode);
+ my $encode_entities = $ENCODE_ENTITIES;
+ $encode_entities .= "\012\015" if ( $encode_entities && $newlinestoo );
+ return HTML::Entities::encode_entities($toencode,$encode_entities);
+}
+
+# unescape HTML -- used internally
+sub unescapeHTML {
+ require HTML::Entities;
+ # hack to work around earlier hacks
+ push @_,$_[0] if @_==1 && $_[0] eq 'CGI';
+ my ($self,$string) = CGI::self_or_default(@_);
+ return undef unless defined($string);
+ return HTML::Entities::decode_entities($string);
+}
+
+# Internal procedure - don't use
+sub _tableize {
+ my($rows,$columns,$rowheaders,$colheaders,@elements) = @_;
+ my @rowheaders = $rowheaders ? @$rowheaders : ();
+ my @colheaders = $colheaders ? @$colheaders : ();
+ my($result);
+
+ if (defined($columns)) {
+ $rows = int(0.99 + @elements/$columns) unless defined($rows);
+ }
+ if (defined($rows)) {
+ $columns = int(0.99 + @elements/$rows) unless defined($columns);
+ }
+
+ # rearrange into a pretty table
+ $result = "<table>";
+ my($row,$column);
+ unshift(@colheaders,'') if @colheaders && @rowheaders;
+ $result .= "<tr>" if @colheaders;
+ for (@colheaders) {
+ $result .= "<th>$_</th>";
+ }
+ for ($row=0;$row<$rows;$row++) {
+ $result .= "<tr>";
+ $result .= "<th>$rowheaders[$row]</th>" if @rowheaders;
+ for ($column=0;$column<$columns;$column++) {
+ $result .= "<td>" . $elements[$column*$rows + $row] . "</td>"
+ if defined($elements[$column*$rows + $row]);
+ }
+ $result .= "</tr>";
+ }
+ $result .= "</table>";
+ return $result;
+}
+
+#### Method: radio_group
+# Create a list of logically-linked radio buttons.
+# Parameters:
+# $name -> Common name for all the buttons.
+# $values -> A pointer to a regular array containing the
+# values for each button in the group.
+# $default -> (optional) Value of the button to turn on by default. Pass '-'
+# to turn _nothing_ on.
+# $linebreak -> (optional) Set to true to place linebreaks
+# between the buttons.
+# $labels -> (optional)
+# A pointer to a hash of labels to print next to each checkbox
+# in the form $label{'value'}="Long explanatory label".
+# Otherwise the provided values are used as the labels.
+# Returns:
+# An ARRAY containing a series of <input type="radio"> fields
+####
+sub radio_group {
+ my($self,@p) = self_or_default(@_);
+ $self->_box_group('radio',@p);
+}
+
+#### Method: checkbox_group
+# Create a list of logically-linked checkboxes.
+# Parameters:
+# $name -> Common name for all the check boxes
+# $values -> A pointer to a regular array containing the
+# values for each checkbox in the group.
+# $defaults -> (optional)
+# 1. If a pointer to a regular array of checkbox values,
+# then this will be used to decide which
+# checkboxes to turn on by default.
+# 2. If a scalar, will be assumed to hold the
+# value of a single checkbox in the group to turn on.
+# $linebreak -> (optional) Set to true to place linebreaks
+# between the buttons.
+# $labels -> (optional)
+# A pointer to a hash of labels to print next to each checkbox
+# in the form $label{'value'}="Long explanatory label".
+# Otherwise the provided values are used as the labels.
+# Returns:
+# An ARRAY containing a series of <input type="checkbox"> fields
+####
+
+sub checkbox_group {
+ my($self,@p) = self_or_default(@_);
+ $self->_box_group('checkbox',@p);
+}
+
+sub _box_group {
+ my $self = shift;
+ my $box_type = shift;
+
+ my($name,$values,$defaults,$linebreak,$labels,$labelattributes,
+ $attributes,$rows,$columns,$rowheaders,$colheaders,
+ $override,$nolabels,$tabindex,$disabled,@other) =
+ rearrange([NAME,[VALUES,VALUE],[DEFAULT,DEFAULTS],LINEBREAK,LABELS,LABELATTRIBUTES,
+ ATTRIBUTES,ROWS,[COLUMNS,COLS],[ROWHEADERS,ROWHEADER],[COLHEADERS,COLHEADER],
+ [OVERRIDE,FORCE],NOLABELS,TABINDEX,DISABLED
+ ],@_);
+
+
+ my($result,$checked,@elements,@values);
+
+ @values = $self->_set_values_and_labels($values,\$labels,$name);
+ my %checked = $self->previous_or_default($name,$defaults,$override);
+
+ # If no check array is specified, check the first by default
+ $checked{$values[0]}++ if $box_type eq 'radio' && !%checked;
+
+ $name=$self->_maybe_escapeHTML($name);
+
+ my %tabs = ();
+ if ($TABINDEX && $tabindex) {
+ if (!ref $tabindex) {
+ $self->element_tab($tabindex);
+ } elsif (ref $tabindex eq 'ARRAY') {
+ %tabs = map {$_=>$self->element_tab} @$tabindex;
+ } elsif (ref $tabindex eq 'HASH') {
+ %tabs = %$tabindex;
+ }
+ }
+ %tabs = map {$_=>$self->element_tab} @values unless %tabs;
+ my $other = @other ? "@other " : '';
+ my $radio_checked;
+
+ # for disabling groups of radio/checkbox buttons
+ my %disabled;
+ for (@{$disabled}) {
+ $disabled{$_}=1;
+ }
+
+ for (@values) {
+ my $disable="";
+ if ($disabled{$_}) {
+ $disable="disabled='1'";
+ }
+
+ my $checkit = $self->_checked($box_type eq 'radio' ? ($checked{$_} && !$radio_checked++)
+ : $checked{$_});
+ my($break);
+ if ($linebreak) {
+ $break = $XHTML ? "<br />" : "<br>";
+ }
+ else {
+ $break = '';
+ }
+ my($label)='';
+ unless (defined($nolabels) && $nolabels) {
+ $label = $_;
+ $label = $labels->{$_} if defined($labels) && defined($labels->{$_});
+ $label = $self->_maybe_escapeHTML($label,1);
+ $label = "<span style=\"color:gray\">$label</span>" if $disabled{$_};
+ }
+ my $attribs = $self->_set_attributes($_, $attributes);
+ my $tab = $tabs{$_};
+ $_=$self->_maybe_escapeHTML($_);
+
+ if ($XHTML) {
+ push @elements,
+ CGI::label($labelattributes,
+ qq(<input type="$box_type" name="$name" value="$_" $checkit$other$tab$attribs$disable/>$label)).${break};
+ } else {
+ push(@elements,qq/<input type="$box_type" name="$name" value="$_" $checkit$other$tab$attribs$disable>${label}${break}/);
+ }
+ }
+ $self->register_parameter($name);
+ return wantarray ? @elements : "@elements"
+ unless defined($columns) || defined($rows);
+ return _tableize($rows,$columns,$rowheaders,$colheaders,@elements);
+}
+
+#### Method: popup_menu
+# Create a popup menu.
+# Parameters:
+# $name -> Name for all the menu
+# $values -> A pointer to a regular array containing the
+# text of each menu item.
+# $default -> (optional) Default item to display
+# $labels -> (optional)
+# A pointer to a hash of labels to print next to each checkbox
+# in the form $label{'value'}="Long explanatory label".
+# Otherwise the provided values are used as the labels.
+# Returns:
+# A string containing the definition of a popup menu.
+####
+sub popup_menu {
+ my($self,@p) = self_or_default(@_);
+
+ my($name,$values,$default,$labels,$attributes,$override,$tabindex,@other) =
+ rearrange([NAME,[VALUES,VALUE],[DEFAULT,DEFAULTS],LABELS,
+ ATTRIBUTES,[OVERRIDE,FORCE],TABINDEX],@p);
+ my($result,%selected);
+
+ if (!$override && defined($self->param($name))) {
+ $selected{$self->param($name)}++;
+ } elsif (defined $default) {
+ %selected = map {$_=>1} ref($default) eq 'ARRAY'
+ ? @$default
+ : $default;
+ }
+ $name=$self->_maybe_escapeHTML($name);
+ # RT #30057 - ignore -multiple, if you need this
+ # then use scrolling_list
+ @other = grep { $_ !~ /^multiple=/i } @other;
+ my($other) = @other ? " @other" : '';
+
+ my(@values);
+ @values = $self->_set_values_and_labels($values,\$labels,$name);
+ $tabindex = $self->element_tab($tabindex);
+ $name = q{} if ! defined $name;
+ $result = qq/<select name="$name" $tabindex$other>\n/;
+ for (@values) {
+ if (/<optgroup/) {
+ for my $v (split(/\n/)) {
+ my $selectit = $XHTML ? 'selected="selected"' : 'selected';
+ for my $selected (keys %selected) {
+ $v =~ s/(value="\Q$selected\E")/$selectit $1/;
+ }
+ $result .= "$v\n";
+ }
+ }
+ else {
+ my $attribs = $self->_set_attributes($_, $attributes);
+ my($selectit) = $self->_selected($selected{$_});
+ my($label) = $_;
+ $label = $labels->{$_} if defined($labels) && defined($labels->{$_});
+ my($value) = $self->_maybe_escapeHTML($_);
+ $label = $self->_maybe_escapeHTML($label,1);
+ $result .= "<option${attribs} ${selectit}value=\"$value\">$label</option>\n";
+ }
+ }
+
+ $result .= "</select>";
+ return $result;
+}
+
+#### Method: optgroup
+# Create a optgroup.
+# Parameters:
+# $name -> Label for the group
+# $values -> A pointer to a regular array containing the
+# values for each option line in the group.
+# $labels -> (optional)
+# A pointer to a hash of labels to print next to each item
+# in the form $label{'value'}="Long explanatory label".
+# Otherwise the provided values are used as the labels.
+# $labeled -> (optional)
+# A true value indicates the value should be used as the label attribute
+# in the option elements.
+# The label attribute specifies the option label presented to the user.
+# This defaults to the content of the <option> element, but the label
+# attribute allows authors to more easily use optgroup without sacrificing
+# compatibility with browsers that do not support option groups.
+# $novals -> (optional)
+# A true value indicates to suppress the val attribute in the option elements
+# Returns:
+# A string containing the definition of an option group.
+####
+sub optgroup {
+ my($self,@p) = self_or_default(@_);
+ my($name,$values,$attributes,$labeled,$noval,$labels,@other)
+ = rearrange([NAME,[VALUES,VALUE],ATTRIBUTES,LABELED,NOVALS,LABELS],@p);
+
+ my($result,@values);
+ @values = $self->_set_values_and_labels($values,\$labels,$name,$labeled,$novals);
+ my($other) = @other ? " @other" : '';
+
+ $name = $self->_maybe_escapeHTML($name) || q{};
+ $result = qq/<optgroup label="$name"$other>\n/;
+ for (@values) {
+ if (/<optgroup/) {
+ for (split(/\n/)) {
+ my $selectit = $XHTML ? 'selected="selected"' : 'selected';
+ s/(value="$selected")/$selectit $1/ if defined $selected;
+ $result .= "$_\n";
+ }
+ }
+ else {
+ my $attribs = $self->_set_attributes($_, $attributes);
+ my($label) = $_;
+ $label = $labels->{$_} if defined($labels) && defined($labels->{$_});
+ $label=$self->_maybe_escapeHTML($label);
+ my($value)=$self->_maybe_escapeHTML($_,1);
+ $result .= $labeled ? $novals ? "<option$attribs label=\"$value\">$label</option>\n"
+ : "<option$attribs label=\"$value\" value=\"$value\">$label</option>\n"
+ : $novals ? "<option$attribs>$label</option>\n"
+ : "<option$attribs value=\"$value\">$label</option>\n";
+ }
+ }
+ $result .= "</optgroup>";
+ return $result;
+}
+
+#### Method: scrolling_list
+# Create a scrolling list.
+# Parameters:
+# $name -> name for the list
+# $values -> A pointer to a regular array containing the
+# values for each option line in the list.
+# $defaults -> (optional)
+# 1. If a pointer to a regular array of options,
+# then this will be used to decide which
+# lines to turn on by default.
+# 2. Otherwise holds the value of the single line to turn on.
+# $size -> (optional) Size of the list.
+# $multiple -> (optional) If set, allow multiple selections.
+# $labels -> (optional)
+# A pointer to a hash of labels to print next to each checkbox
+# in the form $label{'value'}="Long explanatory label".
+# Otherwise the provided values are used as the labels.
+# Returns:
+# A string containing the definition of a scrolling list.
+####
+sub scrolling_list {
+ my($self,@p) = self_or_default(@_);
+ my($name,$values,$defaults,$size,$multiple,$labels,$attributes,$override,$tabindex,@other)
+ = rearrange([NAME,[VALUES,VALUE],[DEFAULTS,DEFAULT],
+ SIZE,MULTIPLE,LABELS,ATTRIBUTES,[OVERRIDE,FORCE],TABINDEX],@p);
+
+ my($result,@values);
+ @values = $self->_set_values_and_labels($values,\$labels,$name);
+
+ $size = $size || scalar(@values);
+
+ my(%selected) = $self->previous_or_default($name,$defaults,$override);
+
+ my($is_multiple) = $multiple ? qq/ multiple="multiple"/ : '';
+ my($has_size) = $size ? qq/ size="$size"/: '';
+ my($other) = @other ? " @other" : '';
+
+ $name=$self->_maybe_escapeHTML($name);
+ $tabindex = $self->element_tab($tabindex);
+ $result = qq/<select name="$name" $tabindex$has_size$is_multiple$other>\n/;
+ for (@values) {
+ if (/<optgroup/) {
+ for my $v (split(/\n/)) {
+ my $selectit = $XHTML ? 'selected="selected"' : 'selected';
+ for my $selected (keys %selected) {
+ $v =~ s/(value="$selected")/$selectit $1/;
+ }
+ $result .= "$v\n";
+ }
+ }
+ else {
+ my $attribs = $self->_set_attributes($_, $attributes);
+ my($selectit) = $self->_selected($selected{$_});
+ my($label) = $_;
+ $label = $labels->{$_} if defined($labels) && defined($labels->{$_});
+ my($value) = $self->_maybe_escapeHTML($_);
+ $label = $self->_maybe_escapeHTML($label,1);
+ $result .= "<option${attribs} ${selectit}value=\"$value\">$label</option>\n";
+ }
+ }
+
+ $result .= "</select>";
+ $self->register_parameter($name);
+ return $result;
+}
+
+#### Method: hidden
+# Parameters:
+# $name -> Name of the hidden field
+# @default -> (optional) Initial values of field (may be an array)
+# or
+# $default->[initial values of field]
+# Returns:
+# A string containing a <input type="hidden" name="name" value="value">
+####
+sub hidden {
+ my($self,@p) = self_or_default(@_);
+
+ # this is the one place where we departed from our standard
+ # calling scheme, so we have to special-case (darn)
+ my(@result,@value);
+ my($name,$default,$override,@other) =
+ rearrange([NAME,[DEFAULT,VALUE,VALUES],[OVERRIDE,FORCE]],@p);
+
+ my $do_override = 0;
+ if ( ref($p[0]) || substr($p[0],0,1) eq '-') {
+ @value = ref($default) ? @{$default} : $default;
+ $do_override = $override;
+ } else {
+ for ($default,$override,@other) {
+ push(@value,$_) if defined($_);
+ }
+ undef @other;
+ }
+
+ # use previous values if override is not set
+ my @prev = $self->param($name);
+ @value = @prev if !$do_override && @prev;
+
+ $name=$self->_maybe_escapeHTML($name);
+ for (@value) {
+ $_ = defined($_) ? $self->_maybe_escapeHTML($_,1) : '';
+ push @result,$XHTML ? qq(<input type="hidden" name="$name" value="$_" @other />)
+ : qq(<input type="hidden" name="$name" value="$_" @other>);
+ }
+ return wantarray ? @result : join('',@result);
+}
+
+#### Method: image_button
+# Parameters:
+# $name -> Name of the button
+# $src -> URL of the image source
+# $align -> Alignment style (TOP, BOTTOM or MIDDLE)
+# Returns:
+# A string containing a <input type="image" name="name" src="url" align="alignment">
+####
+sub image_button {
+ my($self,@p) = self_or_default(@_);
+
+ my($name,$src,$alignment,@other) =
+ rearrange([NAME,SRC,ALIGN],@p);
+
+ my($align) = $alignment ? " align=\L\"$alignment\"" : '';
+ my($other) = @other ? " @other" : '';
+ $name=$self->_maybe_escapeHTML($name);
+ return $XHTML ? qq(<input type="image" name="$name" src="$src"$align$other />)
+ : qq/<input type="image" name="$name" src="$src"$align$other>/;
+}
+
+#### Method: self_url
+# Returns a URL containing the current script and all its
+# param/value pairs arranged as a query. You can use this
+# to create a link that, when selected, will reinvoke the
+# script with all its state information preserved.
+####
+sub self_url {
+ my($self,@p) = self_or_default(@_);
+ return $self->url('-path_info'=>1,'-query'=>1,'-full'=>1,@p);
+}
+
+# This is provided as a synonym to self_url() for people unfortunate
+# enough to have incorporated it into their programs already!
+sub state {
+ &self_url;
+}
+
+#### Method: url
+# Like self_url, but doesn't return the query string part of
+# the URL.
+####
+sub url {
+ my($self,@p) = self_or_default(@_);
+ my ($relative,$absolute,$full,$path_info,$query,$base,$rewrite) =
+ rearrange(['RELATIVE','ABSOLUTE','FULL',['PATH','PATH_INFO'],['QUERY','QUERY_STRING'],'BASE','REWRITE'],@p);
+ my $url = '';
+ $full++ if $base || !($relative || $absolute);
+ $rewrite++ unless defined $rewrite;
+
+ my $path = $self->path_info;
+ my $script_name = $self->script_name;
+ my $request_uri = $self->request_uri || '';
+ my $query_str = $query ? $self->query_string : '';
+
+ $request_uri =~ s/\?.*$//s; # remove query string
+ $request_uri = unescape($request_uri);
+
+ my $uri = $rewrite && $request_uri ? $request_uri : $script_name;
+ $uri =~ s/\?.*$//s; # remove query string
+
+ if ( defined( $ENV{PATH_INFO} ) ) {
+ # IIS sometimes sets PATH_INFO to the same value as SCRIPT_NAME so only sub it out
+ # if SCRIPT_NAME isn't defined or isn't the same value as PATH_INFO
+ $uri =~ s/\Q$ENV{PATH_INFO}\E$//
+ if ( ! defined( $ENV{SCRIPT_NAME} ) or $ENV{PATH_INFO} ne $ENV{SCRIPT_NAME} );
+
+ # if we're not IIS then keep to spec, the relevant info is here:
+ # https://tools.ietf.org/html/rfc3875#section-4.1.13, namely
+ # "No PATH_INFO segment (see section 4.1.5) is included in the
+ # SCRIPT_NAME value." (see GH #126, GH #152, GH #176)
+ if ( ! $IIS ) {
+ $uri =~ s/\Q$ENV{PATH_INFO}\E$//;
+ }
+ }
+
+ if ($full) {
+ my $protocol = $self->protocol();
+ $url = "$protocol://";
+ my $vh = http('x_forwarded_host') || http('host') || '';
+ $vh =~ s/^.*,\s*//; # x_forwarded_host may be a comma-separated list (e.g. when the request has
+ # passed through multiple reverse proxies. Take the last one.
+ $vh =~ s/\:\d+$//; # some clients add the port number (incorrectly). Get rid of it.
+
+ $url .= $vh || server_name();
+
+ my $port = $self->virtual_port;
+
+ # add the port to the url unless it's the protocol's default port
+ $url .= ':' . $port unless (lc($protocol) eq 'http' && $port == 80)
+ or (lc($protocol) eq 'https' && $port == 443);
+
+ return $url if $base;
+
+ $url .= $uri;
+ } elsif ($relative) {
+ ($url) = $uri =~ m!([^/]+)$!;
+ } elsif ($absolute) {
+ $url = $uri;
+ }
+
+ $url .= $path if $path_info and defined $path;
+ $url .= "?$query_str" if $query and $query_str ne '';
+ $url ||= '';
+ $url =~ s/([^a-zA-Z0-9_.%;&?\/\\:+=~-])/sprintf("%%%02X",ord($1))/eg;
+ return $url;
+}
+
+#### Method: cookie
+# Set or read a cookie from the specified name.
+# Cookie can then be passed to header().
+# Usual rules apply to the stickiness of -value.
+# Parameters:
+# -name -> name for this cookie (optional)
+# -value -> value of this cookie (scalar, array or hash)
+# -path -> paths for which this cookie is valid (optional)
+# -domain -> internet domain in which this cookie is valid (optional)
+# -secure -> if true, cookie only passed through secure channel (optional)
+# -expires -> expiry date in format Wdy, DD-Mon-YYYY HH:MM:SS GMT (optional)
+####
+sub cookie {
+ my($self,@p) = self_or_default(@_);
+ my($name,$value,$path,$domain,$secure,$expires,$httponly) =
+ rearrange([NAME,[VALUE,VALUES],PATH,DOMAIN,SECURE,EXPIRES,HTTPONLY],@p);
+
+ require CGI::Cookie;
+
+ # if no value is supplied, then we retrieve the
+ # value of the cookie, if any. For efficiency, we cache the parsed
+ # cookies in our state variables.
+ unless ( defined($value) ) {
+ $self->{'.cookies'} = CGI::Cookie->fetch;
+
+ # If no name is supplied, then retrieve the names of all our cookies.
+ return () unless $self->{'.cookies'};
+ return keys %{$self->{'.cookies'}} unless $name;
+ return () unless $self->{'.cookies'}->{$name};
+ return $self->{'.cookies'}->{$name}->value if defined($name) && $name ne '';
+ }
+
+ # If we get here, we're creating a new cookie
+ return undef unless defined($name) && $name ne ''; # this is an error
+
+ my @param;
+ push(@param,'-name'=>$name);
+ push(@param,'-value'=>$value);
+ push(@param,'-domain'=>$domain) if $domain;
+ push(@param,'-path'=>$path) if $path;
+ push(@param,'-expires'=>$expires) if $expires;
+ push(@param,'-secure'=>$secure) if $secure;
+ push(@param,'-httponly'=>$httponly) if $httponly;
+
+ return CGI::Cookie->new(@param);
+}
+
+sub parse_keywordlist {
+ my($self,$tosplit) = @_;
+ $tosplit = unescape($tosplit); # unescape the keywords
+ $tosplit=~tr/+/ /; # pluses to spaces
+ my(@keywords) = split(/\s+/,$tosplit);
+ return @keywords;
+}
+
+sub param_fetch {
+ my($self,@p) = self_or_default(@_);
+ my($name) = rearrange([NAME],@p);
+ return [] unless defined $name;
+
+ unless (exists($self->{param}{$name})) {
+ $self->add_parameter($name);
+ $self->{param}{$name} = [];
+ }
+
+ return $self->{param}{$name};
+}
+
+###############################################
+# OTHER INFORMATION PROVIDED BY THE ENVIRONMENT
+###############################################
+
+#### Method: path_info
+# Return the extra virtual path information provided
+# after the URL (if any)
+####
+sub path_info {
+ my ($self,$info) = self_or_default(@_);
+ if (defined($info)) {
+ $info = "/$info" if $info ne '' && substr($info,0,1) ne '/';
+ $self->{'.path_info'} = $info;
+ } elsif (! defined($self->{'.path_info'}) ) {
+ my (undef,$path_info) = $self->_name_and_path_from_env;
+ $self->{'.path_info'} = $path_info || '';
+ }
+ return $self->{'.path_info'};
+}
+
+# This function returns a potentially modified version of SCRIPT_NAME
+# and PATH_INFO. Some HTTP servers do sanitise the paths in those
+# variables. It is the case of at least Apache 2. If for instance the
+# user requests: /path/./to/script.cgi/x//y/z/../x?y, Apache will set:
+# REQUEST_URI=/path/./to/script.cgi/x//y/z/../x?y
+# SCRIPT_NAME=/path/to/env.cgi
+# PATH_INFO=/x/y/x
+#
+# This is all fine except that some bogus CGI scripts expect
+# PATH_INFO=/http://foo when the user requests
+# http://xxx/script.cgi/http://foo
+#
+# Old versions of this module used to accomodate with those scripts, so
+# this is why we do this here to keep those scripts backward compatible.
+# Basically, we accomodate with those scripts but within limits, that is
+# we only try to preserve the number of / that were provided by the user
+# if $REQUEST_URI and "$SCRIPT_NAME$PATH_INFO" only differ by the number
+# of consecutive /.
+#
+# So for instance, in: http://foo/x//y/script.cgi/a//b, we'll return a
+# script_name of /x//y/script.cgi and a path_info of /a//b, but in:
+# http://foo/./x//z/script.cgi/a/../b//c, we'll return the versions
+# possibly sanitised by the HTTP server, so in the case of Apache 2:
+# script_name == /foo/x/z/script.cgi and path_info == /b/c.
+#
+# Future versions of this module may no longer do that, so one should
+# avoid relying on the browser, proxy, server, and CGI.pm preserving the
+# number of consecutive slashes as no guarantee can be made there.
+sub _name_and_path_from_env {
+ my $self = shift;
+ my $script_name = $ENV{SCRIPT_NAME} || '';
+ my $path_info = $ENV{PATH_INFO} || '';
+ my $uri = $self->request_uri || '';
+
+ $uri =~ s/\?.*//s;
+ $uri = unescape($uri);
+
+ if ( $IIS ) {
+ # IIS doesn't set $ENV{PATH_INFO} correctly. It sets it to
+ # $ENV{SCRIPT_NAME}path_info
+ # IIS also doesn't set $ENV{REQUEST_URI} so we don't want to do
+ # the test below, hence this comes first
+ $path_info =~ s/^\Q$script_name\E(.*)/$1/;
+ } elsif ($uri ne "$script_name$path_info") {
+ my $script_name_pattern = quotemeta($script_name);
+ my $path_info_pattern = quotemeta($path_info);
+ $script_name_pattern =~ s{(?:\\/)+}{/+}g;
+ $path_info_pattern =~ s{(?:\\/)+}{/+}g;
+
+ if ($uri =~ /^($script_name_pattern)($path_info_pattern)$/s) {
+ # REQUEST_URI and SCRIPT_NAME . PATH_INFO only differ by the
+ # numer of consecutive slashes, so we can extract the info from
+ # REQUEST_URI:
+ ($script_name, $path_info) = ($1, $2);
+ }
+ }
+ return ($script_name,$path_info);
+}
+
+#### Method: request_method
+# Returns 'POST', 'GET', 'PUT' or 'HEAD'
+####
+sub request_method {
+ return (defined $ENV{'REQUEST_METHOD'}) ? $ENV{'REQUEST_METHOD'} : undef;
+}
+
+#### Method: content_type
+# Returns the content_type string
+####
+sub content_type {
+ return (defined $ENV{'CONTENT_TYPE'}) ? $ENV{'CONTENT_TYPE'} : undef;
+}
+
+#### Method: path_translated
+# Return the physical path information provided
+# by the URL (if any)
+####
+sub path_translated {
+ return (defined $ENV{'PATH_TRANSLATED'}) ? $ENV{'PATH_TRANSLATED'} : undef;
+}
+
+#### Method: request_uri
+# Return the literal request URI
+####
+sub request_uri {
+ return (defined $ENV{'REQUEST_URI'}) ? $ENV{'REQUEST_URI'} : undef;
+}
+
+#### Method: query_string
+# Synthesize a query string from our current
+# parameters
+####
+sub query_string {
+ my($self) = self_or_default(@_);
+ my($param,$value,@pairs);
+ for $param ($self->param) {
+ my($eparam) = escape($param);
+ for $value ($self->param($param)) {
+ $value = escape($value);
+ next unless defined $value;
+ push(@pairs,"$eparam=$value");
+ }
+ }
+ for (keys %{$self->{'.fieldnames'}}) {
+ push(@pairs,".cgifields=".escape("$_"));
+ }
+ return join($USE_PARAM_SEMICOLONS ? ';' : '&',@pairs);
+}
+
+sub env_query_string {
+ return (defined $ENV{'QUERY_STRING'}) ? $ENV{'QUERY_STRING'} : undef;
+}
+
+#### Method: accept
+# Without parameters, returns an array of the
+# MIME types the browser accepts.
+# With a single parameter equal to a MIME
+# type, will return undef if the browser won't
+# accept it, 1 if the browser accepts it but
+# doesn't give a preference, or a floating point
+# value between 0.0 and 1.0 if the browser
+# declares a quantitative score for it.
+# This handles MIME type globs correctly.
+####
+sub Accept {
+ my($self,$search) = self_or_CGI(@_);
+ my(%prefs,$type,$pref,$pat);
+
+ my(@accept) = defined $self->http('accept')
+ ? split(',',$self->http('accept'))
+ : ();
+
+ for (@accept) {
+ ($pref) = /q=(\d\.\d+|\d+)/;
+ ($type) = m#(\S+/[^;]+)#;
+ next unless $type;
+ $prefs{$type}=$pref || 1;
+ }
+
+ return keys %prefs unless $search;
+
+ # if a search type is provided, we may need to
+ # perform a pattern matching operation.
+ # The MIME types use a glob mechanism, which
+ # is easily translated into a perl pattern match
+
+ # First return the preference for directly supported
+ # types:
+ return $prefs{$search} if $prefs{$search};
+
+ # Didn't get it, so try pattern matching.
+ for (keys %prefs) {
+ next unless /\*/; # not a pattern match
+ ($pat = $_) =~ s/([^\w*])/\\$1/g; # escape meta characters
+ $pat =~ s/\*/.*/g; # turn it into a pattern
+ return $prefs{$_} if $search=~/$pat/;
+ }
+}
+
+#### Method: user_agent
+# If called with no parameters, returns the user agent.
+# If called with one parameter, does a pattern match (case
+# insensitive) on the user agent.
+####
+sub user_agent {
+ my($self,$match)=self_or_CGI(@_);
+ my $user_agent = $self->http('user_agent');
+ return $user_agent unless defined $match && $match && $user_agent;
+ return $user_agent =~ /$match/i;
+}
+
+#### Method: raw_cookie
+# Returns the magic cookies for the session.
+# The cookies are not parsed or altered in any way, i.e.
+# cookies are returned exactly as given in the HTTP
+# headers. If a cookie name is given, only that cookie's
+# value is returned, otherwise the entire raw cookie
+# is returned.
+####
+sub raw_cookie {
+ my($self,$key) = self_or_CGI(@_);
+
+ require CGI::Cookie;
+
+ if (defined($key)) {
+ $self->{'.raw_cookies'} = CGI::Cookie->raw_fetch
+ unless $self->{'.raw_cookies'};
+
+ return () unless $self->{'.raw_cookies'};
+ return () unless $self->{'.raw_cookies'}->{$key};
+ return $self->{'.raw_cookies'}->{$key};
+ }
+ return $self->http('cookie') || $ENV{'COOKIE'} || '';
+}
+
+#### Method: virtual_host
+# Return the name of the virtual_host, which
+# is not always the same as the server
+######
+sub virtual_host {
+ my $vh = http('x_forwarded_host') || http('host') || server_name();
+ $vh =~ s/:\d+$//; # get rid of port number
+ return $vh;
+}
+
+#### Method: remote_host
+# Return the name of the remote host, or its IP
+# address if unavailable. If this variable isn't
+# defined, it returns "localhost" for debugging
+# purposes.
+####
+sub remote_host {
+ return $ENV{'REMOTE_HOST'} || $ENV{'REMOTE_ADDR'}
+ || 'localhost';
+}
+
+#### Method: remote_addr
+# Return the IP addr of the remote host.
+####
+sub remote_addr {
+ return $ENV{'REMOTE_ADDR'} || '127.0.0.1';
+}
+
+#### Method: script_name
+# Return the partial URL to this script for
+# self-referencing scripts. Also see
+# self_url(), which returns a URL with all state information
+# preserved.
+####
+sub script_name {
+ my ($self,@p) = self_or_default(@_);
+ if (@p) {
+ $self->{'.script_name'} = shift @p;
+ } elsif (!exists $self->{'.script_name'}) {
+ my ($script_name,$path_info) = $self->_name_and_path_from_env();
+ $self->{'.script_name'} = $script_name;
+ }
+ return $self->{'.script_name'};
+}
+
+#### Method: referer
+# Return the HTTP_REFERER: useful for generating
+# a GO BACK button.
+####
+sub referer {
+ my($self) = self_or_CGI(@_);
+ return $self->http('referer');
+}
+
+#### Method: server_name
+# Return the name of the server
+####
+sub server_name {
+ return $ENV{'SERVER_NAME'} || 'localhost';
+}
+
+#### Method: server_software
+# Return the name of the server software
+####
+sub server_software {
+ return $ENV{'SERVER_SOFTWARE'} || 'cmdline';
+}
+
+#### Method: virtual_port
+# Return the server port, taking virtual hosts into account
+####
+sub virtual_port {
+ my($self) = self_or_default(@_);
+ my $vh = $self->http('x_forwarded_host') || $self->http('host');
+ my $protocol = $self->protocol;
+ if ($vh) {
+ return ($vh =~ /:(\d+)$/)[0] || ($protocol eq 'https' ? 443 : 80);
+ } else {
+ return $self->server_port();
+ }
+}
+
+#### Method: server_port
+# Return the tcp/ip port the server is running on
+####
+sub server_port {
+ return $ENV{'SERVER_PORT'} || 80; # for debugging
+}
+
+#### Method: server_protocol
+# Return the protocol (usually HTTP/1.0)
+####
+sub server_protocol {
+ return $ENV{'SERVER_PROTOCOL'} || 'HTTP/1.0'; # for debugging
+}
+
+#### Method: http
+# Return the value of an HTTP variable, or
+# the list of variables if none provided
+####
+sub http {
+ my ($self,$parameter) = self_or_CGI(@_);
+ if ( defined($parameter) ) {
+ $parameter =~ tr/-a-z/_A-Z/;
+ if ( $parameter =~ /^HTTP(?:_|$)/ ) {
+ return $ENV{$parameter};
+ }
+ return $ENV{"HTTP_$parameter"};
+ }
+ return grep { /^HTTP(?:_|$)/ } keys %ENV;
+}
+
+#### Method: https
+# Return the value of HTTPS, or
+# the value of an HTTPS variable, or
+# the list of variables
+####
+sub https {
+ my ($self,$parameter) = self_or_CGI(@_);
+ if ( defined($parameter) ) {
+ $parameter =~ tr/-a-z/_A-Z/;
+ if ( $parameter =~ /^HTTPS(?:_|$)/ ) {
+ return $ENV{$parameter};
+ }
+ return $ENV{"HTTPS_$parameter"};
+ }
+ return wantarray
+ ? grep { /^HTTPS(?:_|$)/ } keys %ENV
+ : $ENV{'HTTPS'};
+}
+
+#### Method: protocol
+# Return the protocol (http or https currently)
+####
+sub protocol {
+ local($^W)=0;
+ my $self = shift;
+ return 'https' if uc($self->https()) eq 'ON';
+ return 'https' if $self->server_port == 443;
+ my $prot = $self->server_protocol;
+ my($protocol,$version) = split('/',$prot);
+ return "\L$protocol\E";
+}
+
+#### Method: remote_ident
+# Return the identity of the remote user
+# (but only if his host is running identd)
+####
+sub remote_ident {
+ return (defined $ENV{'REMOTE_IDENT'}) ? $ENV{'REMOTE_IDENT'} : undef;
+}
+
+#### Method: auth_type
+# Return the type of use verification/authorization in use, if any.
+####
+sub auth_type {
+ return (defined $ENV{'AUTH_TYPE'}) ? $ENV{'AUTH_TYPE'} : undef;
+}
+
+#### Method: remote_user
+# Return the authorization name used for user
+# verification.
+####
+sub remote_user {
+ return (defined $ENV{'REMOTE_USER'}) ? $ENV{'REMOTE_USER'} : undef;
+}
+
+#### Method: user_name
+# Try to return the remote user's name by hook or by
+# crook
+####
+sub user_name {
+ my ($self) = self_or_CGI(@_);
+ return $self->http('from') || $ENV{'REMOTE_IDENT'} || $ENV{'REMOTE_USER'};
+}
+
+#### Method: nosticky
+# Set or return the NOSTICKY global flag
+####
+sub nosticky {
+ my ($self,$param) = self_or_CGI(@_);
+ $CGI::NOSTICKY = $param if defined($param);
+ return $CGI::NOSTICKY;
+}
+
+#### Method: nph
+# Set or return the NPH global flag
+####
+sub nph {
+ my ($self,$param) = self_or_CGI(@_);
+ $CGI::NPH = $param if defined($param);
+ return $CGI::NPH;
+}
+
+#### Method: private_tempfiles
+# Set or return the private_tempfiles global flag
+####
+sub private_tempfiles {
+ warn "private_tempfiles has been deprecated";
+ return 0;
+}
+#### Method: close_upload_files
+# Set or return the close_upload_files global flag
+####
+sub close_upload_files {
+ my ($self,$param) = self_or_CGI(@_);
+ $CGI::CLOSE_UPLOAD_FILES = $param if defined($param);
+ return $CGI::CLOSE_UPLOAD_FILES;
+}
+
+#### Method: default_dtd
+# Set or return the default_dtd global
+####
+sub default_dtd {
+ my ($self,$param,$param2) = self_or_CGI(@_);
+ if (defined $param2 && defined $param) {
+ $CGI::DEFAULT_DTD = [ $param, $param2 ];
+ } elsif (defined $param) {
+ $CGI::DEFAULT_DTD = $param;
+ }
+ return $CGI::DEFAULT_DTD;
+}
+
+# -------------- really private subroutines -----------------
+sub _maybe_escapeHTML {
+ # hack to work around earlier hacks
+ push @_,$_[0] if @_==1 && $_[0] eq 'CGI';
+ my ($self,$toencode,$newlinestoo) = CGI::self_or_default(@_);
+ return undef unless defined($toencode);
+ return $toencode if ref($self) && !$self->{'escape'};
+ return $self->escapeHTML($toencode, $newlinestoo);
+}
+
+sub previous_or_default {
+ my($self,$name,$defaults,$override) = @_;
+ my(%selected);
+
+ if (!$override && ($self->{'.fieldnames'}->{$name} ||
+ defined($self->param($name)) ) ) {
+ $selected{$_}++ for $self->param($name);
+ } elsif (defined($defaults) && ref($defaults) &&
+ (ref($defaults) eq 'ARRAY')) {
+ $selected{$_}++ for @{$defaults};
+ } else {
+ $selected{$defaults}++ if defined($defaults);
+ }
+
+ return %selected;
+}
+
+sub register_parameter {
+ my($self,$param) = @_;
+ $self->{'.parametersToAdd'}->{$param}++;
+}
+
+sub get_fields {
+ my($self) = @_;
+ return $self->CGI::hidden('-name'=>'.cgifields',
+ '-values'=>[keys %{$self->{'.parametersToAdd'}}],
+ '-override'=>1);
+}
+
+sub read_from_cmdline {
+ my($input,@words);
+ my($query_string);
+ my($subpath);
+ if ($DEBUG && @ARGV) {
+ @words = @ARGV;
+ } elsif ($DEBUG > 1) {
+ require Text::ParseWords;
+ print STDERR "(offline mode: enter name=value pairs on standard input; press ^D or ^Z when done)\n";
+ chomp(@lines = <STDIN>); # remove newlines
+ $input = join(" ",@lines);
+ @words = &Text::ParseWords::old_shellwords($input);
+ }
+ for (@words) {
+ s/\\=/%3D/g;
+ s/\\&/%26/g;
+ }
+
+ if ("@words"=~/=/) {
+ $query_string = join('&',@words);
+ } else {
+ $query_string = join('+',@words);
+ }
+ if ($query_string =~ /^(.*?)\?(.*)$/)
+ {
+ $query_string = $2;
+ $subpath = $1;
+ }
+ return { 'query_string' => $query_string, 'subpath' => $subpath };
+}
+
+#####
+# subroutine: read_multipart
+#
+# Read multipart data and store it into our parameters.
+# An interesting feature is that if any of the parts is a file, we
+# create a temporary file and open up a filehandle on it so that the
+# caller can read from it if necessary.
+#####
+sub read_multipart {
+ my($self,$boundary,$length) = @_;
+ my($buffer) = $self->new_MultipartBuffer($boundary,$length);
+ return unless $buffer;
+ my(%header,$body);
+ my $filenumber = 0;
+ while (!$buffer->eof) {
+ %header = $buffer->readHeader;
+
+ unless (%header) {
+ $self->cgi_error("400 Bad request (malformed multipart POST)");
+ return;
+ }
+
+ $header{'Content-Disposition'} ||= ''; # quench uninit variable warning
+
+ my($param)= $header{'Content-Disposition'}=~/[\s;]name="([^"]*)"/;
+ $param .= $TAINTED;
+
+ # See RFC 1867, 2183, 2045
+ # NB: File content will be loaded into memory should
+ # content-disposition parsing fail.
+ my ($filename) = $header{'Content-Disposition'}
+ =~/ filename=(("[^"]*")|([a-z\d!\#'\*\+,\.^_\`\{\}\|\~]*))/i;
+
+ $filename ||= ''; # quench uninit variable warning
+
+ $filename =~ s/^"([^"]*)"$/$1/;
+ # Test for Opera's multiple upload feature
+ my($multipart) = ( defined( $header{'Content-Type'} ) &&
+ $header{'Content-Type'} =~ /multipart\/mixed/ ) ?
+ 1 : 0;
+
+ # add this parameter to our list
+ $self->add_parameter($param);
+
+ # If no filename specified, then just read the data and assign it
+ # to our parameter list.
+ if ( ( !defined($filename) || $filename eq '' ) && !$multipart ) {
+ my($value) = $buffer->readBody;
+ $value .= $TAINTED;
+ push(@{$self->{param}{$param}},$value);
+ next;
+ }
+
+ UPLOADS: {
+ # If we get here, then we are dealing with a potentially large
+ # uploaded form. Save the data to a temporary file, then open
+ # the file for reading.
+
+ # skip the file if uploads disabled
+ if ($DISABLE_UPLOADS) {
+ while (defined($data = $buffer->read)) { }
+ last UPLOADS;
+ }
+
+ # set the filename to some recognizable value
+ if ( ( !defined($filename) || $filename eq '' ) && $multipart ) {
+ $filename = "multipart/mixed";
+ }
+
+ my $tmp_dir = $CGI::OS eq 'WINDOWS'
+ ? ( $ENV{TEMP} || $ENV{TMP} || ( $ENV{WINDIR} ? ( $ENV{WINDIR} . $SL . 'TEMP' ) : undef ) )
+ : undef; # File::Temp defaults to TMPDIR
+
+ require CGI::File::Temp;
+ my $filehandle = CGI::File::Temp->new(
+ UNLINK => $UNLINK_TMP_FILES,
+ DIR => $tmp_dir,
+ );
+ $filehandle->_mp_filename( $filename );
+
+ $CGI::DefaultClass->binmode($filehandle) if $CGI::needs_binmode
+ && defined fileno($filehandle);
+
+ # if this is an multipart/mixed attachment, save the header
+ # together with the body for later parsing with an external
+ # MIME parser module
+ if ( $multipart ) {
+ for ( keys %header ) {
+ print $filehandle "$_: $header{$_}${CRLF}";
+ }
+ print $filehandle "${CRLF}";
+ }
+
+ my ($data);
+ local($\) = '';
+ my $totalbytes = 0;
+ while (defined($data = $buffer->read)) {
+ if (defined $self->{'.upload_hook'})
+ {
+ $totalbytes += length($data);
+ &{$self->{'.upload_hook'}}($filename ,$data, $totalbytes, $self->{'.upload_data'});
+ }
+ print $filehandle $data if ($self->{'use_tempfile'});
+ }
+
+ # back up to beginning of file
+ seek($filehandle,0,0);
+
+ ## Close the filehandle if requested this allows a multipart MIME
+ ## upload to contain many files, and we won't die due to too many
+ ## open file handles. The user can access the files using the hash
+ ## below.
+ close $filehandle if $CLOSE_UPLOAD_FILES;
+ $CGI::DefaultClass->binmode($filehandle) if $CGI::needs_binmode;
+
+ # Save some information about the uploaded file where we can get
+ # at it later.
+ # Use the typeglob + filename as the key, as this is guaranteed to be
+ # unique for each filehandle. Don't use the file descriptor as
+ # this will be re-used for each filehandle if the
+ # close_upload_files feature is used.
+ $self->{'.tmpfiles'}->{$$filehandle . $filehandle} = {
+ hndl => $filehandle,
+ name => $filehandle->filename,
+ info => {%header},
+ };
+ push(@{$self->{param}{$param}},$filehandle);
+ }
+ }
+}
+
+#####
+# subroutine: read_multipart_related
+#
+# Read multipart/related data and store it into our parameters. The
+# first parameter sets the start of the data. The part identified by
+# this Content-ID will not be stored as a file upload, but will be
+# returned by this method. All other parts will be available as file
+# uploads accessible by their Content-ID
+#####
+sub read_multipart_related {
+ my($self,$start,$boundary,$length) = @_;
+ my($buffer) = $self->new_MultipartBuffer($boundary,$length);
+ return unless $buffer;
+ my(%header,$body);
+ my $filenumber = 0;
+ my $returnvalue;
+ while (!$buffer->eof) {
+ %header = $buffer->readHeader;
+
+ unless (%header) {
+ $self->cgi_error("400 Bad request (malformed multipart POST)");
+ return;
+ }
+
+ my($param) = $header{'Content-ID'}=~/\<([^\>]*)\>/;
+ $param .= $TAINTED;
+
+ # If this is the start part, then just read the data and assign it
+ # to our return variable.
+ if ( $param eq $start ) {
+ $returnvalue = $buffer->readBody;
+ $returnvalue .= $TAINTED;
+ next;
+ }
+
+ # add this parameter to our list
+ $self->add_parameter($param);
+
+ UPLOADS: {
+ # If we get here, then we are dealing with a potentially large
+ # uploaded form. Save the data to a temporary file, then open
+ # the file for reading.
+
+ # skip the file if uploads disabled
+ if ($DISABLE_UPLOADS) {
+ while (defined($data = $buffer->read)) { }
+ last UPLOADS;
+ }
+
+ my $tmp_dir = $CGI::OS eq 'WINDOWS'
+ ? ( $ENV{TEMP} || $ENV{TMP} || ( $ENV{WINDIR} ? ( $ENV{WINDIR} . $SL . 'TEMP' ) : undef ) )
+ : undef; # File::Temp defaults to TMPDIR
+
+ require CGI::File::Temp;
+ my $filehandle = CGI::File::Temp->new(
+ UNLINK => $UNLINK_TMP_FILES,
+ DIR => $tmp_dir,
+ );
+ $filehandle->_mp_filename( $filehandle->filename );
+
+ $CGI::DefaultClass->binmode($filehandle) if $CGI::needs_binmode
+ && defined fileno($filehandle);
+
+ my ($data);
+ local($\) = '';
+ my $totalbytes;
+ while (defined($data = $buffer->read)) {
+ if (defined $self->{'.upload_hook'})
+ {
+ $totalbytes += length($data);
+ &{$self->{'.upload_hook'}}($param ,$data, $totalbytes, $self->{'.upload_data'});
+ }
+ print $filehandle $data if ($self->{'use_tempfile'});
+ }
+
+ # back up to beginning of file
+ seek($filehandle,0,0);
+
+ ## Close the filehandle if requested this allows a multipart MIME
+ ## upload to contain many files, and we won't die due to too many
+ ## open file handles. The user can access the files using the hash
+ ## below.
+ close $filehandle if $CLOSE_UPLOAD_FILES;
+ $CGI::DefaultClass->binmode($filehandle) if $CGI::needs_binmode;
+
+ # Save some information about the uploaded file where we can get
+ # at it later.
+ # Use the typeglob + filename as the key, as this is guaranteed to be
+ # unique for each filehandle. Don't use the file descriptor as
+ # this will be re-used for each filehandle if the
+ # close_upload_files feature is used.
+ $self->{'.tmpfiles'}->{$$filehandle . $filehandle} = {
+ hndl => $filehandle,
+ name => $filehandle->filename,
+ info => {%header},
+ };
+ push(@{$self->{param}{$param}},$filehandle);
+ }
+ }
+ return $returnvalue;
+}
+
+sub upload {
+ my($self,$param_name) = self_or_default(@_);
+ my @param = grep {ref($_) && defined(fileno($_))} $self->param($param_name);
+ return unless @param;
+ return wantarray ? @param : $param[0];
+}
+
+sub tmpFileName {
+ my($self,$filename) = self_or_default(@_);
+
+ # preferred calling convention: $filename came directly from param or upload
+ if (ref $filename) {
+ return $self->{'.tmpfiles'}->{$$filename . $filename}->{name} || '';
+ }
+
+ # backwards compatible with older versions: $filename is merely equal to
+ # one of our filenames when compared as strings
+ foreach my $param_name ($self->param) {
+ foreach my $filehandle ($self->multi_param($param_name)) {
+ if ($filehandle eq $filename) {
+ return $self->{'.tmpfiles'}->{$$filehandle . $filehandle}->{name} || '';
+ }
+ }
+ }
+
+ return '';
+}
+
+sub uploadInfo {
+ my($self,$filename) = self_or_default(@_);
+ return if ! defined $$filename;
+ return $self->{'.tmpfiles'}->{$$filename . $filename}->{info};
+}
+
+# internal routine, don't use
+sub _set_values_and_labels {
+ my $self = shift;
+ my ($v,$l,$n) = @_;
+ $$l = $v if ref($v) eq 'HASH' && !ref($$l);
+ return $self->param($n) if !defined($v);
+ return $v if !ref($v);
+ return ref($v) eq 'HASH' ? keys %$v : @$v;
+}
+
+# internal routine, don't use
+sub _set_attributes {
+ my $self = shift;
+ my($element, $attributes) = @_;
+ return '' unless defined($attributes->{$element});
+ $attribs = ' ';
+ for my $attrib (keys %{$attributes->{$element}}) {
+ (my $clean_attrib = $attrib) =~ s/^-//;
+ $attribs .= "@{[lc($clean_attrib)]}=\"$attributes->{$element}{$attrib}\" ";
+ }
+ $attribs =~ s/ $//;
+ return $attribs;
+}
+
+#########################################################
+# Globals and stubs for other packages that we use.
+#########################################################
+
+######################## MultipartBuffer ####################
+
+package MultipartBuffer;
+
+$_DEBUG = 0;
+
+# how many bytes to read at a time. We use
+# a 4K buffer by default.
+$INITIAL_FILLUNIT = 1024 * 4;
+$TIMEOUT = 240*60; # 4 hour timeout for big files
+$SPIN_LOOP_MAX = 2000; # bug fix for some Netscape servers
+$CRLF=$CGI::CRLF;
+
+sub new {
+ my($package,$interface,$boundary,$length) = @_;
+ $FILLUNIT = $INITIAL_FILLUNIT;
+ $CGI::DefaultClass->binmode($IN); # if $CGI::needs_binmode; # just do it always
+
+ # If the user types garbage into the file upload field,
+ # then Netscape passes NOTHING to the server (not good).
+ # We may hang on this read in that case. So we implement
+ # a read timeout. If nothing is ready to read
+ # by then, we return.
+
+ # Netscape seems to be a little bit unreliable
+ # about providing boundary strings.
+ my $boundary_read = 0;
+ if ($boundary) {
+
+ # Under the MIME spec, the boundary consists of the
+ # characters "--" PLUS the Boundary string
+
+ # BUG: IE 3.01 on the Macintosh uses just the boundary -- not
+ # the two extra hyphens. We do a special case here on the user-agent!!!!
+ $boundary = "--$boundary" unless CGI::user_agent('MSIE\s+3\.0[12];\s*Mac|DreamPassport');
+
+ } else { # otherwise we find it ourselves
+ my($old);
+ ($old,$/) = ($/,$CRLF); # read a CRLF-delimited line
+ $boundary = <STDIN>; # BUG: This won't work correctly under mod_perl
+ $length -= length($boundary);
+ chomp($boundary); # remove the CRLF
+ $/ = $old; # restore old line separator
+ $boundary_read++;
+ }
+
+ my $self = {LENGTH=>$length,
+ CHUNKED=>!$length,
+ BOUNDARY=>$boundary,
+ INTERFACE=>$interface,
+ BUFFER=>'',
+ };
+
+ $FILLUNIT = length($boundary)
+ if length($boundary) > $FILLUNIT;
+
+ my $retval = bless $self,ref $package || $package;
+
+ # Read the preamble and the topmost (boundary) line plus the CRLF.
+ unless ($boundary_read) {
+ while ($self->read(0)) { }
+ }
+ die "Malformed multipart POST: data truncated\n" if $self->eof;
+
+ return $retval;
+}
+
+sub readHeader {
+ my($self) = @_;
+ my($end);
+ my($ok) = 0;
+ my($bad) = 0;
+
+ local($CRLF) = "\015\012" if $CGI::OS eq 'VMS' || $CGI::EBCDIC;
+
+ do {
+ $self->fillBuffer($FILLUNIT);
+ $ok++ if ($end = index($self->{BUFFER},"${CRLF}${CRLF}")) >= 0;
+ $ok++ if $self->{BUFFER} eq '';
+ $bad++ if !$ok && $self->{LENGTH} <= 0;
+ # this was a bad idea
+ # $FILLUNIT *= 2 if length($self->{BUFFER}) >= $FILLUNIT;
+ } until $ok || $bad;
+ return () if $bad;
+
+ #EBCDIC NOTE: translate header into EBCDIC, but watch out for continuation lines!
+
+ my($header) = substr($self->{BUFFER},0,$end+2);
+ substr($self->{BUFFER},0,$end+4) = '';
+ my %return;
+
+ if ($CGI::EBCDIC) {
+ warn "untranslated header=$header\n" if $_DEBUG;
+ $header = CGI::Util::ascii2ebcdic($header);
+ warn "translated header=$header\n" if $_DEBUG;
+ }
+
+ # See RFC 2045 Appendix A and RFC 822 sections 3.4.8
+ # (Folding Long Header Fields), 3.4.3 (Comments)
+ # and 3.4.5 (Quoted-Strings).
+
+ my $token = '[-\w!\#$%&\'*+.^_\`|{}~]';
+ $header=~s/$CRLF\s+/ /og; # merge continuation lines
+
+ while ($header=~/($token+):\s+([^$CRLF]*)/mgox) {
+ my ($field_name,$field_value) = ($1,$2);
+ $field_name =~ s/\b(\w)/uc($1)/eg; #canonicalize
+ $return{$field_name}=$field_value;
+ }
+ return %return;
+}
+
+# This reads and returns the body as a single scalar value.
+sub readBody {
+ my($self) = @_;
+ my($data);
+ my($returnval)='';
+
+ #EBCDIC NOTE: want to translate returnval into EBCDIC HERE
+
+ while (defined($data = $self->read)) {
+ $returnval .= $data;
+ }
+
+ if ($CGI::EBCDIC) {
+ warn "untranslated body=$returnval\n" if $_DEBUG;
+ $returnval = CGI::Util::ascii2ebcdic($returnval);
+ warn "translated body=$returnval\n" if $_DEBUG;
+ }
+ return $returnval;
+}
+
+# This will read $bytes or until the boundary is hit, whichever happens
+# first. After the boundary is hit, we return undef. The next read will
+# skip over the boundary and begin reading again;
+sub read {
+ my($self,$bytes) = @_;
+
+ # default number of bytes to read
+ $bytes = $bytes || $FILLUNIT;
+
+ # Fill up our internal buffer in such a way that the boundary
+ # is never split between reads.
+ $self->fillBuffer($bytes);
+
+ my $boundary_start = $CGI::EBCDIC ? CGI::Util::ebcdic2ascii($self->{BOUNDARY}) : $self->{BOUNDARY};
+ my $boundary_end = $CGI::EBCDIC ? CGI::Util::ebcdic2ascii($self->{BOUNDARY}.'--') : $self->{BOUNDARY}.'--';
+
+ # Find the boundary in the buffer (it may not be there).
+ my $start = index($self->{BUFFER},$boundary_start);
+
+ warn "boundary=$self->{BOUNDARY} length=$self->{LENGTH} start=$start\n" if $_DEBUG;
+
+ # protect against malformed multipart POST operations
+ die "Malformed multipart POST\n" unless $self->{CHUNKED} || ($start >= 0 || $self->{LENGTH} > 0);
+
+ #EBCDIC NOTE: want to translate boundary search into ASCII here.
+
+ # If the boundary begins the data, then skip past it
+ # and return undef.
+ if ($start == 0) {
+
+ # clear us out completely if we've hit the last boundary.
+ if (index($self->{BUFFER},$boundary_end)==0) {
+ $self->{BUFFER}='';
+ $self->{LENGTH}=0;
+ return undef;
+ }
+
+ # just remove the boundary.
+ substr($self->{BUFFER},0,length($boundary_start))='';
+ $self->{BUFFER} =~ s/^\012\015?//;
+ return undef;
+ }
+
+ my $bytesToReturn;
+ if ($start > 0) { # read up to the boundary
+ $bytesToReturn = $start-2 > $bytes ? $bytes : $start;
+ } else { # read the requested number of bytes
+ # leave enough bytes in the buffer to allow us to read
+ # the boundary. Thanks to Kevin Hendrick for finding
+ # this one.
+ $bytesToReturn = $bytes - (length($boundary_start)+1);
+ }
+
+ my $returnval=substr($self->{BUFFER},0,$bytesToReturn);
+ substr($self->{BUFFER},0,$bytesToReturn)='';
+
+ # If we hit the boundary, remove the CRLF from the end.
+ return ($bytesToReturn==$start)
+ ? substr($returnval,0,-2) : $returnval;
+}
+
+# This fills up our internal buffer in such a way that the
+# boundary is never split between reads
+sub fillBuffer {
+ my($self,$bytes) = @_;
+ return unless $self->{CHUNKED} || $self->{LENGTH};
+
+ my($boundaryLength) = length($self->{BOUNDARY});
+ my($bufferLength) = length($self->{BUFFER});
+ my($bytesToRead) = $bytes - $bufferLength + $boundaryLength + 2;
+ $bytesToRead = $self->{LENGTH} if !$self->{CHUNKED} && $self->{LENGTH} < $bytesToRead;
+
+ # Try to read some data. We may hang here if the browser is screwed up.
+ my $bytesRead = $self->{INTERFACE}->read_from_client(\$self->{BUFFER},
+ $bytesToRead,
+ $bufferLength);
+ warn "bytesToRead=$bytesToRead, bufferLength=$bufferLength, buffer=$self->{BUFFER}\n" if $_DEBUG;
+ $self->{BUFFER} = '' unless defined $self->{BUFFER};
+
+ # An apparent bug in the Apache server causes the read()
+ # to return zero bytes repeatedly without blocking if the
+ # remote user aborts during a file transfer. I don't know how
+ # they manage this, but the workaround is to abort if we get
+ # more than SPIN_LOOP_MAX consecutive zero reads.
+ if ($bytesRead <= 0) {
+ die "CGI.pm: Server closed socket during multipart read (client aborted?).\n"
+ if ($self->{ZERO_LOOP_COUNTER}++ >= $SPIN_LOOP_MAX);
+ } else {
+ $self->{ZERO_LOOP_COUNTER}=0;
+ }
+
+ $self->{LENGTH} -= $bytesRead if !$self->{CHUNKED} && $bytesRead;
+}
+
+# Return true when we've finished reading
+sub eof {
+ my($self) = @_;
+ return 1 if (length($self->{BUFFER}) == 0)
+ && ($self->{LENGTH} <= 0);
+ undef;
+}
+
+1;
+
+package CGI;
+
+# We get a whole bunch of warnings about "possibly uninitialized variables"
+# when running with the -w switch. Touch them all once to get rid of the
+# warnings. This is ugly and I hate it.
+if ($^W) {
+ $CGI::CGI = '';
+ $CGI::CGI=<<EOF;
+ $CGI::VERSION;
+ $MultipartBuffer::SPIN_LOOP_MAX;
+ $MultipartBuffer::CRLF;
+ $MultipartBuffer::TIMEOUT;
+ $MultipartBuffer::INITIAL_FILLUNIT;
+EOF
+ ;
+}
+
+1;
diff --git a/lib/CGI.pod b/lib/CGI.pod
new file mode 100644
index 0000000..1528ac6
--- /dev/null
+++ b/lib/CGI.pod
@@ -0,0 +1,1843 @@
+=head1 NAME
+
+CGI - Handle Common Gateway Interface requests and responses
+
+=for html
+<a href='https://travis-ci.org/leejo/CGI.pm?branch=master'><img src='https://travis-ci.org/leejo/CGI.pm.svg?branch=master' alt='Build Status' /></a>
+<a href='https://coveralls.io/r/leejo/CGI.pm'><img src='https://coveralls.io/repos/leejo/CGI.pm/badge.png?branch=master' alt='Coverage Status' /></a>
+
+=head1 SYNOPSIS
+
+ use strict;
+ use warnings;
+
+ use CGI;
+
+ my $q = CGI->new;
+
+ # Process an HTTP request
+ my @values = $q->multi_param('form_field');
+ my $value = $q->param('param_name');
+
+ my $fh = $q->upload('file_field');
+
+ my $riddle = $query->cookie('riddle_name');
+ my %answers = $query->cookie('answers');
+
+ # Prepare various HTTP responses
+ print $q->header();
+ print $q->header('application/json');
+
+ my $cookie1 = $q->cookie(
+ -name => 'riddle_name',
+ -value => "The Sphynx's Question"
+ );
+
+ my $cookie2 = $q->cookie(
+ -name => 'answers',
+ -value => \%answers
+ );
+
+ print $q->header(
+ -type => 'image/gif',
+ -expires => '+3d',
+ -cookie => [ $cookie1,$cookie2 ]
+ );
+
+ print $q->redirect('http://somewhere.else/in/movie/land');
+
+=head1 DESCRIPTION
+
+CGI.pm is a stable, complete and mature solution for processing and preparing
+HTTP requests and responses. Major features including processing form
+submissions, file uploads, reading and writing cookies, query string generation
+and manipulation, and processing and preparing HTTP headers.
+
+CGI.pm performs very well in a vanilla CGI.pm environment and also comes
+with built-in support for mod_perl and mod_perl2 as well as FastCGI.
+
+It has the benefit of having developed and refined over 20 years with input
+from dozens of contributors and being deployed on thousands of websites.
+CGI.pm was included in the perl distribution from perl v5.4 to v5.20, however
+is has now been removed from the perl core...
+
+=head1 CGI.pm HAS BEEN REMOVED FROM THE PERL CORE
+
+L<http://perl5.git.perl.org/perl.git/commitdiff/e9fa5a80>
+
+If you upgrade to a new version of perl or if you rely on a
+system or vendor perl and get an updated version of perl through a system
+update, then you will have to install CGI.pm yourself with cpan/cpanm/a vendor
+package/manually. To make this a little easier the L<CGI::Fast> module has been
+split into its own distribution, meaning you do not need access to a compiler
+to install CGI.pm
+
+The rationale for this decision is that CGI.pm is no longer considered good
+practice for developing web applications, B<including> quick prototyping and
+small web scripts. There are far better, cleaner, quicker, easier, safer,
+more scalable, more extensible, more modern alternatives available at this point
+in time. These will be documented with L<CGI::Alternatives>.
+
+For more discussion on the removal of CGI.pm from core please see:
+
+L<http://www.nntp.perl.org/group/perl.perl5.porters/2013/05/msg202130.html>
+
+Note that the v4 releases of CGI.pm will retain back compatibility B<as much>
+B<as possible>, however you may need to make some minor changes to your code
+if you are using deprecated methods or some of the more obscure features of the
+module. If you plan to upgrade to v4.00 and beyond you should read the Changes
+file for more information and B<test your code> against CGI.pm before deploying
+it.
+
+=head1 HTML Generation functions should no longer be used
+
+B<All> HTML generation functions within CGI.pm are no longer being
+maintained. Any issues, bugs, or patches will be rejected unless
+they relate to fundamentally broken page rendering.
+
+The rationale for this is that the HTML generation functions of CGI.pm
+are an obfuscation at best and a maintenance nightmare at worst. You
+should be using a template engine for better separation of concerns.
+See L<CGI::Alternatives> for an example of using CGI.pm with the
+L<Template::Toolkit> module.
+
+These functions, and perldoc for them, will continue to exist in the
+v4 releases of CGI.pm but may be deprecated (soft) in v5 and beyond.
+All documentation for these functions has been moved to L<CGI::HTML::Functions>.
+
+=head1 Programming style
+
+There are two styles of programming with CGI.pm, an object-oriented (OO)
+style and a function-oriented style. You are recommended to use the OO
+style as CGI.pm will create an internal default object when the functions
+are called procedurally and you will not have to worry about method names
+clashing with perl builtins.
+
+In the object-oriented style you create one or more CGI objects and then
+use object methods to create the various elements of the page. Each CGI
+object starts out with the list of named parameters that were passed to
+your CGI script by the server. You can modify the objects, save them to a
+file or database and recreate them. Because each object corresponds to the
+"state" of the CGI script, and because each object's parameter list is
+independent of the others, this allows you to save the state of the
+script and restore it later.
+
+For example, using the object oriented style:
+
+ #!/usr/bin/env perl
+
+ use strict;
+ use warnings;
+
+ use CGI; # load CGI routines
+
+ my $q = CGI->new; # create new CGI object
+ print $q->header; # create the HTTP header
+
+ ...
+
+In the function-oriented style, there is one default CGI object that
+you rarely deal with directly. Instead you just call functions to
+retrieve CGI parameters, manage cookies, and so on. The following example
+is identical to above, in terms of output, but uses the function-oriented
+interface. The main differences are that we now need to import a set of
+functions into our name space (usually the "standard" functions), and we don't
+need to create the CGI object.
+
+ #!/usr/bin/env perl
+
+ use strict;
+ use warnings;
+
+ use CGI qw/:standard/; # load standard CGI routines
+ print header(); # create the HTTP header
+
+ ...
+
+The examples in this document mainly use the object-oriented style. See HOW
+TO IMPORT FUNCTIONS for important information on function-oriented programming
+in CGI.pm
+
+=head2 Calling CGI.pm routines
+
+Most CGI.pm routines accept several arguments, sometimes as many as 20
+optional ones! To simplify this interface, all routines use a named
+argument calling style that looks like this:
+
+ print $q->header(
+ -type => 'image/gif',
+ -expires => '+3d',
+ );
+
+Each argument name is preceded by a dash. Neither case nor order matters in
+the argument list: -type, -Type, and -TYPE are all acceptable. In fact, only
+the first argument needs to begin with a dash. If a dash is present in the
+first argument CGI.pm assumes dashes for the subsequent ones.
+
+Several routines are commonly called with just one argument. In the case
+of these routines you can provide the single argument without an argument
+name. header() happens to be one of these routines. In this case, the single
+argument is the document type.
+
+ print $q->header('text/html');
+
+Other such routines are documented below.
+
+Sometimes named arguments expect a scalar, sometimes a reference to an array,
+and sometimes a reference to a hash. Often, you can pass any type of argument
+and the routine will do whatever is most appropriate. For example, the param()
+routine is used to set a CGI parameter to a single or a multi-valued value.
+The two cases are shown below:
+
+ $q->param(
+ -name => 'veggie',
+ -value => 'tomato',
+ );
+
+ $q->param(
+ -name => 'veggie',
+ -value => [ qw/tomato tomahto potato potahto/ ],
+ );
+
+
+Many routines will do something useful with a named argument that it doesn't
+recognize. For example, you can produce non-standard HTTP header fields by
+providing them as named arguments:
+
+ print $q->header(
+ -type => 'text/html',
+ -cost => 'Three smackers',
+ -annoyance_level => 'high',
+ -complaints_to => 'bit bucket',
+ );
+
+This will produce the following nonstandard HTTP header:
+
+ HTTP/1.0 200 OK
+ Cost: Three smackers
+ Annoyance-level: high
+ Complaints-to: bit bucket
+ Content-type: text/html
+
+Notice the way that underscores are translated automatically into hyphens.
+
+=head2 Creating a new query object (object-oriented style)
+
+ my $query = CGI->new;
+
+This will parse the input (from POST, GET and DELETE methods) and store
+it into a perl5 object called $query. Note that because the input parsing
+happens at object instantiation you have to set any CGI package variables
+that control parsing B<before> you call CGI->new.
+
+Any filehandles from file uploads will have their position reset to the
+beginning of the file.
+
+=head2 Creating a new query object from an input file
+
+ my $query = CGI->new( $input_filehandle );
+
+If you provide a file handle to the new() method, it will read parameters
+from the file (or STDIN, or whatever). The file can be in any of the forms
+describing below under debugging (i.e. a series of newline delimited
+TAG=VALUE pairs will work). Conveniently, this type of file is created by
+the save() method (see below). Multiple records can be saved and restored.
+
+Perl purists will be pleased to know that this syntax accepts references to
+file handles, or even references to filehandle globs, which is the "official"
+way to pass a filehandle. You can also initialize the CGI object with a
+FileHandle or IO::File object.
+
+If you are using the function-oriented interface and want to initialize CGI
+state from a file handle, the way to do this is with B<restore_parameters()>.
+This will (re)initialize the default CGI object from the indicated file handle.
+
+ open( my $in_fh,'<',"test.in") || die "Couldn't open test.in for read: $!";
+ restore_parameters( $in_fh );
+ close( $in_fh );
+
+You can also initialize the query object from a hash reference:
+
+ my $query = CGI->new( {
+ 'dinosaur' => 'barney',
+ 'song' => 'I love you',
+ 'friends' => [ qw/ Jessica George Nancy / ]
+ } );
+
+or from a properly formatted, URL-escaped query string:
+
+ my $query = CGI->new('dinosaur=barney&color=purple');
+
+or from a previously existing CGI object (currently this clones the parameter
+list, but none of the other object-specific fields, such as autoescaping):
+
+ my $old_query = CGI->new;
+ my $new_query = CGI->new($old_query);
+
+To create an empty query, initialize it from an empty string or hash:
+
+ my $empty_query = CGI->new("");
+
+ -or-
+
+ my $empty_query = CGI->new({});
+
+=head2 Fetching a list of keywords from the query
+
+ my @keywords = $query->keywords
+
+If the script was invoked as the result of an ISINDEX search, the parsed
+keywords can be obtained as an array using the keywords() method.
+
+=head2 Fetching the names of all the parameters passed to your script
+
+ my @names = $query->multi_param
+
+ my @names = $query->param
+
+If the script was invoked with a parameter list
+(e.g. "name1=value1&name2=value2&name3=value3"), the param() / multi_param()
+methods will return the parameter names as a list. If the script was invoked
+as an ISINDEX script and contains a string without ampersands
+(e.g. "value1+value2+value3"), there will be a single parameter named
+"keywords" containing the "+"-delimited keywords.
+
+The array of parameter names returned will be in the same order as they were
+submitted by the browser. Usually this order is the same as the order in which
+the parameters are defined in the form (however, this isn't part of the spec,
+and so isn't guaranteed).
+
+=head2 Fetching the value or values of a single named parameter
+
+ my @values = $query->multi_param('foo');
+
+ -or-
+
+ my $value = $query->param('foo');
+
+Pass the param() / multi_param() method a single argument to fetch the value
+of the named parameter. If the parameter is multivalued (e.g. from multiple
+selections in a scrolling list), you can ask to receive an array. Otherwise
+the method will return a single value.
+
+B<Warning> - calling param() in list context can lead to vulnerabilities if
+you do not sanitise user input as it is possible to inject other param
+keys and values into your code. This is why the multi_param() method exists,
+to make it clear that a list is being returned, note that param() can still
+be called in list context and will return a list for back compatibility.
+
+The following code is an example of a vulnerability as the call to param will
+be evaluated in list context and thus possibly inject extra keys and values
+into the hash:
+
+ my %user_info = (
+ id => 1,
+ name => $query->param('name'),
+ );
+
+The fix for the above is to force scalar context on the call to ->param by
+prefixing it with "scalar"
+
+ name => scalar $query->param('name'),
+
+If you call param() in list context with an argument a warning will be raised
+by CGI.pm, you can disable this warning by setting $CGI::LIST_CONTEXT_WARN to 0
+or by using the multi_param() method instead
+
+If a value is not given in the query string, as in the queries "name1=&name2=",
+it will be returned as an empty string.
+
+If the parameter does not exist at all, then param() will return undef in scalar
+context, and the empty list in a list context.
+
+=head2 Setting the value(s) of a named parameter
+
+ $query->param('foo','an','array','of','values');
+
+This sets the value for the named parameter 'foo' to an array of values. This
+is one way to change the value of a field AFTER the script has been invoked
+once before.
+
+param() also recognizes a named parameter style of calling described in more
+detail later:
+
+ $query->param(
+ -name => 'foo',
+ -values => ['an','array','of','values'],
+ );
+
+ -or-
+
+ $query->param(
+ -name => 'foo',
+ -value => 'the value',
+ );
+
+=head2 Appending additional values to a named parameter
+
+ $query->append(
+ -name =>'foo',
+ -values =>['yet','more','values'],
+ );
+
+This adds a value or list of values to the named parameter. The values are
+appended to the end of the parameter if it already exists. Otherwise the
+parameter is created. Note that this method only recognizes the named argument
+calling syntax.
+
+=head2 Importing all parameters into a namespace
+
+ $query->import_names('R');
+
+This creates a series of variables in the 'R' namespace. For example, $R::foo,
+@R:foo. For keyword lists, a variable @R::keywords will appear. If no namespace
+is given, this method will assume 'Q'. B<WARNING>: don't import anything into
+'main'; this is a major security risk!
+
+NOTE 1: Variable names are transformed as necessary into legal perl variable
+names. All non-legal characters are transformed into underscores. If you need
+to keep the original names, you should use the param() method instead to access
+CGI variables by name.
+
+In fact, you should probably not use this method at all given the above caveats
+and security risks.
+
+=head2 Deleting a parameter completely
+
+ $query->delete('foo','bar','baz');
+
+This completely clears a list of parameters. It sometimes useful for resetting
+parameters that you don't want passed down between script invocations.
+
+If you are using the function call interface, use "Delete()" instead to avoid
+conflicts with perl's built-in delete operator.
+
+=head2 Deleting all parameters
+
+ $query->delete_all();
+
+This clears the CGI object completely. It might be useful to ensure that all
+the defaults are taken when you create a fill-out form.
+
+Use Delete_all() instead if you are using the function call interface.
+
+=head2 Handling non-urlencoded arguments
+
+If POSTed data is not of type application/x-www-form-urlencoded or
+multipart/form-data, then the POSTed data will not be processed, but instead
+be returned as-is in a parameter named POSTDATA. To retrieve it, use code like
+this:
+
+ my $data = $query->param('POSTDATA');
+
+Likewise if PUTed data can be retrieved with code like this:
+
+ my $data = $query->param('PUTDATA');
+
+(If you don't know what the preceding means, worry not. It only affects people
+trying to use CGI for XML processing and other specialized tasks)
+
+PUTDATA/POSTDATA are also available via
+L<upload_hook|/Progress bars for file uploads and avoiding temp files>,
+and as L<file uploads|/Processing a file upload field> via L</-putdata_upload>
+option.
+
+=head2 Direct access to the parameter list
+
+ $q->param_fetch('address')->[1] = '1313 Mockingbird Lane';
+ unshift @{$q->param_fetch(-name=>'address')},'George Munster';
+
+If you need access to the parameter list in a way that isn't covered by the
+methods given in the previous sections, you can obtain a direct reference to
+it by calling the B<param_fetch()> method with the name of the parameter. This
+will return an array reference to the named parameter, which you then can
+manipulate in any way you like.
+
+You can also use a named argument style using the B<-name> argument.
+
+=head2 Fetching the parameter list as a hash
+
+ my $params = $q->Vars;
+ print $params->{'address'};
+ my @foo = split("\0",$params->{'foo'});
+ my %params = $q->Vars;
+
+ use CGI ':cgi-lib';
+ my $params = Vars();
+
+Many people want to fetch the entire parameter list as a hash in which the keys
+are the names of the CGI parameters, and the values are the parameters' values.
+The Vars() method does this. Called in a scalar context, it returns the
+parameter list as a tied hash reference. Changing a key changes the value of
+the parameter in the underlying CGI parameter list. Called in a list context,
+it returns the parameter list as an ordinary hash. This allows you to read the
+contents of the parameter list, but not to change it.
+
+When using this, the thing you must watch out for are multivalued CGI
+parameters. Because a hash cannot distinguish between scalar and list context,
+multivalued parameters will be returned as a packed string, separated by the
+"\0" (null) character. You must split this packed string in order to get at the
+individual values. This is the convention introduced long ago by Steve Brenner
+in his cgi-lib.pl module for perl version 4, and may be replaced in future
+versions with array references.
+
+If you wish to use Vars() as a function, import the I<:cgi-lib> set of function
+calls (also see the section on CGI-LIB compatibility).
+
+=head2 Saving the state of the script to a file
+
+ $query->save(\*FILEHANDLE)
+
+This will write the current state of the form to the provided filehandle. You
+can read it back in by providing a filehandle to the new() method. Note that
+the filehandle can be a file, a pipe, or whatever.
+
+The format of the saved file is:
+
+ NAME1=VALUE1
+ NAME1=VALUE1'
+ NAME2=VALUE2
+ NAME3=VALUE3
+ =
+
+Both name and value are URL escaped. Multi-valued CGI parameters are represented
+as repeated names. A session record is delimited by a single = symbol. You can
+write out multiple records and read them back in with several calls to B<new>.
+You can do this across several sessions by opening the file in append mode,
+allowing you to create primitive guest books, or to keep a history of users'
+queries. Here's a short example of creating multiple session records:
+
+ use strict;
+ use warnings;
+ use CGI;
+
+ open (my $out_fh,'>>','test.out') || die "Can't open test.out: $!";
+ my $records = 5;
+ for ( 0 .. $records ) {
+ my $q = CGI->new;
+ $q->param( -name => 'counter',-value => $_ );
+ $q->save( $out_fh );
+ }
+ close( $out_fh );
+
+ # reopen for reading
+ open (my $in_fh,'<','test.out') || die "Can't open test.out: $!";
+ while (!eof($in_fh)) {
+ my $q = CGI->new($in_fh);
+ print $q->param('counter'),"\n";
+ }
+
+The file format used for save/restore is identical to that used by the Whitehead
+Genome Center's data exchange format "Boulderio", and can be manipulated and
+even databased using Boulderio utilities. See L<Boulder> for further details.
+
+If you wish to use this method from the function-oriented (non-OO) interface,
+the exported name for this method is B<save_parameters()>.
+
+=head2 Retrieving cgi errors
+
+Errors can occur while processing user input, particularly when processing
+uploaded files. When these errors occur, CGI will stop processing and return
+an empty parameter list. You can test for the existence and nature of errors
+using the I<cgi_error()> function. The error messages are formatted as HTTP
+status codes. You can either incorporate the error text into a page, or use
+it as the value of the HTTP status:
+
+ if ( my $error = $q->cgi_error ) {
+ print $q->header( -status => $error );
+ print "Error: $error";
+ exit 0;
+ }
+
+When using the function-oriented interface (see the next section), errors may
+only occur the first time you call I<param()>. Be ready for this!
+
+=head2 Using the function-oriented interface
+
+To use the function-oriented interface, you must specify which CGI.pm
+routines or sets of routines to import into your script's namespace.
+There is a small overhead associated with this importation, but it
+isn't much.
+
+ use strict;
+ use warnings;
+
+ use CGI qw/ list of methods /;
+
+The listed methods will be imported into the current package; you can
+call them directly without creating a CGI object first. This example
+shows how to import the B<param()> and B<header()>
+methods, and then use them directly:
+
+ use strict;
+ use warnings;
+
+ use CGI qw/ param header /;
+ print header('text/plain');
+ my $zipcode = param('zipcode');
+
+More frequently, you'll import common sets of functions by referring
+to the groups by name. All function sets are preceded with a ":"
+character as in ":cgi" (for CGI protocol handling methods).
+
+Here is a list of the function sets you can import:
+
+=over 4
+
+=item B<:cgi>
+
+Import all CGI-handling methods, such as B<param()>, B<path_info()>
+and the like.
+
+=item B<:all>
+
+Import all the available methods. For the full list, see the CGI.pm
+code, where the variable %EXPORT_TAGS is defined. (N.B. the :cgi-lib
+imports will B<not> be included in the :all import, you will have to
+import :cgi-lib to get those)
+
+=back
+
+Note that in the interests of execution speed CGI.pm does B<not> use
+the standard L<Exporter> syntax for specifying load symbols. This may
+change in the future.
+
+=head2 Pragmas
+
+In addition to the function sets, there are a number of pragmas that you can
+import. Pragmas, which are always preceded by a hyphen, change the way that
+CGI.pm functions in various ways. Pragmas, function sets, and individual
+functions can all be imported in the same use() line. For example, the
+following use statement imports the cgi set of functions and enables
+debugging mode (pragma -debug):
+
+ use strict;
+ use warninigs;
+ use CGI qw/ :cgi -debug /;
+
+The current list of pragmas is as follows:
+
+=over 4
+
+=item -no_undef_params
+
+This keeps CGI.pm from including undef params in the parameter list.
+
+=item -utf8
+
+This makes CGI.pm treat all parameters as text strings rather than binary
+strings (see L<perlunitut> for the distinction), assuming UTF-8 for the
+encoding.
+
+CGI.pm does the decoding from the UTF-8 encoded input data, restricting this
+decoding to input text as distinct from binary upload data which are left
+untouched. Therefore, a ':utf8' layer must B<not> be used on STDIN.
+
+If you do not use this option you can manually select which fields are
+expected to return utf-8 strings and convert them using code like this:
+
+ use strict;
+ use warnings;
+
+ use CGI;
+ use Encode qw/ decode /;
+
+ my $cgi = CGI->new;
+ my $param = $cgi->param('foo');
+ $param = decode( 'UTF-8',$param );
+
+=item -putdata_upload
+
+Makes C<<< $cgi->param('PUTDATA'); >>> and C<<< $cgi->param('POSTDATA'); >>>
+act like file uploads named PUTDATA and POSTDATA. See
+L</Handling non-urlencoded arguments> and L</Processing a file upload field>
+PUTDATA/POSTDATA are also available via
+L<upload_hook|/Progress bars for file uploads and avoiding temp files>.
+
+=item -nph
+
+This makes CGI.pm produce a header appropriate for an NPH (no parsed header)
+script. You may need to do other things as well to tell the server that the
+script is NPH. See the discussion of NPH scripts below.
+
+=item -newstyle_urls
+
+Separate the name=value pairs in CGI parameter query strings with semicolons
+rather than ampersands. For example:
+
+ ?name=fred;age=24;favorite_color=3
+
+Semicolon-delimited query strings are always accepted, and will be emitted by
+self_url() and query_string(). newstyle_urls became the default in version
+2.64.
+
+=item -oldstyle_urls
+
+Separate the name=value pairs in CGI parameter query strings with ampersands
+rather than semicolons. This is no longer the default.
+
+=item -no_debug
+
+This turns off the command-line processing features. If you want to run a CGI.pm
+script from the command line, and you don't want it to read CGI parameters from
+the command line or STDIN, then use this pragma:
+
+ use CGI qw/ -no_debug :standard /;
+
+=item -debug
+
+This turns on full debugging. In addition to reading CGI arguments from the
+command-line processing, CGI.pm will pause and try to read arguments from STDIN,
+producing the message "(offline mode: enter name=value pairs on standard input)"
+features.
+
+See the section on debugging for more details.
+
+=back
+
+=head1 GENERATING DYNAMIC DOCUMENTS
+
+Most of CGI.pm's functions deal with creating documents on the fly. Generally
+you will produce the HTTP header first, followed by the document itself. CGI.pm
+provides functions for generating HTTP headers of various types.
+
+Each of these functions produces a fragment of HTTP which you can print out
+directly so that it is processed by the browser, appended to a string, or saved
+to a file for later use.
+
+=head2 Creating a standard http header
+
+Normally the first thing you will do in any CGI script is print out an HTTP
+header. This tells the browser what type of document to expect, and gives other
+optional information, such as the language, expiration date, and whether to
+cache the document. The header can also be manipulated for special purposes,
+such as server push and pay per view pages.
+
+ use strict;
+ use warnings;
+
+ use CGI;
+
+ my $cgi = CGI->new;
+
+ print $cgi->header;
+
+ -or-
+
+ print $cgi->header('image/gif');
+
+ -or-
+
+ print $cgi->header('text/html','204 No response');
+
+ -or-
+
+ print $cgi->header(
+ -type => 'image/gif',
+ -nph => 1,
+ -status => '402 Payment required',
+ -expires => '+3d',
+ -cookie => $cookie,
+ -charset => 'utf-8',
+ -attachment => 'foo.gif',
+ -Cost => '$2.00'
+ );
+
+header() returns the Content-type: header. You can provide your own MIME type
+if you choose, otherwise it defaults to text/html. An optional second parameter
+specifies the status code and a human-readable message. For example, you can
+specify 204, "No response" to create a script that tells the browser to do
+nothing at all. Note that RFC 2616 expects the human-readable phase to be there
+as well as the numeric status code.
+
+The last example shows the named argument style for passing arguments to the CGI
+methods using named parameters. Recognized parameters are B<-type>, B<-status>,
+B<-expires>, and B<-cookie>. Any other named parameters will be stripped of
+their initial hyphens and turned into header fields, allowing you to specify
+any HTTP header you desire. Internal underscores will be turned into hyphens:
+
+ print $cgi->header( -Content_length => 3002 );
+
+Most browsers will not cache the output from CGI scripts. Every time the browser
+reloads the page, the script is invoked anew. You can change this behavior with
+the B<-expires> parameter. When you specify an absolute or relative expiration
+interval with this parameter, some browsers and proxy servers will cache the
+script's output until the indicated expiration date. The following forms are all
+valid for the -expires field:
+
+ +30s 30 seconds from now
+ +10m ten minutes from now
+ +1h one hour from now
+ -1d yesterday (i.e. "ASAP!")
+ now immediately
+ +3M in three months
+ +10y in ten years time
+ Thursday, 25-Apr-2018 00:40:33 GMT at the indicated time & date
+
+The B<-cookie> parameter generates a header that tells the browser to provide
+a "magic cookie" during all subsequent transactions with your script. Some
+cookies have a special format that includes interesting attributes such as
+expiration time. Use the cookie() method to create and retrieve session cookies.
+
+The B<-nph> parameter, if set to a true value, will issue the correct headers
+to work with a NPH (no-parse-header) script. This is important to use with
+certain servers that expect all their scripts to be NPH.
+
+The B<-charset> parameter can be used to control the character set sent to the
+browser. If not provided, defaults to ISO-8859-1. As a side effect, this sets
+the charset() method as well. B<Note> that the default being ISO-8859-1 may not
+make sense for all content types, e.g.:
+
+ Content-Type: image/gif; charset=ISO-8859-1
+
+In the above case you need to pass -charset => '' to prevent the default being
+used.
+
+The B<-attachment> parameter can be used to turn the page into an attachment.
+Instead of displaying the page, some browsers will prompt the user to save it
+to disk. The value of the argument is the suggested name for the saved file. In
+order for this to work, you may have to set the B<-type> to
+"application/octet-stream".
+
+The B<-p3p> parameter will add a P3P tag to the outgoing header. The parameter
+can be an arrayref or a space-delimited string of P3P tags. For example:
+
+ print $cgi->header( -p3p => [ qw/ CAO DSP LAW CURa / ] );
+ print $cgi->header( -p3p => 'CAO DSP LAW CURa' );
+
+In either case, the outgoing header will be formatted as:
+
+ P3P: policyref="/w3c/p3p.xml" cp="CAO DSP LAW CURa"
+
+CGI.pm will accept valid multi-line headers when each line is separated with a
+CRLF value ("\r\n" on most platforms) followed by at least one space. For
+example:
+
+ print $cgi->header( -ingredients => "ham\r\n\seggs\r\n\sbacon" );
+
+Invalid multi-line header input will trigger in an exception. When multi-line
+headers are received, CGI.pm will always output them back as a single line,
+according to the folding rules of RFC 2616: the newlines will be removed, while
+the white space remains.
+
+=head2 Generating a redirection header
+
+ print $q->redirect( 'http://somewhere.else/in/movie/land' );
+
+Sometimes you don't want to produce a document yourself, but simply redirect
+the browser elsewhere, perhaps choosing a URL based on the time of day or the
+identity of the user.
+
+The redirect() method redirects the browser to a different URL. If you use
+redirection like this, you should B<not> print out a header as well.
+
+You should always use full URLs (including the http: or ftp: part) in
+redirection requests. Relative URLs will not work correctly.
+
+You can also use named arguments:
+
+ print $q->redirect(
+ -uri => 'http://somewhere.else/in/movie/land',
+ -nph => 1,
+ -status => '301 Moved Permanently'
+ );
+
+All names arguments recognized by header() are also recognized by redirect().
+However, most HTTP headers, including those generated by -cookie and -target,
+are ignored by the browser.
+
+The B<-nph> parameter, if set to a true value, will issue the correct headers
+to work with a NPH (no-parse-header) script. This is important to use with
+certain servers, such as Microsoft IIS, which expect all their scripts to be
+NPH.
+
+The B<-status> parameter will set the status of the redirect. HTTP defines
+several different possible redirection status codes, and the default if not
+specified is 302, which means "moved temporarily." You may change the status
+to another status code if you wish.
+
+Note that the human-readable phrase is also expected to be present to conform
+with RFC 2616, section 6.1.
+
+=head2 Creating a self-referencing url that preserves state information
+
+ my $myself = $q->self_url;
+ print qq(<a href="$myself">I'm talking to myself.</a>);
+
+self_url() will return a URL, that, when selected, will re-invoke this script
+with all its state information intact. This is most useful when you want to
+jump around within the document using internal anchors but you don't want to
+disrupt the current contents of the form(s). Something like this will do the
+trick:
+
+ my $myself = $q->self_url;
+ print "<a href=\"$myself#table1\">See table 1</a>";
+ print "<a href=\"$myself#table2\">See table 2</a>";
+ print "<a href=\"$myself#yourself\">See for yourself</a>";
+
+If you want more control over what's returned, using the B<url()> method
+instead.
+
+You can also retrieve a query string representation of the current object
+state with query_string():
+
+ my $the_string = $q->query_string();
+
+The behavior of calling query_string is currently undefined when the HTTP method
+is something other than GET.
+
+If you want to retrieved the query string as set in the webserver, namely the
+environment variable, you can call env_query_string()
+
+=head2 Obtaining the script's url
+
+ my $full_url = url();
+ my $full_url = url( -full =>1 ); # alternative syntax
+ my $relative_url = url( -relative => 1 );
+ my $absolute_url = url( -absolute =>1 );
+ my $url_with_path = url( -path_info => 1 );
+ my $url_path_qry = url( -path_info => 1, -query =>1 );
+ my $netloc = url( -base => 1 );
+
+B<url()> returns the script's URL in a variety of formats. Called without any
+arguments, it returns the full form of the URL, including host name and port
+number
+
+ http://your.host.com/path/to/script.cgi
+
+You can modify this format with the following named arguments:
+
+=over 4
+
+=item B<-absolute>
+
+If true, produce an absolute URL, e.g.
+
+ /path/to/script.cgi
+
+=item B<-relative>
+
+Produce a relative URL. This is useful if you want to re-invoke your
+script with different parameters. For example:
+
+ script.cgi
+
+=item B<-full>
+
+Produce the full URL, exactly as if called without any arguments. This overrides
+the -relative and -absolute arguments.
+
+=item B<-path> (B<-path_info>)
+
+Append the additional path information to the URL. This can be combined with
+B<-full>, B<-absolute> or B<-relative>. B<-path_info> is provided as a synonym.
+
+=item B<-query> (B<-query_string>)
+
+Append the query string to the URL. This can be combined with B<-full>,
+B<-absolute> or B<-relative>. B<-query_string> is provided as a synonym.
+
+=item B<-base>
+
+Generate just the protocol and net location, as in http://www.foo.com:8000
+
+=item B<-rewrite>
+
+If Apache's mod_rewrite is turned on, then the script name and path info
+probably won't match the request that the user sent. Set -rewrite => 1 (default)
+to return URLs that match what the user sent (the original request URI). Set
+-rewrite => 0 to return URLs that match the URL after the mod_rewrite rules have
+run.
+
+=back
+
+=head2 Mixing post and url parameters
+
+ my $color = url_param('color');
+
+It is possible for a script to receive CGI parameters in the URL as well as in
+the fill-out form by creating a form that POSTs to a URL containing a query
+string (a "?" mark followed by arguments). The B<param()> method will always
+return the contents of the POSTed fill-out form, ignoring the URL's query
+string. To retrieve URL parameters, call the B<url_param()> method. Use it in
+the same way as B<param()>. The main difference is that it allows you to read
+the parameters, but not set them.
+
+Under no circumstances will the contents of the URL query string interfere with
+similarly-named CGI parameters in POSTed forms. If you try to mix a URL query
+string with a form submitted with the GET method, the results will not be what
+you expect.
+
+=head2 Processing a file upload field
+
+=head3 Basics
+
+When the form is processed, you can retrieve an L<IO::File> compatible handle
+for a file upload field like this:
+
+ use autodie;
+
+ # undef may be returned if it's not a valid file handle
+ if ( my $io_handle = $q->upload('field_name') ) {
+ open ( my $out_file,'>>','/usr/local/web/users/feedback' );
+ while ( my $bytesread = $io_handle->read($buffer,1024) ) {
+ print $out_file $buffer;
+ }
+ }
+
+In a list context, upload() will return an array of filehandles. This makes it
+possible to process forms that use the same name for multiple upload fields.
+
+If you want the entered file name for the file, you can just call param():
+
+ my $filename = $q->param('field_name');
+
+Different browsers will return slightly different things for the name. Some
+browsers return the filename only. Others return the full path to the file,
+using the path conventions of the user's machine. Regardless, the name returned
+is always the name of the file on the I<user's> machine, and is unrelated to
+the name of the temporary file that CGI.pm creates during upload spooling
+(see below).
+
+When a file is uploaded the browser usually sends along some information along
+with it in the format of headers. The information usually includes the MIME
+content type. To retrieve this information, call uploadInfo(). It returns a
+reference to a hash containing all the document headers.
+
+ my $filehandle = $q->upload( 'uploaded_file' );
+ my $type = $q->uploadInfo( $filehandle )->{'Content-Type'};
+ if ( $type ne 'text/html' ) {
+ die "HTML FILES ONLY!";
+ }
+
+Note that you must use ->upload or ->param to get the file-handle to pass into
+uploadInfo as internally this is represented as a File::Temp object (which is
+what will be returned by ->upload or ->param). When using ->Vars you will get
+the literal filename rather than the File::Temp object, which will not return
+anything when passed to uploadInfo. So don't use ->Vars.
+
+If you are using a machine that recognizes "text" and "binary" data modes, be
+sure to understand when and how to use them (see the Camel book). Otherwise
+you may find that binary files are corrupted during file uploads.
+
+=head3 Accessing the temp files directly
+
+When processing an uploaded file, CGI.pm creates a temporary file on your hard
+disk and passes you a file handle to that file. After you are finished with the
+file handle, CGI.pm unlinks (deletes) the temporary file. If you need to you
+can access the temporary file directly. You can access the temp file for a file
+upload by passing the file name to the tmpFileName() method:
+
+ my $filehandle = $query->upload( 'uploaded_file' );
+ my $tmpfilename = $query->tmpFileName( $filehandle );
+
+As with ->uploadInfo, using the reference returned by ->upload or ->param is
+preferred, although unlike ->uploadInfo, plain filenames also work if possible
+for backwards compatibility.
+
+The temporary file will be deleted automatically when your program exits unless
+you manually rename it or set $CGI::UNLINK_TMP_FILES to 0. On some operating
+systems (such as Windows NT), you will need to close the temporary file's
+filehandle before your program exits. Otherwise the attempt to delete the
+temporary file will fail.
+
+=head3 Changes in temporary file handling (v4.05+)
+
+CGI.pm had its temporary file handling significantly refactored, this logic is
+now all deferred to File::Temp (which is wrapped in a compatibility object,
+CGI::File::Temp - B<DO NOT USE THIS PACKAGE DIRECTLY>). As a consequence the
+PRIVATE_TEMPFILES variable has been removed along with deprecation of the
+private_tempfiles routine and B<complete> removal of the CGITempFile package.
+The $CGITempFile::TMPDIRECTORY is no longer used to set the temp directory,
+refer to the perldoc for File::Temp if you want to override the default
+settings in that package (the TMPDIR env variable is still available on some
+platforms). For Windows platforms the temporary directory order remains
+as before: TEMP > TMP > WINDIR ( > TMPDIR ) so if you have any of these in
+use in existing scripts they should still work.
+
+The Fh package still exists but does nothing, the CGI::File::Temp class is
+a subclass of both File::Temp and the empty Fh package, so if you have any
+code that checks that the filehandle isa Fh this should still work.
+
+When you get the internal file handle you will receive a File::Temp object,
+this should be transparent as File::Temp isa IO::Handle and isa IO::Seekable
+meaning it behaves as previously. If you are doing anything out of the ordinary
+with regards to temp files you should test your code before deploying this
+update and refer to the File::Temp documentation for more information.
+
+=head3 Handling interrupted file uploads
+
+There are occasionally problems involving parsing the uploaded file. This
+usually happens when the user presses "Stop" before the upload is finished. In
+this case, CGI.pm will return undef for the name of the uploaded file and set
+I<cgi_error()> to the string "400 Bad request (malformed multipart POST)". This
+error message is designed so that you can incorporate it into a status code to
+be sent to the browser. Example:
+
+ my $file = $q->upload( 'uploaded_file' );
+ if ( !$file && $q->cgi_error ) {
+ print $q->header( -status => $q->cgi_error );
+ exit 0;
+ }
+
+=head3 Progress bars for file uploads and avoiding temp files
+
+CGI.pm gives you low-level access to file upload management through a file
+upload hook. You can use this feature to completely turn off the temp file
+storage of file uploads, or potentially write your own file upload progress
+meter.
+
+This is much like the UPLOAD_HOOK facility available in L<Apache::Request>,
+with the exception that the first argument to the callback is an
+L<Apache::Upload> object, here it's the remote filename.
+
+ my $q = CGI->new( \&hook [,$data [,$use_tempfile]] );
+
+ sub hook {
+ my ( $filename, $buffer, $bytes_read, $data ) = @_;
+ print "Read $bytes_read bytes of $filename\n";
+ }
+
+The C<< $data >> field is optional; it lets you pass configuration information
+(e.g. a database handle) to your hook callback.
+
+The C<< $use_tempfile >> field is a flag that lets you turn on and off CGI.pm's
+use of a temporary disk-based file during file upload. If you set this to a
+FALSE value (default true) then $q->param('uploaded_file') will no longer work,
+and the only way to get at the uploaded data is via the hook you provide.
+
+If using the function-oriented interface, call the CGI::upload_hook() method
+before calling param() or any other CGI functions:
+
+ CGI::upload_hook( \&hook [,$data [,$use_tempfile]] );
+
+This method is not exported by default. You will have to import it explicitly
+if you wish to use it without the CGI:: prefix.
+
+=head3 Troubleshooting file uploads on Windows
+
+If you are using CGI.pm on a Windows platform and find that binary files get
+slightly larger when uploaded but that text files remain the same, then you
+have forgotten to activate binary mode on the output filehandle. Be sure to call
+binmode() on any handle that you create to write the uploaded file to disk.
+
+=head3 Older ways to process file uploads
+
+This section is here for completeness. if you are building a new application
+with CGI.pm, you can skip it.
+
+The original way to process file uploads with CGI.pm was to use param(). The
+value it returns has a dual nature as both a file name and a lightweight
+filehandle. This dual nature is problematic if you following the recommended
+practice of having C<use strict> in your code. perl will complain when you try
+to use a string as a filehandle. More seriously, it is possible for the remote
+user to type garbage into the upload field, in which case what you get from
+param() is not a filehandle at all, but a string.
+
+To solve this problem the upload() method was added, which always returns a
+lightweight filehandle. This generally works well, but will have trouble
+interoperating with some other modules because the file handle is not derived
+from L<IO::File>. So that brings us to current recommendation given above,
+which is to call the handle() method on the file handle returned by upload().
+That upgrades the handle to an IO::File. It's a big win for compatibility for
+a small penalty of loading IO::File the first time you call it.
+
+=head1 HTTP COOKIES
+
+CGI.pm has several methods that support cookies.
+
+A cookie is a name=value pair much like the named parameters in a CGI query
+string. CGI scripts create one or more cookies and send them to the browser
+in the HTTP header. The browser maintains a list of cookies that belong to a
+particular Web server, and returns them to the CGI script during subsequent
+interactions.
+
+In addition to the required name=value pair, each cookie has several optional
+attributes:
+
+=over 4
+
+=item 1. an expiration time
+
+This is a time/date string (in a special GMT format) that indicates when a
+cookie expires. The cookie will be saved and returned to your script until this
+expiration date is reached if the user exits the browser and restarts it. If an
+expiration date isn't specified, the cookie will remain active until the user
+quits the browser.
+
+=item 2. a domain
+
+This is a partial or complete domain name for which the cookie is valid. The
+browser will return the cookie to any host that matches the partial domain name.
+For example, if you specify a domain name of ".capricorn.com", then the browser
+will return the cookie to Web servers running on any of the machines
+"www.capricorn.com", "www2.capricorn.com", "feckless.capricorn.com", etc. Domain
+names must contain at least two periods to prevent attempts to match on top
+level domains like ".edu". If no domain is specified, then the browser will
+only return the cookie to servers on the host the cookie originated from.
+
+=item 3. a path
+
+If you provide a cookie path attribute, the browser will check it against your
+script's URL before returning the cookie. For example, if you specify the path
+"/cgi-bin", then the cookie will be returned to each of the scripts
+"/cgi-bin/tally.pl", "/cgi-bin/order.pl", and
+"/cgi-bin/customer_service/complain.pl", but not to the script
+"/cgi-private/site_admin.pl". By default, path is set to "/", which causes the
+cookie to be sent to any CGI script on your site.
+
+=item 4. a "secure" flag
+
+If the "secure" attribute is set, the cookie will only be sent to your script
+if the CGI request is occurring on a secure channel, such as SSL.
+
+=back
+
+The interface to HTTP cookies is the B<cookie()> method:
+
+ my $cookie = $q->cookie(
+ -name => 'sessionID',
+ -value => 'xyzzy',
+ -expires => '+1h',
+ -path => '/cgi-bin/database',
+ -domain => '.capricorn.org',
+ -secure => 1
+ );
+
+ print $q->header( -cookie => $cookie );
+
+B<cookie()> creates a new cookie. Its parameters include:
+
+=over 4
+
+=item B<-name>
+
+The name of the cookie (required). This can be any string at all. Although
+browsers limit their cookie names to non-whitespace alphanumeric characters,
+CGI.pm removes this restriction by escaping and unescaping cookies behind the
+scenes.
+
+=item B<-value>
+
+The value of the cookie. This can be any scalar value, array reference, or even
+hash reference. For example, you can store an entire hash into a cookie this
+way:
+
+ my $cookie = $q->cookie(
+ -name => 'family information',
+ -value => \%childrens_ages
+ );
+
+=item B<-path>
+
+The optional partial path for which this cookie will be valid, as described
+above.
+
+=item B<-domain>
+
+The optional partial domain for which this cookie will be valid, as described
+above.
+
+=item B<-expires>
+
+The optional expiration date for this cookie. The format is as described in the
+section on the B<header()> method:
+
+ "+1h" one hour from now
+
+=item B<-secure>
+
+If set to true, this cookie will only be used within a secure SSL session.
+
+=back
+
+The cookie created by cookie() must be incorporated into the HTTP header within
+the string returned by the header() method:
+
+ use strict;
+ use warnings;
+
+ use CGI;
+
+ my $q = CGI->new;
+ my $cookie = ...
+ print $q->header( -cookie => $cookie );
+
+To create multiple cookies, give header() an array reference:
+
+ my $cookie1 = $q->cookie(
+ -name => 'riddle_name',
+ -value => "The Sphynx's Question"
+ );
+
+ my $cookie2 = $q->cookie(
+ -name => 'answers',
+ -value => \%answers
+ );
+
+ print $q->header( -cookie => [ $cookie1,$cookie2 ] );
+
+To retrieve a cookie, request it by name by calling cookie() method without the
+B<-value> parameter. This example uses the object-oriented form:
+
+ my $riddle = $q->cookie('riddle_name');
+ my %answers = $query->cookie('answers');
+
+Cookies created with a single scalar value, such as the "riddle_name" cookie,
+will be returned in that form. Cookies with array and hash values can also be
+retrieved.
+
+The cookie and CGI namespaces are separate. If you have a parameter named
+'answers' and a cookie named 'answers', the values retrieved by param() and
+cookie() are independent of each other. However, it's simple to turn a CGI
+parameter into a cookie, and vice-versa:
+
+ # turn a CGI parameter into a cookie
+ my $c = cookie( -name => 'answers',-value => [$q->param('answers')] );
+ # vice-versa
+ $q->param( -name => 'answers',-value => [ $q->cookie('answers')] );
+
+If you call cookie() without any parameters, it will return a list of
+the names of all cookies passed to your script:
+
+ my @cookies = $q->cookie();
+
+See the B<cookie.cgi> example script for some ideas on how to use cookies
+effectively.
+
+=head1 DEBUGGING
+
+If you are running the script from the command line or in the perl debugger,
+you can pass the script a list of keywords or parameter=value pairs on the
+command line or from standard input (you don't have to worry about tricking
+your script into reading from environment variables). You can pass keywords
+like this:
+
+ your_script.pl keyword1 keyword2 keyword3
+
+or this:
+
+ your_script.pl keyword1+keyword2+keyword3
+
+or this:
+
+ your_script.pl name1=value1 name2=value2
+
+or this:
+
+ your_script.pl name1=value1&name2=value2
+
+To turn off this feature, use the -no_debug pragma.
+
+To test the POST method, you may enable full debugging with the -debug pragma.
+This will allow you to feed newline-delimited name=value pairs to the script on
+standard input.
+
+When debugging, you can use quotes and backslashes to escape characters in the
+familiar shell manner, letting you place spaces and other funny characters in
+your parameter=value pairs:
+
+ your_script.pl "name1='I am a long value'" "name2=two\ words"
+
+Finally, you can set the path info for the script by prefixing the first
+name/value parameter with the path followed by a question mark (?):
+
+ your_script.pl /your/path/here?name1=value1&name2=value2
+
+=head1 FETCHING ENVIRONMENT VARIABLES
+
+Some of the more useful environment variables can be fetched through this
+interface. The methods are as follows:
+
+=over 4
+
+=item B<Accept()>
+
+Return a list of MIME types that the remote browser accepts. If you give this
+method a single argument corresponding to a MIME type, as in
+Accept('text/html'), it will return a floating point value corresponding to the
+browser's preference for this type from 0.0 (don't want) to 1.0. Glob types
+(e.g. text/*) in the browser's accept list are handled correctly.
+
+Note that the capitalization changed between version 2.43 and 2.44 in order to
+avoid conflict with perl's accept() function.
+
+=item B<raw_cookie()>
+
+Returns the HTTP_COOKIE variable. Cookies have a special format, and this
+method call just returns the raw form (?cookie dough). See cookie() for ways
+of setting and retrieving cooked cookies.
+
+Called with no parameters, raw_cookie() returns the packed cookie structure.
+You can separate it into individual cookies by splitting on the character
+sequence "; ". Called with the name of a cookie, retrieves the B<unescaped>
+form of the cookie. You can use the regular cookie() method to get the names,
+or use the raw_fetch() method from the CGI::Cookie module.
+
+=item B<env_query_string()>
+
+Returns the QUERY_STRING variable, note that this is the original value as set
+in the environment by the webserver and (possibly) not the same value as
+returned by query_string(), which represents the object state
+
+=item B<user_agent()>
+
+Returns the HTTP_USER_AGENT variable. If you give this method a single
+argument, it will attempt to pattern match on it, allowing you to do something
+like user_agent(Mozilla);
+
+=item B<path_info()>
+
+Returns additional path information from the script URL. E.G. fetching
+/cgi-bin/your_script/additional/stuff will result in path_info() returning
+"/additional/stuff".
+
+NOTE: The Microsoft Internet Information Server is broken with respect to
+additional path information. If you use the perl DLL library, the IIS server
+will attempt to execute the additional path information as a perl script. If
+you use the ordinary file associations mapping, the path information will be
+present in the environment, but incorrect. The best thing to do is to avoid
+using additional path information in CGI scripts destined for use with IIS. A
+best attempt has been made to make CGI.pm do the right thing.
+
+=item B<path_translated()>
+
+As per path_info() but returns the additional path information translated into
+a physical path, e.g. "/usr/local/etc/httpd/htdocs/additional/stuff".
+
+The Microsoft IIS is broken with respect to the translated path as well.
+
+=item B<remote_host()>
+
+Returns either the remote host name or IP address if the former is unavailable.
+
+=item B<remote_ident()>
+
+Returns the name of the remote user (as returned by identd) or undef if not set
+
+=item B<remote_addr()>
+
+Returns the remote host IP address, or 127.0.0.1 if the address is unavailable.
+
+=item B<request_uri()>
+
+Returns the interpreted pathname of the requested document or CGI (relative to
+the document root). Or undef if not set.
+
+=item B<script_name()>
+
+Return the script name as a partial URL, for self-referring scripts.
+
+=item B<referer()>
+
+Return the URL of the page the browser was viewing prior to fetching your
+script.
+
+=item B<auth_type()>
+
+Return the authorization/verification method in use for this script, if any.
+
+=item B<server_name()>
+
+Returns the name of the server, usually the machine's host name.
+
+=item B<virtual_host()>
+
+When using virtual hosts, returns the name of the host that the browser
+attempted to contact
+
+=item B<server_port()>
+
+Return the port that the server is listening on.
+
+=item B<server_protocol()>
+
+Returns the protocol and revision of the incoming request, or defaults to
+HTTP/1.0 if this is not set
+
+=item B<virtual_port()>
+
+Like server_port() except that it takes virtual hosts into account. Use this
+when running with virtual hosts.
+
+=item B<server_software()>
+
+Returns the server software and version number.
+
+=item B<remote_user()>
+
+Return the authorization/verification name used for user verification, if this
+script is protected.
+
+=item B<user_name()>
+
+Attempt to obtain the remote user's name, using a variety of different
+techniques. May not work in all browsers.
+
+=item B<request_method()>
+
+Returns the method used to access your script, usually one of 'POST', 'GET'
+or 'HEAD'.
+
+=item B<content_type()>
+
+Returns the content_type of data submitted in a POST, generally
+multipart/form-data or application/x-www-form-urlencoded
+
+=item B<http()>
+
+Called with no arguments returns the list of HTTP environment variables,
+including such things as HTTP_USER_AGENT, HTTP_ACCEPT_LANGUAGE, and
+HTTP_ACCEPT_CHARSET, corresponding to the like-named HTTP header fields in the
+request. Called with the name of an HTTP header field, returns its value.
+Capitalization and the use of hyphens versus underscores are not significant.
+
+For example, all three of these examples are equivalent:
+
+ my $requested_language = $q->http('Accept-language');
+
+ my $requested_language = $q->http('Accept_language');
+
+ my $requested_language = $q->http('HTTP_ACCEPT_LANGUAGE');
+
+=item B<https()>
+
+The same as I<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.
+
+=back
+
+=head1 USING NPH SCRIPTS
+
+NPH, or "no-parsed-header", scripts bypass the server completely by sending the
+complete HTTP header directly to the browser. This has slight performance
+benefits, but is of most use for taking advantage of HTTP extensions that are
+not directly supported by your server, such as server push and PICS headers.
+
+Servers use a variety of conventions for designating CGI scripts as NPH. Many
+Unix servers look at the beginning of the script's name for the prefix "nph-".
+The Macintosh WebSTAR server and Microsoft's Internet Information Server, in
+contrast, try to decide whether a program is an NPH script by examining the
+first line of script output.
+
+CGI.pm supports NPH scripts with a special NPH mode. When in this mode, CGI.pm
+will output the necessary extra header information when the header() and
+redirect() methods are called.
+
+The Microsoft Internet Information Server requires NPH mode. As of version 2.30,
+CGI.pm will automatically detect when the script is running under IIS and put
+itself into this mode. You do not need to do this manually, although it won't
+hurt anything if you do.
+
+=over 4
+
+=item In the B<use> statement
+
+Simply add the "-nph" pragma to the list of symbols to be imported into
+your script:
+
+ use CGI qw(:standard -nph)
+
+=item By calling the B<nph()> method:
+
+Call B<nph()> with a non-zero parameter at any point after using CGI.pm in your
+program.
+
+ CGI->nph(1)
+
+=item By using B<-nph> parameters
+
+in the B<header()> and B<redirect()> statements:
+
+ print header(-nph=>1);
+
+=back
+
+=head1 SERVER PUSH
+
+CGI.pm provides four simple functions for producing multipart documents of the
+type needed to implement server push. These functions were graciously provided
+by Ed Jordan <ed@fidalgo.net>. To import these into your namespace, you must
+import the ":push" set. You are also advised to put the script into NPH mode
+and to set $| to 1 to avoid buffering problems.
+
+Here is a simple script that demonstrates server push:
+
+ #!/usr/bin/env perl
+
+ use strict;
+ use warnings;
+
+ use CGI qw/:push -nph/;
+
+ $| = 1;
+ print multipart_init( -boundary=>'----here we go!' );
+ for (0 .. 4) {
+ print multipart_start( -type=>'text/plain' ),
+ "The current time is ",scalar( localtime ),"\n";
+ if ($_ < 4) {
+ print multipart_end();
+ } else {
+ print multipart_final();
+ }
+ sleep 1;
+ }
+
+This script initializes server push by calling B<multipart_init()>. It then
+enters a loop in which it begins a new multipart section by calling
+B<multipart_start()>, prints the current local time, and ends a multipart
+section with B<multipart_end()>. It then sleeps a second, and begins again.
+On the final iteration, it ends the multipart section with
+B<multipart_final()> rather than with B<multipart_end()>.
+
+=over 4
+
+=item multipart_init()
+
+ multipart_init( -boundary => $boundary, -charset => $charset );
+
+Initialize the multipart system. The -boundary argument specifies what MIME
+boundary string to use to separate parts of the document. If not provided,
+CGI.pm chooses a reasonable boundary for you.
+
+The -charset provides the character set, if not provided this will default to
+ISO-8859-1
+
+=item multipart_start()
+
+ multipart_start( -type => $type, -charset => $charset );
+
+Start a new part of the multipart document using the specified MIME type and
+charset. If not specified, text/html ISO-8859-1 is assumed.
+
+=item multipart_end()
+
+ multipart_end()
+
+End a part. You must remember to call multipart_end() once for each
+multipart_start(), except at the end of the last part of the multipart document
+when multipart_final() should be called instead of multipart_end().
+
+=item multipart_final()
+
+ multipart_final()
+
+End all parts. You should call multipart_final() rather than multipart_end()
+at the end of the last part of the multipart document.
+
+=back
+
+Users interested in server push applications should also have a look at the
+CGI::Push module.
+
+=head1 AVOIDING DENIAL OF SERVICE ATTACKS
+
+A potential problem with CGI.pm is that, by default, it attempts to process
+form POSTings no matter how large they are. A wily hacker could attack your
+site by sending a CGI script a huge POST of many gigabytes. CGI.pm will attempt
+to read the entire POST into a variable, growing hugely in size until it runs
+out of memory. While the script attempts to allocate the memory the system may
+slow down dramatically. This is a form of denial of service attack.
+
+Another possible attack is for the remote user to force CGI.pm to accept a huge
+file upload. CGI.pm will accept the upload and store it in a temporary directory
+even if your script doesn't expect to receive an uploaded file. CGI.pm will
+delete the file automatically when it terminates, but in the meantime the remote
+user may have filled up the server's disk space, causing problems for other
+programs.
+
+The best way to avoid denial of service attacks is to limit the amount of
+memory, CPU time and disk space that CGI scripts can use. Some Web servers come
+with built-in facilities to accomplish this. In other cases, you can use the
+shell I<limit> or I<ulimit> commands to put ceilings on CGI resource usage.
+
+CGI.pm also has some simple built-in protections against denial of service
+attacks, but you must activate them before you can use them. These take the
+form of two global variables in the CGI name space:
+
+=over 4
+
+=item B<$CGI::POST_MAX>
+
+If set to a non-negative integer, this variable puts a ceiling on the size of
+POSTings, in bytes. If CGI.pm detects a POST that is greater than the ceiling,
+it will immediately exit with an error message. This value will affect both
+ordinary POSTs and multipart POSTs, meaning that it limits the maximum size of
+file uploads as well. You should set this to a reasonably high
+value, such as 10 megabytes.
+
+=item B<$CGI::DISABLE_UPLOADS>
+
+If set to a non-zero value, this will disable file uploads completely. Other
+fill-out form values will work as usual.
+
+=back
+
+To use these variables, set the variable at the top of the script, right after
+the "use" statement:
+
+ #!/usr/bin/env perl
+
+ use strict;
+ use warnings;
+
+ use CGI;
+
+ $CGI::POST_MAX = 1024 * 1024 * 10; # max 10MB posts
+ $CGI::DISABLE_UPLOADS = 1; # no uploads
+
+An attempt to send a POST larger than $POST_MAX bytes will cause I<param()> to
+return an empty CGI parameter list. You can test for this event by checking
+I<cgi_error()>, either after you create the CGI object or, if you are using the
+function-oriented interface, call <param()> for the first time. If the POST was
+intercepted, then cgi_error() will return the message "413 POST too large".
+
+This error message is actually defined by the HTTP protocol, and is designed to
+be returned to the browser as the CGI script's status code. For example:
+
+ my $uploaded_file = $q->param('upload');
+ if ( !$uploaded_file && $q->cgi_error() ) {
+ print $q->header( -status => $q->cgi_error() );
+ exit 0;
+ }
+
+However it isn't clear that any browser currently knows what to do with this
+status code. It might be better just to create a page that warns the user of
+the problem.
+
+=head1 COMPATIBILITY WITH CGI-LIB.PL
+
+To make it easier to port existing programs that use cgi-lib.pl the
+compatibility routine "ReadParse" is provided. Porting is simple:
+
+OLD VERSION
+
+ require "cgi-lib.pl";
+ &ReadParse;
+ print "The value of the antique is $in{antique}.\n";
+
+NEW VERSION
+
+ use CGI;
+ CGI::ReadParse();
+ print "The value of the antique is $in{antique}.\n";
+
+CGI.pm's ReadParse() routine creates a tied variable named %in, which can be
+accessed to obtain the query variables. Like ReadParse, you can also provide
+your own variable. Infrequently used features of ReadParse, such as the creation
+of @in and $in variables, are not supported.
+
+Once you use ReadParse, you can retrieve the query object itself this way:
+
+ my $q = $in{CGI};
+
+This allows you to start using the more interesting features of CGI.pm without
+rewriting your old scripts from scratch.
+
+An even simpler way to mix cgi-lib calls with CGI.pm calls is to import both the
+C<:cgi-lib> and C<:standard> method:
+
+ use CGI qw(:cgi-lib :standard);
+ &ReadParse;
+ print "The price of your purchase is $in{price}.\n";
+ print textfield(-name=>'price', -default=>'$1.99');
+
+=head2 Cgi-lib functions that are available in CGI.pm
+
+In compatibility mode, the following cgi-lib.pl functions are
+available for your use:
+
+ ReadParse()
+ PrintHeader()
+ SplitParam()
+ MethGet()
+ MethPost()
+
+=head1 LICENSE
+
+The CGI.pm distribution is copyright 1995-2007, Lincoln D. Stein. It is
+distributed under GPL and the Artistic License 2.0. It is currently maintained
+by Lee Johnson (LEEJO) with help from many contributors.
+
+=head1 CREDITS
+
+Thanks very much to:
+
+=over 4
+
+=item Mark Stosberg (mark@stosberg.com)
+
+=item Matt Heffron (heffron@falstaff.css.beckman.com)
+
+=item James Taylor (james.taylor@srs.gov)
+
+=item Scott Anguish <sanguish@digifix.com>
+
+=item Mike Jewell (mlj3u@virginia.edu)
+
+=item Timothy Shimmin (tes@kbs.citri.edu.au)
+
+=item Joergen Haegg (jh@axis.se)
+
+=item Laurent Delfosse (delfosse@delfosse.com)
+
+=item Richard Resnick (applepi1@aol.com)
+
+=item Craig Bishop (csb@barwonwater.vic.gov.au)
+
+=item Tony Curtis (tc@vcpc.univie.ac.at)
+
+=item Tim Bunce (Tim.Bunce@ig.co.uk)
+
+=item Tom Christiansen (tchrist@convex.com)
+
+=item Andreas Koenig (k@franz.ww.TU-Berlin.DE)
+
+=item Tim MacKenzie (Tim.MacKenzie@fulcrum.com.au)
+
+=item Kevin B. Hendricks (kbhend@dogwood.tyler.wm.edu)
+
+=item Stephen Dahmen (joyfire@inxpress.net)
+
+=item Ed Jordan (ed@fidalgo.net)
+
+=item David Alan Pisoni (david@cnation.com)
+
+=item Doug MacEachern (dougm@opengroup.org)
+
+=item Robin Houston (robin@oneworld.org)
+
+=item ...and many many more...
+
+for suggestions and bug fixes.
+
+=back
+
+=head1 BUGS
+
+Address bug reports and comments to: L<https://github.com/leejo/CGI.pm/issues>
+
+The original bug tracker can be found at:
+L<https://rt.cpan.org/Public/Dist/Display.html?Queue=CGI.pm>
+
+When sending bug reports, please provide the version of CGI.pm, the version of
+perl, the name and version of your Web server, and the name and version of the
+operating system you are using. If the problem is even remotely browser
+dependent, please provide information about the affected browsers as well.
+
+Failing tests cases are appreciated with issues, and if you submit a patch then
+it will *not* be accepted unless you provide a reasonable automated test case
+with it (please see the existing tests in t/ for examples).
+
+Please note the CGI.pm is now considered "done". See also "mature" and "legacy".
+Feature requests and none critical issues will be outright rejected. The module
+is now in maintenance mode for critical issues only.
+
+=head1 SEE ALSO
+
+L<CGI::Carp> - provides L<Carp> implementation tailored to the CGI environment.
+
+L<CGI::Fast> - supports running CGI applications under FastCGI
+
+=cut
diff --git a/lib/CGI/Carp.pm b/lib/CGI/Carp.pm
new file mode 100644
index 0000000..d215732
--- /dev/null
+++ b/lib/CGI/Carp.pm
@@ -0,0 +1,615 @@
+package CGI::Carp;
+use if $] >= 5.019, 'deprecate';
+
+=head1 NAME
+
+B<CGI::Carp> - CGI routines for writing to the HTTPD (or other) error log
+
+=head1 SYNOPSIS
+
+ use CGI::Carp;
+
+ croak "We're outta here!";
+ confess "It was my fault: $!";
+ carp "It was your fault!";
+ warn "I'm confused";
+ die "I'm dying.\n";
+
+ use CGI::Carp qw(cluck);
+ cluck "I wouldn't do that if I were you";
+
+ use CGI::Carp qw(fatalsToBrowser);
+ die "Fatal error messages are now sent to browser";
+
+=head1 DESCRIPTION
+
+CGI scripts have a nasty habit of leaving warning messages in the error
+logs that are neither time stamped nor fully identified. Tracking down
+the script that caused the error is a pain. This fixes that. Replace
+the usual
+
+ use Carp;
+
+with
+
+ use CGI::Carp
+
+The standard warn(), die (), croak(), confess() and carp() calls will
+be replaced with functions that write time-stamped messages to the
+HTTP server error log.
+
+For example:
+
+ [Fri Nov 17 21:40:43 1995] test.pl: I'm confused at test.pl line 3.
+ [Fri Nov 17 21:40:43 1995] test.pl: Got an error message: Permission denied.
+ [Fri Nov 17 21:40:43 1995] test.pl: I'm dying.
+
+=head1 REDIRECTING ERROR MESSAGES
+
+By default, error messages are sent to STDERR. Most HTTPD servers
+direct STDERR to the server's error log. Some applications may wish
+to keep private error logs, distinct from the server's error log, or
+they may wish to direct error messages to STDOUT so that the browser
+will receive them.
+
+The C<carpout()> function is provided for this purpose. Since
+carpout() is not exported by default, you must import it explicitly by
+saying
+
+ use CGI::Carp qw(carpout);
+
+The carpout() function requires one argument, a reference to an open
+filehandle for writing errors. It should be called in a C<BEGIN>
+block at the top of the CGI application so that compiler errors will
+be caught. Example:
+
+ BEGIN {
+ use CGI::Carp qw(carpout);
+ open(LOG, ">>/usr/local/cgi-logs/mycgi-log") or
+ die("Unable to open mycgi-log: $!\n");
+ carpout(LOG);
+ }
+
+carpout() does not handle file locking on the log for you at this
+point. Also, note that carpout() does not work with in-memory file
+handles, although a patch would be welcome to address that.
+
+The real STDERR is not closed -- it is moved to CGI::Carp::SAVEERR.
+Some servers, when dealing with CGI scripts, close their connection to
+the browser when the script closes STDOUT and STDERR.
+CGI::Carp::SAVEERR is there to prevent this from happening
+prematurely.
+
+You can pass filehandles to carpout() in a variety of ways. The "correct"
+way according to Tom Christiansen is to pass a reference to a filehandle
+GLOB:
+
+ carpout(\*LOG);
+
+This looks weird to mere mortals however, so the following syntaxes are
+accepted as well:
+
+ carpout(LOG);
+ carpout(main::LOG);
+ carpout(main'LOG);
+ carpout(\LOG);
+ carpout(\'main::LOG');
+
+ ... and so on
+
+FileHandle and other objects work as well.
+
+Use of carpout() is not great for performance, so it is recommended
+for debugging purposes or for moderate-use applications. A future
+version of this module may delay redirecting STDERR until one of the
+CGI::Carp methods is called to prevent the performance hit.
+
+=head1 MAKING PERL ERRORS APPEAR IN THE BROWSER WINDOW
+
+If you want to send fatal (die, confess) errors to the browser, import
+the special "fatalsToBrowser" subroutine:
+
+ use CGI::Carp qw(fatalsToBrowser);
+ die "Bad error here";
+
+Fatal errors will now be echoed to the browser as well as to the log.
+CGI::Carp arranges to send a minimal HTTP header to the browser so
+that even errors that occur in the early compile phase will be seen.
+Nonfatal errors will still be directed to the log file only (unless
+redirected with carpout).
+
+Note that fatalsToBrowser may B<not> work well with mod_perl version 2.0
+and higher.
+
+=head2 Changing the default message
+
+By default, the software error message is followed by a note to
+contact the Webmaster by e-mail with the time and date of the error.
+If this message is not to your liking, you can change it using the
+set_message() routine. This is not imported by default; you should
+import it on the use() line:
+
+ use CGI::Carp qw(fatalsToBrowser set_message);
+ set_message("It's not a bug, it's a feature!");
+
+You may also pass in a code reference in order to create a custom
+error message. At run time, your code will be called with the text
+of the error message that caused the script to die. Example:
+
+ use CGI::Carp qw(fatalsToBrowser set_message);
+ BEGIN {
+ sub handle_errors {
+ my $msg = shift;
+ print "<h1>Oh gosh</h1>";
+ print "<p>Got an error: $msg</p>";
+ }
+ set_message(\&handle_errors);
+ }
+
+In order to correctly intercept compile-time errors, you should call
+set_message() from within a BEGIN{} block.
+
+=head1 DOING MORE THAN PRINTING A MESSAGE IN THE EVENT OF PERL ERRORS
+
+If fatalsToBrowser in conjunction with set_message does not provide
+you with all of the functionality you need, you can go one step
+further by specifying a function to be executed any time a script
+calls "die", has a syntax error, or dies unexpectedly at runtime
+with a line like "undef->explode();".
+
+ use CGI::Carp qw(set_die_handler);
+ BEGIN {
+ sub handle_errors {
+ my $msg = shift;
+ print "content-type: text/html\n\n";
+ print "<h1>Oh gosh</h1>";
+ print "<p>Got an error: $msg</p>";
+
+ #proceed to send an email to a system administrator,
+ #write a detailed message to the browser and/or a log,
+ #etc....
+ }
+ set_die_handler(\&handle_errors);
+ }
+
+Notice that if you use set_die_handler(), you must handle sending
+HTML headers to the browser yourself if you are printing a message.
+
+If you use set_die_handler(), you will most likely interfere with
+the behavior of fatalsToBrowser, so you must use this or that, not
+both.
+
+Using set_die_handler() sets SIG{__DIE__} (as does fatalsToBrowser),
+and there is only one SIG{__DIE__}. This means that if you are
+attempting to set SIG{__DIE__} yourself, you may interfere with
+this module's functionality, or this module may interfere with
+your module's functionality.
+
+=head1 SUPPRESSING PERL ERRORS APPEARING IN THE BROWSER WINDOW
+
+A problem sometimes encountered when using fatalsToBrowser is
+when a C<die()> is done inside an C<eval> body or expression.
+Even though the
+fatalsToBrower support takes precautions to avoid this,
+you still may get the error message printed to STDOUT.
+This may have some undesirable effects when the purpose of doing the
+eval is to determine which of several algorithms is to be used.
+
+By setting C<$CGI::Carp::TO_BROWSER> to 0 you can suppress printing
+the C<die> messages but without all of the complexity of using
+C<set_die_handler>. You can localize this effect to inside C<eval>
+bodies if this is desirable: For example:
+
+ eval {
+ local $CGI::Carp::TO_BROWSER = 0;
+ die "Fatal error messages not sent browser"
+ }
+ # $@ will contain error message
+
+
+=head1 MAKING WARNINGS APPEAR AS HTML COMMENTS
+
+It is also possible to make non-fatal errors appear as HTML comments
+embedded in the output of your program. To enable this feature,
+export the new "warningsToBrowser" subroutine. Since sending warnings
+to the browser before the HTTP headers have been sent would cause an
+error, any warnings are stored in an internal buffer until you call
+the warningsToBrowser() subroutine with a true argument:
+
+ use CGI::Carp qw(fatalsToBrowser warningsToBrowser);
+ use CGI qw(:standard);
+ print header();
+ warningsToBrowser(1);
+
+You may also give a false argument to warningsToBrowser() to prevent
+warnings from being sent to the browser while you are printing some
+content where HTML comments are not allowed:
+
+ warningsToBrowser(0); # disable warnings
+ print "<script type=\"text/javascript\"><!--\n";
+ print_some_javascript_code();
+ print "//--></script>\n";
+ warningsToBrowser(1); # re-enable warnings
+
+Note: In this respect warningsToBrowser() differs fundamentally from
+fatalsToBrowser(), which you should never call yourself!
+
+=head1 OVERRIDING THE NAME OF THE PROGRAM
+
+CGI::Carp includes the name of the program that generated the error or
+warning in the messages written to the log and the browser window.
+Sometimes, Perl can get confused about what the actual name of the
+executed program was. In these cases, you can override the program
+name that CGI::Carp will use for all messages.
+
+The quick way to do that is to tell CGI::Carp the name of the program
+in its use statement. You can do that by adding
+"name=cgi_carp_log_name" to your "use" statement. For example:
+
+ use CGI::Carp qw(name=cgi_carp_log_name);
+
+. If you want to change the program name partway through the program,
+you can use the C<set_progname()> function instead. It is not
+exported by default, you must import it explicitly by saying
+
+ use CGI::Carp qw(set_progname);
+
+Once you've done that, you can change the logged name of the program
+at any time by calling
+
+ set_progname(new_program_name);
+
+You can set the program back to the default by calling
+
+ set_progname(undef);
+
+Note that this override doesn't happen until after the program has
+compiled, so any compile-time errors will still show up with the
+non-overridden program name
+
+=head1 TURNING OFF TIMESTAMPS IN MESSAGES
+
+If your web server automatically adds a timestamp to each log line,
+you may not need CGI::Carp to add its own. You can disable timestamping
+by importing "noTimestamp":
+
+ use CGI::Carp qw(noTimestamp);
+
+Alternatively you can set C<$CGI::Carp::NO_TIMESTAMP> to 1.
+
+Note that the name of the program is still automatically included in
+the message.
+
+=head1 GETTING THE FULL PATH OF THE SCRIPT IN MESSAGES
+
+Set C<$CGI::Carp::FULL_PATH> to 1.
+
+=head1 AUTHOR INFORMATION
+
+The CGI.pm distribution is copyright 1995-2007, Lincoln D. Stein. It is
+distributed under GPL and the Artistic License 2.0. It is currently
+maintained by Lee Johnson with help from many contributors.
+
+Address bug reports and comments to: https://github.com/leejo/CGI.pm/issues
+
+The original bug tracker can be found at: https://rt.cpan.org/Public/Dist/Display.html?Queue=CGI.pm
+
+When sending bug reports, please provide the version of CGI.pm, the version of
+Perl, the name and version of your Web server, and the name and version of the
+operating system you are using. If the problem is even remotely browser
+dependent, please provide information about the affected browsers as well.
+
+=head1 SEE ALSO
+
+L<Carp>, L<CGI::Base>, L<CGI::BasePlus>, L<CGI::Request>,
+L<CGI::MiniSvr>, L<CGI::Form>, L<CGI::Response>.
+
+=cut
+
+require 5.000;
+use Exporter;
+#use Carp;
+BEGIN {
+ require Carp;
+ *CORE::GLOBAL::die = \&CGI::Carp::die;
+}
+
+use File::Spec;
+
+@ISA = qw(Exporter);
+@EXPORT = qw(confess croak carp);
+@EXPORT_OK = qw(carpout fatalsToBrowser warningsToBrowser wrap noTimestamp set_message set_die_handler set_progname cluck ^name= die);
+
+$main::SIG{__WARN__}=\&CGI::Carp::warn;
+
+$CGI::Carp::VERSION = '4.21';
+$CGI::Carp::CUSTOM_MSG = undef;
+$CGI::Carp::DIE_HANDLER = undef;
+$CGI::Carp::TO_BROWSER = 1;
+$CGI::Carp::NO_TIMESTAMP= 0;
+$CGI::Carp::FULL_PATH = 0;
+
+# fancy import routine detects and handles 'errorWrap' specially.
+sub import {
+ my $pkg = shift;
+ my(%routines);
+ my(@name);
+ if (@name=grep(/^name=/,@_))
+ {
+ my($n) = (split(/=/,$name[0]))[1];
+ set_progname($n);
+ @_=grep(!/^name=/,@_);
+ }
+
+ grep($routines{$_}++,@_,@EXPORT);
+ $WRAP++ if $routines{'fatalsToBrowser'} || $routines{'wrap'};
+ $WARN++ if $routines{'warningsToBrowser'};
+ my($oldlevel) = $Exporter::ExportLevel;
+ $Exporter::ExportLevel = 1;
+ Exporter::import($pkg,keys %routines);
+ $Exporter::ExportLevel = $oldlevel;
+ $main::SIG{__DIE__} =\&CGI::Carp::die if $routines{'fatalsToBrowser'};
+ $CGI::Carp::NO_TIMESTAMP = 1 if $routines{'noTimestamp'};
+}
+
+# These are the originals
+sub realwarn { CORE::warn(@_); }
+sub realdie { CORE::die(@_); }
+
+sub id {
+ my $level = shift;
+ my($pack,$file,$line,$sub) = caller($level);
+ my($dev,$dirs,$id) = File::Spec->splitpath($file);
+ return ($file,$line,$id);
+}
+
+sub stamp {
+ my $frame = 0;
+ my ($id,$pack,$file,$dev,$dirs);
+ if (defined($CGI::Carp::PROGNAME)) {
+ $id = $CGI::Carp::PROGNAME;
+ } else {
+ do {
+ $id = $file;
+ ($pack,$file) = caller($frame++);
+ } until !$file;
+ }
+ if (! $CGI::Carp::FULL_PATH) {
+ ($dev,$dirs,$id) = File::Spec->splitpath($id);
+ }
+ return "$id: " if $CGI::Carp::NO_TIMESTAMP;
+ my $time = scalar(localtime);
+ return "[$time] $id: ";
+}
+
+sub set_progname {
+ $CGI::Carp::PROGNAME = shift;
+ return $CGI::Carp::PROGNAME;
+}
+
+
+sub warn {
+ my $message = shift;
+ my($file,$line,$id) = id(1);
+ $message .= " at $file line $line.\n" unless $message=~/\n$/;
+ _warn($message) if $WARN;
+ my $stamp = stamp;
+ $message=~s/^/$stamp/gm;
+ realwarn $message;
+}
+
+sub _warn {
+ my $msg = shift;
+ if ($EMIT_WARNINGS) {
+ # We need to mangle the message a bit to make it a valid HTML
+ # comment. This is done by substituting similar-looking ISO
+ # 8859-1 characters for <, > and -. This is a hack.
+ $msg =~ tr/<>-/\253\273\255/;
+ chomp $msg;
+ print STDOUT "<!-- warning: $msg -->\n";
+ } else {
+ push @WARNINGS, $msg;
+ }
+}
+
+
+# The mod_perl package Apache::Registry loads CGI programs by calling
+# eval. These evals don't count when looking at the stack backtrace.
+sub _longmess {
+ my $message = Carp::longmess();
+ $message =~ s,eval[^\n]+(ModPerl|Apache)/(?:Registry|Dispatch)\w*\.pm.*,,s
+ if exists $ENV{MOD_PERL};
+ return $message;
+}
+
+sub ineval {
+ (exists $ENV{MOD_PERL} ? 0 : $^S) || _longmess() =~ /eval [\{\']/m
+}
+
+sub die {
+ # if no argument is passed, propagate $@ like
+ # the real die
+ my ($arg,@rest) = @_ ? @_
+ : $@ ? "$@\t...propagated"
+ : "Died"
+ ;
+
+ &$DIE_HANDLER($arg,@rest) if $DIE_HANDLER;
+
+ # the "$arg" is done on purpose!
+ # if called as die( $object, 'string' ),
+ # all is stringified, just like with
+ # the real 'die'
+ $arg = join '' => "$arg", @rest if @rest;
+
+ my($file,$line,$id) = id(1);
+
+ $arg .= " at $file line $line.\n" unless ref $arg or $arg=~/\n$/;
+
+ realdie $arg if ineval();
+ &fatalsToBrowser($arg) if ($WRAP and $CGI::Carp::TO_BROWSER);
+
+ $arg=~s/^/ stamp() /gme if $arg =~ /\n$/ or not exists $ENV{MOD_PERL};
+
+ $arg .= "\n" unless $arg =~ /\n$/;
+
+ realdie $arg;
+}
+
+sub set_message {
+ $CGI::Carp::CUSTOM_MSG = shift;
+ return $CGI::Carp::CUSTOM_MSG;
+}
+
+sub set_die_handler {
+
+ my ($handler) = shift;
+
+ #setting SIG{__DIE__} here is necessary to catch runtime
+ #errors which are not called by literally saying "die",
+ #such as the line "undef->explode();". however, doing this
+ #will interfere with fatalsToBrowser, which also sets
+ #SIG{__DIE__} in the import() function above (or the
+ #import() function above may interfere with this). for
+ #this reason, you should choose to either set the die
+ #handler here, or use fatalsToBrowser, not both.
+ $main::SIG{__DIE__} = $handler;
+
+ $CGI::Carp::DIE_HANDLER = $handler;
+
+ return $CGI::Carp::DIE_HANDLER;
+}
+
+sub confess { CGI::Carp::die Carp::longmess @_; }
+sub croak { CGI::Carp::die Carp::shortmess @_; }
+sub carp { CGI::Carp::warn Carp::shortmess @_; }
+sub cluck { CGI::Carp::warn Carp::longmess @_; }
+
+# We have to be ready to accept a filehandle as a reference
+# or a string.
+sub carpout {
+ my($in) = @_;
+ my($no) = fileno(to_filehandle($in));
+ realdie("Invalid filehandle $in\n") unless defined $no;
+
+ open(SAVEERR, ">&STDERR");
+ open(STDERR, ">&$no") or
+ ( print SAVEERR "Unable to redirect >&$no: $!\n" and exit(1) );
+}
+
+sub warningsToBrowser {
+ $EMIT_WARNINGS = @_ ? shift : 1;
+ _warn(shift @WARNINGS) while $EMIT_WARNINGS and @WARNINGS;
+}
+
+# headers
+sub fatalsToBrowser {
+ my $msg = shift;
+
+ $msg = "$msg" if ref $msg;
+
+ $msg=~s/&/&amp;/g;
+ $msg=~s/>/&gt;/g;
+ $msg=~s/</&lt;/g;
+ $msg=~s/"/&quot;/g;
+
+ my($wm) = $ENV{SERVER_ADMIN} ?
+ qq[the webmaster (<a href="mailto:$ENV{SERVER_ADMIN}">$ENV{SERVER_ADMIN}</a>)] :
+ "this site's webmaster";
+ my ($outer_message) = <<END;
+For help, please send mail to $wm, giving this error message
+and the time and date of the error.
+END
+ ;
+ my $mod_perl = exists $ENV{MOD_PERL};
+
+ if ($CUSTOM_MSG) {
+ if (ref($CUSTOM_MSG) eq 'CODE') {
+ print STDOUT "Content-type: text/html\n\n"
+ unless $mod_perl;
+ eval {
+ &$CUSTOM_MSG($msg); # nicer to perl 5.003 users
+ };
+ if ($@) { print STDERR qq(error while executing the error handler: $@); }
+
+ return;
+ } else {
+ $outer_message = $CUSTOM_MSG;
+ }
+ }
+
+ my $mess = <<END;
+<h1>Software error:</h1>
+<pre>$msg</pre>
+<p>
+$outer_message
+</p>
+END
+ ;
+
+ if ($mod_perl) {
+ my $r;
+ if ($ENV{MOD_PERL_API_VERSION} && $ENV{MOD_PERL_API_VERSION} == 2) {
+ $mod_perl = 2;
+ require Apache2::RequestRec;
+ require Apache2::RequestIO;
+ require Apache2::RequestUtil;
+ require APR::Pool;
+ require ModPerl::Util;
+ require Apache2::Response;
+ $r = Apache2::RequestUtil->request;
+ }
+ else {
+ $r = Apache->request;
+ }
+ # If bytes have already been sent, then
+ # we print the message out directly.
+ # Otherwise we make a custom error
+ # handler to produce the doc for us.
+ if ($r->bytes_sent) {
+ $r->print($mess);
+ $mod_perl == 2 ? ModPerl::Util::exit(0) : $r->exit;
+ } else {
+ # MSIE won't display a custom 500 response unless it is >512 bytes!
+ if (defined($ENV{HTTP_USER_AGENT}) && $ENV{HTTP_USER_AGENT} =~ /MSIE/) {
+ $mess = "<!-- " . (' ' x 513) . " -->\n$mess";
+ }
+ $r->custom_response(500,$mess);
+ }
+ } else {
+ my $bytes_written = eval{tell STDOUT};
+ if (defined $bytes_written && $bytes_written > 0) {
+ print STDOUT $mess;
+ }
+ else {
+ print STDOUT "Status: 500\n";
+ print STDOUT "Content-type: text/html\n\n";
+ # MSIE won't display a custom 500 response unless it is >512 bytes!
+ if (defined($ENV{HTTP_USER_AGENT}) && $ENV{HTTP_USER_AGENT} =~ /MSIE/) {
+ $mess = "<!-- " . (' ' x 513) . " -->\n$mess";
+ }
+ print STDOUT $mess;
+ }
+ }
+
+ warningsToBrowser(1); # emit warnings before dying
+}
+
+# Cut and paste from CGI.pm so that we don't have the overhead of
+# always loading the entire CGI module.
+sub to_filehandle {
+ my $thingy = shift;
+ return undef unless $thingy;
+ return $thingy if UNIVERSAL::isa($thingy,'GLOB');
+ return $thingy if UNIVERSAL::isa($thingy,'FileHandle');
+ if (!ref($thingy)) {
+ my $caller = 1;
+ while (my $package = caller($caller++)) {
+ my($tmp) = $thingy=~/[\':]/ ? $thingy : "$package\:\:$thingy";
+ return $tmp if defined(fileno($tmp));
+ }
+ }
+ return undef;
+}
+
+1;
diff --git a/lib/CGI/Cookie.pm b/lib/CGI/Cookie.pm
new file mode 100644
index 0000000..d403b95
--- /dev/null
+++ b/lib/CGI/Cookie.pm
@@ -0,0 +1,537 @@
+package CGI::Cookie;
+
+use strict;
+use warnings;
+
+use if $] >= 5.019, 'deprecate';
+
+our $VERSION='4.21';
+
+use CGI::Util qw(rearrange unescape escape);
+use overload '""' => \&as_string, 'cmp' => \&compare, 'fallback' => 1;
+
+my $PERLEX = 0;
+# Turn on special checking for ActiveState's PerlEx
+$PERLEX++ if defined($ENV{'GATEWAY_INTERFACE'}) && $ENV{'GATEWAY_INTERFACE'} =~ /^CGI-PerlEx/;
+
+# Turn on special checking for mod_perl
+# PerlEx::DBI tries to fool DBI by setting MOD_PERL
+my $MOD_PERL = 0;
+if (exists $ENV{MOD_PERL} && ! $PERLEX) {
+ if (exists $ENV{MOD_PERL_API_VERSION} && $ENV{MOD_PERL_API_VERSION} == 2) {
+ $MOD_PERL = 2;
+ require Apache2::RequestUtil;
+ require APR::Table;
+ } else {
+ $MOD_PERL = 1;
+ require Apache;
+ }
+}
+
+# fetch a list of cookies from the environment and
+# return as a hash. the cookies are parsed as normal
+# escaped URL data.
+sub fetch {
+ my $class = shift;
+ my $raw_cookie = get_raw_cookie(@_) or return;
+ return $class->parse($raw_cookie);
+}
+
+# Fetch a list of cookies from the environment or the incoming headers and
+# return as a hash. The cookie values are not unescaped or altered in any way.
+ sub raw_fetch {
+ my $class = shift;
+ my $raw_cookie = get_raw_cookie(@_) or return;
+ my %results;
+ my($key,$value);
+
+ my @pairs = split("[;,] ?",$raw_cookie);
+ for my $pair ( @pairs ) {
+ $pair =~ s/^\s+|\s+$//g; # trim leading trailing whitespace
+ my ( $key, $value ) = split "=", $pair;
+
+ $value = defined $value ? $value : '';
+ $results{$key} = $value;
+ }
+ return wantarray ? %results : \%results;
+}
+
+sub get_raw_cookie {
+ my $r = shift;
+ $r ||= eval { $MOD_PERL == 2 ?
+ Apache2::RequestUtil->request() :
+ Apache->request } if $MOD_PERL;
+
+ return $r->headers_in->{'Cookie'} if $r;
+
+ die "Run $r->subprocess_env; before calling fetch()"
+ if $MOD_PERL and !exists $ENV{REQUEST_METHOD};
+
+ return $ENV{HTTP_COOKIE} || $ENV{COOKIE};
+}
+
+
+sub parse {
+ my ($self,$raw_cookie) = @_;
+ return wantarray ? () : {} unless $raw_cookie;
+
+ my %results;
+
+ my @pairs = split("[;,] ?",$raw_cookie);
+ for (@pairs) {
+ s/^\s+//;
+ s/\s+$//;
+
+ my($key,$value) = split("=",$_,2);
+
+ # Some foreign cookies are not in name=value format, so ignore
+ # them.
+ next if !defined($value);
+ my @values = ();
+ if ($value ne '') {
+ @values = map unescape($_),split(/[&;]/,$value.'&dmy');
+ pop @values;
+ }
+ $key = unescape($key);
+ # A bug in Netscape can cause several cookies with same name to
+ # appear. The FIRST one in HTTP_COOKIE is the most recent version.
+ $results{$key} ||= $self->new(-name=>$key,-value=>\@values);
+ }
+ return wantarray ? %results : \%results;
+}
+
+sub new {
+ my ( $class, @params ) = @_;
+ $class = ref( $class ) || $class;
+ # Ignore mod_perl request object--compatibility with Apache::Cookie.
+ shift if ref $params[0]
+ && eval { $params[0]->isa('Apache::Request::Req') || $params[0]->isa('Apache') };
+ my ( $name, $value, $path, $domain, $secure, $expires, $max_age, $httponly )
+ = rearrange(
+ [
+ 'NAME', [ 'VALUE', 'VALUES' ],
+ 'PATH', 'DOMAIN',
+ 'SECURE', 'EXPIRES',
+ 'MAX-AGE','HTTPONLY'
+ ],
+ @params
+ );
+ return undef unless defined $name and defined $value;
+ my $self = {};
+ bless $self, $class;
+ $self->name( $name );
+ $self->value( $value );
+ $path ||= "/";
+ $self->path( $path ) if defined $path;
+ $self->domain( $domain ) if defined $domain;
+ $self->secure( $secure ) if defined $secure;
+ $self->expires( $expires ) if defined $expires;
+ $self->max_age( $max_age ) if defined $max_age;
+ $self->httponly( $httponly ) if defined $httponly;
+ return $self;
+}
+
+sub as_string {
+ my $self = shift;
+ return "" unless $self->name;
+
+ no warnings; # some things may be undefined, that's OK.
+
+ my $name = escape( $self->name );
+ my $value = join "&", map { escape($_) } $self->value;
+ my @cookie = ( "$name=$value" );
+
+ push @cookie,"domain=".$self->domain if $self->domain;
+ push @cookie,"path=".$self->path if $self->path;
+ push @cookie,"expires=".$self->expires if $self->expires;
+ push @cookie,"max-age=".$self->max_age if $self->max_age;
+ push @cookie,"secure" if $self->secure;
+ push @cookie,"HttpOnly" if $self->httponly;
+
+ return join "; ", @cookie;
+}
+
+sub compare {
+ my ( $self, $value ) = @_;
+ return "$self" cmp $value;
+}
+
+sub bake {
+ my ($self, $r) = @_;
+
+ $r ||= eval {
+ $MOD_PERL == 2
+ ? Apache2::RequestUtil->request()
+ : Apache->request
+ } if $MOD_PERL;
+ if ($r) {
+ $r->headers_out->add('Set-Cookie' => $self->as_string);
+ } else {
+ require CGI;
+ print CGI::header(-cookie => $self);
+ }
+
+}
+
+# accessors
+sub name {
+ my ( $self, $name ) = @_;
+ $self->{'name'} = $name if defined $name;
+ return $self->{'name'};
+}
+
+sub value {
+ my ( $self, $value ) = @_;
+ if ( defined $value ) {
+ my @values
+ = ref $value eq 'ARRAY' ? @$value
+ : ref $value eq 'HASH' ? %$value
+ : ( $value );
+ $self->{'value'} = [@values];
+ }
+ return wantarray ? @{ $self->{'value'} } : $self->{'value'}->[0];
+}
+
+sub domain {
+ my ( $self, $domain ) = @_;
+ $self->{'domain'} = lc $domain if defined $domain;
+ return $self->{'domain'};
+}
+
+sub secure {
+ my ( $self, $secure ) = @_;
+ $self->{'secure'} = $secure if defined $secure;
+ return $self->{'secure'};
+}
+
+sub expires {
+ my ( $self, $expires ) = @_;
+ $self->{'expires'} = CGI::Util::expires($expires,'cookie') if defined $expires;
+ return $self->{'expires'};
+}
+
+sub max_age {
+ my ( $self, $max_age ) = @_;
+ $self->{'max-age'} = CGI::Util::expire_calc($max_age)-time() if defined $max_age;
+ return $self->{'max-age'};
+}
+
+sub path {
+ my ( $self, $path ) = @_;
+ $self->{'path'} = $path if defined $path;
+ return $self->{'path'};
+}
+
+
+sub httponly { # HttpOnly
+ my ( $self, $httponly ) = @_;
+ $self->{'httponly'} = $httponly if defined $httponly;
+ return $self->{'httponly'};
+}
+
+1;
+
+=head1 NAME
+
+CGI::Cookie - Interface to HTTP Cookies
+
+=head1 SYNOPSIS
+
+ use CGI qw/:standard/;
+ use CGI::Cookie;
+
+ # Create new cookies and send them
+ $cookie1 = CGI::Cookie->new(-name=>'ID',-value=>123456);
+ $cookie2 = CGI::Cookie->new(-name=>'preferences',
+ -value=>{ font => Helvetica,
+ size => 12 }
+ );
+ print header(-cookie=>[$cookie1,$cookie2]);
+
+ # fetch existing cookies
+ %cookies = CGI::Cookie->fetch;
+ $id = $cookies{'ID'}->value;
+
+ # create cookies returned from an external source
+ %cookies = CGI::Cookie->parse($ENV{COOKIE});
+
+=head1 DESCRIPTION
+
+CGI::Cookie is an interface to HTTP/1.1 cookies, a mechanism
+that allows Web servers to store persistent information on
+the browser's side of the connection. Although CGI::Cookie is
+intended to be used in conjunction with CGI.pm (and is in fact used by
+it internally), you can use this module independently.
+
+For full information on cookies see
+
+ https://tools.ietf.org/html/rfc6265
+
+=head1 USING CGI::Cookie
+
+CGI::Cookie is object oriented. Each cookie object has a name and a
+value. The name is any scalar value. The value is any scalar or
+array value (associative arrays are also allowed). Cookies also have
+several optional attributes, including:
+
+=over 4
+
+=item B<1. expiration date>
+
+The expiration date tells the browser how long to hang on to the
+cookie. If the cookie specifies an expiration date in the future, the
+browser will store the cookie information in a disk file and return it
+to the server every time the user reconnects (until the expiration
+date is reached). If the cookie species an expiration date in the
+past, the browser will remove the cookie from the disk file. If the
+expiration date is not specified, the cookie will persist only until
+the user quits the browser.
+
+=item B<2. domain>
+
+This is a partial or complete domain name for which the cookie is
+valid. The browser will return the cookie to any host that matches
+the partial domain name. For example, if you specify a domain name
+of ".capricorn.com", then the browser will return the cookie to
+Web servers running on any of the machines "www.capricorn.com",
+"ftp.capricorn.com", "feckless.capricorn.com", etc. Domain names
+must contain at least two periods to prevent attempts to match
+on top level domains like ".edu". If no domain is specified, then
+the browser will only return the cookie to servers on the host the
+cookie originated from.
+
+=item B<3. path>
+
+If you provide a cookie path attribute, the browser will check it
+against your script's URL before returning the cookie. For example,
+if you specify the path "/cgi-bin", then the cookie will be returned
+to each of the scripts "/cgi-bin/tally.pl", "/cgi-bin/order.pl", and
+"/cgi-bin/customer_service/complain.pl", but not to the script
+"/cgi-private/site_admin.pl". By default, the path is set to "/", so
+that all scripts at your site will receive the cookie.
+
+=item B<4. secure flag>
+
+If the "secure" attribute is set, the cookie will only be sent to your
+script if the CGI request is occurring on a secure channel, such as SSL.
+
+=item B<5. httponly flag>
+
+If the "httponly" attribute is set, the cookie will only be accessible
+through HTTP Requests. This cookie will be inaccessible via JavaScript
+(to prevent XSS attacks).
+
+This feature is supported by nearly all modern browsers.
+
+See these URLs for more information:
+
+ http://msdn.microsoft.com/en-us/library/ms533046.aspx
+ http://www.browserscope.org/?category=security&v=top
+
+=back
+
+=head2 Creating New Cookies
+
+ my $c = CGI::Cookie->new(-name => 'foo',
+ -value => 'bar',
+ -expires => '+3M',
+ '-max-age' => '+3M',
+ -domain => '.capricorn.com',
+ -path => '/cgi-bin/database',
+ -secure => 1
+ );
+
+Create cookies from scratch with the B<new> method. The B<-name> and
+B<-value> parameters are required. The name must be a scalar value.
+The value can be a scalar, an array reference, or a hash reference.
+(At some point in the future cookies will support one of the Perl
+object serialization protocols for full generality).
+
+B<-expires> accepts any of the relative or absolute date formats
+recognized by CGI.pm, for example "+3M" for three months in the
+future. See CGI.pm's documentation for details.
+
+B<-max-age> accepts the same data formats as B<< -expires >>, but sets a
+relative value instead of an absolute like B<< -expires >>. This is intended to be
+more secure since a clock could be changed to fake an absolute time. In
+practice, as of 2011, C<< -max-age >> still does not enjoy the widespread support
+that C<< -expires >> has. You can set both, and browsers that support
+C<< -max-age >> should ignore the C<< Expires >> header. The drawback
+to this approach is the bit of bandwidth for sending an extra header on each cookie.
+
+B<-domain> points to a domain name or to a fully qualified host name.
+If not specified, the cookie will be returned only to the Web server
+that created it.
+
+B<-path> points to a partial URL on the current server. The cookie
+will be returned to all URLs beginning with the specified path. If
+not specified, it defaults to '/', which returns the cookie to all
+pages at your site.
+
+B<-secure> if set to a true value instructs the browser to return the
+cookie only when a cryptographic protocol is in use.
+
+B<-httponly> if set to a true value, the cookie will not be accessible
+via JavaScript.
+
+For compatibility with Apache::Cookie, you may optionally pass in
+a mod_perl request object as the first argument to C<new()>. It will
+simply be ignored:
+
+ my $c = CGI::Cookie->new($r,
+ -name => 'foo',
+ -value => ['bar','baz']);
+
+=head2 Sending the Cookie to the Browser
+
+The simplest way to send a cookie to the browser is by calling the bake()
+method:
+
+ $c->bake;
+
+This will print the Set-Cookie HTTP header to STDOUT using CGI.pm. CGI.pm
+will be loaded for this purpose if it is not already. Otherwise CGI.pm is not
+required or used by this module.
+
+Under mod_perl, pass in an Apache request object:
+
+ $c->bake($r);
+
+If you want to set the cookie yourself, Within a CGI script you can send
+a cookie to the browser by creating one or more Set-Cookie: fields in the
+HTTP header. Here is a typical sequence:
+
+ my $c = CGI::Cookie->new(-name => 'foo',
+ -value => ['bar','baz'],
+ -expires => '+3M');
+
+ print "Set-Cookie: $c\n";
+ print "Content-Type: text/html\n\n";
+
+To send more than one cookie, create several Set-Cookie: fields.
+
+If you are using CGI.pm, you send cookies by providing a -cookie
+argument to the header() method:
+
+ print header(-cookie=>$c);
+
+Mod_perl users can set cookies using the request object's header_out()
+method:
+
+ $r->headers_out->set('Set-Cookie' => $c);
+
+Internally, Cookie overloads the "" operator to call its as_string()
+method when incorporated into the HTTP header. as_string() turns the
+Cookie's internal representation into an RFC-compliant text
+representation. You may call as_string() yourself if you prefer:
+
+ print "Set-Cookie: ",$c->as_string,"\n";
+
+=head2 Recovering Previous Cookies
+
+ %cookies = CGI::Cookie->fetch;
+
+B<fetch> returns an associative array consisting of all cookies
+returned by the browser. The keys of the array are the cookie names. You
+can iterate through the cookies this way:
+
+ %cookies = CGI::Cookie->fetch;
+ for (keys %cookies) {
+ do_something($cookies{$_});
+ }
+
+In a scalar context, fetch() returns a hash reference, which may be more
+efficient if you are manipulating multiple cookies.
+
+CGI.pm uses the URL escaping methods to save and restore reserved characters
+in its cookies. If you are trying to retrieve a cookie set by a foreign server,
+this escaping method may trip you up. Use raw_fetch() instead, which has the
+same semantics as fetch(), but performs no unescaping.
+
+You may also retrieve cookies that were stored in some external
+form using the parse() class method:
+
+ $COOKIES = `cat /usr/tmp/Cookie_stash`;
+ %cookies = CGI::Cookie->parse($COOKIES);
+
+If you are in a mod_perl environment, you can save some overhead by
+passing the request object to fetch() like this:
+
+ CGI::Cookie->fetch($r);
+
+If the value passed to parse() is undefined, an empty array will returned in list
+context, and an empty hashref will be returned in scalar context.
+
+=head2 Manipulating Cookies
+
+Cookie objects have a series of accessor methods to get and set cookie
+attributes. Each accessor has a similar syntax. Called without
+arguments, the accessor returns the current value of the attribute.
+Called with an argument, the accessor changes the attribute and
+returns its new value.
+
+=over 4
+
+=item B<name()>
+
+Get or set the cookie's name. Example:
+
+ $name = $c->name;
+ $new_name = $c->name('fred');
+
+=item B<value()>
+
+Get or set the cookie's value. Example:
+
+ $value = $c->value;
+ @new_value = $c->value(['a','b','c','d']);
+
+B<value()> is context sensitive. In a list context it will return
+the current value of the cookie as an array. In a scalar context it
+will return the B<first> value of a multivalued cookie.
+
+=item B<domain()>
+
+Get or set the cookie's domain.
+
+=item B<path()>
+
+Get or set the cookie's path.
+
+=item B<expires()>
+
+Get or set the cookie's expiration time.
+
+=item B<max_age()>
+
+Get or set the cookie's max_age value.
+
+=back
+
+
+=head1 AUTHOR INFORMATION
+
+The CGI.pm distribution is copyright 1995-2007, Lincoln D. Stein. It is
+distributed under GPL and the Artistic License 2.0. It is currently
+maintained by Lee Johnson with help from many contributors.
+
+Address bug reports and comments to: https://github.com/leejo/CGI.pm/issues
+
+The original bug tracker can be found at: https://rt.cpan.org/Public/Dist/Display.html?Queue=CGI.pm
+
+When sending bug reports, please provide the version of CGI.pm, the version of
+Perl, the name and version of your Web server, and the name and version of the
+operating system you are using. If the problem is even remotely browser
+dependent, please provide information about the affected browsers as well.
+
+=head1 BUGS
+
+This section intentionally left blank.
+
+=head1 SEE ALSO
+
+L<CGI::Carp>, L<CGI>
+
+L<RFC 2109|http://www.ietf.org/rfc/rfc2109.txt>, L<RFC 2695|http://www.ietf.org/rfc/rfc2965.txt>
+
+=cut
diff --git a/lib/CGI/File/Temp.pm b/lib/CGI/File/Temp.pm
new file mode 100644
index 0000000..0c8136a
--- /dev/null
+++ b/lib/CGI/File/Temp.pm
@@ -0,0 +1,39 @@
+# this is a back compatibility wrapper around File::Temp. DO NOT
+# use this package outside of CGI, i won't provide any help if
+# you use it directly and your code breaks horribly.
+package CGI::File::Temp;
+
+$CGI::File::Temp::VERSION = '4.21';
+
+use parent File::Temp;
+use parent Fh;
+
+use overload
+ '""' => \&asString,
+ 'cmp' => \&compare,
+ 'fallback'=>1;
+
+# back compatibility method since we now return a File::Temp object
+# as the filehandle (which isa IO::Handle) so calling ->handle on
+# it will fail. FIXME: deprecate this method in v5+
+sub handle { return shift; };
+
+sub compare {
+ my ( $self,$value ) = @_;
+ return "$self" cmp $value;
+}
+
+sub _mp_filename {
+ my ( $self,$filename ) = @_;
+ ${*$self}->{ _mp_filename } = $filename
+ if $filename;
+ return ${*$self}->{_mp_filename};
+}
+
+sub asString {
+ my ( $self ) = @_;
+ return $self->_mp_filename;
+}
+
+1;
+
diff --git a/lib/CGI/HTML/Functions.pm b/lib/CGI/HTML/Functions.pm
new file mode 100644
index 0000000..e4983ca
--- /dev/null
+++ b/lib/CGI/HTML/Functions.pm
@@ -0,0 +1,8 @@
+package CGI::HTML::Functions;
+
+use strict;
+use warnings;
+
+# nothing here yet... may move functions here in the long term
+
+1;
diff --git a/lib/CGI/HTML/Functions.pod b/lib/CGI/HTML/Functions.pod
new file mode 100644
index 0000000..8c00c27
--- /dev/null
+++ b/lib/CGI/HTML/Functions.pod
@@ -0,0 +1,1927 @@
+=head1 NAME
+
+CGI::HTML::Functions - Documentation for CGI.pm Legacy HTML Functionality
+
+=head1 SYNOPSIS
+
+Nothing here - please do not use this functionality, it is considered to
+be legacy and essentially deprecated. This documentation exists solely to
+aid in maintenance and migration of legacy code using this functionality.
+
+This functionality is likely to be removed in future versions of CGI.pm so
+you are strongly encouraged to migrate away from it. If you are working
+on new code you should be using a template engine. For more information see
+L<CGI::Alternatives>.
+
+=head1 DESCRIPTION
+
+The documentation here should be considered an addendum to the sections in the
+L<CGI> documentation - the sections here are named the same as those within the
+CGI perldoc.
+
+=head1 Calling CGI.pm routines
+
+HTML tag functions have both attributes (the attribute="value" pairs within the
+tag itself) and contents (the part between the opening and closing pairs). To
+distinguish between attributes and contents, CGI.pm uses the convention of
+passing HTML attributes as a hash reference as the first argument, and the
+contents, if any, as any subsequent arguments. It works out like
+this:
+
+ Code Generated HTML
+ ---- --------------
+ h1() <h1 />
+ h1('some','contents'); <h1>some contents</h1>
+ h1({-align=>left}); <h1 align="LEFT">
+ h1({-align=>left},'contents'); <h1 align="LEFT">contents</h1>
+
+Many newcomers to CGI.pm are puzzled by the difference between the calling
+conventions for the HTML shortcuts, which require curly braces around the HTML
+tag attributes, and the calling conventions for other routines, which manage
+to generate attributes without the curly brackets. Don't be confused. As a
+convenience the curly braces are optional in all but the HTML shortcuts. If you
+like, you can use curly braces when calling any routine that takes named
+arguments. For example:
+
+ print $q->header( { -type => 'image/gif', -expires => '+3d' } );
+
+If you use warnings, you will be warned that some CGI.pm argument names
+conflict with built-in perl functions. The most frequent of these is the
+-values argument, used to create multi-valued menus, radio button clusters
+and the like. To get around this warning, you have several choices:
+
+=over 4
+
+=item 1.
+
+Use another name for the argument, if one is available.
+For example, -value is an alias for -values.
+
+=item 2.
+
+Change the capitalization, e.g. -Values
+
+=item 3.
+
+Put quotes around the argument name, e.g. '-values'
+
+=back
+
+=head2 Function-oriented interface HTML exports
+
+Here is a list of the HTML related function sets you can import:
+
+=over 4
+
+=item B<:form>
+
+Import all fill-out form generating methods, such as B<textfield()>.
+
+=item B<:html2>
+
+Import all methods that generate HTML 2.0 standard elements.
+
+=item B<:html3>
+
+Import all methods that generate HTML 3.0 elements (such as
+<table>, <super> and <sub>).
+
+=item B<:html4>
+
+Import all methods that generate HTML 4 elements (such as
+<abbrev>, <acronym> and <thead>).
+
+=item B<:netscape>
+
+Import the <blink>, <fontsize> and <center> tags.
+
+=item B<:html>
+
+Import all HTML-generating shortcuts (i.e. 'html2', 'html3', 'html4' and 'netscape')
+
+=item B<:standard>
+
+Import "standard" features, 'html2', 'html3', 'html4', 'ssl', 'form' and 'cgi'.
+
+=back
+
+If you import any of the state-maintaining CGI or form-generating methods,
+a default CGI object will be created and initialized automatically the first
+time you use any of the methods that require one to be present. This includes
+B<param()>, B<textfield()>, B<submit()> and the like. (If you need direct access
+to the CGI object, you can find it in the global variable B<$CGI::Q>).
+
+=head2 Pragmas
+
+Additional HTML generation related pragms:
+
+=over 4
+
+=item -nosticky
+
+By default the CGI module implements a state-preserving behavior called
+"sticky" fields. The way this works is that if you are regenerating a form,
+the methods that generate the form field values will interrogate param()
+to see if similarly-named parameters are present in the query string. If
+they find a like-named parameter, they will use it to set their default values.
+
+Sometimes this isn't what you want. The B<-nosticky> pragma prevents this
+behavior. You can also selectively change the sticky behavior in each element
+that you generate.
+
+=item -tabindex
+
+Automatically add tab index attributes to each form field. With this option
+turned off, you can still add tab indexes manually by passing a -tabindex
+option to each field-generating method.
+
+=item -no_xhtml
+
+By default, CGI.pm versions 2.69 and higher emit XHTML
+(http://www.w3.org/TR/xhtml1/). The -no_xhtml pragma disables this feature.
+Thanks to Michalis Kabrianis <kabrianis@hellug.gr> for this feature.
+
+If start_html()'s -dtd parameter specifies an HTML 2.0, 3.2, 4.0 or 4.01 DTD,
+XHTML will automatically be disabled without needing to use this pragma.
+
+=back
+
+=head2 Special forms for importing HTML-tag functions
+
+Many of the methods generate HTML tags. As described below, tag functions
+automatically generate both the opening and closing tags. For example:
+
+ print h1('Level 1 Header');
+
+produces
+
+ <h1>Level 1 Header</h1>
+
+There will be some times when you want to produce the start and end tags
+yourself. In this case, you can use the form start_I<tag_name> and
+end_I<tag_name>, as in:
+
+ print start_h1,'Level 1 Header',end_h1;
+
+=head2 Creating the HTML document header
+
+ print start_html(
+ -title => 'Secrets of the Pyramids',
+ -author => 'fred@capricorn.org',
+ -base => 'true',
+ -target => '_blank',
+ -meta => {'keywords'=>'pharaoh secret mummy',
+ 'copyright' => 'copyright 1996 King Tut'},
+ -style => {'src'=>'/styles/style1.css'},
+ -BGCOLOR => 'blue'
+ );
+
+The start_html() routine creates the top of the page, along with a lot of
+optional information that controls the page's appearance and behavior.
+
+This method returns a canned HTML header and the opening <body> tag. All
+parameters are optional. In the named parameter form, recognized parameters
+are -title, -author, -base, -xbase, -dtd, -lang and -target (see below for the
+explanation). Any additional parameters you provide, such as the unofficial
+BGCOLOR attribute, are added to the <body> tag. Additional parameters must be
+proceeded by a hyphen.
+
+The argument B<-xbase> allows you to provide an HREF for the <base> tag different
+from the current location, as in
+
+ -xbase => "http://home.mcom.com/"
+
+All relative links will be interpreted relative to this tag.
+
+The argument B<-target> allows you to provide a default target frame for all the
+links and fill-out forms on the page. B<This is a non-standard HTTP feature>
+B<which only works with some browsers!>
+
+ -target => "answer_window"
+
+All relative links will be interpreted relative to this tag. You add arbitrary
+meta information to the header with the B<-meta> argument. This argument expects
+a reference to a hash containing name/value pairs of meta information. These will
+be turned into a series of header <meta> tags that look something like this:
+
+ <meta name="keywords" content="pharaoh secret mummy">
+ <meta name="description" content="copyright 1996 King Tut">
+
+To create an HTTP-EQUIV type of <meta> tag, use B<-head>, described below.
+
+The B<-style> argument is used to incorporate cascading stylesheets into your
+code. See the section on CASCADING STYLESHEETS for more information.
+
+The B<-lang> argument is used to incorporate a language attribute into the <html>
+tag. For example:
+
+ print $q->start_html( -lang => 'fr-CA' );
+
+The default if not specified is "en-US" for US English, unless the -dtd parameter
+specifies an HTML 2.0 or 3.2 DTD, in which case the lang attribute is left off.
+You can force the lang attribute to left off in other cases by passing an empty
+string (-lang=>'').
+
+The B<-encoding> argument can be used to specify the character set for XHTML. It
+defaults to iso-8859-1 if not specified.
+
+The B<-dtd> argument can be used to specify a public DTD identifier string. For
+example:
+
+ -dtd => '-//W3C//DTD HTML 4.01 Transitional//EN')
+
+Alternatively, it can take public and system DTD identifiers as an array:
+
+ -dtd => [
+ '-//W3C//DTD HTML 4.01 Transitional//EN',
+ 'http://www.w3.org/TR/html4/loose.dtd'
+ ]
+
+For the public DTD identifier to be considered, it must be valid. Otherwise it
+will be replaced by the default DTD. If the public DTD contains 'XHTML', CGI.pm
+will emit XML.
+
+The B<-declare_xml> argument, when used in conjunction with XHTML, will put a
+<?xml> declaration at the top of the HTML header. The sole purpose of this
+declaration is to declare the character set encoding. In the absence of
+-declare_xml, the output HTML will contain a <meta> tag that specifies the
+encoding, allowing the HTML to pass most validators. The default for -declare_xml
+is false.
+
+You can place other arbitrary HTML elements to the <head> section with the
+B<-head> tag. For example, to place a <link> element in the head section, use
+this:
+
+ print start_html(
+ -head => Link({
+ -rel => 'shortcut icon',
+ -href => 'favicon.ico'
+ })
+ );
+
+To incorporate multiple HTML elements into the <head> section, just pass an
+array reference:
+
+ print start_html(
+ -head => [
+ Link({
+ -rel => 'next',
+ -href => 'http://www.capricorn.com/s2.html'
+ }),
+ Link({
+ -rel => 'previous',
+ -href => 'http://www.capricorn.com/s1.html'
+ })
+ ]
+ );
+
+And here's how to create an HTTP-EQUIV <meta> tag:
+
+ print start_html(
+ -head => meta({
+ -http_equiv => 'Content-Type',
+ -content => 'text/html'
+ })
+ );
+
+
+JAVASCRIPTING: The B<-script>, B<-noScript>, B<-onLoad>, B<-onMouseOver>,
+B<-onMouseOut> and B<-onUnload> parameters are used to add JavaScript calls to
+your pages. B<-script> should point to a block of text containing JavaScript
+function definitions. This block will be placed within a <script> block inside
+the HTML (not HTTP) header. The block is placed in the header in order to give
+your page a fighting chance of having all its JavaScript functions in place even
+if the user presses the stop button before the page has loaded completely. CGI.pm
+attempts to format the script in such a way that JavaScript-naive browsers will
+not choke on the code: unfortunately there are some browsers that get confused by
+it nevertheless.
+
+The B<-onLoad> and B<-onUnload> parameters point to fragments of JavaScript code
+to execute when the page is respectively opened and closed by the browser.
+Usually these parameters are calls to functions defined in the B<-script> field:
+
+ $query = CGI->new;
+ print header;
+ $JSCRIPT = <<END;
+ // Ask a silly question
+ function riddle_me_this() {
+ var r = prompt(
+ "What walks on four legs in the morning, " +
+ "two legs in the afternoon, " +
+ "and three legs in the evening?"
+ );
+ response(r);
+ }
+ // Get a silly answer
+ function response(answer) {
+ if (answer == "man")
+ alert("Right you are!");
+ else
+ alert("Wrong! Guess again.");
+ }
+ END
+ print start_html(
+ -title => 'The Riddle of the Sphinx',
+ -script => $JSCRIPT
+ );
+
+Use the B<-noScript> parameter to pass some HTML text that will be displayed on
+browsers that do not have JavaScript (or browsers where JavaScript is turned
+off).
+
+The <script> tag, has several attributes including "type", "charset" and "src".
+"src" allows you to keep JavaScript code in an external file. To use these
+attributes pass a HASH reference in the B<-script> parameter containing one or
+more of -type, -src, or -code:
+
+ print $q->start_html(
+ -title => 'The Riddle of the Sphinx',
+ -script => {
+ -type => 'JAVASCRIPT',
+ -src => '/javascript/sphinx.js'}
+ );
+
+ print $q->(
+ -title => 'The Riddle of the Sphinx',
+ -script => {
+ -type => 'PERLSCRIPT',
+ -code => 'print "hello world!\n;"'
+ }
+ );
+
+A final feature allows you to incorporate multiple <script> sections into the
+header. Just pass the list of script sections as an array reference. This allows
+you to specify different source files for different dialects of JavaScript.
+Example:
+
+ print $q->start_html(
+ -title => 'The Riddle of the Sphinx',
+ -script => [
+ {
+ -type => 'text/javascript',
+ -src => '/javascript/utilities10.js'
+ },
+ {
+ -type => 'text/javascript',
+ -src => '/javascript/utilities11.js'
+ },
+ {
+ -type => 'text/jscript',
+ -src => '/javascript/utilities12.js'
+ },
+ {
+ -type => 'text/ecmascript',
+ -src => '/javascript/utilities219.js'
+ }
+ ]
+ );
+
+The option "-language" is a synonym for -type, and is supported for backwards
+compatibility.
+
+The old-style positional parameters are as follows:
+
+B<Parameters:>
+
+=over 4
+
+=item 1.
+
+The title
+
+=item 2.
+
+The author's e-mail address (will create a <link rev="MADE"> tag if present
+
+=item 3.
+
+A 'true' flag if you want to include a <base> tag in the header. This helps
+resolve relative addresses to absolute ones when the document is moved, but
+makes the document hierarchy non-portable. Use with care!
+
+=back
+
+Other parameters you want to include in the <body> tag may be appended to these.
+This is a good place to put HTML extensions, such as colors and wallpaper
+patterns.
+
+=head2 Ending the Html document:
+
+ print $q->end_html;
+
+This ends an HTML document by printing the </body></html> tags.
+
+=head1 CREATING STANDARD HTML ELEMENTS:
+
+CGI.pm defines general HTML shortcut methods for many HTML tags. HTML shortcuts are named after a single
+HTML element and return a fragment of HTML text. Example:
+
+ print $q->blockquote(
+ "Many years ago on the island of",
+ $q->a({href=>"http://crete.org/"},"Crete"),
+ "there lived a Minotaur named",
+ $q->strong("Fred."),
+ ),
+ $q->hr;
+
+This results in the following HTML code (extra newlines have been
+added for readability):
+
+ <blockquote>
+ Many years ago on the island of
+ <a href="http://crete.org/">Crete</a> there lived
+ a minotaur named <strong>Fred.</strong>
+ </blockquote>
+ <hr>
+
+If you find the syntax for calling the HTML shortcuts awkward, you can
+import them into your namespace and dispense with the object syntax
+completely (see the next section for more details):
+
+ use CGI ':standard';
+ print blockquote(
+ "Many years ago on the island of",
+ a({href=>"http://crete.org/"},"Crete"),
+ "there lived a minotaur named",
+ strong("Fred."),
+ ),
+ hr;
+
+=head2 Providing arguments to HTML shortcuts
+
+The HTML methods will accept zero, one or multiple arguments. If you
+provide no arguments, you get a single tag:
+
+ print hr; # <hr>
+
+If you provide one or more string arguments, they are concatenated
+together with spaces and placed between opening and closing tags:
+
+ print h1("Chapter","1"); # <h1>Chapter 1</h1>"
+
+If the first argument is a hash reference, then the keys
+and values of the hash become the HTML tag's attributes:
+
+ print a({-href=>'fred.html',-target=>'_new'},
+ "Open a new frame");
+
+ <a href="fred.html",target="_new">Open a new frame</a>
+
+You may dispense with the dashes in front of the attribute names if
+you prefer:
+
+ print img {src=>'fred.gif',align=>'LEFT'};
+
+ <img align="LEFT" src="fred.gif">
+
+Sometimes an HTML tag attribute has no argument. For example, ordered
+lists can be marked as COMPACT. The syntax for this is an argument that
+that points to an undef string:
+
+ print ol({compact=>undef},li('one'),li('two'),li('three'));
+
+Prior to CGI.pm version 2.41, providing an empty ('') string as an
+attribute argument was the same as providing undef. However, this has
+changed in order to accommodate those who want to create tags of the form
+<img alt="">. The difference is shown in these two pieces of code:
+
+ CODE RESULT
+ img({alt=>undef}) <img alt>
+ img({alt=>''}) <img alt="">
+
+=head2 The distributive property of HTML shortcuts
+
+One of the cool features of the HTML shortcuts is that they are
+distributive. If you give them an argument consisting of a
+B<reference> to a list, the tag will be distributed across each
+element of the list. For example, here's one way to make an ordered
+list:
+
+ print ul(
+ li({-type=>'disc'},['Sneezy','Doc','Sleepy','Happy'])
+ );
+
+This example will result in HTML output that looks like this:
+
+ <ul>
+ <li type="disc">Sneezy</li>
+ <li type="disc">Doc</li>
+ <li type="disc">Sleepy</li>
+ <li type="disc">Happy</li>
+ </ul>
+
+This is extremely useful for creating tables. For example:
+
+ print table({-border=>undef},
+ caption('When Should You Eat Your Vegetables?'),
+ Tr({-align=>'CENTER',-valign=>'TOP'},
+ [
+ th(['Vegetable', 'Breakfast','Lunch','Dinner']),
+ td(['Tomatoes' , 'no', 'yes', 'yes']),
+ td(['Broccoli' , 'no', 'no', 'yes']),
+ td(['Onions' , 'yes','yes', 'yes'])
+ ]
+ )
+ );
+
+=head2 HTML shortcuts and list interpolation
+
+Consider this bit of code:
+
+ print blockquote(em('Hi'),'mom!'));
+
+It will ordinarily return the string that you probably expect, namely:
+
+ <blockquote><em>Hi</em> mom!</blockquote>
+
+Note the space between the element "Hi" and the element "mom!".
+CGI.pm puts the extra space there using array interpolation, which is
+controlled by the magic $" variable. Sometimes this extra space is
+not what you want, for example, when you are trying to align a series
+of images. In this case, you can simply change the value of $" to an
+empty string.
+
+ {
+ local($") = '';
+ print blockquote(em('Hi'),'mom!'));
+ }
+
+I suggest you put the code in a block as shown here. Otherwise the
+change to $" will affect all subsequent code until you explicitly
+reset it.
+
+=head2 Non-standard HTML shortcuts
+
+A few HTML tags don't follow the standard pattern for various
+reasons.
+
+B<comment()> generates an HTML comment (<!-- comment -->). Call it
+like
+
+ print comment('here is my comment');
+
+Because of conflicts with built-in perl functions, the following functions
+begin with initial caps:
+
+ Select
+ Tr
+ Link
+ Delete
+ Accept
+ Sub
+
+In addition, start_html(), end_html(), start_form(), end_form(),
+start_multipart_form() and all the fill-out form tags are special.
+See their respective sections.
+
+=head2 Autoescaping HTML
+
+By default, all HTML that is emitted by the form-generating functions
+is passed through a function called escapeHTML():
+
+=over 4
+
+=item $escaped_string = escapeHTML("unescaped string");
+
+Escape HTML formatting characters in a string. Internally this calls
+L<HTML::Entities> (encode_entities) so really you should just use that
+instead - the default list of chars that will be encoded (passed to the
+HTML::Entities encode_entities method) is:
+
+ & < > " \x8b \x9b '
+
+you can control this list by setting the value of $CGI::ENCODE_ENTITIES:
+
+ # only encode < >
+ $CGI::ENCODE_ENTITIES = q{<>}
+
+if you want to encode B<all> entities then undef $CGI::ENCODE_ENTITIES:
+
+ # encode all entities
+ $CGI::ENCODE_ENTITIES = undef;
+
+=back
+
+The automatic escaping does not apply to other shortcuts, such as
+h1(). You should call escapeHTML() yourself on untrusted data in
+order to protect your pages against nasty tricks that people may enter
+into guestbooks, etc.. To change the character set, use charset().
+To turn autoescaping off completely, use autoEscape(0):
+
+=over 4
+
+=item $charset = charset([$charset]);
+
+Get or set the current character set.
+
+=item $flag = autoEscape([$flag]);
+
+Get or set the value of the autoescape flag.
+
+=back
+
+=head1 CREATING FILL-OUT FORMS:
+
+I<General note> The various form-creating methods all return strings
+to the caller, containing the tag or tags that will create the requested
+form element. You are responsible for actually printing out these strings.
+It's set up this way so that you can place formatting tags
+around the form elements.
+
+I<Another note> The default values that you specify for the forms are only
+used the B<first> time the script is invoked (when there is no query
+string). On subsequent invocations of the script (when there is a query
+string), the former values are used even if they are blank.
+
+If you want to change the value of a field from its previous value, you have two
+choices:
+
+(1) call the param() method to set it.
+
+(2) use the -override (alias -force) parameter (a new feature in version 2.15).
+This forces the default value to be used, regardless of the previous value:
+
+ print textfield(-name=>'field_name',
+ -default=>'starting value',
+ -override=>1,
+ -size=>50,
+ -maxlength=>80);
+
+I<Yet another note> By default, the text and labels of form elements are
+escaped according to HTML rules. This means that you can safely use
+"<CLICK ME>" as the label for a button. However, it also interferes with
+your ability to incorporate special HTML character sequences, such as &Aacute;,
+into your fields. If you wish to turn off automatic escaping, call the
+autoEscape() method with a false value immediately after creating the CGI object:
+
+ $query = CGI->new;
+ $query->autoEscape(0);
+
+Note that autoEscape() is exclusively used to effect the behavior of how some
+CGI.pm HTML generation functions handle escaping. Calling escapeHTML()
+explicitly will always escape the HTML.
+
+I<A Lurking Trap!> Some of the form-element generating methods return
+multiple tags. In a scalar context, the tags will be concatenated
+together with spaces, or whatever is the current value of the $"
+global. In a list context, the methods will return a list of
+elements, allowing you to modify them if you wish. Usually you will
+not notice this behavior, but beware of this:
+
+ printf("%s\n",end_form())
+
+end_form() produces several tags, and only the first of them will be
+printed because the format only expects one value.
+
+<p>
+
+
+=head2 Creating an isindex tag
+
+ print isindex(-action=>$action);
+
+ -or-
+
+ print isindex($action);
+
+Prints out an <isindex> tag. Not very exciting. The parameter
+-action specifies the URL of the script to process the query. The
+default is to process the query with the current script.
+
+=head2 Starting and ending a form
+
+ print start_form(-method=>$method,
+ -action=>$action,
+ -enctype=>$encoding);
+ <... various form stuff ...>
+ print end_form;
+
+ -or-
+
+ print start_form($method,$action,$encoding);
+ <... various form stuff ...>
+ print end_form;
+
+start_form() will return a <form> tag with the optional method,
+action and form encoding that you specify. The defaults are:
+
+ method: POST
+ action: this script
+ enctype: application/x-www-form-urlencoded for non-XHTML
+ multipart/form-data for XHTML, see multipart/form-data below.
+
+end_form() returns the closing </form> tag.
+
+start_form()'s enctype argument tells the browser how to package the various
+fields of the form before sending the form to the server. Two
+values are possible:
+
+=over 4
+
+=item B<application/x-www-form-urlencoded>
+
+This is the older type of encoding. It is compatible with many CGI scripts and is
+suitable for short fields containing text data. For your
+convenience, CGI.pm stores the name of this encoding
+type in B<&CGI::URL_ENCODED>.
+
+=item B<multipart/form-data>
+
+This is the newer type of encoding.
+It is suitable for forms that contain very large fields or that
+are intended for transferring binary data. Most importantly,
+it enables the "file upload" feature. For
+your convenience, CGI.pm stores the name of this encoding type
+in B<&CGI::MULTIPART>
+
+Forms that use this type of encoding are not easily interpreted
+by CGI scripts unless they use CGI.pm or another library designed
+to handle them.
+
+If XHTML is activated (the default), then forms will be automatically
+created using this type of encoding.
+
+=back
+
+The start_form() method uses the older form of encoding by
+default unless XHTML is requested. If you want to use the
+newer form of encoding by default, you can call
+B<start_multipart_form()> instead of B<start_form()>. The
+method B<end_multipart_form()> is an alias to B<end_form()>.
+
+JAVASCRIPTING: The B<-name> and B<-onSubmit> parameters are provided
+for use with JavaScript. The -name parameter gives the
+form a name so that it can be identified and manipulated by
+JavaScript functions. -onSubmit should point to a JavaScript
+function that will be executed just before the form is submitted to your
+server. You can use this opportunity to check the contents of the form
+for consistency and completeness. If you find something wrong, you
+can put up an alert box or maybe fix things up yourself. You can
+abort the submission by returning false from this function.
+
+Usually the bulk of JavaScript functions are defined in a <script>
+block in the HTML header and -onSubmit points to one of these function
+call. See start_html() for details.
+
+=head2 Form elements
+
+After starting a form, you will typically create one or more
+textfields, popup menus, radio groups and other form elements. Each
+of these elements takes a standard set of named arguments. Some
+elements also have optional arguments. The standard arguments are as
+follows:
+
+=over 4
+
+=item B<-name>
+
+The name of the field. After submission this name can be used to
+retrieve the field's value using the param() method.
+
+=item B<-value>, B<-values>
+
+The initial value of the field which will be returned to the script
+after form submission. Some form elements, such as text fields, take
+a single scalar -value argument. Others, such as popup menus, take a
+reference to an array of values. The two arguments are synonyms.
+
+=item B<-tabindex>
+
+A numeric value that sets the order in which the form element receives
+focus when the user presses the tab key. Elements with lower values
+receive focus first.
+
+=item B<-id>
+
+A string identifier that can be used to identify this element to
+JavaScript and DHTML.
+
+=item B<-override>
+
+A boolean, which, if true, forces the element to take on the value
+specified by B<-value>, overriding the sticky behavior described
+earlier for the B<-nosticky> pragma.
+
+=item B<-onChange>, B<-onFocus>, B<-onBlur>, B<-onMouseOver>, B<-onMouseOut>, B<-onSelect>
+
+These are used to assign JavaScript event handlers. See the
+JavaScripting section for more details.
+
+=back
+
+Other common arguments are described in the next section. In addition
+to these, all attributes described in the HTML specifications are
+supported.
+
+=head2 Creating a text field
+
+ print textfield(-name=>'field_name',
+ -value=>'starting value',
+ -size=>50,
+ -maxlength=>80);
+ -or-
+
+ print textfield('field_name','starting value',50,80);
+
+textfield() will return a text input field.
+
+B<Parameters>
+
+=over 4
+
+=item 1.
+
+The first parameter is the required name for the field (-name).
+
+=item 2.
+
+The optional second parameter is the default starting value for the field
+contents (-value, formerly known as -default).
+
+=item 3.
+
+The optional third parameter is the size of the field in
+ characters (-size).
+
+=item 4.
+
+The optional fourth parameter is the maximum number of characters the
+ field will accept (-maxlength).
+
+=back
+
+As with all these methods, the field will be initialized with its
+previous contents from earlier invocations of the script.
+When the form is processed, the value of the text field can be
+retrieved with:
+
+ $value = param('foo');
+
+If you want to reset it from its initial value after the script has been
+called once, you can do so like this:
+
+ param('foo',"I'm taking over this value!");
+
+=head2 Creating a big text field
+
+ print textarea(-name=>'foo',
+ -default=>'starting value',
+ -rows=>10,
+ -columns=>50);
+
+ -or
+
+ print textarea('foo','starting value',10,50);
+
+textarea() is just like textfield, but it allows you to specify
+rows and columns for a multiline text entry box. You can provide
+a starting value for the field, which can be long and contain
+multiple lines.
+
+=head2 Creating a password field
+
+ print password_field(-name=>'secret',
+ -value=>'starting value',
+ -size=>50,
+ -maxlength=>80);
+ -or-
+
+ print password_field('secret','starting value',50,80);
+
+password_field() is identical to textfield(), except that its contents
+will be starred out on the web page.
+
+=head2 Creating a file upload field
+
+ print filefield(-name=>'uploaded_file',
+ -default=>'starting value',
+ -size=>50,
+ -maxlength=>80);
+ -or-
+
+ print filefield('uploaded_file','starting value',50,80);
+
+filefield() will return a file upload field.
+In order to take full advantage of this I<you must use the new
+multipart encoding scheme> for the form. You can do this either
+by calling B<start_form()> with an encoding type of B<&CGI::MULTIPART>,
+or by calling the new method B<start_multipart_form()> instead of
+vanilla B<start_form()>.
+
+B<Parameters>
+
+=over 4
+
+=item 1.
+
+The first parameter is the required name for the field (-name).
+
+=item 2.
+
+The optional second parameter is the starting value for the field contents
+to be used as the default file name (-default).
+
+For security reasons, browsers don't pay any attention to this field,
+and so the starting value will always be blank. Worse, the field
+loses its "sticky" behavior and forgets its previous contents. The
+starting value field is called for in the HTML specification, however,
+and possibly some browser will eventually provide support for it.
+
+=item 3.
+
+The optional third parameter is the size of the field in
+characters (-size).
+
+=item 4.
+
+The optional fourth parameter is the maximum number of characters the
+field will accept (-maxlength).
+
+=back
+
+JAVASCRIPTING: The B<-onChange>, B<-onFocus>, B<-onBlur>,
+B<-onMouseOver>, B<-onMouseOut> and B<-onSelect> parameters are
+recognized. See textfield() for details.
+
+=head2 Creating a popup menu
+
+ print popup_menu('menu_name',
+ ['eenie','meenie','minie'],
+ 'meenie');
+
+ -or-
+
+ %labels = ('eenie'=>'your first choice',
+ 'meenie'=>'your second choice',
+ 'minie'=>'your third choice');
+ %attributes = ('eenie'=>{'class'=>'class of first choice'});
+ print popup_menu('menu_name',
+ ['eenie','meenie','minie'],
+ 'meenie',\%labels,\%attributes);
+
+ -or (named parameter style)-
+
+ print popup_menu(-name=>'menu_name',
+ -values=>['eenie','meenie','minie'],
+ -default=>['meenie','minie'],
+ -labels=>\%labels,
+ -attributes=>\%attributes);
+
+popup_menu() creates a menu. Please note that the -multiple option will be
+ignored if passed - use scrolling_list() if you want to create a menu that
+supports multiple selections
+
+=over 4
+
+=item 1.
+
+The required first argument is the menu's name (-name).
+
+=item 2.
+
+The required second argument (-values) is an array B<reference>
+containing the list of menu items in the menu. You can pass the
+method an anonymous array, as shown in the example, or a reference to
+a named array, such as "\@foo".
+
+=item 3.
+
+The optional third parameter (-default) is the name of the default
+menu choice. If not specified, the first item will be the default.
+The values of the previous choice will be maintained across
+queries. Pass an array reference to select multiple defaults.
+
+=item 4.
+
+The optional fourth parameter (-labels) is provided for people who
+want to use different values for the user-visible label inside the
+popup menu and the value returned to your script. It's a pointer to an
+hash relating menu values to user-visible labels. If you
+leave this parameter blank, the menu values will be displayed by
+default. (You can also leave a label undefined if you want to).
+
+=item 5.
+
+The optional fifth parameter (-attributes) is provided to assign
+any of the common HTML attributes to an individual menu item. It's
+a pointer to a hash relating menu values to another
+hash with the attribute's name as the key and the
+attribute's value as the value.
+
+=back
+
+When the form is processed, the selected value of the popup menu can
+be retrieved using:
+
+ $popup_menu_value = param('menu_name');
+
+=head2 Creating an option group
+
+Named parameter style
+
+ print popup_menu(-name=>'menu_name',
+ -values=>[qw/eenie meenie minie/,
+ optgroup(-name=>'optgroup_name',
+ -values => ['moe','catch'],
+ -attributes=>{'catch'=>{'class'=>'red'}})],
+ -labels=>{'eenie'=>'one',
+ 'meenie'=>'two',
+ 'minie'=>'three'},
+ -default=>'meenie');
+
+ Old style
+ print popup_menu('menu_name',
+ ['eenie','meenie','minie',
+ optgroup('optgroup_name', ['moe', 'catch'],
+ {'catch'=>{'class'=>'red'}})],'meenie',
+ {'eenie'=>'one','meenie'=>'two','minie'=>'three'});
+
+optgroup() creates an option group within a popup menu.
+
+=over 4
+
+=item 1.
+
+The required first argument (B<-name>) is the label attribute of the
+optgroup and is B<not> inserted in the parameter list of the query.
+
+=item 2.
+
+The required second argument (B<-values>) is an array reference
+containing the list of menu items in the menu. You can pass the
+method an anonymous array, as shown in the example, or a reference
+to a named array, such as \@foo. If you pass a HASH reference,
+the keys will be used for the menu values, and the values will be
+used for the menu labels (see -labels below).
+
+=item 3.
+
+The optional third parameter (B<-labels>) allows you to pass a reference
+to a hash containing user-visible labels for one or more
+of the menu items. You can use this when you want the user to see one
+menu string, but have the browser return your program a different one.
+If you don't specify this, the value string will be used instead
+("eenie", "meenie" and "minie" in this example). This is equivalent
+to using a hash reference for the -values parameter.
+
+=item 4.
+
+An optional fourth parameter (B<-labeled>) can be set to a true value
+and indicates that the values should be used as the label attribute
+for each option element within the optgroup.
+
+=item 5.
+
+An optional fifth parameter (-novals) can be set to a true value and
+indicates to suppress the val attribute in each option element within
+the optgroup.
+
+See the discussion on optgroup at W3C
+(http://www.w3.org/TR/REC-html40/interact/forms.html#edef-OPTGROUP)
+for details.
+
+=item 6.
+
+An optional sixth parameter (-attributes) is provided to assign
+any of the common HTML attributes to an individual menu item. It's
+a pointer to a hash relating menu values to another
+hash with the attribute's name as the key and the
+attribute's value as the value.
+
+=back
+
+=head2 Creating a scrolling list
+
+ print scrolling_list('list_name',
+ ['eenie','meenie','minie','moe'],
+ ['eenie','moe'],5,'true',{'moe'=>{'class'=>'red'}});
+ -or-
+
+ print scrolling_list('list_name',
+ ['eenie','meenie','minie','moe'],
+ ['eenie','moe'],5,'true',
+ \%labels,%attributes);
+
+ -or-
+
+ print scrolling_list(-name=>'list_name',
+ -values=>['eenie','meenie','minie','moe'],
+ -default=>['eenie','moe'],
+ -size=>5,
+ -multiple=>'true',
+ -labels=>\%labels,
+ -attributes=>\%attributes);
+
+scrolling_list() creates a scrolling list.
+
+B<Parameters:>
+
+=over 4
+
+=item 1.
+
+The first and second arguments are the list name (-name) and values
+(-values). As in the popup menu, the second argument should be an
+array reference.
+
+=item 2.
+
+The optional third argument (-default) can be either a reference to a
+list containing the values to be selected by default, or can be a
+single value to select. If this argument is missing or undefined,
+then nothing is selected when the list first appears. In the named
+parameter version, you can use the synonym "-defaults" for this
+parameter.
+
+=item 3.
+
+The optional fourth argument is the size of the list (-size).
+
+=item 4.
+
+The optional fifth argument can be set to true to allow multiple
+simultaneous selections (-multiple). Otherwise only one selection
+will be allowed at a time.
+
+=item 5.
+
+The optional sixth argument is a pointer to a hash
+containing long user-visible labels for the list items (-labels).
+If not provided, the values will be displayed.
+
+=item 6.
+
+The optional sixth parameter (-attributes) is provided to assign
+any of the common HTML attributes to an individual menu item. It's
+a pointer to a hash relating menu values to another
+hash with the attribute's name as the key and the
+attribute's value as the value.
+
+When this form is processed, all selected list items will be returned as
+a list under the parameter name 'list_name'. The values of the
+selected items can be retrieved with:
+
+ @selected = param('list_name');
+
+=back
+
+=head2 Creating a group of related checkboxes
+
+ print checkbox_group(-name=>'group_name',
+ -values=>['eenie','meenie','minie','moe'],
+ -default=>['eenie','moe'],
+ -linebreak=>'true',
+ -disabled => ['moe'],
+ -labels=>\%labels,
+ -attributes=>\%attributes);
+
+ print checkbox_group('group_name',
+ ['eenie','meenie','minie','moe'],
+ ['eenie','moe'],'true',\%labels,
+ {'moe'=>{'class'=>'red'}});
+
+ HTML3-COMPATIBLE BROWSERS ONLY:
+
+ print checkbox_group(-name=>'group_name',
+ -values=>['eenie','meenie','minie','moe'],
+ -rows=2,-columns=>2);
+
+
+checkbox_group() creates a list of checkboxes that are related
+by the same name.
+
+B<Parameters:>
+
+=over 4
+
+=item 1.
+
+The first and second arguments are the checkbox name and values,
+respectively (-name and -values). As in the popup menu, the second
+argument should be an array reference. These values are used for the
+user-readable labels printed next to the checkboxes as well as for the
+values passed to your script in the query string.
+
+=item 2.
+
+The optional third argument (-default) can be either a reference to a
+list containing the values to be checked by default, or can be a
+single value to checked. If this argument is missing or undefined,
+then nothing is selected when the list first appears.
+
+=item 3.
+
+The optional fourth argument (-linebreak) can be set to true to place
+line breaks between the checkboxes so that they appear as a vertical
+list. Otherwise, they will be strung together on a horizontal line.
+
+=back
+
+The optional B<-labels> argument is a pointer to a hash
+relating the checkbox values to the user-visible labels that will be
+printed next to them. If not provided, the values will be used as the
+default.
+
+
+The optional parameters B<-rows>, and B<-columns> cause
+checkbox_group() to return an HTML3 compatible table containing the
+checkbox group formatted with the specified number of rows and
+columns. You can provide just the -columns parameter if you wish;
+checkbox_group will calculate the correct number of rows for you.
+
+The option B<-disabled> takes an array of checkbox values and disables
+them by greying them out (this may not be supported by all browsers).
+
+The optional B<-attributes> argument is provided to assign any of the
+common HTML attributes to an individual menu item. It's a pointer to
+a hash relating menu values to another hash
+with the attribute's name as the key and the attribute's value as the
+value.
+
+The optional B<-tabindex> argument can be used to control the order in which
+radio buttons receive focus when the user presses the tab button. If
+passed a scalar numeric value, the first element in the group will
+receive this tab index and subsequent elements will be incremented by
+one. If given a reference to an array of radio button values, then
+the indexes will be jiggered so that the order specified in the array
+will correspond to the tab order. You can also pass a reference to a
+hash in which the hash keys are the radio button values and the values
+are the tab indexes of each button. Examples:
+
+ -tabindex => 100 # this group starts at index 100 and counts up
+ -tabindex => ['moe','minie','eenie','meenie'] # tab in this order
+ -tabindex => {meenie=>100,moe=>101,minie=>102,eenie=>200} # tab in this order
+
+The optional B<-labelattributes> argument will contain attributes
+attached to the <label> element that surrounds each button.
+
+When the form is processed, all checked boxes will be returned as
+a list under the parameter name 'group_name'. The values of the
+"on" checkboxes can be retrieved with:
+
+ @turned_on = param('group_name');
+
+The value returned by checkbox_group() is actually an array of button
+elements. You can capture them and use them within tables, lists,
+or in other creative ways:
+
+ @h = checkbox_group(-name=>'group_name',-values=>\@values);
+ &use_in_creative_way(@h);
+
+=head2 Creating a standalone checkbox
+
+ print checkbox(-name=>'checkbox_name',
+ -checked=>1,
+ -value=>'ON',
+ -label=>'CLICK ME');
+
+ -or-
+
+ print checkbox('checkbox_name','checked','ON','CLICK ME');
+
+checkbox() is used to create an isolated checkbox that isn't logically
+related to any others.
+
+B<Parameters:>
+
+=over 4
+
+=item 1.
+
+The first parameter is the required name for the checkbox (-name). It
+will also be used for the user-readable label printed next to the
+checkbox.
+
+=item 2.
+
+The optional second parameter (-checked) specifies that the checkbox
+is turned on by default. Synonyms are -selected and -on.
+
+=item 3.
+
+The optional third parameter (-value) specifies the value of the
+checkbox when it is checked. If not provided, the word "on" is
+assumed.
+
+=item 4.
+
+The optional fourth parameter (-label) is the user-readable label to
+be attached to the checkbox. If not provided, the checkbox name is
+used.
+
+=back
+
+The value of the checkbox can be retrieved using:
+
+ $turned_on = param('checkbox_name');
+
+=head2 Creating a radio button group
+
+ print radio_group(-name=>'group_name',
+ -values=>['eenie','meenie','minie'],
+ -default=>'meenie',
+ -linebreak=>'true',
+ -labels=>\%labels,
+ -attributes=>\%attributes);
+
+ -or-
+
+ print radio_group('group_name',['eenie','meenie','minie'],
+ 'meenie','true',\%labels,\%attributes);
+
+
+ HTML3-COMPATIBLE BROWSERS ONLY:
+
+ print radio_group(-name=>'group_name',
+ -values=>['eenie','meenie','minie','moe'],
+ -rows=2,-columns=>2);
+
+radio_group() creates a set of logically-related radio buttons
+(turning one member of the group on turns the others off)
+
+B<Parameters:>
+
+=over 4
+
+=item 1.
+
+The first argument is the name of the group and is required (-name).
+
+=item 2.
+
+The second argument (-values) is the list of values for the radio
+buttons. The values and the labels that appear on the page are
+identical. Pass an array I<reference> in the second argument, either
+using an anonymous array, as shown, or by referencing a named array as
+in "\@foo".
+
+=item 3.
+
+The optional third parameter (-default) is the name of the default
+button to turn on. If not specified, the first item will be the
+default. You can provide a nonexistent button name, such as "-" to
+start up with no buttons selected.
+
+=item 4.
+
+The optional fourth parameter (-linebreak) can be set to 'true' to put
+line breaks between the buttons, creating a vertical list.
+
+=item 5.
+
+The optional fifth parameter (-labels) is a pointer to an associative
+array relating the radio button values to user-visible labels to be
+used in the display. If not provided, the values themselves are
+displayed.
+
+=back
+
+All modern browsers can take advantage of the optional parameters
+B<-rows>, and B<-columns>. These parameters cause radio_group() to
+return an HTML3 compatible table containing the radio group formatted
+with the specified number of rows and columns. You can provide just
+the -columns parameter if you wish; radio_group will calculate the
+correct number of rows for you.
+
+To include row and column headings in the returned table, you
+can use the B<-rowheaders> and B<-colheaders> parameters. Both
+of these accept a pointer to an array of headings to use.
+The headings are just decorative. They don't reorganize the
+interpretation of the radio buttons -- they're still a single named
+unit.
+
+The optional B<-tabindex> argument can be used to control the order in which
+radio buttons receive focus when the user presses the tab button. If
+passed a scalar numeric value, the first element in the group will
+receive this tab index and subsequent elements will be incremented by
+one. If given a reference to an array of radio button values, then
+the indexes will be jiggered so that the order specified in the array
+will correspond to the tab order. You can also pass a reference to a
+hash in which the hash keys are the radio button values and the values
+are the tab indexes of each button. Examples:
+
+ -tabindex => 100 # this group starts at index 100 and counts up
+ -tabindex => ['moe','minie','eenie','meenie'] # tab in this order
+ -tabindex => {meenie=>100,moe=>101,minie=>102,eenie=>200} # tab in this order
+
+
+The optional B<-attributes> argument is provided to assign any of the
+common HTML attributes to an individual menu item. It's a pointer to
+a hash relating menu values to another hash
+with the attribute's name as the key and the attribute's value as the
+value.
+
+The optional B<-labelattributes> argument will contain attributes
+attached to the <label> element that surrounds each button.
+
+When the form is processed, the selected radio button can
+be retrieved using:
+
+ $which_radio_button = param('group_name');
+
+The value returned by radio_group() is actually an array of button
+elements. You can capture them and use them within tables, lists,
+or in other creative ways:
+
+ @h = radio_group(-name=>'group_name',-values=>\@values);
+ &use_in_creative_way(@h);
+
+=head2 Creating a submit button
+
+ print submit(-name=>'button_name',
+ -value=>'value');
+
+ -or-
+
+ print submit('button_name','value');
+
+submit() will create the query submission button. Every form
+should have one of these.
+
+B<Parameters:>
+
+=over 4
+
+=item 1.
+
+The first argument (-name) is optional. You can give the button a
+name if you have several submission buttons in your form and you want
+to distinguish between them.
+
+=item 2.
+
+The second argument (-value) is also optional. This gives the button
+a value that will be passed to your script in the query string. The
+name will also be used as the user-visible label.
+
+=item 3.
+
+You can use -label as an alias for -value. I always get confused
+about which of -name and -value changes the user-visible label on the
+button.
+
+=back
+
+You can figure out which button was pressed by using different
+values for each one:
+
+ $which_one = param('button_name');
+
+=head2 Creating a reset button
+
+ print reset
+
+reset() creates the "reset" button. Note that it restores the
+form to its value from the last time the script was called,
+NOT necessarily to the defaults.
+
+Note that this conflicts with the perl reset() built-in. Use
+CORE::reset() to get the original reset function.
+
+=head2 Creating a default button
+
+ print defaults('button_label')
+
+defaults() creates a button that, when invoked, will cause the
+form to be completely reset to its defaults, wiping out all the
+changes the user ever made.
+
+=head2 Creating a hidden field
+
+ print hidden(-name=>'hidden_name',
+ -default=>['value1','value2'...]);
+
+ -or-
+
+ print hidden('hidden_name','value1','value2'...);
+
+hidden() produces a text field that can't be seen by the user. It
+is useful for passing state variable information from one invocation
+of the script to the next.
+
+B<Parameters:>
+
+=over 4
+
+=item 1.
+
+The first argument is required and specifies the name of this
+field (-name).
+
+=item 2.
+
+The second argument is also required and specifies its value
+(-default). In the named parameter style of calling, you can provide
+a single value here or a reference to a whole list
+
+=back
+
+Fetch the value of a hidden field this way:
+
+ $hidden_value = param('hidden_name');
+
+Note, that just like all the other form elements, the value of a
+hidden field is "sticky". If you want to replace a hidden field with
+some other values after the script has been called once you'll have to
+do it manually:
+
+ param('hidden_name','new','values','here');
+
+=head2 Creating a clickable image button
+
+ print image_button(-name=>'button_name',
+ -src=>'/source/URL',
+ -align=>'MIDDLE');
+
+ -or-
+
+ print image_button('button_name','/source/URL','MIDDLE');
+
+image_button() produces a clickable image. When it's clicked on the
+position of the click is returned to your script as "button_name.x"
+and "button_name.y", where "button_name" is the name you've assigned
+to it.
+
+B<Parameters:>
+
+=over 4
+
+=item 1.
+
+The first argument (-name) is required and specifies the name of this
+field.
+
+=item 2.
+
+The second argument (-src) is also required and specifies the URL
+
+=item 3.
+
+The third option (-align, optional) is an alignment type, and may be
+TOP, BOTTOM or MIDDLE
+
+=back
+
+Fetch the value of the button this way:
+ $x = param('button_name.x');
+ $y = param('button_name.y');
+
+=head2 Creating a javascript action button
+
+ print button(-name=>'button_name',
+ -value=>'user visible label',
+ -onClick=>"do_something()");
+
+ -or-
+
+ print button('button_name',"user visible value","do_something()");
+
+button() produces an C<< <input> >> tag with C<type="button">. When it's
+pressed the fragment of JavaScript code pointed to by the B<-onClick> parameter
+will be executed.
+
+=head1 WORKING WITH FRAMES
+
+It's possible for CGI.pm scripts to write into several browser panels
+and windows using the HTML 4 frame mechanism. There are three
+techniques for defining new frames programmatically:
+
+=over 4
+
+=item 1. Create a <Frameset> document
+
+After writing out the HTTP header, instead of creating a standard
+HTML document using the start_html() call, create a <frameset>
+document that defines the frames on the page. Specify your script(s)
+(with appropriate parameters) as the SRC for each of the frames.
+
+There is no specific support for creating <frameset> sections
+in CGI.pm, but the HTML is very simple to write.
+
+=item 2. Specify the destination for the document in the HTTP header
+
+You may provide a B<-target> parameter to the header() method:
+
+ print header(-target=>'ResultsWindow');
+
+This will tell the browser to load the output of your script into the
+frame named "ResultsWindow". If a frame of that name doesn't already
+exist, the browser will pop up a new window and load your script's
+document into that. There are a number of magic names that you can
+use for targets. See the HTML C<< <frame> >> documentation for details.
+
+=item 3. Specify the destination for the document in the <form> tag
+
+You can specify the frame to load in the FORM tag itself. With
+CGI.pm it looks like this:
+
+ print start_form(-target=>'ResultsWindow');
+
+When your script is reinvoked by the form, its output will be loaded
+into the frame named "ResultsWindow". If one doesn't already exist
+a new window will be created.
+
+=back
+
+The script "frameset.cgi" in the examples directory shows one way to
+create pages in which the fill-out form and the response live in
+side-by-side frames.
+
+=head1 SUPPORT FOR JAVASCRIPT
+
+The usual way to use JavaScript is to define a set of functions in a
+<SCRIPT> block inside the HTML header and then to register event
+handlers in the various elements of the page. Events include such
+things as the mouse passing over a form element, a button being
+clicked, the contents of a text field changing, or a form being
+submitted. When an event occurs that involves an element that has
+registered an event handler, its associated JavaScript code gets
+called.
+
+The elements that can register event handlers include the <BODY> of an
+HTML document, hypertext links, all the various elements of a fill-out
+form, and the form itself. There are a large number of events, and
+each applies only to the elements for which it is relevant. Here is a
+partial list:
+
+=over 4
+
+=item B<onLoad>
+
+The browser is loading the current document. Valid in:
+
+ + The HTML <BODY> section only.
+
+=item B<onUnload>
+
+The browser is closing the current page or frame. Valid for:
+
+ + The HTML <BODY> section only.
+
+=item B<onSubmit>
+
+The user has pressed the submit button of a form. This event happens
+just before the form is submitted, and your function can return a
+value of false in order to abort the submission. Valid for:
+
+ + Forms only.
+
+=item B<onClick>
+
+The mouse has clicked on an item in a fill-out form. Valid for:
+
+ + Buttons (including submit, reset, and image buttons)
+ + Checkboxes
+ + Radio buttons
+
+=item B<onChange>
+
+The user has changed the contents of a field. Valid for:
+
+ + Text fields
+ + Text areas
+ + Password fields
+ + File fields
+ + Popup Menus
+ + Scrolling lists
+
+=item B<onFocus>
+
+The user has selected a field to work with. Valid for:
+
+ + Text fields
+ + Text areas
+ + Password fields
+ + File fields
+ + Popup Menus
+ + Scrolling lists
+
+=item B<onBlur>
+
+The user has deselected a field (gone to work somewhere else). Valid
+for:
+
+ + Text fields
+ + Text areas
+ + Password fields
+ + File fields
+ + Popup Menus
+ + Scrolling lists
+
+=item B<onSelect>
+
+The user has changed the part of a text field that is selected. Valid
+for:
+
+ + Text fields
+ + Text areas
+ + Password fields
+ + File fields
+
+=item B<onMouseOver>
+
+The mouse has moved over an element.
+
+ + Text fields
+ + Text areas
+ + Password fields
+ + File fields
+ + Popup Menus
+ + Scrolling lists
+
+=item B<onMouseOut>
+
+The mouse has moved off an element.
+
+ + Text fields
+ + Text areas
+ + Password fields
+ + File fields
+ + Popup Menus
+ + Scrolling lists
+
+=back
+
+In order to register a JavaScript event handler with an HTML element,
+just use the event name as a parameter when you call the corresponding
+CGI method. For example, to have your validateAge() JavaScript code
+executed every time the textfield named "age" changes, generate the
+field like this:
+
+ print textfield(-name=>'age',-onChange=>"validateAge(this)");
+
+This example assumes that you've already declared the validateAge()
+function by incorporating it into a <SCRIPT> block. The CGI.pm
+start_html() method provides a convenient way to create this section.
+
+Similarly, you can create a form that checks itself over for
+consistency and alerts the user if some essential value is missing by
+creating it this way:
+ print start_form(-onSubmit=>"validateMe(this)");
+
+See the javascript.cgi script for a demonstration of how this all
+works.
+
+
+=head1 LIMITED SUPPORT FOR CASCADING STYLE SHEETS
+
+CGI.pm has limited support for HTML3's cascading style sheets (css).
+To incorporate a stylesheet into your document, pass the
+start_html() method a B<-style> parameter. The value of this
+parameter may be a scalar, in which case it is treated as the source
+URL for the stylesheet, or it may be a hash reference. In the latter
+case you should provide the hash with one or more of B<-src> or
+B<-code>. B<-src> points to a URL where an externally-defined
+stylesheet can be found. B<-code> points to a scalar value to be
+incorporated into a <style> section. Style definitions in B<-code>
+override similarly-named ones in B<-src>, hence the name "cascading."
+
+You may also specify the type of the stylesheet by adding the optional
+B<-type> parameter to the hash pointed to by B<-style>. If not
+specified, the style defaults to 'text/css'.
+
+To refer to a style within the body of your document, add the
+B<-class> parameter to any HTML element:
+
+ print h1({-class=>'Fancy'},'Welcome to the Party');
+
+Or define styles on the fly with the B<-style> parameter:
+
+ print h1({-style=>'Color: red;'},'Welcome to Hell');
+
+You may also use the new B<span()> element to apply a style to a
+section of text:
+
+ print span({-style=>'Color: red;'},
+ h1('Welcome to Hell'),
+ "Where did that handbasket get to?"
+ );
+
+Note that you must import the ":html3" definitions to have the
+B<span()> method available. Here's a quick and dirty example of using
+CSS's. See the CSS specification at
+http://www.w3.org/Style/CSS/ for more information.
+
+ use CGI qw/:standard :html3/;
+
+ #here's a stylesheet incorporated directly into the page
+ $newStyle=<<END;
+ <!--
+ P.Tip {
+ margin-right: 50pt;
+ margin-left: 50pt;
+ color: red;
+ }
+ P.Alert {
+ font-size: 30pt;
+ font-family: sans-serif;
+ color: red;
+ }
+ -->
+ END
+ print header();
+ print start_html( -title=>'CGI with Style',
+ -style=>{-src=>'http://www.capricorn.com/style/st1.css',
+ -code=>$newStyle}
+ );
+ print h1('CGI with Style'),
+ p({-class=>'Tip'},
+ "Better read the cascading style sheet spec before playing with this!"),
+ span({-style=>'color: magenta'},
+ "Look Mom, no hands!",
+ p(),
+ "Whooo wee!"
+ );
+ print end_html;
+
+Pass an array reference to B<-code> or B<-src> in order to incorporate
+multiple stylesheets into your document.
+
+Should you wish to incorporate a verbatim stylesheet that includes
+arbitrary formatting in the header, you may pass a -verbatim tag to
+the -style hash, as follows:
+
+print start_html (-style => {-verbatim => '@import url("/server-common/css/'.$cssFile.'");',
+ -src => '/server-common/css/core.css'});
+
+
+This will generate an HTML header that contains this:
+
+ <link rel="stylesheet" type="text/css" href="/server-common/css/core.css">
+ <style type="text/css">
+ @import url("/server-common/css/main.css");
+ </style>
+
+Any additional arguments passed in the -style value will be
+incorporated into the <link> tag. For example:
+
+ start_html(-style=>{-src=>['/styles/print.css','/styles/layout.css'],
+ -media => 'all'});
+
+This will give:
+
+ <link rel="stylesheet" type="text/css" href="/styles/print.css" media="all"/>
+ <link rel="stylesheet" type="text/css" href="/styles/layout.css" media="all"/>
+
+<p>
+
+To make more complicated <link> tags, use the Link() function
+and pass it to start_html() in the -head argument, as in:
+
+ @h = (Link({-rel=>'stylesheet',-type=>'text/css',-src=>'/ss/ss.css',-media=>'all'}),
+ Link({-rel=>'stylesheet',-type=>'text/css',-src=>'/ss/fred.css',-media=>'paper'}));
+ print start_html({-head=>\@h})
+
+To create primary and "alternate" stylesheet, use the B<-alternate> option:
+
+ start_html(-style=>{-src=>[
+ {-src=>'/styles/print.css'},
+ {-src=>'/styles/alt.css',-alternate=>1}
+ ]
+ });
+
+=head2 Dumping out all the name/value pairs
+
+The Dump() method produces a string consisting of all the query's name/value
+pairs formatted nicely as a nested list. This is useful for debugging purposes:
+
+ print Dump
+
+Produces something that looks like:
+
+ <ul>
+ <li>name1
+ <ul>
+ <li>value1
+ <li>value2
+ </ul>
+ <li>name2
+ <ul>
+ <li>value1
+ </ul>
+ </ul>
+
+As a shortcut, you can interpolate the entire CGI object into a string
+and it will be replaced with the a nice HTML dump shown above:
+
+ $query=CGI->new;
+ print "<h2>Current Values</h2> $query\n";
+
+
+=head1 BUGS
+
+Address bug reports and comments to: L<https://github.com/leejo/CGI.pm/issues>
+
+The original bug tracker can be found at: L<https://rt.cpan.org/Public/Dist/Display.html?Queue=CGI.pm>
+
+However as stated this functionality is no longer being maintained and is
+considered deprecated. Any feature requests, bug reports, issues, pull
+requests, etc, for this functionality will almost certainly be rejected without
+any action being taken place - this includes fixes to utterly broken page
+rendering, invalid HTML, nonsensical output, and annoyances.
+
+=head1 SEE ALSO
+
+L<CGI> - The original source of this documentation / functionality
+
+=cut
diff --git a/lib/CGI/Pretty.pm b/lib/CGI/Pretty.pm
new file mode 100644
index 0000000..2247438
--- /dev/null
+++ b/lib/CGI/Pretty.pm
@@ -0,0 +1,85 @@
+package CGI::Pretty;
+
+use strict;
+use if $] >= 5.019, 'deprecate';
+use CGI ();
+
+$CGI::Pretty::VERSION = '4.21';
+$CGI::DefaultClass = __PACKAGE__;
+@CGI::Pretty::ISA = qw( CGI );
+
+sub new {
+ my $class = shift;
+ my $this = $class->SUPER::new( @_ );
+ return bless $this, $class;
+}
+
+sub import {
+
+ warn "CGI::Pretty is DEPRECATED and will be removed in a future release. Please see https://github.com/leejo/CGI.pm/issues/162 for more information";
+
+ my $self = shift;
+ no strict 'refs';
+
+ # This causes modules to clash.
+ undef %CGI::EXPORT;
+ undef %CGI::EXPORT;
+
+ $self->_setup_symbols(@_);
+ my ($callpack, $callfile, $callline) = caller;
+
+ # To allow overriding, search through the packages
+ # Till we find one in which the correct subroutine is defined.
+ my @packages = ($self,@{"$self\:\:ISA"});
+ foreach my $sym (keys %CGI::EXPORT) {
+ my $pck;
+ my $def = $CGI::DefaultClass;
+ foreach $pck (@packages) {
+ if (defined(&{"$pck\:\:$sym"})) {
+ $def = $pck;
+ last;
+ }
+ }
+ *{"${callpack}::$sym"} = \&{"$def\:\:$sym"};
+ }
+}
+
+1;
+
+=head1 NAME
+
+CGI::Pretty - module to produce nicely formatted HTML code
+
+=head1 CGI::Pretty IS DEPRECATED
+
+It will be removed from the CGI distribution in a future release, so you
+should no longer use it and remove it from any code that currently uses it.
+
+For now it has been reduced to a shell to prevent your code breaking, but
+the "pretty" functions will no longer output "pretty" HTML.
+
+=head1 Alternatives
+
+L<HTML::HTML5::Parser> + L<HTML::HTML5::Writer> + L<XML::LibXML::PrettyPrint>:
+
+ use HTML::HTML5::Parser qw();
+ use HTML::HTML5::Writer qw();
+ use XML::LibXML::PrettyPrint qw();
+
+ print HTML::HTML5::Writer->new(
+ start_tags => 'force',
+ end_tags => 'force',
+ )->document(
+ XML::LibXML::PrettyPrint->new_for_html( indent_string => "\t" )
+ ->pretty_print(
+ HTML::HTML5::Parser->new->parse_string( $html_string )
+ )
+ );
+
+L<Marpa::R2::HTML> (see the html_fmt script for examples)
+
+L<HTML::Tidy>
+
+L<HTML::Parser>
+
+=cut
diff --git a/lib/CGI/Push.pm b/lib/CGI/Push.pm
new file mode 100644
index 0000000..f1d4574
--- /dev/null
+++ b/lib/CGI/Push.pm
@@ -0,0 +1,306 @@
+package CGI::Push;
+use if $] >= 5.019, 'deprecate';
+
+$CGI::Push::VERSION='4.21';
+use CGI;
+use CGI::Util 'rearrange';
+@ISA = ('CGI');
+
+$CGI::DefaultClass = 'CGI::Push';
+
+# add do_push() and push_delay() to exported tags
+push(@{$CGI::EXPORT_TAGS{':standard'}},'do_push','push_delay');
+
+sub do_push {
+ my ($self,@p) = CGI::self_or_default(@_);
+
+ # unbuffer output
+ $| = 1;
+ srand;
+ my ($random) = sprintf("%08.0f",rand()*1E8);
+ my ($boundary) = "----=_NeXtPaRt$random";
+
+ my (@header);
+ my ($type,$callback,$delay,$last_page,$cookie,$target,$expires,$nph,@other) = rearrange([TYPE,NEXT_PAGE,DELAY,LAST_PAGE,[COOKIE,COOKIES],TARGET,EXPIRES,NPH],@p);
+ $type = 'text/html' unless $type;
+ $callback = \&simple_counter unless $callback && ref($callback) eq 'CODE';
+ $delay = 1 unless defined($delay);
+ $self->push_delay($delay);
+ $nph = 1 unless defined($nph);
+
+ my(@o);
+ foreach (@other) { push(@o,split("=")); }
+ push(@o,'-Target'=>$target) if defined($target);
+ push(@o,'-Cookie'=>$cookie) if defined($cookie);
+ push(@o,'-Type'=>"multipart/x-mixed-replace;boundary=\"$boundary\"");
+ push(@o,'-Server'=>"CGI.pm Push Module") if $nph;
+ push(@o,'-Status'=>'200 OK');
+ push(@o,'-nph'=>1) if $nph;
+ print $self->header(@o);
+
+ $boundary = "$CGI::CRLF--$boundary";
+
+ print "WARNING: YOUR BROWSER DOESN'T SUPPORT THIS SERVER-PUSH TECHNOLOGY.${boundary}$CGI::CRLF";
+
+ my (@contents) = &$callback($self,++$COUNTER);
+
+ # now we enter a little loop
+ while (1) {
+ print "Content-type: ${type}$CGI::CRLF$CGI::CRLF" unless $type =~ /^dynamic|heterogeneous$/i;
+ print @contents;
+ @contents = &$callback($self,++$COUNTER);
+ if ((@contents) && defined($contents[0])) {
+ print "${boundary}$CGI::CRLF";
+ do_sleep($self->push_delay()) if $self->push_delay();
+ } else {
+ if ($last_page && ref($last_page) eq 'CODE') {
+ print "${boundary}$CGI::CRLF";
+ do_sleep($self->push_delay()) if $self->push_delay();
+ print "Content-type: ${type}$CGI::CRLF$CGI::CRLF" unless $type =~ /^dynamic|heterogeneous$/i;
+ print &$last_page($self,$COUNTER);
+ }
+ print "${boundary}--$CGI::CRLF";
+ last;
+ }
+ }
+ print "WARNING: YOUR BROWSER DOESN'T SUPPORT THIS SERVER-PUSH TECHNOLOGY.$CGI::CRLF";
+}
+
+sub simple_counter {
+ my ($self,$count) = @_;
+ return $self->start_html("CGI::Push Default Counter"),
+ $self->h1("CGI::Push Default Counter"),
+ "This page has been updated ",$self->strong($count)," times.",
+ $self->hr(),
+ $self->a({'-href'=>'http://www.genome.wi.mit.edu/ftp/pub/software/WWW/cgi_docs.html'},'CGI.pm home page'),
+ $self->end_html;
+}
+
+sub do_sleep {
+ my $delay = shift;
+ if ( ($delay >= 1) && ($delay!~/\./) ){
+ sleep($delay);
+ } else {
+ select(undef,undef,undef,$delay);
+ return $delay;
+ }
+}
+
+sub push_delay {
+ my ($self,$delay) = CGI::self_or_default(@_);
+ return defined($delay) ? $self->{'.delay'} =
+ $delay : $self->{'.delay'};
+}
+
+1;
+
+=head1 NAME
+
+CGI::Push - Simple Interface to Server Push
+
+=head1 SYNOPSIS
+
+ use strict;
+ use warnings;
+
+ use CGI::Push qw(:standard);
+
+ do_push(
+ -next_page => \&next_page,
+ -last_page => \&last_page,
+ -delay => 0.5
+ );
+
+ sub next_page {
+ my($q,$counter) = @_;
+ return undef if $counter >= 10;
+ ....
+ }
+
+ sub last_page {
+ my($q,$counter) = @_;
+ return ...
+ }
+
+=head1 DESCRIPTION
+
+CGI::Push is a subclass of the CGI object created by CGI.pm. It is
+specialized for server push operations, which allow you to create
+animated pages whose content changes at regular intervals.
+
+You provide CGI::Push with a pointer to a subroutine that will draw
+one page. Every time your subroutine is called, it generates a new
+page. The contents of the page will be transmitted to the browser
+in such a way that it will replace what was there beforehand. The
+technique will work with HTML pages as well as with graphics files,
+allowing you to create animated GIFs.
+
+Only Netscape Navigator supports server push. Internet Explorer
+browsers do not.
+
+=head1 USING CGI::Push
+
+CGI::Push adds one new method to the standard CGI suite, do_push().
+When you call this method, you pass it a reference to a subroutine
+that is responsible for drawing each new page, an interval delay, and
+an optional subroutine for drawing the last page. Other optional
+parameters include most of those recognized by the CGI header()
+method.
+
+You may call do_push() in the object oriented manner or not, as you
+prefer:
+
+ use CGI::Push;
+ $q = CGI::Push->new;
+ $q->do_push(-next_page=>\&draw_a_page);
+
+ -or-
+
+ use CGI::Push qw(:standard);
+ do_push(-next_page=>\&draw_a_page);
+
+Parameters are as follows:
+
+=over 4
+
+=item -next_page
+
+ do_push(-next_page=>\&my_draw_routine);
+
+This required parameter points to a reference to a subroutine responsible for
+drawing each new page. The subroutine should expect two parameters
+consisting of the CGI object and a counter indicating the number
+of times the subroutine has been called. It should return the
+contents of the page as an B<array> of one or more items to print.
+It can return a false value (or an empty array) in order to abort the
+redrawing loop and print out the final page (if any)
+
+ sub my_draw_routine {
+ my($q,$counter) = @_;
+ return undef if $counter > 100;
+ ...
+ }
+
+You are of course free to refer to create and use global variables
+within your draw routine in order to achieve special effects.
+
+=item -last_page
+
+This optional parameter points to a reference to the subroutine
+responsible for drawing the last page of the series. It is called
+after the -next_page routine returns a false value. The subroutine
+itself should have exactly the same calling conventions as the
+-next_page routine.
+
+=item -type
+
+This optional parameter indicates the content type of each page. It
+defaults to "text/html". Normally the module assumes that each page
+is of a homogeneous MIME type. However if you provide either of the
+magic values "heterogeneous" or "dynamic" (the latter provided for the
+convenience of those who hate long parameter names), you can specify
+the MIME type -- and other header fields -- on a per-page basis. See
+"heterogeneous pages" for more details.
+
+=item -delay
+
+This indicates the delay, in seconds, between frames. Smaller delays
+refresh the page faster. Fractional values are allowed.
+
+B<If not specified, -delay will default to 1 second>
+
+=item -cookie, -target, -expires, -nph
+
+These have the same meaning as the like-named parameters in
+CGI::header().
+
+If not specified, -nph will default to 1 (as needed for many servers, see below).
+
+=back
+
+=head2 Heterogeneous Pages
+
+Ordinarily all pages displayed by CGI::Push share a common MIME type.
+However by providing a value of "heterogeneous" or "dynamic" in the
+do_push() -type parameter, you can specify the MIME type of each page
+on a case-by-case basis.
+
+If you use this option, you will be responsible for producing the
+HTTP header for each page. Simply modify your draw routine to
+look like this:
+
+ sub my_draw_routine {
+ my($q,$counter) = @_;
+ return header('text/html'), # note we're producing the header here
+ ....
+ }
+
+You can add any header fields that you like, but some (cookies and
+status fields included) may not be interpreted by the browser. One
+interesting effect is to display a series of pages, then, after the
+last page, to redirect the browser to a new URL. Because redirect()
+does b<not> work, the easiest way is with a -refresh header field,
+as shown below:
+
+ sub my_draw_routine {
+ my($q,$counter) = @_;
+ return undef if $counter > 10;
+ return header('text/html'), # note we're producing the header here
+ ...
+ }
+
+ sub my_last_page {
+ return header(-refresh=>'5; URL=http://somewhere.else/finished.html',
+ -type=>'text/html'),
+ ...
+ }
+
+=head2 Changing the Page Delay on the Fly
+
+If you would like to control the delay between pages on a page-by-page
+basis, call push_delay() from within your draw routine. push_delay()
+takes a single numeric argument representing the number of seconds you
+wish to delay after the current page is displayed and before
+displaying the next one. The delay may be fractional. Without
+parameters, push_delay() just returns the current delay.
+
+=head1 INSTALLING CGI::Push SCRIPTS
+
+Server push scripts must be installed as no-parsed-header (NPH)
+scripts in order to work correctly on many servers. On Unix systems,
+this is most often accomplished by prefixing the script's name with "nph-".
+Recognition of NPH scripts happens automatically with WebSTAR and
+Microsoft IIS. Users of other servers should see their documentation
+for help.
+
+Apache web server from version 1.3b2 on does not need server
+push scripts installed as NPH scripts: the -nph parameter to do_push()
+may be set to a false value to disable the extra headers needed by an
+NPH script.
+
+=head1 AUTHOR INFORMATION
+
+The CGI.pm distribution is copyright 1995-2007, Lincoln D. Stein. It is
+distributed under GPL and the Artistic License 2.0. It is currently
+maintained by Lee Johnson with help from many contributors.
+
+Address bug reports and comments to: https://github.com/leejo/CGI.pm/issues
+
+The original bug tracker can be found at: https://rt.cpan.org/Public/Dist/Display.html?Queue=CGI.pm
+
+When sending bug reports, please provide the version of CGI.pm, the version of
+Perl, the name and version of your Web server, and the name and version of the
+operating system you are using. If the problem is even remotely browser
+dependent, please provide information about the affected browsers as well.
+Copyright 1995-1998, Lincoln D. Stein. All rights reserved.
+
+=head1 BUGS
+
+This section intentionally left blank.
+
+=head1 SEE ALSO
+
+L<CGI::Carp>, L<CGI>
+
+=cut
+
diff --git a/lib/CGI/Util.pm b/lib/CGI/Util.pm
new file mode 100644
index 0000000..308cf56
--- /dev/null
+++ b/lib/CGI/Util.pm
@@ -0,0 +1,354 @@
+package CGI::Util;
+use base 'Exporter';
+require 5.008001;
+use strict;
+use if $] >= 5.019, 'deprecate';
+our @EXPORT_OK = qw(rearrange rearrange_header make_attributes unescape escape
+ expires ebcdic2ascii ascii2ebcdic);
+
+our $VERSION = '4.21';
+
+our $_EBCDIC = "\t" ne "\011";
+
+# This option is not documented and may change or go away.
+# The HTML spec does not require attributes to be sorted,
+# but it's useful for testing to get a predictable order back.
+our $SORT_ATTRIBUTES;
+
+# (ord('^') == 95) for codepage 1047 as on os390, vmesa
+our @A2E = (
+ 0, 1, 2, 3, 55, 45, 46, 47, 22, 5, 21, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 60, 61, 50, 38, 24, 25, 63, 39, 28, 29, 30, 31,
+ 64, 90,127,123, 91,108, 80,125, 77, 93, 92, 78,107, 96, 75, 97,
+ 240,241,242,243,244,245,246,247,248,249,122, 94, 76,126,110,111,
+ 124,193,194,195,196,197,198,199,200,201,209,210,211,212,213,214,
+ 215,216,217,226,227,228,229,230,231,232,233,173,224,189, 95,109,
+ 121,129,130,131,132,133,134,135,136,137,145,146,147,148,149,150,
+ 151,152,153,162,163,164,165,166,167,168,169,192, 79,208,161, 7,
+ 32, 33, 34, 35, 36, 37, 6, 23, 40, 41, 42, 43, 44, 9, 10, 27,
+ 48, 49, 26, 51, 52, 53, 54, 8, 56, 57, 58, 59, 4, 20, 62,255,
+ 65,170, 74,177,159,178,106,181,187,180,154,138,176,202,175,188,
+ 144,143,234,250,190,160,182,179,157,218,155,139,183,184,185,171,
+ 100,101, 98,102, 99,103,158,104,116,113,114,115,120,117,118,119,
+ 172,105,237,238,235,239,236,191,128,253,254,251,252,186,174, 89,
+ 68, 69, 66, 70, 67, 71,156, 72, 84, 81, 82, 83, 88, 85, 86, 87,
+ 140, 73,205,206,203,207,204,225,112,221,222,219,220,141,142,223
+ );
+our @E2A = (
+ 0, 1, 2, 3,156, 9,134,127,151,141,142, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19,157, 10, 8,135, 24, 25,146,143, 28, 29, 30, 31,
+ 128,129,130,131,132,133, 23, 27,136,137,138,139,140, 5, 6, 7,
+ 144,145, 22,147,148,149,150, 4,152,153,154,155, 20, 21,158, 26,
+ 32,160,226,228,224,225,227,229,231,241,162, 46, 60, 40, 43,124,
+ 38,233,234,235,232,237,238,239,236,223, 33, 36, 42, 41, 59, 94,
+ 45, 47,194,196,192,193,195,197,199,209,166, 44, 37, 95, 62, 63,
+ 248,201,202,203,200,205,206,207,204, 96, 58, 35, 64, 39, 61, 34,
+ 216, 97, 98, 99,100,101,102,103,104,105,171,187,240,253,254,177,
+ 176,106,107,108,109,110,111,112,113,114,170,186,230,184,198,164,
+ 181,126,115,116,117,118,119,120,121,122,161,191,208, 91,222,174,
+ 172,163,165,183,169,167,182,188,189,190,221,168,175, 93,180,215,
+ 123, 65, 66, 67, 68, 69, 70, 71, 72, 73,173,244,246,242,243,245,
+ 125, 74, 75, 76, 77, 78, 79, 80, 81, 82,185,251,252,249,250,255,
+ 92,247, 83, 84, 85, 86, 87, 88, 89, 90,178,212,214,210,211,213,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,179,219,220,217,218,159
+ );
+
+if ($_EBCDIC && ord('^') == 106) { # as in the BS2000 posix-bc coded character set
+ $A2E[91] = 187; $A2E[92] = 188; $A2E[94] = 106; $A2E[96] = 74;
+ $A2E[123] = 251; $A2E[125] = 253; $A2E[126] = 255; $A2E[159] = 95;
+ $A2E[162] = 176; $A2E[166] = 208; $A2E[168] = 121; $A2E[172] = 186;
+ $A2E[175] = 161; $A2E[217] = 224; $A2E[219] = 221; $A2E[221] = 173;
+ $A2E[249] = 192;
+
+ $E2A[74] = 96; $E2A[95] = 159; $E2A[106] = 94; $E2A[121] = 168;
+ $E2A[161] = 175; $E2A[173] = 221; $E2A[176] = 162; $E2A[186] = 172;
+ $E2A[187] = 91; $E2A[188] = 92; $E2A[192] = 249; $E2A[208] = 166;
+ $E2A[221] = 219; $E2A[224] = 217; $E2A[251] = 123; $E2A[253] = 125;
+ $E2A[255] = 126;
+ }
+elsif ($_EBCDIC && ord('^') == 176) { # as in codepage 037 on os400
+ $A2E[10] = 37; $A2E[91] = 186; $A2E[93] = 187; $A2E[94] = 176;
+ $A2E[133] = 21; $A2E[168] = 189; $A2E[172] = 95; $A2E[221] = 173;
+
+ $E2A[21] = 133; $E2A[37] = 10; $E2A[95] = 172; $E2A[173] = 221;
+ $E2A[176] = 94; $E2A[186] = 91; $E2A[187] = 93; $E2A[189] = 168;
+}
+
+# Smart rearrangement of parameters to allow named parameter
+# calling. We do the rearrangement if:
+# the first parameter begins with a -
+
+sub rearrange {
+ my ($order,@param) = @_;
+ my ($result, $leftover) = _rearrange_params( $order, @param );
+ push @$result, make_attributes( $leftover, defined $CGI::Q ? $CGI::Q->{escape} : 1 )
+ if keys %$leftover;
+ @$result;
+}
+
+sub rearrange_header {
+ my ($order,@param) = @_;
+
+ my ($result,$leftover) = _rearrange_params( $order, @param );
+ push @$result, make_attributes( $leftover, 0, 1 ) if keys %$leftover;
+
+ @$result;
+}
+
+sub _rearrange_params {
+ my($order,@param) = @_;
+ return [] unless @param;
+
+ if (ref($param[0]) eq 'HASH') {
+ @param = %{$param[0]};
+ } else {
+ return \@param
+ unless (defined($param[0]) && substr($param[0],0,1) eq '-');
+ }
+
+ # map parameters into positional indices
+ my ($i,%pos);
+ $i = 0;
+ foreach (@$order) {
+ foreach (ref($_) eq 'ARRAY' ? @$_ : $_) { $pos{lc($_)} = $i; }
+ $i++;
+ }
+
+ my %params_as_hash = ( @param );
+
+ my (@result,%leftover);
+ $#result = $#$order; # preextend
+
+ foreach my $k (
+ # sort keys alphabetically but favour certain keys before others
+ # specifically for the case where there could be several options
+ # for a param key, but one should be preferred (see GH #155)
+ sort {
+ if ( $a =~ /content/i ) { return 1 }
+ elsif ( $b =~ /content/i ) { return -1 }
+ else { $a cmp $b }
+ }
+ keys( %params_as_hash )
+ ) {
+ my $key = lc($k);
+ $key =~ s/^\-//;
+ if (exists $pos{$key}) {
+ $result[$pos{$key}] = $params_as_hash{$k};
+ } else {
+ $leftover{$key} = $params_as_hash{$k};
+ }
+ }
+
+ return \@result, \%leftover;
+}
+
+sub make_attributes {
+ my $attr = shift;
+ return () unless $attr && ref($attr) && ref($attr) eq 'HASH';
+ my $escape = shift || 0;
+ my $do_not_quote = shift;
+
+ my $quote = $do_not_quote ? '' : '"';
+
+ my @attr_keys= keys %$attr;
+ if ($SORT_ATTRIBUTES) {
+ @attr_keys= sort @attr_keys;
+ }
+ my(@att);
+ foreach (@attr_keys) {
+ my($key) = $_;
+ $key=~s/^\-//; # get rid of initial - if present
+
+ # old way: breaks EBCDIC!
+ # $key=~tr/A-Z_/a-z-/; # parameters are lower case, use dashes
+
+ ($key="\L$key") =~ tr/_/-/; # parameters are lower case, use dashes
+
+ my $value = $escape ? simple_escape($attr->{$_}) : $attr->{$_};
+ push(@att,defined($attr->{$_}) ? qq/$key=$quote$value$quote/ : qq/$key/);
+ }
+ return sort @att;
+}
+
+sub simple_escape {
+ return unless defined(my $toencode = shift);
+ $toencode =~ s{&}{&amp;}gso;
+ $toencode =~ s{<}{&lt;}gso;
+ $toencode =~ s{>}{&gt;}gso;
+ $toencode =~ s{\"}{&quot;}gso;
+# Doesn't work. Can't work. forget it.
+# $toencode =~ s{\x8b}{&#139;}gso;
+# $toencode =~ s{\x9b}{&#155;}gso;
+ $toencode;
+}
+
+sub utf8_chr {
+ my $c = shift(@_);
+ my $u = chr($c);
+ utf8::encode($u); # drop utf8 flag
+ return $u;
+}
+
+# unescape URL-encoded data
+sub unescape {
+ shift() if @_ > 0 and (ref($_[0]) || (defined $_[1] && $_[0] eq $CGI::DefaultClass));
+ my $todecode = shift;
+ return undef unless defined($todecode);
+ $todecode =~ tr/+/ /; # pluses become spaces
+ if ($_EBCDIC) {
+ $todecode =~ s/%([0-9a-fA-F]{2})/chr $A2E[hex($1)]/ge;
+ } else {
+ # handle surrogate pairs first -- dankogai. Ref: http://unicode.org/faq/utf_bom.html#utf16-2
+ $todecode =~ s{
+ %u([Dd][89a-bA-B][0-9a-fA-F]{2}) # hi
+ %u([Dd][c-fC-F][0-9a-fA-F]{2}) # lo
+ }{
+ utf8_chr(
+ 0x10000
+ + (hex($1) - 0xD800) * 0x400
+ + (hex($2) - 0xDC00)
+ )
+ }gex;
+ $todecode =~ s/%(?:([0-9a-fA-F]{2})|u([0-9a-fA-F]{4}))/
+ defined($1)? chr hex($1) : utf8_chr(hex($2))/ge;
+ }
+ return $todecode;
+}
+
+# URL-encode data
+#
+# We cannot use the %u escapes, they were rejected by W3C, so the official
+# way is %XX-escaped utf-8 encoding.
+# Naturally, Unicode strings have to be converted to their utf-8 byte
+# representation.
+# Byte strings were traditionally used directly as a sequence of octets.
+# This worked if they actually represented binary data (i.e. in CGI::Compress).
+# This also worked if these byte strings were actually utf-8 encoded; e.g.,
+# when the source file used utf-8 without the appropriate "use utf8;".
+# This fails if the byte string is actually a Latin 1 encoded string, but it
+# was always so and cannot be fixed without breaking the binary data case.
+# -- Stepan Kasal <skasal@redhat.com>
+#
+
+sub escape {
+ # If we being called in an OO-context, discard the first argument.
+ shift() if @_ > 1 and ( ref($_[0]) || (defined $_[1] && $_[0] eq $CGI::DefaultClass));
+ my $toencode = shift;
+ return undef unless defined($toencode);
+ utf8::encode($toencode) if utf8::is_utf8($toencode);
+ if ($_EBCDIC) {
+ $toencode=~s/([^a-zA-Z0-9_.~-])/uc sprintf("%%%02x",$E2A[ord($1)])/eg;
+ } else {
+ $toencode=~s/([^a-zA-Z0-9_.~-])/uc sprintf("%%%02x",ord($1))/eg;
+ }
+ return $toencode;
+}
+
+# This internal routine creates date strings suitable for use in
+# cookies and HTTP headers. (They differ, unfortunately.)
+# Thanks to Mark Fisher for this.
+sub expires {
+ my($time,$format) = @_;
+ $format ||= 'http';
+
+ my(@MON)=qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/;
+ my(@WDAY) = qw/Sun Mon Tue Wed Thu Fri Sat/;
+
+ # pass through preformatted dates for the sake of expire_calc()
+ $time = expire_calc($time);
+ return $time unless $time =~ /^\d+$/;
+
+ # make HTTP/cookie date string from GMT'ed time
+ # (cookies use '-' as date separator, HTTP uses ' ')
+ my($sc) = ' ';
+ $sc = '-' if $format eq "cookie";
+ my($sec,$min,$hour,$mday,$mon,$year,$wday) = gmtime($time);
+ $year += 1900;
+ return sprintf("%s, %02d$sc%s$sc%04d %02d:%02d:%02d GMT",
+ $WDAY[$wday],$mday,$MON[$mon],$year,$hour,$min,$sec);
+}
+
+# This internal routine creates an expires time exactly some number of
+# hours from the current time. It incorporates modifications from
+# Mark Fisher.
+sub expire_calc {
+ my($time) = @_;
+ my(%mult) = ('s'=>1,
+ 'm'=>60,
+ 'h'=>60*60,
+ 'd'=>60*60*24,
+ 'M'=>60*60*24*30,
+ 'y'=>60*60*24*365);
+ # format for time can be in any of the forms...
+ # "now" -- expire immediately
+ # "+180s" -- in 180 seconds
+ # "+2m" -- in 2 minutes
+ # "+12h" -- in 12 hours
+ # "+1d" -- in 1 day
+ # "+3M" -- in 3 months
+ # "+2y" -- in 2 years
+ # "-3m" -- 3 minutes ago(!)
+ # If you don't supply one of these forms, we assume you are
+ # specifying the date yourself
+ my($offset);
+ if (!$time || (lc($time) eq 'now')) {
+ $offset = 0;
+ } elsif ($time=~/^\d+/) {
+ return $time;
+ } elsif ($time=~/^([+-]?(?:\d+|\d*\.\d*))([smhdMy])/) {
+ $offset = ($mult{$2} || 1)*$1;
+ } else {
+ return $time;
+ }
+ my $cur_time = time;
+ return ($cur_time+$offset);
+}
+
+sub ebcdic2ascii {
+ my $data = shift;
+ $data =~ s/(.)/chr $E2A[ord($1)]/ge;
+ $data;
+}
+
+sub ascii2ebcdic {
+ my $data = shift;
+ $data =~ s/(.)/chr $A2E[ord($1)]/ge;
+ $data;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+CGI::Util - Internal utilities used by CGI module
+
+=head1 SYNOPSIS
+
+none
+
+=head1 DESCRIPTION
+
+no public subroutines
+
+=head1 AUTHOR INFORMATION
+
+The CGI.pm distribution is copyright 1995-2007, Lincoln D. Stein. It is
+distributed under GPL and the Artistic License 2.0. It is currently
+maintained by Lee Johnson with help from many contributors.
+
+Address bug reports and comments to: https://github.com/leejo/CGI.pm/issues
+
+The original bug tracker can be found at: https://rt.cpan.org/Public/Dist/Display.html?Queue=CGI.pm
+
+When sending bug reports, please provide the version of CGI.pm, the version of
+Perl, the name and version of your Web server, and the name and version of the
+operating system you are using. If the problem is even remotely browser
+dependent, please provide information about the affected browsers as well.
+
+=head1 SEE ALSO
+
+L<CGI>
+
+=cut
diff --git a/lib/Fh.pm b/lib/Fh.pm
new file mode 100644
index 0000000..b798769
--- /dev/null
+++ b/lib/Fh.pm
@@ -0,0 +1,7 @@
+# back compatibility package for any code explicitly checking
+# that the filehandle object is a Fh
+package Fh;
+
+$Fh::VERSION = '4.21';
+
+1;
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' );