actionpack 4.2.10 → 7.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (202) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +86 -600
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +13 -14
  5. data/lib/abstract_controller/asset_paths.rb +5 -1
  6. data/lib/abstract_controller/base.rb +166 -136
  7. data/lib/abstract_controller/caching/fragments.rb +149 -0
  8. data/lib/abstract_controller/caching.rb +68 -0
  9. data/lib/abstract_controller/callbacks.rb +126 -57
  10. data/lib/abstract_controller/collector.rb +13 -15
  11. data/lib/abstract_controller/deprecator.rb +9 -0
  12. data/lib/abstract_controller/error.rb +8 -0
  13. data/lib/abstract_controller/helpers.rb +181 -132
  14. data/lib/abstract_controller/logger.rb +5 -1
  15. data/lib/abstract_controller/railties/routes_helpers.rb +10 -3
  16. data/lib/abstract_controller/rendering.rb +56 -56
  17. data/lib/abstract_controller/translation.rb +29 -15
  18. data/lib/abstract_controller/url_for.rb +15 -11
  19. data/lib/abstract_controller.rb +21 -5
  20. data/lib/action_controller/api/api_rendering.rb +18 -0
  21. data/lib/action_controller/api.rb +154 -0
  22. data/lib/action_controller/base.rb +219 -155
  23. data/lib/action_controller/caching.rb +28 -68
  24. data/lib/action_controller/deprecator.rb +9 -0
  25. data/lib/action_controller/form_builder.rb +55 -0
  26. data/lib/action_controller/log_subscriber.rb +35 -22
  27. data/lib/action_controller/metal/allow_browser.rb +119 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
  29. data/lib/action_controller/metal/conditional_get.rb +259 -122
  30. data/lib/action_controller/metal/content_security_policy.rb +86 -0
  31. data/lib/action_controller/metal/cookies.rb +9 -5
  32. data/lib/action_controller/metal/data_streaming.rb +87 -104
  33. data/lib/action_controller/metal/default_headers.rb +21 -0
  34. data/lib/action_controller/metal/etag_with_flash.rb +22 -0
  35. data/lib/action_controller/metal/etag_with_template_digest.rb +35 -26
  36. data/lib/action_controller/metal/exceptions.rb +71 -24
  37. data/lib/action_controller/metal/flash.rb +26 -19
  38. data/lib/action_controller/metal/head.rb +45 -36
  39. data/lib/action_controller/metal/helpers.rb +80 -64
  40. data/lib/action_controller/metal/http_authentication.rb +297 -244
  41. data/lib/action_controller/metal/implicit_render.rb +57 -9
  42. data/lib/action_controller/metal/instrumentation.rb +76 -64
  43. data/lib/action_controller/metal/live.rb +238 -176
  44. data/lib/action_controller/metal/logging.rb +22 -0
  45. data/lib/action_controller/metal/mime_responds.rb +177 -166
  46. data/lib/action_controller/metal/parameter_encoding.rb +84 -0
  47. data/lib/action_controller/metal/params_wrapper.rb +145 -118
  48. data/lib/action_controller/metal/permissions_policy.rb +38 -0
  49. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  50. data/lib/action_controller/metal/redirecting.rb +203 -64
  51. data/lib/action_controller/metal/renderers.rb +108 -65
  52. data/lib/action_controller/metal/rendering.rb +216 -56
  53. data/lib/action_controller/metal/request_forgery_protection.rb +496 -163
  54. data/lib/action_controller/metal/rescue.rb +19 -21
  55. data/lib/action_controller/metal/streaming.rb +179 -138
  56. data/lib/action_controller/metal/strong_parameters.rb +1058 -382
  57. data/lib/action_controller/metal/testing.rb +11 -17
  58. data/lib/action_controller/metal/url_for.rb +37 -21
  59. data/lib/action_controller/metal.rb +236 -138
  60. data/lib/action_controller/railtie.rb +89 -11
  61. data/lib/action_controller/railties/helpers.rb +5 -1
  62. data/lib/action_controller/renderer.rb +161 -0
  63. data/lib/action_controller/template_assertions.rb +13 -0
  64. data/lib/action_controller/test_case.rb +425 -497
  65. data/lib/action_controller.rb +44 -22
  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 +119 -63
  69. data/lib/action_dispatch/http/content_disposition.rb +47 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +364 -0
  71. data/lib/action_dispatch/http/filter_parameters.rb +36 -34
  72. data/lib/action_dispatch/http/filter_redirect.rb +24 -12
  73. data/lib/action_dispatch/http/headers.rb +66 -31
  74. data/lib/action_dispatch/http/mime_negotiation.rb +106 -75
  75. data/lib/action_dispatch/http/mime_type.rb +196 -136
  76. data/lib/action_dispatch/http/mime_types.rb +25 -7
  77. data/lib/action_dispatch/http/parameters.rb +97 -45
  78. data/lib/action_dispatch/http/permissions_policy.rb +187 -0
  79. data/lib/action_dispatch/http/rack_cache.rb +6 -0
  80. data/lib/action_dispatch/http/request.rb +299 -170
  81. data/lib/action_dispatch/http/response.rb +311 -160
  82. data/lib/action_dispatch/http/upload.rb +52 -23
  83. data/lib/action_dispatch/http/url.rb +201 -125
  84. data/lib/action_dispatch/journey/formatter.rb +110 -50
  85. data/lib/action_dispatch/journey/gtg/builder.rb +37 -50
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +20 -17
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +96 -36
  88. data/lib/action_dispatch/journey/nfa/dot.rb +5 -14
  89. data/lib/action_dispatch/journey/nodes/node.rb +100 -20
  90. data/lib/action_dispatch/journey/parser.rb +19 -17
  91. data/lib/action_dispatch/journey/parser.y +4 -3
  92. data/lib/action_dispatch/journey/parser_extras.rb +14 -4
  93. data/lib/action_dispatch/journey/path/pattern.rb +79 -63
  94. data/lib/action_dispatch/journey/route.rb +108 -44
  95. data/lib/action_dispatch/journey/router/utils.rb +41 -29
  96. data/lib/action_dispatch/journey/router.rb +64 -57
  97. data/lib/action_dispatch/journey/routes.rb +23 -21
  98. data/lib/action_dispatch/journey/scanner.rb +28 -17
  99. data/lib/action_dispatch/journey/visitors.rb +100 -54
  100. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  101. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  102. data/lib/action_dispatch/journey.rb +7 -5
  103. data/lib/action_dispatch/log_subscriber.rb +25 -0
  104. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  105. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  106. data/lib/action_dispatch/middleware/callbacks.rb +7 -6
  107. data/lib/action_dispatch/middleware/cookies.rb +471 -328
  108. data/lib/action_dispatch/middleware/debug_exceptions.rb +149 -66
  109. data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
  110. data/lib/action_dispatch/middleware/debug_view.rb +73 -0
  111. data/lib/action_dispatch/middleware/exception_wrapper.rb +275 -73
  112. data/lib/action_dispatch/middleware/executor.rb +32 -0
  113. data/lib/action_dispatch/middleware/flash.rb +143 -101
  114. data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
  115. data/lib/action_dispatch/middleware/public_exceptions.rb +36 -27
  116. data/lib/action_dispatch/middleware/reloader.rb +10 -92
  117. data/lib/action_dispatch/middleware/remote_ip.rb +133 -107
  118. data/lib/action_dispatch/middleware/request_id.rb +29 -15
  119. data/lib/action_dispatch/middleware/server_timing.rb +78 -0
  120. data/lib/action_dispatch/middleware/session/abstract_store.rb +49 -27
  121. data/lib/action_dispatch/middleware/session/cache_store.rb +33 -16
  122. data/lib/action_dispatch/middleware/session/cookie_store.rb +86 -80
  123. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -3
  124. data/lib/action_dispatch/middleware/show_exceptions.rb +66 -36
  125. data/lib/action_dispatch/middleware/ssl.rb +134 -36
  126. data/lib/action_dispatch/middleware/stack.rb +109 -44
  127. data/lib/action_dispatch/middleware/static.rb +159 -90
  128. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +7 -24
  132. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  133. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
  136. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
  137. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
  138. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +26 -7
  139. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +3 -3
  140. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  141. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
  142. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +139 -15
  143. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
  144. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  145. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +6 -6
  146. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +7 -7
  147. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +9 -9
  148. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  149. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  150. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  151. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +7 -4
  152. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +125 -93
  153. data/lib/action_dispatch/railtie.rb +44 -16
  154. data/lib/action_dispatch/request/session.rb +159 -69
  155. data/lib/action_dispatch/request/utils.rb +97 -23
  156. data/lib/action_dispatch/routing/endpoint.rb +11 -2
  157. data/lib/action_dispatch/routing/inspector.rb +195 -106
  158. data/lib/action_dispatch/routing/mapper.rb +1338 -955
  159. data/lib/action_dispatch/routing/polymorphic_routes.rb +234 -201
  160. data/lib/action_dispatch/routing/redirection.rb +78 -51
  161. data/lib/action_dispatch/routing/route_set.rb +460 -374
  162. data/lib/action_dispatch/routing/routes_proxy.rb +36 -12
  163. data/lib/action_dispatch/routing/url_for.rb +172 -124
  164. data/lib/action_dispatch/routing.rb +159 -158
  165. data/lib/action_dispatch/system_test_case.rb +206 -0
  166. data/lib/action_dispatch/system_testing/browser.rb +84 -0
  167. data/lib/action_dispatch/system_testing/driver.rb +85 -0
  168. data/lib/action_dispatch/system_testing/server.rb +33 -0
  169. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
  170. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
  171. data/lib/action_dispatch/testing/assertion_response.rb +48 -0
  172. data/lib/action_dispatch/testing/assertions/response.rb +71 -39
  173. data/lib/action_dispatch/testing/assertions/routing.rb +228 -103
  174. data/lib/action_dispatch/testing/assertions.rb +9 -6
  175. data/lib/action_dispatch/testing/integration.rb +486 -306
  176. data/lib/action_dispatch/testing/request_encoder.rb +60 -0
  177. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  178. data/lib/action_dispatch/testing/test_process.rb +35 -22
  179. data/lib/action_dispatch/testing/test_request.rb +29 -34
  180. data/lib/action_dispatch/testing/test_response.rb +48 -15
  181. data/lib/action_dispatch.rb +82 -40
  182. data/lib/action_pack/gem_version.rb +8 -4
  183. data/lib/action_pack/version.rb +6 -2
  184. data/lib/action_pack.rb +21 -18
  185. metadata +146 -56
  186. data/lib/action_controller/caching/fragments.rb +0 -103
  187. data/lib/action_controller/metal/force_ssl.rb +0 -97
  188. data/lib/action_controller/metal/hide_actions.rb +0 -40
  189. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  190. data/lib/action_controller/middleware.rb +0 -39
  191. data/lib/action_controller/model_naming.rb +0 -12
  192. data/lib/action_dispatch/http/parameter_filter.rb +0 -72
  193. data/lib/action_dispatch/journey/backwards.rb +0 -5
  194. data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
  195. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  196. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
  197. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  198. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  199. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +0 -27
  200. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  201. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  202. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,53 +1,93 @@
