actionpack 2.2.3 → 2.3.2

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 (264) hide show
  1. data/CHANGELOG +433 -375
  2. data/MIT-LICENSE +1 -1
  3. data/README +21 -75
  4. data/Rakefile +1 -1
  5. data/lib/action_controller.rb +80 -43
  6. data/lib/action_controller/assertions/model_assertions.rb +1 -0
  7. data/lib/action_controller/assertions/response_assertions.rb +43 -16
  8. data/lib/action_controller/assertions/routing_assertions.rb +1 -1
  9. data/lib/action_controller/assertions/selector_assertions.rb +17 -12
  10. data/lib/action_controller/assertions/tag_assertions.rb +1 -4
  11. data/lib/action_controller/base.rb +153 -82
  12. data/lib/action_controller/benchmarking.rb +9 -9
  13. data/lib/action_controller/caching.rb +9 -11
  14. data/lib/action_controller/caching/actions.rb +11 -18
  15. data/lib/action_controller/caching/fragments.rb +28 -20
  16. data/lib/action_controller/caching/pages.rb +13 -15
  17. data/lib/action_controller/caching/sweeping.rb +2 -2
  18. data/lib/action_controller/cgi_ext.rb +0 -1
  19. data/lib/action_controller/cgi_ext/cookie.rb +2 -0
  20. data/lib/action_controller/cgi_process.rb +54 -162
  21. data/lib/action_controller/cookies.rb +13 -25
  22. data/lib/action_controller/dispatcher.rb +43 -122
  23. data/lib/action_controller/failsafe.rb +52 -0
  24. data/lib/action_controller/flash.rb +38 -47
  25. data/lib/action_controller/helpers.rb +13 -9
  26. data/lib/action_controller/http_authentication.rb +203 -23
  27. data/lib/action_controller/integration.rb +126 -70
  28. data/lib/action_controller/layout.rb +36 -39
  29. data/lib/action_controller/middleware_stack.rb +119 -0
  30. data/lib/action_controller/middlewares.rb +13 -0
  31. data/lib/action_controller/mime_responds.rb +19 -4
  32. data/lib/action_controller/mime_type.rb +8 -0
  33. data/lib/action_controller/params_parser.rb +71 -0
  34. data/lib/action_controller/performance_test.rb +0 -1
  35. data/lib/action_controller/polymorphic_routes.rb +36 -30
  36. data/lib/action_controller/reloader.rb +14 -0
  37. data/lib/action_controller/request.rb +107 -499
  38. data/lib/action_controller/request_forgery_protection.rb +7 -39
  39. data/lib/action_controller/rescue.rb +55 -35
  40. data/lib/action_controller/resources.rb +34 -31
  41. data/lib/action_controller/response.rb +99 -57
  42. data/lib/action_controller/rewindable_input.rb +28 -0
  43. data/lib/action_controller/routing.rb +7 -7
  44. data/lib/action_controller/routing/builder.rb +4 -1
  45. data/lib/action_controller/routing/optimisations.rb +1 -1
  46. data/lib/action_controller/routing/recognition_optimisation.rb +1 -2
  47. data/lib/action_controller/routing/route.rb +15 -5
  48. data/lib/action_controller/routing/route_set.rb +82 -35
  49. data/lib/action_controller/routing/segments.rb +35 -0
  50. data/lib/action_controller/session/abstract_store.rb +181 -0
  51. data/lib/action_controller/session/cookie_store.rb +197 -175
  52. data/lib/action_controller/session/mem_cache_store.rb +36 -83
  53. data/lib/action_controller/session_management.rb +26 -134
  54. data/lib/action_controller/streaming.rb +24 -7
  55. data/lib/action_controller/templates/rescues/diagnostics.erb +2 -2
  56. data/lib/action_controller/templates/rescues/template_error.erb +2 -2
  57. data/lib/action_controller/test_case.rb +87 -30
  58. data/lib/action_controller/test_process.rb +145 -104
  59. data/lib/action_controller/uploaded_file.rb +44 -0
  60. data/lib/action_controller/url_rewriter.rb +3 -6
  61. data/lib/action_controller/vendor/html-scanner.rb +16 -0
  62. data/lib/action_controller/vendor/html-scanner/html/selector.rb +1 -1
  63. data/lib/action_controller/vendor/rack-1.0/rack.rb +89 -0
  64. data/lib/action_controller/vendor/rack-1.0/rack/adapter/camping.rb +22 -0
  65. data/lib/action_controller/vendor/rack-1.0/rack/auth/abstract/handler.rb +37 -0
  66. data/lib/action_controller/vendor/rack-1.0/rack/auth/abstract/request.rb +37 -0
  67. data/lib/action_controller/vendor/rack-1.0/rack/auth/basic.rb +58 -0
  68. data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/md5.rb +124 -0
  69. data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/nonce.rb +51 -0
  70. data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/params.rb +55 -0
  71. data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/request.rb +40 -0
  72. data/lib/action_controller/vendor/rack-1.0/rack/auth/openid.rb +480 -0
  73. data/lib/action_controller/vendor/rack-1.0/rack/builder.rb +63 -0
  74. data/lib/action_controller/vendor/rack-1.0/rack/cascade.rb +36 -0
  75. data/lib/action_controller/vendor/rack-1.0/rack/chunked.rb +49 -0
  76. data/lib/action_controller/vendor/rack-1.0/rack/commonlogger.rb +61 -0
  77. data/lib/action_controller/vendor/rack-1.0/rack/conditionalget.rb +45 -0
  78. data/lib/action_controller/vendor/rack-1.0/rack/content_length.rb +29 -0
  79. data/lib/action_controller/vendor/rack-1.0/rack/content_type.rb +23 -0
  80. data/lib/action_controller/vendor/rack-1.0/rack/deflater.rb +85 -0
  81. data/lib/action_controller/vendor/rack-1.0/rack/directory.rb +153 -0
  82. data/lib/action_controller/vendor/rack-1.0/rack/file.rb +88 -0
  83. data/lib/action_controller/vendor/rack-1.0/rack/handler.rb +48 -0
  84. data/lib/action_controller/vendor/rack-1.0/rack/handler/cgi.rb +61 -0
  85. data/lib/action_controller/vendor/rack-1.0/rack/handler/evented_mongrel.rb +8 -0
  86. data/lib/action_controller/vendor/rack-1.0/rack/handler/fastcgi.rb +89 -0
  87. data/lib/action_controller/vendor/rack-1.0/rack/handler/lsws.rb +55 -0
  88. data/lib/action_controller/vendor/rack-1.0/rack/handler/mongrel.rb +84 -0
  89. data/lib/action_controller/vendor/rack-1.0/rack/handler/scgi.rb +59 -0
  90. data/lib/action_controller/vendor/rack-1.0/rack/handler/swiftiplied_mongrel.rb +8 -0
  91. data/lib/action_controller/vendor/rack-1.0/rack/handler/thin.rb +18 -0
  92. data/lib/action_controller/vendor/rack-1.0/rack/handler/webrick.rb +67 -0
  93. data/lib/action_controller/vendor/rack-1.0/rack/head.rb +19 -0
  94. data/lib/action_controller/vendor/rack-1.0/rack/lint.rb +462 -0
  95. data/lib/action_controller/vendor/rack-1.0/rack/lobster.rb +65 -0
  96. data/lib/action_controller/vendor/rack-1.0/rack/lock.rb +16 -0
  97. data/lib/action_controller/vendor/rack-1.0/rack/methodoverride.rb +27 -0
  98. data/lib/action_controller/vendor/rack-1.0/rack/mime.rb +204 -0
  99. data/lib/action_controller/vendor/rack-1.0/rack/mock.rb +160 -0
  100. data/lib/action_controller/vendor/rack-1.0/rack/recursive.rb +57 -0
  101. data/lib/action_controller/vendor/rack-1.0/rack/reloader.rb +64 -0
  102. data/lib/action_controller/vendor/rack-1.0/rack/request.rb +241 -0
  103. data/lib/action_controller/vendor/rack-1.0/rack/response.rb +179 -0
  104. data/lib/action_controller/vendor/rack-1.0/rack/session/abstract/id.rb +142 -0
  105. data/lib/action_controller/vendor/rack-1.0/rack/session/cookie.rb +91 -0
  106. data/lib/action_controller/vendor/rack-1.0/rack/session/memcache.rb +109 -0
  107. data/lib/action_controller/vendor/rack-1.0/rack/session/pool.rb +100 -0
  108. data/lib/action_controller/vendor/rack-1.0/rack/showexceptions.rb +349 -0
  109. data/lib/action_controller/vendor/rack-1.0/rack/showstatus.rb +106 -0
  110. data/lib/action_controller/vendor/rack-1.0/rack/static.rb +38 -0
  111. data/lib/action_controller/vendor/rack-1.0/rack/urlmap.rb +55 -0
  112. data/lib/action_controller/vendor/rack-1.0/rack/utils.rb +392 -0
  113. data/lib/action_controller/verification.rb +1 -1
  114. data/lib/action_pack.rb +1 -1
  115. data/lib/action_pack/version.rb +2 -2
  116. data/lib/action_view.rb +22 -17
  117. data/lib/action_view/base.rb +53 -79
  118. data/lib/action_view/erb/util.rb +38 -0
  119. data/lib/action_view/helpers.rb +24 -5
  120. data/lib/action_view/helpers/active_record_helper.rb +2 -2
  121. data/lib/action_view/helpers/asset_tag_helper.rb +81 -50
  122. data/lib/action_view/helpers/atom_feed_helper.rb +1 -1
  123. data/lib/action_view/helpers/benchmark_helper.rb +26 -5
  124. data/lib/action_view/helpers/date_helper.rb +82 -7
  125. data/lib/action_view/helpers/form_helper.rb +295 -64
  126. data/lib/action_view/helpers/form_options_helper.rb +160 -18
  127. data/lib/action_view/helpers/form_tag_helper.rb +2 -2
  128. data/lib/action_view/helpers/number_helper.rb +31 -18
  129. data/lib/action_view/helpers/prototype_helper.rb +2 -12
  130. data/lib/action_view/helpers/sanitize_helper.rb +0 -10
  131. data/lib/action_view/helpers/scriptaculous_helper.rb +1 -0
  132. data/lib/action_view/helpers/tag_helper.rb +3 -4
  133. data/lib/action_view/helpers/text_helper.rb +99 -122
  134. data/lib/action_view/helpers/translation_helper.rb +19 -1
  135. data/lib/action_view/helpers/url_helper.rb +25 -2
  136. data/lib/action_view/inline_template.rb +1 -1
  137. data/lib/action_view/locale/en.yml +19 -1
  138. data/lib/action_view/partials.rb +46 -9
  139. data/lib/action_view/paths.rb +28 -84
  140. data/lib/action_view/reloadable_template.rb +117 -0
  141. data/lib/action_view/renderable.rb +28 -35
  142. data/lib/action_view/renderable_partial.rb +3 -4
  143. data/lib/action_view/template.rb +172 -31
  144. data/lib/action_view/template_error.rb +8 -9
  145. data/lib/action_view/template_handler.rb +1 -1
  146. data/lib/action_view/template_handlers.rb +9 -6
  147. data/lib/action_view/template_handlers/erb.rb +2 -39
  148. data/lib/action_view/template_handlers/rjs.rb +1 -0
  149. data/lib/action_view/test_case.rb +27 -1
  150. data/test/abstract_unit.rb +23 -17
  151. data/test/active_record_unit.rb +5 -4
  152. data/test/activerecord/active_record_store_test.rb +139 -106
  153. data/test/activerecord/render_partial_with_record_identification_test.rb +5 -21
  154. data/test/controller/action_pack_assertions_test.rb +25 -23
  155. data/test/controller/addresses_render_test.rb +3 -6
  156. data/test/controller/assert_select_test.rb +83 -70
  157. data/test/controller/base_test.rb +11 -13
  158. data/test/controller/benchmark_test.rb +3 -3
  159. data/test/controller/caching_test.rb +34 -24
  160. data/test/controller/capture_test.rb +3 -6
  161. data/test/controller/content_type_test.rb +3 -6
  162. data/test/controller/cookie_test.rb +31 -66
  163. data/test/controller/deprecation/deprecated_base_methods_test.rb +9 -11
  164. data/test/controller/dispatcher_test.rb +23 -28
  165. data/test/controller/fake_models.rb +8 -0
  166. data/test/controller/filters_test.rb +6 -2
  167. data/test/controller/flash_test.rb +2 -6
  168. data/test/controller/helper_test.rb +15 -1
  169. data/test/controller/html-scanner/document_test.rb +1 -1
  170. data/test/controller/html-scanner/sanitizer_test.rb +1 -1
  171. data/test/controller/http_basic_authentication_test.rb +88 -0
  172. data/test/controller/http_digest_authentication_test.rb +178 -0
  173. data/test/controller/integration_test.rb +56 -52
  174. data/test/controller/layout_test.rb +46 -44
  175. data/test/controller/middleware_stack_test.rb +90 -0
  176. data/test/controller/mime_responds_test.rb +7 -11
  177. data/test/controller/mime_type_test.rb +9 -0
  178. data/test/controller/polymorphic_routes_test.rb +235 -151
  179. data/test/controller/rack_test.rb +52 -81
  180. data/test/controller/redirect_test.rb +6 -14
  181. data/test/controller/render_test.rb +273 -60
  182. data/test/controller/request/json_params_parsing_test.rb +45 -0
  183. data/test/controller/request/multipart_params_parsing_test.rb +223 -0
  184. data/test/controller/request/query_string_parsing_test.rb +120 -0
  185. data/test/controller/request/url_encoded_params_parsing_test.rb +184 -0
  186. data/test/controller/request/xml_params_parsing_test.rb +88 -0
  187. data/test/controller/request_forgery_protection_test.rb +17 -98
  188. data/test/controller/request_test.rb +45 -530
  189. data/test/controller/rescue_test.rb +45 -22
  190. data/test/controller/resources_test.rb +112 -37
  191. data/test/controller/routing_test.rb +1442 -1384
  192. data/test/controller/selector_test.rb +3 -3
  193. data/test/controller/send_file_test.rb +30 -3
  194. data/test/controller/session/cookie_store_test.rb +169 -240
  195. data/test/controller/session/mem_cache_store_test.rb +94 -148
  196. data/test/controller/session/test_session_test.rb +58 -0
  197. data/test/controller/test_test.rb +32 -13
  198. data/test/controller/url_rewriter_test.rb +54 -4
  199. data/test/controller/verification_test.rb +1 -1
  200. data/test/controller/view_paths_test.rb +15 -15
  201. data/test/controller/webservice_test.rb +178 -147
  202. data/test/fixtures/alternate_helpers/foo_helper.rb +3 -0
  203. data/test/fixtures/layout_tests/alt/layouts/alt.rhtml +0 -0
  204. data/test/fixtures/layouts/default_html.html.erb +1 -0
  205. data/test/fixtures/layouts/xhr.html.erb +2 -0
  206. data/test/fixtures/multipart/empty +10 -0
  207. data/test/fixtures/multipart/hello.txt +1 -0
  208. data/test/fixtures/multipart/none +9 -0
  209. data/test/fixtures/public/500.da.html +1 -0
  210. data/test/fixtures/quiz/questions/_question.html.erb +1 -0
  211. data/test/fixtures/replies.yml +1 -1
  212. data/test/fixtures/test/_one.html.erb +1 -0
  213. data/test/fixtures/test/_two.html.erb +1 -0
  214. data/test/fixtures/test/dont_pick_me +1 -0
  215. data/test/fixtures/test/hello.builder +1 -1
  216. data/test/fixtures/test/hello_world.da.html.erb +1 -0
  217. data/test/fixtures/test/hello_world.erb~ +1 -0
  218. data/test/fixtures/test/hello_world.pt-BR.html.erb +1 -0
  219. data/test/fixtures/test/malformed/malformed.en.html.erb~ +1 -0
  220. data/test/fixtures/test/malformed/malformed.erb~ +1 -0
  221. data/test/fixtures/test/malformed/malformed.html.erb~ +1 -0
  222. data/test/fixtures/test/render_explicit_html_template.js.rjs +1 -0
  223. data/test/fixtures/test/render_implicit_html_template.js.rjs +1 -0
  224. data/test/fixtures/test/render_implicit_html_template_from_xhr_request.da.html.erb +1 -0
  225. data/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb +1 -0
  226. data/test/fixtures/test/render_implicit_js_template_without_layout.js.erb +1 -0
  227. data/test/fixtures/test/utf8.html.erb +2 -0
  228. data/test/template/active_record_helper_i18n_test.rb +31 -33
  229. data/test/template/active_record_helper_test.rb +34 -0
  230. data/test/template/asset_tag_helper_test.rb +52 -14
  231. data/test/template/atom_feed_helper_test.rb +3 -5
  232. data/test/template/benchmark_helper_test.rb +50 -24
  233. data/test/template/compiled_templates_test.rb +177 -33
  234. data/test/template/date_helper_i18n_test.rb +88 -81
  235. data/test/template/date_helper_test.rb +427 -43
  236. data/test/template/form_helper_test.rb +243 -44
  237. data/test/template/form_options_helper_test.rb +631 -565
  238. data/test/template/form_tag_helper_test.rb +9 -2
  239. data/test/template/javascript_helper_test.rb +0 -5
  240. data/test/template/number_helper_i18n_test.rb +60 -48
  241. data/test/template/number_helper_test.rb +1 -0
  242. data/test/template/render_test.rb +117 -35
  243. data/test/template/test_test.rb +4 -6
  244. data/test/template/text_helper_test.rb +129 -50
  245. data/test/template/translation_helper_test.rb +23 -19
  246. data/test/template/url_helper_test.rb +35 -2
  247. data/test/view/test_case_test.rb +8 -0
  248. metadata +197 -23
  249. data/lib/action_controller/assertions.rb +0 -69
  250. data/lib/action_controller/caching/sql_cache.rb +0 -18
  251. data/lib/action_controller/cgi_ext/session.rb +0 -53
  252. data/lib/action_controller/components.rb +0 -169
  253. data/lib/action_controller/rack_process.rb +0 -297
  254. data/lib/action_controller/request_profiler.rb +0 -169
  255. data/lib/action_controller/session/active_record_store.rb +0 -340
  256. data/lib/action_controller/session/drb_server.rb +0 -32
  257. data/lib/action_controller/session/drb_store.rb +0 -35
  258. data/test/controller/cgi_test.rb +0 -269
  259. data/test/controller/components_test.rb +0 -156
  260. data/test/controller/http_authentication_test.rb +0 -54
  261. data/test/controller/integration_upload_test.rb +0 -43
  262. data/test/controller/session_fixation_test.rb +0 -89
  263. data/test/controller/session_management_test.rb +0 -178
  264. data/test/fixtures/test/hello_world.js +0 -1
