actionpack 4.2.11.1 → 6.0.3

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 (182) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +212 -526
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +9 -9
  5. data/lib/abstract_controller/asset_paths.rb +2 -0
  6. data/lib/abstract_controller/base.rb +47 -50
  7. data/lib/{action_controller → abstract_controller}/caching/fragments.rb +64 -17
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/callbacks.rb +59 -31
  10. data/lib/abstract_controller/collector.rb +9 -13
  11. data/lib/abstract_controller/error.rb +6 -0
  12. data/lib/abstract_controller/helpers.rb +31 -30
  13. data/lib/abstract_controller/logger.rb +2 -0
  14. data/lib/abstract_controller/railties/routes_helpers.rb +5 -3
  15. data/lib/abstract_controller/rendering.rb +42 -41
  16. data/lib/abstract_controller/translation.rb +12 -9
  17. data/lib/abstract_controller/url_for.rb +2 -0
  18. data/lib/abstract_controller.rb +12 -5
  19. data/lib/action_controller/api/api_rendering.rb +16 -0
  20. data/lib/action_controller/api.rb +150 -0
  21. data/lib/action_controller/base.rb +25 -22
  22. data/lib/action_controller/caching.rb +13 -57
  23. data/lib/action_controller/form_builder.rb +50 -0
  24. data/lib/action_controller/log_subscriber.rb +15 -17
  25. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  26. data/lib/action_controller/metal/conditional_get.rb +124 -44
  27. data/lib/action_controller/metal/content_security_policy.rb +51 -0
  28. data/lib/action_controller/metal/cookies.rb +3 -3
  29. data/lib/action_controller/metal/data_streaming.rb +29 -49
  30. data/lib/action_controller/metal/default_headers.rb +17 -0
  31. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  32. data/lib/action_controller/metal/etag_with_template_digest.rb +20 -13
  33. data/lib/action_controller/metal/exceptions.rb +30 -15
  34. data/lib/action_controller/metal/flash.rb +9 -8
  35. data/lib/action_controller/metal/force_ssl.rb +23 -62
  36. data/lib/action_controller/metal/head.rb +22 -20
  37. data/lib/action_controller/metal/helpers.rb +26 -17
  38. data/lib/action_controller/metal/http_authentication.rb +76 -70
  39. data/lib/action_controller/metal/implicit_render.rb +53 -9
  40. data/lib/action_controller/metal/instrumentation.rb +22 -27
  41. data/lib/action_controller/metal/live.rb +101 -119
  42. data/lib/action_controller/metal/mime_responds.rb +44 -46
  43. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  44. data/lib/action_controller/metal/params_wrapper.rb +74 -63
  45. data/lib/action_controller/metal/redirecting.rb +53 -32
  46. data/lib/action_controller/metal/renderers.rb +87 -44
  47. data/lib/action_controller/metal/rendering.rb +72 -51
  48. data/lib/action_controller/metal/request_forgery_protection.rb +217 -97
  49. data/lib/action_controller/metal/rescue.rb +9 -16
  50. data/lib/action_controller/metal/streaming.rb +12 -11
  51. data/lib/action_controller/metal/strong_parameters.rb +619 -183
  52. data/lib/action_controller/metal/testing.rb +2 -17
  53. data/lib/action_controller/metal/url_for.rb +19 -10
  54. data/lib/action_controller/metal.rb +104 -87
  55. data/lib/action_controller/railtie.rb +28 -10
  56. data/lib/action_controller/railties/helpers.rb +3 -1
  57. data/lib/action_controller/renderer.rb +130 -0
  58. data/lib/action_controller/template_assertions.rb +11 -0
  59. data/lib/action_controller/test_case.rb +286 -418
  60. data/lib/action_controller.rb +33 -21
  61. data/lib/action_dispatch/http/cache.rb +100 -51
  62. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  63. data/lib/action_dispatch/http/content_security_policy.rb +282 -0
  64. data/lib/action_dispatch/http/filter_parameters.rb +31 -24
  65. data/lib/action_dispatch/http/filter_redirect.rb +10 -12
  66. data/lib/action_dispatch/http/headers.rb +54 -22
  67. data/lib/action_dispatch/http/mime_negotiation.rb +61 -45
  68. data/lib/action_dispatch/http/mime_type.rb +141 -122
  69. data/lib/action_dispatch/http/mime_types.rb +20 -6
  70. data/lib/action_dispatch/http/parameter_filter.rb +8 -68
  71. data/lib/action_dispatch/http/parameters.rb +107 -39
  72. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  73. data/lib/action_dispatch/http/request.rb +204 -117
  74. data/lib/action_dispatch/http/response.rb +248 -114
  75. data/lib/action_dispatch/http/upload.rb +21 -7
  76. data/lib/action_dispatch/http/url.rb +181 -100
  77. data/lib/action_dispatch/journey/formatter.rb +56 -34
  78. data/lib/action_dispatch/journey/gtg/builder.rb +7 -6
  79. data/lib/action_dispatch/journey/gtg/simulator.rb +3 -9
  80. data/lib/action_dispatch/journey/gtg/transition_table.rb +17 -17
  81. data/lib/action_dispatch/journey/nfa/builder.rb +5 -3
  82. data/lib/action_dispatch/journey/nfa/dot.rb +13 -13
  83. data/lib/action_dispatch/journey/nfa/simulator.rb +3 -3
  84. data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -49
  85. data/lib/action_dispatch/journey/nodes/node.rb +25 -12
  86. data/lib/action_dispatch/journey/parser.rb +23 -22
  87. data/lib/action_dispatch/journey/parser.y +3 -2
  88. data/lib/action_dispatch/journey/parser_extras.rb +12 -4
  89. data/lib/action_dispatch/journey/path/pattern.rb +55 -46
  90. data/lib/action_dispatch/journey/route.rb +107 -28
  91. data/lib/action_dispatch/journey/router/utils.rb +25 -16
  92. data/lib/action_dispatch/journey/router.rb +35 -27
  93. data/lib/action_dispatch/journey/routes.rb +17 -17
  94. data/lib/action_dispatch/journey/scanner.rb +26 -17
  95. data/lib/action_dispatch/journey/visitors.rb +98 -54
  96. data/lib/action_dispatch/journey.rb +7 -5
  97. data/lib/action_dispatch/middleware/actionable_exceptions.rb +39 -0
  98. data/lib/action_dispatch/middleware/callbacks.rb +3 -6
  99. data/lib/action_dispatch/middleware/cookies.rb +292 -203
  100. data/lib/action_dispatch/middleware/debug_exceptions.rb +142 -63
  101. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  102. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  103. data/lib/action_dispatch/middleware/exception_wrapper.rb +102 -70
  104. data/lib/action_dispatch/middleware/executor.rb +21 -0
  105. data/lib/action_dispatch/middleware/flash.rb +78 -54
  106. data/lib/action_dispatch/middleware/host_authorization.rb +101 -0
  107. data/lib/action_dispatch/middleware/public_exceptions.rb +32 -27
  108. data/lib/action_dispatch/middleware/reloader.rb +5 -91
  109. data/lib/action_dispatch/middleware/remote_ip.rb +48 -41
  110. data/lib/action_dispatch/middleware/request_id.rb +17 -9
  111. data/lib/action_dispatch/middleware/session/abstract_store.rb +41 -26
  112. data/lib/action_dispatch/middleware/session/cache_store.rb +24 -14
  113. data/lib/action_dispatch/middleware/session/cookie_store.rb +72 -73
  114. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -2
  115. data/lib/action_dispatch/middleware/show_exceptions.rb +26 -23
  116. data/lib/action_dispatch/middleware/ssl.rb +113 -35
  117. data/lib/action_dispatch/middleware/stack.rb +64 -41
  118. data/lib/action_dispatch/middleware/static.rb +57 -51
  119. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -14
  122. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  123. data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +4 -2
  124. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  125. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
  126. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +26 -4
  129. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  130. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +15 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +5 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
  136. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
  137. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
  138. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  139. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  140. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +67 -64
  141. data/lib/action_dispatch/railtie.rb +26 -13
  142. data/lib/action_dispatch/request/session.rb +114 -60
  143. data/lib/action_dispatch/request/utils.rb +67 -24
  144. data/lib/action_dispatch/routing/endpoint.rb +9 -2
  145. data/lib/action_dispatch/routing/inspector.rb +140 -102
  146. data/lib/action_dispatch/routing/mapper.rb +762 -455
  147. data/lib/action_dispatch/routing/polymorphic_routes.rb +161 -142
  148. data/lib/action_dispatch/routing/redirection.rb +36 -26
  149. data/lib/action_dispatch/routing/route_set.rb +322 -298
  150. data/lib/action_dispatch/routing/routes_proxy.rb +32 -5
  151. data/lib/action_dispatch/routing/url_for.rb +65 -26
  152. data/lib/action_dispatch/routing.rb +36 -36
  153. data/lib/action_dispatch/system_test_case.rb +185 -0
  154. data/lib/action_dispatch/system_testing/browser.rb +80 -0
  155. data/lib/action_dispatch/system_testing/driver.rb +68 -0
  156. data/lib/action_dispatch/system_testing/server.rb +31 -0
  157. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +97 -0
  158. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +32 -0
  159. data/lib/action_dispatch/testing/assertion_response.rb +46 -0
  160. data/lib/action_dispatch/testing/assertions/response.rb +44 -20
  161. data/lib/action_dispatch/testing/assertions/routing.rb +44 -28
  162. data/lib/action_dispatch/testing/assertions.rb +6 -4
  163. data/lib/action_dispatch/testing/integration.rb +375 -215
  164. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  165. data/lib/action_dispatch/testing/test_process.rb +28 -22
  166. data/lib/action_dispatch/testing/test_request.rb +27 -34
  167. data/lib/action_dispatch/testing/test_response.rb +11 -11
  168. data/lib/action_dispatch.rb +33 -20
  169. data/lib/action_pack/gem_version.rb +6 -4
  170. data/lib/action_pack/version.rb +3 -1
  171. data/lib/action_pack.rb +4 -2
  172. metadata +71 -40
  173. data/lib/action_controller/metal/hide_actions.rb +0 -40
  174. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  175. data/lib/action_controller/middleware.rb +0 -39
  176. data/lib/action_controller/model_naming.rb +0 -12
  177. data/lib/action_dispatch/journey/backwards.rb +0 -5
  178. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  179. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  180. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  181. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  182. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -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
