actionpack 4.1.7 → 4.2.1
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 +311 -527
- 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 +11 -4
- data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
- data/lib/abstract_controller/url_for.rb +1 -1
- data/lib/action_controller/base.rb +2 -1
- data/lib/action_controller/caching/fragments.rb +7 -1
- data/lib/action_controller/caching.rb +1 -1
- data/lib/action_controller/log_subscriber.rb +26 -26
- data/lib/action_controller/metal/conditional_get.rb +37 -12
- data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
- data/lib/action_controller/metal/exceptions.rb +1 -1
- data/lib/action_controller/metal/force_ssl.rb +1 -1
- data/lib/action_controller/metal/head.rb +7 -3
- data/lib/action_controller/metal/http_authentication.rb +14 -9
- data/lib/action_controller/metal/instrumentation.rb +8 -5
- data/lib/action_controller/metal/live.rb +57 -6
- data/lib/action_controller/metal/mime_responds.rb +23 -246
- data/lib/action_controller/metal/params_wrapper.rb +2 -2
- data/lib/action_controller/metal/rack_delegation.rb +1 -1
- data/lib/action_controller/metal/redirecting.rb +14 -8
- data/lib/action_controller/metal/renderers.rb +30 -10
- data/lib/action_controller/metal/rendering.rb +2 -6
- data/lib/action_controller/metal/request_forgery_protection.rb +78 -7
- data/lib/action_controller/metal/streaming.rb +1 -1
- data/lib/action_controller/metal/strong_parameters.rb +125 -12
- data/lib/action_controller/metal/url_for.rb +11 -12
- data/lib/action_controller/metal.rb +12 -11
- data/lib/action_controller/model_naming.rb +1 -1
- data/lib/action_controller/railtie.rb +4 -0
- data/lib/action_controller/test_case.rb +112 -75
- data/lib/action_controller.rb +1 -1
- data/lib/action_dispatch/http/cache.rb +5 -4
- 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 -3
- data/lib/action_dispatch/http/mime_type.rb +2 -2
- 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 +37 -11
- data/lib/action_dispatch/http/response.rb +70 -18
- data/lib/action_dispatch/http/upload.rb +3 -8
- data/lib/action_dispatch/http/url.rb +88 -69
- data/lib/action_dispatch/journey/formatter.rb +33 -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 +20 -28
- 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/strexp.rb +9 -6
- data/lib/action_dispatch/journey/router.rb +53 -77
- data/lib/action_dispatch/journey/scanner.rb +5 -5
- data/lib/action_dispatch/journey/visitors.rb +81 -92
- data/lib/action_dispatch/journey/visualizer/fsm.css +0 -4
- data/lib/action_dispatch/journey/visualizer/index.html.erb +2 -2
- data/lib/action_dispatch/middleware/callbacks.rb +1 -1
- data/lib/action_dispatch/middleware/cookies.rb +29 -29
- data/lib/action_dispatch/middleware/debug_exceptions.rb +15 -4
- data/lib/action_dispatch/middleware/exception_wrapper.rb +50 -18
- data/lib/action_dispatch/middleware/flash.rb +13 -7
- data/lib/action_dispatch/middleware/params_parser.rb +1 -1
- data/lib/action_dispatch/middleware/public_exceptions.rb +12 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +40 -54
- 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 +66 -37
- data/lib/action_dispatch/middleware/templates/rescues/_source.erb +21 -19
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +37 -9
- 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/missing_template.html.erb +4 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +2 -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 +120 -64
- data/lib/action_dispatch/routing/endpoint.rb +10 -0
- data/lib/action_dispatch/routing/inspector.rb +5 -12
- data/lib/action_dispatch/routing/mapper.rb +410 -281
- data/lib/action_dispatch/routing/polymorphic_routes.rb +191 -79
- data/lib/action_dispatch/routing/redirection.rb +10 -12
- data/lib/action_dispatch/routing/route_set.rb +297 -168
- data/lib/action_dispatch/routing/url_for.rb +15 -4
- 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 +22 -22
- 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/assertions.rb +11 -7
- data/lib/action_dispatch/testing/integration.rb +24 -19
- data/lib/action_dispatch/testing/test_request.rb +1 -1
- data/lib/action_dispatch/testing/test_response.rb +7 -0
- data/lib/action_pack/gem_version.rb +3 -3
- metadata +55 -13
- data/lib/action_controller/metal/responder.rb +0 -297
@@ -35,14 +35,25 @@ module ActionDispatch
|
|
35
35
|
|
36
36
|
if env['action_dispatch.show_detailed_exceptions']
|
37
37
|
request = Request.new(env)
|
38
|
+
traces = wrapper.traces
|
39
|
+
|
40
|
+
trace_to_show = 'Application Trace'
|
41
|
+
if traces[trace_to_show].empty? && wrapper.rescue_template != 'routing_error'
|
42
|
+
trace_to_show = 'Full Trace'
|
43
|
+
end
|
44
|
+
|
45
|
+
if source_to_show = traces[trace_to_show].first
|
46
|
+
source_to_show_id = source_to_show[:id]
|
47
|
+
end
|
48
|
+
|
38
49
|
template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
|
39
50
|
request: request,
|
40
51
|
exception: wrapper.exception,
|
41
|
-
|
42
|
-
|
43
|
-
|
52
|
+
traces: traces,
|
53
|
+
show_source_idx: source_to_show_id,
|
54
|
+
trace_to_show: trace_to_show,
|
44
55
|
routes_inspector: routes_inspector(exception),
|
45
|
-
|
56
|
+
source_extracts: wrapper.source_extracts,
|
46
57
|
line_number: wrapper.line_number,
|
47
58
|
file: wrapper.file
|
48
59
|
)
|
@@ -6,16 +6,17 @@ 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::
|
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
|
+
'ActionController::InvalidCrossOriginRequest' => :unprocessable_entity,
|
17
|
+
'ActionDispatch::ParamsParser::ParseError' => :bad_request,
|
18
|
+
'ActionController::BadRequest' => :bad_request,
|
19
|
+
'ActionController::ParameterMissing' => :bad_request
|
19
20
|
)
|
20
21
|
|
21
22
|
cattr_accessor :rescue_templates
|
@@ -56,21 +57,52 @@ module ActionDispatch
|
|
56
57
|
clean_backtrace(:all)
|
57
58
|
end
|
58
59
|
|
60
|
+
def traces
|
61
|
+
appplication_trace_with_ids = []
|
62
|
+
framework_trace_with_ids = []
|
63
|
+
full_trace_with_ids = []
|
64
|
+
|
65
|
+
full_trace.each_with_index do |trace, idx|
|
66
|
+
trace_with_id = { id: idx, trace: trace }
|
67
|
+
|
68
|
+
if application_trace.include?(trace)
|
69
|
+
appplication_trace_with_ids << trace_with_id
|
70
|
+
else
|
71
|
+
framework_trace_with_ids << trace_with_id
|
72
|
+
end
|
73
|
+
|
74
|
+
full_trace_with_ids << trace_with_id
|
75
|
+
end
|
76
|
+
|
77
|
+
{
|
78
|
+
"Application Trace" => appplication_trace_with_ids,
|
79
|
+
"Framework Trace" => framework_trace_with_ids,
|
80
|
+
"Full Trace" => full_trace_with_ids
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
59
84
|
def self.status_code_for_exception(class_name)
|
60
85
|
Rack::Utils.status_code(@@rescue_responses[class_name])
|
61
86
|
end
|
62
87
|
|
63
|
-
def
|
64
|
-
|
65
|
-
file, line
|
66
|
-
|
67
|
-
|
68
|
-
|
88
|
+
def source_extracts
|
89
|
+
backtrace.map do |trace|
|
90
|
+
file, line = trace.split(":")
|
91
|
+
line_number = line.to_i
|
92
|
+
|
93
|
+
{
|
94
|
+
code: source_fragment(file, line_number),
|
95
|
+
line_number: line_number
|
96
|
+
}
|
69
97
|
end
|
70
98
|
end
|
71
99
|
|
72
100
|
private
|
73
101
|
|
102
|
+
def backtrace
|
103
|
+
Array(@exception.backtrace)
|
104
|
+
end
|
105
|
+
|
74
106
|
def original_exception(exception)
|
75
107
|
if registered_original_exception?(exception)
|
76
108
|
exception.original_exception
|
@@ -85,9 +117,9 @@ module ActionDispatch
|
|
85
117
|
|
86
118
|
def clean_backtrace(*args)
|
87
119
|
if backtrace_cleaner
|
88
|
-
backtrace_cleaner.clean(
|
120
|
+
backtrace_cleaner.clean(backtrace, *args)
|
89
121
|
else
|
90
|
-
|
122
|
+
backtrace
|
91
123
|
end
|
92
124
|
end
|
93
125
|
|
@@ -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
|
@@ -76,7 +79,7 @@ module ActionDispatch
|
|
76
79
|
class FlashHash
|
77
80
|
include Enumerable
|
78
81
|
|
79
|
-
def self.from_session_value(value)
|
82
|
+
def self.from_session_value(value) #:nodoc:
|
80
83
|
flash = case value
|
81
84
|
when FlashHash # Rails 3.1, 3.2
|
82
85
|
new(value.instance_variable_get(:@flashes), value.instance_variable_get(:@used))
|
@@ -88,8 +91,11 @@ module ActionDispatch
|
|
88
91
|
|
89
92
|
flash.tap(&:sweep)
|
90
93
|
end
|
91
|
-
|
92
|
-
|
94
|
+
|
95
|
+
# Builds a hash containing the discarded values and the hashes
|
96
|
+
# representing the flashes.
|
97
|
+
# If there are no values in @flashes, returns nil.
|
98
|
+
def to_session_value #:nodoc:
|
93
99
|
return nil if empty?
|
94
100
|
{'discard' => @discard.to_a, 'flashes' => @flashes}
|
95
101
|
end
|
@@ -129,7 +135,7 @@ module ActionDispatch
|
|
129
135
|
end
|
130
136
|
|
131
137
|
def key?(name)
|
132
|
-
@flashes.key? name
|
138
|
+
@flashes.key? name.to_s
|
133
139
|
end
|
134
140
|
|
135
141
|
def delete(key)
|
@@ -47,7 +47,7 @@ module ActionDispatch
|
|
47
47
|
else
|
48
48
|
false
|
49
49
|
end
|
50
|
-
rescue
|
50
|
+
rescue => e # JSON or Ruby code block errors
|
51
51
|
logger(env).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
|
52
52
|
|
53
53
|
raise ParseError.new(e.message, e)
|
@@ -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
|
|
@@ -32,9 +42,8 @@ module ActionDispatch
|
|
32
42
|
end
|
33
43
|
|
34
44
|
def render_html(status)
|
35
|
-
|
36
|
-
path = "#{public_path}/#{status}
|
37
|
-
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))
|
38
47
|
|
39
48
|
if found || File.exist?(path)
|
40
49
|
render_format(status, 'text/html', File.read(path))
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
|
1
3
|
module ActionDispatch
|
2
4
|
# This middleware calculates the IP address of the remote client that is
|
3
5
|
# making the request. It does this by checking various headers that could
|
@@ -11,7 +13,7 @@ module ActionDispatch
|
|
11
13
|
# Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
|
12
14
|
# requires. Some Rack servers simply drop preceding headers, and only report
|
13
15
|
# 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
|
16
|
+
# If you are behind multiple proxy servers (like NGINX to HAProxy to Unicorn)
|
15
17
|
# then you should test your Rack server to make sure your data is good.
|
16
18
|
#
|
17
19
|
# IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING.
|
@@ -28,14 +30,14 @@ module ActionDispatch
|
|
28
30
|
# guaranteed by the IP specification to be private addresses. Those will
|
29
31
|
# not be the ultimate client IP in production, and so are discarded. See
|
30
32
|
# http://en.wikipedia.org/wiki/Private_network for details.
|
31
|
-
TRUSTED_PROXIES =
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
}
|
33
|
+
TRUSTED_PROXIES = [
|
34
|
+
"127.0.0.1", # localhost IPv4
|
35
|
+
"::1", # localhost IPv6
|
36
|
+
"fc00::/7", # private IPv6 range fc00::/7
|
37
|
+
"10.0.0.0/8", # private IPv4 range 10.x.x.x
|
38
|
+
"172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255
|
39
|
+
"192.168.0.0/16", # private IPv4 range 192.168.x.x
|
40
|
+
].map { |proxy| IPAddr.new(proxy) }
|
39
41
|
|
40
42
|
attr_reader :check_ip, :proxies
|
41
43
|
|
@@ -47,24 +49,24 @@ module ActionDispatch
|
|
47
49
|
# clients (like WAP devices), or behind proxies that set headers in an
|
48
50
|
# incorrect or confusing way (like AWS ELB).
|
49
51
|
#
|
50
|
-
# The +custom_proxies+ argument can take
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
52
|
+
# The +custom_proxies+ argument can take an Array of string, IPAddr, or
|
53
|
+
# Regexp objects which will be used instead of +TRUSTED_PROXIES+. If a
|
54
|
+
# single string, IPAddr, or Regexp object is provided, it will be used in
|
55
|
+
# addition to +TRUSTED_PROXIES+. Any proxy setup will put the value you
|
56
|
+
# want in the middle (or at the beginning) of the X-Forwarded-For list,
|
57
|
+
# with your proxy servers after it. If your proxies aren't removed, pass
|
58
|
+
# them in via the +custom_proxies+ parameter. That way, the middleware will
|
59
|
+
# ignore those IP addresses, and return the one that you want.
|
57
60
|
def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
|
58
61
|
@app = app
|
59
62
|
@check_ip = check_ip_spoofing
|
60
|
-
@proxies =
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
63
|
+
@proxies = if custom_proxies.blank?
|
64
|
+
TRUSTED_PROXIES
|
65
|
+
elsif custom_proxies.respond_to?(:any?)
|
66
|
+
custom_proxies
|
67
|
+
else
|
68
|
+
Array(custom_proxies) + TRUSTED_PROXIES
|
69
|
+
end
|
68
70
|
end
|
69
71
|
|
70
72
|
# Since the IP address may not be needed, we store the object here
|
@@ -80,32 +82,6 @@ module ActionDispatch
|
|
80
82
|
# into an actual IP address. If the ActionDispatch::Request#remote_ip method
|
81
83
|
# is called, this class will calculate the value and then memoize it.
|
82
84
|
class GetIp
|
83
|
-
|
84
|
-
# This constant contains a regular expression that validates every known
|
85
|
-
# form of IP v4 and v6 address, with or without abbreviations, adapted
|
86
|
-
# from {this gist}[https://gist.github.com/gazay/1289635].
|
87
|
-
VALID_IP = %r{
|
88
|
-
(^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})){3}$) | # ip v4
|
89
|
-
(^(
|
90
|
-
(([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}) | # ip v6 not abbreviated
|
91
|
-
(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4}) | # ip v6 with double colon in the end
|
92
|
-
(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4}) | # - ip addresses v6
|
93
|
-
(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4}) | # - with
|
94
|
-
(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4}) | # - double colon
|
95
|
-
(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4}) | # - in the middle
|
96
|
-
(([0-9A-Fa-f]{1,4}:){6} ((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3} (\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
|
97
|
-
(([0-9A-Fa-f]{1,4}:){1,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
|
98
|
-
(([0-9A-Fa-f]{1,4}:){1}:([0-9A-Fa-f]{1,4}:){0,4}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
|
99
|
-
(([0-9A-Fa-f]{1,4}:){0,2}:([0-9A-Fa-f]{1,4}:){0,3}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
|
100
|
-
(([0-9A-Fa-f]{1,4}:){0,3}:([0-9A-Fa-f]{1,4}:){0,2}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
|
101
|
-
(([0-9A-Fa-f]{1,4}:){0,4}:([0-9A-Fa-f]{1,4}:){1}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
|
102
|
-
(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d) |(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
|
103
|
-
([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4}) | # ip v6 with compatible to v4
|
104
|
-
(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the beginning
|
105
|
-
(([0-9A-Fa-f]{1,4}:){1,7}:) # ip v6 without ending
|
106
|
-
)$)
|
107
|
-
}x
|
108
|
-
|
109
85
|
def initialize(env, middleware)
|
110
86
|
@env = env
|
111
87
|
@check_ip = middleware.check_ip
|
@@ -118,7 +94,7 @@ module ActionDispatch
|
|
118
94
|
#
|
119
95
|
# REMOTE_ADDR will be correct if the request is made directly against the
|
120
96
|
# Ruby process, on e.g. Heroku. When the request is proxied by another
|
121
|
-
# server like HAProxy or
|
97
|
+
# server like HAProxy or NGINX, the IP address that made the original
|
122
98
|
# request will be put in an X-Forwarded-For header. If there are multiple
|
123
99
|
# proxies, that header may contain a list of IPs. Other proxy services
|
124
100
|
# set the Client-Ip header instead, so we check that too.
|
@@ -173,12 +149,22 @@ module ActionDispatch
|
|
173
149
|
def ips_from(header)
|
174
150
|
# Split the comma-separated list into an array of strings
|
175
151
|
ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
|
176
|
-
|
177
|
-
|
152
|
+
ips.select do |ip|
|
153
|
+
begin
|
154
|
+
# Only return IPs that are valid according to the IPAddr#new method
|
155
|
+
range = IPAddr.new(ip).to_range
|
156
|
+
# we want to make sure nobody is sneaking a netmask in
|
157
|
+
range.begin == range.end
|
158
|
+
rescue ArgumentError
|
159
|
+
nil
|
160
|
+
end
|
161
|
+
end
|
178
162
|
end
|
179
163
|
|
180
164
|
def filter_proxies(ips)
|
181
|
-
ips.reject
|
165
|
+
ips.reject do |ip|
|
166
|
+
@proxies.any? { |proxy| proxy === ip }
|
167
|
+
end
|
182
168
|
end
|
183
169
|
|
184
170
|
end
|
@@ -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,69 +2,98 @@ 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('/')
|
8
18
|
@compiled_root = /^#{Regexp.escape(root)}/
|
9
|
-
headers
|
10
|
-
@file_server
|
19
|
+
headers = cache_control && { 'Cache-Control' => cache_control }
|
20
|
+
@file_server = ::Rack::File.new(@root, headers)
|
11
21
|
end
|
12
22
|
|
13
23
|
def match?(path)
|
14
|
-
path =
|
24
|
+
path = URI.parser.unescape(path)
|
15
25
|
return false unless path.valid_encoding?
|
16
26
|
|
17
|
-
|
18
|
-
clean_path_info
|
19
|
-
|
27
|
+
paths = [path, "#{path}#{ext}", "#{path}/index#{ext}"].map { |v|
|
28
|
+
Rack::Utils.clean_path_info v
|
29
|
+
}
|
20
30
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
31
|
+
if match = paths.detect { |p|
|
32
|
+
path = File.join(@root, p)
|
33
|
+
begin
|
34
|
+
File.file?(path) && File.readable?(path)
|
35
|
+
rescue SystemCallError
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
}
|
40
|
+
return ::Rack::Utils.escape(match)
|
26
41
|
end
|
27
42
|
end
|
28
43
|
|
29
44
|
def call(env)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
45
|
+
path = env['PATH_INFO']
|
46
|
+
gzip_path = gzip_file_path(path)
|
47
|
+
|
48
|
+
if gzip_path && gzip_encoding_accepted?(env)
|
49
|
+
env['PATH_INFO'] = gzip_path
|
50
|
+
status, headers, body = @file_server.call(env)
|
51
|
+
headers['Content-Encoding'] = 'gzip'
|
52
|
+
headers['Content-Type'] = content_type(path)
|
53
|
+
else
|
54
|
+
status, headers, body = @file_server.call(env)
|
37
55
|
end
|
38
|
-
end
|
39
56
|
|
40
|
-
|
41
|
-
URI.parser.unescape(path)
|
42
|
-
end
|
57
|
+
headers['Vary'] = 'Accept-Encoding' if gzip_path
|
43
58
|
|
44
|
-
|
45
|
-
|
59
|
+
return [status, headers, body]
|
60
|
+
ensure
|
61
|
+
env['PATH_INFO'] = path
|
46
62
|
end
|
47
63
|
|
48
64
|
private
|
65
|
+
def ext
|
66
|
+
::ActionController::Base.default_static_extension
|
67
|
+
end
|
49
68
|
|
50
|
-
|
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
|
69
|
+
def content_type(path)
|
70
|
+
::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
|
60
71
|
end
|
61
72
|
|
62
|
-
|
73
|
+
def gzip_encoding_accepted?(env)
|
74
|
+
env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/i
|
75
|
+
end
|
63
76
|
|
64
|
-
|
65
|
-
|
77
|
+
def gzip_file_path(path)
|
78
|
+
can_gzip_mime = content_type(path) =~ /\A(?:text\/|application\/javascript)/
|
79
|
+
gzip_path = "#{path}.gz"
|
80
|
+
if can_gzip_mime && File.exist?(File.join(@root, ::Rack::Utils.unescape(gzip_path)))
|
81
|
+
gzip_path
|
82
|
+
else
|
83
|
+
false
|
84
|
+
end
|
85
|
+
end
|
66
86
|
end
|
67
87
|
|
88
|
+
# This middleware will attempt to return the contents of a file's body from
|
89
|
+
# disk in the response. If a file is not found on disk, the request will be
|
90
|
+
# delegated to the application stack. This middleware is commonly initialized
|
91
|
+
# to serve assets from a server's `public/` directory.
|
92
|
+
#
|
93
|
+
# This middleware verifies the path to ensure that only files
|
94
|
+
# living in the root directory can be rendered. A request cannot
|
95
|
+
# produce a directory traversal using this middleware. Only 'GET' and 'HEAD'
|
96
|
+
# requests will result in a file being returned.
|
68
97
|
class Static
|
69
98
|
def initialize(app, path, cache_control=nil)
|
70
99
|
@app = app
|