1
- require 'active_support/core_ext/hash/keys'
1
+ # frozen_string_literal: true
2
2
 
3
- module ActionDispatch
4
- class Request < Rack::Request
5
- # Access the contents of the flash. Use <tt>flash["notice"]</tt> to
6
- # read a notice you put there or <tt>flash["notice"] = "hello"</tt>
7
- # to put a new one.
8
- def flash
9
- @env[Flash::KEY] ||= Flash::FlashHash.from_session_value(session["flash"])
10
- end
11
- end
3
+ # :markup: markdown
12
4
 
13
- # The flash provides a way to pass temporary primitive-types (String, Array, Hash) between actions. Anything you place in the flash will be exposed
14
- # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
15
- # action that sets <tt>flash[:notice] = "Post successfully created"</tt> before redirecting to a display action that can
16
- # then expose the flash to its template. Actually, that exposure is automatically done.
5
+ require "active_support/core_ext/hash/keys"
6
+
7
+ module ActionDispatch
8
+ # # Action Dispatch Flash
17
9
  #
18
- # class PostsController < ActionController::Base
19
- # def create
20
- # # save post
21
- # flash[:notice] = "Post successfully created"
22
- # redirect_to @post
23
- # 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
+ #
18
+ # class PostsController < ActionController::Base
19
+ # def create
20
+ # # save post
21
+ # flash[:notice] = "Post successfully created"
22
+ # redirect_to @post
23
+ # end
24
24
  #
