The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
Changes 032
LICENSE 11
MANIFEST 05
META.json 919
META.yml 2332
Makefile.PL 510
lib/HTTP/Message/PSGI.pm 23
lib/Plack/App/File.pm 22
lib/Plack/App/WrapCGI.pm 22
lib/Plack/Handler/FCGI.pm 118
lib/Plack/Loader/Restarter.pm 33
lib/Plack/Middleware/ErrorDocument.pm 11
lib/Plack/Middleware/JSONP.pm 11
lib/Plack/Middleware/Lint.pm 11
lib/Plack/Middleware/Runtime.pm 12
lib/Plack/Middleware/SimpleLogger.pm 39
lib/Plack/Middleware/StackTrace.pm 223
lib/Plack/Middleware.pm 610
lib/Plack/Request.pm 1412
lib/Plack/Response.pm 11
lib/Plack/Test/Suite.pm 03
lib/Plack/Util.pm 12
lib/Plack.pm 11
t/HTTP-Message-PSGI/unknown_response.t 020
t/Plack-Loader/restarter.t 13
t/Plack-Middleware/cgi-bin/hello.cgi 11
t/Plack-Middleware/cgi-bin/hello2.cgi 11
t/Plack-Middleware/file.t 019
t/Plack-Middleware/jsonp.t 11
t/Plack-Middleware/stacktrace/multiple_exceptions.t 0123
t/Plack-Middleware/wrapcgi_exec.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
35 files changed (This is a version diff) 88408
@@ -1,5 +1,37 @@
 Go to http://github.com/plack/Plack/issues for the roadmap and known issues.
 
+1.0033  2014-10-23 12:33:18 PDT
+    [BUG FIXES]
+        - Lint: Fix error messages (fgabolde) #473
+        - ErrorDocument: Reverse the $done filtering (nwellnhof) #474
+
+    [IMPROVEMENTS]
+        - StackTrace: Improve the accuracy of thrown exceptions in case where an excpetion
+          is thrown in destructors (nwellnhof) #476
+
+1.0032  2014-10-04 11:13:24 PDT
+    [IMPROVEMENTS]
+        - Support Server::Starter in FCGI handler (yuryu) #435
+        - Various documentation fixes
+
+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.021.
 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
@@ -188,6 +190,7 @@ t/Plack-Middleware/simple_content_filter.t
 t/Plack-Middleware/simple_logger.t
 t/Plack-Middleware/stacktrace/basic.t
 t/Plack-Middleware/stacktrace/force.t
+t/Plack-Middleware/stacktrace/multiple_exceptions.t
 t/Plack-Middleware/stacktrace/sigdie.t
 t/Plack-Middleware/stacktrace/streaming.t
 t/Plack-Middleware/stacktrace/utf8.t
@@ -219,6 +222,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 +249,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.8, Dist::Zilla version 5.021, CPAN::Meta::Converter version 2.142690",
    "license" : [
       "perl_5"
    ],
@@ -26,12 +26,13 @@
    "prereqs" : {
       "configure" : {
          "requires" : {
-            "ExtUtils::MakeMaker" : "6.30",
-            "File::ShareDir::Install" : "0.03"
+            "ExtUtils::MakeMaker" : "0",
+            "File::ShareDir::Install" : "0.06"
          }
       },
       "develop" : {
          "requires" : {
+            "Dist::Milla" : "v1.0.8",
             "Test::Pod" : "1.41"
          }
       },
@@ -95,35 +96,43 @@
          "web" : "https://github.com/plack/Plack"
       }
    },
-   "version" : "1.0030",
+   "version" : "1.0033",
    "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>",
+      "Ashley Pond V <ashley.pond.v@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>",
       "Daisuke Maki <lestrrat+github@gmail.com>",
       "Daisuke Murase <typester@cpan.org>",
+      "Dave Marr <dave.marr@gmail.com>",
       "Dave Rolsky <autarch@urth.org>",
       "David E. Wheeler <david@justatheory.com>",
       "David Steinbrunner <dsteinbrunner@MountainBook-Pro.local>",
       "Eduardo Arino de la Rubia <earino@rent.com>",
+      "Eric Johnson <eric.git@iijo.org>",
+      "Fabrice Gabolde <fabrice.gabolde@gmail.com>",
       "Florian Ragwitz <rafl@debian.org>",
       "Graham Knop <haarg@haarg.org>",
       "Grant McLean <grant@catalyst.net.nz>",
       "HIROSE Masaaki <hirose31@gmail.com>",
       "Hans Dieter Pearcey <hdp@weftsoar.net>",
