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,98 +1,16 @@
1
- require 'active_support/deprecation/reporting'
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
2
4
 
3
5
  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
- # }
6
+ # # Action Dispatch Reloader
17
7
  #
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.
8
+ # ActionDispatch::Reloader wraps the request with callbacks provided by
9
+ # ActiveSupport::Reloader, intended to assist with code reloading during
10
+ # development.
21
11
  #
22
- # By default, ActionDispatch::Reloader is included in the middleware stack
23
- # 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
12
+ # ActionDispatch::Reloader is included in the middleware stack only if reloading
13
+ # is enabled, which it is by the default in `development` mode.
14
+ class Reloader < Executor
97
15
  end
98
16
  end
@@ -1,37 +1,44 @@
1
- require 'ipaddr'
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "ipaddr"
2
6
 
3
7
  module ActionDispatch
4
- # This middleware calculates the IP address of the remote client that is
5
- # making the request. It does this by checking various headers that could
6
- # contain the address, and then picking the last-set address that is not
7
- # on the list of trusted IPs. This follows the precedent set by e.g.
8
- # {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]
10
- # by @gingerlime. A more detailed explanation of the algorithm is given
11
- # at GetIp#calculate_ip.
8
+ # # Action Dispatch RemoteIp
12
9
  #
13
- # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
14
- # 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].
16
- # If you are behind multiple proxy servers (like NGINX to HAProxy to Unicorn)
17
- # then you should test your Rack server to make sure your data is good.
10
+ # This middleware calculates the IP address of the remote client that is making
11
+ # the request. It does this by checking various headers that could contain the
12
+ # address, and then picking the last-set address that is not on the list of
13
+ # trusted IPs. This follows the precedent set by e.g. [the Tomcat
14
+ # server](https://issues.apache.org/bugzilla/show_bug.cgi?id=50453). A more
15
+ # detailed explanation of the algorithm is given at GetIp#calculate_ip.
18
16
  #
19
- # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING.
20
- # This middleware assumes that there is at least one proxy sitting around
21
- # and setting headers with the client's remote IP address. If you don't use
22
- # a proxy, because you are hosted on e.g. Heroku without SSL, any client can
23
- # claim to have any IP address by setting the X-Forwarded-For header. If you
24
- # care about that, then you need to explicitly drop or ignore those headers
25
- # sometime before this middleware runs.
17
+ # Some Rack servers concatenate repeated headers, like [HTTP RFC
18
+ # 2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2) requires.
19
+ # Some Rack servers simply drop preceding headers, and only report the value
20
+ # that was [given in the last
21
+ # header](https://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-server
22
+ # s). If you are behind multiple proxy servers (like NGINX to HAProxy to
23
+ # Unicorn) then you should test your Rack server to make sure your data is good.
24
+ #
25
+ # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING. This
26
+ # middleware assumes that there is at least one proxy sitting around and setting
27
+ # headers with the client's remote IP address. If you don't use a proxy, because
28
+ # you are hosted on e.g. Heroku without SSL, any client can claim to have any IP
29
+ # address by setting the `X-Forwarded-For` header. If you care about that, then
30
+ # you need to explicitly drop or ignore those headers sometime before this
31
+ # middleware runs. Alternatively, remove this middleware to avoid inadvertently
32
+ # relying on it.
26
33
  class RemoteIp
27
34
  class IpSpoofAttackError < StandardError; end
28
35
 