@@ -5,8 +5,6 @@ module ActionController #:nodoc:
5
5
  module RequestForgeryProtection
6
6
  def self.included(base)
7
7
  base.class_eval do
8
- class_inheritable_accessor :request_forgery_protection_options
9
- self.request_forgery_protection_options = {}
10
8
  helper_method :form_authenticity_token
11
9
  helper_method :protect_against_forgery?
12
10
  end
@@ -14,7 +12,7 @@ module ActionController #:nodoc:
14
12
  end
15
13
 
16
14
  # Protecting controller actions from CSRF attacks by ensuring that all forms are coming from the current web application, not a
17
- # forged link from another site, is done by embedding a token based on the session (which an attacker wouldn't know) in all
15
+ # forged link from another site, is done by embedding a token based on a random string stored in the session (which an attacker wouldn't know) in all
18
16
  # forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller. Only
19
17
  # HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication
20
18
  # scheme there anyway). Also, GET requests are not protected as these should be idempotent anyway.
@@ -57,12 +55,8 @@ module ActionController #:nodoc:
57
55
  # Example:
58
56
  #
59
57
  # class FooController < ApplicationController
60
- # # uses the cookie session store (then you don't need a separate :secret)
61
58
  # protect_from_forgery :except => :index
62
59
  #
