From 56db1527adb758c76c3d0f6e5109229e15980f48 Mon Sep 17 00:00:00 2001 From: a-adam Date: Wed, 22 Jan 2014 22:43:43 +0100 Subject: [PATCH 1/9] Update TestAppUnicode.pm --- t/lib/TestAppUnicode.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/t/lib/TestAppUnicode.pm b/t/lib/TestAppUnicode.pm index e91cb1f0f..25900d8b4 100644 --- a/t/lib/TestAppUnicode.pm +++ b/t/lib/TestAppUnicode.pm @@ -31,5 +31,9 @@ get '/form' => sub { })."\x{E9} - string1: ".params->{'string1'} }; +get '/unicode-json' => sub { + { test => "\x{100}" }; +}; + 1; From 93332341d6e35d7062af1cc6acf49f2632e3090e Mon Sep 17 00:00:00 2001 From: a-adam Date: Wed, 22 Jan 2014 22:44:58 +0100 Subject: [PATCH 2/9] Update 04_charset_server.t --- t/12_response/04_charset_server.t | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/t/12_response/04_charset_server.t b/t/12_response/04_charset_server.t index 091a9db1d..1a78a5c74 100644 --- a/t/12_response/04_charset_server.t +++ b/t/12_response/04_charset_server.t @@ -54,3 +54,35 @@ Test::TCP::test_tcp( Dancer->dance(); }, ); + +Test::TCP::test_tcp( + client => sub { + my $port = shift; + my $ua = LWP::UserAgent->new; + + my $req = HTTP::Request::Common::GET("http://127.0.0.1:$port/unicode-json"); + my $res = $ua->request($req); + + is $res->content_type, 'application/json'; + is_deeply(from_json($res->content), { test => "\x{100}" }); + }, + server => sub { + my $port = shift; + + use lib "t/lib"; + use TestAppUnicode; + Dancer::Config->load; + + set( + # no charset + environment => 'production', + port => $port, + startup_info => 0, + serializer => 'JSON', + ); + Dancer->dance; + }, +); + + + From d4e0f1d757e25530534eb59b67605c5a6c7a0bf0 Mon Sep 17 00:00:00 2001 From: a-adam Date: Wed, 22 Jan 2014 23:35:29 +0100 Subject: [PATCH 3/9] Update TestAppUnicode.pm --- t/lib/TestAppUnicode.pm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/t/lib/TestAppUnicode.pm b/t/lib/TestAppUnicode.pm index 25900d8b4..4b5477af3 100644 --- a/t/lib/TestAppUnicode.pm +++ b/t/lib/TestAppUnicode.pm @@ -31,7 +31,11 @@ get '/form' => sub { })."\x{E9} - string1: ".params->{'string1'} }; -get '/unicode-json' => sub { +get '/unicode-content-length' => sub { + "\x{100}0123456789"; +}; + +get '/unicode-content-length-json' => sub { { test => "\x{100}" }; }; From 8f0e2dc2824112a4b50ef10e554affd0427a31c1 Mon Sep 17 00:00:00 2001 From: a-adam Date: Wed, 22 Jan 2014 23:38:01 +0100 Subject: [PATCH 4/9] Update 04_charset_server.t --- t/12_response/04_charset_server.t | 39 +++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/t/12_response/04_charset_server.t b/t/12_response/04_charset_server.t index 1a78a5c74..981c5b373 100644 --- a/t/12_response/04_charset_server.t +++ b/t/12_response/04_charset_server.t @@ -60,11 +60,15 @@ Test::TCP::test_tcp( my $port = shift; my $ua = LWP::UserAgent->new; - my $req = HTTP::Request::Common::GET("http://127.0.0.1:$port/unicode-json"); + my $req = HTTP::Request::Common::GET( + "http://127.0.0.1:$port/unicode-content-length"); my $res = $ua->request($req); - is $res->content_type, 'application/json'; - is_deeply(from_json($res->content), { test => "\x{100}" }); + is $res->content_type, 'text/html'; + # UTF-8 seems to be Dancer's default encoding + my $v = "\x{100}0123456789"; + utf8::encode($v); + is $res->content, $v; }, server => sub { my $port = shift; @@ -78,11 +82,38 @@ Test::TCP::test_tcp( environment => 'production', port => $port, startup_info => 0, - serializer => 'JSON', ); Dancer->dance; }, ); +Test::TCP::test_tcp( + client => sub { + my $port = shift; + my $ua = LWP::UserAgent->new; + + my $req = HTTP::Request::Common::GET( + "http://127.0.0.1:$port/unicode-content-length-json"); + my $res = $ua->request($req); + + is $res->content_type, 'application/json'; + is_deeply(from_json($res->content), { test => "\x{100}" }); + }, + server => sub { + my $port = shift; + + use lib "t/lib"; + use TestAppUnicode; + Dancer::Config->load; + set( + # no charset + environment => 'production', + port => $port, + startup_info => 0, + serializer => 'JSON', + ); + Dancer->dance; + }, +); From 85793b4cf9b82491408d265628be78cd0b0852b5 Mon Sep 17 00:00:00 2001 From: a-adam Date: Wed, 22 Jan 2014 23:51:09 +0100 Subject: [PATCH 5/9] Update Handler.pm --- lib/Dancer/Handler.pm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/Dancer/Handler.pm b/lib/Dancer/Handler.pm index b7b2a978b..ed54ad387 100644 --- a/lib/Dancer/Handler.pm +++ b/lib/Dancer/Handler.pm @@ -154,8 +154,10 @@ sub render_response { $response->header( 'Content-Type' => "$ctype; charset=$charset" ) if $ctype !~ /$charset/; } - $response->header( 'Content-Length' => length($content) ) - if !defined $response->header('Content-Length'); + if (!defined $response->header('Content-Length')) { + use bytes; # turn off character semantics + $response->header( 'Content-Length' => length($content) ); + } $content = [$content]; } else { From e30fc138eece50dd9edd9ea6fc142c9ccbb22f93 Mon Sep 17 00:00:00 2001 From: a-adam Date: Thu, 23 Jan 2014 00:11:05 +0100 Subject: [PATCH 6/9] update test plan --- t/12_response/04_charset_server.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/12_response/04_charset_server.t b/t/12_response/04_charset_server.t index 981c5b373..6282d5ac3 100644 --- a/t/12_response/04_charset_server.t +++ b/t/12_response/04_charset_server.t @@ -20,7 +20,7 @@ plan skip_all => "HTTP::Request::Common is needed for this test" use LWP::UserAgent; -plan tests => 6; +plan tests => 10; Test::TCP::test_tcp( client => sub { From 1b5cd9f017c7d68c76431342642fb7a899411a14 Mon Sep 17 00:00:00 2001 From: Achim Adam Date: Mon, 27 Jan 2014 01:34:41 +0100 Subject: [PATCH 7/9] serializer tests: do not pseudo-test deserialization by supplying "params"; supply "body". --- t/14_serializer/02_request_json.t | 8 ++++---- t/14_serializer/04_request_xml.t | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/t/14_serializer/02_request_json.t b/t/14_serializer/02_request_json.t index 9f415708d..979de0ce4 100644 --- a/t/14_serializer/02_request_json.t +++ b/t/14_serializer/02_request_json.t @@ -14,13 +14,13 @@ BEGIN { set 'serializer' => 'JSON', 'show_errors' => 1; get '/' => sub { { foo => 'bar' } }; -post '/' => sub { params }; +post '/' => sub { scalar params }; put '/' => sub { param("id") }; get '/error' => sub { send_error( { foo => 42 }, 401 ) }; get '/error_bis' => sub { send_error( 42, 402 ) }; get '/json' => sub { content_type('application/json'); - to_json( { foo => 'bar' } ) + +{ foo => 'bar' }; }; response_content_is [ PUT => '/', @@ -41,8 +41,8 @@ my $res = dancer_response ( POST => '/', { - params => { foo => 1 }, - headers => [ 'Content-Type' => 'application/json' ] + headers => [ 'Content-Type' => 'application/json' ], + body => to_json({ foo => 1 }), } ); diff --git a/t/14_serializer/04_request_xml.t b/t/14_serializer/04_request_xml.t index 3302a2be9..d9fdd7690 100644 --- a/t/14_serializer/04_request_xml.t +++ b/t/14_serializer/04_request_xml.t @@ -34,8 +34,8 @@ SKIP: { my $res = dancer_response( POST => '/', { - params => { foo => 1 }, - headers => [ 'Content-Type' => 'text/xml' ] + headers => [ 'Content-Type' => 'text/xml' ], + body => to_xml({ foo => 1 }), } ); is_deeply( From cb1a81e6fd3716cf47f2e8ea4bcfa0578a93a9c7 Mon Sep 17 00:00:00 2001 From: Achim Adam Date: Mon, 27 Jan 2014 01:36:10 +0100 Subject: [PATCH 8/9] handle deserialization failures properly: return 400. content deserialization is NOT optional. --- lib/Dancer/Handler.pm | 7 +- lib/Dancer/Serializer.pm | 9 ++- t/14_serializer/19_deserialization_failure.t | 81 ++++++++++++++++++++ 3 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 t/14_serializer/19_deserialization_failure.t diff --git a/lib/Dancer/Handler.pm b/lib/Dancer/Handler.pm index ed54ad387..dc2205b77 100644 --- a/lib/Dancer/Handler.pm +++ b/lib/Dancer/Handler.pm @@ -62,8 +62,11 @@ sub handle_request { Dancer::SharedData->request($request); # deserialize the request body if possible - $request = Dancer::Serializer->process_request($request) - if Dancer::App->current->setting('serializer'); + if (Dancer::App->current->setting('serializer')) { + $request = Dancer::Serializer->process_request($request); + return $self->render_response unless $request; + } + # read cookies from client diff --git a/lib/Dancer/Serializer.pm b/lib/Dancer/Serializer.pm index 772e52623..cbb46ca0c 100644 --- a/lib/Dancer/Serializer.pm +++ b/lib/Dancer/Serializer.pm @@ -91,16 +91,21 @@ sub process_request { my $old_params = $request->params('body'); - # try to deserialize + # deserialize my $new_params; eval { $new_params = engine->deserialize($request->body) }; if ($@) { + Dancer::Error->new( + code => 400, + title => "Bad request", + message => "Deserialization failed", + )->render; Dancer::Logger::core "Unable to deserialize request body with " . engine() . " : \n$@"; - return $request; + return; } if(!ref $new_params or ref $new_params ne 'HASH'){ diff --git a/t/14_serializer/19_deserialization_failure.t b/t/14_serializer/19_deserialization_failure.t new file mode 100644 index 000000000..7de051e71 --- /dev/null +++ b/t/14_serializer/19_deserialization_failure.t @@ -0,0 +1,81 @@ +use Dancer ':tests'; +use Dancer::Test; +use Test::More; +use Dancer::ModuleLoader; +use LWP::UserAgent; + +plan skip_all => "skip test with Test::TCP in win32" if $^O eq 'MSWin32'; +plan skip_all => 'Test::TCP is needed to run this test' + unless Dancer::ModuleLoader->load('Test::TCP' => "1.30"); + +plan skip_all => 'JSON is needed to run this test' + unless Dancer::ModuleLoader->load('JSON'); + +plan tests => 3; + +set serializer => 'JSON'; + +my $data = { foo => 'bar' }; + +Test::TCP::test_tcp( + client => sub { + my $port = shift; + my $ua = LWP::UserAgent->new; + my $request = HTTP::Request->new( + PUT => "http://127.0.0.1:$port/deserialization", + [ + Host => 'localhost', + Content_Type => 'application/json' + ], + to_json($data), + ); + my $res = $ua->request($request); + ok $res->is_success, 'Successful response from server'; + is_deeply from_json($res->content), $data, 'Correct content'; + }, + server => sub { + my $port = shift; + use Dancer ':tests'; + set( + apphandler => 'Standalone', + port => $port, + show_errors => 0, + startup_info => 0, + ); + put '/deserialization' => sub { $data }; + Dancer->dance; + }, +); + +Test::TCP::test_tcp( + client => sub { + my $port = shift; + my $ua = LWP::UserAgent->new; + my $request = HTTP::Request->new( + PUT => "http://127.0.0.1:$port/deserialization", + [ + Host => 'localhost', + Content_Type => 'application/json' + ], + # broken JSON + '{ "foo": "bar", }', + ); + my $res = $ua->request($request); + is $res->code, 400, '400 bad request'; + }, + server => sub { + my $port = shift; + use Dancer ':tests'; + set( + apphandler => 'Standalone', + port => $port, + show_errors => 1, + startup_info => 0, + ); + put '/deserialization' => sub { $data }; + Dancer->dance; + }, +); + + + From 076661ff57988ebd17c2ec29c64fff2da1440857 Mon Sep 17 00:00:00 2001 From: Achim Adam Date: Fri, 4 Apr 2014 18:37:02 +0200 Subject: [PATCH 9/9] Dancer::Request unicode bugfix: new parameters left undecoded by params() make sure newly added parameters get _decode()d upon repeated internal calls to _build_params(). --- lib/Dancer/Request.pm | 3 +- t/02_request/19_decode_added_params.t | 50 +++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 t/02_request/19_decode_added_params.t diff --git a/lib/Dancer/Request.pm b/lib/Dancer/Request.pm index c6b58eb39..0b9bc2976 100644 --- a/lib/Dancer/Request.pm +++ b/lib/Dancer/Request.pm @@ -431,7 +431,8 @@ sub _build_params { %$previous, %{$self->{_query_params}}, %{$self->{_route_params}}, %{$self->{_body_params}}, }; - + # there may be new parameters in need of _decode()ing + $self->{_params_are_decoded} = 0; } # Written from PSGI specs: diff --git a/t/02_request/19_decode_added_params.t b/t/02_request/19_decode_added_params.t new file mode 100644 index 000000000..6c2b6d686 --- /dev/null +++ b/t/02_request/19_decode_added_params.t @@ -0,0 +1,50 @@ +use Dancer ':tests'; +use Dancer::Test; +use Test::More; +use Dancer::ModuleLoader; +use LWP::UserAgent; + +plan skip_all => "skip test with Test::TCP in win32" if $^O eq 'MSWin32'; +plan skip_all => 'Test::TCP is needed to run this test' + unless Dancer::ModuleLoader->load('Test::TCP' => "1.30"); + +plan skip_all => 'JSON is needed to run this test' + unless Dancer::ModuleLoader->load('JSON'); + +plan tests => 2; + +my $unicode = "\x{30dc}\x{a9}"; + +Test::TCP::test_tcp( + client => sub { + my $port = shift; + my $ua = LWP::UserAgent->new; + my $request = HTTP::Request->new( + POST => "http://127.0.0.1:$port/foo/$unicode", + [ + Host => 'localhost', + Content_Type => 'application/json', + ], + to_json({ foo => 'bar' }), + ); + my $res = $ua->request($request); + ok $res->is_success, 'Successful response from server'; + is $res->content, 1, 'Correct content'; + }, + server => sub { + my $port = shift; + use Dancer ':tests'; + set( + apphandler => 'Standalone', + port => $port, + show_errors => 0, + startup_info => 0, + charset => 'utf-8', + serializer => 'JSON', + ); + post '/foo/*' => sub { + params->{splat}[0] eq $unicode; + }; + Dancer->dance; + }, +);