actionpack 4.0.1 → 4.2.11.1

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 (241) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +402 -1173
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +7 -7
  5. data/lib/abstract_controller/base.rb +39 -7
  6. data/lib/abstract_controller/callbacks.rb +32 -53
  7. data/lib/abstract_controller/collector.rb +11 -1
  8. data/lib/abstract_controller/helpers.rb +26 -16
  9. data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
  10. data/lib/abstract_controller/rendering.rb +57 -127
  11. data/lib/abstract_controller/url_for.rb +1 -1
  12. data/lib/abstract_controller.rb +1 -2
  13. data/lib/action_controller/base.rb +19 -10
  14. data/lib/action_controller/caching/fragments.rb +7 -1
  15. data/lib/action_controller/caching.rb +2 -12
  16. data/lib/action_controller/log_subscriber.rb +29 -20
  17. data/lib/action_controller/metal/conditional_get.rb +37 -12
  18. data/lib/action_controller/metal/data_streaming.rb +1 -1
  19. data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
  20. data/lib/action_controller/metal/exceptions.rb +1 -1
  21. data/lib/action_controller/metal/flash.rb +17 -0
  22. data/lib/action_controller/metal/force_ssl.rb +2 -2
  23. data/lib/action_controller/metal/head.rb +8 -6
  24. data/lib/action_controller/metal/helpers.rb +6 -2
  25. data/lib/action_controller/metal/http_authentication.rb +45 -23
  26. data/lib/action_controller/metal/instrumentation.rb +9 -6
  27. data/lib/action_controller/metal/live.rb +173 -20
  28. data/lib/action_controller/metal/mime_responds.rb +127 -232
  29. data/lib/action_controller/metal/params_wrapper.rb +16 -9
  30. data/lib/action_controller/metal/rack_delegation.rb +1 -1
  31. data/lib/action_controller/metal/redirecting.rb +34 -26
  32. data/lib/action_controller/metal/renderers.rb +39 -12
  33. data/lib/action_controller/metal/rendering.rb +41 -14
  34. data/lib/action_controller/metal/request_forgery_protection.rb +147 -19
  35. data/lib/action_controller/metal/streaming.rb +19 -21
  36. data/lib/action_controller/metal/strong_parameters.rb +166 -22
  37. data/lib/action_controller/metal/testing.rb +0 -1
  38. data/lib/action_controller/metal/url_for.rb +11 -12
  39. data/lib/action_controller/metal.rb +14 -8
  40. data/lib/action_controller/model_naming.rb +1 -1
  41. data/lib/action_controller/railtie.rb +5 -1
  42. data/lib/action_controller/test_case.rb +160 -94
  43. data/lib/action_controller.rb +2 -18
  44. data/lib/action_dispatch/http/cache.rb +5 -4
  45. data/lib/action_dispatch/http/filter_parameters.rb +2 -2
  46. data/lib/action_dispatch/http/filter_redirect.rb +5 -4
  47. data/lib/action_dispatch/http/headers.rb +46 -10
  48. data/lib/action_dispatch/http/mime_negotiation.rb +31 -4
  49. data/lib/action_dispatch/http/mime_type.rb +25 -26
  50. data/lib/action_dispatch/http/mime_types.rb +1 -0
  51. data/lib/action_dispatch/http/parameter_filter.rb +1 -1
  52. data/lib/action_dispatch/http/parameters.rb +25 -41
  53. data/lib/action_dispatch/http/request.rb +49 -32
  54. data/lib/action_dispatch/http/response.rb +127 -25
  55. data/lib/action_dispatch/http/upload.rb +9 -21
  56. data/lib/action_dispatch/http/url.rb +97 -70
  57. data/lib/action_dispatch/journey/formatter.rb +35 -19
  58. data/lib/action_dispatch/journey/gtg/builder.rb +3 -3
  59. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -7
  60. data/lib/action_dispatch/journey/gtg/transition_table.rb +23 -33
  61. data/lib/action_dispatch/journey/nfa/dot.rb +2 -2
  62. data/lib/action_dispatch/journey/nfa/simulator.rb +1 -1
  63. data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -5
  64. data/lib/action_dispatch/journey/nodes/node.rb +4 -0
  65. data/lib/action_dispatch/journey/parser.rb +51 -59
  66. data/lib/action_dispatch/journey/parser.y +12 -10
  67. data/lib/action_dispatch/journey/path/pattern.rb +16 -19
  68. data/lib/action_dispatch/journey/route.rb +8 -19
  69. data/lib/action_dispatch/journey/router/strexp.rb +9 -6
  70. data/lib/action_dispatch/journey/router/utils.rb +54 -18
  71. data/lib/action_dispatch/journey/router.rb +53 -75
  72. data/lib/action_dispatch/journey/routes.rb +4 -0
  73. data/lib/action_dispatch/journey/scanner.rb +5 -5
  74. data/lib/action_dispatch/journey/visitors.rb +81 -60
  75. data/lib/action_dispatch/journey/visualizer/fsm.css +0 -4
  76. data/lib/action_dispatch/journey/visualizer/index.html.erb +2 -2
  77. data/lib/action_dispatch/middleware/callbacks.rb +7 -7
  78. data/lib/action_dispatch/middleware/cookies.rb +119 -43
  79. data/lib/action_dispatch/middleware/debug_exceptions.rb +32 -13
  80. data/lib/action_dispatch/middleware/exception_wrapper.rb +60 -20
  81. data/lib/action_dispatch/middleware/flash.rb +37 -24
  82. data/lib/action_dispatch/middleware/params_parser.rb +2 -2
  83. data/lib/action_dispatch/middleware/public_exceptions.rb +12 -3
  84. data/lib/action_dispatch/middleware/reloader.rb +11 -2
  85. data/lib/action_dispatch/middleware/remote_ip.rb +40 -54
  86. data/lib/action_dispatch/middleware/request_id.rb +1 -1
  87. data/lib/action_dispatch/middleware/session/cache_store.rb +3 -3
  88. data/lib/action_dispatch/middleware/session/cookie_store.rb +8 -7
  89. data/lib/action_dispatch/middleware/show_exceptions.rb +6 -2
  90. data/lib/action_dispatch/middleware/ssl.rb +10 -7
  91. data/lib/action_dispatch/middleware/static.rb +79 -23
  92. data/lib/action_dispatch/middleware/templates/rescues/{_request_and_response.erb → _request_and_response.html.erb} +0 -0
  93. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  94. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +21 -19
  95. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
  96. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  97. data/lib/action_dispatch/middleware/templates/rescues/{diagnostics.erb → diagnostics.html.erb} +1 -1
  98. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  99. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +6 -0
  100. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  101. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  102. data/lib/action_dispatch/middleware/templates/rescues/{routing_error.erb → routing_error.html.erb} +3 -1
  103. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  104. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  105. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  106. data/lib/action_dispatch/middleware/templates/rescues/{unknown_action.erb → unknown_action.html.erb} +1 -1
  107. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  108. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +120 -64
  109. data/lib/action_dispatch/railtie.rb +5 -2
  110. data/lib/action_dispatch/request/session.rb +12 -0
  111. data/lib/action_dispatch/request/utils.rb +35 -0
  112. data/lib/action_dispatch/routing/endpoint.rb +10 -0
  113. data/lib/action_dispatch/routing/inspector.rb +11 -17
  114. data/lib/action_dispatch/routing/mapper.rb +519 -312
  115. data/lib/action_dispatch/routing/polymorphic_routes.rb +204 -79
  116. data/lib/action_dispatch/routing/redirection.rb +51 -26
  117. data/lib/action_dispatch/routing/route_set.rb +331 -206
  118. data/lib/action_dispatch/routing/routes_proxy.rb +5 -4
  119. data/lib/action_dispatch/routing/url_for.rb +19 -5
  120. data/lib/action_dispatch/routing.rb +9 -6
  121. data/lib/action_dispatch/testing/assertions/dom.rb +2 -26
  122. data/lib/action_dispatch/testing/assertions/response.rb +9 -15
  123. data/lib/action_dispatch/testing/assertions/routing.rb +22 -22
  124. data/lib/action_dispatch/testing/assertions/selector.rb +2 -429
  125. data/lib/action_dispatch/testing/assertions/tag.rb +2 -134
  126. data/lib/action_dispatch/testing/assertions.rb +11 -7
  127. data/lib/action_dispatch/testing/integration.rb +31 -29
  128. data/lib/action_dispatch/testing/test_request.rb +1 -1
  129. data/lib/action_dispatch/testing/test_response.rb +1 -5
  130. data/lib/action_dispatch.rb +5 -8
  131. data/lib/action_pack/gem_version.rb +15 -0
  132. data/lib/action_pack/version.rb +4 -7
  133. data/lib/action_pack.rb +1 -1
  134. metadata +77 -159
  135. data/lib/abstract_controller/layouts.rb +0 -423
  136. data/lib/abstract_controller/view_paths.rb +0 -96
  137. data/lib/action_controller/deprecated/integration_test.rb +0 -5
  138. data/lib/action_controller/deprecated.rb +0 -7
  139. data/lib/action_controller/metal/responder.rb +0 -287
  140. data/lib/action_controller/record_identifier.rb +0 -31
  141. data/lib/action_controller/vendor/html-scanner.rb +0 -5
  142. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +0 -24
  143. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +0 -7
  144. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +0 -43
  145. data/lib/action_view/base.rb +0 -201
  146. data/lib/action_view/buffers.rb +0 -49
  147. data/lib/action_view/context.rb +0 -36
  148. data/lib/action_view/dependency_tracker.rb +0 -93
  149. data/lib/action_view/digestor.rb +0 -113
  150. data/lib/action_view/flows.rb +0 -76
  151. data/lib/action_view/helpers/active_model_helper.rb +0 -49
  152. data/lib/action_view/helpers/asset_tag_helper.rb +0 -320
  153. data/lib/action_view/helpers/asset_url_helper.rb +0 -355
  154. data/lib/action_view/helpers/atom_feed_helper.rb +0 -203
  155. data/lib/action_view/helpers/cache_helper.rb +0 -196
  156. data/lib/action_view/helpers/capture_helper.rb +0 -216
  157. data/lib/action_view/helpers/controller_helper.rb +0 -25
  158. data/lib/action_view/helpers/csrf_helper.rb +0 -30
  159. data/lib/action_view/helpers/date_helper.rb +0 -1083
  160. data/lib/action_view/helpers/debug_helper.rb +0 -39
  161. data/lib/action_view/helpers/form_helper.rb +0 -1880
  162. data/lib/action_view/helpers/form_options_helper.rb +0 -838
  163. data/lib/action_view/helpers/form_tag_helper.rb +0 -785
  164. data/lib/action_view/helpers/javascript_helper.rb +0 -117
  165. data/lib/action_view/helpers/number_helper.rb +0 -441
  166. data/lib/action_view/helpers/output_safety_helper.rb +0 -38
  167. data/lib/action_view/helpers/record_tag_helper.rb +0 -106
  168. data/lib/action_view/helpers/rendering_helper.rb +0 -90
  169. data/lib/action_view/helpers/sanitize_helper.rb +0 -256
  170. data/lib/action_view/helpers/tag_helper.rb +0 -173
  171. data/lib/action_view/helpers/tags/base.rb +0 -148
  172. data/lib/action_view/helpers/tags/check_box.rb +0 -64
  173. data/lib/action_view/helpers/tags/checkable.rb +0 -16
  174. data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -44
  175. data/lib/action_view/helpers/tags/collection_helpers.rb +0 -84
  176. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -36
  177. data/lib/action_view/helpers/tags/collection_select.rb +0 -28
  178. data/lib/action_view/helpers/tags/color_field.rb +0 -25
  179. data/lib/action_view/helpers/tags/date_field.rb +0 -13
  180. data/lib/action_view/helpers/tags/date_select.rb +0 -72
  181. data/lib/action_view/helpers/tags/datetime_field.rb +0 -22
  182. data/lib/action_view/helpers/tags/datetime_local_field.rb +0 -19
  183. data/lib/action_view/helpers/tags/datetime_select.rb +0 -8
  184. data/lib/action_view/helpers/tags/email_field.rb +0 -8
  185. data/lib/action_view/helpers/tags/file_field.rb +0 -8
  186. data/lib/action_view/helpers/tags/grouped_collection_select.rb +0 -29
  187. data/lib/action_view/helpers/tags/hidden_field.rb +0 -8
  188. data/lib/action_view/helpers/tags/label.rb +0 -66
  189. data/lib/action_view/helpers/tags/month_field.rb +0 -13
  190. data/lib/action_view/helpers/tags/number_field.rb +0 -18
  191. data/lib/action_view/helpers/tags/password_field.rb +0 -12
  192. data/lib/action_view/helpers/tags/radio_button.rb +0 -31
  193. data/lib/action_view/helpers/tags/range_field.rb +0 -8
  194. data/lib/action_view/helpers/tags/search_field.rb +0 -24
  195. data/lib/action_view/helpers/tags/select.rb +0 -40
  196. data/lib/action_view/helpers/tags/tel_field.rb +0 -8
  197. data/lib/action_view/helpers/tags/text_area.rb +0 -18
  198. data/lib/action_view/helpers/tags/text_field.rb +0 -29
  199. data/lib/action_view/helpers/tags/time_field.rb +0 -13
  200. data/lib/action_view/helpers/tags/time_select.rb +0 -8
  201. data/lib/action_view/helpers/tags/time_zone_select.rb +0 -20
  202. data/lib/action_view/helpers/tags/url_field.rb +0 -8
  203. data/lib/action_view/helpers/tags/week_field.rb +0 -13
  204. data/lib/action_view/helpers/tags.rb +0 -39
  205. data/lib/action_view/helpers/text_helper.rb +0 -443
  206. data/lib/action_view/helpers/translation_helper.rb +0 -107
  207. data/lib/action_view/helpers/url_helper.rb +0 -635
  208. data/lib/action_view/helpers.rb +0 -58
  209. data/lib/action_view/locale/en.yml +0 -56
  210. data/lib/action_view/log_subscriber.rb +0 -30
  211. data/lib/action_view/lookup_context.rb +0 -241
  212. data/lib/action_view/model_naming.rb +0 -12
  213. data/lib/action_view/path_set.rb +0 -77
  214. data/lib/action_view/railtie.rb +0 -43
  215. data/lib/action_view/record_identifier.rb +0 -84
  216. data/lib/action_view/renderer/abstract_renderer.rb +0 -47
  217. data/lib/action_view/renderer/partial_renderer.rb +0 -492
  218. data/lib/action_view/renderer/renderer.rb +0 -50
  219. data/lib/action_view/renderer/streaming_template_renderer.rb +0 -103
  220. data/lib/action_view/renderer/template_renderer.rb +0 -96
  221. data/lib/action_view/routing_url_for.rb +0 -107
  222. data/lib/action_view/tasks/dependencies.rake +0 -17
  223. data/lib/action_view/template/error.rb +0 -138
  224. data/lib/action_view/template/handlers/builder.rb +0 -26
  225. data/lib/action_view/template/handlers/erb.rb +0 -146
  226. data/lib/action_view/template/handlers/raw.rb +0 -11
  227. data/lib/action_view/template/handlers.rb +0 -53
  228. data/lib/action_view/template/resolver.rb +0 -326
  229. data/lib/action_view/template/text.rb +0 -34
  230. data/lib/action_view/template/types.rb +0 -57
  231. data/lib/action_view/template.rb +0 -339
  232. data/lib/action_view/test_case.rb +0 -270
  233. data/lib/action_view/testing/resolvers.rb +0 -50
  234. data/lib/action_view/vendor/html-scanner/html/document.rb +0 -68
  235. data/lib/action_view/vendor/html-scanner/html/node.rb +0 -532
  236. data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +0 -188
  237. data/lib/action_view/vendor/html-scanner/html/selector.rb +0 -830
  238. data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +0 -107
  239. data/lib/action_view/vendor/html-scanner/html/version.rb +0 -11
  240. data/lib/action_view/vendor/html-scanner.rb +0 -20
  241. data/lib/action_view.rb +0 -93