63
- # # uses one of the other session stores that uses a session_id value.
64
- # protect_from_forgery :secret => 'my-little-pony', :except => :index
65
- #
66
60
  # # you can disable csrf protection on controller-by-controller basis:
67
61
  # skip_before_filter :verify_authenticity_token
68
62
  # end
@@ -70,13 +64,12 @@ module ActionController #:nodoc:
70
64
  # Valid Options:
71
65
  #
72
66
  # * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified.
73
- # * <tt>:secret</tt> - Custom salt used to generate the <tt>form_authenticity_token</tt>.
74
- # Leave this off if you are using the cookie session store.
75
- # * <tt>:digest</tt> - Message digest used for hashing. Defaults to 'SHA1'.
76
67
  def protect_from_forgery(options = {})
77
68
  self.request_forgery_protection_token ||= :authenticity_token
78
69
  before_filter :verify_authenticity_token, :only => options.delete(:only), :except => options.delete(:except)
79
- request_forgery_protection_options.update(options)
70
+ if options[:secret] || options[:digest]
71
+ ActiveSupport::Deprecation.warn("protect_from_forgery only takes :only and :except options now. :digest and :secret have no effect", caller)
72
+ end
80
73
  end
81
74
  end
82
75
 
@@ -90,7 +83,7 @@ module ActionController #:nodoc:
90
83
  #
