actionpack 3.2.19 → 4.2.11.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 (244) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +412 -503
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +11 -294
  5. data/lib/abstract_controller/asset_paths.rb +2 -2
  6. data/lib/abstract_controller/base.rb +52 -18
  7. data/lib/abstract_controller/callbacks.rb +87 -89
  8. data/lib/abstract_controller/collector.rb +17 -3
  9. data/lib/abstract_controller/helpers.rb +41 -14
  10. data/lib/abstract_controller/logger.rb +1 -2
  11. data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
  12. data/lib/abstract_controller/rendering.rb +65 -118
  13. data/lib/abstract_controller/translation.rb +16 -1
  14. data/lib/abstract_controller/url_for.rb +7 -7
  15. data/lib/abstract_controller.rb +2 -10
  16. data/lib/action_controller/base.rb +61 -28
  17. data/lib/action_controller/caching/fragments.rb +30 -54
  18. data/lib/action_controller/caching.rb +38 -35
  19. data/lib/action_controller/log_subscriber.rb +35 -18
  20. data/lib/action_controller/metal/conditional_get.rb +103 -34
  21. data/lib/action_controller/metal/data_streaming.rb +20 -26
  22. data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
  23. data/lib/action_controller/metal/exceptions.rb +19 -6
  24. data/lib/action_controller/metal/flash.rb +41 -9
  25. data/lib/action_controller/metal/force_ssl.rb +70 -12
  26. data/lib/action_controller/metal/head.rb +30 -7
  27. data/lib/action_controller/metal/helpers.rb +11 -11
  28. data/lib/action_controller/metal/hide_actions.rb +0 -1
  29. data/lib/action_controller/metal/http_authentication.rb +140 -94
  30. data/lib/action_controller/metal/implicit_render.rb +1 -1
  31. data/lib/action_controller/metal/instrumentation.rb +11 -7
  32. data/lib/action_controller/metal/live.rb +328 -0
  33. data/lib/action_controller/metal/mime_responds.rb +161 -152
  34. data/lib/action_controller/metal/params_wrapper.rb +126 -81
  35. data/lib/action_controller/metal/rack_delegation.rb +10 -4
  36. data/lib/action_controller/metal/redirecting.rb +44 -41
  37. data/lib/action_controller/metal/renderers.rb +48 -19
  38. data/lib/action_controller/metal/rendering.rb +46 -11
  39. data/lib/action_controller/metal/request_forgery_protection.rb +250 -29
  40. data/lib/action_controller/metal/streaming.rb +30 -38
  41. data/lib/action_controller/metal/strong_parameters.rb +669 -0
  42. data/lib/action_controller/metal/testing.rb +12 -18
  43. data/lib/action_controller/metal/url_for.rb +31 -29
  44. data/lib/action_controller/metal.rb +31 -40
  45. data/lib/action_controller/model_naming.rb +12 -0
  46. data/lib/action_controller/railtie.rb +38 -18
  47. data/lib/action_controller/railties/helpers.rb +22 -0
  48. data/lib/action_controller/test_case.rb +359 -173
  49. data/lib/action_controller.rb +9 -16
  50. data/lib/action_dispatch/http/cache.rb +64 -11
  51. data/lib/action_dispatch/http/filter_parameters.rb +20 -10
  52. data/lib/action_dispatch/http/filter_redirect.rb +38 -0
  53. data/lib/action_dispatch/http/headers.rb +85 -17
  54. data/lib/action_dispatch/http/mime_negotiation.rb +55 -5
  55. data/lib/action_dispatch/http/mime_type.rb +167 -114
  56. data/lib/action_dispatch/http/mime_types.rb +2 -1
  57. data/lib/action_dispatch/http/parameter_filter.rb +44 -46
  58. data/lib/action_dispatch/http/parameters.rb +30 -46
  59. data/lib/action_dispatch/http/rack_cache.rb +2 -3
  60. data/lib/action_dispatch/http/request.rb +108 -45
  61. data/lib/action_dispatch/http/response.rb +247 -48
  62. data/lib/action_dispatch/http/upload.rb +60 -29
  63. data/lib/action_dispatch/http/url.rb +135 -45
  64. data/lib/action_dispatch/journey/backwards.rb +5 -0
  65. data/lib/action_dispatch/journey/formatter.rb +166 -0
  66. data/lib/action_dispatch/journey/gtg/builder.rb +162 -0
  67. data/lib/action_dispatch/journey/gtg/simulator.rb +47 -0
  68. data/lib/action_dispatch/journey/gtg/transition_table.rb +157 -0
  69. data/lib/action_dispatch/journey/nfa/builder.rb +76 -0
  70. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  71. data/lib/action_dispatch/journey/nfa/simulator.rb +47 -0
  72. data/lib/action_dispatch/journey/nfa/transition_table.rb +163 -0
  73. data/lib/action_dispatch/journey/nodes/node.rb +128 -0
  74. data/lib/action_dispatch/journey/parser.rb +198 -0
  75. data/lib/action_dispatch/journey/parser.y +49 -0
  76. data/lib/action_dispatch/journey/parser_extras.rb +23 -0
  77. data/lib/action_dispatch/journey/path/pattern.rb +193 -0
  78. data/lib/action_dispatch/journey/route.rb +125 -0
  79. data/lib/action_dispatch/journey/router/strexp.rb +27 -0
  80. data/lib/action_dispatch/journey/router/utils.rb +93 -0
  81. data/lib/action_dispatch/journey/router.rb +144 -0
  82. data/lib/action_dispatch/journey/routes.rb +80 -0
  83. data/lib/action_dispatch/journey/scanner.rb +61 -0
  84. data/lib/action_dispatch/journey/visitors.rb +221 -0
  85. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  86. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  87. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  88. data/lib/action_dispatch/journey.rb +5 -0
  89. data/lib/action_dispatch/middleware/callbacks.rb +16 -11
  90. data/lib/action_dispatch/middleware/cookies.rb +346 -125
  91. data/lib/action_dispatch/middleware/debug_exceptions.rb +52 -24
  92. data/lib/action_dispatch/middleware/exception_wrapper.rb +75 -9
  93. data/lib/action_dispatch/middleware/flash.rb +85 -72
  94. data/lib/action_dispatch/middleware/params_parser.rb +16 -31
  95. data/lib/action_dispatch/middleware/public_exceptions.rb +39 -14
  96. data/lib/action_dispatch/middleware/reloader.rb +16 -7
  97. data/lib/action_dispatch/middleware/remote_ip.rb +132 -40
  98. data/lib/action_dispatch/middleware/request_id.rb +3 -7
  99. data/lib/action_dispatch/middleware/session/abstract_store.rb +22 -20
  100. data/lib/action_dispatch/middleware/session/cache_store.rb +3 -3
  101. data/lib/action_dispatch/middleware/session/cookie_store.rb +84 -29
  102. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -3
  103. data/lib/action_dispatch/middleware/show_exceptions.rb +15 -44
  104. data/lib/action_dispatch/middleware/ssl.rb +72 -0
  105. data/lib/action_dispatch/middleware/stack.rb +6 -1
  106. data/lib/action_dispatch/middleware/static.rb +80 -23
  107. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +34 -0
  108. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  109. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +27 -0
  110. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
  111. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  112. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +16 -0
  113. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  114. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +133 -5
  115. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  116. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  117. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  122. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  123. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  124. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +200 -0
  125. data/lib/action_dispatch/railtie.rb +19 -6
  126. data/lib/action_dispatch/request/session.rb +193 -0
  127. data/lib/action_dispatch/request/utils.rb +35 -0
  128. data/lib/action_dispatch/routing/endpoint.rb +10 -0
  129. data/lib/action_dispatch/routing/inspector.rb +234 -0
  130. data/lib/action_dispatch/routing/mapper.rb +897 -436
  131. data/lib/action_dispatch/routing/polymorphic_routes.rb +213 -92
  132. data/lib/action_dispatch/routing/redirection.rb +97 -37
  133. data/lib/action_dispatch/routing/route_set.rb +432 -239
  134. data/lib/action_dispatch/routing/routes_proxy.rb +7 -4
  135. data/lib/action_dispatch/routing/url_for.rb +63 -34
  136. data/lib/action_dispatch/routing.rb +57 -89
  137. data/lib/action_dispatch/testing/assertions/dom.rb +2 -36
  138. data/lib/action_dispatch/testing/assertions/response.rb +24 -38
  139. data/lib/action_dispatch/testing/assertions/routing.rb +55 -54
  140. data/lib/action_dispatch/testing/assertions/selector.rb +2 -434
  141. data/lib/action_dispatch/testing/assertions/tag.rb +2 -137
  142. data/lib/action_dispatch/testing/assertions.rb +11 -7
  143. data/lib/action_dispatch/testing/integration.rb +88 -72
  144. data/lib/action_dispatch/testing/test_process.rb +9 -6
  145. data/lib/action_dispatch/testing/test_request.rb +13 -9
  146. data/lib/action_dispatch/testing/test_response.rb +1 -5
  147. data/lib/action_dispatch.rb +24 -21
  148. data/lib/action_pack/gem_version.rb +15 -0
  149. data/lib/action_pack/version.rb +5 -7
  150. data/lib/action_pack.rb +1 -1
  151. metadata +181 -292
  152. data/lib/abstract_controller/layouts.rb +0 -423
  153. data/lib/abstract_controller/view_paths.rb +0 -96
  154. data/lib/action_controller/caching/actions.rb +0 -185
  155. data/lib/action_controller/caching/pages.rb +0 -187
  156. data/lib/action_controller/caching/sweeping.rb +0 -97
  157. data/lib/action_controller/deprecated/integration_test.rb +0 -2
  158. data/lib/action_controller/deprecated/performance_test.rb +0 -1
  159. data/lib/action_controller/deprecated.rb +0 -3
  160. data/lib/action_controller/metal/compatibility.rb +0 -65
  161. data/lib/action_controller/metal/responder.rb +0 -286
  162. data/lib/action_controller/metal/session_management.rb +0 -14
  163. data/lib/action_controller/railties/paths.rb +0 -25
  164. data/lib/action_controller/record_identifier.rb +0 -85
  165. data/lib/action_controller/vendor/html-scanner/html/document.rb +0 -68
  166. data/lib/action_controller/vendor/html-scanner/html/node.rb +0 -532
  167. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +0 -177
  168. data/lib/action_controller/vendor/html-scanner/html/selector.rb +0 -830
  169. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +0 -107
  170. data/lib/action_controller/vendor/html-scanner/html/version.rb +0 -11
  171. data/lib/action_controller/vendor/html-scanner.rb +0 -20
  172. data/lib/action_dispatch/middleware/best_standards_support.rb +0 -30
  173. data/lib/action_dispatch/middleware/body_proxy.rb +0 -30
  174. data/lib/action_dispatch/middleware/head.rb +0 -18
  175. data/lib/action_dispatch/middleware/rescue.rb +0 -26
  176. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +0 -31
  177. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +0 -26
  178. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +0 -10
  179. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +0 -2
  180. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +0 -15
  181. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +0 -17
  182. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +0 -2
  183. data/lib/action_dispatch/testing/performance_test.rb +0 -10
  184. data/lib/action_view/asset_paths.rb +0 -142
  185. data/lib/action_view/base.rb +0 -220
  186. data/lib/action_view/buffers.rb +0 -43
  187. data/lib/action_view/context.rb +0 -36
  188. data/lib/action_view/flows.rb +0 -79
  189. data/lib/action_view/helpers/active_model_helper.rb +0 -50
  190. data/lib/action_view/helpers/asset_paths.rb +0 -7
  191. data/lib/action_view/helpers/asset_tag_helper.rb +0 -457
  192. data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +0 -146
  193. data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +0 -93
  194. data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +0 -193
  195. data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +0 -148
  196. data/lib/action_view/helpers/atom_feed_helper.rb +0 -200
  197. data/lib/action_view/helpers/cache_helper.rb +0 -64
  198. data/lib/action_view/helpers/capture_helper.rb +0 -203
  199. data/lib/action_view/helpers/controller_helper.rb +0 -25
  200. data/lib/action_view/helpers/csrf_helper.rb +0 -32
  201. data/lib/action_view/helpers/date_helper.rb +0 -1062
  202. data/lib/action_view/helpers/debug_helper.rb +0 -40
  203. data/lib/action_view/helpers/form_helper.rb +0 -1486
  204. data/lib/action_view/helpers/form_options_helper.rb +0 -658
  205. data/lib/action_view/helpers/form_tag_helper.rb +0 -685
  206. data/lib/action_view/helpers/javascript_helper.rb +0 -110
  207. data/lib/action_view/helpers/number_helper.rb +0 -622
  208. data/lib/action_view/helpers/output_safety_helper.rb +0 -38
  209. data/lib/action_view/helpers/record_tag_helper.rb +0 -111
  210. data/lib/action_view/helpers/rendering_helper.rb +0 -90
  211. data/lib/action_view/helpers/sanitize_helper.rb +0 -259
  212. data/lib/action_view/helpers/tag_helper.rb +0 -160
  213. data/lib/action_view/helpers/text_helper.rb +0 -426
  214. data/lib/action_view/helpers/translation_helper.rb +0 -91
  215. data/lib/action_view/helpers/url_helper.rb +0 -693
  216. data/lib/action_view/helpers.rb +0 -60
  217. data/lib/action_view/locale/en.yml +0 -160
  218. data/lib/action_view/log_subscriber.rb +0 -28
  219. data/lib/action_view/lookup_context.rb +0 -254
  220. data/lib/action_view/path_set.rb +0 -89
  221. data/lib/action_view/railtie.rb +0 -55
  222. data/lib/action_view/renderer/abstract_renderer.rb +0 -41
  223. data/lib/action_view/renderer/partial_renderer.rb +0 -415
  224. data/lib/action_view/renderer/renderer.rb +0 -54
  225. data/lib/action_view/renderer/streaming_template_renderer.rb +0 -106
  226. data/lib/action_view/renderer/template_renderer.rb +0 -94
  227. data/lib/action_view/template/error.rb +0 -128
  228. data/lib/action_view/template/handlers/builder.rb +0 -26
  229. data/lib/action_view/template/handlers/erb.rb +0 -125
  230. data/lib/action_view/template/handlers.rb +0 -50
  231. data/lib/action_view/template/resolver.rb +0 -272
  232. data/lib/action_view/template/text.rb +0 -30
  233. data/lib/action_view/template.rb +0 -337
  234. data/lib/action_view/test_case.rb +0 -245
  235. data/lib/action_view/testing/resolvers.rb +0 -50
  236. data/lib/action_view.rb +0 -84
  237. data/lib/sprockets/assets.rake +0 -99
  238. data/lib/sprockets/bootstrap.rb +0 -37
  239. data/lib/sprockets/compressors.rb +0 -83
  240. data/lib/sprockets/helpers/isolated_helper.rb +0 -13
  241. data/lib/sprockets/helpers/rails_helper.rb +0 -182
  242. data/lib/sprockets/helpers.rb +0 -6
  243. data/lib/sprockets/railtie.rb +0 -62
  244. data/lib/sprockets/static_compiler.rb +0 -56
