actionpack 3.2.19 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +850 -401
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -288
  5. data/lib/abstract_controller/asset_paths.rb +2 -2
  6. data/lib/abstract_controller/base.rb +39 -37
  7. data/lib/abstract_controller/callbacks.rb +101 -82
  8. data/lib/abstract_controller/collector.rb +7 -3
  9. data/lib/abstract_controller/helpers.rb +25 -13
  10. data/lib/abstract_controller/layouts.rb +74 -74
  11. data/lib/abstract_controller/logger.rb +1 -2
  12. data/lib/abstract_controller/rendering.rb +30 -13
  13. data/lib/abstract_controller/translation.rb +16 -1
  14. data/lib/abstract_controller/url_for.rb +6 -6
  15. data/lib/abstract_controller/view_paths.rb +1 -1
  16. data/lib/abstract_controller.rb +1 -8
  17. data/lib/action_controller/base.rb +46 -22
  18. data/lib/action_controller/caching/fragments.rb +23 -53
  19. data/lib/action_controller/caching.rb +46 -33
  20. data/lib/action_controller/deprecated/integration_test.rb +3 -0
  21. data/lib/action_controller/deprecated.rb +5 -1
  22. data/lib/action_controller/log_subscriber.rb +16 -8
  23. data/lib/action_controller/metal/conditional_get.rb +76 -32
  24. data/lib/action_controller/metal/data_streaming.rb +20 -26
  25. data/lib/action_controller/metal/exceptions.rb +19 -6
  26. data/lib/action_controller/metal/flash.rb +24 -9
  27. data/lib/action_controller/metal/force_ssl.rb +70 -12
  28. data/lib/action_controller/metal/head.rb +25 -4
  29. data/lib/action_controller/metal/helpers.rb +5 -9
  30. data/lib/action_controller/metal/hide_actions.rb +0 -1
  31. data/lib/action_controller/metal/http_authentication.rb +107 -83
  32. data/lib/action_controller/metal/implicit_render.rb +1 -1
  33. data/lib/action_controller/metal/instrumentation.rb +2 -1
  34. data/lib/action_controller/metal/live.rb +175 -0
  35. data/lib/action_controller/metal/mime_responds.rb +161 -47
  36. data/lib/action_controller/metal/params_wrapper.rb +112 -74
  37. data/lib/action_controller/metal/rack_delegation.rb +9 -3
  38. data/lib/action_controller/metal/redirecting.rb +15 -20
  39. data/lib/action_controller/metal/renderers.rb +11 -9
  40. data/lib/action_controller/metal/rendering.rb +9 -1
  41. data/lib/action_controller/metal/request_forgery_protection.rb +112 -19
  42. data/lib/action_controller/metal/responder.rb +20 -19
  43. data/lib/action_controller/metal/streaming.rb +12 -18
  44. data/lib/action_controller/metal/strong_parameters.rb +520 -0
  45. data/lib/action_controller/metal/testing.rb +13 -18
  46. data/lib/action_controller/metal/url_for.rb +28 -25
  47. data/lib/action_controller/metal.rb +17 -32
  48. data/lib/action_controller/model_naming.rb +12 -0
  49. data/lib/action_controller/railtie.rb +33 -17
  50. data/lib/action_controller/railties/helpers.rb +22 -0
  51. data/lib/action_controller/record_identifier.rb +18 -72
  52. data/lib/action_controller/test_case.rb +251 -131
  53. data/lib/action_controller/vendor/html-scanner.rb +4 -19
  54. data/lib/action_controller.rb +15 -6
  55. data/lib/action_dispatch/http/cache.rb +63 -11
  56. data/lib/action_dispatch/http/filter_parameters.rb +18 -8
  57. data/lib/action_dispatch/http/filter_redirect.rb +37 -0
  58. data/lib/action_dispatch/http/headers.rb +49 -17
  59. data/lib/action_dispatch/http/mime_negotiation.rb +24 -1
  60. data/lib/action_dispatch/http/mime_type.rb +154 -100
  61. data/lib/action_dispatch/http/mime_types.rb +1 -1
  62. data/lib/action_dispatch/http/parameter_filter.rb +44 -46
  63. data/lib/action_dispatch/http/parameters.rb +28 -28
  64. data/lib/action_dispatch/http/rack_cache.rb +2 -3
  65. data/lib/action_dispatch/http/request.rb +64 -18
  66. data/lib/action_dispatch/http/response.rb +130 -35
  67. data/lib/action_dispatch/http/upload.rb +63 -20
  68. data/lib/action_dispatch/http/url.rb +98 -35
  69. data/lib/action_dispatch/journey/backwards.rb +5 -0
  70. data/lib/action_dispatch/journey/formatter.rb +146 -0
  71. data/lib/action_dispatch/journey/gtg/builder.rb +162 -0
  72. data/lib/action_dispatch/journey/gtg/simulator.rb +44 -0
  73. data/lib/action_dispatch/journey/gtg/transition_table.rb +156 -0
  74. data/lib/action_dispatch/journey/nfa/builder.rb +76 -0
  75. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  76. data/lib/action_dispatch/journey/nfa/simulator.rb +47 -0
  77. data/lib/action_dispatch/journey/nfa/transition_table.rb +163 -0
  78. data/lib/action_dispatch/journey/nodes/node.rb +124 -0
  79. data/lib/action_dispatch/journey/parser.rb +206 -0
  80. data/lib/action_dispatch/journey/parser.y +47 -0
  81. data/lib/action_dispatch/journey/parser_extras.rb +23 -0
  82. data/lib/action_dispatch/journey/path/pattern.rb +196 -0
  83. data/lib/action_dispatch/journey/route.rb +124 -0
  84. data/lib/action_dispatch/journey/router/strexp.rb +24 -0
  85. data/lib/action_dispatch/journey/router/utils.rb +54 -0
  86. data/lib/action_dispatch/journey/router.rb +166 -0
  87. data/lib/action_dispatch/journey/routes.rb +75 -0
  88. data/lib/action_dispatch/journey/scanner.rb +61 -0
  89. data/lib/action_dispatch/journey/visitors.rb +197 -0
  90. data/lib/action_dispatch/journey/visualizer/fsm.css +34 -0
  91. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  92. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  93. data/lib/action_dispatch/journey.rb +5 -0
  94. data/lib/action_dispatch/middleware/callbacks.rb +9 -4
  95. data/lib/action_dispatch/middleware/cookies.rb +259 -114
  96. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -17
  97. data/lib/action_dispatch/middleware/exception_wrapper.rb +29 -3
  98. data/lib/action_dispatch/middleware/flash.rb +58 -58
  99. data/lib/action_dispatch/middleware/params_parser.rb +14 -29
  100. data/lib/action_dispatch/middleware/public_exceptions.rb +30 -14
  101. data/lib/action_dispatch/middleware/reloader.rb +6 -6
  102. data/lib/action_dispatch/middleware/remote_ip.rb +145 -39
  103. data/lib/action_dispatch/middleware/request_id.rb +2 -6
  104. data/lib/action_dispatch/middleware/session/abstract_store.rb +22 -20
  105. data/lib/action_dispatch/middleware/session/cookie_store.rb +82 -28
  106. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -3
  107. data/lib/action_dispatch/middleware/show_exceptions.rb +12 -45
  108. data/lib/action_dispatch/middleware/ssl.rb +70 -0
  109. data/lib/action_dispatch/middleware/stack.rb +6 -1
  110. data/lib/action_dispatch/middleware/static.rb +2 -1
  111. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +14 -11
  112. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +25 -0
  113. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +7 -9
  114. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +15 -9
  115. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +127 -5
  116. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +7 -2
  117. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +30 -15
  118. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +39 -13
  119. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +6 -2
  120. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  121. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +144 -0
  122. data/lib/action_dispatch/railtie.rb +16 -6
  123. data/lib/action_dispatch/request/session.rb +181 -0
  124. data/lib/action_dispatch/routing/inspector.rb +240 -0
  125. data/lib/action_dispatch/routing/mapper.rb +540 -291
  126. data/lib/action_dispatch/routing/polymorphic_routes.rb +16 -20
  127. data/lib/action_dispatch/routing/redirection.rb +46 -29
  128. data/lib/action_dispatch/routing/route_set.rb +207 -164
  129. data/lib/action_dispatch/routing/routes_proxy.rb +2 -0
  130. data/lib/action_dispatch/routing/url_for.rb +48 -33
  131. data/lib/action_dispatch/routing.rb +48 -83
  132. data/lib/action_dispatch/testing/assertions/dom.rb +3 -13
  133. data/lib/action_dispatch/testing/assertions/response.rb +32 -40
  134. data/lib/action_dispatch/testing/assertions/routing.rb +42 -41
  135. data/lib/action_dispatch/testing/assertions/selector.rb +17 -22
  136. data/lib/action_dispatch/testing/assertions/tag.rb +20 -23
  137. data/lib/action_dispatch/testing/integration.rb +65 -51
  138. data/lib/action_dispatch/testing/test_process.rb +9 -6
  139. data/lib/action_dispatch/testing/test_request.rb +7 -3
  140. data/lib/action_dispatch.rb +21 -15
  141. data/lib/action_pack/version.rb +7 -6
  142. data/lib/action_pack.rb +1 -1
  143. data/lib/action_view/base.rb +15 -34
  144. data/lib/action_view/buffers.rb +7 -1
  145. data/lib/action_view/context.rb +4 -4
  146. data/lib/action_view/dependency_tracker.rb +93 -0
  147. data/lib/action_view/digestor.rb +85 -0
  148. data/lib/action_view/flows.rb +1 -4
  149. data/lib/action_view/helpers/active_model_helper.rb +3 -4
  150. data/lib/action_view/helpers/asset_tag_helper.rb +215 -352
  151. data/lib/action_view/helpers/asset_url_helper.rb +355 -0
  152. data/lib/action_view/helpers/atom_feed_helper.rb +13 -10
  153. data/lib/action_view/helpers/cache_helper.rb +150 -18
  154. data/lib/action_view/helpers/capture_helper.rb +44 -31
  155. data/lib/action_view/helpers/csrf_helper.rb +0 -2
  156. data/lib/action_view/helpers/date_helper.rb +269 -248
  157. data/lib/action_view/helpers/debug_helper.rb +10 -11
  158. data/lib/action_view/helpers/form_helper.rb +931 -537
  159. data/lib/action_view/helpers/form_options_helper.rb +341 -166
  160. data/lib/action_view/helpers/form_tag_helper.rb +190 -90
  161. data/lib/action_view/helpers/javascript_helper.rb +23 -16
  162. data/lib/action_view/helpers/number_helper.rb +148 -329
  163. data/lib/action_view/helpers/output_safety_helper.rb +3 -3
  164. data/lib/action_view/helpers/record_tag_helper.rb +17 -22
  165. data/lib/action_view/helpers/rendering_helper.rb +2 -2
  166. data/lib/action_view/helpers/sanitize_helper.rb +3 -6
  167. data/lib/action_view/helpers/tag_helper.rb +46 -33
  168. data/lib/action_view/helpers/tags/base.rb +147 -0
  169. data/lib/action_view/helpers/tags/check_box.rb +64 -0
  170. data/lib/action_view/helpers/tags/checkable.rb +16 -0
  171. data/lib/action_view/helpers/tags/collection_check_boxes.rb +43 -0
  172. data/lib/action_view/helpers/tags/collection_helpers.rb +83 -0
  173. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +36 -0
  174. data/lib/action_view/helpers/tags/collection_select.rb +28 -0
  175. data/lib/action_view/helpers/tags/color_field.rb +25 -0
  176. data/lib/action_view/helpers/tags/date_field.rb +13 -0
  177. data/lib/action_view/helpers/tags/date_select.rb +72 -0
  178. data/lib/action_view/helpers/tags/datetime_field.rb +22 -0
  179. data/lib/action_view/helpers/tags/datetime_local_field.rb +19 -0
  180. data/lib/action_view/helpers/tags/datetime_select.rb +8 -0
  181. data/lib/action_view/helpers/tags/email_field.rb +8 -0
  182. data/lib/action_view/helpers/tags/file_field.rb +8 -0
  183. data/lib/action_view/helpers/tags/grouped_collection_select.rb +29 -0
  184. data/lib/action_view/helpers/tags/hidden_field.rb +8 -0
  185. data/lib/action_view/helpers/tags/label.rb +65 -0
  186. data/lib/action_view/helpers/tags/month_field.rb +13 -0
  187. data/lib/action_view/helpers/tags/number_field.rb +18 -0
  188. data/lib/action_view/helpers/tags/password_field.rb +12 -0
  189. data/lib/action_view/helpers/tags/radio_button.rb +31 -0
  190. data/lib/action_view/helpers/tags/range_field.rb +8 -0
  191. data/lib/action_view/helpers/tags/search_field.rb +24 -0
  192. data/lib/action_view/helpers/tags/select.rb +40 -0
  193. data/lib/action_view/helpers/tags/tel_field.rb +8 -0
  194. data/lib/action_view/helpers/tags/text_area.rb +18 -0
  195. data/lib/action_view/helpers/tags/text_field.rb +29 -0
  196. data/lib/action_view/helpers/tags/time_field.rb +13 -0
  197. data/lib/action_view/helpers/tags/time_select.rb +8 -0
  198. data/lib/action_view/helpers/tags/time_zone_select.rb +20 -0
  199. data/lib/action_view/helpers/tags/url_field.rb +8 -0
  200. data/lib/action_view/helpers/tags/week_field.rb +13 -0
  201. data/lib/action_view/helpers/tags.rb +39 -0
  202. data/lib/action_view/helpers/text_helper.rb +130 -114
  203. data/lib/action_view/helpers/translation_helper.rb +32 -16
  204. data/lib/action_view/helpers/url_helper.rb +211 -270
  205. data/lib/action_view/helpers.rb +2 -4
  206. data/lib/action_view/locale/en.yml +1 -105
  207. data/lib/action_view/log_subscriber.rb +6 -4
  208. data/lib/action_view/lookup_context.rb +15 -28
  209. data/lib/action_view/model_naming.rb +12 -0
  210. data/lib/action_view/path_set.rb +8 -20
  211. data/lib/action_view/railtie.rb +6 -22
  212. data/lib/action_view/record_identifier.rb +84 -0
  213. data/lib/action_view/renderer/abstract_renderer.rb +25 -19
  214. data/lib/action_view/renderer/partial_renderer.rb +158 -81
  215. data/lib/action_view/renderer/renderer.rb +8 -12
  216. data/lib/action_view/renderer/streaming_template_renderer.rb +2 -5
  217. data/lib/action_view/renderer/template_renderer.rb +12 -10
  218. data/lib/action_view/routing_url_for.rb +107 -0
  219. data/lib/action_view/template/error.rb +22 -12
  220. data/lib/action_view/template/handlers/builder.rb +1 -1
  221. data/lib/action_view/template/handlers/erb.rb +40 -19
  222. data/lib/action_view/template/handlers/raw.rb +11 -0
  223. data/lib/action_view/template/handlers.rb +12 -9
  224. data/lib/action_view/template/resolver.rb +107 -53
  225. data/lib/action_view/template/text.rb +12 -8
  226. data/lib/action_view/template/types.rb +57 -0
  227. data/lib/action_view/template.rb +25 -23
  228. data/lib/action_view/test_case.rb +67 -42
  229. data/lib/{action_controller → action_view}/vendor/html-scanner/html/document.rb +0 -0
  230. data/lib/{action_controller → action_view}/vendor/html-scanner/html/node.rb +12 -12
  231. data/lib/{action_controller → action_view}/vendor/html-scanner/html/sanitizer.rb +13 -2
  232. data/lib/{action_controller → action_view}/vendor/html-scanner/html/selector.rb +9 -9
  233. data/lib/{action_controller → action_view}/vendor/html-scanner/html/tokenizer.rb +1 -1
  234. data/lib/{action_controller → action_view}/vendor/html-scanner/html/version.rb +0 -0
  235. data/lib/action_view/vendor/html-scanner.rb +20 -0
  236. data/lib/action_view.rb +17 -8
  237. metadata +184 -214
  238. data/lib/action_controller/caching/actions.rb +0 -185
  239. data/lib/action_controller/caching/pages.rb +0 -187
  240. data/lib/action_controller/caching/sweeping.rb +0 -97
  241. data/lib/action_controller/deprecated/performance_test.rb +0 -1
  242. data/lib/action_controller/metal/compatibility.rb +0 -65
  243. data/lib/action_controller/metal/session_management.rb +0 -14
  244. data/lib/action_controller/railties/paths.rb +0 -25
  245. data/lib/action_dispatch/middleware/best_standards_support.rb +0 -30
  246. data/lib/action_dispatch/middleware/body_proxy.rb +0 -30
  247. data/lib/action_dispatch/middleware/head.rb +0 -18
  248. data/lib/action_dispatch/middleware/rescue.rb +0 -26
  249. data/lib/action_dispatch/testing/performance_test.rb +0 -10
  250. data/lib/action_view/asset_paths.rb +0 -142
  251. data/lib/action_view/helpers/asset_paths.rb +0 -7
  252. data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +0 -146
  253. data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +0 -93
  254. data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +0 -193
  255. data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +0 -148
  256. data/lib/sprockets/assets.rake +0 -99
  257. data/lib/sprockets/bootstrap.rb +0 -37
  258. data/lib/sprockets/compressors.rb +0 -83
  259. data/lib/sprockets/helpers/isolated_helper.rb +0 -13
  260. data/lib/sprockets/helpers/rails_helper.rb +0 -182
  261. data/lib/sprockets/helpers.rb +0 -6
  262. data/lib/sprockets/railtie.rb +0 -62
  263. data/lib/sprockets/static_compiler.rb +0 -56