91
84
  # * is the format restricted? By default, only HTML and AJAX requests are checked.
92
85
  # * is it a GET request? Gets should be safe and idempotent
93
- # * Does the form_authenticity_token match the given _token value from the params?
86
+ # * Does the form_authenticity_token match the given token value from the params?
94
87
  def verified_request?
95
88
  !protect_against_forgery? ||
96
89
  request.method == :get ||
@@ -105,34 +98,9 @@ module ActionController #:nodoc:
105
98
  # Sets the token value for the current session. Pass a <tt>:secret</tt> option
106
99
  # in +protect_from_forgery+ to add a custom salt to the hash.
107
100
  def form_authenticity_token
108
- @form_authenticity_token ||= if !session.respond_to?(:session_id)
109
- raise InvalidAuthenticityToken, "Request Forgery Protection requires a valid session. Use #allow_forgery_protection to disable it, or use a valid session."
110
- elsif request_forgery_protection_options[:secret]
111
- authenticity_token_from_session_id
112
- elsif session.respond_to?(:dbman) && session.dbman.respond_to?(:generate_digest)
113
- authenticity_token_from_cookie_session
114
- else
115
- 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)."
116
- end
117
- end
118
-
119
- # Generates a unique digest using the session_id and the CSRF secret.
120
- def authenticity_token_from_session_id
121
- key = if request_forgery_protection_options[:secret].respond_to?(:call)
122
- request_forgery_protection_options[:secret].call(@session)
123
- else
124
- request_forgery_protection_options[:secret]
125
- end
126
- digest = request_forgery_protection_options[:digest] ||= 'SHA1'
127
- OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(digest), key.to_s, session.session_id.to_s)
128
- end
129
-
130
- # No secret was given, so assume this is a cookie session store.
131
- def authenticity_token_from_cookie_session
132
- session[:csrf_id] ||= CGI::Session.generate_unique_id
133
- session.dbman.generate_digest(session[:csrf_id])
101
+ session[:_csrf_token] ||= ActiveSupport::SecureRandom.base64(32)
134
102
  end
