actionpack 3.2.22.5 → 4.0.0.beta1

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 (265) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +641 -418
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -288
  5. data/lib/abstract_controller.rb +1 -8
  6. data/lib/abstract_controller/asset_paths.rb +2 -2
  7. data/lib/abstract_controller/base.rb +39 -37
  8. data/lib/abstract_controller/callbacks.rb +101 -82
  9. data/lib/abstract_controller/collector.rb +7 -3
  10. data/lib/abstract_controller/helpers.rb +23 -11
  11. data/lib/abstract_controller/layouts.rb +68 -73
  12. data/lib/abstract_controller/logger.rb +1 -2
  13. data/lib/abstract_controller/rendering.rb +22 -13
  14. data/lib/abstract_controller/translation.rb +16 -1
  15. data/lib/abstract_controller/url_for.rb +6 -6
  16. data/lib/abstract_controller/view_paths.rb +1 -1
  17. data/lib/action_controller.rb +15 -6
  18. data/lib/action_controller/base.rb +46 -22
  19. data/lib/action_controller/caching.rb +46 -33
  20. data/lib/action_controller/caching/fragments.rb +23 -53
  21. data/lib/action_controller/deprecated.rb +5 -1
  22. data/lib/action_controller/deprecated/integration_test.rb +3 -0
  23. data/lib/action_controller/log_subscriber.rb +11 -8
  24. data/lib/action_controller/metal.rb +16 -30
  25. data/lib/action_controller/metal/conditional_get.rb +76 -32
  26. data/lib/action_controller/metal/data_streaming.rb +20 -26
  27. data/lib/action_controller/metal/exceptions.rb +19 -6
  28. data/lib/action_controller/metal/flash.rb +24 -9
  29. data/lib/action_controller/metal/force_ssl.rb +32 -9
  30. data/lib/action_controller/metal/head.rb +25 -4
  31. data/lib/action_controller/metal/helpers.rb +6 -9
  32. data/lib/action_controller/metal/hide_actions.rb +1 -2
  33. data/lib/action_controller/metal/http_authentication.rb +105 -87
  34. data/lib/action_controller/metal/implicit_render.rb +1 -1
  35. data/lib/action_controller/metal/instrumentation.rb +2 -1
  36. data/lib/action_controller/metal/live.rb +141 -0
  37. data/lib/action_controller/metal/mime_responds.rb +161 -47
  38. data/lib/action_controller/metal/params_wrapper.rb +112 -74
  39. data/lib/action_controller/metal/rack_delegation.rb +9 -3
  40. data/lib/action_controller/metal/redirecting.rb +15 -20
  41. data/lib/action_controller/metal/renderers.rb +11 -9
  42. data/lib/action_controller/metal/rendering.rb +8 -0
  43. data/lib/action_controller/metal/request_forgery_protection.rb +112 -19
  44. data/lib/action_controller/metal/responder.rb +20 -19
  45. data/lib/action_controller/metal/streaming.rb +12 -18
  46. data/lib/action_controller/metal/strong_parameters.rb +516 -0
  47. data/lib/action_controller/metal/testing.rb +13 -18
  48. data/lib/action_controller/metal/url_for.rb +27 -25
  49. data/lib/action_controller/model_naming.rb +12 -0
  50. data/lib/action_controller/railtie.rb +33 -17
  51. data/lib/action_controller/railties/helpers.rb +22 -0
  52. data/lib/action_controller/record_identifier.rb +18 -72
  53. data/lib/action_controller/test_case.rb +215 -123
  54. data/lib/action_controller/vendor/html-scanner.rb +4 -19
  55. data/lib/action_dispatch.rb +27 -19
  56. data/lib/action_dispatch/http/cache.rb +63 -11
  57. data/lib/action_dispatch/http/filter_parameters.rb +18 -8
  58. data/lib/action_dispatch/http/filter_redirect.rb +37 -0
  59. data/lib/action_dispatch/http/headers.rb +27 -19
  60. data/lib/action_dispatch/http/mime_negotiation.rb +25 -2
  61. data/lib/action_dispatch/http/mime_type.rb +145 -113
  62. data/lib/action_dispatch/http/mime_types.rb +1 -1
  63. data/lib/action_dispatch/http/parameter_filter.rb +44 -46
  64. data/lib/action_dispatch/http/parameters.rb +12 -5
  65. data/lib/action_dispatch/http/rack_cache.rb +2 -3
  66. data/lib/action_dispatch/http/request.rb +49 -18
  67. data/lib/action_dispatch/http/response.rb +129 -35
  68. data/lib/action_dispatch/http/upload.rb +60 -17
  69. data/lib/action_dispatch/http/url.rb +53 -31
  70. data/lib/action_dispatch/journey.rb +5 -0
  71. data/lib/action_dispatch/journey/backwards.rb +5 -0
  72. data/lib/action_dispatch/journey/formatter.rb +146 -0
  73. data/lib/action_dispatch/journey/gtg/builder.rb +162 -0
  74. data/lib/action_dispatch/journey/gtg/simulator.rb +44 -0
  75. data/lib/action_dispatch/journey/gtg/transition_table.rb +156 -0
  76. data/lib/action_dispatch/journey/nfa/builder.rb +76 -0
  77. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  78. data/lib/action_dispatch/journey/nfa/simulator.rb +47 -0
  79. data/lib/action_dispatch/journey/nfa/transition_table.rb +163 -0
  80. data/lib/action_dispatch/journey/nodes/node.rb +124 -0
  81. data/lib/action_dispatch/journey/parser.rb +206 -0
  82. data/lib/action_dispatch/journey/parser.y +47 -0
  83. data/lib/action_dispatch/journey/parser_extras.rb +23 -0
  84. data/lib/action_dispatch/journey/path/pattern.rb +196 -0
  85. data/lib/action_dispatch/journey/route.rb +116 -0
  86. data/lib/action_dispatch/journey/router.rb +164 -0
  87. data/lib/action_dispatch/journey/router/strexp.rb +24 -0
  88. data/lib/action_dispatch/journey/router/utils.rb +54 -0
  89. data/lib/action_dispatch/journey/routes.rb +75 -0
  90. data/lib/action_dispatch/journey/scanner.rb +61 -0
  91. data/lib/action_dispatch/journey/visitors.rb +189 -0
  92. data/lib/action_dispatch/journey/visualizer/fsm.css +34 -0
  93. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  94. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  95. data/lib/action_dispatch/middleware/callbacks.rb +9 -4
  96. data/lib/action_dispatch/middleware/cookies.rb +168 -57
  97. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -17
  98. data/lib/action_dispatch/middleware/exception_wrapper.rb +27 -3
  99. data/lib/action_dispatch/middleware/flash.rb +58 -58
  100. data/lib/action_dispatch/middleware/params_parser.rb +14 -29
  101. data/lib/action_dispatch/middleware/public_exceptions.rb +31 -14
  102. data/lib/action_dispatch/middleware/reloader.rb +6 -6
  103. data/lib/action_dispatch/middleware/remote_ip.rb +145 -39
  104. data/lib/action_dispatch/middleware/request_id.rb +2 -6
  105. data/lib/action_dispatch/middleware/session/abstract_store.rb +22 -20
  106. data/lib/action_dispatch/middleware/session/cache_store.rb +3 -3
  107. data/lib/action_dispatch/middleware/session/cookie_store.rb +81 -7
  108. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -3
  109. data/lib/action_dispatch/middleware/show_exceptions.rb +12 -45
  110. data/lib/action_dispatch/middleware/ssl.rb +70 -0
  111. data/lib/action_dispatch/middleware/stack.rb +6 -1
  112. data/lib/action_dispatch/middleware/static.rb +5 -24
  113. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +14 -11
  114. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +25 -0
  115. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +3 -3
  116. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +15 -9
  117. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +121 -5
  118. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +7 -2
  119. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +30 -15
  120. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +39 -13
  121. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +6 -2
  122. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  123. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +144 -0
  124. data/lib/action_dispatch/railtie.rb +16 -6
  125. data/lib/action_dispatch/request/session.rb +181 -0
  126. data/lib/action_dispatch/routing.rb +41 -40
  127. data/lib/action_dispatch/routing/inspector.rb +240 -0
  128. data/lib/action_dispatch/routing/mapper.rb +501 -273
  129. data/lib/action_dispatch/routing/polymorphic_routes.rb +16 -20
  130. data/lib/action_dispatch/routing/redirection.rb +46 -29
  131. data/lib/action_dispatch/routing/route_set.rb +203 -164
  132. data/lib/action_dispatch/routing/routes_proxy.rb +2 -0
  133. data/lib/action_dispatch/routing/url_for.rb +48 -33
  134. data/lib/action_dispatch/testing/assertions/dom.rb +3 -13
  135. data/lib/action_dispatch/testing/assertions/response.rb +32 -40
  136. data/lib/action_dispatch/testing/assertions/routing.rb +40 -39
  137. data/lib/action_dispatch/testing/assertions/selector.rb +15 -20
  138. data/lib/action_dispatch/testing/assertions/tag.rb +20 -23
  139. data/lib/action_dispatch/testing/integration.rb +41 -22
  140. data/lib/action_dispatch/testing/test_process.rb +9 -6
  141. data/lib/action_dispatch/testing/test_request.rb +7 -3
  142. data/lib/action_pack.rb +1 -1
  143. data/lib/action_pack/version.rb +4 -4
  144. data/lib/action_view.rb +17 -8
  145. data/lib/action_view/base.rb +15 -34
  146. data/lib/action_view/buffers.rb +1 -1
  147. data/lib/action_view/context.rb +4 -4
  148. data/lib/action_view/dependency_tracker.rb +91 -0
  149. data/lib/action_view/digestor.rb +85 -0
  150. data/lib/action_view/flows.rb +1 -4
  151. data/lib/action_view/helpers.rb +2 -4
  152. data/lib/action_view/helpers/active_model_helper.rb +3 -4
  153. data/lib/action_view/helpers/asset_tag_helper.rb +211 -353
  154. data/lib/action_view/helpers/asset_url_helper.rb +354 -0
  155. data/lib/action_view/helpers/atom_feed_helper.rb +13 -10
  156. data/lib/action_view/helpers/cache_helper.rb +150 -18
  157. data/lib/action_view/helpers/capture_helper.rb +42 -29
  158. data/lib/action_view/helpers/csrf_helper.rb +0 -2
  159. data/lib/action_view/helpers/date_helper.rb +268 -247
  160. data/lib/action_view/helpers/debug_helper.rb +10 -11
  161. data/lib/action_view/helpers/form_helper.rb +904 -547
  162. data/lib/action_view/helpers/form_options_helper.rb +341 -166
  163. data/lib/action_view/helpers/form_tag_helper.rb +188 -88
  164. data/lib/action_view/helpers/javascript_helper.rb +23 -16
  165. data/lib/action_view/helpers/number_helper.rb +148 -354
  166. data/lib/action_view/helpers/output_safety_helper.rb +3 -3
  167. data/lib/action_view/helpers/record_tag_helper.rb +17 -22
  168. data/lib/action_view/helpers/rendering_helper.rb +2 -4
  169. data/lib/action_view/helpers/sanitize_helper.rb +3 -6
  170. data/lib/action_view/helpers/tag_helper.rb +43 -37
  171. data/lib/action_view/helpers/tags.rb +39 -0
  172. data/lib/action_view/helpers/tags/base.rb +148 -0
  173. data/lib/action_view/helpers/tags/check_box.rb +64 -0
  174. data/lib/action_view/helpers/tags/checkable.rb +16 -0
  175. data/lib/action_view/helpers/tags/collection_check_boxes.rb +43 -0
  176. data/lib/action_view/helpers/tags/collection_helpers.rb +83 -0
  177. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +36 -0
  178. data/lib/action_view/helpers/tags/collection_select.rb +28 -0
  179. data/lib/action_view/helpers/tags/color_field.rb +25 -0
  180. data/lib/action_view/helpers/tags/date_field.rb +13 -0
  181. data/lib/action_view/helpers/tags/date_select.rb +72 -0
  182. data/lib/action_view/helpers/tags/datetime_field.rb +22 -0
  183. data/lib/action_view/helpers/tags/datetime_local_field.rb +19 -0
  184. data/lib/action_view/helpers/tags/datetime_select.rb +8 -0
  185. data/lib/action_view/helpers/tags/email_field.rb +8 -0
  186. data/lib/action_view/helpers/tags/file_field.rb +8 -0
  187. data/lib/action_view/helpers/tags/grouped_collection_select.rb +29 -0
  188. data/lib/action_view/helpers/tags/hidden_field.rb +8 -0
  189. data/lib/action_view/helpers/tags/label.rb +65 -0
  190. data/lib/action_view/helpers/tags/month_field.rb +13 -0
  191. data/lib/action_view/helpers/tags/number_field.rb +18 -0
  192. data/lib/action_view/helpers/tags/password_field.rb +12 -0
  193. data/lib/action_view/helpers/tags/radio_button.rb +31 -0
  194. data/lib/action_view/helpers/tags/range_field.rb +8 -0
  195. data/lib/action_view/helpers/tags/search_field.rb +24 -0
  196. data/lib/action_view/helpers/tags/select.rb +41 -0
  197. data/lib/action_view/helpers/tags/tel_field.rb +8 -0
  198. data/lib/action_view/helpers/tags/text_area.rb +18 -0
  199. data/lib/action_view/helpers/tags/text_field.rb +29 -0
  200. data/lib/action_view/helpers/tags/time_field.rb +13 -0
  201. data/lib/action_view/helpers/tags/time_select.rb +8 -0
  202. data/lib/action_view/helpers/tags/time_zone_select.rb +20 -0
  203. data/lib/action_view/helpers/tags/url_field.rb +8 -0
  204. data/lib/action_view/helpers/tags/week_field.rb +13 -0
  205. data/lib/action_view/helpers/text_helper.rb +126 -113
  206. data/lib/action_view/helpers/translation_helper.rb +32 -16
  207. data/lib/action_view/helpers/url_helper.rb +200 -271
  208. data/lib/action_view/locale/en.yml +1 -105
  209. data/lib/action_view/log_subscriber.rb +6 -4
  210. data/lib/action_view/lookup_context.rb +15 -39
  211. data/lib/action_view/model_naming.rb +12 -0
  212. data/lib/action_view/path_set.rb +9 -39
  213. data/lib/action_view/railtie.rb +6 -22
  214. data/lib/action_view/record_identifier.rb +84 -0
  215. data/lib/action_view/renderer/abstract_renderer.rb +10 -19
  216. data/lib/action_view/renderer/partial_renderer.rb +144 -81
  217. data/lib/action_view/renderer/renderer.rb +2 -19
  218. data/lib/action_view/renderer/streaming_template_renderer.rb +2 -5
  219. data/lib/action_view/renderer/template_renderer.rb +14 -13
  220. data/lib/action_view/routing_url_for.rb +107 -0
  221. data/lib/action_view/template.rb +22 -21
  222. data/lib/action_view/template/error.rb +22 -12
  223. data/lib/action_view/template/handlers.rb +12 -9
  224. data/lib/action_view/template/handlers/builder.rb +1 -1
  225. data/lib/action_view/template/handlers/erb.rb +11 -16
  226. data/lib/action_view/template/handlers/raw.rb +11 -0
  227. data/lib/action_view/template/resolver.rb +111 -83
  228. data/lib/action_view/template/text.rb +12 -8
  229. data/lib/action_view/template/types.rb +57 -0
  230. data/lib/action_view/test_case.rb +66 -43
  231. data/lib/action_view/testing/resolvers.rb +3 -2
  232. data/lib/action_view/vendor/html-scanner.rb +20 -0
  233. data/lib/{action_controller → action_view}/vendor/html-scanner/html/document.rb +0 -0
  234. data/lib/{action_controller → action_view}/vendor/html-scanner/html/node.rb +12 -12
  235. data/lib/{action_controller → action_view}/vendor/html-scanner/html/sanitizer.rb +18 -7
  236. data/lib/{action_controller → action_view}/vendor/html-scanner/html/selector.rb +1 -1
  237. data/lib/{action_controller → action_view}/vendor/html-scanner/html/tokenizer.rb +1 -1
  238. data/lib/{action_controller → action_view}/vendor/html-scanner/html/version.rb +0 -0
  239. metadata +135 -125
  240. data/lib/action_controller/caching/actions.rb +0 -185
  241. data/lib/action_controller/caching/pages.rb +0 -187
  242. data/lib/action_controller/caching/sweeping.rb +0 -97
  243. data/lib/action_controller/deprecated/performance_test.rb +0 -1
  244. data/lib/action_controller/metal/compatibility.rb +0 -65
  245. data/lib/action_controller/metal/session_management.rb +0 -14
  246. data/lib/action_controller/railties/paths.rb +0 -25
  247. data/lib/action_dispatch/middleware/best_standards_support.rb +0 -30
  248. data/lib/action_dispatch/middleware/body_proxy.rb +0 -30
  249. data/lib/action_dispatch/middleware/head.rb +0 -18
  250. data/lib/action_dispatch/middleware/rescue.rb +0 -26
  251. data/lib/action_dispatch/testing/performance_test.rb +0 -10
  252. data/lib/action_view/asset_paths.rb +0 -142
  253. data/lib/action_view/helpers/asset_paths.rb +0 -7
  254. data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +0 -146
  255. data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +0 -93
  256. data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +0 -193
  257. data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +0 -148
  258. data/lib/sprockets/assets.rake +0 -99
  259. data/lib/sprockets/bootstrap.rb +0 -37
  260. data/lib/sprockets/compressors.rb +0 -83
  261. data/lib/sprockets/helpers.rb +0 -6
  262. data/lib/sprockets/helpers/isolated_helper.rb +0 -13
  263. data/lib/sprockets/helpers/rails_helper.rb +0 -182
  264. data/lib/sprockets/railtie.rb +0 -62
  265. data/lib/sprockets/static_compiler.rb +0 -56