@@ -1,5 +1,14 @@
1
1
  module ActionDispatch
2
- # A simple Rack application that renders exceptions in the given public path.
2
+ # 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`
4
+ # directory. For example when this middleware receives a 500 response it will
5
+ # render the template found in `/public/500.html`.
6
+ # If an internationalized locale is set, this middleware will attempt to render
7
+ # the template in `/public/500.<locale>.html`. If an internationalized template
8
+ # is not found it will fall back on `/public/500.html`.
9
+ #
10
+ # When a request with a content type other than HTML is made, this middleware
11
+ # will attempt to convert error information into the appropriate response type.
3
12
  class PublicExceptions
4
13
  attr_accessor :public_path
5
14
 
@@ -8,23 +17,39 @@ module ActionDispatch
8
17
  end
9
18
 
10
19
  def call(env)
11
- status = env["PATH_INFO"][1..-1]
12
- locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
13
- path = "#{public_path}/#{status}.html"
14
-
15
- if locale_path && File.exist?(locale_path)
16
- render(status, File.read(locale_path))
17
- elsif File.exist?(path)
18
- render(status, File.read(path))
20
+ status = env["PATH_INFO"][1..-1]
21
+ 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]) }
24
+
25
+ render(status, content_type, body)
26
+ end
27
+
28
+ 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))
19
34
  else