@@ -6,6 +6,17 @@ module ActionController
6
6
  Renderers.add(key, &block)
7
7
  end
8
8
 
9
+ # See <tt>Renderers.remove</tt>
10
+ def self.remove_renderer(key)
11
+ Renderers.remove(key)
12
+ end
13
+
14
+ class MissingRenderer < LoadError
15
+ def initialize(format)
16
+ super "No renderer defined for format: #{format}"
17
+ end
18
+ end
19
+
9
20
  module Renderers
10
21
  extend ActiveSupport::Concern
11
22
 
@@ -23,23 +34,28 @@ module ActionController
23
34
  end
24
35
 
25
36
  def render_to_body(options)
26
- _handle_render_options(options) || super
37
+ _render_to_body_with_renderer(options) || super
27
38
  end
28
39
 
29
- def _handle_render_options(options)
40
+ def _render_to_body_with_renderer(options)
30
41
  _renderers.each do |name|
31
42
  if options.key?(name)
32
43
  _process_options(options)
33
- return send("_render_option_#{name}", options.delete(name), options)
44
+ method_name = Renderers._render_with_renderer_method_name(name)
45
+ return send(method_name, options.delete(name), options)
34
46
  end
35
47
  end
36
48
  nil
37
49
  end
38
50
 
39
- # Hash of available renderers, mapping a renderer name to its proc.
40
- # Default keys are :json, :js, :xml.
51
+ # A Set containing renderer names that correspond to available renderer procs.
52
+ # Default values are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
41
53
  RENDERERS = Set.new
