The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package CGITest;

use strict;
use warnings;

use IO::String;
use URI::Escape;
use File::Slurp;
use CGI;

require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(simulate_cgi_request);

sub simulate_cgi_request
{
    no warnings 'redefine';

    my %args = @_;

    my @required_args = (
       'request_method',
       'cgi_script',
    );
    
    foreach my $arg (@required_args) {
        die "missing required arg '$arg'" unless grep($_ eq $arg, keys %args);
    }

    $args{request_method} = uc($args{request_method});

    local $ENV{REQUEST_METHOD} = $args{request_method} if $args{request_method};
    local $ENV{CONTENT_TYPE} = $args{content_type} if $args{content_type};
    local $ENV{HTTP_ACCEPT} = $args{http_accept} if $args{http_accept};
    local $ENV{QUERY_STRING} = _build_query_string($args{params}) if $args{params};

    local *STDIN;
    local *STDOUT;
    
    if ($args{request_method} eq 'POST') {
        die "missing required arg 'input_file'" unless $args{input_file};
        open(STDIN, '<', $args{input_file}) or die sprintf('couldn\'t open %s as STDIN: %s', $args{input_file}, $!);
    } 

    tie *STDOUT, 'IO::String', my $cgi_output or die "couldn\'t redirect STDOUT to variable: $!";
    
    my $code = read_file($args{cgi_script});
    
    # CGI.pm relies uses global state variables (e.g. @QUERY_PARAMS), based on the
    # assumption that only one CGI request will be handled per Perl script.
    # In order to work around this, we need to manually reset the global
    # variables.

    CGI::initialize_globals;

    { 
        no warnings 'redefine';
        eval $code; 

        if ($@) {
            warn sprintf('error executing \'%s\': %s', $args{cgi_script}, $@);
        }
    }   

    my ($headers, $output) = split(/\cM\cJ\cM\cJ/s, $cgi_output, 2);
    my $headers_hash = _parse_headers($headers);

    return wantarray ? ($output, $headers_hash) : $output;   
}

sub _build_query_string
{
    my $params = shift;

    my $query_string = '';        
    my $first_param = 1;

    foreach my $key (keys %$params) {
        $query_string .= '&' unless $first_param;
        $query_string .= sprintf('%s=%s', uri_escape($key), uri_escape($params->{$key}));
        $first_param = 0;
    }

    return $query_string;
}
 
sub _parse_headers
{
    print ".\n";
    my $headers = shift;
    print ".\n";
    my %hash = ();
    print ".\n";
    foreach my $header_line (split(/\cM\cJ/, $headers)) {
        print ".\n";
        my ($key, $value) = split(/:/, $header_line, 2);
        $value =~ s/^\s*(\S*)\s*$/$1/;
        $hash{$key} = $value;
    }

    print ".\n";
    return \%hash;
}

1;