20
- [404, { "X-Cascade" => "pass" }, []]
35
+ render_html(status)
21
36
  end
22
37
  end
23
38
 
24
- private
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
43
+
44
+ def render_html(status)
45
+ path = "#{public_path}/#{status}.#{I18n.locale}.html"
46
+ path = "#{public_path}/#{status}.html" unless (found = File.exist?(path))
25
47
 
26
- def render(status, body)
27
- [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
48
+ if found || File.exist?(path)
49
+ render_format(status, 'text/html', File.read(path))
50
+ else
51
+ [404, { "X-Cascade" => "pass" }, []]
52
+ end
28
53
  end
29
54
  end
30
- end
55
+ end
@@ -1,4 +1,4 @@
1
- require 'action_dispatch/middleware/body_proxy'
1
+ require 'active_support/deprecation/reporting'
2
2
 
3
3
  module ActionDispatch
4
4
  # ActionDispatch::Reloader provides prepare and cleanup callbacks,
@@ -20,26 +20,33 @@ module ActionDispatch
20
20
  # classes before they are unloaded.
21
21
  #
22
22
  # By default, ActionDispatch::Reloader is included in the middleware stack
23
- # only in the development environment; specifically, when config.cache_classes
23
+ # only in the development environment; specifically, when +config.cache_classes+
24
24
  # is false. Callbacks may be registered even when it is not included in the
25
- # middleware stack, but are executed only when +ActionDispatch::Reloader.prepare!+
26
- # or +ActionDispatch::Reloader.cleanup!+ are called manually.
25
+ # middleware stack, but are executed only when <tt>ActionDispatch::Reloader.prepare!</tt>
26
+ # or <tt>ActionDispatch::Reloader.cleanup!</tt> are called manually.
27
27
  #
28
28
  class Reloader
29
29
  include ActiveSupport::Callbacks
30
+ include ActiveSupport::Deprecation::Reporting
30
31
 
31
- define_callbacks :prepare, :scope => :name
32
- define_callbacks :cleanup, :scope => :name
32
+ define_callbacks :prepare
33
+ define_callbacks :cleanup
33
34
 
34
35
  # Add a prepare callback. Prepare callbacks are run before each request, prior
35
36
  # to ActionDispatch::Callback's before callbacks.
36
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
37
41
  set_callback(:prepare, *args, &block)
38
42
  end
39
43
 
40
44
  # Add a cleanup callback. Cleanup callbacks are run after each request is
41
45
  # complete (after #close is called on the response body).
42
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
43
50
  set_callback(:cleanup, *args, &block)
44
51
  end
45
52
 
@@ -62,8 +69,10 @@ module ActionDispatch
62
69
  def call(env)
63
70
  @validated = @condition.call
64
71
  prepare!
72
+
65
73
  response = @app.call(env)
66
- response[2] = ActionDispatch::BodyProxy.new(response[2]) { cleanup! }
74
+ response[2] = ::Rack::BodyProxy.new(response[2]) { cleanup! }
75
+
67
76
  response
68
77
  rescue Exception
69
78
  cleanup!
@@ -1,80 +1,172 @@
1
+ require 'ipaddr'
2
+
1
3
  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.
12
+ #
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.
18
+ #
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.
2
26
  class RemoteIp
3
- class IpSpoofAttackError < StandardError ; end
4
-
5
- # IP addresses that are "trusted proxies" that can be stripped from
6
- # the comma-delimited list in the X-Forwarded-For header. See also:
7
- # http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
8
- TRUSTED_PROXIES = %r{
9
- ^127\.0\.0\.1$ | # localhost
10
- ^(10 | # private IP 10.x.x.x
11
- 172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255
12
- 192\.168 # private IP 192.168.x.x
13
- )\.
14
- }x
27
+ class IpSpoofAttackError < StandardError; end
28
+
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.
33
+ TRUSTED_PROXIES = [
34
+ "127.0.0.1", # localhost IPv4
35
+ "::1", # localhost IPv6
36
+ "fc00::/7", # private IPv6 range fc00::/7
37
+ "10.0.0.0/8", # private IPv4 range 10.x.x.x
38
+ "172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255
39
+ "192.168.0.0/16", # private IPv4 range 192.168.x.x
40
+ ].map { |proxy| IPAddr.new(proxy) }
15
41
 
16
42
  attr_reader :check_ip, :proxies
17
43
 
44
+ # Create a new +RemoteIp+ middleware instance.
45
+ #
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).
51
+ #
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.
18
60
  def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
