pax_global_header00006660000000000000000000000064145555161010014515gustar00rootroot0000000000000052 comment=5b2659eaba310d7beabeeda3c76ed0d0d5ff96ae libcgi-tiny-perl-1.002/000077500000000000000000000000001455551610100146715ustar00rootroot00000000000000libcgi-tiny-perl-1.002/CONTRIBUTING.md000066400000000000000000000105551455551610100171300ustar00rootroot00000000000000# HOW TO CONTRIBUTE Thank you for considering contributing to this distribution. This file contains instructions that will help you work with the source code. The distribution is managed with [Dist::Zilla](https://metacpan.org/pod/Dist::Zilla). This means that many of the usual files you might expect are not in the repository, but are generated at release time. Some generated files are kept in the repository as a convenience (e.g. Build.PL/Makefile.PL and META.json). Generally, **you do not need Dist::Zilla to contribute patches**. You may need Dist::Zilla to create a tarball. See below for guidance. ## Getting dependencies If you have App::cpanminus 1.6 or later installed, you can use [cpanm](https://metacpan.org/pod/cpanm) to satisfy dependencies like this: $ cpanm --installdeps --with-develop . You can also run this command (or any other cpanm command) without installing App::cpanminus first, using the fatpacked `cpanm` script via curl or wget: $ curl -L https://cpanmin.us | perl - --installdeps --with-develop . $ wget -qO - https://cpanmin.us | perl - --installdeps --with-develop . Otherwise, look for either a `cpanfile`, `prereqs.json`/`prereqs.yml`, or `META.json` file for a list of dependencies to satisfy. ## Running tests You can run tests directly using the `prove` tool: $ prove -l $ prove -lv t/some_test_file.t For most of my distributions, `prove` is entirely sufficient for you to test any patches you have. I use `prove` for 99% of my testing during development. ## Code style and tidying Please try to match any existing coding style. If there is a `.perltidyrc` file, please install Perl::Tidy and use perltidy before submitting patches. ## Installing and using Dist::Zilla [Dist::Zilla](https://metacpan.org/pod/Dist::Zilla) is a very powerful authoring tool, optimized for maintaining a large number of distributions with a high degree of automation, but it has a large dependency chain, a bit of a learning curve and requires a number of author-specific plugins. To install it from CPAN, I recommend one of the following approaches for the quickest installation: # using CPAN.pm, but bypassing non-functional pod tests $ cpan TAP::Harness::Restricted $ PERL_MM_USE_DEFAULT=1 HARNESS_CLASS=TAP::Harness::Restricted cpan Dist::Zilla # using cpanm, bypassing *all* tests $ cpanm -n Dist::Zilla In either case, it's probably going to take about 10 minutes. Go for a walk, go get a cup of your favorite beverage, take a bathroom break, or whatever. When you get back, Dist::Zilla should be ready for you. Then you need to install any plugins specific to this distribution: $ dzil authordeps --missing | cpanm You can use Dist::Zilla to install the distribution's dependencies if you haven't already installed them with cpanm: $ dzil listdeps --missing --develop | cpanm You can instead combine these two steps into one command by installing Dist::Zilla::App::Command::installdeps then running: $ dzil installdeps Once everything is installed, here are some dzil commands you might try: $ dzil build $ dzil test $ dzil regenerate You can learn more about Dist::Zilla at http://dzil.org/ ## Other notes This distribution maintains the generated `META.json` and either `Makefile.PL` or `Build.PL` in the repository. This allows two things: [Travis CI](https://travis-ci.org/) can build and test the distribution without requiring Dist::Zilla, and the distribution can be installed directly from Github or a local git repository using `cpanm` for testing (again, not requiring Dist::Zilla). $ cpanm git://github.com/Author/Distribution-Name.git $ cd Distribution-Name; cpanm . Contributions are preferred in the form of a Github pull request. See [Using pull requests](https://help.github.com/articles/using-pull-requests/) for further information. You can use the Github issue tracker to report issues without an accompanying patch. # CREDITS This file was adapted from an initial `CONTRIBUTING.mkdn` file from David Golden under the terms of the [CC0](https://creativecommons.org/share-your-work/public-domain/cc0/), with inspiration from the contributing documents from [Dist::Zilla::Plugin::Author::KENTNL::CONTRIBUTING](https://metacpan.org/pod/Dist::Zilla::Plugin::Author::KENTNL::CONTRIBUTING) and [Dist::Zilla::PluginBundle::Author::ETHER](https://metacpan.org/pod/Dist::Zilla::PluginBundle::Author::ETHER). libcgi-tiny-perl-1.002/Changes000066400000000000000000000113601455551610100161650ustar00rootroot000000000000001.002 2021-06-09 00:33:32 EDT - Documentation updates 1.001 2021-06-07 23:16:34 EDT - Move "EXTENDING" examples to CGI::Tiny::Cookbook and examples/ 1.000 2021-05-30 17:09:42 EDT - Remove experimental status 0.020 2021-05-30 00:05:56 EDT - Replace set_discard_form_files method with more generic set_multipart_form_options - Improved test coverage 0.019 2021-05-25 23:52:51 EDT - Add params, param_names, param, and param_array general request parameter accessors - Add on_file_buffer callback option to CGI::Tiny::Multipart::parse_multipart_form_data for custom form parsing - Rename tempfiles option to parse_as_files for CGI::Tiny::Multipart::parse_multipart_form_data 0.018 2021-05-19 18:12:06 EDT - CGI::Tiny::Multipart::parse_multipart_form_data now takes a single tempfiles option instead of all_tempfiles and no_tempfiles 0.017 2021-05-19 01:41:51 EDT - Split multipart/form-data parser into CGI::Tiny::Multipart module, which is reusable and only loaded if needed - Add set_discard_form_files method 0.016 2021-05-05 21:31:12 EDT - Add CGI::Tiny::escape_html convenience function 0.015 2021-05-05 01:57:53 EDT - Add debugging commands for commandline convenience - Ignore response content passed to render and render_chunk in HEAD requests - Omit autodetected Content-Type header when rendering empty fixed-length responses 0.014 2021-05-04 03:26:46 EDT - Remove headers_rendered method, instead pass rendered status to error handlers 0.013 2021-05-03 23:32:56 EDT - Skip tests that don't work on Windows - Rendering a redirect will now set the response status to 302 if any non-300 status was set - Exceptions and failure to render now set a 500 response status unless any error status was set 0.012 2021-05-02 23:28:28 EDT - Remove set_response_fixed_length - render now always sets a Content-Length and can only be called once - Add render_chunk method which can be used instead of render for chunked content - set_nph now sets a true value if called without a value - Gracefully handle premature exit before the cgi block - Exceptions no longer cause the response status to default to 500 unless the error handler doesn't render anything 0.011 2021-04-29 02:12:59 EDT - Rename set_response_content_type and set_response_content_disposition to set_response_type and set_response_disposition - Add set_response_fixed_length method 0.010 2021-04-28 23:14:59 EDT - Replace set_response_download method with set_response_content_disposition - Add reset_response_headers method 0.009 2021-04-28 03:03:52 EDT - query_param_names, body_param_names, cookie_names, and upload_names now return names in the original request order - Add set_response_download and set_response_body_buffer methods - Add file and handle render options 0.008 2021-04-27 21:11:22 EDT - Fix parsing of empty multipart/form-data forms and some other edge cases - Add response_status_code method 0.007 2021-04-27 02:00:33 EDT - Support reading request body parameters from multipart/form-data requests - Add uploads, upload_names, upload, and upload_array methods to support multipart/form-data file uploads - Add body_parts method to return raw multipart/form-data parts - Add set_request_body_buffer and set_multipart_form_charset methods 0.006 2021-04-25 14:48:30 EDT - Use Unicode::UTF8 for encoding output data if available - query_params, body_params, and cookies methods now return pairs instead of a hashref - Remove query_pairs and body_pairs methods - Add query_param_names, body_param_names, and cookie_names methods - Add cookie_array method to support multiple request cookies with the same name 0.005 2021-04-22 21:11:18 EDT - Separate documentation from code for efficiency 0.004 2021-04-21 21:27:52 EDT - Fork-safety in error handling and cleanup - set_response_status now also accepts a full status string which will be passed as-is - Remove the response_charset method - Prohibit newline characters in response header values to prevent HTTP response splitting vulnerabilities 0.003 2021-04-21 03:31:54 EDT - Add set_nph method and support NPH response mode - Add cookies and cookie methods to parse request cookies - Add add_response_cookie method to set response cookies - Add CGI::Tiny::epoch_to_date and CGI::Tiny::date_to_epoch convenience functions - Set Date header in all responses - Pass the CGI::Tiny object to the cgi block as $_ instead of in @_ - Remove the request_body_limit method - Handle when exit is called without rendering a response 0.002 2021-04-19 03:59:16 EDT - Remove header_names method - Throw an exception if render is called with an unknown type 0.001 2021-04-19 00:43:20 EDT - First release libcgi-tiny-perl-1.002/INSTALL000066400000000000000000000045171455551610100157310ustar00rootroot00000000000000This is the Perl distribution CGI-Tiny. Installing CGI-Tiny is straightforward. ## Installation with cpanm If you have cpanm, you only need one line: % cpanm CGI::Tiny If it does not have permission to install modules to the current perl, cpanm will automatically set up and install to a local::lib in your home directory. See the local::lib documentation (https://metacpan.org/pod/local::lib) for details on enabling it in your environment. ## Installing with the CPAN shell Alternatively, if your CPAN shell is set up, you should just be able to do: % cpan CGI::Tiny ## Manual installation As a last resort, you can manually install it. If you have not already downloaded the release tarball, you can find the download link on the module's MetaCPAN page: https://metacpan.org/pod/CGI::Tiny Untar the tarball, install configure prerequisites (see below), then build it: % perl Makefile.PL % make && make test Then install it: % make install On Windows platforms, you should use `dmake` or `nmake`, instead of `make`. If your perl is system-managed, you can create a local::lib in your home directory to install modules to. For details, see the local::lib documentation: https://metacpan.org/pod/local::lib The prerequisites of this distribution will also have to be installed manually. The prerequisites are listed in one of the files: `MYMETA.yml` or `MYMETA.json` generated by running the manual build process described above. ## Configure Prerequisites This distribution requires other modules to be installed before this distribution's installer can be run. They can be found under the "configure_requires" key of META.yml or the "{prereqs}{configure}{requires}" key of META.json. ## Other Prerequisites This distribution may require additional modules to be installed after running Makefile.PL. Look for prerequisites in the following phases: * to run make, PHASE = build * to use the module code itself, PHASE = runtime * to run tests, PHASE = test They can all be found in the "PHASE_requires" key of MYMETA.yml or the "{prereqs}{PHASE}{requires}" key of MYMETA.json. ## Documentation CGI-Tiny documentation is available as POD. You can run `perldoc` from a shell to read the documentation: % perldoc CGI::Tiny For more information on installing Perl modules via CPAN, please see: https://www.cpan.org/modules/INSTALL.html libcgi-tiny-perl-1.002/LICENSE000066400000000000000000000215151455551610100157020ustar00rootroot00000000000000This software is Copyright (c) 2021 by Dan Book. This is free software, licensed under: The Artistic License 2.0 (GPL Compatible) The Artistic License 2.0 Copyright (c) 2000-2006, The Perl Foundation. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble This license establishes the terms under which a given free software Package may be copied, modified, distributed, and/or redistributed. The intent is that the Copyright Holder maintains some artistic control over the development of that Package while still keeping the Package available as open source and free software. You are always permitted to make arrangements wholly outside of this license directly with the Copyright Holder of a given Package. If the terms of this license do not permit the full use that you propose to make of the Package, you should contact the Copyright Holder and seek a different licensing arrangement. Definitions "Copyright Holder" means the individual(s) or organization(s) named in the copyright notice for the entire Package. "Contributor" means any party that has contributed code or other material to the Package, in accordance with the Copyright Holder's procedures. "You" and "your" means any person who would like to copy, distribute, or modify the Package. "Package" means the collection of files distributed by the Copyright Holder, and derivatives of that collection and/or of those files. A given Package may consist of either the Standard Version, or a Modified Version. "Distribute" means providing a copy of the Package or making it accessible to anyone else, or in the case of a company or organization, to others outside of your company or organization. "Distributor Fee" means any fee that you charge for Distributing this Package or providing support for this Package to another party. It does not mean licensing fees. "Standard Version" refers to the Package if it has not been modified, or has been modified only in ways explicitly requested by the Copyright Holder. "Modified Version" means the Package, if it has been changed, and such changes were not explicitly requested by the Copyright Holder. "Original License" means this Artistic License as Distributed with the Standard Version of the Package, in its current version or as it may be modified by The Perl Foundation in the future. "Source" form means the source code, documentation source, and configuration files for the Package. "Compiled" form means the compiled bytecode, object code, binary, or any other form resulting from mechanical transformation or translation of the Source form. Permission for Use and Modification Without Distribution (1) You are permitted to use the Standard Version and create and use Modified Versions for any purpose without restriction, provided that you do not Distribute the Modified Version. Permissions for Redistribution of the Standard Version (2) You may Distribute verbatim copies of the Source form of the Standard Version of this Package in any medium without restriction, either gratis or for a Distributor Fee, provided that you duplicate all of the original copyright notices and associated disclaimers. At your discretion, such verbatim copies may or may not include a Compiled form of the Package. (3) You may apply any bug fixes, portability changes, and other modifications made available from the Copyright Holder. The resulting Package will still be considered the Standard Version, and as such will be subject to the Original License. Distribution of Modified Versions of the Package as Source (4) You may Distribute your Modified Version as Source (either gratis or for a Distributor Fee, and with or without a Compiled form of the Modified Version) provided that you clearly document how it differs from the Standard Version, including, but not limited to, documenting any non-standard features, executables, or modules, and provided that you do at least ONE of the following: (a) make the Modified Version available to the Copyright Holder of the Standard Version, under the Original License, so that the Copyright Holder may include your modifications in the Standard Version. (b) ensure that installation of your Modified Version does not prevent the user installing or running the Standard Version. In addition, the Modified Version must bear a name that is different from the name of the Standard Version. (c) allow anyone who receives a copy of the Modified Version to make the Source form of the Modified Version available to others under (i) the Original License or (ii) a license that permits the licensee to freely copy, modify and redistribute the Modified Version using the same licensing terms that apply to the copy that the licensee received, and requires that the Source form of the Modified Version, and of any works derived from it, be made freely available in that license fees are prohibited but Distributor Fees are allowed. Distribution of Compiled Forms of the Standard Version or Modified Versions without the Source (5) You may Distribute Compiled forms of the Standard Version without the Source, provided that you include complete instructions on how to get the Source of the Standard Version. Such instructions must be valid at the time of your distribution. If these instructions, at any time while you are carrying out such distribution, become invalid, you must provide new instructions on demand or cease further distribution. If you provide valid instructions or cease distribution within thirty days after you become aware that the instructions are invalid, then you do not forfeit any of your rights under this license. (6) You may Distribute a Modified Version in Compiled form without the Source, provided that you comply with Section 4 with respect to the Source of the Modified Version. Aggregating or Linking the Package (7) You may aggregate the Package (either the Standard Version or Modified Version) with other packages and Distribute the resulting aggregation provided that you do not charge a licensing fee for the Package. Distributor Fees are permitted, and licensing fees for other components in the aggregation are permitted. The terms of this license apply to the use and Distribution of the Standard or Modified Versions as included in the aggregation. (8) You are permitted to link Modified and Standard Versions with other works, to embed the Package in a larger work of your own, or to build stand-alone binary or bytecode versions of applications that include the Package, and Distribute the result without restriction, provided the result does not expose a direct interface to the Package. Items That are Not Considered Part of a Modified Version (9) Works (including, but not limited to, modules and scripts) that merely extend or make use of the Package, do not, by themselves, cause the Package to be a Modified Version. In addition, such works are not considered parts of the Package itself, and are not subject to the terms of this license. General Provisions (10) Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license. (11) If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license. (12) This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder. (13) This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed. (14) Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. libcgi-tiny-perl-1.002/MANIFEST000066400000000000000000000013531455551610100160240ustar00rootroot00000000000000# This file was automatically generated by Dist::Zilla::Plugin::Manifest v6.017. CONTRIBUTING.md Changes INSTALL LICENSE MANIFEST META.json META.yml Makefile.PL README dist.ini examples/cookies-hash.cgi examples/cookies-unicode.cgi examples/files.cgi examples/logging.cgi examples/routing.cgi examples/sessions-basic.cgi examples/sessions-cookie.cgi examples/templates/index.html.ep examples/templates/index.tx examples/templating-mojo.cgi examples/templating-xslate.cgi lib/CGI/Tiny.pm lib/CGI/Tiny.pod lib/CGI/Tiny/Cookbook.pod lib/CGI/Tiny/Multipart.pm lib/CGI/Tiny/Multipart.pod lib/CGI/Tiny/_Debug.pm prereqs.yml t/00-report-prereqs.dd t/00-report-prereqs.t t/cgi.t t/functions.t t/multipart.t xt/author/pod-coverage.t xt/author/pod-syntax.t libcgi-tiny-perl-1.002/META.json000066400000000000000000000050721455551610100163160ustar00rootroot00000000000000{ "abstract" : "Common Gateway Interface, with no frills", "author" : [ "Dan Book " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.017, CPAN::Meta::Converter version 2.150010", "license" : [ "artistic_2" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "CGI-Tiny", "no_index" : { "directory" : [ "eg", "examples", "inc", "share", "t", "xt" ], "package" : [ "CGI::Tiny::_Debug" ] }, "prereqs" : { "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "develop" : { "requires" : { "Pod::Coverage::TrustPod" : "0", "Test::Pod" : "1.41", "Test::Pod::Coverage" : "1.08" } }, "runtime" : { "recommends" : { "Cpanel::JSON::XS" : "4.09", "Unicode::UTF8" : "0" }, "requires" : { "Carp" : "0", "Encode" : "0", "Exporter" : "5.57", "File::Basename" : "0", "File::Temp" : "0", "Getopt::Long" : "0", "IO::Handle" : "0", "JSON::PP" : "0", "Sys::Hostname" : "0", "Time::Local" : "0", "constant" : "0", "perl" : "5.008001" } }, "test" : { "recommends" : { "CPAN::Meta" : "2.120900" }, "requires" : { "ExtUtils::MakeMaker" : "0", "File::Spec" : "0", "MIME::Base64" : "0", "Test::More" : "0.96" } } }, "provides" : { "CGI::Tiny" : { "file" : "lib/CGI/Tiny.pm", "version" : "1.002" }, "CGI::Tiny::Multipart" : { "file" : "lib/CGI/Tiny/Multipart.pm", "version" : "1.002" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/Grinnz/CGI-Tiny/issues" }, "homepage" : "https://github.com/Grinnz/CGI-Tiny", "repository" : { "type" : "git", "url" : "https://github.com/Grinnz/CGI-Tiny.git", "web" : "https://github.com/Grinnz/CGI-Tiny" } }, "version" : "1.002", "x_contributors" : [ "Dan Book " ], "x_generated_by_perl" : "v5.34.0", "x_serialization_backend" : "Cpanel::JSON::XS version 4.26", "x_spdx_expression" : "Artistic-2.0" } libcgi-tiny-perl-1.002/META.yml000066400000000000000000000025371455551610100161510ustar00rootroot00000000000000--- abstract: 'Common Gateway Interface, with no frills' author: - 'Dan Book ' build_requires: ExtUtils::MakeMaker: '0' File::Spec: '0' MIME::Base64: '0' Test::More: '0.96' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 generated_by: 'Dist::Zilla version 6.017, CPAN::Meta::Converter version 2.150010' license: artistic_2 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: CGI-Tiny no_index: directory: - eg - examples - inc - share - t - xt package: - CGI::Tiny::_Debug provides: CGI::Tiny: file: lib/CGI/Tiny.pm version: '1.002' CGI::Tiny::Multipart: file: lib/CGI/Tiny/Multipart.pm version: '1.002' recommends: Cpanel::JSON::XS: '4.09' Unicode::UTF8: '0' requires: Carp: '0' Encode: '0' Exporter: '5.57' File::Basename: '0' File::Temp: '0' Getopt::Long: '0' IO::Handle: '0' JSON::PP: '0' Sys::Hostname: '0' Time::Local: '0' constant: '0' perl: '5.008001' resources: bugtracker: https://github.com/Grinnz/CGI-Tiny/issues homepage: https://github.com/Grinnz/CGI-Tiny repository: https://github.com/Grinnz/CGI-Tiny.git version: '1.002' x_contributors: - 'Dan Book ' x_generated_by_perl: v5.34.0 x_serialization_backend: 'YAML::Tiny version 1.73' x_spdx_expression: Artistic-2.0 libcgi-tiny-perl-1.002/Makefile.PL000066400000000000000000000031511455551610100166430ustar00rootroot00000000000000# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v6.017. use strict; use warnings; use 5.008001; use ExtUtils::MakeMaker; my %WriteMakefileArgs = ( "ABSTRACT" => "Common Gateway Interface, with no frills", "AUTHOR" => "Dan Book ", "CONFIGURE_REQUIRES" => { "ExtUtils::MakeMaker" => 0 }, "DISTNAME" => "CGI-Tiny", "LICENSE" => "artistic_2", "MIN_PERL_VERSION" => "5.008001", "NAME" => "CGI::Tiny", "PREREQ_PM" => { "Carp" => 0, "Encode" => 0, "Exporter" => "5.57", "File::Basename" => 0, "File::Temp" => 0, "Getopt::Long" => 0, "IO::Handle" => 0, "JSON::PP" => 0, "Sys::Hostname" => 0, "Time::Local" => 0, "constant" => 0 }, "TEST_REQUIRES" => { "ExtUtils::MakeMaker" => 0, "File::Spec" => 0, "MIME::Base64" => 0, "Test::More" => "0.96" }, "VERSION" => "1.002", "test" => { "TESTS" => "t/*.t" } ); my %FallbackPrereqs = ( "Carp" => 0, "Encode" => 0, "Exporter" => "5.57", "ExtUtils::MakeMaker" => 0, "File::Basename" => 0, "File::Spec" => 0, "File::Temp" => 0, "Getopt::Long" => 0, "IO::Handle" => 0, "JSON::PP" => 0, "MIME::Base64" => 0, "Sys::Hostname" => 0, "Test::More" => "0.96", "Time::Local" => 0, "constant" => 0 ); unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) { delete $WriteMakefileArgs{TEST_REQUIRES}; delete $WriteMakefileArgs{BUILD_REQUIRES}; $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs; } delete $WriteMakefileArgs{CONFIGURE_REQUIRES} unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; WriteMakefile(%WriteMakefileArgs); libcgi-tiny-perl-1.002/README000066400000000000000000001243771455551610100155670ustar00rootroot00000000000000NAME CGI::Tiny - Common Gateway Interface, with no frills SYNOPSIS #!/usr/bin/perl use strict; use warnings; use utf8; use CGI::Tiny; cgi { my $cgi = $_; $cgi->set_error_handler(sub { my ($cgi, $error, $rendered) = @_; warn $error; unless ($rendered) { if ($cgi->response_status_code == 413) { $cgi->render(json => {error => 'Request body limit exceeded'}); } elsif ($cgi->response_status_code == 400) { $cgi->render(json => {error => 'Bad request'}); } else { $cgi->render(json => {error => 'Internal server error'}); } } }); my $method = $cgi->method; my $fribble; if ($method eq 'GET' or $method eq 'HEAD') { $fribble = $cgi->query_param('fribble'); } elsif ($method eq 'POST') { $fribble = $cgi->body_param('fribble'); } else { $cgi->set_response_status(405)->render; exit; } die "Invalid fribble parameter" unless length $fribble; if ($cgi->param('download')) { $cgi->set_response_disposition(attachment => 'fribble.json'); } $cgi->render(json => {fribble => $fribble}); }; DESCRIPTION CGI::Tiny provides a modern interface to write CGI scripts to dynamically respond to HTTP requests as defined in RFC 3875 . It is intended to be: * Minimal CGI::Tiny contains a small amount of code and (on modern Perls) no non-core requirements. No framework needed. * Simple CGI::Tiny is straightforward to use, avoids anything magical or surprising, and provides easy access to the most commonly needed features. * Robust CGI::Tiny's interface is designed to help the developer follow best practices and avoid common pitfalls and vulnerabilities by default. * Lazy CGI::Tiny only loads code or processes information once it is needed, so simple requests can be handled without unnecessary overhead. * Restrained CGI::Tiny is designed for the CGI protocol which executes the program again for every request. It is not suitable for persistent protocols like FastCGI or PSGI. * Flexible CGI::Tiny can be used with other modules to handle tasks like routing and templating, and doesn't impose unnecessary constraints to reading input or rendering output. Most applications are better written in a PSGI-compatible framework (e.g. Dancer2 or Mojolicious) and deployed in a persistent application server so that the application does not have to start up again every time it receives a request. CGI::Tiny, and the CGI protocol in general, is only suited for restricted deployment environments that can only run CGI scripts, or applications that don't need to scale. See "COMPARISON TO CGI.PM". USAGE CGI::Tiny's interface is the cgi block. use CGI::Tiny; cgi { my $cgi = $_; # set up error handling on $cgi # inspect request data via $cgi # set response headers if needed via $cgi # render response with $cgi->render or $cgi->render_chunk }; The block is immediately run with $_ set to a CGI::Tiny object, which "METHODS" can be called on to read request information and render a response. If an exception is thrown within the block, or the block does not render a response, it will run the handler set by "set_error_handler" if any, or by default emit the error as a warning and (if nothing has been rendered yet) render a 500 Internal Server Error. The default server error will also be rendered if the process ends abnormally between importing from CGI::Tiny and the start of the cgi block. To load CGI::Tiny without triggering this cleanup mechanism or making the cgi block available (such as to use convenience "FUNCTIONS" in non-CGI code), load the module with use CGI::Tiny (); or require CGI::Tiny;. NOTE: The cgi block's current implementation as a regular exported subroutine is an implementation detail, and future implementations reserve the right to provide it as an XSUB or keyword for performance reasons. Don't call it as CGI::Tiny::cgi, don't rely on @_ being set, and don't use return to exit the block; use exit to end a CGI script early after rendering a response. See CGI::Tiny::Cookbook for advanced usage examples. DATA SAFETY CGI::Tiny does not provide any special affordances for taint mode as it is overeager, imprecise, and can significantly impact performance. Web developers should instead proactively take care not to use any request data (including request headers, form fields, or other request content) directly in an unsafe manner, as it can make the program vulnerable to injections that cause undesired or dangerous behavior. The most common risks to watch out for include: * System commands Do not interpolate arbitrary data into a shell command, such as with system or backticks. Data can be safely passed as command arguments using methods that bypass the shell, such as the list form of system, or modules like IPC::System::Simple, IPC::ReadpipeX, and IPC::Run3. If shell features are needed, data can be escaped for bourne-style shells with String::ShellQuote. * Database queries Do not interpolate arbitrary data into database queries. Data can be safely passed to database queries using placeholders . * Regex Do not interpolate arbitrary data into regular expressions, such as the m// or s/// operators, or the first argument to split. Data can be safely included in a regex to match it as an exact string by escaping it with the quotemeta function or equivalent \Q escape sequence. * HTML Do not interpolate arbitrary data into HTML. Data can be safely included in HTML by escaping it with "escape_html", or passing it to an HTML template engine with an auto-escape feature; see "Templating" in CGI::Tiny::Cookbook. METHODS The following methods can be called on the CGI::Tiny object provided to the cgi block. Setup set_error_handler $cgi = $cgi->set_error_handler(sub { my ($cgi, $error, $rendered) = @_; ... }); Sets an error handler to run in the event of an exception or if the script ends without rendering a response. The handler will be called with the CGI::Tiny object, the error value, and a boolean indicating whether response headers have been rendered yet. The error value can be any exception thrown by Perl or user code. It should generally not be included in any response rendered to the client, but instead warned or logged. Exceptions may occur before or after response headers have been rendered. If response headers have not been rendered, error handlers may inspect "response_status_code" and/or render some error response. The response status code will be set to 500 when this handler is called if it has not been set to a specific 400- or 500-level error status. If the error handler itself throws an exception, that error and the original error will be emitted as a warning. If no response has been rendered after the error handler completes or dies, a default error response will be rendered. NOTE: The error handler is only meant for logging and customization of the final error response in a failed request dispatch; to handle exceptions within standard application flow without causing an error response, use an exception handling mechanism such as Syntax::Keyword::Try or Feature::Compat::Try (which will use the new try feature if available). set_request_body_buffer $cgi = $cgi->set_request_body_buffer(256*1024); Sets the buffer size (number of bytes to read at once) for reading the request body. Defaults to the value of the CGI_TINY_REQUEST_BODY_BUFFER environment variable or 262144 (256 KiB). A value of 0 will use the default value. set_request_body_limit $cgi = $cgi->set_request_body_limit(16*1024*1024); Sets the limit in bytes for the request body. Defaults to the value of the CGI_TINY_REQUEST_BODY_LIMIT environment variable or 16777216 (16 MiB). A value of 0 will remove the limit (not recommended unless you have other safeguards on memory usage). Since the request body is not parsed until needed, methods that parse the request body like "body" or "upload" will set the response status to 413 Payload Too Large and throw an exception if the content length is over the limit. Files uploaded through a multipart/form-data request body also count toward this limit, though they are streamed to temporary files when parsed. set_multipart_form_options $cgi = $cgi->set_multipart_form_options({discard_files => 1, tempfile_args => [SUFFIX => '.dat']}); Set a hash reference of options to pass when parsing a multipart/form-data request body with "parse_multipart_form_data" in CGI::Tiny::Multipart. No effect after the form data has been parsed such as by calling "body_params" or "uploads" for the first time. NOTE: Options like parse_as_files and on_file_buffer can alter the content and file keys of the form field structure returned by "body_parts". Thus "uploads" may not contain file and may instead contain content, and "body_params" text field values may be read from file, which will be expected to be a seekable filehandle if present. set_multipart_form_charset $cgi = $cgi->set_multipart_form_charset('UTF-8'); Sets the default charset for decoding multipart/form-data forms, defaults to UTF-8. Parameter and upload field names, upload filenames, and text parameter values that don't specify a charset will be decoded from this charset. Set to an empty string to disable this decoding, effectively interpreting such values in ISO-8859-1. set_input_handle $cgi = $cgi->set_input_handle($fh); Sets the input handle to read the request body from. If not set, reads from STDIN. The handle will have binmode applied before reading to remove any translation layers. set_output_handle $cgi = $cgi->set_output_handle($fh); Sets the output handle to print the response to. If not set, prints to STDOUT. The handle will have binmode applied before printing to remove any translation layers. Request Environment CGI::Tiny provides direct access to CGI request meta-variables via methods that map to the equivalent uppercase names (and a few short aliases). Since CGI does not distinguish between missing and empty values, missing values will be normalized to an empty string. auth_type # AUTH_TYPE="Basic" my $auth_type = $cgi->auth_type; The authentication scheme used in the Authorization HTTP request header if any. content_length # CONTENT_LENGTH="42" my $content_length = $cgi->content_length; The size in bytes of the request body content if any. content_type # CONTENT_TYPE="text/plain;charset=UTF-8" my $content_type = $cgi->content_type; The MIME type of the request body content if any. gateway_interface # GATEWAY_INTERFACE="CGI/1.1" my $gateway_inteface = $cgi->gateway_interface; The CGI version used for communication with the CGI server. path_info path # PATH_INFO="/foo/42" my $path = $cgi->path_info; my $path = $cgi->path; The URL path following the SCRIPT_NAME in the request URL if any. path_translated # PATH_TRANSLATED="/var/www/html/foo/42" my $path_translated = $cgi->path_translated; A local file path derived from PATH_INFO by the CGI server, as if it were a request to the document root, if it chooses to provide it. query_string query # QUERY_STRING="foo=bar" my $query = $cgi->query_string; my $query = $cgi->query; The query string component of the request URL. remote_addr # REMOTE_ADDR="8.8.8.8" my $remote_addr = $cgi->remote_addr; The IPv4 or IPv6 address of the requesting client. remote_host # REMOTE_HOST="example.com" my $remote_host = $cgi->remote_host; The domain name of the requesting client if available, or REMOTE_ADDR. remote_ident # REMOTE_IDENT="someuser" my $remote_ident = $cgi->remote_ident; The identity of the client reported by an RFC 1413 ident request if available. remote_user # REMOTE_USER="someuser" my $remote_user = $cgi->remote_user; The user identity provided as part of an AUTH_TYPE authenticated request. request_method method # REQUEST_METHOD="GET" my $method = $cgi->request_method; my $method = $cgi->method; The HTTP request method verb. script_name # SCRIPT_NAME="/cgi-bin/script.cgi" my $script_name = $cgi->script_name; The host-relative URL path to the CGI script. server_name # SERVER_NAME="example.com" my $server_name = $cgi->server_name; The hostname of the server as the target of the request, such as from the Host HTTP request header. server_port # SERVER_PORT="443" my $server_port = $cgi->server_port; The TCP port on which the server received the request. server_protocol # SERVER_PROTOCOL="HTTP/1.1" my $server_protocol = $cgi->server_protocol; The HTTP protocol version of the request. server_software # SERVER_SOFTWARE="Apache\/2.4.37 (centos)" my $server_software = $cgi->server_software; The name and version of the CGI server. Request Parsing headers my $hashref = $cgi->headers; Hash reference of available request header names and values. Header names are represented in lowercase. header my $value = $cgi->header('Accept-Language'); Retrieve the value of a request header by name (case insensitive). CGI request headers can only contain a single value, which may be combined from multiple values. cookies my $pairs = $cgi->cookies; Retrieve request cookies as an ordered array reference of name/value pairs, represented as two-element array references. cookie_names my $arrayref = $cgi->cookie_names; Retrieve request cookie names as an ordered array reference, without duplication. cookie my $value = $cgi->cookie('foo'); Retrieve the value of a request cookie by name. If multiple cookies were passed with the same name, returns the last value. Use "cookie_array" to get multiple values of a cookie name. cookie_array my $arrayref = $cgi->cookie_array('foo'); Retrieve values of a request cookie name as an ordered array reference. params my $pairs = $cgi->params; Retrieve URL query string parameters and application/x-www-form-urlencoded or multipart/form-data body parameters as an ordered array reference of name/value pairs, represented as two-element array references. Names and values are decoded to Unicode characters. Query parameters are returned first, followed by body parameters. Use "query_params" or "body_params" to retrieve query or body parameters separately. NOTE: This will read the text form fields into memory as in "body_params". param_names my $arrayref = $cgi->param_names; Retrieve URL query string parameter names and application/x-www-form-urlencoded or multipart/form-data body parameter names, decoded to Unicode characters, as an ordered array reference, without duplication. Query parameter names are returned first, followed by body parameter names. Use "query_param_names" or "body_param_names" to retrieve query or body parameter names separately. NOTE: This will read the text form fields into memory as in "body_params". param my $value = $cgi->param('foo'); Retrieve value of a named URL query string parameter or application/x-www-form-urlencoded or multipart/form-data body parameter, decoded to Unicode characters. If the parameter name was passed multiple times, returns the last body parameter value if any, otherwise the last query parameter value. Use "param_array" to get multiple values of a parameter, or "query_param" or "body_param" to retrieve the last query or body parameter value specifically. NOTE: This will read the text form fields into memory as in "body_params". param_array my $arrayref = $cgi->param_array('foo'); Retrieve values of a named URL query string parameter or application/x-www-form-urlencoded or multipart/form-data body parameter, decoded to Unicode characters, as an ordered array reference. Query parameter values will be returned first, followed by body parameter values. Use "query_param_array" or "body_param_array" to retrieve query or body parameter values separately. NOTE: This will read the text form fields into memory as in "body_params". query_params my $pairs = $cgi->query_params; Retrieve URL query string parameters as an ordered array reference of name/value pairs, represented as two-element array references. Names and values are decoded to Unicode characters. query_param_names my $arrayref = $cgi->query_param_names; Retrieve URL query string parameter names, decoded to Unicode characters, as an ordered array reference, without duplication. query_param my $value = $cgi->query_param('foo'); Retrieve value of a named URL query string parameter, decoded to Unicode characters. If the parameter name was passed multiple times, returns the last value. Use "query_param_array" to get multiple values of a parameter. query_param_array my $arrayref = $cgi->query_param_array('foo'); Retrieve values of a named URL query string parameter, decoded to Unicode characters, as an ordered array reference. body my $bytes = $cgi->body; Retrieve the request body as bytes. NOTE: This will read the whole request body into memory, so make sure the "set_request_body_limit" can fit well within the available memory. Not available after calling "body_parts", "body_params", or "uploads" (or related accessors) on a multipart/form-data request, since this type of request body is not retained in memory after parsing. body_json my $data = $cgi->body_json; Decode an application/json request body from UTF-8-encoded JSON. NOTE: This will read the whole request body into memory, so make sure the "set_request_body_limit" can fit well within the available memory. body_params my $pairs = $cgi->body_params; Retrieve application/x-www-form-urlencoded or multipart/form-data body parameters as an ordered array reference of name/value pairs, represented as two-element array references. Names and values are decoded to Unicode characters. NOTE: This will read the text form fields into memory, so make sure the "set_request_body_limit" can fit well within the available memory. multipart/form-data file uploads will be streamed to temporary files accessible via "uploads" and related methods. body_param_names my $arrayref = $cgi->body_param_names; Retrieve application/x-www-form-urlencoded or multipart/form-data body parameter names, decoded to Unicode characters, as an ordered array reference, without duplication. NOTE: This will read the text form fields into memory as in "body_params". body_param my $value = $cgi->body_param('foo'); Retrieve value of a named application/x-www-form-urlencoded or multipart/form-data body parameter, decoded to Unicode characters. If the parameter name was passed multiple times, returns the last value. Use "body_param_array" to get multiple values of a parameter. NOTE: This will read the text form fields into memory as in "body_params". body_param_array my $arrayref = $cgi->body_param_array('foo'); Retrieve values of a named application/x-www-form-urlencoded or multipart/form-data body parameter, decoded to Unicode characters, as an ordered array reference. NOTE: This will read the text form fields into memory as in "body_params". body_parts my $parts = $cgi->body_parts; Retrieve multipart/form-data request body parts as an ordered array reference using "parse_multipart_form_data" in CGI::Tiny::Multipart. Most applications should retrieve multipart form data through "body_params" and "uploads" (or related accessors) instead. NOTE: This will read the text form fields into memory, so make sure the "set_request_body_limit" can fit well within the available memory. File uploads will be streamed to temporary files. uploads my $pairs = $cgi->uploads; Retrieve multipart/form-data file uploads as an ordered array reference of name/upload pairs, represented as two-element array references. Names are decoded to Unicode characters. NOTE: This will read the text form fields into memory, so make sure the "set_request_body_limit" can fit well within the available memory. File uploads are represented as a hash reference containing the following keys: filename Original filename supplied to file input. An empty filename may indicate that no file was submitted. content_type Content-Type of uploaded file, undef if unspecified. size File size in bytes. file File::Temp object storing the file contents in a temporary file, which will be cleaned up when the CGI script ends by default. The filehandle will be open with the seek pointer at the start of the file for reading. upload_names my $arrayref = $cgi->upload_names; Retrieve multipart/form-data file upload names, decoded to Unicode characters, as an ordered array reference, without duplication. NOTE: This will read the text form fields into memory as in "uploads". upload my $upload = $cgi->upload('foo'); Retrieve a named multipart/form-data file upload. If the upload name was passed multiple times, returns the last value. Use "upload_array" to get multiple uploads with the same name. See "uploads" for details on the representation of the upload. NOTE: This will read the text form fields into memory as in "uploads". upload_array my $arrayref = $cgi->upload_array('foo'); Retrieve all multipart/form-data file uploads of the specified name as an ordered array reference. See "uploads" for details on the representation of the uploads. NOTE: This will read the text form fields into memory as in "uploads". Response set_nph $cgi = $cgi->set_nph; $cgi = $cgi->set_nph(1); If set to a true value or called without a value before rendering response headers, CGI::Tiny will act as a NPH (Non-Parsed Header) script and render full HTTP response headers. This may be required for some CGI servers, or enable unbuffered responses or HTTP extensions not supported by the CGI server. No effect after response headers have been rendered. set_response_body_buffer $cgi = $cgi->set_response_body_buffer(128*1024); Sets the buffer size (number of bytes to read at once) for streaming a file or handle response body with "render" or "render_chunk". Defaults to the value of the CGI_TINY_RESPONSE_BODY_BUFFER environment variable or 131072 (128 KiB). A value of 0 will use the default value. set_response_status $cgi = $cgi->set_response_status(404); $cgi = $cgi->set_response_status('500 Internal Server Error'); Sets the response HTTP status code. A full status string including a human-readable message will be used as-is. A bare status code must be a known HTTP status code and will have the standard human-readable message appended. No effect after response headers have been rendered. The CGI protocol assumes a status of 200 OK if no response status is set. set_response_disposition $cgi = $cgi->set_response_disposition('attachment'); $cgi = $cgi->set_response_disposition(attachment => $filename); $cgi = $cgi->set_response_disposition('inline'); # default behavior $cgi = $cgi->set_response_disposition(inline => $filename); Sets the response Content-Disposition header to indicate how the client should present the response, with an optional filename specified in Unicode characters. attachment suggests to download the content as a file, and inline suggests to display the content inline (the default behavior). No effect after response headers have been rendered. set_response_type $cgi = $cgi->set_response_type('application/xml'); Sets the response Content-Type header, to override autodetection in "render" or "render_chunk". undef will remove the override. No effect after response headers have been rendered. set_response_charset $cgi = $cgi->set_response_charset('UTF-8'); Set charset to use when rendering text, html, or xml response content, defaults to UTF-8. add_response_header $cgi = $cgi->add_response_header('Content-Language' => 'en'); Adds a custom response header. No effect after response headers have been rendered. NOTE: Header names are case insensitive and CGI::Tiny does not attempt to deduplicate or munge headers that have been added manually. Headers are printed in the response in the same order added, and adding the same header multiple times will result in multiple instances of that response header. add_response_cookie $cgi = $cgi->add_response_cookie($name => $value, Expires => 'Sun, 06 Nov 1994 08:49:37 GMT', HttpOnly => 1, 'Max-Age' => 3600, Path => '/foo', SameSite => 'Strict', Secure => 1, ); Adds a Set-Cookie response header. No effect after response headers have been rendered. Cookie values should consist only of simple ASCII text; see "Cookies" in CGI::Tiny::Cookbook for methods of storing more complex strings and data structures. Optional cookie attributes are specified in key-value pairs after the cookie name and value. Cookie attribute names are case-insensitive. Domain Domain for which cookie is valid. Defaults to the host of the current document URL, not including subdomains. Expires Expiration date string for cookie. Defaults to persisting for the current browser session. "epoch_to_date" can be used to generate the appropriate date string format. HttpOnly If set to a true value, the cookie will be restricted from client-side scripts. Max-Age Max age of cookie before it expires, in seconds, as an alternative to specifying Expires. Path URL path for which cookie is valid. SameSite Strict to restrict the cookie to requests from the same site, Lax to allow it additionally in certain cross-site requests. This attribute is currently part of a draft specification so its handling may change, but it is supported by most browsers. Secure If set to a true value, the cookie will be restricted to HTTPS requests. reset_response_headers $cgi = $cgi->reset_response_headers; Remove any pending response headers set by "add_response_header" or "add_response_cookie". No effect after response headers have been rendered. response_status_code my $code = $cgi->response_status_code; Numerical response HTTP status code that will be sent when headers are rendered, as set by "set_response_status" or an error occurring. Defaults to 200. render $cgi = $cgi->render; # default Content-Type: $cgi = $cgi->render(text => $text); # text/plain;charset=$charset $cgi = $cgi->render(html => $html); # text/html;charset=$charset $cgi = $cgi->render(xml => $xml); # application/xml;charset=$charset $cgi = $cgi->render(json => $ref); # application/json;charset=UTF-8 $cgi = $cgi->render(data => $bytes); # application/octet-stream $cgi = $cgi->render(file => $filepath); # application/octet-stream $cgi = $cgi->render(redirect => $url); Renders response headers and then fixed-length response content of a type indicated by the first parameter, if any. A Content-Length header will be set to the length of the encoded response content, and further calls to render or "render_chunk" will throw an exception. Use "render_chunk" instead to render without a Content-Length header. The Content-Type response header will be set according to "set_response_type", or autodetected depending on the data type of any non-empty response content passed. The Date response header will be set to the current time as an HTTP date string if not set manually. If the "request_method" is HEAD, any provided response content will be ignored (other than redirect URLs) and Content-Length will be set to 0. text, html, or xml data is expected to be decoded Unicode characters, and will be encoded according to "set_response_charset" (UTF-8 by default). Unicode::UTF8 will be used for efficient UTF-8 encoding if available. json data structures will be encoded to JSON and UTF-8. data or file will render bytes from a string or local file path respectively. A handle, or a file whose size cannot be determined accurately from the filesystem, must be rendered using "render_chunk" since its Content-Length cannot be determined beforehand. redirect will set a Location header to redirect the client to another URL. The response status will be set to 302 Found unless a different 300-level status has been set with "set_response_status". It will set a Content-Length of 0, and it will not set a Content-Type response header. render_chunk $cgi = $cgi->render_chunk; # default Content-Type: $cgi = $cgi->render_chunk(text => $text); # text/plain;charset=$charset $cgi = $cgi->render_chunk(html => $html); # text/html;charset=$charset $cgi = $cgi->render_chunk(xml => $xml); # application/xml;charset=$charset $cgi = $cgi->render_chunk(json => $ref); # application/json;charset=UTF-8 $cgi = $cgi->render_chunk(data => $bytes); # application/octet-stream $cgi = $cgi->render_chunk(file => $filepath); # application/octet-stream $cgi = $cgi->render_chunk(handle => $filehandle); # application/octet-stream Renders response headers the first time it is called, and then chunked response content of a type indicated by the first parameter, if any. No Content-Length header will be set, and render_chunk may be called additional times with more response content. render_chunk does not impose a chunked response, it simply does not generate a Content-Length header. For content where the total encoded content length is known in advance but the content can't be passed to a single "render" call, a Content-Length header can be set manually with "add_response_header", and then render_chunk may be used to render each part. The Content-Type response header will be set according to "set_response_type", or autodetected depending on the data type passed in the first call to render_chunk, or to application/octet-stream if there is no more appropriate value. It will be set even if no content is passed to the first render_chunk call, in case content is rendered in subsequent calls. The Date response header will be set to the current time as an HTTP date string if not set manually. If the "request_method" is HEAD, any provided response content will be ignored. text, html, or xml data is expected to be decoded Unicode characters, and will be encoded according to "set_response_charset" (UTF-8 by default). Unicode::UTF8 will be used for efficient UTF-8 encoding if available. json data structures will be encoded to JSON and UTF-8. data, file, or handle will render bytes from a string, local file path, or open filehandle respectively. A handle will have binmode applied to remove any translation layers, and its contents will be streamed until EOF. redirect responses must be rendered with "render". FUNCTIONS The following convenience functions are provided but not exported. epoch_to_date my $date = CGI::Tiny::epoch_to_date $epoch; Convert a Unix epoch timestamp, such as returned by time, to a RFC 1123 HTTP date string suitable for use in HTTP headers such as Date and Expires. date_to_epoch my $epoch = CGI::Tiny::date_to_epoch $date; Parse a RFC 1123 HTTP date string to a Unix epoch timestamp. For compatibility as required by RFC 7231 , legacy RFC 850 and ANSI C asctime date formats are also recognized. Returns undef if the string does not parse as any of these formats. # RFC 1123 my $epoch = CGI::Tiny::date_to_epoch 'Sun, 06 Nov 1994 08:49:37 GMT'; # RFC 850 my $epoch = CGI::Tiny::date_to_epoch 'Sunday, 06-Nov-94 08:49:37 GMT'; # asctime my $epoch = CGI::Tiny::date_to_epoch 'Sun Nov 6 08:49:37 1994'; escape_html my $escaped = CGI::Tiny::escape_html $text; Escapes characters that are unsafe for embedding in HTML text. The characters &<>"' will each be replaced with the corresponding HTML character reference (HTML entity). This functionality is built into most HTML template engines; see "Templating" in CGI::Tiny::Cookbook. For more general HTML entity escaping and unescaping use HTML::Entities. ENVIRONMENT CGI::Tiny recognizes the following environment variables, in addition to the standard CGI environment variables. CGI_TINY_REQUEST_BODY_BUFFER Default value for "set_request_body_buffer". CGI_TINY_REQUEST_BODY_LIMIT Default value for "set_request_body_limit". CGI_TINY_RESPONSE_BODY_BUFFER Default value for "set_response_body_buffer". DEBUGGING COMMANDS CGI::Tiny scripts can be executed from the commandline for debugging purposes. A command can be passed as the first argument to help set up the CGI environment. These commands are considered a development interface and come with no stability guarantee. $ ./script.cgi get '/?foo=bar' $ ./script.cgi head $ ./script.cgi post '/form' -C 'one=value' -C 'two=value' --content='foo=bar+baz' -H 'Content-Type: application/x-www-form-urlencoded' $ ./script.cgi put -H "Content-Length: $(stat --printf='%s' foo.dat)" -H "Content-Type: $(file -bi foo.dat)" , -c Passes the string value as request body content and sets the Content-Length request header to its size. --cookie=, -C String values of the form name=value will be passed as request cookies. Can appear multiple times. --header=, -H String values of the form Name: value will be passed as request headers. Can appear multiple times. If the same header name is provided multiple times, the values will be joined with commas, which is only valid for certain headers. --verbose, -v Includes response CGI headers (or HTTP headers in NPH mode) in the output before response content. Enabled automatically for head. COMPARISON TO CGI.PM Traditionally, the CGI module (referred to as CGI.pm to differentiate it from the CGI protocol) has been used to write Perl CGI scripts. This module fills a similar need but has a number of interface differences to be aware of. * There is no CGI::Tiny object constructor; the object is accessible within the cgi block, only reads request data from the environment once it is accessed, and ensures that a valid response is rendered to avoid gateway errors even in the event of an exception or premature exit. * Instead of global variables like $CGI::POST_MAX, global behavior settings are applied to the CGI::Tiny object inside the cgi block. * Exceptions within the cgi block are handled by default by rendering a server error response and emitting the error as a warning. This can be customized with "set_error_handler". * Request parameter accessors in CGI::Tiny are not context sensitive, as context sensitivity can lead to surprising behavior and vulnerabilities . "param", "query_param", "body_param", and "upload" always return a single value; "param_array", "query_param_array", "body_param_array", and "upload_array" must be used to retrieve multi-value parameters. * CGI::Tiny's "param" accessor is also not method-sensitive; it accesses either query or body request parameters with the same behavior regardless of request method, and query and body request parameters can be accessed separately with "query_param" and "body_param" respectively. * CGI::Tiny's "param" accessor only retrieves text parameters; uploaded files and their metadata are accessed with "upload" and related methods. * CGI::Tiny decodes request parameters to Unicode characters automatically, and "render"/"render_chunk" provide methods to encode response content from Unicode characters to UTF-8 by default. * In CGI.pm, response headers must be printed manually before any response content is printed to avoid malformed responses. In CGI::Tiny, the "render" or "render_chunk" methods are used to print response content, and automatically print response headers when first called. redirect responses are also handled by "render". * In CGI::Tiny, a custom response status is set by calling "set_response_status" before the first "render" or "render_chunk", which only requires the status code and will add the appropriate human-readable status message itself. * Response setters are distinct methods from request accessors in CGI::Tiny. "content_type", "header", and "cookie" are used to access request data, and "set_response_type", "add_response_header", and "add_response_cookie" are used to set response headers for the pending response before the first call to "render" or "render_chunk". * CGI::Tiny does not provide any HTML generation helpers, as this functionality is much better implemented by other robust implementations on CPAN; see "Templating" in CGI::Tiny::Cookbook. * CGI::Tiny does not do any implicit encoding of cookie values or the Expires header or cookie attribute. The "epoch_to_date" convenience function is provided to render appropriate Expires date values. There are a number of alternatives to CGI.pm but they do not sufficiently address the design issues; primarily, none of them gracefully handle exceptions or failure to render a response, and several of them have no features for rendering responses. * CGI::Simple shares all of the interface design problems of CGI.pm, though it does not reimplement the HTML generation helpers. * CGI::Thin is ancient and only implements parsing of request query or body parameters, without decoding them to Unicode characters. * CGI::Minimal has context-sensitive parameter accessors, and only implements parsing of request query/body parameters (without decoding them to Unicode characters) and uploads. * CGI::Lite has context-sensitive parameter accessors, and only implements parsing of request query/body parameters (without decoding them to Unicode characters), uploads, and cookies. * CGI::Easy has a robust interface, but pre-parses all request information. CAVEATS CGI is an extremely simplistic protocol and relies particularly on the global state of environment variables and the STDIN and STDOUT standard filehandles. CGI::Tiny does not prevent you from messing with these interfaces directly, but it may result in confusion. CGI::Tiny eschews certain sanity checking for performance reasons. For example, Content-Type and other header values set for the response should only contain ASCII text with no control characters, but CGI::Tiny does not verify this (though it does verify they do not contain newline characters to protect against HTTP response splitting). Field names and filenames in multipart/form-data requests do not have a well-defined escape mechanism for special characters, so CGI::Tiny will not attempt to decode these names from however the client passes them aside from "set_multipart_form_charset". For best compatibility, form field names should be ASCII without double quotes or semicolons. BUGS Report any issues on the public bugtracker. AUTHOR Dan Book COPYRIGHT AND LICENSE This software is Copyright (c) 2021 by Dan Book. This is free software, licensed under: The Artistic License 2.0 (GPL Compatible) SEE ALSO CGI::Alternatives, Mojolicious, Dancer2 libcgi-tiny-perl-1.002/dist.ini000066400000000000000000000003371455551610100163400ustar00rootroot00000000000000name = CGI-Tiny author = Dan Book license = Artistic_2_0 copyright_holder = Dan Book copyright_year = 2021 [@Author::DBOOK] :version = v1.0.3 pod_tests = 1 [MetaNoIndex] package = CGI::Tiny::_Debug libcgi-tiny-perl-1.002/examples/000077500000000000000000000000001455551610100165075ustar00rootroot00000000000000libcgi-tiny-perl-1.002/examples/cookies-hash.cgi000077500000000000000000000014351455551610100215560ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use utf8; use CGI::Tiny; use Cpanel::JSON::XS qw(decode_json encode_json); use MIME::Base64 qw(decode_base64 encode_base64); cgi { my $cgi = $_; my $key = $cgi->param('cookie_key'); my $hashref; if (defined $key) { $hashref->{$key} = $cgi->param('cookie_value'); } else { my $cookie = $cgi->cookie('hash'); $hashref = decode_json decode_base64 $cookie if defined $cookie; $key = (keys %$hashref)[0] if defined $hashref; } if (defined $hashref) { my $encoded_value = encode_base64 encode_json($hashref), ''; $cgi->add_response_cookie(hash => $encoded_value, Path => '/'); $cgi->render(text => "Set cookie hash key $key: $hashref->{$key}"); } else { $cgi->render(text => "No cookie value set"); } }; libcgi-tiny-perl-1.002/examples/cookies-unicode.cgi000077500000000000000000000012201455551610100222510ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use utf8; use CGI::Tiny; use Unicode::UTF8 qw(decode_utf8 encode_utf8); use MIME::Base64 qw(decode_base64 encode_base64); cgi { my $cgi = $_; my $value = $cgi->param('cookie_value'); unless (defined $value) { my $cookie = $cgi->cookie('unicode'); $value = decode_utf8 decode_base64 $cookie if defined $cookie; } if (defined $value) { my $encoded_value = encode_base64 encode_utf8($value), ''; $cgi->add_response_cookie(unicode => $encoded_value, Path => '/'); $cgi->render(text => "Set cookie value: $value"); } else { $cgi->render(text => "No cookie value set"); } }; libcgi-tiny-perl-1.002/examples/files.cgi000077500000000000000000000022261455551610100203020ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use utf8; use CGI::Tiny; use Path::Tiny; use MIME::Types; use Unicode::UTF8 qw(encode_utf8 decode_utf8); cgi { my $cgi = $_; my $filename = $cgi->query_param('filename'); unless (length $filename) { $cgi->set_response_status(404)->render(text => 'Not Found'); exit; } # get files from public/ next to cgi-bin/ my $public_dir = path(__FILE__)->realpath->parent->sibling('public'); my $encoded_filename = encode_utf8 $filename; my $filepath = $public_dir->child($encoded_filename); # ensure file exists, is readable, and is not a directory unless (-r $filepath and !-d _) { $cgi->set_response_status(404)->render(text => 'Not Found'); exit; } # ensure file path doesn't escape the public/ directory unless ($public_dir->subsumes($filepath->realpath)) { $cgi->set_response_status(404)->render(text => 'Not Found'); exit; } my $basename = decode_utf8 $filepath->basename; my $mime = MIME::Types->new->mimeTypeOf($basename); $cgi->set_response_type($mime->type) if defined $mime; $cgi->set_response_disposition(attachment => $basename)->render(file => $filepath); }; libcgi-tiny-perl-1.002/examples/logging.cgi000077500000000000000000000016171455551610100206310ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use utf8; use CGI::Tiny; use Log::Any; use Log::Any::Adapter {category => 'cgi-script'}, # only log our category here File => '/path/to/log/file.log', binmode => ':encoding(UTF-8)', log_level => $ENV{MYCGI_LOG_LEVEL} || 'info'; my $log = Log::Any->get_logger(category => 'cgi-script'); local $SIG{__WARN__} = sub { my ($warning) = @_; chomp $warning; $log->warn($warning); }; cgi { my $cgi = $_; $cgi->set_error_handler(sub { my ($cgi, $error, $rendered) = @_; chomp $error; $log->error($error); }); # only logged if MYCGI_LOG_LEVEL=debug set in CGI server environment $log->debugf('Method: %s, Path: %s, Query: %s', $cgi->method, $cgi->path, $cgi->query); my $number = $cgi->param('number'); die "Excessive number\n" if abs($number) > 1000; my $doubled = $number * 2; $cgi->render(text => "Doubled: $doubled"); }; libcgi-tiny-perl-1.002/examples/routing.cgi000077500000000000000000000017401455551610100206670ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use utf8; use CGI::Tiny; use Routes::Tiny; my %dispatch = ( foos => sub { my ($cgi) = @_; my $method = $cgi->method; $cgi->render(text => "$method foos"); }, get_foo => sub { my ($cgi, $captures) = @_; my $id = $captures->{id}; $cgi->render(text => "Retrieved foo $id"); }, put_foo => sub { my ($cgi, $captures) = @_; my $id = $captures->{id}; $cgi->render(text => "Stored foo $id"); }, ); cgi { my $cgi = $_; my $routes = Routes::Tiny->new; # /script.cgi/foo $routes->add_route('/foo', name => 'foos'); # /script.cgi/foo/42 $routes->add_route('/foo/:id', method => 'GET', name => 'get_foo'); $routes->add_route('/foo/:id', method => 'PUT', name => 'put_foo'); if (defined(my $match = $routes->match($cgi->path, method => $cgi->method))) { $dispatch{$match->name}->($cgi, $match->captures); } else { $cgi->set_response_status(404)->render(text => 'Not Found'); } }; libcgi-tiny-perl-1.002/examples/sessions-basic.cgi000077500000000000000000000013361455551610100221260ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use utf8; use CGI::Tiny; use MIME::Base64 'decode_base64'; use Unicode::UTF8 'decode_utf8'; sub verify_password { my ($user, $pass) = @_; ... } cgi { my $cgi = $_; my $authed_user; if (defined(my $auth = $cgi->header('Authorization'))) { if (my ($hash) = $auth =~ m/^Basic (\S+)/i) { my ($user, $pass) = split /:/, decode_utf8(decode_base64($hash)), 2; $authed_user = $user if verify_password($user, $pass); } } unless (defined $authed_user) { $cgi->add_response_header('WWW-Authenticate' => 'Basic realm="My Website", charset="UTF-8"'); $cgi->set_response_status(401)->render; exit; } $cgi->render(text => "Welcome, $authed_user!"); }; libcgi-tiny-perl-1.002/examples/sessions-cookie.cgi000077500000000000000000000040531455551610100223150ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use utf8; use CGI::Tiny; use Text::Xslate; use Data::Section::Simple 'get_data_section'; sub verify_password { my ($user, $pass) = @_; ... } sub store_new_session { my ($user) = @_; ... } sub get_session_user { my ($session_id) = @_; ... } sub invalidate_session { my ($session_id) = @_; ... } cgi { my $cgi = $_; my $tx = Text::Xslate->new(path => [get_data_section]); my ($authed_user, $session_id); if ($cgi->path eq '/login') { if ($cgi->method eq 'GET' or $cgi->method eq 'HEAD') { $cgi->render(html => $tx->render('login.tx', {login_failed => 0})); exit; } elsif ($cgi->method eq 'POST') { my $user = $cgi->body_param('login_user'); my $pass = $cgi->body_param('login_pass'); if (verify_password($user, $pass)) { $session_id = store_new_session($user); $authed_user = $user; } else { $cgi->render(html => $tx->render('login.tx', {login_failed => 1})); exit; } } } elsif (defined($session_id = $cgi->cookie('myapp_session'))) { if ($cgi->path eq '/logout') { invalidate_session($session_id); # expire session cookie $cgi->add_response_cookie(myapp_session => $session_id, 'Max-Age' => 0, Path => '/', HttpOnly => 1); $cgi->render(redirect => $cgi->script_name . '/login'); exit; } else { $authed_user = get_session_user($session_id); } } unless (defined $authed_user) { $cgi->render(redirect => $cgi->script_name . '/login'); exit; } # set/refresh session cookie $cgi->add_response_cookie(myapp_session => $session_id, 'Max-Age' => 3600, Path => '/', HttpOnly => 1); $cgi->render(text => "Welcome, $authed_user!"); }; __DATA__ @@ login.tx Login
: if $login_failed {

Login failed

: } libcgi-tiny-perl-1.002/examples/templates/000077500000000000000000000000001455551610100205055ustar00rootroot00000000000000libcgi-tiny-perl-1.002/examples/templates/index.html.ep000066400000000000000000000000571455551610100231070ustar00rootroot00000000000000

