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,72 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
1
5
  module ActionDispatch
6
+ # # Action Dispatch SSL
7
+ #
8
+ # This middleware is added to the stack when `config.force_ssl = true`, and is
9
+ # passed the options set in `config.ssl_options`. It does three jobs to enforce
10
+ # secure HTTP requests:
11
+ #
12
+ # 1. **TLS redirect**: Permanently redirects `http://` requests to `https://`
13
+ # with the same URL host, path, etc. Enabled by default. Set
14
+ # `config.ssl_options` to modify the destination URL (e.g. `redirect: {
15
+ # host: "secure.widgets.com", port: 8080 }`), or set `redirect: false` to
16
+ # disable this feature.
17
+ #
18
+ # Requests can opt-out of redirection with `exclude`:
19
+ #
20
+ # config.ssl_options = { redirect: { exclude: -> request { /healthcheck/.match?(request.path) } } }
21
+ #
22
+ # Cookies will not be flagged as secure for excluded requests.
23
+ #
24
+ # 2. **Secure cookies**: Sets the `secure` flag on cookies to tell browsers
25
+ # they must not be sent along with `http://` requests. Enabled by default.
26
+ # Set `config.ssl_options` with `secure_cookies: false` to disable this
27
+ # feature.
28
+ #
29
+ # 3. **HTTP Strict Transport Security (HSTS)**: Tells the browser to remember
30
+ # this site as TLS-only and automatically redirect non-TLS requests. Enabled
31
+ # by default. Configure `config.ssl_options` with `hsts: false` to disable.
32
+ #
33
+ # Set `config.ssl_options` with `hsts: { ... }` to configure HSTS:
34
+ #
35
+ # * `expires`: How long, in seconds, these settings will stick. The
36
+ # minimum required to qualify for browser preload lists is 1 year.
37
+ # Defaults to 2 years (recommended).
38
+ #
39
+ # * `subdomains`: Set to `true` to tell the browser to apply these
40
+ # settings to all subdomains. This protects your cookies from
41
+ # interception by a vulnerable site on a subdomain. Defaults to `true`.
42
+ #
43
+ # * `preload`: Advertise that this site may be included in browsers'
44
+ # preloaded HSTS lists. HSTS protects your site on every visit *except
45
+ # the first visit* since it hasn't seen your HSTS header yet. To close
46
+ # this gap, browser vendors include a baked-in list of HSTS-enabled
47
+ # sites. Go to https://hstspreload.org to submit your site for
48
+ # inclusion. Defaults to `false`.
49
+ #
50
+ #
51
+ # To turn off HSTS, omitting the header is not enough. Browsers will
52
+ # remember the original HSTS directive until it expires. Instead, use the
53
+ # header to tell browsers to expire HSTS immediately. Setting `hsts: false`
54
+ # is a shortcut for `hsts: { expires: 0 }`.
55
+ #
2
56
  class SSL
3
- YEAR = 31536000
57
+ # :stopdoc: Default to 2 years as recommended on hstspreload.org.
58
+ HSTS_EXPIRES_IN = 63072000
59
+
60
+ PERMANENT_REDIRECT_REQUEST_METHODS = %w[GET HEAD] # :nodoc:
4
61
 
5
62
  def self.default_hsts_options
6
- { :expires => YEAR, :subdomains => false }
63
+ { expires: HSTS_EXPIRES_IN, subdomains: true, preload: false }
7
64
  end
8
65
 
9
- def initialize(app, options = {})
66
+ def initialize(app, redirect: {}, hsts: {}, secure_cookies: true, ssl_default_redirect_status: nil)
10
67
  @app = app
11
68
 
12
- @hsts = options.fetch(:hsts, {})
13
- @hsts = {} if @hsts == true
14
- @hsts = self.class.default_hsts_options.merge(@hsts) if @hsts
69
+ @redirect = redirect
70
+
71
+ @exclude = @redirect && @redirect[:exclude] || proc { !@redirect }
72
+ @secure_cookies = secure_cookies
15
73
 
16
- @host = options[:host]
17
- @port = options[:port]
74
+ @hsts_header = build_hsts_header(normalize_hsts_options(hsts))
75
+ @ssl_default_redirect_status = ssl_default_redirect_status
18
76
  end
