actionpack 4.1.16 → 4.2.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +163 -690
- data/README.rdoc +7 -2
- data/lib/abstract_controller/base.rb +16 -6
- data/lib/abstract_controller/callbacks.rb +28 -51
- data/lib/abstract_controller/helpers.rb +0 -3
- data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
- data/lib/abstract_controller/rendering.rb +1 -7
- data/lib/abstract_controller/url_for.rb +1 -1
- data/lib/action_controller.rb +1 -0
- data/lib/action_controller/base.rb +2 -1
- data/lib/action_controller/caching.rb +1 -1
- data/lib/action_controller/caching/fragments.rb +7 -1
- data/lib/action_controller/log_subscriber.rb +26 -25
- data/lib/action_controller/metal.rb +11 -7
- data/lib/action_controller/metal/conditional_get.rb +31 -6
- data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
- data/lib/action_controller/metal/force_ssl.rb +1 -1
- data/lib/action_controller/metal/head.rb +2 -0
- data/lib/action_controller/metal/http_authentication.rb +3 -15
- data/lib/action_controller/metal/instrumentation.rb +4 -7
- data/lib/action_controller/metal/live.rb +57 -6
- data/lib/action_controller/metal/mime_responds.rb +17 -227
- data/lib/action_controller/metal/redirecting.rb +14 -8
- data/lib/action_controller/metal/renderers.rb +19 -3
- data/lib/action_controller/metal/rendering.rb +2 -6
- data/lib/action_controller/metal/request_forgery_protection.rb +75 -7
- data/lib/action_controller/metal/streaming.rb +1 -1
- data/lib/action_controller/metal/strong_parameters.rb +111 -11
- data/lib/action_controller/metal/url_for.rb +11 -12
- data/lib/action_controller/model_naming.rb +1 -1
- data/lib/action_controller/railtie.rb +4 -0
- data/lib/action_controller/test_case.rb +87 -75
- data/lib/action_dispatch/http/cache.rb +1 -1
- data/lib/action_dispatch/http/filter_parameters.rb +2 -2
- data/lib/action_dispatch/http/headers.rb +43 -9
- data/lib/action_dispatch/http/mime_negotiation.rb +10 -4
- data/lib/action_dispatch/http/mime_type.rb +2 -16
- data/lib/action_dispatch/http/parameter_filter.rb +1 -1
- data/lib/action_dispatch/http/parameters.rb +11 -26
- data/lib/action_dispatch/http/request.rb +30 -10
- data/lib/action_dispatch/http/response.rb +52 -17
- data/lib/action_dispatch/http/upload.rb +3 -8
- data/lib/action_dispatch/http/url.rb +87 -70
- data/lib/action_dispatch/journey/formatter.rb +18 -17
- data/lib/action_dispatch/journey/gtg/builder.rb +3 -3
- data/lib/action_dispatch/journey/gtg/simulator.rb +10 -7
- data/lib/action_dispatch/journey/gtg/transition_table.rb +18 -26
- data/lib/action_dispatch/journey/nfa/dot.rb +2 -2
- data/lib/action_dispatch/journey/nfa/simulator.rb +1 -1
- data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -5
- data/lib/action_dispatch/journey/nodes/node.rb +4 -0
- data/lib/action_dispatch/journey/parser.rb +52 -60
- data/lib/action_dispatch/journey/parser.y +11 -10
- data/lib/action_dispatch/journey/path/pattern.rb +16 -19
- data/lib/action_dispatch/journey/route.rb +3 -18
- data/lib/action_dispatch/journey/router.rb +34 -65
- data/lib/action_dispatch/journey/router/strexp.rb +9 -6
- data/lib/action_dispatch/journey/routes.rb +0 -4
- data/lib/action_dispatch/journey/visitors.rb +81 -92
- data/lib/action_dispatch/journey/visualizer/index.html.erb +2 -2
- data/lib/action_dispatch/middleware/cookies.rb +27 -31
- data/lib/action_dispatch/middleware/debug_exceptions.rb +32 -3
- data/lib/action_dispatch/middleware/exception_wrapper.rb +19 -17
- data/lib/action_dispatch/middleware/flash.rb +7 -4
- data/lib/action_dispatch/middleware/public_exceptions.rb +13 -8
- data/lib/action_dispatch/middleware/remote_ip.rb +3 -3
- data/lib/action_dispatch/middleware/request_id.rb +1 -1
- data/lib/action_dispatch/middleware/session/cookie_store.rb +1 -1
- data/lib/action_dispatch/middleware/show_exceptions.rb +1 -0
- data/lib/action_dispatch/middleware/static.rb +22 -23
- data/lib/action_dispatch/middleware/templates/rescues/_source.erb +22 -18
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +36 -8
- data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +2 -8
- data/lib/action_dispatch/middleware/templates/rescues/{diagnostics.erb → diagnostics.html.erb} +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +6 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -24
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +0 -1
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +119 -63
- data/lib/action_dispatch/routing/endpoint.rb +10 -0
- data/lib/action_dispatch/routing/inspector.rb +4 -11
- data/lib/action_dispatch/routing/mapper.rb +399 -278
- data/lib/action_dispatch/routing/polymorphic_routes.rb +190 -78
- data/lib/action_dispatch/routing/redirection.rb +10 -12
- data/lib/action_dispatch/routing/route_set.rb +224 -177
- data/lib/action_dispatch/routing/url_for.rb +9 -4
- data/lib/action_dispatch/testing/assertions.rb +11 -7
- data/lib/action_dispatch/testing/assertions/dom.rb +2 -26
- data/lib/action_dispatch/testing/assertions/response.rb +2 -7
- data/lib/action_dispatch/testing/assertions/routing.rb +9 -9
- data/lib/action_dispatch/testing/assertions/selector.rb +2 -429
- data/lib/action_dispatch/testing/assertions/tag.rb +2 -134
- data/lib/action_dispatch/testing/integration.rb +15 -18
- data/lib/action_dispatch/testing/test_request.rb +1 -1
- data/lib/action_dispatch/testing/test_response.rb +5 -1
- data/lib/action_pack/gem_version.rb +3 -3
- metadata +57 -15
- data/lib/action_controller/metal/responder.rb +0 -297
@@ -2,13 +2,13 @@
|
|
2
2
|
<html>
|
3
3
|
<head>
|
4
4
|
<title><%= title %></title>
|
5
|
-
<link rel="stylesheet" href="https://
|
5
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.css" type="text/css">
|
6
6
|
<style>
|
7
7
|
<% stylesheets.each do |style| %>
|
8
8
|
<%= style %>
|
9
9
|
<% end %>
|
10
10
|
</style>
|
11
|
-
<script src="https://
|
11
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min.js" type="text/javascript"></script>
|
12
12
|
</head>
|
13
13
|
<body>
|
14
14
|
<div id="wrapper">
|
@@ -3,6 +3,7 @@ require 'active_support/core_ext/module/attribute_accessors'
|
|
3
3
|
require 'active_support/core_ext/object/blank'
|
4
4
|
require 'active_support/key_generator'
|
5
5
|
require 'active_support/message_verifier'
|
6
|
+
require 'active_support/json'
|
6
7
|
|
7
8
|
module ActionDispatch
|
8
9
|
class Request < Rack::Request
|
@@ -70,13 +71,11 @@ module ActionDispatch
|
|
70
71
|
# restrict to the domain level. If you use a schema like www.example.com
|
71
72
|
# and want to share session with user.example.com set <tt>:domain</tt>
|
72
73
|
# to <tt>:all</tt>. Make sure to specify the <tt>:domain</tt> option with
|
73
|
-
# <tt>:all</tt>
|
74
|
+
# <tt>:all</tt> again when deleting cookies.
|
74
75
|
#
|
75
76
|
# domain: nil # Does not sets cookie domain. (default)
|
76
77
|
# domain: :all # Allow the cookie for the top most level
|
77
|
-
#
|
78
|
-
# domain: %w(.example.com .example.org) # Allow the cookie
|
79
|
-
# for concrete domain names.
|
78
|
+
# # domain and subdomains.
|
80
79
|
#
|
81
80
|
# * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object.
|
82
81
|
# * <tt>:secure</tt> - Whether this cookie is only transmitted to HTTPS servers.
|
@@ -92,6 +91,7 @@ module ActionDispatch
|
|
92
91
|
SECRET_TOKEN = "action_dispatch.secret_token".freeze
|
93
92
|
SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
|
94
93
|
COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze
|
94
|
+
COOKIES_DIGEST = "action_dispatch.cookies_digest".freeze
|
95
95
|
|
96
96
|
# Cookies can typically store 4096 bytes.
|
97
97
|
MAX_COOKIE_SIZE = 4096
|
@@ -120,7 +120,7 @@ module ActionDispatch
|
|
120
120
|
# the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
|
121
121
|
# cookie was tampered with by the user (or a 3rd party), nil will be returned.
|
122
122
|
#
|
123
|
-
# If +secrets.secret_key_base+ and +
|
123
|
+
# If +secrets.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
|
124
124
|
# legacy cookies signed with the old key generator will be transparently upgraded.
|
125
125
|
#
|
126
126
|
# This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
|
@@ -143,7 +143,7 @@ module ActionDispatch
|
|
143
143
|
# Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
|
144
144
|
# If the cookie was tampered with by the user (or a 3rd party), nil will be returned.
|
145
145
|
#
|
146
|
-
# If +secrets.secret_key_base+ and +
|
146
|
+
# If +secrets.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
|
147
147
|
# legacy cookies signed with the old key generator will be transparently upgraded.
|
148
148
|
#
|
149
149
|
# This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
|
@@ -175,10 +175,14 @@ module ActionDispatch
|
|
175
175
|
end
|
176
176
|
end
|
177
177
|
|
178
|
+
# Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream
|
179
|
+
# to the Message{Encryptor,Verifier} allows us to handle the
|
180
|
+
# (de)serialization step within the cookie jar, which gives us the
|
181
|
+
# opportunity to detect and migrate legacy cookies.
|
178
182
|
module VerifyAndUpgradeLegacySignedMessage
|
179
183
|
def initialize(*args)
|
180
184
|
super
|
181
|
-
@legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token], serializer: NullSerializer)
|
185
|
+
@legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token], serializer: ActiveSupport::MessageEncryptor::NullSerializer)
|
182
186
|
end
|
183
187
|
|
184
188
|
def verify_and_upgrade_legacy_signed_message(name, signed_message)
|
@@ -214,7 +218,8 @@ module ActionDispatch
|
|
214
218
|
secret_token: env[SECRET_TOKEN],
|
215
219
|
secret_key_base: env[SECRET_KEY_BASE],
|
216
220
|
upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?,
|
217
|
-
serializer: env[COOKIES_SERIALIZER]
|
221
|
+
serializer: env[COOKIES_SERIALIZER],
|
222
|
+
digest: env[COOKIES_DIGEST]
|
218
223
|
}
|
219
224
|
end
|
220
225
|
|
@@ -291,8 +296,8 @@ module ActionDispatch
|
|
291
296
|
end
|
292
297
|
end
|
293
298
|
|
294
|
-
# Sets the cookie named +name+. The second argument may be the
|
295
|
-
# value
|
299
|
+
# Sets the cookie named +name+. The second argument may be the cookie's
|
300
|
+
# value or a hash of options as documented above.
|
296
301
|
def []=(name, options)
|
297
302
|
if options.is_a?(Hash)
|
298
303
|
options.symbolize_keys!
|
@@ -387,24 +392,11 @@ module ActionDispatch
|
|
387
392
|
|
388
393
|
class JsonSerializer
|
389
394
|
def self.load(value)
|
390
|
-
JSON.
|
395
|
+
ActiveSupport::JSON.decode(value)
|
391
396
|
end
|
392
397
|
|
393
398
|
def self.dump(value)
|
394
|
-
JSON.
|
395
|
-
end
|
396
|
-
end
|
397
|
-
|
398
|
-
# Passing the NullSerializer downstream to the Message{Encryptor,Verifier}
|
399
|
-
# allows us to handle the (de)serialization step within the cookie jar,
|
400
|
-
# which gives us the opportunity to detect and migrate legacy cookies.
|
401
|
-
class NullSerializer
|
402
|
-
def self.load(value)
|
403
|
-
value
|
404
|
-
end
|
405
|
-
|
406
|
-
def self.dump(value)
|
407
|
-
value
|
399
|
+
ActiveSupport::JSON.encode(value)
|
408
400
|
end
|
409
401
|
end
|
410
402
|
|
@@ -443,6 +435,10 @@ module ActionDispatch
|
|
443
435
|
serializer
|
444
436
|
end
|
445
437
|
end
|
438
|
+
|
439
|
+
def digest
|
440
|
+
@options[:digest] || 'SHA1'
|
441
|
+
end
|
446
442
|
end
|
447
443
|
|
448
444
|
class SignedCookieJar #:nodoc:
|
@@ -453,7 +449,7 @@ module ActionDispatch
|
|
453
449
|
@parent_jar = parent_jar
|
454
450
|
@options = options
|
455
451
|
secret = key_generator.generate_key(@options[:signed_cookie_salt])
|
456
|
-
@verifier = ActiveSupport::MessageVerifier.new(secret, serializer: NullSerializer)
|
452
|
+
@verifier = ActiveSupport::MessageVerifier.new(secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
|
457
453
|
end
|
458
454
|
|
459
455
|
def [](name)
|
@@ -470,7 +466,7 @@ module ActionDispatch
|
|
470
466
|
options = { :value => @verifier.generate(serialize(name, options)) }
|
471
467
|
end
|
472
468
|
|
473
|
-
raise CookieOverflow if options[:value].
|
469
|
+
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
|
474
470
|
@parent_jar[name] = options
|
475
471
|
end
|
476
472
|
|
@@ -483,7 +479,7 @@ module ActionDispatch
|
|
483
479
|
end
|
484
480
|
|
485
481
|
# UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if
|
486
|
-
#
|
482
|
+
# config.secret_token and secrets.secret_key_base are both set. It reads
|
487
483
|
# legacy cookies signed with the old dummy key generator and re-saves
|
488
484
|
# them using the new key generator to provide a smooth upgrade path.
|
489
485
|
class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc:
|
@@ -510,7 +506,7 @@ module ActionDispatch
|
|
510
506
|
@options = options
|
511
507
|
secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
|
512
508
|
sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt])
|
513
|
-
@encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: NullSerializer)
|
509
|
+
@encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
|
514
510
|
end
|
515
511
|
|
516
512
|
def [](name)
|
@@ -528,7 +524,7 @@ module ActionDispatch
|
|
528
524
|
|
529
525
|
options[:value] = @encryptor.encrypt_and_sign(serialize(name, options[:value]))
|
530
526
|
|
531
|
-
raise CookieOverflow if options[:value].
|
527
|
+
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
|
532
528
|
@parent_jar[name] = options
|
533
529
|
end
|
534
530
|
|
@@ -541,7 +537,7 @@ module ActionDispatch
|
|
541
537
|
end
|
542
538
|
|
543
539
|
# UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore
|
544
|
-
# instead of EncryptedCookieJar if
|
540
|
+
# instead of EncryptedCookieJar if config.secret_token and secrets.secret_key_base
|
545
541
|
# are both set. It reads legacy cookies signed with the old dummy key generator and
|
546
542
|
# encrypts and re-saves them using the new key generator to provide a smooth upgrade path.
|
547
543
|
class UpgradeLegacyEncryptedCookieJar < EncryptedCookieJar #:nodoc:
|
@@ -38,9 +38,7 @@ module ActionDispatch
|
|
38
38
|
template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
|
39
39
|
request: request,
|
40
40
|
exception: wrapper.exception,
|
41
|
-
|
42
|
-
framework_trace: wrapper.framework_trace,
|
43
|
-
full_trace: wrapper.full_trace,
|
41
|
+
traces: traces_from_wrapper(wrapper),
|
44
42
|
routes_inspector: routes_inspector(exception),
|
45
43
|
source_extract: wrapper.source_extract,
|
46
44
|
line_number: wrapper.line_number,
|
@@ -95,5 +93,36 @@ module ActionDispatch
|
|
95
93
|
ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes)
|
96
94
|
end
|
97
95
|
end
|
96
|
+
|
97
|
+
# Augment the exception traces by providing ids for all unique stack frame
|
98
|
+
def traces_from_wrapper(wrapper)
|
99
|
+
application_trace = wrapper.application_trace
|
100
|
+
framework_trace = wrapper.framework_trace
|
101
|
+
full_trace = wrapper.full_trace
|
102
|
+
|
103
|
+
if application_trace && framework_trace
|
104
|
+
id_counter = 0
|
105
|
+
|
106
|
+
application_trace = application_trace.map do |trace|
|
107
|
+
prev = id_counter
|
108
|
+
id_counter += 1
|
109
|
+
{ id: prev, trace: trace }
|
110
|
+
end
|
111
|
+
|
112
|
+
framework_trace = framework_trace.map do |trace|
|
113
|
+
prev = id_counter
|
114
|
+
id_counter += 1
|
115
|
+
{ id: prev, trace: trace }
|
116
|
+
end
|
117
|
+
|
118
|
+
full_trace = application_trace + framework_trace
|
119
|
+
end
|
120
|
+
|
121
|
+
{
|
122
|
+
"Application Trace" => application_trace,
|
123
|
+
"Framework Trace" => framework_trace,
|
124
|
+
"Full Trace" => full_trace
|
125
|
+
}
|
126
|
+
end
|
98
127
|
end
|
99
128
|
end
|
@@ -6,17 +6,16 @@ module ActionDispatch
|
|
6
6
|
cattr_accessor :rescue_responses
|
7
7
|
@@rescue_responses = Hash.new(:internal_server_error)
|
8
8
|
@@rescue_responses.merge!(
|
9
|
-
'ActionController::RoutingError'
|
10
|
-
'AbstractController::ActionNotFound'
|
11
|
-
'ActionController::MethodNotAllowed'
|
12
|
-
'ActionController::UnknownHttpMethod'
|
13
|
-
'ActionController::NotImplemented'
|
14
|
-
'ActionController::UnknownFormat'
|
15
|
-
'ActionController::InvalidAuthenticityToken'
|
16
|
-
'
|
17
|
-
'
|
18
|
-
'ActionController::
|
19
|
-
'ActionController::ParameterMissing' => :bad_request
|
9
|
+
'ActionController::RoutingError' => :not_found,
|
10
|
+
'AbstractController::ActionNotFound' => :not_found,
|
11
|
+
'ActionController::MethodNotAllowed' => :method_not_allowed,
|
12
|
+
'ActionController::UnknownHttpMethod' => :method_not_allowed,
|
13
|
+
'ActionController::NotImplemented' => :not_implemented,
|
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
|
20
19
|
)
|
21
20
|
|
22
21
|
cattr_accessor :rescue_templates
|
@@ -62,12 +61,15 @@ module ActionDispatch
|
|
62
61
|
end
|
63
62
|
|
64
63
|
def source_extract
|
65
|
-
|
66
|
-
file, line
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
64
|
+
exception.backtrace.map do |trace|
|
65
|
+
file, line = trace.split(":")
|
66
|
+
line_number = line.to_i
|
67
|
+
{
|
68
|
+
code: source_fragment(file, line_number),
|
69
|
+
file: file,
|
70
|
+
line_number: line_number
|
71
|
+
}
|
72
|
+
end if exception.backtrace
|
71
73
|
end
|
72
74
|
|
73
75
|
private
|
@@ -10,7 +10,7 @@ module ActionDispatch
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
# The flash provides a way to pass temporary
|
13
|
+
# The flash provides a way to pass temporary primitive-types (String, Array, Hash) between actions. Anything you place in the flash will be exposed
|
14
14
|
# to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
|
15
15
|
# action that sets <tt>flash[:notice] = "Post successfully created"</tt> before redirecting to a display action that can
|
16
16
|
# then expose the flash to its template. Actually, that exposure is automatically done.
|
@@ -37,8 +37,11 @@ module ActionDispatch
|
|
37
37
|
# flash.alert = "You must be logged in"
|
38
38
|
# flash.notice = "Post successfully created"
|
39
39
|
#
|
40
|
-
# This example
|
41
|
-
#
|
40
|
+
# This example places a string in the flash. And of course, you can put as many as you like at a time too. If you want to pass
|
41
|
+
# non-primitive types, you will have to handle that in your application. Example: To show messages with links, you will have to
|
42
|
+
# use sanitize helper.
|
43
|
+
#
|
44
|
+
# Just remember: They'll be gone by the time the next action has been performed.
|
42
45
|
#
|
43
46
|
# See docs on the FlashHash class for more details about the flash.
|
44
47
|
class Flash
|
@@ -129,7 +132,7 @@ module ActionDispatch
|
|
129
132
|
end
|
130
133
|
|
131
134
|
def key?(name)
|
132
|
-
@flashes.key? name
|
135
|
+
@flashes.key? name
|
133
136
|
end
|
134
137
|
|
135
138
|
def delete(key)
|
@@ -1,4 +1,14 @@
|
|
1
1
|
module ActionDispatch
|
2
|
+
# When called, this middleware renders an error page. By default if an HTML
|
3
|
+
# response is expected it will render static error pages from the `/public`
|
4
|
+
# directory. For example when this middleware receives a 500 response it will
|
5
|
+
# render the template found in `/public/500.html`.
|
6
|
+
# If an internationalized locale is set, this middleware will attempt to render
|
7
|
+
# the template in `/public/500.<locale>.html`. If an internationalized template
|
8
|
+
# is not found it will fall back on `/public/500.html`.
|
9
|
+
#
|
10
|
+
# When a request with a content type other than HTML is made, this middleware
|
11
|
+
# will attempt to convert error information into the appropriate response type.
|
2
12
|
class PublicExceptions
|
3
13
|
attr_accessor :public_path
|
4
14
|
|
@@ -9,12 +19,8 @@ module ActionDispatch
|
|
9
19
|
def call(env)
|
10
20
|
status = env["PATH_INFO"][1..-1]
|
11
21
|
request = ActionDispatch::Request.new(env)
|
22
|
+
content_type = request.formats.first
|
12
23
|
body = { :status => status, :error => Rack::Utils::HTTP_STATUS_CODES.fetch(status.to_i, Rack::Utils::HTTP_STATUS_CODES[500]) }
|
13
|
-
content_type = begin
|
14
|
-
request.formats.first
|
15
|
-
rescue ActionController::BadRequest
|
16
|
-
Mime::HTML
|
17
|
-
end
|
18
24
|
|
19
25
|
render(status, content_type, body)
|
20
26
|
end
|
@@ -36,9 +42,8 @@ module ActionDispatch
|
|
36
42
|
end
|
37
43
|
|
38
44
|
def render_html(status)
|
39
|
-
|
40
|
-
path = "#{public_path}/#{status}
|
41
|
-
path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path))
|
45
|
+
path = "#{public_path}/#{status}.#{I18n.locale}.html"
|
46
|
+
path = "#{public_path}/#{status}.html" unless (found = File.exist?(path))
|
42
47
|
|
43
48
|
if found || File.exist?(path)
|
44
49
|
render_format(status, 'text/html', File.read(path))
|
@@ -11,7 +11,7 @@ module ActionDispatch
|
|
11
11
|
# Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
|
12
12
|
# requires. Some Rack servers simply drop preceding headers, and only report
|
13
13
|
# the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers].
|
14
|
-
# If you are behind multiple proxy servers (like
|
14
|
+
# If you are behind multiple proxy servers (like NGINX to HAProxy to Unicorn)
|
15
15
|
# then you should test your Rack server to make sure your data is good.
|
16
16
|
#
|
17
17
|
# IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING.
|
@@ -31,7 +31,7 @@ module ActionDispatch
|
|
31
31
|
TRUSTED_PROXIES = %r{
|
32
32
|
^127\.0\.0\.1$ | # localhost IPv4
|
33
33
|
^::1$ | # localhost IPv6
|
34
|
-
^
|
34
|
+
^[fF][cCdD] | # private IPv6 range fc00::/7
|
35
35
|
^10\. | # private IPv4 range 10.x.x.x
|
36
36
|
^172\.(1[6-9]|2[0-9]|3[0-1])\.| # private IPv4 range 172.16.0.0 .. 172.31.255.255
|
37
37
|
^192\.168\. # private IPv4 range 192.168.x.x
|
@@ -118,7 +118,7 @@ module ActionDispatch
|
|
118
118
|
#
|
119
119
|
# REMOTE_ADDR will be correct if the request is made directly against the
|
120
120
|
# Ruby process, on e.g. Heroku. When the request is proxied by another
|
121
|
-
# server like HAProxy or
|
121
|
+
# server like HAProxy or NGINX, the IP address that made the original
|
122
122
|
# request will be put in an X-Forwarded-For header. If there are multiple
|
123
123
|
# proxies, that header may contain a list of IPs. Other proxy services
|
124
124
|
# set the Client-Ip header instead, so we check that too.
|
@@ -5,7 +5,7 @@ module ActionDispatch
|
|
5
5
|
# Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through
|
6
6
|
# ActionDispatch::Request#uuid) and sends the same id to the client via the X-Request-Id header.
|
7
7
|
#
|
8
|
-
# The unique request id is either based
|
8
|
+
# The unique request id is either based on the X-Request-Id header in the request, which would typically be generated
|
9
9
|
# by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
|
10
10
|
# header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
|
11
11
|
#
|
@@ -49,7 +49,7 @@ module ActionDispatch
|
|
49
49
|
# reasonably sure that your upgrade is otherwise complete. Additionally,
|
50
50
|
# you should take care to make sure you are not relying on the ability to
|
51
51
|
# decode signed cookies generated by your app in external applications or
|
52
|
-
#
|
52
|
+
# JavaScript before upgrading.
|
53
53
|
#
|
54
54
|
# Note that changing the secret key will invalidate all existing sessions!
|
55
55
|
class CookieStore < Rack::Session::Abstract::ID
|
@@ -42,6 +42,7 @@ module ActionDispatch
|
|
42
42
|
wrapper = ExceptionWrapper.new(env, exception)
|
43
43
|
status = wrapper.status_code
|
44
44
|
env["action_dispatch.exception"] = wrapper.exception
|
45
|
+
env["action_dispatch.original_path"] = env["PATH_INFO"]
|
45
46
|
env["PATH_INFO"] = "/#{status}"
|
46
47
|
response = @exceptions_app.call(env)
|
47
48
|
response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response
|
@@ -2,6 +2,16 @@ require 'rack/utils'
|
|
2
2
|
require 'active_support/core_ext/uri'
|
3
3
|
|
4
4
|
module ActionDispatch
|
5
|
+
# This middleware returns a file's contents from disk in the body response.
|
6
|
+
# When initialized it can accept an optional 'Cache-Control' header which
|
7
|
+
# will be set when a response containing a file's contents is delivered.
|
8
|
+
#
|
9
|
+
# This middleware will render the file specified in `env["PATH_INFO"]`
|
10
|
+
# where the base path is in the +root+ directory. For example if the +root+
|
11
|
+
# is set to `public/` then a request with `env["PATH_INFO"]` of
|
12
|
+
# `assets/application.js` will return a response with contents of a file
|
13
|
+
# located at `public/assets/application.js` if the file exists. If the file
|
14
|
+
# does not exist a 404 "File not Found" response will be returned.
|
5
15
|
class FileHandler
|
6
16
|
def initialize(root, cache_control)
|
7
17
|
@root = root.chomp('/')
|
@@ -14,12 +24,11 @@ module ActionDispatch
|
|
14
24
|
path = unescape_path(path)
|
15
25
|
return false unless path.valid_encoding?
|
16
26
|
|
17
|
-
full_path = path.empty? ? @root : File.join(@root,
|
18
|
-
clean_path_info(escape_glob_chars(path)))
|
27
|
+
full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(path))
|
19
28
|
paths = "#{full_path}#{ext}"
|
20
29
|
|
21
30
|
matches = Dir[paths]
|
22
|
-
match = matches.detect { |m| File.file?(m)
|
31
|
+
match = matches.detect { |m| File.file?(m) }
|
23
32
|
if match
|
24
33
|
match.sub!(@compiled_root, '')
|
25
34
|
::Rack::Utils.escape(match)
|
@@ -42,29 +51,19 @@ module ActionDispatch
|
|
42
51
|
end
|
43
52
|
|
44
53
|
def escape_glob_chars(path)
|
45
|
-
path.gsub(/[*?{}\[\]
|
46
|
-
end
|
47
|
-
|
48
|
-
private
|
49
|
-
|
50
|
-
PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
|
51
|
-
|
52
|
-
def clean_path_info(path_info)
|
53
|
-
parts = path_info.split PATH_SEPS
|
54
|
-
|
55
|
-
clean = []
|
56
|
-
|
57
|
-
parts.each do |part|
|
58
|
-
next if part.empty? || part == '.'
|
59
|
-
part == '..' ? clean.pop : clean << part
|
60
|
-
end
|
61
|
-
|
62
|
-
clean.unshift '/' if parts.empty? || parts.first.empty?
|
63
|
-
|
64
|
-
::File.join(*clean)
|
54
|
+
path.gsub(/[*?{}\[\]]/, "\\\\\\&")
|
65
55
|
end
|
66
56
|
end
|
67
57
|
|
58
|
+
# This middleware will attempt to return the contents of a file's body from
|
59
|
+
# disk in the response. If a file is not found on disk, the request will be
|
60
|
+
# delegated to the application stack. This middleware is commonly initialized
|
61
|
+
# to serve assets from a server's `public/` directory.
|
62
|
+
#
|
63
|
+
# This middleware verifies the path to ensure that only files
|
64
|
+
# living in the root directory can be rendered. A request cannot
|
65
|
+
# produce a directory traversal using this middleware. Only 'GET' and 'HEAD'
|
66
|
+
# requests will result in a file being returned.
|
68
67
|
class Static
|
69
68
|
def initialize(app, path, cache_control=nil)
|
70
69
|
@app = app
|