135
-
103
+
136
104
  def protect_against_forgery?
137
105
  allow_forgery_protection && request_forgery_protection_token
138
106
  end
@@ -1,13 +1,19 @@
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
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.
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.
2
+ # Actions that fail to perform as expected throw exceptions. These
3
+ # exceptions can either be rescued for the public view (with a nice
4
+ # user-friendly explanation) or for the developers view (with tons of
5
+ # debugging information). The developers view is already implemented by
6
+ # the Action Controller, but the public view should be tailored to your
7
+ # specific application.
8
8
  #
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.
9
+ # The default behavior for public exceptions is to render a static html
10
+ # file with the name of the error code thrown. If no such file exists, an
11
+ # empty response is sent with the correct status code.
12
+ #
13
+ # You can override what constitutes a local request by overriding the
14
+ # <tt>local_request?</tt> method in your own controller. Custom rescue
15
+ # behavior is achieved by overriding the <tt>rescue_action_in_public</tt>
16
+ # and <tt>rescue_action_locally</tt> methods.
11
17
  module Rescue
12
18
  LOCALHOST = '127.0.0.1'.freeze
13
19
 
@@ -32,6 +38,9 @@ module ActionController #:nodoc:
32
38
  'ActionView::TemplateError' => 'template_error'
33
39
  }
34
40
 
41
+ RESCUES_TEMPLATE_PATH = ActionView::Template::EagerPath.new_and_loaded(
42
+ File.join(File.dirname(__FILE__), "templates"))
43
+
35
44
  def self.included(base) #:nodoc:
36
45
  base.cattr_accessor :rescue_responses
37
46
  base.rescue_responses = Hash.new(DEFAULT_RESCUE_RESPONSE)
@@ -50,47 +59,60 @@ module ActionController #:nodoc:
50
59
  end
51
60
 
52
61
  module ClassMethods
53
- def process_with_exception(request, response, exception) #:nodoc:
62
+ def call_with_exception(env, exception) #:nodoc:
63
+ request = env["action_controller.rescue.request"] ||= Request.new(env)
64
+ response = env["action_controller.rescue.response"] ||= Response.new
54
65
  new.process(request, response, :rescue_action, exception)
55
66
  end
56
67
  end
57
68
 
58
69
  protected
59
- # Exception handler called when the performance of an action raises an exception.
70
+ # Exception handler called when the performance of an action raises
71
+ # an exception.
60
72
  def rescue_action(exception)
61
- rescue_with_handler(exception) || rescue_action_without_handler(exception)
73
+ rescue_with_handler(exception) ||
74
+ rescue_action_without_handler(exception)
62
75
  end
63
76
 
64
- # Overwrite to implement custom logging of errors. By default logs as fatal.
77
+ # Overwrite to implement custom logging of errors. By default
78
+ # logs as fatal.
65
79
  def log_error(exception) #:doc:
66
80
  ActiveSupport::Deprecation.silence do
67
81
  if ActionView::TemplateError === exception
68
82
  logger.fatal(exception.to_s)
69
83
  else
70
84
  logger.fatal(
71
- "\n\n#{exception.class} (#{exception.message}):\n " +
72
- clean_backtrace(exception).join("\n ") +
73
- "\n\n"
85
+ "\n#{exception.class} (#{exception.message}):\n " +
86
+ clean_backtrace(exception).join("\n ") + "\n\n"
74
87
  )
75
88
  end
76
89
  end
77
90
  end
78
91
 
79
- # Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>). By
80
- # default will call render_optional_error_file. Override this method to provide more user friendly error messages.
92
+ # Overwrite to implement public exception handling (for requests
93
+ # answering false to <tt>local_request?</tt>). By default will call
94
+ # render_optional_error_file. Override this method to provide more
95
+ # user friendly error messages.
81
96
  def rescue_action_in_public(exception) #:doc:
82
97
  render_optional_error_file response_code_for_rescue(exception)
83
98
  end
84
-
85
- # Attempts to render a static error page based on the <tt>status_code</tt> thrown,
86
- # or just return headers if no such file exists. For example, if a 500 error is
87
- # being handled Rails will first attempt to render the file at <tt>public/500.html</tt>.
88
- # If the file doesn't exist, the body of the response will be left empty.
99
+
100
+ # Attempts to render a static error page based on the
101
+ # <tt>status_code</tt> thrown, or just return headers if no such file
102
+ # exists. At first, it will try to render a localized static page.
103
+ # For example, if a 500 error is being handled Rails and locale is :da,
104
+ # it will first attempt to render the file at <tt>public/500.da.html</tt>
105
+ # then attempt to render <tt>public/500.html</tt>. If none of them exist,
106
+ # the body of the response will be left empty.
89
107
  def render_optional_error_file(status_code)
90
108
  status = interpret_status(status_code)