19
77
 
20
78
  def call(env)
21
- request = Request.new(env)
79
+ request = Request.new env
22
80
 
23
81
  if request.ssl?
24
- status, headers, body = @app.call(env)
25
- headers.reverse_merge!(hsts_headers)
26
- flag_cookies_as_secure!(headers)
27
- [status, headers, body]
82
+ @app.call(env).tap do |status, headers, body|
83
+ set_hsts_header! headers
84
+ flag_cookies_as_secure! headers if @secure_cookies && !@exclude.call(request)
85
+ end
28
86
  else
29
- redirect_to_https(request)
87
+ return redirect_to_https request unless @exclude.call(request)
88
+ @app.call(env)
30
89
  end
31
90
  end
32
91
 
33
92
  private
34
- def redirect_to_https(request)
35
- host = @host || request.host
36
- port = @port || request.port
37
-
38
- location = "https://#{host}"
39
- location << ":#{port}" if port != 80
40
- location << request.fullpath
41
-
42
- headers = { 'Content-Type' => 'text/html', 'Location' => location }
43
-
44
- [301, headers, []]
93
+ def set_hsts_header!(headers)
94
+ headers[Constants::STRICT_TRANSPORT_SECURITY] ||= @hsts_header
45
95
  end
46
96
 
47
- # http://tools.ietf.org/html/draft-hodges-strict-transport-sec-02
48
- def hsts_headers
49
- if @hsts
50
- value = "max-age=#{@hsts[:expires].to_i}"
51
- value += "; includeSubDomains" if @hsts[:subdomains]
52
- { 'Strict-Transport-Security' => value }
97
+ def normalize_hsts_options(options)
98
+ case options
99
+ # Explicitly disabling HSTS clears the existing setting from browsers by setting
100
+ # expiry to 0.
101
+ when false
102
+ self.class.default_hsts_options.merge(expires: 0)
103
+ # Default to enabled, with default options.
104
+ when nil, true
105
+ self.class.default_hsts_options
53
106
  else
54
- {}
107
+ self.class.default_hsts_options.merge(options)
55
108
  end
56
109
  end
57
110
 
111
+ # https://tools.ietf.org/html/rfc6797#section-6.1
112
+ def build_hsts_header(hsts)
113
+ value = +"max-age=#{hsts[:expires].to_i}"
114
+ value << "; includeSubDomains" if hsts[:subdomains]
115
+ value << "; preload" if hsts[:preload]
116
+ value
117
+ end
118
+
58
119
  def flag_cookies_as_secure!(headers)
59
- if cookies = headers['Set-Cookie']
60
- cookies = cookies.split("\n")
120
+ cookies = headers[Rack::SET_COOKIE]
121
+ return unless cookies
61
122
 
