use strict;
use base 'Class::Accessor::Fast';
-use CGI::Cookie;
-use Data::Dumper;
+use CGI::Simple::Cookie;
+use Data::Dump qw/dump/;
+use Errno 'EWOULDBLOCK';
use HTML::Entities;
use HTTP::Body;
use HTTP::Headers;
use URI::QueryParam;
+use Scalar::Util ();
# input position and length
__PACKAGE__->mk_accessors(qw/read_position read_length/);
use overload '""' => sub { return ref shift }, fallback => 1;
# Amount of data to read from input on each pass
-our $CHUNKSIZE = 4096;
+our $CHUNKSIZE = 64 * 1024;
=head1 NAME
sub finalize_body {
my ( $self, $c ) = @_;
my $body = $c->response->body;
- if ( ref $body && ($body->can('read') || ref($body) eq 'GLOB') ) {
+ no warnings 'uninitialized';
+ if ( Scalar::Util::blessed($body) && $body->can('read') or ref($body) eq 'GLOB' ) {
while ( !eof $body ) {
- read $body, my $buffer, $CHUNKSIZE;
+ read $body, my ($buffer), $CHUNKSIZE;
last unless $self->write( $c, $buffer );
}
close $body;
=head2 $self->finalize_cookies($c)
-Create CGI::Cookies from $c->res->cookies, and set them as response headers.
+Create CGI::Simple::Cookie objects from $c->res->cookies, and set them as
+response headers.
=cut
my ( $self, $c ) = @_;
my @cookies;
- while ( my ( $name, $cookie ) = each %{ $c->response->cookies } ) {
-
- my $cookie = CGI::Cookie->new(
- -name => $name,
- -value => $cookie->{value},
- -expires => $cookie->{expires},
- -domain => $cookie->{domain},
- -path => $cookie->{path},
- -secure => $cookie->{secure} || 0
+
+ foreach my $name ( keys %{ $c->response->cookies } ) {
+
+ my $val = $c->response->cookies->{$name};
+
+ my $cookie = (
+ Scalar::Util::blessed($val)
+ ? $val
+ : CGI::Simple::Cookie->new(
+ -name => $name,
+ -value => $val->{value},
+ -expires => $val->{expires},
+ -domain => $val->{domain},
+ -path => $val->{path},
+ -secure => $val->{secure} || 0
+ )
);
push @cookies, $cookie->as_string;
my ( $self, $c ) = @_;
$c->res->content_type('text/html; charset=utf-8');
- my $name = $c->config->{name} || 'Catalyst Application';
+ my $name = $c->config->{name} || join(' ', split('::', ref $c));
my ( $title, $error, $infos );
if ( $c->debug ) {
# For pretty dumps
- local $Data::Dumper::Terse = 1;
$error = join '', map {
'<p><code class="error">'
. encode_entities($_)
# Don't show response header state in dump
delete $c->res->{_finalized_headers};
- my $req = encode_entities Dumper $c->req;
- my $res = encode_entities Dumper $c->res;
- my $stash = encode_entities Dumper $c->stash;
-
my @infos;
my $i = 0;
for my $dump ( $c->dump_these ) {
my $name = $dump->[0];
- my $value = encode_entities( Dumper $dump->[1] );
+ my $value = encode_entities( dump( $dump->[1] ));
push @infos, sprintf <<"EOF", $name, $value;
<h2><a href="#" onclick="toggleDump('dump_$i'); return false">%s</a></h2>
<div id="dump_$i">
$infos = <<"";
<pre>
(en) Please come back later
+(fr) SVP veuillez revenir plus tard
(de) Bitte versuchen sie es spaeter nocheinmal
(at) Konnten's bitt'schoen spaeter nochmal reinschauen
(no) Vennligst prov igjen senere
(dk) Venligst prov igen senere
(pl) Prosze sprobowac pozniej
+(pt) Por favor volte mais tarde
+(ru) Попробуйте еще раз позже
+(ua) Спробуйте ще раз пізніше
</pre>
$name = '';
body {
font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
Tahoma, Arial, helvetica, sans-serif;
- color: #ddd;
+ color: #333;
background-color: #eee;
margin: 0px;
padding: 0px;
}
:link, :link:hover, :visited, :visited:hover {
- color: #ddd;
+ color: #000;
}
div.box {
position: relative;
border: 1px solid #aaa;
padding: 4px;
margin: 10px;
- -moz-border-radius: 10px;
}
div.error {
- background-color: #977;
+ background-color: #cce;
border: 1px solid #755;
padding: 8px;
margin: 4px;
margin-bottom: 10px;
- -moz-border-radius: 10px;
}
div.infos {
- background-color: #797;
+ background-color: #eee;
border: 1px solid #575;
padding: 8px;
margin: 4px;
margin-bottom: 10px;
- -moz-border-radius: 10px;
}
div.name {
- background-color: #779;
+ background-color: #cce;
border: 1px solid #557;
padding: 8px;
margin: 4px;
- -moz-border-radius: 10px;
}
code.error {
display: block;
=cut
-sub finalize_read {
- my ( $self, $c ) = @_;
-
- undef $self->{_prepared_read};
-}
+sub finalize_read { }
=head2 $self->finalize_uploads($c)
sub prepare_body {
my ( $self, $c ) = @_;
- $self->read_length( $c->request->header('Content-Length') || 0 );
- my $type = $c->request->header('Content-Type');
-
- unless ( $c->request->{_body} ) {
- $c->request->{_body} = HTTP::Body->new( $type, $self->read_length );
- $c->request->{_body}->{tmpdir} = $c->config->{uploadtmp} if exists $c->config->{uploadtmp};
- }
-
- if ( $self->read_length > 0 ) {
+ if ( my $length = $self->read_length ) {
+ unless ( $c->request->{_body} ) {
+ my $type = $c->request->header('Content-Type');
+ $c->request->{_body} = HTTP::Body->new( $type, $length );
+ $c->request->{_body}->tmpdir( $c->config->{uploadtmp} )
+ if exists $c->config->{uploadtmp};
+ }
+
while ( my $buffer = $self->read($c) ) {
$c->prepare_body_chunk($buffer);
}
# paranoia against wrong Content-Length header
- my $remaining = $self->read_length - $self->read_position;
- if ($remaining > 0) {
+ my $remaining = $length - $self->read_position;
+ if ( $remaining > 0 ) {
$self->finalize_read($c);
- Catalyst::Exception->throw("Wrong Content-Length value: ". $self->read_length);
+ Catalyst::Exception->throw(
+ "Wrong Content-Length value: $length" );
}
}
+ else {
+ # Defined but will cause all body code to be skipped
+ $c->request->{_body} = 0;
+ }
}
=head2 $self->prepare_body_chunk($c)
sub prepare_body_parameters {
my ( $self, $c ) = @_;
+
+ return unless $c->request->{_body};
+
$c->request->body_parameters( $c->request->{_body}->param );
}
=head2 $self->prepare_cookies($c)
-Parse cookies from header. Sets a L<CGI::Cookie> object.
+Parse cookies from header. Sets a L<CGI::Simple::Cookie> object.
=cut
my ( $self, $c ) = @_;
if ( my $header = $c->request->header('Cookie') ) {
- $c->req->cookies( { CGI::Cookie->parse($header) } );
+ $c->req->cookies( { CGI::Simple::Cookie->parse($header) } );
}
}
my ( $self, $c ) = @_;
# We copy, no references
- while ( my ( $name, $param ) = each %{ $c->request->query_parameters } ) {
+ foreach my $name ( keys %{ $c->request->query_parameters } ) {
+ my $param = $c->request->query_parameters->{$name};
$param = ref $param eq 'ARRAY' ? [ @{$param} ] : $param;
$c->request->parameters->{$name} = $param;
}
# Merge query and body parameters
- while ( my ( $name, $param ) = each %{ $c->request->body_parameters } ) {
+ foreach my $name ( keys %{ $c->request->body_parameters } ) {
+ my $param = $c->request->body_parameters->{$name};
$param = ref $param eq 'ARRAY' ? [ @{$param} ] : $param;
if ( my $old_param = $c->request->parameters->{$name} ) {
if ( ref $old_param eq 'ARRAY' ) {
sub prepare_query_parameters {
my ( $self, $c, $query_string ) = @_;
+
+ # Check for keywords (no = signs)
+ # (yes, index() is faster than a regex :))
+ if ( index( $query_string, '=' ) < 0 ) {
+ $c->request->query_keywords( $self->unescape_uri($query_string) );
+ return;
+ }
+
+ my %query;
# replace semi-colons
$query_string =~ s/;/&/g;
-
- my $u = URI->new( '', 'http' );
- $u->query($query_string);
- for my $key ( $u->query_param ) {
- my @vals = $u->query_param($key);
- $c->request->query_parameters->{$key} = @vals > 1 ? [@vals] : $vals[0];
+
+ my @params = grep { length $_ } split /&/, $query_string;
+
+ for my $item ( @params ) {
+
+ my ($param, $value)
+ = map { $self->unescape_uri($_) }
+ split( /=/, $item, 2 );
+
+ $param = $self->unescape_uri($item) unless defined $param;
+
+ if ( exists $query{$param} ) {
+ if ( ref $query{$param} ) {
+ push @{ $query{$param} }, $value;
+ }
+ else {
+ $query{$param} = [ $query{$param}, $value ];
+ }
+ }
+ else {
+ $query{$param} = $value;
+ }
}
+
+ $c->request->query_parameters( \%query );
}
=head2 $self->prepare_read($c)
sub prepare_read {
my ( $self, $c ) = @_;
- # Reset the read position
+ # Initialize the read position
$self->read_position(0);
+
+ # Initialize the amount of data we think we need to read
+ $self->read_length( $c->request->header('Content-Length') || 0 );
}
=head2 $self->prepare_request(@arguments)
sub prepare_uploads {
my ( $self, $c ) = @_;
+
+ return unless $c->request->{_body};
+
my $uploads = $c->request->{_body}->upload;
for my $name ( keys %$uploads ) {
my $files = $uploads->{$name};
# support access to the filename as a normal param
my @filenames = map { $_->{filename} } @uploads;
- $c->request->parameters->{$name} =
- @filenames > 1 ? \@filenames : $filenames[0];
+ # append, if there's already params with this name
+ if (exists $c->request->parameters->{$name}) {
+ if (ref $c->request->parameters->{$name} eq 'ARRAY') {
+ push @{ $c->request->parameters->{$name} }, @filenames;
+ }
+ else {
+ $c->request->parameters->{$name} =
+ [ $c->request->parameters->{$name}, @filenames ];
+ }
+ }
+ else {
+ $c->request->parameters->{$name} =
+ @filenames > 1 ? \@filenames : $filenames[0];
+ }
}
}
sub read {
my ( $self, $c, $maxlength ) = @_;
- unless ( $self->{_prepared_read} ) {
- $self->prepare_read($c);
- $self->{_prepared_read} = 1;
- }
-
my $remaining = $self->read_length - $self->read_position;
$maxlength ||= $CHUNKSIZE;
=head2 $self->write($c, $buffer)
-Writes the buffer to the client. Can only be called once for a request.
+Writes the buffer to the client.
=cut
$self->prepare_write($c);
$self->{_prepared_write} = 1;
}
+
+ my $len = length($buffer);
+ my $wrote = syswrite STDOUT, $buffer;
+
+ if ( !defined $wrote && $! == EWOULDBLOCK ) {
+ # Unable to write on the first try, will retry in the loop below
+ $wrote = 0;
+ }
+
+ if ( defined $wrote && $wrote < $len ) {
+ # We didn't write the whole buffer
+ while (1) {
+ my $ret = syswrite STDOUT, $buffer, $CHUNKSIZE, $wrote;
+ if ( defined $ret ) {
+ $wrote += $ret;
+ }
+ else {
+ next if $! == EWOULDBLOCK;
+ return;
+ }
+
+ last if $wrote >= $len;
+ }
+ }
+
+ return $wrote;
+}
+
+=head2 $self->unescape_uri($uri)
+
+Unescapes a given URI using the most efficient method available. Engines such
+as Apache may implement this using Apache's C-based modules, for example.
- print STDOUT $buffer;
+=cut
+
+sub unescape_uri {
+ my ( $self, $str ) = @_;
+
+ $str =~ s/(?:%([0-9A-Fa-f]{2})|\+)/defined $1 ? chr(hex($1)) : ' '/eg;
+
+ return $str;
}
=head2 $self->finalize_output
=head1 AUTHORS
-Sebastian Riedel, <sri@cpan.org>
-
-Andy Grundman, <andy@hybridized.org>
+Catalyst Contributors, see Catalyst.pm
=head1 COPYRIGHT