25
- # def show
26
- # # doesn't need to assign the flash notice to the template, that's done automatically
25
+ # def show
26
+ # # doesn't need to assign the flash notice to the template, that's done automatically
27
+ # end
27
28
  # end
28
- # end
29
29
  #
30
- # show.html.erb
30
+ # Then in `show.html.erb`:
31
+ #
31
32
  # <% if flash[:notice] %>
32
33
  # <div class="notice"><%= flash[:notice] %></div>
33
34
  # <% end %>
34
35
  #
35
- # 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:
36
38
  #
37
- # flash.alert = "You must be logged in"
38
- # flash.notice = "Post successfully created"
39
+ # flash.alert = "You must be logged in"
40
+ # flash.notice = "Post successfully created"
39
41
  #
40
- # This example places a string in the flash. And of course, you can put as many as you like at a time too. If you want to pass
41
- # non-primitive types, you will have to handle that in your application. Example: To show messages with links, you will have to
42
- # use sanitize helper.
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.
43
46
  #
44
47
  # Just remember: They'll be gone by the time the next action has been performed.
45
48
  #
46
49
  # See docs on the FlashHash class for more details about the flash.
47
50
  class Flash
48
- KEY = 'action_dispatch.request.flash_hash'.freeze
51
+ KEY = "action_dispatch.request.flash_hash"
52
+
53
+ module RequestMethods
54
+ # Access the contents of the flash. Returns a ActionDispatch::Flash::FlashHash.
55
+ #
56
+ # See ActionDispatch::Flash for example usage.
57
+ def flash
58
+ flash = flash_hash
59
+ return flash if flash
60
+ self.flash = Flash::FlashHash.from_session_value(session["flash"])
61
+ end
62
+
63
+ def flash=(flash)
64
+ set_header Flash::KEY, flash
65
+ end
66
+
67
+ def flash_hash # :nodoc:
68
+ get_header Flash::KEY
69
+ end
49
70
 
