actionpack 7.0.8.7 → 7.2.2.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.
Files changed (171) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +90 -537
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/lib/abstract_controller/asset_paths.rb +2 -0
  6. data/lib/abstract_controller/base.rb +119 -106
  7. data/lib/abstract_controller/caching/fragments.rb +51 -52
  8. data/lib/abstract_controller/caching.rb +2 -0
  9. data/lib/abstract_controller/callbacks.rb +94 -67
  10. data/lib/abstract_controller/collector.rb +6 -6
  11. data/lib/abstract_controller/deprecator.rb +9 -0
  12. data/lib/abstract_controller/error.rb +2 -0
  13. data/lib/abstract_controller/helpers.rb +121 -91
  14. data/lib/abstract_controller/logger.rb +2 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +3 -16
  16. data/lib/abstract_controller/rendering.rb +14 -13
  17. data/lib/abstract_controller/translation.rb +12 -30
  18. data/lib/abstract_controller/url_for.rb +9 -5
  19. data/lib/abstract_controller.rb +8 -0
  20. data/lib/action_controller/api/api_rendering.rb +2 -0
  21. data/lib/action_controller/api.rb +78 -73
  22. data/lib/action_controller/base.rb +199 -141
  23. data/lib/action_controller/caching.rb +16 -11
  24. data/lib/action_controller/deprecator.rb +9 -0
  25. data/lib/action_controller/form_builder.rb +21 -16
  26. data/lib/action_controller/log_subscriber.rb +19 -5
  27. data/lib/action_controller/metal/allow_browser.rb +123 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
  29. data/lib/action_controller/metal/conditional_get.rb +187 -174
  30. data/lib/action_controller/metal/content_security_policy.rb +26 -25
  31. data/lib/action_controller/metal/cookies.rb +4 -2
  32. data/lib/action_controller/metal/data_streaming.rb +65 -54
  33. data/lib/action_controller/metal/default_headers.rb +6 -2
  34. data/lib/action_controller/metal/etag_with_flash.rb +4 -0
  35. data/lib/action_controller/metal/etag_with_template_digest.rb +18 -14
  36. data/lib/action_controller/metal/exceptions.rb +19 -9
  37. data/lib/action_controller/metal/flash.rb +12 -10
  38. data/lib/action_controller/metal/head.rb +20 -16
  39. data/lib/action_controller/metal/helpers.rb +64 -67
  40. data/lib/action_controller/metal/http_authentication.rb +212 -199
  41. data/lib/action_controller/metal/implicit_render.rb +21 -17
  42. data/lib/action_controller/metal/instrumentation.rb +22 -12
  43. data/lib/action_controller/metal/live.rb +125 -92
  44. data/lib/action_controller/metal/logging.rb +6 -4
  45. data/lib/action_controller/metal/mime_responds.rb +151 -142
  46. data/lib/action_controller/metal/parameter_encoding.rb +34 -32
  47. data/lib/action_controller/metal/params_wrapper.rb +58 -58
  48. data/lib/action_controller/metal/permissions_policy.rb +14 -13
  49. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  50. data/lib/action_controller/metal/redirecting.rb +110 -84
  51. data/lib/action_controller/metal/renderers.rb +50 -49
  52. data/lib/action_controller/metal/rendering.rb +103 -82
  53. data/lib/action_controller/metal/request_forgery_protection.rb +279 -161
  54. data/lib/action_controller/metal/rescue.rb +12 -8
  55. data/lib/action_controller/metal/streaming.rb +174 -132
  56. data/lib/action_controller/metal/strong_parameters.rb +598 -473
  57. data/lib/action_controller/metal/testing.rb +2 -0
  58. data/lib/action_controller/metal/url_for.rb +23 -14
  59. data/lib/action_controller/metal.rb +145 -61
  60. data/lib/action_controller/railtie.rb +25 -9
  61. data/lib/action_controller/railties/helpers.rb +2 -0
  62. data/lib/action_controller/renderer.rb +105 -66
  63. data/lib/action_controller/template_assertions.rb +4 -2
  64. data/lib/action_controller/test_case.rb +157 -128
  65. data/lib/action_controller.rb +17 -3
  66. data/lib/action_dispatch/constants.rb +34 -0
  67. data/lib/action_dispatch/deprecator.rb +9 -0
  68. data/lib/action_dispatch/http/cache.rb +28 -29
  69. data/lib/action_dispatch/http/content_disposition.rb +2 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +48 -45
  71. data/lib/action_dispatch/http/filter_parameters.rb +18 -8
  72. data/lib/action_dispatch/http/filter_redirect.rb +22 -1
  73. data/lib/action_dispatch/http/headers.rb +23 -21
  74. data/lib/action_dispatch/http/mime_negotiation.rb +37 -48
  75. data/lib/action_dispatch/http/mime_type.rb +60 -30
  76. data/lib/action_dispatch/http/mime_types.rb +5 -1
  77. data/lib/action_dispatch/http/parameters.rb +12 -10
  78. data/lib/action_dispatch/http/permissions_policy.rb +32 -27
  79. data/lib/action_dispatch/http/rack_cache.rb +4 -0
  80. data/lib/action_dispatch/http/request.rb +132 -79
  81. data/lib/action_dispatch/http/response.rb +136 -103
  82. data/lib/action_dispatch/http/upload.rb +19 -15
  83. data/lib/action_dispatch/http/url.rb +75 -73
  84. data/lib/action_dispatch/journey/formatter.rb +19 -6
  85. data/lib/action_dispatch/journey/gtg/builder.rb +4 -3
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +2 -0
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +10 -8
  88. data/lib/action_dispatch/journey/nfa/dot.rb +2 -0
  89. data/lib/action_dispatch/journey/nodes/node.rb +6 -5
  90. data/lib/action_dispatch/journey/parser.rb +4 -3
  91. data/lib/action_dispatch/journey/parser_extras.rb +2 -0
  92. data/lib/action_dispatch/journey/path/pattern.rb +18 -15
  93. data/lib/action_dispatch/journey/route.rb +12 -9
  94. data/lib/action_dispatch/journey/router/utils.rb +16 -15
  95. data/lib/action_dispatch/journey/router.rb +13 -10
  96. data/lib/action_dispatch/journey/routes.rb +6 -4
  97. data/lib/action_dispatch/journey/scanner.rb +4 -2
  98. data/lib/action_dispatch/journey/visitors.rb +2 -0
  99. data/lib/action_dispatch/journey.rb +2 -0
  100. data/lib/action_dispatch/log_subscriber.rb +25 -0
  101. data/lib/action_dispatch/middleware/actionable_exceptions.rb +7 -6
  102. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  103. data/lib/action_dispatch/middleware/callbacks.rb +4 -0
  104. data/lib/action_dispatch/middleware/cookies.rb +192 -194
  105. data/lib/action_dispatch/middleware/debug_exceptions.rb +36 -27
  106. data/lib/action_dispatch/middleware/debug_locks.rb +18 -13
  107. data/lib/action_dispatch/middleware/debug_view.rb +9 -2
  108. data/lib/action_dispatch/middleware/exception_wrapper.rb +181 -27
  109. data/lib/action_dispatch/middleware/executor.rb +9 -1
  110. data/lib/action_dispatch/middleware/flash.rb +65 -46
  111. data/lib/action_dispatch/middleware/host_authorization.rb +22 -17
  112. data/lib/action_dispatch/middleware/public_exceptions.rb +12 -8
  113. data/lib/action_dispatch/middleware/reloader.rb +9 -5
  114. data/lib/action_dispatch/middleware/remote_ip.rb +88 -83
  115. data/lib/action_dispatch/middleware/request_id.rb +15 -8
  116. data/lib/action_dispatch/middleware/server_timing.rb +8 -6
  117. data/lib/action_dispatch/middleware/session/abstract_store.rb +7 -0
  118. data/lib/action_dispatch/middleware/session/cache_store.rb +14 -7
  119. data/lib/action_dispatch/middleware/session/cookie_store.rb +32 -25
  120. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +9 -3
  121. data/lib/action_dispatch/middleware/show_exceptions.rb +42 -28
  122. data/lib/action_dispatch/middleware/ssl.rb +60 -45
  123. data/lib/action_dispatch/middleware/stack.rb +15 -9
  124. data/lib/action_dispatch/middleware/static.rb +40 -34
  125. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  126. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
  127. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  128. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
  129. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  130. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
  132. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
  133. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  134. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
  135. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  136. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  137. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  138. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +47 -38
  139. data/lib/action_dispatch/railtie.rb +12 -4
  140. data/lib/action_dispatch/request/session.rb +39 -27
  141. data/lib/action_dispatch/request/utils.rb +10 -3
  142. data/lib/action_dispatch/routing/endpoint.rb +2 -0
  143. data/lib/action_dispatch/routing/inspector.rb +59 -9
  144. data/lib/action_dispatch/routing/mapper.rb +686 -639
  145. data/lib/action_dispatch/routing/polymorphic_routes.rb +70 -61
  146. data/lib/action_dispatch/routing/redirection.rb +52 -38
  147. data/lib/action_dispatch/routing/route_set.rb +106 -62
  148. data/lib/action_dispatch/routing/routes_proxy.rb +16 -19
  149. data/lib/action_dispatch/routing/url_for.rb +131 -122
  150. data/lib/action_dispatch/routing.rb +152 -150
  151. data/lib/action_dispatch/system_test_case.rb +91 -81
  152. data/lib/action_dispatch/system_testing/browser.rb +27 -19
  153. data/lib/action_dispatch/system_testing/driver.rb +16 -22
  154. data/lib/action_dispatch/system_testing/server.rb +2 -0
  155. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +53 -31
  156. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
  157. data/lib/action_dispatch/testing/assertion_response.rb +9 -7
  158. data/lib/action_dispatch/testing/assertions/response.rb +36 -26
  159. data/lib/action_dispatch/testing/assertions/routing.rb +203 -95
  160. data/lib/action_dispatch/testing/assertions.rb +5 -1
  161. data/lib/action_dispatch/testing/integration.rb +240 -229
  162. data/lib/action_dispatch/testing/request_encoder.rb +6 -1
  163. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  164. data/lib/action_dispatch/testing/test_process.rb +14 -9
  165. data/lib/action_dispatch/testing/test_request.rb +4 -2
  166. data/lib/action_dispatch/testing/test_response.rb +34 -19
  167. data/lib/action_dispatch.rb +52 -21
  168. data/lib/action_pack/gem_version.rb +6 -4
  169. data/lib/action_pack/version.rb +3 -1
  170. data/lib/action_pack.rb +18 -17
  171. metadata +86 -27
