From: John Napiorkowski Date: Wed, 12 Jun 2013 14:27:20 +0000 (-0400) Subject: merged after conflict resolution X-Git-Tag: 5.90040~3 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=409d48fb495802db8eb6e02a927dd2915d8643b4;hp=8275d3b9bc376e0449ad1716b64c6cd4b9ada0b7;p=catagits%2FCatalyst-Runtime.git merged after conflict resolution --- diff --git a/Makefile.PL b/Makefile.PL index a37f617..b50b74f 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -48,7 +48,6 @@ requires 'HTTP::Headers' => '1.64'; requires 'HTTP::Request' => '5.814'; requires 'HTTP::Response' => '5.813'; requires 'HTTP::Request::AsCGI' => '1.0'; -requires 'LWP::UserAgent'; requires 'Module::Pluggable' => '3.9'; requires 'Path::Class' => '0.09'; requires 'Scalar::Util'; @@ -59,7 +58,6 @@ requires 'Tree::Simple' => '1.15'; requires 'Tree::Simple::Visitor::FindByPath'; requires 'Try::Tiny'; requires 'Safe::Isa'; -requires 'URI' => '1.35'; requires 'Task::Weaken'; requires 'Text::Balanced'; # core in 5.8.x but mentioned for completeness requires 'MRO::Compat'; @@ -69,16 +67,21 @@ requires 'Devel::InnerPackage'; # No longer core in blead requires 'Plack' => '0.9991'; # IIS6+7 fix middleware requires 'Plack::Middleware::ReverseProxy' => '0.04'; requires 'Plack::Test::ExternalServer'; +requires 'Class::Data::Inheritable'; +requires 'Encode' => '2.49'; +requires 'LWP' => '5.837'; # LWP had unicode fail in 5.8.26 +requires 'URI' => '1.36'; # Install the standalone Regex dispatch modules in order to ease the # depreciation transition requires 'Catalyst::DispatchType::Regex' => '5.90021'; -test_requires 'Class::Data::Inheritable'; test_requires 'Test::Exception'; test_requires 'Test::More' => '0.88'; test_requires 'Data::Dump'; test_requires 'HTTP::Request::Common'; +test_requires 'IO::Scalar'; +test_requires 'HTTP::Status'; # aggregate tests if AGGREGATE_TESTS is set and a recent Test::Aggregate and a Test::Simple it works with is available my @author_requires; @@ -94,6 +97,7 @@ else { push(@author_requires, 'CatalystX::LeakChecker', '0.05'); push(@author_requires, 'Catalyst::Devel', '1.0'); # For http server test +push(@author_requires, 'Test::WWW::Mechanize::Catalyst', '0.51'); push(@author_requires, 'Test::TCP', '1.27'); # ditto, ships Net::EmptyPort author_tests('t/author'); @@ -110,6 +114,7 @@ author_requires( Test::Pod::Coverage Test::Spelling Pod::Coverage::TrustPod + Catalyst::Plugin::Params::Nested )); if ($Module::Install::AUTHOR) { diff --git a/lib/Catalyst.pm b/lib/Catalyst.pm index 2e03326..17605d4 100644 --- a/lib/Catalyst.pm +++ b/lib/Catalyst.pm @@ -2982,10 +2982,26 @@ the plugin name does not begin with C. return $class; } + sub _default_plugins { return qw(Unicode::Encoding) } + sub setup_plugins { my ( $class, $plugins ) = @_; $class->_plugins( {} ) unless $class->_plugins; + $plugins = [ grep { + m/Unicode::Encoding/ ? do { + $class->log->warn( + 'Unicode::Encoding plugin is auto-applied,' + . ' please remove this from your appclass' + . ' and make sure to define "encoding" config' + ); + unless (exists $class->config->{'encoding'}) { + $class->config->{'encoding'} = 'UTF-8'; + } + () } + : $_ + } @$plugins ]; + unshift @$plugins, $class->_default_plugins; $plugins = Data::OptList::mkopt($plugins || []); my @plugins = map { @@ -3183,6 +3199,10 @@ C<< $c->request->base >> will be incorrect. C - See L. +=item * + +C - See L + =back =item abort_chain_on_error_fix => 1 @@ -3285,6 +3305,53 @@ If you plan to operate in a threaded environment, remember that all other modules you are using must also be thread-safe. Some modules, most notably L, are not thread-safe. +=head1 ENCODING + +On request, decodes all params from encoding into a sequence of +logical characters. On response, encodes body into encoding. + +=head2 Methods + +=over 4 + +=item encoding + +Returns an instance of an C encoding + + print $c->encoding->name + +=item handle_unicode_encoding_exception ($exception_context) + +Method called when decoding process for a request fails. + +An C<$exception_context> hashref is provided to allow you to override the +behaviour of your application when given data with incorrect encodings. + +The default method throws exceptions in the case of invalid request parameters +(resulting in a 500 error), but ignores errors in upload filenames. + +The keys passed in the C<$exception_context> hash are: + +=over + +=item param_value + +The value which was not able to be decoded. + +=item error_msg + +The exception received from L. + +=item encoding_step + +What type of data was being decoded. Valid values are (currently) +C - for request parameters / arguments / captures +and C - for request upload filenames. + +=back + +=back + =head1 SUPPORT IRC: @@ -3458,7 +3525,7 @@ Will Hawes C willert: Sebastian Willert -wreis: Wallace Reis +wreis: Wallace Reis Yuval Kogman, C diff --git a/lib/Catalyst/Engine/HTTP.pm b/lib/Catalyst/Engine/HTTP.pm index b708a7f..7b506e6 100644 --- a/lib/Catalyst/Engine/HTTP.pm +++ b/lib/Catalyst/Engine/HTTP.pm @@ -18,6 +18,27 @@ to update your scripts to not do this.\n") unless $ENV{HARNESS_ACTIVE}; 1; -# This is here only as some old generated scripts require Catalyst::Engine::HTTP +__END__ +=head1 NAME +Catalyst::Engine::HTTP + +=head1 SYNOPSIS + +See L. + +=head1 DESCRIPTION + +This is here only as some old generated scripts require Catalyst::Engine::HTTP + +=head1 AUTHORS + +Catalyst Contributors, see Catalyst.pm + +=head1 COPYRIGHT + +This library is free software. You can redistribute it and/or modify it under +the same terms as Perl itself. + +=cut diff --git a/lib/Catalyst/Plugin/Unicode/Encoding.pm b/lib/Catalyst/Plugin/Unicode/Encoding.pm new file mode 100644 index 0000000..402087e --- /dev/null +++ b/lib/Catalyst/Plugin/Unicode/Encoding.pm @@ -0,0 +1,209 @@ +package Catalyst::Plugin::Unicode::Encoding; + +use strict; +use base 'Class::Data::Inheritable'; + +use Carp (); +use MRO::Compat; +use Try::Tiny; + +use Encode 2.21 (); +our $CHECK = Encode::FB_CROAK | Encode::LEAVE_SRC; + +our $VERSION = '2.1'; + +__PACKAGE__->mk_classdata('_encoding'); + +sub encoding { + my $c = shift; + my $encoding; + + if ( scalar @_ ) { + # Let it be set to undef + if (my $wanted = shift) { + $encoding = Encode::find_encoding($wanted) + or Carp::croak( qq/Unknown encoding '$wanted'/ ); + } + + $encoding = ref $c + ? $c->{encoding} = $encoding + : $c->_encoding($encoding); + } else { + $encoding = ref $c && exists $c->{encoding} + ? $c->{encoding} + : $c->_encoding; + } + + return $encoding; +} + +sub finalize_headers { + my $c = shift; + + my $body = $c->response->body; + + return $c->next::method(@_) + unless defined($body); + + my $enc = $c->encoding; + + return $c->next::method(@_) + unless $enc; + + my ($ct, $ct_enc) = $c->response->content_type; + + # Only touch 'text-like' contents + return $c->next::method(@_) + unless $c->response->content_type =~ /^text|xml$|javascript$/; + + if ($ct_enc && $ct_enc =~ /charset=([^;]*)/) { + if (uc($1) ne uc($enc->mime_name)) { + $c->log->debug("Unicode::Encoding is set to encode in '" . + $enc->mime_name . + "', content type is '$1', not encoding "); + return $c->next::method(@_); + } + } else { + $c->res->content_type($c->res->content_type . "; charset=" . $enc->mime_name); + } + + # Encode expects plain scalars (IV, NV or PV) and segfaults on ref's + $c->response->body( $c->encoding->encode( $body, $CHECK ) ) + if ref(\$body) eq 'SCALAR'; + + $c->next::method(@_); +} + +# Note we have to hook here as uploads also add to the request parameters +sub prepare_uploads { + my $c = shift; + + $c->next::method(@_); + + my $enc = $c->encoding; + + for my $key (qw/ parameters query_parameters body_parameters /) { + for my $value ( values %{ $c->request->{$key} } ) { + # N.B. Check if already a character string and if so do not try to double decode. + # http://www.mail-archive.com/catalyst@lists.scsys.co.uk/msg02350.html + # this avoids exception if we have already decoded content, and is _not_ the + # same as not encoding on output which is bad news (as it does the wrong thing + # for latin1 chars for example).. + $value = $c->_handle_unicode_decoding($value); + } + } + for my $value ( values %{ $c->request->uploads } ) { + # skip if it fails for uploads, as we don't usually want uploads touched + # in any way + $_->{filename} = try { + $enc->decode( $_->{filename}, $CHECK ) + } catch { + $c->handle_unicode_encoding_exception({ + param_value => $_->{filename}, + error_msg => $_, + encoding_step => 'uploads', + }); + } for ( ref($value) eq 'ARRAY' ? @{$value} : $value ); + } +} + +sub prepare_action { + my $c = shift; + + my $ret = $c->next::method(@_); + + foreach (@{$c->req->arguments}, @{$c->req->captures}) { + $_ = $c->_handle_param_unicode_decoding($_); + } + + return $ret; +} + +sub setup { + my $self = shift; + + my $conf = $self->config; + + # Allow an explict undef encoding to disable default of utf-8 + my $enc = delete $conf->{encoding}; + $self->encoding( $enc ); + + return $self->next::method(@_); +} + +sub _handle_unicode_decoding { + my ( $self, $value ) = @_; + + return unless defined $value; + + if ( ref $value eq 'ARRAY' ) { + foreach ( @$value ) { + $_ = $self->_handle_unicode_decoding($_); + } + return $value; + } + elsif ( ref $value eq 'HASH' ) { + foreach ( values %$value ) { + $_ = $self->_handle_unicode_decoding($_); + } + return $value; + } + else { + return $self->_handle_param_unicode_decoding($value); + } +} + +sub _handle_param_unicode_decoding { + my ( $self, $value ) = @_; + my $enc = $self->encoding; + return try { + Encode::is_utf8( $value ) ? + $value + : $enc->decode( $value, $CHECK ); + } + catch { + $self->handle_unicode_encoding_exception({ + param_value => $value, + error_msg => $_, + encoding_step => 'params', + }); + }; +} + +sub handle_unicode_encoding_exception { + my ( $self, $exception_ctx ) = @_; + $self->log->warn($exception_ctx->{error_msg}); + return $exception_ctx->{'param_value'}; +} + +1; + +__END__ + +=head1 NAME + +Catalyst::Plugin::Unicode::Encoding - Unicode aware Catalyst + +=head1 SYNOPSIS + + use Catalyst; + + MyApp->config( encoding => 'UTF-8' ); # A valid Encode encoding + + +=head1 DESCRIPTION + +This plugin is automatically loaded by apps. Even though is not a core component +yet, it will vanish as soon as the code is fully integrated. For more +information, please refer to C section at L. + +=head1 AUTHORS + +Catalyst Contributors, see Catalyst.pm + +=head1 COPYRIGHT + +This library is free software. You can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/t/aggregate/live_plugin_loaded.t b/t/aggregate/live_plugin_loaded.t index 6795043..64161aa 100644 --- a/t/aggregate/live_plugin_loaded.t +++ b/t/aggregate/live_plugin_loaded.t @@ -15,6 +15,7 @@ my @expected = qw[ Catalyst::Plugin::Test::Inline Catalyst::Plugin::Test::MangleDollarUnderScore Catalyst::Plugin::Test::Plugin + Catalyst::Plugin::Unicode::Encoding TestApp::Plugin::AddDispatchTypes TestApp::Plugin::FullyQualified ]; diff --git a/t/aggregate/unit_core_plugin.t b/t/aggregate/unit_core_plugin.t index 16a5e24..becc3c1 100644 --- a/t/aggregate/unit_core_plugin.t +++ b/t/aggregate/unit_core_plugin.t @@ -53,6 +53,7 @@ my @expected = qw( Catalyst::Plugin::Test::Inline Catalyst::Plugin::Test::MangleDollarUnderScore Catalyst::Plugin::Test::Plugin + Catalyst::Plugin::Unicode::Encoding TestApp::Plugin::AddDispatchTypes TestApp::Plugin::FullyQualified ); diff --git a/t/author/podcoverage.t b/t/author/podcoverage.t index bee250c..894f122 100644 --- a/t/author/podcoverage.t +++ b/t/author/podcoverage.t @@ -8,6 +8,7 @@ use Test::Pod::Coverage 1.04; my @modules = all_modules; our @private = ( 'BUILD' ); foreach my $module (@modules) { + next if $module =~ /Unicode::Encoding/; local @private = (@private, 'run', 'dont_close_all_files') if $module =~ /^Catalyst::Script::/; local @private = (@private, 'plugin') if $module =~ /^Catalyst$/; local @private = (@private, 'snippets') if $module =~ /^Catalyst::Request$/; diff --git a/t/lib/ACLTestApp.pm b/t/lib/ACLTestApp.pm index ec87027..2b7a010 100644 --- a/t/lib/ACLTestApp.pm +++ b/t/lib/ACLTestApp.pm @@ -5,10 +5,13 @@ use strict; use warnings; use MRO::Compat; use Scalar::Util (); +use TestLogger; use base qw/Catalyst Catalyst::Controller/; use Catalyst qw//; +__PACKAGE__->log(TestLogger->new); + sub execute { my $c = shift; my ( $class, $action ) = @_; diff --git a/t/lib/ChainedActionsApp.pm b/t/lib/ChainedActionsApp.pm index 375ce10..3be4faf 100644 --- a/t/lib/ChainedActionsApp.pm +++ b/t/lib/ChainedActionsApp.pm @@ -1,6 +1,7 @@ package ChainedActionsApp; use Moose; use namespace::autoclean; +use TestLogger; use Catalyst::Runtime 5.80; @@ -16,6 +17,8 @@ __PACKAGE__->config( disable_component_regex_fallback => 1, ); +__PACKAGE__->log(TestLogger->new); + __PACKAGE__->setup; 1; diff --git a/t/lib/PluginTestApp.pm b/t/lib/PluginTestApp.pm index 29a02cd..7af690d 100644 --- a/t/lib/PluginTestApp.pm +++ b/t/lib/PluginTestApp.pm @@ -14,8 +14,9 @@ sub _test_plugins { is_deeply [ $c->registered_plugins ], [ qw/Catalyst::Plugin::Test::Plugin + Catalyst::Plugin::Unicode::Encoding TestApp::Plugin::FullyQualified/ - ], + ], '... and it should report the correct plugins'; ok $c->registered_plugins('Catalyst::Plugin::Test::Plugin'), '... or if we have a particular plugin'; diff --git a/t/lib/PluginTestApp/Controller/Root.pm b/t/lib/PluginTestApp/Controller/Root.pm index 7bec366..94f4378 100644 --- a/t/lib/PluginTestApp/Controller/Root.pm +++ b/t/lib/PluginTestApp/Controller/Root.pm @@ -49,6 +49,7 @@ sub run_time_plugins : Local { is_deeply [ $c->registered_plugins ], [ qw/Catalyst::Plugin::Test::Plugin + Catalyst::Plugin::Unicode::Encoding Faux::Plugin TestApp::Plugin::FullyQualified/ ], diff --git a/t/lib/TestApp.pm b/t/lib/TestApp.pm index 89332ba..b06880c 100644 --- a/t/lib/TestApp.pm +++ b/t/lib/TestApp.pm @@ -50,6 +50,7 @@ TestApp->config( action_action_nine => { another_extra_arg => 13 } } }, + encoding => 'UTF-8', abort_chain_on_error_fix => 1, ); diff --git a/t/lib/TestApp/Controller/Root.pm b/t/lib/TestApp/Controller/Root.pm index f2acb21..b626bdb 100644 --- a/t/lib/TestApp/Controller/Root.pm +++ b/t/lib/TestApp/Controller/Root.pm @@ -2,6 +2,7 @@ package TestApp::Controller::Root; use strict; use warnings; use base 'Catalyst::Controller'; +use utf8; __PACKAGE__->config->{namespace} = ''; diff --git a/t/lib/TestApp2.pm b/t/lib/TestApp2.pm new file mode 100644 index 0000000..53b483f --- /dev/null +++ b/t/lib/TestApp2.pm @@ -0,0 +1,19 @@ +package TestApp2; +use strict; +use warnings; +use base qw/Catalyst/; +use Catalyst qw/Params::Nested/; + +__PACKAGE__->config( + 'name' => 'TestApp2', + encoding => 'UTF-8', +); + +__PACKAGE__->setup; + +sub handle_unicode_encoding_exception { + my ( $self, $param_value, $error_msg ) = @_; + return $param_value; +} + +1; diff --git a/t/lib/TestApp2/Controller/Root.pm b/t/lib/TestApp2/Controller/Root.pm new file mode 100644 index 0000000..0fefe63 --- /dev/null +++ b/t/lib/TestApp2/Controller/Root.pm @@ -0,0 +1,16 @@ +package TestApp2::Controller::Root; +use strict; +use warnings; +use utf8; + +__PACKAGE__->config(namespace => q{}); + +use base 'Catalyst::Controller'; + +# your actions replace this one +sub main :Path('') { + $_[1]->res->body('