@@ -1,23 +1,23 @@
1
1
  module ActionDispatch
2
- class Request
2
+ class Request < Rack::Request
3
3
  # Access the contents of the flash. Use <tt>flash["notice"]</tt> to
4
4
  # read a notice you put there or <tt>flash["notice"] = "hello"</tt>
5
5
  # to put a new one.
6
6
  def flash
7
- @env[Flash::KEY] ||= (session["flash"] || Flash::FlashHash.new)
7
+ @env[Flash::KEY] ||= Flash::FlashHash.from_session_value(session["flash"])
8
8
  end
9
9
  end
10
10
 
11
11
  # The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
12
12
  # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
13
13
  # action that sets <tt>flash[:notice] = "Post successfully created"</tt> before redirecting to a display action that can
14
- # then expose the flash to its template. Actually, that exposure is automatically done. Example:
14
+ # then expose the flash to its template. Actually, that exposure is automatically done.
15
15
  #
16
16
  # class PostsController < ActionController::Base
17
17
  # def create
18
18
  # # save post
19
19
  # flash[:notice] = "Post successfully created"
20
- # redirect_to posts_path(@post)
20
+ # redirect_to @post
21
21
  # end
22
22
  #
23
23
  # def show
@@ -59,28 +59,41 @@ module ActionDispatch
59
59
  @flash[k]