109
+ locale_path = "#{Rails.public_path}/#{status[0,3]}.#{I18n.locale}.html" if I18n.locale
91
110
  path = "#{Rails.public_path}/#{status[0,3]}.html"
92
- if File.exist?(path)
93
- render :file => path, :status => status
111
+
112
+ if locale_path && File.exist?(locale_path)
113
+ render :file => locale_path, :status => status, :content_type => Mime::HTML
114
+ elsif File.exist?(path)
115
+ render :file => path, :status => status, :content_type => Mime::HTML
94
116
  else
95
117
  head status
96
118
  end
@@ -107,11 +129,13 @@ module ActionController #:nodoc:
107
129
  # a controller action.
108
130
  def rescue_action_locally(exception)
109
131
  @template.instance_variable_set("@exception", exception)
110
- @template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
111
- @template.instance_variable_set("@contents", @template.render(:file => template_path_for_local_rescue(exception)))
132
+ @template.instance_variable_set("@rescues_path", RESCUES_TEMPLATE_PATH)
133
+ @template.instance_variable_set("@contents",
134
+ @template.render(:file => template_path_for_local_rescue(exception)))
112
135
 
113
136
  response.content_type = Mime::HTML
114
- render_for_file(rescues_path("layout"), response_code_for_rescue(exception))
137
+ render_for_file(rescues_path("layout"),
138
+ response_code_for_rescue(exception))
115
139
  end
116
140
 
117
141
  def rescue_action_without_handler(exception)
@@ -139,7 +163,7 @@ module ActionController #:nodoc:
139
163
  end
140
164
 
141
165
  def rescues_path(template_name)
142
- "#{File.dirname(__FILE__)}/templates/rescues/#{template_name}.erb"
166
+ RESCUES_TEMPLATE_PATH["rescues/#{template_name}.erb"]
143
167
  end
144
168
 
145
169
  def template_path_for_local_rescue(exception)
@@ -151,13 +175,9 @@ module ActionController #:nodoc:
151
175
  end
152
176
 
153
177
  def clean_backtrace(exception)
154
- if backtrace = exception.backtrace
155
- if defined?(RAILS_ROOT)
156
- backtrace.map { |line| line.sub RAILS_ROOT, '' }
157
- else
158
- backtrace
159
- end
160
- end
178
+ defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ?
179
+ Rails.backtrace_cleaner.clean(exception.backtrace) :
180
+ exception.backtrace
161
181
  end
162
182
  end
163
183
  end
@@ -42,7 +42,7 @@ module ActionController
42
42
  #
43
43
  # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
44
44
  module Resources
45
- INHERITABLE_OPTIONS = :namespace, :shallow, :actions
45
+ INHERITABLE_OPTIONS = :namespace, :shallow
46
46
 
47
47
  class Resource #:nodoc:
48
48
  DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy
@@ -91,7 +91,7 @@ module ActionController
91
91
  end
92
92
 
93
93
  def shallow_path_prefix
94
- @shallow_path_prefix ||= "#{path_prefix unless @options[:shallow]}"
94
+ @shallow_path_prefix ||= @options[:shallow] ? @options[:namespace].try(:sub, /\/$/, '') : path_prefix
95
95
  end
96
96
 
97
97
  def member_path
@@ -103,7 +103,7 @@ module ActionController
103
103
  end
104
104
 
105
105
  def shallow_name_prefix
106
- @shallow_name_prefix ||= "#{name_prefix unless @options[:shallow]}"
106
+ @shallow_name_prefix ||= @options[:shallow] ? @options[:namespace].try(:gsub, /\//, '_') : name_prefix
107
107
  end
108
108
 
109
109
  def nesting_name_prefix
@@ -119,7 +119,7 @@ module ActionController
119
119
  end
120
120
 
121
121
  def has_action?(action)
122
- !DEFAULT_ACTIONS.include?(action) || @options[:actions].nil? || @options[:actions].include?(action)
122
+ !DEFAULT_ACTIONS.include?(action) || action_allowed?(action)
123
123
  end
124
124
 
125
125
  protected
@@ -135,24 +135,29 @@ module ActionController
135
135
  end
136
136
 
137
137
  def set_allowed_actions
138
- only = @options.delete(:only)
139
- except = @options.delete(:except)
138
+ only, except = @options.values_at(:only, :except)
139
+ @allowed_actions ||= {}
140
140
 
141
- if only && except
142
- raise ArgumentError, 'Please supply either :only or :except, not both.'
143
- elsif only == :all || except == :none
144
- options[:actions] = DEFAULT_ACTIONS
141
+ if only == :all || except == :none
142
+ only = nil
143
+ except = []
145
144
  elsif only == :none || except == :all
146
- options[:actions] = []
147
- elsif only
148
- options[:actions] = DEFAULT_ACTIONS & Array(only).map(&:to_sym)
145
+ only = []
146
+ except = nil
147
+ end
148
+
149
+ if only
150
+ @allowed_actions[:only] = Array(only).map(&:to_sym)
149
151
  elsif except
150
- options[:actions] = DEFAULT_ACTIONS - Array(except).map(&:to_sym)
151
- else
152
- # leave options[:actions] alone
152
+ @allowed_actions[:except] = Array(except).map(&:to_sym)
153
153
  end
154
154
  end
155
155
 
156
+ def action_allowed?(action)
157
+ only, except = @allowed_actions.values_at(:only, :except)
158
+ (!only || only.include?(action)) && (!except || !except.include?(action))
159
+ end
160
+
156
161
  def set_prefixes
157
162
  @path_prefix = options.delete(:path_prefix)
158
163
  @name_prefix = options.delete(:name_prefix)
@@ -283,7 +288,12 @@ module ActionController
283
288
  # * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action.
284
289
  # * <tt>:controller</tt> - Specify the controller name for the routes.
285
290
  # * <tt>:singular</tt> - Specify the singular name used in the member routes.
286
- # * <tt>:requirements</tt> - Set custom routing parameter requirements.
291
+ # * <tt>:requirements</tt> - Set custom routing parameter requirements; this is a hash of either
292
+ # regular expressions (which must match for the route to match) or extra parameters. For example:
293
+ #
294
+ # map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' }
295
+ #
296
+ # will only match if the first part is alphabetic, and will pass the parameter :extra to the controller.
287
297
  # * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes.
288
298
  # * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example:
289
299
  # # products_path == '/productos'
@@ -398,8 +408,6 @@ module ActionController
398
408
  # # --> POST /posts/1/comments (maps to the CommentsController#create action)
399
409
  # # --> PUT /posts/1/comments/1 (fails)
400
410
  #
401
- # The <tt>:only</tt> and <tt>:except</tt> options are inherited by any nested resource(s).
402
- #
403
411
  # If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied.
404
412
  #
405
413
  # Examples:
@@ -622,7 +630,7 @@ module ActionController
622
630
  action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
623
631
  action_path ||= Base.resources_path_names[action] || action
624
632
 
625
- map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m)
633
+ map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true })
626
634
  end