62
- headers['Set-Cookie'] = cookies.map { |cookie|
63
- if cookie !~ /;\s*secure\s*(;|$)/i
123
+ if Gem::Version.new(Rack::RELEASE) < Gem::Version.new("3")
124
+ cookies = cookies.split("\n")
125
+ headers[Rack::SET_COOKIE] = cookies.map { |cookie|
126
+ if !/;\s*secure\s*(;|$)/i.match?(cookie)
64
127
  "#{cookie}; secure"
65
128
  else
66
129
  cookie
67
130
  end
68
131
  }.join("\n")
132
+ else
133
+ headers[Rack::SET_COOKIE] = Array(cookies).map do |cookie|
134
+ if !/;\s*secure\s*(;|$)/i.match?(cookie)
135
+ "#{cookie}; secure"
136
+ else
137
+ cookie
138
+ end
139
+ end
69
140
  end
70
141
  end
142
+
143
+ def redirect_to_https(request)
144
+ [ @redirect.fetch(:status, redirection_status(request)),
145
+ { Rack::CONTENT_TYPE => "text/html; charset=utf-8",
146
+ Constants::LOCATION => https_location_for(request) },
147
+ (@redirect[:body] || []) ]
148
+ end
149
+
150
+ def redirection_status(request)
151
+ if PERMANENT_REDIRECT_REQUEST_METHODS.include?(request.raw_request_method)
152
+ 301 # Issue a permanent redirect via a GET request.
153
+ elsif @ssl_default_redirect_status
154
+ @ssl_default_redirect_status
155
+ else
156
+ 307 # Issue a fresh request redirect to preserve the HTTP method.
157
+ end
158
+ end
159
+
160
+ def https_location_for(request)
161
+ host = @redirect[:host] || request.host
162
+ port = @redirect[:port] || request.port
163
+
164
+ location = +"https://#{host}"
165
+ location << ":#{port}" if port != 80 && port != 443
166
+ location << request.fullpath
167
+ location
168
+ end
71
169
  end
72
170
  end
@@ -1,52 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
1
5
  require "active_support/inflector/methods"
2
6
  require "active_support/dependencies"
3
7
 
4
8
  module ActionDispatch
9
+ # # Action Dispatch MiddlewareStack
10
+ #
11
+ # Read more about [Rails middleware
12
+ # stack](https://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack)
13
+ # in the guides.
5
14
  class MiddlewareStack
6
15
  class Middleware
7
- attr_reader :args, :block, :name, :classcache
8
-
9
- def initialize(klass_or_name, *args, &block)
10
- @klass = nil
11
-
12
- if klass_or_name.respond_to?(:name)
13
- @klass = klass_or_name
14
- @name = @klass.name
15
- else
16
- @name = klass_or_name.to_s
17
- end
16
+ attr_reader :args, :block, :klass
18
17
 
19
- @classcache = ActiveSupport::Dependencies::Reference
20
- @args, @block = args, block
18
+ def initialize(klass, args, block)
19
+ @klass = klass
20
+ @args = args
21
+ @block = block
21
22
  end
22
23
 
23
- def klass
24
- @klass || classcache[@name]
25
- end
24
+ def name; klass.name; end
26
25
 
27
26
  def ==(middleware)
28
27
  case middleware
29
28
  when Middleware
30
29
  klass == middleware.klass
31
- when Class
30
+ when Module
32
31
  klass == middleware
33
- else
34
- normalize(@name) == normalize(middleware)
35
32
  end
36
33
  end
37
34
 
38
35
  def inspect
39
- klass.to_s
36
+ if klass.is_a?(Module)
37
+ klass.to_s
38
+ else
39
+ klass.class.to_s
40
+ end
40
41
  end
41
42
 
42
43
  def build(app)
43
44
  klass.new(app, *args, &block)
44
45
  end
45
46
 
46
- private
47
+ def build_instrumented(app)
48
+ InstrumentationProxy.new(build(app), inspect)
49
+ end
50
+ end
51
+
52
+ # This class is used to instrument the execution of a single middleware. It
53
+ # proxies the `call` method transparently and instruments the method call.
54
+ class InstrumentationProxy
55
+ EVENT_NAME = "process_middleware.action_dispatch"
56
+
57
+ def initialize(middleware, class_name)
58
+ @middleware = middleware
47
59
 
48
- def normalize(object)
49
- object.to_s.strip.sub(/^::/, '')
60
+ @payload = {
61
+ middleware: class_name,
62
+ }
63
+ end
64
+
65
+ def call(env)
66
+ ActiveSupport::Notifications.instrument(EVENT_NAME, @payload) do
67
+ @middleware.call(env)
68
+ end
50
69
  end
51
70
  end
52
71
 
@@ -59,8 +78,8 @@ module ActionDispatch
59
78
  yield(self) if block_given?
60
79
  end
61
80
 
62
- def each
63
- @middlewares.each { |x| yield x }
81
+ def each(&block)
82
+ @middlewares.each(&block)
64
83
  end
65
84
 
66
85
  def size
@@ -75,20 +94,20 @@ module ActionDispatch
75
94
  middlewares[i]
76
95
  end
77
96
 
78
- def unshift(*args, &block)
79
- middleware = self.class::Middleware.new(*args, &block)
80
- middlewares.unshift(middleware)
97
+ def unshift(klass, *args, &block)
98
+ middlewares.unshift(build_middleware(klass, args, block))
81
99
  end
100
+ ruby2_keywords(:unshift)
82
101
 
83
102
  def initialize_copy(other)
84
103
  self.middlewares = other.middlewares.dup
85
104
  end
86
105
 
87
- def insert(index, *args, &block)
106
+ def insert(index, klass, *args, &block)
88
107
  index = assert_index(index, :before)
89
- middleware = self.class::Middleware.new(*args, &block)
90
- middlewares.insert(index, middleware)
108
+ middlewares.insert(index, build_middleware(klass, args, block))
91
109
  end
110
+ ruby2_keywords(:insert)
92
111
 
93
112
  alias_method :insert_before, :insert
94
113
 
@@ -96,34 +115,80 @@ module ActionDispatch
96
115
  index = assert_index(index, :after)
97
116
  insert(index + 1, *args, &block)
98
117
  end
118
+ ruby2_keywords(:insert_after)
99
119
 
100
120
  def swap(target, *args, &block)
101
121
  index = assert_index(target, :before)
102
122
  insert(index, *args, &block)
103
123
  middlewares.delete_at(index + 1)
104
124
  end
125
+ ruby2_keywords(:swap)
105
126
 
127
+ # Deletes a middleware from the middleware stack.
128
+ #
129
+ # Returns the array of middlewares not including the deleted item, or returns
130
+ # nil if the target is not found.
106
131
  def delete(target)
107
- middlewares.delete target
132
+ middlewares.reject! { |m| m.name == target.name }
108
133
  end
109
134
 
110
- def use(*args, &block)
111
- middleware = self.class::Middleware.new(*args, &block)
112
- middlewares.push(middleware)
135
+ # Deletes a middleware from the middleware stack.
136
+ #
137
+ # Returns the array of middlewares not including the deleted item, or raises
138
+ # `RuntimeError` if the target is not found.
139
+ def delete!(target)
140
+ delete(target) || (raise "No such middleware to remove: #{target.inspect}")
113
141
  end
114
142
 
115
- def build(app = nil, &block)
116
- app ||= block
117
- raise "MiddlewareStack#build requires an app" unless app
118
- middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) }
143
+ def move(target, source)
144
+ source_index = assert_index(source, :before)
145
+ source_middleware = middlewares.delete_at(source_index)
146
+
147
+ target_index = assert_index(target, :before)
148
+ middlewares.insert(target_index, source_middleware)
119
149
  end