29
- # The default trusted IPs list simply includes IP addresses that are
30
- # guaranteed by the IP specification to be private addresses. Those will
31
- # not be the ultimate client IP in production, and so are discarded. See
32
- # http://en.wikipedia.org/wiki/Private_network for details.
36
+ # The default trusted IPs list simply includes IP addresses that are guaranteed
37
+ # by the IP specification to be private addresses. Those will not be the
38
+ # ultimate client IP in production, and so are discarded. See
39
+ # https://en.wikipedia.org/wiki/Private_network for details.
33
40
  TRUSTED_PROXIES = [
34
- "127.0.0.1", # localhost IPv4
41
+ "127.0.0.0/8", # localhost IPv4 range, per RFC-3330
35
42
  "::1", # localhost IPv6
36
43
  "fc00::/7", # private IPv6 range fc00::/7
37
44
  "10.0.0.0/8", # private IPv4 range 10.x.x.x
@@ -41,101 +48,123 @@ module ActionDispatch
41
48
 
42
49
  attr_reader :check_ip, :proxies
43
50
 
44
- # Create a new +RemoteIp+ middleware instance.
51
+ # Create a new `RemoteIp` middleware instance.
45
52
  #
46
- # The +check_ip_spoofing+ option is on by default. When on, an exception
47
- # is raised if it looks like the client is trying to lie about its own IP
48
- # address. It makes sense to turn off this check on sites aimed at non-IP
49
- # clients (like WAP devices), or behind proxies that set headers in an
50
- # incorrect or confusing way (like AWS ELB).
53
+ # The `ip_spoofing_check` option is on by default. When on, an exception is
54
+ # raised if it looks like the client is trying to lie about its own IP address.
55
+ # It makes sense to turn off this check on sites aimed at non-IP clients (like
56
+ # WAP devices), or behind proxies that set headers in an incorrect or confusing
57
+ # way (like AWS ELB).
51
58
  #
52
- # The +custom_proxies+ argument can take an Array of string, IPAddr, or
53
- # Regexp objects which will be used instead of +TRUSTED_PROXIES+. If a
54
- # single string, IPAddr, or Regexp object is provided, it will be used in
55
- # addition to +TRUSTED_PROXIES+. Any proxy setup will put the value you
56
- # want in the middle (or at the beginning) of the X-Forwarded-For list,
57
- # with your proxy servers after it. If your proxies aren't removed, pass
58
- # them in via the +custom_proxies+ parameter. That way, the middleware will
59
- # ignore those IP addresses, and return the one that you want.
60
- def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
59
+ # The `custom_proxies` argument can take an enumerable which will be used
60
+ # instead of `TRUSTED_PROXIES`. Any proxy setup will put the value you want in
61
+ # the middle (or at the beginning) of the `X-Forwarded-For` list, with your
62
+ # proxy servers after it. If your proxies aren't removed, pass them in via the
63
+ # `custom_proxies` parameter. That way, the middleware will ignore those IP
64
+ # addresses, and return the one that you want.
65
+ def initialize(app, ip_spoofing_check = true, custom_proxies = nil)
61
66
  @app = app
62
- @check_ip = check_ip_spoofing
67
+ @check_ip = ip_spoofing_check
63
68
  @proxies = if custom_proxies.blank?
64
69
  TRUSTED_PROXIES
65
70
  elsif custom_proxies.respond_to?(:any?)
66
71
  custom_proxies
67
72
  else
68
- Array(custom_proxies) + TRUSTED_PROXIES
73
+ raise(ArgumentError, <<~EOM)
74
+ Setting config.action_dispatch.trusted_proxies to a single value isn't
75
+ supported. Please set this to an enumerable instead. For
76
+ example, instead of:
77
+
78
+ config.action_dispatch.trusted_proxies = IPAddr.new("10.0.0.0/8")
79
+
80
+ Wrap the value in an Array:
81
+
82
+ config.action_dispatch.trusted_proxies = [IPAddr.new("10.0.0.0/8")]
83
+
84
+ Note that passing an enumerable will *replace* the default set of trusted proxies.
85
+ EOM
69
86
  end
70
87
  end
71
88
 
72
- # Since the IP address may not be needed, we store the object here
73
- # without calculating the IP to keep from slowing down the majority of
74
- # requests. For those requests that do need to know the IP, the
75
- # GetIp#calculate_ip method will calculate the memoized client IP address.
89
+ # Since the IP address may not be needed, we store the object here without
90
+ # calculating the IP to keep from slowing down the majority of requests. For
91
+ # those requests that do need to know the IP, the GetIp#calculate_ip method will
92
+ # calculate the memoized client IP address.
76
93
  def call(env)
77
- env["action_dispatch.remote_ip"] = GetIp.new(env, self)
78
- @app.call(env)
94
+ req = ActionDispatch::Request.new env
95
+ req.remote_ip = GetIp.new(req, check_ip, proxies)
96
+ @app.call(req.env)
79
97
  end
80
98
 
81
- # The GetIp class exists as a way to defer processing of the request data
82
- # into an actual IP address. If the ActionDispatch::Request#remote_ip method
83
- # is called, this class will calculate the value and then memoize it.
99
+ # The GetIp class exists as a way to defer processing of the request data into
100
+ # an actual IP address. If the ActionDispatch::Request#remote_ip method is
101
+ # called, this class will calculate the value and then memoize it.
84
102
  class GetIp
85
- def initialize(env, middleware)
86
- @env = env
87
- @check_ip = middleware.check_ip
88
- @proxies = middleware.proxies
103
+ def initialize(req, check_ip, proxies)
104
+ @req = req
105
+ @check_ip = check_ip
106
+ @proxies = proxies
89
107
  end
90
108
 
91
- # Sort through the various IP address headers, looking for the IP most
92
- # likely to be the address of the actual remote client making this
93
- # request.
109
+ # Sort through the various IP address headers, looking for the IP most likely to
110
+ # be the address of the actual remote client making this request.
94
111
  #
95
- # REMOTE_ADDR will be correct if the request is made directly against the
96
- # Ruby process, on e.g. Heroku. When the request is proxied by another
97
- # server like HAProxy or NGINX, the IP address that made the original
98
- # request will be put in an X-Forwarded-For header. If there are multiple
99
- # proxies, that header may contain a list of IPs. Other proxy services
100
- # set the Client-Ip header instead, so we check that too.
112
+ # REMOTE_ADDR will be correct if the request is made directly against the Ruby
113
+ # process, on e.g. Heroku. When the request is proxied by another server like
114
+ # HAProxy or NGINX, the IP address that made the original request will be put in
115
+ # an `X-Forwarded-For` header. If there are multiple proxies, that header may
116
+ # contain a list of IPs. Other proxy services set the `Client-Ip` header
117
+ # instead, so we check that too.
101
118
  #
102
- # As discussed in {this post about Rails IP Spoofing}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
103
- # while the first IP in the list is likely to be the "originating" IP,
104
- # it could also have been set by the client maliciously.
119
+ # As discussed in [this post about Rails IP
120
+ # Spoofing](https://web.archive.org/web/20170626095448/https://blog.gingerlime.c
121
+ # om/2012/rails-ip-spoofing-vulnerabilities-and-protection/), while the first IP
122
+ # in the list is likely to be the "originating" IP, it could also have been set
123
+ # by the client maliciously.
105
124
  #
106
- # In order to find the first address that is (probably) accurate, we
107
- # take the list of IPs, remove known and trusted proxies, and then take
108
- # the last address left, which was presumably set by one of those proxies.
125
+ # In order to find the first address that is (probably) accurate, we take the
126
+ # list of IPs, remove known and trusted proxies, and then take the last address
127
+ # left, which was presumably set by one of those proxies.
109
128
  def calculate_ip
110
129
  # Set by the Rack web server, this is a single value.
111
- remote_addr = ips_from('REMOTE_ADDR').last
130
+ remote_addr = ips_from(@req.remote_addr).last
112
131
 
113
132
  # 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
116
-
117
- # +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.
133
+ client_ips = ips_from(@req.client_ip).reverse!
134
+ forwarded_ips = ips_from(@req.x_forwarded_for).reverse!
135
+
136
+ # `Client-Ip` and `X-Forwarded-For` should not, generally, both be set. If they
137
+ # are both set, it means that either:
138
+ #
139
+ # 1) This request passed through two proxies with incompatible IP header
140
+ # conventions.
141
+ #
142
+ # 2) The client passed one of `Client-Ip` or `X-Forwarded-For`
143
+ # (whichever the proxy servers weren't using) themselves.
144
+ #
145
+ # Either way, there is no way for us to determine which header is the right one
146
+ # after the fact. Since we have no idea, if we are concerned about IP spoofing
147
+ # we need to give up and explode. (If you're not concerned about IP spoofing you
148
+ # can turn the `ip_spoofing_check` option off.)
122
149
  should_check_ip = @check_ip && client_ips.last && forwarded_ips.last
123
150
  if should_check_ip && !forwarded_ips.include?(client_ips.last)
124
151
  # 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}"
152
+ raise IpSpoofAttackError, "IP spoofing attack?! " \
153
+ "HTTP_CLIENT_IP=#{@req.client_ip.inspect} " \
154
+ "HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
128
155
  end
129
156
 
130
157
  # We assume these things about the IP headers:
131
158
  #
132
- # - X-Forwarded-For will be a list of IPs, one per proxy, or blank
133
- # - Client-Ip is propagated from the outermost proxy, or is blank
134
- # - REMOTE_ADDR will be the IP that made the request to Rack
135
- ips = [forwarded_ips, client_ips, remote_addr].flatten.compact
136
-
137
- # If every single IP option is in the trusted list, just return REMOTE_ADDR
138
- filter_proxies(ips).first || remote_addr
159
+ # - X-Forwarded-For will be a list of IPs, one per proxy, or blank
160
+ # - Client-Ip is propagated from the outermost proxy, or is blank
161
+ # - REMOTE_ADDR will be the IP that made the request to Rack
162
+ ips = forwarded_ips + client_ips
163
+ ips.compact!
164
+
165
+ # If every single IP option is in the trusted list, return the IP that's
166
+ # furthest away
167
+ filter_proxies(ips + [remote_addr]).first || ips.last || remote_addr
139
168
  end
140
169
 
141
170
  # Memoizes the value returned by #calculate_ip and returns it for
@@ -144,30 +173,27 @@ module ActionDispatch
144
173
  @ip ||= calculate_ip
145
174
  end
146
175
 
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]+/) : []
152
- 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
176
+ private
177
+ def ips_from(header) # :doc:
178
+ return [] unless header
179
+ # Split the comma-separated list into an array of strings.
180
+ ips = header.strip.split(/[,\s]+/)
181
+ ips.select! do |ip|
182
+ # Only return IPs that are valid according to the IPAddr#new method.
183
+ range = IPAddr.new(ip).to_range
184
+ # We want to make sure nobody is sneaking a netmask in.
185
+ range.begin == range.end
186
+ rescue ArgumentError
187
+ nil
161
188
  end