42
54
 
55
+ def self._render_with_renderer_method_name(key)
56
+ "_render_with_renderer_#{key}"
57
+ end
58
+
43
59
  # Adds a new renderer to call within controller actions.
44
60
  # A renderer is invoked by passing its name as an option to
45
61
  # <tt>AbstractController::Rendering#render</tt>. To create a renderer
@@ -67,16 +83,24 @@ module ActionController
67
83
  # respond_to do |format|
68
84
  # format.html
69
85
  # format.csv { render csv: @csvable, filename: @csvable.name }
70
- # }
86
+ # end
71
87
  # end
72
- # To use renderers and their mime types in more concise ways, see
73
- # <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt> and
74
- # <tt>ActionController::MimeResponds#respond_with</tt>
75
88
  def self.add(key, &block)
76
- define_method("_render_option_#{key}", &block)
89
+ define_method(_render_with_renderer_method_name(key), &block)
77
90
  RENDERERS << key.to_sym
78
91
  end
79
92
 
93
+ # This method is the opposite of add method.
94
+ #
95
+ # Usage:
96
+ #
97
+ # ActionController::Renderers.remove(:csv)
98
+ def self.remove(key)
99
+ RENDERERS.delete(key.to_sym)
100
+ method_name = _render_with_renderer_method_name(key)
101
+ remove_method(method_name) if method_defined?(method_name)
102
+ end
103
+
80
104
  module All