50
- class FlashNow #:nodoc:
71
+ def commit_flash # :nodoc:
72
+ return unless session.enabled?
73
+
74
+ if flash_hash && (flash_hash.present? || session.key?("flash"))
75
+ session["flash"] = flash_hash.to_session_value
76
+ self.flash = flash_hash.dup
77
+ end
78
+
79
+ if session.loaded? && session.key?("flash") && session["flash"].nil?
80
+ session.delete("flash")
81
+ end
82
+ end
83
+
84
+ def reset_session # :nodoc:
85
+ super
86
+ self.flash = nil
87
+ end
88
+ end
89
+
90
+ class FlashNow # :nodoc:
51
91
  attr_accessor :flash
52
92
 
53
93
  def initialize(flash)
@@ -65,12 +105,12 @@ module ActionDispatch
65
105
  @flash[k.to_s]
66
106
  end
67
107
 
68
- # Convenience accessor for <tt>flash.now[:alert]=</tt>.
108
+ # Convenience accessor for `flash.now[:alert]=`.
69
109
  def alert=(message)
70
110
  self[:alert] = message
71
111
  end
72
112
 
73
- # Convenience accessor for <tt>flash.now[:notice]=</tt>.
113
+ # Convenience accessor for `flash.now[:notice]=`.
74
114
  def notice=(message)
75
115
  self[:notice] = message
76
116
  end
@@ -79,28 +119,34 @@ module ActionDispatch
79
119
  class FlashHash
80
120
  include Enumerable
81
121
 