@@ -1,39 +1,48 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "active_support/core_ext/hash/keys"
4
6
 
5
7
  module ActionDispatch
6
- # The flash provides a way to pass temporary primitive-types (String, Array, Hash) between actions. Anything you place in the flash will be exposed
7
- # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
8
- # action that sets <tt>flash[:notice] = "Post successfully created"</tt> before redirecting to a display action that can
9
- # then expose the flash to its template. Actually, that exposure is automatically done.
8
+ # # Action Dispatch Flash
10
9
  #
11
- # class PostsController < ActionController::Base
12
- # def create
13
- # # save post
14
- # flash[:notice] = "Post successfully created"
15
- # redirect_to @post
16
- # end
10
+ # The flash provides a way to pass temporary primitive-types (String, Array,
11
+ # Hash) between actions. Anything you place in the flash will be exposed to the
12
+ # very next action and then cleared out. This is a great way of doing notices
13
+ # and alerts, such as a create action that sets `flash[:notice] = "Post
14
+ # successfully created"` before redirecting to a display action that can then
15
+ # expose the flash to its template. Actually, that exposure is automatically
16
+ # done.
17
17
  #
18
- # def show
19
- # # doesn't need to assign the flash notice to the template, that's done automatically
18
+ # class PostsController < ActionController::Base
19
+ # def create
20
+ # # save post
21
+ # flash[:notice] = "Post successfully created"
22
+ # redirect_to @post
23
+ # end
24
+ #
25
+ # def show
26
+ # # doesn't need to assign the flash notice to the template, that's done automatically
27
+ # end
20
28
  # end