81
105
  extend ActiveSupport::Concern
82
106
  include Renderers
@@ -90,8 +114,11 @@ module ActionController
90
114
  json = json.to_json(options) unless json.kind_of?(String)
91
115
 
92
116
  if options[:callback].present?
93
- self.content_type ||= Mime::JS
94
- "#{options[:callback]}(#{json})"
117
+ if content_type.nil? || content_type == Mime::JSON
118
+ self.content_type = Mime::JS
119
+ end
120
+
121
+ "/**/#{options[:callback]}(#{json})"
95
122
  else
96
123
  self.content_type ||= Mime::JSON
97
124
  json
@@ -2,7 +2,7 @@ module ActionController
2
2
  module Rendering
3
3
  extend ActiveSupport::Concern
4
4
 
5
- include AbstractController::Rendering
5
+ RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html]
6
6
 
7
7
  # Before processing, set the request formats in current controller formats.
8
8
  def process_action(*) #:nodoc:
@@ -12,29 +12,46 @@ module ActionController
12
12
 
13
13
  # Check for double render errors and set the content_type after rendering.
14
14
  def render(*args) #:nodoc:
15
- raise ::AbstractController::DoubleRenderError if response_body
15
+ raise ::AbstractController::DoubleRenderError if self.response_body
16
16
  super