@@ -1,5 +1,4 @@
1
1
  module ActionDispatch
2
- # A simple Rack application that renders exceptions in the given public path.
3
2
  class PublicExceptions
4
3
  attr_accessor :public_path
5
4
 
@@ -8,23 +7,41 @@ module ActionDispatch
8
7
  end
9
8
 
10
9
  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))
10
+ exception = env["action_dispatch.exception"]
11
+ status = env["PATH_INFO"][1..-1]
12
+ request = ActionDispatch::Request.new(env)
13
+ content_type = request.formats.first
14
+ body = { :status => status, :error => exception.message }
15
+
16
+ render(status, content_type, body)
17
+ end
18
+
19
+ private
20
+
21
+ def render(status, content_type, body)
22
+ format = content_type && "to_#{content_type.to_sym}"
23
+ if format && body.respond_to?(format)
24
+ render_format(status, content_type, body.public_send(format))
19
25
  else
20
- [404, { "X-Cascade" => "pass" }, []]
26
+ render_html(status)
21
27
  end
22
28
  end
23
29
 
24
- private
30
+ def render_format(status, content_type, body)
31
+ [status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
32
+ 'Content-Length' => body.bytesize.to_s}, [body]]
33
+ end
34
+
35
+ def render_html(status)
36
+ found = false
37
+ path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
38
+ path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path))
25
39
 