627
635
  end
628
636
  end
@@ -633,16 +641,14 @@ module ActionController
633
641
  map_resource_routes(map, resource, :destroy, resource.member_path, route_path)
634
642
  end
635
643
 
636
- def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil)
644
+ def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} )
637
645
  if resource.has_action?(action)
638
- action_options = action_options_for(action, resource, method)
646
+ action_options = action_options_for(action, resource, method, resource_options)
639
647
  formatted_route_path = "#{route_path}.:format"
640
648
 
641
649
  if route_name && @set.named_routes[route_name.to_sym].nil?
642
- map.named_route(route_name, route_path, action_options)
643
- map.named_route("formatted_#{route_name}", formatted_route_path, action_options)
650
+ map.named_route(route_name, formatted_route_path, action_options)
644
651
  else
645
- map.connect(route_path, action_options)
646
652
  map.connect(formatted_route_path, action_options)
647
653
  end
648
654
  end
@@ -654,9 +660,10 @@ module ActionController
654
660
  end
655
661
  end
656
662
 
657
- def action_options_for(action, resource, method = nil)
663
+ def action_options_for(action, resource, method = nil, resource_options = {})
658
664
  default_options = { :action => action.to_s }
659
665
  require_id = !resource.kind_of?(SingletonResource)
666
+ force_id = resource_options[:force_id] && !resource.kind_of?(SingletonResource)
660
667
 
661
668
  case default_options[:action]
662
669
  when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
@@ -664,12 +671,8 @@ module ActionController
664
671
  when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id))
665
672
  when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id))
666
673
  when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id))
667
- else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements)
674
+ else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(force_id))
668
675
  end
669
676
  end
670
677
  end
671
678
  end
672
-
673
- class ActionController::Routing::RouteSet::Mapper
674
- include ActionController::Resources
675
- end
@@ -1,24 +1,25 @@
1
1
  require 'digest/md5'
2
2
 
3
3
  module ActionController # :nodoc:
4
- # Represents an HTTP response generated by a controller action. One can use an
5
- # ActionController::AbstractResponse object to retrieve the current state of the
6
- # response, or customize the response. An AbstractResponse object can either
7
- # represent a "real" HTTP response (i.e. one that is meant to be sent back to the
8
- # web browser) or a test response (i.e. one that is generated from integration
9
- # tests). See CgiResponse and TestResponse, respectively.
4
+ # Represents an HTTP response generated by a controller action. One can use
5
+ # an ActionController::Response object to retrieve the current state
6
+ # of the response, or customize the response. An Response object can
7
+ # either represent a "real" HTTP response (i.e. one that is meant to be sent
8
+ # back to the web browser) or a test response (i.e. one that is generated
9
+ # from integration tests). See CgiResponse and TestResponse, respectively.
10
10
  #
11
- # AbstractResponse is mostly a Ruby on Rails framework implement detail, and should
12
- # never be used directly in controllers. Controllers should use the methods defined
13
- # in ActionController::Base instead. For example, if you want to set the HTTP
14
- # response's content MIME type, then use ActionControllerBase#headers instead of
15
- # AbstractResponse#headers.
11
+ # Response is mostly a Ruby on Rails framework implement detail, and
12
+ # should never be used directly in controllers. Controllers should use the
13
+ # methods defined in ActionController::Base instead. For example, if you want
14
+ # to set the HTTP response's content MIME type, then use
15
+ # ActionControllerBase#headers instead of Response#headers.
16
16
  #
17
- # Nevertheless, integration tests may want to inspect controller responses in more
18
- # detail, and that's when AbstractResponse can be useful for application developers.
19
- # Integration test methods such as ActionController::Integration::Session#get and
20
- # ActionController::Integration::Session#post return objects of type TestResponse
21
- # (which are of course also of type AbstractResponse).
17
+ # Nevertheless, integration tests may want to inspect controller responses in
18
+ # more detail, and that's when Response can be useful for application
19
+ # developers. Integration test methods such as
20
+ # ActionController::Integration::Session#get and
21
+ # ActionController::Integration::Session#post return objects of type
22
+ # TestResponse (which are of course also of type Response).
22
23
  #
23
24
  # For example, the following demo integration "test" prints the body of the
24
25
  # controller response to the console:
@@ -29,25 +30,25 @@ module ActionController # :nodoc:
29
30
  # puts @response.body
30
31
  # end
31
32
  # end
32
- class AbstractResponse
33
+ class Response < Rack::Response
33
34
  DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
34
35
  attr_accessor :request
35
36
 