82
- def self.from_session_value(value) #:nodoc:
83
- flash = case value
84
- when FlashHash # Rails 3.1, 3.2
85
- new(value.instance_variable_get(:@flashes), value.instance_variable_get(:@used))
86
- when Hash # Rails 4.0
87
- new(value['flashes'], value['discard'])
88
- else
89
- new
90
- end
91
-
92
- flash.tap(&:sweep)
122
+ def self.from_session_value(value) # :nodoc:
123
+ case value
124
+ when FlashHash # Rails 3.1, 3.2
125
+ flashes = value.instance_variable_get(:@flashes)
126
+ if discard = value.instance_variable_get(:@used)
127
+ flashes.except!(*discard)
128
+ end
129
+ new(flashes, flashes.keys)
130
+ when Hash # Rails 4.0
131
+ flashes = value["flashes"]
132
+ if discard = value["discard"]
133
+ flashes.except!(*discard)
134
+ end
135
+ new(flashes, flashes.keys)
136
+ else
137
+ new
138
+ end
93
139
  end
94
-
95
- # Builds a hash containing the discarded values and the hashes
96
- # representing the flashes.
97
- # If there are no values in @flashes, returns nil.
98
- def to_session_value #:nodoc:
99
- return nil if empty?
100
- {'discard' => @discard.to_a, 'flashes' => @flashes}
140
+
141
+ # Builds a hash containing the flashes to keep for the next request. If there
142
+ # are none to keep, returns `nil`.
143
+ def to_session_value # :nodoc:
144
+ flashes_to_keep = @flashes.except(*@discard)
145
+ return nil if flashes_to_keep.empty?
146
+ { "discard" => [], "flashes" => flashes_to_keep }
101
147
  end
102
148
 
103
- def initialize(flashes = {}, discard = []) #:nodoc:
149
+ def initialize(flashes = {}, discard = []) # :nodoc:
104
150
  @discard = Set.new(stringify_array(discard))
105
151
  @flashes = flashes.stringify_keys
106
152
  @now = nil
@@ -124,7 +170,7 @@ module ActionDispatch
124
170
  @flashes[k.to_s]
125
171
  end
126
172
 
127
- def update(h) #:nodoc:
173
+ def update(h) # :nodoc:
128
174
  @discard.subtract stringify_array(h.keys)
129
175
  @flashes.update h.stringify_keys
130
176
  self
@@ -138,6 +184,8 @@ module ActionDispatch
138
184
  @flashes.key? name.to_s
139
185
  end
140
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.
141
189
  def delete(key)
142
190
  key = key.to_s
143
191
  @discard.delete key
@@ -164,48 +212,55 @@ module ActionDispatch
164
212
 
165
213
  alias :merge! :update
166
214
 
167
- def replace(h) #:nodoc:
215
+ def replace(h) # :nodoc:
168
216
  @discard.clear
169
217
  @flashes.replace h.stringify_keys
170
218
  self
171
219
  end
172
220
 
173
- # 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.
174
223
  #
175
224
  # flash.now[:message] = "Hello current action"
176
225
  #
177
- # This method enables you to use the flash as a central messaging system in your app.
178
- # When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>).
179
- # When you need to pass an object to the current action, you use <tt>now</tt>, and your object will
180
- # 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.
181
230
  #
182
- # 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']`.
183
233
  #
184
234
  # Also, brings two convenience accessors:
185
235
  #
186
- # flash.now.alert = "Beware now!"
187
- # # Equivalent to flash.now[:alert] = "Beware now!"
236
+ # flash.now.alert = "Beware now!"
237
+ # # Equivalent to flash.now[:alert] = "Beware now!"
188
238
  #
189
- # flash.now.notice = "Good luck now!"
190
- # # Equivalent to flash.now[:notice] = "Good luck now!"
239
+ # flash.now.notice = "Good luck now!"
240
+ # # Equivalent to flash.now[:notice] = "Good luck now!"
191
241
  def now
192
242
  @now ||= FlashNow.new(self)
193
243
  end
194
244
 
195
- # 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:
196
247
  #
197
- # flash.keep # keeps the entire flash
198
- # 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
199
250
  def keep(k = nil)
200
251
  k = k.to_s if k
201
252
  @discard.subtract Array(k || keys)