+      "Haruka Iwao <haruka@fout.jp>",
       "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 +158,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 +171,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 +200,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.8, Dist::Zilla version 5.021, CPAN::Meta::Converter version 2.142690'
 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.0033'
 x_authority: cpan:MIYAGAWA
 x_contributors:
   - 'Aaron Trevena <aaron.trevena@gmail.com>'
@@ -53,26 +53,34 @@ x_contributors:
   - 'Andrew Rodland <andrew@cleverdomain.org>'
   - 'Andy Wardley <abw@wardley.org>'
   - 'Aristotle Pagaltzis <pagaltzis@gmx.de>'
+  - "Arthur Axel 'fREW' Schmidt <frioux@gmail.com>"
+  - 'Ashley Pond V <ashley.pond.v@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>'
   - 'Daisuke Maki <lestrrat+github@gmail.com>'
   - 'Daisuke Murase <typester@cpan.org>'
+  - 'Dave Marr <dave.marr@gmail.com>'
   - 'Dave Rolsky <autarch@urth.org>'
   - 'David E. Wheeler <david@justatheory.com>'
   - 'David Steinbrunner <dsteinbrunner@MountainBook-Pro.local>'
   - 'Eduardo Arino de la Rubia <earino@rent.com>'
+  - 'Eric Johnson <eric.git@iijo.org>'
+  - 'Fabrice Gabolde <fabrice.gabolde@gmail.com>'
   - 'Florian Ragwitz <rafl@debian.org>'
   - 'Graham Knop <haarg@haarg.org>'
   - 'Grant McLean <grant@catalyst.net.nz>'
   - 'HIROSE Masaaki <hirose31@gmail.com>'
   - 'Hans Dieter Pearcey <hdp@weftsoar.net>'
+  - 'Haruka Iwao <haruka@fout.jp>'
   - '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 +119,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,28 +1,31 @@
 
+# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v5.021.
 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" => [
     "script/plackup"
   ],
   "LICENSE" => "perl",
+  "MIN_PERL_VERSION" => "5.008001",
   "NAME" => "Plack",
   "PREREQ_PM" => {
     "Apache::LogFormat::Compiler" => "0.12",
@@ -45,7 +48,7 @@ my %WriteMakefileArgs = (
     "Test::More" => "0.88",
     "Test::Requires" => 0
   },
-  "VERSION" => "1.0030",
+  "VERSION" => "1.0033",
   "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"
   }