19
61
  @app = app
20
62
  @check_ip = check_ip_spoofing
21
- if custom_proxies
22
- custom_regexp = Regexp.new(custom_proxies)
23
- @proxies = Regexp.union(TRUSTED_PROXIES, custom_regexp)
63
+ @proxies = if custom_proxies.blank?
64
+ TRUSTED_PROXIES
65
+ elsif custom_proxies.respond_to?(:any?)
66
+ custom_proxies
24
67
  else
25
- @proxies = TRUSTED_PROXIES
68
+ Array(custom_proxies) + TRUSTED_PROXIES
26
69
  end
27
70
  end
28
71
 
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.
29
76
  def call(env)
30
77
  env["action_dispatch.remote_ip"] = GetIp.new(env, self)
31
78
  @app.call(env)
32
79
  end
33
80
 
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.
34
84
  class GetIp
35
85
  def initialize(env, middleware)
36
- @env = env
37
- @middleware = middleware
38
- @calculated_ip = false
86
+ @env = env
87
+ @check_ip = middleware.check_ip
88
+ @proxies = middleware.proxies
39
89
  end
40
90
 
41
- # Determines originating IP address. REMOTE_ADDR is the standard
42
- # but will be wrong if the user is behind a proxy. Proxies will set
43
- # HTTP_CLIENT_IP and/or HTTP_X_FORWARDED_FOR, so we prioritize those.
44
- # HTTP_X_FORWARDED_FOR may be a comma-delimited list in the case of
45
- # multiple chained proxies. The last address which is not a known proxy
46
- # will be the originating IP.
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.
94
+ #
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.
101
+ #
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.
105
+ #
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.
47
109
  def calculate_ip