60
60
  end
61
61
 
62
- # Convenience accessor for flash.now[:alert]=
62
+ # Convenience accessor for <tt>flash.now[:alert]=</tt>.
63
63
  def alert=(message)
64
64
  self[:alert] = message
65
65
  end
66
66
 
67
- # Convenience accessor for flash.now[:notice]=
67
+ # Convenience accessor for <tt>flash.now[:notice]=</tt>.
68
68
  def notice=(message)
69
69
  self[:notice] = message
70
70
  end
71
71
  end
72
72
 
73
- # Implementation detail: please do not change the signature of the
74
- # FlashHash class. Doing that will likely affect all Rails apps in
75
- # production as the FlashHash currently stored in their sessions will
76
- # become invalid.
77
73
  class FlashHash
78
74
  include Enumerable
79
75
 
80
- def initialize #:nodoc:
81
- @used = Set.new
82
- @closed = false
83
- @flashes = {}
76
+ def self.from_session_value(value)
77
+ flash = case value
78
+ when FlashHash # Rails 3.1, 3.2
79
+ new(value.instance_variable_get(:@flashes), value.instance_variable_get(:@used))
80
+ when Hash # Rails 4.0
81
+ new(value['flashes'], value['discard'])
82
+ else
83
+ new
84
+ end
85
+
86
+ flash.tap(&:sweep)
87
+ end
88
+
89
+ def to_session_value
90
+ return nil if empty?
91
+ {'discard' => @discard.to_a, 'flashes' => @flashes}
92
+ end
93
+
94
+ def initialize(flashes = {}, discard = []) #:nodoc:
95
+ @discard = Set.new(discard)
96
+ @flashes = flashes
84
97
  @now = nil
