The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
Changes 018
LICENSE 11
MANIFEST 04
META.json 913
META.yml 2327
Makefile.PL 57
lib/HTTP/Message/PSGI.pm 23
lib/Plack/App/File.pm 11
lib/Plack/App/WrapCGI.pm 22
lib/Plack/Middleware/JSONP.pm 11
lib/Plack/Middleware/Runtime.pm 12
lib/Plack/Middleware/SimpleLogger.pm 39
lib/Plack/Middleware.pm 610
lib/Plack/Request.pm 1412
lib/Plack/Response.pm 11
lib/Plack/Util.pm 12
lib/Plack.pm 11
t/HTTP-Message-PSGI/unknown_response.t 020
t/Plack-Middleware/file.t 019
t/Plack-Middleware/jsonp.t 11
t/Plack-Request/query_string.t 022
t/Plack-Request/uri.t 07
t/Plack-Util/can.t 015
t/release-pod-syntax.t 32
24 files changed (This is a version diff) 75200
@@ -1,5 +1,23 @@
 Go to http://github.com/plack/Plack/issues for the roadmap and known issues.
 
+1.0031  2014-08-01 13:19:14 PDT
+    [SECURITY]
+        - Plack::App::File would previously strip trailing slashes off
+          provided paths. This in combination with the common pattern
+          of serving files with Plack::Middleware::Static could allow
+          an attacker to bypass a whitelist of generated files (avar) #446
+
+    [IMPROVEMENTS]
+        - Let HTTP::Message::PSGI warn in case of invalid PSGI response (wchristian) #437
+        - Update documentation on how response_cb works with writer (doy)
+        - Make AccessLog work on non-POSIX environment (dex4er) #442
+        - Plack::App::WrapCGI no longer warns under 5.19.9 (frew)
+        - Avoid Rosetta Flash attack in JSONP middleware (nichtich) #464
+        - Fix Plack::Util::inline_object to make it work with can() as a class method
+
+    [NEW FEATURES]
+        - Add $req->query_string shortcut to access QUERY_STRING in PSGI environment
+
 1.0030  2013-11-23 08:54:01 CET
     [IMPROVEMENTS]
         - Middleware::LogDispatch stringifies objects (oalders) #427
@@ -22,7 +22,7 @@ This is free software, licensed under:
                      Version 1, February 1989
 
  Copyright (C) 1989 Free Software Foundation, Inc.
- 51 Franklin St, Suite 500, Boston, MA  02110-1335  USA
+ 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.
@@ -1,3 +1,4 @@
+# This file was automatically generated by Dist::Zilla::Plugin::Manifest v5.020.
 Changes
 LICENSE
 MANIFEST
@@ -106,6 +107,7 @@ t/HTTP-Message-PSGI/content_length.t
 t/HTTP-Message-PSGI/empty_streamed_response.t
 t/HTTP-Message-PSGI/host.t
 t/HTTP-Message-PSGI/path_info.t
+t/HTTP-Message-PSGI/unknown_response.t
 t/HTTP-Message-PSGI/utf8_req.t
 t/HTTP-Server-PSGI/harakiri.t
 t/HTTP-Server-PSGI/post.t
@@ -219,6 +221,7 @@ t/Plack-Request/parameters.t
 t/Plack-Request/params.t
 t/Plack-Request/path_info.t
 t/Plack-Request/path_info_escaped.t
+t/Plack-Request/query_string.t
 t/Plack-Request/readbody.t
 t/Plack-Request/request_uri.t
 t/Plack-Request/upload-basename.t
@@ -245,6 +248,7 @@ t/Plack-Util/Hello.pm
 t/Plack-Util/bad.psgi
 t/Plack-Util/bad2.psgi
 t/Plack-Util/bin/findbin.psgi
+t/Plack-Util/can.t
 t/Plack-Util/error.psgi
 t/Plack-Util/foreach.t
 t/Plack-Util/headers.t