48
- client_ip = @env['HTTP_CLIENT_IP']
49
- forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR')
50
- remote_addrs = ips_from('REMOTE_ADDR')
110
+ # Set by the Rack web server, this is a single value.
111
+ remote_addr = ips_from('REMOTE_ADDR').last
51
112
 
52
- check_ip = client_ip && forwarded_ips.present? && @middleware.check_ip
53
- if check_ip && !forwarded_ips.include?(client_ip)
113
+ # 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.
122
+ should_check_ip = @check_ip && client_ips.last && forwarded_ips.last
123
+ if should_check_ip && !forwarded_ips.include?(client_ips.last)
54
124
  # We don't know which came from the proxy, and which from the user
55
- raise IpSpoofAttackError, "IP spoofing attack?!" \
56
- "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \
125
+ raise IpSpoofAttackError, "IP spoofing attack?! " +
126
+ "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect} " +
57
127
  "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
58
128
  end
59
129
 
60
- not_proxy = client_ip || forwarded_ips.last || remote_addrs.first
130
+ # We assume these things about the IP headers:
131
+ #
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
61
136
 
62
- # Return first REMOTE_ADDR if there are no other options
63
- not_proxy || ips_from('REMOTE_ADDR', :allow_proxies).first
137
+ # If every single IP option is in the trusted list, just return REMOTE_ADDR
138
+ filter_proxies(ips).first || remote_addr
64
139
  end