85
98
  end
86
99
 
@@ -92,8 +105,8 @@ module ActionDispatch
92
105
  super
93
106
  end
94
107
 
95
- def []=(k, v) #:nodoc:
96
- keep(k)
108
+ def []=(k, v)
109
+ @discard.delete k
97
110
  @flashes[k] = v
98
111
  end
99
112
 
@@ -102,7 +115,7 @@ module ActionDispatch
102
115
  end
103
116
 
104
117
  def update(h) #:nodoc:
105
- h.keys.each { |k| keep(k) }
118
+ @discard.subtract h.keys
106
119
  @flashes.update h
107
120
  self
108
121
  end
@@ -116,6 +129,7 @@ module ActionDispatch
116
129
  end
117
130
 
118
131
  def delete(key)
132
+ @discard.delete key
119
133
  @flashes.delete key
120
134
  self
121
135
  end
@@ -129,6 +143,7 @@ module ActionDispatch
129
143
  end
130
144
 
131
145
  def clear
146
+ @discard.clear
132
147
  @flashes.clear
133
148
  end
134
149
 
@@ -139,7 +154,7 @@ module ActionDispatch
139
154
  alias :merge! :update
140
155
 
141
156
  def replace(h) #:nodoc:
142
- @used = Set.new
157
+ @discard.clear
143
158
  @flashes.replace h
