package CGI::PSGI; use strict; use 5.008_001; our $VERSION = '0.15'; use base qw(CGI); sub new { my($class, $env) = @_; CGI::initialize_globals(); my $self = bless { psgi_env => $env, use_tempfile => 1, }, $class; local *ENV = $env; local $CGI::MOD_PERL = 0; $self->SUPER::init; $self; } sub env { $_[0]->{psgi_env}; } sub read_from_client { my($self, $buff, $len, $offset) = @_; $self->{psgi_env}{'psgi.input'}->read($$buff, $len, $offset); } # copied from CGI.pm sub read_from_stdin { my($self, $buff) = @_; my($eoffound) = 0; my($localbuf) = ''; my($tempbuf) = ''; my($bufsiz) = 1024; my($res); while ($eoffound == 0) { $res = $self->{psgi_env}{'psgi.input'}->read($tempbuf, $bufsiz, 0); if ( !defined($res) ) { # TODO: how to do error reporting ? $eoffound = 1; last; } if ( $res == 0 ) { $eoffound = 1; last; } $localbuf .= $tempbuf; } $$buff = $localbuf; return $res; } # copied and rearanged from CGI::header sub psgi_header { my($self, @p) = @_; my(@header); my($type,$status,$cookie,$target,$expires,$nph,$charset,$attachment,$p3p,@other) = CGI::rearrange([['TYPE','CONTENT_TYPE','CONTENT-TYPE'], 'STATUS',['COOKIE','COOKIES'],'TARGET', 'EXPIRES','NPH','CHARSET', 'ATTACHMENT','P3P'],@p); # CR escaping for values, per RFC 822 for my $header ($type,$status,$cookie,$target,$expires,$nph,$charset,$attachment,$p3p,@other) { if (defined $header) { # From RFC 822: # Unfolding is accomplished by regarding CRLF immediately # followed by a LWSP-char as equivalent to the LWSP-char. $header =~ s/$CGI::CRLF(\s)/$1/g; # All other uses of newlines are invalid input. if ($header =~ m/$CGI::CRLF|\015|\012/) { # shorten very long values in the diagnostic $header = substr($header,0,72).'...' if (length $header > 72); die "Invalid header value contains a newline not followed by whitespace: $header"; } } } $type ||= 'text/html' unless defined($type); if (defined $charset) { $self->charset($charset); } else { $charset = $self->charset if $type =~ /^text\//; } $charset ||= ''; # rearrange() was designed for the HTML portion, so we # need to fix it up a little. my @other_headers; for (@other) { # Don't use \s because of perl bug 21951 next unless my($header,$value) = /([^ \r\n\t=]+)=\"?(.+?)\"?$/; $header =~ s/^(\w)(.*)/"\u$1\L$2"/e; push @other_headers, $header, $self->unescapeHTML($value); } $type .= "; charset=$charset" if $type ne '' and $type !~ /\bcharset\b/ and defined $charset and $charset ne ''; # Maybe future compatibility. Maybe not. my $protocol = $self->{psgi_env}{SERVER_PROTOCOL} || 'HTTP/1.0'; push(@header, "Window-Target", $target) if $target; if ($p3p) { $p3p = join ' ',@$p3p if ref($p3p) eq 'ARRAY'; push(@header,"P3P", qq(policyref="/w3c/p3p.xml", CP="$p3p")); } # push all the cookies -- there may be several if ($cookie) { my(@cookie) = ref($cookie) && ref($cookie) eq 'ARRAY' ? @{$cookie} : $cookie; for (@cookie) { my $cs = UNIVERSAL::isa($_,'CGI::Cookie') ? $_->as_string : $_; push(@header,"Set-Cookie", $cs) if $cs ne ''; } } # if the user indicates an expiration time, then we need # both an Expires and a Date header (so that the browser is # uses OUR clock) push(@header,"Expires", CGI::expires($expires,'http')) if $expires; push(@header,"Date", CGI::expires(0,'http')) if $expires || $cookie || $nph; push(@header,"Pragma", "no-cache") if $self->cache(); push(@header,"Content-Disposition", "attachment; filename=\"$attachment\"") if $attachment; push(@header, @other_headers); push(@header,"Content-Type", $type) if $type ne ''; $status ||= "200"; $status =~ s/\D*$//; return $status, \@header; } # Ported from CGI.pm's redirect() method. sub psgi_redirect { my ($self,@p) = @_; my($url,$target,$status,$cookie,$nph,@other) = CGI::rearrange([['LOCATION','URI','URL'],'TARGET','STATUS',['COOKIE','COOKIES'],'NPH'],@p); $status = '302 Found' unless defined $status; $url ||= $self->self_url; my(@o); for (@other) { tr/\"//d; push(@o,split("=",$_,2)); } unshift(@o, '-Status' => $status, '-Location'=> $url, '-nph' => $nph); unshift(@o,'-Target'=>$target) if $target; unshift(@o,'-Type'=>''); my @unescaped; unshift(@unescaped,'-Cookie'=>$cookie) if $cookie; return $self->psgi_header((map {$self->unescapeHTML($_)} @o),@unescaped); } # The list is auto generated and modified with: # perl -nle '/^sub (\w+)/ and $sub=$1; \ # /^}\s*$/ and do { print $sub if $code{$sub} =~ /([\%\$]ENV|http\()/; undef $sub };\ # $code{$sub} .= "$_\n" if $sub; \ # /^\s*package [^C]/ and exit' \ # `perldoc -l CGI` for my $method (qw( url_param url cookie raw_cookie _name_and_path_from_env request_method content_type path_translated request_uri Accept user_agent virtual_host remote_host remote_addr referrer server_name server_software virtual_port server_port server_protocol http https remote_ident auth_type remote_user user_name read_multipart read_multipart_related )) { no strict 'refs'; *$method = sub { my $self = shift; my $super = "SUPER::$method"; local *ENV = $self->{psgi_env}; $self->$super(@_); }; } sub DESTROY { my $self = shift; CGI::initialize_globals(); } 1; __END__ =encoding utf-8 =for stopwords =head1 NAME CGI::PSGI - Adapt CGI.pm to the PSGI protocol =head1 SYNOPSIS use CGI::PSGI; my $app = sub { my $env = shift; my $q = CGI::PSGI->new($env); return [ $q->psgi_header, [ $body ] ]; }; =head1 DESCRIPTION This module is for web application framework developers who currently uses L to handle query parameters, and would like for the frameworks to comply with the L protocol. Only slight modifications should be required if the framework is already collecting the body content to print to STDOUT at one place (rather using the print-as-you-go approach). On the other hand, if you are an "end user" of CGI.pm and have a CGI script that you want to run under PSGI web servers, this module might not be what you want. Take a look at L instead. Your application, typically the web application framework adapter should update the code to do C<< CGI::PSGI->new($env) >> instead of C<< CGI->new >> to create a new CGI object. (This is similar to how L object is initialized in a FastCGI environment.) =head1 INTERFACES SUPPORTED Only the object-oriented interface of CGI.pm is supported through CGI::PSGI. This means you should always create an object with C<< CGI::PSGI->new($env) >> and should call methods on the object. The function-based interface like C<< use CGI ':standard' >> does not work with this module. =head1 METHODS CGI::PSGI adds the following extra methods to CGI.pm: =head2 env $env = $cgi->env; Returns the PSGI environment in a hash reference. This allows CGI.pm-based application frameworks such as L to access PSGI extensions, typically set by Plack Middleware components. So if you enable L, your application and plugin developers can access the session via: $cgi->env->{'plack.session'}->get("foo"); Of course this should be coded carefully by checking the existence of C method as well as the hash key C. =head2 psgi_header my ($status_code, $headers_aref) = $cgi->psgi_header(%args); Works like CGI.pm's L, but the return format is modified. It returns an array with the status code and arrayref of header pairs that PSGI requires. If your application doesn't use C<< $cgi->header >>, you can ignore this method and generate the status code and headers arrayref another way. =head2 psgi_redirect my ($status_code, $headers_aref) = $cgi->psgi_redirect(%args); Works like CGI.pm's L, but the return format is modified. It returns an array with the status code and arrayref of header pairs that PSGI requires. If your application doesn't use C<< $cgi->redirect >>, you can ignore this method and generate the status code and headers arrayref another way. =head1 LIMITATIONS Do not use L or something similar in your controller. The module messes up L's DIY autoloader and breaks CGI::PSGI (and potentially other) inheritance. =head1 AUTHOR Tatsuhiko Miyagawa Emiyagawa@bulknews.netE Mark Stosberg Emark@summersault.comE =head1 LICENSE This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO L, L =cut