merged
John Napiorkowski [Thu, 29 Oct 2015 15:13:48 +0000 (10:13 -0500)]
74 files changed:
.travis.yml
Changes
MANIFEST.SKIP
Makefile.PL
README.mkdn [new file with mode: 0644]
README.pod [deleted file]
TODO [deleted file]
lib/Catalyst.pm
lib/Catalyst/Action.pm
lib/Catalyst/ActionChain.pm
lib/Catalyst/ActionRole/HTTPMethods.pm
lib/Catalyst/ActionRole/QueryMatching.pm [new file with mode: 0644]
lib/Catalyst/ActionRole/Scheme.pm [new file with mode: 0644]
lib/Catalyst/Component.pm
lib/Catalyst/Contributing.pod [new file with mode: 0644]
lib/Catalyst/Controller.pm
lib/Catalyst/Delta.pod
lib/Catalyst/DispatchType/Chained.pm
lib/Catalyst/DispatchType/Path.pm
lib/Catalyst/Dispatcher.pm
lib/Catalyst/Engine.pm
lib/Catalyst/Log.pm
lib/Catalyst/Middleware/Stash.pm
lib/Catalyst/ROADMAP.pod [deleted file]
lib/Catalyst/Request.pm
lib/Catalyst/Request/PartData.pm [new file with mode: 0644]
lib/Catalyst/Request/Upload.pm
lib/Catalyst/Response.pm
lib/Catalyst/Response/Writer.pm [new file with mode: 0644]
lib/Catalyst/RouteMatching.pod [new file with mode: 0644]
lib/Catalyst/Runtime.pm
lib/Catalyst/UTF8.pod [new file with mode: 0644]
lib/Catalyst/Upgrading.pod
lib/Catalyst/Utils.pm
t/accept_context_regression.t [new file with mode: 0644]
t/aggregate/live_component_controller_httpmethods.t
t/aggregate/to_app.t [new file with mode: 0644]
t/aggregate/unit_core_uri_for.t
t/aggregate/unit_core_uri_for_multibytechar.t
t/aggregate/utf8_content_length.t
t/arg_constraints.t [new file with mode: 0644]
t/args0_bug.t [new file with mode: 0644]
t/author/spelling.t
t/body_fh.t
t/class_traits.t [new file with mode: 0644]
t/configured_comps.t [new file with mode: 0644]
t/consumes.t [new file with mode: 0644]
t/dead_load_bad_args.t
t/dispatch_on_scheme.t [new file with mode: 0644]
t/http_exceptions.t
t/http_exceptions_backcompat.t [new file with mode: 0644]
t/inject_component_util.t [new file with mode: 0644]
t/lib/Test/Apple.pm [new file with mode: 0644]
t/lib/TestApp/Controller/HTTPMethods.pm
t/lib/TestAppEncoding/Controller/Root.pm
t/lib/TestAppUnicode.pm
t/live_component_controller_context_closure.t
t/middleware-stash.t [new file with mode: 0644]
t/no_test_stash_bug.t [new file with mode: 0644]
t/not_utf8_query_bug.t [new file with mode: 0644]
t/plack-middleware.t
t/psgi-log.t
t/psgi_utils.t
t/query_constraints.t [new file with mode: 0644]
t/set_allowed_method.t [new file with mode: 0644]
t/undef-params.t
t/unicode_plugin_charset_utf8.t
t/unicode_plugin_config.t
t/unicode_plugin_live.t
t/unicode_plugin_no_encoding.t
t/unicode_plugin_request_decode.t
t/unit_utils_load_class.t
t/utf8.txt [new file with mode: 0644]
t/utf_incoming.t [new file with mode: 0644]

index a239757..4db74ef 100644 (file)
@@ -1,4 +1,5 @@
 language: perl
+sudo: false
 perl:
    - "5.20"
    - "5.18"
@@ -17,7 +18,7 @@ install:
 
    # author deps -- wish there was a better way
    - cpanm --notest --metacpan --skip-satisfied CatalystX::LeakChecker Catalyst::Devel Catalyst::Engine::PSGI Starman MooseX::Daemonize Test::WWW::Mechanize::Catalyst Catalyst::Plugin::Params::Nested
-   - cpanm --notest --metacpan --skip-satisfied Test::Without::Module Test::NoTabs Test::Pod Test::Pod::Coverage Test::Spelling Pod::Coverage::TrustPod
+   - cpanm --notest --metacpan --skip-satisfied Test::Without::Module Test::NoTabs Test::Pod Test::Pod::Coverage Test::Spelling Pod::Coverage::TrustPod Type::Tiny
    - cpanm --notest --metacpan --skip-satisfied --installdeps .
    - echo y | perl Makefile.PL
 
diff --git a/Changes b/Changes
index 5c246ec..644e0f5 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,5 +1,343 @@
 # This file documents the revision history for Perl extension Catalyst.
 
+5.90102 - 2015-10-29
+  - Better warnings when there's an error reading the psgi.input (billmosley++)
+  - Fixed spurious warnings in uri_for when using no arguments (melmothx++ and 
+    paultcochrane++)
+  - Documentation improvements (paultcochrane++)
+  - Improvements to 'search_extra' configuration and tests around using 
+    uri_for as a class method (cngarrison++)
+
+5.90101 - 2015-09-04
+  - Fixed a regression introduced in the last release which caused test
+    case failure when using a version of Perl 5.14 or older.
+
+5.90100 - 2015-08-24
+  - Document using namespace::autoclean with controllers that have actions
+    with type constraints.
+  - Look for type constraints in super classes and consumed roles.
+  - Change the way the stash middleware works to no longer localize $psgi_env.
+  - If you delegate control to a sub Catalyst application, that application
+    may now return information to the parent application via the stash.
+  - Fix for RT#106373 (Issue when you try to install and also have an old
+    version of Test::Mechanize::WWW::Catalyst)
+
+5.90097 - 2015-07-28
+  - $c->uri_for now defines a final argument for setting the URL fragment
+  /URL anchor.  This is now the canonical approach to setting a fragment
+  via uri_for.
+  - Reverted how we treat $c->uri_for($path) where $path is a string.  When
+    we introduced the UTF-8 work we started encoding stringy paths, which
+    breaks code that did not expect that.  We now consider stringy $path to
+    be 'expert' mode and you are expected to perform all nessary encoding.
+
+5.90096 - 2015-07-27
+  - Fixed regression introduced in previous release that prevented a URI
+    fragment from getting properly encoded.  Added more tests around this
+    to define behavior better.
+
+5.90095 - 2015-07-27
+  - Minor test case tweak that I hope solve some minor hiesenfails reported
+    on CPAN testers.
+  - (https://github.com/perl-catalyst/catalyst-runtime/pull/109) added som
+    additional directions to how to setup a development sandbox
+  - (https://github.com/perl-catalyst/catalyst-runtime/pull/108) fix bug in
+    encoding where URI fragment seperator '#' in ->uri_for would get encoded.
+
+5.90094 - 2015-07-24
+  - When there is a multipart POST request and the parts have extended
+    HTTP headers, try harder to decode and squeeze a meaningful value
+    out of it before giving up and crying.  Updated docs and tests to
+    reflect this change.  This should solve problems when your clients
+    are posting multipart form values with special character sets.
+  - Fixed issue where last_error actually returned the first error.  Took
+    the change to add a 'pop_errors' to give the inverse of shift_errors.
+  - Merged Pull Requests:
+    - https://github.com/perl-catalyst/catalyst-runtime/pull/95
+    - https://github.com/perl-catalyst/catalyst-runtime/pull/96
+    - https://github.com/perl-catalyst/catalyst-runtime/pull/97
+    - https://github.com/perl-catalyst/catalyst-runtime/pull/98
+    - https://github.com/perl-catalyst/catalyst-runtime/pull/106
+    - https://github.com/perl-catalyst/catalyst-runtime/pull/107
+
+5.90093 - 2015-05-29
+  - Fixed a bug where if you used $res->write and then $res->body, the
+    contents of body would be double encoded (gshank++).
+
+5.90092 - 2015-05-19
+  - Allows you to use a namespace suffix for request, response and stats
+    class traits.  Docs and tests for this.
+  - Refactor the change introduced in 5.90091 to solve reported issues (for
+    example Catalyst::Controller::DBIC::API fails its tests) and to be a more
+    conservative refactor (new code more closely resembles the orginal code
+    that has proven to work for years.)
+
+5.90091 - 2015-05-08
+  - Fixed a bug where if an injected component expanded sub components, those
+    sub components would not show up in the startup debug dev console (
+    even though they were actually created).
+
+5.90090 - 2015-04-29
+  - Updated some documention in Catalyst::Request::Upload to clarify behavior
+    that RT ticket reported as confusing or unexpected
+  - Merged all changes from 5.90089_XXX development cycle.
+  - removed a mistaken use of Test::Most, which is not a core Catalyst
+    dependency.  Used Test::More instead.
+
+5.90089_004 - 2015-04-28
+  - Added swanky github badges.
+  - Reverted a change to how the stats engine is setup that was incorrect.
+  - New application setup hook 'config_for' which allows one to get the
+    canonical application configuration for a controller, view or model, or
+    a plugin.  Can also be used to override and adapt what configuration is
+    retrieved.
+
+5.90089_003 - 2015-04-27
+  - Fixed an issue where a delayed controller that did ACCEPT_CONTEXT would
+    raise an error when registering its actions.
+  - Updated some documentation around route matching.
+  - refactored the setup of injected components to allow you to hook into
+    the injection and do custom injection types.
+
+5.90089_002 - 2015-04-17
+  - Changed the way we check for presence of Type::Tiny in a test case to be
+    more explicit in the version requirement.  Hopefully a fix for reported
+    test fail.
+  - When declaring type constraints in Args and CaptureArgs, if you want to
+    use a Moose builtin type (or a custom stringy type that you've already
+    defined and associated with the Moose::TypeRegistry) you must now quote
+    the type name.  This is to clearly disambiguate between Moose stringy types
+    and imported types.
+  - Additional changes to type constraint detection to between determine when a
+    type constraint for reference types have a measured number of arguments or
+    not.  clarify restriction on reference type constraints.
+  - Several bugs with type constraints and uri_for squashed.  More test cases
+    around all the argument type constraints to tighten scope of action.
+  - NEW FEATURE: New method in Catalyst::Utils 'inject_component', which is a core
+    version of the previously external addon 'CatalystX::InjectComponent'.  You should
+    start to convert your existing code which uses the stand alone version, since
+    going forward only the core version will be supported.  Also the core version in
+    Catalyst::Utils has an additional feature to compose roles into the injected
+    component.
+  - NEW FEATURE: Concepts from 'CatalystX::RoleApplicator' have been moved to core
+    so we now have the follow application attributes 'request_class_traits',
+    'response_class_traits' and 'stats_class_traits' which allow you to compose
+    traits for these core Catalyst classes without needing to create subclasses. So
+    in general any request or response trait on CPAN that used 'CatalystX::RoleApplicator'
+    should now just work with this core feature.  Note that  can also set thse roles
+    via new configuration keys, 'request_class_traits', 'response_class_traits' 
+    and 'stats_class_traits'. If you use both configuration and application class methods,
+    they are combined.
+  - NEW FEATURE: Core concepts from 'CatalystX::ComponentsFromConfig'.  You can now
+    setup components directly from configuration.  This could save you some effort and
+    creating 'empty' base classes in your Model/View and Controller directories.  This
+    feature is currently limited in that you can only configure components that are
+    'true' Catalyst components (but you may use Catalyst::Model::Adaptor to proxy
+    stand alone classes...).
+  - Only create a stats object if you are using stats.  This is a minor performance
+    optimization, but there's a small chance it is a breaking change, so please
+    report any stats related issues.
+  - Added a developer mode warning if you call a component with arguments that does not
+    expect arguments (for example calling $c->model('Foo', 1,2,3,4) where Myapp::Model::Foo
+    does not ACCEPT_CONTEXT.  Only components that ACCEPT_CONTEXT do anything with
+    passed arguments in $c->controller/view/model.
+  - Change the way components are setup so that you can now rely on all components
+    when setting up a component.  Previously application scoped components could not
+    reliably use an existing application scoped component as a dependecy for initialization.
+
+5.90089_001 - 2015-03-26
+  - New development branch synched with 5.90085.
+  - NEW FEATURE: Type Constraints on Args/CaptureArgs.  Allows you to declare
+    a Moose, MooseX::Types or Type::Tiny named constraint on your Arg or 
+    CaptureArg.
+  - When using $c->uri_for (or the derived $c->uri_for_action) and the target
+    action has type constrainted args (or captures), verify that the proposed
+    URL matches the defined args.  In general $c->uri_for will be a bit more
+    noisy if the supplied arguments are not correct.
+  - New top level document on Route matching. (Catalyst::RouteMatching).  This
+    document is still in development, but is worth review and comments.
+
+5.90085 - 2015-03-25
+  - Small change to Catalyst::Action to prevent autovivication of Args value (dim1++)
+  - Minor typo fixes (Abraxxa++)
+  - Make sure than when using chained actions and when more than one action
+    matches the same path specification AND has Args(0), that we follow the
+    "in a tie, the last action defined wins" rule.  There is a small chance
+    this is a breaking change for you.  See Catalyst::Upgrading for more.
+    You may use the application configuration setting "use_chained_args_0_special_case"
+    to disable this new behavior, if you must for back-compat reasons.
+  - Added PATCH HTTP Method action attribute shortcut.
+  - Several new configuration options aimed to give improved backwards compatibility
+    for when your URL query parameters or keywords have non UTF-8 encodings.
+    See Catalyst::Upgrading.
+
+5.90084 - 2015-02-23
+  - Small change to the way body parameters are created in order to prevent
+    trying to create parameters twice.
+  - Use new HTTP::Body and code updates to fix issue when POSTed params have
+    non UTF-8 charset encodings or otherwise complex upload parts that are not
+    file uploads. In these cases when Catalyst can't determine what the value of
+    a form upload is, will return an instance of Catalyst::Request::PartData with
+    all the information need to figure it out.  Documentation about this corner
+    case. For RT https://rt.cpan.org/Ticket/Display.html?id=101556
+  - Two new application configuration parameters 'skip_body_param_unicode_decoding'
+    and 'skip_complex_post_part_handling' to assist you with any backward
+    compatibility issues with all the new UTF8 work in the most recent stable
+    Catalyst.  You may use these settings to TEMPORARILY disable certain new
+    features while you are seeking a long term fix.
+
+5.90083 - 2015-02-16
+  - Fixed typo in support for OPTIONS method matching (andre++)
+  - Stop using $env->{'plack.request.query'} as a query parsing optimization
+    since 1) it doesn't belong to us and 2) there's subtle differences in the
+    way plack parses parameters and catalyst does.  This fixes a bug when you
+    are using middleware that uses Plack::Request to do its thing.  This change
+    might have subtle impact on query parsing.  Please test this change!
+
+5.90082 - 2015-01-10
+  - Fixed a regression created in $response->from_psgi_response and test case
+    to prevent it happening again.
+
+5.90081 - 2015-01-10
+  - created class attribute 'finalized_default_middleware' which determines
+    if the default middleware has been added to the stack yet or not.  This
+    removes a horrible hack that polluted the configuration hash.  Added
+    test case to prevent regressions.
+
+5.90080 - 2015-01-09
+  - Minor documentation corrections
+  - Make the '79 development series stable
+
+5.90079_008  - 2015-01-07
+  - If we get a response set from $res->from_psgi_response and that response
+    has a charset for the content type, we clear encoding for the rest of the
+    response (avoid double encoding).  Added more documentation around this.
+  - Documentation updates and typo fixes across various UTF8 docs (Mark Ellis++)
+
+5.90079_007  - 2015-01-07
+  - Merged from Stable (5.90079)
+  - reviewed and cleaned up UTF8 related docs
+  - replace missing utf8 pragma in Catalyst::Engine
+  - Cleaned up spelling errors in various docs (abbraxxa++)
+  - New document Catalyst::UTF8 which attempts to summarize UTF8 and encoding
+    changes introduced in v5.90080.
+
+5.90079_006  - 2015-01-02
+  - Removed unneeded dependency on RenderView in new test case that was causing fails
+    on CPAN testers that did not just happen to have that dependency already installed
+  - Updated copyright notices to 2015
+  - Documentation patches around the setup methods and clarification on on security
+    note posted a few months ago.
+  - Added my name to the contributors list
+
+5.90079_005 - 2014-12-31
+  - Merged changes from 5.90078
+  - If configuration 'using_frontend_proxy' is set, we add the correct middleware
+    to the default middleware list.  This way you get the correct and expected
+    behavior if you are starting your application via one of the generated scripts
+    or if you are calling MyApp->psgi_app.  Previously if you started the application
+    with ->psgi_app (or to_app) we ignored this configuration option
+  - New configuration option 'using_frontend_proxy_path' which enables
+    Plack::Middleware::ReverseProxyPath on your application easily.  Please note that
+    Plack::Middleware::ReverseProxyPath is not an automatic dependency of Catalyst at
+    this time, so if you want this feature you should add it to your project dependency
+    list.  This is done to avoid continued growth of Catalyst dependencies.
+  - Tweaks encoding docs a bit to get closer to final.
+
+5.90079_004 - 2014-12-26
+  - Starting adding some docs around the new encoding stuff
+  - Exposed the reqexp we use to match content types that need encoding via a
+    global variable.
+  - Added some test cases for JSON utf8 and tested file uploads with utf8.
+  - Fixes to decoding on file upload filenames and related methods
+  - new methods on upload object that tries to do the right thing if we find
+    a character set on the upload and its UTF8.
+  - new additional helper methods on the file upload object.
+  - new helper methods has_encoding and clear_encoding on context.
+  - Method on Catalyst::Response to determine if the response should be encoded.
+  - Warn if changing headers only if headers are finalized AND the response callback
+    has already been called (and headers already sent).
+  - Centralized rules about detecting if we need to automatically encode or not and
+    added tests around cases when you choose to skip auto encoding.
+
+5.90079_003 - 2014-12-03
+  - Make sure all tests run even if debug mode is enabled.
+  - Fixed issue with middleware stash test case that failed on older Perls
+
+5.90079_002 - 2014-12-02
+  - Fixed typo in Makefile.PL which borked the previous distribution. No other
+    changes.
+
+5.90079_001 - 2014-12-02
+  - MyApp->to_app is now an alias for MyApp->psgi_app in order to better support
+    existing Plack conventions.
+  - Modify Catalyst::Response->from_psgi_response to allow the first argument to
+    be an object that does ->as_psgi.
+  - Modified Catalyst::Middleware::Stash to be a shallow copy in $env.  Added some
+    docs.  Added a test case to make sure stash keys added in a child application
+    don't bubble back up to the main application.
+  - We no longer use Encode::is_utf8 since it doesn't work the way we think it
+    does... This required some UTF-8 changes.  If your application is UTF-8 aware
+    I highly suggest you test this release.
+  - We always do utf8 decoding on incoming URLs (before we only did so if the server
+    encoding was utf8.  I believe this is correct as per the w3c spec, but please
+    correct if incorrect :)
+  - Debug output now shows utf8 characters if those are incoming via Args or as
+    path or pathparts in your actions.  query and body parameter keys are now also
+    subject to utf8 decoding (or as specified via the encoding configuration value).
+  - lots of UTF8 changes.  Again we think this is now more correct but please test.
+  - Allow $c->res->redirect($url) to accept $url as an object that does ->as_string
+    which I think will ease a common case (and common bug) and added documentation.
+  - !!! UTF-8 is now the default encoding (there used to be none...).  You can disable
+    this if you need to with MyApp->config(encoding => undef) if it causes you trouble.
+  - Calling $c->res->write($data) now encodes $data based on the configured encoding
+    (UTF-8 is default).
+  - $c->res->writer_fh now returns Catalyst::Response::Writer which is a decorator
+    over the PSGI writer and provides an additional method 'write_encoded' that just
+    does the right thing for encoding your responses.  This is probably the method
+    you want to use.
+  - New dispatch matching attribute: Scheme.  This lets you match a route based on
+    the incoming URI scheme (http, https, ws, wss).
+  - If $c->uri_for targets an action or action chain that defines Scheme, use that
+    scheme for the generated URI object instead of just using whatever the incoming
+    request uses.
+
+5.90079 - 2015-01-02
+  - Removed dependency from test case that we don't install for testing (
+    rt #101243)
+  - updated year in copyright notices
+
+5.90078 - 2014-12-30
+  - POD corrections (sergey++)
+  - New configuration option to disable the HTTP Exception passthrough feature
+    introduced in 5.90060.  You can use this if that feature is causing you
+    trouble. (davewood++);
+  - Some additional helper methods for dealing with errors.
+  - More clear exception when $request->body_data tries to parse malformed POSTed
+    data.  Added documentation and tests around this.
+
+5.90077 - 2014-11-18
+  - We store the PSGI $env in Catalyst::Engine for backcompat reasons.  Changed
+    this so that the storage is a weak reference, so that it goes out of scope
+    with the request.  This solves an issue where items in the stash (now in the
+    PSGI env) would not get closed at the end of the request.  This caused some
+    regression, primarily in custom testing classes.
+
+5.90076 - 2014-11-13
+  - If throwing an exception object that does the code method, make sure that
+    method returns an expected HTTP status code before passing it on to the
+    HTTP Exception middleware.
+
+5.90075 - 2014-10-06
+  - Documentation patch for $c->req->param to point out the recently discovered
+    potential security issues: http://blog.gerv.net/2014/10/new-class-of-vulnerability-in-perl-web-applications/
+  - You don't need to install this update, but you should read about the exploit
+    and review if your code is vulnerable.  If you use the $c->req->param interface
+    you really need to review this exploit.
+
+5.90074 - 2014-10-01
+  - Specify Carp minimum version to avoid pointless test fails (valy++)
+
 5.90073 - 2014-09-23
   - Fixed a regression caused by the last release where we broke what happened
     when you tried to set request parameters via $c->req->param('foo', 'bar').
     in the wild (Catalyst-Plugin-Authentication-0.10023) - (removed in 5.90070)
   - Reverted changes to debug log/handling (5.90069_003) to fix
     rev dep Catalyst-Plugin-Static-Simple-0.32 test suite.
-  - Added italian translation of default error.
+  - Added Italian translation of default error.
 
 5.90070 - 2014-08-07
   - Retagged previous release as stable; no changes
@@ -97,7 +435,7 @@ s
 
 5.90059_006 - 2014-02-06
   - MyApp->setup now returns $app to allow class method chaining.
-  - New Util helper functional localize $env to make it easier to mount PSIG
+  - New Util helper functional localize $env to make it easier to mount PSGI
     applications under controllers and actions.  See Catalyst::Utils/PSGI Helpers.
   - NOTICE: Final Development release for Runner, unless significant issues are
     raised.  Please test.
@@ -112,7 +450,7 @@ s
     that does 'read' but not 'getline'.  Added deprecation notice for this
     case.  Added docs to Catalyst::Delta.
   - Catalyst::Delta contains a list of behaviors which will be considered
-    deprecated immediatelty.  Most items have workarounds and tweaks you can
+    deprecated immediately.  Most items have workarounds and tweaks you can
     make to avoid issues.  These deprecations are targeted for removal/enforcement
     in the Catalyst 6 release.  Please review and give your feedback.
   - More middleware to replace inline code (upasana++)
@@ -169,7 +507,7 @@ s
   - Give a more descriptive error message when trying to load middleware that
     does not exist.
   - Change the way we initialize plugins to fix a bug where when using the 
-    populare ConfigLoader plugin, configs merged are not available for setting
+    popular ConfigLoader plugin, configs merged are not available for setting
     up middleware and data handlers (and probably other things as well).
 
     NOTE: This change might cause issues if you had code that was relying on the
@@ -179,12 +517,12 @@ s
 
   - You may now also call 'setup_middleware' as a package method if you think
     that loading middleware via configuration is a weird or broken idea.
-  - Various POD formating fixed.
+  - Various POD formatting fixed.
   - Improved some documentation about what type of filehandles that ->body can
     accept and issues that might arise.
 
 5.90051 - 2013-11-06
-  - Be more skeptical of the existance of $request->env to fix a regression
+  - Be more skeptical of the existence of $request->env to fix a regression
     introduced in Catalyst::Action::REST by the previous release
 
 5.90050 - 2013-11-05
@@ -222,7 +560,7 @@ s
 
 5.90049_004 - 2013-10-18
   - JSON Data handler looks for both JSON::MaybeXS and JSON, and uses
-    whichever is first (prefering to find JSON::MaybeXS).  This should
+    whichever is first (preferring to find JSON::MaybeXS).  This should
     improve compatibility as you likely already have one installed.
   - Fixed a warning in the server script (bokutin++)
   - We now populate various Plack $env keys in order to play nice with
@@ -254,7 +592,7 @@ s
 
 5.90049_003 - 2013-09-20
   - Documented the new body_data method added in the previous release
-  - Merged from master many important bugfixes and forward compatiblity
+  - Merged from master many important bugfixes and forward compatibility
     updates, including:
     - Use modern preferred method for Moose metaclass access and many other
       small changes to how we use Moose for better forward compat (ether++)
@@ -307,7 +645,7 @@ s
     1) Only allow one of either :CaptureArgs or :Args
     2) :CaptureArgs() argument must be numeric
     3) :CaptureArgs() and :Args() arguments cannot be negative
-  - Add Devel::InnerPackage to dependencies, fixing tests on perl 5.17.11
+  - Add Devel::InnerPackage to dependencies, fixing tests on Perl 5.17.11
     as it's been removed from core. RT#84787
   - New support for closing over the PSGI $writer object, useful for working
     with event loops.
@@ -342,7 +680,7 @@ s
   - Added cpanfile as a way to notice we are a dev checkout.
   - Added 'x-tunneled-method' HTTP Header method override to match features in
     Catalyst::Action::REST and in other similar systems on CPAN.
-  - smarter valiation around action attributes.
+  - smarter validation around action attributes.
 
 5.90020 - 2013-02-22
   ! Catalyst::Action now defines 'match_captures' so it is no long considered
@@ -361,9 +699,9 @@ s
   - Some test and documentation improvements
 
 5.90019 - 2012-12-04 21:31:00
-  - Fix for perl 5.17.6 (commit g7dc8663). RT#81601
-  - Fix for perl 5.8. RT#61122
-  - Remove use of MooseX::Types as MooseX::Types is broken on perl5.8
+  - Fix for Perl 5.17.6 (commit g7dc8663). RT#81601
+  - Fix for Perl 5.8. RT#61122
+  - Remove use of MooseX::Types as MooseX::Types is broken on Perl 5.8
     RT#77100 & RT#81121
 
 5.90018 - 2012-10-23 20:55:00
@@ -390,7 +728,7 @@ s
 5.90016 - 2012-08-16 15:35:00
   - prepare_parameters is no longer an attribute builder.  It is now a method
     that calls the correct underlying functionality (Bill Moseley++)
-  - Updated Makefile.PL to handle MacOXS tar
+  - Updated Makefile.PL to handle MacOSX tar
   - Fix uri_for to handle a stringifiable object
   - Fix model/view/controller methods to handle stringifiable objects
   - Fix RT#78377 - IIS7 ignores response body for 3xx requests, which
@@ -500,7 +838,7 @@ s
     refactoring.
 
   - The Catalyst::Utils::home function is used to find if the application
-    is a checkout in Catalyst::ScriptRunner. This means that a non-existant
+    is a checkout in Catalyst::ScriptRunner. This means that a non-existent
     lib directory that is relative to the script install location is not
     included when not running from a checkout.
 
@@ -579,7 +917,7 @@ s
      application authors to add custom options to their scripts then
      get them passed through to the application.
 
-  Doumentation:
+  Documentation:
    - Clarify that if you manually write your own .psgi file, then optional
      proxy support (via the using_frontend_proxy config value) will not be
      enabled unless you explicitly apply the default middlewares from
@@ -589,7 +927,7 @@ s
    - Fix issue due to perl internals bugs in 5.8 and 5.10 (not present in
      other perl versions) require can pass the context inappropriately,
      meaning that some methods of loading classes can fail due to void
-     context being passed throuh to make_immutable, causing it to not return
+     context being passed through to make_immutable, causing it to not return
      a value.
      This bug caused loading Catalyst::Script::XXX to fail and is fixed
      both by bumping the Class::Load dependency, and also adding an explicit
@@ -624,7 +962,7 @@ s
 
    - Document how to get the vhost of the request in $c->req->hostname
      to avoid confusion
-   - Remove documentation showing Global / Regex / Private actionsi
+   - Remove documentation showing Global / Regex / Private actions
      as whilst these still exist (and work), they are not recommended.
    - Remove references to the -Engine flag.
    - Remove references to the deprecated Catalyst->plugin method
@@ -642,7 +980,7 @@ s
 5.90003 - 2011-10-05 08:32:00
   Bug fixes:
 
-   - Make default body reponses for 302s W3C compliant. RT#71237
+   - Make default body responses for 302s W3C compliant. RT#71237
 
    - Fix issue where groups of attributes to override controller actions
      in config would be (incorrectly) overwritten, if the parser for that
@@ -659,7 +997,7 @@ s
   Backward compatibility fixes:
 
    - Restore (an almost empty) Catalyst::Engine::HTTP to the dist for old
-     scripts which explictly require Catalyst::Engine::HTTP
+     scripts which explicitly require Catalyst::Engine::HTTP
 
   Documentation fixes:
 
@@ -716,7 +1054,7 @@ s
 
     - nginx specific behaviour is removed as it is not needed with any
       web server configuration I can come up with (recommended config is
-      documented in Catalst::Manual::Deployment::nginx::FastCGI)
+      documented in Catalyst::Manual::Deployment::nginx::FastCGI)
 
 5.89003 2011-07-28 20:11:50 (TRIAL release)
 
@@ -755,7 +1093,7 @@ s
   - Added a Catalyst::PSGI manual page with information about writing a .psgi
     file for your application.
 
-   - Catalyst::Uprading has been improved, and the status of old Catalyst
+   - Catalyst::Upgrading has been improved, and the status of old Catalyst
      engines clarified.
 
  Deprecations:
@@ -1094,7 +1432,7 @@ s
 
   Bug fixed:
    - $c->uri_for will now escape unsafe characters in captures
-     ($c->request->captures) and correctly encode utf8 charracters.
+     ($c->request->captures) and correctly encode utf8 characters.
 
 5.80020 2010-02-04 06:51:18
 
@@ -1194,7 +1532,7 @@ s
    - Restore -p option for pid file in the FastCGI server script.
    - Fix the script environment variables MYAPP_PORT and MYAPP_RELOAD RT#52604
    - Fix aliasing applications under non-root paths with mod_rewrite in
-     some apache versions where %ENV{SCRIPT_NAME} is set to the real name of
+     some Apache versions where %ENV{SCRIPT_NAME} is set to the real name of
      the script, by using $ENV{REDIRECT_URL} which contains the non-rewritten
      URI.
    - Fix usage display when myapp_create.pl is run with no arguments. RT#52630
@@ -1275,7 +1613,7 @@ s
     - Stop warnings when actions are forwarded to during dispatch.
     - Remove warnings for using Catalyst::Dispatcher->dispatch_types as this is a
       valid method to publicly call on the dispatcher.
-    - Args ($c->request->args) and CaptureArgs ($c->request->captrues)
+    - Args ($c->request->args) and CaptureArgs ($c->request->captures)
       passed to $c->uri_for with an action object ($c->action) will now
       correctly round-trip when args or captures contain / as it is now
       correctly uri encoded to %2F.
@@ -1433,7 +1771,7 @@ s
        - Fix POD to refer to ->config(key => $val), rather than
          ->config->{key} = $val, as the latter form is deprecated.
        - Clearer docs for the 'uri_for' method.
-       - Fix POD refering to CGI::Cookie. We're using CGI::Simple::Cookie.
+       - Fix POD referring to CGI::Cookie. We're using CGI::Simple::Cookie.
          (Forrest Cahoon)
 
 5.80007 2009-06-30 23:54:34
@@ -1531,7 +1869,7 @@ s
           in Catalyst::Engine (kmx)
 
 5.80004 2009-05-18 17:03:23
-        - Rename the actions attribute in Catalyt::Controller to
+        - Rename the actions attribute in Catalyst::Controller to
           _controller_actions to avoid name clashes with application
           controller naming. (random)
         - Test for using Moose in components which have a non-Moose base class
@@ -1583,7 +1921,7 @@ s
         - Fix RT#43375 by sorting results before testing them
         - Fixes for uri_for_action when using Catalyst::DispatchType::Regex
           + tests from RT#39369 (norbi)
-        - Partial rewrite and reoganisation of the C3 docs in
+        - Partial rewrite and reorganization of the C3 docs in
           Catalyst::Upgrading based on feedback from kiffin
         - If you make your application class immutable and turn off
           constructor inlining, Catalyst will die and tell you pass
@@ -1592,7 +1930,7 @@ s
 
 5.80002 2009-04-22 01:28:36
         - Fix CATALYST_DEBUG and MYAPP_DEBUG environment variables
-          turning debuging on if defined, rather than if set.
+          turning debugging on if defined, rather than if set.
           They now force debugging on or off, taking precedence over
           configuration in your application.
           - Tests for this
@@ -1682,7 +2020,7 @@ s
           allowing method modifiers on actions to work as expected.
         - Provide a reasonable API in Catalyst::Controller for working with
           and registering actions, allowing a controller sub-class to replace
-          subroutine attributes for action declerations with an alternate
+          subroutine attributes for action declarations with an alternate
           syntax.
         - Instantiate correct sub-class of Moose::Meta::Class for non-Moose
           components where Catalyst forces the creation of a metaclass instance.
@@ -1723,7 +2061,7 @@ s
           parameter used to be ignored, but started breaking if the parameter
           was not a hash in 5.8000_04. Extra parameter is now ignored if
           it isn't a hashref
-        - Fix request argumentss getting corrupted if you override the
+        - Fix request arguments getting corrupted if you override the
           dispatcher and call an action which detaches (for
           Catalyst::Plugin::Authorization::ACL)
         - Fix calling use Catalyst::Test 'MyApp' 'foo' which used to work,
@@ -1922,7 +2260,7 @@ s
           was introduced when attempting to allow "0" as a Path.
 
 5.7013  2008-05-16 18:20:00
-        - Provide backwards compatability methods in Catalyst::Stats
+        - Provide backwards compatibility methods in Catalyst::Stats
         - Fix subdirs for scripts that run in subdirs more than one level deep.
         - Added test and updated docs for handling the Authorization header
           under mod_fastcgi/mod_cgi.
index 31fd9fc..90b0676 100644 (file)
@@ -1,2 +1,2 @@
-^(?!script/\w+\.pl$|TODO$|lib/.+(?<!ROADMAP)\.p(m|od)$|inc/|t/a(uthor|ggregate)/.*\.t$|t/([^/]+|.{1,2}|[^t][^m][^p].*)\.(gif|yml|pl|t)$|t/lib/.*\.pm$|t/something/(Makefile.PL|script/foo/bar/for_dist)$|t/conf/extra.conf.in$|Makefile.PL$|README$|MANIFEST$|Changes$|META.yml$|.+testappencodingsetinconfig.json|.+TestMiddleware/share.*|.+TestMiddlewareFromConfig/share.*|.+TestContentNegotiation/share.*)
+^(?!script/\w+\.pl$|TODO$|lib/.+(?<!ROADMAP)\.p(m|od)$|inc/|t/a(uthor|ggregate)/.*\.t$|t/([^/]+|.{1,2}|[^t][^m][^p].*)\.(gif|yml|pl|t)$|t/lib/.*\.pm$|t/something/(Makefile.PL|script/foo/bar/for_dist)$|t/conf/extra.conf.in$|Makefile.PL$|README$|MANIFEST$|Changes$|META.yml$|.+testappencodingsetinconfig.json|.+TestMiddleware/share.*|.+TestMiddlewareFromConfig/share.*|.+TestContentNegotiation/share.*|t/utf8.txt)
 /cpanfile
index ff61083..a0aeb41 100644 (file)
@@ -35,14 +35,14 @@ requires 'Data::OptList';
 requires 'Moose' => '1.03';
 requires 'MooseX::MethodAttributes::Role::AttrContainer::Inheritable' => '0.24';
 requires 'MooseX::Role::WithOverloading' => '0.09';
-requires 'Carp';
+requires 'Carp' => '1.25';
 requires 'Class::C3::Adopt::NEXT' => '0.07';
 requires 'CGI::Simple::Cookie' => '1.109';
 requires 'Data::Dump';
 requires 'Data::OptList';
 requires 'HTML::Entities';
 requires 'HTML::HeadParser';
-requires 'HTTP::Body'    => '1.06'; # ->cleanup(1)
+requires 'HTTP::Body'    => '1.22';
 requires 'HTTP::Headers' => '1.64';
 requires 'HTTP::Request' => '5.814';
 requires 'HTTP::Response' => '5.813';
@@ -69,7 +69,8 @@ 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';
+requires 'URI' => '1.65';
+requires 'URI::ws' => '0.03';
 requires 'JSON::MaybeXS' => '1.000000';
 requires 'Stream::Buffered';
 requires 'Hash::MultiValue';
@@ -83,7 +84,7 @@ requires "Plack::Middleware::ContentLength";
 requires "Plack::Middleware::Head";
 requires "Plack::Middleware::HTTPExceptions";
 requires "Plack::Middleware::FixMissingBodyInRedirect" => '0.09';
-requires "Plack::Middleware::MethodOverride";
+requires "Plack::Middleware::MethodOverride" => '0.12';
 requires "Plack::Middleware::RemoveRedundantBody" => '0.03';
 
 test_requires 'Test::Fatal';
diff --git a/README.mkdn b/README.mkdn
new file mode 100644 (file)
index 0000000..ad3aa2d
--- /dev/null
@@ -0,0 +1,2168 @@
+# NAME
+
+Catalyst - The Elegant MVC Web Application Framework
+
+<div>
+
+    <a href="https://badge.fury.io/pl/Catalyst-Runtime"><img src="https://badge.fury.io/pl/Catalyst-Runtime.svg" alt="CPAN version" height="18"></a>
+    <a href="https://travis-ci.org/perl-catalyst/catalyst-runtime/"><img src="https://api.travis-ci.org/perl-catalyst/catalyst-runtime.png" alt="Catalyst></a>
+    <a href="http://cpants.cpanauthors.org/dist/Catalyst-Runtime"><img src="http://cpants.cpanauthors.org/dist/Catalyst-Runtime.png" alt='Kwalitee Score' /></a>
+</div>
+
+# SYNOPSIS
+
+See the [Catalyst::Manual](https://metacpan.org/pod/Catalyst::Manual) distribution for comprehensive
+documentation and tutorials.
+
+    # Building Catalyst for development
+    cpanm --local-lib=~/perl5 local::lib && eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib)
+    cpanm --installdeps --with-develop .
+    perl Makefile.PL
+
+    # Install Catalyst::Devel for helpers and other development tools
+    # use the helper to create a new application
+    catalyst.pl MyApp
+
+    # add models, views, controllers
+    script/myapp_create.pl model MyDatabase DBIC::Schema create=static dbi:SQLite:/path/to/db
+    script/myapp_create.pl view MyTemplate TT
+    script/myapp_create.pl controller Search
+
+    # built in testserver -- use -r to restart automatically on changes
+    # --help to see all available options
+    script/myapp_server.pl
+
+    # command line testing interface
+    script/myapp_test.pl /yada
+
+    ### in lib/MyApp.pm
+    use Catalyst qw/-Debug/; # include plugins here as well
+
+    ### In lib/MyApp/Controller/Root.pm (autocreated)
+    sub foo : Chained('/') Args() { # called for /foo, /foo/1, /foo/1/2, etc.
+        my ( $self, $c, @args ) = @_; # args are qw/1 2/ for /foo/1/2
+        $c->stash->{template} = 'foo.tt'; # set the template
+        # lookup something from db -- stash vars are passed to TT
+        $c->stash->{data} =
+          $c->model('Database::Foo')->search( { country => $args[0] } );
+        if ( $c->req->params->{bar} ) { # access GET or POST parameters
+            $c->forward( 'bar' ); # process another action
+            # do something else after forward returns
+        }
+    }
+
+    # The foo.tt TT template can use the stash data from the database
+    [% WHILE (item = data.next) %]
+        [% item.foo %]
+    [% END %]
+
+    # called for /bar/of/soap, /bar/of/soap/10, etc.
+    sub bar : Chained('/') PathPart('/bar/of/soap') Args() { ... }
+
+    # called after all actions are finished
+    sub end : Action {
+        my ( $self, $c ) = @_;
+        if ( scalar @{ $c->error } ) { ... } # handle errors
+        return if $c->res->body; # already have a response
+        $c->forward( 'MyApp::View::TT' ); # render template
+    }
+
+See [Catalyst::Manual::Intro](https://metacpan.org/pod/Catalyst::Manual::Intro) for additional information.
+
+# DESCRIPTION
+
+Catalyst is a modern framework for making web applications without the
+pain usually associated with this process. This document is a reference
+to the main Catalyst application. If you are a new user, we suggest you
+start with [Catalyst::Manual::Tutorial](https://metacpan.org/pod/Catalyst::Manual::Tutorial) or [Catalyst::Manual::Intro](https://metacpan.org/pod/Catalyst::Manual::Intro).
+
+See [Catalyst::Manual](https://metacpan.org/pod/Catalyst::Manual) for more documentation.
+
+Catalyst plugins can be loaded by naming them as arguments to the "use
+Catalyst" statement. Omit the `Catalyst::Plugin::` prefix from the
+plugin name, i.e., `Catalyst::Plugin::My::Module` becomes
+`My::Module`.
+
+    use Catalyst qw/My::Module/;
+
+If your plugin starts with a name other than `Catalyst::Plugin::`, you can
+fully qualify the name by using a unary plus:
+
+    use Catalyst qw/
+        My::Module
+        +Fully::Qualified::Plugin::Name
+    /;
+
+Special flags like `-Debug` can also be specified as
+arguments when Catalyst is loaded:
+
+    use Catalyst qw/-Debug My::Module/;
+
+The position of plugins and flags in the chain is important, because
+they are loaded in the order in which they appear.
+
+The following flags are supported:
+
+## -Debug
+
+Enables debug output. You can also force this setting from the system
+environment with CATALYST\_DEBUG or <MYAPP>\_DEBUG. The environment
+settings override the application, with <MYAPP>\_DEBUG having the highest
+priority.
+
+This sets the log level to 'debug' and enables full debug output on the
+error screen. If you only want the latter, see [$c->debug](https://metacpan.org/pod/$c->debug).
+
+## -Home
+
+Forces Catalyst to use a specific home directory, e.g.:
+
+    use Catalyst qw[-Home=/usr/mst];
+
+This can also be done in the shell environment by setting either the
+`CATALYST_HOME` environment variable or `MYAPP_HOME`; where `MYAPP`
+is replaced with the uppercased name of your application, any "::" in
+the name will be replaced with underscores, e.g. MyApp::Web should use
+MYAPP\_WEB\_HOME. If both variables are set, the MYAPP\_HOME one will be used.
+
+If none of these are set, Catalyst will attempt to automatically detect the
+home directory. If you are working in a development environment, Catalyst
+will try and find the directory containing either Makefile.PL, Build.PL,
+dist.ini, or cpanfile. If the application has been installed into the system
+(i.e. you have done `make install`), then Catalyst will use the path to your
+application module, without the .pm extension (e.g., /foo/MyApp if your
+application was installed at /foo/MyApp.pm)
+
+## -Log
+
+    use Catalyst '-Log=warn,fatal,error';
+
+Specifies a comma-delimited list of log levels.
+
+## -Stats
+
+Enables statistics collection and reporting.
+
+    use Catalyst qw/-Stats=1/;
+
+You can also force this setting from the system environment with CATALYST\_STATS
+or <MYAPP>\_STATS. The environment settings override the application, with
+<MYAPP>\_STATS having the highest priority.
+
+Stats are also enabled if [debugging ](#debug) is enabled.
+
+# METHODS
+
+## INFORMATION ABOUT THE CURRENT REQUEST
+
+## $c->action
+
+Returns a [Catalyst::Action](https://metacpan.org/pod/Catalyst::Action) object for the current action, which
+stringifies to the action name. See [Catalyst::Action](https://metacpan.org/pod/Catalyst::Action).
+
+## $c->namespace
+
+Returns the namespace of the current action, i.e., the URI prefix
+corresponding to the controller of the current action. For example:
+
+    # in Controller::Foo::Bar
+    $c->namespace; # returns 'foo/bar';
+
+## $c->request
+
+## $c->req
+
+Returns the current [Catalyst::Request](https://metacpan.org/pod/Catalyst::Request) object, giving access to
+information about the current client request (including parameters,
+cookies, HTTP headers, etc.). See [Catalyst::Request](https://metacpan.org/pod/Catalyst::Request).
+
+## REQUEST FLOW HANDLING
+
+## $c->forward( $action \[, \\@arguments \] )
+
+## $c->forward( $class, $method, \[, \\@arguments \] )
+
+This is one way of calling another action (method) in the same or
+a different controller. You can also use `$self->my_method($c, @args)`
+in the same controller or `$c->controller('MyController')->my_method($c, @args)`
+in a different controller.
+The main difference is that 'forward' uses some of the Catalyst request
+cycle overhead, including debugging, which may be useful to you. On the
+other hand, there are some complications to using 'forward', restrictions
+on values returned from 'forward', and it may not handle errors as you prefer.
+Whether you use 'forward' or not is up to you; it is not considered superior to
+the other ways to call a method.
+
+'forward' calls  another action, by its private name. If you give a
+class name but no method, `process()` is called. You may also optionally
+pass arguments in an arrayref. The action will receive the arguments in
+`@_` and `$c->req->args`. Upon returning from the function,
+`$c->req->args` will be restored to the previous values.
+
+Any data `return`ed from the action forwarded to, will be returned by the
+call to forward.
+
+    my $foodata = $c->forward('/foo');
+    $c->forward('index');
+    $c->forward(qw/Model::DBIC::Foo do_stuff/);
+    $c->forward('View::TT');
+
+Note that [forward](#c-forward-action-arguments) implies
+an `eval { }` around the call (actually
+[execute](#c-execute-class-coderef) does), thus rendering all
+exceptions thrown by the called action non-fatal and pushing them onto
+$c->error instead. If you want `die` to propagate you need to do something
+like:
+
+    $c->forward('foo');
+    die join "\n", @{ $c->error } if @{ $c->error };
+
+Or make sure to always return true values from your actions and write
+your code like this:
+
+    $c->forward('foo') || return;
+
+Another note is that `$c->forward` always returns a scalar because it
+actually returns $c->state which operates in a scalar context.
+Thus, something like:
+
+    return @array;
+
+in an action that is forwarded to is going to return a scalar,
+i.e. how many items are in that array, which is probably not what you want.
+If you need to return an array then return a reference to it,
+or stash it like so:
+
+    $c->stash->{array} = \@array;
+
+and access it from the stash.
+
+Keep in mind that the `end` method used is that of the caller action. So a `$c->detach` inside a forwarded action would run the `end` method from the original action requested.
+
+## $c->detach( $action \[, \\@arguments \] )
+
+## $c->detach( $class, $method, \[, \\@arguments \] )
+
+## $c->detach()
+
+The same as [forward](#c-forward-action-arguments), but
+doesn't return to the previous action when processing is finished.
+
+When called with no arguments it escapes the processing chain entirely.
+
+## $c->visit( $action \[, \\@arguments \] )
+
+## $c->visit( $action \[, \\@captures, \\@arguments \] )
+
+## $c->visit( $class, $method, \[, \\@arguments \] )
+
+## $c->visit( $class, $method, \[, \\@captures, \\@arguments \] )
+
+Almost the same as [forward](#c-forward-action-arguments),
+but does a full dispatch, instead of just calling the new `$action` /
+`$class->$method`. This means that `begin`, `auto` and the method
+you go to are called, just like a new request.
+
+In addition both `$c->action` and `$c->namespace` are localized.
+This means, for example, that `$c->action` methods such as
+[name](https://metacpan.org/pod/Catalyst::Action#name), [class](https://metacpan.org/pod/Catalyst::Action#class) and
+[reverse](https://metacpan.org/pod/Catalyst::Action#reverse) return information for the visited action
+when they are invoked within the visited action.  This is different from the
+behavior of [forward](#c-forward-action-arguments), which
+continues to use the $c->action object from the caller action even when
+invoked from the called action.
+
+`$c->stash` is kept unchanged.
+
+In effect, [visit](#c-visit-action-captures-arguments)
+allows you to "wrap" another action, just as it would have been called by
+dispatching from a URL, while the analogous
+[go](#c-go-action-captures-arguments) allows you to
+transfer control to another action as if it had been reached directly from a URL.
+
+## $c->go( $action \[, \\@arguments \] )
+
+## $c->go( $action \[, \\@captures, \\@arguments \] )
+
+## $c->go( $class, $method, \[, \\@arguments \] )
+
+## $c->go( $class, $method, \[, \\@captures, \\@arguments \] )
+
+The relationship between `go` and
+[visit](#c-visit-action-captures-arguments) is the same as
+the relationship between
+[forward](#c-forward-class-method-arguments) and
+[detach](#c-detach-action-arguments). Like `$c->visit`,
+`$c->go` will perform a full dispatch on the specified action or method,
+with localized `$c->action` and `$c->namespace`. Like `detach`,
+`go` escapes the processing of the current request chain on completion, and
+does not return to its cunless blessed $cunless blessed $caller.
+
+@arguments are arguments to the final destination of $action. @captures are
+arguments to the intermediate steps, if any, on the way to the final sub of
+$action.
+
+## $c->response
+
+## $c->res
+
+Returns the current [Catalyst::Response](https://metacpan.org/pod/Catalyst::Response) object, see there for details.
+
+## $c->stash
+
+Returns a hashref to the stash, which may be used to store data and pass
+it between components during a request. You can also set hash keys by
+passing arguments. The stash is automatically sent to the view. The
+stash is cleared at the end of a request; it cannot be used for
+persistent storage (for this you must use a session; see
+[Catalyst::Plugin::Session](https://metacpan.org/pod/Catalyst::Plugin::Session) for a complete system integrated with
+Catalyst).
+
+    $c->stash->{foo} = $bar;
+    $c->stash( { moose => 'majestic', qux => 0 } );
+    $c->stash( bar => 1, gorch => 2 ); # equivalent to passing a hashref
+
+    # stash is automatically passed to the view for use in a template
+    $c->forward( 'MyApp::View::TT' );
+
+The stash hash is currently stored in the PSGI `$env` and is managed by
+[Catalyst::Middleware::Stash](https://metacpan.org/pod/Catalyst::Middleware::Stash).  Since it's part of the `$env` items in
+the stash can be accessed in sub applications mounted under your main
+[Catalyst](https://metacpan.org/pod/Catalyst) application.  For example if you delegate the response of an
+action to another [Catalyst](https://metacpan.org/pod/Catalyst) application, that sub application will have
+access to all the stash keys of the main one, and if can of course add
+more keys of its own.  However those new keys will not 'bubble' back up
+to the main application.
+
+For more information the best thing to do is to review the test case:
+t/middleware-stash.t in the distribution /t directory.
+
+## $c->error
+
+## $c->error($error, ...)
+
+## $c->error($arrayref)
+
+Returns an arrayref containing error messages.  If Catalyst encounters an
+error while processing a request, it stores the error in $c->error.  This
+method should only be used to store fatal error messages.
+
+    my @error = @{ $c->error };
+
+Add a new error.
+
+    $c->error('Something bad happened');
+
+Calling this will always return an arrayref (if there are no errors it
+will be an empty arrayref.
+
+## $c->state
+
+Contains the return value of the last executed action.
+Note that << $c->state >> operates in a scalar context which means that all
+values it returns are scalar.
+
+## $c->clear\_errors
+
+Clear errors.  You probably don't want to clear the errors unless you are
+implementing a custom error screen.
+
+This is equivalent to running
+
+    $c->error(0);
+
+## $c->has\_errors
+
+Returns true if you have errors
+
+## $c->last\_error
+
+Returns the most recent error in the stack (the one most recently added...)
+or nothing if there are no errors.
+
+## shift\_errors
+
+shifts the most recently added error off the error stack and returns if.  Returns
+nothing if there are no more errors.
+
+## COMPONENT ACCESSORS
+
+## $c->controller($name)
+
+Gets a [Catalyst::Controller](https://metacpan.org/pod/Catalyst::Controller) instance by name.
+
+    $c->controller('Foo')->do_stuff;
+
+If the name is omitted, will return the controller for the dispatched
+action.
+
+If you want to search for controllers, pass in a regexp as the argument.
+
+    # find all controllers that start with Foo
+    my @foo_controllers = $c->controller(qr{^Foo});
+
+## $c->model($name)
+
+Gets a [Catalyst::Model](https://metacpan.org/pod/Catalyst::Model) instance by name.
+
+    $c->model('Foo')->do_stuff;
+
+Any extra arguments are directly passed to ACCEPT\_CONTEXT, if the model
+defines ACCEPT\_CONTEXT.  If it does not, the args are discarded.
+
+If the name is omitted, it will look for
+ - a model object in $c->stash->{current\_model\_instance}, then
+ - a model name in $c->stash->{current\_model}, then
+ - a config setting 'default\_model', or
+ - check if there is only one model, and return it if that's the case.
+
+If you want to search for models, pass in a regexp as the argument.
+
+    # find all models that start with Foo
+    my @foo_models = $c->model(qr{^Foo});
+
+## $c->view($name)
+
+Gets a [Catalyst::View](https://metacpan.org/pod/Catalyst::View) instance by name.
+
+    $c->view('Foo')->do_stuff;
+
+Any extra arguments are directly passed to ACCEPT\_CONTEXT.
+
+If the name is omitted, it will look for
+ - a view object in $c->stash->{current\_view\_instance}, then
+ - a view name in $c->stash->{current\_view}, then
+ - a config setting 'default\_view', or
+ - check if there is only one view, and return it if that's the case.
+
+If you want to search for views, pass in a regexp as the argument.
+
+    # find all views that start with Foo
+    my @foo_views = $c->view(qr{^Foo});
+
+## $c->controllers
+
+Returns the available names which can be passed to $c->controller
+
+## $c->models
+
+Returns the available names which can be passed to $c->model
+
+## $c->views
+
+Returns the available names which can be passed to $c->view
+
+## $c->comp($name)
+
+## $c->component($name)
+
+Gets a component object by name. This method is not recommended,
+unless you want to get a specific component by full
+class. `$c->controller`, `$c->model`, and `$c->view`
+should be used instead.
+
+If `$name` is a regexp, a list of components matched against the full
+component name will be returned.
+
+If Catalyst can't find a component by name, it will fallback to regex
+matching by default. To disable this behaviour set
+disable\_component\_resolution\_regex\_fallback to a true value.
+
+    __PACKAGE__->config( disable_component_resolution_regex_fallback => 1 );
+
+## CLASS DATA AND HELPER CLASSES
+
+## $c->config
+
+Returns or takes a hashref containing the application's configuration.
+
+    __PACKAGE__->config( { db => 'dsn:SQLite:foo.db' } );
+
+You can also use a `YAML`, `XML` or [Config::General](https://metacpan.org/pod/Config::General) config file
+like `myapp.conf` in your applications home directory. See
+[Catalyst::Plugin::ConfigLoader](https://metacpan.org/pod/Catalyst::Plugin::ConfigLoader).
+
+### Cascading configuration
+
+The config method is present on all Catalyst components, and configuration
+will be merged when an application is started. Configuration loaded with
+[Catalyst::Plugin::ConfigLoader](https://metacpan.org/pod/Catalyst::Plugin::ConfigLoader) takes precedence over other configuration,
+followed by configuration in your top level `MyApp` class. These two
+configurations are merged, and then configuration data whose hash key matches a
+component name is merged with configuration for that component.
+
+The configuration for a component is then passed to the `new` method when a
+component is constructed.
+
+For example:
+
+    MyApp->config({ 'Model::Foo' => { bar => 'baz', overrides => 'me' } });
+    MyApp::Model::Foo->config({ quux => 'frob', overrides => 'this' });
+
+will mean that `MyApp::Model::Foo` receives the following data when
+constructed:
+
+    MyApp::Model::Foo->new({
+        bar => 'baz',
+        quux => 'frob',
+        overrides => 'me',
+    });
+
+It's common practice to use a Moose attribute
+on the receiving component to access the config value.
+
+    package MyApp::Model::Foo;
+
+    use Moose;
+
+    # this attr will receive 'baz' at construction time
+    has 'bar' => (
+        is  => 'rw',
+        isa => 'Str',
+    );
+
+You can then get the value 'baz' by calling $c->model('Foo')->bar
+(or $self->bar inside code in the model).
+
+**NOTE:** you MUST NOT call `$self->config` or `__PACKAGE__->config`
+as a way of reading config within your code, as this **will not** give you the
+correctly merged config back. You **MUST** take the config values supplied to
+the constructor and use those instead.
+
+## $c->log
+
+Returns the logging object instance. Unless it is already set, Catalyst
+sets this up with a [Catalyst::Log](https://metacpan.org/pod/Catalyst::Log) object. To use your own log class,
+set the logger with the `__PACKAGE__->log` method prior to calling
+`__PACKAGE__->setup`.
+
+    __PACKAGE__->log( MyLogger->new );
+    __PACKAGE__->setup;
+
+And later:
+
+    $c->log->info( 'Now logging with my own logger!' );
+
+Your log class should implement the methods described in
+[Catalyst::Log](https://metacpan.org/pod/Catalyst::Log).
+
+## has\_encoding
+
+Returned True if there's a valid encoding
+
+## clear\_encoding
+
+Clears the encoding for the current context
+
+## encoding
+
+Sets or gets the application encoding.  Setting encoding takes either an
+Encoding object or a string that we try to resolve via [Encode::find\_encoding](https://metacpan.org/pod/Encode::find_encoding).
+
+You would expect to get the encoding object back if you attempt to set it.  If
+there is a failure you will get undef returned and an error message in the log.
+
+## $c->debug
+
+Returns 1 if debug mode is enabled, 0 otherwise.
+
+You can enable debug mode in several ways:
+
+- By calling myapp\_server.pl with the -d flag
+- With the environment variables MYAPP\_DEBUG, or CATALYST\_DEBUG
+- The -Debug option in your MyApp.pm
+- By declaring `sub debug { 1 }` in your MyApp.pm.
+
+The first three also set the log level to 'debug'.
+
+Calling `$c->debug(1)` has no effect.
+
+## $c->dispatcher
+
+Returns the dispatcher instance. See [Catalyst::Dispatcher](https://metacpan.org/pod/Catalyst::Dispatcher).
+
+## $c->engine
+
+Returns the engine instance. See [Catalyst::Engine](https://metacpan.org/pod/Catalyst::Engine).
+
+## UTILITY METHODS
+
+## $c->path\_to(@path)
+
+Merges `@path` with `$c->config->{home}` and returns a
+[Path::Class::Dir](https://metacpan.org/pod/Path::Class::Dir) object. Note you can usually use this object as
+a filename, but sometimes you will have to explicitly stringify it
+yourself by calling the `->stringify` method.
+
+For example:
+
+    $c->path_to( 'db', 'sqlite.db' );
+
+## MyApp->setup
+
+Initializes the dispatcher and engine, loads any plugins, and loads the
+model, view, and controller components. You may also specify an array
+of plugins to load here, if you choose to not load them in the `use
+Catalyst` line.
+
+    MyApp->setup;
+    MyApp->setup( qw/-Debug/ );
+
+**Note:** You **should not** wrap this method with method modifiers
+or bad things will happen - wrap the `setup_finalize` method instead.
+
+**Note:** You can create a custom setup stage that will execute when the
+application is starting.  Use this to customize setup.
+
+    MyApp->setup(-Custom=value);
+
+    sub setup_custom {
+      my ($class, $value) = @_;
+    }
+
+Can be handy if you want to hook into the setup phase.
+
+## $app->setup\_finalize
+
+A hook to attach modifiers to. This method does not do anything except set the
+`setup_finished` accessor.
+
+Applying method modifiers to the `setup` method doesn't work, because of quirky things done for plugin setup.
+
+Example:
+
+    after setup_finalize => sub {
+        my $app = shift;
+
+        ## do stuff here..
+    };
+
+## $c->uri\_for( $path?, @args?, \\%query\_values? )
+
+## $c->uri\_for( $action, \\@captures?, @args?, \\%query\_values? )
+
+## $c->uri\_for( $action, \[@captures, @args\], \\%query\_values? )
+
+Constructs an absolute [URI](https://metacpan.org/pod/URI) object based on the application root, the
+provided path, and the additional arguments and query parameters provided.
+When used as a string, provides a textual URI.  If you need more flexibility
+than this (i.e. the option to provide relative URIs etc.) see
+[Catalyst::Plugin::SmartURI](https://metacpan.org/pod/Catalyst::Plugin::SmartURI).
+
+If no arguments are provided, the URI for the current action is returned.
+To return the current action and also provide @args, use
+`$c->uri_for( $c->action, @args )`.
+
+If the first argument is a string, it is taken as a public URI path relative
+to `$c->namespace` (if it doesn't begin with a forward slash) or
+relative to the application root (if it does). It is then merged with
+`$c->request->base`; any `@args` are appended as additional path
+components; and any `%query_values` are appended as `?foo=bar` parameters.
+
+If the first argument is a [Catalyst::Action](https://metacpan.org/pod/Catalyst::Action) it represents an action which
+will have its path resolved using `$c->dispatcher->uri_for_action`. The
+optional `\@captures` argument (an arrayref) allows passing the captured
+variables that are needed to fill in the paths of Chained and Regex actions;
+once the path is resolved, `uri_for` continues as though a path was
+provided, appending any arguments or parameters and creating an absolute
+URI.
+
+The captures for the current request can be found in
+`$c->request->captures`, and actions can be resolved using
+`Catalyst::Controller->action_for($name)`. If you have a private action
+path, use `$c->uri_for_action` instead.
+
+    # Equivalent to $c->req->uri
+    $c->uri_for($c->action, $c->req->captures,
+        @{ $c->req->args }, $c->req->params);
+
+    # For the Foo action in the Bar controller
+    $c->uri_for($c->controller('Bar')->action_for('Foo'));
+
+    # Path to a static resource
+    $c->uri_for('/static/images/logo.png');
+
+In general the scheme of the generated URI object will follow the incoming request
+however if your targeted action or action chain has the Scheme attribute it will
+use that instead.
+
+Also, if the targeted Action or Action chain declares Args/CaptureArgs that have
+type constraints, we will require that your proposed URL verify on those declared
+constraints.
+
+## $c->uri\_for\_action( $path, \\@captures\_and\_args?, @args?, \\%query\_values? )
+
+## $c->uri\_for\_action( $action, \\@captures\_and\_args?, @args?, \\%query\_values? )
+
+- $path
+
+    A private path to the Catalyst action you want to create a URI for.
+
+    This is a shortcut for calling `$c->dispatcher->get_action_by_path($path)` and passing the resulting `$action` and the remaining arguments to `$c->uri_for`.
+
+    You can also pass in a Catalyst::Action object, in which case it is passed to
+    `$c->uri_for`.
+
+    Note that although the path looks like a URI that dispatches to the wanted action, it is not a URI, but an internal path to that action.
+
+    For example, if the action looks like:
+
+        package MyApp::Controller::Users;
+
+        sub lst : Path('the-list') {}
+
+    You can use:
+
+        $c->uri_for_action('/users/lst')
+
+    and it will create the URI /users/the-list.
+
+- \\@captures\_and\_args?
+
+    Optional array reference of Captures (i.e. `<CaptureArgs or $c-`req->captures>)
+    and arguments to the request. Usually used with [Catalyst::DispatchType::Chained](https://metacpan.org/pod/Catalyst::DispatchType::Chained)
+    to interpolate all the parameters in the URI.
+
+- @args?
+
+    Optional list of extra arguments - can be supplied in the
+    `\@captures_and_args?` array ref, or here - whichever is easier for your
+    code.
+
+    Your action can have zero, a fixed or a variable number of args (e.g.
+    `Args(1)` for a fixed number or `Args()` for a variable number)..
+
+- \\%query\_values?
+
+    Optional array reference of query parameters to append. E.g.
+
+        { foo => 'bar' }
+
+    will generate
+
+        /rest/of/your/uri?foo=bar
+
+## $c->welcome\_message
+
+Returns the Catalyst welcome HTML page.
+
+## run\_options
+
+Contains a hash of options passed from the application script, including
+the original ARGV the script received, the processed values from that
+ARGV and any extra arguments to the script which were not processed.
+
+This can be used to add custom options to your application's scripts
+and setup your application differently depending on the values of these
+options.
+
+# INTERNAL METHODS
+
+These methods are not meant to be used by end users.
+
+## $c->components
+
+Returns a hash of components.
+
+## $c->context\_class
+
+Returns or sets the context class.
+
+## $c->counter
+
+Returns a hashref containing coderefs and execution counts (needed for
+deep recursion detection).
+
+## $c->depth
+
+Returns the number of actions on the current internal execution stack.
+
+## $c->dispatch
+
+Dispatches a request to actions.
+
+## $c->dispatcher\_class
+
+Returns or sets the dispatcher class.
+
+## $c->dump\_these
+
+Returns a list of 2-element array references (name, structure) pairs
+that will be dumped on the error page in debug mode.
+
+## $c->engine\_class
+
+Returns or sets the engine class.
+
+## $c->execute( $class, $coderef )
+
+Execute a coderef in given class and catch exceptions. Errors are available
+via $c->error.
+
+## $c->finalize
+
+Finalizes the request.
+
+## $c->finalize\_body
+
+Finalizes body.
+
+## $c->finalize\_cookies
+
+Finalizes cookies.
+
+## $c->finalize\_error
+
+Finalizes error.  If there is only one error in ["error"](#error) and it is an object that
+does `as_psgi` or `code` we rethrow the error and presume it caught by middleware
+up the ladder.  Otherwise we return the debugging error page (in debug mode) or we
+return the default error page (production mode).
+
+## $c->finalize\_headers
+
+Finalizes headers.
+
+## $c->finalize\_encoding
+
+Make sure your body is encoded properly IF you set an encoding.  By
+default the encoding is UTF-8 but you can disable it by explicitly setting the
+encoding configuration value to undef.
+
+We can only encode when the body is a scalar.  Methods for encoding via the
+streaming interfaces (such as `write` and `write_fh` on [Catalyst::Response](https://metacpan.org/pod/Catalyst::Response)
+are available).
+
+See ["ENCODING"](#encoding).
+
+## $c->finalize\_output
+
+An alias for finalize\_body.
+
+## $c->finalize\_read
+
+Finalizes the input after reading is complete.
+
+## $c->finalize\_uploads
+
+Finalizes uploads. Cleans up any temporary files.
+
+## $c->get\_action( $action, $namespace )
+
+Gets an action in a given namespace.
+
+## $c->get\_actions( $action, $namespace )
+
+Gets all actions of a given name in a namespace and all parent
+namespaces.
+
+## $app->handle\_request( @arguments )
+
+Called to handle each HTTP request.
+
+## $class->prepare( @arguments )
+
+Creates a Catalyst context from an engine-specific request (Apache, CGI,
+etc.).
+
+## $c->prepare\_action
+
+Prepares action. See [Catalyst::Dispatcher](https://metacpan.org/pod/Catalyst::Dispatcher).
+
+## $c->prepare\_body
+
+Prepares message body.
+
+## $c->prepare\_body\_chunk( $chunk )
+
+Prepares a chunk of data before sending it to [HTTP::Body](https://metacpan.org/pod/HTTP::Body).
+
+See [Catalyst::Engine](https://metacpan.org/pod/Catalyst::Engine).
+
+## $c->prepare\_body\_parameters
+
+Prepares body parameters.
+
+## $c->prepare\_connection
+
+Prepares connection.
+
+## $c->prepare\_cookies
+
+Prepares cookies by ensuring that the attribute on the request
+object has been built.
+
+## $c->prepare\_headers
+
+Prepares request headers by ensuring that the attribute on the request
+object has been built.
+
+## $c->prepare\_parameters
+
+Prepares parameters.
+
+## $c->prepare\_path
+
+Prepares path and base.
+
+## $c->prepare\_query\_parameters
+
+Prepares query parameters.
+
+## $c->log\_request
+
+Writes information about the request to the debug logs.  This includes:
+
+- Request method, path, and remote IP address
+- Query keywords (see ["query\_keywords" in Catalyst::Request](https://metacpan.org/pod/Catalyst::Request#query_keywords))
+- Request parameters
+- File uploads
+
+## $c->log\_response
+
+Writes information about the response to the debug logs by calling
+`$c->log_response_status_line` and `$c->log_response_headers`.
+
+## $c->log\_response\_status\_line($response)
+
+Writes one line of information about the response to the debug logs.  This includes:
+
+- Response status code
+- Content-Type header (if present)
+- Content-Length header (if present)
+
+## $c->log\_response\_headers($headers);
+
+Hook method which can be wrapped by plugins to log the response headers.
+No-op in the default implementation.
+
+## $c->log\_request\_parameters( query => {}, body => {} )
+
+Logs request parameters to debug logs
+
+## $c->log\_request\_uploads
+
+Logs file uploads included in the request to the debug logs.
+The parameter name, filename, file type, and file size are all included in
+the debug logs.
+
+## $c->log\_request\_headers($headers);
+
+Hook method which can be wrapped by plugins to log the request headers.
+No-op in the default implementation.
+
+## $c->log\_headers($type => $headers)
+
+Logs [HTTP::Headers](https://metacpan.org/pod/HTTP::Headers) (either request or response) to the debug logs.
+
+## $c->prepare\_read
+
+Prepares the input for reading.
+
+## $c->prepare\_request
+
+Prepares the engine request.
+
+## $c->prepare\_uploads
+
+Prepares uploads.
+
+## $c->prepare\_write
+
+Prepares the output for writing.
+
+## $c->request\_class
+
+Returns or sets the request class. Defaults to [Catalyst::Request](https://metacpan.org/pod/Catalyst::Request).
+
+## $app->request\_class\_traits
+
+An arrayref of [Moose::Role](https://metacpan.org/pod/Moose::Role)s which are applied to the request class.  
+
+## $app->composed\_request\_class
+
+This is the request class which has been composed with any request\_class\_traits.
+
+## $c->response\_class
+
+Returns or sets the response class. Defaults to [Catalyst::Response](https://metacpan.org/pod/Catalyst::Response).
+
+## $app->response\_class\_traits
+
+An arrayref of [Moose::Role](https://metacpan.org/pod/Moose::Role)s which are applied to the response class.
+
+## $app->composed\_response\_class
+
+This is the request class which has been composed with any response\_class\_traits.
+
+## $c->read( \[$maxlength\] )
+
+Reads a chunk of data from the request body. This method is designed to
+be used in a while loop, reading `$maxlength` bytes on every call.
+`$maxlength` defaults to the size of the request if not specified.
+
+You have to set `MyApp->config(parse_on_demand => 1)` to use this
+directly.
+
+Warning: If you use read(), Catalyst will not process the body,
+so you will not be able to access POST parameters or file uploads via
+$c->request.  You must handle all body parsing yourself.
+
+## $c->run
+
+Starts the engine.
+
+## $c->set\_action( $action, $code, $namespace, $attrs )
+
+Sets an action in a given namespace.
+
+## $c->setup\_actions($component)
+
+Sets up actions for a component.
+
+## $c->setup\_components
+
+This method is called internally to set up the application's components.
+
+It finds modules by calling the [locate\_components](https://metacpan.org/pod/locate_components) method, expands them to
+package names with the [expand\_component\_module](https://metacpan.org/pod/expand_component_module) method, and then installs
+each component into the application.
+
+The `setup_components` config option is passed to both of the above methods.
+
+Installation of each component is performed by the [setup\_component](https://metacpan.org/pod/setup_component) method,
+below.
+
+## $app->setup\_injected\_components
+
+Called by setup\_compoents to setup components that are injected.
+
+## $app->setup\_injected\_component( $injected\_component\_name, $config )
+
+Setup a given injected component.
+
+## $app->inject\_component($MyApp\_Component\_name => \\%args);
+
+Add a component that is injected at setup:
+
+    MyApp->inject_component( 'Model::Foo' => { from_component => 'Common::Foo' } );
+
+Must be called before ->setup.  Expects a component name for your
+current application and \\%args where
+
+- from\_component
+
+    The target component being injected into your application
+
+- roles
+
+    An arrayref of [Moose::Role](https://metacpan.org/pod/Moose::Role)s that are applied to your component.
+
+Example
+
+    MyApp->inject_component(
+      'Model::Foo' => {
+        from_component => 'Common::Model::Foo',
+        roles => ['Role1', 'Role2'],
+      });
+
+## $app->inject\_components
+
+Inject a list of components:
+
+    MyApp->inject_components(
+      'Model::FooOne' => {
+        from_component => 'Common::Model::Foo',
+        roles => ['Role1', 'Role2'],
+      },
+      'Model::FooTwo' => {
+        from_component => 'Common::Model::Foo',
+        roles => ['Role1', 'Role2'],
+      });
+
+## $c->locate\_components( $setup\_component\_config )
+
+This method is meant to provide a list of component modules that should be
+setup for the application.  By default, it will use [Module::Pluggable](https://metacpan.org/pod/Module::Pluggable).
+
+Specify a `setup_components` config option to pass additional options directly
+to [Module::Pluggable](https://metacpan.org/pod/Module::Pluggable). To add additional search paths, specify a key named
+`search_extra` as an array reference. Items in the array beginning with `::`
+will have the application class name prepended to them.
+
+## $c->expand\_component\_module( $component, $setup\_component\_config )
+
+Components found by `locate_components` will be passed to this method, which
+is expected to return a list of component (package) names to be set up.
+
+## $app->delayed\_setup\_component
+
+Returns a coderef that points to a setup\_component instance.  Used
+internally for when you want to delay setup until the first time
+the component is called.
+
+## $c->setup\_component
+
+## $app->config\_for( $component\_name )
+
+Return the application level configuration (which is not yet merged with any
+local component configuration, via $component\_class->config) for the named
+component or component object. Example:
+
+    MyApp->config(
+      'Model::Foo' => { a => 1, b => 2},
+    );
+
+    my $config = MyApp->config_for('MyApp::Model::Foo');
+
+In this case $config is the hashref ` {a=`1, b=>2} >.
+
+This is also handy for looking up configuration for a plugin, to make sure you follow
+existing [Catalyst](https://metacpan.org/pod/Catalyst) standards for where a plugin should put its configuration.
+
+## $c->setup\_dispatcher
+
+Sets up dispatcher.
+
+## $c->setup\_engine
+
+Sets up engine.
+
+## $c->apply\_default\_middlewares
+
+Adds the following [Plack](https://metacpan.org/pod/Plack) middlewares to your application, since they are
+useful and commonly needed:
+
+[Plack::Middleware::LighttpdScriptNameFix](https://metacpan.org/pod/Plack::Middleware::LighttpdScriptNameFix) (if you are using Lighttpd),
+[Plack::Middleware::IIS6ScriptNameFix](https://metacpan.org/pod/Plack::Middleware::IIS6ScriptNameFix) (always applied since this middleware
+is smart enough to conditionally apply itself).
+
+We will also automatically add [Plack::Middleware::ReverseProxy](https://metacpan.org/pod/Plack::Middleware::ReverseProxy) if we notice
+that your HTTP $env variable `REMOTE_ADDR` is '127.0.0.1'.  This is usually
+an indication that your server is running behind a proxy frontend.  However in
+2014 this is often not the case.  We preserve this code for backwards compatibility
+however I **highly** recommend that if you are running the server behind a front
+end proxy that you clearly indicate so with the `using_frontend_proxy` configuration
+setting to true for your environment configurations that run behind a proxy.  This
+way if you change your front end proxy address someday your code would inexplicably
+stop working as expected.
+
+Additionally if we detect we are using Nginx, we add a bit of custom middleware
+to solve some problems with the way that server handles $ENV{PATH\_INFO} and
+$ENV{SCRIPT\_NAME}.
+
+Please **NOTE** that if you do use `using_frontend_proxy` the middleware is now
+adding via `registered_middleware` rather than this method.
+
+If you are using Lighttpd or IIS6 you may wish to apply these middlewares.  In
+general this is no longer a common case but we have this here for backward
+compatibility.
+
+## App->psgi\_app
+
+## App->to\_app
+
+Returns a PSGI application code reference for the catalyst application
+`$c`. This is the bare application created without the `apply_default_middlewares`
+method called.  We do however apply `registered_middleware` since those are
+integral to how [Catalyst](https://metacpan.org/pod/Catalyst) functions.  Also, unlike starting your application
+with a generated server script (via [Catalyst::Devel](https://metacpan.org/pod/Catalyst::Devel) and `catalyst.pl`) we do
+not attempt to return a valid [PSGI](https://metacpan.org/pod/PSGI) application using any existing `${myapp}.psgi`
+scripts in your $HOME directory.
+
+**NOTE** `apply_default_middlewares` was originally created when the first PSGI
+port was done for v5.90000.  These are middlewares that are added to achieve
+backward compatibility with older applications.  If you start your application
+using one of the supplied server scripts (generated with [Catalyst::Devel](https://metacpan.org/pod/Catalyst::Devel) and
+the project skeleton script `catalyst.pl`) we apply `apply_default_middlewares`
+automatically.  This was done so that pre and post PSGI port applications would
+work the same way.
+
+This is what you want to be using to retrieve the PSGI application code
+reference of your Catalyst application for use in a custom `.psgi` or in your
+own created server modules.
+
+## $c->setup\_home
+
+Sets up the home directory.
+
+## $c->setup\_encoding
+
+Sets up the input/output encoding. See [ENCODING](https://metacpan.org/pod/ENCODING)
+
+## handle\_unicode\_encoding\_exception
+
+Hook to let you customize how encoding errors are handled.  By default
+we just throw an exception.  Receives a hashref of debug information.
+Example:
+
+    $c->handle_unicode_encoding_exception({
+        param_value => $value,
+        error_msg => $_,
+            encoding_step => 'params',
+        });
+
+## $c->setup\_log
+
+Sets up log by instantiating a [Catalyst::Log](https://metacpan.org/pod/Catalyst::Log) object and
+passing it to `log()`. Pass in a comma-delimited list of levels to set the
+log to.
+
+This method also installs a `debug` method that returns a true value into the
+catalyst subclass if the "debug" level is passed in the comma-delimited list,
+or if the `$CATALYST_DEBUG` environment variable is set to a true value.
+
+Note that if the log has already been setup, by either a previous call to
+`setup_log` or by a call such as `__PACKAGE__->log( MyLogger->new )`,
+that this method won't actually set up the log object.
+
+## $c->setup\_plugins
+
+Sets up plugins.
+
+## $c->setup\_stats
+
+Sets up timing statistics class.
+
+## $c->registered\_plugins
+
+Returns a sorted list of the plugins which have either been stated in the
+import list.
+
+If passed a given plugin name, it will report a boolean value indicating
+whether or not that plugin is loaded.  A fully qualified name is required if
+the plugin name does not begin with `Catalyst::Plugin::`.
+
+    if ($c->registered_plugins('Some::Plugin')) {
+        ...
+    }
+
+## default\_middleware
+
+Returns a list of instantiated PSGI middleware objects which is the default
+middleware that is active for this application (taking any configuration
+options into account, excluding your custom added middleware via the `psgi_middleware`
+configuration option).  You can override this method if you wish to change
+the default middleware (although do so at risk since some middleware is vital
+to application function.)
+
+The current default middleware list is:
+
+      Catalyst::Middleware::Stash
+      Plack::Middleware::HTTPExceptions
+      Plack::Middleware::RemoveRedundantBody
+      Plack::Middleware::FixMissingBodyInRedirect
+      Plack::Middleware::ContentLength
+      Plack::Middleware::MethodOverride
+      Plack::Middleware::Head
+
+If the configuration setting `using_frontend_proxy` is true we add:
+
+      Plack::Middleware::ReverseProxy
+
+If the configuration setting `using_frontend_proxy_path` is true we add:
+
+      Plack::Middleware::ReverseProxyPath
+
+But **NOTE** that [Plack::Middleware::ReverseProxyPath](https://metacpan.org/pod/Plack::Middleware::ReverseProxyPath) is not a dependency of the
+[Catalyst](https://metacpan.org/pod/Catalyst) distribution so if you want to use this option you should add it to
+your project distribution file.
+
+These middlewares will be added at ["setup\_middleware"](#setup_middleware) during the
+["setup"](#setup) phase of application startup.
+
+## registered\_middlewares
+
+Read only accessor that returns an array of all the middleware in the order
+that they were added (which is the REVERSE of the order they will be applied).
+
+The values returned will be either instances of [Plack::Middleware](https://metacpan.org/pod/Plack::Middleware) or of a
+compatible interface, or a coderef, which is assumed to be inlined middleware
+
+## setup\_middleware (?@middleware)
+
+Read configuration information stored in configuration key `psgi_middleware` or
+from passed @args.
+
+See under ["CONFIGURATION"](#configuration) information regarding `psgi_middleware` and how
+to use it to enable [Plack::Middleware](https://metacpan.org/pod/Plack::Middleware)
+
+This method is automatically called during 'setup' of your application, so
+you really don't need to invoke it.  However you may do so if you find the idea
+of loading middleware via configuration weird :).  For example:
+
+    package MyApp;
+
+    use Catalyst;
+
+    __PACKAGE__->setup_middleware('Head');
+    __PACKAGE__->setup;
+
+When we read middleware definitions from configuration, we reverse the list
+which sounds odd but is likely how you expect it to work if you have prior
+experience with [Plack::Builder](https://metacpan.org/pod/Plack::Builder) or if you previously used the plugin
+[Catalyst::Plugin::EnableMiddleware](https://metacpan.org/pod/Catalyst::Plugin::EnableMiddleware) (which is now considered deprecated)
+
+So basically your middleware handles an incoming request from the first
+registered middleware, down and handles the response from the last middleware
+up.
+
+## registered\_data\_handlers
+
+A read only copy of registered Data Handlers returned as a Hash, where each key
+is a content type and each value is a subref that attempts to decode that content
+type.
+
+## setup\_data\_handlers (?@data\_handler)
+
+Read configuration information stored in configuration key `data_handlers` or
+from passed @args.
+
+See under ["CONFIGURATION"](#configuration) information regarding `data_handlers`.
+
+This method is automatically called during 'setup' of your application, so
+you really don't need to invoke it.
+
+## default\_data\_handlers
+
+Default Data Handlers that come bundled with [Catalyst](https://metacpan.org/pod/Catalyst).  Currently there are
+only two default data handlers, for 'application/json' and an alternative to
+'application/x-www-form-urlencoded' which supposed nested form parameters via
+[CGI::Struct](https://metacpan.org/pod/CGI::Struct) or via [CGI::Struct::XS](https://metacpan.org/pod/CGI::Struct::XS) IF you've installed it.
+
+The 'application/json' data handler is used to parse incoming JSON into a Perl
+data structure.  It used either [JSON::MaybeXS](https://metacpan.org/pod/JSON::MaybeXS) or [JSON](https://metacpan.org/pod/JSON), depending on which
+is installed.  This allows you to fail back to [JSON:PP](JSON:PP), which is a Pure Perl
+JSON decoder, and has the smallest dependency impact.
+
+Because we don't wish to add more dependencies to [Catalyst](https://metacpan.org/pod/Catalyst), if you wish to
+use this new feature we recommend installing [JSON](https://metacpan.org/pod/JSON) or [JSON::MaybeXS](https://metacpan.org/pod/JSON::MaybeXS) in
+order to get the best performance.  You should add either to your dependency
+list (Makefile.PL, dist.ini, cpanfile, etc.)
+
+## $c->stack
+
+Returns an arrayref of the internal execution stack (actions that are
+currently executing).
+
+## $c->stats
+
+Returns the current timing statistics object. By default Catalyst uses
+[Catalyst::Stats](https://metacpan.org/pod/Catalyst::Stats), but can be set otherwise with
+[stats\_class](#c-stats_class).
+
+Even if [-Stats](#stats) is not enabled, the stats object is still
+available. By enabling it with ` $c-`stats->enabled(1) >, it can be used to
+profile explicitly, although MyApp.pm still won't profile nor output anything
+by itself.
+
+## $c->stats\_class
+
+Returns or sets the stats (timing statistics) class. [Catalyst::Stats](https://metacpan.org/pod/Catalyst::Stats) is used by default.
+
+## $app->stats\_class\_traits
+
+A arrayref of [Moose::Role](https://metacpan.org/pod/Moose::Role)s that are applied to the stats\_class before creating it.
+
+## $app->composed\_stats\_class
+
+this is the stats\_class composed with any 'stats\_class\_traits'.
+
+## $c->use\_stats
+
+Returns 1 when [stats collection](#stats) is enabled.
+
+Note that this is a static method, not an accessor and should be overridden
+by declaring `sub use_stats { 1 }` in your MyApp.pm, not by calling `$c->use_stats(1)`.
+
+## $c->write( $data )
+
+Writes $data to the output stream. When using this method directly, you
+will need to manually set the `Content-Length` header to the length of
+your output data, if known.
+
+## version
+
+Returns the Catalyst version number. Mostly useful for "powered by"
+messages in template systems.
+
+# CONFIGURATION
+
+There are a number of 'base' config variables which can be set:
+
+- `always_catch_http_exceptions` - As of version 5.90060 Catalyst
+rethrows errors conforming to the interface described by
+[Plack::Middleware::HTTPExceptions](https://metacpan.org/pod/Plack::Middleware::HTTPExceptions) and lets the middleware deal with it.
+Set true to get the deprecated behaviour and have Catalyst catch HTTP exceptions.
+- `default_model` - The default model picked if you say `$c->model`. See ["$c->model($name)"](#c-model-name).
+- `default_view` - The default view to be rendered or returned when `$c->view` is called. See ["$c->view($name)"](#c-view-name).
+- `disable_component_resolution_regex_fallback` - Turns
+off the deprecated component resolution functionality so
+that if any of the component methods (e.g. `$c->controller('Foo')`)
+are called then regex search will not be attempted on string values and
+instead `undef` will be returned.
+- `home` - The application home directory. In an uninstalled application,
+this is the top level application directory. In an installed application,
+this will be the directory containing `MyApp.pm`.
+- `ignore_frontend_proxy` - See ["PROXY SUPPORT"](#proxy-support)
+- `name` - The name of the application in debug messages and the debug and
+welcome screens
+- `parse_on_demand` - The request body (for example file uploads) will not be parsed
+until it is accessed. This allows you to (for example) check authentication (and reject
+the upload) before actually receiving all the data. See ["ON-DEMAND PARSER"](#on-demand-parser)
+- `root` - The root directory for templates. Usually this is just a
+subdirectory of the home directory, but you can set it to change the
+templates to a different directory.
+- `search_extra` - Array reference passed to Module::Pluggable to for additional
+namespaces from which components will be loaded (and constructed and stored in
+`$c->components`).
+- `show_internal_actions` - If true, causes internal actions such as `_DISPATCH`
+to be shown in hit debug tables in the test server.
+- `use_request_uri_for_path` - Controls if the `REQUEST_URI` or `PATH_INFO` environment
+variable should be used for determining the request path.
+
+    Most web server environments pass the requested path to the application using environment variables,
+    from which Catalyst has to reconstruct the request base (i.e. the top level path to / in the application,
+    exposed as `$c->request->base`) and the request path below that base.
+
+    There are two methods of doing this, both of which have advantages and disadvantages. Which method is used
+    is determined by the `$c->config(use_request_uri_for_path)` setting (which can either be true or false).
+
+    - use\_request\_uri\_for\_path => 0
+
+        This is the default (and the) traditional method that Catalyst has used for determining the path information.
+        The path is generated from a combination of the `PATH_INFO` and `SCRIPT_NAME` environment variables.
+        The allows the application to behave correctly when `mod_rewrite` is being used to redirect requests
+        into the application, as these variables are adjusted by mod\_rewrite to take account for the redirect.
+
+        However this method has the major disadvantage that it is impossible to correctly decode some elements
+        of the path, as RFC 3875 says: "`Unlike a URI path, the PATH_INFO is not URL-encoded, and cannot
+        contain path-segment parameters.`" This means PATH\_INFO is **always** decoded, and therefore Catalyst
+        can't distinguish / vs %2F in paths (in addition to other encoded values).
+
+    - use\_request\_uri\_for\_path => 1
+
+        This method uses the `REQUEST_URI` and `SCRIPT_NAME` environment variables. As `REQUEST_URI` is never
+        decoded, this means that applications using this mode can correctly handle URIs including the %2F character
+        (i.e. with `AllowEncodedSlashes` set to `On` in Apache).
+
+        Given that this method of path resolution is probably more correct, it is recommended that you use
+        this unless you have a specific need to deploy your application in a non-standard environment, and you are
+        aware of the implications of not being able to handle encoded URI paths correctly.
+
+        However it also means that in a number of cases when the app isn't installed directly at a path, but instead
+        is having paths rewritten into it (e.g. as a .cgi/fcgi in a public\_html directory, with mod\_rewrite in a
+        .htaccess file, or when SSI is used to rewrite pages into the app, or when sub-paths of the app are exposed
+        at other URIs than that which the app is 'normally' based at with `mod_rewrite`), the resolution of
+        `$c->request->base` will be incorrect.
+
+- `using_frontend_proxy` - See ["PROXY SUPPORT"](#proxy-support).
+- `using_frontend_proxy_path` - Enabled [Plack::Middleware::ReverseProxyPath](https://metacpan.org/pod/Plack::Middleware::ReverseProxyPath) on your application (if
+installed, otherwise log an error).  This is useful if your application is not running on the
+'root' (or /) of your host server.  **NOTE** if you use this feature you should add the required
+middleware to your project dependency list since it's not automatically a dependency of [Catalyst](https://metacpan.org/pod/Catalyst).
+This has been done since not all people need this feature and we wish to restrict the growth of
+[Catalyst](https://metacpan.org/pod/Catalyst) dependencies.
+- `encoding` - See ["ENCODING"](#encoding)
+
+    This now defaults to 'UTF-8'.  You my turn it off by setting this configuration
+    value to undef.
+
+- `abort_chain_on_error_fix`
+
+    When there is an error in an action chain, the default behavior is to continue
+    processing the remaining actions and then catch the error upon chain end.  This
+    can lead to running actions when the application is in an unexpected state.  If
+    you have this issue, setting this config value to true will promptly exit a
+    chain when there is an error raised in any action (thus terminating the chain
+    early.)
+
+    use like:
+
+        __PACKAGE__->config(abort_chain_on_error_fix => 1);
+
+    In the future this might become the default behavior.
+
+- `use_hash_multivalue_in_request`
+
+    In [Catalyst::Request](https://metacpan.org/pod/Catalyst::Request) the methods `query_parameters`, `body_parametes`
+    and `parameters` return a hashref where values might be scalar or an arrayref
+    depending on the incoming data.  In many cases this can be undesirable as it
+    leads one to writing defensive code like the following:
+
+        my ($val) = ref($c->req->parameters->{a}) ?
+          @{$c->req->parameters->{a}} :
+            $c->req->parameters->{a};
+
+    Setting this configuration item to true will make [Catalyst](https://metacpan.org/pod/Catalyst) populate the
+    attributes underlying these methods with an instance of [Hash::MultiValue](https://metacpan.org/pod/Hash::MultiValue)
+    which is used by [Plack::Request](https://metacpan.org/pod/Plack::Request) and others to solve this very issue.  You
+    may prefer this behavior to the default, if so enable this option (be warned
+    if you enable it in a legacy application we are not sure if it is completely
+    backwardly compatible).
+
+- `skip_complex_post_part_handling`
+
+    When creating body parameters from a POST, if we run into a multipart POST
+    that does not contain uploads, but instead contains inlined complex data
+    (very uncommon) we cannot reliably convert that into field => value pairs.  So
+    instead we create an instance of [Catalyst::Request::PartData](https://metacpan.org/pod/Catalyst::Request::PartData).  If this causes
+    issue for you, you can disable this by setting `skip_complex_post_part_handling`
+    to true (default is false).  
+
+- `skip_body_param_unicode_decoding`
+
+    Generally we decode incoming POST params based on your declared encoding (the
+    default for this is to decode UTF-8).  If this is causing you trouble and you
+    do not wish to turn all encoding support off (with the `encoding` configuration
+    parameter) you may disable this step atomically by setting this configuration
+    parameter to true.
+
+- `do_not_decode_query`
+
+    If true, then do not try to character decode any wide characters in your
+    request URL query or keywords.  Most readings of the relevant specifications
+    suggest these should be UTF-\* encoded, which is the default that [Catalyst](https://metacpan.org/pod/Catalyst)
+    will use, however if you are creating a lot of URLs manually or have external
+    evil clients, this might cause you trouble.  If you find the changes introduced
+    in Catalyst version 5.90080+ break some of your query code, you may disable 
+    the UTF-8 decoding globally using this configuration.
+
+    This setting takes precedence over `default_query_encoding` and
+    `decode_query_using_global_encoding`
+
+- `default_query_encoding`
+
+    By default we decode query and keywords in your request URL using UTF-8, which
+    is our reading of the relevant specifications.  This setting allows one to
+    specify a fixed value for how to decode your query.  You might need this if
+    you are doing a lot of custom encoding of your URLs and not using UTF-8.
+
+    This setting take precedence over `decode_query_using_global_encoding`.
+
+- `decode_query_using_global_encoding`
+
+    Setting this to true will default your query decoding to whatever your
+    general global encoding is (the default is UTF-8).
+
+- `use_chained_args_0_special_case`
+
+    In older versions of Catalyst, when more than one action matched the same path
+    AND all those matching actions declared Args(0), we'd break the tie by choosing
+    the first action defined.  We now normalized how Args(0) works so that it
+    follows the same rule as Args(N), which is to say when we need to break a tie
+    we choose the LAST action defined.  If this breaks your code and you don't
+    have time to update to follow the new normalized approach, you may set this
+    value to true and it will globally revert to the original chaining behavior.
+
+- `psgi_middleware` - See ["PSGI MIDDLEWARE"](#psgi-middleware).
+- `data_handlers` - See ["DATA HANDLERS"](#data-handlers).
+- `stats_class_traits`
+
+    An arrayref of [Moose::Role](https://metacpan.org/pod/Moose::Role)s that get composed into your stats class.
+
+- `request_class_traits`
+
+    An arrayref of [Moose::Role](https://metacpan.org/pod/Moose::Role)s that get composed into your request class.
+
+- `response_class_traits`
+
+    An arrayref of [Moose::Role](https://metacpan.org/pod/Moose::Role)s that get composed into your response class.
+
+- `inject_components`
+
+    A Hashref of [Catalyst::Component](https://metacpan.org/pod/Catalyst::Component) subclasses that are 'injected' into configuration.
+    For example:
+
+        MyApp->config({
+          inject_components => {
+            'Controller::Err' => { from_component => 'Local::Controller::Errors' },
+            'Model::Zoo' => { from_component => 'Local::Model::Foo' },
+            'Model::Foo' => { from_component => 'Local::Model::Foo', roles => ['TestRole'] },
+          },
+          'Controller::Err' => { a => 100, b=>200, namespace=>'error' },
+          'Model::Zoo' => { a => 2 },
+          'Model::Foo' => { a => 100 },
+        });
+
+    Generally [Catalyst](https://metacpan.org/pod/Catalyst) looks for components in your Model/View or Controller directories.
+    However for cases when you which to use an existing component and you don't need any
+    customization (where for when you can apply a role to customize it) you may inject those
+    components into your application.  Please note any configuration should be done 'in the
+    normal way', with a key under configuration named after the component affix, as in the
+    above example.
+
+    Using this type of injection allows you to construct significant amounts of your application
+    with only configuration!.  This may or may not lead to increased code understanding.
+
+    Please not you may also call the ->inject\_components application method as well, although
+    you must do so BEFORE setup.
+
+# EXCEPTIONS
+
+Generally when you throw an exception inside an Action (or somewhere in
+your stack, such as in a model that an Action is calling) that exception
+is caught by Catalyst and unless you either catch it yourself (via eval
+or something like [Try::Tiny](https://metacpan.org/pod/Try::Tiny) or by reviewing the ["error"](#error) stack, it
+will eventually reach ["finalize\_errors"](#finalize_errors) and return either the debugging
+error stack page, or the default error page.  However, if your exception
+can be caught by [Plack::Middleware::HTTPExceptions](https://metacpan.org/pod/Plack::Middleware::HTTPExceptions), [Catalyst](https://metacpan.org/pod/Catalyst) will
+instead rethrow it so that it can be handled by that middleware (which
+is part of the default middleware).  For example this would allow
+
+    use HTTP::Throwable::Factory 'http_throw';
+
+    sub throws_exception :Local {
+      my ($self, $c) = @_;
+
+      http_throw(SeeOther => { location =>
+        $c->uri_for($self->action_for('redirect')) });
+
+    }
+
+# INTERNAL ACTIONS
+
+Catalyst uses internal actions like `_DISPATCH`, `_BEGIN`, `_AUTO`,
+`_ACTION`, and `_END`. These are by default not shown in the private
+action table, but you can make them visible with a config parameter.
+
+    MyApp->config(show_internal_actions => 1);
+
+# ON-DEMAND PARSER
+
+The request body is usually parsed at the beginning of a request,
+but if you want to handle input yourself, you can enable on-demand
+parsing with a config parameter.
+
+    MyApp->config(parse_on_demand => 1);
+
+# PROXY SUPPORT
+
+Many production servers operate using the common double-server approach,
+with a lightweight frontend web server passing requests to a larger
+backend server. An application running on the backend server must deal
+with two problems: the remote user always appears to be `127.0.0.1` and
+the server's hostname will appear to be `localhost` regardless of the
+virtual host that the user connected through.
+
+Catalyst will automatically detect this situation when you are running
+the frontend and backend servers on the same machine. The following
+changes are made to the request.
+
+    $c->req->address is set to the user's real IP address, as read from
+    the HTTP X-Forwarded-For header.
+
+    The host value for $c->req->base and $c->req->uri is set to the real
+    host, as read from the HTTP X-Forwarded-Host header.
+
+Additionally, you may be running your backend application on an insecure
+connection (port 80) while your frontend proxy is running under SSL.  If there
+is a discrepancy in the ports, use the HTTP header `X-Forwarded-Port` to
+tell Catalyst what port the frontend listens on.  This will allow all URIs to
+be created properly.
+
+In the case of passing in:
+
+    X-Forwarded-Port: 443
+
+All calls to `uri_for` will result in an https link, as is expected.
+
+Obviously, your web server must support these headers for this to work.
+
+In a more complex server farm environment where you may have your
+frontend proxy server(s) on different machines, you will need to set a
+configuration option to tell Catalyst to read the proxied data from the
+headers.
+
+    MyApp->config(using_frontend_proxy => 1);
+
+If you do not wish to use the proxy support at all, you may set:
+
+    MyApp->config(ignore_frontend_proxy => 0);
+
+## Note about psgi files
+
+Note that if you supply your own .psgi file, calling
+`MyApp->psgi_app(@_);`, then **this will not happen automatically**.
+
+You either need to apply [Plack::Middleware::ReverseProxy](https://metacpan.org/pod/Plack::Middleware::ReverseProxy) yourself
+in your psgi, for example:
+
+    builder {
+        enable "Plack::Middleware::ReverseProxy";
+        MyApp->psgi_app
+    };
+
+This will unconditionally add the ReverseProxy support, or you need to call
+`$app = MyApp->apply_default_middlewares($app)` (to conditionally
+apply the support depending upon your config).
+
+See [Catalyst::PSGI](https://metacpan.org/pod/Catalyst::PSGI) for more information.
+
+# THREAD SAFETY
+
+Catalyst has been tested under Apache 2's threading `mpm_worker`,
+`mpm_winnt`, and the standalone forking HTTP server on Windows. We
+believe the Catalyst core to be thread-safe.
+
+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
+[DBD::SQLite](https://metacpan.org/pod/DBD::SQLite), are not thread-safe.
+
+# DATA HANDLERS
+
+The [Catalyst::Request](https://metacpan.org/pod/Catalyst::Request) object uses [HTTP::Body](https://metacpan.org/pod/HTTP::Body) to populate 'classic' HTML
+form parameters and URL search query fields.  However it has become common
+for various alternative content types to be PUT or POSTed to your controllers
+and actions.  People working on RESTful APIs, or using AJAX often use JSON,
+XML and other content types when communicating with an application server.  In
+order to better support this use case, [Catalyst](https://metacpan.org/pod/Catalyst) defines a global configuration
+option, `data_handlers`, which lets you associate a content type with a coderef
+that parses that content type into something Perl can readily access.
+
+       package MyApp::Web;
+    
+       use Catalyst;
+       use JSON::Maybe;
+    
+       __PACKAGE__->config(
+         data_handlers => {
+           'application/json' => sub { local $/; decode_json $_->getline },
+         },
+         ## Any other configuration.
+       );
+    
+       __PACKAGE__->setup;
+
+By default [Catalyst](https://metacpan.org/pod/Catalyst) comes with a generic JSON data handler similar to the
+example given above, which uses [JSON::Maybe](https://metacpan.org/pod/JSON::Maybe) to provide either [JSON::PP](https://metacpan.org/pod/JSON::PP)
+(a pure Perl, dependency free JSON parser) or [Cpanel::JSON::XS](https://metacpan.org/pod/Cpanel::JSON::XS) if you have
+it installed (if you want the faster XS parser, add it to you project Makefile.PL
+or dist.ini, cpanfile, etc.)
+
+The `data_handlers` configuration is a hashref whose keys are HTTP Content-Types
+(matched against the incoming request type using a regexp such as to be case
+insensitive) and whose values are coderefs that receive a localized version of
+`$_` which is a filehandle object pointing to received body.
+
+This feature is considered an early access release and we reserve the right
+to alter the interface in order to provide a performant and secure solution to
+alternative request body content.  Your reports welcomed!
+
+# PSGI MIDDLEWARE
+
+You can define middleware, defined as [Plack::Middleware](https://metacpan.org/pod/Plack::Middleware) or a compatible
+interface in configuration.  Your middleware definitions are in the form of an
+arrayref under the configuration key `psgi_middleware`.  Here's an example
+with details to follow:
+
+       package MyApp::Web;
+    
+       use Catalyst;
+       use Plack::Middleware::StackTrace;
+    
+       my $stacktrace_middleware = Plack::Middleware::StackTrace->new;
+    
+       __PACKAGE__->config(
+         'psgi_middleware', [
+           'Debug',
+           '+MyApp::Custom',
+           $stacktrace_middleware,
+           'Session' => {store => 'File'},
+           sub {
+             my $app = shift;
+             return sub {
+               my $env = shift;
+               $env->{myapp.customkey} = 'helloworld';
+               $app->($env);
+             },
+           },
+         ],
+       );
+    
+       __PACKAGE__->setup;
+
+So the general form is:
+
+    __PACKAGE__->config(psgi_middleware => \@middleware_definitions);
+
+Where `@middleware` is one or more of the following, applied in the REVERSE of
+the order listed (to make it function similarly to [Plack::Builder](https://metacpan.org/pod/Plack::Builder):
+
+Alternatively, you may also define middleware by calling the ["setup\_middleware"](#setup_middleware)
+package method:
+
+    package MyApp::Web;
+
+    use Catalyst;
+
+    __PACKAGE__->setup_middleware( \@middleware_definitions);
+    __PACKAGE__->setup;
+
+In the case where you do both (use 'setup\_middleware' and configuration) the
+package call to setup\_middleware will be applied earlier (in other words its
+middleware will wrap closer to the application).  Keep this in mind since in
+some cases the order of middleware is important.
+
+The two approaches are not exclusive.
+
+- Middleware Object
+
+    An already initialized object that conforms to the [Plack::Middleware](https://metacpan.org/pod/Plack::Middleware)
+    specification:
+
+           my $stacktrace_middleware = Plack::Middleware::StackTrace->new;
+        
+           __PACKAGE__->config(
+             'psgi_middleware', [
+               $stacktrace_middleware,
+             ]);
+        
+        
+
+- coderef
+
+    A coderef that is an inlined middleware:
+
+           __PACKAGE__->config(
+             'psgi_middleware', [
+               sub {
+                 my $app = shift;
+                 return sub {
+                   my $env = shift;
+                   if($env->{PATH_INFO} =~m/forced/) {
+                     Plack::App::File
+                       ->new(file=>TestApp->path_to(qw/share static forced.txt/))
+                       ->call($env);
+                   } else {
+                     return $app->($env);
+                   }
+                },
+             },
+           ]);
+        
+        
+        
+
+- a scalar
+
+    We assume the scalar refers to a namespace after normalizing it using the
+    following rules:
+
+    (1) If the scalar is prefixed with a "+" (as in `+MyApp::Foo`) then the full string
+    is assumed to be 'as is', and we just install and use the middleware.
+
+    (2) If the scalar begins with "Plack::Middleware" or your application namespace
+    (the package name of your Catalyst application subclass), we also assume then
+    that it is a full namespace, and use it.
+
+    (3) Lastly, we then assume that the scalar is a partial namespace, and attempt to
+    resolve it first by looking for it under your application namespace (for example
+    if you application is "MyApp::Web" and the scalar is "MyMiddleware", we'd look
+    under "MyApp::Web::Middleware::MyMiddleware") and if we don't find it there, we
+    will then look under the regular [Plack::Middleware](https://metacpan.org/pod/Plack::Middleware) namespace (i.e. for the
+    previous we'd try "Plack::Middleware::MyMiddleware").  We look under your application
+    namespace first to let you 'override' common [Plack::Middleware](https://metacpan.org/pod/Plack::Middleware) locally, should
+    you find that a good idea.
+
+    Examples:
+
+           package MyApp::Web;
+
+           __PACKAGE__->config(
+             'psgi_middleware', [
+               'Debug',  ## MyAppWeb::Middleware::Debug->wrap or Plack::Middleware::Debug->wrap
+               'Plack::Middleware::Stacktrace', ## Plack::Middleware::Stacktrace->wrap
+               '+MyApp::Custom',  ## MyApp::Custom->wrap
+             ],
+           );
+        
+
+- a scalar followed by a hashref
+
+    Just like the previous, except the following `HashRef` is used as arguments
+    to initialize the middleware object.
+
+        __PACKAGE__->config(
+          'psgi_middleware', [
+             'Session' => {store => 'File'},
+        ]);
+
+Please see [PSGI](https://metacpan.org/pod/PSGI) for more on middleware.
+
+# ENCODING
+
+Starting in [Catalyst](https://metacpan.org/pod/Catalyst) version 5.90080 encoding is automatically enabled
+and set to encode all body responses to UTF8 when possible and applicable.
+Following is documentation on this process.  If you are using an older
+version of [Catalyst](https://metacpan.org/pod/Catalyst) you should review documentation for that version since
+a lot has changed.
+
+By default encoding is now 'UTF-8'.  You may turn it off by setting
+the encoding configuration to undef.
+
+    MyApp->config(encoding => undef);
+
+This is recommended for temporary backwards compatibility only.
+
+Encoding is automatically applied when the content-type is set to
+a type that can be encoded.  Currently we encode when the content type
+matches the following regular expression:
+
+    $content_type =~ /^text|xml$|javascript$/
+
+Encoding is set on the application, but it is copied to the context object
+so that you can override it on a request basis.
+
+Be default we don't automatically encode 'application/json' since the most
+common approaches to generating this type of response (Either via [Catalyst::View::JSON](https://metacpan.org/pod/Catalyst::View::JSON)
+or [Catalyst::Action::REST](https://metacpan.org/pod/Catalyst::Action::REST)) will do so already and we want to avoid double
+encoding issues.
+
+If you are producing JSON response in an unconventional manner (such
+as via a template or manual strings) you should perform the UTF8 encoding
+manually as well such as to conform to the JSON specification.
+
+NOTE: We also examine the value of $c->response->content\_encoding.  If
+you set this (like for example 'gzip', and manually gzipping the body)
+we assume that you have done all the necessary encoding yourself, since
+we cannot encode the gzipped contents.  If you use a plugin like
+[Catalyst::Plugin::Compress](https://metacpan.org/pod/Catalyst::Plugin::Compress) you need to update to a modern version in order
+to have this function correctly  with the new UTF8 encoding code, or you
+can use [Plack::Middleware::Deflater](https://metacpan.org/pod/Plack::Middleware::Deflater) or (probably best) do your compression on
+a front end proxy.
+
+## Methods
+
+- encoding
+
+    Returns an instance of an `Encode` encoding
+
+        print $c->encoding->name
+
+- handle\_unicode\_encoding\_exception ($exception\_context)
+
+    Method called when decoding process for a request fails.
+
+    An `$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 `$exception_context` hash are:
+
+    - param\_value
+
+        The value which was not able to be decoded.
+
+    - error\_msg
+
+        The exception received from [Encode](https://metacpan.org/pod/Encode).
+
+    - encoding\_step
+
+        What type of data was being decoded. Valid values are (currently)
+        `params` - for request parameters / arguments / captures
+        and `uploads` - for request upload filenames.
+
+# SUPPORT
+
+IRC:
+
+    Join #catalyst on irc.perl.org.
+
+Mailing Lists:
+
+    http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/catalyst
+    http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/catalyst-dev
+
+Web:
+
+    http://catalyst.perl.org
+
+Wiki:
+
+    http://dev.catalyst.perl.org
+
+# SEE ALSO
+
+## [Task::Catalyst](https://metacpan.org/pod/Task::Catalyst) - All you need to start with Catalyst
+
+## [Catalyst::Manual](https://metacpan.org/pod/Catalyst::Manual) - The Catalyst Manual
+
+## [Catalyst::Component](https://metacpan.org/pod/Catalyst::Component), [Catalyst::Controller](https://metacpan.org/pod/Catalyst::Controller) - Base classes for components
+
+## [Catalyst::Engine](https://metacpan.org/pod/Catalyst::Engine) - Core engine
+
+## [Catalyst::Log](https://metacpan.org/pod/Catalyst::Log) - Log class.
+
+## [Catalyst::Request](https://metacpan.org/pod/Catalyst::Request) - Request object
+
+## [Catalyst::Response](https://metacpan.org/pod/Catalyst::Response) - Response object
+
+## [Catalyst::Test](https://metacpan.org/pod/Catalyst::Test) - The test suite.
+
+# PROJECT FOUNDER
+
+sri: Sebastian Riedel <sri@cpan.org>
+
+# CONTRIBUTORS
+
+abw: Andy Wardley
+
+acme: Leon Brocard <leon@astray.com>
+
+abraxxa: Alexander Hartmaier <abraxxa@cpan.org>
+
+andrewalker: André Walker <andre@cpan.org>
+
+Andrew Bramble
+
+Andrew Ford <A.Ford@ford-mason.co.uk>
+
+Andrew Ruthven
+
+andyg: Andy Grundman <andy@hybridized.org>
+
+audreyt: Audrey Tang
+
+bricas: Brian Cassidy <bricas@cpan.org>
+
+Caelum: Rafael Kitover <rkitover@io.com>
+
+chansen: Christian Hansen
+
+chicks: Christopher Hicks
+
+Chisel Wright `pause@herlpacker.co.uk`
+
+Danijel Milicevic `me@danijel.de`
+
+davewood: David Schmidt <davewood@cpan.org>
+
+David Kamholz <dkamholz@cpan.org>
+
+David Naughton, `naughton@umn.edu`
+
+David E. Wheeler
+
+dhoss: Devin Austin <dhoss@cpan.org>
+
+dkubb: Dan Kubb <dan.kubb-cpan@onautopilot.com>
+
+Drew Taylor
+
+dwc: Daniel Westermann-Clark <danieltwc@cpan.org>
+
+esskar: Sascha Kiefer
+
+fireartist: Carl Franks <cfranks@cpan.org>
+
+frew: Arthur Axel "fREW" Schmidt <frioux@gmail.com>
+
+gabb: Danijel Milicevic
+
+Gary Ashton Jones
+
+Gavin Henry `ghenry@perl.me.uk`
+
+Geoff Richards
+
+groditi: Guillermo Roditi <groditi@gmail.com>
+
+hobbs: Andrew Rodland <andrew@cleverdomain.org>
+
+ilmari: Dagfinn Ilmari Mannsåker <ilmari@ilmari.org>
+
+jcamacho: Juan Camacho
+
+jester: Jesse Sheidlower `jester@panix.com`
+
+jhannah: Jay Hannah <jay@jays.net>
+
+Jody Belka
+
+Johan Lindstrom
+
+jon: Jon Schutz <jjschutz@cpan.org>
+
+Jonathan Rockway `<jrockway@cpan.org>`
+
+Kieren Diment `kd@totaldatasolution.com`
+
+konobi: Scott McWhirter <konobi@cpan.org>
+
+marcus: Marcus Ramberg <mramberg@cpan.org>
+
+Mischa Spiegelmock <revmischa@cpan.org>
+
+miyagawa: Tatsuhiko Miyagawa <miyagawa@bulknews.net>
+
+mgrimes: Mark Grimes <mgrimes@cpan.org>
+
+mst: Matt S. Trout <mst@shadowcatsystems.co.uk>
+
+mugwump: Sam Vilain
+
+naughton: David Naughton
+
+ningu: David Kamholz <dkamholz@cpan.org>
+
+nothingmuch: Yuval Kogman <nothingmuch@woobling.org>
+
+numa: Dan Sully <daniel@cpan.org>
+
+obra: Jesse Vincent
+
+Octavian Rasnita
+
+omega: Andreas Marienborg
+
+Oleg Kostyuk <cub.uanic@gmail.com>
+
+phaylon: Robert Sedlacek <phaylon@dunkelheit.at>
+
+rafl: Florian Ragwitz <rafl@debian.org>
+
+random: Roland Lammel <lammel@cpan.org>
+
+Robert Sedlacek `<rs@474.at>`
+
+SpiceMan: Marcel Montes
+
+sky: Arthur Bergman
+
+szbalint: Balint Szilakszi <szbalint@cpan.org>
+
+t0m: Tomas Doran <bobtfish@bobtfish.net>
+
+Ulf Edvinsson
+
+vanstyn: Henry Van Styn <vanstyn@cpan.org>
+
+Viljo Marrandi `vilts@yahoo.com`
+
+Will Hawes `info@whawes.co.uk`
+
+willert: Sebastian Willert <willert@cpan.org>
+
+wreis: Wallace Reis <wreis@cpan.org>
+
+Yuval Kogman, `nothingmuch@woobling.org`
+
+rainboxx: Matthias Dietrich, `perl@rainboxx.de`
+
+dd070: Dhaval Dhanani <dhaval070@gmail.com>
+
+Upasana <me@upasana.me>
+
+John Napiorkowski (jnap) <jjnapiork@cpan.org>
+
+# COPYRIGHT
+
+Copyright (c) 2005-2015, the above named PROJECT FOUNDER and CONTRIBUTORS.
+
+# LICENSE
+
+This library is free software. You can redistribute it and/or modify it under
+the same terms as Perl itself.
diff --git a/README.pod b/README.pod
deleted file mode 100644 (file)
index 8d874e7..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-=head1 Welcome to Catalyst
-
-This is the Runtime distribution for the L<Catalyst MVC framework|http://www.catalystframework.org/>.
-
-For more information about Catalyst, write
-
-    perldoc Catalyst
-
-at the command line, or visit http://www.catalystframework.org/.
-
-=head2 Getting Started
-
-1. Install Catalyst if you haven't yet:
-
-    cpanm Catalyst
-
-2. Create a new catalyst application:
-
-    catalyst.pl DemoApp
-
-3. Change the directory to the newly created directory and start the built-in developer server
-
-    cd DemoApp; plackup -Ilib demoapp.psgi
-
-4. Go to http://localhost:5000 and you'll see the default welcome page.
-
-=head2 Resources
-
-You can also install L<Catalyst::Manual|https://metacpan.org/module/Catalyst::Manual> 
-from CPAN for more comprehensive information.
-
-If you are going to write your own Catalyst application, you will need to
-install L<Catalyst::Devel|https://metacpan.org/module/Catalyst::Devel>.
-Afterwards run I<catalyst.pl> for more information about creating your first
-app.
-
-=head2 Contributing
-
-If you would like to contribute to Catalyst, please 
-L<join us|http://chat.mibbit.com/#catalyst@irc.perl.org> on IRC,
-or visit the L<mailing list|http://lists.scsys.co.uk/mailman/listinfo/catalyst>.
diff --git a/TODO b/TODO
deleted file mode 100644 (file)
index 999e237..0000000
--- a/TODO
+++ /dev/null
@@ -1,59 +0,0 @@
-# Known Bugs:
-
-   - Bug ->go or ->visit causes actions which have Args or CaptureArgs called
-     twice when called via ->go or ->visit.
-
-     Test app: http://github.com/bobtfish/catalyst-app-bug-go_chain/tree/master
-
-# Compatibility warnings to add:
-
-  - $self->config should warn as config should only ever be called as a
-    class method (TESTS).
-
-# Proposed functionality / feature additions:
-
-## Log setup needs to be less lame
-
-So Catalyst::Plugin::Log::* can die
-in a fire. Having $c->log_class would be a good start. kane volunteered
-to do some of this.
-
-Simple example: Catalyst::Plugin::Log::Colorful should just be a
-subclass of Catalyst::Log, no ::Plugin:: needed.
-
-See also: Catalyst::Plugin::Log::Dispatch and
-http://github.com/willert/catalyst-plugin-log4perl-simple/tree
-
-## throw away the restarter and allow using the restarters Plack provides
-
-## be smarter about how we use PSGI - not every response needs to be delayed
-    and streaming
-
-#  The horrible hack for plugin setup - replacing it:
-
- * Have a look at the Devel::REPL BEFORE_PLUGIN stuff
-   I wonder if what we need is that combined with plugins-as-roles
-
-# App / ctx split:
-
-  NOTE - these are notes that t0m thought up after doing back compat for
-         catalyst_component_class, may be inaccurate, wrong or missing things
-         bug mst (at least) to correct before trying more than the first 2
-         steps. Please knock yourself out on the first two however :)
-
-  - Eliminate actions in MyApp from the main test suite
-  - Uncomment warning in C::C::register_action_methods, add tests it works
-    by mocking out the logging..
-  - Remove MyApp @ISA controller (ask metaclass if it has attributes, and if
-                                  so you need back compat :/)
-  - Make Catalyst::Context, move the per request stuff in there, handles from
-    main app class to delegate
-  - Make an instance of the app class which is a global variable
-  - Make new instance of the context class, not the app class per-request
-  - Remove the components as class data, move to instance data on the app
-    class (you probably have to do this for _all_ the class data, good luck!)
-  - Make it possible for users to spin up different instances of the app class
-    (with different config etc each)
-  - Profit! (Things like changing the complete app config per vhost, i.e.
-    writing a config loader / app class role which dispatches per vhost to
-    differently configured apps is piss easy)
index 471f9c3..820fc35 100644 (file)
@@ -50,7 +50,7 @@ use Plack::Middleware::RemoveRedundantBody;
 use Catalyst::Middleware::Stash;
 use Plack::Util;
 use Class::Load 'load_class';
-use Encode 2.21 ();
+use Encode 2.21 'decode_utf8', 'encode_utf8';
 
 BEGIN { require 5.008003; }
 
@@ -63,7 +63,9 @@ has request => (
     is => 'rw',
     default => sub {
         my $self = shift;
-        $self->request_class->new($self->_build_request_constructor_args);
+        my $class = ref $self;
+        my $composed_request_class = $class->composed_request_class;
+        return $composed_request_class->new( $self->_build_request_constructor_args);
     },
     lazy => 1,
 );
@@ -77,17 +79,51 @@ sub _build_request_constructor_args {
     \%p;
 }
 
+sub composed_request_class {
+  my $class = shift;
+  my @traits = (@{$class->request_class_traits||[]}, @{$class->config->{request_class_traits}||[]});
+
+  # For each trait listed, figure out what the namespace is.  First we try the $trait
+  # as it is in the config.  Then try $MyApp::TraitFor::Request:$trait. Last we try
+  # Catalyst::TraitFor::Request::$trait.  If none load, throw error.
+
+  my $trait_ns = 'TraitFor::Request';
+  my @normalized_traits = map {
+    Class::Load::load_first_existing_class($_, $class.'::'.$trait_ns.'::'. $_, 'Catalyst::'.$trait_ns.'::'.$_)
+  } @traits;
+
+  return $class->_composed_request_class ||
+    $class->_composed_request_class(Moose::Util::with_traits($class->request_class, @normalized_traits));
+}
+
 has response => (
     is => 'rw',
     default => sub {
         my $self = shift;
-        $self->response_class->new($self->_build_response_constructor_args);
+        my $class = ref $self;
+        my $composed_response_class = $class->composed_response_class;
+        return $composed_response_class->new( $self->_build_response_constructor_args);
     },
     lazy => 1,
 );
 sub _build_response_constructor_args {
-    my $self = shift;
-    { _log => $self->log };
+    return +{
+      _log => $_[0]->log,
+      encoding => $_[0]->encoding,
+    };
+}
+
+sub composed_response_class {
+  my $class = shift;
+  my @traits = (@{$class->response_class_traits||[]}, @{$class->config->{response_class_traits}||[]});
+
+  my $trait_ns = 'TraitFor::Response';
+  my @normalized_traits = map {
+    Class::Load::load_first_existing_class($_, $class.'::'.$trait_ns.'::'. $_, 'Catalyst::'.$trait_ns.'::'.$_)
+  } @traits;
+
+  return $class->_composed_response_class ||
+    $class->_composed_response_class(Moose::Util::with_traits($class->response_class, @normalized_traits));
 }
 
 has namespace => (is => 'rw');
@@ -112,22 +148,40 @@ our $RECURSION = 1000;
 our $DETACH    = Catalyst::Exception::Detach->new;
 our $GO        = Catalyst::Exception::Go->new;
 
-#I imagine that very few of these really need to be class variables. if any.
+#I imagine that very few of these really 
+#need to be class variables. if any.
 #maybe we should just make them attributes with a default?
 __PACKAGE__->mk_classdata($_)
   for qw/components arguments dispatcher engine log dispatcher_class
   engine_loader context_class request_class response_class stats_class
   setup_finished _psgi_app loading_psgi_file run_options _psgi_middleware
-  _data_handlers _encoding _encode_check/;
+  _data_handlers _encoding _encode_check finalized_default_middleware
+  request_class_traits response_class_traits stats_class_traits
+  _composed_request_class _composed_response_class _composed_stats_class/;
 
 __PACKAGE__->dispatcher_class('Catalyst::Dispatcher');
 __PACKAGE__->request_class('Catalyst::Request');
 __PACKAGE__->response_class('Catalyst::Response');
 __PACKAGE__->stats_class('Catalyst::Stats');
+
+sub composed_stats_class {
+  my $class = shift;
+  my @traits = (@{$class->stats_class_traits||[]}, @{$class->config->{stats_class_traits}||[]});
+
+  my $trait_ns = 'TraitFor::Stats';
+  my @normalized_traits = map {
+    Class::Load::load_first_existing_class($_, $class.'::'.$trait_ns.'::'. $_, 'Catalyst::'.$trait_ns.'::'.$_)
+  } @traits;
+
+  return $class->_composed_stats_class ||
+    $class->_composed_stats_class(Moose::Util::with_traits($class->stats_class, @normalized_traits));
+}
+
 __PACKAGE__->_encode_check(Encode::FB_CROAK | Encode::LEAVE_SRC);
 
 # Remember to update this in Catalyst::Runtime as well!
-our $VERSION = '5.90073';
+our $VERSION = '5.90101';
+$VERSION = eval $VERSION if $VERSION =~ /_/; # numify for warning-free dev releases
 
 sub import {
     my ( $class, @arguments ) = @_;
@@ -168,6 +222,11 @@ sub _application { $_[0] }
 
 Catalyst - The Elegant MVC Web Application Framework
 
+=for html
+<a href="https://badge.fury.io/pl/Catalyst-Runtime"><img src="https://badge.fury.io/pl/Catalyst-Runtime.svg" alt="CPAN version" height="18"></a>
+<a href="https://travis-ci.org/perl-catalyst/catalyst-runtime/"><img src="https://api.travis-ci.org/perl-catalyst/catalyst-runtime.png" alt="Catalyst></a>
+<a href="http://cpants.cpanauthors.org/dist/Catalyst-Runtime"><img src="http://cpants.cpanauthors.org/dist/Catalyst-Runtime.png" alt='Kwalitee Score' /></a>
+
 =head1 SYNOPSIS
 
 See the L<Catalyst::Manual> distribution for comprehensive
@@ -495,10 +554,23 @@ Catalyst).
     # stash is automatically passed to the view for use in a template
     $c->forward( 'MyApp::View::TT' );
 
+The stash hash is currently stored in the PSGI C<$env> and is managed by
+L<Catalyst::Middleware::Stash>.  Since it's part of the C<$env> items in
+the stash can be accessed in sub applications mounted under your main
+L<Catalyst> application.  For example if you delegate the response of an
+action to another L<Catalyst> application, that sub application will have
+access to all the stash keys of the main one, and if can of course add
+more keys of its own.  However those new keys will not 'bubble' back up
+to the main application.
+
+For more information the best thing to do is to review the test case:
+t/middleware-stash.t in the distribution /t directory.
+
 =cut
 
 sub stash {
   my $c = shift;
+  $c->log->error("You are requesting the stash but you don't have a context") unless blessed $c;
   return Catalyst::Middleware::Stash::get_stash($c->req->env)->(@_);
 }
 
@@ -518,6 +590,9 @@ Add a new error.
 
     $c->error('Something bad happened');
 
+Calling this will always return an arrayref (if there are no errors it
+will be an empty arrayref.
+
 =cut
 
 sub error {
@@ -562,6 +637,49 @@ Returns true if you have errors
 
 sub has_errors { scalar(@{shift->error}) ? 1:0 }
 
+=head2 $c->last_error
+
+Returns the most recent error in the stack (the one most recently added...)
+or nothing if there are no errors.  This does not modify the contents of the
+error stack.
+
+=cut
+
+sub last_error {
+  my (@errs) = @{shift->error};
+  return scalar(@errs) ? $errs[-1]: undef;
+}
+
+=head2 shift_errors
+
+shifts the most recently added error off the error stack and returns it.  Returns
+nothing if there are no more errors.
+
+=cut
+
+sub shift_errors {
+    my ($self) = @_;
+    my @errors = @{$self->error};
+    my $err = shift(@errors);
+    $self->{error} = \@errors;
+    return $err;
+}
+
+=head2 pop_errors
+
+pops the most recently added error off the error stack and returns it.  Returns
+nothing if there are no more errors.
+
+=cut
+
+sub pop_errors {
+    my ($self) = @_;
+    my @errors = @{$self->error};
+    my $err = pop(@errors);
+    $self->{error} = \@errors;
+    return $err;
+}
+
 sub _comp_search_prefixes {
     my $c = shift;
     return map $c->components->{ $_ }, $c->_comp_names_search_prefixes(@_);
@@ -644,13 +762,20 @@ sub _comp_names {
 }
 
 # Filter a component before returning by calling ACCEPT_CONTEXT if available
+
 sub _filter_component {
     my ( $c, $comp, @args ) = @_;
 
+    if(ref $comp eq 'CODE') {
+      $comp = $comp->();
+    }
+
     if ( eval { $comp->can('ACCEPT_CONTEXT'); } ) {
-        return $comp->ACCEPT_CONTEXT( $c, @args );
+      return $comp->ACCEPT_CONTEXT( $c, @args );
     }
 
+    $c->log->warn("You called component '${\$comp->catalyst_component_name}' with arguments [@args], but this component does not ACCEPT_CONTEXT, so args are ignored.") if scalar(@args) && $c->debug;
+
     return $comp;
 }
 
@@ -702,7 +827,8 @@ Gets a L<Catalyst::Model> instance by name.
 
     $c->model('Foo')->do_stuff;
 
-Any extra arguments are directly passed to ACCEPT_CONTEXT.
+Any extra arguments are directly passed to ACCEPT_CONTEXT, if the model
+defines ACCEPT_CONTEXT.  If it does not, the args are discarded.
 
 If the name is omitted, it will look for
  - a model object in $c->stash->{current_model_instance}, then
@@ -1013,17 +1139,47 @@ And later:
 Your log class should implement the methods described in
 L<Catalyst::Log>.
 
+=head2 has_encoding
+
+Returned True if there's a valid encoding
+
+=head2 clear_encoding
+
+Clears the encoding for the current context
+
 =head2 encoding
 
-Sets or gets the application encoding.
+Sets or gets the application encoding.  Setting encoding takes either an
+Encoding object or a string that we try to resolve via L<Encode::find_encoding>.
+
+You would expect to get the encoding object back if you attempt to set it.  If
+there is a failure you will get undef returned and an error message in the log.
 
 =cut
 
+sub has_encoding { shift->encoding ? 1:0 }
+
+sub clear_encoding {
+    my $c = shift;
+    if(blessed $c) {
+        $c->encoding(undef);
+    } else {
+        $c->log->error("You can't clear encoding on the application");
+    }
+}
+
 sub encoding {
     my $c = shift;
     my $encoding;
 
     if ( scalar @_ ) {
+
+        # Don't let one change this once we are too far into the response
+        if(blessed $c && $c->res->finalized_headers) {
+          Carp::croak("You may not change the encoding once the headers are finalized");
+          return;
+        }
+
         # Let it be set to undef
         if (my $wanted = shift)  {
             $encoding = Encode::find_encoding($wanted)
@@ -1139,6 +1295,17 @@ Catalyst> line.
 B<Note:> You B<should not> wrap this method with method modifiers
 or bad things will happen - wrap the C<setup_finalize> method instead.
 
+B<Note:> You can create a custom setup stage that will execute when the
+application is starting.  Use this to customize setup.
+
+    MyApp->setup(-Custom=value);
+
+    sub setup_custom {
+      my ($class, $value) = @_;
+    }
+
+Can be handy if you want to hook into the setup phase.
+
 =cut
 
 sub setup {
@@ -1275,6 +1442,7 @@ EOF
           : $class->log->debug(q/Couldn't find home/);
 
         my $column_width = Catalyst::Utils::term_width() - 8 - 9;
+
         my $t = Text::SimpleTable->new( [ $column_width, 'Class' ], [ 8, 'Type' ] );
         for my $comp ( sort keys %{ $class->components } ) {
             my $type = ref $class->components->{$comp} ? 'instance' : 'class';
@@ -1331,9 +1499,11 @@ sub setup_finalize {
     $class->setup_finished(1);
 }
 
-=head2 $c->uri_for( $path?, @args?, \%query_values? )
+=head2 $c->uri_for( $path?, @args?, \%query_values?, \$fragment? )
 
-=head2 $c->uri_for( $action, \@captures?, @args?, \%query_values? )
+=head2 $c->uri_for( $action, \@captures?, @args?, \%query_values?, \$fragment? )
+
+=head2 $c->uri_for( $action, [@captures, @args], \%query_values?, \$fragment? )
 
 Constructs an absolute L<URI> object based on the application root, the
 provided path, and the additional arguments and query parameters provided.
@@ -1351,6 +1521,15 @@ relative to the application root (if it does). It is then merged with
 C<< $c->request->base >>; any C<@args> are appended as additional path
 components; and any C<%query_values> are appended as C<?foo=bar> parameters.
 
+B<NOTE> If you are using this 'stringy' first argument, we skip encoding and
+allow you to declare something like:
+
+    $c->uri_for('/foo/bar#baz')
+
+Where 'baz' is a URI fragment.  We consider this first argument string to be
+'expert' mode where you are expected to create a valid URL and we for the most
+part just pass it through without a lot of internal effort to escape and encode.
+
 If the first argument is a L<Catalyst::Action> it represents an action which
 will have its path resolved using C<< $c->dispatcher->uri_for_action >>. The
 optional C<\@captures> argument (an arrayref) allows passing the captured
@@ -1374,6 +1553,14 @@ path, use C<< $c->uri_for_action >> instead.
   # Path to a static resource
   $c->uri_for('/static/images/logo.png');
 
+In general the scheme of the generated URI object will follow the incoming request
+however if your targeted action or action chain has the Scheme attribute it will
+use that instead.
+
+Also, if the targeted Action or Action chain declares Args/CaptureArgs that have
+type constraints, we will require that your proposed URL verify on those declared
+constraints.
+
 =cut
 
 sub uri_for {
@@ -1385,17 +1572,27 @@ sub uri_for {
         $path .= '/';
     }
 
-    undef($path) if (defined $path && $path eq '');
+    my $fragment =  ((scalar(@args) && ref($args[-1]) eq 'SCALAR') ? pop @args : undef );
+
+    unless(blessed $path) {
+      if (defined($path) and $path =~ s/#(.+)$//)  {
+        if(defined($1) and $fragment) {
+          carp "Abiguious fragment declaration: You cannot define a fragment in '$path' and as an argument '$fragment'";
+        }
+        if(defined($1)) {
+          $fragment = $1;
+        }
+      }
+    }
 
     my $params =
       ( scalar @args && ref $args[$#args] eq 'HASH' ? pop @args : {} );
 
+    undef($path) if (defined $path && $path eq '');
+
     carp "uri_for called with undef argument" if grep { ! defined $_ } @args;
-    foreach my $arg (@args) {
-        utf8::encode($arg) if utf8::is_utf8($arg);
-        $arg =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go;
-    }
 
+    my $target_action = $path->$_isa('Catalyst::Action') ? $path : undef;
     if ( $path->$_isa('Catalyst::Action') ) { # action object
         s|/|%2F|g for @args;
         my $captures = [ map { s|/|%2F|g; $_; }
@@ -1403,27 +1600,37 @@ sub uri_for {
                          ? @{ shift(@args) }
                          : ()) ];
 
-        foreach my $capture (@$captures) {
-            utf8::encode($capture) if utf8::is_utf8($capture);
-            $capture =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go;
-        }
-
         my $action = $path;
+        my $expanded_action = $c->dispatcher->expand_action( $action );
+        my $num_captures = $expanded_action->number_of_captures;
+
         # ->uri_for( $action, \@captures_and_args, \%query_values? )
         if( !@args && $action->number_of_args ) {
-            my $expanded_action = $c->dispatcher->expand_action( $action );
+          unshift @args, splice @$captures, $num_captures;
+        }
 
-            my $num_captures = $expanded_action->number_of_captures;
-            unshift @args, splice @$captures, $num_captures;
+        if($num_captures) {
+          unless($expanded_action->match_captures_constraints($c, $captures)) {
+            carp "captures [@{$captures}] do not match the type constraints in actionchain ending with '$expanded_action'";
+            return;
+          }
         }
 
-       $path = $c->dispatcher->uri_for_action($action, $captures);
+        $path = $c->dispatcher->uri_for_action($action, $captures);
         if (not defined $path) {
             $c->log->debug(qq/Can't find uri_for action '$action' @$captures/)
                 if $c->debug;
             return undef;
         }
         $path = '/' if $path eq '';
+
+        # At this point @encoded_args is the remaining Args (all captures removed).
+        if($expanded_action->has_args_constraints) {
+          unless($expanded_action->match_args($c,\@args)) {
+             carp "args [@args] do not match the type constraints in action '$expanded_action'";
+             return;
+          }
+        }
     }
 
     unshift(@args, $path);
@@ -1444,30 +1651,61 @@ sub uri_for {
     my ($base, $class) = ('/', 'URI::_generic');
     if(blessed($c)) {
       $base = $c->req->base;
-      $class = ref($base);
+      if($target_action) {
+        $target_action = $c->dispatcher->expand_action($target_action);
+        if(my $s = $target_action->scheme) {
+          $s = lc($s);
+          $class = "URI::$s";
+          $base->scheme($s);
+        } else {
+          $class = ref($base);
+        }
+      } else {
+        $class = ref($base);
+      }
+
       $base =~ s{(?<!/)$}{/};
     }
 
     my $query = '';
-
     if (my @keys = keys %$params) {
       # somewhat lifted from URI::_query's query_form
       $query = '?'.join('&', map {
           my $val = $params->{$_};
-          s/([;\/?:@&=+,\$\[\]%])/$URI::Escape::escapes{$1}/go;
+          #s/([;\/?:@&=+,\$\[\]%])/$URI::Escape::escapes{$1}/go; ## Commented out because seems to lead to double encoding - JNAP
           s/ /+/g;
           my $key = $_;
           $val = '' unless defined $val;
           (map {
               my $param = "$_";
-              utf8::encode( $param ) if utf8::is_utf8($param);
+              $param = encode_utf8($param);
               # using the URI::Escape pattern here so utf8 chars survive
               $param =~ s/([^A-Za-z0-9\-_.!~*'() ])/$URI::Escape::escapes{$1}/go;
               $param =~ s/ /+/g;
+
+              $key = encode_utf8($key);
+              # using the URI::Escape pattern here so utf8 chars survive
+              $key =~ s/([^A-Za-z0-9\-_.!~*'() ])/$URI::Escape::escapes{$1}/go;
+              $key =~ s/ /+/g;
+
               "${key}=$param"; } ( ref $val eq 'ARRAY' ? @$val : $val ));
       } @keys);
     }
 
+    $base = encode_utf8 $base;
+    $base =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go;
+    $args = encode_utf8 $args;
+    $args =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go;
+
+    if(defined $fragment) {
+      if(blessed $path) {
+        $fragment = encode_utf8(${$fragment});
+        $fragment =~ s/([^A-Za-z0-9\-_.!~*'() ])/$URI::Escape::escapes{$1}/go;
+        $fragment =~ s/ /+/g;
+      }
+      $query .= "#$fragment";
+    }
+
     my $res = bless(\"${base}${args}${query}", $class);
     $res;
 }
@@ -1790,7 +2028,7 @@ sub execute {
 
     if ( my $error = $@ ) {
         #rethow if this can be handled by middleware
-        if(blessed $error && ($error->can('as_psgi') || $error->can('code'))) {
+        if ( $c->_handle_http_exception($error) ) {
             foreach my $err (@{$c->error}) {
                 $c->log->error($err);
             }
@@ -1903,7 +2141,7 @@ sub finalize {
 
     # Support skipping finalize for psgix.io style 'jailbreak'.  Used to support
     # stuff like cometd and websockets
-    
+
     if($c->request->_has_io_fh) {
       $c->log_response;
       return;
@@ -1971,10 +2209,7 @@ sub finalize_error {
         $c->engine->finalize_error( $c, @_ );
     } else {
         my ($error) = @{$c->error};
-        if(
-          blessed $error &&
-          ($error->can('as_psgi') || $error->can('code'))
-        ) {
+        if ( $c->_handle_http_exception($error) ) {
             # In the case where the error 'knows what it wants', becauses its PSGI
             # aware, just rethow and let middleware catch it
             $error->can('rethrow') ? $error->rethrow : croak $error;
@@ -2009,6 +2244,8 @@ sub finalize_headers {
 
     $c->finalize_cookies;
 
+    # This currently is a NOOP but I don't want to remove it since I guess people
+    # might have Response subclasses that use it for something... (JNAP)
     $c->response->finalize_headers();
 
     # Done
@@ -2017,42 +2254,50 @@ sub finalize_headers {
 
 =head2 $c->finalize_encoding
 
-Make sure your headers and body are encoded properly IF you set an encoding.
+Make sure your body is encoded properly IF you set an encoding.  By
+default the encoding is UTF-8 but you can disable it by explicitly setting the
+encoding configuration value to undef.
+
+We can only encode when the body is a scalar.  Methods for encoding via the
+streaming interfaces (such as C<write> and C<write_fh> on L<Catalyst::Response>
+are available).
+
 See L</ENCODING>.
 
 =cut
 
 sub finalize_encoding {
     my $c = shift;
-
-    my $body = $c->response->body;
-
-    return unless defined($body);
-
-    my $enc = $c->encoding;
-
-    return unless $enc;
-
-    my ($ct, $ct_enc) = $c->response->content_type;
-
-    # Only touch 'text-like' contents
-    return 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;
-        }
-    } else {
-        $c->res->content_type($c->res->content_type . "; charset=" . $enc->mime_name);
+    my $res = $c->res || return;
+
+    # Warn if the set charset is different from the one you put into encoding.  We need
+    # to do this early since encodable_response is false for this condition and we need
+    # to match the debug output for backcompat (there's a test for this...) -JNAP
+    if(
+      $res->content_type_charset and $c->encoding and 
+      (uc($c->encoding->mime_name) ne uc($res->content_type_charset))
+    ) {
+        my $ct = lc($res->content_type_charset);
+        $c->log->debug("Catalyst encoding config is set to encode in '" .
+            $c->encoding->mime_name .
+            "', content type is '$ct', not encoding ");
     }
 
-    # Oh my, I wonder what filehandle responses and streams do... - jnap.
-    # Encode expects plain scalars (IV, NV or PV) and segfaults on ref's
-    $c->response->body( $c->encoding->encode( $body, $c->_encode_check ) )
-        if ref(\$body) eq 'SCALAR';
+    if(
+      ($res->encodable_response) and
+      (defined($res->body)) and
+      (ref(\$res->body) eq 'SCALAR')
+    ) {
+        $c->res->body( $c->encoding->encode( $c->res->body, $c->_encode_check ) );
+
+        # Set the charset if necessary.  This might be a bit bonkers since encodable response
+        # is false when the set charset is not the same as the encoding mimetype (maybe 
+        # confusing action at a distance here..
+        # Don't try to set the charset if one already exists or if headers are already finalized
+        $c->res->content_type($c->res->content_type . "; charset=" . $c->encoding->mime_name)
+          unless($c->res->content_type_charset ||
+                ($c->res->_context && $c->res->finalized_headers && !$c->res->_has_response_cb));
+    }
 }
 
 =head2 $c->finalize_output
@@ -2116,7 +2361,7 @@ sub handle_request {
         $status = $c->finalize;
     } catch {
         #rethow if this can be handled by middleware
-        if(blessed $_ && ($_->can('as_psgi') || $_->can('code'))) {
+        if ( $class->_handle_http_exception($_) ) {
             $_->can('rethrow') ? $_->rethrow : croak $_;
         }
         chomp(my $error = $_);
@@ -2156,8 +2401,8 @@ sub prepare {
 
     $c->response->_context($c);
 
-    #surely this is not the most efficient way to do things...
     $c->stats($class->stats_class->new)->enable($c->use_stats);
+
     if ( $c->debug || $c->config->{enable_catalyst_header} ) {
         $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
     }
@@ -2266,7 +2511,7 @@ Prepares body parameters.
 
 sub prepare_body_parameters {
     my $c = shift;
-    $c->engine->prepare_body_parameters( $c, @_ );
+    $c->request->prepare_body_parameters( $c, @_ );
 }
 
 =head2 $c->prepare_connection
@@ -2360,6 +2605,10 @@ sub log_request {
     $method ||= '';
     $path = '/' unless length $path;
     $address ||= '';
+
+    $path =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
+    $path = decode_utf8($path);
+
     $c->log->debug(qq/"$method" request for "$path" from "$address"/);
 
     $c->log_request_headers($request->headers);
@@ -2545,37 +2794,6 @@ Prepares uploads.
 sub prepare_uploads {
     my $c = shift;
     $c->engine->prepare_uploads( $c, @_ );
-
-    my $enc = $c->encoding;
-    return unless $enc;
-
-    # Uggg we hook prepare uploads to do the encoding crap on post and query
-    # parameters!  Sorry -jnap
-    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
-        for my $inner_value ( ref($value) eq 'ARRAY' ? @{$value} : $value ) {
-            $inner_value->{filename} = try {
-                $enc->decode( $inner_value->{filename}, $c->_encode_check )
-            } catch {
-                $c->handle_unicode_encoding_exception({
-                    param_value => $inner_value->{filename},
-                    error_msg => $_,
-                    encoding_step => 'uploads',
-                });
-            };
-        }
-    }
 }
 
 =head2 $c->prepare_write
@@ -2590,10 +2808,65 @@ sub prepare_write { my $c = shift; $c->engine->prepare_write( $c, @_ ) }
 
 Returns or sets the request class. Defaults to L<Catalyst::Request>.
 
+=head2 $app->request_class_traits
+
+An arrayref of L<Moose::Role>s which are applied to the request class.  You can
+name the full namespace of the role, or a namespace suffix, which will then
+be tried against the following standard namespace prefixes.
+
+    $MyApp::TraitFor::Request::$trait_suffix
+    Catalyst::TraitFor::Request::$trait_suffix
+
+So for example if you set:
+
+    MyApp->request_class_traits(['Foo']);
+
+We try each possible role in turn (and throw an error if none load)
+
+    Foo
+    MyApp::TraitFor::Request::Foo
+    Catalyst::TraitFor::Request::Foo
+
+The namespace part 'TraitFor::Request' was chosen to assist in backwards
+compatibility with L<CatalystX::RoleApplicator> which previously provided
+these features in a stand alone package.
+  
+=head2 $app->composed_request_class
+
+This is the request class which has been composed with any request_class_traits.
+
 =head2 $c->response_class
 
 Returns or sets the response class. Defaults to L<Catalyst::Response>.
 
+=head2 $app->response_class_traits
+
+An arrayref of L<Moose::Role>s which are applied to the response class.  You can
+name the full namespace of the role, or a namespace suffix, which will then
+be tried against the following standard namespace prefixes.
+
+    $MyApp::TraitFor::Response::$trait_suffix
+    Catalyst::TraitFor::Response::$trait_suffix
+
+So for example if you set:
+
+    MyApp->response_class_traits(['Foo']);
+
+We try each possible role in turn (and throw an error if none load)
+
+    Foo
+    MyApp::TraitFor::Response::Foo
+    Catalyst::TraitFor::Responset::Foo
+
+The namespace part 'TraitFor::Response' was chosen to assist in backwards
+compatibility with L<CatalystX::RoleApplicator> which previously provided
+these features in a stand alone package.
+
+
+=head2 $app->composed_response_class
+
+This is the request class which has been composed with any response_class_traits.
+
 =head2 $c->read( [$maxlength] )
 
 Reads a chunk of data from the request body. This method is designed to
@@ -2702,17 +2975,119 @@ sub setup_components {
     }
 
     for my $component (@comps) {
-        my $instance = $class->components->{ $component } = $class->setup_component($component);
-        my @expanded_components = $instance->can('expand_modules')
-            ? $instance->expand_modules( $component, $config )
-            : $class->expand_component_module( $component, $config );
-        for my $component (@expanded_components) {
-            next if $comps{$component};
-            $class->components->{ $component } = $class->setup_component($component);
-        }
+        my $instance = $class->components->{ $component } = $class->delayed_setup_component($component);
+    }
+
+    # Inject a component or wrap a stand alone class in an adaptor. This makes a list
+    # of named components in the configuration that are not actually existing (not a
+    # real file).
+
+    my @injected = $class->setup_injected_components;
+
+    # All components are registered, now we need to 'init' them.
+    foreach my $component_name (@comps, @injected) {
+      $class->components->{$component_name} = $class->components->{$component_name}->() if
+        (ref($class->components->{$component_name}) || '') eq 'CODE';
     }
 }
 
+=head2 $app->setup_injected_components
+
+Called by setup_compoents to setup components that are injected.
+
+=cut
+
+sub setup_injected_components {
+    my ($class) = @_;
+    my @injected_components = keys %{$class->config->{inject_components} ||+{}};
+
+    foreach my $injected_comp_name(@injected_components) {
+        $class->setup_injected_component(
+          $injected_comp_name,
+          $class->config->{inject_components}->{$injected_comp_name});
+    }
+
+    return map { $class ."::" . $_ }
+      @injected_components;
+}
+
+=head2 $app->setup_injected_component( $injected_component_name, $config )
+
+Setup a given injected component.
+
+=cut
+
+sub setup_injected_component {
+    my ($class, $injected_comp_name, $config) = @_;
+    if(my $component_class = $config->{from_component}) {
+        my @roles = @{$config->{roles} ||[]};
+        Catalyst::Utils::inject_component(
+          into => $class,
+          component => $component_class,
+          (scalar(@roles) ? (traits => \@roles) : ()),
+          as => $injected_comp_name);
+    }
+}
+
+=head2 $app->inject_component($MyApp_Component_name => \%args);
+
+Add a component that is injected at setup:
+
+    MyApp->inject_component( 'Model::Foo' => { from_component => 'Common::Foo' } );
+
+Must be called before ->setup.  Expects a component name for your
+current application and \%args where
+
+=over 4
+
+=item from_component
+
+The target component being injected into your application
+
+=item roles
+
+An arrayref of L<Moose::Role>s that are applied to your component.
+
+=back
+
+Example
+
+    MyApp->inject_component(
+      'Model::Foo' => {
+        from_component => 'Common::Model::Foo',
+        roles => ['Role1', 'Role2'],
+      });
+
+=head2 $app->inject_components
+
+Inject a list of components:
+
+    MyApp->inject_components(
+      'Model::FooOne' => {
+        from_component => 'Common::Model::Foo',
+        roles => ['Role1', 'Role2'],
+      },
+      'Model::FooTwo' => {
+        from_component => 'Common::Model::Foo',
+        roles => ['Role1', 'Role2'],
+      });
+
+=cut
+
+sub inject_component {
+  my ($app, $name, $args) = @_;
+  die "Component $name exists" if
+    $app->config->{inject_components}->{$name};
+  $app->config->{inject_components}->{$name} = $args;
+}
+
+sub inject_components {
+  my $app = shift;
+  while(@_) {
+    $app->inject_component(shift, shift);
+  }
+}
+
 =head2 $c->locate_components( $setup_component_config )
 
 This method is meant to provide a list of component modules that should be
@@ -2754,6 +3129,21 @@ sub expand_component_module {
     return Devel::InnerPackage::list_packages( $module );
 }
 
+=head2 $app->delayed_setup_component
+
+Returns a coderef that points to a setup_component instance.  Used
+internally for when you want to delay setup until the first time
+the component is called.
+
+=cut
+
+sub delayed_setup_component {
+  my($class, $component, @more) = @_;
+  return sub {
+    return my $instance = $class->setup_component($component, @more);
+  };
+}
+
 =head2 $c->setup_component
 
 =cut
@@ -2765,21 +3155,21 @@ sub setup_component {
         return $component;
     }
 
-    my $suffix = Catalyst::Utils::class2classsuffix( $component );
-    my $config = $class->config->{ $suffix } || {};
+    my $config = $class->config_for($component);
     # Stash catalyst_component_name in the config here, so that custom COMPONENT
     # methods also pass it. local to avoid pointlessly shitting in config
     # for the debug screen, as $component is already the key name.
     local $config->{catalyst_component_name} = $component;
 
-    my $instance = eval { $component->COMPONENT( $class, $config ); };
-
-    if ( my $error = $@ ) {
-        chomp $error;
-        Catalyst::Exception->throw(
-            message => qq/Couldn't instantiate component "$component", "$error"/
-        );
-    }
+    my $instance = eval {
+      $component->COMPONENT( $class, $config );
+    } || do {
+      my $error = $@;
+      chomp $error;
+      Catalyst::Exception->throw(
+        message => qq/Couldn't instantiate component "$component", "$error"/
+      );
+    };
 
     unless (blessed $instance) {
         my $metaclass = Moose::Util::find_meta($component);
@@ -2791,7 +3181,43 @@ sub setup_component {
             qq/Couldn't instantiate component "$component", COMPONENT() method (from $component_method_from) didn't return an object-like value (value was $value)./
         );
     }
-    return $instance;
+
+    my @expanded_components = $instance->can('expand_modules')
+      ? $instance->expand_modules( $component, $config )
+      : $class->expand_component_module( $component, $config );
+    for my $component (@expanded_components) {
+      next if $class->components->{ $component };
+      $class->components->{ $component } = $class->setup_component($component);
+    }
+
+    return $instance; 
+}
+
+=head2 $app->config_for( $component_name )
+
+Return the application level configuration (which is not yet merged with any
+local component configuration, via $component_class->config) for the named
+component or component object. Example:
+
+    MyApp->config(
+      'Model::Foo' => { a => 1, b => 2},
+    );
+
+    my $config = MyApp->config_for('MyApp::Model::Foo');
+
+In this case $config is the hashref C< {a=>1, b=>2} >.
+
+This is also handy for looking up configuration for a plugin, to make sure you follow
+existing L<Catalyst> standards for where a plugin should put its configuration.
+
+=cut
+
+sub config_for {
+    my ($class, $component_name) = @_;
+    my $component_suffix = Catalyst::Utils::class2classsuffix($component_name);
+    my $config = $class->config->{ $component_suffix } || {};
+
+    return $config;
 }
 
 =head2 $c->setup_dispatcher
@@ -2950,15 +3376,30 @@ EOW
 Adds the following L<Plack> middlewares to your application, since they are
 useful and commonly needed:
 
-L<Plack::Middleware::ReverseProxy>, (conditionally added based on the status
-of your $ENV{REMOTE_ADDR}, and can be forced on with C<using_frontend_proxy>
-or forced off with C<ignore_frontend_proxy>), L<Plack::Middleware::LighttpdScriptNameFix>
-(if you are using Lighttpd), L<Plack::Middleware::IIS6ScriptNameFix> (always
-applied since this middleware is smart enough to conditionally apply itself).
+L<Plack::Middleware::LighttpdScriptNameFix> (if you are using Lighttpd),
+L<Plack::Middleware::IIS6ScriptNameFix> (always applied since this middleware
+is smart enough to conditionally apply itself).
+
+We will also automatically add L<Plack::Middleware::ReverseProxy> if we notice
+that your HTTP $env variable C<REMOTE_ADDR> is '127.0.0.1'.  This is usually
+an indication that your server is running behind a proxy frontend.  However in
+2014 this is often not the case.  We preserve this code for backwards compatibility
+however I B<highly> recommend that if you are running the server behind a front
+end proxy that you clearly indicate so with the C<using_frontend_proxy> configuration
+setting to true for your environment configurations that run behind a proxy.  This
+way if you change your front end proxy address someday your code would inexplicably
+stop working as expected.
 
 Additionally if we detect we are using Nginx, we add a bit of custom middleware
 to solve some problems with the way that server handles $ENV{PATH_INFO} and
-$ENV{SCRIPT_NAME}
+$ENV{SCRIPT_NAME}.
+
+Please B<NOTE> that if you do use C<using_frontend_proxy> the middleware is now
+adding via C<registered_middleware> rather than this method.
+
+If you are using Lighttpd or IIS6 you may wish to apply these middlewares.  In
+general this is no longer a common case but we have this here for backward
+compatibility.
 
 =cut
 
@@ -2966,16 +3407,21 @@ $ENV{SCRIPT_NAME}
 sub apply_default_middlewares {
     my ($app, $psgi_app) = @_;
 
-    $psgi_app = Plack::Middleware::Conditional->wrap(
-        $psgi_app,
-        builder   => sub { Plack::Middleware::ReverseProxy->wrap($_[0]) },
-        condition => sub {
-            my ($env) = @_;
-            return if $app->config->{ignore_frontend_proxy};
-            return $env->{REMOTE_ADDR} eq '127.0.0.1'
-                || $app->config->{using_frontend_proxy};
-        },
-    );
+    # Don't add this conditional IF we are explicitly saying we want the
+    # frontend proxy support.  We don't need it here since if that is the
+    # case it will be always loaded in the default_middleware.
+
+    unless($app->config->{using_frontend_proxy}) {
+      $psgi_app = Plack::Middleware::Conditional->wrap(
+          $psgi_app,
+          builder   => sub { Plack::Middleware::ReverseProxy->wrap($_[0]) },
+          condition => sub {
+              my ($env) = @_;
+              return if $app->config->{ignore_frontend_proxy};
+              return $env->{REMOTE_ADDR} eq '127.0.0.1';
+          },
+      );
+    }
 
     # If we're running under Lighttpd, swap PATH_INFO and SCRIPT_NAME
     # http://lists.scsys.co.uk/pipermail/catalyst/2006-June/008361.html
@@ -3008,17 +3454,34 @@ sub apply_default_middlewares {
     return $psgi_app;
 }
 
-=head2 $c->psgi_app
+=head2 App->psgi_app
+
+=head2 App->to_app
 
 Returns a PSGI application code reference for the catalyst application
-C<$c>. This is the bare application without any middlewares
-applied. C<${myapp}.psgi> is not taken into account.
+C<$c>. This is the bare application created without the C<apply_default_middlewares>
+method called.  We do however apply C<registered_middleware> since those are
+integral to how L<Catalyst> functions.  Also, unlike starting your application
+with a generated server script (via L<Catalyst::Devel> and C<catalyst.pl>) we do
+not attempt to return a valid L<PSGI> application using any existing C<${myapp}.psgi>
+scripts in your $HOME directory.
+
+B<NOTE> C<apply_default_middlewares> was originally created when the first PSGI
+port was done for v5.90000.  These are middlewares that are added to achieve
+backward compatibility with older applications.  If you start your application
+using one of the supplied server scripts (generated with L<Catalyst::Devel> and
+the project skeleton script C<catalyst.pl>) we apply C<apply_default_middlewares>
+automatically.  This was done so that pre and post PSGI port applications would
+work the same way.
 
 This is what you want to be using to retrieve the PSGI application code
-reference of your Catalyst application for use in F<.psgi> files.
+reference of your Catalyst application for use in a custom F<.psgi> or in your
+own created server modules.
 
 =cut
 
+*to_app = \&psgi_app;
+
 sub psgi_app {
     my ($app) = @_;
     my $psgi = $app->engine->build_psgi_app($app);
@@ -3049,14 +3512,20 @@ sub setup_home {
 
 =head2 $c->setup_encoding
 
-Sets up the input/output encoding.  See L<ENCODING>
+Sets up the input/output encoding. See L<ENCODING>
 
 =cut
 
 sub setup_encoding {
     my $c = shift;
-    my $enc = delete $c->config->{encoding};
-    $c->encoding( $enc ) if defined $enc;
+    if( exists($c->config->{encoding}) && !defined($c->config->{encoding}) ) {
+        # Ok, so the user has explicitly said "I don't want encoding..."
+        return;
+    } else {
+      my $enc = defined($c->config->{encoding}) ?
+        delete $c->config->{encoding} : 'UTF-8'; # not sure why we delete it... (JNAP)
+      $c->encoding($enc);
+    }
 }
 
 =head2 handle_unicode_encoding_exception
@@ -3094,8 +3563,13 @@ sub _handle_unicode_decoding {
         return $value;
     }
     elsif ( ref $value eq 'HASH' ) {
-        foreach ( values %$value ) {
-            $_ = $self->_handle_unicode_decoding($_);
+        foreach (keys %$value) {
+            my $encoded_key = $self->_handle_param_unicode_decoding($_);
+            $value->{$encoded_key} = $self->_handle_unicode_decoding($value->{$_});
+
+            # If the key was encoded we now have two (the original and current so
+            # delete the original.
+            delete $value->{$_} if $_ ne $encoded_key;
         }
         return $value;
     }
@@ -3107,12 +3581,11 @@ sub _handle_unicode_decoding {
 sub _handle_param_unicode_decoding {
     my ( $self, $value ) = @_;
     return unless defined $value; # not in love with just ignoring undefs - jnap
+    return $value if blessed($value); #don't decode when the value is an object.
 
     my $enc = $self->encoding;
     return try {
-        Encode::is_utf8( $value ) ?
-            $value
-        : $enc->decode( $value, $self->_encode_check );
+      $enc->decode( $value, $self->_encode_check );
     }
     catch {
         $self->handle_unicode_encoding_exception({
@@ -3282,7 +3755,69 @@ the plugin name does not begin with C<Catalyst::Plugin::>.
             $class => @roles
         ) if @roles;
     }
-}    
+}
+
+=head2 default_middleware
+
+Returns a list of instantiated PSGI middleware objects which is the default
+middleware that is active for this application (taking any configuration
+options into account, excluding your custom added middleware via the C<psgi_middleware>
+configuration option).  You can override this method if you wish to change
+the default middleware (although do so at risk since some middleware is vital
+to application function.)
+
+The current default middleware list is:
+
+      Catalyst::Middleware::Stash
+      Plack::Middleware::HTTPExceptions
+      Plack::Middleware::RemoveRedundantBody
+      Plack::Middleware::FixMissingBodyInRedirect
+      Plack::Middleware::ContentLength
+      Plack::Middleware::MethodOverride
+      Plack::Middleware::Head
+
+If the configuration setting C<using_frontend_proxy> is true we add:
+
+      Plack::Middleware::ReverseProxy
+
+If the configuration setting C<using_frontend_proxy_path> is true we add:
+
+      Plack::Middleware::ReverseProxyPath
+
+But B<NOTE> that L<Plack::Middleware::ReverseProxyPath> is not a dependency of the
+L<Catalyst> distribution so if you want to use this option you should add it to
+your project distribution file.
+
+These middlewares will be added at L</setup_middleware> during the
+L</setup> phase of application startup.
+
+=cut
+
+sub default_middleware {
+    my $class = shift;
+    my @mw = (
+      Catalyst::Middleware::Stash->new,
+      Plack::Middleware::HTTPExceptions->new,
+      Plack::Middleware::RemoveRedundantBody->new,
+      Plack::Middleware::FixMissingBodyInRedirect->new,
+      Plack::Middleware::ContentLength->new,
+      Plack::Middleware::MethodOverride->new,
+      Plack::Middleware::Head->new);
+
+    if($class->config->{using_frontend_proxy}) {
+        push @mw, Plack::Middleware::ReverseProxy->new;
+    }
+
+    if($class->config->{using_frontend_proxy_path}) {
+        if(Class::Load::try_load_class('Plack::Middleware::ReverseProxyPath')) {
+            push @mw, Plack::Middleware::ReverseProxyPath->new;
+        } else {
+          $class->log->error("Cannot use configuration 'using_frontend_proxy_path' because 'Plack::Middleware::ReverseProxyPath' is not installed");
+        }
+    }
+
+    return @mw;
+}
 
 =head2 registered_middlewares
 
@@ -3325,15 +3860,13 @@ up.
 sub registered_middlewares {
     my $class = shift;
     if(my $middleware = $class->_psgi_middleware) {
-        return (
-          Catalyst::Middleware::Stash->new,
-          Plack::Middleware::HTTPExceptions->new,
-          Plack::Middleware::RemoveRedundantBody->new,
-          Plack::Middleware::FixMissingBodyInRedirect->new,
-          Plack::Middleware::ContentLength->new,
-          Plack::Middleware::MethodOverride->new,
-          Plack::Middleware::Head->new,
-          @$middleware);
+        my @mw = ($class->default_middleware, @$middleware);
+
+        if($class->config->{using_frontend_proxy}) {
+          push @mw, Plack::Middleware::ReverseProxy->new;
+        }
+
+        return @mw;
     } else {
         die "You cannot call ->registered_middlewares until middleware has been setup";
     }
@@ -3341,8 +3874,17 @@ sub registered_middlewares {
 
 sub setup_middleware {
     my $class = shift;
-    my @middleware_definitions = @_ ? 
-      reverse(@_) : reverse(@{$class->config->{'psgi_middleware'}||[]});
+    my @middleware_definitions;
+
+    # If someone calls this method you can add middleware with args.  However if its
+    # called without an arg we need to setup the configuration middleware.
+    if(@_) {
+      @middleware_definitions = reverse(@_);
+    } else {
+      @middleware_definitions = reverse(@{$class->config->{'psgi_middleware'}||[]})
+        unless $class->finalized_default_middleware;
+      $class->finalized_default_middleware(1); # Only do this once, just in case some people call setup over and over...
+    }
 
     my @middleware = ();
     while(my $next = shift(@middleware_definitions)) {
@@ -3433,12 +3975,34 @@ sub default_data_handlers {
             ->can('build_cgi_struct')->($params);
       },
       'application/json' => sub {
-          Class::Load::load_first_existing_class('JSON::MaybeXS', 'JSON')
-            ->can('decode_json')->(do { local $/; $_->getline });
-      },
+          my ($fh, $req) = @_;
+          my $parser = Class::Load::load_first_existing_class('JSON::MaybeXS', 'JSON');
+          my $slurped;
+          return eval { 
+            local $/;
+            $slurped = $fh->getline;
+            $parser->can("decode_json")->($slurped); # decode_json does utf8 decoding for us
+          } || Catalyst::Exception->throw(sprintf "Error Parsing POST '%s', Error: %s", (defined($slurped) ? $slurped : 'undef') ,$@);
+        },
     };
 }
 
+sub _handle_http_exception {
+    my ( $self, $error ) = @_;
+    if (
+           !$self->config->{always_catch_http_exceptions}
+        && blessed $error
+        && (
+            $error->can('as_psgi')
+            || (   $error->can('code')
+                && $error->code =~ m/^[1-5][0-9][0-9]$/ )
+        )
+      )
+    {
+        return 1;
+    }
+}
+
 =head2 $c->stack
 
 Returns an arrayref of the internal execution stack (actions that are
@@ -3459,6 +4023,33 @@ by itself.
 
 Returns or sets the stats (timing statistics) class. L<Catalyst::Stats|Catalyst::Stats> is used by default.
 
+=head2 $app->stats_class_traits
+
+A arrayref of L<Moose::Role>s that are applied to the stats_class before creating it.
+
+=head2 $app->composed_stats_class
+
+this is the stats_class composed with any 'stats_class_traits'.  You can
+name the full namespace of the role, or a namespace suffix, which will then
+be tried against the following standard namespace prefixes.
+
+    $MyApp::TraitFor::Stats::$trait_suffix
+    Catalyst::TraitFor::Stats::$trait_suffix
+
+So for example if you set:
+
+    MyApp->stats_class_traits(['Foo']);
+
+We try each possible role in turn (and throw an error if none load)
+
+    Foo
+    MyApp::TraitFor::Stats::Foo
+    Catalyst::TraitFor::Stats::Foo
+
+The namespace part 'TraitFor::Stats' was chosen to assist in backwards
+compatibility with L<CatalystX::RoleApplicator> which previously provided
+these features in a stand alone package.
+
 =head2 $c->use_stats
 
 Returns 1 when L<< stats collection|/"-Stats" >> is enabled.
@@ -3505,6 +4096,13 @@ There are a number of 'base' config variables which can be set:
 
 =item *
 
+C<always_catch_http_exceptions> - As of version 5.90060 Catalyst
+rethrows errors conforming to the interface described by
+L<Plack::Middleware::HTTPExceptions> and lets the middleware deal with it.
+Set true to get the deprecated behaviour and have Catalyst catch HTTP exceptions.
+
+=item *
+
 C<default_model> - The default model picked if you say C<< $c->model >>. See L<< /$c->model($name) >>.
 
 =item *
@@ -3560,7 +4158,7 @@ to be shown in hit debug tables in the test server.
 =item *
 
 C<use_request_uri_for_path> - Controls if the C<REQUEST_URI> or C<PATH_INFO> environment
-variable should be used for determining the request path. 
+variable should be used for determining the request path.
 
 Most web server environments pass the requested path to the application using environment variables,
 from which Catalyst has to reconstruct the request base (i.e. the top level path to / in the application,
@@ -3599,7 +4197,7 @@ is having paths rewritten into it (e.g. as a .cgi/fcgi in a public_html director
 at other URIs than that which the app is 'normally' based at with C<mod_rewrite>), the resolution of
 C<< $c->request->base >> will be incorrect.
 
-=back 
+=back
 
 =item *
 
@@ -3607,8 +4205,20 @@ C<using_frontend_proxy> - See L</PROXY SUPPORT>.
 
 =item *
 
+C<using_frontend_proxy_path> - Enabled L<Plack::Middleware::ReverseProxyPath> on your application (if
+installed, otherwise log an error).  This is useful if your application is not running on the
+'root' (or /) of your host server.  B<NOTE> if you use this feature you should add the required
+middleware to your project dependency list since its not automatically a dependency of L<Catalyst>.
+This has been done since not all people need this feature and we wish to restrict the growth of
+L<Catalyst> dependencies.
+
+=item *
+
 C<encoding> - See L</ENCODING>
 
+This now defaults to 'UTF-8'.  You my turn it off by setting this configuration
+value to undef.
+
 =item *
 
 C<abort_chain_on_error_fix>
@@ -3617,7 +4227,7 @@ When there is an error in an action chain, the default behavior is to continue
 processing the remaining actions and then catch the error upon chain end.  This
 can lead to running actions when the application is in an unexpected state.  If
 you have this issue, setting this config value to true will promptly exit a
-chain when there is an error raised in any action (thus terminating the chain 
+chain when there is an error raised in any action (thus terminating the chain
 early.)
 
 use like:
@@ -3648,12 +4258,127 @@ backwardly compatible).
 
 =item *
 
+C<skip_complex_post_part_handling>
+
+When creating body parameters from a POST, if we run into a multipart POST
+that does not contain uploads, but instead contains inlined complex data
+(very uncommon) we cannot reliably convert that into field => value pairs.  So
+instead we create an instance of L<Catalyst::Request::PartData>.  If this causes
+issue for you, you can disable this by setting C<skip_complex_post_part_handling>
+to true (default is false).  
+
+=item *
+
+C<skip_body_param_unicode_decoding>
+
+Generally we decode incoming POST params based on your declared encoding (the
+default for this is to decode UTF-8).  If this is causing you trouble and you
+do not wish to turn all encoding support off (with the C<encoding> configuration
+parameter) you may disable this step atomically by setting this configuration
+parameter to true.
+
+=item *
+
+C<do_not_decode_query>
+
+If true, then do not try to character decode any wide characters in your
+request URL query or keywords.  Most readings of the relevant specifications
+suggest these should be UTF-* encoded, which is the default that L<Catalyst>
+will use, however if you are creating a lot of URLs manually or have external
+evil clients, this might cause you trouble.  If you find the changes introduced
+in Catalyst version 5.90080+ break some of your query code, you may disable 
+the UTF-8 decoding globally using this configuration.
+
+This setting takes precedence over C<default_query_encoding> and
+C<decode_query_using_global_encoding>
+
+=item *
+
+C<default_query_encoding>
+
+By default we decode query and keywords in your request URL using UTF-8, which
+is our reading of the relevant specifications.  This setting allows one to
+specify a fixed value for how to decode your query.  You might need this if
+you are doing a lot of custom encoding of your URLs and not using UTF-8.
+
+This setting take precedence over C<decode_query_using_global_encoding>.
+
+=item *
+
+C<decode_query_using_global_encoding>
+
+Setting this to true will default your query decoding to whatever your
+general global encoding is (the default is UTF-8).
+
+=item *
+
+C<use_chained_args_0_special_case>
+
+In older versions of Catalyst, when more than one action matched the same path
+AND all those matching actions declared Args(0), we'd break the tie by choosing
+the first action defined.  We now normalized how Args(0) works so that it
+follows the same rule as Args(N), which is to say when we need to break a tie
+we choose the LAST action defined.  If this breaks your code and you don't
+have time to update to follow the new normalized approach, you may set this
+value to true and it will globally revert to the original chaining behavior.
+
+=item *
+
 C<psgi_middleware> - See L<PSGI MIDDLEWARE>.
 
 =item *
 
 C<data_handlers> - See L<DATA HANDLERS>.
 
+=item *
+
+C<stats_class_traits>
+
+An arrayref of L<Moose::Role>s that get composed into your stats class.
+
+=item *
+
+C<request_class_traits>
+
+An arrayref of L<Moose::Role>s that get composed into your request class.
+
+=item *
+
+C<response_class_traits>
+
+An arrayref of L<Moose::Role>s that get composed into your response class.
+
+=item *
+
+C<inject_components>
+
+A Hashref of L<Catalyst::Component> subclasses that are 'injected' into configuration.
+For example:
+
+    MyApp->config({
+      inject_components => {
+        'Controller::Err' => { from_component => 'Local::Controller::Errors' },
+        'Model::Zoo' => { from_component => 'Local::Model::Foo' },
+        'Model::Foo' => { from_component => 'Local::Model::Foo', roles => ['TestRole'] },
+      },
+      'Controller::Err' => { a => 100, b=>200, namespace=>'error' },
+      'Model::Zoo' => { a => 2 },
+      'Model::Foo' => { a => 100 },
+    });
+
+Generally L<Catalyst> looks for components in your Model/View or Controller directories.
+However for cases when you which to use an existing component and you don't need any
+customization (where for when you can apply a role to customize it) you may inject those
+components into your application.  Please note any configuration should be done 'in the
+normal way', with a key under configuration named after the component affix, as in the
+above example.
+
+Using this type of injection allows you to construct significant amounts of your application
+with only configuration!.  This may or may not lead to increased code understanding.
+
+Please not you may also call the ->inject_components application method as well, although
+you must do so BEFORE setup.
+
 =back
 
 =head1 EXCEPTIONS
@@ -3663,7 +4388,7 @@ your stack, such as in a model that an Action is calling) that exception
 is caught by Catalyst and unless you either catch it yourself (via eval
 or something like L<Try::Tiny> or by reviewing the L</error> stack, it
 will eventually reach L</finalize_errors> and return either the debugging
-error stack page, or the default error page.  However, if your exception 
+error stack page, or the default error page.  However, if your exception
 can be caught by L<Plack::Middleware::HTTPExceptions>, L<Catalyst> will
 instead rethrow it so that it can be handled by that middleware (which
 is part of the default middleware).  For example this would allow
@@ -3673,7 +4398,7 @@ is part of the default middleware).  For example this would allow
     sub throws_exception :Local {
       my ($self, $c) = @_;
 
-      http_throw(SeeOther => { location => 
+      http_throw(SeeOther => { location =>
         $c->uri_for($self->action_for('redirect')) });
 
     }
@@ -3951,8 +4676,45 @@ Please see L<PSGI> for more on middleware.
 
 =head1 ENCODING
 
-On request, decodes all params from encoding into a sequence of
-logical characters. On response, encodes body into encoding.
+Starting in L<Catalyst> version 5.90080 encoding is automatically enabled
+and set to encode all body responses to UTF8 when possible and applicable.
+Following is documentation on this process.  If you are using an older
+version of L<Catalyst> you should review documentation for that version since
+a lot has changed.
+
+By default encoding is now 'UTF-8'.  You may turn it off by setting
+the encoding configuration to undef.
+
+    MyApp->config(encoding => undef);
+
+This is recommended for temporary backwards compatibility only.
+
+Encoding is automatically applied when the content-type is set to
+a type that can be encoded.  Currently we encode when the content type
+matches the following regular expression:
+
+    $content_type =~ /^text|xml$|javascript$/
+
+Encoding is set on the application, but it is copied to the context object
+so that you can override it on a request basis.
+
+Be default we don't automatically encode 'application/json' since the most
+common approaches to generating this type of response (Either via L<Catalyst::View::JSON>
+or L<Catalyst::Action::REST>) will do so already and we want to avoid double
+encoding issues.
+
+If you are producing JSON response in an unconventional manner (such
+as via a template or manual strings) you should perform the UTF8 encoding
+manually as well such as to conform to the JSON specification.
+
+NOTE: We also examine the value of $c->response->content_encoding.  If
+you set this (like for example 'gzip', and manually gzipping the body)
+we assume that you have done all the necessary encoding yourself, since
+we cannot encode the gzipped contents.  If you use a plugin like
+L<Catalyst::Plugin::Compress> you need to update to a modern version in order
+to have this function correctly  with the new UTF8 encoding code, or you
+can use L<Plack::Middleware::Deflater> or (probably best) do your compression on
+a front end proxy.
 
 =head2 Methods
 
@@ -4045,6 +4807,8 @@ acme: Leon Brocard <leon@astray.com>
 
 abraxxa: Alexander Hartmaier <abraxxa@cpan.org>
 
+andrewalker: André Walker <andre@cpan.org>
+
 Andrew Bramble
 
 Andrew Ford E<lt>A.Ford@ford-mason.co.ukE<gt>
@@ -4067,6 +4831,8 @@ Chisel Wright C<pause@herlpacker.co.uk>
 
 Danijel Milicevic C<me@danijel.de>
 
+davewood: David Schmidt <davewood@cpan.org>
+
 David Kamholz E<lt>dkamholz@cpan.orgE<gt>
 
 David Naughton, C<naughton@umn.edu>
@@ -4181,9 +4947,11 @@ dd070: Dhaval Dhanani <dhaval070@gmail.com>
 
 Upasana <me@upasana.me>
 
+John Napiorkowski (jnap) <jjnapiork@cpan.org>
+
 =head1 COPYRIGHT
 
-Copyright (c) 2005-2014, the above named PROJECT FOUNDER and CONTRIBUTORS.
+Copyright (c) 2005-2015, the above named PROJECT FOUNDER and CONTRIBUTORS.
 
 =head1 LICENSE
 
index 555c939..3497e21 100644 (file)
@@ -20,7 +20,8 @@ L<Catalyst::Controller> subclasses.
 =cut
 
 use Moose;
-use Scalar::Util 'looks_like_number';
+use Scalar::Util 'looks_like_number', 'blessed';
+use Moose::Util::TypeConstraints ();
 with 'MooseX::Emulate::Class::Accessor::Fast';
 use namespace::clean -except => 'meta';
 
@@ -38,6 +39,294 @@ has private_path => (
   default => sub { '/'.shift->reverse },
 );
 
+has number_of_args => (
+  is=>'ro',
+  init_arg=>undef,
+  isa=>'Int|Undef',
+  required=>1,
+  lazy=>1,
+  builder=>'_build_number_of_args');
+
+  sub _build_number_of_args {
+    my $self = shift;
+    if( ! exists $self->attributes->{Args} ) {
+      # When 'Args' does not exist, that means we want 'any number of args'.
+      return undef;
+    } elsif(!defined($self->attributes->{Args}[0])) {
+      # When its 'Args' that internal cue for 'unlimited'
+      return undef;
+    } elsif(
+      scalar(@{$self->attributes->{Args}}) == 1 &&
+      looks_like_number($self->attributes->{Args}[0])
+    ) {
+      # 'Old school' numbered args (is allowed to be undef as well)
+      return $self->attributes->{Args}[0];
+    } else {
+      # New hotness named arg constraints
+      return $self->number_of_args_constraints;
+    }
+  }
+
+sub normalized_arg_number {
+  return defined($_[0]->number_of_args) ? $_[0]->number_of_args : ~0;
+}
+
+has number_of_args_constraints => (
+  is=>'ro',
+  isa=>'Int|Undef',
+  init_arg=>undef,
+  required=>1,
+  lazy=>1,
+  builder=>'_build_number_of_args_constraints');
+
+  sub _build_number_of_args_constraints {
+    my $self = shift;
+    return unless $self->has_args_constraints;
+
+    # If there is one constraint and its a ref, we need to decide
+    # if this number 'unknown' number or if the ref allows us to
+    # determine a length.
+
+    if(scalar @{$self->args_constraints} == 1) {
+      my $tc = $self->args_constraints->[0];
+      if(
+        $tc->can('is_strictly_a_type_of') &&
+        $tc->is_strictly_a_type_of('Tuple'))
+      {
+        my @parameters = @{ $tc->parameters||[]};
+        if( defined($parameters[-1]) and exists($parameters[-1]->{slurpy})) {
+          return undef;
+        } else {
+          return my $total_params = scalar(@parameters);
+        }
+      } elsif($tc->is_a_type_of('Ref')) {
+        return undef;
+      } else {
+        return 1; # Its a normal 1 arg type constraint.
+      }
+    } else {
+      # We need to loop through and error on ref types.  We don't allow a ref type
+      # in the middle.
+      my $total = 0;
+      foreach my $tc( @{$self->args_constraints}) {
+        if($tc->is_a_type_of('Ref')) {
+          die "$tc is a Ref type constraint.  You cannot mix Ref and non Ref type constraints in Args for action ${\$self->reverse}";
+        } else {
+          ++$total;
+        }
+      }
+      return $total;
+    }
+  }
+
+has args_constraints => (
+  is=>'ro',
+  init_arg=>undef,
+  traits=>['Array'],
+  isa=>'ArrayRef',
+  required=>1,
+  lazy=>1,
+  builder=>'_build_args_constraints',
+  handles => {
+    has_args_constraints => 'count',
+    args_constraint_count => 'count',
+  });
+
+  sub _build_args_constraints {
+    my $self = shift;
+    my @arg_protos = @{$self->attributes->{Args}||[]};
+
+    return [] unless scalar(@arg_protos);
+    return [] unless defined($arg_protos[0]);
+
+    # If there is only one arg and it looks like a number
+    # we assume its 'classic' and the number is the number of
+    # constraints.
+    my @args = ();
+    if(
+      scalar(@arg_protos) == 1 &&
+      looks_like_number($arg_protos[0])
+    ) {
+      return \@args;
+    } else {
+      @args =
+        map {  my @tc = $self->resolve_type_constraint($_); scalar(@tc) ? @tc : die "$_ is not a constraint!" }
+        @arg_protos;
+    }
+    return \@args;
+  }
+
+has number_of_captures_constraints => (
+  is=>'ro',
+  isa=>'Int|Undef',
+  init_arg=>undef,
+  required=>1,
+  lazy=>1,
+  builder=>'_build_number_of_capture_constraints');
+
+  sub _build_number_of_capture_constraints {
+    my $self = shift;
+    return unless $self->has_captures_constraints;
+
+    # If there is one constraint and its a ref, we need to decide
+    # if this number 'unknown' number or if the ref allows us to
+    # determine a length.
+
+    if(scalar @{$self->captures_constraints} == 1) {
+      my $tc = $self->captures_constraints->[0];
+      if(
+        $tc->can('is_strictly_a_type_of') &&
+        $tc->is_strictly_a_type_of('Tuple'))
+      {
+        my @parameters = @{ $tc->parameters||[]};
+        if( defined($parameters[-1]) and exists($parameters[-1]->{slurpy})) {
+          return undef;
+        } else {
+          return my $total_params = scalar(@parameters);
+        }
+      } elsif($tc->is_a_type_of('Ref')) {
+        die "You cannot use CaptureArgs($tc) in ${\$self->reverse} because we cannot determined the number of its parameters";
+      } else {
+        return 1; # Its a normal 1 arg type constraint.
+      }
+    } else {
+      # We need to loop through and error on ref types.  We don't allow a ref type
+      # in the middle.
+      my $total = 0;
+      foreach my $tc( @{$self->captures_constraints}) {
+        if($tc->is_a_type_of('Ref')) {
+          die "$tc is a Ref type constraint.  You cannot mix Ref and non Ref type constraints in CaptureArgs for action ${\$self->reverse}";
+        } else {
+          ++$total;
+        }
+      }
+      return $total;
+    }
+  }
+
+has captures_constraints => (
+  is=>'ro',
+  init_arg=>undef,
+  traits=>['Array'],
+  isa=>'ArrayRef',
+  required=>1,
+  lazy=>1,
+  builder=>'_build_captures_constraints',
+  handles => {
+    has_captures_constraints => 'count',
+    captures_constraints_count => 'count',
+  });
+
+  sub _build_captures_constraints {
+    my $self = shift;
+    my @arg_protos = @{$self->attributes->{CaptureArgs}||[]};
+
+    return [] unless scalar(@arg_protos);
+    return [] unless defined($arg_protos[0]);
+    # If there is only one arg and it looks like a number
+    # we assume its 'classic' and the number is the number of
+    # constraints.
+    my @args = ();
+    if(
+      scalar(@arg_protos) == 1 &&
+      looks_like_number($arg_protos[0])
+    ) {
+      return \@args;
+    } else {
+      @args =
+        map {  my @tc = $self->resolve_type_constraint($_); scalar(@tc) ? @tc : die "$_ is not a constraint!" }
+        @arg_protos;
+    }
+
+    return \@args;
+  }
+
+sub resolve_type_constraint {
+  my ($self, $name) = @_;
+
+  if(defined($name) && blessed($name) && $name->can('check')) {
+    # Its already a TC, good to go.
+    return $name;
+  }
+
+  # This is broken for when there is more than one constraint
+  if($name=~m/::/) {
+    eval "use Type::Registry; 1" || die "Can't resolve type constraint $name without installing Type::Tiny";
+    my $tc =  Type::Registry->new->foreign_lookup($name);
+    return defined $tc ? $tc : die "'$name' not a full namespace type constraint in ${\$self->private_path}";
+  }
+  
+  my @tc = grep { defined $_ } (eval("package ${\$self->class}; $name"));
+
+  unless(scalar @tc) {
+    # ok... so its not defined in the package.  we need to look at all the roles
+    # and superclasses, look for attributes and figure it out.
+    # Superclasses take precedence;
+
+    my @supers = $self->class->can('meta') ? map { $_->meta } $self->class->meta->superclasses : ();
+    my @roles = $self->class->can('meta') ? $self->class->meta->calculate_all_roles : ();
+
+    # So look through all the super and roles in order and return the
+    # first type constraint found. We should probably find all matching
+    # type constraints and try to do some sort of resolution.
+
+    foreach my $parent (@roles, @supers) {
+      if(my $m = $parent->get_method($self->name)) {
+        if($m->can('attributes')) {
+          my ($key, $value) = map { $_ =~ /^(.*?)(?:\(\s*(.+?)\s*\))?$/ }
+            grep { $_=~/^Args\(/ or $_=~/^CaptureArgs\(/ }
+              @{$m->attributes};
+          next unless $value eq $name;
+          my @tc = eval "package ${\$parent->name}; $name";
+          if(scalar(@tc)) {
+            return map { ref($_) ? $_ : Moose::Util::TypeConstraints::find_or_parse_type_constraint($_) } @tc;
+          } else {
+            return;
+          }
+        } 
+      }
+    }
+    
+    my $classes = join(',', $self->class, @roles, @supers);
+    die "'$name' not a type constraint in '${\$self->private_path}', Looked in: $classes";
+  }
+
+  if(scalar(@tc)) {
+    return map { ref($_) ? $_ : Moose::Util::TypeConstraints::find_or_parse_type_constraint($_) } @tc;
+  } else {
+    return;
+  }
+}
+
+has number_of_captures => (
+  is=>'ro',
+  init_arg=>undef,
+  isa=>'Int',
+  required=>1,
+  lazy=>1,
+  builder=>'_build_number_of_captures');
+
+  sub _build_number_of_captures {
+    my $self = shift;
+    if( ! exists $self->attributes->{CaptureArgs} ) {
+      # If there are no defined capture args, thats considered 0.
+      return 0;
+    } elsif(!defined($self->attributes->{CaptureArgs}[0])) {
+      # If you fail to give a defined value, that's also 0
+      return 0;
+    } elsif(
+      scalar(@{$self->attributes->{CaptureArgs}}) == 1 &&
+      looks_like_number($self->attributes->{CaptureArgs}[0])
+    ) {
+      # 'Old school' numbered captures
+      return $self->attributes->{CaptureArgs}[0];
+    } else {
+      # New hotness named arg constraints
+      return $self->number_of_captures_constraints;
+    }
+  }
+
+
 use overload (
 
     # Stringify to reverse for debug output etc.
@@ -51,8 +340,6 @@ use overload (
 
 );
 
-
-
 no warnings 'recursion';
 
 sub dispatch {    # Execute ourselves against a context
@@ -67,46 +354,114 @@ sub execute {
 
 sub match {
     my ( $self, $c ) = @_;
-    #would it be unreasonable to store the number of arguments
-    #the action has as its own attribute?
-    #it would basically eliminate the code below.  ehhh. small fish
-    return 1 unless exists $self->attributes->{Args};
-    my $args = $self->attributes->{Args}[0];
-    return 1 unless defined($args) && length($args);
-    return scalar( @{ $c->req->args } ) == $args;
+    return $self->match_args($c, $c->req->args);
 }
 
-sub match_captures { 1 }
-
-sub compare {
-    my ($a1, $a2) = @_;
+sub match_args {
+    my ($self, $c, $args) = @_;
+    my @args = @{$args||[]};
+
+    # There there are arg constraints, we must see to it that the constraints
+    # check positive for each arg in the list.
+    if($self->has_args_constraints) {
+      # If there is only one type constraint, and its a Ref or subtype of Ref,
+      # That means we expect a reference, so use the full args arrayref.
+      if(
+        $self->args_constraint_count == 1 &&
+        (
+          $self->args_constraints->[0]->is_a_type_of('Ref') ||
+          $self->args_constraints->[0]->is_a_type_of('ClassName')
+        )
+      ) {
+        # Ok, the the type constraint is a ref type, which is allowed to have
+        # any number of args.  We need to check the arg length, if one is defined.
+        # If we had a ref type constraint that allowed us to determine the allowed
+        # number of args, we need to match that number.  Otherwise if there was an
+        # undetermined number (~0) then we allow all the args.  This is more of an
+        # Optimization since Tuple[Int, Int] would fail on 3,4,5 anyway, but this
+        # way we can avoid calling the constraint when the arg length is incorrect.
+        if(
+          $self->normalized_arg_number == ~0 ||
+          scalar( @args ) == $self->normalized_arg_number
+        ) {
+          return $self->args_constraints->[0]->check($args);
+        } else {
+          return 0;
+        }
+        # Removing coercion stuff for the first go
+        #if($self->args_constraints->[0]->coercion && $self->attributes->{Coerce}) {
+        #  my $coerced = $self->args_constraints->[0]->coerce($c) || return 0;
+        #  $c->req->args([$coerced]);
+        #  return 1;
+        #}
+      } else {
+        # Because of the way chaining works, we can expect args that are totally not
+        # what you'd expect length wise.  When they don't match length, thats a fail
+        return 0 unless scalar( @args ) == $self->normalized_arg_number;
+
+        for my $i(0..$#args) {
+          $self->args_constraints->[$i]->check($args[$i]) || return 0;
+        }
+        return 1;
+      }
+    } else {
+      # If infinite args with no constraints, we always match
+      return 1 if $self->normalized_arg_number == ~0;
+
+      # Otherwise, we just need to match the number of args.
+      return scalar( @args ) == $self->normalized_arg_number;
+    }
+}
 
-    my ($a1_args) = @{ $a1->attributes->{Args} || [] };
-    my ($a2_args) = @{ $a2->attributes->{Args} || [] };
+sub match_captures {
+  my ($self, $c, $captures) = @_;
+  my @captures = @{$captures||[]};
 
-    $_ = looks_like_number($_) ? $_ : ~0
-        for $a1_args, $a2_args;
+  return 1 unless scalar(@captures); # If none, just say its ok
+  return $self->has_captures_constraints ?
+    $self->match_captures_constraints($c, $captures) : 1;
 
-    return $a1_args <=> $a2_args;
+  return 1;
 }
 
-sub number_of_args {
-    my ( $self ) = @_;
-    return 0 unless exists $self->attributes->{Args};
-    return $self->attributes->{Args}[0];
+sub match_captures_constraints {
+  my ($self, $c, $captures) = @_;
+  my @captures = @{$captures||[]};
+
+  # Match is positive if you don't have any.
+  return 1 unless $self->has_captures_constraints;
+
+  if(
+    $self->captures_constraints_count == 1 &&
+    (
+      $self->captures_constraints->[0]->is_a_type_of('Ref') ||
+      $self->captures_constraints->[0]->is_a_type_of('ClassName')
+    )
+  ) {
+    return $self->captures_constraints->[0]->check($captures);
+  } else {
+    for my $i(0..$#captures) {
+      $self->captures_constraints->[$i]->check($captures[$i]) || return 0;
+    }
+    return 1;
+    }
+
 }
 
-sub number_of_captures {
-    my ( $self ) = @_;
 
-    return 0 unless exists $self->attributes->{CaptureArgs};
-    return $self->attributes->{CaptureArgs}[0] || 0;
+sub compare {
+    my ($a1, $a2) = @_;
+    return $a1->normalized_arg_number <=> $a2->normalized_arg_number;
+}
+
+sub scheme {
+  return exists $_[0]->attributes->{Scheme} ? $_[0]->attributes->{Scheme}[0] : undef;
 }
 
 sub list_extra_info {
   my $self = shift;
   return {
-    Args => $self->attributes->{Args}[0],
+    Args => $self->normalized_arg_number,
     CaptureArgs => $self->number_of_captures,
   }
 } 
@@ -157,6 +512,18 @@ of the captures for this action.
 Returning true from this method causes the chain match to continue, returning
 makes the chain not match (and alternate, less preferred chains will be attempted).
 
+=head2 match_captures_constraints ($c, \@captures);
+
+Does the \@captures given match any constraints (if any constraints exist).  Returns
+true if you ask but there are no constraints.
+
+=head2 match_args($c, $args)
+
+Does the Args match or not?
+
+=head2 resolve_type_constraint
+
+Tries to find a type constraint if you have on on a type constrained method.
 
 =head2 compare
 
@@ -182,7 +549,13 @@ Returns the sub name of this action.
 
 =head2 number_of_args
 
-Returns the number of args this action expects. This is 0 if the action doesn't take any arguments and undef if it will take any number of arguments.
+Returns the number of args this action expects. This is 0 if the action doesn't
+take any arguments and undef if it will take any number of arguments.
+
+=head2 normalized_arg_number
+
+For the purposes of comparison we normalize 'number_of_args' so that if it is
+undef we mean ~0 (as many args are we can think of).
 
 =head2 number_of_captures
 
@@ -192,6 +565,10 @@ Returns the number of captures this action expects for L<Chained|Catalyst::Dispa
 
 A HashRef of key-values that an action can provide to a debugging screen
 
+=head2 scheme
+
+Any defined scheme for the action
+
 =head2 meta
 
 Provided by Moose.
@@ -206,3 +583,5 @@ This library is free software. You can redistribute it and/or modify it under
 the same terms as Perl itself.
 
 =cut
+
+
index fc39f09..4f72839 100644 (file)
@@ -37,7 +37,7 @@ sub dispatch {
 
         # break the chain if exception occurs in the middle of chain.  We
         # check the global config flag 'abort_chain_on_error_fix', but this
-        # is now considered true by default, so unless someone explictly sets
+        # is now considered true by default, so unless someone explicitly sets
         # it to false we default it to true (if its not defined).
         my $abort = defined($c->config->{abort_chain_on_error_fix}) ?
           $c->config->{abort_chain_on_error_fix} : 1;
@@ -61,6 +61,44 @@ sub number_of_captures {
     return $captures;
 }
 
+sub match_captures {
+  my ($self, $c, $captures) = @_;
+  my @captures = @{$captures||[]};
+
+  foreach my $link(@{$self->chain}) {
+    my @local_captures = splice @captures,0,$link->number_of_captures;
+    return unless $link->match_captures($c, \@local_captures);
+  }
+  return 1;
+}
+sub match_captures_constraints {
+  my ($self, $c, $captures) = @_;
+  my @captures = @{$captures||[]};
+
+  foreach my $link(@{$self->chain}) {
+    my @local_captures = splice @captures,0,$link->number_of_captures;
+    next unless $link->has_captures_constraints;
+    return unless $link->match_captures_constraints($c, \@local_captures);
+  }
+  return 1;
+}
+
+# the scheme defined at the end of the chain is the one we use
+# but warn if too many.
+
+sub scheme {
+  my $self = shift;
+  my @chain = @{ $self->chain };
+  my ($scheme, @more) = map {
+    exists $_->attributes->{Scheme} ? $_->attributes->{Scheme}[0] : ();
+  } reverse @chain;
+
+  warn "$self is a chain with two many Scheme attributes (only one is allowed)"
+    if @more;
+
+  return $scheme;
+}
+
 __PACKAGE__->meta->make_immutable;
 1;
 
@@ -87,6 +125,14 @@ Catalyst::ActionChain object representing a chain of these actions
 
 Returns the total number of captures for the entire chain of actions.
 
+=head2 match_captures
+
+Match all the captures that this chain encloses, if any.
+
+=head2 scheme
+
+Any defined scheme for the actionchain
+
 =head2 meta
 
 Provided by Moose
index 8b9eef8..9558223 100644 (file)
@@ -4,14 +4,7 @@ use Moose::Role;
 
 requires 'match', 'match_captures', 'list_extra_info';
 
-around ['match','match_captures'] => sub {
-  my ($orig, $self, $ctx, @args) = @_;
-  my $expected = $ctx->req->method;
-  return $self->_has_expected_http_method($expected) ?
-    $self->$orig($ctx, @args) :
-    0;
-};
-
+sub allowed_http_methods { @{shift->attributes->{Method}||[]} }
 
 sub _has_expected_http_method {
   my ($self, $expected) = @_;
@@ -20,7 +13,13 @@ sub _has_expected_http_method {
     1 : 0;
 }
 
-sub allowed_http_methods { @{shift->attributes->{Method}||[]} }
+around ['match','match_captures'] => sub {
+  my ($orig, $self, $ctx, @args) = @_;
+  return 0 unless $self->$orig($ctx, @args);
+
+  my $expected = $ctx->req->method;
+  return $self->_has_expected_http_method($expected);
+};
 
 around 'list_extra_info' => sub {
   my ($orig, $self, @args) = @_;
@@ -47,13 +46,13 @@ Catalyst::ActionRole::HTTPMethods - Match on HTTP Methods
 
     sub user_base : Chained('/') CaptureArg(0) { ... }
 
-      sub get_user    : Chained('user_base') Args(1) GET { ... }
-      sub post_user   : Chained('user_base') Args(1) POST { ... }
-      sub put_user    : Chained('user_base') Args(1) PUT { ... }
-      sub delete_user : Chained('user_base') Args(1) DELETE { ... }
-      sub head_user   : Chained('user_base') Args(1) HEAD { ... }
-      sub option_user : Chained('user_base') Args(1) OPTION { ... }
-      sub option_user : Chained('user_base') Args(1) PATCH { ... }
+      sub get_user     : Chained('user_base') Args(1) GET { ... }
+      sub post_user    : Chained('user_base') Args(1) POST { ... }
+      sub put_user     : Chained('user_base') Args(1) PUT { ... }
+      sub delete_user  : Chained('user_base') Args(1) DELETE { ... }
+      sub head_user    : Chained('user_base') Args(1) HEAD { ... }
+      sub options_user : Chained('user_base') Args(1) OPTIONS { ... }
+      sub patch_user   : Chained('user_base') Args(1) PATCH { ... }
 
 
       sub post_and_put : Chained('user_base') POST PUT Args(1) { ... }
diff --git a/lib/Catalyst/ActionRole/QueryMatching.pm b/lib/Catalyst/ActionRole/QueryMatching.pm
new file mode 100644 (file)
index 0000000..955258b
--- /dev/null
@@ -0,0 +1,131 @@
+package Catalyst::ActionRole::QueryMatching;
+
+use Moose::Role;
+use Moose::Util::TypeConstraints ();
+
+requires 'match', 'match_captures', 'list_extra_info';
+
+sub _query_attr { @{shift->attributes->{Query}||[]} }
+
+has is_slurpy => (
+  is=>'ro',
+  init_arg=>undef,
+  isa=>'Bool',
+  required=>1,
+  lazy=>1,
+  builder=>'_build_is_slurpy');
+
+  sub _build_is_slurpy {
+    my $self = shift;
+    my($query, @extra) = $self->_query_attr;
+    return $query =~m/^.+,\.\.\.$/ ? 1:0;
+  }
+
+has query_constraints => (
+  is=>'ro',
+  init_arg=>undef,
+  isa=>'ArrayRef|Ref',
+  required=>1,
+  lazy=>1,
+  builder=>'_build_query_constraints');
+
+  sub _build_query_constraints {
+    my $self = shift;
+    my ($constraint_proto, @extra) = $self->_query_attr;
+    
+    die "Action ${\$self->private_path} defines more than one 'Query' attribute" if scalar @extra;
+    return +{} unless defined($constraint_proto);
+
+    $constraint_proto =~s/^(.+),\.\.\.$/$1/; # slurpy is handled elsewhere
+    
+    # Query may be a Hash like Query(p=>Int,q=>Str) OR it may be a Ref like
+    # Query(Tuple[p=>Int, slurpy HashRef]).  The only way to figure is to eval it
+    # and look at what we have.
+    my @signature = eval "package ${\$self->class}; $constraint_proto"
+      or die "'$constraint_proto' is not valid Query Contraint at action ${\$self->private_path}, error '$@'";
+
+    if(scalar(@signature) > 1) {
+      # Do a dance to support old school stringy types
+      # At this point we 'should' have a hash...
+      my %pairs = @signature;
+      foreach my $key(keys %pairs) {
+        next if ref $pairs{$key};
+        $pairs{$key} = Moose::Util::TypeConstraints::find_or_parse_type_constraint($pairs{$key}) ||
+          die "'$pairs{$key}' is not a valid type constraint in Action ${\$self->private_path}";
+      }
+      return \%pairs;
+    } else {
+      # We have a 'reference type' constraint, like Dict[p=>Int,...]
+      return $signature[0] if ref($signature[0]); # Is like Tiny::Type
+      return Moose::Util::TypeConstraints::find_or_parse_type_constraint($signature[0]) ||
+          die "'$signature[0]' is not a valid type constraint in Action ${\$self->private_path}";
+    }
+  }
+
+around ['match','match_captures'] => sub {
+    my ($orig, $self, $c, @args) = @_;
+    my $tc = $self->query_constraints;
+    if(ref $tc eq 'HASH') {
+      # Do the key names match, unless slurpy?
+      unless($self->is_slurpy) {
+        return 0 unless $self->_compare_arrays([sort keys %$tc],[sort keys %{$c->req->query_parameters}]);
+      }
+      for my $key(keys %$tc) {
+        $tc->{$key}->check($c->req->query_parameters->{$key}) || return 0;
+      }
+    } else {
+      $tc->check($c->req->query_parameters) || return 0;
+    }
+
+    return $self->$orig($c, @args);
+};
+
+around 'list_extra_info' => sub {
+  my ($orig, $self, @args) = @_;
+  return {
+    %{ $self->$orig(@args) }, 
+  };
+};
+
+sub _compare_arrays {
+  my ($self, $first, $second) = @_;
+  no warnings;  # silence spurious -w undef complaints
+  return 0 unless @$first == @$second;
+  for (my $i = 0; $i < @$first; $i++) {
+    return 0 if $first->[$i] ne $second->[$i];
+  }
+  return 1;
+}
+
+1;
+
+=head1 NAME
+
+Catalyst::ActionRole::QueryMatching - Match on GET parameters using type constraints
+
+=head1 SYNOPSIS
+
+    TBD
+
+=head1 DESCRIPTION
+
+    TBD
+
+=head1 METHODS
+
+This role defines the following methods
+
+=head2 TBD
+
+    TBD
+
+=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/ActionRole/Scheme.pm b/lib/Catalyst/ActionRole/Scheme.pm
new file mode 100644 (file)
index 0000000..240538d
--- /dev/null
@@ -0,0 +1,95 @@
+package Catalyst::ActionRole::Scheme;
+
+use Moose::Role;
+
+requires 'match', 'match_captures', 'list_extra_info';
+
+around ['match','match_captures'] => sub {
+    my ($orig, $self, $ctx, @args) = @_;
+    my $request_scheme = lc($ctx->req->env->{'psgi.url_scheme'});
+    my $match_scheme = lc($self->scheme||'');
+
+    return $request_scheme eq $match_scheme ? $self->$orig($ctx, @args) : 0;
+};
+
+around 'list_extra_info' => sub {
+  my ($orig, $self, @args) = @_;
+  return {
+    %{ $self->$orig(@args) },
+    Scheme => $self->attributes->{Scheme}[0]||'',
+  };
+};
+
+1;
+
+=head1 NAME
+
+Catalyst::ActionRole::Scheme - Match on HTTP Request Scheme
+
+=head1 SYNOPSIS
+
+    package MyApp::Web::Controller::MyController;
+
+    use base 'Catalyst::Controller';
+
+    sub is_http :Path(scheme) Scheme(http) Args(0) {
+      my ($self, $c) = @_;
+      Test::More::is $c->action->scheme, 'http';
+      $c->response->body("is_http");
+    }
+
+    sub is_https :Path(scheme) Scheme(https) Args(0)  {
+      my ($self, $c) = @_;
+      Test::More::is $c->action->scheme, 'https';
+      $c->response->body("is_https");
+    }
+
+    1;
+
+=head1 DESCRIPTION
+
+This is an action role that lets your L<Catalyst::Action> match on the scheme
+type of the request.  Typically this is C<http> or C<https> but other common
+schemes that L<Catalyst> can handle include C<ws> and C<wss> (web socket and web
+socket secure).
+
+This also ensures that if you use C<uri_for> on an action that specifies a
+match scheme, that the generated L<URI> object sets its scheme to that automatically
+(rather than the scheme of the current request object, which is and remains the
+default behavior.)
+
+For matching purposes, we match strings but the casing is insensitive.
+
+=head1 REQUIRES
+
+This role requires the following methods in the consuming class.
+
+=head2 match
+
+=head2 match_captures
+
+Returns 1 if the action matches the existing request and zero if not.
+
+=head1 METHODS
+
+This role defines the following methods
+
+=head2 match
+
+=head2 match_captures
+
+Around method modifier that return 1 if the scheme matches
+
+=head2 list_extra_info
+
+Add the scheme declaration if present to the debug screen.
+
+=head1 AUTHORS
+
+Catalyst Contributors, see L<Catalyst>
+
+=head1 COPYRIGHT
+
+See L<Catalyst>
+
+=cut
index 13c9323..55f25c0 100644 (file)
@@ -202,6 +202,19 @@ something like this:
       return $class->new($app, $args);
   }
 
+B<NOTE:> Generally when L<Catalyst> starts, it initializes all the components
+and passes the hashref present in any configuration information to the
+COMPONENT method.  For example
+
+    MyApp->config(
+      'Model::Foo' => {
+        bar => 'baz',
+      });
+
+You would expect COMPONENT to be called like this ->COMPONENT( 'MyApp', +{ bar=>'baz'});
+
+This would happen ONCE during setup.
+
 =head2 $c->config
 
 =head2 $c->config($hashref)
@@ -251,6 +264,39 @@ would cause your MyApp::Model::Foo instance's ACCEPT_CONTEXT to be called with
 ($c, 'bar', 'baz')) and the return value of this method is returned to the
 calling code in the application rather than the component itself.
 
+B<NOTE:> All classes that are L<Catalyst::Component>s will have a COMPONENT
+method, but classes that are intended to be factories or generators will
+have ACCEPT_CONTEXT.  If you have initialization arguments (such as from
+configuration) that you wish to expose to the ACCEPT_CONTEXT you should
+proxy them in the factory instance.  For example:
+
+    MyApp::Model::FooFactory;
+
+    use Moose;
+    extends 'Catalyst::Model';
+
+    has type => (is=>'ro', required=>1);
+
+    sub ACCEPT_CONTEXT {
+      my ($self, $c, @args) = @_;
+      return bless { args=>\@args }, $self->type;
+    }
+
+    MyApp::Model::Foo->meta->make_immutable;
+    MyApp::Model::Foo->config( type => 'Type1' );
+
+And in a controller:
+
+    my $type = $c->model('FooFactory', 1,2,3,4): # $type->isa('Type1')
+
+B<NOTE:> If you define a ACCEPT_CONTEXT method it MUST check to see if the
+second argument is blessed (is a context) or not (is an application class name) and
+it MUST return something valid for the case when the scope is application.  This is
+required because a component maybe be called from the application scope even if it
+requires a context and you must prevent errors from being issued if this happens.
+Remember not all components that ACCEPT_CONTEXT actually need or use context information
+(and there is a school of thought that suggestions doing so is a design error anyway...)
+
 =head1 SEE ALSO
 
 L<Catalyst>, L<Catalyst::Model>, L<Catalyst::View>, L<Catalyst::Controller>.
diff --git a/lib/Catalyst/Contributing.pod b/lib/Catalyst/Contributing.pod
new file mode 100644 (file)
index 0000000..e5397ef
--- /dev/null
@@ -0,0 +1,41 @@
+=encoding UTF-8
+
+=head1 Name
+
+Catalyst::Contributing - Contributing to Catalyst and Change management
+
+=head1 Description
+
+How to contribute to L<Catalyst> and what are the criteria for evaluating change and
+deciding on the future direction of the project.
+
+=head2 Change Management
+
+In general there are two rules when thinking about changing Catalyst. The first is technical merit of the idea. If there is a bug, then its obvious it needs to be fixed. Less obvious is the types of refactoring that went into giving Catalyst modern features like websocket support, interoperability with event loops and to expose more and more of Catalyst's PSGI underpinnings.
+
+When an idea has strong technical merit, it recommends itself. The only thing to consider is the needs of backward compatibility, and to offer people upgrading at least some sort of path forward when features change (such as to have plugins or configuration options to replace or replicate something that is no longer available).
+
+Then there is a second and more difficult type of change consideration, which is the general will of the community. Like technical merit, this needs to balance against our commitment to not leave existing users high and dry with changes that break code and offer no path forward that does not involve significant code rewrites. Unlike technical merit, the will of the community can be hard to figure. In general we don't get a lot of bug reports or conversation around Catalyst future evolution. I wish I could find a way to get more involvement, but I also understand this is not very unusual issue for open source projects. I personally don't believe that "silence is consent" either. I think choices need to have broad acceptability or the choosers lose respect and authority. Typical that results in people just drifting away.
+
+Without direct involvement the only other way to measure the will of the community is to look at what other choices people are making and what other projects have received the acceptance of a broad number of people. Since Plack is clearly accepted and important it leads me to feel the choice to make Catalyst expose more of its Plack nature and to better play with the larger Plack ecosystem are correct ones. One can also pay attention to the kinds of problems that get reported on IRC, at conferences and the problems that I see having looked at how Catalyst has been used in the wild. For example its clear that Chaining actions could use a tweak in some way since it seems to trip up people a lot. The same goes with $c->forward and $c->go, which tend to lead to confusing code (and combined with the stash is a particularly toxic brew).
+
+Going further, if we allow ourselves to look hard at projects outside of Perl we can get lots of great ideas about what has worked for other projects in other languages. When we see certain features and approaches have excited programmers using frameworks like Ruby on Rails, Django, Scala Play, etc. then it should provide us with with help in thinking about how those features might influence the evolution of Catalyst as well.
+
+=head2 Reporting a bug
+
+Reported bugs via RT or L<Github Issues|https://github.com/perl-catalyst/catalyst-runtime/issues> that come with attached test cases will be more likely addressed quickly than those that do not.  Proposing a bugfix patch is also always very welcome, although it is recommended to stick as closely as possible to an actual bug (rather than a feature change) and to not include unneeded changes in your patch such as formatting corrections.  In any case it is recommended before spending a lot of time on a patch to discuss the issue and your proposed solution, else you risk spending a lot of time on code that may not get merged, which tends to be frustrating.
+
+For bug patches you should create a new branch from the current master.
+
+=head2 Proposing a new feature
+
+You should first ask yourself if your new idea could rationally live in the extended Catalyst ecosystem independently on CPAN.  Ideas that have demonstrated worth over time as stand alone modules are more likely to be considered for core inclusion.  Additionally, ideas that are best achieved in core rather than as standalone, are more likely considered for core inclusion than those ideas which could just as well be stand alone.  For example, the PSGI integration project happened because it was clear that building Catalyst on top of PSGI standards would lead to a better overall version than keeping it stand alone.
+
+You should propose your new idea in a L<github issue|https://github.com/perl-catalyst/catalyst-runtime/issues>, on IRC and ideally on the mailing list so that other people can comment on your idea and its merits prior to you writing code.  If you write code before proposing the idea you stand a high chance of being frustrated when you idea is not accepted.
+
+=head2 AUTHOR
+
+John Napiorkowski L<jjnapiork@cpan.org|email:jjnapiork@cpan.org>
+
+=cut
+
index 02db77a..b1a9bef 100644 (file)
@@ -372,6 +372,11 @@ sub gather_default_action_roles {
   push @roles, 'Catalyst::ActionRole::ConsumesContent'
     if $args{attributes}->{Consumes};
 
+  push @roles, 'Catalyst::ActionRole::Scheme'
+    if $args{attributes}->{Scheme};
+
+  push @roles, 'Catalyst::ActionRole::QueryMatching'
+    if $args{attributes}->{Query};
     return @roles;
 }
 
@@ -542,12 +547,13 @@ sub _parse_Does_attr {
     return Does => $self->_expand_role_shortname($value);
 }
 
-sub _parse_GET_attr    { Method => 'GET'    }
-sub _parse_POST_attr   { Method => 'POST'   }
-sub _parse_PUT_attr    { Method => 'PUT'    }
-sub _parse_DELETE_attr { Method => 'DELETE' }
-sub _parse_OPTION_attr { Method => 'OPTION' }
-sub _parse_HEAD_attr   { Method => 'HEAD'   }
+sub _parse_GET_attr     { Method => 'GET'     }
+sub _parse_POST_attr    { Method => 'POST'    }
+sub _parse_PUT_attr     { Method => 'PUT'     }
+sub _parse_DELETE_attr  { Method => 'DELETE'  }
+sub _parse_OPTIONS_attr { Method => 'OPTIONS' }
+sub _parse_HEAD_attr    { Method => 'HEAD'    }
+sub _parse_PATCH_attr  { Method => 'PATCH'  }
 
 sub _expand_role_shortname {
     my ($self, @shortnames) = @_;
@@ -784,7 +790,29 @@ Like L</Regex> but scoped under the namespace of the containing controller
 
 =head2 CaptureArgs
 
-Please see L<Catalyst::DispatchType::Chained>
+Allowed values for CaptureArgs is a single integer (CaptureArgs(2), meaning two
+allowed) or you can declare a L<Moose>, L<MooseX::Types> or L<Type::Tiny>
+named constraint such as CaptureArgs(Int,Str) would require two args with
+the first being a Integer and the second a string.  You may declare your own
+custom type constraints and import them into the controller namespace:
+
+    package MyApp::Controller::Root;
+
+    use Moose;
+    use MooseX::MethodAttributes;
+    use MyApp::Types qw/Int/;
+
+    extends 'Catalyst::Controller';
+
+    sub chain_base :Chained(/) CaptureArgs(1) { }
+
+      sub any_priority_chain :Chained(chain_base) PathPart('') Args(1) { }
+
+      sub int_priority_chain :Chained(chain_base) PathPart('') Args(Int) { }
+
+See L<Catalyst::RouteMatching> for more.
+
+Please see L<Catalyst::DispatchType::Chained> for more.
 
 =head2 ActionClass
 
@@ -834,6 +862,53 @@ When used with L</Path> indicates the number of arguments expected in
 the path.  However if no Args value is set, assumed to 'slurp' all
 remaining path pars under this namespace.
 
+Allowed values for Args is a single integer (Args(2), meaning two allowed) or you
+can declare a L<Moose>, L<MooseX::Types> or L<Type::Tiny> named constraint such
+as Args(Int,Str) would require two args with the first being a Integer and the
+second a string.  You may declare your own custom type constraints and import
+them into the controller namespace:
+
+    package MyApp::Controller::Root;
+
+    use Moose;
+    use MooseX::MethodAttributes;
+    use MyApp::Types qw/Tuple Int Str StrMatch UserId/;
+
+    extends 'Catalyst::Controller';
+
+    sub user :Local Args(UserId) {
+      my ($self, $c, $int) = @_;
+    }
+
+    sub an_int :Local Args(Int) {
+      my ($self, $c, $int) = @_;
+    }
+
+    sub many_ints :Local Args(ArrayRef[Int]) {
+      my ($self, $c, @ints) = @_;
+    }
+
+    sub match :Local Args(StrMatch[qr{\d\d-\d\d-\d\d}]) {
+      my ($self, $c, $int) = @_;
+    }
+
+If you choose not to use imported type constraints (like L<Type::Tiny>, or <MooseX::Types>
+you may use L<Moose> 'stringy' types however just like when you use these types in your
+declared attributes you must quote them:
+
+    sub my_moose_type :Local Args('Int') { ... }
+
+If you use 'reference' type constraints (such as ArrayRef[Int]) that have an unknown
+number of allowed matches, we set this the same way "Args" is.  Please keep in mind
+that actions with an undetermined number of args match at lower precedence than those
+with a fixed number.  You may use reference types such as Tuple from L<Types::Standard>
+that allows you to fix the number of allowed args.  For example Args(Tuple[Int,Int])
+would be determined to be two args (or really the same as Args(Int,Int).)  You may
+find this useful for creating custom subtypes with complex matching rules that you 
+wish to reuse over many actions.
+
+See L<Catalyst::RouteMatching> for more.
+
 =head2 Consumes('...')
 
 Matches the current action against the content-type of the request.  Typically
@@ -889,6 +964,39 @@ most accurate matches early in the Chain, and your 'catchall' actions last.
 
 See L<Catalyst::ActionRole::ConsumesContent> for more.
 
+=head2 Scheme(...)
+
+Allows you to specify a URI scheme for the action or action chain.  For example
+you can required that a given path be C<https> or that it is a websocket endpoint
+C<ws> or C<wss>.  For an action chain you may currently only have one defined
+Scheme.
+
+    package MyApp::Controller::Root;
+
+    use base 'Catalyst::Controller';
+
+    sub is_http :Path(scheme) Scheme(http) Args(0) {
+      my ($self, $c) = @_;
+      $c->response->body("is_http");
+    }
+
+    sub is_https :Path(scheme) Scheme(https) Args(0)  {
+      my ($self, $c) = @_;
+      $c->response->body("is_https");
+    }
+
+In the above example http://localhost/root/scheme would match the first
+action (is_http) but https://localhost/root/scheme would match the second.
+
+As an added benefit, if an action or action chain defines a Scheme, when using
+$c->uri_for the scheme of the generated URL will use what you define in the action
+or action chain (the current behavior is to set the scheme based on the current
+incoming request).  This makes it easier to use uri_for on websites where some
+paths are secure and others are not.  You may also use this to other schemes
+like websockets.
+
+See L<Catalyst::ActionRole::Scheme> for more.
+
 =head1 OPTIONAL METHODS
 
 =head2 _parse_[$name]_attr
index 2d8c31d..95afdc0 100755 (executable)
@@ -7,6 +7,147 @@ Catalyst::Delta - Overview of changes between versions of Catalyst
 This is an overview of the user-visible changes to Catalyst between major
 Catalyst releases.
 
+=head2 VERSION 5.90100
+
+Support for type constraints in Args and CaptureArgs has been improved.  You may
+now inherit from a base controller that declares type constraints and use roles
+that declare type constraints.  See L<Catalyst::RouteMatching> for more.
+
+You may now. also use a full type constraint namespace instead of importing type
+constraints into your package namespace.
+
+We changed the way the middleware stash works so that it no longer localizes
+the PSGI env hashref.  This was done to fix bugs where people set PSGI ENV hash
+keys and found them to disappear in certain cases.  It also means that now if
+a sub applications sets stash variables, that stash will now bubble up to the
+parent application.  This may be a breaking change for you since previous
+versions of this code did not allow that.  A workaround is to explicitly delete
+stash keys in your sub application before returning control to the parent
+application.
+
+=head2 VERSION 5.90097
+
+=head3 Defined how $c->uri_for adds a URI fragment.
+
+We now have a specification for creating URIs with fragments (or HTML anchors).
+Previously you could do this as a side effect of how we create URIs but this
+side effect behavior was never documented or tested, and was broken when we
+introduced default UTF-8 encoding.  When creating URIs with fragments please
+follow the new, supported specification:
+
+    $c->uri_for($action_or_path, \@captures_or_args, @args, \$query, \$fragment);
+
+This will be a breaking change for some codebases, we recommend testing if
+you are creating URLs with fragments.
+
+B<NOTE> If you are using the alternative:
+
+    $c->uri_for('/foo/bar#baz')
+
+construction, we do not attempt to encode this and it will make a URL with a
+fragment of 'baz'.
+
+=head2 VERSION 5.90094
+
+=head3 Multipart form POST with character set headers
+
+When we did the UTF8 work, we punted on Form POSTs when the POST envelope was
+multipart and each part had complex headers such as content-types, character
+sets and so forth.  In those cases instead of returning a possibly incorrect
+value, we returned an object describing the part so that you could figure it
+out manually.  This turned out to be a bad workaround as people did not expect
+to find that object.  So we changed this to try much harder to get a correct
+value.  We still return an object if we fail but we try much harder now.  If
+you used to check for the object you might find that code is no longer needed
+(although checking for it should not hurt or break anything either).
+
+=head2 VERSION 5.90091
+
+=head3 'case_sensitive' configuration
+
+At one point in time we allowed you to set a 'case_sensitive' configuration value so
+that you could find actions by their private names using mixed case.  We highly
+discourage that.  If you are using this 'feature' you should be on notice that we
+plan to remove the code around it in the near future.
+
+=head2 VERSION 5.90090+
+
+=head3 Type constraints on Args and CaptureArgs.
+
+You may now use a type constraint (using L<Moose>, L<MooseX::Types> or preferably
+L<Type::Tiny> in your Args or CaptureArgs action attributes.  This can be used
+to restrict the value of the Arg.  For example:
+
+    sub myaction :Local Args(Int) { ... }
+
+Would match '.../myaction/5' but not '.../myaction/string'.
+
+When an action (or action chain) has Args (or CaptureArgs) that declare type constraints
+your arguments to $c->uri_for(...) must match those constraints.
+
+See L<Catalyst::RouteMatching> for more.
+
+=head3 Move CatalystX::InjectComponent into core
+
+L<Catalyst::Utils> has a new method 'inject_component' which works the same as the method of
+the same name in L<CatalystX::InjectComponent>.
+
+=head3 inject_components
+
+New configuration key allows you to inject components directly into your application without
+any subclasses.  For example:
+
+    MyApp->config({
+      inject_components => {
+        'Controller::Err' => { from_component => 'Local::Controller::Errors' },
+        'Model::Zoo' => { from_component => 'Local::Model::Foo' },
+        'Model::Foo' => { from_component => 'Local::Model::Foo', roles => ['TestRole'] },
+      },
+      'Controller::Err' => { a => 100, b=>200, namespace=>'error' },
+      'Model::Zoo' => { a => 2 },
+      'Model::Foo' => { a => 100 },
+    });
+
+Injected components are useful to reduce the amount of nearly empty boilerplate classes
+you might have, particularly when first starting an application.
+
+=head3 Component setup changes.
+
+Previously you could not depend on an application scoped component doing setup_components
+since components were setup 'in order'.  Now all components are first registered and then
+setup, so you can now reliably use any component doing setup_components.
+
+=head2 VERSION 5.90080+
+
+The biggest change in this release is that UTF8 encoding is now enabled by
+default.  So you no longer need any plugins (such as L<Catalyst::Plugin::Unicode::Encoding>)
+which you can just no go ahead and remove.  You also don't need to set
+the encoding configuration (__PACKAGE__->config(encoding=>'UTF-8')) anymore
+as well (although its presence hurts nothing).
+
+If this change causes you trouble, you can disable it:
+
+    __PACKAGE__->config(encoding=>undef);
+
+For further information, please see L<Catalyst::UTF8>
+
+But please report bugs.  You will find that a number of common Views have been
+updated for this release (such as L<Catalyst::View::TT>).  In all cases that the
+author is aware of these updates were to fix test cases only.  You shouldn't
+need to update unless you are installing fresh and want tests to pass.
+
+L<Catalyst::Plugin::Compress> was updated to be compatible with this release.
+You will need to upgrade if you are using this plugin.  L<Catalyst::Upgrading>
+also has details.
+
+A small change is that the configuration setting C<using_frontend_proxy>
+was not doing the right thing if you started your application with C<psgi_app>
+and did not apply the default middleware.  This setting is now honored in
+all the ways an application may be started.  This could cause trouble if you
+are using the configuration value and also adding the proxy middleware
+manually with a custom application startup.  The solution is that you only
+need the configuration value set, or the middleware manually added (not both).
+
 =head2 VERSION 5.90060+
 
 =head3 Catalyst::Log object autoflush on by default
@@ -58,7 +199,7 @@ Plack middleware to aid in backwards compatibility.
 
 =head3 Distinguish between body null versus undef.
 
-We also now more carefully distingush the different between a body set
+We also now more carefully distinguish the different between a body set
 to '' and a body that is undef.  This might lead to situations where
 again you'll get a content-length were you didn't get one before or
 where a supporting server will start chunking output.  If this is an
@@ -67,7 +208,7 @@ or report specific problems to the dev team.
 
 =head3 More Catalyst Middleware
 
-We have started migrating code in Catalyst to equivilent Plack
+We have started migrating code in Catalyst to equivalent Plack
 Middleware when such exists and is correct to do so.  For example we now use
 L<Plack::Middleware::ContentLength> to determine content length of a response
 when none is provided.  This replaces similar code inlined with L<Catalyst>
index 05fc514..b74c29d 100644 (file)
@@ -8,6 +8,7 @@ use Catalyst::ActionChain;
 use Catalyst::Utils;
 use URI;
 use Scalar::Util ();
+use Encode 2.21 'decode_utf8';
 
 has _endpoints => (
                    is => 'rw',
@@ -97,11 +98,12 @@ sub list {
                            @{ $self->_endpoints }
                   ) {
         my $args = $endpoint->list_extra_info->{Args};
-        my @parts = (defined($args) ? (("*") x $args) : '...');
+        my @parts = (defined($endpoint->attributes->{Args}[0]) ? (("*") x $args) : '...');
         my @parents = ();
         my $parent = "DUMMY";
         my $extra  = $self->_list_extra_http_methods($endpoint);
         my $consumes = $self->_list_extra_consumes($endpoint);
+        my $scheme = $self->_list_extra_scheme($endpoint);
         my $curr = $endpoint;
         while ($curr) {
             if (my $cap = $curr->list_extra_info->{CaptureArgs}) {
@@ -128,19 +130,35 @@ sub list {
                 $name = "${extra} ${name}";
             }
             if (defined(my $cap = $p->list_extra_info->{CaptureArgs})) {
-                $name .= ' ('.$cap.')';
+                if($p->has_captures_constraints) {
+                  my $tc = join ',', @{$p->captures_constraints};
+                  $name .= " ($tc)";
+                } else {
+                  $name .= " ($cap)";
+                }
             }
             if (defined(my $ct = $p->list_extra_info->{Consumes})) {
                 $name .= ' :'.$ct;
             }
+            if (defined(my $s = $p->list_extra_info->{Scheme})) {
+                $scheme = uc $s;
+            }
 
             unless ($p eq $parents[0]) {
                 $name = "-> ${name}";
             }
             push(@rows, [ '', $name ]);
         }
-        push(@rows, [ '', (@rows ? "=> " : '').($extra ? "$extra " : '')."/${endpoint}". ($consumes ? " :$consumes":"" ) ]);
-        $rows[0][0] = join('/', '', @parts) || '/';
+
+        if($endpoint->has_args_constraints) {
+          my $tc = join ',', @{$endpoint->args_constraints};
+          $endpoint .= " ($tc)";
+        } else {
+          $endpoint .= defined($endpoint->attributes->{Args}[0]) ? " ($args)" : " (...)";
+        }
+        push(@rows, [ '', (@rows ? "=> " : '').($extra ? "$extra " : ''). ($scheme ? "$scheme: ":'')."/${endpoint}". ($consumes ? " :$consumes":"" ) ]);
+        my @display_parts = map { $_ =~s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; decode_utf8 $_ } @parts;
+        $rows[0][0] = join('/', '', @display_parts) || '/';
         $paths->row(@$_) for @rows;
     }
 
@@ -162,6 +180,11 @@ sub _list_extra_consumes {
     return join(', ', @{$action->list_extra_info->{CONSUMES}});
 }
 
+sub _list_extra_scheme {
+    my ( $self, $action ) = @_;
+    return unless defined $action->list_extra_info->{Scheme};
+    return uc $action->list_extra_info->{Scheme};
+}
 
 =head2 $self->match( $c, $path )
 
@@ -224,8 +247,10 @@ sub recurse_match {
         }
         my @try_actions = @{$children->{$try_part}};
         TRY_ACTION: foreach my $action (@try_actions) {
+
+
             if (my $capture_attr = $action->attributes->{CaptureArgs}) {
-                my $capture_count = $capture_attr->[0] || 0;
+                my $capture_count = $action->number_of_captures|| 0;
 
                 # Short-circuit if not enough remaining parts
                 next TRY_ACTION unless @parts >= $capture_count;
@@ -237,7 +262,7 @@ sub recurse_match {
                 push(@captures, splice(@parts, 0, $capture_count));
 
                 # check if the action may fit, depending on a given test by the app
-                if ($action->can('match_captures')) { next TRY_ACTION unless $action->match_captures($c, \@captures) }
+                next TRY_ACTION unless $action->match_captures($c, \@captures);
 
                 # try the remaining parts against children of this action
                 my ($actions, $captures, $action_parts, $n_pathparts) = $self->recurse_match(
@@ -267,6 +292,7 @@ sub recurse_match {
                     next TRY_ACTION unless $action->match($c);
                 }
                 my $args_attr = $action->attributes->{Args}->[0];
+                my $args_count = $action->normalized_arg_number;
                 my @pathparts = split /\//, $action->attributes->{PathPart}->[0];
                 #    No best action currently
                 # OR This one matches with fewer parts left than the current best action,
@@ -274,14 +300,29 @@ sub recurse_match {
                 # OR No parts and this expects 0
                 #    The current best action might also be Args(0),
                 #    but we couldn't chose between then anyway so we'll take the last seen
-
-                if (!$best_action                       ||
+                if (
+                    !$best_action                       ||
                     @parts < @{$best_action->{parts}}   ||
-                    (!@parts && defined($args_attr) && $args_attr eq "0")){
+                    (
+                        !@parts && 
+                        defined($args_attr) && 
+                        (
+                            $args_count eq "0" &&
+                            (
+                              ($c->config->{use_chained_args_0_special_case}||0) || 
+                                (
+                                  exists($best_action->{args_count}) && defined($best_action->{args_count}) ?
+                                  ($best_action->{args_count} ne 0) : 1
+                                )
+                            )
+                        )
+                    )
+                ){
                     $best_action = {
                         actions => [ $action ],
                         captures=> [],
                         parts   => \@parts,
+                        args_count => $args_count,
                         n_pathparts => scalar(@pathparts),
                     };
                 }
@@ -298,32 +339,6 @@ Calls register_path for every Path attribute for the given $action.
 
 =cut
 
-sub _check_args_attr {
-    my ( $self, $action, $name ) = @_;
-
-    return unless exists $action->attributes->{$name};
-
-    if (@{$action->attributes->{$name}} > 1) {
-        Catalyst::Exception->throw(
-          "Multiple $name attributes not supported registering " . $action->reverse()
-        );
-    }
-    my $args = $action->attributes->{$name}->[0];
-    if (defined($args) and not (
-        Scalar::Util::looks_like_number($args) and
-        int($args) == $args and $args >= 0
-    )) {
-        require Data::Dumper;
-        local $Data::Dumper::Terse = 1;
-        local $Data::Dumper::Indent = 0;
-        $args = Data::Dumper::Dumper($args);
-        Catalyst::Exception->throw(
-          "Invalid $name($args) for action " . $action->reverse() .
-          " (use '$name' or '$name(<number>)')"
-        );
-    }
-}
-
 sub register {
     my ( $self, $c, $action ) = @_;
 
@@ -362,15 +377,14 @@ sub register {
         );
     }
 
-    $action->attributes->{PathPart} = [ $part ];
+    my $encoded_part = URI->new($part)->canonical;
+    $encoded_part =~ s{(?<=[^/])/+\z}{};
 
-    unshift(@{ $children->{$part} ||= [] }, $action);
+    $action->attributes->{PathPart} = [ $encoded_part ];
 
-    $self->_actions->{'/'.$action->reverse} = $action;
+    unshift(@{ $children->{$encoded_part} ||= [] }, $action);
 
-    foreach my $name (qw(Args CaptureArgs)) {
-        $self->_check_args_attr($action, $name);
-    }
+    $self->_actions->{'/'.$action->reverse} = $action;
 
     if (exists $action->attributes->{Args} and exists $action->attributes->{CaptureArgs}) {
         Catalyst::Exception->throw(
@@ -403,11 +417,15 @@ sub uri_for_action {
     my @captures = @$captures;
     my $parent = "DUMMY";
     my $curr = $action;
+    # If this is an action chain get the last action in the chain
+    if($curr->can('chain') ) {
+      $curr = ${$curr->chain}[-1];
+    }
     while ($curr) {
-        if (my $cap = $curr->attributes->{CaptureArgs}) {
-            return undef unless @captures >= ($cap->[0]||0); # not enough captures
-            if ($cap->[0]) {
-                unshift(@parts, splice(@captures, -$cap->[0]));
+        if (my $cap = $curr->number_of_captures) {
+            return undef unless @captures >= $cap; # not enough captures
+            if ($cap) {
+                unshift(@parts, splice(@captures, -$cap));
             }
         }
         if (my $pp = $curr->attributes->{PathPart}) {
@@ -691,6 +709,32 @@ An action that is part of a chain (that is, one that has a C<:Chained>
 attribute) but has no C<:CaptureArgs> attribute is treated by Catalyst
 as a chain end.
 
+Allowed values for CaptureArgs is a single integer (CaptureArgs(2), meaning two
+allowed) or you can declare a L<Moose>, L<MooseX::Types> or L<Type::Tiny>
+named constraint such as CaptureArgs(Int,Str) would require two args with
+the first being a Integer and the second a string.  You may declare your own
+custom type constraints and import them into the controller namespace:
+
+    package MyApp::Controller::Root;
+
+    use Moose;
+    use MooseX::MethodAttributes;
+    use MyApp::Types qw/Int/;
+
+    extends 'Catalyst::Controller';
+
+    sub chain_base :Chained(/) CaptureArgs(1) { }
+
+      sub any_priority_chain :Chained(chain_base) PathPart('') Args(1) { }
+
+      sub int_priority_chain :Chained(chain_base) PathPart('') Args(Int) { }
+
+If you use a reference type constraint in CaptureArgs, it must be a type
+like Tuple in L<Types::Standard> that allows us to determine the number of
+args to match.  Otherwise this will raise an error during startup.
+
+See L<Catalyst::RouteMatching> for more.
+
 =item Args
 
 By default, endpoints receive the rest of the arguments in the path. You
@@ -709,6 +753,9 @@ Just as with C<:CaptureArgs>, the arguments get passed to the action in
 C<@_> after the context object. They can also be reached through
 C<$c-E<gt>request-E<gt>arguments>.
 
+You should see 'Args' in L<Catalyst::Controller> for more details on using
+type constraints in your Args declarations.
+
 =back
 
 =head2 Auto actions, dispatching and forwarding
index 0578ff4..38719ea 100644 (file)
@@ -6,6 +6,7 @@ extends 'Catalyst::DispatchType';
 use Text::SimpleTable;
 use Catalyst::Utils;
 use URI;
+use Encode 2.21 'decode_utf8';
 
 has _paths => (
                is => 'rw',
@@ -55,12 +56,13 @@ sub list {
     );
     foreach my $path ( sort keys %{ $self->_paths } ) {
         foreach my $action ( @{ $self->_paths->{$path} } ) {
-            my $args  = $action->attributes->{Args}->[0];
+            my $args  = $action->number_of_args;
             my $parts = defined($args) ? '/*' x $args : '/...';
 
             my $display_path = "/$path/$parts";
             $display_path =~ s{/{1,}}{/}g;
-
+            $display_path =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; # deconvert urlencoded for pretty view 
+            $display_path = decode_utf8 $display_path;  # URI does encoding
             $paths->row( $display_path, "/$action" );
         }
     }
index 6fde402..f63267b 100644 (file)
@@ -15,6 +15,7 @@ use Text::SimpleTable;
 use Tree::Simple;
 use Tree::Simple::Visitor::FindByPath;
 use Class::Load qw(load_class try_load_class);
+use Encode 2.21 'decode_utf8';
 
 use namespace::clean -except => 'meta';
 
@@ -108,6 +109,9 @@ sub dispatch {
     }
     else {
         my $path  = $c->req->path;
+        $path =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
+        $path = decode_utf8($path);
+
         my $error = $path
           ? qq/Unknown resource "$path"/
           : "No default action defined";
@@ -385,10 +389,14 @@ sub prepare_action {
 
     s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg for grep { defined } @{$req->captures||[]};
 
-    $c->log->debug( 'Path is "' . $req->match . '"' )
-      if ( $c->debug && defined $req->match && length $req->match );
+    if($c->debug && defined $req->match && length $req->match) {
+      my $match = $req->match;
+      $match =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
+      $match = decode_utf8($match);
+      $c->log->debug( 'Path is "' . $match . '"' )
+    }
 
-    $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
+    $c->log->debug( 'Arguments are "' . join( '/', map { decode_utf8 $_ } @args ) . '"' )
       if ( $c->debug && @args );
 }
 
@@ -606,6 +614,7 @@ sub setup_actions {
     @{ $self->_registered_dispatch_types }{@classes} = (1) x @classes;
 
     foreach my $comp ( values %{ $c->components } ) {
+        $comp = $comp->() if ref($comp) eq 'CODE';
         $comp->register_actions($c) if $comp->can('register_actions');
     }
 
index 927d363..5e87e9f 100644 (file)
@@ -7,23 +7,20 @@ use CGI::Simple::Cookie;
 use Data::Dump qw/dump/;
 use Errno 'EWOULDBLOCK';
 use HTML::Entities;
-use HTTP::Body;
 use HTTP::Headers;
-use URI::QueryParam;
 use Plack::Loader;
 use Catalyst::EngineLoader;
-use Encode ();
+use Encode 2.21 'decode_utf8', 'encode', 'decode';
 use Plack::Request::Upload;
 use Hash::MultiValue;
-use utf8;
-
 use namespace::clean -except => 'meta';
+use utf8;
 
 # Amount of data to read from input on each pass
 our $CHUNKSIZE = 64 * 1024;
 
 # XXX - this is only here for compat, do not use!
-has env => ( is => 'rw', writer => '_set_env' );
+has env => ( is => 'rw', writer => '_set_env' , weak_ref=>1);
 my $WARN_ABOUT_ENV = 0;
 around env => sub {
   my ($orig, $self, @args) = @_;
@@ -133,7 +130,6 @@ sub finalize_body {
             # There's no body...
             $body = [];
         }
-
         $res->_response_cb->([ $res->status, \@headers, $body]);
         $res->_clear_response_cb;
 
@@ -163,11 +159,11 @@ sub finalize_body {
           }
           else {
               
-              # Case where body was set afgter calling ->write.  We'd prefer not to
+              # Case where body was set after calling ->write.  We'd prefer not to
               # support this, but I can see some use cases with the way most of the
-              # views work.
-
-              $self->write($c, $body );
+              # views work. Since body has already been encoded, we need to do
+              # an 'unencoded_write' here.
+              $self->unencoded_write( $c, $body );
           }
         }
 
@@ -577,14 +573,17 @@ process the query string and extract query parameters.
 sub prepare_query_parameters {
     my ($self, $c) = @_;
     my $env = $c->request->env;
-
-    if(my $query_obj = $env->{'plack.request.query'}) {
-         $c->request->query_parameters(
-           $c->request->_use_hash_multivalue ?
-              $query_obj->clone :
-              $query_obj->as_hashref_mixed);
-         return;
-    }
+    my $do_not_decode_query = $c->config->{do_not_decode_query};
+    my $default_query_encoding = $c->config->{default_query_encoding} || 
+      ($c->config->{decode_query_using_global_encoding} ?
+        $c->encoding : 'UTF-8');
+
+    my $decoder = sub {
+      my $str = shift;
+      return $str if $do_not_decode_query;
+      return $str unless $default_query_encoding;
+      return decode( $default_query_encoding, $str);
+    };
 
     my $query_string = exists $env->{QUERY_STRING}
         ? $env->{QUERY_STRING}
@@ -593,42 +592,21 @@ sub prepare_query_parameters {
     # Check for keywords (no = signs)
     # (yes, index() is faster than a regex :))
     if ( index( $query_string, '=' ) < 0 ) {
-        $c->request->query_keywords($self->unescape_uri($query_string));
+        my $keywords = $self->unescape_uri($query_string);
+        $keywords = $decoder->($keywords);
+        $c->request->query_keywords($keywords);
         return;
     }
 
-    my %query;
+    $query_string =~ s/\A[&;]+//;
 
-    # replace semi-colons
-    $query_string =~ s/;/&/g;
+    my $p = Hash::MultiValue->new(
+        map { defined $_ ? $decoder->($self->unescape_uri($_)) : $_ }
+        map { ( split /=/, $_, 2 )[0,1] } # slice forces two elements
+        split /[&;]+/, $query_string
+    );
 
-    my @params = grep { length $_ } split /&/, $query_string;
-
-    for my $item ( @params ) {
-
-        my ($param, $value)
-            = map { $self->unescape_uri($_) }
-              split( /=/, $item, 2 );
-
-        $param = $self->unescape_uri($item) unless defined $param;
-
-        if ( exists $query{$param} ) {
-            if ( ref $query{$param} ) {
-                push @{ $query{$param} }, $value;
-            }
-            else {
-                $query{$param} = [ $query{$param}, $value ];
-            }
-        }
-        else {
-            $query{$param} = $value;
-        }
-    }
-
-    $c->request->query_parameters( 
-      $c->request->_use_hash_multivalue ?
-        Hash::MultiValue->from_mixed(\%query) :
-        \%query);
+    $c->request->query_parameters( $c->request->_use_hash_multivalue ? $p : $p->mixed );
 }
 
 =head2 $self->prepare_read($c)
@@ -668,20 +646,26 @@ sub prepare_uploads {
     my $request = $c->request;
     return unless $request->_body;
 
+    my $enc = $c->encoding;
     my $uploads = $request->_body->upload;
     my $parameters = $request->parameters;
     foreach my $name (keys %$uploads) {
+        $name = $c->_handle_unicode_decoding($name) if $enc;
         my $files = $uploads->{$name};
         my @uploads;
         for my $upload (ref $files eq 'ARRAY' ? @$files : ($files)) {
             my $headers = HTTP::Headers->new( %{ $upload->{headers} } );
+            my $filename = $upload->{filename};
+            $filename = $c->_handle_unicode_decoding($filename) if $enc;
+
             my $u = Catalyst::Request::Upload->new
               (
                size => $upload->{size},
                type => scalar $headers->content_type,
+               charset => scalar $headers->content_type_charset,
                headers => $headers,
                tempname => $upload->{tempname},
-               filename => $upload->{filename},
+               filename => $filename,
               );
             push @uploads, $u;
         }
@@ -716,6 +700,20 @@ sub write {
     $c->response->write($buffer);
 }
 
+=head2 $self->unencoded_write($c, $buffer)
+
+Writes the buffer to the client without encoding. Necessary for
+already encoded buffers. Used when a $c->write has been done
+followed by $c->res->body.
+
+=cut
+
+sub unencoded_write {
+    my ( $self, $c, $buffer ) = @_;
+
+    $c->response->unencoded_write($buffer);
+}
+
 =head2 $self->read($c, [$maxlength])
 
 Reads from the input stream by calling C<< $self->read_chunk >>.
index e70197f..a599284 100644 (file)
@@ -141,6 +141,7 @@ sub _send_to_log {
     if ($self->can('_has_psgi_errors') and $self->_has_psgi_errors) {
         $self->_psgi_errors->print(@_);
     } else {
+        binmode STDERR, ":utf8";
         print STDERR @_;
     }
 }
index 170fa11..c8d5945 100644 (file)
@@ -9,12 +9,12 @@ use Carp 'croak';
 
 our @EXPORT_OK = qw(stash get_stash);
 
-sub PSGI_KEY { 'Catalyst.Stash.v1' };
+sub PSGI_KEY () { 'Catalyst.Stash.v2' }
 
 sub get_stash {
   my $env = shift;
-  return $env->{&PSGI_KEY} ||
-    _init_stash_in($env);
+  return $env->{+PSGI_KEY} ||
+   croak "You requested a stash, but one does not exist.";
 }
 
 sub stash {
@@ -24,6 +24,7 @@ sub stash {
 }
 
 sub _create_stash {
+  my $self = shift;
   my $stash = shift || +{};
   return sub {
     if(@_) {
@@ -38,19 +39,15 @@ sub _create_stash {
   };
 }
 
-sub _init_stash_in {
-  my ($env) = @_;
-  return $env->{&PSGI_KEY} ||=
-    _create_stash;
-}
-
 sub call {
   my ($self, $env) = @_;
-  _init_stash_in($env);
+  $env->{+PSGI_KEY} = $self->_create_stash 
+    unless exists($env->{+PSGI_KEY});
+
   return $self->app->($env);
 }
 
-=head1 TITLE
+=head1 NAME
 
 Catalyst::Middleware::Stash - The Catalyst stash - in middleware
 
@@ -61,7 +58,18 @@ directly since it is likely to move off the Catalyst namespace into a stand
 alone distribution
 
 We store a coderef under the C<PSGI_KEY> which can be dereferenced with
-key values or nothing to access the underly hashref.
+key values or nothing to access the underlying hashref.
+
+Anything placed into the stash will be available in the stash of any 'mounted'
+Catalyst applications.  A mounted Catalyst application may set the stash and
+'pass back' information to the parent application.  Non Catalyst applications
+may use this middleware to access and set stash values.
+
+Please note I highly recommend having a stronger interface than a stash key
+between applications.
+
+For more information the current test case t/middleware-stash.t is the best
+documentation.
 
 =head1 SUBROUTINES
 
@@ -104,7 +112,7 @@ clients.  Stash key / value are stored in memory.
         ["I found $stashed in the stash!"]];
     };
 
-If the stash does not yet exist, we initialize one and return that.
+If the stash does not yet exist, an exception is thrown.
 
 =head1 METHODS
 
diff --git a/lib/Catalyst/ROADMAP.pod b/lib/Catalyst/ROADMAP.pod
deleted file mode 100644 (file)
index 470b645..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-=head1 ROADMAP
-
-This is a living document, that represents the core team's current plans for
-the Catalyst framework. It's liable to change at any time. This document lives
-in the the catalyst trunk, currently at
-
-  http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits/Catalyst-Runtime.git;a=blob;f=lib/Catalyst/ROADMAP.pod;h=acb5775e4f9ec2db88ab90953f8cf175ba276009;hb=HEAD
-
-Make sure you get it from there to ensure you have the latest version.
-
-=head2 5.91000 
-
-=over
-
-=item Reduce core class data usage.
-
-Refactor everything that doesn't have to be class data into object data
-
-=item Work towards a declarative syntax mode
-
-Dispatcher refactoring to provide alternatives to deprecated methods, and
-support for pluggable dispatcher builders (so that attributes can be
-replaced).
-
-=back
-
-=head2 5.92000
-
-=over
-
-=item Extend pluggability of the Catalyst core.
-
-good support for reusable components good support for reusable plugins good
-separation of plugins (some reusable components want different plugins) near
-total engine independence
-
-=back
-
-=head2 6.00000
-
-=over
-
-=item  Application / Context Split 
-
-Catalyst needs to be split so that $c refers to the current context, and is a
-separate thing from the Application class.
-
-=back
-
-=head2 Wishlist
-
-=over
-
-=item move all inline pod to bottom of file.
-
-=item update pod coverage tests to detect stubbed pod, ensure real coverage
-
-=back
index 021bf24..1306b94 100644 (file)
@@ -10,7 +10,9 @@ use HTTP::Headers;
 use Stream::Buffered;
 use Hash::MultiValue;
 use Scalar::Util;
-
+use HTTP::Body;
+use Catalyst::Exception;
+use Catalyst::Request::PartData;
 use Moose;
 
 use namespace::clean -except => 'meta';
@@ -118,7 +120,11 @@ has body_data => (
 
 sub _build_body_data {
     my ($self) = @_;
-    my $content_type = $self->content_type;
+
+    # Not sure if these returns should not be exceptions...
+    my $content_type = $self->content_type || return;
+    return unless ($self->method eq 'POST' || $self->method eq 'PUT');
+
     my ($match) = grep { $content_type =~/$_/i }
       keys(%{$self->data_handlers});
 
@@ -127,7 +133,7 @@ sub _build_body_data {
       local $_ = $fh;
       return $self->data_handlers->{$match}->($fh, $self);
     } else { 
-      return undef;
+      Catalyst::Exception->throw("$content_type is does not have an available data handler");
     }
 }
 
@@ -174,6 +180,7 @@ has body_parameters => (
   is => 'rw',
   required => 1,
   lazy => 1,
+  predicate => 'has_body_parameters',
   builder => 'prepare_body_parameters',
 );
 
@@ -286,7 +293,10 @@ sub prepare_body {
     # Check for definedness as you could read '0'
     while ( defined ( my $chunk = $self->read() ) ) {
         $self->prepare_body_chunk($chunk);
-        $stream_buffer->print($chunk) if $stream_buffer;
+        next unless $stream_buffer;
+
+        $stream_buffer->print($chunk)
+            || die sprintf "Failed to write %d bytes to psgi.input file: $!", length( $chunk );
     }
 
     # Ok, we read the body.  Lets play nice for any PSGI app down the pipe
@@ -312,17 +322,60 @@ sub prepare_body_chunk {
 }
 
 sub prepare_body_parameters {
-    my ( $self ) = @_;
-
+    my ( $self, $c ) = @_;
+    return $self->body_parameters if $self->has_body_parameters;
     $self->prepare_body if ! $self->_has_body;
 
     unless($self->_body) {
-      return $self->_use_hash_multivalue ? Hash::MultiValue->new : {};
+      my $return = $self->_use_hash_multivalue ? Hash::MultiValue->new : {};
+      $self->body_parameters($return);
+      return $return;
+    }
+
+    my $params;
+    my %part_data = %{$self->_body->part_data};
+    if(scalar %part_data && !$c->config->{skip_complex_post_part_handling}) {
+      foreach my $key (keys %part_data) {
+        my $proto_value = $part_data{$key};
+        my ($val, @extra) = (ref($proto_value)||'') eq 'ARRAY' ? @$proto_value : ($proto_value);
+
+        $key = $c->_handle_param_unicode_decoding($key)
+          if ($c and $c->encoding and !$c->config->{skip_body_param_unicode_decoding});
+
+        if(@extra) {
+          $params->{$key} = [map { Catalyst::Request::PartData->build_from_part_data($c, $_) } ($val,@extra)];
+        } else {
+          $params->{$key} = Catalyst::Request::PartData->build_from_part_data($c, $val);
+        }
+      }
+    } else {
+      $params = $self->_body->param;
+
+      # If we have an encoding configured (like UTF-8) in general we expect a client
+      # to POST with the encoding we fufilled the request in. Otherwise don't do any
+      # encoding (good change wide chars could be in HTML entity style llike the old
+      # days -JNAP
+
+      # so, now that HTTP::Body prepared the body params, we gotta 'walk' the structure
+      # and do any needed decoding.
+
+      # This only does something if the encoding is set via the encoding param.  Remember
+      # this is assuming the client is not bad and responds with what you provided.  In
+      # general you can just use utf8 and get away with it.
+      #
+      # I need to see if $c is here since this also doubles as a builder for the object :(
+
+      if($c and $c->encoding and !$c->config->{skip_body_param_unicode_decoding}) {
+        $params = $c->_handle_unicode_decoding($params);
+      }
     }
 
-    return $self->_use_hash_multivalue ?
-        Hash::MultiValue->from_mixed($self->_body->param) :
-        $self->_body->param;
+    my $return = $self->_use_hash_multivalue ?
+        Hash::MultiValue->from_mixed($params) :
+        $params;
+
+    $self->body_parameters($return) unless $self->has_body_parameters;
+    return $return;
 }
 
 sub prepare_connection {
@@ -502,6 +555,13 @@ data of the type 'application/json' and return access to that data via this
 method.  You may define addition data_handlers via a global configuration
 setting.  See L<Catalyst\DATA HANDLERS> for more information.
 
+If the POST is malformed in some way (such as undefined or not content that
+matches the content-type) we raise a L<Catalyst::Exception> with the error
+text as the message.
+
+If the POSTed content type does not match an available data handler, this
+will also raise an exception.
+
 =head2 $req->body_parameters
 
 Returns a reference to a hash containing body (POST) parameters. Values can
@@ -512,6 +572,16 @@ be either a scalar or an arrayref containing scalars.
 
 These are the parameters from the POST part of the request, if any.
 
+B<NOTE> If your POST is multipart, but contains non file upload parts (such
+as an line part with an alternative encoding or content type) we do our best to
+try and figure out how the value should be presented.  If there's a specified character
+set we will use that to decode rather than the default encoding set by the application.
+However if there are complex headers and we cannot determine
+the correct way to extra a meaningful value from the upload, in this case any
+part like this will be represented as an instance of L<Catalyst::Request::PartData>.
+
+Patches and review of this part of the code welcomed.
+
 =head2 $req->body_params
 
 Shortcut for body_parameters.
@@ -636,6 +706,60 @@ If multiple C<baz> parameters are provided this code might corrupt data or
 cause a hash initialization error. For a more straightforward interface see
 C<< $c->req->parameters >>.
 
+B<NOTE> Interfaces like this, which are based on L<CGI> and the C<param> method
+are known to cause demonstrated exploits. It is highly recommended that you
+avoid using this method, and migrate existing code away from it.  Here's a
+whitepaper of the exploit:
+
+L<http://blog.gerv.net/2014/10/new-class-of-vulnerability-in-perl-web-applications/>
+
+B<NOTE> Further discussion on IRC indicate that the L<Catalyst> core team from 'back then'
+were well aware of this hack and this is the main reason we added the new approach to
+getting parameters in the first place.
+
+Basically this is an exploit that takes advantage of how L<\param> will do one thing
+in scalar context and another thing in list context.  This is combined with how Perl
+chooses to deal with duplicate keys in a hash definition by overwriting the value of
+existing keys with a new value if the same key shows up again.  Generally you will be
+vulnerable to this exploit if you are using this method in a direct assignment in a
+hash, such as with a L<DBIx::Class> create statement.  For example, if you have
+parameters like:
+
+    user?user=123&foo=a&foo=user&foo=456
+
+You could end up with extra parameters injected into your method calls:
+
+    $c->model('User')->create({
+      user => $c->req->param('user'),
+      foo => $c->req->param('foo'),
+    });
+
+Which would look like:
+
+    $c->model('User')->create({
+      user => 123,
+      foo => qw(a user 456),
+    });
+
+(or to be absolutely clear if you are not seeing it):
+
+    $c->model('User')->create({
+      user => 456,
+      foo => 'a',
+    });
+
+Possible remediations include scrubbing your parameters with a form validator like
+L<HTML::FormHandler> or being careful to force scalar context using the scalar
+keyword:
+
+    $c->model('User')->create({
+      user => scalar($c->req->param('user')),
+      foo => scalar($c->req->param('foo')),
+    });
+
+Upcoming versions of L<Catalyst> will disable this interface by default and require
+you to positively enable it should you require it for backwards compatibility reasons.
+
 =cut
 
 sub param {
@@ -877,7 +1001,7 @@ sub mangle_params {
         next unless defined $value;
         for ( ref $value eq 'ARRAY' ? @$value : $value ) {
             $_ = "$_";
-            utf8::encode( $_ ) if utf8::is_utf8($_);
+            #      utf8::encode($_);
         }
     };
 
diff --git a/lib/Catalyst/Request/PartData.pm b/lib/Catalyst/Request/PartData.pm
new file mode 100644 (file)
index 0000000..d6358f3
--- /dev/null
@@ -0,0 +1,159 @@
+package Catalyst::Request::PartData;
+
+use Moose;
+use HTTP::Headers;
+use Encode;
+
+has [qw/raw_data name size/] => (is=>'ro', required=>1);
+
+has headers => (
+  is=>'ro',
+  required=>1,
+  handles=>[qw/content_type content_encoding content_type_charset/]);
+
+sub build_from_part_data {
+  my ($class, $c, $part_data) = @_;
+
+  # If the headers are complex, we need to work harder to figure out what to do
+  if(my $hdrs = $class->part_data_has_complex_headers($part_data)) {
+
+    # Ok so its one of two possibilities.  If I can inspect the headers and
+    # Figure out what to do, the I will return data.  Otherwise I will return
+    # a PartData object and expect you do deal with it.
+    # For now if I can find a charset in the content type I will just decode and
+    # assume I got it right (patches and bug reports welcomed).
+
+    # Any of these headers means I can't decode
+
+    if(
+        $hdrs->content_encoding
+    ) {
+      return $class->new(
+        raw_data => $part_data->{data},
+        name => $part_data->{name},
+        size => $part_data->{size},
+        headers => HTTP::Headers->new(%{ $part_data->{headers} }));
+    }
+
+    my ($ct, $charset) = $hdrs->content_type_charset;
+
+    if($ct) {
+      # Good news, we probably have data we can return.  If there is a charset
+      # then use that to decode otherwise use the default decoding.
+      if($charset) {
+        return  Encode::decode($charset, $part_data->{data})
+      } else {
+        if($c and $c->encoding and !$c->config->{skip_body_param_unicode_decoding}) {
+          return $c->_handle_param_unicode_decoding($part_data->{data});
+        } else {
+          return $part_data->{data}
+        }
+      }
+    } else {
+      # I have no idea what to do with this now..
+      return $class->new(
+        raw_data => $part_data->{data},
+        name => $part_data->{name},
+        size => $part_data->{size},
+        headers => HTTP::Headers->new(%{ $part_data->{headers} }));
+    }
+  } else {
+    if($c and $c->encoding and !$c->config->{skip_body_param_unicode_decoding}) {
+      return $c->_handle_param_unicode_decoding($part_data->{data});
+    } else {
+      return $part_data->{data}
+    }
+  }
+
+  return $part_data->{data} unless $class->part_data_has_complex_headers($part_data);
+  return $class->new(
+    raw_data => $part_data->{data},
+    name => $part_data->{name},
+    size => $part_data->{size},
+    headers => HTTP::Headers->new(%{ $part_data->{headers} }));
+}
+
+sub part_data_has_complex_headers {
+  my ($class, $part_data) = @_;
+  my %h = %{$part_data->{headers}};
+  my $hdrs = HTTP::Headers->new(%h);
+
+  # Remove non threatening headers.
+  $hdrs->remove_header('Content-Length', 'Expires', 'Last-Modified', 'Content-Language');
+
+  # If we still have more than one (Content-Disposition) header we need to understand
+  # that and deal with it.
+
+  return $hdrs->header_field_names > 1 ? $hdrs :0;
+}
+
+__PACKAGE__->meta->make_immutable;
+
+=head1 NAME
+
+Catalyst::Request::Upload - handles file upload requests
+
+=head1 SYNOPSIS
+
+    my $data_part = 
+
+To specify where Catalyst should put the temporary files, set the 'uploadtmp'
+option in the Catalyst config. If unset, Catalyst will use the system temp dir.
+
+    __PACKAGE__->config( uploadtmp => '/path/to/tmpdir' );
+
+See also L<Catalyst>.
+
+=head1 DESCRIPTION
+
+=head1 ATTRIBUTES
+
+This class defines the following immutable attributes
+
+=head2 raw_data
+
+The raw data as returned via L<HTTP::Body>.
+
+=head2 name
+
+The part name that gets extracted from the content-disposition header.
+
+=head2 size
+
+The raw byte count (over http) of the data.  This is not the same as the character
+length
+
+=head2 headers
+
+An L<HTTP::Headers> object that represents the submitted headers of the POST.  This
+object will handle the following methods:
+
+=head3 content_type
+
+=head3 content_encoding
+
+=head3 content_type_charset
+
+These three methods are the same as methods described in L<HTTP::Headers>.
+
+=head1 METHODS
+
+=head2 build_from_part_data
+
+Factory method to build an object from part data returned by L<HTTP::Body>
+
+=head2 part_data_has_complex_headers
+
+Returns true if there more than one header (indicates the part data is complex and
+contains content type and encoding information.).
+
+=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
index d8e58be..bf9318f 100644 (file)
@@ -15,6 +15,8 @@ has size => (is => 'rw');
 has tempname => (is => 'rw');
 has type => (is => 'rw');
 has basename => (is => 'ro', lazy_build => 1);
+has raw_basename => (is => 'ro', lazy_build => 1);
+has charset => (is=>'ro', predicate=>'has_charset');
 
 has fh => (
   is => 'rw',
@@ -29,17 +31,21 @@ has fh => (
       Catalyst::Exception->throw(
           message => qq/Can't open '$filename': '$!'/ );
     }
-
     return $fh;
   },
 );
 
 sub _build_basename {
+    my $basename = shift->raw_basename;
+    $basename =~ s|[^\w\.-]+|_|g;
+    return $basename;
+}
+
+sub _build_raw_basename {
     my $self = shift;
     my $basename = $self->filename;
     $basename =~ s|\\|/|g;
     $basename = ( File::Spec::Unix->splitpath($basename) )[2];
-    $basename =~ s|[^\w\.-]+|_|g;
     return $basename;
 }
 
@@ -58,13 +64,16 @@ Catalyst::Request::Upload - handles file upload requests
     $upload->basename;
     $upload->copy_to;
     $upload->fh;
+    $upload->decoded_fh
     $upload->filename;
     $upload->headers;
     $upload->link_to;
     $upload->size;
     $upload->slurp;
+    $upload->decoded_slurp;
     $upload->tempname;
     $upload->type;
+    $upload->charset;
 
 To specify where Catalyst should put the temporary files, set the 'uploadtmp'
 option in the Catalyst config. If unset, Catalyst will use the system temp dir.
@@ -90,6 +99,14 @@ false for failure.
 
      $upload->copy_to('/path/to/target');
 
+Please note the filename used for the copy target is the 'tempname' that
+is the actual filename on the filesystem, NOT the 'filename' that was
+part of the upload headers.  This might seem counter intuitive but at this
+point this behavior is so established that its not something we can change.
+
+You can always create your own copy routine that munges the target path
+as you wish.
+
 =cut
 
 sub copy_to {
@@ -97,10 +114,56 @@ sub copy_to {
     return File::Copy::copy( $self->tempname, @_ );
 }
 
+=head2 $upload->is_utf8_encoded
+
+Returns true of the upload defines a character set at that value is 'UTF-8'.
+This does not try to inspect your upload and make any guesses if the Content
+Type charset is undefined.
+
+=cut
+
+sub is_utf8_encoded {
+    my $self = shift;
+    if(my $charset = $self->charset) {
+      return $charset eq 'UTF-8' ? 1 : 0;
+    }
+    return 0;
+}
+
 =head2 $upload->fh
 
 Opens a temporary file (see tempname below) and returns an L<IO::File> handle.
 
+This is a filehandle that is opened with no additional IO Layers.
+
+=head2 $upload->decoded_fh(?$encoding)
+
+Returns a filehandle that has binmode set to UTF-8 if a UTF-8 character set
+is found. This also accepts an override encoding value that you can use to
+force a particular L<PerlIO> layer.  If neither are found the filehandle is
+set to :raw.
+
+This is useful if you are pulling the file into code and inspecting bits and
+maybe then sending those bits back as the response.  (Please note this is not
+a suitable filehandle to set in the body; use C<fh> if you are doing that).
+
+Please note that using this method sets the underlying filehandle IO layer
+so once you use this method if you go back and use the C<fh> method you
+still get the IO layer applied.
+
+=cut
+
+sub decoded_fh {
+    my ($self, $layer) = @_;
+    my $fh  = $self->fh;
+
+    $layer = ":encoding(UTF-8)" if !$layer && $self->is_utf8_encoded;
+    $layer = ':raw' unless $layer;
+
+    binmode($fh, $layer);
+    return $fh;
+}
+
 =head2 $upload->filename
 
 Returns the client-supplied filename.
@@ -127,13 +190,17 @@ sub link_to {
 
 Returns the size of the uploaded file in bytes.
 
-=head2 $upload->slurp
+=head2 $upload->slurp(?$encoding)
+
+Optionally accepts an argument to define an IO Layer (which is applied to
+the filehandle via binmode; if no layer is defined the default is set to
+":raw".
 
 Returns a scalar containing the contents of the temporary file.
 
 Note that this will cause the filehandle pointed to by C<< $upload->fh >> to
 be reset to the start of the file using seek and the file handle to be put
-into binary mode.
+into whatever encoding mode is applied.
 
 =cut
 
@@ -158,9 +225,39 @@ sub slurp {
     return $content;
 }
 
+=head2 $upload->decoded_slurp(?$encoding)
+
+Works just like C<slurp> except we use C<decoded_fh> instead of C<fh> to
+open a filehandle to slurp.  This means if your upload charset is UTF8
+we binmode the filehandle to that encoding.
+
+=cut
+
+sub decoded_slurp {
+    my ( $self, $layer ) = @_;
+    my $handle = $self->decoded_fh($layer);
+
+    my $content = undef;
+    $handle->seek(0, IO::File::SEEK_SET);
+    while ( $handle->sysread( my $buffer, 8192 ) ) {
+        $content .= $buffer;
+    }
+
+    $handle->seek(0, IO::File::SEEK_SET);
+    return $content;
+}
+
 =head2 $upload->basename
 
-Returns basename for C<filename>.
+Returns basename for C<filename>.  This filters the name through a regexp
+C<basename =~ s|[^\w\.-]+|_|g> to make it safe for filesystems that don't
+like advanced characters.  This will of course filter UTF8 characters.
+If you need the exact basename unfiltered use C<raw_basename>.
+
+=head2 $upload->raw_basename
+
+Just like C<basename> but without filtering the filename for characters that
+don't always write to a filesystem.
 
 =head2 $upload->tempname
 
@@ -170,6 +267,11 @@ Returns the path to the temporary file.
 
 Returns the client-supplied Content-Type.
 
+=head2 $upload->charset
+
+The character set information part of the content type, if any.  Useful if you
+need to figure out any encodings on the file upload.
+
 =head2 meta
 
 Provided by Moose
index f049ebf..e87ba61 100644 (file)
@@ -4,9 +4,20 @@ use Moose;
 use HTTP::Headers;
 use Moose::Util::TypeConstraints;
 use namespace::autoclean;
+use Scalar::Util 'blessed';
+use Catalyst::Response::Writer;
+use Catalyst::Utils ();
 
 with 'MooseX::Emulate::Class::Accessor::Fast';
 
+our $DEFAULT_ENCODE_CONTENT_TYPE_MATCH = qr{text|xml$|javascript$};
+
+has encodable_content_type => (
+    is => 'rw',
+    required => 1,
+    default => sub { $DEFAULT_ENCODE_CONTENT_TYPE_MATCH }
+);
+
 has _response_cb => (
     is      => 'ro',
     isa     => 'CodeRef', 
@@ -51,7 +62,17 @@ has write_fh => (
   builder=>'_build_write_fh',
 );
 
-sub _build_write_fh { shift ->_writer }
+sub _build_write_fh {
+  my $writer = $_[0]->_writer; # We need to get the finalize headers side effect...
+  my $requires_encoding = $_[0]->encodable_response;
+  my %fields = (
+    _writer => $writer,
+    _context => $_[0]->_context,
+    _requires_encoding => $requires_encoding,
+  );
+
+  return bless \%fields, 'Catalyst::Response::Writer';
+}
 
 sub DEMOLISH {
   my $self = shift;
@@ -71,7 +92,7 @@ has finalized_headers => (is => 'rw', default => 0);
 has headers   => (
   is      => 'rw',
   isa => 'HTTP::Headers',
-  handles => [qw(content_encoding content_length content_type header)],
+  handles => [qw(content_encoding content_length content_type content_type_charset header)],
   default => sub { HTTP::Headers->new() },
   required => 1,
   lazy => 1,
@@ -86,9 +107,9 @@ before [qw(status headers content_encoding content_length content_type header)]
   my $self = shift;
 
   $self->_context->log->warn( 
-    "Useless setting a header value after finalize_headers called." .
+    "Useless setting a header value after finalize_headers and the response callback has been called." .
     " Not what you want." )
-      if ( $self->finalized_headers && @_ );
+      if ( $self->_context && $self->finalized_headers && !$self->_has_response_cb && @_ );
 };
 
 sub output { shift->body(@_) }
@@ -103,6 +124,24 @@ sub write {
 
     $buffer = q[] unless defined $buffer;
 
+    if($self->encodable_response) {
+      $buffer = $self->_context->encoding->encode( $buffer, $self->_context->_encode_check )
+    }
+
+    my $len = length($buffer);
+    $self->_writer->write($buffer);
+
+    return $len;
+}
+
+sub unencoded_write {
+    my ( $self, $buffer ) = @_;
+
+    # Finalize headers if someone manually writes output
+    $self->_context->finalize_headers unless $self->finalized_headers;
+
+    $buffer = q[] unless defined $buffer;
+
     my $len = length($buffer);
     $self->_writer->write($buffer);
 
@@ -116,11 +155,17 @@ sub finalize_headers {
 
 sub from_psgi_response {
     my ($self, $psgi_res) = @_;
+    if(blessed($psgi_res) && $psgi_res->can('as_psgi')) {
+      $psgi_res = $psgi_res->as_psgi;
+    }
     if(ref $psgi_res eq 'ARRAY') {
         my ($status, $headers, $body) = @$psgi_res;
         $self->status($status);
         $self->headers(HTTP::Headers->new(@$headers));
-        $self->body($body);
+        # Can be arrayref or filehandle...
+        if(defined $body) { # probably paranoia
+          ref $body eq 'ARRAY' ? $self->body(join('', @$body)) : $self->body($body);
+        }
     } elsif(ref $psgi_res eq 'CODE') {
         $psgi_res->(sub {
             my $response = shift;
@@ -128,7 +173,8 @@ sub from_psgi_response {
             $self->status($status);
             $self->headers(HTTP::Headers->new(@$headers));
             if(defined $maybe_body) {
-                $self->body($maybe_body);
+                # Can be arrayref or filehandle...
+                ref $maybe_body eq 'ARRAY' ? $self->body(join('', @$maybe_body)) : $self->body($maybe_body);
             } else {
                 return $self->write_fh;
             }
@@ -136,6 +182,15 @@ sub from_psgi_response {
      } else {
         die "You can't set a Catalyst response from that, expect a valid PSGI response";
     }
+
+    # Encoding compatibilty.   If the response set a charset, well... we need
+    # to assume its properly encoded and NOT encode for this response.  Otherwise
+    # We risk double encoding.
+    if($self->content_type_charset) {
+      # We have to do this since for backcompat reasons having a charset doesn't always
+      # mean that the body is already encoded :(
+      $self->_context->clear_encoding;
+    }
 }
 
 =head1 NAME
@@ -171,9 +226,26 @@ will turn the Catalyst::Response into a HTTP Response and return it to the clien
     $c->response->body('Catalyst rocks!');
 
 Sets or returns the output (text or binary data). If you are returning a large body,
-you might want to use a L<IO::Handle> type of object (Something that implements the read method
-in the same fashion), or a filehandle GLOB. Catalyst
-will write it piece by piece into the response.
+you might want to use a L<IO::Handle> type of object (Something that implements the getline method 
+in the same fashion), or a filehandle GLOB. These will be passed down to the PSGI
+handler you are using and might be optimized using server specific abilities (for
+example L<Twiggy> will attempt to server a real local file in a non blocking manner).
+
+If you are using a filehandle as the body response you are responsible for
+making sure it conforms to the L<PSGI> specification with regards to content
+encoding.  Unlike with scalar body values or when using the streaming interfaces
+we currently do not attempt to normalize and encode your filehandle.  In general
+this means you should be sure to be sending bytes not UTF8 decoded multibyte
+characters.
+
+Most of the time when you do:
+
+    open(my $fh, '<:raw', $path);
+
+You should be fine.  If you open a filehandle with a L<PerlIO> layer you probably
+are not fine.  You can usually fix this by explicitly using binmode to set
+the IOLayer to :raw.  Its possible future versions of L<Catalyst> will try to
+'do the right thing'.
 
 When using a L<IO::Handle> type of object and no content length has been
 already set in the response headers Catalyst will make a reasonable attempt
@@ -184,7 +256,11 @@ it is recommended that you set the content length in the response headers
 yourself, which will be respected and sent by Catalyst in the response.
 
 Please note that the object needs to implement C<getline>, not just
-C<read>.
+C<read>.  Older versions of L<Catalyst> expected your filehandle like objects
+to do read.  If you have code written for this expectation and you cannot
+change the code to meet the L<PSGI> specification, you can try the following
+middleware L<Plack::Middleware::AdaptFilehandleRead> which will attempt to
+wrap your object in an interface that so conforms.
 
 Starting from version 5.90060, when using an L<IO::Handle> object, you
 may want to use L<Plack::Middleware::XSendfile>, to delegate the
@@ -286,6 +362,10 @@ This value is typically set by your view or plugin. For example,
 L<Catalyst::Plugin::Static::Simple> will guess the mime type based on the file
 it found, while L<Catalyst::View::TT> defaults to C<text/html>.
 
+=head2 $res->content_type_charset
+
+Shortcut for $res->headers->content_type_charset;
+
 =head2 $res->cookies
 
 Returns a reference to a hash containing cookies to be set. The keys of the
@@ -347,6 +427,12 @@ qualified (= C<http://...>, etc.) or that starts with a slash
 thing and is not a standard behaviour. You may opt to use uri_for() or
 uri_for_action() instead.
 
+B<Note:> If $url is an object that does ->as_string (such as L<URI>, which is
+what you get from ->uri_for) we automatically call that to stringify.  This
+should ease the common case usage
+
+    return $c->res->redirect( $c->uri_for(...));
+
 =cut
 
 sub redirect {
@@ -356,6 +442,10 @@ sub redirect {
         my $location = shift;
         my $status   = shift || 302;
 
+        if(blessed($location) && $location->can('as_string')) {
+            $location = $location->as_string;
+        }
+
         $self->location($location);
         $self->status($status);
     }
@@ -377,13 +467,45 @@ $res->code is an alias for this, to match HTTP::Response->code.
 
 =head2 $res->write( $data )
 
-Writes $data to the output stream.
+Writes $data to the output stream.  Calling this method will finalize your
+headers and send the headers and status code response to the client (so changing
+them afterwards is a waste... be sure to set your headers correctly first).
+
+You may call this as often as you want throughout your response cycle.  You may
+even set a 'body' afterward.  So for example you might write your HTTP headers
+and the HEAD section of your document and then set the body from a template
+driven from a database.  In some cases this can seem to the client as if you had
+a faster overall response (but note that unless your server support chunked
+body your content is likely to get queued anyway (L<Starman> and most other 
+http 1.1 webservers support this).
+
+If there is an encoding set, we encode each line of the response (the default
+encoding is UTF-8).
+
+=head2 $res->unencoded_write( $data )
+
+Works just like ->write but we don't apply any content encoding to C<$data>.  Use
+this if you are already encoding the $data or the data is arriving from an encoded
+storage.
 
 =head2 $res->write_fh
 
-Returns a PSGI $writer object that has two methods, write and close.  You can
-close over this object for asynchronous and nonblocking applications.  For
-example (assuming you are using a supporting server, like L<Twiggy>
+Returns an instance of L<Catalyst::Response::Writer>, which is a lightweight
+decorator over the PSGI C<$writer> object (see L<PSGI.pod\Delayed-Response-and-Streaming-Body>).
+
+In addition to proxying the C<write> and C<close> method from the underlying PSGI
+writer, this proxy object knows any application wide encoding, and provides a method
+C<write_encoded> that will properly encode your written lines based upon your
+encoding settings.  By default in L<Catalyst> responses are UTF-8 encoded and this
+is the encoding used if you respond via C<write_encoded>.  If you want to handle
+encoding yourself, you can use the C<write> method directly.
+
+Encoding only applies to content types for which it matters.  Currently the following
+content types are assumed to need encoding: text (including HTML), xml and javascript.
+
+We provide access to this object so that you can properly close over it for use in
+asynchronous and nonblocking applications.  For example (assuming you are using a supporting
+server, like L<Twiggy>:
 
     package AsyncExample::Controller::Root;
 
@@ -413,6 +535,10 @@ example (assuming you are using a supporting server, like L<Twiggy>
         });
     }
 
+Like the 'write' method, calling this will finalize headers. Unlike 'write' when you
+can this it is assumed you are taking control of the response so the body is never
+finalized (there isn't one anyway) and you need to call the close method.
+
 =head2 $res->print( @data )
 
 Prints @data to the output stream, separated by $,.  This lets you pass
@@ -430,6 +556,8 @@ a $responder) set the response from it.
 Properly supports streaming and delayed response and / or async IO if running
 under an expected event loop.
 
+If passed an object, will expect that object to do a method C<as_psgi>.
+
 Example:
 
     package MyApp::Web::Controller::Test;
@@ -447,7 +575,86 @@ Example:
     }
 
 Please note this does not attempt to map or nest your PSGI application under
-the Controller and Action namespace or path.  
+the Controller and Action namespace or path. You may wish to review 'PSGI Helpers'
+under L<Catalyst::Utils> for help in properly nesting applications.
+
+B<NOTE> If your external PSGI application returns a response that has a character
+set associated with the content type (such as "text/html; charset=UTF-8") we set
+$c->clear_encoding to remove any additional content type encoding processing later
+in the application (this is done to avoid double encoding issues).
+
+=head2 encodable_content_type
+
+This is a regular expression used to determine of the current content type
+should be considered encodable.  Currently we apply default encoding (usually
+UTF8) to text type contents.  Here's the default regular expression:
+
+This would match content types like:
+
+    text/plain
+    text/html
+    text/xml
+    application/javascript
+    application/xml
+    application/vnd.user+xml
+
+B<NOTE>: We don't encode JSON content type responses by default since most
+of the JSON serializers that are commonly used for this task will do so
+automatically and we don't want to double encode.  If you are not using a
+tool like L<JSON> to produce JSON type content, (for example you are using
+a template system, or creating the strings manually) you will need to either
+encoding the body yourself:
+
+    $c->response->body( $c->encoding->encode( $body, $c->_encode_check ) );
+
+Or you can alter the regular expression using this attribute.
+
+=head2 encodable_response
+
+Given a L<Catalyst::Response> return true if its one that can be encoded.  
+
+     make sure there is an encoding set on the response
+     make sure the content type is encodable
+     make sure no content type charset has been already set to something different from the global encoding
+     make sure no content encoding is present.
+
+Note this does not inspect a body since we do allow automatic encoding on streaming
+type responses.
+
+=cut
+
+sub encodable_response {
+  my ($self) = @_;
+  return 0 unless $self->_context; # Cases like returning a HTTP Exception response you don't have a context here...
+  return 0 unless $self->_context->encoding;
+
+  # The response is considered to have a 'manual charset' when a charset is already set on
+  # the content type of the response AND it is not the same as the one we set in encoding.
+  # If there is no charset OR we are asking for the one which is the same as the current
+  # required encoding, that is a flag that we want Catalyst to encode the response automatically.
+  my $has_manual_charset = 0;
+  if(my $charset = $self->content_type_charset) {
+    $has_manual_charset = (uc($charset) ne uc($self->_context->encoding->mime_name)) ? 1:0;
+  }
+
+  # Content type is encodable if it matches the regular expression stored in this attribute
+  my $encodable_content_type = $self->content_type =~ m/${\$self->encodable_content_type}/ ? 1:0;
+
+  # The content encoding is allowed (for charset encoding) only if its empty or is set to identity
+  my $allowed_content_encoding = (!$self->content_encoding || $self->content_encoding eq 'identity') ? 1:0;
+
+  # The content type must be an encodable type, and there must be NO manual charset and also
+  # the content encoding must be the allowed values;
+  if(
+      $encodable_content_type and
+      !$has_manual_charset and
+      $allowed_content_encoding
+  ) {
+    return 1;
+  } else {
+    return 0;
+  }
+}
 
 =head2 DEMOLISH
 
diff --git a/lib/Catalyst/Response/Writer.pm b/lib/Catalyst/Response/Writer.pm
new file mode 100644 (file)
index 0000000..5044bbd
--- /dev/null
@@ -0,0 +1,65 @@
+package Catalyst::Response::Writer;
+
+sub write { shift->{_writer}->write(@_) }
+sub close { shift->{_writer}->close }
+
+sub write_encoded {
+  my ($self, $line) = @_;
+  if((my $enc = $self->{_context}->encoding) && $self->{_requires_encoding}) {
+    # Not going to worry about CHECK arg since Unicode always croaks I think - jnap
+    $line = $enc->encode($line);
+  }
+
+  $self->write($line);
+}
+
+=head1 TITLE 
+
+Catalyst::Response::Writer - Proxy over the PSGI Writer
+
+=head1 SYNOPSIS
+
+    sub myaction : Path {
+      my ($self, $c) = @_;
+      my $w = $c->response->writer_fh;
+
+      $w->write("hello world");
+      $w->close;
+    }
+
+=head1 DESCRIPTION
+
+This wraps the PSGI writer (see L<PSGI.pod\Delayed-Response-and-Streaming-Body>)
+for more.  We wrap this object so we can provide some additional methods that
+make sense from inside L<Catalyst>
+
+=head1 METHODS
+
+This class does the following methods
+
+=head2 write
+
+=head2 close
+
+These delegate to the underlying L<PSGI> writer object
+
+=head2 write_encoded
+
+If the application defines a response encoding (default is UTF8) and the 
+content type is a type that needs to be encoded (text types like HTML or XML and
+Javascript) we first encode the line you want to write.  This is probably the
+thing you want to always do.  If you use the L<\write> method directly you will
+need to handle your own encoding.
+
+=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
+
+1;
diff --git a/lib/Catalyst/RouteMatching.pod b/lib/Catalyst/RouteMatching.pod
new file mode 100644 (file)
index 0000000..3d26706
--- /dev/null
@@ -0,0 +1,393 @@
+=encoding UTF-8
+
+=head1 Name
+
+Catalyst::RouteMatching - How Catalyst maps an incoming URL to actions in controllers.
+
+=head1 Description
+
+This is a WIP document intended to help people understand the logic that L<Catalyst>
+uses to determine how to match in incoming request to an action (or action chain)
+in a controller.
+
+=head2 Request to Controller/Action Matching
+
+L<Catalyst> maps requests to action using a 'longest path wins' approach.  That means
+that if the request is '/foo/bar/baz' That means the action 'baz' matches:
+
+    package MyApp::Controller::Foo;
+
+    use Moose;
+    use MooseX::MethodAttributes
+
+    extends 'Catalyst::Controller';
+
+    sub bar :Path('bar') Args(1) { ...}
+    sub baz :Path('bar/baz') Args(0) { ... }
+
+Path length matches take precedence over all other types of matches (included HTTP
+Method, Scheme, etc.).  The same holds true for Chained actions.  Generally the
+chain that matches the most PathParts wins.
+
+=head2 Args(N) versus Args
+
+'Args' matches any number of args.  Because this functions as a sort of catchall, we
+treat 'Args' as the lowest precedence of any Args(N) when N is 0 to infinity.  An
+action with 'Args' always get the last chance to match.
+
+=head2 When two or more actions match a given Path
+
+Sometimes two or more actions match the same path and all have the same PathPart
+length.  For example:
+
+    package MyApp::Controller::Root;
+
+    use Moose;
+    use MooseX::MethodAttributes
+
+    extends 'Catalyst::Controller';
+
+    sub root :Chained(/) CaptureArgs(0) { }
+
+      sub one :Chained(root) PathPart('') Args(0) { }
+      sub two :Chained(root) PathPart('') Args(0) { }
+      sub three :Chained(root) PathPart('') Args(0) { }
+
+    __PACKAGE__->meta->make_immutable;
+
+In this case the last defined action wins (for the example that is action 'three').
+
+This is most common to happen when you are using action matching beyond paths, such as
+when using method matching:
+
+    package MyApp::Controller::Root;
+
+    use Moose;
+    use MooseX::MethodAttributes
+
+    extends 'Catalyst::Controller';
+
+    sub root :Chained(/) CaptureArgs(0) { }
+
+      sub any :Chained(root) PathPart('') Args(0) { }
+      sub get :GET Chained(root) PathPart('') Args(0) { }
+
+    __PACKAGE__->meta->make_immutable;
+
+In the above example GET /root could match both actions.  In this case you should define
+your 'catchall' actions higher in the controller.
+
+=head2 Type Constraints in Args and Capture Args
+
+Beginning in Version 5.90090+ you may use L<Moose>, L<MooseX::Types> or L<Type::Tiny>
+type constraints to further declare allowed matching for Args or CaptureArgs.  Here
+is a simple example:
+
+    package MyApp::Controller::User;
+
+    use Moose;
+    use MooseX::MethodAttributes;
+    use MooseX::Types::Moose qw(Int);
+
+    extends 'Catalyst::Controller';
+
+    sub find :Path('') Args(Int) {
+      my ($self, $c, $int) = @_;
+    }
+
+    __PACKAGE__->meta->make_immutable;
+
+In this case the incoming request "http://localhost:/user/100" would match the action
+C<find> but "http://localhost:/user/not_a_number" would not. You may find declaring
+constraints in this manner aids with debugging, automatic generation of documentation
+and reducing the amount of manual checking you might need to do in your actions.  For
+example if the argument in the given action was going to be used to lookup a row
+in a database, if the matching field expected an integer, a string might cause a database
+exception, prompting you to add additional checking of the argument prior to using it.
+In general it is hoped this feature can lead to reduced validation boilerplate and more
+easily understood and declarative actions.
+
+More than one argument may be added by comma separating your type constraint names, for
+example:
+
+    use Types::Standard qw/Int Str/;
+
+    sub find :Path('') Args(Int,Int,Str) {
+      my ($self, $c, $int1, $int2, $str) = @_;
+    }
+
+Would require three arguments, an integer, integer and a string.  Note in this example we
+constrained the args using imported types via L<Types::Standard>.  Although you may use
+stringy Moose types, we recommend imported types since this is less ambiguous to your readers.
+If you want to use Moose stringy types. you must quote them (either "Int" or 'Int' is fine).
+
+Conversely, you should not quote types that are imported!
+
+=head3 Using type constraints in a controller
+
+By default L<Catalyst> allows all the standard, built-in, named type constraints that come
+bundled with L<Moose>.  However it is trivial to create your own Type constraint libraries
+and export them to a controller that wishes to use them.  We recommend using L<Type::Tiny> or
+L<MooseX::Types> for this.  Here is an example using some extended type constraints via
+the L<Types::Standard> library that is packaged with L<Type::Tiny>:
+
+    package MyApp::Controller::User;
+
+    use Moose;
+    use MooseX::MethodAttributes;
+    use Types::Standard qw/StrMatch Int/;
+    
+    extends 'Catalyst::Controller';
+
+    sub looks_like_a_date :Path('') Args(StrMatch[qr{\d\d-\d\d-\d\d}]) {
+      my ($self, $c, $int) = @_;
+    }
+
+    __PACKAGE__->meta->make_immutable;
+
+This would match URLs like "http://localhost/user/11-11-2015" for example.  If you've been
+missing the old RegExp matching, this can emulate a good chunk of that ability, and more.
+
+A tutorial on how to make custom type libraries is outside the scope of this document.  I'd
+recommend looking at the copious documentation in L<Type::Tiny> or in L<MooseX::Types> if
+you prefer that system.  The author recommends L<Type::Tiny> if you are unsure which to use.
+
+=head3 Type constraint namespace.
+
+By default we assume the namespace which defines the type constraint is in the package
+which contains the action declaring the arg or capture arg.  However if you do not wish
+to import type constraints into you package, you may use a fully qualified namespace for
+your type constraint.  If you do this you must install L<Type::Tiny> which defines the
+code used to lookup and normalize the various types of Type constraint libraries.
+
+Example:
+
+    package MyApp::Example;
+
+    use Moose;
+    use MooseX::MethodAttributes;
+
+    extends 'Catalyst::Controller';
+
+    sub an_int_ns :Local Args(MyApp::Types::Int) {
+      my ($self, $c, $int) = @_;
+      $c->res->body('an_int (withrole)');
+    }
+
+Would basically work the same as:
+
+    package MyApp::Example;
+
+    use Moose;
+    use MooseX::MethodAttributes;
+    use MyApp::Types 'Int';
+
+    extends 'Catalyst::Controller';
+
+    sub an_int_ns :Local Args(Int) {
+      my ($self, $c, $int) = @_;
+      $c->res->body('an_int (withrole)');
+    }
+
+=head3 namespace::autoclean
+
+If you want to use L<namespace::autoclean> in your controllers you must 'except' imported
+type constraints since the code that resolves type constraints in args / capture args
+run after the cleaning.  For example:
+
+    package MyApp::Controller::Autoclean;
+
+    use Moose;
+    use MooseX::MethodAttributes;
+    use namespace::autoclean -except => 'Int';
+    use MyApp::Types qw/Int/;
+
+    extends 'Catalyst::Controller';
+
+    sub an_int :Local Args(Int) {
+      my ($self, $c, $int) = @_;
+      $c->res->body('an_int (autoclean)');
+    }
+
+=head3 Using roles and base controller with type constraints
+
+If your controller is using a base class or a role that has an action with a type constraint
+you should declare your use of the type constraint in that role or base controller in the
+same way as you do in main controllers.  Catalyst will try to find the package with declares
+the type constraint first by looking in any roles and then in superclasses.  It will use the
+first package that defines the type constraint.  For example:
+
+    package MyApp::Role;
+
+    use Moose::Role;
+    use MooseX::MethodAttributes::Role;
+    use MyApp::Types qw/Int/;
+
+    sub an_int :Local Args(Int) {
+      my ($self, $c, $int) = @_;
+      $c->res->body('an_int (withrole)');
+    }
+
+    sub an_int_ns :Local Args(MyApp::Types::Int) {
+      my ($self, $c, $int) = @_;
+      $c->res->body('an_int (withrole)');
+    }
+
+    package MyApp::BaseController;
+
+    use Moose;
+    use MooseX::MethodAttributes;
+    use MyApp::Types qw/Int/;
+
+    extends 'Catalyst::Controller';
+
+    sub from_parent :Local Args(Int) {
+      my ($self, $c, $id) = @_;
+      $c->res->body('from_parent $id');
+    }
+
+    package MyApp::Controller::WithRole;
+
+    use Moose;
+    use MooseX::MethodAttributes;
+
+    extends 'MyApp::BaseController';
+
+    with 'MyApp::Role';
+
+If you have complex controller hierarchy, we
+do not at this time attempt to look for all packages with a match type constraint, but instead
+take the first one found.  In the future we may add code that attempts to insure a sane use
+of subclasses with type constraints but right now there are no clear use cases so report issues
+and interests.
+
+=head3 Match order when more than one Action matches a path.
+
+As previously described, L<Catalyst> will match 'the longest path', which generally means
+that named path / path_parts will take precedence over Args or CaptureArgs.  However, what
+will happen if two actions match the same path with equal args?  For example:
+
+    sub an_int :Path(user) Args(Int) {
+    }
+
+    sub an_any :Path(user) Args(1) {
+    }
+
+In this case L<Catalyst> will check actions starting from the LAST one defined.  Generally
+this means you should put your most specific action rules LAST and your 'catch-alls' first.
+In the above example, since Args(1) will match any argument, you will find that that 'an_int'
+action NEVER gets hit.  You would need to reverse the order:
+
+    sub an_any :Path(user) Args(1) {
+    }
+
+    sub an_int :Path(user) Args(Int) {
+    }
+
+Now requests that match this path would first hit the 'an_int' action and will check to see if
+the argument is an integer.  If it is, then the action will execute, otherwise it will pass and
+the dispatcher will check the next matching action (in this case we fall through to the 'an_any'
+action).
+
+=head3 Type Constraints and Chained Actions
+
+Using type constraints in Chained actions works the same as it does for Path and Local or Global
+actions.  The only difference is that you may declare type constraints on CaptureArgs as
+well as Args.  For Example:
+
+  use Types::Standard qw/Int Tuple/;
+  
+  sub chain_base :Chained(/) CaptureArgs(1) { }
+
+    sub any_priority_chain :GET Chained(chain_base) PathPart('') Args(1) {  }
+
+    sub int_priority_chain :Chained(chain_base) PathPart('') Args(Int) {  }
+
+    sub link_any :Chained(chain_base) PathPart('') CaptureArgs(1) { }
+
+      sub any_priority_link_any :Chained(link_any) PathPart('') Args(1) {  }
+
+      sub int_priority_link_any :Chained(link_any) PathPart('') Args(Int) {  }
+    
+    sub link_int :Chained(chain_base) PathPart('') CaptureArgs(Int) { }
+
+      sub any_priority_link :Chained(link_int) PathPart('') Args(1) {  }
+
+      sub int_priority_link :Chained(link_int) PathPart('') Args(Int) {  }
+
+    sub link_int_int :Chained(chain_base) PathPart('') CaptureArgs(Int,Int) { }
+
+      sub any_priority_link2 :Chained(link_int_int) PathPart('') Args(1) {  }
+
+      sub int_priority_link2 :Chained(link_int_int) PathPart('') Args(Int) {  }
+
+    sub link_tuple :Chained(chain_base) PathPart('') CaptureArgs(Tuple[Int,Int,Int]) { }
+
+      sub any_priority_link3 :Chained(link_tuple) PathPart('') Args(1) {  }
+
+      sub int_priority_link3 :Chained(link_tuple) PathPart('') Args(Int) {  }
+
+These chained actions might create match tables like the following:
+
+    [debug] Loaded Chained actions:
+    .-------------------------------------+--------------------------------------.
+    | Path Spec                           | Private                              |
+    +-------------------------------------+--------------------------------------+
+    | /chain_base/*/*                     | /chain_base (1)                      |
+    |                                     | => GET /any_priority_chain (1)       |
+    | /chain_base/*/*/*                   | /chain_base (1)                      |
+    |                                     | -> /link_int (Int)                   |
+    |                                     | => /any_priority_link (1)            |
+    | /chain_base/*/*/*/*                 | /chain_base (1)                      |
+    |                                     | -> /link_int_int (Int,Int)           |
+    |                                     | => /any_priority_link2 (1)           |
+    | /chain_base/*/*/*/*/*               | /chain_base (1)                      |
+    |                                     | -> /link_tuple (Tuple[Int,Int,Int])  |
+    |                                     | => /any_priority_link3 (1)           |
+    | /chain_base/*/*/*                   | /chain_base (1)                      |
+    |                                     | -> /link_any (1)                     |
+    |                                     | => /any_priority_link_any (1)        |
+    | /chain_base/*/*/*/*/*/*             | /chain_base (1)                      |
+    |                                     | -> /link_tuple (Tuple[Int,Int,Int])  |
+    |                                     | -> /link2_int (UserId)               |
+    |                                     | => GET /finally (Int)                |
+    | /chain_base/*/*/*/*/*/...           | /chain_base (1)                      |
+    |                                     | -> /link_tuple (Tuple[Int,Int,Int])  |
+    |                                     | -> /link2_int (UserId)               |
+    |                                     | => GET /finally2 (...)               |
+    | /chain_base/*/*                     | /chain_base (1)                      |
+    |                                     | => /int_priority_chain (Int)         |
+    | /chain_base/*/*/*                   | /chain_base (1)                      |
+    |                                     | -> /link_int (Int)                   |
+    |                                     | => /int_priority_link (Int)          |
+    | /chain_base/*/*/*/*                 | /chain_base (1)                      |
+    |                                     | -> /link_int_int (Int,Int)           |
+    |                                     | => /int_priority_link2 (Int)         |
+    | /chain_base/*/*/*/*/*               | /chain_base (1)                      |
+    |                                     | -> /link_tuple (Tuple[Int,Int,Int])  |
+    |                                     | => /int_priority_link3 (Int)         |
+    | /chain_base/*/*/*                   | /chain_base (1)                      |
+    |                                     | -> /link_any (1)                     |
+    |                                     | => /int_priority_link_any (Int)      |
+    '-------------------------------------+--------------------------------------'
+
+As you can see the same general path could be matched by various action chains.  In this case
+the rule described in the previous section should be followed, which is that L<Catalyst>
+will start with the last defined action and work upward.  For example the action C<int_priority_chain>
+would be checked before C<any_priority_chain>.  The same applies for actions that are midway links
+in a longer chain.  In this case C<link_int> would be checked before C<link_any>.  So as always we
+recommend that you place you priority or most constrained actions last and you least or catch-all
+actions first.
+
+Although this reverse order checking may seen counter intuitive it does have the added benefit that
+when inheriting controllers any new actions added would take check precedence over those in your
+parent controller or consumed role.
+
+Please note that your declared type constraint names will now appear in the debug console.
+
+=head1 Author
+
+John Napiorkowski L<jjnapiork@cpan.org|email:jjnapiork@cpan.org>
+
+=cut
+
index fa9b74b..a9c86d1 100644 (file)
@@ -7,7 +7,8 @@ BEGIN { require 5.008003; }
 
 # Remember to update this in Catalyst as well!
 
-our $VERSION = '5.90073';
+our $VERSION = '5.90101';
+$VERSION = eval $VERSION if $VERSION =~ /_/; # numify for warning-free dev releases
 
 =head1 NAME
 
diff --git a/lib/Catalyst/UTF8.pod b/lib/Catalyst/UTF8.pod
new file mode 100644 (file)
index 0000000..33c9c1f
--- /dev/null
@@ -0,0 +1,653 @@
+=encoding UTF-8
+
+=head1 Name
+
+Catalyst::UTF8 - All About UTF8 and Catalyst Encoding
+
+=head1 Description
+
+Starting in 5.90080 L<Catalyst> will enable UTF8 encoding by default for
+text like body responses.  In addition we've made a ton of fixes around encoding
+and utf8 scattered throughout the codebase.  This document attempts to give
+an overview of the assumptions and practices that  L<Catalyst> uses when
+dealing with UTF8 and encoding issues.  You should also review the
+Changes file, L<Catalyst::Delta> and L<Catalyst::Upgrading> for more.
+
+We attempt to describe all relevant processes, try to give some advice
+and explain where we may have been exceptional to respect our commitment
+to backwards compatibility.
+
+=head1 UTF8 in Controller Actions
+
+Using UTF8 characters in your Controller classes and actions.
+
+=head2 Summary
+
+In this section we will review changes to how UTF8 characters can be used in
+controller actions, how it looks in the debugging screens (and your logs)
+as well as how you construct L<URL> objects to actions with UTF8 paths
+(or using UTF8 args or captures).
+
+=head2 Unicode in Controllers and URLs
+
+    package MyApp::Controller::Root;
+
+    use utf8;
+    use base 'Catalyst::Controller';
+
+    sub heart_with_arg :Path('♥') Args(1)  {
+      my ($self, $c, $arg) = @_;
+    }
+
+    sub base :Chained('/') CaptureArgs(0) {
+      my ($self, $c) = @_;
+    }
+
+      sub capture :Chained('base') PathPart('♥') CaptureArgs(1) {
+        my ($self, $c, $capture) = @_;
+      }
+
+        sub arg :Chained('capture') PathPart('♥') Args(1) {
+          my ($self, $c, $arg) = @_;
+        }
+
+=head2 Discussion
+
+In the example controller above we have constructed two matchable URL routes:
+
+    http://localhost/root/♥/{arg}
+    http://localhost/base/♥/{capture}/♥/{arg}
+
+The first one is a classic Path type action and the second uses Chaining, and
+spans three actions in total.  As you can see, you can use unicode characters
+in your Path and PathPart attributes (remember to use the C<utf8> pragma to allow
+these multibyte characters in your source).  The two constructed matchable routes
+would match the following incoming URLs:
+
+    (heart_with_arg) -> http://localhost/root/%E2%99%A5/{arg}
+    (base/capture/arg) -> http://localhost/base/%E2%99%A5/{capture}/%E2%99%A5/{arg}
+
+That path path C<%E2%99%A5> is url encoded unicode (assuming you are hitting this with
+a reasonably modern browser).  Its basically what goes over HTTP when your type a
+browser location that has the unicode 'heart' in it.  However we will use the unicode
+symbol in your debugging messages:
+
+    [debug] Loaded Path actions:
+    .-------------------------------------+--------------------------------------.
+    | Path                                | Private                              |
+    +-------------------------------------+--------------------------------------+
+    | /root/♥/*                          | /root/heart_with_arg                  |
+    '-------------------------------------+--------------------------------------'
+
+    [debug] Loaded Chained actions:
+    .-------------------------------------+--------------------------------------.
+    | Path Spec                           | Private                              |
+    +-------------------------------------+--------------------------------------+
+    | /base/♥/*/♥/*                       | /root/base (0)                       |
+    |                                     | -> /root/capture (1)                 |
+    |                                     | => /root/arg                         |
+    '-------------------------------------+--------------------------------------'
+
+And if the requested URL uses unicode characters in your captures or args (such as
+C<http://localhost:/base/♥/♥/♥/♥>) you should see the arguments and captures as their
+unicode characters as well:
+
+    [debug] Arguments are "♥"
+    [debug] "GET" request for "base/♥/♥/♥/♥" from "127.0.0.1"
+    .------------------------------------------------------------+-----------.
+    | Action                                                     | Time      |
+    +------------------------------------------------------------+-----------+
+    | /root/base                                                 | 0.000080s |
+    | /root/capture                                              | 0.000075s |
+    | /root/arg                                                  | 0.000755s |
+    '------------------------------------------------------------+-----------'
+
+Again, remember that we are display the unicode character and using it to match actions
+containing such multibyte characters BUT over HTTP you are getting these as URL encoded
+bytes.  For example if you looked at the L<PSGI> C<$env> value for C<REQUEST_URI> you
+would see (for the above request)
+
+    REQUEST_URI => "/base/%E2%99%A5/%E2%99%A5/%E2%99%A5/%E2%99%A5"
+
+So on the incoming request we decode so that we can match and display unicode characters
+(after decoding the URL encoding).  This makes it straightforward to use these types of
+multibyte characters in your actions and see them incoming in captures and arguments.  Please
+keep this in might if you are doing for example regular expression matching, length determination
+or other string comparisons, you will need to try these incoming variables as though UTF8
+strings.  For example in the following action:
+
+        sub arg :Chained('capture') PathPart('♥') Args(1) {
+          my ($self, $c, $arg) = @_;
+        }
+
+when $arg is "♥" you should expect C<length($arg)> to be C<1> since it is indeed one character
+although it will take more than one byte to store.
+
+=head2 UTF8 in constructing URLs via $c->uri_for
+
+For the reverse (constructing meaningful URLs to actions that contain multibyte characters
+in their paths or path parts, or when you want to include such characters in your captures
+or arguments) L<Catalyst> will do the right thing (again just remember to use the C<utf8>
+pragma).
+
+    use utf8;
+    my $url = $c->uri_for( $c->controller('Root')->action_for('arg'), ['♥','♥']);
+
+When you stringify this object (for use in a template, for example) it will automatically
+do the right thing regarding utf8 encoding and url encoding.
+
+    http://localhost/base/%E2%99%A5/%E2%99%A5/%E2%99%A5/%E2%99%A5
+
+Since again what you want is a properly url encoded version of this.  In this case your string
+length will reflect URL encoded bytes, not the character length.  Ultimately what you want
+to send over the wire via HTTP needs to be bytes.
+
+=head1 UTF8 in GET Query and Form POST
+
+What Catalyst does with UTF8 in your GET and classic HTML Form POST
+
+=head2 UTF8 in URL query and keywords
+
+The same rules that we find in URL paths also cover URL query parts.  That is
+if one types a URL like this into the browser
+
+       http://localhost/example?♥=♥♥
+
+When this goes 'over the wire' to your application server its going to be as
+percent encoded bytes:
+
+
+       http://localhost/example?%E2%99%A5=%E2%99%A5%E2%99%A5
+
+When L<Catalyst> encounters this we decode the percent encoding and the utf8
+so that we can properly display this information (such as in the debugging
+logs or in a response.)
+
+       [debug] Query Parameters are:
+       .-------------------------------------+--------------------------------------.
+       | Parameter                           | Value                                |
+       +-------------------------------------+--------------------------------------+
+       | ♥                                   | ♥♥                                   |
+       '-------------------------------------+--------------------------------------'
+
+All the values and keys that are part of $c->req->query_parameters will be
+utf8 decoded.  So you should not need to do anything special to take those
+values/keys and send them to the body response (since as we will see later
+L<Catalyst> will do all the necessary encoding for you).
+
+Again, remember that values of your parameters are now decode into Unicode strings.  so
+for example you'd expect the result of length to reflect the character length not
+the byte length.
+
+Just like with arguments and captures, you can use utf8 literals (or utf8
+strings) in $c->uri_for:
+
+       use utf8;
+       my $url = $c->uri_for( $c->controller('Root')->action_for('example'), {'♥' => '♥♥'});
+
+When you stringify this object (for use in a template, for example) it will automatically
+do the right thing regarding utf8 encoding and url encoding.
+
+       http://localhost/example?%E2%99%A5=%E2%99%A5%E2%99%A5
+
+Since again what you want is a properly url encoded version of this.  Ultimately what you want
+to send over the wire via HTTP needs to be bytes (not unicode characters).
+
+Remember if you use any utf8 literals in your source code, you should use the
+C<use utf8> pragma.
+
+B<NOTE:> Assuming UTF-8 in your query parameters and keywords may be an issue if you have
+legacy code where you created URL in templates manually and used an encoding other than UTF-8.
+In these cases you may find versions of Catalyst after 5.90080+ will incorrectly decode.  For
+backwards compatibility we offer three configurations settings, here described in order of
+precedence:
+
+C<do_not_decode_query>
+
+If true, then do not try to character decode any wide characters in your
+request URL query or keywords.  You will need to handle this manually in your action code
+(although if you choose this setting, chances are you already do this).
+
+C<default_query_encoding>
+
+This setting allows one to specify a fixed value for how to decode your query, instead of using
+the default, UTF-8.
+
+C<decode_query_using_global_encoding>
+
+If this is true we decode using whatever you set C<encoding> to.
+
+=head2 UTF8 in Form POST
+
+In general most modern browsers will follow the specification, which says that POSTed
+form fields should be encoded in the same way that the document was served with.  That means
+that if you are using modern Catalyst and serving UTF8 encoded responses, a browser is
+supposed to notice that and encode the form POSTs accordingly.
+
+As a result since L<Catalyst> now serves UTF8 encoded responses by default, this means that
+you can mostly rely on incoming form POSTs to be so encoded.  L<Catalyst> will make this
+assumption and decode accordingly (unless you explicitly turn off encoding...)  If you are
+running Catalyst in developer debug, then you will see the correct unicode characters in
+the debug output.  For example if you generate a POST request:
+
+       use Catalyst::Test 'MyApp';
+       use utf8;
+
+       my $res = request POST "/example/posted", ['♥'=>'♥', '♥♥'=>'♥'];
+
+Running in CATALYST_DEBUG=1 mode you should see output like this:
+
+    [debug] Body Parameters are:
+    .-------------------------------------+--------------------------------------.
+    | Parameter                           | Value                                |
+    +-------------------------------------+--------------------------------------+
+    | ♥                                   | ♥                                    |
+    | ♥♥                                  | ♥                                    |
+    '-------------------------------------+--------------------------------------'
+
+And if you had a controller like this:
+
+       package MyApp::Controller::Example;
+
+       use base 'Catalyst::Controller';
+
+       sub posted :POST Local {
+               my ($self, $c) = @_;
+               $c->res->content_type('text/plain');
+               $c->res->body("hearts => ${\$c->req->post_parameters->{♥}}");
+       }
+
+The following test case would be true:
+
+       use Encode 2.21 'decode_utf8';
+       is decode_utf8($req->content), 'hearts => ♥';
+
+In this case we decode so that we can print and compare strings with multibyte characters.
+
+B<NOTE>  In some cases some browsers may not follow the specification and set the form POST
+encoding based on the server response.  Catalyst itself doesn't attempt any workarounds, but one
+common approach is to use a hidden form field with a UTF8 value (You might be familiar with
+this from how Ruby on Rails has HTML form helpers that do that automatically).  In that case
+some browsers will send UTF8 encoded if it notices the hidden input field contains such a
+character.  Also, you can add an HTML attribute to your form tag which many modern browsers
+will respect to set the encoding (accept-charset="utf-8").  And lastly there are some javascript
+based tricks and workarounds for even more odd cases (just search the web for this will return
+a number of approaches.  Hopefully as more compliant browsers become popular these edge cases
+will fade.
+
+B<NOTE>  It is possible for a form POST multipart response (normally a file upload) to contain
+inline content with mixed content character sets and encoding.  For example one might create
+a POST like this:
+
+    use utf8;
+    use HTTP::Request::Common;
+
+    my $utf8 = 'test ♥';
+    my $shiftjs = 'test テスト';
+    my $req = POST '/root/echo_arg',
+        Content_Type => 'form-data',
+          Content =>  [
+            arg0 => 'helloworld',
+            Encode::encode('UTF-8','♥') => Encode::encode('UTF-8','♥♥'),
+            arg1 => [
+              undef, '',
+              'Content-Type' =>'text/plain; charset=UTF-8',
+              'Content' => Encode::encode('UTF-8', $utf8)],
+            arg2 => [
+              undef, '',
+              'Content-Type' =>'text/plain; charset=SHIFT_JIS',
+              'Content' => Encode::encode('SHIFT_JIS', $shiftjs)],
+            arg2 => [
+              undef, '',
+              'Content-Type' =>'text/plain; charset=SHIFT_JIS',
+              'Content' => Encode::encode('SHIFT_JIS', $shiftjs)],
+          ];
+
+In this case we've created a POST request but each part specifies its own content
+character set (and setting a content encoding would also be possible).  Generally one
+would not run into this situation in a web browser context but for completeness sake
+Catalyst will notice if a multipart POST contains parts with complex or extended
+header information.  In these cases we will try to inspect the meta data and do the
+right thing (in the above case we'd use SHIFT_JIS to decode, not UTF-8).  However if
+after inspecting the headers we cannot figure out how to decode the data, in those cases it
+will not attempt to apply decoding to the form values.  Instead the part will be represented as
+an instance of an object L<Catalyst::Request::PartData> which will contain all the header 
+information needed for you to perform custom parser of the data.
+
+Ideally we'd fix L<Catalyst> to be smarter about decoding so please submit your cases of
+this so we can add intelligence to the parser and find a way to extract a valid value out
+of it.
+
+=head1 UTF8 Encoding in Body Response
+
+When does L<Catalyst> encode your response body and what rules does it use to
+determine when that is needed.
+
+=head2 Summary
+
+       use utf8;
+       use warnings;
+       use strict;
+
+       package MyApp::Controller::Root;
+
+       use base 'Catalyst::Controller';
+       use File::Spec;
+
+       sub scalar_body :Local {
+               my ($self, $c) = @_;
+               $c->response->content_type('text/html');
+               $c->response->body("<p>This is scalar_body action ♥</p>");
+       }
+
+       sub stream_write :Local {
+               my ($self, $c) = @_;
+               $c->response->content_type('text/html');
+               $c->response->write("<p>This is stream_write action ♥</p>");
+       }
+
+       sub stream_write_fh :Local {
+               my ($self, $c) = @_;
+               $c->response->content_type('text/html');
+
+               my $writer = $c->res->write_fh;
+               $writer->write_encoded('<p>This is stream_write_fh action ♥</p>');
+               $writer->close;
+       }
+
+       sub stream_body_fh :Local {
+               my ($self, $c) = @_;
+               my $path = File::Spec->catfile('t', 'utf8.txt');
+               open(my $fh, '<', $path) || die "trouble: $!";
+               $c->response->content_type('text/html');
+               $c->response->body($fh);
+       }
+
+=head2 Discussion
+
+Beginning with L<Catalyst> version 5.90080 You no longer need to set the encoding
+configuration (although doing so won't hurt anything).
+
+Currently we only encode if the content type is one of the types which generally expects a
+UTF8 encoding.  This is determined by the following regular expression:
+
+    our $DEFAULT_ENCODE_CONTENT_TYPE_MATCH = qr{text|xml$|javascript$};
+    $c->response->content_type =~ /$DEFAULT_ENCODE_CONTENT_TYPE_MATCH/
+
+This is a global variable in L<Catalyst::Response> which is stored in the C<encodable_content_type>
+attribute of $c->response.  You may currently alter this directly on the response or globally.  In
+the future we may offer a configuration setting for this.
+
+This would match content-types like the following (examples)
+
+    text/plain
+    text/html
+    text/xml
+    application/javascript
+    application/xml
+    application/vnd.user+xml
+
+You should set your content type prior to header finalization if you want L<Catalyst> to
+encode.
+
+B<NOTE> We do not attempt to encode C<application/json> since the two most commonly used
+approaches (L<Catalyst::View::JSON> and L<Catalyst::Action::REST>) have already configured
+their JSON encoders to produce properly encoding UTF8 responses.  If you are rolling your
+own JSON encoding, you may need to set the encoder to do the right thing (or override
+the global regular expression to include the JSON media type).
+
+=head2 Encoding with Scalar Body
+
+L<Catalyst> supports several methods of supplying your response with body content.  The first
+and currently most common is to set the L<Catalyst::Response> ->body with a scalar string (
+as in the example):
+
+        use utf8;
+
+       sub scalar_body :Local {
+               my ($self, $c) = @_;
+               $c->response->content_type('text/html');
+               $c->response->body("<p>This is scalar_body action ♥</p>");
+       }
+
+In general you should need to do nothing else since L<Catalyst> will automatically encode
+this string during body finalization.  The only matter to watch out for is to make sure
+the string has not already been encoded, as this will result in double encoding errors.
+
+B<NOTE> pay attention to the content-type setting in the example.  L<Catalyst> inspects that
+content type carefully to determine if the body needs encoding).
+
+B<NOTE> If you set the character set of the response L<Catalyst> will skip encoding IF the
+character set is set to something that doesn't match $c->encoding->mime_name. We will assume
+if you are setting an alternative character set, that means you want to handle the encoding
+yourself.  However it might be easier to set $c->encoding for a given response cycle since
+you can override this for a given response.  For example here's how to override the default
+encoding and set the correct character set in the response:
+
+    sub override_encoding :Local {
+      my ($self, $c) = @_;
+      $c->res->content_type('text/plain');
+      $c->encoding(Encode::find_encoding('Shift_JIS'));
+      $c->response->body("テスト");
+    }
+
+This will use the alternative encoding for a single response.
+
+B<NOTE> If you manually set the content-type character set to whatever $c->encoding->mime_name
+is set to, we STILL encode, rather than assume your manual setting is a flag to override.  This
+is done to support backward compatible assumptions (in particular L<Catalyst::View::TT> has set
+a utf-8 character set in its default content-type for ages, even though it does not itself do any
+encoding on the body response).  If you are going to handle encoding manually you may set
+$c->clear_encoding for a single request response cycle, or as in the above example set an alternative
+encoding.
+
+=head2 Encoding with streaming type responses
+
+L<Catalyst> offers two approaches to streaming your body response.  Again, you must remember
+to set your content type prior to streaming, since invoking a streaming response will automatically
+finalize and send your HTTP headers (and your content type MUST be one that matches the regular
+expression given above.)
+
+Also, if you are going to override $c->encoding (or invoke $c->clear_encoding), you should do
+that before anything else!
+
+The first streaming method is to use the C<write> method on the response object.  This method
+allows 'inlined' streaming and is generally used with blocking style servers.
+
+       sub stream_write :Local {
+               my ($self, $c) = @_;
+               $c->response->content_type('text/html');
+               $c->response->write("<p>This is stream_write action ♥</p>");
+       }
+
+You may call the C<write> method as often as you need to finish streaming all your content.
+L<Catalyst> will encode each line in turn as long as the content-type meets the 'encodable types'
+requirement and $c->encoding is set (which it is, as long as you did not change it).
+
+B<NOTE> If you try to change the encoding after you start the stream, this will invoke an error
+response.  However since you've already started streaming this will not show up as an HTTP error
+status code, but rather error information in your body response and an error in your logs.
+
+B<NOTE> If you use ->body AFTER using ->write (for example you may do this to write your HTML
+HEAD information as fast as possible) we expect the contents to body to be encoded as it
+normally would be if you never called ->write.  In general unless you are doing weird custom
+stuff with encoding this is likely to just already do the correct thing.
+
+The second way to stream a response is to get the response writer object and invoke methods
+on that directly:
+
+       sub stream_write_fh :Local {
+               my ($self, $c) = @_;
+               $c->response->content_type('text/html');
+
+               my $writer = $c->res->write_fh;
+               $writer->write_encoded('<p>This is stream_write_fh action ♥</p>');
+               $writer->close;
+       }
+
+This can be used just like the C<write> method, but typically you request this object when
+you want to do a nonblocking style response since the writer object can be closed over or
+sent to a model that will invoke it in a non blocking manner.  For more on using the writer
+object for non blocking responses you should review the C<Catalyst> documentation and also
+you can look at several articles from last years advent, in particular:
+
+L<http://www.catalystframework.org/calendar/2013/10>, L<http://www.catalystframework.org/calendar/2013/11>,
+L<http://www.catalystframework.org/calendar/2013/12>, L<http://www.catalystframework.org/calendar/2013/13>,
+L<http://www.catalystframework.org/calendar/2013/14>.
+
+The main difference this year is that previously calling ->write_fh would return the actual
+L<Plack> writer object that was supplied by your Plack application handler, whereas now we wrap
+that object in a lightweight decorator object that proxies the C<write> and C<close> methods
+and supplies an additional C<write_encoded> method.  C<write_encoded> does the exact same thing
+as C<write> except that it will first encode the string when necessary.  In general if you are
+streaming encodable content such as HTML this is the method to use.  If you are streaming
+binary content, you should just use the C<write> method (although if the content type is set
+correctly we would skip encoding anyway, but you may as well avoid the extra noop overhead).
+
+The last style of content response that L<Catalyst> supports is setting the body to a filehandle
+like object.  In this case the object is passed down to the Plack application handler directly
+and currently we do nothing to set encoding.
+
+       sub stream_body_fh :Local {
+               my ($self, $c) = @_;
+               my $path = File::Spec->catfile('t', 'utf8.txt');
+               open(my $fh, '<', $path) || die "trouble: $!";
+               $c->response->content_type('text/html');
+               $c->response->body($fh);
+       }
+
+In this example we create a filehandle to a text file that contains UTF8 encoded characters. We
+pass this down without modification, which I think is correct since we don't want to double
+encode.  However this may change in a future development release so please be sure to double
+check the current docs and changelog.  Its possible a future release will require you to to set
+a encoding on the IO layer level so that we can be sure to properly encode at body finalization.
+So this is still an edge case we are writing test examples for.  But for now if you are returning
+a filehandle like response, you are expected to make sure you are following the L<PSGI> specification
+and return raw bytes.
+
+=head2 Override the Encoding on Context
+
+As already noted you may change the current encoding (or remove it) by setting an alternative
+encoding on the context;
+
+    $c->encoding(Encode::find_encoding('Shift_JIS'));
+
+Please note that you can continue to change encoding UNTIL the headers have been finalized.  The
+last setting always wins.  Trying to change encoding after header finalization is an error.
+
+=head2 Setting the Content Encoding HTTP Header
+
+In some cases you may set a content encoding on your response.  For example if you are encoding
+your response with gzip.  In this case you are again on your own.  If we notice that the
+content encoding header is set when we hit finalization, we skip automatic encoding:
+
+    use Encode;
+    use Compress::Zlib;
+    use utf8;
+
+    sub gzipped :Local {
+      my ($self, $c) = @_;
+
+      $c->res->content_type('text/plain');
+      $c->res->content_type_charset('UTF-8');
+      $c->res->content_encoding('gzip');
+
+      $c->response->body(
+        Compress::Zlib::memGzip(
+          Encode::encode_utf8("manual_1 ♥")));
+    }
+
+
+If you are using L<Catalyst::Plugin::Compress> you need to upgrade to the most recent version
+in order to be compatible with changes introduced in L<Catalyst> 5.90080.  Other plugins may
+require updates (please open bugs if you find them).
+
+B<NOTE> Content encoding may be set to 'identify' and we will still perform automatic encoding
+if the content type is encodable and an encoding is present for the context.
+
+=head2 Using Common Views
+
+The following common views have been updated so that their tests pass with default UTF8
+encoding for L<Catalyst>:
+
+L<Catalyst::View::TT>, L<Catalyst::View::Mason>, L<Catalyst::View::HTML::Mason>,
+L<Catalyst::View::Xslate>
+
+See L<Catalyst::Upgrading> for additional information on L<Catalyst> extensions that require
+upgrades.
+
+In generally for the common views you should not need to do anything special.  If your actual
+template files contain UTF8 literals you should set configuration on your View to enable that.
+For example in TT, if your template has actual UTF8 character in it you should do the following:
+
+    MyApp::View::TT->config(ENCODING => 'utf-8');
+
+However L<Catalyst::View::Xslate> wants to do the UTF8 encoding for you (We assume that the
+authors of that view did this as a workaround to the fact that until now encoding was not core
+to L<Catalyst>.  So if you use that view, you either need to tell it to not encode, or you need
+to turn off encoding for Catalyst.
+
+    MyApp::View::Xslate->config(encode_body => 0);
+
+or
+
+    MyApp->config(encoding=>undef);
+
+Preference is to disable it in the View.
+
+Other views may be similar.  You should review View documentation and test during upgrading.
+We tried to make sure most common views worked properly and noted all workaround but if we
+missed something please alert the development team (instead of introducing a local hack into
+your application that will mean nobody will ever upgrade it...).
+
+=head2 Setting the response from an external PSGI application.
+
+L<Catalyst::Response> allows one to set the response from an external L<PSGI> application.
+If you do this, and that external application sets a character set on the content-type, we
+C<clear_encoding> for the rest of the response.  This is done to prevent double encoding.
+
+B<NOTE> Even if the character set of the content type is the same as the encoding set in
+$c->encoding, we still skip encoding.  This is a regrettable difference from the general rule
+outlined above, where if the current character set is the same as the current encoding, we
+encode anyway.  Nevertheless I think this is the correct behavior since the earlier rule exists
+only to support backward compatibility with L<Catalyst::View::TT>.
+
+In general if you want L<Catalyst> to handle encoding, you should avoid setting the content
+type character set since Catalyst will do so automatically based on the requested response
+encoding.  Its best to request alternative encodings by setting $c->encoding and if you  really
+want manual control of encoding you should always $c->clear_encoding so that programmers that
+come after you are very clear as to your intentions.
+
+=head2 Disabling default UTF8 encoding
+
+You may encounter issues with your legacy code running under default UTF8 body encoding.  If
+so you can disable this with the following configurations setting:
+
+       MyApp->config(encoding=>undef);
+
+Where C<MyApp> is your L<Catalyst> subclass.
+
+If you do not wish to disable all the Catalyst encoding features, you may disable specific
+features via two additional configuration options:  'skip_body_param_unicode_decoding'
+and 'skip_complex_post_part_handling'.  The first will skip any attempt to decode POST
+parameters in the creating of body parameters and the second will skip creation of instances
+of L<Catalyst::Request::PartData> in the case that the multipart form upload contains parts
+with a mix of content character sets.
+
+If you believe you have discovered a bug in UTF8 body encoding, I strongly encourage you to
+report it (and not try to hack a workaround in your local code).  We also recommend that you
+regard such a workaround as a temporary solution.  It is ideal if L<Catalyst> extension
+authors can start to count on L<Catalyst> doing the write thing for encoding.
+
+=head1 Conclusion
+
+This document has attempted to be a complete review of how UTF8 and encoding works in the
+current version of L<Catalyst> and also to document known issues, gotchas and backward
+compatible hacks.  Please report issues to the development team.
+
+=head1 Author
+
+John Napiorkowski L<jjnapiork@cpan.org|email:jjnapiork@cpan.org>
+
+=cut
+
index 11d0198..1c9de49 100644 (file)
@@ -2,6 +2,286 @@
 
 Catalyst::Upgrading - Instructions for upgrading to the latest Catalyst
 
+=head1 Upgrading to Catalyst 5.90100
+
+We changed the way the middleware stash works so that it no longer localizes
+the PSGI env hashref.  This was done to fix bugs where people set PSGI ENV hash
+keys and found them to disappear in certain cases.  It also means that now if
+a sub applications sets stash variables, that stash will now bubble up to the
+parent application.  This may be a breaking change for you since previous
+versions of this code did not allow that.  A workaround is to explicitly delete
+stash keys in your sub application before returning control to the parent
+application.
+
+=head1 Upgrading to Catalyst 5.90097
+
+In older versions of Catalyst one could construct a L<URI> with a fragment (such as
+https://localhost/foo/bar#fragment) by using a '#' in the path or final argument, for
+example:
+
+    $c->uri_for($action, 'foo#fragment');
+
+This behavior was never documented and would break if using the Unicode plugin, or when
+adding a query to the arguments:
+
+    $c->uri_for($action, 'foo#fragment', +{ a=>1, b=>2});
+
+would define a fragment like "#fragment?a=1&b=2".
+
+When we introduced UTF-8 encoding by default in Catalyst 5.9008x this side effect behavior
+was broken since we started encoding the '#' when it was part of the URI path.
+
+In version 5.90095 and 5.90096 we attempted to fix this, but all we managed to do was break
+people with URIs that included '#' as part of the path data, when it was not expected to
+be a fragment delimiter.
+
+In general L<Catalyst> prefers an explicit specification rather than relying on side effects
+or domain specific mini languages.  As a result we are now defining how to set a fragment
+for a URI via ->uri_for:
+
+    $c->uri_for($action_or_path, \@captures_or_args, @args, \$query, \$fragment);
+
+If you are relying on the previous side effect behavior your URLs will now encode the '#'
+delimiter, which is going to be a breaking change for you.  You need to alter your code
+to match the new specification or modify uri_for for your local case.  Patches to solve
+this are very welcomed, as long as they don't break existing test cases.
+
+B<NOTE> If you are using the string form of the first argument:
+
+    $c->uri_for('/foo/bar#baz')
+
+construction, we do not attempt to encode this and it will make a URL with a
+fragment of 'baz'.
+
+
+=head1 Upgrading to Catalyst 5.90095
+
+The method C<last_error> in L</Catalyst> was actually returning the first error.  This has
+been fixed but there is a small chance it could be a breaking issue for you.  If this gives
+you trouble changing to C<shift_errors> is the easiest workaround (although that does
+modify the error stack so if you are relying on that not being changed you should try something
+like @{$c->errors}[-1] instead.  Since this method is relatively new and the cases when the
+error stack actually has more than one error in it, we feel the exposure is very low, but bug
+reports are very welcomed.
+
+=head1 Upgrading to Catalyst 5.90090
+
+L<Catalyst::Utils> has a new method 'inject_component' which works the same as the method of
+the same name in L<CatalystX::InjectComponent>.  You should start converting any
+use of the non core method in your code as future changes to Catalyst will be
+synchronized to the core method first.  We reserve the right to cease support
+of the non core version should we reach a point in time where it cannot be
+properly supported as an external module.  Luckily this should be a trivial
+search and replace.  Change all occurences of:
+
+    CatalystX::InjectComponent->inject(...)
+
+Into
+
+    Catalyst::Utils::inject_component(...)
+
+and we expect everything to work the same (we'd consider it not working the same
+to be a bug, and please report it.)
+
+We also cored features from L<CatalystX::RoleApplicator> to compose a role into the
+request, response and stats classes.  The main difference is that with L<CatalystX::RoleApplicator>
+you did:
+
+    package MyApp;
+
+    use Catalyst;
+    use CatalystX::RoleApplicator;
+
+    __PACKAGE__->apply_request_class_roles(
+      qw/My::Request::Role Other::Request::Role/);
+
+Whereas now we have three class attributes, 'request_class_traits', 'response_class_traits'
+and 'stats_class_traits', so you use like this (note this value is an ArrayRef)
+
+
+    package MyApp;
+
+    use Catalyst;
+
+    __PACKAGE__->request_class_traits([qw/
+      My::Request::Role
+      Other::Request::Role/]);
+
+(And the same for response_class_traits and stats_class_traits.  We left off the
+traits for Engine, since that class does a lot less nowadays, and dispatcher.  If you
+used those and can share a use case, we'd be likely to support them.
+
+Lastly, we have some of the feature from L<CatalystX::ComponentsFromConfig> in
+core.  This should mostly work the same way in core, except for now the
+core version does not create an automatic base wrapper class for your configured
+components (it requires these to be catalyst components and injects them directly.
+So if you make heavy use of custom base classes in L<CatalystX::ComponentsFromConfig>
+you might need a bit of work to use the core version (although there is no reason
+to stop using L<CatalystX::ComponentsFromConfig> since it should continue to work
+fine and we'd consider issues with it to be bugs).  Here's one way to map from
+L<CatalystX::ComponentsFromConfig> to core:
+
+In L<CatalystX::ComponentsFromConfig>:
+
+    MyApp->config(
+      'Model::MyClass' => {
+          class => 'MyClass',
+          args => { %args },
+
+      });
+
+and now in core:
+
+    MyApp->config(
+      inject_components => {
+        'Model::MyClass' => { from_component => 'My::Class' },
+      },
+      'Model::MyClass' => {
+        %args
+      },
+    );
+
+Although the core behavior requires more code, it better separates concerns
+as well as plays more into core Catalyst expectations of how configuration should
+look.
+
+Also we added a new develop console mode only warning when you call a component
+with arguments that don't expect or do anything meaningful with those args.  Its
+possible if you are logging debug mode in production (please don't...) this 
+could add verbosity to those logs if you also happen to be calling for components
+and passing pointless arguments.  We added this warning to help people not make this
+error and to better understand the component resolution flow.
+
+=head1 Upgrading to Catalyst 5.90085
+
+In this version of Catalyst we made a small change to Chained Dispatching so
+that when two or more actions all have the same path specification AND they
+all have Args(0), we break the tie by choosing the last action defined, and
+not the first one defined.  This was done to normalize Chaining to following
+the 'longest Path wins, and when several actions match the same Path specification
+we choose the last defined.' rule. Previously Args(0) was hard coded to be a special
+case such that the first action defined would match (which is not the case when
+Args is not zero.)
+
+Its possible that this could be a breaking change for you, if you had used
+action roles (custom or otherwise) to add additional matching rules to differentiate
+between several Args(0) actions that share the same root action chain.  For
+example if you have code now like this:
+
+    sub check_default :Chained(/) CaptureArgs(0) { ... }
+
+      sub default_get :Chained('check_default') PathPart('') Args(0) GET {
+          pop->res->body('get3');
+      }
+
+      sub default_post :Chained('check_default') PathPart('') Args(0) POST {
+          pop->res->body('post3');
+      }
+
+      sub chain_default :Chained('check_default') PathPart('') Args(0) {
+          pop->res->body('chain_default');
+      }
+
+The way that chaining will work previous is that when two or more equal actions can
+match, the 'top' one wins.  So if the request is "GET .../check_default" BOTH
+actions 'default_get' AND 'chain_default' would match.  To break the tie in
+the case when Args is 0, we'd previous take the 'top' (or first defined) action.
+Unfortunately this treatment of Args(0) is special case.  In all other cases
+we choose the 'last defined' action to break a tie.  So this version of
+Catalyst changed the dispatcher to make Args(0) no longer a special case for
+breaking ties.  This means that the above code must now become:
+
+    sub check_default :Chained(/) CaptureArgs(0) { ... }
+
+      sub chain_default :Chained('check_default') PathPart('') Args(0) {
+          pop->res->body('chain_default');
+      }
+
+      sub default_get :Chained('check_default') PathPart('') Args(0) GET {
+          pop->res->body('get3');
+      }
+
+      sub default_post :Chained('check_default') PathPart('') Args(0) POST {
+          pop->res->body('post3');
+      }
+
+If we want it to work as expected (for example we we GET to match 'default_get' and
+POST to match 'default_post' and any other http Method to match 'chain_default').
+
+In other words Arg(0) and chained actions must now follow the normal rule where
+in a tie the last defined action wins and you should place all your less defined
+or 'catch all' actions first.
+
+If this causes you trouble and you can't fix your code to conform, you may set the
+application configuration setting "use_chained_args_0_special_case" to true and
+that will revert you code to the previous behavior.
+
+=head2 More backwards compatibility options with UTF-8 changes
+
+In order to give better backwards compatibility with the 5.90080+ UTF-8 changes
+we've added several configuration options around control of how we try to decode
+your URL keywords / query parameters.
+
+C<do_not_decode_query>
+
+If true, then do not try to character decode any wide characters in your
+request URL query or keywords.  Most readings of the relevant specifications
+suggest these should be UTF-* encoded, which is the default that L<Catalyst>
+will use, however if you are creating a lot of URLs manually or have external
+evil clients, this might cause you trouble.  If you find the changes introduced
+in Catalyst version 5.90080+ break some of your query code, you may disable 
+the UTF-8 decoding globally using this configuration.
+
+This setting takes precedence over C<default_query_encoding> and
+C<decode_query_using_global_encoding>
+
+C<default_query_encoding>
+
+By default we decode query and keywords in your request URL using UTF-8, which
+is our reading of the relevant specifications.  This setting allows one to
+specify a fixed value for how to decode your query.  You might need this if
+you are doing a lot of custom encoding of your URLs and not using UTF-8.
+
+This setting take precedence over C<decode_query_using_global_encoding>.
+
+C<decode_query_using_global_encoding>
+
+Setting this to true will default your query decoding to whatever your
+general global encoding is (the default is UTF-8).
+
+
+=head1 Upgrading to Catalyst 5.90080
+
+UTF8 encoding is now default.  For temporary backwards compatibility, if this
+change is causing you trouble, you can disable it by setting the application
+configuration option to undef:
+
+    MyApp->config(encoding => undef);
+
+But please consider this a temporary measure since it is the intention that
+UTF8 is enabled going forwards and the expectation is that other ecosystem
+projects will assume this as well.  At some point you application will not
+correctly function without this setting.
+
+As of 5.90084 we've added two additional configuration flags for more selective
+control over some encoding changes: 'skip_body_param_unicode_decoding' and
+'skip_complex_post_part_handling'.  You may use these to more selectively
+disable new features while you are seeking a long term fix.  Please review
+CONFIGURATION in L<Catalyst>.
+
+For further information, please see L<Catalyst::UTF8>
+
+A number of projects in the wider ecosystem required minor updates to be able
+to work correctly.  Here's the known list:
+
+L<Catalyst::View::TT>, L<Catalyst::View::Mason>, L<Catalyst::View::HTML::Mason>,
+L<Catalyst::View::Xslate>, L<Test::WWW::Mechanize::Catalyst>
+
+You will need to update to modern versions in most cases, although quite a few
+of these only needed minor test case and documentation changes so you will need
+to review the changelog of each one that is relevant to you to determine your
+true upgrade needs.
+
 =head1 Upgrading to Catalyst 5.90060
 
 Starting in the v5.90059_001 development release, the regexp dispatch type is
@@ -30,7 +310,7 @@ to add a configuration setting for the encoding type.  For example:
 
 Please note that this is different from the old stand alone plugin which applied
 C<UTF-8> encoding by default (that is, if you did not set an explicit
-C<encoding> configuration value, it assumed you wanted UTF-8).  In order to 
+C<encoding> configuration value, it assumed you wanted UTF-8).  In order to
 preserve backwards compatibility you will need to explicitly turn it on via the
 configuration setting.  THIS MIGHT CHANGE IN THE FUTURE, so please consider
 starting to test your application with proper UTF-8 support and remove all those
@@ -598,7 +878,7 @@ same effect.
 Having actions in your application class will now emit a warning at application
 startup as this is deprecated. It is highly recommended that these actions are moved
 into a MyApp::Controller::Root (as demonstrated by the scaffold application
-generated by catalyst.pl). 
+generated by catalyst.pl).
 
 This warning, also affects tests. You should move actions in your test,
 creating a myTest::Controller::Root, like the following example:
@@ -614,7 +894,7 @@ creating a myTest::Controller::Root, like the following example:
 
     sub action : Local {
         my ( $self, $c ) = @_;
-        $c->do_something; 
+        $c->do_something;
     }
 
     1;
index 5ee7451..9fb1e92 100644 (file)
@@ -10,8 +10,9 @@ use Cwd;
 use Class::Load 'is_class_loaded';
 use String::RewritePrefix;
 use Class::Load ();
-
 use namespace::clean;
+use Devel::InnerPackage;
+use Moose::Util;
 
 =head1 NAME
 
@@ -503,6 +504,83 @@ sub apply_registered_middleware {
     return $new_psgi;
 }
 
+=head2 inject_component
+
+Used to add components at runtime:
+
+    into        The Catalyst package to inject into (e.g. My::App)
+    component   The component package to inject
+    traits      (Optional) ArrayRef of L<Moose::Role>s that the componet should consume.
+    as          An optional moniker to use as the package name for the derived component
+
+For example:
+
+    Catalyst::Utils::inject_component( into => My::App, component => Other::App::Controller::Apple )
+
+        The above will create 'My::App::Controller::Other::App::Controller::Apple'
+
+    Catalyst::Utils::inject_component( into => My::App, component => Other::App::Controller::Apple, as => Apple )
+
+        The above will create 'My::App::Controller::Apple'
+
+    Catalyst::Utils::inject_component( into => $myapp, component => 'MyRootV', as => 'Controller::Root' );
+
+Will inject Controller, Model, and View components into your Catalyst application
+at setup (run)time. It does this by creating a new package on-the-fly, having that
+package extend the given component, and then having Catalyst setup the new component
+(via $app->setup_component).
+
+B<NOTE:> This is basically a core version of L<CatalystX::InjectComponent>.  If you were using that
+you can now use this safely instead.  Going forward changes required to make this work will be
+synchronized with the core method.
+
+B<NOTE:> The 'traits' option is unique to the L<Catalyst::Utils> version of this feature.
+
+B<NOTE:> These injected components really need to be a L<Catalyst::Component> and a L<Moose>
+based class.
+
+=cut
+
+sub inject_component {
+    my %given = @_;
+    my ($into, $component, $as) = @given{qw/into component as/};
+
+    croak "No Catalyst (package) given" unless $into;
+    croak "No component (package) given" unless $component;
+
+    Class::Load::load_class($component);
+
+    $as ||= $component;
+    unless ( $as =~ m/^(?:Controller|Model|View)::/ || $given{skip_mvc_renaming} ) {
+        my $category;
+        for (qw/ Controller Model View /) {
+            if ( $component->isa( "Catalyst::$_" ) ) {
+                $category = $_;
+                last;
+            }
+        }
+        croak "Don't know what kind of component \"$component\" is" unless $category;
+        $as = "${category}::$as";
+    }
+    my $component_package = join '::', $into, $as;
+
+    unless ( Class::Load::is_class_loaded $component_package ) {
+        eval "package $component_package; use base qw/$component/; 1;" or
+            croak "Unable to build component package for \"$component_package\": $@";
+        Moose::Util::apply_all_roles($component_package, @{$given{traits}}) if $given{traits};
+        (my $file = "$component_package.pm") =~ s{::}{/}g;
+        $INC{$file} ||= 1;    
+    }
+
+    my $_setup_component = sub {
+      my $into = shift;
+      my $component_package = shift;
+      $into->components->{$component_package} = $into->delayed_setup_component( $component_package );
+    };
+
+    $_setup_component->( $into, $component_package );
+}
+
 =head1 PSGI Helpers
 
 Utility functions to make it easier to work with PSGI applications under Catalyst
diff --git a/t/accept_context_regression.t b/t/accept_context_regression.t
new file mode 100644 (file)
index 0000000..9828702
--- /dev/null
@@ -0,0 +1,40 @@
+use strict;
+use warnings;
+use Test::More;
+
+{
+  package MyApp::Model::AcceptContext;
+  use base 'Catalyst::Model';
+
+  sub ACCEPT_CONTEXT {
+    my ($self, $c, @args) = @_;
+    Test::More::ok( ref $c);
+  }
+
+  $INC{'MyApp/Model/AcceptContext.pm'} = __FILE__;
+
+  package MyApp::Controller::Root;
+  use base 'Catalyst::Controller';
+
+  sub test_model :Local {
+    my ($self, $c) = @_;
+    $c->res->body('test');
+  }
+
+  $INC{'MyApp/Controller/Root.pm'} = __FILE__;
+
+  package MyApp;
+  use Catalyst;
+  
+  MyApp->setup;
+}
+
+use Catalyst::Test 'MyApp';
+
+my ($res, $c) = ctx_request('/test_model');
+
+ok $res;
+
+
+done_testing;
+
index 6507af1..9cc6e9f 100644 (file)
@@ -1,13 +1,17 @@
 use strict;
 use warnings;
 use Test::More;
-use HTTP::Request::Common qw/GET POST DELETE PUT /;
+use HTTP::Request::Common qw/GET POST DELETE PUT/;
  
 use FindBin;
 use lib "$FindBin::Bin/../lib";
 
 use Catalyst::Test 'TestApp';
+
+sub OPTIONS {
+    HTTP::Request->new('OPTIONS', @_);
+}
+
 is(request(GET    '/httpmethods/foo')->content, 'get');
 is(request(POST   '/httpmethods/foo')->content, 'post');
 is(request(DELETE '/httpmethods/foo')->content, 'default');
@@ -34,4 +38,12 @@ is(request(GET    '/httpmethods/check_default')->content, 'get3');
 is(request(POST   '/httpmethods/check_default')->content, 'post3');
 is(request(PUT    '/httpmethods/check_default')->content, 'chain_default');
 
+is(request(GET    '/httpmethods/opt_typo')->content, 'typo');
+is(request(POST   '/httpmethods/opt_typo')->content, 'typo');
+is(request(PUT    '/httpmethods/opt_typo')->content, 'typo');
+
+is(request(OPTIONS '/httpmethods/opt')->content, 'options');
+is(request(GET     '/httpmethods/opt')->content, 'default');
+is(request(POST    '/httpmethods/opt')->content, 'default');
+
 done_testing;
diff --git a/t/aggregate/to_app.t b/t/aggregate/to_app.t
new file mode 100644 (file)
index 0000000..7bcb497
--- /dev/null
@@ -0,0 +1,11 @@
+use strict;
+use warnings;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use TestApp;
+use Test::More;
+
+ok(TestApp->can('to_app'));
+is(ref(TestApp->to_app), 'CODE');
+
+done_testing;
index d7fdbea..ab256f0 100644 (file)
@@ -60,6 +60,30 @@ is(
 );
 
 is(
+    Catalyst::uri_for( $context, '/bar#fragment', { param1 => 'value1' } )->as_string,
+    'http://127.0.0.1/foo/bar?param1=value1#fragment',
+    'URI for path with fragment and query params 1'
+);
+
+is(
+    Catalyst::uri_for( $context, '0#fragment', { param1 => 'value1' } )->as_string,
+    'http://127.0.0.1/foo/yada/0?param1=value1#fragment',
+    'URI for path 0 with fragment and query params 1'
+);
+
+is(
+    Catalyst::uri_for( $context, '/bar#fragment^%$', { param1 => 'value1' } )->as_string,
+    'http://127.0.0.1/foo/bar?param1=value1#fragment^%$',
+    'URI for path with fragment and query params 3'
+);
+
+is(
+    Catalyst::uri_for( $context, '/foo#bar/baz', { param1 => 'value1' } )->as_string,
+    'http://127.0.0.1/foo/foo?param1=value1#bar/baz',
+    'URI for path with fragment and query params 3'
+);
+
+is(
     Catalyst::uri_for( 'TestApp', '/bar/baz' )->as_string,
     '/bar/baz',
     'URI for absolute path, called with only class name'
@@ -129,7 +153,26 @@ is(
     Catalyst::uri_for( $context, 'quux', { param1 => $request->base } )->as_string,
     'http://127.0.0.1/foo/yada/quux?param1=http%3A%2F%2F127.0.0.1%2Ffoo',
     'URI for undef action with query param as object'
-);
+  );
+
+# test with empty arg
+{
+    my @warnings;
+    local $SIG{__WARN__} = sub { push @warnings, @_ };
+    is(
+       Catalyst::uri_for( $context )->as_string,
+       'http://127.0.0.1/foo/yada',
+       'URI with no action'
+      );
+
+    is(
+       Catalyst::uri_for( $context, 0 )->as_string,
+       'http://127.0.0.1/foo/yada/0',
+       'URI with 0 path'
+      );
+
+    is_deeply(\@warnings, [], "No warnings with no path argument");
+}
 
 $request->base( URI->new('http://localhost:3000/') );
 $request->match( 'orderentry/contract' );
index b167818..f6d4f7c 100644 (file)
@@ -1,3 +1,4 @@
+use utf8;
 use strict;
 use warnings;
 use FindBin;
@@ -38,14 +39,17 @@ is($context->req->uri_with({ name => "\x{6751}\x{702c}\x{5927}\x{8f14}" }), $uri
 my $action = $context->controller('Action::Chained')
     ->action_for('roundtrip_urifor_end');
 
-{
-use utf8;
-
 is($context->uri_for($action, ['hütte'], 'hütte', {
     test => 'hütte'
 }),
 'http://127.0.0.1/chained/roundtrip_urifor/h%C3%BCtte/h%C3%BCtte?test=h%C3%BCtte',
 'uri_for with utf8 captures and args');
-}
+
+is(
+  $context->uri_for($action, ['♥'], '♥', { '♥' => '♥'}),
+  'http://127.0.0.1/chained/roundtrip_urifor/' . '%E2%99%A5' . '/' . '%E2%99%A5' . '?' . '%E2%99%A5' . '=' . '%E2%99%A5',
+    'uri_for with utf8 captures and args');
+
+# ^ the match string is purposefully broken up to aid viewing, please to 'fix' it.
 
 done_testing;
index 64d4eb8..bf71b8e 100644 (file)
@@ -29,4 +29,3 @@ my $size = -s $fn;
 }
 
 done_testing;
-
diff --git a/t/arg_constraints.t b/t/arg_constraints.t
new file mode 100644 (file)
index 0000000..c1e3733
--- /dev/null
@@ -0,0 +1,596 @@
+use warnings;
+use strict;
+use HTTP::Request::Common;
+use utf8;
+
+BEGIN {
+  use Test::More;
+  eval "use Type::Tiny 1.000005; 1" || do {
+    plan skip_all => "Trouble loading Type::Tiny and friends => $@";
+  };
+}
+
+BEGIN {
+  package MyApp::Types;
+  $INC{'MyApp/Types.pm'} = __FILE__;
+
+  use strict;
+  use warnings;
+  use Type::Utils -all;
+  use Types::Standard -types;
+  use Type::Library
+   -base,
+   -declare => qw( UserId Heart User ContextLike );
+
+  extends "Types::Standard"; 
+
+  class_type User, { class => "MyApp::Model::User::user" };
+  duck_type ContextLike, [qw/model/];
+
+  declare UserId,
+   as Int,
+   where { $_ < 5 };
+
+  declare Heart,
+   as Str,
+   where { $_ eq '♥' };
+
+  # Tests using this are skipped pending deeper thought
+  coerce User,
+   from ContextLike,
+     via { $_->model('User')->find( $_->req->args->[0] ) };
+}
+
+{
+  package MyApp::Role::Controller;
+  $INC{'MyApp/Role/Controller.pm'} = __FILE__;
+
+  use Moose::Role;
+  use MooseX::MethodAttributes::Role;
+  use MyApp::Types qw/Int Str/;
+
+  sub role_str :Path('role_test') Args(Str) {
+    my ($self, $c, $arg) = @_;
+    $c->res->body('role_str'.$arg);
+  }
+
+  sub role_int :Path('role_test') Args(Int) {
+    my ($self, $c, $arg) = @_;
+    $c->res->body('role_int'.$arg);
+  }
+
+  package MyApp::Model::User;
+  $INC{'MyApp/Model/User.pm'} = __FILE__;
+
+  use base 'Catalyst::Model';
+
+  our %users = (
+    1 => { name => 'john', age => 46 },
+    2 => { name => 'mary', age => 36 },
+    3 => { name => 'ian', age => 25 },
+    4 => { name => 'visha', age => 18 },
+  );
+
+  sub find {
+    my ($self, $id) = @_;
+    my $user = $users{$id} || return;
+    return bless $user, "MyApp::Model::User::user";
+  }
+
+  package MyApp::Controller::Root;
+  $INC{'MyApp/Controller/Root.pm'} = __FILE__;
+
+  use Moose;
+  use MooseX::MethodAttributes;
+  use Types::Standard qw/slurpy/;
+  use MyApp::Types qw/Tuple Int Str StrMatch ArrayRef UserId User Heart/;
+
+  extends 'Catalyst::Controller';
+  with 'MyApp::Role::Controller';
+
+
+  sub user :Local Args(UserId) {
+    my ($self, $c, $int) = @_;
+    my $user = $c->model("User")->find($int);
+    $c->res->body("name: $user->{name}, age: $user->{age}");
+  }
+
+  # Tests using this are current skipped pending coercion rethink
+  sub user_object :Local Args(User) Coerce(1) {
+    my ($self, $c, $user) = @_;
+    $c->res->body("name: $user->{name}, age: $user->{age}");
+  }
+
+  sub stringy_enum :Local Args('Int',Int) {
+    my ($self, $c) = @_;
+    $c->res->body('enum');
+  }
+
+  sub an_int :Local Args(Int) {
+    my ($self, $c, $int) = @_;
+    $c->res->body('an_int');
+  }
+
+  sub two_ints :Local Args(Int,Int) {
+    my ($self, $c, $int) = @_;
+    $c->res->body('two_ints');
+  }
+
+  sub many_ints :Local Args(ArrayRef[Int]) {
+    my ($self, $c, @ints) = @_;
+    $c->res->body('many_ints');
+  }
+
+  sub tuple :Local Args(Tuple[Str,Int]) {
+    my ($self, $c, $str, $int) = @_;
+    $c->res->body('tuple');
+  }
+
+  sub slurpy_tuple :Local Args(Tuple[Str,Int, slurpy ArrayRef[Int]]) {
+    my ($self, $c, $str, $int) = @_;
+    $c->res->body('tuple');
+  }
+
+  sub match :Local Args(StrMatch[qr{\d\d-\d\d-\d\d}]) {
+    my ($self, $c, $int) = @_;
+    $c->res->body('match');
+  }
+
+  sub any_priority :Path('priority_test') Args(1) { $_[1]->res->body('any_priority') }
+
+  sub int_priority :Path('priority_test') Args(Int) { $_[1]->res->body('int_priority') }
+
+  sub chain_base :Chained(/) CaptureArgs(1) { }
+
+    sub any_priority_chain :GET Chained(chain_base) PathPart('') Args(1) { $_[1]->res->body('any_priority_chain') }
+
+    sub int_priority_chain :Chained(chain_base) PathPart('') Args(Int) { $_[1]->res->body('int_priority_chain') }
+
+    sub link_any :Chained(chain_base) PathPart('') CaptureArgs(1) { }
+
+      sub any_priority_link_any :Chained(link_any) PathPart('') Args(1) { $_[1]->res->body('any_priority_link_any') }
+
+      sub int_priority_link_any :Chained(link_any) PathPart('') Args(Int) { $_[1]->res->body('int_priority_link_any') }
+    
+    sub link_int :Chained(chain_base) PathPart('') CaptureArgs(Int) { }
+
+      sub any_priority_link :Chained(link_int) PathPart('') Args(1) { $_[1]->res->body('any_priority_link') }
+
+      sub int_priority_link :Chained(link_int) PathPart('') Args(Int) { $_[1]->res->body('int_priority_link') }
+
+    sub link_int_int :Chained(chain_base) PathPart('') CaptureArgs(Int,Int) { }
+
+      sub any_priority_link2 :Chained(link_int_int) PathPart('') Args(1) { $_[1]->res->body('any_priority_link2') }
+
+      sub int_priority_link2 :Chained(link_int_int) PathPart('') Args(Int) { $_[1]->res->body('int_priority_link2') }
+
+    sub link_tuple :Chained(chain_base) PathPart('') CaptureArgs(Tuple[Int,Int,Int]) { }
+
+      sub any_priority_link3 :Chained(link_tuple) PathPart('') Args(1) { $_[1]->res->body('any_priority_link3') }
+
+      sub int_priority_link3 :Chained(link_tuple) PathPart('') Args(Int) { $_[1]->res->body('int_priority_link3') }
+
+      sub link2_int :Chained(link_tuple) PathPart('') CaptureArgs(UserId) { }
+
+        sub finally2 :GET Chained(link2_int) PathPart('') Args { $_[1]->res->body('finally2') }
+        sub finally :GET Chained(link2_int) PathPart('') Args(Int) { $_[1]->res->body('finally') }
+
+  sub chain_base2 :Chained(/) CaptureArgs(1) { }
+
+    sub chained_zero_again : Chained(chain_base2) PathPart('') Args(0) { $_[1]->res->body('chained_zero_again') }
+    sub chained_zero_post2 : Chained(chain_base2) PathPart('') Args(0) { $_[1]->res->body('chained_zero_post2') }
+    sub chained_zero2      :     Chained(chain_base2) PathPart('') Args(0) { $_[1]->res->body('chained_zero2') }
+
+    sub chained_zero_post3 : Chained(chain_base2) PathPart('') Args(1) { $_[1]->res->body('chained_zero_post3') }
+    sub chained_zero3      :     Chained(chain_base2) PathPart('') Args(1) { $_[1]->res->body('chained_zero3') }
+
+
+  sub heart :Local Args(Heart) { }
+
+  sub utf8_base :Chained(/) CaptureArgs(Heart) { }
+    sub utf8_end :Chained(utf8_base) PathPart('') Args(Heart) { }
+
+  sub default :Default {
+    my ($self, $c, $int) = @_;
+    $c->res->body('default');
+  }
+
+  MyApp::Controller::Root->config(namespace=>'');
+
+  package MyApp::Controller::Autoclean;
+  $INC{'MyApp/Controller/Autoclean.pm'} = __FILE__;
+
+  use Moose;
+  use MooseX::MethodAttributes;
+  use namespace::autoclean -except => 'Int';
+
+  use MyApp::Types qw/Int/;
+
+  extends 'Catalyst::Controller';
+
+  sub an_int :Local Args(Int) {
+    my ($self, $c, $int) = @_;
+    $c->res->body('an_int (autoclean)');
+  }
+
+  MyApp::Controller::Autoclean->config(namespace=>'autoclean');
+
+  package MyApp::Role;
+  $INC{'MyApp/Role.pm'} = __FILE__;
+
+  use Moose::Role;
+  use MooseX::MethodAttributes::Role;
+  use MyApp::Types qw/Int/;
+
+  sub an_int :Local Args(Int) {
+    my ($self, $c, $int) = @_;
+    $c->res->body('an_int (withrole)');
+  }
+
+  sub an_int_ns :Local Args(MyApp::Types::Int) {
+    my ($self, $c, $int) = @_;
+    $c->res->body('an_int (withrole)');
+  }
+
+  package MyApp::BaseController;
+  $INC{'MyApp/BaseController.pm'} = __FILE__;
+
+  use Moose;
+  use MooseX::MethodAttributes;
+  use MyApp::Types qw/Int/;
+
+  extends 'Catalyst::Controller';
+
+  sub from_parent :Local Args(Int) {
+    my ($self, $c, $id) = @_;
+    $c->res->body("from_parent $id");
+  }
+
+  package MyApp::Controller::WithRole;
+  $INC{'MyApp/Controller/WithRole.pm'} = __FILE__;
+
+  use Moose;
+  use MooseX::MethodAttributes;
+
+  extends 'MyApp::BaseController';
+
+  with 'MyApp::Role';
+
+  MyApp::Controller::WithRole->config(namespace=>'withrole');
+
+  package MyApp;
+  use Catalyst;
+
+  MyApp->setup;
+}
+
+use Catalyst::Test 'MyApp';
+
+{
+  my $res = request '/an_int/1';
+  is $res->content, 'an_int';
+}
+
+{
+  my $res = request '/an_int/aa';
+  is $res->content, 'default';
+}
+
+{
+  my $res = request '/many_ints/1';
+  is $res->content, 'many_ints';
+}
+
+{
+  my $res = request '/many_ints/1/2';
+  is $res->content, 'many_ints';
+}
+
+{
+  my $res = request '/many_ints/1/2/3';
+  is $res->content, 'many_ints';
+}
+
+{
+  my $res = request '/priority_test/1';
+  is $res->content, 'int_priority';
+}
+
+{
+  my $res = request '/priority_test/a';
+  is $res->content, 'any_priority';
+}
+
+{
+  my $res = request '/match/11-22-33';
+  is $res->content, 'match';
+}
+
+{
+  my $res = request '/match/aaa';
+  is $res->content, 'default';
+}
+
+{
+  my $res = request '/user/2';
+  is $res->content, 'name: mary, age: 36';
+}
+
+{
+  my $res = request '/user/20';
+  is $res->content, 'default';
+}
+
+
+SKIP: {
+  skip "coercion support needs more thought", 1;
+  my $res = request '/user_object/20';
+  is $res->content, 'default';
+}
+
+SKIP: {
+  skip "coercion support needs more thought", 1;
+  my $res = request '/user_object/2';
+  is $res->content, 'name: mary, age: 36';
+}
+
+{
+  my $res = request '/chain_base/capture/arg';
+  is $res->content, 'any_priority_chain';
+}
+
+{
+  my $res = request '/chain_base/cap1/100/arg';
+  is $res->content, 'any_priority_link';
+}
+
+{
+  my $res = request '/chain_base/cap1/101/102';
+  is $res->content, 'int_priority_link';
+}
+
+{
+  my $res = request '/chain_base/capture/100';
+  is $res->content, 'int_priority_chain', 'got expected';
+}
+
+{
+  my $res = request '/chain_base/cap1/a/arg';
+  is $res->content, 'any_priority_link_any';
+}
+
+{
+  my $res = request '/chain_base/cap1/a/102';
+  is $res->content, 'int_priority_link_any';
+}
+
+{
+  my $res = request '/two_ints/1/2';
+  is $res->content, 'two_ints';
+}
+
+{
+  my $res = request '/two_ints/aa/111';
+  is $res->content, 'default';
+}
+
+{
+  my $res = request '/tuple/aaa/aaa';
+  is $res->content, 'default';
+}
+
+{
+  my $res = request '/tuple/aaa/111';
+  is $res->content, 'tuple';
+}
+
+{
+  my $res = request '/tuple/aaa/111/111/111';
+  is $res->content, 'default';
+}
+
+{
+  my $res = request '/slurpy_tuple/aaa/111/111/111';
+  is $res->content, 'tuple';
+}
+
+
+{
+  my $res = request '/many_ints/1/2/a';
+  is $res->content, 'default';
+}
+
+{
+  my $res = request '/chain_base/100/100/100/100';
+  is $res->content, 'int_priority_link2';
+}
+
+{
+  my $res = request '/chain_base/100/ss/100/100';
+  is $res->content, 'default';
+}
+
+{
+  my $res = request '/chain_base/100/100/100/100/100';
+  is $res->content, 'int_priority_link3';
+}
+
+{
+  my $res = request '/chain_base/100/ss/100/100/100';
+  is $res->content, 'default';
+}
+
+{
+  my $res = request '/chain_base/1/2/3/3/3/6';
+  is $res->content, 'finally';
+}
+
+{
+  my $res = request '/chain_base/1/2/3/3/3/a';
+  is $res->content, 'finally2';
+}
+
+{
+  my $res = request '/chain_base/1/2/3/3/3/6/7/8/9';
+  is $res->content, 'finally2';
+}
+
+
+{
+    my $res = request PUT '/chain_base2/capture/1';
+    is $res->content, 'chained_zero3', "request PUT '/chain_base2/capture/1'";
+}
+
+{
+    my $res = request '/chain_base2/capture/1';
+    is $res->content, 'chained_zero3', "request '/chain_base2/capture/1'";
+}
+
+{
+    my $res = request POST '/chain_base2/capture/1';
+    is $res->content, 'chained_zero3', "request POST '/chain_base2/capture/1'";
+}
+
+{
+    my $res = request PUT '/chain_base2/capture';
+    is $res->content, 'chained_zero2', "request PUT '/chain_base2/capture'";
+}
+
+{
+    my $res = request '/chain_base2/capture';
+    is $res->content, 'chained_zero2', "request '/chain_base2/capture'";
+}
+
+{
+    my $res = request POST '/chain_base2/capture';
+    is $res->content, 'chained_zero2', "request POST '/chain_base2/capture'";
+}
+
+{
+    my $res = request '/stringy_enum/1/2';
+    is $res->content, 'enum', "request '/stringy_enum/a'";
+}
+
+{
+    my $res = request '/stringy_enum/b/2';
+    is $res->content, 'default', "request '/stringy_enum/a'";
+}
+
+{
+    my $res = request '/stringy_enum/1/a';
+    is $res->content, 'default', "request '/stringy_enum/a'";
+}
+
+=over
+
+| /chain_base/*/*/*/*/*/*                 | /chain_base (1)
+|                                         | -> /link_tuple (Tuple[Int,Int,Int])
+|                                         | -> /link2_int (UserId)
+|                                         | => GET /finally (Int)
+
+=cut
+
+{
+  # URI testing
+  my ($res, $c) = ctx_request '/';
+
+  {
+    ok my $url = eval { $c->uri_for($c->controller('Root')->action_for('user'), 2) };
+    is $url, 'http://localhost/user/2';
+  }
+
+  {
+    ok my $url = eval { $c->uri_for($c->controller('Root')->action_for('user'), [2]) };
+    is $url, 'http://localhost/user/2';
+  }
+
+  {
+    ok my $url = ! eval { $c->uri_for($c->controller('Root')->action_for('user'), [20]) };
+  }
+
+  {
+    ok my $url = eval { $c->uri_for($c->controller('Root')->action_for('finally'), [1,2,3,4,4],6) };
+    is $url, 'http://localhost/chain_base/1/2/3/4/4/6';
+  }
+
+  {
+    ok my $url = eval { $c->uri_for($c->controller('Root')->action_for('finally'), [1,2,3,4,4,6]) };
+    is $url, 'http://localhost/chain_base/1/2/3/4/4/6';
+  }
+
+  {
+    ok my $url = ! eval { $c->uri_for($c->controller('Root')->action_for('finally'), [1,2,3,4,5,6]) };
+  }
+
+  {
+    ok my $url = eval { $c->uri_for($c->controller('Root')->action_for('finally'), ['a',2,3,4,4,6]) };
+    is $url, 'http://localhost/chain_base/a/2/3/4/4/6';
+  }
+
+  {
+    ok my $url = ! eval { $c->uri_for($c->controller('Root')->action_for('finally'), ['a','1',3,4,4,'a']) };
+  }
+
+  {
+    ok my $url = ! eval { $c->uri_for($c->controller('Root')->action_for('finally'), ['a','a',3,4,4,'6']) };
+  }
+
+  {
+    ok my $url = eval { $c->uri_for($c->controller('Root')->action_for('heart'), ['♥']) };
+    is $url, 'http://localhost/heart/%E2%99%A5';
+  }
+
+  {
+    ok my $url = ! eval { $c->uri_for($c->controller('Root')->action_for('heart'), ['1']) };
+  }
+
+  {
+    ok my $url = eval { $c->uri_for($c->controller('Root')->action_for('utf8_end'), ['♥','♥']) };
+    is $url, 'http://localhost/utf8_base/%E2%99%A5/%E2%99%A5';
+  }
+
+  {
+    ok my $url = ! eval { $c->uri_for($c->controller('Root')->action_for('utf8_end'), ['2','1']) };
+  }
+
+}
+
+# Test Roles
+
+{
+    my $res = request '/role_test/1';
+    is $res->content, 'role_int1';
+}
+
+{
+    my $res = request '/role_test/a';
+    is $res->content, 'role_stra';
+}
+
+
+{
+  my $res = request '/autoclean/an_int/1';
+  is $res->content, 'an_int (autoclean)';
+}
+
+{
+  my $res = request '/withrole/an_int_ns/S';
+  is $res->content, 'default';
+}
+
+{
+  my $res = request '/withrole/an_int_ns/111';
+  is $res->content, 'an_int (withrole)';
+}
+
+{
+  my $res = request '/withrole/an_int/1';
+  is $res->content, 'an_int (withrole)';
+}
+
+{
+  my $res = request '/withrole/from_parent/1';
+  is $res->content, 'from_parent 1';
+}
+done_testing;
diff --git a/t/args0_bug.t b/t/args0_bug.t
new file mode 100644 (file)
index 0000000..1d498e5
--- /dev/null
@@ -0,0 +1,65 @@
+use warnings;
+use strict;
+use Test::More;
+
+{
+  package MyApp::Controller::Root;
+  $INC{'MyApp/Controller/Root.pm'} = __FILE__;
+
+  use Moose;
+  use MooseX::MethodAttributes;
+
+  extends 'Catalyst::Controller';
+
+  sub chain_base :Chained(/) CaptureArgs(1) { }
+
+    sub chained_one_args_0  : Chained(chain_base) PathPart('') Args(1) { $_[1]->res->body('chained_one_args_0') }
+    sub chained_one_args_1  : Chained(chain_base) PathPart('') Args(1) { $_[1]->res->body('chained_one_args_1') }
+    sub chained_one_args_2  : Chained(chain_base) PathPart('') Args(1) { $_[1]->res->body('chained_one_args_2') }
+
+    sub chained_zero_args_0 : Chained(chain_base) PathPart('') Args(0) { $_[1]->res->body('chained_zero_args_0') }
+    sub chained_zero_args_1 : Chained(chain_base) PathPart('') Args(0) { $_[1]->res->body('chained_zero_args_1') }
+    sub chained_zero_args_2 : Chained(chain_base) PathPart('') Args(0) { $_[1]->res->body('chained_zero_args_2') }
+
+  MyApp::Controller::Root->config(namespace=>'');
+
+  package MyApp;
+  use Catalyst;
+
+  #MyApp->config(use_chained_args_0_special_case=>1);
+  MyApp->setup;
+}
+
+=over
+
+[debug] Loaded Chained actions:
+.-----------------------------------------+---------------------------------------------------.
+| Path Spec                               | Private                                           |
++-----------------------------------------+---------------------------------------------------+
+| /chain_base/*/*                         | /chain_base (1)                                   |
+|                                         | => /chained_one_args_0 (1)                        |
+| /chain_base/*/*                         | /chain_base (1)                                   |
+|                                         | => /chained_one_args_1 (1)                        |
+| /chain_base/*                           | /chain_base (1)                                   |
+|                                         | => /chained_zero_args_0 (0)                       |
+| /chain_base/*                           | /chain_base (1)                                   |
+|                                         | => /chained_zero_args_1 (0)                       |
+'-----------------------------------------+---------------------------------------------------'
+
+=cut
+
+use Catalyst::Test 'MyApp';
+{
+   my $res = request '/chain_base/capturearg/arg';
+  is $res->content, 'chained_one_args_2', "request '/chain_base/capturearg/arg'";
+}
+
+{
+    my $res = request '/chain_base/capturearg';
+    is $res->content, 'chained_zero_args_2', "request '/chain_base/capturearg'";
+}
+
+done_testing;
+
+__END__
+
index 910a657..c90f51a 100644 (file)
@@ -4,26 +4,27 @@ use Test::More;
 use Test::Spelling;
 
 add_stopwords(qw(
-    API CGI MVC PSGI Plack README SSI Starman XXXX URI htaccess middleware
+    Accel API CGI MVC PSGI Plack README SSI Starman XXXX URI htaccess middleware
     mixins namespace psgi startup Deprecations catamoose cataplack linearize
-    subclasses subdirectories refactoring adaptors
+    subclasses subdirectories refactoring adaptors validator remediations
     undef env regex unary rethrow rethrows stringifies CPAN STDERR SIGCHLD baz
     roadmap wishlist refactor refactored Runtime pluggable pluggability hoc apis
-    fastcgi nginx Lighttpd IIS middlewares backend IRC
+    fastcgi nginx Lighttpd IIS middlewares backend IRC IOLayer
     ctx _application MyApp restarter httponly Utils stash's unescapes
-    dispatchtype dispatchtypes redispatch redispatching
-    CaptureArgs ChainedParent PathPart PathPrefix
-    BUILDARGS metaclass namespaces pre ARGV ReverseProxy
+    actionchain dispatchtype dispatchtypes redispatch redispatching
+    CaptureArgs ChainedParent PathPart PathParts PathPrefix
+    BUILDARGS metaclass namespaces pre ARGV ReverseProxy TT UI
     filename tempname request's subdirectory ini uninstalled uppercased
     wiki bitmask uri url urls dir hostname proxied http https IP SSL
-    inline INLINE plugins cpanfile
+    inline INLINE plugins cpanfile resized
     FastCGI Stringifies Rethrows DispatchType Wishlist Refactor ROADMAP HTTPS Unescapes Restarter Nginx Refactored
-    ActionClass LocalRegex LocalRegexp MyAction metadata cometd io psgix websockets
-    UTF async codebase dev filenames params MyMiddleware
-    JSON POSTed RESTful performant subref actionrole
-    chunked chunking codewise distingush equivilent plack Javascript
-    ConfigLoader getline
+    ActionClass LocalRegex LocalRegexp MyAction metadata cometd io psgix websocket websockets proxying
+    UTF unicode async codebase dev encodable filenames params MyMiddleware Sendfile
+    JSON xml POSTs POSTed RESTful performant subref actionrole
+    chunked chunking codewise distingush equivilent plack Javascript gzipping
+    ConfigLoader getline whitepaper matchable TBD WIP
     Andreas
+    André
     Ashton
     Axel
     Balint
@@ -73,6 +74,7 @@ add_stopwords(qw(
     Rodland
     Ruthven
     Sascha
+    Scala
     Schutz
     Sedlacek
     Sheidlower
@@ -90,10 +92,14 @@ add_stopwords(qw(
     Yuval
     abraxxa
     abw
+    alls
+    andrewalker
     andyg
     audreyt
     bricas
     chansen
+    codebases
+    davewood
     dhoss
     dkubb
     dwc
@@ -107,12 +113,15 @@ add_stopwords(qw(
     ilmari
     jcamacho
     jhannah
+    jnap
     jon
     konobi
     marcus
     mgrimes
     miyagawa
     mst
+    multipart
+    Napiorkowski
     naughton
     ningu
     nothingmuch
@@ -123,6 +132,7 @@ add_stopwords(qw(
     rainboxx
     sri
     szbalint
+    uploadtmp
     vanstyn
     willert
     wreis
index 447794a..3b7f616 100644 (file)
@@ -40,12 +40,14 @@ use Plack::Util;
     $c->res->body('manual_write');
   }
 
+  $INC{'MyApp/Controller/Root.pm'} = __FILE__; # sorry...
+
   package MyApp;
   use Catalyst;
 
 }
 
-$INC{'MyApp/Controller/Root.pm'} = '1'; # sorry...
+
 
 ok(MyApp->setup);
 ok(my $psgi = MyApp->psgi_app);
diff --git a/t/class_traits.t b/t/class_traits.t
new file mode 100644 (file)
index 0000000..8c65b38
--- /dev/null
@@ -0,0 +1,68 @@
+use strict;
+use warnings;
+use Test::More;
+use Class::MOP;
+
+BEGIN {
+  package TestRole;
+  use Moose::Role;
+
+  sub a { 'a' }
+  sub b { 'b' }
+
+  package Catalyst::TraitFor::Request::Foo;
+  use Moose::Role;
+
+  sub c { 'c' }
+
+  package TestApp::TraitFor::Request::Bar;
+  use Moose::Role;
+
+  sub d { 'd' }
+
+  package Catalyst::TraitFor::Response::Foo;
+  use Moose::Role;
+
+  sub c { 'c' }
+
+  package TestApp::TraitFor::Response::Bar;
+  use Moose::Role;
+
+  sub d { 'd' }
+}
+{
+  package TestApp;
+  use Catalyst;
+
+  __PACKAGE__->request_class_traits([qw/TestRole Foo Bar/]);
+  __PACKAGE__->response_class_traits([qw/TestRole Foo Bar/]);
+  __PACKAGE__->stats_class_traits([qw/TestRole/]);
+
+  __PACKAGE__->setup;
+}
+foreach my $class_prefix (qw/request response stats/) {
+  my $method = 'composed_' .$class_prefix. '_class';
+  ok(
+    Class::MOP::class_of(TestApp->$method)->does_role('TestRole'),
+    "$method does TestRole",
+  );
+}
+
+use Catalyst::Test 'TestApp';
+
+my ($res, $c) = ctx_request '/';
+
+is $c->req->a, 'a';
+is $c->req->b, 'b';
+is $c->req->c, 'c';
+is $c->req->d, 'd';
+is $c->res->a, 'a';
+is $c->res->b, 'b';
+is $c->res->c, 'c';
+is $c->res->d, 'd';
+
+done_testing;
diff --git a/t/configured_comps.t b/t/configured_comps.t
new file mode 100644 (file)
index 0000000..c543c08
--- /dev/null
@@ -0,0 +1,126 @@
+use warnings;
+use strict;
+use HTTP::Request::Common;
+use Test::More;
+
+{
+  package TestRole;
+
+  use Moose::Role;
+
+  sub role { 'role' }
+  
+  package Local::Model::Foo;
+
+  use Moose;
+  extends 'Catalyst::Model';
+
+  has a => (is=>'ro', required=>1);
+  has b => (is=>'ro');
+
+  sub foo { shift->a . 'foo' }
+
+  package Local::Controller::Errors;
+
+  use Moose;
+  use MooseX::MethodAttributes;
+
+  extends 'Catalyst::Controller';
+
+  has ['a', 'b'] => (is=>'ro', required=>1);
+
+  sub not_found :Local { pop->res->from_psgi_response([404, [], ['Not Found']]) }
+
+  package MyApp::Model::User;
+  $INC{'MyApp/Model/User.pm'} = __FILE__;
+
+  use Moose;
+  extends 'Catalyst::Model';
+
+  has 'zoo' => (is=>'ro', required=>1, isa=>'Object');
+
+  around 'COMPONENT', sub {
+    my ($orig, $class, $app, $config) = @_;
+    $config->{zoo} = $app->model('Zoo');
+
+    return $class->$orig($app, $config);
+  };
+
+  our %users = (
+    1 => { name => 'john', age => 46 },
+    2 => { name => 'mary', age => 36 },
+    3 => { name => 'ian', age => 25 },
+    4 => { name => 'visha', age => 18 },
+  );
+
+  sub find {
+    my ($self, $id) = @_;
+    my $user = $users{$id} || return;
+    return bless $user, "MyApp::Model::User::user";
+  }
+
+  package MyApp::Controller::Root;
+  $INC{'MyApp/Controller/Root.pm'} = __FILE__;
+
+  use Moose;
+  use MooseX::MethodAttributes;
+
+  extends 'Catalyst::Controller';
+
+  sub user :Local Args(1) {
+    my ($self, $c, $int) = @_;
+    
+    Test::More::ok(my $user = $c->model("User")->find($int));
+    Test::More::is($c->model("User")->zoo->a, 2);
+    Test::More::is($c->model("Foo")->role, 'role');
+    Test::More::is($c->model("One")->a, 'one');
+    Test::More::is($c->model("Two")->a, 'two');
+   
+    $c->res->body("name: $user->{name}, age: $user->{age}");
+  }
+
+  sub default :Default {
+    my ($self, $c, $int) = @_;
+    $c->res->body('default');
+  }
+
+  MyApp::Controller::Root->config(namespace=>'');
+
+  package MyApp;
+  use Catalyst;
+
+  MyApp->inject_components(
+      'Model::One' => { from_component => 'Local::Model::Foo' },
+      'Model::Two' => { from_component => 'Local::Model::Foo' },
+  );
+
+  MyApp->config({
+    inject_components => {
+      'Controller::Err' => { from_component => 'Local::Controller::Errors' },
+      'Model::Zoo' => { from_component => 'Local::Model::Foo' },
+      'Model::Foo' => { from_component => 'Local::Model::Foo', roles => ['TestRole'] },
+    },
+    'Controller::Err' => { a => 100, b => 200, namespace => 'error' },
+    'Model::Zoo' => { a => 2 },
+    'Model::Foo' => { a => 100 },
+    'Model::One' => { a => 'one' },
+    'Model::Two' => { a => 'two' },
+
+  });
+
+  MyApp->setup;
+}
+
+use Catalyst::Test 'MyApp';
+
+{
+  my $res = request '/user/1';
+  is $res->content, 'name: john, age: 46';
+}
+
+{
+  my $res = request '/error/not_found';
+  is $res->content, 'Not Found';
+}
+
+done_testing;
diff --git a/t/consumes.t b/t/consumes.t
new file mode 100644 (file)
index 0000000..a96d209
--- /dev/null
@@ -0,0 +1,59 @@
+use warnings;
+use strict;
+use Test::More;
+
+# Test case for reported issue when an action consumes JSON but a
+# POST sends nothing we get a hard error
+
+{
+  package MyApp::Controller::Root;
+  $INC{'MyApp/Controller/Root.pm'} = __FILE__;
+
+  use base 'Catalyst::Controller';
+
+  sub bar :Local Args(0) POST Consumes(JSON) {
+    my( $self, $c ) = @_;
+    my $foo = $c->req->body_data;
+  }
+
+  sub end :Private {
+    my( $self, $c ) = @_;
+    my $body = $c->shift_errors;
+    $c->res->body( $body || "No errors");
+  }
+
+  package MyApp;
+  use Catalyst;
+  MyApp->setup;
+}
+
+use HTTP::Request::Common;
+use Catalyst::Test 'MyApp';
+
+{
+  # Test to send no post
+  ok my $res = request POST 'root/bar',
+    'Content-Type' => 'application/json';
+
+  like $res->content, qr"Error Parsing POST 'undef'";
+}
+
+{
+  # Test to send bad (malformed JSON) post
+  ok my $res = request POST 'root/bar',
+    'Content-Type' => 'application/json',
+    'Content' => 'i am not JSON';
+
+  like $res->content, qr/Error Parsing POST 'i am not JSON'/;
+}
+
+{
+  # Test to send bad (malformed JSON) post
+  ok my $res = request POST 'root/bar',
+    'Content-Type' => 'application/json',
+    'Content' => '{ "a":"b" }';
+
+  is $res->content, 'No errors';
+}
+
+done_testing();
index 98b566c..58c2f11 100644 (file)
@@ -4,6 +4,13 @@ use lib 't/lib';
 
 use Test::More;
 
+# This test needs to be rewritten (and the code it was using as well) since
+# when we added the arg and capturearg type constraint support, we now allow
+# non integer values.  however we could probably support some additional sanity
+# testing on the values, so this is a nice TODO for someone -jnap
+
+plan skip_all => 'Removing this test because constraint arg types allow this';
+
 use Catalyst::Test 'TestApp';
 
 for my $fail (
diff --git a/t/dispatch_on_scheme.t b/t/dispatch_on_scheme.t
new file mode 100644 (file)
index 0000000..1da72a2
--- /dev/null
@@ -0,0 +1,123 @@
+use warnings;
+use strict;
+use Test::More;
+use HTTP::Request::Common;
+
+# Test cases for dispatching on URI Scheme
+
+{
+  package MyApp::Controller::Root;
+  $INC{'MyApp/Controller/Root.pm'} = __FILE__;
+
+  use base 'Catalyst::Controller';
+
+  sub is_http :Path(scheme) Scheme(http) Args(0) {
+    my ($self, $c) = @_;
+    Test::More::is $c->action->scheme, 'http';
+    $c->response->body("is_http");
+  }
+
+  sub is_https :Path(scheme) Scheme(https) Args(0)  {
+    my ($self, $c) = @_;
+    Test::More::is $c->action->scheme, 'https';
+    $c->response->body("is_https");
+  }
+
+  sub base :Chained('/') CaptureArgs(0) { }
+
+    sub is_http_chain :GET Chained('base') PathPart(scheme) Scheme(http) Args(0) {
+      my ($self, $c) = @_;
+      Test::More::is $c->action->scheme, 'http';
+      $c->response->body("base/is_http");
+    }
+
+    sub is_https_chain :Chained('base') PathPart(scheme) Scheme(https) Args(0) {
+      my ($self, $c) = @_;
+      Test::More::is $c->action->scheme, 'https';
+      $c->response->body("base/is_https");
+    }
+
+    sub uri_for1 :Chained('base') Scheme(https) Args(0) {
+      my ($self, $c) = @_;
+      Test::More::is $c->action->scheme, 'https';
+      $c->response->body($c->uri_for($c->action)->as_string);
+    }
+
+    sub uri_for2 :Chained('base') Scheme(https) Args(0) {
+      my ($self, $c) = @_;
+      Test::More::is $c->action->scheme, 'https';
+      $c->response->body($c->uri_for($self->action_for('is_http'))->as_string);
+    }
+
+    sub uri_for3 :Chained('base') Scheme(http) Args(0) {
+      my ($self, $c) = @_;
+      Test::More::is $c->action->scheme, 'http';
+      $c->response->body($c->uri_for($self->action_for('endpoint'))->as_string);
+    }
+
+  sub base2 :Chained('/') CaptureArgs(0) { }
+    sub link :Chained(base2) Scheme(https) CaptureArgs(0) { }
+      sub endpoint :Chained(link) Args(0) {
+        my ($self, $c) = @_;
+        Test::More::is $c->action->scheme, 'https';
+        $c->response->body("end");
+      }
+
+
+  package MyApp;
+  use Catalyst;
+
+  Test::More::ok(MyApp->setup, 'setup app');
+}
+
+use Catalyst::Test 'MyApp';
+
+{
+  my $res = request "/root/scheme";
+  is $res->code, 200, 'OK';
+  is $res->content, 'is_http', 'correct body';
+}
+
+{
+  my $res = request "https://localhost/root/scheme";
+  is $res->code, 200, 'OK';
+  is $res->content, 'is_https', 'correct body';
+}
+
+{
+  my $res = request "/base/scheme";
+  is $res->code, 200, 'OK';
+  is $res->content, 'base/is_http', 'correct body';
+}
+
+{
+  my $res = request "https://localhost/base/scheme";
+  is $res->code, 200, 'OK';
+  is $res->content, 'base/is_https', 'correct body';
+}
+
+{
+  my $res = request "https://localhost/base/uri_for1";
+  is $res->code, 200, 'OK';
+  is $res->content, 'https://localhost/base/uri_for1', 'correct body';
+}
+
+{
+  my $res = request "https://localhost/base/uri_for2";
+  is $res->code, 200, 'OK';
+  is $res->content, 'http://localhost/root/scheme', 'correct body';
+}
+
+{
+  my $res = request "/base/uri_for3";
+  is $res->code, 200, 'OK';
+  is $res->content, 'https://localhost/base2/link/endpoint', 'correct body';
+}
+
+{
+  my $res = request "https://localhost/base2/link/endpoint";
+  is $res->code, 200, 'OK';
+  is $res->content, 'end', 'correct body';
+}
+
+done_testing;
index 5cb3117..ad6544a 100644 (file)
@@ -12,10 +12,6 @@ use Plack::Test;
 {
   package MyApp::Exception;
 
-  use overload
-    # Use the overloading thet HTTP::Exception uses
-    bool => sub { 1 }, '""' => 'as_string', fallback => 1;
-
   sub new {
     my ($class, $code, $headers, $body) = @_;
     return bless +{res => [$code, $headers, $body]}, $class;
@@ -35,8 +31,14 @@ use Plack::Test;
     };
   }
 
+  package MyApp::AnotherException;
+
+  sub new { bless +{}, shift }
+
+  sub code { 400 }
+
   sub as_string { 'bad stringy bad' }
-  
+
   package MyApp::Controller::Root;
 
   use base 'Catalyst::Controller';
@@ -60,6 +62,11 @@ use Plack::Test;
       403, ['content-type'=>'text/plain'], ['Forbidden']);
   }
 
+  sub from_code_type :Local {
+    my $e = MyApp::AnotherException->new;
+    die $e;
+  }
+
   sub classic_error :Local {
     my ($self, $c) = @_;
     Catalyst::Exception->throw("Ex Parrot");
@@ -107,6 +114,14 @@ test_psgi $psgi, sub {
 
 test_psgi $psgi, sub {
     my $cb = shift;
+    my $res = $cb->(GET "/root/from_code_type");
+    is $res->code, 400;
+    is $res->content, 'bad stringy bad', 'bad stringy bad';
+    unlike $res->content, qr'HTTPExceptions', 'HTTPExceptions';
+};
+
+test_psgi $psgi, sub {
+    my $cb = shift;
     my $res = $cb->(GET "/root/classic_error");
     is $res->code, 500;
     like $res->content, qr'Ex Parrot', 'Ex Parrot';
@@ -127,5 +142,5 @@ test_psgi $psgi, sub {
 # in the callbacks might never get run (thus all ran tests pass but not all
 # required tests run).
 
-done_testing(14);
+done_testing(17);
 
diff --git a/t/http_exceptions_backcompat.t b/t/http_exceptions_backcompat.t
new file mode 100644 (file)
index 0000000..f2112dd
--- /dev/null
@@ -0,0 +1,138 @@
+use warnings;
+use strict;
+use Test::More;
+use HTTP::Request::Common;
+use HTTP::Message::PSGI;
+use Plack::Util;
+use Plack::Test;
+
+# Test to make sure HTTP style exceptions do NOT bubble up to the middleware
+# if the backcompat setting 'always_catch_http_exceptions' is enabled.
+
+{
+  package MyApp::Exception;
+
+  sub new {
+    my ($class, $code, $headers, $body) = @_;
+    return bless +{res => [$code, $headers, $body]}, $class;
+  }
+
+  sub throw { die shift->new(@_) }
+
+  sub as_psgi {
+    my ($self, $env) = @_;
+    my ($code, $headers, $body) = @{$self->{res}};
+
+    return [$code, $headers, $body]; # for now
+
+    return sub {
+      my $responder = shift;
+      $responder->([$code, $headers, $body]);
+    };
+  }
+
+  package MyApp::AnotherException;
+
+  sub new { bless +{}, shift }
+
+  sub code { 400 }
+
+  sub as_string { 'bad stringy bad' }
+
+  package MyApp::Controller::Root;
+
+  use base 'Catalyst::Controller';
+
+  my $psgi_app = sub {
+    my $env = shift;
+    die MyApp::Exception->new(
+      404, ['content-type'=>'text/plain'], ['Not Found']);
+  };
+
+  sub from_psgi_app :Local {
+    my ($self, $c) = @_;
+    $c->res->from_psgi_response(
+      $psgi_app->(
+        $c->req->env));
+  }
+
+  sub from_catalyst :Local {
+    my ($self, $c) = @_;
+    MyApp::Exception->throw(
+      403, ['content-type'=>'text/plain'], ['Forbidden']);
+  }
+
+  sub from_code_type :Local {
+    my $e = MyApp::AnotherException->new;
+    die $e;
+  }
+
+  sub classic_error :Local {
+    my ($self, $c) = @_;
+    Catalyst::Exception->throw("Ex Parrot");
+  }
+
+  sub just_die :Local {
+    my ($self, $c) = @_;
+    die "I'm not dead yet";
+  }
+
+  package MyApp;
+  use Catalyst;
+
+  MyApp->config(
+      abort_chain_on_error_fix=>1,
+      always_catch_http_exceptions=>1,
+  );
+
+  sub debug { 1 }
+
+  MyApp->setup_log('fatal');
+}
+
+$INC{'MyApp/Controller/Root.pm'} = __FILE__; # sorry...
+MyApp->setup_log('error');
+
+Test::More::ok(MyApp->setup);
+
+ok my $psgi = MyApp->psgi_app;
+test_psgi $psgi, sub {
+    my $cb = shift;
+    my $res = $cb->(GET "/root/from_psgi_app");
+    is $res->code, 500;
+    like $res->content, qr/MyApp::Exception=HASH/;
+};
+
+test_psgi $psgi, sub {
+    my $cb = shift;
+    my $res = $cb->(GET "/root/from_catalyst");
+    is $res->code, 500;
+    like $res->content, qr/MyApp::Exception=HASH/;
+};
+
+test_psgi $psgi, sub {
+    my $cb = shift;
+    my $res = $cb->(GET "/root/from_code_type");
+    is $res->code, 500;
+    like $res->content, qr/MyApp::AnotherException=HASH/;
+};
+
+test_psgi $psgi, sub {
+    my $cb = shift;
+    my $res = $cb->(GET "/root/classic_error");
+    is $res->code, 500;
+    like $res->content, qr'Ex Parrot', 'Ex Parrot';
+};
+
+test_psgi $psgi, sub {
+    my $cb = shift;
+    my $res = $cb->(GET "/root/just_die");
+    is $res->code, 500;
+    like $res->content, qr'not dead yet', 'not dead yet';
+};
+
+# We need to specify the number of expected tests because tests that live
+# in the callbacks might never get run (thus all ran tests pass but not all
+# required tests run).
+
+done_testing(12);
diff --git a/t/inject_component_util.t b/t/inject_component_util.t
new file mode 100644 (file)
index 0000000..3ac9dcc
--- /dev/null
@@ -0,0 +1,67 @@
+use strict;
+use warnings; 
+use Test::More;
+use FindBin;
+use lib "$FindBin::Bin/lib";
+
+BEGIN {
+  package RoleTest1;
+  use Moose::Role;
+
+  sub aaa { 'aaa' }
+
+ $INC{'RoleTest1.pm'} = __FILE__;
+
+  package RoleTest2;
+  use Moose::Role;
+
+  sub bbb { 'bbb' }
+
+ $INC{'RoleTest2.pm'} = __FILE__;
+
+  package Model::Banana;
+  use base qw/Catalyst::Model/;
+
+ $INC{'Model/Banana.pm'} = __FILE__;
+
+  package Model::BananaMoose;
+
+  use Moose;
+  extends 'Catalyst::Model';
+
+  Model::BananaMoose->meta->make_immutable;
+ $INC{'Model/BananaMoose.pm'} = __FILE__;
+}
+
+{
+  package TestCatalyst;
+  $INC{'TestCatalyst.pm'} = __FILE__;
+   
+  use Moose;
+  use Catalyst;
+  use Catalyst::Utils;
+   
+  after 'setup_components' => sub {
+      my $self = shift;
+      Catalyst::Utils::inject_component( into => __PACKAGE__, component => 'Model::Banana' );
+      Catalyst::Utils::inject_component( into => __PACKAGE__, component => 'Test::Apple' );
+      Catalyst::Utils::inject_component( into => __PACKAGE__, component => 'Model::Banana', as => 'Cherry' );
+      Catalyst::Utils::inject_component( into => __PACKAGE__, component => 'Model::BananaMoose', as => 'CherryMoose', traits => ['RoleTest1', 'RoleTest2'] );
+      Catalyst::Utils::inject_component( into => __PACKAGE__, component => 'Test::Apple', as => 'Apple' );
+      Catalyst::Utils::inject_component( into => __PACKAGE__, component => 'Test::Apple', as => 'Apple2', traits => ['RoleTest1', 'RoleTest2'] );
+  };
+   
+  TestCatalyst->config( 'home' => '.' ); 
+  TestCatalyst->setup;
+}
+use Catalyst::Test qw/TestCatalyst/;
+ok( TestCatalyst->controller( $_ ) ) for qw/ Apple Test::Apple /;
+ok( TestCatalyst->model( $_ ) ) for qw/ Banana Cherry /;
+is( TestCatalyst->controller('Apple2')->aaa, 'aaa');
+is( TestCatalyst->controller('Apple2')->bbb, 'bbb');
+is( TestCatalyst->model('CherryMoose')->aaa, 'aaa');
+is( TestCatalyst->model('CherryMoose')->bbb, 'bbb');
+
+done_testing;
diff --git a/t/lib/Test/Apple.pm b/t/lib/Test/Apple.pm
new file mode 100644 (file)
index 0000000..98b4df1
--- /dev/null
@@ -0,0 +1,14 @@
+package Test::Apple;
+
+use strict;
+use warnings;
+
+use parent qw/Catalyst::Controller/;
+
+sub default :Path {
+}
+
+sub apple :Local {
+}
+
+1;
index e687372..5d42858 100644 (file)
@@ -30,6 +30,16 @@ sub any_method : Path('baz') {
     $ctx->response->body('any');
 }
 
+sub typo_option : Path('opt_typo') OPTION {
+    my ($self, $ctx) = @_;
+    $ctx->response->body('typo');
+}
+
+sub real_options : Path('opt') OPTIONS {
+    my ($self, $ctx) = @_;
+    $ctx->response->body('options');
+}
+
 sub base :Chained('/') PathPrefix CaptureArgs(0) { }
 
 sub chained_get :Chained('base') Args(0) GET {
@@ -70,6 +80,10 @@ sub delete2 :Chained('post_or_delete') PathPart('') Args(0) DELETE {
 
 sub check_default :Chained('base') CaptureArgs(0) { }
 
+sub chain_default :Chained('check_default') PathPart('') Args(0) {
+    pop->res->body('chain_default');
+}
+
 sub default_get :Chained('check_default') PathPart('') Args(0) GET {
     pop->res->body('get3');
 }
@@ -78,8 +92,6 @@ sub default_post :Chained('check_default') PathPart('') Args(0) POST {
     pop->res->body('post3');
 }
 
-sub chain_default :Chained('check_default') PathPart('') Args(0) {
-    pop->res->body('chain_default');
-}
+
 
 __PACKAGE__->meta->make_immutable;
index b82e1bf..1c42bfa 100644 (file)
@@ -8,6 +8,7 @@ __PACKAGE__->config->{namespace} = '';
 
 sub binary : Local {
     my ($self, $c) = @_;
+    $c->res->content_type('image/gif');
     $c->res->body(do {
         open(my $fh, '<', $c->path_to('..', '..', 'catalyst_130pix.gif')) or die $!; 
         binmode($fh); 
@@ -31,12 +32,8 @@ sub utf8_non_ascii_content : Local {
     
     my $str = 'ʇsʎlɐʇɐɔ';  # 'catalyst' flipped at http://www.revfad.com/flip.html
     ok utf8::is_utf8($str), '$str is in UTF8 internally';
-    
-    # encode $str into a sequence of octets and turn off the UTF-8 flag, so that
-    # we don't get the 'Wide character in syswrite' error in Catalyst::Engine
-    utf8::encode($str);
-    ok !utf8::is_utf8($str), '$str is a sequence of octets (byte string)';
-    
+
+    $c->res->content_type('text/plain');
     $c->res->body($str);
 }
 
index 55359f7..8338f3d 100644 (file)
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 use TestLogger;
 use base qw/Catalyst/;
-use Catalyst qw/Unicode::Encoding/;
+use Catalyst;
 
 __PACKAGE__->config(
   'name' => 'TestAppUnicode',
index fc0d622..72ddb15 100644 (file)
@@ -22,7 +22,9 @@ use Catalyst::Test 'TestApp';
     ok($resp->is_success);
     #is($ctx->count_leaks, 1);
     # FIXME: find out why this changed from 1 to 2 after 52af51596d
-    is($ctx->count_leaks, 2);
+    # ^^ probably has something to do with env being in Engine and Request - JNAP
+    # ^^ I made the env in Engine a weak ref, should help until we can remove it
+    is($ctx->count_leaks, 1);
 }
 
 {
diff --git a/t/middleware-stash.t b/t/middleware-stash.t
new file mode 100644 (file)
index 0000000..dc22ab6
--- /dev/null
@@ -0,0 +1,78 @@
+use warnings;
+use strict;
+
+{
+
+  package MyMiddleware;
+  $INC{'MyMiddleware'} = __FILE__;
+
+  our $INNER_VAR_EXPOSED;
+
+  use base 'Plack::Middleware';
+
+  sub call {
+    my ($self, $env) = @_;
+
+    my $res = $self->app->($env);
+
+    return $self->response_cb($res, sub{
+      my $inner = shift;
+
+      $INNER_VAR_EXPOSED = $env->{inner_var_from_catalyst};
+
+      return;
+    });
+
+  }
+
+  package MyAppChild::Controller::User;
+  $INC{'MyAppChild/Controller/User.pm'} = __FILE__;
+
+  use base 'Catalyst::Controller';
+  use Test::More;
+
+  sub stash :Local {
+    my ($self, $c) = @_;
+    $c->stash->{inner} = "inner";
+    $c->res->body( "inner: ${\$c->stash->{inner}}, outer: ${\$c->stash->{outer}}");
+
+    $c->req->env->{inner_var_from_catalyst} = 'station';
+
+    is_deeply [sort {$a cmp $b} keys(%{$c->stash})], ['inner','outer'], 'both keys in stash';
+  }
+
+  package MyAppChild;
+  $INC{'MyAppChild.pm'} = __FILE__;
+
+  use Catalyst;
+  MyAppChild->setup;
+
+  package MyAppParent::Controller::User;
+  $INC{'MyAppParent/Controller/User.pm'} = __FILE__;
+
+  use base 'Catalyst::Controller';
+  use Test::More;
+
+  sub stash :Local {
+    my ($self, $c) = @_;
+    $c->stash->{outer} = "outer";
+    $c->res->from_psgi_response( MyAppChild->to_app->($c->req->env) );
+
+    is_deeply [sort keys(%{$c->stash})], ['inner','outer'];
+  }
+
+  package MyAppParent;
+  use Catalyst;
+  MyAppParent->config(psgi_middleware=>['+MyMiddleware']);
+  MyAppParent->setup;
+
+}
+
+use Test::More;
+use Catalyst::Test 'MyAppParent';
+
+my $res = request '/user/stash';
+is $res->content, 'inner: inner, outer: outer', 'got expected response';
+is $MyMiddleware::INNER_VAR_EXPOSED, 'station', 'env does not get trampled';
+
+done_testing;
diff --git a/t/no_test_stash_bug.t b/t/no_test_stash_bug.t
new file mode 100644 (file)
index 0000000..72549df
--- /dev/null
@@ -0,0 +1,28 @@
+use warnings;
+use strict;
+
+# For reported: https://rt.cpan.org/Ticket/Display.html?id=97948
+
+{
+  package MyApp::Controller::Root;
+  $INC{'MyApp/Controller/Root.pm'} = __FILE__;
+
+  use base 'Catalyst::Controller';
+
+  sub example :Local Args(0) {
+    pop->stash->{testing1} = 'testing2';
+  }
+
+  package MyApp;
+  use Catalyst;
+
+  MyApp->setup;
+}
+
+use Test::More;
+use Catalyst::Test 'MyApp';
+
+my ($res, $c) = ctx_request('/root/example');
+is $c->stash->{testing1}, 'testing2', 'got expected stash value';
+
+done_testing;
diff --git a/t/not_utf8_query_bug.t b/t/not_utf8_query_bug.t
new file mode 100644 (file)
index 0000000..52578bc
--- /dev/null
@@ -0,0 +1,45 @@
+use utf8;
+use warnings;
+use strict;
+
+# For reported: https://rt.cpan.org/Ticket/Display.html?id=103063
+
+{
+  package MyApp::Controller::Root;
+  $INC{'MyApp/Controller/Root.pm'} = __FILE__;
+
+  use base 'Catalyst::Controller';
+
+  sub example :Local Args(0) {
+    pop->stash->{testing1} = 'testing2';
+  }
+
+  package MyApp;
+  use Catalyst;
+
+  #MyApp->config(decode_query_using_global_encoding=>1, encoding => 'SHIFT_JIS');
+  #MyApp->config(do_not_decode_query=>1);
+  #MyApp->config(decode_query_using_global_encoding=>1, encoding => undef);
+  MyApp->config(default_query_encoding=>'SHIFT_JIS');
+
+  MyApp->setup;
+}
+
+use Test::More;
+use Catalyst::Test 'MyApp';
+use Encode;
+use HTTP::Request::Common;
+
+{
+  my $shiftjs = 'test テスト';
+  my $encoded = Encode::encode('SHIFT_JIS', $shiftjs);
+
+  ok my $req = GET "/root/example?a=$encoded";
+  my ($res, $c) = ctx_request $req;
+
+  is $c->req->query_parameters->{'a'}, $shiftjs, 'got expected value';
+}
+
+
+done_testing;
+
index 4cc3e72..f6bf563 100644 (file)
@@ -54,4 +54,15 @@ ok my($res, $c) = ctx_request('/');
   ok $response->headers->{"x-runtime"}, "Got value for expected middleware";
 }
 
+{
+  my $total_mw = scalar(TestMiddleware->registered_middlewares);
+
+  TestMiddleware->setup_middleware;
+  TestMiddleware->setup_middleware;
+
+  my $post_mw = scalar(TestMiddleware->registered_middlewares);
+
+  is $total_mw, $post_mw, 'Calling ->setup_middleware does not re-add default middleware';
+}
+
 done_testing;
index 56b9dad..91a36dc 100644 (file)
@@ -72,7 +72,7 @@ my $cmp = TestApp->debug ? '>=' : '==';
         my $res = $cb->(GET "/log/info");
         my @logs = $handle->logs;
         cmp_ok(scalar(@logs), $cmp, 1, "psgi.errors: one event output");
-        like($logs[0], qr/info$/m, "psgi.errors: event matches test data");
+        like($logs[0], qr/info$/m, "psgi.errors: event matches test data") unless TestApp->debug;
     };
 };
 
index 078dd82..eb69e9d 100644 (file)
@@ -9,6 +9,12 @@ my $psgi_app = sub {
 };
 
 {
+  package MyApp::PSGIObject;
+
+  sub as_psgi {
+    return [200, ['Content-Type' => 'text/plain'], ['as_psgi']];
+  };
+
   package MyApp::Controller::Docs;
   $INC{'MyApp/Controller/Docs.pm'} = __FILE__;
 
@@ -16,6 +22,12 @@ my $psgi_app = sub {
   use Plack::Request;
   use Catalyst::Utils;
 
+  sub as_psgi :Local {
+    my ($self, $c) = @_;
+    my $as_psgi = bless +{}, 'MyApp::PSGIObject';
+    $c->res->from_psgi_response($as_psgi);
+  }
+
   sub name :Local {
     my ($self, $c) = @_;
     my $env = $c->Catalyst::Utils::env_at_action;
@@ -31,6 +43,18 @@ my $psgi_app = sub {
       $psgi_app->($env));
   }
 
+  sub filehandle :Local {
+    my ($self, $c, $arg) = @_;
+    my $path = File::Spec->catfile('t', 'utf8.txt');
+    open(my $fh, '<', $path) || die "trouble: $!";
+    $c->res->from_psgi_response([200, ['Content-Type'=>'text/html'], $fh]);
+  }
+
+  sub direct :Local {
+    my ($self, $c, $arg) = @_;
+    $c->res->from_psgi_response([200, ['Content-Type'=>'text/html'], ["hello","world"]]);
+  }
+
   package MyApp::Controller::User;
   $INC{'MyApp/Controller/User.pm'} = __FILE__;
 
@@ -122,6 +146,11 @@ use Test::More;
 use Catalyst::Test 'MyApp';
 
 {
+  my ($res, $c) = ctx_request('/docs/as_psgi');
+  is $res->content, 'as_psgi';
+}
+
+{
   my ($res, $c) = ctx_request('/user/mounted/111?path_prefix=1');
   is $c->action, 'user/mounted';
   is $res->content, 'http://localhost/user/user/local_example_args1/111';
@@ -366,33 +395,16 @@ use Catalyst::Test 'MyApp';
   is_deeply $c->req->args, [111];
 }
 
-done_testing();
-
-__END__
-
-
-use Plack::App::URLMap;
-use HTTP::Request::Common;
-use HTTP::Message::PSGI;
-
-my $urlmap = Plack::App::URLMap->new;
-
-my $app1 = sub {
-  my $env = shift;
-  return [200, [], [
-    "REQUEST_URI: $env->{REQUEST_URI}, FROM: $env->{MAP_TO}, PATH_INFO: $env->{PATH_INFO}, SCRIPT_NAME $env->{SCRIPT_NAME}"]];
-};
-
-$urlmap->map("/" => sub { my $env = shift; $env->{MAP_TO} = '/'; $app1->($env)});
-$urlmap->map("/foo" => sub { my $env = shift; $env->{MAP_TO} = '/foo'; $app1->($env)});
-$urlmap->map("/bar/baz" => sub { my $env = shift; $env->{MAP_TO} = '/foo/bar'; $app1->($env)});
-
-my $app = $urlmap->to_app;
+{
+  use utf8;
+  use Encode;
+  my ($res, $c) = ctx_request('/docs/filehandle');
+  is Encode::decode_utf8($res->content), "<p>This is stream_body_fh action ♥</p>\n";
+}
 
-warn $app->(req_to_psgi(GET '/'))->[2]->[0];
-warn $app->(req_to_psgi(GET '/111'))->[2]->[0];
-warn $app->(req_to_psgi(GET '/foo'))->[2]->[0];
-warn $app->(req_to_psgi(GET '/foo/222'))->[2]->[0];
-warn $app->(req_to_psgi(GET '/bar/baz'))->[2]->[0];
-warn $app->(req_to_psgi(GET '/bar/baz/333'))->[2]->[0];
+{
+  my ($res, $c) = ctx_request('/docs/direct');
+  is $res->content, "helloworld";
+}
 
+done_testing();
diff --git a/t/query_constraints.t b/t/query_constraints.t
new file mode 100644 (file)
index 0000000..81e3e1d
--- /dev/null
@@ -0,0 +1,177 @@
+use warnings;
+use strict;
+use HTTP::Request::Common;
+use utf8;
+
+BEGIN {
+  use Test::More;
+  eval "use Type::Tiny 1.000005; 1" || do {
+    plan skip_all => "Trouble loading Type::Tiny and friends => $@";
+  };
+}
+
+BEGIN {
+  package MyApp::Types;
+  $INC{'MyApp/Types.pm'} = __FILE__;
+
+  use strict;
+  use warnings;
+  use Type::Utils -all;
+  use Types::Standard -types;
+  use Type::Library
+   -base,
+   -declare => qw( UserId Heart );
+
+  extends "Types::Standard"; 
+
+  declare UserId,
+   as Int,
+   where { $_ < 5 };
+
+  declare Heart,
+   as Str,
+   where { $_ eq '♥' };
+
+}
+
+{
+  package MyApp::Controller::Root;
+  $INC{'MyApp/Controller/Root.pm'} = __FILE__;
+
+  use Moose;
+  use MooseX::MethodAttributes;
+  use Types::Standard 'slurpy';
+  use MyApp::Types qw/Dict Tuple Int StrMatch HashRef ArrayRef Enum UserId  Heart/;
+
+  extends 'Catalyst::Controller';
+
+  sub user :Local Args(1)
+   Query(page=>Int,user=>Tuple[Enum['a','b'],Int]) {
+    my ($self, $c, $int) = @_;
+    $c->res->body("page ${\$c->req->query_parameters->{page}}, user ${\$c->req->query_parameters->{user}[1]}");
+  }
+
+  sub user_slurps :Local Args(1)
+   Query(page=>Int,user=>Tuple[Enum['a','b'],Int],...) {
+    my ($self, $c, $int) = @_;
+    $c->res->body("page ${\$c->req->query_parameters->{page}}, user ${\$c->req->query_parameters->{user}[1]}");
+  }
+
+  sub string_types :Local Query(q=>'Str',age=>'Int') { pop->res->body('string_type') }
+  sub as_ref :Local Query(Dict[age=>Int,sex=>Enum['f','m','o'], slurpy HashRef[Int]]) { pop->res->body('as_ref') }
+
+  sub utf8 :Local Query(utf8=>Heart) { pop->res->body("heart") }
+
+  sub chain :Chained(/) CaptureArgs(0) Query(age=>Int,...) { }
+
+    sub big :Chained(chain) PathPart('') Args(0) Query(size=>Int,...) { pop->res->body('big') }
+    sub small :Chained(chain) PathPart('') Args(0) Query(size=>UserId,...) { pop->res->body('small') }
+  
+  sub default :Default {
+    my ($self, $c, $int) = @_;
+    $c->res->body('default');
+  }
+
+  MyApp::Controller::Root->config(namespace=>'');
+
+  package MyApp;
+  use Catalyst;
+
+  MyApp->setup;
+}
+
+use Catalyst::Test 'MyApp';
+
+{
+  my $res = request '/user/1?page=10&user=a&user=100';
+  is $res->content, 'page 10, user 100';
+}
+
+{
+  my $res = request '/user/1?page=10&user=d&user=100';
+  is $res->content, 'default';
+}
+
+{
+  my $res = request '/user/1?page=string&user=a&user=100';
+  is $res->content, 'default';
+}
+
+{
+  my $res = request '/user/1?page=10&user=a&user=100&foo=bar';
+  is $res->content, 'default';
+}
+
+{
+  my $res = request '/user/1?page=10&user=a&user=100&user=bar';
+  is $res->content, 'default';
+}
+
+{
+  my $res = request '/user_slurps/1?page=10&user=a&user=100&foo=bar';
+  is $res->content, 'page 10, user 100';
+}
+
+{
+  my $res = request '/string_types?q=sssss&age=10';
+  is $res->content, 'string_type';
+}
+
+{
+  my $res = request '/string_types?w=sssss&age=10';
+  is $res->content, 'default';
+}
+
+{
+  my $res = request '/string_types?q=sssss&age=string';
+  is $res->content, 'default';
+}
+
+{
+  my $res = request '/as_ref?q=sssss&age=string';
+  is $res->content, 'default';
+}
+
+{
+  my $res = request '/as_ref?age=10&sex=o&foo=bar&baz=bot';
+  is $res->content, 'default';
+}
+
+{
+  my $res = request '/as_ref?age=10&sex=o&foo=122&baz=300';
+  is $res->content, 'as_ref';
+}
+
+{
+  my $res = request '/utf8?utf8=♥';
+  is $res->content, 'heart';
+}
+
+{
+  my $res = request '/chain?age=string&size=2';
+  is $res->content, 'default';
+}
+
+{
+  my $res = request '/chain?age=string&size=string';
+  is $res->content, 'default';
+}
+
+{
+  my $res = request '/chain?age=50&size=string';
+  is $res->content, 'default';
+}
+
+{
+  my $res = request '/chain?age=10&size=100';
+  is $res->content, 'big';
+}
+
+{
+  my $res = request '/chain?age=10&size=2';
+  is $res->content, 'small';
+}
+
+done_testing;
diff --git a/t/set_allowed_method.t b/t/set_allowed_method.t
new file mode 100644 (file)
index 0000000..7a9ce1b
--- /dev/null
@@ -0,0 +1,32 @@
+use warnings;
+use strict;
+use Test::More;
+
+# Test case for reported issue when an action consumes JSON but a
+# POST sends nothing we get a hard error
+
+{
+  package MyApp::Controller::Root;
+  $INC{'MyApp/Controller/Root.pm'} = __FILE__;
+
+  use base 'Catalyst::Controller';
+
+  sub root :Chained(/) CaptureArgs(0) { }
+
+    sub get :GET Chained(root) PathPart('') Args(0) { }
+    sub post :POST Chained(root) PathPart('') Args(0) { }
+    sub put :PUT Chained(root) PathPart('') Args(0) { }
+
+  package MyApp;
+  use Catalyst;
+  MyApp->setup;
+}
+
+use HTTP::Request::Common;
+use Catalyst::Test 'MyApp';
+
+{
+   ok my $res = request POST 'root/';
+}
+
+done_testing();
index a6d7594..24b23cc 100644 (file)
@@ -33,10 +33,11 @@ use Plack::Test;
 
   $SIG{__WARN__} = sub {
     my $error = shift;
-    Test::More::is($error, "You called ->params with an undefined value at t/undef-params.t line 20.\n");
+    Test::More::like($error, qr[You called ->params with an undefined value])
+      unless MyApp->debug;
   };
 
-  MyApp->setup, 'setup app';
+  MyApp->setup;
 }
 
 ok my $psgi = MyApp->psgi_app, 'build psgi app';
index 81ba9f7..4c7c0c6 100644 (file)
@@ -6,7 +6,7 @@ use lib "$Bin/lib";
 use Data::Dumper;
 
 BEGIN {
-    $ENV{TESTAPP_ENCODING} = 'UTF-8';
+  # $ENV{TESTAPP_ENCODING} = 'UTF-8'; # This is now default
     $ENV{TESTAPP_DEBUG} = 0;
     $ENV{CATALYST_DEBUG} = 0;
 }
@@ -27,6 +27,6 @@ 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/;
+#like $TestLogger::ELOGS[0], qr/Unicode::Encoding plugin/; #no longer a plugin
 
 done_testing;
index 37c5c6b..8335b1f 100644 (file)
@@ -9,7 +9,7 @@ use FindBin qw($Bin);
 use lib "$Bin/lib";
 
 BEGIN {
-if ( !eval { require Test::WWW::Mechanize::Catalyst } || ! Test::WWW::Mechanize::Catalyst->VERSION('0.51') ) {
+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';
 }
 }
index 87d9061..901ae84 100644 (file)
@@ -8,9 +8,9 @@ use FindBin qw($Bin);
 use lib "$Bin/lib";
 
 BEGIN {
-if ( !eval { require Test::WWW::Mechanize::Catalyst } || ! Test::WWW::Mechanize::Catalyst->VERSION('0.51') ) {
+  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
index 7b562f8..feed681 100644 (file)
@@ -18,7 +18,12 @@ 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");
+# JNAP - I am removing this test case because I think its not correct.  I think
+# we do not check the server encoding to determine if the parts of a request URL
+# both paths and query should be decoded.  I think its always safe to assume utf8
+# encoded urlencoded bits.  That is my reading of the spec.  Please correct me if
+# I am wrong
+#check_parameter(GET "/?myparam=$escape_str");
 check_parameter(POST '/',
     Content_Type => 'form-data',
     Content => [
@@ -33,7 +38,6 @@ sub check_parameter {
     my ( undef, $c ) = ctx_request(shift);
 
     my $myparam = $c->req->param('myparam');
-    ok !utf8::is_utf8($myparam);
     unless ( $c->request->method eq 'POST' ) {
         is $c->res->output => $encode_str;
         is $myparam => $encode_str;
index c3b7171..42a9a72 100644 (file)
@@ -1,6 +1,6 @@
 use strict;
 use warnings;
-use Test::More tests => 5 * 5;
+use Test::More;
 use utf8;
 
 # setup library path
@@ -17,42 +17,19 @@ 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 => '<h1>It works</h1>';
 
     my $foo = $c->req->param('foo');
-    ok utf8::is_utf8($foo);
-    is $foo => $decode_str;
+    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;
 }
 
@@ -61,7 +38,6 @@ sub check_argument {
     is $c->res->output => '<h1>It works</h1>';
 
     my $foo = $c->req->args->[0];
-    ok utf8::is_utf8($foo);
     is $foo => $decode_str;
 }
 
@@ -70,7 +46,6 @@ sub check_capture {
     is $c->res->output => '<h1>It works</h1>';
 
     my $foo = $c->req->captures->[0];
-    ok utf8::is_utf8($foo);
     is $foo => $decode_str;
 }
 
@@ -78,3 +53,27 @@ sub check_fallback {
   my ( $res, $c ) = ctx_request(shift);
   ok(!is_server_error($res->code)) or diag('Response code is: ' . $res->code);
 }
+
+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]);
+
+done_testing;
index 47994c5..510d9c1 100644 (file)
@@ -1,12 +1,17 @@
 use strict;
 use warnings;
 
-use Test::More tests => 18;
+use Test::More;
 use Class::Load 'is_class_loaded';
-
 use lib "t/lib";
 
-BEGIN { use_ok("Catalyst::Utils") };
+BEGIN {
+  if ($^O =~ m/^MSWin/) {
+    plan skip_all => 'Skipping this test on Windows until someone with Windows has time to fix it';
+  }
+
+  use_ok("Catalyst::Utils");
+}
 
 {
     package This::Module::Is::Not::In::Inc::But::Does::Exist;
@@ -70,3 +75,4 @@ Catalyst::Utils::ensure_class_loaded("NullPackage");
 is( $warnings, 1, 'Loading a package which defines no symbols warns');
 is( $@, undef, '$@ still undef' );
 
+done_testing;
diff --git a/t/utf8.txt b/t/utf8.txt
new file mode 100644 (file)
index 0000000..484d2cb
--- /dev/null
@@ -0,0 +1 @@
+<p>This is stream_body_fh action ♥</p>
diff --git a/t/utf_incoming.t b/t/utf_incoming.t
new file mode 100644 (file)
index 0000000..5f12ecb
--- /dev/null
@@ -0,0 +1,505 @@
+use utf8;
+use warnings;
+use strict;
+use Test::More;
+use HTTP::Request::Common;
+use HTTP::Message::PSGI ();
+use Encode 2.21 'decode_utf8', 'encode_utf8', 'encode';
+use File::Spec;
+use JSON::MaybeXS;
+use Scalar::Util ();
+
+# Test cases for incoming utf8 
+
+{
+  package MyApp::Controller::Root;
+  $INC{'MyApp/Controller/Root.pm'} = __FILE__;
+
+  use base 'Catalyst::Controller';
+
+  sub heart :Path('♥') {
+    my ($self, $c) = @_;
+    $c->response->content_type('text/html');
+    $c->response->body("<p>This is path-heart action ♥</p>");
+    # We let the content length middleware find the length...
+  }
+
+  sub hat :Path('^') {
+    my ($self, $c) = @_;
+    $c->response->content_type('text/html');
+    $c->response->body("<p>This is path-hat action ^</p>");
+  }
+
+  sub uri_for :Path('uri_for') {
+    my ($self, $c) = @_;
+    $c->response->content_type('text/html');
+    $c->response->body("${\$c->uri_for($c->controller('Root')->action_for('argend'), ['♥'], '♥#X♥X', {'♥'=>'♥♥'})}");
+  }
+
+  sub heart_with_arg :Path('a♥') Args(1)  {
+    my ($self, $c, $arg) = @_;
+    $c->response->content_type('text/html');
+    $c->response->body("<p>This is path-heart-arg action $arg</p>");
+    Test::More::is $c->req->args->[0], '♥';
+  }
+
+  sub base :Chained('/') CaptureArgs(0) { }
+    sub link :Chained('base') PathPart('♥') Args(0) {
+      my ($self, $c) = @_;
+      $c->response->content_type('text/html');
+      $c->response->body("<p>This is base-link action ♥</p>");
+    }
+    sub arg :Chained('base') PathPart('♥') Args(1) {
+      my ($self, $c, $arg) = @_;
+      $c->response->content_type('text/html');
+      $c->response->body("<p>This is base-link action ♥ $arg</p>");
+    }
+    sub capture :Chained('base') PathPart('♥') CaptureArgs(1) {
+      my ($self, $c, $arg) = @_;
+      $c->stash(capture=>$arg);
+    }
+      sub argend :Chained('capture') PathPart('♥') Args(1) {
+        my ($self, $c, $arg) = @_;
+        $c->response->content_type('text/html');
+
+        Test::More::is $c->req->args->[0], '♥';
+        Test::More::is $c->req->captures->[0], '♥';
+        Test::More::is $arg, '♥';
+        Test::More::is length($arg), 1, "got length of one";
+
+        $c->response->body("<p>This is base-link action ♥ ${\$c->req->args->[0]}</p>");
+
+        # Test to make sure redirect can now take an object (sorry don't have a better place for it
+        # but wanted test coverage.
+        my $location = $c->res->redirect( $c->uri_for($c->controller('Root')->action_for('uri_for')) );
+        Test::More::ok !ref $location; 
+      }
+
+  sub stream_write :Local {
+    my ($self, $c) = @_;
+    $c->response->content_type('text/html');
+    $c->response->write("<p>This is stream_write action ♥</p>");
+  }
+
+  sub stream_write_fh :Local {
+    my ($self, $c) = @_;
+    $c->response->content_type('text/html');
+
+    my $writer = $c->res->write_fh;
+    $writer->write_encoded('<p>This is stream_write_fh action ♥</p>');
+    $writer->close;
+  }
+
+  # Stream a file with utf8 chars directly, you don't need to decode
+  sub stream_body_fh :Local {
+    my ($self, $c) = @_;
+    my $path = File::Spec->catfile('t', 'utf8.txt');
+    open(my $fh, '<', $path) || die "trouble: $!";
+    $c->response->content_type('text/html');
+    $c->response->body($fh);
+  }
+
+  # If you pull the file contents into a var, NOW you need to specify the
+  # IO encoding on the FH.  Ultimately Plack at the end wants bytes...
+  sub stream_body_fh2 :Local {
+    my ($self, $c) = @_;
+    my $path = File::Spec->catfile('t', 'utf8.txt');
+    open(my $fh, '<:encoding(UTF-8)', $path) || die "trouble: $!";
+    my $contents = do { local $/; <$fh> };
+
+    $c->response->content_type('text/html');
+    $c->response->body($contents);
+  }
+
+  sub write_then_body :Local {
+    my ($self, $c) = @_;
+
+    $c->res->content_type('text/html');
+    $c->res->write("<p>This is early_write action ♥</p>");
+    $c->res->body("<p>This is body_write action ♥</p>");
+  }
+
+  sub file_upload :POST  Consumes(Multipart) Local {
+    my ($self, $c) = @_;
+
+    Test::More::is $c->req->body_parameters->{'♥'}, '♥♥';
+    Test::More::ok my $upload = $c->req->uploads->{file};
+    Test::More::is $upload->charset, 'UTF-8';
+
+    my $text = $upload->slurp;
+    Test::More::is Encode::decode_utf8($text), "<p>This is stream_body_fh action ♥</p>\n";
+
+    my $decoded_text = $upload->decoded_slurp;
+    Test::More::is $decoded_text, "<p>This is stream_body_fh action ♥</p>\n";
+
+    Test::More::is $upload->filename, '♥ttachment.txt';
+    Test::More::is $upload->raw_basename, '♥ttachment.txt';
+
+    $c->response->content_type('text/html');
+    $c->response->body($decoded_text);
+  }
+
+  sub json :POST Consumes(JSON) Local {
+    my ($self, $c) = @_;
+    my $post = $c->req->body_data;
+
+    Test::More::is $post->{'♥'}, '♥♥';
+    Test::More::is length($post->{'♥'}), 2;
+    $c->response->content_type('application/json');
+
+    # Encode JSON also encodes to a UTF-8 encoded, binary string. This is why we don't
+    # have application/json as one of the things we match, otherwise we get double
+    # encoding.  
+    $c->response->body(JSON::MaybeXS::encode_json($post));
+  }
+
+  ## If someone clears encoding, they can do as they wish
+  sub manual_1 :Local {
+    my ($self, $c) = @_;
+    $c->clear_encoding;
+    $c->res->content_type('text/plain');
+    $c->res->content_type_charset('UTF-8');
+    $c->response->body( Encode::encode_utf8("manual_1 ♥"));
+  }
+
+  ## If you do like gzip, well handle that yourself!  Basically if you do some sort
+  ## of content encoding like gzip, you must do on top of the encoding.  We will fix
+  ## the encoding plugins (Catalyst::Plugin::Compress) to do this properly for you.
+  #
+  sub gzipped :Local {
+    require Compress::Zlib;
+    my ($self, $c) = @_;
+    $c->res->content_type('text/plain');
+    $c->res->content_type_charset('UTF-8');
+    $c->res->content_encoding('gzip');
+    $c->response->body(Compress::Zlib::memGzip(Encode::encode_utf8("manual_1 ♥")));
+  }
+
+  sub override_encoding :Local {
+    my ($self, $c) = @_;
+    $c->res->content_type('text/plain');
+    $c->encoding(Encode::find_encoding('UTF-8'));
+    $c->encoding(Encode::find_encoding('Shift_JIS'));
+    $c->response->body("テスト");
+  }
+
+  sub stream_write_error :Local {
+    my ($self, $c) = @_;
+    $c->response->content_type('text/html');
+    $c->response->write("<p>This is stream_write action ♥</p>");
+    $c->encoding(Encode::find_encoding('Shift_JIS'));
+    $c->response->write("<p>This is stream_write action ♥</p>");
+  }
+
+  sub from_external_psgi :Local {
+    my ($self, $c) = @_;
+    my $env = HTTP::Message::PSGI::req_to_psgi( HTTP::Request::Common::GET '/root/♥');
+    $c->res->from_psgi_response( ref($c)->to_app->($env));
+  }
+
+  sub echo_arg :Local {
+    my ($self, $c) = @_;
+    $c->response->content_type('text/plain');
+    $c->response->body($c->req->body_parameters->{arg});
+  }
+
+  package MyApp;
+  use Catalyst;
+
+  Test::More::ok(MyApp->setup, 'setup app');
+}
+
+ok my $psgi = MyApp->psgi_app, 'build psgi app';
+
+use Catalyst::Test 'MyApp';
+
+{
+  my $res = request "/root/♥";
+
+  is $res->code, 200, 'OK';
+  is decode_utf8($res->content), '<p>This is path-heart action ♥</p>', 'correct body';
+  is $res->content_length, 36, 'correct length';
+  is $res->content_charset, 'UTF-8';
+}
+
+{
+  my $res = request "/root/a♥/♥";
+
+  is $res->code, 200, 'OK';
+  is decode_utf8($res->content), '<p>This is path-heart-arg action ♥</p>', 'correct body';
+  is $res->content_length, 40, 'correct length';
+  is $res->content_charset, 'UTF-8';
+}
+
+{
+  my $res = request "/root/^";
+
+  is $res->code, 200, 'OK';
+  is decode_utf8($res->content), '<p>This is path-hat action ^</p>', 'correct body';
+  is $res->content_length, 32, 'correct length';
+  is $res->content_charset, 'UTF-8';
+}
+
+{
+  my $res = request "/base/♥";
+
+  is $res->code, 200, 'OK';
+  is decode_utf8($res->content), '<p>This is base-link action ♥</p>', 'correct body';
+  is $res->content_length, 35, 'correct length';
+  is $res->content_charset, 'UTF-8';
+}
+
+{
+  my ($res, $c) = ctx_request POST "/base/♥?♥=♥&♥=♥♥", [a=>1, b=>'', '♥'=>'♥', '♥'=>'♥♥'];
+
+  is $res->code, 200, 'OK';
+  is decode_utf8($res->content), '<p>This is base-link action ♥</p>', 'correct body';
+  is $res->content_length, 35, 'correct length';
+  is $c->req->parameters->{'♥'}[0], '♥';
+  is $c->req->query_parameters->{'♥'}[0], '♥';
+  is $c->req->body_parameters->{'♥'}[0], '♥';
+  is $c->req->parameters->{'♥'}[0], '♥';
+  is $c->req->parameters->{a}, 1;
+  is $c->req->body_parameters->{a}, 1;
+  is $res->content_charset, 'UTF-8';
+}
+
+{
+  my ($res, $c) = ctx_request GET "/base/♥?♥♥♥";
+
+  is $res->code, 200, 'OK';
+  is decode_utf8($res->content), '<p>This is base-link action ♥</p>', 'correct body';
+  is $res->content_length, 35, 'correct length';
+  is $c->req->query_keywords, '♥♥♥';
+  is $res->content_charset, 'UTF-8';
+}
+
+{
+  my $res = request "/base/♥/♥";
+
+  is $res->code, 200, 'OK';
+  is decode_utf8($res->content), '<p>This is base-link action ♥ ♥</p>', 'correct body';
+  is $res->content_length, 39, 'correct length';
+  is $res->content_charset, 'UTF-8';
+}
+
+{
+  my $res = request "/base/♥/♥/♥/♥";
+
+  is decode_utf8($res->content), '<p>This is base-link action ♥ ♥</p>', 'correct body';
+  is $res->content_length, 39, 'correct length';
+  is $res->content_charset, 'UTF-8';
+}
+
+{
+  my ($res, $c) = ctx_request POST "/base/♥/♥/♥/♥?♥=♥♥", [a=>1, b=>'2', '♥'=>'♥♥'];
+
+  ## Make sure that the urls we generate work the same
+  my $uri_for1 = $c->uri_for($c->controller('Root')->action_for('argend'), ['♥'], '♥', {'♥'=>'♥♥'});
+  my $uri_for2 = $c->uri_for($c->controller('Root')->action_for('argend'), ['♥', '♥'], {'♥'=>'♥♥'});
+  my $uri = $c->req->uri;
+
+  is "$uri_for1", "$uri_for2";
+  is "$uri", "$uri_for1";
+
+  {
+    my ($res, $c) = ctx_request POST "$uri_for1", [a=>1, b=>'2', '♥'=>'♥♥'];
+    is $c->req->query_parameters->{'♥'}, '♥♥';
+    is $c->req->body_parameters->{'♥'}, '♥♥';
+    is $c->req->parameters->{'♥'}[0], '♥♥'; #combined with query and body
+    is $c->req->args->[0], '♥';
+    is length($c->req->parameters->{'♥'}[0]), 2;
+    is length($c->req->query_parameters->{'♥'}), 2;
+    is length($c->req->body_parameters->{'♥'}), 2;
+    is length($c->req->args->[0]), 1;
+    is $res->content_charset, 'UTF-8';
+  }
+}
+
+{
+  my ($res, $c) = ctx_request "/root/uri_for";
+  my $url = $c->uri_for($c->controller('Root')->action_for('argend'), ['♥'], '♥#X♥X', {'♥'=>'♥♥'});
+
+  is $res->code, 200, 'OK';
+  is decode_utf8($res->content), "$url", 'correct body'; #should do nothing
+  is $res->content, "$url", 'correct body';
+  is $res->content_length, 104, 'correct length';
+  is $res->content_charset, 'UTF-8';
+
+  {
+    my $url = $c->uri_for($c->controller->action_for('heart_with_arg'), '♥');
+    is "$url", 'http://localhost/root/a%E2%99%A5/%E2%99%A5', "correct $url";
+  }
+
+  {
+    my $url = $c->uri_for($c->controller->action_for('heart_with_arg'), ['♥']);
+    is "$url", 'http://localhost/root/a%E2%99%A5/%E2%99%A5', "correct $url";
+  }
+}
+
+{
+  my $res = request "/root/stream_write";
+
+  is $res->code, 200, 'OK GET /root/stream_write';
+  is decode_utf8($res->content), '<p>This is stream_write action ♥</p>', 'correct body';
+  is $res->content_charset, 'UTF-8';
+}
+
+{
+  my $res = request "/root/stream_body_fh";
+
+  is $res->code, 200, 'OK';
+  is decode_utf8($res->content), "<p>This is stream_body_fh action ♥</p>\n", 'correct body';
+  is $res->content_charset, 'UTF-8';
+  # Not sure why there is a trailing newline above... its not in catalyst code I can see. Not sure
+  # if is a problem or just an artifact of the why the test stuff works - JNAP
+}
+
+{
+  my $res = request "/root/stream_write_fh";
+
+  is $res->code, 200, 'OK';
+  is decode_utf8($res->content), '<p>This is stream_write_fh action ♥</p>', 'correct body';
+  #is $res->content_length, 41, 'correct length';
+  is $res->content_charset, 'UTF-8';
+}
+
+{
+  my $res = request "/root/stream_body_fh2";
+
+  is $res->code, 200, 'OK';
+  is decode_utf8($res->content), "<p>This is stream_body_fh action ♥</p>\n", 'correct body';
+  is $res->content_length, 41, 'correct length';
+  is $res->content_charset, 'UTF-8';
+}
+
+{
+  my $res = request "/root/write_then_body";
+
+  is $res->code, 200, 'OK';
+  is decode_utf8($res->content), "<p>This is early_write action ♥</p><p>This is body_write action ♥</p>";
+  is $res->content_charset, 'UTF-8';
+}
+
+{
+  ok my $path = File::Spec->catfile('t', 'utf8.txt');
+  ok my $req = POST '/root/file_upload',
+    Content_Type => 'form-data',
+    Content =>  [encode_utf8('♥')=>encode_utf8('♥♥'), file=>["$path", encode_utf8('♥ttachment.txt'), 'Content-Type' =>'text/html; charset=UTF-8', ]];
+
+  ok my $res = request $req;
+  is decode_utf8($res->content), "<p>This is stream_body_fh action ♥</p>\n";
+}
+
+{
+  ok my $req = POST '/root/json',
+     Content_Type => 'application/json',
+     Content => encode_json +{'♥'=>'♥♥'}; # Note: JSON does the UTF* encoding for us
+
+  ok my $res = request $req;
+
+  ## decode_json expect the binary utf8 string and does the decoded bit for us.
+  is_deeply decode_json(($res->content)), +{'♥'=>'♥♥'}, 'JSON was decoded correctly';
+}
+
+{
+  ok my $res = request "/root/override_encoding";
+  ok my $enc = Encode::find_encoding('SHIFT_JIS');
+
+  is $res->code, 200, 'OK';
+  is $enc->decode($res->content), "テスト", 'correct body';
+  is $res->content_length, 6, 'correct length'; # Bytes over the wire
+  is length($enc->decode($res->content)), 3;
+  is $res->content_charset, 'SHIFT_JIS', 'content charset is SHIFT_JIS as expected';
+}
+
+{
+  my $res = request "/root/manual_1";
+
+  is $res->code, 200, 'OK';
+  is decode_utf8($res->content), "manual_1 ♥", 'correct body';
+  is $res->content_length, 12, 'correct length';
+  is $res->content_charset, 'UTF-8';
+}
+
+SKIP: {
+  eval { require Compress::Zlib; 1} || do {
+    skip "Compress::Zlib needed to test gzip encoding", 5 };
+
+  my $res = request "/root/gzipped";
+  ok my $raw_content = $res->content;
+  ok my $content = Compress::Zlib::memGunzip($raw_content), 'no gunzip error';
+
+  is $res->code, 200, 'OK';
+  is decode_utf8($content), "manual_1 ♥", 'correct body';
+  is $res->content_charset, 'UTF-8', 'zlib charset is set correctly';
+}
+
+{
+  my $res = request "/root/stream_write_error";
+
+  is $res->code, 200, 'OK';
+  like decode_utf8($res->content), qr[<p>This is stream_write action ♥</p><!DOCTYPE html], 'correct body';
+}
+
+{
+  my $res = request "/root/from_external_psgi";
+
+  is $res->code, 200, 'OK';
+  is decode_utf8($res->content), '<p>This is path-heart action ♥</p>', 'correct body';
+  is $res->content_length, 36, 'correct length';
+  is $res->content_charset, 'UTF-8', 'external PSGI app has expected charset';
+}
+
+{
+  my $utf8 = 'test ♥';
+  my $shiftjs = 'test テスト';
+
+  ok my $req = POST '/root/echo_arg',
+    Content_Type => 'form-data',
+      Content =>  [
+        arg0 => 'helloworld',
+        Encode::encode('UTF-8','♥') => Encode::encode('UTF-8','♥♥'),  # Long form POST simple does not auto encode...
+        Encode::encode('UTF-8','♥♥♥') => [
+          undef, '',
+          'Content-Type' =>'text/plain; charset=SHIFT_JIS',
+          'Content' => Encode::encode('SHIFT_JIS', $shiftjs)],
+        arg1 => [
+          undef, '',
+          'Content-Type' =>'text/plain; charset=UTF-8',
+          'Content' => Encode::encode('UTF-8', $utf8)],
+        arg2 => [
+          undef, '',
+          'Content-Type' =>'text/plain; charset=SHIFT_JIS',
+          'Content' => Encode::encode('SHIFT_JIS', $shiftjs)],
+        arg2 => [
+          undef, '',
+          'Content-Type' =>'text/plain; charset=SHIFT_JIS',
+          'Content' => Encode::encode('SHIFT_JIS', $shiftjs)],
+      ];
+
+  my ($res, $c) = ctx_request $req;
+
+  is $c->req->body_parameters->{'arg0'}, 'helloworld', 'got helloworld value';
+  is $c->req->body_parameters->{'♥'}, '♥♥';
+  is $c->req->body_parameters->{'arg1'}, $utf8, 'decoded utf8 param';
+  is $c->req->body_parameters->{'arg2'}[0], $shiftjs, 'decoded shiftjs param';
+  is $c->req->body_parameters->{'arg2'}[1], $shiftjs, 'decoded shiftjs param';
+  is $c->req->body_parameters->{'♥♥♥'}, $shiftjs, 'decoded shiftjs param';
+
+}
+
+{
+  my $shiftjs = 'test テスト';
+  my $encoded = Encode::encode('UTF-8', $shiftjs);
+
+  ok my $req = GET "/root/echo_arg?a=$encoded";
+  my ($res, $c) = ctx_request $req;
+
+  is $c->req->query_parameters->{'a'}, $shiftjs, 'got expected value';
+}
+
+## should we use binmode on filehandles to force the encoding...?
+## Not sure what else to do with multipart here, if docs are enough...
+
+done_testing;