144
159
  self
145
160
  end
@@ -154,6 +169,14 @@ module ActionDispatch
154
169
  # vanish when the current action is done.
155
170
  #
156
171
  # Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
172
+ #
173
+ # Also, brings two convenience accessors:
174
+ #
175
+ # flash.now.alert = "Beware now!"
176
+ # # Equivalent to flash.now[:alert] = "Beware now!"
177
+ #
178
+ # flash.now.notice = "Good luck now!"
179
+ # # Equivalent to flash.now[:notice] = "Good luck now!"
157
180
  def now
158
181
  @now ||= FlashNow.new(self)
159
182
  end
@@ -163,7 +186,8 @@ module ActionDispatch
163
186
  # flash.keep # keeps the entire flash
164
187
  # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
165
188
  def keep(k = nil)
166
- use(k, false)
189
+ @discard.subtract Array(k || keys)
190
+ k ? self[k] : self
167
191
  end
168
192
 
169
193
  # Marks the entire flash or a single flash entry to be discarded by the end of the current action:
@@ -171,63 +195,42 @@ module ActionDispatch
171
195
  # flash.discard # discard the entire flash at the end of the current action
172
196
  # flash.discard(:warning) # discard only the "warning" entry at the end of the current action
173
197
  def discard(k = nil)
174
- use(k)
198
+ @discard.merge Array(k || keys)
199
+ k ? self[k] : self
175
200
  end
