actionpack 3.2.19 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +850 -401
- data/MIT-LICENSE +1 -1
- data/README.rdoc +5 -288
- data/lib/abstract_controller/asset_paths.rb +2 -2
- data/lib/abstract_controller/base.rb +39 -37
- data/lib/abstract_controller/callbacks.rb +101 -82
- data/lib/abstract_controller/collector.rb +7 -3
- data/lib/abstract_controller/helpers.rb +25 -13
- data/lib/abstract_controller/layouts.rb +74 -74
- data/lib/abstract_controller/logger.rb +1 -2
- data/lib/abstract_controller/rendering.rb +30 -13
- data/lib/abstract_controller/translation.rb +16 -1
- data/lib/abstract_controller/url_for.rb +6 -6
- data/lib/abstract_controller/view_paths.rb +1 -1
- data/lib/abstract_controller.rb +1 -8
- data/lib/action_controller/base.rb +46 -22
- data/lib/action_controller/caching/fragments.rb +23 -53
- data/lib/action_controller/caching.rb +46 -33
- data/lib/action_controller/deprecated/integration_test.rb +3 -0
- data/lib/action_controller/deprecated.rb +5 -1
- data/lib/action_controller/log_subscriber.rb +16 -8
- data/lib/action_controller/metal/conditional_get.rb +76 -32
- data/lib/action_controller/metal/data_streaming.rb +20 -26
- data/lib/action_controller/metal/exceptions.rb +19 -6
- data/lib/action_controller/metal/flash.rb +24 -9
- data/lib/action_controller/metal/force_ssl.rb +70 -12
- data/lib/action_controller/metal/head.rb +25 -4
- data/lib/action_controller/metal/helpers.rb +5 -9
- data/lib/action_controller/metal/hide_actions.rb +0 -1
- data/lib/action_controller/metal/http_authentication.rb +107 -83
- data/lib/action_controller/metal/implicit_render.rb +1 -1
- data/lib/action_controller/metal/instrumentation.rb +2 -1
- data/lib/action_controller/metal/live.rb +175 -0
- data/lib/action_controller/metal/mime_responds.rb +161 -47
- data/lib/action_controller/metal/params_wrapper.rb +112 -74
- data/lib/action_controller/metal/rack_delegation.rb +9 -3
- data/lib/action_controller/metal/redirecting.rb +15 -20
- data/lib/action_controller/metal/renderers.rb +11 -9
- data/lib/action_controller/metal/rendering.rb +9 -1
- data/lib/action_controller/metal/request_forgery_protection.rb +112 -19
- data/lib/action_controller/metal/responder.rb +20 -19
- data/lib/action_controller/metal/streaming.rb +12 -18
- data/lib/action_controller/metal/strong_parameters.rb +520 -0
- data/lib/action_controller/metal/testing.rb +13 -18
- data/lib/action_controller/metal/url_for.rb +28 -25
- data/lib/action_controller/metal.rb +17 -32
- data/lib/action_controller/model_naming.rb +12 -0
- data/lib/action_controller/railtie.rb +33 -17
- data/lib/action_controller/railties/helpers.rb +22 -0
- data/lib/action_controller/record_identifier.rb +18 -72
- data/lib/action_controller/test_case.rb +251 -131
- data/lib/action_controller/vendor/html-scanner.rb +4 -19
- data/lib/action_controller.rb +15 -6
- data/lib/action_dispatch/http/cache.rb +63 -11
- data/lib/action_dispatch/http/filter_parameters.rb +18 -8
- data/lib/action_dispatch/http/filter_redirect.rb +37 -0
- data/lib/action_dispatch/http/headers.rb +49 -17
- data/lib/action_dispatch/http/mime_negotiation.rb +24 -1
- data/lib/action_dispatch/http/mime_type.rb +154 -100
- data/lib/action_dispatch/http/mime_types.rb +1 -1
- data/lib/action_dispatch/http/parameter_filter.rb +44 -46
- data/lib/action_dispatch/http/parameters.rb +28 -28
- data/lib/action_dispatch/http/rack_cache.rb +2 -3
- data/lib/action_dispatch/http/request.rb +64 -18
- data/lib/action_dispatch/http/response.rb +130 -35
- data/lib/action_dispatch/http/upload.rb +63 -20
- data/lib/action_dispatch/http/url.rb +98 -35
- data/lib/action_dispatch/journey/backwards.rb +5 -0
- data/lib/action_dispatch/journey/formatter.rb +146 -0
- data/lib/action_dispatch/journey/gtg/builder.rb +162 -0
- data/lib/action_dispatch/journey/gtg/simulator.rb +44 -0
- data/lib/action_dispatch/journey/gtg/transition_table.rb +156 -0
- data/lib/action_dispatch/journey/nfa/builder.rb +76 -0
- data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
- data/lib/action_dispatch/journey/nfa/simulator.rb +47 -0
- data/lib/action_dispatch/journey/nfa/transition_table.rb +163 -0
- data/lib/action_dispatch/journey/nodes/node.rb +124 -0
- data/lib/action_dispatch/journey/parser.rb +206 -0
- data/lib/action_dispatch/journey/parser.y +47 -0
- data/lib/action_dispatch/journey/parser_extras.rb +23 -0
- data/lib/action_dispatch/journey/path/pattern.rb +196 -0
- data/lib/action_dispatch/journey/route.rb +124 -0
- data/lib/action_dispatch/journey/router/strexp.rb +24 -0
- data/lib/action_dispatch/journey/router/utils.rb +54 -0
- data/lib/action_dispatch/journey/router.rb +166 -0
- data/lib/action_dispatch/journey/routes.rb +75 -0
- data/lib/action_dispatch/journey/scanner.rb +61 -0
- data/lib/action_dispatch/journey/visitors.rb +197 -0
- data/lib/action_dispatch/journey/visualizer/fsm.css +34 -0
- data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
- data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
- data/lib/action_dispatch/journey.rb +5 -0
- data/lib/action_dispatch/middleware/callbacks.rb +9 -4
- data/lib/action_dispatch/middleware/cookies.rb +259 -114
- data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -17
- data/lib/action_dispatch/middleware/exception_wrapper.rb +29 -3
- data/lib/action_dispatch/middleware/flash.rb +58 -58
- data/lib/action_dispatch/middleware/params_parser.rb +14 -29
- data/lib/action_dispatch/middleware/public_exceptions.rb +30 -14
- data/lib/action_dispatch/middleware/reloader.rb +6 -6
- data/lib/action_dispatch/middleware/remote_ip.rb +145 -39
- data/lib/action_dispatch/middleware/request_id.rb +2 -6
- data/lib/action_dispatch/middleware/session/abstract_store.rb +22 -20
- data/lib/action_dispatch/middleware/session/cookie_store.rb +82 -28
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -3
- data/lib/action_dispatch/middleware/show_exceptions.rb +12 -45
- data/lib/action_dispatch/middleware/ssl.rb +70 -0
- data/lib/action_dispatch/middleware/stack.rb +6 -1
- data/lib/action_dispatch/middleware/static.rb +2 -1
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +14 -11
- data/lib/action_dispatch/middleware/templates/rescues/_source.erb +25 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +7 -9
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +15 -9
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +127 -5
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +7 -2
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +30 -15
- data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +39 -13
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +6 -2
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +144 -0
- data/lib/action_dispatch/railtie.rb +16 -6
- data/lib/action_dispatch/request/session.rb +181 -0
- data/lib/action_dispatch/routing/inspector.rb +240 -0
- data/lib/action_dispatch/routing/mapper.rb +540 -291
- data/lib/action_dispatch/routing/polymorphic_routes.rb +16 -20
- data/lib/action_dispatch/routing/redirection.rb +46 -29
- data/lib/action_dispatch/routing/route_set.rb +207 -164
- data/lib/action_dispatch/routing/routes_proxy.rb +2 -0
- data/lib/action_dispatch/routing/url_for.rb +48 -33
- data/lib/action_dispatch/routing.rb +48 -83
- data/lib/action_dispatch/testing/assertions/dom.rb +3 -13
- data/lib/action_dispatch/testing/assertions/response.rb +32 -40
- data/lib/action_dispatch/testing/assertions/routing.rb +42 -41
- data/lib/action_dispatch/testing/assertions/selector.rb +17 -22
- data/lib/action_dispatch/testing/assertions/tag.rb +20 -23
- data/lib/action_dispatch/testing/integration.rb +65 -51
- data/lib/action_dispatch/testing/test_process.rb +9 -6
- data/lib/action_dispatch/testing/test_request.rb +7 -3
- data/lib/action_dispatch.rb +21 -15
- data/lib/action_pack/version.rb +7 -6
- data/lib/action_pack.rb +1 -1
- data/lib/action_view/base.rb +15 -34
- data/lib/action_view/buffers.rb +7 -1
- data/lib/action_view/context.rb +4 -4
- data/lib/action_view/dependency_tracker.rb +93 -0
- data/lib/action_view/digestor.rb +85 -0
- data/lib/action_view/flows.rb +1 -4
- data/lib/action_view/helpers/active_model_helper.rb +3 -4
- data/lib/action_view/helpers/asset_tag_helper.rb +215 -352
- data/lib/action_view/helpers/asset_url_helper.rb +355 -0
- data/lib/action_view/helpers/atom_feed_helper.rb +13 -10
- data/lib/action_view/helpers/cache_helper.rb +150 -18
- data/lib/action_view/helpers/capture_helper.rb +44 -31
- data/lib/action_view/helpers/csrf_helper.rb +0 -2
- data/lib/action_view/helpers/date_helper.rb +269 -248
- data/lib/action_view/helpers/debug_helper.rb +10 -11
- data/lib/action_view/helpers/form_helper.rb +931 -537
- data/lib/action_view/helpers/form_options_helper.rb +341 -166
- data/lib/action_view/helpers/form_tag_helper.rb +190 -90
- data/lib/action_view/helpers/javascript_helper.rb +23 -16
- data/lib/action_view/helpers/number_helper.rb +148 -329
- data/lib/action_view/helpers/output_safety_helper.rb +3 -3
- data/lib/action_view/helpers/record_tag_helper.rb +17 -22
- data/lib/action_view/helpers/rendering_helper.rb +2 -2
- data/lib/action_view/helpers/sanitize_helper.rb +3 -6
- data/lib/action_view/helpers/tag_helper.rb +46 -33
- data/lib/action_view/helpers/tags/base.rb +147 -0
- data/lib/action_view/helpers/tags/check_box.rb +64 -0
- data/lib/action_view/helpers/tags/checkable.rb +16 -0
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +43 -0
- data/lib/action_view/helpers/tags/collection_helpers.rb +83 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +36 -0
- data/lib/action_view/helpers/tags/collection_select.rb +28 -0
- data/lib/action_view/helpers/tags/color_field.rb +25 -0
- data/lib/action_view/helpers/tags/date_field.rb +13 -0
- data/lib/action_view/helpers/tags/date_select.rb +72 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +22 -0
- data/lib/action_view/helpers/tags/datetime_local_field.rb +19 -0
- data/lib/action_view/helpers/tags/datetime_select.rb +8 -0
- data/lib/action_view/helpers/tags/email_field.rb +8 -0
- data/lib/action_view/helpers/tags/file_field.rb +8 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +29 -0
- data/lib/action_view/helpers/tags/hidden_field.rb +8 -0
- data/lib/action_view/helpers/tags/label.rb +65 -0
- data/lib/action_view/helpers/tags/month_field.rb +13 -0
- data/lib/action_view/helpers/tags/number_field.rb +18 -0
- data/lib/action_view/helpers/tags/password_field.rb +12 -0
- data/lib/action_view/helpers/tags/radio_button.rb +31 -0
- data/lib/action_view/helpers/tags/range_field.rb +8 -0
- data/lib/action_view/helpers/tags/search_field.rb +24 -0
- data/lib/action_view/helpers/tags/select.rb +40 -0
- data/lib/action_view/helpers/tags/tel_field.rb +8 -0
- data/lib/action_view/helpers/tags/text_area.rb +18 -0
- data/lib/action_view/helpers/tags/text_field.rb +29 -0
- data/lib/action_view/helpers/tags/time_field.rb +13 -0
- data/lib/action_view/helpers/tags/time_select.rb +8 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +20 -0
- data/lib/action_view/helpers/tags/url_field.rb +8 -0
- data/lib/action_view/helpers/tags/week_field.rb +13 -0
- data/lib/action_view/helpers/tags.rb +39 -0
- data/lib/action_view/helpers/text_helper.rb +130 -114
- data/lib/action_view/helpers/translation_helper.rb +32 -16
- data/lib/action_view/helpers/url_helper.rb +211 -270
- data/lib/action_view/helpers.rb +2 -4
- data/lib/action_view/locale/en.yml +1 -105
- data/lib/action_view/log_subscriber.rb +6 -4
- data/lib/action_view/lookup_context.rb +15 -28
- data/lib/action_view/model_naming.rb +12 -0
- data/lib/action_view/path_set.rb +8 -20
- data/lib/action_view/railtie.rb +6 -22
- data/lib/action_view/record_identifier.rb +84 -0
- data/lib/action_view/renderer/abstract_renderer.rb +25 -19
- data/lib/action_view/renderer/partial_renderer.rb +158 -81
- data/lib/action_view/renderer/renderer.rb +8 -12
- data/lib/action_view/renderer/streaming_template_renderer.rb +2 -5
- data/lib/action_view/renderer/template_renderer.rb +12 -10
- data/lib/action_view/routing_url_for.rb +107 -0
- data/lib/action_view/template/error.rb +22 -12
- data/lib/action_view/template/handlers/builder.rb +1 -1
- data/lib/action_view/template/handlers/erb.rb +40 -19
- data/lib/action_view/template/handlers/raw.rb +11 -0
- data/lib/action_view/template/handlers.rb +12 -9
- data/lib/action_view/template/resolver.rb +107 -53
- data/lib/action_view/template/text.rb +12 -8
- data/lib/action_view/template/types.rb +57 -0
- data/lib/action_view/template.rb +25 -23
- data/lib/action_view/test_case.rb +67 -42
- data/lib/{action_controller → action_view}/vendor/html-scanner/html/document.rb +0 -0
- data/lib/{action_controller → action_view}/vendor/html-scanner/html/node.rb +12 -12
- data/lib/{action_controller → action_view}/vendor/html-scanner/html/sanitizer.rb +13 -2
- data/lib/{action_controller → action_view}/vendor/html-scanner/html/selector.rb +9 -9
- data/lib/{action_controller → action_view}/vendor/html-scanner/html/tokenizer.rb +1 -1
- data/lib/{action_controller → action_view}/vendor/html-scanner/html/version.rb +0 -0
- data/lib/action_view/vendor/html-scanner.rb +20 -0
- data/lib/action_view.rb +17 -8
- metadata +184 -214
- data/lib/action_controller/caching/actions.rb +0 -185
- data/lib/action_controller/caching/pages.rb +0 -187
- data/lib/action_controller/caching/sweeping.rb +0 -97
- data/lib/action_controller/deprecated/performance_test.rb +0 -1
- data/lib/action_controller/metal/compatibility.rb +0 -65
- data/lib/action_controller/metal/session_management.rb +0 -14
- data/lib/action_controller/railties/paths.rb +0 -25
- data/lib/action_dispatch/middleware/best_standards_support.rb +0 -30
- data/lib/action_dispatch/middleware/body_proxy.rb +0 -30
- data/lib/action_dispatch/middleware/head.rb +0 -18
- data/lib/action_dispatch/middleware/rescue.rb +0 -26
- data/lib/action_dispatch/testing/performance_test.rb +0 -10
- data/lib/action_view/asset_paths.rb +0 -142
- data/lib/action_view/helpers/asset_paths.rb +0 -7
- data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +0 -146
- data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +0 -93
- data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +0 -193
- data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +0 -148
- data/lib/sprockets/assets.rake +0 -99
- data/lib/sprockets/bootstrap.rb +0 -37
- data/lib/sprockets/compressors.rb +0 -83
- data/lib/sprockets/helpers/isolated_helper.rb +0 -13
- data/lib/sprockets/helpers/rails_helper.rb +0 -182
- data/lib/sprockets/helpers.rb +0 -6
- data/lib/sprockets/railtie.rb +0 -62
- data/lib/sprockets/static_compiler.rb +0 -56
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
require 'active_support/core_ext/object/blank'
|
|
2
1
|
require 'active_support/core_ext/hash/keys'
|
|
3
2
|
require 'active_support/core_ext/module/attribute_accessors'
|
|
3
|
+
require 'active_support/core_ext/object/blank'
|
|
4
|
+
require 'active_support/key_generator'
|
|
5
|
+
require 'active_support/message_verifier'
|
|
4
6
|
|
|
5
7
|
module ActionDispatch
|
|
6
|
-
class Request
|
|
8
|
+
class Request < Rack::Request
|
|
7
9
|
def cookie_jar
|
|
8
10
|
env['action_dispatch.cookies'] ||= Cookies::CookieJar.build(self)
|
|
9
11
|
end
|
|
@@ -15,7 +17,7 @@ module ActionDispatch
|
|
|
15
17
|
# being written will be sent out with the response. Reading a cookie does not get
|
|
16
18
|
# the cookie object itself back, just the value it holds.
|
|
17
19
|
#
|
|
18
|
-
# Examples
|
|
20
|
+
# Examples of writing:
|
|
19
21
|
#
|
|
20
22
|
# # Sets a simple session cookie.
|
|
21
23
|
# # This cookie will be deleted when the user's browser is closed.
|
|
@@ -25,11 +27,11 @@ module ActionDispatch
|
|
|
25
27
|
# cookies[:lat_lon] = [47.68, -122.37]
|
|
26
28
|
#
|
|
27
29
|
# # Sets a cookie that expires in 1 hour.
|
|
28
|
-
# cookies[:login] = { :
|
|
30
|
+
# cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now }
|
|
29
31
|
#
|
|
30
|
-
# # Sets a signed cookie, which prevents
|
|
31
|
-
# # The cookie is signed by your app's <tt>config.
|
|
32
|
-
# #
|
|
32
|
+
# # Sets a signed cookie, which prevents users from tampering with its value.
|
|
33
|
+
# # The cookie is signed by your app's <tt>config.secret_key_base</tt> value.
|
|
34
|
+
# # It can be read using the signed method <tt>cookies.signed[:name]</tt>
|
|
33
35
|
# cookies.signed[:user_id] = current_user.id
|
|
34
36
|
#
|
|
35
37
|
# # Sets a "permanent" cookie (which expires in 20 years from now).
|
|
@@ -38,11 +40,12 @@ module ActionDispatch
|
|
|
38
40
|
# # You can also chain these methods:
|
|
39
41
|
# cookies.permanent.signed[:login] = "XJ-122"
|
|
40
42
|
#
|
|
41
|
-
# Examples
|
|
43
|
+
# Examples of reading:
|
|
42
44
|
#
|
|
43
|
-
# cookies[:user_name]
|
|
44
|
-
# cookies.size
|
|
45
|
-
# cookies[:lat_lon]
|
|
45
|
+
# cookies[:user_name] # => "david"
|
|
46
|
+
# cookies.size # => 2
|
|
47
|
+
# cookies[:lat_lon] # => [47.68, -122.37]
|
|
48
|
+
# cookies.signed[:login] # => "XJ-122"
|
|
46
49
|
#
|
|
47
50
|
# Example for deleting:
|
|
48
51
|
#
|
|
@@ -50,13 +53,13 @@ module ActionDispatch
|
|
|
50
53
|
#
|
|
51
54
|
# Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie:
|
|
52
55
|
#
|
|
53
|
-
# cookies[:
|
|
54
|
-
# :
|
|
55
|
-
# :
|
|
56
|
-
# :
|
|
56
|
+
# cookies[:name] = {
|
|
57
|
+
# value: 'a yummy cookie',
|
|
58
|
+
# expires: 1.year.from_now,
|
|
59
|
+
# domain: 'domain.com'
|
|
57
60
|
# }
|
|
58
61
|
#
|
|
59
|
-
# cookies.delete(:
|
|
62
|
+
# cookies.delete(:name, domain: 'domain.com')
|
|
60
63
|
#
|
|
61
64
|
# The option symbols for setting cookies are:
|
|
62
65
|
#
|
|
@@ -67,26 +70,125 @@ module ActionDispatch
|
|
|
67
70
|
# restrict to the domain level. If you use a schema like www.example.com
|
|
68
71
|
# and want to share session with user.example.com set <tt>:domain</tt>
|
|
69
72
|
# to <tt>:all</tt>. Make sure to specify the <tt>:domain</tt> option with
|
|
70
|
-
# <tt>:all</tt> again when deleting
|
|
73
|
+
# <tt>:all</tt> again when deleting cookies.
|
|
71
74
|
#
|
|
72
|
-
# :
|
|
73
|
-
# :
|
|
75
|
+
# domain: nil # Does not sets cookie domain. (default)
|
|
76
|
+
# domain: :all # Allow the cookie for the top most level
|
|
74
77
|
# domain and subdomains.
|
|
75
78
|
#
|
|
76
79
|
# * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object.
|
|
77
|
-
# * <tt>:secure</tt> - Whether this cookie is
|
|
80
|
+
# * <tt>:secure</tt> - Whether this cookie is only transmitted to HTTPS servers.
|
|
78
81
|
# Default is +false+.
|
|
79
82
|
# * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
|
|
80
83
|
# only HTTP. Defaults to +false+.
|
|
81
84
|
class Cookies
|
|
82
|
-
HTTP_HEADER
|
|
83
|
-
|
|
85
|
+
HTTP_HEADER = "Set-Cookie".freeze
|
|
86
|
+
GENERATOR_KEY = "action_dispatch.key_generator".freeze
|
|
87
|
+
SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze
|
|
88
|
+
ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze
|
|
89
|
+
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
|
|
90
|
+
SECRET_TOKEN = "action_dispatch.secret_token".freeze
|
|
91
|
+
SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
|
|
92
|
+
|
|
93
|
+
# Cookies can typically store 4096 bytes.
|
|
94
|
+
MAX_COOKIE_SIZE = 4096
|
|
84
95
|
|
|
85
96
|
# Raised when storing more than 4K of session data.
|
|
86
|
-
|
|
97
|
+
CookieOverflow = Class.new StandardError
|
|
98
|
+
|
|
99
|
+
# Include in a cookie jar to allow chaining, e.g. cookies.permanent.signed
|
|
100
|
+
module ChainedCookieJars
|
|
101
|
+
# Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
|
|
102
|
+
#
|
|
103
|
+
# cookies.permanent[:prefers_open_id] = true
|
|
104
|
+
# # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
|
|
105
|
+
#
|
|
106
|
+
# This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
|
|
107
|
+
#
|
|
108
|
+
# This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
|
|
109
|
+
#
|
|
110
|
+
# cookies.permanent.signed[:remember_me] = current_user.id
|
|
111
|
+
# # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
|
|
112
|
+
def permanent
|
|
113
|
+
@permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
|
|
117
|
+
# the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
|
|
118
|
+
# cookie was tampered with by the user (or a 3rd party), nil will be returned.
|
|
119
|
+
#
|
|
120
|
+
# If +config.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
|
|
121
|
+
# legacy cookies signed with the old key generator will be transparently upgraded.
|
|
122
|
+
#
|
|
123
|
+
# This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
|
|
124
|
+
#
|
|
125
|
+
# Example:
|
|
126
|
+
#
|
|
127
|
+
# cookies.signed[:discount] = 45
|
|
128
|
+
# # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
|
|
129
|
+
#
|
|
130
|
+
# cookies.signed[:discount] # => 45
|
|
131
|
+
def signed
|
|
132
|
+
@signed ||=
|
|
133
|
+
if @options[:upgrade_legacy_signed_cookies]
|
|
134
|
+
UpgradeLegacySignedCookieJar.new(self, @key_generator, @options)
|
|
135
|
+
else
|
|
136
|
+
SignedCookieJar.new(self, @key_generator, @options)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
|
|
141
|
+
# If the cookie was tampered with by the user (or a 3rd party), nil will be returned.
|
|
142
|
+
#
|
|
143
|
+
# If +config.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
|
|
144
|
+
# legacy cookies signed with the old key generator will be transparently upgraded.
|
|
145
|
+
#
|
|
146
|
+
# This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
|
|
147
|
+
#
|
|
148
|
+
# Example:
|
|
149
|
+
#
|
|
150
|
+
# cookies.encrypted[:discount] = 45
|
|
151
|
+
# # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/
|
|
152
|
+
#
|
|
153
|
+
# cookies.encrypted[:discount] # => 45
|
|
154
|
+
def encrypted
|
|
155
|
+
@encrypted ||=
|
|
156
|
+
if @options[:upgrade_legacy_signed_cookies]
|
|
157
|
+
UpgradeLegacyEncryptedCookieJar.new(self, @key_generator, @options)
|
|
158
|
+
else
|
|
159
|
+
EncryptedCookieJar.new(self, @key_generator, @options)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Returns the +signed+ or +encrypted jar, preferring +encrypted+ if +secret_key_base+ is set.
|
|
164
|
+
# Used by ActionDispatch::Session::CookieStore to avoid the need to introduce new cookie stores.
|
|
165
|
+
def signed_or_encrypted
|
|
166
|
+
@signed_or_encrypted ||=
|
|
167
|
+
if @options[:secret_key_base].present?
|
|
168
|
+
encrypted
|
|
169
|
+
else
|
|
170
|
+
signed
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
module VerifyAndUpgradeLegacySignedMessage
|
|
176
|
+
def initialize(*args)
|
|
177
|
+
super
|
|
178
|
+
@legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token])
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def verify_and_upgrade_legacy_signed_message(name, signed_message)
|
|
182
|
+
@legacy_verifier.verify(signed_message).tap do |value|
|
|
183
|
+
self[name] = value
|
|
184
|
+
end
|
|
185
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
|
186
|
+
nil
|
|
187
|
+
end
|
|
188
|
+
end
|
|
87
189
|
|
|
88
190
|
class CookieJar #:nodoc:
|
|
89
|
-
include Enumerable
|
|
191
|
+
include Enumerable, ChainedCookieJars
|
|
90
192
|
|
|
91
193
|
# This regular expression is used to split the levels of a domain.
|
|
92
194
|
# The top level domain can be any string without a period or
|
|
@@ -102,23 +204,36 @@ module ActionDispatch
|
|
|
102
204
|
# $& => example.local
|
|
103
205
|
DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
|
|
104
206
|
|
|
207
|
+
def self.options_for_env(env) #:nodoc:
|
|
208
|
+
{ signed_cookie_salt: env[SIGNED_COOKIE_SALT] || '',
|
|
209
|
+
encrypted_cookie_salt: env[ENCRYPTED_COOKIE_SALT] || '',
|
|
210
|
+
encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '',
|
|
211
|
+
secret_token: env[SECRET_TOKEN],
|
|
212
|
+
secret_key_base: env[SECRET_KEY_BASE],
|
|
213
|
+
upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?
|
|
214
|
+
}
|
|
215
|
+
end
|
|
216
|
+
|
|
105
217
|
def self.build(request)
|
|
106
|
-
|
|
218
|
+
env = request.env
|
|
219
|
+
key_generator = env[GENERATOR_KEY]
|
|
220
|
+
options = options_for_env env
|
|
221
|
+
|
|
107
222
|
host = request.host
|
|
108
223
|
secure = request.ssl?
|
|
109
224
|
|
|
110
|
-
new(
|
|
225
|
+
new(key_generator, host, secure, options).tap do |hash|
|
|
111
226
|
hash.update(request.cookies)
|
|
112
227
|
end
|
|
113
228
|
end
|
|
114
229
|
|
|
115
|
-
def initialize(
|
|
116
|
-
@
|
|
230
|
+
def initialize(key_generator, host = nil, secure = false, options = {})
|
|
231
|
+
@key_generator = key_generator
|
|
117
232
|
@set_cookies = {}
|
|
118
233
|
@delete_cookies = {}
|
|
119
234
|
@host = host
|
|
120
235
|
@secure = secure
|
|
121
|
-
@
|
|
236
|
+
@options = options
|
|
122
237
|
@cookies = {}
|
|
123
238
|
end
|
|
124
239
|
|
|
@@ -131,6 +246,10 @@ module ActionDispatch
|
|
|
131
246
|
@cookies[name.to_s]
|
|
132
247
|
end
|
|
133
248
|
|
|
249
|
+
def fetch(name, *args, &block)
|
|
250
|
+
@cookies.fetch(name.to_s, *args, &block)
|
|
251
|
+
end
|
|
252
|
+
|
|
134
253
|
def key?(name)
|
|
135
254
|
@cookies.key?(name.to_s)
|
|
136
255
|
end
|
|
@@ -155,13 +274,13 @@ module ActionDispatch
|
|
|
155
274
|
end
|
|
156
275
|
elsif options[:domain].is_a? Array
|
|
157
276
|
# if host matches one of the supplied domains without a dot in front of it
|
|
158
|
-
options[:domain] = options[:domain].find {|domain| @host.include? domain
|
|
277
|
+
options[:domain] = options[:domain].find {|domain| @host.include? domain.sub(/^\./, '') }
|
|
159
278
|
end
|
|
160
279
|
end
|
|
161
280
|
|
|
162
281
|
# Sets the cookie named +name+. The second argument may be the very cookie
|
|
163
282
|
# value, or a hash of options as documented above.
|
|
164
|
-
def []=(
|
|
283
|
+
def []=(name, options)
|
|
165
284
|
if options.is_a?(Hash)
|
|
166
285
|
options.symbolize_keys!
|
|
167
286
|
value = options[:value]
|
|
@@ -172,65 +291,43 @@ module ActionDispatch
|
|
|
172
291
|
|
|
173
292
|
handle_options(options)
|
|
174
293
|
|
|
175
|
-
if @cookies[
|
|
176
|
-
@cookies[
|
|
177
|
-
@set_cookies[
|
|
178
|
-
@delete_cookies.delete(
|
|
294
|
+
if @cookies[name.to_s] != value or options[:expires]
|
|
295
|
+
@cookies[name.to_s] = value
|
|
296
|
+
@set_cookies[name.to_s] = options
|
|
297
|
+
@delete_cookies.delete(name.to_s)
|
|
179
298
|
end
|
|
180
299
|
|
|
181
300
|
value
|
|
182
301
|
end
|
|
183
302
|
|
|
184
303
|
# Removes the cookie on the client machine by setting the value to an empty string
|
|
185
|
-
# and
|
|
304
|
+
# and the expiration date in the past. Like <tt>[]=</tt>, you can pass in
|
|
186
305
|
# an options hash to delete cookies with extra data such as a <tt>:path</tt>.
|
|
187
|
-
def delete(
|
|
188
|
-
|
|
306
|
+
def delete(name, options = {})
|
|
307
|
+
return unless @cookies.has_key? name.to_s
|
|
189
308
|
|
|
309
|
+
options.symbolize_keys!
|
|
190
310
|
handle_options(options)
|
|
191
311
|
|
|
192
|
-
value = @cookies.delete(
|
|
193
|
-
@delete_cookies[
|
|
312
|
+
value = @cookies.delete(name.to_s)
|
|
313
|
+
@delete_cookies[name.to_s] = options
|
|
194
314
|
value
|
|
195
315
|
end
|
|
196
316
|
|
|
317
|
+
# Whether the given cookie is to be deleted by this CookieJar.
|
|
318
|
+
# Like <tt>[]=</tt>, you can pass in an options hash to test if a
|
|
319
|
+
# deletion applies to a specific <tt>:path</tt>, <tt>:domain</tt> etc.
|
|
320
|
+
def deleted?(name, options = {})
|
|
321
|
+
options.symbolize_keys!
|
|
322
|
+
handle_options(options)
|
|
323
|
+
@delete_cookies[name.to_s] == options
|
|
324
|
+
end
|
|
325
|
+
|
|
197
326
|
# Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie
|
|
198
327
|
def clear(options = {})
|
|
199
328
|
@cookies.each_key{ |k| delete(k, options) }
|
|
200
329
|
end
|
|
201
330
|
|
|
202
|
-
# Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
|
|
203
|
-
#
|
|
204
|
-
# cookies.permanent[:prefers_open_id] = true
|
|
205
|
-
# # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
|
|
206
|
-
#
|
|
207
|
-
# This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
|
|
208
|
-
#
|
|
209
|
-
# This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
|
|
210
|
-
#
|
|
211
|
-
# cookies.permanent.signed[:remember_me] = current_user.id
|
|
212
|
-
# # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
|
|
213
|
-
def permanent
|
|
214
|
-
@permanent ||= PermanentCookieJar.new(self, @secret)
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
# Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
|
|
218
|
-
# the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
|
|
219
|
-
# cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will
|
|
220
|
-
# be raised.
|
|
221
|
-
#
|
|
222
|
-
# This jar requires that you set a suitable secret for the verification on your app's config.secret_token.
|
|
223
|
-
#
|
|
224
|
-
# Example:
|
|
225
|
-
#
|
|
226
|
-
# cookies.signed[:discount] = 45
|
|
227
|
-
# # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
|
|
228
|
-
#
|
|
229
|
-
# cookies.signed[:discount] # => 45
|
|
230
|
-
def signed
|
|
231
|
-
@signed ||= SignedCookieJar.new(self, @secret)
|
|
232
|
-
end
|
|
233
|
-
|
|
234
331
|
def write(headers)
|
|
235
332
|
@set_cookies.each { |k, v| ::Rack::Utils.set_cookie_header!(headers, k, v) if write_cookie?(v) }
|
|
236
333
|
@delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) }
|
|
@@ -245,18 +342,25 @@ module ActionDispatch
|
|
|
245
342
|
self.always_write_cookie = false
|
|
246
343
|
|
|
247
344
|
private
|
|
248
|
-
|
|
249
345
|
def write_cookie?(cookie)
|
|
250
346
|
@secure || !cookie[:secure] || always_write_cookie
|
|
251
347
|
end
|
|
252
348
|
end
|
|
253
349
|
|
|
254
|
-
class PermanentCookieJar
|
|
255
|
-
|
|
256
|
-
|
|
350
|
+
class PermanentCookieJar #:nodoc:
|
|
351
|
+
include ChainedCookieJars
|
|
352
|
+
|
|
353
|
+
def initialize(parent_jar, key_generator, options = {})
|
|
354
|
+
@parent_jar = parent_jar
|
|
355
|
+
@key_generator = key_generator
|
|
356
|
+
@options = options
|
|
257
357
|
end
|
|
258
358
|
|
|
259
|
-
def []
|
|
359
|
+
def [](name)
|
|
360
|
+
@parent_jar[name.to_s]
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def []=(name, options)
|
|
260
364
|
if options.is_a?(Hash)
|
|
261
365
|
options.symbolize_keys!
|
|
262
366
|
else
|
|
@@ -264,37 +368,27 @@ module ActionDispatch
|
|
|
264
368
|
end
|
|
265
369
|
|
|
266
370
|
options[:expires] = 20.years.from_now
|
|
267
|
-
@parent_jar[
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
def signed
|
|
271
|
-
@signed ||= SignedCookieJar.new(self, @secret)
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
def method_missing(method, *arguments, &block)
|
|
275
|
-
@parent_jar.send(method, *arguments, &block)
|
|
371
|
+
@parent_jar[name] = options
|
|
276
372
|
end
|
|
277
373
|
end
|
|
278
374
|
|
|
279
|
-
class SignedCookieJar
|
|
280
|
-
|
|
281
|
-
SECRET_MIN_LENGTH = 30 # Characters
|
|
375
|
+
class SignedCookieJar #:nodoc:
|
|
376
|
+
include ChainedCookieJars
|
|
282
377
|
|
|
283
|
-
def initialize(parent_jar,
|
|
284
|
-
ensure_secret_secure(secret)
|
|
378
|
+
def initialize(parent_jar, key_generator, options = {})
|
|
285
379
|
@parent_jar = parent_jar
|
|
380
|
+
@options = options
|
|
381
|
+
secret = key_generator.generate_key(@options[:signed_cookie_salt])
|
|
286
382
|
@verifier = ActiveSupport::MessageVerifier.new(secret)
|
|
287
383
|
end
|
|
288
384
|
|
|
289
385
|
def [](name)
|
|
290
386
|
if signed_message = @parent_jar[name]
|
|
291
|
-
|
|
387
|
+
verify(signed_message)
|
|
292
388
|
end
|
|
293
|
-
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
|
294
|
-
nil
|
|
295
389
|
end
|
|
296
390
|
|
|
297
|
-
def []=(
|
|
391
|
+
def []=(name, options)
|
|
298
392
|
if options.is_a?(Hash)
|
|
299
393
|
options.symbolize_keys!
|
|
300
394
|
options[:value] = @verifier.generate(options[:value])
|
|
@@ -303,31 +397,83 @@ module ActionDispatch
|
|
|
303
397
|
end
|
|
304
398
|
|
|
305
399
|
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
|
|
306
|
-
@parent_jar[
|
|
400
|
+
@parent_jar[name] = options
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
private
|
|
404
|
+
def verify(signed_message)
|
|
405
|
+
@verifier.verify(signed_message)
|
|
406
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
|
407
|
+
nil
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if
|
|
412
|
+
# config.secret_token and config.secret_key_base are both set. It reads
|
|
413
|
+
# legacy cookies signed with the old dummy key generator and re-saves
|
|
414
|
+
# them using the new key generator to provide a smooth upgrade path.
|
|
415
|
+
class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc:
|
|
416
|
+
include VerifyAndUpgradeLegacySignedMessage
|
|
417
|
+
|
|
418
|
+
def [](name)
|
|
419
|
+
if signed_message = @parent_jar[name]
|
|
420
|
+
verify(signed_message) || verify_and_upgrade_legacy_signed_message(name, signed_message)
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
class EncryptedCookieJar #:nodoc:
|
|
426
|
+
include ChainedCookieJars
|
|
427
|
+
|
|
428
|
+
def initialize(parent_jar, key_generator, options = {})
|
|
429
|
+
if ActiveSupport::LegacyKeyGenerator === key_generator
|
|
430
|
+
raise "You didn't set config.secret_key_base, which is required for this cookie jar. " +
|
|
431
|
+
"Read the upgrade documentation to learn more about this new config option."
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
@parent_jar = parent_jar
|
|
435
|
+
@options = options
|
|
436
|
+
secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
|
|
437
|
+
sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt])
|
|
438
|
+
@encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
|
|
307
439
|
end
|
|
308
440
|
|
|
309
|
-
def
|
|
310
|
-
@parent_jar
|
|
441
|
+
def [](name)
|
|
442
|
+
if encrypted_message = @parent_jar[name]
|
|
443
|
+
decrypt_and_verify(encrypted_message)
|
|
444
|
+
end
|
|
311
445
|
end
|
|
312
446
|
|
|
313
|
-
|
|
447
|
+
def []=(name, options)
|
|
448
|
+
if options.is_a?(Hash)
|
|
449
|
+
options.symbolize_keys!
|
|
450
|
+
else
|
|
451
|
+
options = { :value => options }
|
|
452
|
+
end
|
|
453
|
+
options[:value] = @encryptor.encrypt_and_sign(options[:value])
|
|
454
|
+
|
|
455
|
+
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
|
|
456
|
+
@parent_jar[name] = options
|
|
457
|
+
end
|
|
314
458
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
"integrity hash for cookie session data. Use " +
|
|
321
|
-
"config.secret_token = \"some secret phrase of at " +
|
|
322
|
-
"least #{SECRET_MIN_LENGTH} characters\"" +
|
|
323
|
-
"in config/initializers/secret_token.rb"
|
|
459
|
+
private
|
|
460
|
+
def decrypt_and_verify(encrypted_message)
|
|
461
|
+
@encryptor.decrypt_and_verify(encrypted_message)
|
|
462
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
|
|
463
|
+
nil
|
|
324
464
|
end
|
|
465
|
+
end
|
|
325
466
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
467
|
+
# UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore
|
|
468
|
+
# instead of EncryptedCookieJar if config.secret_token and config.secret_key_base
|
|
469
|
+
# are both set. It reads legacy cookies signed with the old dummy key generator and
|
|
470
|
+
# encrypts and re-saves them using the new key generator to provide a smooth upgrade path.
|
|
471
|
+
class UpgradeLegacyEncryptedCookieJar < EncryptedCookieJar #:nodoc:
|
|
472
|
+
include VerifyAndUpgradeLegacySignedMessage
|
|
473
|
+
|
|
474
|
+
def [](name)
|
|
475
|
+
if encrypted_or_signed_message = @parent_jar[name]
|
|
476
|
+
decrypt_and_verify(encrypted_or_signed_message) || verify_and_upgrade_legacy_signed_message(name, encrypted_or_signed_message)
|
|
331
477
|
end
|
|
332
478
|
end
|
|
333
479
|
end
|
|
@@ -337,7 +483,6 @@ module ActionDispatch
|
|
|
337
483
|
end
|
|
338
484
|
|
|
339
485
|
def call(env)
|
|
340
|
-
cookie_jar = nil
|
|
341
486
|
status, headers, body = @app.call(env)
|
|
342
487
|
|
|
343
488
|
if cookie_jar = env['action_dispatch.cookies']
|
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
require 'action_dispatch/http/request'
|
|
2
2
|
require 'action_dispatch/middleware/exception_wrapper'
|
|
3
|
+
require 'action_dispatch/routing/inspector'
|
|
3
4
|
|
|
4
5
|
module ActionDispatch
|
|
5
6
|
# This middleware is responsible for logging exceptions and
|
|
6
7
|
# showing a debugging page in case the request is local.
|
|
7
8
|
class DebugExceptions
|
|
8
|
-
RESCUES_TEMPLATE_PATH = File.
|
|
9
|
+
RESCUES_TEMPLATE_PATH = File.expand_path('../templates', __FILE__)
|
|
9
10
|
|
|
10
|
-
def initialize(app)
|
|
11
|
-
@app
|
|
11
|
+
def initialize(app, routes_app = nil)
|
|
12
|
+
@app = app
|
|
13
|
+
@routes_app = routes_app
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def call(env)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if
|
|
19
|
-
|
|
20
|
-
body.close if body.respond_to?(:close)
|
|
21
|
-
raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
|
|
22
|
-
end
|
|
23
|
-
rescue Exception => exception
|
|
24
|
-
raise exception if env['action_dispatch.show_exceptions'] == false
|
|
17
|
+
_, headers, body = response = @app.call(env)
|
|
18
|
+
|
|
19
|
+
if headers['X-Cascade'] == 'pass'
|
|
20
|
+
body.close if body.respond_to?(:close)
|
|
21
|
+
raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
|
|
25
22
|
end
|
|
26
23
|
|
|
27
|
-
|
|
24
|
+
response
|
|
25
|
+
rescue Exception => exception
|
|
26
|
+
raise exception if env['action_dispatch.show_exceptions'] == false
|
|
27
|
+
render_exception(env, exception)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
private
|
|
@@ -39,9 +39,12 @@ module ActionDispatch
|
|
|
39
39
|
:exception => wrapper.exception,
|
|
40
40
|
:application_trace => wrapper.application_trace,
|
|
41
41
|
:framework_trace => wrapper.framework_trace,
|
|
42
|
-
:full_trace => wrapper.full_trace
|
|
42
|
+
:full_trace => wrapper.full_trace,
|
|
43
|
+
:routes_inspector => routes_inspector(exception),
|
|
44
|
+
:source_extract => wrapper.source_extract,
|
|
45
|
+
:line_number => wrapper.line_number,
|
|
46
|
+
:file => wrapper.file
|
|
43
47
|
)
|
|
44
|
-
|
|
45
48
|
file = "rescues/#{wrapper.rescue_template}"
|
|
46
49
|
body = template.render(:template => file, :layout => 'rescues/layout')
|
|
47
50
|
render(wrapper.status_code, body)
|
|
@@ -76,7 +79,13 @@ module ActionDispatch
|
|
|
76
79
|
end
|
|
77
80
|
|
|
78
81
|
def stderr_logger
|
|
79
|
-
@stderr_logger ||= Logger.new($stderr)
|
|
82
|
+
@stderr_logger ||= ActiveSupport::Logger.new($stderr)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def routes_inspector(exception)
|
|
86
|
+
if @routes_app.respond_to?(:routes) && (exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error))
|
|
87
|
+
ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes)
|
|
88
|
+
end
|
|
80
89
|
end
|
|
81
90
|
end
|
|
82
91
|
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
require 'action_controller/metal/exceptions'
|
|
2
|
-
require 'active_support/core_ext/
|
|
2
|
+
require 'active_support/core_ext/class/attribute_accessors'
|
|
3
3
|
|
|
4
4
|
module ActionDispatch
|
|
5
5
|
class ExceptionWrapper
|
|
@@ -9,8 +9,13 @@ module ActionDispatch
|
|
|
9
9
|
'ActionController::RoutingError' => :not_found,
|
|
10
10
|
'AbstractController::ActionNotFound' => :not_found,
|
|
11
11
|
'ActionController::MethodNotAllowed' => :method_not_allowed,
|
|
12
|
+
'ActionController::UnknownHttpMethod' => :method_not_allowed,
|
|
12
13
|
'ActionController::NotImplemented' => :not_implemented,
|
|
13
|
-
'ActionController::
|
|
14
|
+
'ActionController::UnknownFormat' => :not_acceptable,
|
|
15
|
+
'ActionController::InvalidAuthenticityToken' => :unprocessable_entity,
|
|
16
|
+
'ActionDispatch::ParamsParser::ParseError' => :bad_request,
|
|
17
|
+
'ActionController::BadRequest' => :bad_request,
|
|
18
|
+
'ActionController::ParameterMissing' => :bad_request
|
|
14
19
|
)
|
|
15
20
|
|
|
16
21
|
cattr_accessor :rescue_templates
|
|
@@ -22,7 +27,7 @@ module ActionDispatch
|
|
|
22
27
|
'ActionView::Template::Error' => 'template_error'
|
|
23
28
|
)
|
|
24
29
|
|
|
25
|
-
attr_reader :env, :exception
|
|
30
|
+
attr_reader :env, :exception, :line_number, :file
|
|
26
31
|
|
|
27
32
|
def initialize(env, exception)
|
|
28
33
|
@env = env
|
|
@@ -53,6 +58,15 @@ module ActionDispatch
|
|
|
53
58
|
Rack::Utils.status_code(@@rescue_responses[class_name])
|
|
54
59
|
end
|
|
55
60
|
|
|
61
|
+
def source_extract
|
|
62
|
+
if application_trace && trace = application_trace.first
|
|
63
|
+
file, line, _ = trace.split(":")
|
|
64
|
+
@file = file
|
|
65
|
+
@line_number = line.to_i
|
|
66
|
+
source_fragment(@file, @line_number)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
56
70
|
private
|
|
57
71
|
|
|
58
72
|
def original_exception(exception)
|
|
@@ -78,5 +92,17 @@ module ActionDispatch
|
|
|
78
92
|
def backtrace_cleaner
|
|
79
93
|
@backtrace_cleaner ||= @env['action_dispatch.backtrace_cleaner']
|
|
80
94
|
end
|
|
95
|
+
|
|
96
|
+
def source_fragment(path, line)
|
|
97
|
+
return unless Rails.respond_to?(:root) && Rails.root
|
|
98
|
+
full_path = Rails.root.join(path)
|
|
99
|
+
if File.exists?(full_path)
|
|
100
|
+
File.open(full_path, "r") do |file|
|
|
101
|
+
start = [line - 3, 0].max
|
|
102
|
+
lines = file.each_line.drop(start).take(6)
|
|
103
|
+
Hash[*(start+1..(lines.count+start)).zip(lines).flatten]
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
81
107
|
end
|
|
82
108
|
end
|