<%= $foo %>

libcgi-tiny-perl-1.002/examples/templates/index.tx000066400000000000000000000000561455551610100221720ustar00rootroot00000000000000

<: $foo :>

libcgi-tiny-perl-1.002/examples/templating-mojo.cgi000077500000000000000000000012441455551610100223050ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use utf8; use CGI::Tiny; use Mojo::Template; use Mojo::File 'curfile'; use Mojo::Loader 'data_section'; cgi { my $cgi = $_; my $mt = Mojo::Template->new(auto_escape => 1, vars => 1); my $foo = $cgi->query_param('foo'); # from templates/ my $template_path = curfile->sibling('templates', 'index.html.ep'); my $output = $mt->render_file($template_path, {foo => $foo}); # or from __DATA__ my $template = data_section __PACKAGE__, 'index.html.ep'; my $output = $mt->render($template, {foo => $foo}); $cgi->render(html => $output); }; __DATA__ @@ index.html.ep

<%= $foo %>

libcgi-tiny-perl-1.002/examples/templating-xslate.cgi000077500000000000000000000007461455551610100226470ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use utf8; use CGI::Tiny; use Text::Xslate; use Data::Section::Simple 'get_data_section'; cgi { my $cgi = $_; # from templates/ my $tx = Text::Xslate->new(path => ['templates']); # or from __DATA__ my $tx = Text::Xslate->new(path => [get_data_section]); my $foo = $cgi->query_param('foo'); $cgi->render(html => $tx->render('index.tx', {foo => $foo})); }; __DATA__ @@ index.tx

<: $foo :>

libcgi-tiny-perl-1.002/lib/000077500000000000000000000000001455551610100154375ustar00rootroot00000000000000libcgi-tiny-perl-1.002/lib/CGI/000077500000000000000000000000001455551610100160415ustar00rootroot00000000000000libcgi-tiny-perl-1.002/lib/CGI/Tiny.pm000066400000000000000000000753241455551610100173350ustar00rootroot00000000000000package CGI::Tiny; # ABSTRACT: Common Gateway Interface, with no frills # This file is part of CGI::Tiny which is released under: # The Artistic License 2.0 (GPL Compatible) # See the documentation for CGI::Tiny for full license details. use strict; use warnings; use Carp (); use IO::Handle (); use Exporter (); our $VERSION = '1.002'; use constant DEFAULT_REQUEST_BODY_LIMIT => 16777216; use constant DEFAULT_REQUEST_BODY_BUFFER => 262144; use constant DEFAULT_RESPONSE_BODY_BUFFER => 131072; our @EXPORT = 'cgi'; # List from HTTP::Status 6.29 # Unmarked codes are from RFC 7231 (2017-12-20) my %HTTP_STATUS = ( 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', # RFC 2518: WebDAV 103 => 'Early Hints', # RFC 8297: Indicating Hints 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', # RFC 7233: Range Requests 207 => 'Multi-Status', # RFC 4918: WebDAV 208 => 'Already Reported', # RFC 5842: WebDAV bindings 226 => 'IM Used', # RFC 3229: Delta encoding 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', # RFC 7232: Conditional Request 305 => 'Use Proxy', 307 => 'Temporary Redirect', 308 => 'Permanent Redirect', # RFC 7528: Permanent Redirect 400 => 'Bad Request', 401 => 'Unauthorized', # RFC 7235: Authentication 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', # RFC 7235: Authentication 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', # RFC 7232: Conditional Request 413 => 'Payload Too Large', 414 => 'URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Range Not Satisfiable', # RFC 7233: Range Requests 417 => 'Expectation Failed', 418 => 'I\'m a teapot', # RFC 2324: HTCPC/1.0 1-april 421 => 'Misdirected Request', # RFC 7540: HTTP/2 422 => 'Unprocessable Entity', # RFC 4918: WebDAV 423 => 'Locked', # RFC 4918: WebDAV 424 => 'Failed Dependency', # RFC 4918: WebDAV 425 => 'Too Early', # RFC 8470: Using Early Data in HTTP 426 => 'Upgrade Required', 428 => 'Precondition Required', # RFC 6585: Additional Codes 429 => 'Too Many Requests', # RFC 6585: Additional Codes 431 => 'Request Header Fields Too Large', # RFC 6585: Additional Codes 451 => 'Unavailable For Legal Reasons', # RFC 7725: Legal Obstacles 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported', 506 => 'Variant Also Negotiates', # RFC 2295: Transparant Ngttn 507 => 'Insufficient Storage', # RFC 4918: WebDAV 508 => 'Loop Detected', # RFC 5842: WebDAV bindings 509 => 'Bandwidth Limit Exceeded', # Apache / cPanel 510 => 'Not Extended', # RFC 2774: Extension Framework 511 => 'Network Authentication Required', # RFC 6585: Additional Codes ); { my $cgi; sub import { # for cleanup in END in case of premature exit $cgi ||= bless {pid => $$}, $_[0]; goto &Exporter::import; } sub cgi (&) { my ($handler) = @_; $cgi ||= bless {pid => $$}, __PACKAGE__; if (@ARGV and !defined $ENV{REQUEST_METHOD}) { require CGI::Tiny::_Debug; CGI::Tiny::_Debug::debug_command($cgi, [@ARGV]); } my ($error, $errored); { local $@; eval { local $_ = $cgi; $handler->(); 1 } or do { $error = $@; $errored = 1 }; } if ($errored) { _handle_error($cgi, $error); } elsif (!$cgi->{headers_rendered}) { _handle_error($cgi, "cgi completed without rendering a response\n"); } undef $cgi; 1; } # cleanup of premature exit, more reliable than potentially doing this in global destruction # ModPerl::Registry or CGI::Compile won't run END after each request, # but they override exit to throw an exception which we handle already END { if (defined $cgi) { _handle_error($cgi, "cgi exited without rendering a response\n") unless $cgi->{headers_rendered}; undef $cgi; } } } sub _handle_error { my ($cgi, $error) = @_; return unless $cgi->{pid} == $$; # in case of fork $cgi->{response_status} = "500 $HTTP_STATUS{500}" unless $cgi->{headers_rendered} or (defined $cgi->{response_status} and $cgi->{response_status} =~ m/^[45][0-9]{2} /); if (defined(my $handler = $cgi->{on_error})) { my ($error_error, $error_errored); { local $@; eval { $handler->($cgi, $error, !!$cgi->{headers_rendered}); 1 } or do { $error_error = $@; $error_errored = 1 }; } return unless $cgi->{pid} == $$; # in case of fork in error handler if ($error_errored) { warn "Exception in error handler: $error_error"; warn "Original error: $error"; } } else { warn $error; } $cgi->set_response_type('text/plain')->render(data => $cgi->{response_status}) unless $cgi->{headers_rendered}; } sub set_error_handler { $_[0]{on_error} = $_[1]; $_[0] } sub set_request_body_buffer { $_[0]{request_body_buffer} = $_[1]; $_[0] } sub set_request_body_limit { $_[0]{request_body_limit} = $_[1]; $_[0] } sub set_multipart_form_options { $_[0]{multipart_form_options} = $_[1]; $_[0] } sub set_multipart_form_charset { $_[0]{multipart_form_charset} = $_[1]; $_[0] } sub set_input_handle { $_[0]{input_handle} = $_[1]; $_[0] } sub set_output_handle { $_[0]{output_handle} = $_[1]; $_[0] } sub auth_type { defined $ENV{AUTH_TYPE} ? $ENV{AUTH_TYPE} : '' } sub content_length { defined $ENV{CONTENT_LENGTH} ? $ENV{CONTENT_LENGTH} : '' } sub content_type { defined $ENV{CONTENT_TYPE} ? $ENV{CONTENT_TYPE} : '' } sub gateway_interface { defined $ENV{GATEWAY_INTERFACE} ? $ENV{GATEWAY_INTERFACE} : '' } sub path_info { defined $ENV{PATH_INFO} ? $ENV{PATH_INFO} : '' } *path = \&path_info; sub path_translated { defined $ENV{PATH_TRANSLATED} ? $ENV{PATH_TRANSLATED} : '' } sub query_string { defined $ENV{QUERY_STRING} ? $ENV{QUERY_STRING} : '' } *query = \&query_string; sub remote_addr { defined $ENV{REMOTE_ADDR} ? $ENV{REMOTE_ADDR} : '' } sub remote_host { defined $ENV{REMOTE_HOST} ? $ENV{REMOTE_HOST} : '' } sub remote_ident { defined $ENV{REMOTE_IDENT} ? $ENV{REMOTE_IDENT} : '' } sub remote_user { defined $ENV{REMOTE_USER} ? $ENV{REMOTE_USER} : '' } sub request_method { defined $ENV{REQUEST_METHOD} ? $ENV{REQUEST_METHOD} : '' } *method = \&request_method; sub script_name { defined $ENV{SCRIPT_NAME} ? $ENV{SCRIPT_NAME} : '' } sub server_name { defined $ENV{SERVER_NAME} ? $ENV{SERVER_NAME} : '' } sub server_port { defined $ENV{SERVER_PORT} ? $ENV{SERVER_PORT} : '' } sub server_protocol { defined $ENV{SERVER_PROTOCOL} ? $ENV{SERVER_PROTOCOL} : '' } sub server_software { defined $ENV{SERVER_SOFTWARE} ? $ENV{SERVER_SOFTWARE} : '' } sub headers { my ($self) = @_; unless (exists $self->{request_headers}) { my %headers; foreach my $key (keys %ENV) { my $name = $key; next unless $name =~ s/^HTTP_//; $name =~ tr/_/-/; $headers{lc $name} = $ENV{$key}; } $self->{request_headers} = \%headers; } return {%{$self->{request_headers}}}; } sub header { (my $name = $_[1]) =~ tr/-/_/; $ENV{"HTTP_\U$name"} } sub cookies { [map { [@$_] } @{$_[0]->_cookies->{ordered}}] } sub cookie_names { [@{$_[0]->_cookies->{names}}] } sub cookie { my $c = $_[0]->_cookies->{keyed}; exists $c->{$_[1]} ? $c->{$_[1]}[-1] : undef } sub cookie_array { my $c = $_[0]->_cookies->{keyed}; exists $c->{$_[1]} ? [@{$c->{$_[1]}}] : [] } sub _cookies { my ($self) = @_; unless (exists $self->{request_cookies}) { $self->{request_cookies} = {names => \my @names, ordered => \my @ordered, keyed => \my %keyed}; if (defined $ENV{HTTP_COOKIE}) { foreach my $pair (split /\s*;\s*/, $ENV{HTTP_COOKIE}) { next unless length $pair; my ($name, $value) = split /=/, $pair, 2; next unless defined $value; push @names, $name unless exists $keyed{$name}; push @ordered, [$name, $value]; push @{$keyed{$name}}, $value; } } } return $self->{request_cookies}; } sub params { [map { [@$_] } @{$_[0]->_query_params->{ordered}}, @{$_[0]->_body_params->{ordered}}] } sub param_names { my $q = $_[0]->_query_params; [@{$q->{names}}, grep { !exists $q->{keyed}{$_} } @{$_[0]->_body_params->{names}}] } sub param { my ($self, $name) = @_; my $p = $self->_body_params->{keyed}; return $p->{$name}[-1] if exists $p->{$name}; my $q = $self->_query_params->{keyed}; return exists $q->{$name} ? $q->{$name}[-1] : undef; } sub param_array { [map { exists $_->{$_[1]} ? @{$_->{$_[1]}} : () } $_[0]->_query_params->{keyed}, $_[0]->_body_params->{keyed}] } sub query_params { [map { [@$_] } @{$_[0]->_query_params->{ordered}}] } sub query_param_names { [@{$_[0]->_query_params->{names}}] } sub query_param { my $p = $_[0]->_query_params->{keyed}; exists $p->{$_[1]} ? $p->{$_[1]}[-1] : undef } sub query_param_array { my $p = $_[0]->_query_params->{keyed}; exists $p->{$_[1]} ? [@{$p->{$_[1]}}] : [] } sub _query_params { my ($self) = @_; unless (exists $self->{query_params}) { $self->{query_params} = {names => \my @names, ordered => \my @ordered, keyed => \my %keyed}; foreach my $pair (split /[&;]/, $self->query) { my ($name, $value) = split /=/, $pair, 2; $value = '' unless defined $value; do { tr/+/ /; s/%([0-9a-fA-F]{2})/chr hex $1/ge; utf8::decode $_ } for $name, $value; push @names, $name unless exists $keyed{$name}; push @ordered, [$name, $value]; push @{$keyed{$name}}, $value; } } return $self->{query_params}; } sub body { my ($self) = @_; unless (exists $self->{body_content} or exists $self->{body_parts}) { $self->{body_content} = ''; my $length = $self->_body_length; my $in_fh = defined $self->{input_handle} ? $self->{input_handle} : *STDIN; binmode $in_fh; my $buffer_size = 0 + ($self->{request_body_buffer} || $ENV{CGI_TINY_REQUEST_BODY_BUFFER} || DEFAULT_REQUEST_BODY_BUFFER); while ($length > 0) { my $chunk = $length < $buffer_size ? $length : $buffer_size; last unless my $read = read $in_fh, $self->{body_content}, $chunk, length $self->{body_content}; $length -= $read; } } return $self->{body_content}; } sub body_json { my ($self) = @_; unless (exists $self->{body_json}) { $self->{body_json} = undef; if ($ENV{CONTENT_TYPE} and $ENV{CONTENT_TYPE} =~ m/^application\/json\b/i) { $self->{body_json} = $self->_json->decode($self->body); } } return $self->{body_json}; } sub body_params { [map { [@$_] } @{$_[0]->_body_params->{ordered}}] } sub body_param_names { [@{$_[0]->_body_params->{names}}] } sub body_param { my $p = $_[0]->_body_params->{keyed}; exists $p->{$_[1]} ? $p->{$_[1]}[-1] : undef } sub body_param_array { my $p = $_[0]->_body_params->{keyed}; exists $p->{$_[1]} ? [@{$p->{$_[1]}}] : [] } sub _body_params { my ($self) = @_; unless (exists $self->{body_params}) { $self->{body_params} = {names => \my @names, ordered => \my @ordered, keyed => \my %keyed}; if ($ENV{CONTENT_TYPE} and $ENV{CONTENT_TYPE} =~ m/^application\/x-www-form-urlencoded\b/i) { foreach my $pair (split /&/, $self->body) { my ($name, $value) = split /=/, $pair, 2; $value = '' unless defined $value; do { tr/+/ /; s/%([0-9a-fA-F]{2})/chr hex $1/ge; utf8::decode $_ } for $name, $value; push @names, $name unless exists $keyed{$name}; push @ordered, [$name, $value]; push @{$keyed{$name}}, $value; } } elsif ($ENV{CONTENT_TYPE} and $ENV{CONTENT_TYPE} =~ m/^multipart\/form-data\b/i) { my $default_charset = $self->{multipart_form_charset}; $default_charset = 'UTF-8' unless defined $default_charset; foreach my $part (@{$self->_body_multipart}) { next if defined $part->{filename}; my ($name, $headers, $content, $file) = @$part{'name','headers','content','file'}; if (length $default_charset) { require Encode; $name = Encode::decode($default_charset, "$name"); } my $value = ''; if (defined $content) { $value = $content; } elsif (defined $file) { binmode $file; seek $file, 0, 0; $value = do { local $/; readline $file }; seek $file, 0, 0; } my $value_charset; if (defined $headers->{'content-type'}) { if (my ($charset_quoted, $charset_unquoted) = $headers->{'content-type'} =~ m/;\s*charset=(?:"((?:\\[\\"]|[^"])+)"|([^";]+))/i) { $charset_quoted =~ s/\\([\\"])/$1/g if defined $charset_quoted; $value_charset = defined $charset_quoted ? $charset_quoted : $charset_unquoted; } } if (defined $value_charset or !defined $headers->{'content-type'} or $headers->{'content-type'} =~ m/^text\/plain\b/i) { require Encode; if (defined $value_charset) { $value = Encode::decode($value_charset, "$value"); } elsif (length $default_charset) { $value = Encode::decode($default_charset, "$value"); } } push @names, $name unless exists $keyed{$name}; push @ordered, [$name, $value]; push @{$keyed{$name}}, $value; } } } return $self->{body_params}; } sub body_parts { my ($self) = @_; return [] unless $ENV{CONTENT_TYPE} and $ENV{CONTENT_TYPE} =~ m/^multipart\/form-data\b/i; return [map { +{%$_} } @{$self->_body_multipart}]; } sub uploads { [map { [@$_] } @{$_[0]->_body_uploads->{ordered}}] } sub upload_names { [@{$_[0]->_body_uploads->{names}}] } sub upload { my $u = $_[0]->_body_uploads->{keyed}; exists $u->{$_[1]} ? $u->{$_[1]}[-1] : undef } sub upload_array { my $u = $_[0]->_body_uploads->{keyed}; exists $u->{$_[1]} ? [@{$u->{$_[1]}}] : [] } sub _body_uploads { my ($self) = @_; unless (exists $self->{body_uploads}) { $self->{body_uploads} = {names => \my @names, ordered => \my @ordered, keyed => \my %keyed}; if ($ENV{CONTENT_TYPE} and $ENV{CONTENT_TYPE} =~ m/^multipart\/form-data\b/i) { my $default_charset = $self->{multipart_form_charset}; $default_charset = 'UTF-8' unless defined $default_charset; foreach my $part (@{$self->_body_multipart}) { next unless defined $part->{filename}; my ($name, $filename, $size, $headers, $file, $content) = @$part{'name','filename','size','headers','file','content'}; if (length $default_charset) { require Encode; $name = Encode::decode($default_charset, "$name"); $filename = Encode::decode($default_charset, "$filename"); } my $upload = { filename => $filename, size => $size, content_type => $headers->{'content-type'}, }; $upload->{file} = $file if defined $file; $upload->{content} = $content if defined $content; push @names, $name unless exists $keyed{$name}; push @ordered, [$name, $upload]; push @{$keyed{$name}}, $upload; } } } return $self->{body_uploads}; } sub _body_length { my ($self) = @_; my $limit = $self->{request_body_limit}; $limit = $ENV{CGI_TINY_REQUEST_BODY_LIMIT} unless defined $limit; $limit = DEFAULT_REQUEST_BODY_LIMIT unless defined $limit; my $length = $ENV{CONTENT_LENGTH} || 0; if ($limit and $length > $limit) { $self->{response_status} = "413 $HTTP_STATUS{413}" unless $self->{headers_rendered}; die "Request body limit exceeded\n"; } return 0 + $length; } sub _body_multipart { my ($self) = @_; unless (exists $self->{body_parts}) { $self->{body_parts} = []; require CGI::Tiny::Multipart; my $boundary = CGI::Tiny::Multipart::extract_multipart_boundary($ENV{CONTENT_TYPE}); unless (defined $boundary) { $self->{response_status} = "400 $HTTP_STATUS{400}" unless $self->{headers_rendered}; die "Malformed multipart/form-data request\n"; } my ($input, $length); if (exists $self->{body_content}) { $length = length $self->{body_content}; $input = \$self->{body_content}; } else { $length = $self->_body_length; $input = defined $self->{input_handle} ? $self->{input_handle} : *STDIN; } my $parts = CGI::Tiny::Multipart::parse_multipart_form_data($input, $length, $boundary, { buffer_size => $self->{request_body_buffer} || $ENV{CGI_TINY_REQUEST_BODY_BUFFER}, %{$self->{multipart_form_options} || {}}, }); unless (defined $parts) { $self->{response_status} = "400 $HTTP_STATUS{400}" unless $self->{headers_rendered}; die "Malformed multipart/form-data request\n"; } $self->{body_parts} = $parts; } return $self->{body_parts}; } sub set_nph { my ($self, $value) = @_; if ($self->{headers_rendered}) { Carp::carp "Attempted to set NPH response mode but headers have already been rendered"; } else { $self->{nph} = @_ < 2 ? 1 : $value; } return $self; } sub set_response_body_buffer { $_[0]{response_body_buffer} = $_[1]; $_[0] } sub set_response_status { my ($self, $status) = @_; if ($self->{headers_rendered}) { Carp::carp "Attempted to set HTTP response status but headers have already been rendered"; } else { if (defined $status and $status =~ m/\A[0-9]+ [^\r\n]*\z/) { $self->{response_status} = $status; } elsif (defined $status) { Carp::croak "Attempted to set unknown HTTP response status $status" unless exists $HTTP_STATUS{$status}; $self->{response_status} = "$status $HTTP_STATUS{$status}"; } else { delete $self->{response_status}; } } return $self; } { my %DISPOSITIONS = (attachment => 1, inline => 1); sub set_response_disposition { my ($self, $disposition, $filename) = @_; if ($self->{headers_rendered}) { Carp::carp "Attempted to set HTTP response content disposition but headers have already been rendered"; } else { Carp::croak "Attempted to set unknown Content-Disposition value '$disposition'" unless exists $DISPOSITIONS{lc $disposition}; $self->{response_disposition} = $disposition; # filename will be quoted/escaped later $self->{response_filename} = $filename; } return $self; } } sub set_response_type { my ($self, $content_type) = @_; if ($self->{headers_rendered}) { Carp::carp "Attempted to set HTTP response content type but headers have already been rendered"; } else { Carp::croak "Newline characters not allowed in HTTP response content type" if defined $content_type and $content_type =~ tr/\r\n//; $self->{response_type} = $content_type; } return $self; } sub set_response_charset { my ($self, $charset) = @_; Carp::croak "Invalid characters in HTTP response charset" if defined $charset and $charset =~ m/[^a-zA-Z0-9!#\$%&'*+\-.^_`|~]/; $self->{response_charset} = $charset; return $self; } sub add_response_header { my ($self, $name, $value) = @_; if ($self->{headers_rendered}) { Carp::carp "Attempted to add HTTP response header '$name' but headers have already been rendered"; } else { Carp::croak "Newline characters not allowed in HTTP response header '$name'" if $value =~ tr/\r\n//; push @{$self->{response_headers}}, [$name, $value]; } return $self; } { my %COOKIE_ATTR_VALUE = (expires => 1, domain => 1, path => 1, secure => 0, httponly => 0, samesite => 1, 'max-age' => 1); sub add_response_cookie { my ($self, $name, $value, @attrs) = @_; if ($self->{headers_rendered}) { Carp::carp "Attempted to add HTTP response cookie '$name' but headers have already been rendered"; } else { my $cookie_str = "$name=$value"; my $i = 0; while ($i <= $#attrs) { my ($key, $val) = @attrs[$i, $i+1]; my $has_value = $COOKIE_ATTR_VALUE{lc $key}; if (!defined $has_value) { Carp::croak "Attempted to set unknown cookie attribute '$key' for HTTP response cookie '$name'"; } elsif ($has_value) { $cookie_str .= "; $key=$val" if defined $val; } else { $cookie_str .= "; $key" if $val; } } continue { $i += 2; } Carp::croak "Newline characters not allowed in HTTP response cookie '$name'" if $cookie_str =~ tr/\r\n//; push @{$self->{response_headers}}, ['Set-Cookie', $cookie_str]; } return $self; } } sub reset_response_headers { delete $_[0]{response_headers}; $_[0] } sub response_status_code { my ($self) = @_; if (defined $self->{response_status} and $self->{response_status} =~ m/\A([0-9]+)/) { return 0+$1; } return 200; } { my %RENDER_TYPES = (text => 1, html => 1, xml => 1, json => 1, data => 1, file => 1, handle => 1, redirect => 1); sub render { my ($self, $type, $data) = @_; Carp::croak "Cannot render additional data with ->render; use ->render_chunk" if $self->{headers_rendered}; $type = '' unless defined $type; Carp::croak "Don't know how to render '$type'" if length $type and !exists $RENDER_TYPES{$type}; Carp::croak "Cannot render from an open filehandle with ->render; use ->render_chunk" if $type eq 'handle'; my ($response_body, $response_length, $redirect_url); if ($type eq 'redirect') { Carp::croak "Newline characters not allowed in HTTP redirect" if $data =~ tr/\r\n//; $redirect_url = $data; } elsif (uc($ENV{REQUEST_METHOD} || '') eq 'HEAD') { # no response content } elsif ($type eq 'text' or $type eq 'html' or $type eq 'xml') { my $charset = $self->{response_charset}; $charset = 'UTF-8' unless defined $charset; if (uc $charset eq 'UTF-8' and do { local $@; eval { require Unicode::UTF8; 1 } }) { $response_body = Unicode::UTF8::encode_utf8($data); } else { require Encode; $response_body = Encode::encode($charset, "$data"); } $response_length = length $response_body; } elsif ($type eq 'json') { $response_body = $self->_json->encode($data); $response_length = length $response_body; } elsif ($type eq 'data') { $response_body = $data; $response_length = length $response_body; } elsif ($type eq 'file') { $response_length = -s $data; Carp::croak "Failed to retrieve size of file '$data': $!" unless defined $response_length; } $response_length = 0 unless defined $response_length; my $headers_str = $self->_response_headers($type, $response_length, $redirect_url); my $out_fh = defined $self->{output_handle} ? $self->{output_handle} : *STDOUT; binmode $out_fh; $out_fh->printflush($headers_str); $self->{headers_rendered} = 1; $self->{response_fixed_length} = 1; return $self unless $response_length; if ($type eq 'file') { open my $in_fh, '<', $data or Carp::croak "Failed to open file '$data' for rendering: $!"; binmode $in_fh; my $buffer_size = 0 + ($self->{response_body_buffer} || $ENV{CGI_TINY_RESPONSE_BODY_BUFFER} || DEFAULT_RESPONSE_BODY_BUFFER); while (read $in_fh, my $buffer, $buffer_size) { $out_fh->print($buffer); } $out_fh->flush; } else { $out_fh->printflush($response_body); } return $self; } sub render_chunk { my ($self, $type, $data) = @_; Carp::croak "Cannot render additional data after ->render" if $self->{response_fixed_length}; $type = '' unless defined $type; Carp::croak "Don't know how to render '$type'" if length $type and !exists $RENDER_TYPES{$type}; Carp::croak "Cannot render a chunked redirect" if $type eq 'redirect'; my $out_fh = defined $self->{output_handle} ? $self->{output_handle} : *STDOUT; unless ($self->{headers_rendered}) { my $headers_str = $self->_response_headers($type); binmode $out_fh; $out_fh->printflush($headers_str); $self->{headers_rendered} = 1; } if (uc($ENV{REQUEST_METHOD} || '') eq 'HEAD') { # no response content } elsif ($type eq 'text' or $type eq 'html' or $type eq 'xml') { my $charset = $self->{response_charset}; $charset = 'UTF-8' unless defined $charset; my $response_body; if (uc $charset eq 'UTF-8' and do { local $@; eval { require Unicode::UTF8; 1 } }) { $response_body = Unicode::UTF8::encode_utf8($data); } else { require Encode; $response_body = Encode::encode($charset, "$data"); } $out_fh->printflush($response_body); } elsif ($type eq 'json') { my $response_body = $self->_json->encode($data); $out_fh->printflush($response_body); } elsif ($type eq 'data') { $out_fh->printflush($data); } elsif ($type eq 'file' or $type eq 'handle') { my $in_fh; if ($type eq 'file') { open $in_fh, '<', $data or Carp::croak "Failed to open file '$data' for rendering: $!"; } else { $in_fh = $data; } binmode $in_fh; my $buffer_size = 0 + ($self->{response_body_buffer} || $ENV{CGI_TINY_RESPONSE_BODY_BUFFER} || DEFAULT_RESPONSE_BODY_BUFFER); while (read $in_fh, my $buffer, $buffer_size) { $out_fh->print($buffer); } $out_fh->flush; } return $self; } } sub _response_headers { my ($self, $type, $content_length, $location) = @_; my $headers_str = ''; return $headers_str if defined $self->{debug_method} and !$self->{debug_verbose}; my %headers_set; foreach my $header (@{$self->{response_headers} || []}) { my ($name, $value) = @$header; $headers_str .= "$name: $value\r\n"; $headers_set{lc $name} = 1; } if (!$headers_set{'content-length'} and defined $content_length) { $headers_str = "Content-Length: $content_length\r\n$headers_str"; } if (!$headers_set{'content-disposition'} and (defined $self->{response_disposition} or defined $self->{response_filename})) { my $value = defined $self->{response_disposition} ? $self->{response_disposition} : 'inline'; if (defined(my $filename = $self->{response_filename})) { require Encode; my $quoted_filename = Encode::encode('ISO-8859-1', "$filename"); $quoted_filename =~ tr/\r\n/ /; $quoted_filename =~ s/([\\"])/\\$1/g; $value .= "; filename=\"$quoted_filename\""; my $ext_filename = Encode::encode('UTF-8', "$filename"); $ext_filename =~ s/([^a-zA-Z0-9!#\$&+\-.^_`|~])/sprintf '%%%02X', ord $1/ge; $value .= "; filename*=UTF-8''$ext_filename"; } $headers_str = "Content-Disposition: $value\r\n$headers_str" unless lc $value eq 'inline'; } if (!$headers_set{location} and $type eq 'redirect') { $headers_str = "Location: $location\r\n$headers_str"; } if (!$headers_set{'content-type'} and $type ne 'redirect') { my $content_type = $self->{response_type}; my $charset = $self->{response_charset}; $charset = 'UTF-8' unless defined $charset; $content_type = $type eq 'text' ? "text/plain;charset=$charset" : $type eq 'html' ? "text/html;charset=$charset" : $type eq 'xml' ? "application/xml;charset=$charset" : $type eq 'json' ? 'application/json;charset=UTF-8' : 'application/octet-stream' unless defined $content_type or (defined $content_length and $content_length == 0); $headers_str = "Content-Type: $content_type\r\n$headers_str" if defined $content_type; } if (!$headers_set{date}) { my $date_str = epoch_to_date(time); $headers_str = "Date: $date_str\r\n$headers_str"; } my $status = $self->{response_status}; $status = $self->{response_status} = "302 $HTTP_STATUS{302}" if $type eq 'redirect' and !(defined $status and $status =~ m/^3[0-9]{2} /); if ($self->{nph}) { $status = "200 $HTTP_STATUS{200}" unless defined $status; my $protocol = $ENV{SERVER_PROTOCOL}; $protocol = 'HTTP/1.0' unless defined $protocol and length $protocol; $headers_str = "$protocol $status\r\n$headers_str"; my $server = $ENV{SERVER_SOFTWARE}; $headers_str .= "Server: $server\r\n" if defined $server and length $server; } elsif (!$headers_set{status} and defined $status) { $headers_str = "Status: $status\r\n$headers_str"; } return "$headers_str\r\n"; } sub _json { my ($self) = @_; unless (exists $self->{json}) { if (do { local $@; eval { require Cpanel::JSON::XS; Cpanel::JSON::XS->VERSION('4.09'); 1 } }) { $self->{json} = Cpanel::JSON::XS->new->allow_dupkeys->stringify_infnan; } else { require JSON::PP; $self->{json} = JSON::PP->new; } $self->{json}->utf8->canonical->allow_nonref->allow_unknown->allow_blessed->convert_blessed->escape_slash; } return $self->{json}; } { my @DAYS_OF_WEEK = qw(Sun Mon Tue Wed Thu Fri Sat); my @MONTH_NAMES = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); my %MONTH_NUMS; @MONTH_NUMS{@MONTH_NAMES} = 0..11; sub epoch_to_date { my ($sec,$min,$hour,$mday,$mon,$year,$wday) = gmtime $_[0]; return sprintf '%s, %02d %s %04d %02d:%02d:%02d GMT', $DAYS_OF_WEEK[$wday], $mday, $MONTH_NAMES[$mon], $year + 1900, $hour, $min, $sec; } sub date_to_epoch { # RFC 1123 (Sun, 06 Nov 1994 08:49:37 GMT) my ($mday,$mon,$year,$hour,$min,$sec) = $_[0] =~ m/^ (?:Sun|Mon|Tue|Wed|Thu|Fri|Sat), [ ] ([0-9]{2}) [ ] (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [ ] ([0-9]{4}) [ ] ([0-9]{2}) : ([0-9]{2}) : ([0-9]{2}) [ ] GMT $/x; # RFC 850 (Sunday, 06-Nov-94 08:49:37 GMT) ($mday,$mon,$year,$hour,$min,$sec) = $_[0] =~ m/^ (?:Sun|Mon|Tues|Wednes|Thurs|Fri|Satur)day, [ ] ([0-9]{2}) - (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) - ([0-9]{2}) [ ] ([0-9]{2}) : ([0-9]{2}) : ([0-9]{2}) [ ] GMT $/x unless defined $mday; # asctime (Sun Nov 6 08:49:37 1994) ($mon,$mday,$hour,$min,$sec,$year) = $_[0] =~ m/^ (?:Sun|Mon|Tue|Wed|Thu|Fri|Sat) [ ] (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [ ]{1,2} ([0-9]{1,2}) [ ] ([0-9]{2}) : ([0-9]{2}) : ([0-9]{2}) [ ] ([0-9]{4}) $/x unless defined $mday; return undef unless defined $mday; require Time::Local; # 4 digit years interpreted literally, but may have leading zeroes # 2 digit years interpreted with best effort heuristic return scalar Time::Local::timegm($sec, $min, $hour, $mday, $MONTH_NUMS{$mon}, (length($year) == 4 && $year < 1900) ? $year - 1900 : $year); } } { my %ESCAPES = ('&' => '&', '<' => '<', '>' => '>', '"' => '"', '\'' => '''); sub escape_html { (my $escaped = $_[0]) =~ s/([&<>"'])/$ESCAPES{$1}/ge; $escaped } } 1; libcgi-tiny-perl-1.002/lib/CGI/Tiny.pod000066400000000000000000001221551455551610100174760ustar00rootroot00000000000000=pod =encoding UTF-8 =head1 NAME CGI::Tiny - Common Gateway Interface, with no frills =head1 SYNOPSIS #!/usr/bin/perl use strict; use warnings; use utf8; use CGI::Tiny; cgi { my $cgi = $_; $cgi->set_error_handler(sub { my ($cgi, $error, $rendered) = @_; warn $error; unless ($rendered) { if ($cgi->response_status_code == 413) { $cgi->render(json => {error => 'Request body limit exceeded'}); } elsif ($cgi->response_status_code == 400) { $cgi->render(json => {error => 'Bad request'}); } else { $cgi->render(json => {error => 'Internal server error'}); } } }); my $method = $cgi->method; my $fribble; if ($method eq 'GET' or $method eq 'HEAD') { $fribble = $cgi->query_param('fribble'); } elsif ($method eq 'POST') { $fribble = $cgi->body_param('fribble'); } else { $cgi->set_response_status(405)->render; exit; } die "Invalid fribble parameter" unless length $fribble; if ($cgi->param('download')) { $cgi->set_response_disposition(attachment => 'fribble.json'); } $cgi->render(json => {fribble => $fribble}); }; =head1 DESCRIPTION CGI::Tiny provides a modern interface to write L scripts to dynamically respond to HTTP requests as defined in L. It is intended to be: =over =item * Minimal CGI::Tiny contains a small amount of code and (on modern Perls) no non-core requirements. No framework needed. =item * Simple CGI::Tiny is straightforward to use, avoids anything magical or surprising, and provides easy access to the most commonly needed features. =item * Robust CGI::Tiny's interface is designed to help the developer follow best practices and avoid common pitfalls and vulnerabilities by default. =item * Lazy CGI::Tiny only loads code or processes information once it is needed, so simple requests can be handled without unnecessary overhead. =item * Restrained CGI::Tiny is designed for the CGI protocol which executes the program again for every request. It is not suitable for persistent protocols like FastCGI or PSGI. =item * Flexible CGI::Tiny can be used with other modules to handle tasks like routing and templating, and doesn't impose unnecessary constraints to reading input or rendering output. =back Most applications are better written in a L-compatible framework (e.g. L or L) and deployed in a persistent application server so that the application does not have to start up again every time it receives a request. CGI::Tiny, and the CGI protocol in general, is only suited for restricted deployment environments that can only run CGI scripts, or applications that don't need to scale. See L. =head1 USAGE =for Pod::Coverage cgi CGI::Tiny's interface is the C block. use CGI::Tiny; cgi { my $cgi = $_; # set up error handling on $cgi # inspect request data via $cgi # set response headers if needed via $cgi # render response with $cgi->render or $cgi->render_chunk }; The block is immediately run with C<$_> set to a CGI::Tiny object, which L can be called on to read request information and render a response. If an exception is thrown within the block, or the block does not render a response, it will run the handler set by L if any, or by default emit the error as a warning and (if nothing has been rendered yet) render a 500 Internal Server Error. The default server error will also be rendered if the process ends abnormally between importing from CGI::Tiny and the start of the C block. To load CGI::Tiny without triggering this cleanup mechanism or making the C block available (such as to use convenience L in non-CGI code), load the module with C or C. B The C block's current implementation as a regular exported subroutine is an implementation detail, and future implementations reserve the right to provide it as an XSUB or keyword for performance reasons. Don't call it as C, don't rely on C<@_> being set, and don't use C to exit the block; use C to end a CGI script early after rendering a response. See L for advanced usage examples. =head1 DATA SAFETY CGI::Tiny does not provide any special affordances for L as it is overeager, imprecise, and can significantly impact performance. Web developers should instead proactively take care not to use any request data (including request headers, form fields, or other request content) directly in an unsafe manner, as it can make the program vulnerable to injections that cause undesired or dangerous behavior. The most common risks to watch out for include: =over =item * System commands Do not interpolate arbitrary data into a shell command, such as with C or backticks. Data can be safely passed as command arguments using methods that bypass the shell, such as the list form of C, or modules like L, L, and L. If shell features are needed, data can be escaped for bourne-style shells with L. =item * Database queries Do not interpolate arbitrary data into database queries. Data can be safely passed to database queries using L. =item * Regex Do not interpolate arbitrary data into regular expressions, such as the C or C operators, or the first argument to C. Data can be safely included in a regex to match it as an exact string by escaping it with the C function or equivalent C<\Q> escape sequence. =item * HTML Do not interpolate arbitrary data into HTML. Data can be safely included in HTML by escaping it with L, or passing it to an HTML template engine with an auto-escape feature; see L. =back =head1 METHODS The following methods can be called on the CGI::Tiny object provided to the C block. =head2 Setup =head3 set_error_handler $cgi = $cgi->set_error_handler(sub { my ($cgi, $error, $rendered) = @_; ... }); Sets an error handler to run in the event of an exception or if the script ends without rendering a response. The handler will be called with the CGI::Tiny object, the error value, and a boolean indicating whether response headers have been rendered yet. The error value can be any exception thrown by Perl or user code. It should generally not be included in any response rendered to the client, but instead warned or logged. Exceptions may occur before or after response headers have been rendered. If response headers have not been rendered, error handlers may inspect L and/or render some error response. The response status code will be set to 500 when this handler is called if it has not been set to a specific 400- or 500-level error status. If the error handler itself throws an exception, that error and the original error will be emitted as a warning. If no response has been rendered after the error handler completes or dies, a default error response will be rendered. B The error handler is only meant for logging and customization of the final error response in a failed request dispatch; to handle exceptions within standard application flow without causing an error response, use an exception handling mechanism such as L or L (which will use the new C feature if available). =head3 set_request_body_buffer $cgi = $cgi->set_request_body_buffer(256*1024); Sets the buffer size (number of bytes to read at once) for reading the request body. Defaults to the value of the C environment variable or 262144 (256 KiB). A value of 0 will use the default value. =head3 set_request_body_limit $cgi = $cgi->set_request_body_limit(16*1024*1024); Sets the limit in bytes for the request body. Defaults to the value of the C environment variable or 16777216 (16 MiB). A value of 0 will remove the limit (not recommended unless you have other safeguards on memory usage). Since the request body is not parsed until needed, methods that parse the request body like L or L will set the response status to C<413 Payload Too Large> and throw an exception if the content length is over the limit. Files uploaded through a Cform-data> request body also count toward this limit, though they are streamed to temporary files when parsed. =head3 set_multipart_form_options $cgi = $cgi->set_multipart_form_options({discard_files => 1, tempfile_args => [SUFFIX => '.dat']}); Set a hash reference of options to pass when parsing a Cform-data> request body with L. No effect after the form data has been parsed such as by calling L or L for the first time. B Options like C and C can alter the C and C keys of the form field structure returned by L. Thus L may not contain C and may instead contain C, and L text field values may be read from C, which will be expected to be a seekable filehandle if present. =head3 set_multipart_form_charset $cgi = $cgi->set_multipart_form_charset('UTF-8'); Sets the default charset for decoding Cform-data> forms, defaults to C. Parameter and upload field names, upload filenames, and text parameter values that don't specify a charset will be decoded from this charset. Set to an empty string to disable this decoding, effectively interpreting such values in C. =head3 set_input_handle $cgi = $cgi->set_input_handle($fh); Sets the input handle to read the request body from. If not set, reads from C. The handle will have C applied before reading to remove any translation layers. =head3 set_output_handle $cgi = $cgi->set_output_handle($fh); Sets the output handle to print the response to. If not set, prints to C. The handle will have C applied before printing to remove any translation layers. =head2 Request Environment CGI::Tiny provides direct access to CGI L via methods that map to the equivalent uppercase names (and a few short aliases). Since CGI does not distinguish between missing and empty values, missing values will be normalized to an empty string. =head3 auth_type # AUTH_TYPE="Basic" my $auth_type = $cgi->auth_type; The authentication scheme used in the C HTTP request header if any. =head3 content_length # CONTENT_LENGTH="42" my $content_length = $cgi->content_length; The size in bytes of the request body content if any. =head3 content_type # CONTENT_TYPE="text/plain;charset=UTF-8" my $content_type = $cgi->content_type; The MIME type of the request body content if any. =head3 gateway_interface # GATEWAY_INTERFACE="CGI/1.1" my $gateway_inteface = $cgi->gateway_interface; The CGI version used for communication with the CGI server. =head3 path_info =head3 path # PATH_INFO="/foo/42" my $path = $cgi->path_info; my $path = $cgi->path; The URL path following the C in the request URL if any. =head3 path_translated # PATH_TRANSLATED="/var/www/html/foo/42" my $path_translated = $cgi->path_translated; A local file path derived from C by the CGI server, as if it were a request to the document root, if it chooses to provide it. =head3 query_string =head3 query # QUERY_STRING="foo=bar" my $query = $cgi->query_string; my $query = $cgi->query; The query string component of the request URL. =head3 remote_addr # REMOTE_ADDR="8.8.8.8" my $remote_addr = $cgi->remote_addr; The IPv4 or IPv6 address of the requesting client. =head3 remote_host # REMOTE_HOST="example.com" my $remote_host = $cgi->remote_host; The domain name of the requesting client if available, or C. =head3 remote_ident # REMOTE_IDENT="someuser" my $remote_ident = $cgi->remote_ident; The identity of the client reported by an RFC 1413 ident request if available. =head3 remote_user # REMOTE_USER="someuser" my $remote_user = $cgi->remote_user; The user identity provided as part of an C authenticated request. =head3 request_method =head3 method # REQUEST_METHOD="GET" my $method = $cgi->request_method; my $method = $cgi->method; The HTTP request method verb. =head3 script_name # SCRIPT_NAME="/cgi-bin/script.cgi" my $script_name = $cgi->script_name; The host-relative URL path to the CGI script. =head3 server_name # SERVER_NAME="example.com" my $server_name = $cgi->server_name; The hostname of the server as the target of the request, such as from the C HTTP request header. =head3 server_port # SERVER_PORT="443" my $server_port = $cgi->server_port; The TCP port on which the server received the request. =head3 server_protocol # SERVER_PROTOCOL="HTTP/1.1" my $server_protocol = $cgi->server_protocol; The HTTP protocol version of the request. =head3 server_software # SERVER_SOFTWARE="Apache\/2.4.37 (centos)" my $server_software = $cgi->server_software; The name and version of the CGI server. =head2 Request Parsing =head3 headers my $hashref = $cgi->headers; Hash reference of available request header names and values. Header names are represented in lowercase. =head3 header my $value = $cgi->header('Accept-Language'); Retrieve the value of a request header by name (case insensitive). CGI request headers can only contain a single value, which may be combined from multiple values. =head3 cookies my $pairs = $cgi->cookies; Retrieve request cookies as an ordered array reference of name/value pairs, represented as two-element array references. =head3 cookie_names my $arrayref = $cgi->cookie_names; Retrieve request cookie names as an ordered array reference, without duplication. =head3 cookie my $value = $cgi->cookie('foo'); Retrieve the value of a request cookie by name. If multiple cookies were passed with the same name, returns the last value. Use L to get multiple values of a cookie name. =head3 cookie_array my $arrayref = $cgi->cookie_array('foo'); Retrieve values of a request cookie name as an ordered array reference. =head3 params my $pairs = $cgi->params; Retrieve URL query string parameters and C or C body parameters as an ordered array reference of name/value pairs, represented as two-element array references. Names and values are decoded to Unicode characters. Query parameters are returned first, followed by body parameters. Use L or L to retrieve query or body parameters separately. B This will read the text form fields into memory as in L. =head3 param_names my $arrayref = $cgi->param_names; Retrieve URL query string parameter names and C or C body parameter names, decoded to Unicode characters, as an ordered array reference, without duplication. Query parameter names are returned first, followed by body parameter names. Use L or L to retrieve query or body parameter names separately. B This will read the text form fields into memory as in L. =head3 param my $value = $cgi->param('foo'); Retrieve value of a named URL query string parameter or C or C body parameter, decoded to Unicode characters. If the parameter name was passed multiple times, returns the last body parameter value if any, otherwise the last query parameter value. Use L to get multiple values of a parameter, or L or L to retrieve the last query or body parameter value specifically. B This will read the text form fields into memory as in L. =head3 param_array my $arrayref = $cgi->param_array('foo'); Retrieve values of a named URL query string parameter or C or C body parameter, decoded to Unicode characters, as an ordered array reference. Query parameter values will be returned first, followed by body parameter values. Use L or L to retrieve query or body parameter values separately. B This will read the text form fields into memory as in L. =head3 query_params my $pairs = $cgi->query_params; Retrieve URL query string parameters as an ordered array reference of name/value pairs, represented as two-element array references. Names and values are decoded to Unicode characters. =head3 query_param_names my $arrayref = $cgi->query_param_names; Retrieve URL query string parameter names, decoded to Unicode characters, as an ordered array reference, without duplication. =head3 query_param my $value = $cgi->query_param('foo'); Retrieve value of a named URL query string parameter, decoded to Unicode characters. If the parameter name was passed multiple times, returns the last value. Use L to get multiple values of a parameter. =head3 query_param_array my $arrayref = $cgi->query_param_array('foo'); Retrieve values of a named URL query string parameter, decoded to Unicode characters, as an ordered array reference. =head3 body my $bytes = $cgi->body; Retrieve the request body as bytes. B This will read the whole request body into memory, so make sure the L can fit well within the available memory. Not available after calling L, L, or L (or related accessors) on a Cform-data> request, since this type of request body is not retained in memory after parsing. =head3 body_json my $data = $cgi->body_json; Decode an C request body from UTF-8-encoded JSON. B This will read the whole request body into memory, so make sure the L can fit well within the available memory. =head3 body_params my $pairs = $cgi->body_params; Retrieve Cx-www-form-urlencoded> or Cform-data> body parameters as an ordered array reference of name/value pairs, represented as two-element array references. Names and values are decoded to Unicode characters. B This will read the text form fields into memory, so make sure the L can fit well within the available memory. Cform-data> file uploads will be streamed to temporary files accessible via L and related methods. =head3 body_param_names my $arrayref = $cgi->body_param_names; Retrieve Cx-www-form-urlencoded> or Cform-data> body parameter names, decoded to Unicode characters, as an ordered array reference, without duplication. B This will read the text form fields into memory as in L. =head3 body_param my $value = $cgi->body_param('foo'); Retrieve value of a named Cx-www-form-urlencoded> or Cform-data> body parameter, decoded to Unicode characters. If the parameter name was passed multiple times, returns the last value. Use L to get multiple values of a parameter. B This will read the text form fields into memory as in L. =head3 body_param_array my $arrayref = $cgi->body_param_array('foo'); Retrieve values of a named Cx-www-form-urlencoded> or Cform-data> body parameter, decoded to Unicode characters, as an ordered array reference. B This will read the text form fields into memory as in L. =head3 body_parts my $parts = $cgi->body_parts; Retrieve Cform-data> request body parts as an ordered array reference using L. Most applications should retrieve multipart form data through L and L (or related accessors) instead. B This will read the text form fields into memory, so make sure the L can fit well within the available memory. File uploads will be streamed to temporary files. =head3 uploads my $pairs = $cgi->uploads; Retrieve Cform-data> file uploads as an ordered array reference of name/upload pairs, represented as two-element array references. Names are decoded to Unicode characters. B This will read the text form fields into memory, so make sure the L can fit well within the available memory. File uploads are represented as a hash reference containing the following keys: =over =item filename Original filename supplied to file input. An empty filename may indicate that no file was submitted. =item content_type C of uploaded file, undef if unspecified. =item size File size in bytes. =item file L object storing the file contents in a temporary file, which will be cleaned up when the CGI script ends by default. The filehandle will be open with the C pointer at the start of the file for reading. =back =head3 upload_names my $arrayref = $cgi->upload_names; Retrieve Cform-data> file upload names, decoded to Unicode characters, as an ordered array reference, without duplication. B This will read the text form fields into memory as in L. =head3 upload my $upload = $cgi->upload('foo'); Retrieve a named Cform-data> file upload. If the upload name was passed multiple times, returns the last value. Use L to get multiple uploads with the same name. See L for details on the representation of the upload. B This will read the text form fields into memory as in L. =head3 upload_array my $arrayref = $cgi->upload_array('foo'); Retrieve all Cform-data> file uploads of the specified name as an ordered array reference. See L for details on the representation of the uploads. B This will read the text form fields into memory as in L. =head2 Response =head3 set_nph $cgi = $cgi->set_nph; $cgi = $cgi->set_nph(1); If set to a true value or called without a value before rendering response headers, CGI::Tiny will act as a L script and render full HTTP response headers. This may be required for some CGI servers, or enable unbuffered responses or HTTP extensions not supported by the CGI server. No effect after response headers have been rendered. =head3 set_response_body_buffer $cgi = $cgi->set_response_body_buffer(128*1024); Sets the buffer size (number of bytes to read at once) for streaming a C or C response body with L or L. Defaults to the value of the C environment variable or 131072 (128 KiB). A value of 0 will use the default value. =head3 set_response_status $cgi = $cgi->set_response_status(404); $cgi = $cgi->set_response_status('500 Internal Server Error'); Sets the response HTTP status code. A full status string including a human-readable message will be used as-is. A bare status code must be a known L and will have the standard human-readable message appended. No effect after response headers have been rendered. The CGI protocol assumes a status of C<200 OK> if no response status is set. =head3 set_response_disposition $cgi = $cgi->set_response_disposition('attachment'); $cgi = $cgi->set_response_disposition(attachment => $filename); $cgi = $cgi->set_response_disposition('inline'); # default behavior $cgi = $cgi->set_response_disposition(inline => $filename); Sets the response C header to indicate how the client should present the response, with an optional filename specified in Unicode characters. C suggests to download the content as a file, and C suggests to display the content inline (the default behavior). No effect after response headers have been rendered. =head3 set_response_type $cgi = $cgi->set_response_type('application/xml'); Sets the response C header, to override autodetection in L or L. C will remove the override. No effect after response headers have been rendered. =head3 set_response_charset $cgi = $cgi->set_response_charset('UTF-8'); Set charset to use when rendering C, C, or C response content, defaults to C. =head3 add_response_header $cgi = $cgi->add_response_header('Content-Language' => 'en'); Adds a custom response header. No effect after response headers have been rendered. B Header names are case insensitive and CGI::Tiny does not attempt to deduplicate or munge headers that have been added manually. Headers are printed in the response in the same order added, and adding the same header multiple times will result in multiple instances of that response header. =head3 add_response_cookie $cgi = $cgi->add_response_cookie($name => $value, Expires => 'Sun, 06 Nov 1994 08:49:37 GMT', HttpOnly => 1, 'Max-Age' => 3600, Path => '/foo', SameSite => 'Strict', Secure => 1, ); Adds a C response header. No effect after response headers have been rendered. Cookie values should consist only of simple ASCII text; see L for methods of storing more complex strings and data structures. Optional cookie attributes are specified in key-value pairs after the cookie name and value. Cookie attribute names are case-insensitive. =over =item Domain Domain for which cookie is valid. Defaults to the host of the current document URL, not including subdomains. =item Expires Expiration date string for cookie. Defaults to persisting for the current browser session. L can be used to generate the appropriate date string format. =item HttpOnly If set to a true value, the cookie will be restricted from client-side scripts. =item Max-Age Max age of cookie before it expires, in seconds, as an alternative to specifying C. =item Path URL path for which cookie is valid. =item SameSite C to restrict the cookie to requests from the same site, C to allow it additionally in certain cross-site requests. This attribute is currently part of a draft specification so its handling may change, but it is supported by most browsers. =item Secure If set to a true value, the cookie will be restricted to HTTPS requests. =back =head3 reset_response_headers $cgi = $cgi->reset_response_headers; Remove any pending response headers set by L or L. No effect after response headers have been rendered. =head3 response_status_code my $code = $cgi->response_status_code; Numerical response HTTP status code that will be sent when headers are rendered, as set by L or an error occurring. Defaults to C<200>. =head3 render $cgi = $cgi->render; # default Content-Type: $cgi = $cgi->render(text => $text); # text/plain;charset=$charset $cgi = $cgi->render(html => $html); # text/html;charset=$charset $cgi = $cgi->render(xml => $xml); # application/xml;charset=$charset $cgi = $cgi->render(json => $ref); # application/json;charset=UTF-8 $cgi = $cgi->render(data => $bytes); # application/octet-stream $cgi = $cgi->render(file => $filepath); # application/octet-stream $cgi = $cgi->render(redirect => $url); Renders response headers and then fixed-length response content of a type indicated by the first parameter, if any. A C header will be set to the length of the encoded response content, and further calls to C or L will throw an exception. Use L instead to render without a C header. The C response header will be set according to L, or autodetected depending on the data type of any non-empty response content passed. The C response header will be set to the current time as an HTTP date string if not set manually. If the L is C, any provided response content will be ignored (other than redirect URLs) and C will be set to 0. C, C, or C data is expected to be decoded Unicode characters, and will be encoded according to L (UTF-8 by default). L will be used for efficient UTF-8 encoding if available. C data structures will be encoded to JSON and UTF-8. C or C will render bytes from a string or local file path respectively. A C, or a C whose size cannot be determined accurately from the filesystem, must be rendered using L since its C cannot be determined beforehand. C will set a C header to redirect the client to another URL. The response status will be set to 302 Found unless a different 300-level status has been set with L. It will set a C of 0, and it will not set a C response header. =head3 render_chunk $cgi = $cgi->render_chunk; # default Content-Type: $cgi = $cgi->render_chunk(text => $text); # text/plain;charset=$charset $cgi = $cgi->render_chunk(html => $html); # text/html;charset=$charset $cgi = $cgi->render_chunk(xml => $xml); # application/xml;charset=$charset $cgi = $cgi->render_chunk(json => $ref); # application/json;charset=UTF-8 $cgi = $cgi->render_chunk(data => $bytes); # application/octet-stream $cgi = $cgi->render_chunk(file => $filepath); # application/octet-stream $cgi = $cgi->render_chunk(handle => $filehandle); # application/octet-stream Renders response headers the first time it is called, and then chunked response content of a type indicated by the first parameter, if any. No C header will be set, and C may be called additional times with more response content. C does not impose a chunked response, it simply does not generate a C header. For content where the total encoded content length is known in advance but the content can't be passed to a single L call, a C header can be set manually with L, and then C may be used to render each part. The C response header will be set according to L, or autodetected depending on the data type passed in the first call to C, or to C if there is no more appropriate value. It will be set even if no content is passed to the first C call, in case content is rendered in subsequent calls. The C response header will be set to the current time as an HTTP date string if not set manually. If the L is C, any provided response content will be ignored. C, C, or C data is expected to be decoded Unicode characters, and will be encoded according to L (UTF-8 by default). L will be used for efficient UTF-8 encoding if available. C data structures will be encoded to JSON and UTF-8. C, C, or C will render bytes from a string, local file path, or open filehandle respectively. A C will have C applied to remove any translation layers, and its contents will be streamed until EOF. C responses must be rendered with L. =head1 FUNCTIONS The following convenience functions are provided but not exported. =head2 epoch_to_date my $date = CGI::Tiny::epoch_to_date $epoch; Convert a Unix epoch timestamp, such as returned by C