21
- # end
22
29
  #
23
- # Then in +show.html.erb+:
30
+ # Then in `show.html.erb`:
24
31
  #
25
- # <% if flash[:notice] %>
26
- # <div class="notice"><%= flash[:notice] %></div>
27
- # <% end %>
32
+ # <% if flash[:notice] %>
33
+ # <div class="notice"><%= flash[:notice] %></div>
34
+ # <% end %>
28
35
  #
29
- # Since the +notice+ and +alert+ keys are a common idiom, convenience accessors are available:
36
+ # Since the `notice` and `alert` keys are a common idiom, convenience accessors
37
+ # are available:
30
38
  #
31
- # flash.alert = "You must be logged in"
32
- # flash.notice = "Post successfully created"
39
+ # flash.alert = "You must be logged in"
40
+ # flash.notice = "Post successfully created"
33
41
  #
34
- # 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
35
- # non-primitive types, you will have to handle that in your application. Example: To show messages with links, you will have to
36
- # use sanitize helper.
42
+ # This example places a string in the flash. And of course, you can put as many
43
+ # as you like at a time too. If you want to pass non-primitive types, you will
44
+ # have to handle that in your application. Example: To show messages with links,
45
+ # you will have to use sanitize helper.
37
46
  #
38
47
  # Just remember: They'll be gone by the time the next action has been performed.
39
48
  #
@@ -96,12 +105,12 @@ module ActionDispatch
96
105
  @flash[k.to_s]
97
106
  end
98
107
 
99
- # Convenience accessor for <tt>flash.now[:alert]=</tt>.
108
+ # Convenience accessor for `flash.now[:alert]=`.
100
109
  def alert=(message)
101
110
  self[:alert] = message
102
111
  end
103
112
 
104
- # Convenience accessor for <tt>flash.now[:notice]=</tt>.
113
+ # Convenience accessor for `flash.now[:notice]=`.
105
114
  def notice=(message)
106
115
  self[:notice] = message
107
116
  end
@@ -129,8 +138,8 @@ module ActionDispatch
129
138
  end
130
139
  end
131
140
 
132
- # Builds a hash containing the flashes to keep for the next request.
133
- # If there are none to keep, returns +nil+.
141
+ # Builds a hash containing the flashes to keep for the next request. If there
142
+ # are none to keep, returns `nil`.
134
143
  def to_session_value # :nodoc:
135
144
  flashes_to_keep = @flashes.except(*@discard)
136
145
  return nil if flashes_to_keep.empty?
@@ -175,6 +184,8 @@ module ActionDispatch
175
184
  @flashes.key? name.to_s
176
185
  end
177
186
 
187
+ # Immediately deletes the single flash entry. Use this method when you want
188
+ # remove the message within the current action. See also #discard.
178
189
  def delete(key)
179
190
  key = key.to_s
180
191
  @discard.delete key
@@ -207,42 +218,49 @@ module ActionDispatch
207
218
  self