65
140
 
141
+ # Memoizes the value returned by #calculate_ip and returns it for
142
+ # ActionDispatch::Request to use.
66
143
  def to_s
67
- return @ip if @calculated_ip
68
- @calculated_ip = true
69
- @ip = calculate_ip
144
+ @ip ||= calculate_ip
70
145
  end
71
146
 
72
147
  protected
73
148
 
74
- def ips_from(header, allow_proxies = false)
149
+ def ips_from(header)
150
+ # Split the comma-separated list into an array of strings
75
151
  ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
76
- allow_proxies ? ips : ips.reject{|ip| ip =~ @middleware.proxies }
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
161
+ end
77
162
  end
163
+
164
+ def filter_proxies(ips)
165
+ ips.reject do |ip|
166
+ @proxies.any? { |proxy| proxy === ip }
167
+ end
168
+ end
169
+
78
170
  end
79
171
 
80
172
  end
@@ -1,12 +1,11 @@
1
1
  require 'securerandom'
2
2
  require 'active_support/core_ext/string/access'
3
- require 'active_support/core_ext/object/blank'
4
3
 
5
4
  module ActionDispatch
6
5
  # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through
7
6
  # ActionDispatch::Request#uuid) and sends the same id to the client via the X-Request-Id header.
8
7
  #
9
- # The unique request id is either based off the X-Request-Id header in the request, which would typically be generated
8
+ # The unique request id is either based on the X-Request-Id header in the request, which would typically be generated
10
9
  # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
11
10
  # header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
12
11
  #
@@ -19,10 +18,7 @@ module ActionDispatch
19
18
 
20
19
  def call(env)
21
20
  env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id
22
- status, headers, body = @app.call(env)
23
-
24
- headers["X-Request-Id"] = env["action_dispatch.request_id"]
25
- [ status, headers, body ]
21
+ @app.call(env).tap { |_status, headers, _body| headers["X-Request-Id"] = env["action_dispatch.request_id"] }
26
22
  end
27
23
 
28
24
  private
@@ -33,7 +29,7 @@ module ActionDispatch
33
29
  end
34
30
 
35
31
  def internal_request_id
36
- SecureRandom.hex(16)
32
+ SecureRandom.uuid
37
33
  end
38
34
  end
39
35
  end
@@ -2,37 +2,31 @@ require 'rack/utils'
2
2
  require 'rack/request'
3
3
  require 'rack/session/abstract/id'
4
4
  require 'action_dispatch/middleware/cookies'
5
- require 'active_support/core_ext/object/blank'
5
+ require 'action_dispatch/request/session'
6
6
 
7
7
  module ActionDispatch
8
8
  module Session
9
9
  class SessionRestoreError < StandardError #:nodoc:
10
- end
10
+ attr_reader :original_exception
11
+
12
+ def initialize(const_error)
13
+ @original_exception = const_error
11
14
 
12
- module DestroyableSession
13
- def destroy
14
- clear
15
- options = @env[Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY] if @env
16
- options ||= {}
17
- @by.send(:destroy_session, @env, options[:id], options) if @by
18
- options[:id] = nil
19
- @loaded = false
15
+ super("Session contains objects whose class definition isn't available.\n" +
16
+ "Remember to require the classes for all objects kept in the session.\n" +
17
+ "(Original exception: #{const_error.message} [#{const_error.class}])\n")
20
18
  end
21
19
  end
22
20
 
23
- ::Rack::Session::Abstract::SessionHash.send :include, DestroyableSession
24
-
25
21
  module Compatibility
26
22
  def initialize(app, options = {})
27
23
  options[:key] ||= '_session_id'
28
- # FIXME Rack's secret is not being used
29
- options[:secret] ||= SecureRandom.hex(30)
30
24
  super
31
25
  end
32
26
 
33
27
  def generate_sid
34
28
  sid = SecureRandom.hex(16)
35
- sid.encode!('UTF-8') if sid.respond_to?(:encode!)
29
+ sid.encode!(Encoding::UTF_8)
36
30
  sid