176
201
 
177
202
  # Mark for removal entries that were kept, and delete unkept ones.
178
203
  #
179
204
  # This method is called automatically by filters, so you generally don't need to care about it.
180
205
  def sweep #:nodoc:
181
- keys.each do |k|
182
- unless @used.include?(k)
183
- @used << k
184
- else
185
- delete(k)
186
- @used.delete(k)
187
- end
188
- end
189
-
190
- # clean up after keys that could have been left over by calling reject! or shift on the flash
191
- (@used - keys).each{ |k| @used.delete(k) }
206
+ @discard.each { |k| @flashes.delete k }
207
+ @discard.replace @flashes.keys
192
208
  end
193
209
 
194
- # Convenience accessor for flash[:alert]
210
+ # Convenience accessor for <tt>flash[:alert]</tt>.
195
211
  def alert
196
212
  self[:alert]
197
213
  end
198
214
 
199
- # Convenience accessor for flash[:alert]=
215
+ # Convenience accessor for <tt>flash[:alert]=</tt>.
200
216
  def alert=(message)
201
217
  self[:alert] = message
202
218
  end
203
219
 
204
- # Convenience accessor for flash[:notice]
220
+ # Convenience accessor for <tt>flash[:notice]</tt>.
205
221
  def notice
206
222
  self[:notice]