208
219
  end
209
220
 
210
- # Sets a flash that will not be available to the next action, only to the current.
221
+ # Sets a flash that will not be available to the next action, only to the
222
+ # current.
211
223
  #
212
224
  # flash.now[:message] = "Hello current action"
213
225
  #
214
- # This method enables you to use the flash as a central messaging system in your app.
215
- # When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>).
216
- # When you need to pass an object to the current action, you use <tt>now</tt>, and your object will
217
- # vanish when the current action is done.
226
+ # This method enables you to use the flash as a central messaging system in your
227
+ # app. When you need to pass an object to the next action, you use the standard
228
+ # flash assign (`[]=`). When you need to pass an object to the current action,
229
+ # you use `now`, and your object will vanish when the current action is done.
218
230
  #
219
- # Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
231
+ # Entries set via `now` are accessed the same way as standard entries:
232
+ # `flash['my-key']`.
220
233
  #
221
234
  # Also, brings two convenience accessors:
222
235
  #
223
- # flash.now.alert = "Beware now!"
224
- # # Equivalent to flash.now[:alert] = "Beware now!"
236
+ # flash.now.alert = "Beware now!"
237
+ # # Equivalent to flash.now[:alert] = "Beware now!"
225
238
  #
226
- # flash.now.notice = "Good luck now!"
227
- # # Equivalent to flash.now[:notice] = "Good luck now!"
239
+ # flash.now.notice = "Good luck now!"
240
+ # # Equivalent to flash.now[:notice] = "Good luck now!"
228
241
  def now
229
242
  @now ||= FlashNow.new(self)
230
243
  end
231
244
 
232
- # Keeps either the entire current flash or a specific flash entry available for the next action:
245
+ # Keeps either the entire current flash or a specific flash entry available for
246
+ # the next action:
233
247
  #
234
- # flash.keep # keeps the entire flash
235
- # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
248
+ # flash.keep # keeps the entire flash
249
+ # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
236
250
  def keep(k = nil)
237
251
  k = k.to_s if k
238
252
  @discard.subtract Array(k || keys)
239
253
  k ? self[k] : self
240
254
  end
241
255
 
242
- # Marks the entire flash or a single flash entry to be discarded by the end of the current action:
256
+ # Marks the entire flash or a single flash entry to be discarded by the end of
257
+ # the current action:
243
258
  #
244
259
  # flash.discard # discard the entire flash at the end of the current action
245
260
  # flash.discard(:warning) # discard only the "warning" entry at the end of the current action
261
+ #
262
+ # Use this method when you want to display the message in the current action but
263
+ # not in the next one. See also #delete.
246
264
  def discard(k = nil)
247
265
  k = k.to_s if k
248
266
  @discard.merge Array(k || keys)
@@ -251,28 +269,29 @@ module ActionDispatch
251
269
 
252
270
  # Mark for removal entries that were kept, and delete unkept ones.
253
271
  #
254
- # This method is called automatically by filters, so you generally don't need to care about it.
272
+ # This method is called automatically by filters, so you generally don't need to
273
+ # care about it.
255
274
  def sweep # :nodoc:
256
275
  @discard.each { |k| @flashes.delete k }
257
276
  @discard.replace @flashes.keys
258
277
  end
259
278
 
260
- # Convenience accessor for <tt>flash[:alert]</tt>.
279
+ # Convenience accessor for `flash[:alert]`.
261
280
  def alert
262
281
  self[:alert]
263
282
  end
264
283
 
265
- # Convenience accessor for <tt>flash[:alert]=</tt>.
284
+ # Convenience accessor for `flash[:alert]=`.
266
285
  def alert=(message)
267
286
  self[:alert] = message
268
287
  end
269
288
 
270
- # Convenience accessor for <tt>flash[:notice]</tt>.
289
+ # Convenience accessor for `flash[:notice]`.
271
290
  def notice
272
291
  self[:notice]
273
292
  end
274
293
 
275
- # Convenience accessor for <tt>flash[:notice]=</tt>.
294
+ # Convenience accessor for `flash[:notice]=`.
276
295
  def notice=(message)
277
296
  self[:notice] = message
278
297
  end
@@ -1,23 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionDispatch
4
- # This middleware guards from DNS rebinding attacks by explicitly permitting
5
- # the hosts a request can be sent to, and is passed the options set in
6
- # +config.host_authorization+.
6
+ # # Action Dispatch HostAuthorization
7
+ #
8
+ # This middleware guards from DNS rebinding attacks by explicitly permitting the
9
+ # hosts a request can be sent to, and is passed the options set in
10
+ # `config.host_authorization`.
7
11
  #
8
- # Requests can opt-out of Host Authorization with +exclude+:
12
+ # Requests can opt-out of Host Authorization with `exclude`:
9
13
  #
10
- # config.host_authorization = { exclude: ->(request) { request.path =~ /healthcheck/ } }
14
+ # config.host_authorization = { exclude: ->(request) { request.path =~ /healthcheck/ } }
11
15
  #