@@ -1,15 +1,8 @@
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
+ require "active_support/core_ext/hash/keys"
12
4
 
5
+ module ActionDispatch
13
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
14
7
  # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
15
8
  # action that sets <tt>flash[:notice] = "Post successfully created"</tt> before redirecting to a display action that can
@@ -45,7 +38,46 @@ module ActionDispatch
45
38
  #
46
39
  # See docs on the FlashHash class for more details about the flash.
47
40
  class Flash
48
- KEY = 'action_dispatch.request.flash_hash'.freeze
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
49
81
 
50
82
  class FlashNow #:nodoc:
51
83
  attr_accessor :flash
@@ -80,24 +112,30 @@ module ActionDispatch
80
112
  include Enumerable
81
113
 
82
114
  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)
93
- 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.
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+.
98
135
  def to_session_value #:nodoc:
99
- return nil if empty?
100
- {'discard' => @discard.to_a, 'flashes' => @flashes}
136
+ flashes_to_keep = @flashes.except(*@discard)
137
+ return nil if flashes_to_keep.empty?
138
+ { "discard" => [], "flashes" => flashes_to_keep }
101
139
  end
102
140
 
103
141
  def initialize(flashes = {}, discard = []) #:nodoc:
@@ -241,36 +279,22 @@ module ActionDispatch
241
279
  end