26
- def render(status, body)
27
- [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
40
+ if found || File.exist?(path)
41
+ render_format(status, 'text/html', File.read(path))
42
+ else
43
+ [404, { "X-Cascade" => "pass" }, []]
44
+ end
28
45
  end
29
46
  end
30
- end
47
+ end
@@ -1,5 +1,3 @@
1
- require 'action_dispatch/middleware/body_proxy'
2
-
3
1
  module ActionDispatch
4
2
  # ActionDispatch::Reloader provides prepare and cleanup callbacks,
5
3
  # intended to assist with code reloading during development.
@@ -20,10 +18,10 @@ module ActionDispatch
20
18
  # classes before they are unloaded.
21
19
  #
22
20
  # By default, ActionDispatch::Reloader is included in the middleware stack
23
- # only in the development environment; specifically, when config.cache_classes
21
+ # only in the development environment; specifically, when +config.cache_classes+
24
22
  # 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.
23
+ # middleware stack, but are executed only when <tt>ActionDispatch::Reloader.prepare!</tt>
24
+ # or <tt>ActionDispatch::Reloader.cleanup!</tt> are called manually.
27
25
  #
28
26
  class Reloader
29
27
  include ActiveSupport::Callbacks
@@ -62,8 +60,10 @@ module ActionDispatch
62
60
  def call(env)
63
61
  @validated = @condition.call
64
62
  prepare!
63
+
65
64
  response = @app.call(env)
66
- response[2] = ActionDispatch::BodyProxy.new(response[2]) { cleanup! }
65
+ response[2] = ::Rack::BodyProxy.new(response[2]) { cleanup! }
66
+
67
67
  response
68
68
  rescue Exception
69
69
  cleanup!
@@ -1,80 +1,186 @@
1
1
  module ActionDispatch
2
+ # This middleware calculates the IP address of the remote client that is
3
+ # making the request. It does this by checking various headers that could
4
+ # contain the address, and then picking the last-set address that is not
5
+ # on the list of trusted IPs. This follows the precedent set by e.g.
6
+ # {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453],
7
+ # with {reasoning explained at length}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection]
8
+ # by @gingerlime. A more detailed explanation of the algorithm is given
9
+ # at GetIp#calculate_ip.
10
+ #
11
+ # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
12
+ # requires. Some Rack servers simply drop preceeding headers, and only report
13
+ # the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers].
14
+ # If you are behind multiple proxy servers (like Nginx to HAProxy to Unicorn)
15
+ # then you should test your Rack server to make sure your data is good.
16
+ #
17
+ # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING.
18
+ # This middleware assumes that there is at least one proxy sitting around
19
+ # and setting headers with the client's remote IP address. If you don't use
20
+ # a proxy, because you are hosted on e.g. Heroku without SSL, any client can
21
+ # claim to have any IP address by setting the X-Forwarded-For header. If you
22
+ # care about that, then you need to explicitly drop or ignore those headers
23
+ # sometime before this middleware runs.
2
24
  class RemoteIp
3
- class IpSpoofAttackError < StandardError ; end
25
+ class IpSpoofAttackError < StandardError; end
4
26
 
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
27
+ # The default trusted IPs list simply includes IP addresses that are
28
+ # guaranteed by the IP specification to be private addresses. Those will
29
+ # not be the ultimate client IP in production, and so are discarded. See
30
+ # http://en.wikipedia.org/wiki/Private_network for details.
8
31
  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
- )\.
32
+ ^127\.0\.0\.1$ | # localhost IPv4
33
+ ^::1$ | # localhost IPv6
34
+ ^fc00: | # private IPv6 range fc00
35
+ ^10\. | # private IPv4 range 10.x.x.x
36
+ ^172\.(1[6-9]|2[0-9]|3[0-1])\.| # private IPv4 range 172.16.0.0 .. 172.31.255.255
37
+ ^192\.168\. # private IPv4 range 192.168.x.x
14
38
  }x