It works

'); + $_[1]->res->content_type('text/html'); +} + +1; diff --git a/t/lib/TestAppDoubleAutoBug.pm b/t/lib/TestAppDoubleAutoBug.pm index 524ed8b..1044a30 100644 --- a/t/lib/TestAppDoubleAutoBug.pm +++ b/t/lib/TestAppDoubleAutoBug.pm @@ -3,6 +3,7 @@ use warnings; package TestAppDoubleAutoBug; +use TestLogger; use Catalyst qw/ Test::Errors Test::Headers @@ -13,6 +14,8 @@ our $VERSION = '0.01'; __PACKAGE__->config( name => 'TestAppDoubleAutoBug', root => '/some/dir' ); +__PACKAGE__->log(TestLogger->new); + __PACKAGE__->setup; sub execute { diff --git a/t/lib/TestAppIndexDefault.pm b/t/lib/TestAppIndexDefault.pm index 9a129cb..57e3f85 100644 --- a/t/lib/TestAppIndexDefault.pm +++ b/t/lib/TestAppIndexDefault.pm @@ -1,8 +1,11 @@ package TestAppIndexDefault; use strict; use warnings; +use TestLogger; use Catalyst; +__PACKAGE__->log(TestLogger->new); + __PACKAGE__->setup; 1; diff --git a/t/lib/TestAppMatchSingleArg.pm b/t/lib/TestAppMatchSingleArg.pm index 8f87993..6687eac 100644 --- a/t/lib/TestAppMatchSingleArg.pm +++ b/t/lib/TestAppMatchSingleArg.pm @@ -1,8 +1,11 @@ package TestAppMatchSingleArg; use strict; use warnings; +use TestLogger; use Catalyst; +__PACKAGE__->log(TestLogger->new); + __PACKAGE__->setup; 1; diff --git a/t/lib/TestAppOneView.pm b/t/lib/TestAppOneView.pm index 59354b3..33fafea 100644 --- a/t/lib/TestAppOneView.pm +++ b/t/lib/TestAppOneView.pm @@ -1,8 +1,11 @@ package TestAppOneView; use strict; use warnings; +use TestLogger; use Catalyst; +__PACKAGE__->log(TestLogger->new); + __PACKAGE__->setup; 1; diff --git a/t/lib/TestAppUnicode.pm b/t/lib/TestAppUnicode.pm new file mode 100644 index 0000000..7d66522 --- /dev/null +++ b/t/lib/TestAppUnicode.pm @@ -0,0 +1,22 @@ +package TestAppUnicode; +use strict; +use warnings; +use TestLogger; +use base qw/Catalyst/; +use Catalyst qw/Unicode::Encoding Params::Nested/; + +__PACKAGE__->config( + 'name' => 'TestAppUnicode', + $ENV{TESTAPP_ENCODING} ? ( encoding => $ENV{TESTAPP_ENCODING} ) : (), +); + +__PACKAGE__->log(TestLogger->new); + +__PACKAGE__->setup; + +sub handle_unicode_encoding_exception { + my ( $self, $param_value, $error_msg ) = @_; + return $param_value; +} + +1; diff --git a/t/lib/TestAppUnicode/Controller/Root.pm b/t/lib/TestAppUnicode/Controller/Root.pm new file mode 100644 index 0000000..a944b95 --- /dev/null +++ b/t/lib/TestAppUnicode/Controller/Root.pm @@ -0,0 +1,80 @@ +package TestAppUnicode::Controller::Root; +use strict; +use warnings; +use utf8; + +__PACKAGE__->config(namespace => q{}); + +use base 'Catalyst::Controller'; + +sub main :Path('') { + my ($self, $ctx, $charset) = @_; + my $content_type = 'text/html'; + if ($ctx->stash->{charset}) { + $content_type .= ";charset=" . $ctx->stash->{charset}; + } + $ctx->res->body('