242
280
 
243
281
  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
282
+ def now_is_loaded?
283
+ @now
251
284
  end
252
- end
253
- end
254
285
 
255
- def initialize(app)
256
- @app = app
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
257
292
  end
258
293
 
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
294
+ def self.new(app) app; end
295
+ end
269
296
 
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
297
+ class Request
298
+ prepend Flash::RequestMethods
275
299
  end
276
300
  end
@@ -0,0 +1,101 @@
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
+ def sanitize_hosts(hosts)
34
+ Array(hosts).map do |host|
35
+ case host
36
+ when Regexp then sanitize_regexp(host)
37
+ when String then sanitize_string(host)
38
+ else host
39
+ end
40
+ end
41
+ end
42
+
43
+ def sanitize_regexp(host)
44
+ /\A#{host}\z/
45
+ end
46
+
47
+ def sanitize_string(host)
48
+ if host.start_with?(".")
49
+ /\A(.+\.)?#{Regexp.escape(host[1..-1])}\z/
50
+ else
51
+ host
52
+ end
53
+ end
54
+ end
55
+
56
+ DEFAULT_RESPONSE_APP = -> env do
57
+ request = Request.new(env)
58
+
59
+ format = request.xhr? ? "text/plain" : "text/html"
60
+ template = DebugView.new(host: request.host)
61
+ body = template.render(template: "rescues/blocked_host", layout: "rescues/layout")
62
+
63
+ [403, {
64
+ "Content-Type" => "#{format}; charset=#{Response.default_charset}",
65
+ "Content-Length" => body.bytesize.to_s,
66
+ }, [body]]
67
+ end
68
+
69
+ def initialize(app, hosts, response_app = nil)
70
+ @app = app
71
+ @permissions = Permissions.new(hosts)
72
+ @response_app = response_app || DEFAULT_RESPONSE_APP
73
+ end
74
+
75
+ def call(env)
76
+ return @app.call(env) if @permissions.empty?
77
+
78
+ request = Request.new(env)
79
+
80
+ if authorized?(request)
81
+ mark_as_authorized(request)
82
+ @app.call(env)
83
+ else
84
+ @response_app.call(env)
85
+ end
86
+ end
87
+
88
+ private
89
+ def authorized?(request)
90
+ origin_host = request.get_header("HTTP_HOST").to_s.sub(/:\d+\z/, "")
91
+ forwarded_host = request.x_forwarded_host.to_s.split(/,\s?/).last.to_s.sub(/:\d+\z/, "")
92
+
93
+ @permissions.allows?(origin_host) &&
94
+ (forwarded_host.blank? || @permissions.allows?(forwarded_host))
95
+ end
96
+
97
+ def mark_as_authorized(request)
98
+ request.set_header("action_dispatch.authorized_host", request.host)
99
+ end
100
+ end
101
+ end
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionDispatch
2
4
  # When called, this middleware renders an error page. By default if an HTML
