actionpack 1.13.6 → 2.0.0

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 (317) hide show
  1. data/CHANGELOG +1400 -20
  2. data/MIT-LICENSE +1 -1
  3. data/README +5 -5
  4. data/RUNNING_UNIT_TESTS +4 -5
  5. data/Rakefile +5 -6
  6. data/install.rb +2 -2
  7. data/lib/action_controller.rb +11 -15
  8. data/lib/action_controller/assertions.rb +12 -25
  9. data/lib/action_controller/assertions/dom_assertions.rb +18 -4
  10. data/lib/action_controller/assertions/model_assertions.rb +8 -1
  11. data/lib/action_controller/assertions/response_assertions.rb +35 -12
  12. data/lib/action_controller/assertions/routing_assertions.rb +56 -12
  13. data/lib/action_controller/assertions/selector_assertions.rb +105 -38
  14. data/lib/action_controller/assertions/tag_assertions.rb +28 -15
  15. data/lib/action_controller/base.rb +318 -250
  16. data/lib/action_controller/benchmarking.rb +33 -29
  17. data/lib/action_controller/caching.rb +130 -64
  18. data/lib/action_controller/cgi_ext.rb +16 -0
  19. data/lib/action_controller/cgi_ext/{cookie_performance_fix.rb → cookie.rb} +25 -40
  20. data/lib/action_controller/cgi_ext/query_extension.rb +22 -0
  21. data/lib/action_controller/cgi_ext/session.rb +73 -0
  22. data/lib/action_controller/cgi_ext/stdinput.rb +23 -0
  23. data/lib/action_controller/cgi_process.rb +34 -57
  24. data/lib/action_controller/components.rb +19 -36
  25. data/lib/action_controller/cookies.rb +10 -9
  26. data/lib/action_controller/dispatcher.rb +195 -0
  27. data/lib/action_controller/filters.rb +35 -34
  28. data/lib/action_controller/flash.rb +30 -35
  29. data/lib/action_controller/helpers.rb +121 -47
  30. data/lib/action_controller/http_authentication.rb +126 -0
  31. data/lib/action_controller/integration.rb +105 -101
  32. data/lib/action_controller/layout.rb +59 -47
  33. data/lib/action_controller/mime_responds.rb +57 -68
  34. data/lib/action_controller/mime_type.rb +43 -80
  35. data/lib/action_controller/mime_types.rb +20 -0
  36. data/lib/action_controller/polymorphic_routes.rb +88 -0
  37. data/lib/action_controller/record_identifier.rb +91 -0
  38. data/lib/action_controller/request.rb +553 -88
  39. data/lib/action_controller/request_forgery_protection.rb +126 -0
  40. data/lib/action_controller/request_profiler.rb +138 -0
  41. data/lib/action_controller/rescue.rb +185 -69
  42. data/lib/action_controller/resources.rb +211 -172
  43. data/lib/action_controller/response.rb +49 -8
  44. data/lib/action_controller/routing.rb +359 -236
  45. data/lib/action_controller/routing_optimisation.rb +119 -0
  46. data/lib/action_controller/session/active_record_store.rb +3 -2
  47. data/lib/action_controller/session/cookie_store.rb +161 -0
  48. data/lib/action_controller/session/mem_cache_store.rb +9 -16
  49. data/lib/action_controller/session_management.rb +17 -8
  50. data/lib/action_controller/streaming.rb +6 -3
  51. data/lib/action_controller/templates/rescues/_request_and_response.erb +24 -0
  52. data/lib/action_controller/templates/rescues/{_trace.rhtml → _trace.erb} +0 -0
  53. data/lib/action_controller/templates/rescues/{diagnostics.rhtml → diagnostics.erb} +2 -2
  54. data/lib/action_controller/templates/rescues/{layout.rhtml → layout.erb} +0 -0
  55. data/lib/action_controller/templates/rescues/{missing_template.rhtml → missing_template.erb} +0 -0
  56. data/lib/action_controller/templates/rescues/{routing_error.rhtml → routing_error.erb} +0 -0
  57. data/lib/action_controller/templates/rescues/{template_error.rhtml → template_error.erb} +2 -2
  58. data/lib/action_controller/templates/rescues/{unknown_action.rhtml → unknown_action.erb} +0 -0
  59. data/lib/action_controller/test_case.rb +53 -0
  60. data/lib/action_controller/test_process.rb +59 -46
  61. data/lib/action_controller/url_rewriter.rb +48 -24
  62. data/lib/action_controller/vendor/html-scanner/html/document.rb +7 -4
  63. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +173 -0
  64. data/lib/action_controller/vendor/html-scanner/html/selector.rb +11 -6
  65. data/lib/action_controller/verification.rb +27 -21
  66. data/lib/action_pack.rb +1 -1
  67. data/lib/action_pack/version.rb +4 -4
  68. data/lib/action_view.rb +2 -3
  69. data/lib/action_view/base.rb +218 -63
  70. data/lib/action_view/compiled_templates.rb +1 -2
  71. data/lib/action_view/helpers/active_record_helper.rb +35 -17
  72. data/lib/action_view/helpers/asset_tag_helper.rb +395 -87
  73. data/lib/action_view/helpers/atom_feed_helper.rb +111 -0
  74. data/lib/action_view/helpers/benchmark_helper.rb +12 -5
  75. data/lib/action_view/helpers/cache_helper.rb +29 -0
  76. data/lib/action_view/helpers/capture_helper.rb +97 -63
  77. data/lib/action_view/helpers/date_helper.rb +295 -35
  78. data/lib/action_view/helpers/debug_helper.rb +6 -2
  79. data/lib/action_view/helpers/form_helper.rb +354 -111
  80. data/lib/action_view/helpers/form_options_helper.rb +171 -109
  81. data/lib/action_view/helpers/form_tag_helper.rb +332 -76
  82. data/lib/action_view/helpers/javascript_helper.rb +35 -11
  83. data/lib/action_view/helpers/javascripts/controls.js +484 -354
  84. data/lib/action_view/helpers/javascripts/dragdrop.js +88 -58
  85. data/lib/action_view/helpers/javascripts/effects.js +396 -364
  86. data/lib/action_view/helpers/javascripts/prototype.js +2817 -1107
  87. data/lib/action_view/helpers/number_helper.rb +84 -60
  88. data/lib/action_view/helpers/prototype_helper.rb +419 -43
  89. data/lib/action_view/helpers/record_identification_helper.rb +20 -0
  90. data/lib/action_view/helpers/record_tag_helper.rb +59 -0
  91. data/lib/action_view/helpers/sanitize_helper.rb +223 -0
  92. data/lib/action_view/helpers/scriptaculous_helper.rb +63 -4
  93. data/lib/action_view/helpers/tag_helper.rb +69 -39
  94. data/lib/action_view/helpers/text_helper.rb +221 -148
  95. data/lib/action_view/helpers/url_helper.rb +283 -165
  96. data/lib/action_view/partials.rb +134 -62
  97. data/lib/action_view/template_error.rb +4 -12
  98. data/lib/actionpack.rb +1 -0
  99. data/test/abstract_unit.rb +21 -1
  100. data/test/action_view_test.rb +26 -0
  101. data/test/active_record_unit.rb +12 -20
  102. data/test/activerecord/active_record_store_test.rb +2 -2
  103. data/test/activerecord/render_partial_with_record_identification_test.rb +74 -0
  104. data/test/controller/action_pack_assertions_test.rb +21 -152
  105. data/test/controller/addresses_render_test.rb +2 -7
  106. data/test/controller/assert_select_test.rb +120 -14
  107. data/test/controller/base_test.rb +11 -13
  108. data/test/controller/caching_test.rb +125 -5
  109. data/test/controller/capture_test.rb +23 -16
  110. data/test/controller/cgi_test.rb +66 -391
  111. data/test/controller/components_test.rb +31 -42
  112. data/test/controller/content_type_test.rb +1 -1
  113. data/test/controller/cookie_test.rb +42 -14
  114. data/test/controller/deprecation/deprecated_base_methods_test.rb +1 -42
  115. data/test/controller/dispatcher_test.rb +123 -0
  116. data/test/controller/fake_models.rb +5 -0
  117. data/test/controller/filters_test.rb +44 -7
  118. data/test/controller/flash_test.rb +46 -2
  119. data/test/controller/fragment_store_setting_test.rb +10 -8
  120. data/test/controller/helper_test.rb +19 -2
  121. data/test/controller/html-scanner/document_test.rb +124 -0
  122. data/test/controller/html-scanner/node_test.rb +69 -0
  123. data/test/controller/html-scanner/sanitizer_test.rb +250 -0
  124. data/test/controller/html-scanner/tag_node_test.rb +239 -0
  125. data/test/controller/html-scanner/text_node_test.rb +51 -0
  126. data/test/controller/html-scanner/tokenizer_test.rb +125 -0
  127. data/test/controller/http_authentication_test.rb +54 -0
  128. data/test/controller/integration_test.rb +12 -26
  129. data/test/controller/layout_test.rb +64 -12
  130. data/test/controller/mime_responds_test.rb +193 -38
  131. data/test/controller/mime_type_test.rb +30 -8
  132. data/test/controller/new_render_test.rb +104 -22
  133. data/test/controller/polymorphic_routes_test.rb +98 -0
  134. data/test/controller/record_identifier_test.rb +103 -0
  135. data/test/controller/redirect_test.rb +120 -18
  136. data/test/controller/render_test.rb +195 -45
  137. data/test/controller/request_forgery_protection_test.rb +217 -0
  138. data/test/controller/request_test.rb +545 -27
  139. data/test/controller/rescue_test.rb +501 -0
  140. data/test/controller/resources_test.rb +258 -132
  141. data/test/controller/routing_test.rb +502 -106
  142. data/test/controller/selector_test.rb +5 -5
  143. data/test/controller/send_file_test.rb +17 -7
  144. data/test/controller/session/cookie_store_test.rb +246 -0
  145. data/test/controller/session/mem_cache_store_test.rb +182 -0
  146. data/test/controller/session_fixation_test.rb +8 -11
  147. data/test/controller/session_management_test.rb +7 -7
  148. data/test/controller/test_test.rb +150 -38
  149. data/test/controller/url_rewriter_test.rb +87 -12
  150. data/test/controller/verification_test.rb +11 -0
  151. data/test/controller/view_paths_test.rb +137 -0
  152. data/test/controller/webservice_test.rb +11 -75
  153. data/test/fixtures/addresses/{list.rhtml → list.erb} +0 -0
  154. data/test/fixtures/db_definitions/sqlite.sql +2 -1
  155. data/test/fixtures/developer.rb +2 -0
  156. data/test/fixtures/fun/games/{hello_world.rhtml → hello_world.erb} +0 -0
  157. data/test/fixtures/helpers/fun/pdf_helper.rb +1 -1
  158. data/test/fixtures/layout_tests/alt/hello.rhtml +1 -0
  159. data/test/fixtures/layout_tests/layouts/multiple_extensions.html.erb +1 -0
  160. data/test/fixtures/layouts/{builder.rxml → builder.builder} +0 -0
  161. data/test/fixtures/layouts/{standard.rhtml → standard.erb} +0 -0
  162. data/test/fixtures/layouts/{talk_from_action.rhtml → talk_from_action.erb} +0 -0
  163. data/test/fixtures/layouts/{yield.rhtml → yield.erb} +0 -0
  164. data/test/fixtures/multipart/binary_file +0 -0
  165. data/test/fixtures/multipart/bracketed_param +5 -0
  166. data/test/fixtures/override/test/hello_world.erb +1 -0
  167. data/test/fixtures/override2/layouts/test/sub.erb +1 -0
  168. data/test/fixtures/post_test/layouts/post.html.erb +1 -0
  169. data/test/fixtures/post_test/layouts/super_post.iphone.erb +1 -0
  170. data/test/fixtures/post_test/post/index.html.erb +1 -0
  171. data/test/fixtures/post_test/post/index.iphone.erb +1 -0
  172. data/test/fixtures/post_test/super_post/index.html.erb +1 -0
  173. data/test/fixtures/post_test/super_post/index.iphone.erb +1 -0
  174. data/test/fixtures/public/404.html +1 -0
  175. data/test/fixtures/public/500.html +1 -0
  176. data/test/fixtures/public/javascripts/application.js +0 -1
  177. data/test/fixtures/public/javascripts/bank.js +1 -0
  178. data/test/fixtures/public/javascripts/robber.js +1 -0
  179. data/test/fixtures/public/stylesheets/bank.css +1 -0
  180. data/test/fixtures/public/stylesheets/robber.css +1 -0
  181. data/test/fixtures/replies.yml +2 -0
  182. data/test/fixtures/reply.rb +2 -1
  183. data/test/fixtures/respond_to/{all_types_with_layout.rhtml → all_types_with_layout.html.erb} +0 -0
  184. data/test/fixtures/respond_to/{all_types_with_layout.rjs → all_types_with_layout.js.rjs} +0 -0
  185. data/test/fixtures/respond_to/custom_constant_handling_without_block.mobile.erb +1 -0
  186. data/test/fixtures/respond_to/iphone_with_html_response_type.html.erb +1 -0
  187. data/test/fixtures/respond_to/iphone_with_html_response_type.iphone.erb +1 -0
  188. data/test/fixtures/respond_to/layouts/missing.html.erb +1 -0
  189. data/test/fixtures/respond_to/layouts/standard.html.erb +1 -0
  190. data/test/fixtures/respond_to/layouts/standard.iphone.erb +1 -0
  191. data/test/fixtures/respond_to/{using_defaults.rhtml → using_defaults.html.erb} +0 -0
  192. data/test/fixtures/respond_to/{using_defaults.rjs → using_defaults.js.rjs} +0 -0
  193. data/test/fixtures/respond_to/{using_defaults.rxml → using_defaults.xml.builder} +0 -0
  194. data/test/fixtures/respond_to/{using_defaults_with_type_list.rhtml → using_defaults_with_type_list.html.erb} +0 -0
  195. data/test/fixtures/respond_to/{using_defaults_with_type_list.rjs → using_defaults_with_type_list.js.rjs} +0 -0
  196. data/test/fixtures/respond_to/{using_defaults_with_type_list.rxml → using_defaults_with_type_list.xml.builder} +0 -0
  197. data/test/fixtures/scope/test/{modgreet.rhtml → modgreet.erb} +0 -0
  198. data/test/fixtures/test/{_customer.rhtml → _customer.erb} +0 -0
  199. data/test/fixtures/test/{_customer_greeting.rhtml → _customer_greeting.erb} +0 -0
  200. data/test/fixtures/test/_hash_greeting.erb +1 -0
  201. data/test/fixtures/test/_hash_object.erb +2 -0
  202. data/test/fixtures/test/{_hello.rxml → _hello.builder} +0 -0
  203. data/test/fixtures/test/_layout_for_partial.html.erb +3 -0
  204. data/test/fixtures/test/_partial.erb +1 -0
  205. data/test/fixtures/test/_partial.html.erb +1 -0
  206. data/test/fixtures/test/_partial.js.erb +1 -0
  207. data/test/fixtures/test/_partial_for_use_in_layout.html.erb +1 -0
  208. data/test/fixtures/test/{_partial_only.rhtml → _partial_only.erb} +0 -0
  209. data/test/fixtures/test/{_person.rhtml → _person.erb} +0 -0
  210. data/test/fixtures/test/{action_talk_to_layout.rhtml → action_talk_to_layout.erb} +0 -0
  211. data/test/fixtures/test/{block_content_for.rhtml → block_content_for.erb} +0 -0
  212. data/test/fixtures/test/calling_partial_with_layout.html.erb +1 -0
  213. data/test/fixtures/test/{capturing.rhtml → capturing.erb} +0 -0
  214. data/test/fixtures/test/{content_for.rhtml → content_for.erb} +0 -0
  215. data/test/fixtures/test/content_for_concatenated.erb +3 -0
  216. data/test/fixtures/test/content_for_with_parameter.erb +2 -0
  217. data/test/fixtures/test/dot.directory/{render_file_with_ivar.rhtml → render_file_with_ivar.erb} +0 -0
  218. data/test/fixtures/test/{erb_content_for.rhtml → erb_content_for.erb} +0 -0
  219. data/test/fixtures/test/formatted_html_erb.html.erb +1 -0
  220. data/test/fixtures/test/formatted_xml_erb.builder +1 -0
  221. data/test/fixtures/test/formatted_xml_erb.html.erb +1 -0
  222. data/test/fixtures/test/formatted_xml_erb.xml.erb +1 -0
  223. data/test/fixtures/test/{greeting.rhtml → greeting.erb} +0 -0
  224. data/test/fixtures/test/{hello.rxml → hello.builder} +0 -0
  225. data/test/fixtures/test/{hello_world.rxml → hello_world.builder} +0 -0
  226. data/test/fixtures/test/{hello_world.rhtml → hello_world.erb} +0 -0
  227. data/test/fixtures/test/{hello_world_container.rxml → hello_world_container.builder} +0 -0
  228. data/test/fixtures/test/{hello_world_with_layout_false.rhtml → hello_world_with_layout_false.erb} +0 -0
  229. data/test/fixtures/test/{hello_xml_world.rxml → hello_xml_world.builder} +0 -0
  230. data/test/fixtures/test/list.erb +1 -0
  231. data/test/fixtures/test/{non_erb_block_content_for.rxml → non_erb_block_content_for.builder} +0 -0
  232. data/test/fixtures/test/{potential_conflicts.rhtml → potential_conflicts.erb} +0 -0
  233. data/test/fixtures/test/{render_file_with_ivar.rhtml → render_file_with_ivar.erb} +0 -0
  234. data/test/fixtures/test/{render_file_with_locals.rhtml → render_file_with_locals.erb} +0 -0
  235. data/test/fixtures/test/{render_to_string_test.rhtml → render_to_string_test.erb} +0 -0
  236. data/test/fixtures/test/{update_element_with_capture.rhtml → update_element_with_capture.erb} +0 -0
  237. data/test/fixtures/test/using_layout_around_block.html.erb +1 -0
  238. data/test/fixtures/topic.rb +1 -1
  239. data/test/template/active_record_helper_test.rb +67 -20
  240. data/test/template/asset_tag_helper_test.rb +222 -54
  241. data/test/template/atom_feed_helper_test.rb +101 -0
  242. data/test/template/benchmark_helper_test.rb +2 -2
  243. data/test/template/compiled_templates_test.rb +76 -32
  244. data/test/template/date_helper_test.rb +125 -9
  245. data/test/template/form_helper_test.rb +326 -33
  246. data/test/template/form_options_helper_test.rb +822 -15
  247. data/test/template/form_tag_helper_test.rb +96 -30
  248. data/test/template/javascript_helper_test.rb +61 -13
  249. data/test/template/number_helper_test.rb +12 -11
  250. data/test/template/prototype_helper_test.rb +185 -24
  251. data/test/template/sanitize_helper_test.rb +49 -0
  252. data/test/template/scriptaculous_helper_test.rb +9 -3
  253. data/test/template/tag_helper_test.rb +13 -2
  254. data/test/template/text_helper_test.rb +38 -52
  255. data/test/template/url_helper_test.rb +216 -46
  256. metadata +144 -116
  257. data/examples/.htaccess +0 -24
  258. data/examples/address_book/index.rhtml +0 -33
  259. data/examples/address_book/layout.rhtml +0 -8
  260. data/examples/address_book_controller.cgi +0 -9
  261. data/examples/address_book_controller.fcgi +0 -6
  262. data/examples/address_book_controller.rb +0 -52
  263. data/examples/address_book_controller.rbx +0 -4
  264. data/examples/benchmark.rb +0 -52
  265. data/examples/benchmark_with_ar.fcgi +0 -89
  266. data/examples/blog_controller.cgi +0 -53
  267. data/examples/debate/index.rhtml +0 -14
  268. data/examples/debate/new_topic.rhtml +0 -22
  269. data/examples/debate/topic.rhtml +0 -32
  270. data/examples/debate_controller.cgi +0 -57
  271. data/lib/action_controller/assertions/deprecated_assertions.rb +0 -228
  272. data/lib/action_controller/cgi_ext/cgi_ext.rb +0 -36
  273. data/lib/action_controller/cgi_ext/cgi_methods.rb +0 -211
  274. data/lib/action_controller/cgi_ext/pstore_performance_fix.rb +0 -30
  275. data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +0 -95
  276. data/lib/action_controller/cgi_ext/session_performance_fix.rb +0 -30
  277. data/lib/action_controller/deprecated_dependencies.rb +0 -65
  278. data/lib/action_controller/deprecated_redirects.rb +0 -17
  279. data/lib/action_controller/deprecated_request_methods.rb +0 -34
  280. data/lib/action_controller/macros/auto_complete.rb +0 -53
  281. data/lib/action_controller/macros/in_place_editing.rb +0 -33
  282. data/lib/action_controller/pagination.rb +0 -408
  283. data/lib/action_controller/scaffolding.rb +0 -189
  284. data/lib/action_controller/templates/rescues/_request_and_response.rhtml +0 -44
  285. data/lib/action_controller/templates/scaffolds/edit.rhtml +0 -7
  286. data/lib/action_controller/templates/scaffolds/layout.rhtml +0 -69
  287. data/lib/action_controller/templates/scaffolds/list.rhtml +0 -27
  288. data/lib/action_controller/templates/scaffolds/new.rhtml +0 -6
  289. data/lib/action_controller/templates/scaffolds/show.rhtml +0 -9
  290. data/lib/action_controller/vendor/xml_node.rb +0 -97
  291. data/lib/action_view/helpers/deprecated_helper.rb +0 -37
  292. data/lib/action_view/helpers/java_script_macros_helper.rb +0 -233
  293. data/lib/action_view/helpers/pagination_helper.rb +0 -86
  294. data/test/activerecord/active_record_assertions_test.rb +0 -92
  295. data/test/activerecord/pagination_test.rb +0 -165
  296. data/test/controller/deprecated_instance_variables_test.rb +0 -48
  297. data/test/controller/raw_post_test.rb +0 -68
  298. data/test/fixtures/deprecated_instance_variables/_cookies_ivar.rhtml +0 -1
  299. data/test/fixtures/deprecated_instance_variables/_cookies_method.rhtml +0 -1
  300. data/test/fixtures/deprecated_instance_variables/_flash_ivar.rhtml +0 -1
  301. data/test/fixtures/deprecated_instance_variables/_flash_method.rhtml +0 -1
  302. data/test/fixtures/deprecated_instance_variables/_headers_ivar.rhtml +0 -1
  303. data/test/fixtures/deprecated_instance_variables/_headers_method.rhtml +0 -1
  304. data/test/fixtures/deprecated_instance_variables/_params_ivar.rhtml +0 -1
  305. data/test/fixtures/deprecated_instance_variables/_params_method.rhtml +0 -1
  306. data/test/fixtures/deprecated_instance_variables/_request_ivar.rhtml +0 -1
  307. data/test/fixtures/deprecated_instance_variables/_request_method.rhtml +0 -1
  308. data/test/fixtures/deprecated_instance_variables/_response_ivar.rhtml +0 -1
  309. data/test/fixtures/deprecated_instance_variables/_response_method.rhtml +0 -1
  310. data/test/fixtures/deprecated_instance_variables/_session_ivar.rhtml +0 -1
  311. data/test/fixtures/deprecated_instance_variables/_session_method.rhtml +0 -1
  312. data/test/fixtures/respond_to/layouts/standard.rhtml +0 -1
  313. data/test/fixtures/test/_hash_object.rhtml +0 -1
  314. data/test/fixtures/test/list.rhtml +0 -1
  315. data/test/template/deprecated_helper_test.rb +0 -36
  316. data/test/template/deprecated_instance_variables_test.rb +0 -43
  317. data/test/template/java_script_macros_helper_test.rb +0 -109