It works

'); + $ctx->res->content_type($content_type); +} + +sub unicode_no_enc :Local { + my ($self, $c) = @_; + my $data = "ほげ"; # hoge! + utf8::encode($data); + $c->response->body($data); + $c->res->content_type('text/plain'); + $c->encoding(undef); +} + +sub unicode :Local { + my ($self, $c) = @_; + my $data = "ほげ"; # hoge! + $c->response->body($data); # should be decoded + $c->res->content_type('text/plain'); +} + +sub not_unicode :Local { + my ($self, $c) = @_; + my $data = "\x{1234}\x{5678}"; + utf8::encode($data); # DO NOT WANT unicode + $c->response->body($data); # just some octets + $c->res->content_type('text/plain'); + $c->encoding(undef); +} + +sub latin1 :Local { + my ($self, $c) = @_; + + $c->res->content_type('text/plain'); + $c->response->body('LATIN SMALL LETTER E WITH ACUTE: é'); +} + +sub file :Local { + my ($self, $c) = @_; + close *STDERR; # i am evil. + $c->response->body($main::TEST_FILE); # filehandle from test file +} + +sub capture : Chained('/') CaptureArgs(1) {} + +sub decode_capture : Chained('capture') PathPart('') Args(0) { + my ( $self, $c, $cap_arg ) = @_; + $c->forward('main'); +} + +sub capture_charset : Chained('/') Args(1) { + my ( $self, $c, $cap_arg ) = @_; + $c->stash(charset => $cap_arg); + $c->forward('main'); +} + +sub shift_jis :Local { + my ($self, $c) = @_; + my $data = "ほげ"; # hoge! + $c->response->body($data); # should be decoded + $c->res->content_type('text/plain; charset=Shift_JIS'); + $c->encoding("Shift_JIS"); +} + +1; + diff --git a/t/lib/TestAppWithoutUnicode.pm b/t/lib/TestAppWithoutUnicode.pm new file mode 100644 index 0000000..5cb3d81 --- /dev/null +++ b/t/lib/TestAppWithoutUnicode.pm @@ -0,0 +1,14 @@ +package TestAppWithoutUnicode; +use strict; +use warnings; +use TestLogger; +use base qw/Catalyst/; +use Catalyst qw/Params::Nested/; + +__PACKAGE__->config('name' => 'TestAppWithoutUnicode'); + +__PACKAGE__->log(TestLogger->new); + +__PACKAGE__->setup; + +1; diff --git a/t/lib/TestAppWithoutUnicode/Controller/Root.pm b/t/lib/TestAppWithoutUnicode/Controller/Root.pm new file mode 100644 index 0000000..4328fb9 --- /dev/null +++ b/t/lib/TestAppWithoutUnicode/Controller/Root.pm @@ -0,0 +1,17 @@ +package TestAppWithoutUnicode::Controller::Root; + +use Moose; +BEGIN { extends 'Catalyst::Controller' } +use Encode qw(encode_utf8 decode_utf8); + +__PACKAGE__->config( namespace => q{} ); + +sub default : Private { + my ( $self, $c ) = @_; + my $param = decode_utf8($c->request->parameters->{'myparam'}); + $c->response->body( encode_utf8($param) ); +} + +__PACKAGE__->meta->make_immutable; + +1; diff --git a/t/lib/TestLogger.pm b/t/lib/TestLogger.pm new file mode 100644 index 0000000..f1dc7e6 --- /dev/null +++ b/t/lib/TestLogger.pm @@ -0,0 +1,23 @@ +package TestLogger; +use strict; +use warnings; + +our @LOGS; +our @ELOGS; + +sub new { + return bless {}, __PACKAGE__; +} + +sub debug { + shift; + push(@LOGS, shift()); +} + +sub warn { + shift; + push(@ELOGS, shift()); +} + +1; + diff --git a/t/live_redirect_body.t b/t/live_redirect_body.t index 913f0e9..8b9d62c 100644 --- a/t/live_redirect_body.t +++ b/t/live_redirect_body.t @@ -40,7 +40,7 @@ use Test::More; is( $response->code, 302, 'Response Code' ); # When the developer sets both the content body and content type, the set content body and content_type should get through. - is( $response->header( 'Content-Type' ), 'text/plain', 'Content Type' ); + like( $response->header( 'Content-Type' ), qr{text/plain}, 'Content Type' ); like( $response->content, qr/kind sir/, 'Content contains content set by the Controller' ); } diff --git a/t/unicode_plugin_charset_utf8.t b/t/unicode_plugin_charset_utf8.t new file mode 100644 index 0000000..81ba9f7 --- /dev/null +++ b/t/unicode_plugin_charset_utf8.t @@ -0,0 +1,32 @@ +use strict; +use warnings; +use Test::More; +use FindBin qw/ $Bin /; +use lib "$Bin/lib"; +use Data::Dumper; + +BEGIN { + $ENV{TESTAPP_ENCODING} = 'UTF-8'; + $ENV{TESTAPP_DEBUG} = 0; + $ENV{CATALYST_DEBUG} = 0; +} + +use Catalyst::Test 'TestAppUnicode'; + +ok request('/capture_charset/utf-8'); +is scalar(@TestLogger::LOGS), 0; + +ok request('/capture_charset/latin1'); +is scalar(@TestLogger::LOGS), 1 + or diag Dumper(\@TestLogger::LOGS); + +@TestLogger::LOGS = (); + +ok request('/capture_charset/iso-8859-1; header=present'); +is scalar(@TestLogger::LOGS), 1 + or diag Dumper(\@TestLogger::LOGS); +like $TestLogger::LOGS[0], qr/content type is 'iso-8859-1'/; + +like $TestLogger::ELOGS[0], qr/Unicode::Encoding plugin/; + +done_testing; diff --git a/t/unicode_plugin_config.t b/t/unicode_plugin_config.t new file mode 100644 index 0000000..513c978 --- /dev/null +++ b/t/unicode_plugin_config.t @@ -0,0 +1,31 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use Test::More; + +BEGIN { $ENV{TESTAPP_ENCODING} = 'UTF-8' }; + +# setup library path +use FindBin qw($Bin); +use lib "$Bin/lib"; + +BEGIN { +if ( !eval { require Test::WWW::Mechanize::Catalyst } || ! Test::WWW::Mechanize::Catalyst->VERSION('0.51') ) { + plan skip_all => 'Need Test::WWW::Mechanize::Catalyst for this test'; +} +} + +# make sure testapp works +use_ok('TestAppUnicode'); + +use Test::WWW::Mechanize::Catalyst 'TestAppUnicode'; +my $mech = Test::WWW::Mechanize::Catalyst->new; + +{ + TestAppUnicode->encoding('UTF-8'); + $mech->get_ok('http://localhost/unicode', 'encoding configured ok'); +} + +done_testing; + diff --git a/t/unicode_plugin_live.t b/t/unicode_plugin_live.t new file mode 100644 index 0000000..de810aa --- /dev/null +++ b/t/unicode_plugin_live.t @@ -0,0 +1,93 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use Test::More; +use IO::Scalar; + +# setup library path +use FindBin qw($Bin); +use lib "$Bin/lib"; + +BEGIN { +if ( !eval { require Test::WWW::Mechanize::Catalyst } || ! Test::WWW::Mechanize::Catalyst->VERSION('0.51') ) { + plan skip_all => 'Need Test::WWW::Mechanize::Catalyst for this test'; +} +} + +# make sure testapp works +use_ok('TestAppUnicode') or BAIL_OUT($@); + +our $TEST_FILE = IO::Scalar->new(\"this is a test"); +sub IO::Scalar::FILENO { -1 }; # needed? + +# a live test against TestAppUnicode, the test application +use Test::WWW::Mechanize::Catalyst 'TestAppUnicode'; +my $mech = Test::WWW::Mechanize::Catalyst->new; +$mech->get_ok('http://localhost/', 'get main page'); +$mech->content_like(qr/it works/i, 'see if it has our text'); +is ($mech->response->header('Content-Type'), 'text/html; charset=UTF-8', + 'Content-Type with charset' +); + +{ + $mech->get_ok('http://localhost/unicode_no_enc', 'get unicode_no_enc'); + + my $exp = "\xE3\x81\xBB\xE3\x81\x92"; + my $got = Encode::encode_utf8($mech->content); + + is ($mech->response->header('Content-Type'), 'text/plain', + 'Content-Type with no charset'); + + is($got, $exp, 'content contains hoge'); +} + +{ + $mech->get_ok('http://localhost/unicode', 'get unicode'); + + is ($mech->response->header('Content-Type'), 'text/plain; charset=UTF-8', + 'Content-Type with charset'); + + my $exp = "\xE3\x81\xBB\xE3\x81\x92"; + my $got = Encode::encode_utf8($mech->content); + + is($got, $exp, 'content contains hoge'); +} + +{ + $mech->get_ok('http://localhost/not_unicode', 'get bytes'); + my $exp = "\xE1\x88\xB4\xE5\x99\xB8"; + my $got = Encode::encode_utf8($mech->content); + + is($got, $exp, 'got 1234 5678'); +} + +{ + $mech->get_ok('http://localhost/file', 'get file'); + $mech->content_like(qr/this is a test/, 'got filehandle contents'); +} + +{ + # The latin 1 case is the one everyone forgets. I want to really make sure + # its right, so lets check the damn bytes. + $mech->get_ok('http://localhost/latin1', 'get latin1'); + is ($mech->response->header('Content-Type'), 'text/plain; charset=UTF-8', + 'Content-Type with charset'); + + + my $exp = "LATIN SMALL LETTER E WITH ACUTE: \xC3\xA9"; + my $got = Encode::encode_utf8($mech->content); + + is ($got, $exp, 'content octets are UTF-8'); +} + +{ + $mech->get_ok('http://localhost/shift_jis', 'get shift_jis'); + is ($mech->response->header('Content-Type'), 'text/plain; charset=Shift_JIS', 'Content-Type with charset'); + my $exp = "\xE3\x81\xBB\xE3\x81\x92"; + my $got = Encode::encode_utf8($mech->content); + is ($got, $exp, 'content octets are Shift_JIS'); +} + +done_testing; + diff --git a/t/unicode_plugin_nested_params.t b/t/unicode_plugin_nested_params.t new file mode 100644 index 0000000..b31f4ce --- /dev/null +++ b/t/unicode_plugin_nested_params.t @@ -0,0 +1,71 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use Test::More; +use utf8; + +# setup library path +use FindBin qw($Bin); +use lib "$Bin/lib"; + +BEGIN { eval { require Catalyst::Plugin::Params::Nested; 1; } || + plan skip_all => 'Need Catalyst::Plugin::Params::Nested' } + +use Catalyst::Test 'TestApp2'; +use Encode; +use HTTP::Request::Common; +use URI::Escape qw/uri_escape_utf8/; +use HTTP::Status 'is_server_error'; + +my $encode_str = "\x{e3}\x{81}\x{82}"; # e38182 is japanese 'あ' +my $decode_str = Encode::decode('utf-8' => $encode_str); +my $escape_str = uri_escape_utf8($decode_str); + +BEGIN { + eval 'require Catalyst::Plugin::Params::Nested'; + plan skip_all => 'Catalyst::Plugin::Params::Nested is required' if $@; +} + +{ + my ($res, $c) = ctx_request("/?foo.1=bar&foo.2=$escape_str"); + is( $c->res->output, '