3
- # response is expected it will render static error pages from the `/public`
5
+ # response is expected it will render static error pages from the <tt>/public</tt>
4
6
  # directory. For example when this middleware receives a 500 response it will
5
- # render the template found in `/public/500.html`.
7
+ # render the template found in <tt>/public/500.html</tt>.
6
8
  # 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`.
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>.
9
11
  #
10
12
  # When a request with a content type other than HTML is made, this middleware
11
13
  # will attempt to convert error information into the appropriate response type.
@@ -17,39 +19,42 @@ module ActionDispatch
17
19
  end
18
20
 
19
21
  def call(env)
20
- status = env["PATH_INFO"][1..-1]
21
22
  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]) }
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]) }
24
30
 
25
31
  render(status, content_type, body)
26
32
  end
27
33
 
28
34
  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)
35
+ def render(status, content_type, body)
36
+ format = "to_#{content_type.to_sym}" if content_type
37
+ if format && body.respond_to?(format)
38
+ render_format(status, content_type, body.public_send(format))
39
+ else
40
+ render_html(status)
41
+ end
36
42
  end
37
- end
38
43
 
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
44
+ def render_format(status, content_type, body)
45
+ [status, { "Content-Type" => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
46
+ "Content-Length" => body.bytesize.to_s }, [body]]
47
+ end
43
48
 
44
- def render_html(status)
45
- path = "#{public_path}/#{status}.#{I18n.locale}.html"
46
- path = "#{public_path}/#{status}.html" unless (found = File.exist?(path))
49
+ def render_html(status)
50
+ path = "#{public_path}/#{status}.#{I18n.locale}.html"
51
+ path = "#{public_path}/#{status}.html" unless (found = File.exist?(path))
47
52
 