37
31
  end
38
32
 
@@ -60,11 +54,8 @@ module ActionDispatch
60
54
  begin
61
55
  # Note that the regexp does not allow $1 to end with a ':'
62
56
  $1.constantize
63
- rescue LoadError, NameError => const_error
64
- raise ActionDispatch::Session::SessionRestoreError,
65
- "Session contains objects whose class definition isn't available.\n" +
66
- "Remember to require the classes for all objects kept in the session.\n" +
67
- "(Original exception: #{const_error.message} [#{const_error.class}])\n"
57
+ rescue LoadError, NameError => e
58
+ raise ActionDispatch::Session::SessionRestoreError, e, e.backtrace
68
59
  end
69
60
  retry
70
61
  else
@@ -73,9 +64,20 @@ module ActionDispatch
73
64
  end
74
65
  end
75
66
 
67
+ module SessionObject # :nodoc:
68
+ def prepare_session(env)
69
+ Request::Session.create(self, env, @default_options)
70
+ end
71
+
72
+ def loaded_session?(session)
73
+ !session.is_a?(Request::Session) || session.loaded?
74
+ end
75
+ end
76
+
76
77
  class AbstractStore < Rack::Session::Abstract::ID
77
78
  include Compatibility
78
79
  include StaleSessionCheck
80
+ include SessionObject
79
81
 
80
82
  private
81
83
 
@@ -16,9 +16,9 @@ module ActionDispatch
16
16
 
17
17
  # Get a session from the cache.
18
18
  def get_session(env, sid)
19
- sid ||= generate_sid
20
- session = @cache.read(cache_key(sid))
21
- session ||= {}
19
+ unless sid and session = @cache.read(cache_key(sid))
20
+ sid, session = generate_sid, {}
21
+ end
22
22
  [sid, session]
23
23
  end
24
24
 
@@ -1,53 +1,93 @@
1
1
  require 'active_support/core_ext/hash/keys'
2
- require 'active_support/core_ext/object/blank'
3
2
  require 'action_dispatch/middleware/session/abstract_store'
4
3
  require 'rack/session/cookie'
5
4
 
6
5
  module ActionDispatch
7
6
  module Session
8
- # This cookie-based session store is the Rails default. Sessions typically
9
- # contain at most a user_id and flash message; both fit within the 4K cookie
10
- # size limit. Cookie-based sessions are dramatically faster than the
11
- # alternatives.
7
+ # This cookie-based session store is the Rails default. It is
8
+ # dramatically faster than the alternatives.
12
9
  #
13
- # If you have more than 4K of session data or don't want your data to be
14
- # visible to the user, pick another session store.
10
+ # Sessions typically contain at most a user_id and flash message; both fit
11
+ # within the 4K cookie size limit. A CookieOverflow exception is raised if
12
+ # you attempt to store more than 4K of data.
15
13
  #
16
- # CookieOverflow is raised if you attempt to store more than 4K of data.
14
+ # The cookie jar used for storage is automatically configured to be the
15
+ # best possible option given your application's configuration.
17
16
  #
18
- # A message digest is included with the cookie to ensure data integrity:
19
- # a user cannot alter his +user_id+ without knowing the secret key
20
- # included in the hash. New apps are generated with a pregenerated secret
21
- # in config/environment.rb. Set your own for old apps you're upgrading.
17
+ # If you only have secret_token set, your cookies will be signed, but
18
+ # not encrypted. This means a user cannot alter their +user_id+ without
19
+ # knowing your app's secret key, but can easily read their +user_id+. This
20
+ # was the default for Rails 3 apps.
22
21
  #
23
- # Session options:
22
+ # If you have secret_key_base set, your cookies will be encrypted. This
23
+ # goes a step further than signed cookies in that encrypted cookies cannot
24
+ # be altered or read by users. This is the default starting in Rails 4.
24
25
  #
25
- # * <tt>:secret</tt>: An application-wide key string. It's important that
26
- # the secret is not vulnerable to a dictionary attack. Therefore, you
27
- # should choose a secret consisting of random numbers and letters and
28
- # more than 30 characters.
26
+ # If you have both secret_token and secret_key base set, your cookies will
27
+ # be encrypted, and signed cookies generated by Rails 3 will be
28
+ # transparently read and encrypted to provide a smooth upgrade path.
29
29
  #
30
- # :secret => '449fe2e7daee471bffae2fd8dc02313d'
30
+ # Configure your session store in config/initializers/session_store.rb:
31
31
  #
32
- # * <tt>:digest</tt>: The message digest algorithm used to verify session
33
- # integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
34
- # such as 'MD5', 'RIPEMD160', 'SHA256', etc.
32
+ # Rails.application.config.session_store :cookie_store, key: '_your_app_session'
35
33
  #