189
+ ips
162
190
  end
163
191
 
164
- def filter_proxies(ips)
192
+ def filter_proxies(ips) # :doc:
165
193
  ips.reject do |ip|
166
194
  @proxies.any? { |proxy| proxy === ip }
167
195
  end
168
196
  end
169
-
170
197
  end
171
-
172
198
  end
173
199
  end
@@ -1,30 +1,44 @@
1
- require 'securerandom'
2
- require 'active_support/core_ext/string/access'
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "securerandom"
6
+ require "active_support/core_ext/string/access"
3
7
 
4
8
  module ActionDispatch
5
- # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through
6
- # ActionDispatch::Request#uuid) and sends the same id to the client via the X-Request-Id header.
9
+ # # Action Dispatch RequestId
10
+ #
11
+ # Makes a unique request id available to the `action_dispatch.request_id` env
12
+ # variable (which is then accessible through ActionDispatch::Request#request_id
13
+ # or the alias ActionDispatch::Request#uuid) and sends the same id to the client
14
+ # via the `X-Request-Id` header.
7
15
  #
8
- # The unique request id is either based on the X-Request-Id header in the request, which would typically be generated
9
- # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
10
- # header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
16
+ # The unique request id is either based on the `X-Request-Id` header in the
17
+ # request, which would typically be generated by a firewall, load balancer, or
18
+ # the web server, or, if this header is not available, a random uuid. If the
19
+ # header is accepted from the outside world, we sanitize it to a max of 255
20
+ # chars and alphanumeric and dashes only.
11
21
  #