48
- if found || File.exist?(path)
49
- render_format(status, 'text/html', File.read(path))
50
- else
51
- [404, { "X-Cascade" => "pass" }, []]
53
+ if found || File.exist?(path)
54
+ render_format(status, "text/html", File.read(path))
55
+ else
56
+ [404, { "X-Cascade" => "pass" }, []]
57
+ end
52
58
  end
53
- end
54
59
  end
55
60
  end
@@ -1,98 +1,12 @@
1
- require 'active_support/deprecation/reporting'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module ActionDispatch
4
- # ActionDispatch::Reloader provides prepare and cleanup callbacks,
5
- # intended to assist with code reloading during development.
6
- #
7
- # Prepare callbacks are run before each request, and cleanup callbacks
8
- # after each request. In this respect they are analogs of ActionDispatch::Callback's
9
- # before and after callbacks. However, cleanup callbacks are not called until the
10
- # request is fully complete -- that is, after #close has been called on
11
- # the response body. This is important for streaming responses such as the
12
- # following:
13
- #
14
- # self.response_body = lambda { |response, output|
15
- # # code here which refers to application models
16
- # }
17
- #
18
- # Cleanup callbacks will not be called until after the response_body lambda
19
- # is evaluated, ensuring that it can refer to application models and other
20
- # classes before they are unloaded.
4
+ # ActionDispatch::Reloader wraps the request with callbacks provided by ActiveSupport::Reloader
5
+ # callbacks, intended to assist with code reloading during development.
21
6
  #
22
7
  # By default, ActionDispatch::Reloader is included in the middleware stack
23
8
  # only in the development environment; specifically, when +config.cache_classes+
24
- # is false. Callbacks may be registered even when it is not included in the
25
- # middleware stack, but are executed only when <tt>ActionDispatch::Reloader.prepare!</tt>
26
- # or <tt>ActionDispatch::Reloader.cleanup!</tt> are called manually.
27
- #
28
- class Reloader
29
- include ActiveSupport::Callbacks
30
- include ActiveSupport::Deprecation::Reporting
31
-
32
- define_callbacks :prepare
33
- define_callbacks :cleanup
34
-
35
- # Add a prepare callback. Prepare callbacks are run before each request, prior
36
- # to ActionDispatch::Callback's before callbacks.
37
- def self.to_prepare(*args, &block)
38
- unless block_given?
39
- warn "to_prepare without a block is deprecated. Please use a block"
40
- end
41
- set_callback(:prepare, *args, &block)
42
- end
43
-
44
- # Add a cleanup callback. Cleanup callbacks are run after each request is
45
- # complete (after #close is called on the response body).
46
- def self.to_cleanup(*args, &block)
47
- unless block_given?
48
- warn "to_cleanup without a block is deprecated. Please use a block"
49
- end
50
- set_callback(:cleanup, *args, &block)
51
- end
52
-
53
- # Execute all prepare callbacks.
54
- def self.prepare!
55
- new(nil).prepare!
56
- end
57
-
58
- # Execute all cleanup callbacks.
59
- def self.cleanup!
60
- new(nil).cleanup!
61
- end
62
-
63
- def initialize(app, condition=nil)
64
- @app = app
65
- @condition = condition || lambda { true }
66
- @validated = true
67
- end
68
-
69
- def call(env)
70
- @validated = @condition.call
71
- prepare!
72
-
73
- response = @app.call(env)
74
- response[2] = ::Rack::BodyProxy.new(response[2]) { cleanup! }
75
-
76
- response
77
- rescue Exception
78
- cleanup!
79
- raise
80
- end
81
-
82
- def prepare! #:nodoc:
83
- run_callbacks :prepare if validated?
84
- end
85
-
86
- def cleanup! #:nodoc:
87
- run_callbacks :cleanup if validated?
88
- ensure
89
- @validated = true
90
- end
91
-
92
- private
93
-
94
- def validated? #:nodoc:
95
- @validated
96
- end
9
+ # is false.
10
+ class Reloader < Executor
97
11
  end