15
39
 
16
40
  attr_reader :check_ip, :proxies
17
41
 
42
+ # Create a new +RemoteIp+ middleware instance.
43
+ #
44
+ # The +check_ip_spoofing+ option is on by default. When on, an exception
45
+ # is raised if it looks like the client is trying to lie about its own IP
46
+ # address. It makes sense to turn off this check on sites aimed at non-IP
47
+ # clients (like WAP devices), or behind proxies that set headers in an
48
+ # incorrect or confusing way (like AWS ELB).
49
+ #
50
+ # The +custom_trusted+ argument can take a regex, which will be used
51
+ # instead of +TRUSTED_PROXIES+, or a string, which will be used in addition
52
+ # to +TRUSTED_PROXIES+. Any proxy setup will put the value you want in the
53
+ # middle (or at the beginning) of the X-Forwarded-For list, with your proxy
54
+ # servers after it. If your proxies aren't removed, pass them in via the
55
+ # +custom_trusted+ parameter. That way, the middleware will ignore those
56
+ # IP addresses, and return the one that you want.
18
57
  def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
19
58
  @app = app
20
59
  @check_ip = check_ip_spoofing
21
- if custom_proxies
22
- custom_regexp = Regexp.new(custom_proxies)
23
- @proxies = Regexp.union(TRUSTED_PROXIES, custom_regexp)
24
- else
25
- @proxies = TRUSTED_PROXIES
26
- end
60
+ @proxies = case custom_proxies
61
+ when Regexp
62
+ custom_proxies
63
+ when nil
64
+ TRUSTED_PROXIES
65
+ else
66
+ Regexp.union(TRUSTED_PROXIES, custom_proxies)
67
+ end
27
68
  end