202
253
  k ? self[k] : self
203
254
  end
204
255
 
205
- # 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:
206
258
  #
207
259
  # flash.discard # discard the entire flash at the end of the current action
208
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.
209
264
  def discard(k = nil)
210
265
  k = k.to_s if k
211
266
  @discard.merge Array(k || keys)
@@ -214,63 +269,50 @@ module ActionDispatch
214
269
 
215
270
  # Mark for removal entries that were kept, and delete unkept ones.
216
271
  #
217
- # This method is called automatically by filters, so you generally don't need to care about it.
218
- def sweep #:nodoc:
272
+ # This method is called automatically by filters, so you generally don't need to
273
+ # care about it.
274
+ def sweep # :nodoc:
219
275
  @discard.each { |k| @flashes.delete k }
220
276
  @discard.replace @flashes.keys
221
277
  end
222
278
 
223
- # Convenience accessor for <tt>flash[:alert]</tt>.
279
+ # Convenience accessor for `flash[:alert]`.
224
280
  def alert
225
281
  self[:alert]
226
282
  end
227
283
 
228
- # Convenience accessor for <tt>flash[:alert]=</tt>.
284
+ # Convenience accessor for `flash[:alert]=`.
229
285
  def alert=(message)
230
286
  self[:alert] = message
231
287
  end
232
288
 
233
- # Convenience accessor for <tt>flash[:notice]</tt>.
289
+ # Convenience accessor for `flash[:notice]`.
234
290
  def notice
235
291
  self[:notice]
236
292
  end
237
293
 
238
- # Convenience accessor for <tt>flash[:notice]=</tt>.
294
+ # Convenience accessor for `flash[:notice]=`.
239
295
  def notice=(message)
240
296
  self[:notice] = message
241
297
  end
242
298
 
243
299
  protected
244
- def now_is_loaded?
245
- @now
246
- end
247
-
248
- def stringify_array(array)
249
- array.map do |item|
250
- item.kind_of?(Symbol) ? item.to_s : item
300
+ def now_is_loaded?
301
+ @now
251
302
  end
252
- end
253
- end
254
303
 
255
- def initialize(app)
256
- @app = app
304
+ private
305
+ def stringify_array(array) # :doc:
306
+ array.map do |item|
307
+ item.kind_of?(Symbol) ? item.to_s : item
308
+ end
309
+ end
257
310
  end
258
311
 
259
- def call(env)
260
- @app.call(env)
261
- ensure
262
- session = Request::Session.find(env) || {}
263
- flash_hash = env[KEY]
264
-
265
- if flash_hash && (flash_hash.present? || session.key?('flash'))
266
- session["flash"] = flash_hash.to_session_value
267
- env[KEY] = flash_hash.dup
268
- end
312
+ def self.new(app) app; end
313
+ end
269
314
 
270
- if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?)
271
- session.key?('flash') && session['flash'].nil?
272
- session.delete('flash')
273
- end
274
- end
315
+ class Request
316
+ prepend Flash::RequestMethods
275
317
  end