17
- self.content_type ||= Mime[lookup_context.rendered_format].to_s
18
- response_body
19
17
  end
20
18
 
21
19
  # Overwrite render_to_string because body can now be set to a rack body.
22
20
  def render_to_string(*)
23
- if self.response_body = super
21
+ result = super
22
+ if result.respond_to?(:each)
24
23
  string = ""
25
- response_body.each { |r| string << r }
24
+ result.each { |r| string << r }
26
25
  string
26
+ else
27
+ result
27
28
  end
28
- ensure
29
- self.response_body = nil
30
29
  end
31
30
 
32
- def render_to_body(*)
33
- super || " "
31
+ def render_to_body(options = {})
32
+ super || _render_in_priorities(options) || ' '
34
33
  end
35
34
 
36
35
  private
37
36
 
37
+ def _render_in_priorities(options)
38
+ RENDER_FORMATS_IN_PRIORITY.each do |format|
39
+ return options[format] if options.key?(format)
40
+ end
41
+
42
+ nil
43
+ end
44
+
45
+ def _process_format(format, options = {})
46
+ super
47
+
48
+ if options[:plain]
49
+ self.content_type = Mime::TEXT
50
+ else
51
+ self.content_type ||= format.to_s
52
+ end
53
+ end
54
+
38
55
  # Normalize arguments by catching blocks and setting them on :update.
39
56
  def _normalize_args(action=nil, options={}, &blk) #:nodoc:
40
57
  options = super
@@ -44,12 +61,14 @@ module ActionController
44
61
 
45
62
  # Normalize both text and status options.
46
63
  def _normalize_options(options) #:nodoc:
47
- if options.key?(:text) && options[:text].respond_to?(:to_text)
48
- options[:text] = options[:text].to_text
64
+ _normalize_text(options)
65
+
66
+ if options[:html]
67
+ options[:html] = ERB::Util.html_escape(options[:html])
49
68
  end
50
69
 
51
- if options.delete(:nothing) || (options.key?(:text) && options[:text].nil?)
52
- options[:text] = " "
70
+ if options.delete(:nothing)
71
+ options[:body] = nil
53
72
  end
54
73
 
55
74
  if options[:status]
@@ -59,6 +78,14 @@ module ActionController
59
78
  super
60
79
  end
61
80
 