36
- # The body content (e.g. HTML) of the response, as a String.
37
- attr_accessor :body
38
- # The headers of the response, as a Hash. It maps header names to header values.
39
- attr_accessor :headers
40
- attr_accessor :session, :cookies, :assigns, :template, :layout
37
+ attr_accessor :session, :assigns, :template, :layout
41
38
  attr_accessor :redirected_to, :redirected_to_method_params
42
39
 
43
40
  delegate :default_charset, :to => 'ActionController::Base'
44
41
 
45
42
  def initialize
46
- @body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], []
47
- end
43
+ @status = 200
44
+ @header = Rack::Utils::HeaderHash.new(DEFAULT_HEADERS)
45
+
46
+ @writer = lambda { |x| @body << x }
47
+ @block = nil
48
48
 
49
- def status; headers['Status'] end
50
- def status=(status) headers['Status'] = status end
49
+ @body = "",
50
+ @session, @assigns = [], []
51
+ end
51
52
 
52
53
  def location; headers['Location'] end
53
54
  def location=(url) headers['Location'] = url end
@@ -109,13 +110,17 @@ module ActionController # :nodoc:
109
110
  def etag
110
111
  headers['ETag']
111
112
  end
112
-
113
+
113
114
  def etag?
114
115
  headers.include?('ETag')
115
116
  end
116
-
117
+
117
118
  def etag=(etag)
118
- headers['ETag'] = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}")
119
+ if etag.blank?
120
+ headers.delete('ETag')
121
+ else
122
+ headers['ETag'] = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}")
123
+ end
119
124
  end
120
125
 
121
126
  def redirect(url, status)
@@ -138,26 +143,58 @@ module ActionController # :nodoc:
138
143
  handle_conditional_get!
139
144
  set_content_length!
140
145
  convert_content_type!
146
+ convert_language!
147
+ convert_cookies!
148
+ end
149
+
150
+ def each(&callback)
151
+ if @body.respond_to?(:call)
152
+ @writer = lambda { |x| callback.call(x) }
153
+ @body.call(self, self)
154
+ elsif @body.is_a?(String)
155
+ @body.each_line(&callback)
156
+ else
157
+ @body.each(&callback)
158
+ end
159
+
160
+ @writer = callback
161
+ @block.call(self) if @block
162
+ end
163
+
164
+ def write(str)
165
+ @writer.call str.to_s
166
+ str
167
+ end
168
+
169
+ def set_cookie(key, value)
170
+ if value.has_key?(:http_only)
171
+ ActiveSupport::Deprecation.warn(
172
+ "The :http_only option in ActionController::Response#set_cookie " +
173
+ "has been renamed. Please use :httponly instead.", caller)
174
+ value[:httponly] ||= value.delete(:http_only)
175
+ end
176
+
177
+ super(key, value)
141
178
  end
142
179
 
143
180
  private
144
- def handle_conditional_get!
145
- if etag? || last_modified?
146
- set_conditional_cache_control!
147
- elsif nonempty_ok_response?
148
- self.etag = body
149
-
150
- if request && request.etag_matches?(etag)
151
- self.status = '304 Not Modified'
152
- self.body = ''
153
- end
154
-
155
- set_conditional_cache_control!
156
- end
181
+ def handle_conditional_get!
182
+ if etag? || last_modified?
183
+ set_conditional_cache_control!
184
+ elsif nonempty_ok_response?
185
+ self.etag = body
186
+
187
+ if request && request.etag_matches?(etag)
188
+ self.status = '304 Not Modified'
189
+ self.body = ''
190
+ end
191
+
192
+ set_conditional_cache_control!
193
+ end
157
194
  end
158
195
 
159
196
  def nonempty_ok_response?
160
- ok = !status || status[0..2] == '200'
197
+ ok = !status || status.to_s[0..2] == '200'
161
198
  ok && body.is_a?(String) && !body.empty?
162
199
  end
163
200
 
@@ -168,23 +205,28 @@ module ActionController # :nodoc:
168
205
  end
169
206
 
170
207
  def convert_content_type!
171
- if content_type = headers.delete("Content-Type")
172
- self.headers["type"] = content_type
173
- end
174
- if content_type = headers.delete("Content-type")
175
- self.headers["type"] = content_type
176
- end
177
- if content_type = headers.delete("content-type")
178
- self.headers["type"] = content_type
179
- end
208
+ headers['Content-Type'] ||= "text/html"
209
+ headers['Content-Type'] += "; charset=" + headers.delete('charset') if headers['charset']
180
210
  end
181
-
182
- # Don't set the Content-Length for block-based bodies as that would mean reading it all into memory. Not nice
183
- # for, say, a 2GB streaming file.
211
+
212
+ # Don't set the Content-Length for block-based bodies as that would mean
213
+ # reading it all into memory. Not nice for, say, a 2GB streaming file.
184
214
  def set_content_length!
185
- unless body.respond_to?(:call) || (status && status[0..2] == '304')
186
- self.headers["Content-Length"] ||= body.size
215
+ if status && status.to_s[0..2] == '204'
216
+ headers.delete('Content-Length')
217
+ elsif length = headers['Content-Length']
218
+ headers['Content-Length'] = length.to_s
219
+ elsif !body.respond_to?(:call) && (!status || status.to_s[0..2] != '304')
220
+ headers["Content-Length"] = (body.respond_to?(:bytesize) ? body.bytesize : body.size).to_s
187
221
  end
188
222
  end
223
+
224
+ def convert_language!
225
+ headers["Content-Language"] = headers.delete("language") if headers["language"]
226
+ end
227
+
228
+ def convert_cookies!
229
+ headers['Set-Cookie'] = Array(headers['Set-Cookie']).compact
230
+ end
189
231
  end
190
232
  end