276
318
  end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActionDispatch
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`.
11
+ #
12
+ # Requests can opt-out of Host Authorization with `exclude`:
13
+ #
14
+ # config.host_authorization = { exclude: ->(request) { request.path =~ /healthcheck/ } }
15
+ #
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.
22
+ class HostAuthorization
23
+ ALLOWED_HOSTS_IN_DEVELOPMENT = [".localhost", ".test", IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0")]
24
+ PORT_REGEX = /(?::\d+)/ # :nodoc:
25
+ SUBDOMAIN_REGEX = /(?:[a-z0-9-]+\.)/i # :nodoc:
26
+ IPV4_HOSTNAME = /(?<host>\d+\.\d+\.\d+\.\d+)#{PORT_REGEX}?/ # :nodoc:
27
+ IPV6_HOSTNAME = /(?<host>[a-f0-9]*:[a-f0-9.:]+)/i # :nodoc:
28
+ IPV6_HOSTNAME_WITH_PORT = /\[#{IPV6_HOSTNAME}\]#{PORT_REGEX}/i # :nodoc:
29
+ VALID_IP_HOSTNAME = Regexp.union( # :nodoc:
30
+ /\A#{IPV4_HOSTNAME}\z/,
31
+ /\A#{IPV6_HOSTNAME}\z/,
32
+ /\A#{IPV6_HOSTNAME_WITH_PORT}\z/,
33
+ )
34
+
35
+ class Permissions # :nodoc:
36
+ def initialize(hosts)
37
+ @hosts = sanitize_hosts(hosts)
38
+ end
39
+
40
+ def empty?
41
+ @hosts.empty?
42
+ end
43
+
44
+ def allows?(host)
45
+ @hosts.any? do |allowed|
46
+ if allowed.is_a?(IPAddr)
47
+ begin
48
+ allowed === extract_hostname(host)
49
+ rescue
50
+ # IPAddr#=== raises an error if you give it a hostname instead of IP. Treat
51
+ # similar errors as blocked access.
52
+ false
53
+ end
54
+ else
55
+ allowed === host
56
+ end
57
+ end
58
+ end
59
+
60
+ private
61
+ def sanitize_hosts(hosts)
62
+ Array(hosts).map do |host|
63
+ case host
64
+ when Regexp then sanitize_regexp(host)
65
+ when String then sanitize_string(host)
66
+ else host
67
+ end
68
+ end
69
+ end
70
+
71
+ def sanitize_regexp(host)
72
+ /\A#{host}#{PORT_REGEX}?\z/
73
+ end
74
+
75
+ def sanitize_string(host)
76
+ if host.start_with?(".")
77
+ /\A#{SUBDOMAIN_REGEX}?#{Regexp.escape(host[1..-1])}#{PORT_REGEX}?\z/i
78
+ else
79
+ /\A#{Regexp.escape host}#{PORT_REGEX}?\z/i
80
+ end
81
+ end
82
+
83
+ def extract_hostname(host)
84
+ host.slice(VALID_IP_HOSTNAME, "host") || host
85
+ end
86
+ end
87
+
88
+ class DefaultResponseApp # :nodoc:
89
+ RESPONSE_STATUS = 403
90
+
91
+ def call(env)
92
+ request = Request.new(env)
93
+ format = request.xhr? ? "text/plain" : "text/html"
94
+
95
+ log_error(request)
96
+ response(format, response_body(request))
97
+ end
98
+
99
+ private
100
+ def response_body(request)
101
+ return "" unless request.get_header("action_dispatch.show_detailed_exceptions")
102
+
103
+ template = DebugView.new(hosts: request.env["action_dispatch.blocked_hosts"])
104
+ template.render(template: "rescues/blocked_host", layout: "rescues/layout")
105
+ end
106
+
107
+ def response(format, body)
108
+ [RESPONSE_STATUS,
109
+ { Rack::CONTENT_TYPE => "#{format}; charset=#{Response.default_charset}",
110
+ Rack::CONTENT_LENGTH => body.bytesize.to_s },
111
+ [body]]
112
+ end
113
+
114
+ def log_error(request)
115
+ logger = available_logger(request)
116
+
117
+ return unless logger
118
+
119
+ logger.error("[#{self.class.name}] Blocked hosts: #{request.env["action_dispatch.blocked_hosts"].join(", ")}")
120
+ end
121
+
122
+ def available_logger(request)
123
+ request.logger || ActionView::Base.logger
124
+ end
125
+ end
126
+
127
+ def initialize(app, hosts, exclude: nil, response_app: nil)
128
+ @app = app
129
+ @permissions = Permissions.new(hosts)
130
+ @exclude = exclude
131
+
132
+ @response_app = response_app || DefaultResponseApp.new
133
+ end
134
+
135
+ def call(env)
136
+ return @app.call(env) if @permissions.empty?
137
+
138
+ request = Request.new(env)
139
+ hosts = blocked_hosts(request)
140
+
141
+ if hosts.empty? || excluded?(request)
142
+ mark_as_authorized(request)
143
+ @app.call(env)
144
+ else
145
+ env["action_dispatch.blocked_hosts"] = hosts
146
+ @response_app.call(env)
147
+ end
148
+ end
149
+
150
+ private
151
+ def blocked_hosts(request)
152
+ hosts = []
153
+
154
+ origin_host = request.get_header("HTTP_HOST")
155
+ hosts << origin_host unless @permissions.allows?(origin_host)
156
+
157
+ forwarded_host = request.x_forwarded_host&.split(/,\s?/)&.last
158
+ hosts << forwarded_host unless forwarded_host.blank? || @permissions.allows?(forwarded_host)
159
+
160
+ hosts
161
+ end
162
+
163
+ def excluded?(request)
164
+ @exclude && @exclude.call(request)
165
+ end
166
+
167
+ def mark_as_authorized(request)
168
+ request.set_header("action_dispatch.authorized_host", request.host)
169
+ end
170
+ end
171
+ end
@@ -1,11 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
1
5
  module ActionDispatch
6
+ # # Action Dispatch PublicExceptions
7
+ #
2
8
  # When called, this middleware renders an error page. By default if an HTML
3
9
  # response is expected it will render static error pages from the `/public`
4
10
  # directory. For example when this middleware receives a 500 response it will
5
- # render the template found in `/public/500.html`.
6
- # If an internationalized locale is set, this middleware will attempt to render
7
- # the template in `/public/500.<locale>.html`. If an internationalized template
8
- # is not found it will fall back on `/public/500.html`.
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`.
9
15
  #