207
223
  end
208
224
 
209
- # Convenience accessor for flash[:notice]=
225
+ # Convenience accessor for <tt>flash[:notice]=</tt>.
210
226
  def notice=(message)
211
227
  self[:notice] = message
212
228
  end
213
229
 
214
230
  protected
215
-
216
- def now_is_loaded?
217
- !!@now
218
- end
219
-
220
- # Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
221
- # use() # marks the entire flash as used
222
- # use('msg') # marks the "msg" entry as used
223
- # use(nil, false) # marks the entire flash as unused (keeps it around for one more action)
224
- # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
225
- # Returns the single value for the key you asked to be marked (un)used or the FlashHash itself
226
- # if no key is passed.
227
- def use(key = nil, used = true)
228
- Array(key || keys).each { |k| used ? @used << k : @used.delete(k) }
229
- return key ? self[key] : self
230
- end
231
+ def now_is_loaded?
232
+ @now
233
+ end
231
234
  end
232
235
 
233
236
  def initialize(app)
@@ -235,18 +238,14 @@ module ActionDispatch
235
238
  end
236
239
 
237
240
  def call(env)
238
- if (session = env['rack.session']) && (flash = session['flash'])
239
- flash.sweep
240
- end
241
-
242
241
  @app.call(env)
243
242
  ensure
244
- session = env['rack.session'] || {}
243
+ session = Request::Session.find(env) || {}
245
244
  flash_hash = env[KEY]
246
245
 
247
246
  if flash_hash
248
247
  if !flash_hash.empty? || session.key?('flash')
249
- session["flash"] = flash_hash
248
+ session["flash"] = flash_hash.to_session_value
250
249
  new_hash = flash_hash.dup
251
250
  else
252
251
  new_hash = flash_hash
@@ -255,7 +254,8 @@ module ActionDispatch
255
254
  env[KEY] = new_hash
256
255
  end
257
256
 
258
- if session.key?('flash') && session['flash'].empty?
257
+ if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?)
258
+ session.key?('flash') && session['flash'].nil?
259
259
  session.delete('flash')
260
260
  end
261
261
  end
@@ -4,10 +4,16 @@ require 'active_support/core_ext/hash/indifferent_access'
4
4
 
5
5
  module ActionDispatch
6
6
  class ParamsParser
