actionpack 6.1.7.5 → 7.1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

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