It works

', 'Content displayed' ); + + my $got = $c->request->parameters; + my $expected = { + 'foo.1' => 'bar', + 'foo.2' => $decode_str, + 'foo' => [undef, 'bar', $decode_str], + }; + + is( $got->{foo}->[0], undef, '{foo}->[0] is undef' ); + is( $got->{foo}->[1], 'bar', '{foo}->[1] is bar' ); + ok( utf8::is_utf8( $got->{'foo.2'} ), '{foo.2} is utf8' ); + ok( utf8::is_utf8( $got->{foo}->[2] ), '{foo}->[2] is utf8' ); + is_deeply($got, $expected, 'nested params' ); +} + +{ + my ($res, $c) = ctx_request("/?foo.1=bar&foo.2=$escape_str&bar.baz=$escape_str&baz.bar.foo=$escape_str&&arr.0.1=$escape_str"); + + my $got = $c->request->parameters; + my $expected = { + 'foo.1' => 'bar', + 'foo.2' => $decode_str, + 'bar.baz' => $decode_str, + 'baz.bar.foo' => $decode_str, + 'arr.0.1' => $decode_str, + 'arr' => [ [undef, $decode_str] ], + 'foo' => [undef, 'bar', $decode_str], + 'bar' => { baz => $decode_str }, + 'baz' => { bar => { foo => $decode_str } }, + }; + + is( ref $got->{arr}->[0], 'ARRAY', '{arr}->[0] is ARRAY' ); + ok( utf8::is_utf8( $got->{arr}->[0]->[1] ), '{arr}->[0]->[1] is utf8' ); + ok( utf8::is_utf8( $got->{bar}{baz} ), '{bar}{baz} is utf8' ); + ok( utf8::is_utf8( $got->{baz}{bar}{foo} ), '{baz}{bar}{foo} is utf8' ); + is_deeply($got, $expected, 'nested params' ); +} + +done_testing(); diff --git a/t/unicode_plugin_no_encoding.t b/t/unicode_plugin_no_encoding.t new file mode 100644 index 0000000..5d0dfe3 --- /dev/null +++ b/t/unicode_plugin_no_encoding.t @@ -0,0 +1,37 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use Test::More; +use utf8; + +# setup library path +use FindBin qw($Bin); +use lib "$Bin/lib"; + +use Catalyst::Test 'TestAppWithoutUnicode'; +use Encode; +use HTTP::Request::Common; +use URI::Escape qw/uri_escape_utf8/; +use HTTP::Status 'is_server_error'; + +my $encode_str = "\x{e3}\x{81}\x{82}"; # e38182 is japanese 'あ' +my $decode_str = Encode::decode('utf-8' => $encode_str); +my $escape_str = uri_escape_utf8($decode_str); + +check_parameter(GET "/?myparam=$escape_str"); + +sub check_parameter { + my ( undef, $c ) = ctx_request(shift); + is $c->res->output => $encode_str; + + my $myparam = $c->req->param('myparam'); + ok !utf8::is_utf8($myparam); + is $myparam => $encode_str; + + is scalar(@TestLogger::ELOGS), 2 + or diag Dumper(\@TestLogger::ELOGS); + like $TestLogger::ELOGS[0], qr/method \"decode\"/; +} + +done_testing; diff --git a/t/unicode_plugin_request_decode.t b/t/unicode_plugin_request_decode.t new file mode 100644 index 0000000..a3bab97 --- /dev/null +++ b/t/unicode_plugin_request_decode.t @@ -0,0 +1,82 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use Test::More tests => 5 * 5; +use utf8; + +# setup library path +use FindBin qw($Bin); +use lib "$Bin/lib"; + +use Catalyst::Test 'TestAppUnicode'; +use Encode; +use HTTP::Request::Common; +use URI::Escape qw/uri_escape_utf8/; +use HTTP::Status 'is_server_error'; + +my $encode_str = "\x{e3}\x{81}\x{82}"; # e38182 is japanese 'あ' +my $decode_str = Encode::decode('utf-8' => $encode_str); +my $escape_str = uri_escape_utf8($decode_str); + +check_parameter(GET "/?foo=$escape_str"); +check_parameter(POST '/', ['foo' => $encode_str]); +check_parameter(POST '/', + Content_Type => 'form-data', + Content => [ + 'foo' => [ + "$Bin/unicode_plugin_request_decode.t", + $encode_str, + ] + ], +); + +check_argument(GET "/$escape_str"); +check_capture(GET "/capture/$escape_str"); + +# sending non-utf8 data +my $non_utf8_data = "%C3%E6%CB%AA"; +check_fallback(GET "/?q=${non_utf8_data}"); +check_fallback(GET "/${non_utf8_data}"); +check_fallback(GET "/capture/${non_utf8_data}"); +check_fallback(POST '/', ['foo' => $non_utf8_data]); + +sub check_parameter { + my ( undef, $c ) = ctx_request(shift); + is $c->res->output => '

It works

'; + + my $foo = $c->req->param('foo'); + ok utf8::is_utf8($foo); + is $foo => $decode_str; + + my $other_foo = $c->req->method eq 'POST' + ? $c->req->upload('foo') + ? $c->req->upload('foo')->filename + : $c->req->body_parameters->{foo} + : $c->req->query_parameters->{foo}; + ok utf8::is_utf8($other_foo); + is $other_foo => $decode_str; +} + +sub check_argument { + my ( undef, $c ) = ctx_request(shift); + is $c->res->output => '

It works

'; + + my $foo = $c->req->args->[0]; + ok utf8::is_utf8($foo); + is $foo => $decode_str; +} + +sub check_capture { + my ( undef, $c ) = ctx_request(shift); + is $c->res->output => '

It works

'; + + my $foo = $c->req->captures->[0]; + ok utf8::is_utf8($foo); + is $foo => $decode_str; +} + +sub check_fallback { + my ( $res, $c ) = ctx_request(shift); + ok(!is_server_error($res->code)) or diag('Response code is: ' . $res->code); +}