98
12
  end
@@ -1,4 +1,6 @@
1
- require 'ipaddr'
1
+ # frozen_string_literal: true
2
+
3
+ require "ipaddr"
2
4
 
3
5
  module ActionDispatch
4
6
  # This middleware calculates the IP address of the remote client that is
@@ -6,13 +8,13 @@ module ActionDispatch
6
8
  # contain the address, and then picking the last-set address that is not
7
9
  # on the list of trusted IPs. This follows the precedent set by e.g.
8
10
  # {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453],
9
- # with {reasoning explained at length}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection]
11
+ # with {reasoning explained at length}[https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection]
10
12
  # by @gingerlime. A more detailed explanation of the algorithm is given
11
13
  # at GetIp#calculate_ip.
12
14
  #
13
- # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
15
+ # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
14
16
  # requires. Some Rack servers simply drop preceding headers, and only report
15
- # the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers].
17
+ # the value that was {given in the last header}[https://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers].
16
18
  # If you are behind multiple proxy servers (like NGINX to HAProxy to Unicorn)
17
19
  # then you should test your Rack server to make sure your data is good.
18
20
  #
@@ -29,7 +31,7 @@ module ActionDispatch
29
31
  # The default trusted IPs list simply includes IP addresses that are
30
32
  # guaranteed by the IP specification to be private addresses. Those will
31
33
  # not be the ultimate client IP in production, and so are discarded. See
