actionpack 6.0.0

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 (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +311 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +58 -0
  5. data/lib/abstract_controller.rb +27 -0
  6. data/lib/abstract_controller/asset_paths.rb +12 -0
  7. data/lib/abstract_controller/base.rb +267 -0
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/caching/fragments.rb +150 -0
  10. data/lib/abstract_controller/callbacks.rb +224 -0
  11. data/lib/abstract_controller/collector.rb +43 -0
  12. data/lib/abstract_controller/error.rb +6 -0
  13. data/lib/abstract_controller/helpers.rb +194 -0
  14. data/lib/abstract_controller/logger.rb +14 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +20 -0
  16. data/lib/abstract_controller/rendering.rb +127 -0
  17. data/lib/abstract_controller/translation.rb +32 -0
  18. data/lib/abstract_controller/url_for.rb +35 -0
  19. data/lib/action_controller.rb +67 -0
  20. data/lib/action_controller/api.rb +150 -0
  21. data/lib/action_controller/api/api_rendering.rb +16 -0
  22. data/lib/action_controller/base.rb +271 -0
  23. data/lib/action_controller/caching.rb +46 -0
  24. data/lib/action_controller/form_builder.rb +50 -0
  25. data/lib/action_controller/log_subscriber.rb +81 -0
  26. data/lib/action_controller/metal.rb +256 -0
  27. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  28. data/lib/action_controller/metal/conditional_get.rb +280 -0
  29. data/lib/action_controller/metal/content_security_policy.rb +52 -0
  30. data/lib/action_controller/metal/cookies.rb +16 -0
  31. data/lib/action_controller/metal/data_streaming.rb +151 -0
  32. data/lib/action_controller/metal/default_headers.rb +17 -0
  33. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  34. data/lib/action_controller/metal/etag_with_template_digest.rb +57 -0
  35. data/lib/action_controller/metal/exceptions.rb +74 -0
  36. data/lib/action_controller/metal/flash.rb +61 -0
  37. data/lib/action_controller/metal/force_ssl.rb +58 -0
  38. data/lib/action_controller/metal/head.rb +60 -0
  39. data/lib/action_controller/metal/helpers.rb +122 -0
  40. data/lib/action_controller/metal/http_authentication.rb +518 -0
  41. data/lib/action_controller/metal/implicit_render.rb +63 -0
  42. data/lib/action_controller/metal/instrumentation.rb +105 -0
  43. data/lib/action_controller/metal/live.rb +314 -0
  44. data/lib/action_controller/metal/mime_responds.rb +324 -0
  45. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  46. data/lib/action_controller/metal/params_wrapper.rb +297 -0
  47. data/lib/action_controller/metal/redirecting.rb +133 -0
  48. data/lib/action_controller/metal/renderers.rb +181 -0
  49. data/lib/action_controller/metal/rendering.rb +122 -0
  50. data/lib/action_controller/metal/request_forgery_protection.rb +456 -0
  51. data/lib/action_controller/metal/rescue.rb +28 -0
  52. data/lib/action_controller/metal/streaming.rb +223 -0
  53. data/lib/action_controller/metal/strong_parameters.rb +1105 -0
  54. data/lib/action_controller/metal/testing.rb +16 -0
  55. data/lib/action_controller/metal/url_for.rb +58 -0
  56. data/lib/action_controller/railtie.rb +89 -0
  57. data/lib/action_controller/railties/helpers.rb +24 -0
  58. data/lib/action_controller/renderer.rb +130 -0
  59. data/lib/action_controller/template_assertions.rb +11 -0
  60. data/lib/action_controller/test_case.rb +626 -0
  61. data/lib/action_dispatch.rb +114 -0
  62. data/lib/action_dispatch/http/cache.rb +226 -0
  63. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  64. data/lib/action_dispatch/http/content_security_policy.rb +284 -0
  65. data/lib/action_dispatch/http/filter_parameters.rb +86 -0
  66. data/lib/action_dispatch/http/filter_redirect.rb +37 -0
  67. data/lib/action_dispatch/http/headers.rb +132 -0
  68. data/lib/action_dispatch/http/mime_negotiation.rb +177 -0
  69. data/lib/action_dispatch/http/mime_type.rb +350 -0
  70. data/lib/action_dispatch/http/mime_types.rb +50 -0
  71. data/lib/action_dispatch/http/parameter_filter.rb +12 -0
  72. data/lib/action_dispatch/http/parameters.rb +136 -0
  73. data/lib/action_dispatch/http/rack_cache.rb +63 -0
  74. data/lib/action_dispatch/http/request.rb +427 -0
  75. data/lib/action_dispatch/http/response.rb +534 -0
  76. data/lib/action_dispatch/http/upload.rb +92 -0
  77. data/lib/action_dispatch/http/url.rb +350 -0
  78. data/lib/action_dispatch/journey.rb +7 -0
  79. data/lib/action_dispatch/journey/formatter.rb +189 -0
  80. data/lib/action_dispatch/journey/gtg/builder.rb +164 -0
  81. data/lib/action_dispatch/journey/gtg/simulator.rb +41 -0
  82. data/lib/action_dispatch/journey/gtg/transition_table.rb +158 -0
  83. data/lib/action_dispatch/journey/nfa/builder.rb +78 -0
  84. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  85. data/lib/action_dispatch/journey/nfa/simulator.rb +47 -0
  86. data/lib/action_dispatch/journey/nfa/transition_table.rb +120 -0
  87. data/lib/action_dispatch/journey/nodes/node.rb +141 -0
  88. data/lib/action_dispatch/journey/parser.rb +199 -0
  89. data/lib/action_dispatch/journey/parser.y +50 -0
  90. data/lib/action_dispatch/journey/parser_extras.rb +31 -0
  91. data/lib/action_dispatch/journey/path/pattern.rb +203 -0
  92. data/lib/action_dispatch/journey/route.rb +204 -0
  93. data/lib/action_dispatch/journey/router.rb +153 -0
  94. data/lib/action_dispatch/journey/router/utils.rb +102 -0
  95. data/lib/action_dispatch/journey/routes.rb +81 -0
  96. data/lib/action_dispatch/journey/scanner.rb +71 -0
  97. data/lib/action_dispatch/journey/visitors.rb +268 -0
  98. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  99. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  100. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  101. data/lib/action_dispatch/middleware/actionable_exceptions.rb +39 -0
  102. data/lib/action_dispatch/middleware/callbacks.rb +34 -0
  103. data/lib/action_dispatch/middleware/cookies.rb +663 -0
  104. data/lib/action_dispatch/middleware/debug_exceptions.rb +185 -0
  105. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  106. data/lib/action_dispatch/middleware/debug_view.rb +68 -0
  107. data/lib/action_dispatch/middleware/exception_wrapper.rb +181 -0
  108. data/lib/action_dispatch/middleware/executor.rb +21 -0
  109. data/lib/action_dispatch/middleware/flash.rb +300 -0
  110. data/lib/action_dispatch/middleware/host_authorization.rb +103 -0
  111. data/lib/action_dispatch/middleware/public_exceptions.rb +61 -0
  112. data/lib/action_dispatch/middleware/reloader.rb +12 -0
  113. data/lib/action_dispatch/middleware/remote_ip.rb +181 -0
  114. data/lib/action_dispatch/middleware/request_id.rb +43 -0
  115. data/lib/action_dispatch/middleware/session/abstract_store.rb +92 -0
  116. data/lib/action_dispatch/middleware/session/cache_store.rb +54 -0
  117. data/lib/action_dispatch/middleware/session/cookie_store.rb +113 -0
  118. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +28 -0
  119. data/lib/action_dispatch/middleware/show_exceptions.rb +62 -0
  120. data/lib/action_dispatch/middleware/ssl.rb +150 -0
  121. data/lib/action_dispatch/middleware/stack.rb +148 -0
  122. data/lib/action_dispatch/middleware/static.rb +129 -0
  123. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  124. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  125. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +24 -0
  126. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +29 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +62 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +38 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  136. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +15 -0
  137. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +165 -0
  138. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  139. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  140. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  141. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  142. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  143. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  144. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  145. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  146. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  147. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  148. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  149. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +203 -0
  150. data/lib/action_dispatch/railtie.rb +58 -0
  151. data/lib/action_dispatch/request/session.rb +242 -0
  152. data/lib/action_dispatch/request/utils.rb +78 -0
  153. data/lib/action_dispatch/routing.rb +261 -0
  154. data/lib/action_dispatch/routing/endpoint.rb +17 -0
  155. data/lib/action_dispatch/routing/inspector.rb +274 -0
  156. data/lib/action_dispatch/routing/mapper.rb +2289 -0
  157. data/lib/action_dispatch/routing/polymorphic_routes.rb +351 -0
  158. data/lib/action_dispatch/routing/redirection.rb +201 -0
  159. data/lib/action_dispatch/routing/route_set.rb +887 -0
  160. data/lib/action_dispatch/routing/routes_proxy.rb +69 -0
  161. data/lib/action_dispatch/routing/url_for.rb +237 -0
  162. data/lib/action_dispatch/system_test_case.rb +168 -0
  163. data/lib/action_dispatch/system_testing/browser.rb +80 -0
  164. data/lib/action_dispatch/system_testing/driver.rb +68 -0
  165. data/lib/action_dispatch/system_testing/server.rb +31 -0
  166. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +97 -0
  167. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +33 -0
  168. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  169. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  170. data/lib/action_dispatch/testing/assertions.rb +24 -0
  171. data/lib/action_dispatch/testing/assertions/response.rb +106 -0
  172. data/lib/action_dispatch/testing/assertions/routing.rb +234 -0
  173. data/lib/action_dispatch/testing/integration.rb +659 -0
  174. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  175. data/lib/action_dispatch/testing/test_process.rb +50 -0
  176. data/lib/action_dispatch/testing/test_request.rb +71 -0
  177. data/lib/action_dispatch/testing/test_response.rb +25 -0
  178. data/lib/action_pack.rb +26 -0
  179. data/lib/action_pack/gem_version.rb +17 -0
  180. data/lib/action_pack/version.rb +10 -0
  181. metadata +329 -0
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/body_proxy"
4
+
5
+ module ActionDispatch
6
+ class Executor
7
+ def initialize(app, executor)
8
+ @app, @executor = app, executor
9
+ end
10
+
11
+ def call(env)
12
+ state = @executor.run!
13
+ begin
14
+ response = @app.call(env)
15
+ returned = response << ::Rack::BodyProxy.new(response.pop) { state.complete! }
16
+ ensure
17
+ state.complete! unless returned
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,300 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/keys"
4
+
5
+ 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.
10
+ #
11
+ # class PostsController < ActionController::Base
12
+ # def create
13
+ # # save post
14
+ # flash[:notice] = "Post successfully created"
15
+ # redirect_to @post
16
+ # end
17
+ #
18
+ # def show
19
+ # # doesn't need to assign the flash notice to the template, that's done automatically
20
+ # end
21
+ # end
22
+ #
23
+ # show.html.erb
24
+ # <% if flash[:notice] %>
25
+ # <div class="notice"><%= flash[:notice] %></div>
26
+ # <% end %>
27
+ #
28
+ # Since the +notice+ and +alert+ keys are a common idiom, convenience accessors are available:
29
+ #
30
+ # flash.alert = "You must be logged in"
31
+ # flash.notice = "Post successfully created"
32
+ #
33
+ # 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
34
+ # non-primitive types, you will have to handle that in your application. Example: To show messages with links, you will have to
35
+ # use sanitize helper.
36
+ #
37
+ # Just remember: They'll be gone by the time the next action has been performed.
38
+ #
39
+ # See docs on the FlashHash class for more details about the flash.
40
+ class Flash
41
+ KEY = "action_dispatch.request.flash_hash"
42
+
43
+ module RequestMethods
44
+ # Access the contents of the flash. Use <tt>flash["notice"]</tt> to
45
+ # read a notice you put there or <tt>flash["notice"] = "hello"</tt>
46
+ # to put a new one.
47
+ def flash
48
+ flash = flash_hash
49
+ return flash if flash
50
+ self.flash = Flash::FlashHash.from_session_value(session["flash"])
51
+ end
52
+
53
+ def flash=(flash)
54
+ set_header Flash::KEY, flash
55
+ end
56
+
57
+ def flash_hash # :nodoc:
58
+ get_header Flash::KEY
59
+ end
60
+
61
+ def commit_flash # :nodoc:
62
+ session = self.session || {}
63
+ flash_hash = self.flash_hash
64
+
65
+ if flash_hash && (flash_hash.present? || session.key?("flash"))
66
+ session["flash"] = flash_hash.to_session_value
67
+ self.flash = flash_hash.dup
68
+ end
69
+
70
+ if (!session.respond_to?(:loaded?) || session.loaded?) && # reset_session uses {}, which doesn't implement #loaded?
71
+ session.key?("flash") && session["flash"].nil?
72
+ session.delete("flash")
73
+ end
74
+ end
75
+
76
+ def reset_session # :nodoc:
77
+ super
78
+ self.flash = nil
79
+ end
80
+ end
81
+
82
+ class FlashNow #:nodoc:
83
+ attr_accessor :flash
84
+
85
+ def initialize(flash)
86
+ @flash = flash
87
+ end
88
+
89
+ def []=(k, v)
90
+ k = k.to_s
91
+ @flash[k] = v
92
+ @flash.discard(k)
93
+ v
94
+ end
95
+
96
+ def [](k)
97
+ @flash[k.to_s]
98
+ end
99
+
100
+ # Convenience accessor for <tt>flash.now[:alert]=</tt>.
101
+ def alert=(message)
102
+ self[:alert] = message
103
+ end
104
+
105
+ # Convenience accessor for <tt>flash.now[:notice]=</tt>.
106
+ def notice=(message)
107
+ self[:notice] = message
108
+ end
109
+ end
110
+
111
+ class FlashHash
112
+ include Enumerable
113
+
114
+ def self.from_session_value(value) #:nodoc:
115
+ case value
116
+ when FlashHash # Rails 3.1, 3.2
117
+ flashes = value.instance_variable_get(:@flashes)
118
+ if discard = value.instance_variable_get(:@used)
119
+ flashes.except!(*discard)
120
+ end
121
+ new(flashes, flashes.keys)
122
+ when Hash # Rails 4.0
123
+ flashes = value["flashes"]
124
+ if discard = value["discard"]
125
+ flashes.except!(*discard)
126
+ end
127
+ new(flashes, flashes.keys)
128
+ else
129
+ new
130
+ end
131
+ end
132
+
133
+ # Builds a hash containing the flashes to keep for the next request.
134
+ # If there are none to keep, returns +nil+.
135
+ def to_session_value #:nodoc:
136
+ flashes_to_keep = @flashes.except(*@discard)
137
+ return nil if flashes_to_keep.empty?
138
+ { "discard" => [], "flashes" => flashes_to_keep }
139
+ end
140
+
141
+ def initialize(flashes = {}, discard = []) #:nodoc:
142
+ @discard = Set.new(stringify_array(discard))
143
+ @flashes = flashes.stringify_keys
144
+ @now = nil
145
+ end
146
+
147
+ def initialize_copy(other)
148
+ if other.now_is_loaded?
149
+ @now = other.now.dup
150
+ @now.flash = self
151
+ end
152
+ super
153
+ end
154
+
155
+ def []=(k, v)
156
+ k = k.to_s
157
+ @discard.delete k
158
+ @flashes[k] = v
159
+ end
160
+
161
+ def [](k)
162
+ @flashes[k.to_s]
163
+ end
164
+
165
+ def update(h) #:nodoc:
166
+ @discard.subtract stringify_array(h.keys)
167
+ @flashes.update h.stringify_keys
168
+ self
169
+ end
170
+
171
+ def keys
172
+ @flashes.keys
173
+ end
174
+
175
+ def key?(name)
176
+ @flashes.key? name.to_s
177
+ end
178
+
179
+ def delete(key)
180
+ key = key.to_s
181
+ @discard.delete key
182
+ @flashes.delete key
183
+ self
184
+ end
185
+
186
+ def to_hash
187
+ @flashes.dup
188
+ end
189
+
190
+ def empty?
191
+ @flashes.empty?
192
+ end
193
+
194
+ def clear
195
+ @discard.clear
196
+ @flashes.clear
197
+ end
198
+
199
+ def each(&block)
200
+ @flashes.each(&block)
201
+ end
202
+
203
+ alias :merge! :update
204
+
205
+ def replace(h) #:nodoc:
206
+ @discard.clear
207
+ @flashes.replace h.stringify_keys
208
+ self
209
+ end
210
+
211
+ # Sets a flash that will not be available to the next action, only to the current.
212
+ #
213
+ # flash.now[:message] = "Hello current action"
214
+ #
215
+ # This method enables you to use the flash as a central messaging system in your app.
216
+ # When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>).
217
+ # When you need to pass an object to the current action, you use <tt>now</tt>, and your object will
218
+ # vanish when the current action is done.
219
+ #
220
+ # Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
221
+ #
222
+ # Also, brings two convenience accessors:
223
+ #
224
+ # flash.now.alert = "Beware now!"
225
+ # # Equivalent to flash.now[:alert] = "Beware now!"
226
+ #
227
+ # flash.now.notice = "Good luck now!"
228
+ # # Equivalent to flash.now[:notice] = "Good luck now!"
229
+ def now
230
+ @now ||= FlashNow.new(self)
231
+ end
232
+
233
+ # Keeps either the entire current flash or a specific flash entry available for the next action:
234
+ #
235
+ # flash.keep # keeps the entire flash
236
+ # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
237
+ def keep(k = nil)
238
+ k = k.to_s if k
239
+ @discard.subtract Array(k || keys)
240
+ k ? self[k] : self
241
+ end
242
+
243
+ # Marks the entire flash or a single flash entry to be discarded by the end of the current action:
244
+ #
245
+ # flash.discard # discard the entire flash at the end of the current action
246
+ # flash.discard(:warning) # discard only the "warning" entry at the end of the current action
247
+ def discard(k = nil)
248
+ k = k.to_s if k
249
+ @discard.merge Array(k || keys)
250
+ k ? self[k] : self
251
+ end
252
+
253
+ # Mark for removal entries that were kept, and delete unkept ones.
254
+ #
255
+ # This method is called automatically by filters, so you generally don't need to care about it.
256
+ def sweep #:nodoc:
257
+ @discard.each { |k| @flashes.delete k }
258
+ @discard.replace @flashes.keys
259
+ end
260
+
261
+ # Convenience accessor for <tt>flash[:alert]</tt>.
262
+ def alert
263
+ self[:alert]
264
+ end
265
+
266
+ # Convenience accessor for <tt>flash[:alert]=</tt>.
267
+ def alert=(message)
268
+ self[:alert] = message
269
+ end
270
+
271
+ # Convenience accessor for <tt>flash[:notice]</tt>.
272
+ def notice
273
+ self[:notice]
274
+ end
275
+
276
+ # Convenience accessor for <tt>flash[:notice]=</tt>.
277
+ def notice=(message)
278
+ self[:notice] = message
279
+ end
280
+
281
+ protected
282
+ def now_is_loaded?
283
+ @now
284
+ end
285
+
286
+ private
287
+ def stringify_array(array) # :doc:
288
+ array.map do |item|
289
+ item.kind_of?(Symbol) ? item.to_s : item
290
+ end
291
+ end
292
+ end
293
+
294
+ def self.new(app) app; end
295
+ end
296
+
297
+ class Request
298
+ prepend Flash::RequestMethods
299
+ end
300
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch/http/request"
4
+
5
+ module ActionDispatch
6
+ # This middleware guards from DNS rebinding attacks by explicitly permitting
7
+ # the hosts a request can be sent to.
8
+ #
9
+ # When a request comes to an unauthorized host, the +response_app+
10
+ # application will be executed and rendered. If no +response_app+ is given, a
11
+ # default one will run, which responds with +403 Forbidden+.
12
+ class HostAuthorization
13
+ class Permissions # :nodoc:
14
+ def initialize(hosts)
15
+ @hosts = sanitize_hosts(hosts)
16
+ end
17
+
18
+ def empty?
19
+ @hosts.empty?
20
+ end
21
+
22
+ def allows?(host)
23
+ @hosts.any? do |allowed|
24
+ allowed === host
25
+ rescue
26
+ # IPAddr#=== raises an error if you give it a hostname instead of
27
+ # IP. Treat similar errors as blocked access.
28
+ false
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def sanitize_hosts(hosts)
35
+ Array(hosts).map do |host|
36
+ case host
37
+ when Regexp then sanitize_regexp(host)
38
+ when String then sanitize_string(host)
39
+ else host
40
+ end
41
+ end
42
+ end
43
+
44
+ def sanitize_regexp(host)
45
+ /\A#{host}\z/
46
+ end
47
+
48
+ def sanitize_string(host)
49
+ if host.start_with?(".")
50
+ /\A(.+\.)?#{Regexp.escape(host[1..-1])}\z/
51
+ else
52
+ host
53
+ end
54
+ end
55
+ end
56
+
57
+ DEFAULT_RESPONSE_APP = -> env do
58
+ request = Request.new(env)
59
+
60
+ format = request.xhr? ? "text/plain" : "text/html"
61
+ template = DebugView.new(host: request.host)
62
+ body = template.render(template: "rescues/blocked_host", layout: "rescues/layout")
63
+
64
+ [403, {
65
+ "Content-Type" => "#{format}; charset=#{Response.default_charset}",
66
+ "Content-Length" => body.bytesize.to_s,
67
+ }, [body]]
68
+ end
69
+
70
+ def initialize(app, hosts, response_app = nil)
71
+ @app = app
72
+ @permissions = Permissions.new(hosts)
73
+ @response_app = response_app || DEFAULT_RESPONSE_APP
74
+ end
75
+
76
+ def call(env)
77
+ return @app.call(env) if @permissions.empty?
78
+
79
+ request = Request.new(env)
80
+
81
+ if authorized?(request)
82
+ mark_as_authorized(request)
83
+ @app.call(env)
84
+ else
85
+ @response_app.call(env)
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def authorized?(request)
92
+ origin_host = request.get_header("HTTP_HOST").to_s.sub(/:\d+\z/, "")
93
+ forwarded_host = request.x_forwarded_host.to_s.split(/,\s?/).last.to_s.sub(/:\d+\z/, "")
94
+
95
+ @permissions.allows?(origin_host) &&
96
+ (forwarded_host.blank? || @permissions.allows?(forwarded_host))
97
+ end
98
+
99
+ def mark_as_authorized(request)
100
+ request.set_header("action_dispatch.authorized_host", request.host)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ # 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>
6
+ # 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
+ #
12
+ # When a request with a content type other than HTML is made, this middleware
13
+ # will attempt to convert error information into the appropriate response type.
14
+ class PublicExceptions
15
+ attr_accessor :public_path
16
+
17
+ def initialize(public_path)
18
+ @public_path = public_path
19
+ end
20
+
21
+ def call(env)
22
+ request = ActionDispatch::Request.new(env)
23
+ status = request.path_info[1..-1].to_i
24
+ begin
25
+ content_type = request.formats.first
26
+ rescue Mime::Type::InvalidMimeType
27
+ content_type = Mime[:text]
28
+ end
29
+ body = { status: status, error: Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) }
30
+
31
+ render(status, content_type, body)
32
+ end
33
+
34
+ private
35
+
36
+ def render(status, content_type, body)
37
+ format = "to_#{content_type.to_sym}" if content_type
38
+ if format && body.respond_to?(format)
39
+ render_format(status, content_type, body.public_send(format))
40
+ else
41
+ render_html(status)
42
+ end
43
+ end
44
+
45
+ def render_format(status, content_type, body)
46
+ [status, { "Content-Type" => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
47
+ "Content-Length" => body.bytesize.to_s }, [body]]
48
+ end
49
+
50
+ def render_html(status)
51
+ path = "#{public_path}/#{status}.#{I18n.locale}.html"
52
+ path = "#{public_path}/#{status}.html" unless (found = File.exist?(path))
53
+
54
+ if found || File.exist?(path)
55
+ render_format(status, "text/html", File.read(path))
56
+ else
57
+ [404, { "X-Cascade" => "pass" }, []]
58
+ end
59
+ end
60
+ end
61
+ end