actionpack 6.1.7.5 → 7.1.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +355 -435
  3. data/MIT-LICENSE +2 -1
  4. data/README.rdoc +6 -7
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +33 -37
  7. data/lib/abstract_controller/caching/fragments.rb +4 -2
  8. data/lib/abstract_controller/caching.rb +1 -1
  9. data/lib/abstract_controller/callbacks.rb +50 -11
  10. data/lib/abstract_controller/collector.rb +2 -2
  11. data/lib/abstract_controller/deprecator.rb +7 -0
  12. data/lib/abstract_controller/error.rb +1 -1
  13. data/lib/abstract_controller/helpers.rb +78 -30
  14. data/lib/abstract_controller/logger.rb +1 -1
  15. data/lib/abstract_controller/railties/routes_helpers.rb +3 -16
  16. data/lib/abstract_controller/rendering.rb +12 -14
  17. data/lib/abstract_controller/translation.rb +26 -7
  18. data/lib/abstract_controller/url_for.rb +6 -6
  19. data/lib/abstract_controller.rb +6 -0
  20. data/lib/action_controller/api.rb +12 -10
  21. data/lib/action_controller/base.rb +8 -21
  22. data/lib/action_controller/caching.rb +2 -0
  23. data/lib/action_controller/deprecator.rb +7 -0
  24. data/lib/action_controller/form_builder.rb +4 -2
  25. data/lib/action_controller/log_subscriber.rb +20 -7
  26. data/lib/action_controller/metal/basic_implicit_render.rb +3 -1
  27. data/lib/action_controller/metal/conditional_get.rb +137 -102
  28. data/lib/action_controller/metal/content_security_policy.rb +37 -3
  29. data/lib/action_controller/metal/cookies.rb +1 -1
  30. data/lib/action_controller/metal/data_streaming.rb +25 -31
  31. data/lib/action_controller/metal/default_headers.rb +2 -0
  32. data/lib/action_controller/metal/etag_with_flash.rb +3 -1
  33. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
  34. data/lib/action_controller/metal/exceptions.rb +27 -30
  35. data/lib/action_controller/metal/flash.rb +6 -2
  36. data/lib/action_controller/metal/head.rb +9 -7
  37. data/lib/action_controller/metal/helpers.rb +5 -16
  38. data/lib/action_controller/metal/http_authentication.rb +78 -42
  39. data/lib/action_controller/metal/implicit_render.rb +5 -3
  40. data/lib/action_controller/metal/instrumentation.rb +62 -50
  41. data/lib/action_controller/metal/live.rb +67 -2
  42. data/lib/action_controller/metal/mime_responds.rb +5 -5
  43. data/lib/action_controller/metal/params_wrapper.rb +24 -13
  44. data/lib/action_controller/metal/permissions_policy.rb +20 -29
  45. data/lib/action_controller/metal/redirecting.rb +96 -23
  46. data/lib/action_controller/metal/renderers.rb +14 -15
  47. data/lib/action_controller/metal/rendering.rb +121 -16
  48. data/lib/action_controller/metal/request_forgery_protection.rb +208 -68
  49. data/lib/action_controller/metal/rescue.rb +7 -4
  50. data/lib/action_controller/metal/streaming.rb +74 -36
  51. data/lib/action_controller/metal/strong_parameters.rb +254 -151
  52. data/lib/action_controller/metal/testing.rb +9 -2
  53. data/lib/action_controller/metal/url_for.rb +10 -5
  54. data/lib/action_controller/metal.rb +89 -34
  55. data/lib/action_controller/railtie.rb +66 -9
  56. data/lib/action_controller/renderer.rb +99 -85
  57. data/lib/action_controller/test_case.rb +42 -11
  58. data/lib/action_controller.rb +10 -6
  59. data/lib/action_dispatch/constants.rb +32 -0
  60. data/lib/action_dispatch/deprecator.rb +7 -0
  61. data/lib/action_dispatch/http/cache.rb +21 -16
  62. data/lib/action_dispatch/http/content_security_policy.rb +122 -44
  63. data/lib/action_dispatch/http/filter_parameters.rb +14 -23
  64. data/lib/action_dispatch/http/headers.rb +3 -1
  65. data/lib/action_dispatch/http/mime_negotiation.rb +25 -15
  66. data/lib/action_dispatch/http/mime_type.rb +43 -22
  67. data/lib/action_dispatch/http/mime_types.rb +3 -1
  68. data/lib/action_dispatch/http/parameters.rb +6 -6
  69. data/lib/action_dispatch/http/permissions_policy.rb +57 -19
  70. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  71. data/lib/action_dispatch/http/request.rb +75 -51
  72. data/lib/action_dispatch/http/response.rb +81 -77
  73. data/lib/action_dispatch/http/upload.rb +15 -2
  74. data/lib/action_dispatch/http/url.rb +11 -19
  75. data/lib/action_dispatch/journey/formatter.rb +8 -2
  76. data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
  77. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
  78. data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
  79. data/lib/action_dispatch/journey/nodes/node.rb +70 -5
  80. data/lib/action_dispatch/journey/path/pattern.rb +36 -27
  81. data/lib/action_dispatch/journey/route.rb +8 -14
  82. data/lib/action_dispatch/journey/router/utils.rb +2 -2
  83. data/lib/action_dispatch/journey/router.rb +10 -9
  84. data/lib/action_dispatch/journey/routes.rb +5 -5
  85. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  86. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  87. data/lib/action_dispatch/log_subscriber.rb +23 -0
  88. data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -7
  89. data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
  90. data/lib/action_dispatch/middleware/callbacks.rb +2 -0
  91. data/lib/action_dispatch/middleware/cookies.rb +97 -107
  92. data/lib/action_dispatch/middleware/debug_exceptions.rb +31 -28
  93. data/lib/action_dispatch/middleware/debug_locks.rb +7 -4
  94. data/lib/action_dispatch/middleware/debug_view.rb +7 -2
  95. data/lib/action_dispatch/middleware/exception_wrapper.rb +190 -27
  96. data/lib/action_dispatch/middleware/executor.rb +3 -0
  97. data/lib/action_dispatch/middleware/flash.rb +24 -18
  98. data/lib/action_dispatch/middleware/host_authorization.rb +19 -20
  99. data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
  100. data/lib/action_dispatch/middleware/reloader.rb +7 -5
  101. data/lib/action_dispatch/middleware/remote_ip.rb +32 -19
  102. data/lib/action_dispatch/middleware/request_id.rb +5 -3
  103. data/lib/action_dispatch/middleware/server_timing.rb +76 -0
  104. data/lib/action_dispatch/middleware/session/abstract_store.rb +6 -1
  105. data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
  106. data/lib/action_dispatch/middleware/session/cookie_store.rb +19 -13
  107. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
  108. data/lib/action_dispatch/middleware/show_exceptions.rb +30 -25
  109. data/lib/action_dispatch/middleware/ssl.rb +18 -6
  110. data/lib/action_dispatch/middleware/stack.rb +34 -11
  111. data/lib/action_dispatch/middleware/static.rb +16 -16
  112. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  113. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +5 -5
  114. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
  115. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  116. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
  117. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +10 -5
  118. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -3
  119. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +9 -9
  120. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  121. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
  122. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +45 -18
  123. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -15
  124. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +4 -4
  125. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +6 -6
  126. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +7 -7
  127. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  128. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  129. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  130. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +64 -55
  131. data/lib/action_dispatch/railtie.rb +20 -4
  132. data/lib/action_dispatch/request/session.rb +59 -19
  133. data/lib/action_dispatch/request/utils.rb +8 -3
  134. data/lib/action_dispatch/routing/inspector.rb +55 -7
  135. data/lib/action_dispatch/routing/mapper.rb +117 -107
  136. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
  137. data/lib/action_dispatch/routing/redirection.rb +20 -8
  138. data/lib/action_dispatch/routing/route_set.rb +67 -27
  139. data/lib/action_dispatch/routing/routes_proxy.rb +11 -16
  140. data/lib/action_dispatch/routing/url_for.rb +29 -26
  141. data/lib/action_dispatch/routing.rb +12 -13
  142. data/lib/action_dispatch/system_test_case.rb +8 -8
  143. data/lib/action_dispatch/system_testing/browser.rb +20 -29
  144. data/lib/action_dispatch/system_testing/driver.rb +34 -18
  145. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +35 -20
  146. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
  147. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  148. data/lib/action_dispatch/testing/assertions/response.rb +14 -7
  149. data/lib/action_dispatch/testing/assertions/routing.rb +70 -30
  150. data/lib/action_dispatch/testing/assertions.rb +3 -4
  151. data/lib/action_dispatch/testing/integration.rb +33 -25
  152. data/lib/action_dispatch/testing/request_encoder.rb +4 -1
  153. data/lib/action_dispatch/testing/test_process.rb +5 -30
  154. data/lib/action_dispatch/testing/test_request.rb +1 -1
  155. data/lib/action_dispatch/testing/test_response.rb +34 -2
  156. data/lib/action_dispatch.rb +38 -4
  157. data/lib/action_pack/gem_version.rb +4 -4
  158. data/lib/action_pack/version.rb +1 -1
  159. data/lib/action_pack.rb +1 -1
  160. metadata +67 -30
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "action_dispatch/http/request"
4
-
5
3
  module ActionDispatch