7
- DEFAULT_PARSERS = {
8
- Mime::XML => :xml_simple,
9
- Mime::JSON => :json
10
- }
7
+ class ParseError < StandardError
8
+ attr_reader :original_exception
9
+
10
+ def initialize(message, original_exception)
11
+ super(message)
12
+ @original_exception = original_exception
13
+ end
14
+ end
15
+
16
+ DEFAULT_PARSERS = { Mime::JSON => :json }
11
17
 
12
18
  def initialize(app, parsers = {})
13
19
  @app, @parsers = app, DEFAULT_PARSERS.merge(parsers)
@@ -27,49 +33,28 @@ module ActionDispatch
27
33
 
28
34
  return false if request.content_length.zero?
29
35
 
30
- mime_type = content_type_from_legacy_post_data_format_header(env) ||
31
- request.content_mime_type
32
-
33
- strategy = @parsers[mime_type]
36
+ strategy = @parsers[request.content_mime_type]
34
37
 
35
38
  return false unless strategy
36
39
 
37
40
  case strategy
38
41
  when Proc
39
42
  strategy.call(request.raw_post)
40
- when :xml_simple, :xml_node
41
- data = request.deep_munge(Hash.from_xml(request.body.read) || {})
42
- request.body.rewind if request.body.respond_to?(:rewind)
43
- data.with_indifferent_access
44
- when :yaml
45
- YAML.load(request.raw_post)
46
43
  when :json
47
44
  data = ActiveSupport::JSON.decode(request.body)
48
- request.body.rewind if request.body.respond_to?(:rewind)
49
45
  data = {:_json => data} unless data.is_a?(Hash)
50
46
  request.deep_munge(data).with_indifferent_access
51
47
  else
52
48
  false
53
49
  end
54
- rescue Exception => e # YAML, XML or Ruby code block errors
50
+ rescue Exception => e # JSON or Ruby code block errors
55
51
  logger(env).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
56
52
 
57
- raise e
58
- end
59
-
60
- def content_type_from_legacy_post_data_format_header(env)
61
- if x_post_format = env['HTTP_X_POST_DATA_FORMAT']
62
- case x_post_format.to_s.downcase
63
- when 'yaml' then return Mime::YAML
64
- when 'xml' then return Mime::XML
65
- end
66
- end
67
-
68
- nil
53
+ raise ParseError.new(e.message, e)
69
54
  end
70
55
 
71
56
  def logger(env)
72
- env['action_dispatch.logger'] || Logger.new($stderr)
57
+ env['action_dispatch.logger'] || ActiveSupport::Logger.new($stderr)
73
58
  end
74
59
  end
75
60
  end
@@ -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,40 @@ 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
+ status = env["PATH_INFO"][1..-1]
11
+ request = ActionDispatch::Request.new(env)
12
+ content_type = request.formats.first
13
+ body = { :status => status, :error => Rack::Utils::HTTP_STATUS_CODES.fetch(status.to_i, Rack::Utils::HTTP_STATUS_CODES[500]) }
14
+
15
+ render(status, content_type, body)
16
+ end
17
+
18
+ private
19
+
20
+ def render(status, content_type, body)
21
+ format = "to_#{content_type.to_sym}" if content_type
22
+ if format && body.respond_to?(format)
23
+ render_format(status, content_type, body.public_send(format))
19
24
  else
20
- [404, { "X-Cascade" => "pass" }, []]
25
+ render_html(status)
21
26
  end
22
27
  end
23
28
 
24
- private
29
+ def render_format(status, content_type, body)
30
+ [status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
31
+ 'Content-Length' => body.bytesize.to_s}, [body]]
32
+ end
33
+
34
+ def render_html(status)
35
+ found = false
36
+ path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
37
+ path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path))
25
38
 
26
- def render(status, body)
27
- [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
39
+ if found || File.exist?(path)
40
+ render_format(status, 'text/html', File.read(path))
41
+ else
42
+ [404, { "X-Cascade" => "pass" }, []]
43
+ end
28
44
  end
29
45
  end
30
- end
46
+ 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 preceding 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 beginning
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