@@ -0,0 +1,126 @@
1
+ module ActionController #:nodoc:
2
+ class InvalidAuthenticityToken < ActionControllerError #:nodoc:
3
+ end
4
+
5
+ module RequestForgeryProtection
6
+ def self.included(base)
7
+ base.class_eval do
8
+ class_inheritable_accessor :request_forgery_protection_options
9
+ self.request_forgery_protection_options = {}
10
+ helper_method :form_authenticity_token
11
+ helper_method :protect_against_forgery?
12
+ end
13
+ base.extend(ClassMethods)
14
+ end
15
+
16
+ module ClassMethods
17
+ # Protect a controller's actions from CSRF attacks by ensuring that all forms are coming from the current web application, not
18
+ # a forged link from another site. This is done by embedding a token based on the session (which an attacker wouldn't know) in
19
+ # all forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller. Only
20
+ # HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication
21
+ # scheme there anyway). Also, GET requests are not protected as these should be indempotent anyway.
22
+ #
23
+ # You turn this on with the #protect_from_forgery method, which will perform the check and raise
24
+ # an ActionController::InvalidAuthenticityToken if the token doesn't match what was expected. And it will add
25
+ # a _authenticity_token parameter to all forms that are automatically generated by Rails. You can customize the error message
26
+ # given through public/422.html.
27
+ #
28
+ # Learn more about CSRF (Cross-Site Request Forgery) attacks:
29
+ #
30
+ # * http://isc.sans.org/diary.html?storyid=1750
31
+ # * http://en.wikipedia.org/wiki/Cross-site_request_forgery
32
+ #
33
+ # Keep in mind, this is NOT a silver-bullet, plug 'n' play, warm security blanket for your rails application.
34
+ # There are a few guidelines you should follow:
35
+ #
36
+ # * Keep your GET requests safe and idempotent. More reading material:
37
+ # * http://www.xml.com/pub/a/2002/04/24/deviant.html
38
+ # * http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
39
+ # * Make sure the session cookies that Rails creates are non-persistent. Check in Firefox and look for "Expires: at end of session"
40
+ #
41
+ # If you need to construct a request yourself, but still want to take advantage of forgery protection, you can grab the
42
+ # authenticity_token using the form_authenticity_token helper method and make it part of the parameters yourself.
43
+ #
44
+ # Example:
45
+ #
46
+ # class FooController < ApplicationController
47
+ # # uses the cookie session store (then you don't need a separate :secret)
48
+ # protect_from_forgery :except => :index
49
+ #
50
+ # # uses one of the other session stores that uses a session_id value.
51
+ # protect_from_forgery :secret => 'my-little-pony', :except => :index
52
+ #
53
+ # # you can disable csrf protection on controller-by-controller basis:
54
+ # skip_before_filter :verify_authenticity_token
55
+ # end
56
+ #
57
+ # Valid Options:
58
+ #
59
+ # * <tt>:only/:except</tt> - passed to the before_filter call. Set which actions are verified.
60
+ # * <tt>:secret</tt> - Custom salt used to generate the form_authenticity_token.
61
+ # Leave this off if you are using the cookie session store.
62
+ # * <tt>:digest</tt> - Message digest used for hashing. Defaults to 'SHA1'
63
+ def protect_from_forgery(options = {})
64
+ self.request_forgery_protection_token ||= :authenticity_token
65
+ before_filter :verify_authenticity_token, :only => options.delete(:only), :except => options.delete(:except)
66
+ request_forgery_protection_options.update(options)
67
+ end
68
+ end
69
+
70
+ protected
71
+ # The actual before_filter that is used. Modify this to change how you handle unverified requests.
72
+ def verify_authenticity_token
73
+ verified_request? || raise(ActionController::InvalidAuthenticityToken)
74
+ end
75
+
76
+ # Returns true or false if a request is verified. Checks:
77
+ #
78
+ # * is the format restricted? By default, only HTML and AJAX requests are checked.
79
+ # * is it a GET request? Gets should be safe and idempotent
80
+ # * Does the form_authenticity_token match the given _token value from the params?
81
+ def verified_request?
82
+ !protect_against_forgery? ||
83
+ request.method == :get ||
84
+ !verifiable_request_format? ||
85
+ form_authenticity_token == params[request_forgery_protection_token]
86
+ end
87
+
88
+ def verifiable_request_format?
89
+ request.format.html? || request.format.js?
90
+ end
91
+
92
+ # Sets the token value for the current session. Pass a :secret option in #protect_from_forgery to add a custom salt to the hash.
93
+ def form_authenticity_token
94
+ @form_authenticity_token ||= if request_forgery_protection_options[:secret]
95
+ authenticity_token_from_session_id
96
+ elsif session.respond_to?(:dbman) && session.dbman.respond_to?(:generate_digest)
97
+ authenticity_token_from_cookie_session
98
+ elsif session.nil?
99
+ raise InvalidAuthenticityToken, "Request Forgery Protection requires a valid session. Use #allow_forgery_protection to disable it, or use a valid session."
100
+ else
101
+ raise InvalidAuthenticityToken, "No :secret given to the #protect_from_forgery call. Set that or use a session store capable of generating its own keys (Cookie Session Store)."
102
+ end
103
+ end
104
+
105
+ # Generates a unique digest using the session_id and the CSRF secret.
106
+ def authenticity_token_from_session_id
107
+ key = if request_forgery_protection_options[:secret].respond_to?(:call)
108
+ request_forgery_protection_options[:secret].call(@session)
109
+ else
110
+ request_forgery_protection_options[:secret]
111
+ end
112
+ digest = request_forgery_protection_options[:digest] ||= 'SHA1'
113
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(digest), key.to_s, session.session_id.to_s)
114
+ end
115
+
116
+ # No secret was given, so assume this is a cookie session store.
117
+ def authenticity_token_from_cookie_session
118
+ session[:csrf_id] ||= CGI::Session.generate_unique_id
119
+ session.dbman.generate_digest(session[:csrf_id])
120
+ end
121
+
122
+ def protect_against_forgery?
123
+ allow_forgery_protection && request_forgery_protection_token
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,138 @@
1
+ require 'optparse'
2
+ require 'action_controller/integration'
3
+
4
+ module ActionController
5
+ class RequestProfiler
6
+ # Wrap up the integration session runner.
7
+ class Sandbox
8
+ include Integration::Runner
9
+
10
+ def self.benchmark(n, script)
11
+ new(script).benchmark(n)
12
+ end
13
+
14
+ def initialize(script_path)
15
+ @quiet = false
16
+ define_run_method(File.read(script_path))
17
+ reset!
18
+ end
19
+
20
+ def benchmark(n)
21
+ @quiet = true
22
+ print ' '
23
+ result = Benchmark.realtime do
24
+ n.times do |i|
25
+ run
26
+ print i % 10 == 0 ? 'x' : '.'
27
+ $stdout.flush
28
+ end
29
+ end
30
+ puts
31
+ result
32
+ ensure
33
+ @quiet = false
34
+ end
35
+
36
+ def say(message)
37
+ puts " #{message}" unless @quiet
38
+ end
39
+
40
+ private
41
+ def define_run_method(script)
42
+ instance_eval "def run; #{script}; end", __FILE__, __LINE__
43
+ end
44
+ end
45
+
46
+
47
+ attr_reader :options
48
+
49
+ def initialize(options = {})
50
+ @options = default_options.merge(options)
51
+ end
52
+
53
+
54
+ def self.run(args = nil, options = {})
55
+ profiler = new(options)
56
+ profiler.parse_options(args) if args
57
+ profiler.run
58
+ end
59
+
60
+ def run
61
+ sandbox = Sandbox.new(options[:script])
62
+
63
+ puts 'Warming up once'
64
+
65
+ elapsed = warmup(sandbox)
66
+ puts '%.2f sec, %d requests, %d req/sec' % [elapsed, sandbox.request_count, sandbox.request_count / elapsed]
67
+ puts "\n#{options[:benchmark] ? 'Benchmarking' : 'Profiling'} #{options[:n]}x"
68
+
69
+ options[:benchmark] ? benchmark(sandbox) : profile(sandbox)
70
+ end
71
+
72
+ def profile(sandbox)
73
+ load_ruby_prof
74
+
75
+ results = RubyProf.profile { benchmark(sandbox) }
76
+
77
+ show_profile_results results
78
+ results
79
+ end
80
+
81
+ def benchmark(sandbox)
82
+ sandbox.request_count = 0
83
+ elapsed = sandbox.benchmark(options[:n]).to_f
84
+ count = sandbox.request_count.to_i
85
+ puts '%.2f sec, %d requests, %d req/sec' % [elapsed, count, count / elapsed]
86
+ end
87
+
88
+ def warmup(sandbox)
89
+ Benchmark.realtime { sandbox.run }
90
+ end
91
+
92
+ def default_options
93
+ { :n => 100, :open => 'open %s &' }
94
+ end
95
+
96
+ # Parse command-line options
97
+ def parse_options(args)
98
+ OptionParser.new do |opt|
99
+ opt.banner = "USAGE: #{$0} [options] [session script path]"
100
+
101
+ opt.on('-n', '--times [0000]', 'How many requests to process. Defaults to 100.') { |v| options[:n] = v.to_i }
102
+ opt.on('-b', '--benchmark', 'Benchmark instead of profiling') { |v| options[:benchmark] = v }
103
+ opt.on('--open [CMD]', 'Command to open profile results. Defaults to "open %s &"') { |v| options[:open] = v }
104
+ opt.on('-h', '--help', 'Show this help') { puts opt; exit }
105
+
106
+ opt.parse args
107
+
108
+ if args.empty?
109
+ puts opt
110
+ exit
111
+ end
112
+ options[:script] = args.pop
113
+ end
114
+ end
115
+
116
+ protected
117
+ def load_ruby_prof
118
+ begin
119
+ require 'ruby-prof'
120
+ #RubyProf.measure_mode = RubyProf::ALLOCATED_OBJECTS
121
+ rescue LoadError
122
+ abort '`gem install ruby-prof` to use the profiler'
123
+ end
124
+ end
125
+
126
+ def show_profile_results(results)
127
+ File.open "#{RAILS_ROOT}/tmp/profile-graph.html", 'w' do |file|
128
+ RubyProf::GraphHtmlPrinter.new(results).print(file)
129
+ `#{options[:open] % file.path}` if options[:open]
130
+ end
131
+
132
+ File.open "#{RAILS_ROOT}/tmp/profile-flat.txt", 'w' do |file|
133
+ RubyProf::FlatPrinter.new(results).print(file)
134
+ `#{options[:open] % file.path}` if options[:open]
135
+ end
136
+ end
137
+ end
138
+ end
@@ -1,22 +1,110 @@
1
1
  module ActionController #:nodoc:
2
- # Actions that fail to perform as expected throw exceptions. These exceptions can either be rescued for the public view
2
+ # Actions that fail to perform as expected throw exceptions. These exceptions can either be rescued for the public view
3
3
  # (with a nice user-friendly explanation) or for the developers view (with tons of debugging information). The developers view
4
- # is already implemented by the Action Controller, but the public view should be tailored to your specific application. So too
5
- # could the decision on whether something is a public or a developer request.
4
+ # is already implemented by the Action Controller, but the public view should be tailored to your specific application.
5
+ #
6
+ # The default behavior for public exceptions is to render a static html file with the name of the error code thrown. If no such
7
+ # file exists, an empty response is sent with the correct status code.
6
8
  #
7
- # You can tailor the rescuing behavior and appearance by overwriting the following two stub methods.
9
+ # You can override what constitutes a local request by overriding the <tt>local_request?</tt> method in your own controller.
10
+ # Custom rescue behavior is achieved by overriding the <tt>rescue_action_in_public</tt> and <tt>rescue_action_locally</tt> methods.
8
11
  module Rescue
12
+ LOCALHOST = '127.0.0.1'.freeze
13
+
14
+ DEFAULT_RESCUE_RESPONSE = :internal_server_error
15
+ DEFAULT_RESCUE_RESPONSES = {
16
+ 'ActionController::RoutingError' => :not_found,
17
+ 'ActionController::UnknownAction' => :not_found,
18
+ 'ActiveRecord::RecordNotFound' => :not_found,
19
+ 'ActiveRecord::StaleObjectError' => :conflict,
20
+ 'ActiveRecord::RecordInvalid' => :unprocessable_entity,
21
+ 'ActiveRecord::RecordNotSaved' => :unprocessable_entity,
22
+ 'ActionController::MethodNotAllowed' => :method_not_allowed,
23
+ 'ActionController::NotImplemented' => :not_implemented,
24
+ 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
25
+ }
26
+
27
+ DEFAULT_RESCUE_TEMPLATE = 'diagnostics'
28
+ DEFAULT_RESCUE_TEMPLATES = {
29
+ 'ActionController::MissingTemplate' => 'missing_template',
30
+ 'ActionController::RoutingError' => 'routing_error',
31
+ 'ActionController::UnknownAction' => 'unknown_action',
32
+ 'ActionView::TemplateError' => 'template_error'
33
+ }
34
+
9
35
  def self.included(base) #:nodoc:
36
+ base.cattr_accessor :rescue_responses
37
+ base.rescue_responses = Hash.new(DEFAULT_RESCUE_RESPONSE)
38
+ base.rescue_responses.update DEFAULT_RESCUE_RESPONSES
39
+
40
+ base.cattr_accessor :rescue_templates
41
+ base.rescue_templates = Hash.new(DEFAULT_RESCUE_TEMPLATE)
42
+ base.rescue_templates.update DEFAULT_RESCUE_TEMPLATES
43
+
44
+ base.class_inheritable_array :rescue_handlers
45
+ base.rescue_handlers = []
46
+
10
47
  base.extend(ClassMethods)
11
48
  base.class_eval do
12
49
  alias_method_chain :perform_action, :rescue
13
50
  end
14
51
  end
15
52
 
16
- module ClassMethods #:nodoc:
17
- def process_with_exception(request, response, exception)
53
+ module ClassMethods
54
+ def process_with_exception(request, response, exception) #:nodoc:
18
55
  new.process(request, response, :rescue_action, exception)
19
56
  end
57
+
58
+ # Rescue exceptions raised in controller actions.
59
+ #
60
+ # <tt>rescue_from</tt> receives a series of exception classes or class
61
+ # names, and a trailing :with option with the name of a method or a Proc
62
+ # object to be called to handle them. Alternatively a block can be given.
63
+ #
64
+ # Handlers that take one argument will be called with the exception, so
65
+ # that the exception can be inspected when dealing with it.
66
+ #
67
+ # Handlers are inherited. They are searched from right to left, from
68
+ # bottom to top, and up the hierarchy. The handler of the first class for
69
+ # which exception.is_a?(klass) holds true is the one invoked, if any.
70
+ #
71
+ # class ApplicationController < ActionController::Base
72
+ # rescue_from User::NotAuthorized, :with => :deny_access # self defined exception
73
+ # rescue_from ActiveRecord::RecordInvalid, :with => :show_errors
74
+ #
75
+ # rescue_from 'MyAppError::Base' do |exception|
76
+ # render :xml => exception, :status => 500
77
+ # end
78
+ #
79
+ # protected
80
+ # def deny_access
81
+ # ...
82
+ # end
83
+ #
84
+ # def show_errors(exception)
85
+ # exception.record.new_record? ? ...
86
+ # end
87
+ # end
88
+ def rescue_from(*klasses, &block)
89
+ options = klasses.extract_options!
90
+ unless options.has_key?(:with)
91
+ block_given? ? options[:with] = block : raise(ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument.")
92
+ end
93
+
94
+ klasses.each do |klass|
95
+ key = if klass.is_a?(Class) && klass <= Exception
96
+ klass.name
97
+ elsif klass.is_a?(String)
98
+ klass
99
+ else
100
+ raise(ArgumentError, "#{klass} is neither an Exception nor a String")
101
+ end
102
+
103
+ # Order is important, we put the pair at the end. When dealing with an
104
+ # exception we will follow the documented order going from right to left.
105
+ rescue_handlers << [key, options[:with]]
106
+ end
107
+ end
20
108
  end
21
109
 
22
110
  protected
@@ -25,6 +113,12 @@ module ActionController #:nodoc:
25
113
  log_error(exception) if logger
26
114
  erase_results if performed?
27
115
 
116
+ # Let the exception alter the response if it wants.
117
+ # For example, MethodNotAllowed sets the Allow header.
118
+ if exception.respond_to?(:handle_response!)
119
+ exception.handle_response!(response)
120
+ end
121
+
28
122
  if consider_all_requests_local || local_request?
29
123
  rescue_action_locally(exception)
30
124
  else
@@ -47,96 +141,118 @@ module ActionController #:nodoc:
47
141
  end
48
142
  end
49
143
 
50
- # Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>).
144
+ # Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>). By
145
+ # default will call render_optional_error_file. Override this method to provide more user friendly error messages.s
51
146
  def rescue_action_in_public(exception) #:doc:
52
- case exception
53
- when RoutingError, UnknownAction
54
- render_text(IO.read(File.join(RAILS_ROOT, 'public', '404.html')), "404 Not Found")
55
- else
56
- render_text(IO.read(File.join(RAILS_ROOT, 'public', '500.html')), "500 Internal Error")
147
+ render_optional_error_file response_code_for_rescue(exception)
148
+ end
149
+
150
+ # Attempts to render a static error page based on the <tt>status_code</tt> thrown,
151
+ # or just return headers if no such file exists. For example, if a 500 error is
152
+ # being handled Rails will first attempt to render the file at <tt>public/500.html</tt>.
153
+ # If the file doesn't exist, the body of the response will be left empty.
154
+ def render_optional_error_file(status_code)
155
+ status = interpret_status(status_code)
156
+ path = "#{RAILS_ROOT}/public/#{status[0,3]}.html"
157
+ if File.exists?(path)
158
+ render :file => path, :status => status
159
+ else
160
+ head status
57
161
  end
58
162
  end
59
163
 
60
- # Overwrite to expand the meaning of a local request in order to show local rescues on other occurrences than
61
- # the remote IP being 127.0.0.1. For example, this could include the IP of the developer machine when debugging
62
- # remotely.
164
+ # True if the request came from localhost, 127.0.0.1. Override this
165
+ # method if you wish to redefine the meaning of a local request to
166
+ # include remote IP addresses or other criteria.
63
167
  def local_request? #:doc:
64
- [request.remote_addr, request.remote_ip] == ["127.0.0.1"] * 2
168
+ request.remote_addr == LOCALHOST and request.remote_ip == LOCALHOST
65
169
  end
66
170
 
67
- # Renders a detailed diagnostics screen on action exceptions.
171
+ # Render detailed diagnostics for unhandled exceptions rescued from
172
+ # a controller action.
68
173
  def rescue_action_locally(exception)
69
174
  add_variables_to_assigns
70
175
  @template.instance_variable_set("@exception", exception)
71
- @template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
72
- @template.send(:assign_variables_from_controller)
176
+ @template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
177
+ @template.send!(:assign_variables_from_controller)
73
178
 
74
179
  @template.instance_variable_set("@contents", @template.render_file(template_path_for_local_rescue(exception), false))
75
-
180
+
76
181
  response.content_type = Mime::HTML
77
- render_file(rescues_path("layout"), response_code_for_rescue(exception))
182
+ render_for_file(rescues_path("layout"), response_code_for_rescue(exception))
78
183
  end
79
-
80
- private
81
- def perform_action_with_rescue #:nodoc:
82
- begin
83
- perform_action_without_rescue
84
- rescue Exception => exception # errors from action performed
85
- if defined?(Breakpoint) && params["BP-RETRY"]
86
- msg = exception.backtrace.first
87
- if md = /^(.+?):(\d+)(?::in `(.+)')?$/.match(msg) then
88
- origin_file, origin_line = md[1], md[2].to_i
89
-
90
- set_trace_func(lambda do |type, file, line, method, context, klass|
91
- if file == origin_file and line == origin_line then
92
- set_trace_func(nil)
93
- params["BP-RETRY"] = false
94
-
95
- callstack = caller
96
- callstack.slice!(0) if callstack.first["rescue.rb"]
97
- file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
98
-
99
- message = "Exception at #{file}:#{line}#{" in `#{method}'" if method}." # `� ( for ruby-mode)
100
-
101
- Breakpoint.handle_breakpoint(context, message, file, line)
102
- end
103
- end)
104
-
105
- retry
106
- end
107
- end
108
184
 
109
- rescue_action(exception)
185
+ # Tries to rescue the exception by looking up and calling a registered handler.
186
+ def rescue_action_with_handler(exception)
187
+ if handler = handler_for_rescue(exception)
188
+ if handler.arity != 0
189
+ handler.call(exception)
190
+ else
191
+ handler.call
192
+ end
193
+ true # don't rely on the return value of the handler
110
194
  end
111
195
  end
112
196
 
197
+ private
198
+ def perform_action_with_rescue #:nodoc:
199
+ perform_action_without_rescue
200
+ rescue Exception => exception # errors from action performed
201
+ return if rescue_action_with_handler(exception)
202
+
203
+ rescue_action(exception)
204
+ end
205
+
113
206
  def rescues_path(template_name)
114
- File.dirname(__FILE__) + "/templates/rescues/#{template_name}.rhtml"
207
+ "#{File.dirname(__FILE__)}/templates/rescues/#{template_name}.erb"
115
208
  end
116
209
 
117
210
  def template_path_for_local_rescue(exception)
118
- rescues_path(
119
- case exception
120
- when MissingTemplate then "missing_template"
121
- when RoutingError then "routing_error"
122
- when UnknownAction then "unknown_action"
123
- when ActionView::TemplateError then "template_error"
124
- else "diagnostics"
125
- end
126
- )
211
+ rescues_path(rescue_templates[exception.class.name])
127
212
  end
128
-
213
+
129
214
  def response_code_for_rescue(exception)
130
- case exception
131
- when UnknownAction, RoutingError
132
- "404 Page Not Found"
133
- else
134
- "500 Internal Error"
215
+ rescue_responses[exception.class.name]
216
+ end
217
+
218
+ def handler_for_rescue(exception)
219
+ # We go from right to left because pairs are pushed onto rescue_handlers
220
+ # as rescue_from declarations are found.
221
+ _, handler = *rescue_handlers.reverse.detect do |klass_name, handler|
222
+ # The purpose of allowing strings in rescue_from is to support the
223
+ # declaration of handler associations for exception classes whose
224
+ # definition is yet unknown.
225
+ #
226
+ # Since this loop needs the constants it would be inconsistent to
227
+ # assume they should exist at this point. An early raised exception
228
+ # could trigger some other handler and the array could include
229
+ # precisely a string whose corresponding constant has not yet been
230
+ # seen. This is why we are tolerant to unknown constants.
231
+ #
232
+ # Note that this tolerance only matters if the exception was given as
233
+ # a string, otherwise a NameError will be raised by the interpreter
234
+ # itself when rescue_from CONSTANT is executed.
235
+ klass = self.class.const_get(klass_name) rescue nil
236
+ klass ||= klass_name.constantize rescue nil
237
+ exception.is_a?(klass) if klass
238
+ end
239
+
240
+ case handler
241
+ when Symbol
242
+ method(handler)
243
+ when Proc
244
+ handler.bind(self)
135
245
  end
136
246
  end
137
-
247
+
138
248
  def clean_backtrace(exception)
139
- exception.backtrace.collect { |line| Object.const_defined?(:RAILS_ROOT) ? line.gsub(RAILS_ROOT, "") : line }
249
+ if backtrace = exception.backtrace
250
+ if defined?(RAILS_ROOT)
251
+ backtrace.map { |line| line.sub RAILS_ROOT, '' }
252
+ else
253
+ backtrace
254
+ end
255
+ end
140
256
  end
141
257
  end
142
258
  end