@@ -56,7 +59,9 @@ my %FallbackPrereqs = (
   "Apache::LogFormat::Compiler" => "0.12",
   "Devel::StackTrace" => "1.23",
   "Devel::StackTrace::AsHTML" => "0.11",
+  "ExtUtils::MakeMaker" => 0,
   "File::ShareDir" => "1.00",
+  "File::ShareDir::Install" => "0.06",
   "Filesys::Notify::Simple" => 0,
   "HTTP::Body" => "1.06",
   "HTTP::Message" => "5.814",
@@ -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 {
@@ -143,7 +143,7 @@ Plack::App::File - Serve static files from root directory
   # Or map the path to a specific file
   use Plack::Builder;
   builder {
-      mount "/favicon.ico" => Plack::App::File->new(file => '/path/to/favicon.ico');
+      mount "/favicon.ico" => Plack::App::File->new(file => '/path/to/favicon.ico')->to_app;
   };
 
 =head1 DESCRIPTION
@@ -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);
@@ -30,10 +30,19 @@ sub new {
 sub run {
     my ($self, $app) = @_;
 
+    my $running_on_server_starter = exists $ENV{SERVER_STARTER_PORT};
     my $sock = 0;
     if (-S STDIN) {
         # running from web server. Do nothing
         # Note it should come before listen check because of plackup's default
+    } elsif ($running_on_server_starter) {
+        # Runing under Server::Starter
+        require Server::Starter;
+        my %socks = %{Server::Starter::server_ports()};
+        if (scalar(keys(%socks)) > 1) {
+            die "More than one socket are specified by Server::Starter";
+        }
+        $sock = (values %socks)[0];
     } elsif ($self->{listen}) {
         my $old_umask = umask;
         unless ($self->{leave_umask}) {
@@ -60,7 +69,7 @@ sub run {
 
     my $proc_manager;
 
-    if ($self->{listen}) {
+    if ($self->{listen} or $running_on_server_starter) {
         $self->daemon_fork if $self->{daemonize};
 
         if ($self->{manager}) {
@@ -409,6 +418,14 @@ mod_fcgid:
 
   FcgiPassHeader Authorization
 
+=head2 Server::Starter
+
+This plack handler supports L<Server::Starter> as a superdaemon.
+Simply launch plackup from start_server with a path option.
+The listen option is ignored when launched from Server::Starter.
+
+  start_server --path=/tmp/socket -- plackup -s FCGI app.psgi 
+
 =head1 SEE ALSO
 
 L<Plack>
@@ -56,9 +56,9 @@ sub valid_file {
 }
 
 sub run {
-    my($self, $server, $builder) = @_;
+    my($self, $server) = @_;
 
-    $self->_fork_and_start($server, $builder);
+    $self->_fork_and_start($server);
     return unless $self->{pid};
 
     require Filesys::Notify::Simple;
@@ -86,7 +86,7 @@ sub run {
 
         $self->_kill_child;
         warn "Successfully killed! Restarting the new server process.\n";
-        $self->_fork_and_start($server, $builder);
+        $self->_fork_and_start($server);
         return unless $self->{pid};
     }
 }
@@ -72,9 +72,9 @@ sub call {
                 my $done;
                 return sub {
                     unless ($done) {
+                        $done = 1;
                         return join '', <$fh>;
                     }
-                    $done = 1;
                     return defined $_[0] ? '' : undef;
                 };
             };
@@ -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');
@@ -58,7 +58,7 @@ sub validate_env {
         die('SERVER_PORT must not be empty string');
     }
     if (defined($env->{SERVER_PROTOCOL}) and $env->{SERVER_PROTOCOL} !~ m{^HTTP/1.\d$}) {
-        die("Invalid SERVER_PROTOCOL: $env->{SEREVR_PROTOCOL}");
+        die("Invalid SERVER_PROTOCOL: $env->{SERVER_PROTOCOL}");
     }
     for my $param (qw/version url_scheme input errors multithread multiprocess/) {
         unless (exists $env->{"psgi.$param"}) {
@@ -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;
 }
 
@@ -4,6 +4,7 @@ use warnings;
 use parent qw/Plack::Middleware/;
 use Devel::StackTrace;
 use Devel::StackTrace::AsHTML;
+use Scalar::Util qw( refaddr );
 use Try::Tiny;
 use Plack::Util::Accessor qw( force no_print_errors );
 
@@ -17,12 +18,18 @@ if (try { require Devel::StackTrace::WithLexicals; Devel::StackTrace::WithLexica
 sub call {
     my($self, $env) = @_;
 
-    my $trace;
+    my ($trace, %string_traces, %ref_traces);
     local $SIG{__DIE__} = sub {
         $trace = $StackTraceClass->new(
             indent => 1, message => munge_error($_[0], [ caller ]),
-            ignore_package => __PACKAGE__,
+            ignore_package => __PACKAGE__, no_refs => 1,
         );
+        if (ref $_[0]) {
+            $ref_traces{refaddr($_[0])} ||= $trace;
+        }
+        else {
+            $string_traces{$_[0]} ||= $trace;
+        }
         die @_;
     };
 
@@ -34,6 +41,20 @@ sub call {
         [ 500, [ "Content-Type", "text/plain; charset=utf-8" ], [ no_trace_error(utf8_safe($caught)) ] ];
     };
 
+    if ($caught) {
+        # Try to find the correct trace for the caught exception
+        my $caught_trace;
+        if (ref $caught) {
+            $caught_trace = $ref_traces{refaddr($caught)};
+        }
+        else {
+            # This is not guaranteed to work if multiple exceptions with
+            # the same message are thrown.
+            $caught_trace = $string_traces{$caught};
+        }
+        $trace = $caught_trace if $caught_trace;
+    }
+
     if ($trace && ($caught || ($self->force && ref $res eq 'ARRAY' && $res->[0] == 500)) ) {
         my $text = $trace->as_string;
         my $html = $trace->as_html;
@@ -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.0033';
 
 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.0033';
 
 use Plack::Util::Accessor qw(body status);
 use Carp ();
@@ -813,6 +813,9 @@ test script would look like:
 
   Plack::Test::Suite->run_server_tests('Foo');
 
+Developers writing Plack applications should look at C<Plack::Test> for testing,
+as subclassing C<Plack::Handler> is for developing server implementations.
+
 =head1 AUTHOR
 
 Tokuhiro Matsuno
@@ -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.0033';
 
 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;
@@ -5,7 +5,9 @@ use Test::Requires qw(LWP::UserAgent);
 use HTTP::Request::Common;
 use Plack::Loader::Restarter;
 
-plan skip_all => "release test only" unless $ENV{AUTHOR_TESTING};
+plan skip_all => "author test only" unless $ENV{AUTHOR_TESTING};
+
+$SIG{__WARN__} = sub { diag @_ };
 
 my @return_bodies = ('Hi first', 'Hi second', 'Hi third');
 my @restartertestfiles = ('t/restartertestfile1.pl', 't/restartertestfile2.pl');
@@ -1,4 +1,4 @@
 #!/usr/bin/perl
 use CGI;
 my $q = CGI->new;
-print $q->header, "Hello ", $q->param('name'), " counter=", ++$COUNTER;
+print $q->header, "Hello ", scalar $q->param('name'), " counter=", ++$COUNTER;
@@ -1,4 +1,4 @@
 #!/usr/bin/perl
 use CGI;
 my $q = CGI->new;
-print $q->header, "Hello ", $q->param('name'), " counter=", ++$COUNTER;
+print $q->header, "Hello ", scalar $q->param('name'), " counter=", ++$COUNTER;
@@ -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,123 @@
+use strict;
+use warnings;
+use Test::More;
+use Plack::Middleware::StackTrace;
+use Plack::Test;
+use HTTP::Request::Common;
+
+{
+    # Simple exception object
+    package Plack::Middleware::StackTrace::Exception;
+
+    use overload '""' => sub { $_[0]->{message} };
+
+    sub new {
+        my ($class, $message) = @_;
+        return bless { message => $message }, $class;
+    }
+}
+
+# Tracks how often the destructor was called
+my $dtor_count;
+
+{
+    # A class similar to DBIx::Class::Storage::TxnScopeGuard where the
+    # destructor might throw and catch another exception.
+    package Plack::Middleware::StackTrace::Guard;
+    use Try::Tiny;
+
+    sub new {
+        my ($class, $exception) = @_;
+        return bless { exception => $exception }, $class;
+    }
+
+    sub DESTROY {
+        my $self = shift;
+        ++$dtor_count;
+        try { die $self->{exception}; };
+    }
+}
+
+sub test_dtor_exception {
+    my ($orig_exception, $dtor_exception) = @_;
+
+    my $dtor_exception_app = sub {
+        my $guard = Plack::Middleware::StackTrace::Guard->new($dtor_exception);
+        die $orig_exception;
+    };
+
+    my $trace_app = Plack::Middleware::StackTrace->wrap($dtor_exception_app,
+        no_print_errors => 1,
+    );
+
+    test_psgi $trace_app, sub {
+        my $cb = shift;
+
+        $dtor_count = 0;
+        my $req = GET "/";
+        my $res = $cb->($req);
+
+        is $res->code, 500, "Status code is 500";
+        like $res->content, qr/^\Q$orig_exception\E at /,
+             "Original exception returned";
+        is $dtor_count, 1, "Destructor called only once";
+    };
+}
+
+test_dtor_exception("urz", "orz");
+test_dtor_exception(
+    Plack::Middleware::StackTrace::Exception->new("urz"),
+    Plack::Middleware::StackTrace::Exception->new("orz"),
+);
+
+{
+    # A middleware that rethrows exceptions
+    package Plack::Middleware::StackTrace::Rethrow;
+    use parent qw(Plack::Middleware);
+    use Try::Tiny;
+
+    sub call {
+        my ($self, $env) = @_;
+        try {
+            $self->app->($env);
+        } catch {
+            die $_;
+        };
+    }
+}
+
+# This sub is expected to appear in the stack trace.
+sub fizzle {
+    my $exception = shift;
+    die $exception;
+}
+
+sub test_rethrown_exception {
+    my $exception = shift;
+
+    my $die_app = sub {
+        fizzle($exception);
+    };
+
+    my $rethrow_app = Plack::Middleware::StackTrace::Rethrow->wrap($die_app);
+
+    my $trace_app = Plack::Middleware::StackTrace->wrap($rethrow_app,
+        no_print_errors => 1,
+    );
+
+    test_psgi $trace_app, sub {
+        my $cb = shift;
+
+        my $req = GET "/";
+        my $res = $cb->($req);
+
+        is $res->code, 500, "Status code is 500";
+        like $res->content, qr/\bfizzle\b/, "Original stack trace returned";
+    };
+}
+
+test_rethrown_exception("orz");
+test_rethrown_exception(Plack::Middleware::StackTrace::Exception->new("orz"));
+
+done_testing;
+
@@ -15,7 +15,7 @@ plan skip_all => $^O if $^O eq "MSWin32";
 #!$^X
 use CGI;
 my \$q = CGI->new;
-print \$q->header, "Hello ", \$q->param('name'), " counter=", ++\$COUNTER;
+print \$q->header, "Hello ", scalar \$q->param('name'), " counter=", ++\$COUNTER;
 ...
     close $tmp;
 
@@ -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();