12
- # When a request comes to an unauthorized host, the +response_app+
13
- # application will be executed and rendered. If no +response_app+ is given, a
14
- # default one will run.
15
- # The default response app logs blocked host info with level 'error' and
16
- # responds with <tt>403 Forbidden</tt>. The body of the response contains debug info
17
- # if +config.consider_all_requests_local+ is set to true, otherwise the body is empty.
16
+ # When a request comes to an unauthorized host, the `response_app` application
17
+ # will be executed and rendered. If no `response_app` is given, a default one
18
+ # will run. The default response app logs blocked host info with level 'error'
19
+ # and responds with `403 Forbidden`. The body of the response contains debug
20
+ # info if `config.consider_all_requests_local` is set to true, otherwise the
21
+ # body is empty.
18
22
  class HostAuthorization
19
- ALLOWED_HOSTS_IN_DEVELOPMENT = [".localhost", IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0")]
23
+ ALLOWED_HOSTS_IN_DEVELOPMENT = [".localhost", ".test", IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0")]
20
24
  PORT_REGEX = /(?::\d+)/ # :nodoc:
25
+ SUBDOMAIN_REGEX = /(?:[a-z0-9-]+\.)/i # :nodoc:
21
26
  IPV4_HOSTNAME = /(?<host>\d+\.\d+\.\d+\.\d+)#{PORT_REGEX}?/ # :nodoc:
22
27
  IPV6_HOSTNAME = /(?<host>[a-f0-9]*:[a-f0-9.:]+)/i # :nodoc:
23
28
  IPV6_HOSTNAME_WITH_PORT = /\[#{IPV6_HOSTNAME}\]#{PORT_REGEX}/i # :nodoc:
@@ -42,8 +47,8 @@ module ActionDispatch
42
47
  begin
43
48
  allowed === extract_hostname(host)
44
49
  rescue
45
- # IPAddr#=== raises an error if you give it a hostname instead of
46
- # IP. Treat similar errors as blocked access.
50
+ # IPAddr#=== raises an error if you give it a hostname instead of IP. Treat
51
+ # similar errors as blocked access.
47
52
  false
48
53
  end
49
54
  else
@@ -69,7 +74,7 @@ module ActionDispatch
69
74
 
70
75
  def sanitize_string(host)
71
76
  if host.start_with?(".")
72
- /\A([a-z0-9-]+\.)?#{Regexp.escape(host[1..-1])}#{PORT_REGEX}?\z/i
77
+ /\A#{SUBDOMAIN_REGEX}?#{Regexp.escape(host[1..-1])}#{PORT_REGEX}?\z/i
73
78
  else
74
79
  /\A#{Regexp.escape host}#{PORT_REGEX}?\z/i
75
80
  end
@@ -101,8 +106,8 @@ module ActionDispatch
101
106
 
102
107
  def response(format, body)
103
108
  [RESPONSE_STATUS,
104
- { "Content-Type" => "#{format}; charset=#{Response.default_charset}",
105
- "Content-Length" => body.bytesize.to_s },
109
+ { Rack::CONTENT_TYPE => "#{format}; charset=#{Response.default_charset}",
110
+ Rack::CONTENT_LENGTH => body.bytesize.to_s },
106
111
  [body]]
107
112
  end
108
113
 
@@ -1,13 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionDispatch
6
+ # # Action Dispatch PublicExceptions
7
+ #
4
8
  # When called, this middleware renders an error page. By default if an HTML
5
- # response is expected it will render static error pages from the <tt>/public</tt>
9
+ # response is expected it will render static error pages from the `/public`
6
10
  # directory. For example when this middleware receives a 500 response it will
7
- # render the template found in <tt>/public/500.html</tt>.
8
- # If an internationalized locale is set, this middleware will attempt to render
9
- # the template in <tt>/public/500.<locale>.html</tt>. If an internationalized template
10
- # is not found it will fall back on <tt>/public/500.html</tt>.
11
+ # render the template found in `/public/500.html`. If an internationalized
12
+ # locale is set, this middleware will attempt to render the template in
13
+ # `/public/500.<locale>.html`. If an internationalized template is not found it
14
+ # will fall back on `/public/500.html`.
11
15
  #
12
16
  # When a request with a content type other than HTML is made, this middleware
13
17
  # will attempt to convert error information into the appropriate response type.
@@ -42,8 +46,8 @@ module ActionDispatch
42
46
  end
43
47
 
44
48
  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]]
49
+ [status, { Rack::CONTENT_TYPE => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
50
+ Rack::CONTENT_LENGTH => body.bytesize.to_s }, [body]]
47
51
  end
48
52
 
49
53
  def render_html(status)
@@ -53,7 +57,7 @@ module ActionDispatch
53
57
  if found || File.exist?(path)
54
58
  render_format(status, "text/html", File.read(path))
55
59
  else
56
- [404, { "X-Cascade" => "pass" }, []]
60
+ [404, { Constants::X_CASCADE => "pass" }, []]
57
61
  end
58
62
  end
59
63
  end
@@ -1,12 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionDispatch
4
- # ActionDispatch::Reloader wraps the request with callbacks provided by ActiveSupport::Reloader
5
- # callbacks, intended to assist with code reloading during development.
6
+ # # Action Dispatch Reloader
7
+ #
8
+ # ActionDispatch::Reloader wraps the request with callbacks provided by
9
+ # ActiveSupport::Reloader, intended to assist with code reloading during
10
+ # development.
6
11
  #
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.
12
+ # ActionDispatch::Reloader is included in the middleware stack only if reloading
13
+ # is enabled, which it is by the default in `development` mode.
10
14
  class Reloader < Executor
11
15
  end
12
16
  end
@@ -1,36 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "ipaddr"
4
6
 
5
7
  module ActionDispatch
6
- # This middleware calculates the IP address of the remote client that is
7
- # making the request. It does this by checking various headers that could
8
- # contain the address, and then picking the last-set address that is not
9
- # 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.
8
+ # # Action Dispatch RemoteIp
9
+ #
10
+ # This middleware calculates the IP address of the remote client that is making
11
+ # the request. It does this by checking various headers that could contain the
12
+ # address, and then picking the last-set address that is not on the list of
13
+ # trusted IPs. This follows the precedent set by e.g. [the Tomcat
14
+ # server](https://issues.apache.org/bugzilla/show_bug.cgi?id=50453). A more
15
+ # detailed explanation of the algorithm is given at GetIp#calculate_ip.
14
16
  #
15
- # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
16
- # requires. Some Rack servers simply drop preceding headers, and only report
17
- # the value that was {given in the last header}[https://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers].
18
- # If you are behind multiple proxy servers (like NGINX to HAProxy to Unicorn)
19
- # then you should test your Rack server to make sure your data is good.
17
+ # Some Rack servers concatenate repeated headers, like [HTTP RFC
18
+ # 2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2) requires.
19
+ # Some Rack servers simply drop preceding headers, and only report the value
20
+ # that was [given in the last
21
+ # header](https://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers).
22
+ # If you are behind multiple proxy servers (like NGINX to HAProxy to
23
+ # Unicorn) then you should test your Rack server to make sure your data is good.
20
24
  #
21
- # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING.
22
- # This middleware assumes that there is at least one proxy sitting around
23
- # and setting headers with the client's remote IP address. If you don't use
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
26
- # care about that, then you need to explicitly drop or ignore those headers
27
- # sometime before this middleware runs.
25
+ # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING. This
26
+ # middleware assumes that there is at least one proxy sitting around and setting
27
+ # headers with the client's remote IP address. If you don't use a proxy, because
28
+ # you are hosted on e.g. Heroku without SSL, any client can claim to have any IP
29
+ # address by setting the `X-Forwarded-For` header. If you care about that, then
30
+ # you need to explicitly drop or ignore those headers sometime before this
31
+ # middleware runs. Alternatively, remove this middleware to avoid inadvertently
32
+ # relying on it.
28
33
  class RemoteIp
29
34
  class IpSpoofAttackError < StandardError; end
30
35
 
31
- # The default trusted IPs list simply includes IP addresses that are
32
- # guaranteed by the IP specification to be private addresses. Those will
33
- # not be the ultimate client IP in production, and so are discarded. See
36
+ # The default trusted IPs list simply includes IP addresses that are guaranteed
37
+ # by the IP specification to be private addresses. Those will not be the
38
+ # ultimate client IP in production, and so are discarded. See
34
39
  # https://en.wikipedia.org/wiki/Private_network for details.
35
40
  TRUSTED_PROXIES = [
36
41
  "127.0.0.0/8", # localhost IPv4 range, per RFC-3330
@@ -43,20 +48,20 @@ module ActionDispatch
43
48
 
44
49
  attr_reader :check_ip, :proxies
45
50
 
46
- # Create a new +RemoteIp+ middleware instance.
51
+ # Create a new `RemoteIp` middleware instance.
47
52
  #
48
- # The +ip_spoofing_check+ option is on by default. When on, an exception
49
- # is raised if it looks like the client is trying to lie about its own IP
50
- # address. It makes sense to turn off this check on sites aimed at non-IP
51
- # clients (like WAP devices), or behind proxies that set headers in an
52
- # incorrect or confusing way (like AWS ELB).
53
+ # The `ip_spoofing_check` option is on by default. When on, an exception is
54
+ # raised if it looks like the client is trying to lie about its own IP address.
55
+ # It makes sense to turn off this check on sites aimed at non-IP clients (like
56
+ # WAP devices), or behind proxies that set headers in an incorrect or confusing
57
+ # way (like AWS ELB).
53
58
  #
54
- # The +custom_proxies+ argument can take an enumerable which will be used
55
- # instead of +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.
59
+ # The `custom_proxies` argument can take an enumerable which will be used
60
+ # instead of `TRUSTED_PROXIES`. Any proxy setup will put the value you want in
61
+ # the middle (or at the beginning) of the `X-Forwarded-For` list, with your
62
+ # proxy servers after it. If your proxies aren't removed, pass them in via the
63
+ # `custom_proxies` parameter. That way, the middleware will ignore those IP
64
+ # addresses, and return the one that you want.
60
65
  def initialize(app, ip_spoofing_check = true, custom_proxies = nil)
61
66
  @app = app
62
67
  @check_ip = ip_spoofing_check
@@ -65,9 +70,9 @@ module ActionDispatch
65
70
  elsif custom_proxies.respond_to?(:any?)
66
71
  custom_proxies
67
72
  else
68
- ActiveSupport::Deprecation.warn(<<~EOM)
69
- Setting config.action_dispatch.trusted_proxies to a single value has
70
- been deprecated. Please set this to an enumerable instead. For
73
+ raise(ArgumentError, <<~EOM)
74
+ Setting config.action_dispatch.trusted_proxies to a single value isn't
75
+ supported. Please set this to an enumerable instead. For
71
76
  example, instead of:
72
77
 
73
78
  config.action_dispatch.trusted_proxies = IPAddr.new("10.0.0.0/8")
@@ -76,26 +81,24 @@ module ActionDispatch
76
81
 
77
82
  config.action_dispatch.trusted_proxies = [IPAddr.new("10.0.0.0/8")]
78
83
 
79
- Note that unlike passing a single argument, passing an enumerable
80
- will *replace* the default set of trusted proxies.
84
+ Note that passing an enumerable will *replace* the default set of trusted proxies.
81
85
  EOM
82
- Array(custom_proxies) + TRUSTED_PROXIES
83
86
  end
84
87
  end
85
88
 
86
- # Since the IP address may not be needed, we store the object here
87
- # without calculating the IP to keep from slowing down the majority of
88
- # requests. For those requests that do need to know the IP, the
89
- # GetIp#calculate_ip method will calculate the memoized client IP address.
89
+ # Since the IP address may not be needed, we store the object here without
90
+ # calculating the IP to keep from slowing down the majority of requests. For
91
+ # those requests that do need to know the IP, the GetIp#calculate_ip method will
92
+ # calculate the memoized client IP address.
90
93
  def call(env)
91
94
  req = ActionDispatch::Request.new env
92
95
  req.remote_ip = GetIp.new(req, check_ip, proxies)
93
96
  @app.call(req.env)
94
97
  end
95
98
 
96
- # The GetIp class exists as a way to defer processing of the request data
97
- # into an actual IP address. If the ActionDispatch::Request#remote_ip method
98
- # is called, this class will calculate the value and then memoize it.
99
+ # The GetIp class exists as a way to defer processing of the request data into
100
+ # an actual IP address. If the ActionDispatch::Request#remote_ip method is
101
+ # called, this class will calculate the value and then memoize it.
99
102
  class GetIp
100
103
  def initialize(req, check_ip, proxies)
101
104
  @req = req
@@ -103,45 +106,45 @@ module ActionDispatch
103
106
  @proxies = proxies
104
107
  end
105
108
 
106
- # Sort through the various IP address headers, looking for the IP most
107
- # likely to be the address of the actual remote client making this
108
- # request.
109
+ # Sort through the various IP address headers, looking for the IP most likely to
110
+ # be the address of the actual remote client making this request.
109
111
  #
110
- # REMOTE_ADDR will be correct if the request is made directly against the
111
- # Ruby process, on e.g. Heroku. When the request is proxied by another
112
- # server like HAProxy or NGINX, the IP address that made the original
113
- # request will be put in an +X-Forwarded-For+ header. If there are multiple
114
- # proxies, that header may contain a list of IPs. Other proxy services
115
- # set the +Client-Ip+ header instead, so we check that too.
112
+ # REMOTE_ADDR will be correct if the request is made directly against the Ruby
113
+ # process, on e.g. Heroku. When the request is proxied by another server like
114
+ # HAProxy or NGINX, the IP address that made the original request will be put in
115
+ # an `X-Forwarded-For` header. If there are multiple proxies, that header may
116
+ # contain a list of IPs. Other proxy services set the `Client-Ip` header
117
+ # instead, so we check that too.
116
118
  #
117
- # As discussed in {this post about Rails IP Spoofing}[https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
118
- # while the first IP in the list is likely to be the "originating" IP,
119
- # it could also have been set by the client maliciously.
119
+ # As discussed in [this post about Rails IP
120
+ # Spoofing](https://web.archive.org/web/20170626095448/https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/),
121
+ # while the first IP in the list is likely to be the "originating" IP, it
122
+ # could also have been set by the client maliciously.
120
123
  #
121
- # In order to find the first address that is (probably) accurate, we
122
- # take the list of IPs, remove known and trusted proxies, and then take
123
- # the last address left, which was presumably set by one of those proxies.
124
+ # In order to find the first address that is (probably) accurate, we take the
125
+ # list of IPs, remove known and trusted proxies, and then take the last address
126
+ # left, which was presumably set by one of those proxies.
124
127
  def calculate_ip
125
128
  # Set by the Rack web server, this is a single value.
126
129
  remote_addr = ips_from(@req.remote_addr).last
127
130
 
128
131
  # Could be a CSV list and/or repeated headers that were concatenated.
129
- client_ips = ips_from(@req.client_ip).reverse
130
- forwarded_ips = ips_from(@req.x_forwarded_for).reverse
132
+ client_ips = ips_from(@req.client_ip).reverse!
133
+ forwarded_ips = ips_from(@req.x_forwarded_for).reverse!
131
134
 
132
- # +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set.
133
- # If they are both set, it means that either:
135
+ # `Client-Ip` and `X-Forwarded-For` should not, generally, both be set. If they
136
+ # are both set, it means that either:
134
137
  #
135
138
  # 1) This request passed through two proxies with incompatible IP header
136
- # conventions.
137
- # 2) The client passed one of +Client-Ip+ or +X-Forwarded-For+
138
- # (whichever the proxy servers weren't using) themselves.
139
+ # conventions.
139
140
  #
140
- # Either way, there is no way for us to determine which header is the
141
- # right one after the fact. Since we have no idea, if we are concerned
142
- # about IP spoofing we need to give up and explode. (If you're not
143
- # concerned about IP spoofing you can turn the +ip_spoofing_check+
144
- # option off.)
141
+ # 2) The client passed one of `Client-Ip` or `X-Forwarded-For`
142
+ # (whichever the proxy servers weren't using) themselves.
143
+ #
144
+ # Either way, there is no way for us to determine which header is the right one
145
+ # after the fact. Since we have no idea, if we are concerned about IP spoofing
146
+ # we need to give up and explode. (If you're not concerned about IP spoofing you
147
+ # can turn the `ip_spoofing_check` option off.)
145
148
  should_check_ip = @check_ip && client_ips.last && forwarded_ips.last
146
149
  if should_check_ip && !forwarded_ips.include?(client_ips.last)
147
150
  # We don't know which came from the proxy, and which from the user
@@ -152,13 +155,14 @@ module ActionDispatch
152
155
 
153
156
  # We assume these things about the IP headers:
154
157
  #
155
- # - X-Forwarded-For will be a list of IPs, one per proxy, or blank
156
- # - Client-Ip is propagated from the outermost proxy, or is blank
157
- # - REMOTE_ADDR will be the IP that made the request to Rack
158
- ips = [forwarded_ips, client_ips].flatten.compact
159
-
160
- # If every single IP option is in the trusted list, return the IP
161
- # that's furthest away
158
+ # - X-Forwarded-For will be a list of IPs, one per proxy, or blank
159
+ # - Client-Ip is propagated from the outermost proxy, or is blank
160
+ # - REMOTE_ADDR will be the IP that made the request to Rack
161
+ ips = forwarded_ips + client_ips
162
+ ips.compact!
163
+
164
+ # If every single IP option is in the trusted list, return the IP that's
165
+ # furthest away
162
166
  filter_proxies(ips + [remote_addr]).first || ips.last || remote_addr
163
167
  end
164
168
 
@@ -173,7 +177,7 @@ module ActionDispatch
173
177
  return [] unless header
174
178
  # Split the comma-separated list into an array of strings.
175
179
  ips = header.strip.split(/[,\s]+/)
176
- ips.select do |ip|
180
+ ips.select! do |ip|
177
181
  # Only return IPs that are valid according to the IPAddr#new method.
178
182
  range = IPAddr.new(ip).to_range
179
183
  # We want to make sure nobody is sneaking a netmask in.
@@ -181,6 +185,7 @@ module ActionDispatch
181
185
  rescue ArgumentError
182
186
  nil
183
187
  end
188
+ ips
184
189
  end
185
190
 
186
191
  def filter_proxies(ips) # :doc:
@@ -1,19 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "securerandom"
4
6
  require "active_support/core_ext/string/access"
5
7
 
6
8
  module ActionDispatch
7
- # Makes a unique request id available to the +action_dispatch.request_id+ env variable (which is then accessible
8
- # through ActionDispatch::Request#request_id or the alias ActionDispatch::Request#uuid) and sends
9
- # the same id to the client via the +X-Request-Id+ header.
9
+ # # Action Dispatch RequestId
10
+ #
11
+ # Makes a unique request id available to the `action_dispatch.request_id` env
12
+ # variable (which is then accessible through ActionDispatch::Request#request_id
13
+ # or the alias ActionDispatch::Request#uuid) and sends the same id to the client
14
+ # via the `X-Request-Id` header.
10
15
  #
11
- # The unique request id is either based on the +X-Request-Id+ header in the request, which would typically be generated
12
- # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
13
- # header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
16
+ # The unique request id is either based on the `X-Request-Id` header in the
17
+ # request, which would typically be generated by a firewall, load balancer, or
18
+ # the web server, or, if this header is not available, a random uuid. If the
19
+ # header is accepted from the outside world, we sanitize it to a max of 255
20
+ # chars and alphanumeric and dashes only.
14
21
  #
15
- # The unique request id can be used to trace a request end-to-end and would typically end up being part of log files
16
- # from multiple pieces of the stack.
22
+ # The unique request id can be used to trace a request end-to-end and would
23
+ # typically end up being part of log files from multiple pieces of the stack.
17
24
  class RequestId
18
25
  def initialize(app, header:)
19
26
  @app = app