@@ -4,7 +4,7 @@
       "Tatsuhiko Miyagawa"
    ],
    "dynamic_config" : 0,
-   "generated_by" : "Dist::Milla version v1.0.4, Dist::Zilla version 5.006, CPAN::Meta::Converter version 2.132830",
+   "generated_by" : "Dist::Milla version v1.0.5, Dist::Zilla version 5.020, CPAN::Meta::Converter version 2.142060",
    "license" : [
       "perl_5"
    ],
@@ -26,8 +26,8 @@
    "prereqs" : {
       "configure" : {
          "requires" : {
-            "ExtUtils::MakeMaker" : "6.30",
-            "File::ShareDir::Install" : "0.03"
+            "ExtUtils::MakeMaker" : "0",
+            "File::ShareDir::Install" : "0.06"
          }
       },
       "develop" : {
@@ -95,18 +95,20 @@
          "web" : "https://github.com/plack/Plack"
       }
    },
-   "version" : "1.0030",
+   "version" : "1.0031",
    "x_authority" : "cpan:MIYAGAWA",
    "x_contributors" : [
       "Aaron Trevena <aaron.trevena@gmail.com>",
-      "Alex J. G. Burzy\u0144ski <ajgb@cpan.org>",
+      "Alex J. G. Burzyński <ajgb@cpan.org>",
       "Alexandr Ciornii <alexchorny@gmail.com>",
       "Andrew Rodland <andrew@cleverdomain.org>",
       "Andy Wardley <abw@wardley.org>",
       "Aristotle Pagaltzis <pagaltzis@gmx.de>",
-      "Ask Bj\u00f8rn Hansen <ask@develooper.com>",
+      "Arthur Axel 'fREW' Schmidt <frioux@gmail.com>",
+      "Ask Bjørn Hansen <ask@develooper.com>",
       "Ben Morrow <ben@morrow.me.uk>",
       "Bernhard Graf <augensalat@gmail.com>",
+      "Chad Granum <chad.granum@dreamhost.com>",
       "Chia-liang Kao <clkao@clkao.org>",
       "Christian Walde <walde.christian@googlemail.com>",
       "Cosimo Streppone <cosimo@cpan.org>",
@@ -124,6 +126,7 @@
       "Henry Baragar <Henry.Baragar@Instantiated.Ca>",
       "Hiroshi Sakai <ziguzagu@gmail.com>",
       "Jakob Voss <jakob@nichtich.de>",
+      "Jakob Voss <voss@gbv.de>",
       "Jay Hannah <jay.hannah@iinteractive.com>",
       "Jesse Luehrs <doy@cpan.org>",
       "Jiro Nishiguchi <jiro@cpan.org>",
@@ -149,7 +152,7 @@
       "Olaf Alders <olaf@wundersolutions.com>",
       "Oliver Gorwits <oliver@cpan.org>",
       "Oliver Paukstadt <pstadt@sourcentral.org>",
-      "Olivier Mengu\u00e9 <dolmen@cpan.org>",
+      "Olivier Mengué <dolmen@cpan.org>",
       "Panu Ervamaa <panu.ervamaa@frantic.com>",
       "Paul Driver <frodwith@gmail.com>",
       "Pedro Melo <melo@simplicidade.org>",
@@ -162,6 +165,7 @@
       "Ricky Morse <remorse@partners.org>",
       "Rob Hoelz <rob@hoelz.ro>",
       "Ryo Miyake <ryo.studiom@gmail.com>",
+      "Sawyer X <xsawyerx@cpan.org>",
       "Scott S. McCoy <smccoy@saymedia.com>",
       "Shawn M Moore <sartak@gmail.com>",
       "Stephen Clouse <stephenclouse@gmail.com>",
@@ -190,8 +194,8 @@
       "vti <viacheslav.t@gmail.com>",
       "xaicron <xaicron@gmail.com>",
       "yappo <yappo@shibuya.pl>",
-      "\u00c6var Arnfj\u00f6r\u00f0 Bjarmason <avarab@gmail.com>",
-      "\u5510\u9cf3 <audreyt@audreyt.org>"
+      "Ævar Arnfjörð Bjarmason <avarab@gmail.com>",
+      "唐鳳 <audreyt@audreyt.org>"
    ]
 }
 