4
+ # = Action Dispatch \HostAuthorization
5
+ #
6
6
  # This middleware guards from DNS rebinding attacks by explicitly permitting
7
7
  # the hosts a request can be sent to, and is passed the options set in
8
8
  # +config.host_authorization+.
@@ -20,6 +20,7 @@ module ActionDispatch
20
20
  class HostAuthorization
21
21
  ALLOWED_HOSTS_IN_DEVELOPMENT = [".localhost", IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0")]
22
22
  PORT_REGEX = /(?::\d+)/ # :nodoc:
23
+ SUBDOMAIN_REGEX = /(?:[a-z0-9-]+\.)/i # :nodoc:
23
24
  IPV4_HOSTNAME = /(?<host>\d+\.\d+\.\d+\.\d+)#{PORT_REGEX}?/ # :nodoc:
24
25
  IPV6_HOSTNAME = /(?<host>[a-f0-9]*:[a-f0-9.:]+)/i # :nodoc:
25
26
  IPV6_HOSTNAME_WITH_PORT = /\[#{IPV6_HOSTNAME}\]#{PORT_REGEX}/i # :nodoc:
@@ -71,7 +72,7 @@ module ActionDispatch
71
72
 
72
73
  def sanitize_string(host)
73
74
  if host.start_with?(".")
74
- /\A([a-z0-9-]+\.)?#{Regexp.escape(host[1..-1])}#{PORT_REGEX}?\z/i
75
+ /\A#{SUBDOMAIN_REGEX}?#{Regexp.escape(host[1..-1])}#{PORT_REGEX}?\z/i
75
76
  else
76
77
  /\A#{Regexp.escape host}#{PORT_REGEX}?\z/i
77
78
  end
@@ -97,14 +98,14 @@ module ActionDispatch
97
98
  def response_body(request)
98
99
  return "" unless request.get_header("action_dispatch.show_detailed_exceptions")
99
100
 
100
- template = DebugView.new(host: request.host)
101
+ template = DebugView.new(hosts: request.env["action_dispatch.blocked_hosts"])
101
102
  template.render(template: "rescues/blocked_host", layout: "rescues/layout")
102
103
  end
103
104
 
104
105
  def response(format, body)
105
106
  [RESPONSE_STATUS,
106
- { "Content-Type" => "#{format}; charset=#{Response.default_charset}",
107
- "Content-Length" => body.bytesize.to_s },
107
+ { Rack::CONTENT_TYPE => "#{format}; charset=#{Response.default_charset}",
108
+ Rack::CONTENT_LENGTH => body.bytesize.to_s },
108
109
  [body]]
109
110
  end
110
111
 
@@ -113,7 +114,7 @@ module ActionDispatch
113
114
 
114
115
  return unless logger
115
116
 
116
- logger.error("[#{self.class.name}] Blocked host: #{request.host}")
117
+ logger.error("[#{self.class.name}] Blocked hosts: #{request.env["action_dispatch.blocked_hosts"].join(", ")}")
117
118
  end
118
119
 
119
120
  def available_logger(request)
@@ -121,20 +122,11 @@ module ActionDispatch
121
122
  end
122
123
  end
123
124
 
124
- def initialize(app, hosts, deprecated_response_app = nil, exclude: nil, response_app: nil)
125
+ def initialize(app, hosts, exclude: nil, response_app: nil)
125
126
  @app = app
126
127
  @permissions = Permissions.new(hosts)
127
128
  @exclude = exclude
128
129
 
129
- unless deprecated_response_app.nil?
130
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
131
- `action_dispatch.hosts_response_app` is deprecated and will be ignored in Rails 7.0.
132
- Use the Host Authorization `response_app` setting instead.
133
- MSG
134
-
135
- response_app ||= deprecated_response_app
136
- end
137
-
138
130
  @response_app = response_app || DefaultResponseApp.new
139
131
  end
140
132
 
@@ -142,21 +134,28 @@ module ActionDispatch
142
134
  return @app.call(env) if @permissions.empty?
143
135
 
144
136
  request = Request.new(env)
137
+ hosts = blocked_hosts(request)
145
138
 
146
- if authorized?(request) || excluded?(request)
139
+ if hosts.empty? || excluded?(request)
147
140
  mark_as_authorized(request)
148
141
  @app.call(env)
149
142
  else
143
+ env["action_dispatch.blocked_hosts"] = hosts
150
144
  @response_app.call(env)
151
145
  end
152
146
  end
153
147
 
154
148
  private
155
- def authorized?(request)
149
+ def blocked_hosts(request)
150
+ hosts = []
151
+
156
152
  origin_host = request.get_header("HTTP_HOST")
153
+ hosts << origin_host unless @permissions.allows?(origin_host)
154
+
157
155
  forwarded_host = request.x_forwarded_host&.split(/,\s?/)&.last
156
+ hosts << forwarded_host unless forwarded_host.blank? || @permissions.allows?(forwarded_host)
158
157
 
159
- @permissions.allows?(origin_host) && (forwarded_host.blank? || @permissions.allows?(forwarded_host))
158
+ hosts
160
159
  end
161
160
 
162
161
  def excluded?(request)
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionDispatch
4
+ # = Action Dispatch \PublicExceptions
5
+ #
4
6
  # When called, this middleware renders an error page. By default if an HTML
5
7
  # response is expected it will render static error pages from the <tt>/public</tt>
6
8
  # directory. For example when this middleware receives a 500 response it will
@@ -42,8 +44,8 @@ module ActionDispatch
42
44
  end
43
45
 
44
46
  def render_format(status, content_type, body)
45
- [status, { "Content-Type" => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
46
- "Content-Length" => body.bytesize.to_s }, [body]]
47
+ [status, { Rack::CONTENT_TYPE => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
48
+ Rack::CONTENT_LENGTH => body.bytesize.to_s }, [body]]
47
49
  end
48
50
 
49
51
  def render_html(status)
@@ -53,7 +55,7 @@ module ActionDispatch
53
55
  if found || File.exist?(path)
54
56
  render_format(status, "text/html", File.read(path))
55
57
  else
56
- [404, { "X-Cascade" => "pass" }, []]
58
+ [404, { Constants::X_CASCADE => "pass" }, []]
57
59
  end
58
60
  end
59
61
  end
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionDispatch
4
- # ActionDispatch::Reloader wraps the request with callbacks provided by ActiveSupport::Reloader
5
- # callbacks, intended to assist with code reloading during development.
4
+ # = Action Dispatch \Reloader
6
5
  #
7
- # By default, ActionDispatch::Reloader is included in the middleware stack
8
- # only in the development environment; specifically, when +config.cache_classes+
9
- # is false.
6
+ # ActionDispatch::Reloader wraps the request with callbacks provided by
7
+ # ActiveSupport::Reloader, intended to assist with code reloading during
8
+ # development.
9
+ #
10
+ # ActionDispatch::Reloader is included in the middleware stack only if
11
+ # reloading is enabled, which it is by the default in +development+ mode.
10
12
  class Reloader < Executor
11
13
  end
12
14
  end
@@ -3,14 +3,14 @@
3
3
  require "ipaddr"
4
4
 
5
5
  module ActionDispatch
6
+ # = Action Dispatch \RemoteIp
7
+ #
6
8
  # This middleware calculates the IP address of the remote client that is
7
9
  # making the request. It does this by checking various headers that could
8
10
  # contain the address, and then picking the last-set address that is not
9
11
  # on the list of trusted IPs. This follows the precedent set by e.g.
10
- # {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453],
11
- # with {reasoning explained at length}[https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection]
12
- # by @gingerlime. A more detailed explanation of the algorithm is given
13
- # at GetIp#calculate_ip.
12
+ # {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453].
13
+ # A more detailed explanation of the algorithm is given at GetIp#calculate_ip.
14
14
  #
15
15
  # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
16
16
  # requires. Some Rack servers simply drop preceding headers, and only report
@@ -22,9 +22,10 @@ module ActionDispatch
22
22
  # This middleware assumes that there is at least one proxy sitting around
23
23
  # and setting headers with the client's remote IP address. If you don't use
24
24
  # a proxy, because you are hosted on e.g. Heroku without SSL, any client can
25
- # claim to have any IP address by setting the X-Forwarded-For header. If you
25
+ # claim to have any IP address by setting the +X-Forwarded-For+ header. If you
26
26
  # care about that, then you need to explicitly drop or ignore those headers
27
- # sometime before this middleware runs.
27
+ # sometime before this middleware runs. Alternatively, remove this middleware
28
+ # to avoid inadvertently relying on it.
28
29
  class RemoteIp
29
30
  class IpSpoofAttackError < StandardError; end
30
31
 
@@ -51,11 +52,9 @@ module ActionDispatch
51
52
  # clients (like WAP devices), or behind proxies that set headers in an
52
53
  # incorrect or confusing way (like AWS ELB).
53
54
  #
54
- # The +custom_proxies+ argument can take an Array of string, IPAddr, or
55
- # Regexp objects which will be used instead of +TRUSTED_PROXIES+. If a
56
- # single string, IPAddr, or Regexp object is provided, it will be used in
57
- # addition to +TRUSTED_PROXIES+. Any proxy setup will put the value you
58
- # want in the middle (or at the beginning) of the X-Forwarded-For list,
55
+ # The +custom_proxies+ argument can take an enumerable which will be used
56
+ # instead of +TRUSTED_PROXIES+. Any proxy setup will put the value you
57
+ # want in the middle (or at the beginning) of the +X-Forwarded-For+ list,
59
58
  # with your proxy servers after it. If your proxies aren't removed, pass
60
59
  # them in via the +custom_proxies+ parameter. That way, the middleware will
61
60
  # ignore those IP addresses, and return the one that you want.
@@ -67,7 +66,19 @@ module ActionDispatch
67
66
  elsif custom_proxies.respond_to?(:any?)
68
67
  custom_proxies
69
68
  else
70
- Array(custom_proxies) + TRUSTED_PROXIES
69
+ raise(ArgumentError, <<~EOM)
70
+ Setting config.action_dispatch.trusted_proxies to a single value isn't
71
+ supported. Please set this to an enumerable instead. For
72
+ example, instead of:
73
+
74
+ config.action_dispatch.trusted_proxies = IPAddr.new("10.0.0.0/8")
75
+
76
+ Wrap the value in an Array:
77
+
78
+ config.action_dispatch.trusted_proxies = [IPAddr.new("10.0.0.0/8")]
79
+
80
+ Note that passing an enumerable will *replace* the default set of trusted proxies.
81
+ EOM
71
82
  end
72
83
  end
73
84
 
@@ -98,11 +109,11 @@ module ActionDispatch
98
109
  # REMOTE_ADDR will be correct if the request is made directly against the
99
110
  # Ruby process, on e.g. Heroku. When the request is proxied by another
100
111
  # server like HAProxy or NGINX, the IP address that made the original
101
- # request will be put in an X-Forwarded-For header. If there are multiple
112
+ # request will be put in an +X-Forwarded-For+ header. If there are multiple
102
113
  # proxies, that header may contain a list of IPs. Other proxy services
103
- # set the Client-Ip header instead, so we check that too.
114
+ # set the +Client-Ip+ header instead, so we check that too.
104
115
  #
105
- # As discussed in {this post about Rails IP Spoofing}[https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
116
+ # As discussed in {this post about Rails IP Spoofing}[https://web.archive.org/web/20170626095448/https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
106
117
  # while the first IP in the list is likely to be the "originating" IP,
107
118
  # it could also have been set by the client maliciously.
108
119
  #
@@ -114,8 +125,8 @@ module ActionDispatch
114
125
  remote_addr = ips_from(@req.remote_addr).last
115
126
 
116
127
  # Could be a CSV list and/or repeated headers that were concatenated.
117
- client_ips = ips_from(@req.client_ip).reverse
118
- forwarded_ips = ips_from(@req.x_forwarded_for).reverse
128
+ client_ips = ips_from(@req.client_ip).reverse!
129
+ forwarded_ips = ips_from(@req.x_forwarded_for).reverse!
119
130
 
120
131
  # +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set.
121
132
  # If they are both set, it means that either:
@@ -143,7 +154,8 @@ module ActionDispatch
143
154
  # - X-Forwarded-For will be a list of IPs, one per proxy, or blank
144
155
  # - Client-Ip is propagated from the outermost proxy, or is blank
145
156
  # - REMOTE_ADDR will be the IP that made the request to Rack
146
- ips = [forwarded_ips, client_ips].flatten.compact
157
+ ips = forwarded_ips + client_ips
158
+ ips.compact!
147
159
 
148
160
  # If every single IP option is in the trusted list, return the IP
149
161
  # that's furthest away
@@ -161,7 +173,7 @@ module ActionDispatch
161
173
  return [] unless header
162
174
  # Split the comma-separated list into an array of strings.
163
175
  ips = header.strip.split(/[,\s]+/)
164
- ips.select do |ip|
176
+ ips.select! do |ip|
165
177
  # Only return IPs that are valid according to the IPAddr#new method.
166
178
  range = IPAddr.new(ip).to_range
167
179
  # We want to make sure nobody is sneaking a netmask in.
@@ -169,6 +181,7 @@ module ActionDispatch
169
181
  rescue ArgumentError
170
182
  nil
171
183
  end
184
+ ips
172
185
  end
173
186
 
174
187
  def filter_proxies(ips) # :doc:
@@ -4,11 +4,13 @@ require "securerandom"
4
4
  require "active_support/core_ext/string/access"
5
5
 
6
6
  module ActionDispatch
7
+ # = Action Dispatch \RequestId
8
+ #
7
9
  # Makes a unique request id available to the +action_dispatch.request_id+ env variable (which is then accessible
8
- # through <tt>ActionDispatch::Request#request_id</tt> or the alias <tt>ActionDispatch::Request#uuid</tt>) and sends
9
- # the same id to the client via the X-Request-Id header.
10
+ # through ActionDispatch::Request#request_id or the alias ActionDispatch::Request#uuid) and sends
11
+ # the same id to the client via the +X-Request-Id+ header.
10
12
  #
11
- # The unique request id is either based on the X-Request-Id header in the request, which would typically be generated
13
+ # The unique request id is either based on the +X-Request-Id+ header in the request, which would typically be generated
12
14
  # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
13
15
  # header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
14
16
  #
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/notifications"
4
+
5
+ module ActionDispatch
6
+ class ServerTiming
7
+ class Subscriber # :nodoc:
8
+ include Singleton
9
+ KEY = :action_dispatch_server_timing_events
10
+
11
+ def initialize
12
+ @mutex = Mutex.new
13
+ end
14
+
15
+ def call(event)
16
+ if events = ActiveSupport::IsolatedExecutionState[KEY]
17
+ events << event
18
+ end
19
+ end
20
+
21
+ def collect_events
22
+ events = []
23
+ ActiveSupport::IsolatedExecutionState[KEY] = events
24
+ yield
25
+ events
26
+ ensure
27
+ ActiveSupport::IsolatedExecutionState.delete(KEY)
28
+ end
29
+
30
+ def ensure_subscribed
31
+ @mutex.synchronize do
32
+ # Subscribe to all events, except those beginning with "!"
33
+ # Ideally we would be more selective of what is being measured
34
+ @subscriber ||= ActiveSupport::Notifications.subscribe(/\A[^!]/, self)
35
+ end
36
+ end
37
+
38
+ def unsubscribe
39
+ @mutex.synchronize do
40
+ ActiveSupport::Notifications.unsubscribe @subscriber
41
+ @subscriber = nil
42
+ end
43
+ end
44
+ end
45
+
46
+ def self.unsubscribe # :nodoc:
47
+ Subscriber.instance.unsubscribe
48
+ end
49
+
50
+ def initialize(app)
51
+ @app = app
52
+ @subscriber = Subscriber.instance
53
+ @subscriber.ensure_subscribed
54
+ end
55
+
56
+ def call(env)
57
+ response = nil
58
+ events = @subscriber.collect_events do
59
+ response = @app.call(env)
60
+ end
61
+
62
+ headers = response[1]
63
+
64
+ header_info = events.group_by(&:name).map do |event_name, events_collection|
65
+ "%s;dur=%.2f" % [event_name, events_collection.sum(&:duration)]
66
+ end
67
+
68
+ if headers[ActionDispatch::Constants::SERVER_TIMING].present?
69
+ header_info.prepend(headers[ActionDispatch::Constants::SERVER_TIMING])
70
+ end
71
+ headers[ActionDispatch::Constants::SERVER_TIMING] = header_info.join(", ")
72
+
73
+ response
74
+ end
75
+ end
76
+ end
@@ -8,7 +8,7 @@ require "action_dispatch/request/session"
8
8
 
9
9
  module ActionDispatch
10
10
  module Session
11
- class SessionRestoreError < StandardError #:nodoc:
11
+ class SessionRestoreError < StandardError # :nodoc:
12
12
  def initialize
13
13
  super("Session contains objects whose class definition isn't available.\n" \
14
14
  "Remember to require the classes for all objects kept in the session.\n" \
@@ -67,6 +67,11 @@ module ActionDispatch
67
67
  end
68
68
 
69
69
  module SessionObject # :nodoc:
70
+ def commit_session(req, res)
71
+ req.commit_csrf_token
72
+ super(req, res)
73
+ end
74
+
70
75
  def prepare_session(req)
71
76
  Request::Session.create(self, req, @default_options)
72
77
  end
@@ -4,6 +4,8 @@ require "action_dispatch/middleware/session/abstract_store"
4
4
 
5
5
  module ActionDispatch
6
6
  module Session
7
+ # = Action Dispatch Session \CacheStore
8
+ #
7
9
  # A session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful
8
10
  # if you don't store critical data in your sessions and you don't need them to live for extended periods
9
11
  # of time.
@@ -6,46 +6,48 @@ require "rack/session/cookie"
6
6
 
7
7
  module ActionDispatch
8
8
  module Session
9
- # This cookie-based session store is the Rails default. It is
9
+ # = Action Dispatch Session \CookieStore
10
+ #
11
+ # This cookie-based session store is the \Rails default. It is
10
12
  # dramatically faster than the alternatives.
11
13
  #
12
- # Sessions typically contain at most a user_id and flash message; both fit
13
- # within the 4096 bytes cookie size limit. A CookieOverflow exception is raised if
14
+ # Sessions typically contain at most a user ID and flash message; both fit
15
+ # within the 4096 bytes cookie size limit. A +CookieOverflow+ exception is raised if
14
16
  # you attempt to store more than 4096 bytes of data.
15
17
  #
16
18
  # The cookie jar used for storage is automatically configured to be the
17
19
  # best possible option given your application's configuration.
18
20
  #
19
- # Your cookies will be encrypted using your apps secret_key_base. This
21
+ # Your cookies will be encrypted using your application's +secret_key_base+. This
20
22
  # goes a step further than signed cookies in that encrypted cookies cannot
21
- # be altered or read by users. This is the default starting in Rails 4.
23
+ # be altered or read by users. This is the default starting in \Rails 4.
22
24
  #
23
25
  # Configure your session store in an initializer:
24
26
  #
25
27
  # Rails.application.config.session_store :cookie_store, key: '_your_app_session'
26
28
  #
27
- # In the development and test environments your application's secret key base is
28
- # generated by Rails and stored in a temporary file in <tt>tmp/development_secret.txt</tt>.
29
+ # In the development and test environments your application's +secret_key_base+ is
30
+ # generated by \Rails and stored in a temporary file in <tt>tmp/local_secret.txt</tt>.
29
31
  # In all other environments, it is stored encrypted in the
30
32
  # <tt>config/credentials.yml.enc</tt> file.
31
33
  #
32
- # If your application was not updated to Rails 5.2 defaults, the secret_key_base
34
+ # If your application was not updated to \Rails 5.2 defaults, the +secret_key_base+
33
35
  # will be found in the old <tt>config/secrets.yml</tt> file.
34
36
  #
35
- # Note that changing your secret_key_base will invalidate all existing session.
37
+ # Note that changing your +secret_key_base+ will invalidate all existing session.
36
38
  # Additionally, you should take care to make sure you are not relying on the
37
39
  # ability to decode signed cookies generated by your app in external
38
40
  # applications or JavaScript before changing it.
39
41
  #
40
- # Because CookieStore extends Rack::Session::Abstract::Persisted, many of the
42
+ # Because CookieStore extends +Rack::Session::Abstract::Persisted+, many of the
41
43
  # options described there can be used to customize the session cookie that
42
44
  # is generated. For example:
43
45
  #
44
46
  # Rails.application.config.session_store :cookie_store, expire_after: 14.days
45
47
  #
46
48
  # would set the session cookie to expire automatically 14 days after creation.
47
- # Other useful options include <tt>:key</tt>, <tt>:secure</tt> and
48
- # <tt>:httponly</tt>.
49
+ # Other useful options include <tt>:key</tt>, <tt>:secure</tt>,
50
+ # <tt>:httponly</tt>, and <tt>:same_site</tt>.
49
51
  class CookieStore < AbstractSecureStore
50
52
  class SessionId < DelegateClass(Rack::Session::SessionId)
51
53
  attr_reader :cookie_value
@@ -56,8 +58,12 @@ module ActionDispatch
56
58
  end
57
59
  end
58
60
 
61
+ DEFAULT_SAME_SITE = proc { |request| request.cookies_same_site_protection } # :nodoc:
62
+
59
63
  def initialize(app, options = {})
60
- super(app, options.merge!(cookie_only: true))
64
+ options[:cookie_only] = true
65
+ options[:same_site] = DEFAULT_SAME_SITE if !options.key?(:same_site)
66
+ super
61
67
  end
62
68
 
63
69
  def delete_session(req, session_id, options)
@@ -4,12 +4,14 @@ require "action_dispatch/middleware/session/abstract_store"
4
4
  begin
5
5
  require "rack/session/dalli"
6
6
  rescue LoadError => e
7
- $stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
7
+ warn "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
8
8
  raise e
9
9
  end
10
10
 
11
11
  module ActionDispatch
12
12
  module Session
13
+ # = Action Dispatch Session \MemCacheStore
14
+ #
13
15
  # A session store that uses MemCache to implement storage.
14
16
  #
15
17
  # ==== Options
@@ -1,49 +1,48 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "action_dispatch/http/request"
4
3
  require "action_dispatch/middleware/exception_wrapper"
5
4
 
6
5
  module ActionDispatch
6
+ # = Action Dispatch \ShowExceptions
7
+ #
7
8
  # This middleware rescues any exception returned by the application
8
9
  # and calls an exceptions app that will wrap it in a format for the end user.
9
10
  #
10
- # The exceptions app should be passed as parameter on initialization
11
- # of ShowExceptions. Every time there is an exception, ShowExceptions will
12
- # store the exception in env["action_dispatch.exception"], rewrite the
13
- # PATH_INFO to the exception status code and call the Rack app.
11
+ # The exceptions app should be passed as a parameter on initialization of
12
+ # +ShowExceptions+. Every time there is an exception, +ShowExceptions+ will
13
+ # store the exception in <tt>env["action_dispatch.exception"]</tt>, rewrite
14
+ # the +PATH_INFO+ to the exception status code, and call the Rack app.
15
+ #
16
+ # In \Rails applications, the exceptions app can be configured with
17
+ # +config.exceptions_app+, which defaults to ActionDispatch::PublicExceptions.
14
18
  #
15
- # If the application returns a "X-Cascade" pass response, this middleware
16
- # will send an empty response as result with the correct status code.
17
- # If any exception happens inside the exceptions app, this middleware
18
- # catches the exceptions and returns a FAILSAFE_RESPONSE.
19
+ # If the application returns a response with the <tt>X-Cascade</tt> header
20
+ # set to <tt>"pass"</tt>, this middleware will send an empty response as a
21
+ # result with the correct status code. If any exception happens inside the
22
+ # exceptions app, this middleware catches the exceptions and returns a
23
+ # failsafe response.
19
24
  class ShowExceptions
20
- FAILSAFE_RESPONSE = [500, { "Content-Type" => "text/plain" },
21
- ["500 Internal Server Error\n" \
22
- "If you are the administrator of this website, then please read this web " \
23
- "application's log file and/or the web server's log file to find out what " \
24
- "went wrong."]]
25
-
26
25
  def initialize(app, exceptions_app)
27
26
  @app = app
28
27
  @exceptions_app = exceptions_app
29
28
  end
30
29
 
31
30
  def call(env)
32
- request = ActionDispatch::Request.new env
33
31
  @app.call(env)
34
32
  rescue Exception => exception
35
- if request.show_exceptions?
36
- render_exception(request, exception)
33
+ request = ActionDispatch::Request.new env
34
+ backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
35
+ wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
36
+ if wrapper.show?(request)
37
+ render_exception(request, wrapper)
37
38
  else
38
39
  raise exception
39
40
  end
40
41
  end
41
42
 
42
43
  private
43
- def render_exception(request, exception)
44
- backtrace_cleaner = request.get_header "action_dispatch.backtrace_cleaner"
45
- wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
46
- status = wrapper.status_code
44
+ def render_exception(request, wrapper)
45
+ status = wrapper.status_code
47
46
  request.set_header "action_dispatch.exception", wrapper.unwrapped_exception
48
47
  request.set_header "action_dispatch.original_path", request.path_info
49
48
  request.set_header "action_dispatch.original_request_method", request.raw_request_method
@@ -51,10 +50,15 @@ module ActionDispatch
51
50
  request.path_info = "/#{status}"
52
51
  request.request_method = "GET"
53
52
  response = @exceptions_app.call(request.env)
54
- response[1]["X-Cascade"] == "pass" ? pass_response(status) : response
53
+ response[1][Constants::X_CASCADE] == "pass" ? pass_response(status) : response
55
54
  rescue Exception => failsafe_error
56
55
  $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
57
- FAILSAFE_RESPONSE
56
+
57
+ [500, { Rack::CONTENT_TYPE => "text/plain; charset=utf-8" },
58
+ ["500 Internal Server Error\n" \
59
+ "If you are the administrator of this website, then please read this web " \
60
+ "application's log file and/or the web server's log file to find out what " \
61
+ "went wrong."]]
58
62
  end
59
63
 
60
64
  def fallback_to_html_format_if_invalid_mime_type(request)
@@ -67,7 +71,8 @@ module ActionDispatch
67
71
  end
68
72
 
69
73
  def pass_response(status)
70
- [status, { "Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0" }, []]
74
+ [status, { Rack::CONTENT_TYPE => "text/html; charset=#{Response.default_charset}",
75
+ Rack::CONTENT_LENGTH => "0" }, []]
71
76
  end
72
77
  end
73
78
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionDispatch
4
+ # = Action Dispatch \SSL
5
+ #
4
6
  # This middleware is added to the stack when <tt>config.force_ssl = true</tt>, and is passed
5
7
  # the options set in +config.ssl_options+. It does three jobs to enforce secure HTTP
6
8
  # requests:
@@ -86,7 +88,7 @@ module ActionDispatch
86
88
 
87
89
  private
88
90
  def set_hsts_header!(headers)
89
- headers["Strict-Transport-Security"] ||= @hsts_header
91
+ headers[Constants::STRICT_TRANSPORT_SECURITY] ||= @hsts_header
90
92
  end
91
93
 
92
94
  def normalize_hsts_options(options)
@@ -112,23 +114,33 @@ module ActionDispatch
112
114
  end
113
115
 
114
116
  def flag_cookies_as_secure!(headers)
115
- if cookies = headers["Set-Cookie"]
116
- cookies = cookies.split("\n")
117
+ cookies = headers[Rack::SET_COOKIE]
118
+ return unless cookies
117
119
 
118
- headers["Set-Cookie"] = cookies.map { |cookie|
120
+ if Gem::Version.new(Rack::RELEASE) < Gem::Version.new("3")
121
+ cookies = cookies.split("\n")
122
+ headers[Rack::SET_COOKIE] = cookies.map { |cookie|
119
123
  if !/;\s*secure\s*(;|$)/i.match?(cookie)
120
124
  "#{cookie}; secure"
121
125
  else
122
126
  cookie
123
127
  end
124
128
  }.join("\n")
129
+ else
130
+ headers[Rack::SET_COOKIE] = Array(cookies).map do |cookie|
131
+ if !/;\s*secure\s*(;|$)/i.match?(cookie)
132
+ "#{cookie}; secure"
133
+ else
134
+ cookie
135
+ end
136
+ end
125
137
  end
126
138
  end
127
139
 
128
140
  def redirect_to_https(request)
129
141
  [ @redirect.fetch(:status, redirection_status(request)),
130
- { "Content-Type" => "text/html",
131
- "Location" => https_location_for(request) },
142
+ { Rack::CONTENT_TYPE => "text/html; charset=utf-8",
143
+ Constants::LOCATION => https_location_for(request) },
132
144
  (@redirect[:body] || []) ]
133
145
  end
134
146