81
+ def _normalize_text(options)
82
+ RENDER_FORMATS_IN_PRIORITY.each do |format|
83
+ if options.key?(format) && options[format].respond_to?(:to_text)
84
+ options[format] = options[format].to_text
85
+ end
86
+ end
87
+ end
88
+
62
89
  # Process controller specific options, as status, content-type and location.
63
90
  def _process_options(options) #:nodoc:
64
91
  status, content_type, location = options.values_at(:status, :content_type, :location)
@@ -1,18 +1,29 @@
1
1
  require 'rack/session/abstract/id'
2
2
  require 'action_controller/metal/exceptions'
3
+ require 'active_support/security_utils'
3
4
 
4
5
  module ActionController #:nodoc:
5
6
  class InvalidAuthenticityToken < ActionControllerError #:nodoc:
6
7
  end
7
8
 
9
+ class InvalidCrossOriginRequest < ActionControllerError #:nodoc:
10
+ end
11
+
8
12
  # Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks
9
- # by including a token in the rendered html for your application. This token is
13
+ # by including a token in the rendered HTML for your application. This token is
10
14
  # stored as a random string in the session, to which an attacker does not have
11
15
  # access. When a request reaches your application, \Rails verifies the received
12
16
  # token with the token in the session. Only HTML and JavaScript requests are checked,
13
17
  # so this will not protect your XML API (presumably you'll have a different
14
- # authentication scheme there anyway). Also, GET requests are not protected as these
15
- # should be idempotent.
18
+ # authentication scheme there anyway).
19
+ #
20
+ # GET requests are not protected since they don't have side effects like writing
21
+ # to the database and don't leak sensitive information. JavaScript requests are
22
+ # an exception: a third-party site can use a <script> tag to reference a JavaScript
23
+ # URL on your site. When your JavaScript response loads on their site, it executes.
24
+ # With carefully crafted JavaScript on their end, sensitive data in your JavaScript
25
+ # response may be extracted. To prevent this, only XmlHttpRequest (known as XHR or
26
+ # Ajax) requests are allowed to make GET requests for JavaScript responses.
16
27
  #
17
28
  # It's important to remember that XML or JSON requests are also affected and if
18
29
  # you're building an API you'll need something like:
@@ -34,7 +45,7 @@ module ActionController #:nodoc:
34
45
  #
35
46
  # The token parameter is named <tt>authenticity_token</tt> by default. The name and
36
47
  # value of this token must be added to every layout that renders forms by including
37
- # <tt>csrf_meta_tags</tt> in the html +head+.
48
+ # <tt>csrf_meta_tags</tt> in the HTML +head+.
38
49
  #
39
50
  # Learn more about CSRF attacks and securing your application in the
40
51
  # {Ruby on Rails Security Guide}[http://guides.rubyonrails.org/security.html].
@@ -58,24 +69,27 @@ module ActionController #:nodoc:
58
69
  config_accessor :allow_forgery_protection
59
70
  self.allow_forgery_protection = true if allow_forgery_protection.nil?
60
71
 
72
+ # Controls whether a CSRF failure logs a warning. On by default.
73
+ config_accessor :log_warning_on_csrf_failure
74
+ self.log_warning_on_csrf_failure = true
75
+
61
76
  helper_method :form_authenticity_token
62
77
  helper_method :protect_against_forgery?
63
78
  end
64
79
 
65
80
  module ClassMethods
66
- # Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked.
81
+ # Turn on request forgery protection. Bear in mind that GET and HEAD requests are not checked.
82
+ #
83
+ # class ApplicationController < ActionController::Base
84
+ # protect_from_forgery
85
+ # end
67
86
  #
68
87
  # class FooController < ApplicationController
69
88
  # protect_from_forgery except: :index
70
89
  #
71
- # You can disable csrf protection on controller-by-controller basis:
72
- #
90
+ # You can disable CSRF protection on controller by skipping the verification before_action:
73
91
  # skip_before_action :verify_authenticity_token
74
92
  #
75
- # It can also be disabled for specific controller actions:
76
- #
77
- # skip_before_action :verify_authenticity_token, except: [:create]
78
- #
79
93
  # Valid Options:
80
94
  #
81
95
  # * <tt>:only/:except</tt> - Passed to the <tt>before_action</tt> call. Set which actions are verified.
@@ -89,6 +103,7 @@ module ActionController #:nodoc:
89
103
  self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
90
104
  self.request_forgery_protection_token ||= :authenticity_token
91
105
  prepend_before_action :verify_authenticity_token, options
106
+ append_after_action :verify_same_origin_request
92
107
  end
93
108
 
94
109
  private
@@ -124,6 +139,9 @@ module ActionController #:nodoc:
124
139
  @loaded = true