36
- # To generate a secret key for an existing application, run
37
- # "rake secret" and set the key in config/initializers/secret_token.rb.
34
+ # Configure your secret key in config/secrets.yml:
38
35
  #
39
- # Note that changing digest or secret invalidates all existing sessions!
40
- class CookieStore < Rack::Session::Cookie
36
+ # development:
37
+ # secret_key_base: 'secret key'
38
+ #
39
+ # To generate a secret key for an existing application, run `rake secret`.
40
+ #
41
+ # If you are upgrading an existing Rails 3 app, you should leave your
42
+ # existing secret_token in place and simply add the new secret_key_base.
43
+ # Note that you should wait to set secret_key_base until you have 100% of
44
+ # your userbase on Rails 4 and are reasonably sure you will not need to
45
+ # rollback to Rails 3. This is because cookies signed based on the new
46
+ # secret_key_base in Rails 4 are not backwards compatible with Rails 3.
47
+ # You are free to leave your existing secret_token in place, not set the
48
+ # new secret_key_base, and ignore the deprecation warnings until you are
49
+ # reasonably sure that your upgrade is otherwise complete. Additionally,
50
+ # you should take care to make sure you are not relying on the ability to
51
+ # decode signed cookies generated by your app in external applications or
52
+ # JavaScript before upgrading.
53
+ #
54
+ # Note that changing the secret key will invalidate all existing sessions!
55
+ class CookieStore < Rack::Session::Abstract::ID
41
56
  include Compatibility
42
57
  include StaleSessionCheck
58
+ include SessionObject
59
+
60
+ def initialize(app, options={})
61
+ super(app, options.merge!(:cookie_only => true))
62
+ end
63
+
64
+ def destroy_session(env, session_id, options)
65
+ new_sid = generate_sid unless options[:drop]
66
+ # Reset hash and Assign the new session id
67
+ env["action_dispatch.request.unsigned_session_cookie"] = new_sid ? { "session_id" => new_sid } : {}
68
+ new_sid
69
+ end
70
+
71
+ def load_session(env)
72
+ stale_session_check! do
73
+ data = unpacked_cookie_data(env)
74
+ data = persistent_session_id!(data)
75
+ [data["session_id"], data]
76
+ end
77
+ end
43
78
 
44
79
  private
45
80
 
81
+ def extract_session_id(env)
82
+ stale_session_check! do
83
+ unpacked_cookie_data(env)["session_id"]
84
+ end
85
+ end
86
+
46
87
  def unpacked_cookie_data(env)
47
88
  env["action_dispatch.request.unsigned_session_cookie"] ||= begin
48
89
  stale_session_check! do
49
- request = ActionDispatch::Request.new(env)
50
- if data = request.cookie_jar.signed[@key]
90
+ if data = get_cookie(env)
51
91
  data.stringify_keys!
52
92
  end
53
93
  data || {}
@@ -55,13 +95,28 @@ module ActionDispatch
55
95
  end
56
96
  end
57
97
 
98
+ def persistent_session_id!(data, sid=nil)
99
+ data ||= {}
100
+ data["session_id"] ||= sid || generate_sid
101
+ data
102
+ end
103
+
58
104
  def set_session(env, sid, session_data, options)
59
- session_data.merge!("session_id" => sid)
105
+ session_data["session_id"] = sid
106
+ session_data
60
107
  end
61
108
 
62
109
  def set_cookie(env, session_id, cookie)
110
+ cookie_jar(env)[@key] = cookie
111
+ end
112
+
113
+ def get_cookie(env)
114
+ cookie_jar(env)[@key]
115
+ end
116
+
117
+ def cookie_jar(env)
63
118
  request = ActionDispatch::Request.new(env)
64
- request.cookie_jar.signed[@key] = cookie
119
+ request.cookie_jar.signed_or_encrypted
65
120
  end
66
121
  end
67
122
  end
@@ -1,14 +1,19 @@
1
1
  require 'action_dispatch/middleware/session/abstract_store'
2
- require 'rack/session/memcache'
2
+ begin
3
+ require 'rack/session/dalli'
4
+ rescue LoadError => e
5
+ $stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
6
+ raise e
7
+ end
3
8
 
4
9
  module ActionDispatch
5
10
  module Session
6
- class MemCacheStore < Rack::Session::Memcache
11
+ class MemCacheStore < Rack::Session::Dalli
7
12
  include Compatibility
8
13
  include StaleSessionCheck
14
+ include SessionObject
9
15
 
10
16
  def initialize(app, options = {})
11
- require 'memcache'
12
17
  options[:expire_after] ||= options[:expires]
13
18
  super
14
19
  end