120
150
 
121
- protected
151
+ alias_method :move_before, :move
152
+
153
+ def move_after(target, source)
154
+ source_index = assert_index(source, :after)
155
+ source_middleware = middlewares.delete_at(source_index)
122
156
 
123
- def assert_index(index, where)
124
- i = index.is_a?(Integer) ? index : middlewares.index(index)
125
- raise "No such middleware to insert #{where}: #{index.inspect}" unless i
126
- i
157
+ target_index = assert_index(target, :after)
158
+ middlewares.insert(target_index + 1, source_middleware)
159
+ end
160
+
161
+ def use(klass, *args, &block)
162
+ middlewares.push(build_middleware(klass, args, block))
163
+ end
164
+ ruby2_keywords(:use)
165
+
166
+ def build(app = nil, &block)
167
+ instrumenting = ActiveSupport::Notifications.notifier.listening?(InstrumentationProxy::EVENT_NAME)
168
+ middlewares.freeze.reverse.inject(app || block) do |a, e|
169
+ if instrumenting
170
+ e.build_instrumented(a)
171
+ else
172
+ e.build(a)
173
+ end
174
+ end
127
175
  end
176
+
177
+ private
178
+ def assert_index(index, where)
179
+ i = index.is_a?(Integer) ? index : index_of(index)
180
+ raise "No such middleware to insert #{where}: #{index.inspect}" unless i
181
+ i
182
+ end
183
+
184
+ def build_middleware(klass, args, block)
185
+ Middleware.new(klass, args, block)
186
+ end
187
+
188
+ def index_of(klass)
189
+ middlewares.index do |m|
190
+ m.name == klass.name
191
+ end
192
+ end
128
193
  end
129
194
  end