32
- # http://en.wikipedia.org/wiki/Private_network for details.
34
+ # https://en.wikipedia.org/wiki/Private_network for details.
33
35
  TRUSTED_PROXIES = [
34
36
  "127.0.0.1", # localhost IPv4
35
37
  "::1", # localhost IPv6
@@ -43,7 +45,7 @@ module ActionDispatch
43
45
 
44
46
  # Create a new +RemoteIp+ middleware instance.
45
47
  #
46
- # The +check_ip_spoofing+ option is on by default. When on, an exception
48
+ # The +ip_spoofing_check+ option is on by default. When on, an exception
47
49
  # is raised if it looks like the client is trying to lie about its own IP
48
50
  # address. It makes sense to turn off this check on sites aimed at non-IP
49
51
  # clients (like WAP devices), or behind proxies that set headers in an
@@ -57,9 +59,9 @@ module ActionDispatch
57
59
  # with your proxy servers after it. If your proxies aren't removed, pass
58
60
  # them in via the +custom_proxies+ parameter. That way, the middleware will
59
61
  # ignore those IP addresses, and return the one that you want.
60
- def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
62
+ def initialize(app, ip_spoofing_check = true, custom_proxies = nil)
61
63
  @app = app
62
- @check_ip = check_ip_spoofing
64
+ @check_ip = ip_spoofing_check
63
65
  @proxies = if custom_proxies.blank?
64
66
  TRUSTED_PROXIES
65
67
  elsif custom_proxies.respond_to?(:any?)
@@ -74,18 +76,19 @@ module ActionDispatch
74
76
  # requests. For those requests that do need to know the IP, the
75
77
  # GetIp#calculate_ip method will calculate the memoized client IP address.
76
78
  def call(env)
77
- env["action_dispatch.remote_ip"] = GetIp.new(env, self)
78
- @app.call(env)
79
+ req = ActionDispatch::Request.new env
80
+ req.remote_ip = GetIp.new(req, check_ip, proxies)
81
+ @app.call(req.env)
79
82
  end
80
83
 
81
84
  # The GetIp class exists as a way to defer processing of the request data
82
85
  # into an actual IP address. If the ActionDispatch::Request#remote_ip method
83
86
  # is called, this class will calculate the value and then memoize it.
84
87
  class GetIp
85
- def initialize(env, middleware)
86
- @env = env
87
- @check_ip = middleware.check_ip
88
- @proxies = middleware.proxies
88
+ def initialize(req, check_ip, proxies)
89
+ @req = req
90
+ @check_ip = check_ip
91
+ @proxies = proxies
89
92
  end
90
93
 
91
94
  # Sort through the various IP address headers, looking for the IP most
@@ -99,7 +102,7 @@ module ActionDispatch
99
102
  # proxies, that header may contain a list of IPs. Other proxy services
100
103
  # set the Client-Ip header instead, so we check that too.
101
104
  #
102
- # As discussed in {this post about Rails IP Spoofing}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
105
+ # As discussed in {this post about Rails IP Spoofing}[https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
103
106
  # while the first IP in the list is likely to be the "originating" IP,
104
107
  # it could also have been set by the client maliciously.
105
108
  #
@@ -108,23 +111,31 @@ module ActionDispatch
108
111
  # the last address left, which was presumably set by one of those proxies.
109
112
  def calculate_ip
110
113
  # Set by the Rack web server, this is a single value.
111
- remote_addr = ips_from('REMOTE_ADDR').last
114
+ remote_addr = ips_from(@req.remote_addr).last
112
115
 
113
116
  # Could be a CSV list and/or repeated headers that were concatenated.
114
- client_ips = ips_from('HTTP_CLIENT_IP').reverse
115
- forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR').reverse
117
+ client_ips = ips_from(@req.client_ip).reverse
118
+ forwarded_ips = ips_from(@req.x_forwarded_for).reverse
116
119
 
117
120
  # +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set.
118
- # If they are both set, it means that this request passed through two
119
- # proxies with incompatible IP header conventions, and there is no way
120
- # for us to determine which header is the right one after the fact.
121
- # Since we have no idea, we give up and explode.
121
+ # If they are both set, it means that either:
122
+ #
123
+ # 1) This request passed through two proxies with incompatible IP header
124
+ # conventions.
125
+ # 2) The client passed one of +Client-Ip+ or +X-Forwarded-For+
126
+ # (whichever the proxy servers weren't using) themselves.
127
+ #
128
+ # Either way, there is no way for us to determine which header is the
129
+ # right one after the fact. Since we have no idea, if we are concerned
130
+ # about IP spoofing we need to give up and explode. (If you're not
131
+ # concerned about IP spoofing you can turn the +ip_spoofing_check+
132
+ # option off.)
122
133
  should_check_ip = @check_ip && client_ips.last && forwarded_ips.last
123
134
  if should_check_ip && !forwarded_ips.include?(client_ips.last)
124
135
  # We don't know which came from the proxy, and which from the user
125
- raise IpSpoofAttackError, "IP spoofing attack?! " +
126
- "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect} " +
127
- "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
136
+ raise IpSpoofAttackError, "IP spoofing attack?! " \
137
+ "HTTP_CLIENT_IP=#{@req.client_ip.inspect} " \
138
+ "HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
128
139
  end
129
140
 
130
141
  # We assume these things about the IP headers:
@@ -144,30 +155,26 @@ module ActionDispatch
144
155
  @ip ||= calculate_ip
145
156
  end
146
157
 
147
- protected
148
-
149
- def ips_from(header)
150
- # Split the comma-separated list into an array of strings
151
- ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
158
+ private
159
+ def ips_from(header) # :doc:
160
+ return [] unless header
161
+ # Split the comma-separated list into an array of strings.
162
+ ips = header.strip.split(/[,\s]+/)
152
163
  ips.select do |ip|
153
- begin
154
- # Only return IPs that are valid according to the IPAddr#new method
155
- range = IPAddr.new(ip).to_range
156
- # we want to make sure nobody is sneaking a netmask in
157
- range.begin == range.end
158
- rescue ArgumentError
159
- nil
160
- end
164
+ # Only return IPs that are valid according to the IPAddr#new method.
165
+ range = IPAddr.new(ip).to_range
166
+ # We want to make sure nobody is sneaking a netmask in.
167
+ range.begin == range.end
168
+ rescue ArgumentError
169
+ nil
161
170
  end
162
171
  end
163
172
 
164
- def filter_proxies(ips)
173
+ def filter_proxies(ips) # :doc:
165
174
  ips.reject do |ip|
166
175
  @proxies.any? { |proxy| proxy === ip }
167
176
  end
168
177
  end
169
-
170
178
  end
171
-
172
179
  end
173
180
  end