125
140
  end
126
141
 
142
+ # no-op
143
+ def destroy; end
144
+
127
145
  def exists?
128
146
  true
129
147
  end
@@ -166,18 +184,66 @@ module ActionController #:nodoc:
166
184
  end
167
185
 
168
186
  protected
187
+ # The actual before_action that is used to verify the CSRF token.
188
+ # Don't override this directly. Provide your own forgery protection
189
+ # strategy instead. If you override, you'll disable same-origin
190
+ # `<script>` verification.
191
+ #
192
+ # Lean on the protect_from_forgery declaration to mark which actions are
193
+ # due for same-origin request verification. If protect_from_forgery is
194
+ # enabled on an action, this before_action flags its after_action to
195
+ # verify that JavaScript responses are for XHR requests, ensuring they
196
+ # follow the browser's same-origin policy.
197
+ def verify_authenticity_token
198
+ mark_for_same_origin_verification!
199
+
200
+ if !verified_request?
201
+ if logger && log_warning_on_csrf_failure
202
+ logger.warn "Can't verify CSRF token authenticity"
203
+ end
204
+ handle_unverified_request
205
+ end
206
+ end
207
+
169
208
  def handle_unverified_request
170
209
  forgery_protection_strategy.new(self).handle_unverified_request
171
210
  end
172
211
 
173
- # The actual before_action that is used. Modify this to change how you handle unverified requests.
174
- def verify_authenticity_token
175
- unless verified_request?
176
- logger.warn "Can't verify CSRF token authenticity" if logger
177
- handle_unverified_request
212
+ #:nodoc:
213
+ CROSS_ORIGIN_JAVASCRIPT_WARNING = "Security warning: an embedded " \
214
+ "<script> tag on another site requested protected JavaScript. " \
215
+ "If you know what you're doing, go ahead and disable forgery " \
216
+ "protection on this action to permit cross-origin JavaScript embedding."
217
+ private_constant :CROSS_ORIGIN_JAVASCRIPT_WARNING
218
+
219
+ # If `verify_authenticity_token` was run (indicating that we have
220
+ # forgery protection enabled for this request) then also verify that
221
+ # we aren't serving an unauthorized cross-origin response.
222
+ def verify_same_origin_request
223
+ if marked_for_same_origin_verification? && non_xhr_javascript_response?
224
+ logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING if logger
225
+ raise ActionController::InvalidCrossOriginRequest, CROSS_ORIGIN_JAVASCRIPT_WARNING
178
226
  end
179
227
  end
180
228
 
229
+ # GET requests are checked for cross-origin JavaScript after rendering.
230
+ def mark_for_same_origin_verification!
231
+ @marked_for_same_origin_verification = request.get?
232
+ end
233
+
234
+ # If the `verify_authenticity_token` before_action ran, verify that
235
+ # JavaScript responses are only served to same-origin GET requests.
236
+ def marked_for_same_origin_verification?
237
+ @marked_for_same_origin_verification ||= false
238
+ end
239
+
240
+ # Check for cross-origin JavaScript responses.
241
+ def non_xhr_javascript_response?
242
+ content_type =~ %r(\Atext/javascript) && !request.xhr?
243
+ end
244
+
245
+ AUTHENTICITY_TOKEN_LENGTH = 32
246
+
181
247
  # Returns true or false if a request is verified. Checks:
182
248
  #
183
249
  # * is it a GET or HEAD request? Gets should be safe and idempotent
@@ -185,13 +251,74 @@ module ActionController #:nodoc:
185
251
  # * Does the X-CSRF-Token header match the form_authenticity_token
186
252
  def verified_request?
187
253
  !protect_against_forgery? || request.get? || request.head? ||
188
- form_authenticity_token == params[request_forgery_protection_token] ||
189
- form_authenticity_token == request.headers['X-CSRF-Token']
254
+ valid_authenticity_token?(session, form_authenticity_param) ||
255
+ valid_authenticity_token?(session, request.headers['X-CSRF-Token'])
190
256
  end
191
257
 
192
258
  # Sets the token value for the current session.
193
259
  def form_authenticity_token