@@ -3,17 +3,17 @@ abstract: 'Perl Superglue for Web frameworks and Web Servers (PSGI toolkit)'
 author:
   - 'Tatsuhiko Miyagawa'
 build_requires:
-  Test::More: 0.88
-  Test::Requires: 0
+  Test::More: '0.88'
+  Test::Requires: '0'
 configure_requires:
-  ExtUtils::MakeMaker: 6.30
-  File::ShareDir::Install: 0.03
+  ExtUtils::MakeMaker: '0'
+  File::ShareDir::Install: '0.06'
 dynamic_config: 0
-generated_by: 'Dist::Milla version v1.0.4, Dist::Zilla version 5.006, CPAN::Meta::Converter version 2.132830'
+generated_by: 'Dist::Milla version v1.0.5, Dist::Zilla version 5.020, CPAN::Meta::Converter version 2.142060'
 license: perl
 meta-spec:
   url: http://module-build.sourceforge.net/META-spec-v1.4.html
-  version: 1.4
+  version: '1.4'
 name: Plack
 no_index:
   directory:
@@ -24,27 +24,27 @@ no_index:
     - eg
     - examples
 requires:
-  Apache::LogFormat::Compiler: 0.12
-  Devel::StackTrace: 1.23
-  Devel::StackTrace::AsHTML: 0.11
-  File::ShareDir: 1.00
-  Filesys::Notify::Simple: 0
-  HTTP::Body: 1.06
-  HTTP::Message: 5.814
-  HTTP::Tiny: 0.034
-  Hash::MultiValue: 0.05
-  Pod::Usage: 1.36
-  Stream::Buffered: 0.02
-  Test::TCP: 2.00
-  Try::Tiny: 0
-  URI: 1.59
-  parent: 0
-  perl: 5.008001
+  Apache::LogFormat::Compiler: '0.12'
+  Devel::StackTrace: '1.23'
+  Devel::StackTrace::AsHTML: '0.11'
+  File::ShareDir: '1.00'
+  Filesys::Notify::Simple: '0'
+  HTTP::Body: '1.06'
+  HTTP::Message: '5.814'
+  HTTP::Tiny: '0.034'
+  Hash::MultiValue: '0.05'
+  Pod::Usage: '1.36'
+  Stream::Buffered: '0.02'
+  Test::TCP: '2.00'
+  Try::Tiny: '0'
+  URI: '1.59'
+  parent: '0'
+  perl: '5.008001'
 resources:
   bugtracker: https://github.com/plack/Plack/issues
   homepage: https://github.com/plack/Plack
   repository: https://github.com/plack/Plack.git
-version: 1.0030
+version: '1.0031'
 x_authority: cpan:MIYAGAWA
 x_contributors:
   - 'Aaron Trevena <aaron.trevena@gmail.com>'
@@ -53,9 +53,11 @@ x_contributors:
   - 'Andrew Rodland <andrew@cleverdomain.org>'
   - 'Andy Wardley <abw@wardley.org>'
   - 'Aristotle Pagaltzis <pagaltzis@gmx.de>'
+  - "Arthur Axel 'fREW' Schmidt <frioux@gmail.com>"
   - 'Ask Bjørn Hansen <ask@develooper.com>'
   - 'Ben Morrow <ben@morrow.me.uk>'
   - 'Bernhard Graf <augensalat@gmail.com>'
+  - 'Chad Granum <chad.granum@dreamhost.com>'
   - 'Chia-liang Kao <clkao@clkao.org>'
   - 'Christian Walde <walde.christian@googlemail.com>'
   - 'Cosimo Streppone <cosimo@cpan.org>'