12
- # The unique request id can be used to trace a request end-to-end and would typically end up being part of log files
13
- # from multiple pieces of the stack.
22
+ # The unique request id can be used to trace a request end-to-end and would
23
+ # typically end up being part of log files from multiple pieces of the stack.
14
24
  class RequestId
15
- def initialize(app)
25
+ def initialize(app, header:)
16
26
  @app = app
27
+ @header = header
17
28
  end
18
29
 
19
30
  def call(env)
20
- env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id
21
- @app.call(env).tap { |_status, headers, _body| headers["X-Request-Id"] = env["action_dispatch.request_id"] }
31
+ req = ActionDispatch::Request.new env
32
+ req.request_id = make_request_id(req.headers[@header])
33
+ @app.call(env).tap { |_status, headers, _body| headers[@header] = req.request_id }
22
34
  end
23
35
 
24
36
  private
25
- def external_request_id(env)
26
- if request_id = env["HTTP_X_REQUEST_ID"].presence
27
- request_id.gsub(/[^\w\-]/, "").first(255)
37
+ def make_request_id(request_id)
38
+ if request_id.presence
39
+ request_id.gsub(/[^\w\-@]/, "").first(255)
40
+ else
41
+ internal_request_id
28
42
  end
29
43
  end
30
44
 
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "active_support/notifications"
6
+
7
+ module ActionDispatch
8
+ class ServerTiming
9
+ class Subscriber # :nodoc:
10
+ include Singleton
11
+ KEY = :action_dispatch_server_timing_events
12
+
13
+ def initialize
14
+ @mutex = Mutex.new
15
+ end
16
+
17
+ def call(event)
18
+ if events = ActiveSupport::IsolatedExecutionState[KEY]
19
+ events << event
20
+ end
21
+ end
22
+
23
+ def collect_events
24
+ events = []
25
+ ActiveSupport::IsolatedExecutionState[KEY] = events
26
+ yield
27
+ events
28
+ ensure
29
+ ActiveSupport::IsolatedExecutionState.delete(KEY)
30
+ end
31
+
32
+ def ensure_subscribed
33
+ @mutex.synchronize do
34
+ # Subscribe to all events, except those beginning with "!" Ideally we would be
35
+ # more selective of what is being measured
36
+ @subscriber ||= ActiveSupport::Notifications.subscribe(/\A[^!]/, self)
37
+ end
38
+ end
39
+
40
+ def unsubscribe
41
+ @mutex.synchronize do
42
+ ActiveSupport::Notifications.unsubscribe @subscriber
43
+ @subscriber = nil
44
+ end
45
+ end
46
+ end
47
+
48
+ def self.unsubscribe # :nodoc:
49
+ Subscriber.instance.unsubscribe
50
+ end
51
+
52
+ def initialize(app)
53
+ @app = app
54
+ @subscriber = Subscriber.instance
55
+ @subscriber.ensure_subscribed
56
+ end
57
+
58
+ def call(env)
59
+ response = nil
60
+ events = @subscriber.collect_events do
61
+ response = @app.call(env)
62
+ end
63
+
64
+ headers = response[1]
65
+
66
+ header_info = events.group_by(&:name).map do |event_name, events_collection|
67
+ "%s;dur=%.2f" % [event_name, events_collection.sum(&:duration)]
68
+ end
69
+
70
+ if headers[ActionDispatch::Constants::SERVER_TIMING].present?
71
+ header_info.prepend(headers[ActionDispatch::Constants::SERVER_TIMING])
72
+ end
73
+ headers[ActionDispatch::Constants::SERVER_TIMING] = header_info.join(", ")
74
+
75
+ response
76
+ end
77
+ end
78
+ end