10
16
  # When a request with a content type other than HTML is made, this middleware
11
17
  # will attempt to convert error information into the appropriate response type.
@@ -17,39 +23,42 @@ module ActionDispatch
17
23
  end
18
24
 
19
25
  def call(env)
20
- status = env["PATH_INFO"][1..-1]
21
26
  request = ActionDispatch::Request.new(env)
22
- content_type = request.formats.first
23
- body = { :status => status, :error => Rack::Utils::HTTP_STATUS_CODES.fetch(status.to_i, Rack::Utils::HTTP_STATUS_CODES[500]) }
27
+ status = request.path_info[1..-1].to_i
28
+ begin
29
+ content_type = request.formats.first
30
+ rescue ActionDispatch::Http::MimeNegotiation::InvalidType
31
+ content_type = Mime[:text]
32
+ end
33
+ body = { status: status, error: Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) }
24
34
 
25
35
  render(status, content_type, body)
26
36
  end
27
37
 
28
38
  private
29
-
30
- def render(status, content_type, body)
31
- format = "to_#{content_type.to_sym}" if content_type
32
- if format && body.respond_to?(format)
33
- render_format(status, content_type, body.public_send(format))
34
- else
35
- render_html(status)
39
+ def render(status, content_type, body)
40
+ format = "to_#{content_type.to_sym}" if content_type
41
+ if format && body.respond_to?(format)
42
+ render_format(status, content_type, body.public_send(format))
43
+ else
44
+ render_html(status)
45
+ end
36
46
  end
37
- end
38
47
 
39
- def render_format(status, content_type, body)
40
- [status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
41
- 'Content-Length' => body.bytesize.to_s}, [body]]
42
- end
48
+ def render_format(status, content_type, body)
49
+ [status, { Rack::CONTENT_TYPE => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
50
+ Rack::CONTENT_LENGTH => body.bytesize.to_s }, [body]]
51
+ end
43
52
 
44
- def render_html(status)
45
- path = "#{public_path}/#{status}.#{I18n.locale}.html"
46
- path = "#{public_path}/#{status}.html" unless (found = File.exist?(path))
53
+ def render_html(status)
54
+ path = "#{public_path}/#{status}.#{I18n.locale}.html"
55
+ path = "#{public_path}/#{status}.html" unless (found = File.exist?(path))
47
56
 
48
- if found || File.exist?(path)
49
- render_format(status, 'text/html', File.read(path))
50
- else
51
- [404, { "X-Cascade" => "pass" }, []]
57
+ if found || File.exist?(path)
58
+ render_format(status, "text/html", File.read(path))
59
+ else
60
+ [404, { Constants::X_CASCADE => "pass" }, []]
61
+ end
52
62
  end
53
- end
54
63
  end
55
64
  end