@@ -73,6 +75,7 @@ x_contributors:
   - 'Henry Baragar <Henry.Baragar@Instantiated.Ca>'
   - 'Hiroshi Sakai <ziguzagu@gmail.com>'
   - 'Jakob Voss <jakob@nichtich.de>'
+  - 'Jakob Voss <voss@gbv.de>'
   - 'Jay Hannah <jay.hannah@iinteractive.com>'
   - 'Jesse Luehrs <doy@cpan.org>'
   - 'Jiro Nishiguchi <jiro@cpan.org>'
@@ -111,6 +114,7 @@ x_contributors:
   - 'Ricky Morse <remorse@partners.org>'
   - 'Rob Hoelz <rob@hoelz.ro>'
   - 'Ryo Miyake <ryo.studiom@gmail.com>'
+  - 'Sawyer X <xsawyerx@cpan.org>'
   - 'Scott S. McCoy <smccoy@saymedia.com>'
   - 'Shawn M Moore <sartak@gmail.com>'
   - 'Stephen Clouse <stephenclouse@gmail.com>'
@@ -1,22 +1,24 @@
 
+# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v5.020.
 use strict;
 use warnings;
 
 use 5.008001;
 
-use ExtUtils::MakeMaker 6.30;
+use ExtUtils::MakeMaker ;
 
 use File::ShareDir::Install;