194
- session[:_csrf_token] ||= SecureRandom.base64(32)
260
+ masked_authenticity_token(session)
261
+ end
262
+
263
+ # Creates a masked version of the authenticity token that varies
264
+ # on each request. The masking is used to mitigate SSL attacks
265
+ # like BREACH.
266
+ def masked_authenticity_token(session)
267
+ one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
268
+ encrypted_csrf_token = xor_byte_strings(one_time_pad, real_csrf_token(session))
269
+ masked_token = one_time_pad + encrypted_csrf_token
270
+ Base64.strict_encode64(masked_token)
271
+ end
272
+
273
+ # Checks the client's masked token to see if it matches the
274
+ # session token. Essentially the inverse of
275
+ # +masked_authenticity_token+.
276
+ def valid_authenticity_token?(session, encoded_masked_token)
277
+ if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String)
278
+ return false
279
+ end
280
+
281
+ begin
282
+ masked_token = Base64.strict_decode64(encoded_masked_token)
283
+ rescue ArgumentError # encoded_masked_token is invalid Base64
284
+ return false
285
+ end
286
+
287
+ # See if it's actually a masked token or not. In order to
288
+ # deploy this code, we should be able to handle any unmasked
289
+ # tokens that we've issued without error.
290
+
291
+ if masked_token.length == AUTHENTICITY_TOKEN_LENGTH
292
+ # This is actually an unmasked token. This is expected if
293
+ # you have just upgraded to masked tokens, but should stop
294
+ # happening shortly after installing this gem
295
+ compare_with_real_token masked_token, session
296
+
297
+ elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
298
+ # Split the token into the one-time pad and the encrypted
299
+ # value and decrypt it
300
+ one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
301
+ encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
302
+ csrf_token = xor_byte_strings(one_time_pad, encrypted_csrf_token)
303
+
304
+ compare_with_real_token csrf_token, session
305
+
306
+ else
307
+ false # Token is malformed
308
+ end
309
+ end
310
+
311
+ def compare_with_real_token(token, session)
312
+ ActiveSupport::SecurityUtils.secure_compare(token, real_csrf_token(session))
313
+ end
314
+
315
+ def real_csrf_token(session)
316
+ session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
317
+ Base64.strict_decode64(session[:_csrf_token])
318
+ end
319
+
320
+ def xor_byte_strings(s1, s2)
321
+ s1.bytes.zip(s2.bytes).map { |(c1,c2)| c1 ^ c2 }.pack('c*')
195
322
  end
196
323
 
197
324
  # The form's authenticity parameter. Override to provide your own.
@@ -199,6 +326,7 @@ module ActionController #:nodoc:
199
326
  params[request_forgery_protection_token]
200
327
  end
201
328
 
329
+ # Checks if the controller allows forgery protection.
202
330
  def protect_against_forgery?
203
331
  allow_forgery_protection
204
332
  end
@@ -183,7 +183,7 @@ module ActionController #:nodoc:
183
183
  # You may also want to configure other parameters like <tt>:tcp_nodelay</tt>.
184
184
  # Please check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen
185
185
  #
186
- # If you are using Unicorn with Nginx, you may need to tweak Nginx.
186
+ # If you are using Unicorn with NGINX, you may need to tweak NGINX.
187
187
  # Streaming should work out of the box on Rainbows.
188
188
  #
189
189
  # ==== Passenger
@@ -193,31 +193,29 @@ module ActionController #:nodoc:
193
193
  module Streaming
194
194
  extend ActiveSupport::Concern
195
195
 
196
- include AbstractController::Rendering
197
-
198
196
  protected
199
197
 
200
- # Set proper cache control and transfer encoding when streaming
201
- def _process_options(options) #:nodoc:
202
- super
203
- if options[:stream]
204
- if env["HTTP_VERSION"] == "HTTP/1.0"
205
- options.delete(:stream)
206
- else
207
- headers["Cache-Control"] ||= "no-cache"
208
- headers["Transfer-Encoding"] = "chunked"
209
- headers.delete("Content-Length")
198
+ # Set proper cache control and transfer encoding when streaming
199
+ def _process_options(options) #:nodoc:
200
+ super
201
+ if options[:stream]
202
+ if env["HTTP_VERSION"] == "HTTP/1.0"
203
+ options.delete(:stream)
204
+ else
205
+ headers["Cache-Control"] ||= "no-cache"
206
+ headers["Transfer-Encoding"] = "chunked"
207
+ headers.delete("Content-Length")
208
+ end
210
209
  end
211
210
  end
212
- end
213
211
 
214
- # Call render_body if we are streaming instead of usual +render+.
215
- def _render_template(options) #:nodoc:
216
- if options.delete(:stream)
217
- Rack::Chunked::Body.new view_renderer.render_body(view_context, options)
218
- else
219
- super
212
+ # Call render_body if we are streaming instead of usual +render+.
213
+ def _render_template(options) #:nodoc:
214
+ if options.delete(:stream)
215
+ Rack::Chunked::Body.new view_renderer.render_body(view_context, options)
216
+ else
217
+ super
218
+ end
220
219
  end
221
- end
222
220
  end
223
221
  end