28
69
 
70
+ # Since the IP address may not be needed, we store the object here
71
+ # without calculating the IP to keep from slowing down the majority of
72
+ # requests. For those requests that do need to know the IP, the
73
+ # GetIp#calculate_ip method will calculate the memoized client IP address.
29
74
  def call(env)
30
75
  env["action_dispatch.remote_ip"] = GetIp.new(env, self)
31
76
  @app.call(env)
32
77
  end
33
78
 
79
+ # The GetIp class exists as a way to defer processing of the request data
80
+ # into an actual IP address. If the ActionDispatch::Request#remote_ip method
81
+ # is called, this class will calculate the value and then memoize it.
34
82
  class GetIp
83
+
84
+ # This constant contains a regular expression that validates every known
85
+ # form of IP v4 and v6 address, with or without abbreviations, adapted
86
+ # from {this gist}[https://gist.github.com/gazay/1289635].
87
+ VALID_IP = %r{
88
+ (^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})){3}$) | # ip v4
89
+ (^(
90
+ (([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}) | # ip v6 not abbreviated
91
+ (([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4}) | # ip v6 with double colon in the end
92
+ (([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4}) | # - ip addresses v6
93
+ (([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4}) | # - with
94
+ (([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4}) | # - double colon
95
+ (([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4}) | # - in the middle
96
+ (([0-9A-Fa-f]{1,4}:){6} ((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3} (\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
97
+ (([0-9A-Fa-f]{1,4}:){1,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
98
+ (([0-9A-Fa-f]{1,4}:){1}:([0-9A-Fa-f]{1,4}:){0,4}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
99
+ (([0-9A-Fa-f]{1,4}:){0,2}:([0-9A-Fa-f]{1,4}:){0,3}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
100
+ (([0-9A-Fa-f]{1,4}:){0,3}:([0-9A-Fa-f]{1,4}:){0,2}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
101
+ (([0-9A-Fa-f]{1,4}:){0,4}:([0-9A-Fa-f]{1,4}:){1}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
102
+ (::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d) |(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
103
+ ([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4}) | # ip v6 with compatible to v4
104
+ (::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the begining
105
+ (([0-9A-Fa-f]{1,4}:){1,7}:) # ip v6 without ending
106
+ )$)
107
+ }x
108
+
35
109
  def initialize(env, middleware)
36
- @env = env
37
- @middleware = middleware
38
- @calculated_ip = false
110
+ @env = env
111
+ @check_ip = middleware.check_ip
112
+ @proxies = middleware.proxies
39
113
  end
40
114
 
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.
115
+ # Sort through the various IP address headers, looking for the IP most
116
+ # likely to be the address of the actual remote client making this
117
+ # request.
118
+ #
119
+ # REMOTE_ADDR will be correct if the request is made directly against the
120
+ # Ruby process, on e.g. Heroku. When the request is proxied by another
121
+ # server like HAProxy or Nginx, the IP address that made the original
122
+ # request will be put in an X-Forwarded-For header. If there are multiple
123
+ # proxies, that header may contain a list of IPs. Other proxy services
124
+ # set the Client-Ip header instead, so we check that too.
125
+ #
126
+ # As discussed in {this post about Rails IP Spoofing}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
127
+ # while the first IP in the list is likely to be the "originating" IP,
128
+ # it could also have been set by the client maliciously.
129
+ #
130
+ # In order to find the first address that is (probably) accurate, we
131
+ # take the list of IPs, remove known and trusted proxies, and then take
132
+ # the last address left, which was presumably set by one of those proxies.
47
133
  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')
134
+ # Set by the Rack web server, this is a single value.
135
+ remote_addr = ips_from('REMOTE_ADDR').last
51
136
 
52
- check_ip = client_ip && forwarded_ips.present? && @middleware.check_ip
53
- if check_ip && !forwarded_ips.include?(client_ip)
137
+ # Could be a CSV list and/or repeated headers that were concatenated.
138
+ client_ips = ips_from('HTTP_CLIENT_IP').reverse
139
+ forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR').reverse
140
+
141
+ # +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set.
142
+ # If they are both set, it means that this request passed through two
143
+ # proxies with incompatible IP header conventions, and there is no way
144
+ # for us to determine which header is the right one after the fact.
145
+ # Since we have no idea, we give up and explode.
146
+ should_check_ip = @check_ip && client_ips.last
147
+ if should_check_ip && !forwarded_ips.include?(client_ips.last)
54
148
  # 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}" \
149
+ raise IpSpoofAttackError, "IP spoofing attack?! " +
150
+ "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect} " +
57
151
  "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
58
152
  end
59
153
 
60
- not_proxy = client_ip || forwarded_ips.last || remote_addrs.first
154
+ # We assume these things about the IP headers:
155
+ #
156
+ # - X-Forwarded-For will be a list of IPs, one per proxy, or blank
157
+ # - Client-Ip is propagated from the outermost proxy, or is blank
158
+ # - REMOTE_ADDR will be the IP that made the request to Rack
159
+ ips = [forwarded_ips, client_ips, remote_addr].flatten.compact
61
160
 
62
- # Return first REMOTE_ADDR if there are no other options
63
- not_proxy || ips_from('REMOTE_ADDR', :allow_proxies).first
161
+ # If every single IP option is in the trusted list, just return REMOTE_ADDR
162
+ filter_proxies(ips).first || remote_addr
64
163
  end
65
164
 
165
+ # Memoizes the value returned by #calculate_ip and returns it for
166
+ # ActionDispatch::Request to use.
66
167
  def to_s
67
- return @ip if @calculated_ip
68
- @calculated_ip = true
69
- @ip = calculate_ip
168
+ @ip ||= calculate_ip
70
169
  end
71
170
 
72
171
  protected
73
172
 
74
- def ips_from(header, allow_proxies = false)
173
+ def ips_from(header)
174
+ # Split the comma-separated list into an array of strings
75
175
  ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
76
- allow_proxies ? ips : ips.reject{|ip| ip =~ @middleware.proxies }
176
+ # Only return IPs that are valid according to the regex
177
+ ips.select{ |ip| ip =~ VALID_IP }
178
+ end
179
+
180
+ def filter_proxies(ips)
181
+ ips.reject { |ip| ip =~ @proxies }
77
182
  end
183
+
78
184
  end
79
185
 
80
186
  end
@@ -1,6 +1,5 @@
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
@@ -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
- unless sid and session = @cache.read(cache_key(sid))
20
- sid, session = generate_sid, {}
21
- end
19
+ sid ||= generate_sid
20
+ session = @cache.read(cache_key(sid))
21
+ session ||= {}
22
22
  [sid, session]
23
23
  end
24
24
 
@@ -1,5 +1,4 @@
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
 
@@ -27,7 +26,7 @@ module ActionDispatch
27
26
  # should choose a secret consisting of random numbers and letters and
28
27
  # more than 30 characters.
29
28
  #
30
- # :secret => '449fe2e7daee471bffae2fd8dc02313d'
29
+ # secret: '449fe2e7daee471bffae2fd8dc02313d'
31
30
  #
32
31
  # * <tt>:digest</tt>: The message digest algorithm used to verify session
33
32
  # integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
@@ -37,17 +36,42 @@ module ActionDispatch
37
36
  # "rake secret" and set the key in config/initializers/secret_token.rb.
38
37
  #
39
38
  # Note that changing digest or secret invalidates all existing sessions!
40
- class CookieStore < Rack::Session::Cookie
39
+ class CookieStore < Rack::Session::Abstract::ID
41
40
  include Compatibility
42
41
  include StaleSessionCheck
42
+ include SessionObject
43
+
44
+ def initialize(app, options={})
45
+ super(app, options.merge!(:cookie_only => true))
46
+ end
47
+
48
+ def destroy_session(env, session_id, options)
49
+ new_sid = generate_sid unless options[:drop]
50
+ # Reset hash and Assign the new session id
51
+ env["action_dispatch.request.unsigned_session_cookie"] = new_sid ? { "session_id" => new_sid } : {}
52
+ new_sid
53
+ end
54
+
55
+ def load_session(env)
56
+ stale_session_check! do
57
+ data = unpacked_cookie_data(env)
58
+ data = persistent_session_id!(data)
59
+ [data["session_id"], data]
60
+ end
61
+ end
43
62
 
44
63
  private
45
64
 
65
+ def extract_session_id(env)
66
+ stale_session_check! do
67
+ unpacked_cookie_data(env)["session_id"]
68
+ end
69
+ end
70
+
46
71
  def unpacked_cookie_data(env)
47
72
  env["action_dispatch.request.unsigned_session_cookie"] ||= begin
48
73
  stale_session_check! do
49
- request = ActionDispatch::Request.new(env)
50
- if data = request.cookie_jar.signed[@key]
74
+ if data = get_cookie(env)
51
75
  data.stringify_keys!
52
76
  end
53
77
  data || {}
@@ -55,13 +79,63 @@ module ActionDispatch
55
79
  end
56
80
  end
57
81
 
82
+ def persistent_session_id!(data, sid=nil)
83
+ data ||= {}
84
+ data["session_id"] ||= sid || generate_sid
85
+ data
86
+ end
87
+
58
88
  def set_session(env, sid, session_data, options)
59
- session_data.merge!("session_id" => sid)
89
+ session_data["session_id"] = sid
90
+ session_data
60
91
  end
61
92
 
62
93
  def set_cookie(env, session_id, cookie)
94
+ cookie_jar(env)[@key] = cookie
95
+ end
96
+
97
+ def get_cookie(env)
98
+ cookie_jar(env)[@key]
99
+ end
100
+
101
+ def cookie_jar(env)
102
+ request = ActionDispatch::Request.new(env)
103
+ request.cookie_jar.signed
104
+ end
105
+ end
106
+
107
+ class EncryptedCookieStore < CookieStore
108
+
109
+ private
110
+
111
+ def cookie_jar(env)
112
+ request = ActionDispatch::Request.new(env)
113
+ request.cookie_jar.encrypted
114
+ end
115
+ end
116
+
117
+ # This cookie store helps you upgrading apps that use +CookieStore+ to the new default +EncryptedCookieStore+
118
+ # To use this CookieStore set
119
+ #
120
+ # Myapp::Application.config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session'
121
+ #
122
+ # in your config/initializers/session_store.rb
123
+ #
124
+ # You will also need to add
125
+ #
126
+ # Myapp::Application.config.secret_key_base = 'some secret'
127
+ #
128
+ # in your config/initializers/secret_token.rb, but do not remove +Myapp::Application.config.secret_token = 'some secret'+
129
+ class UpgradeSignatureToEncryptionCookieStore < EncryptedCookieStore
130
+ private
131
+
132
+ def get_cookie(env)
133
+ signed_using_old_secret_cookie_jar(env)[@key] || cookie_jar(env)[@key]
134
+ end
135
+
136
+ def signed_using_old_secret_cookie_jar(env)
63
137
  request = ActionDispatch::Request.new(env)
64
- request.cookie_jar.signed[@key] = cookie
138
+ request.cookie_jar.signed_using_old_secret
65
139
  end
66
140
  end
67
141
  end