+$File::ShareDir::Install::INCLUDE_DOTFILES = 1;
+$File::ShareDir::Install::INCLUDE_DOTDIRS = 1;
 install_share dist => "share";
 
 
 my %WriteMakefileArgs = (
   "ABSTRACT" => "Perl Superglue for Web frameworks and Web Servers (PSGI toolkit)",
   "AUTHOR" => "Tatsuhiko Miyagawa",
-  "BUILD_REQUIRES" => {},
   "CONFIGURE_REQUIRES" => {
-    "ExtUtils::MakeMaker" => "6.30",
-    "File::ShareDir::Install" => "0.03"
+    "ExtUtils::MakeMaker" => 0,
+    "File::ShareDir::Install" => "0.06"
   },
   "DISTNAME" => "Plack",
   "EXE_FILES" => [
@@ -45,7 +47,7 @@ my %WriteMakefileArgs = (
     "Test::More" => "0.88",
     "Test::Requires" => 0
   },
-  "VERSION" => "1.0030",
+  "VERSION" => "1.0031",
   "test" => {
     "TESTS" => "t/*.t t/HTTP-Message-PSGI/*.t t/HTTP-Server-PSGI/*.t t/Plack-Builder/*.t t/Plack-HTTPParser-PP/*.t t/Plack-Handler/*.t t/Plack-Loader/*.t t/Plack-MIME/*.t t/Plack-Middleware/*.t t/Plack-Middleware/cascade/*.t t/Plack-Middleware/recursive/*.t t/Plack-Middleware/stacktrace/*.t t/Plack-Request/*.t t/Plack-Response/*.t t/Plack-Runner/*.t t/Plack-TempBuffer/*.t t/Plack-Test/*.t t/Plack-Util/*.t"
   }
@@ -99,11 +99,12 @@ sub res_from_psgi {
     my $res;
     if (ref $psgi_res eq 'ARRAY') {
         _res_from_psgi($psgi_res, \$res);
-    }
-    elsif (ref $psgi_res eq 'CODE') {
+    } elsif (ref $psgi_res eq 'CODE') {
         $psgi_res->(sub {
             _res_from_psgi($_[0], \$res);
         });
+    } else {
+        Carp::croak("Bad response: ", defined $psgi_res ? $psgi_res : 'undef');
     }
 
     return $res;
@@ -44,7 +44,7 @@ sub locate_file {
     }
 
     my $docroot = $self->root || ".";
-    my @path = split /[\\\/]/, $path;
+    my @path = split /[\\\/]/, $path, -1; # -1 *MUST* be here to avoid security issues!
     if (@path) {
         shift @path if $path[0] eq '';
     } else {
@@ -63,9 +63,9 @@ sub prepare_app {
 
             my $res = '';
             while (waitpid($pid, WNOHANG) <= 0) {
-                $res .= do { local $/; <$stdoutr> };
+                $res .= do { local $/; <$stdoutr> } || '';
             }
-            $res .= do { local $/; <$stdoutr> };
+            $res .= do { local $/; <$stdoutr> } || '';
 
             if (POSIX::WIFEXITED($?)) {
                 return CGI::Parse::PSGI::parse_cgi_output(\$res);
@@ -27,7 +27,7 @@ sub call {
                 if ($cb =~ /^[\w\.\[\]]+$/) {
                     my $body;
                     Plack::Util::foreach($res->[2], sub { $body .= $_[0] });
-                    my $jsonp = "$cb($body)";
+                    my $jsonp = "/**/$cb($body)";
                     $res->[2] = [ $jsonp ];
                     $h->set('Content-Length', length $jsonp);
                     $h->set('Content-Type', 'text/javascript');
@@ -10,11 +10,12 @@ sub call {
 
     my $start = [ Time::HiRes::gettimeofday ];
     my $res = $self->app->($env);
+    my $header = $self->header_name || 'X-Runtime';
 
     $self->response_cb($res, sub {
         my $res = shift;
         my $req_time = sprintf '%.6f', Time::HiRes::tv_interval($start);
-        Plack::Util::header_set($res->[1], 'X-Runtime', $req_time);
+        Plack::Util::header_set($res->[1], $header, $req_time);
     });
 }
 
@@ -1,6 +1,7 @@
 package Plack::Middleware::SimpleLogger;
 use strict;
 use parent qw(Plack::Middleware);
+use Config ();
 use Plack::Util::Accessor qw(level);
 use POSIX ();
 use Scalar::Util ();
@@ -29,10 +30,15 @@ sub call {
 }
 
 sub format_time {
-    my $old_locale = POSIX::setlocale(&POSIX::LC_ALL);
-    POSIX::setlocale(&POSIX::LC_ALL, 'C');
+    my $old_locale;
+    if ( $Config::config{d_setlocale} ) {
+        $old_locale = POSIX::setlocale(&POSIX::LC_ALL);
+        POSIX::setlocale(&POSIX::LC_ALL, 'C');
+    }
     my $out = POSIX::strftime(@_);
-    POSIX::setlocale(&POSIX::LC_ALL, $old_locale);
+    if ( $Config::config{d_setlocale} ) {
+        POSIX::setlocale(&POSIX::LC_ALL, $old_locale);
+    };
     return $out;
 }
 
@@ -106,8 +106,11 @@ middleware.
       # do something with $res;
   });
 
-The callback function gets a PSGI response as a 3 element array
-reference, and you can update the reference to implement the post-processing.
+The callback function gets a response as an array reference, and you can
+update the reference to implement the post-processing. In the normal
+case, this arrayref will have three elements (as described by the PSGI
+spec), but will have only two elements when using a C<$writer> as
+described below.
 
   package Plack::Middleware::Always500;
   use parent qw(Plack::Middleware);
@@ -151,12 +154,13 @@ do:
       return;
   });
 
-The third element of the PSGI response array ref is a body, and it could
+The third element of the response array ref is a body, and it could
 be either an arrayref or L<IO::Handle>-ish object. The application could
 also make use of the C<$writer> object if C<psgi.streaming> is in
-effect. Dealing with these variants is again really painful, and
-C<response_cb> can take care of that too, by allowing you to return a
-content filter as a code reference.
+effect, and in this case, the third element will not exist
+(C<@$res == 2>). Dealing with these variants is again really painful,
+and C<response_cb> can take care of that too, by allowing you to return
+a content filter as a code reference.
 
   # replace all "Foo" in content body with "Bar"
   Plack::Util::response_cb($res, sub {
@@ -2,7 +2,7 @@ package Plack::Request;
 use strict;
 use warnings;
 use 5.008_001;
-our $VERSION = '1.0030';
+our $VERSION = '1.0031';
 
 use HTTP::Headers;
 use Carp ();
@@ -33,6 +33,7 @@ sub user        { $_[0]->env->{REMOTE_USER} }
 sub request_uri { $_[0]->env->{REQUEST_URI} }
 sub path_info   { $_[0]->env->{PATH_INFO} }
 sub path        { $_[0]->env->{PATH_INFO} || '/' }
+sub query_string{ $_[0]->env->{QUERY_STRING} }
 sub script_name { $_[0]->env->{SCRIPT_NAME} }
 sub scheme      { $_[0]->env->{'psgi.url_scheme'} }
 sub secure      { $_[0]->scheme eq 'https' }
@@ -85,18 +86,10 @@ sub _parse_query {
     my @query;
     my $query_string = $self->env->{QUERY_STRING};
     if (defined $query_string) {
-        if ($query_string =~ /=/) {
-            # Handle  ?foo=bar&bar=foo type of query
-            @query =
-                map { s/\+/ /g; URI::Escape::uri_unescape($_) }
-                map { /=/ ? split(/=/, $_, 2) : ($_ => '')}
-                split(/[&;]/, $query_string);
-        } else {
-            # Handle ...?dog+bones type of query
-            @query =
-                map { (URI::Escape::uri_unescape($_), '') }
-                split(/\+/, $query_string, -1);
-        }
+        @query =
+            map { s/\+/ /g; URI::Escape::uri_unescape($_) }
+            map { /=/ ? split(/=/, $_, 2) : ($_ => '')}
+            split(/[&;]/, $query_string);
     }
 
     Hash::MultiValue->new(@query);
@@ -417,6 +410,11 @@ Similar to C<path_info> but returns C</> in case it is empty. In other
 words, it returns the virtual path of the request URI after C<<
 $req->base >>. See L</"DISPATCHING"> for details.
 
+=item query_string
+
+Returns B<QUERY_STRING> in the environment. This is the undecoded
+query string in the request URI.
+
 =item script_name
 
 Returns B<SCRIPT_NAME> in the environment. This is the absolute path
@@ -459,7 +457,7 @@ strings that are sent by clients and are URI decoded.
 If there are multiple cookies with the same name in the request, this
 method will ignore the duplicates and return only the first value. If
 that causes issues for you, you may have to use modules like
-CGI::Simple::Cookie to parse C<$request->header('Cookies')> by
+CGI::Simple::Cookie to parse C<<$request->header('Cookies')>> by
 yourself.
 
 =item query_parameters
@@ -1,7 +1,7 @@
 package Plack::Response;
 use strict;
 use warnings;
-our $VERSION = '1.0030';
+our $VERSION = '1.0031';
 
 use Plack::Util::Accessor qw(body status);
 use Carp ();
@@ -309,7 +309,8 @@ package Plack::Util::Prototype;
 
 our $AUTOLOAD;
 sub can {
-    $_[0]->{$_[1]};
+    return $_[0]->{$_[1]} if Scalar::Util::blessed($_[0]);
+    goto &UNIVERSAL::can;
 }
 
 sub AUTOLOAD {
@@ -3,7 +3,7 @@ package Plack;
 use strict;
 use warnings;
 use 5.008_001;
-our $VERSION = '1.0030';
+our $VERSION = '1.0031';
 
 1;
 __END__
@@ -0,0 +1,20 @@
+use strict;
+use warnings;
+use Test::More;
+use HTTP::Message::PSGI;
+use HTTP::Request;
+use HTTP::Response;
+
+my $res;
+my $app = sub { $res };
+my $env = req_to_psgi(HTTP::Request->new(GET => "http://localhost/"));
+
+eval { HTTP::Response->from_psgi($app->($env)) };
+like($@, qr/Bad response: undef/, 'converting undef PSGI response results in error');
+
+$res = 5;
+
+eval { HTTP::Response->from_psgi($app->($env)) };
+like($@, qr/Bad response: 5/, 'converting invalid PSGI response results in error');
+
+done_testing;
@@ -3,6 +3,7 @@ use Plack::Test;
 use Test::More;
 use HTTP::Request::Common;
 use Plack::App::File;
+use FindBin qw($Bin);
 
 my $app = Plack::App::File->new(file => 'Changes');
 
@@ -35,6 +36,24 @@ test_psgi $app_content_type, sub {
     is $res->code, 200;
 };
 
+my $app_secure = Plack::App::File->new(root => $Bin);
 
+test_psgi $app_secure, sub {
+    my $cb = shift;
+
+    my $res = $cb->(GET "/file.t");
+    is $res->code, 200;
+    like $res->content, qr/We will find for this literal string/;
+
+    my $res = $cb->(GET "/../Plack-Middleware/file.t");
+    is $res->code, 403;
+    is $res->content, 'forbidden';
+
+    for my $i (1..100) {
+        $res = $cb->(GET "/file.t" . ("/" x $i));
+        is $res->code, 404;
+        is $res->content, 'not found';
+    }
+};
 
 done_testing;
@@ -50,7 +50,7 @@ for my $test ( @tests ) {
         is $res->content, $json;
         $res = $cb->(HTTP::Request->new(GET => 'http://localhost/?'.$callback_key.'=foo'));
         is $res->content_type, 'text/javascript';
-        is $res->content, "foo($json)";
+        is $res->content, "/**/foo($json)";
     };
 }
 
@@ -0,0 +1,22 @@
+use strict;
+use Test::More;
+use Plack::Request;
+use Plack::Test;
+use HTTP::Request::Common;
+
+my $app = sub {
+    my $req = Plack::Request->new(shift);
+    return [ 200, [], [ $req->query_string ] ];
+};
+
+test_psgi $app, sub {
+    my $cb = shift;
+
+    my $res = $cb->(GET "http://localhost/?foo=bar");
+    is $res->content, 'foo=bar';
+
+    $res = $cb->(GET "http://localhost/?foo+bar");
+    is $res->content, 'foo+bar';
+};
+
+done_testing;
@@ -82,6 +82,13 @@ my @tests = (
     { add_env => {
         HTTP_HOST => 'example.com',
         SCRIPT_NAME => "",
+        QUERY_STRING => "foo+bar"
+      },
+      uri => 'http://example.com/?foo+bar',
+      parameters => { 'foo bar' => '' } },
+    { add_env => {
+        HTTP_HOST => 'example.com',
+        SCRIPT_NAME => "",
         QUERY_STRING => 0
       },
       uri => 'http://example.com/?0',
@@ -0,0 +1,15 @@
+use strict;
+use warnings;
+use Test::More;
+use Plack::Util;
+
+my $can;
+my $lives = eval { $can = Plack::Util->can('something_obviously_fake'); 1 };
+ok($lives, "Did not die calling 'can' on Plack::Util package with invalid sub");
+is($can, undef, "Cannot do that method");
+
+$lives = eval { $can = Plack::Util->can('content_length'); 1 };
+ok($lives, "Did not die calling 'can' on Plack::Util package with real sub");
+is($can, \&Plack::Util::content_length, "can() returns the sub");
+
+done_testing;
@@ -7,9 +7,8 @@ BEGIN {
   }
 }
 
+# This file was automatically generated by Dist::Zilla::Plugin::PodSyntaxTests.
 use Test::More;
-
-eval "use Test::Pod 1.41";
-plan skip_all => "Test::Pod 1.41 required for testing POD" if $@;
+use Test::Pod 1.41;
 
 all_pod_files_ok();