eactionpack 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) hide show
  1. data/CHANGELOG +7 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +469 -0
  4. data/RUNNING_UNIT_TESTS +24 -0
  5. data/Rakefile +146 -0
  6. data/install.rb +30 -0
  7. data/lib/action_controller.rb +79 -0
  8. data/lib/action_controller/assertions.rb +69 -0
  9. data/lib/action_controller/assertions/dom_assertions.rb +39 -0
  10. data/lib/action_controller/assertions/model_assertions.rb +20 -0
  11. data/lib/action_controller/assertions/response_assertions.rb +172 -0
  12. data/lib/action_controller/assertions/routing_assertions.rb +146 -0
  13. data/lib/action_controller/assertions/selector_assertions.rb +491 -0
  14. data/lib/action_controller/assertions/tag_assertions.rb +130 -0
  15. data/lib/action_controller/base.rb +1288 -0
  16. data/lib/action_controller/benchmarking.rb +94 -0
  17. data/lib/action_controller/caching.rb +72 -0
  18. data/lib/action_controller/caching/actions.rb +144 -0
  19. data/lib/action_controller/caching/fragments.rb +138 -0
  20. data/lib/action_controller/caching/pages.rb +154 -0
  21. data/lib/action_controller/caching/sql_cache.rb +18 -0
  22. data/lib/action_controller/caching/sweeping.rb +97 -0
  23. data/lib/action_controller/cgi_ext.rb +16 -0
  24. data/lib/action_controller/cgi_ext/cookie.rb +110 -0
  25. data/lib/action_controller/cgi_ext/query_extension.rb +22 -0
  26. data/lib/action_controller/cgi_ext/session.rb +73 -0
  27. data/lib/action_controller/cgi_ext/stdinput.rb +24 -0
  28. data/lib/action_controller/cgi_process.rb +223 -0
  29. data/lib/action_controller/components.rb +166 -0
  30. data/lib/action_controller/cookies.rb +96 -0
  31. data/lib/action_controller/dispatcher.rb +162 -0
  32. data/lib/action_controller/filters.rb +642 -0
  33. data/lib/action_controller/flash.rb +172 -0
  34. data/lib/action_controller/headers.rb +31 -0
  35. data/lib/action_controller/helpers.rb +221 -0
  36. data/lib/action_controller/http_authentication.rb +124 -0
  37. data/lib/action_controller/integration.rb +634 -0
  38. data/lib/action_controller/layout.rb +309 -0
  39. data/lib/action_controller/mime_responds.rb +173 -0
  40. data/lib/action_controller/mime_type.rb +186 -0
  41. data/lib/action_controller/mime_types.rb +20 -0
  42. data/lib/action_controller/polymorphic_routes.rb +191 -0
  43. data/lib/action_controller/record_identifier.rb +102 -0
  44. data/lib/action_controller/request.rb +764 -0
  45. data/lib/action_controller/request_forgery_protection.rb +140 -0
  46. data/lib/action_controller/request_profiler.rb +169 -0
  47. data/lib/action_controller/rescue.rb +258 -0
  48. data/lib/action_controller/resources.rb +572 -0
  49. data/lib/action_controller/response.rb +76 -0
  50. data/lib/action_controller/routing.rb +387 -0
  51. data/lib/action_controller/routing/builder.rb +203 -0
  52. data/lib/action_controller/routing/optimisations.rb +120 -0
  53. data/lib/action_controller/routing/recognition_optimisation.rb +162 -0
  54. data/lib/action_controller/routing/route.rb +240 -0
  55. data/lib/action_controller/routing/route_set.rb +436 -0
  56. data/lib/action_controller/routing/routing_ext.rb +46 -0
  57. data/lib/action_controller/routing/segments.rb +283 -0
  58. data/lib/action_controller/session/active_record_store.rb +340 -0
  59. data/lib/action_controller/session/cookie_store.rb +166 -0
  60. data/lib/action_controller/session/drb_server.rb +32 -0
  61. data/lib/action_controller/session/drb_store.rb +35 -0
  62. data/lib/action_controller/session/mem_cache_store.rb +98 -0
  63. data/lib/action_controller/session_management.rb +158 -0
  64. data/lib/action_controller/status_codes.rb +88 -0
  65. data/lib/action_controller/streaming.rb +155 -0
  66. data/lib/action_controller/templates/rescues/_request_and_response.erb +24 -0
  67. data/lib/action_controller/templates/rescues/_trace.erb +26 -0
  68. data/lib/action_controller/templates/rescues/diagnostics.erb +11 -0
  69. data/lib/action_controller/templates/rescues/layout.erb +29 -0
  70. data/lib/action_controller/templates/rescues/missing_template.erb +2 -0
  71. data/lib/action_controller/templates/rescues/routing_error.erb +10 -0
  72. data/lib/action_controller/templates/rescues/template_error.erb +21 -0
  73. data/lib/action_controller/templates/rescues/unknown_action.erb +2 -0
  74. data/lib/action_controller/test_case.rb +83 -0
  75. data/lib/action_controller/test_process.rb +526 -0
  76. data/lib/action_controller/url_rewriter.rb +142 -0
  77. data/lib/action_controller/vendor/html-scanner/html/document.rb +68 -0
  78. data/lib/action_controller/vendor/html-scanner/html/node.rb +537 -0
  79. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +173 -0
  80. data/lib/action_controller/vendor/html-scanner/html/selector.rb +828 -0
  81. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +105 -0
  82. data/lib/action_controller/vendor/html-scanner/html/version.rb +11 -0
  83. data/lib/action_controller/verification.rb +130 -0
  84. data/lib/action_pack.rb +24 -0
  85. data/lib/action_pack/version.rb +9 -0
  86. data/lib/action_view.rb +44 -0
  87. data/lib/action_view/base.rb +335 -0
  88. data/lib/action_view/helpers/active_record_helper.rb +276 -0
  89. data/lib/action_view/helpers/asset_tag_helper.rb +599 -0
  90. data/lib/action_view/helpers/atom_feed_helper.rb +143 -0
  91. data/lib/action_view/helpers/benchmark_helper.rb +33 -0
  92. data/lib/action_view/helpers/cache_helper.rb +40 -0
  93. data/lib/action_view/helpers/capture_helper.rb +161 -0
  94. data/lib/action_view/helpers/date_helper.rb +711 -0
  95. data/lib/action_view/helpers/debug_helper.rb +31 -0
  96. data/lib/action_view/helpers/form_helper.rb +767 -0
  97. data/lib/action_view/helpers/form_options_helper.rb +458 -0
  98. data/lib/action_view/helpers/form_tag_helper.rb +458 -0
  99. data/lib/action_view/helpers/javascript_helper.rb +148 -0
  100. data/lib/action_view/helpers/number_helper.rb +186 -0
  101. data/lib/action_view/helpers/record_identification_helper.rb +20 -0
  102. data/lib/action_view/helpers/record_tag_helper.rb +59 -0
  103. data/lib/action_view/helpers/sanitize_helper.rb +229 -0
  104. data/lib/action_view/helpers/tag_helper.rb +134 -0
  105. data/lib/action_view/helpers/text_helper.rb +507 -0
  106. data/lib/action_view/helpers/url_helper.rb +573 -0
  107. data/lib/action_view/inline_template.rb +20 -0
  108. data/lib/action_view/partial_template.rb +70 -0
  109. data/lib/action_view/partials.rb +158 -0
  110. data/lib/action_view/template.rb +125 -0
  111. data/lib/action_view/template_error.rb +110 -0
  112. data/lib/action_view/template_finder.rb +176 -0
  113. data/lib/action_view/template_handler.rb +34 -0
  114. data/lib/action_view/template_handlers/builder.rb +27 -0
  115. data/lib/action_view/template_handlers/compilable.rb +128 -0
  116. data/lib/action_view/template_handlers/erb.rb +56 -0
  117. data/lib/action_view/test_case.rb +58 -0
  118. data/lib/actionpack.rb +1 -0
  119. data/test/abstract_unit.rb +36 -0
  120. data/test/active_record_unit.rb +105 -0
  121. data/test/activerecord/active_record_store_test.rb +141 -0
  122. data/test/activerecord/render_partial_with_record_identification_test.rb +191 -0
  123. data/test/adv_attr_test.rb +20 -0
  124. data/test/controller/action_pack_assertions_test.rb +543 -0
  125. data/test/controller/addresses_render_test.rb +43 -0
  126. data/test/controller/assert_select_test.rb +331 -0
  127. data/test/controller/base_test.rb +219 -0
  128. data/test/controller/benchmark_test.rb +32 -0
  129. data/test/controller/caching_test.rb +581 -0
  130. data/test/controller/capture_test.rb +89 -0
  131. data/test/controller/cgi_test.rb +116 -0
  132. data/test/controller/components_test.rb +140 -0
  133. data/test/controller/content_type_test.rb +139 -0
  134. data/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb +0 -0
  135. data/test/controller/controller_fixtures/app/controllers/user_controller.rb +0 -0
  136. data/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb +0 -0
  137. data/test/controller/cookie_test.rb +146 -0
  138. data/test/controller/custom_handler_test.rb +45 -0
  139. data/test/controller/deprecation/deprecated_base_methods_test.rb +37 -0
  140. data/test/controller/dispatcher_test.rb +105 -0
  141. data/test/controller/fake_controllers.rb +33 -0
  142. data/test/controller/fake_models.rb +11 -0
  143. data/test/controller/filter_params_test.rb +49 -0
  144. data/test/controller/filters_test.rb +881 -0
  145. data/test/controller/flash_test.rb +146 -0
  146. data/test/controller/header_test.rb +14 -0
  147. data/test/controller/helper_test.rb +210 -0
  148. data/test/controller/html-scanner/cdata_node_test.rb +15 -0
  149. data/test/controller/html-scanner/document_test.rb +148 -0
  150. data/test/controller/html-scanner/node_test.rb +89 -0
  151. data/test/controller/html-scanner/sanitizer_test.rb +269 -0
  152. data/test/controller/html-scanner/tag_node_test.rb +238 -0
  153. data/test/controller/html-scanner/text_node_test.rb +50 -0
  154. data/test/controller/html-scanner/tokenizer_test.rb +131 -0
  155. data/test/controller/http_authentication_test.rb +54 -0
  156. data/test/controller/integration_test.rb +252 -0
  157. data/test/controller/integration_upload_test.rb +43 -0
  158. data/test/controller/layout_test.rb +255 -0
  159. data/test/controller/mime_responds_test.rb +514 -0
  160. data/test/controller/mime_type_test.rb +84 -0
  161. data/test/controller/new_render_test.rb +843 -0
  162. data/test/controller/polymorphic_routes_test.rb +174 -0
  163. data/test/controller/record_identifier_test.rb +139 -0
  164. data/test/controller/redirect_test.rb +289 -0
  165. data/test/controller/render_test.rb +484 -0
  166. data/test/controller/request_forgery_protection_test.rb +305 -0
  167. data/test/controller/request_test.rb +928 -0
  168. data/test/controller/rescue_test.rb +517 -0
  169. data/test/controller/resources_test.rb +873 -0
  170. data/test/controller/routing_test.rb +2464 -0
  171. data/test/controller/selector_test.rb +628 -0
  172. data/test/controller/send_file_test.rb +138 -0
  173. data/test/controller/session/cookie_store_test.rb +258 -0
  174. data/test/controller/session/mem_cache_store_test.rb +181 -0
  175. data/test/controller/session_fixation_test.rb +89 -0
  176. data/test/controller/session_management_test.rb +178 -0
  177. data/test/controller/test_test.rb +695 -0
  178. data/test/controller/url_rewriter_test.rb +310 -0
  179. data/test/controller/verification_test.rb +270 -0
  180. data/test/controller/view_paths_test.rb +140 -0
  181. data/test/controller/webservice_test.rb +229 -0
  182. data/test/fixtures/addresses/list.erb +1 -0
  183. data/test/fixtures/bad_customers/_bad_customer.html.erb +1 -0
  184. data/test/fixtures/companies.yml +24 -0
  185. data/test/fixtures/company.rb +10 -0
  186. data/test/fixtures/content_type/render_default_content_types_for_respond_to.rhtml +1 -0
  187. data/test/fixtures/content_type/render_default_for_js.js.erb +1 -0
  188. data/test/fixtures/content_type/render_default_for_rhtml.rhtml +1 -0
  189. data/test/fixtures/content_type/render_default_for_rxml.rxml +1 -0
  190. data/test/fixtures/customers/_customer.html.erb +1 -0
  191. data/test/fixtures/db_definitions/sqlite.sql +49 -0
  192. data/test/fixtures/developer.rb +9 -0
  193. data/test/fixtures/developers.yml +21 -0
  194. data/test/fixtures/developers_projects.yml +13 -0
  195. data/test/fixtures/fun/games/hello_world.erb +1 -0
  196. data/test/fixtures/functional_caching/_partial.erb +3 -0
  197. data/test/fixtures/functional_caching/fragment_cached.html.erb +2 -0
  198. data/test/fixtures/functional_caching/html_fragment_cached_with_partial.html.erb +1 -0
  199. data/test/fixtures/functional_caching/js_fragment_cached_with_partial.js.rjs +1 -0
  200. data/test/fixtures/good_customers/_good_customer.html.erb +1 -0
  201. data/test/fixtures/helpers/abc_helper.rb +5 -0
  202. data/test/fixtures/helpers/fun/games_helper.rb +3 -0
  203. data/test/fixtures/helpers/fun/pdf_helper.rb +3 -0
  204. data/test/fixtures/layout_tests/alt/hello.rhtml +1 -0
  205. data/test/fixtures/layout_tests/layouts/controller_name_space/nested.rhtml +1 -0
  206. data/test/fixtures/layout_tests/layouts/item.rhtml +1 -0
  207. data/test/fixtures/layout_tests/layouts/layout_test.rhtml +1 -0
  208. data/test/fixtures/layout_tests/layouts/multiple_extensions.html.erb +1 -0
  209. data/test/fixtures/layout_tests/layouts/third_party_template_library.mab +1 -0
  210. data/test/fixtures/layout_tests/views/hello.rhtml +1 -0
  211. data/test/fixtures/layouts/block_with_layout.erb +3 -0
  212. data/test/fixtures/layouts/builder.builder +3 -0
  213. data/test/fixtures/layouts/partial_with_layout.erb +3 -0
  214. data/test/fixtures/layouts/standard.erb +1 -0
  215. data/test/fixtures/layouts/talk_from_action.erb +2 -0
  216. data/test/fixtures/layouts/yield.erb +2 -0
  217. data/test/fixtures/mascot.rb +3 -0
  218. data/test/fixtures/mascots.yml +4 -0
  219. data/test/fixtures/mascots/_mascot.html.erb +1 -0
  220. data/test/fixtures/multipart/binary_file +0 -0
  221. data/test/fixtures/multipart/boundary_problem_file +10 -0
  222. data/test/fixtures/multipart/bracketed_param +5 -0
  223. data/test/fixtures/multipart/large_text_file +10 -0
  224. data/test/fixtures/multipart/mixed_files +0 -0
  225. data/test/fixtures/multipart/mona_lisa.jpg +0 -0
  226. data/test/fixtures/multipart/single_parameter +5 -0
  227. data/test/fixtures/multipart/text_file +10 -0
  228. data/test/fixtures/override/test/hello_world.erb +1 -0
  229. data/test/fixtures/override2/layouts/test/sub.erb +1 -0
  230. data/test/fixtures/post_test/layouts/post.html.erb +1 -0
  231. data/test/fixtures/post_test/layouts/super_post.iphone.erb +1 -0
  232. data/test/fixtures/post_test/post/index.html.erb +1 -0
  233. data/test/fixtures/post_test/post/index.iphone.erb +1 -0
  234. data/test/fixtures/post_test/super_post/index.html.erb +1 -0
  235. data/test/fixtures/post_test/super_post/index.iphone.erb +1 -0
  236. data/test/fixtures/project.rb +3 -0
  237. data/test/fixtures/projects.yml +7 -0
  238. data/test/fixtures/public/404.html +1 -0
  239. data/test/fixtures/public/500.html +1 -0
  240. data/test/fixtures/public/images/rails.png +0 -0
  241. data/test/fixtures/public/javascripts/application.js +1 -0
  242. data/test/fixtures/public/javascripts/bank.js +1 -0
  243. data/test/fixtures/public/javascripts/robber.js +1 -0
  244. data/test/fixtures/public/javascripts/version.1.0.js +1 -0
  245. data/test/fixtures/public/stylesheets/bank.css +1 -0
  246. data/test/fixtures/public/stylesheets/robber.css +1 -0
  247. data/test/fixtures/public/stylesheets/version.1.0.css +1 -0
  248. data/test/fixtures/replies.yml +15 -0
  249. data/test/fixtures/reply.rb +7 -0
  250. data/test/fixtures/respond_to/all_types_with_layout.html.erb +1 -0
  251. data/test/fixtures/respond_to/custom_constant_handling_without_block.mobile.erb +1 -0
  252. data/test/fixtures/respond_to/iphone_with_html_response_type.html.erb +1 -0
  253. data/test/fixtures/respond_to/iphone_with_html_response_type.iphone.erb +1 -0
  254. data/test/fixtures/respond_to/layouts/missing.html.erb +1 -0
  255. data/test/fixtures/respond_to/layouts/standard.html.erb +1 -0
  256. data/test/fixtures/respond_to/layouts/standard.iphone.erb +1 -0
  257. data/test/fixtures/respond_to/using_defaults.html.erb +1 -0
  258. data/test/fixtures/respond_to/using_defaults.js.rjs +1 -0
  259. data/test/fixtures/respond_to/using_defaults.xml.builder +1 -0
  260. data/test/fixtures/respond_to/using_defaults_with_type_list.html.erb +1 -0
  261. data/test/fixtures/respond_to/using_defaults_with_type_list.js.rjs +1 -0
  262. data/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder +1 -0
  263. data/test/fixtures/scope/test/modgreet.erb +1 -0
  264. data/test/fixtures/shared.html.erb +1 -0
  265. data/test/fixtures/symlink_parent/symlinked_layout.erb +5 -0
  266. data/test/fixtures/test/_customer.erb +1 -0
  267. data/test/fixtures/test/_customer_counter.erb +1 -0
  268. data/test/fixtures/test/_customer_greeting.erb +1 -0
  269. data/test/fixtures/test/_form.erb +1 -0
  270. data/test/fixtures/test/_hash_greeting.erb +1 -0
  271. data/test/fixtures/test/_hash_object.erb +2 -0
  272. data/test/fixtures/test/_hello.builder +1 -0
  273. data/test/fixtures/test/_labelling_form.erb +1 -0
  274. data/test/fixtures/test/_layout_for_partial.html.erb +3 -0
  275. data/test/fixtures/test/_partial.erb +1 -0
  276. data/test/fixtures/test/_partial.html.erb +1 -0
  277. data/test/fixtures/test/_partial.js.erb +1 -0
  278. data/test/fixtures/test/_partial_for_use_in_layout.html.erb +1 -0
  279. data/test/fixtures/test/_partial_only.erb +1 -0
  280. data/test/fixtures/test/_person.erb +2 -0
  281. data/test/fixtures/test/_raise.html.erb +1 -0
  282. data/test/fixtures/test/action_talk_to_layout.erb +2 -0
  283. data/test/fixtures/test/block_content_for.erb +2 -0
  284. data/test/fixtures/test/calling_partial_with_layout.html.erb +1 -0
  285. data/test/fixtures/test/capturing.erb +4 -0
  286. data/test/fixtures/test/content_for.erb +2 -0
  287. data/test/fixtures/test/content_for_concatenated.erb +3 -0
  288. data/test/fixtures/test/content_for_with_parameter.erb +2 -0
  289. data/test/fixtures/test/delete_with_js.rjs +2 -0
  290. data/test/fixtures/test/dot.directory/render_file_with_ivar.erb +1 -0
  291. data/test/fixtures/test/enum_rjs_test.rjs +6 -0
  292. data/test/fixtures/test/erb_content_for.erb +2 -0
  293. data/test/fixtures/test/formatted_html_erb.html.erb +1 -0
  294. data/test/fixtures/test/formatted_xml_erb.builder +1 -0
  295. data/test/fixtures/test/formatted_xml_erb.html.erb +1 -0
  296. data/test/fixtures/test/formatted_xml_erb.xml.erb +1 -0
  297. data/test/fixtures/test/greeting.erb +1 -0
  298. data/test/fixtures/test/greeting.js.rjs +1 -0
  299. data/test/fixtures/test/hello.builder +4 -0
  300. data/test/fixtures/test/hello_world.erb +1 -0
  301. data/test/fixtures/test/hello_world_container.builder +3 -0
  302. data/test/fixtures/test/hello_world_from_rxml.builder +4 -0
  303. data/test/fixtures/test/hello_world_with_layout_false.erb +1 -0
  304. data/test/fixtures/test/hello_xml_world.builder +11 -0
  305. data/test/fixtures/test/list.erb +1 -0
  306. data/test/fixtures/test/non_erb_block_content_for.builder +4 -0
  307. data/test/fixtures/test/potential_conflicts.erb +4 -0
  308. data/test/fixtures/test/render_file_from_template.html.erb +1 -0
  309. data/test/fixtures/test/render_file_with_ivar.erb +1 -0
  310. data/test/fixtures/test/render_file_with_locals.erb +1 -0
  311. data/test/fixtures/test/render_to_string_test.erb +1 -0
  312. data/test/fixtures/test/update_element_with_capture.erb +9 -0
  313. data/test/fixtures/test/using_layout_around_block.html.erb +1 -0
  314. data/test/fixtures/topic.rb +3 -0
  315. data/test/fixtures/topics.yml +22 -0
  316. data/test/fixtures/topics/_topic.html.erb +1 -0
  317. data/test/template/active_record_helper_test.rb +268 -0
  318. data/test/template/asset_tag_helper_test.rb +514 -0
  319. data/test/template/atom_feed_helper_test.rb +179 -0
  320. data/test/template/benchmark_helper_test.rb +60 -0
  321. data/test/template/date_helper_test.rb +1791 -0
  322. data/test/template/deprecated_erb_variable_test.rb +9 -0
  323. data/test/template/erb_util_test.rb +24 -0
  324. data/test/template/form_helper_test.rb +885 -0
  325. data/test/template/form_options_helper_test.rb +1333 -0
  326. data/test/template/form_tag_helper_test.rb +272 -0
  327. data/test/template/javascript_helper_test.rb +73 -0
  328. data/test/template/number_helper_test.rb +97 -0
  329. data/test/template/record_tag_helper_test.rb +54 -0
  330. data/test/template/sanitize_helper_test.rb +48 -0
  331. data/test/template/tag_helper_test.rb +77 -0
  332. data/test/template/template_finder_test.rb +73 -0
  333. data/test/template/template_object_test.rb +95 -0
  334. data/test/template/test_test.rb +56 -0
  335. data/test/template/text_helper_test.rb +367 -0
  336. data/test/template/url_helper_test.rb +544 -0
  337. data/test/testing_sandbox.rb +15 -0
  338. metadata +469 -0
@@ -0,0 +1,764 @@
1
+ require 'tempfile'
2
+ require 'stringio'
3
+ require 'strscan'
4
+
5
+ module ActionController
6
+ # HTTP methods which are accepted by default.
7
+ ACCEPTED_HTTP_METHODS = Set.new(%w( get head put post delete options ))
8
+
9
+ # CgiRequest and TestRequest provide concrete implementations.
10
+ class AbstractRequest
11
+ cattr_accessor :relative_url_root
12
+ remove_method :relative_url_root
13
+
14
+ # The hash of environment variables for this request,
15
+ # such as { 'RAILS_ENV' => 'production' }.
16
+ attr_reader :env
17
+
18
+ # The true HTTP request method as a lowercase symbol, such as <tt>:get</tt>.
19
+ # UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS.
20
+ def request_method
21
+ @request_method ||= begin
22
+ method = ((@env['REQUEST_METHOD'] == 'POST' && !parameters[:_method].blank?) ? parameters[:_method].to_s : @env['REQUEST_METHOD']).downcase
23
+ if ACCEPTED_HTTP_METHODS.include?(method)
24
+ method.to_sym
25
+ else
26
+ raise UnknownHttpMethod, "#{method}, accepted HTTP methods are #{ACCEPTED_HTTP_METHODS.to_a.to_sentence}"
27
+ end
28
+ end
29
+ end
30
+
31
+ # The HTTP request method as a lowercase symbol, such as <tt>:get</tt>.
32
+ # Note, HEAD is returned as <tt>:get</tt> since the two are functionally
33
+ # equivalent from the application's perspective.
34
+ def method
35
+ request_method == :head ? :get : request_method
36
+ end
37
+
38
+ # Is this a GET (or HEAD) request? Equivalent to <tt>request.method == :get</tt>.
39
+ def get?
40
+ method == :get
41
+ end
42
+
43
+ # Is this a POST request? Equivalent to <tt>request.method == :post</tt>.
44
+ def post?
45
+ request_method == :post
46
+ end
47
+
48
+ # Is this a PUT request? Equivalent to <tt>request.method == :put</tt>.
49
+ def put?
50
+ request_method == :put
51
+ end
52
+
53
+ # Is this a DELETE request? Equivalent to <tt>request.method == :delete</tt>.
54
+ def delete?
55
+ request_method == :delete
56
+ end
57
+
58
+ # Is this a HEAD request? <tt>request.method</tt> sees HEAD as <tt>:get</tt>,
59
+ # so check the HTTP method directly.
60
+ def head?
61
+ request_method == :head
62
+ end
63
+
64
+ # Provides acccess to the request's HTTP headers, for example:
65
+ # request.headers["Content-Type"] # => "text/plain"
66
+ def headers
67
+ @headers ||= ActionController::Http::Headers.new(@env)
68
+ end
69
+
70
+ def content_length
71
+ @content_length ||= env['CONTENT_LENGTH'].to_i
72
+ end
73
+
74
+ # The MIME type of the HTTP request, such as Mime::XML.
75
+ #
76
+ # For backward compatibility, the post format is extracted from the
77
+ # X-Post-Data-Format HTTP header if present.
78
+ def content_type
79
+ @content_type ||= Mime::Type.lookup(content_type_without_parameters)
80
+ end
81
+
82
+ # Returns the accepted MIME type for the request
83
+ def accepts
84
+ @accepts ||=
85
+ if @env['HTTP_ACCEPT'].to_s.strip.empty?
86
+ [ content_type, Mime::ALL ].compact # make sure content_type being nil is not included
87
+ else
88
+ Mime::Type.parse(@env['HTTP_ACCEPT'])
89
+ end
90
+ end
91
+
92
+ # Returns the Mime type for the format used in the request. If there is no format available, the first of the
93
+ # accept types will be used. Examples:
94
+ #
95
+ # GET /posts/5.xml | request.format => Mime::XML
96
+ # GET /posts/5.xhtml | request.format => Mime::HTML
97
+ # GET /posts/5 | request.format => request.accepts.first (usually Mime::HTML for browsers)
98
+ def format
99
+ @format ||= parameters[:format] ? Mime::Type.lookup_by_extension(parameters[:format]) : accepts.first
100
+ end
101
+
102
+
103
+ # Sets the format by string extension, which can be used to force custom formats that are not controlled by the extension.
104
+ # Example:
105
+ #
106
+ # class ApplicationController < ActionController::Base
107
+ # before_filter :adjust_format_for_iphone
108
+ #
109
+ # private
110
+ # def adjust_format_for_iphone
111
+ # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
112
+ # end
113
+ # end
114
+ def format=(extension)
115
+ parameters[:format] = extension.to_s
116
+ @format = Mime::Type.lookup_by_extension(parameters[:format])
117
+ end
118
+
119
+ # Returns true if the request's "X-Requested-With" header contains
120
+ # "XMLHttpRequest". (The Prototype Javascript library sends this header with
121
+ # every Ajax request.)
122
+ def xml_http_request?
123
+ !(@env['HTTP_X_REQUESTED_WITH'] !~ /XMLHttpRequest/i)
124
+ end
125
+ alias xhr? :xml_http_request?
126
+
127
+ # Which IP addresses are "trusted proxies" that can be stripped from
128
+ # the right-hand-side of X-Forwarded-For
129
+ TRUSTED_PROXIES = /^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i
130
+
131
+ # Determine originating IP address. REMOTE_ADDR is the standard
132
+ # but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or
133
+ # HTTP_X_FORWARDED_FOR are set by proxies so check for these if
134
+ # REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma-
135
+ # delimited list in the case of multiple chained proxies; the last
136
+ # address which is not trusted is the originating IP.
137
+ def remote_ip
138
+ remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].split(',').collect(&:strip)
139
+
140
+ unless remote_addr_list.blank?
141
+ not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES}
142
+ return not_trusted_addrs.first unless not_trusted_addrs.empty?
143
+ end
144
+ remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',')
145
+
146
+ if @env.include? 'HTTP_CLIENT_IP'
147
+ if remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP'])
148
+ # We don't know which came from the proxy, and which from the user
149
+ raise ActionControllerError.new(<<EOM)
150
+ IP spoofing attack?!
151
+ HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}
152
+ HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}
153
+ EOM
154
+ end
155
+
156
+ return @env['HTTP_CLIENT_IP']
157
+ end
158
+
159
+ if remote_ips
160
+ while remote_ips.size > 1 && TRUSTED_PROXIES =~ remote_ips.last.strip
161
+ remote_ips.pop
162
+ end
163
+
164
+ return remote_ips.last.strip
165
+ end
166
+
167
+ @env['REMOTE_ADDR']
168
+ end
169
+
170
+ # Returns the lowercase name of the HTTP server software.
171
+ def server_software
172
+ (@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
173
+ end
174
+
175
+
176
+ # Returns the complete URL used for this request
177
+ def url
178
+ protocol + host_with_port + request_uri
179
+ end
180
+
181
+ # Return 'https://' if this is an SSL request and 'http://' otherwise.
182
+ def protocol
183
+ ssl? ? 'https://' : 'http://'
184
+ end
185
+
186
+ # Is this an SSL request?
187
+ def ssl?
188
+ @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
189
+ end
190
+
191
+ # Returns the host for this request, such as example.com.
192
+ def host
193
+ end
194
+
195
+ # Returns a host:port string for this request, such as example.com or
196
+ # example.com:8080.
197
+ def host_with_port
198
+ @host_with_port ||= host + port_string
199
+ end
200
+
201
+ # Returns the port number of this request as an integer.
202
+ def port
203
+ @port_as_int ||= @env['SERVER_PORT'].to_i
204
+ end
205
+
206
+ # Returns the standard port number for this request's protocol
207
+ def standard_port
208
+ case protocol
209
+ when 'https://' then 443
210
+ else 80
211
+ end
212
+ end
213
+
214
+ # Returns a port suffix like ":8080" if the port number of this request
215
+ # is not the default HTTP port 80 or HTTPS port 443.
216
+ def port_string
217
+ (port == standard_port) ? '' : ":#{port}"
218
+ end
219
+
220
+ # Returns the domain part of a host, such as rubyonrails.org in "www.rubyonrails.org". You can specify
221
+ # a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
222
+ def domain(tld_length = 1)
223
+ return nil unless named_host?(host)
224
+
225
+ host.split('.').last(1 + tld_length).join('.')
226
+ end
227
+
228
+ # Returns all the subdomains as an array, so ["dev", "www"] would be returned for "dev.www.rubyonrails.org".
229
+ # You can specify a different <tt>tld_length</tt>, such as 2 to catch ["www"] instead of ["www", "rubyonrails"]
230
+ # in "www.rubyonrails.co.uk".
231
+ def subdomains(tld_length = 1)
232
+ return [] unless named_host?(host)
233
+ parts = host.split('.')
234
+ parts[0..-(tld_length+2)]
235
+ end
236
+
237
+ # Return the query string, accounting for server idiosyncracies.
238
+ def query_string
239
+ if uri = @env['REQUEST_URI']
240
+ uri.split('?', 2)[1] || ''
241
+ else
242
+ @env['QUERY_STRING'] || ''
243
+ end
244
+ end
245
+
246
+ # Return the request URI, accounting for server idiosyncracies.
247
+ # WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
248
+ def request_uri
249
+ if uri = @env['REQUEST_URI']
250
+ # Remove domain, which webrick puts into the request_uri.
251
+ (%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri
252
+ else
253
+ # Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
254
+ script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
255
+ uri = @env['PATH_INFO']
256
+ uri = uri.sub(/#{script_filename}\//, '') unless script_filename.nil?
257
+ unless (env_qs = @env['QUERY_STRING']).nil? || env_qs.empty?
258
+ uri << '?' << env_qs
259
+ end
260
+
261
+ if uri.nil?
262
+ @env.delete('REQUEST_URI')
263
+ uri
264
+ else
265
+ @env['REQUEST_URI'] = uri
266
+ end
267
+ end
268
+ end
269
+
270
+ # Returns the interpreted path to requested resource after all the installation directory of this application was taken into account
271
+ def path
272
+ path = (uri = request_uri) ? uri.split('?').first.to_s : ''
273
+
274
+ # Cut off the path to the installation directory if given
275
+ path.sub!(%r/^#{relative_url_root}/, '')
276
+ path || ''
277
+ end
278
+
279
+ # Returns the path minus the web server relative installation directory.
280
+ # This can be set with the environment variable RAILS_RELATIVE_URL_ROOT.
281
+ # It can be automatically extracted for Apache setups. If the server is not
282
+ # Apache, this method returns an empty string.
283
+ def relative_url_root
284
+ @@relative_url_root ||= case
285
+ when @env["RAILS_RELATIVE_URL_ROOT"]
286
+ @env["RAILS_RELATIVE_URL_ROOT"]
287
+ when server_software == 'apache'
288
+ @env["SCRIPT_NAME"].to_s.sub(/\/dispatch\.(fcgi|rb|cgi)$/, '')
289
+ else
290
+ ''
291
+ end
292
+ end
293
+
294
+
295
+ # Read the request body. This is useful for web services that need to
296
+ # work with raw requests directly.
297
+ def raw_post
298
+ unless env.include? 'RAW_POST_DATA'
299
+ env['RAW_POST_DATA'] = body.read(content_length)
300
+ body.rewind if body.respond_to?(:rewind)
301
+ end
302
+ env['RAW_POST_DATA']
303
+ end
304
+
305
+ # Returns both GET and POST parameters in a single hash.
306
+ def parameters
307
+ @parameters ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
308
+ end
309
+
310
+ def path_parameters=(parameters) #:nodoc:
311
+ @path_parameters = parameters
312
+ @symbolized_path_parameters = @parameters = nil
313
+ end
314
+
315
+ # The same as <tt>path_parameters</tt> with explicitly symbolized keys
316
+ def symbolized_path_parameters
317
+ @symbolized_path_parameters ||= path_parameters.symbolize_keys
318
+ end
319
+
320
+ # Returns a hash with the parameters used to form the path of the request.
321
+ # Returned hash keys are strings. See <tt>symbolized_path_parameters</tt> for symbolized keys.
322
+ #
323
+ # Example:
324
+ #
325
+ # {'action' => 'my_action', 'controller' => 'my_controller'}
326
+ def path_parameters
327
+ @path_parameters ||= {}
328
+ end
329
+
330
+
331
+ #--
332
+ # Must be implemented in the concrete request
333
+ #++
334
+
335
+ # The request body is an IO input stream.
336
+ def body
337
+ end
338
+
339
+ def query_parameters #:nodoc:
340
+ end
341
+
342
+ def request_parameters #:nodoc:
343
+ end
344
+
345
+ def cookies #:nodoc:
346
+ end
347
+
348
+ def session #:nodoc:
349
+ end
350
+
351
+ def session=(session) #:nodoc:
352
+ @session = session
353
+ end
354
+
355
+ def reset_session #:nodoc:
356
+ end
357
+
358
+ protected
359
+ # The raw content type string. Use when you need parameters such as
360
+ # charset or boundary which aren't included in the content_type MIME type.
361
+ # Overridden by the X-POST_DATA_FORMAT header for backward compatibility.
362
+ def content_type_with_parameters
363
+ content_type_from_legacy_post_data_format_header ||
364
+ env['CONTENT_TYPE'].to_s
365
+ end
366
+
367
+ # The raw content type string with its parameters stripped off.
368
+ def content_type_without_parameters
369
+ @content_type_without_parameters ||= self.class.extract_content_type_without_parameters(content_type_with_parameters)
370
+ end
371
+
372
+ private
373
+ def content_type_from_legacy_post_data_format_header
374
+ if x_post_format = @env['HTTP_X_POST_DATA_FORMAT']
375
+ case x_post_format.to_s.downcase
376
+ when 'yaml'; 'application/x-yaml'
377
+ when 'xml'; 'application/xml'
378
+ end
379
+ end
380
+ end
381
+
382
+ def parse_formatted_request_parameters
383
+ return {} if content_length.zero?
384
+
385
+ content_type, boundary = self.class.extract_multipart_boundary(content_type_with_parameters)
386
+
387
+ # Don't parse params for unknown requests.
388
+ return {} if content_type.blank?
389
+
390
+ mime_type = Mime::Type.lookup(content_type)
391
+ strategy = ActionController::Base.param_parsers[mime_type]
392
+
393
+ # Only multipart form parsing expects a stream.
394
+ body = (strategy && strategy != :multipart_form) ? raw_post : self.body
395
+
396
+ case strategy
397
+ when Proc
398
+ strategy.call(body)
399
+ when :url_encoded_form
400
+ self.class.clean_up_ajax_request_body! body
401
+ self.class.parse_query_parameters(body)
402
+ when :multipart_form
403
+ self.class.parse_multipart_form_parameters(body, boundary, content_length, env)
404
+ when :xml_simple, :xml_node
405
+ body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
406
+ when :yaml
407
+ YAML.load(body)
408
+ when :json
409
+ if body.blank?
410
+ {}
411
+ else
412
+ data = ActiveSupport::JSON.decode(body)
413
+ data = {:_json => data} unless data.is_a?(Hash)
414
+ data.with_indifferent_access
415
+ end
416
+ else
417
+ {}
418
+ end
419
+ rescue Exception => e # YAML, XML or Ruby code block errors
420
+ raise
421
+ { "body" => body,
422
+ "content_type" => content_type_with_parameters,
423
+ "content_length" => content_length,
424
+ "exception" => "#{e.message} (#{e.class})",
425
+ "backtrace" => e.backtrace }
426
+ end
427
+
428
+ def named_host?(host)
429
+ !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
430
+ end
431
+
432
+ class << self
433
+ def parse_query_parameters(query_string)
434
+ return {} if query_string.blank?
435
+
436
+ pairs = query_string.split('&').collect do |chunk|
437
+ next if chunk.empty?
438
+ key, value = chunk.split('=', 2)
439
+ next if key.empty?
440
+ value = value.nil? ? nil : CGI.unescape(value)
441
+ [ CGI.unescape(key), value ]
442
+ end.compact
443
+
444
+ UrlEncodedPairParser.new(pairs).result
445
+ end
446
+
447
+ def parse_request_parameters(params)
448
+ parser = UrlEncodedPairParser.new
449
+
450
+ params = params.dup
451
+ until params.empty?
452
+ for key, value in params
453
+ if key.blank?
454
+ params.delete key
455
+ elsif !key.include?('[')
456
+ # much faster to test for the most common case first (GET)
457
+ # and avoid the call to build_deep_hash
458
+ parser.result[key] = get_typed_value(value[0])
459
+ params.delete key
460
+ elsif value.is_a?(Array)
461
+ parser.parse(key, get_typed_value(value.shift))
462
+ params.delete key if value.empty?
463
+ else
464
+ raise TypeError, "Expected array, found #{value.inspect}"
465
+ end
466
+ end
467
+ end
468
+
469
+ parser.result
470
+ end
471
+
472
+ def parse_multipart_form_parameters(body, boundary, body_size, env)
473
+ parse_request_parameters(read_multipart(body, boundary, body_size, env))
474
+ end
475
+
476
+ def extract_multipart_boundary(content_type_with_parameters)
477
+ if content_type_with_parameters =~ MULTIPART_BOUNDARY
478
+ ['multipart/form-data', $1.dup]
479
+ else
480
+ extract_content_type_without_parameters(content_type_with_parameters)
481
+ end
482
+ end
483
+
484
+ def extract_content_type_without_parameters(content_type_with_parameters)
485
+ $1.strip.downcase if content_type_with_parameters =~ /^([^,\;]*)/
486
+ end
487
+
488
+ def clean_up_ajax_request_body!(body)
489
+ body.chop! if body[-1] == 0
490
+ body.gsub!(/&_=$/, '')
491
+ end
492
+
493
+
494
+ private
495
+ def get_typed_value(value)
496
+ case value
497
+ when String
498
+ value
499
+ when NilClass
500
+ ''
501
+ when Array
502
+ value.map { |v| get_typed_value(v) }
503
+ else
504
+ if value.respond_to? :original_filename
505
+ # Uploaded file
506
+ if value.original_filename
507
+ value
508
+ # Multipart param
509
+ else
510
+ result = value.read
511
+ value.rewind
512
+ result
513
+ end
514
+ # Unknown value, neither string nor multipart.
515
+ else
516
+ raise "Unknown form value: #{value.inspect}"
517
+ end
518
+ end
519
+ end
520
+
521
+ MULTIPART_BOUNDARY = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
522
+
523
+ EOL = "\015\012"
524
+
525
+ def read_multipart(body, boundary, body_size, env)
526
+ params = Hash.new([])
527
+ boundary = "--" + boundary
528
+ quoted_boundary = Regexp.quote(boundary)
529
+ buf = ""
530
+ bufsize = 10 * 1024
531
+ boundary_end=""
532
+
533
+ # start multipart/form-data
534
+ body.binmode if defined? body.binmode
535
+ case body
536
+ when File
537
+ body.set_encoding(Encoding::BINARY) if body.respond_to?(:set_encoding)
538
+ when StringIO
539
+ body.string.force_encoding(Encoding::BINARY) if body.string.respond_to?(:force_encoding)
540
+ end
541
+ boundary_size = boundary.size + EOL.size
542
+ body_size -= boundary_size
543
+ status = body.read(boundary_size)
544
+ if nil == status
545
+ raise EOFError, "no content body"
546
+ elsif boundary + EOL != status
547
+ raise EOFError, "bad content body"
548
+ end
549
+
550
+ loop do
551
+ head = nil
552
+ content =
553
+ if 10240 < body_size
554
+ UploadedTempfile.new("CGI")
555
+ else
556
+ UploadedStringIO.new
557
+ end
558
+ content.binmode if defined? content.binmode
559
+
560
+ until head and /#{quoted_boundary}(?:#{EOL}|--)/n.match(buf)
561
+
562
+ if (not head) and /#{EOL}#{EOL}/n.match(buf)
563
+ buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
564
+ head = $1.dup
565
+ ""
566
+ end
567
+ next
568
+ end
569
+
570
+ if head and ( (EOL + boundary + EOL).size < buf.size )
571
+ content.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)]
572
+ buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
573
+ end
574
+
575
+ c = if bufsize < body_size
576
+ body.read(bufsize)
577
+ else
578
+ body.read(body_size)
579
+ end
580
+ if c.nil? || c.empty?
581
+ raise EOFError, "bad content body"
582
+ end
583
+ buf.concat(c)
584
+ body_size -= c.size
585
+ end
586
+
587
+ buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
588
+ content.print $1
589
+ if "--" == $2
590
+ body_size = -1
591
+ end
592
+ boundary_end = $2.dup
593
+ ""
594
+ end
595
+
596
+ content.rewind
597
+
598
+ head =~ /Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;]*))/ni
599
+ if filename = $1 || $2
600
+ if /Mac/ni.match(env['HTTP_USER_AGENT']) and
601
+ /Mozilla/ni.match(env['HTTP_USER_AGENT']) and
602
+ (not /MSIE/ni.match(env['HTTP_USER_AGENT']))
603
+ filename = CGI.unescape(filename)
604
+ end
605
+ content.original_path = filename.dup
606
+ end
607
+
608
+ head =~ /Content-Type: ([^\r]*)/ni
609
+ content.content_type = $1.dup if $1
610
+
611
+ head =~ /Content-Disposition:.* name="?([^\";]*)"?/ni
612
+ name = $1.dup if $1
613
+
614
+ if params.has_key?(name)
615
+ params[name].push(content)
616
+ else
617
+ params[name] = [content]
618
+ end
619
+ break if body_size == -1
620
+ end
621
+ raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/
622
+
623
+ begin
624
+ body.rewind if body.respond_to?(:rewind)
625
+ rescue Errno::ESPIPE
626
+ # Handles exceptions raised by input streams that cannot be rewound
627
+ # such as when using plain CGI under Apache
628
+ end
629
+
630
+ params
631
+ end
632
+ end
633
+ end
634
+
635
+ class UrlEncodedPairParser < StringScanner #:nodoc:
636
+ attr_reader :top, :parent, :result
637
+
638
+ def initialize(pairs = [])
639
+ super('')
640
+ @result = {}
641
+ pairs.each { |key, value| parse(key, value) }
642
+ end
643
+
644
+ KEY_REGEXP = %r{([^\[\]=&]+)}
645
+ BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
646
+
647
+ # Parse the query string
648
+ def parse(key, value)
649
+ self.string = key
650
+ @top, @parent = result, nil
651
+
652
+ # First scan the bare key
653
+ key = scan(KEY_REGEXP) or return
654
+ key = post_key_check(key)
655
+
656
+ # Then scan as many nestings as present
657
+ until eos?
658
+ r = scan(BRACKETED_KEY_REGEXP) or return
659
+ key = self[1]
660
+ key = post_key_check(key)
661
+ end
662
+
663
+ bind(key, value)
664
+ end
665
+
666
+ private
667
+ # After we see a key, we must look ahead to determine our next action. Cases:
668
+ #
669
+ # [] follows the key. Then the value must be an array.
670
+ # = follows the key. (A value comes next)
671
+ # & or the end of string follows the key. Then the key is a flag.
672
+ # otherwise, a hash follows the key.
673
+ def post_key_check(key)
674
+ if scan(/\[\]/) # a[b][] indicates that b is an array
675
+ container(key, Array)
676
+ nil
677
+ elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
678
+ container(key, Hash)
679
+ nil
680
+ else # End of key? We do nothing.
681
+ key
682
+ end
683
+ end
684
+
685
+ # Add a container to the stack.
686
+ def container(key, klass)
687
+ type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
688
+ value = bind(key, klass.new)
689
+ type_conflict! klass, value unless value.is_a?(klass)
690
+ push(value)
691
+ end
692
+
693
+ # Push a value onto the 'stack', which is actually only the top 2 items.
694
+ def push(value)
695
+ @parent, @top = @top, value
696
+ end
697
+
698
+ # Bind a key (which may be nil for items in an array) to the provided value.
699
+ def bind(key, value)
700
+ if top.is_a? Array
701
+ if key
702
+ if top[-1].is_a?(Hash) && ! top[-1].key?(key)
703
+ top[-1][key] = value
704
+ else
705
+ top << {key => value}.with_indifferent_access
706
+ push top.last
707
+ value = top[key]
708
+ end
709
+ else
710
+ top << value
711
+ end
712
+ elsif top.is_a? Hash
713
+ key = CGI.unescape(key)
714
+ parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
715
+ top[key] ||= value
716
+ return top[key]
717
+ else
718
+ raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
719
+ end
720
+
721
+ return value
722
+ end
723
+
724
+ def type_conflict!(klass, value)
725
+ raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)"
726
+ end
727
+ end
728
+
729
+ module UploadedFile
730
+ def self.included(base)
731
+ base.class_eval do
732
+ attr_accessor :original_path, :content_type
733
+ alias_method :local_path, :path
734
+ end
735
+ end
736
+
737
+ # Take the basename of the upload's original filename.
738
+ # This handles the full Windows paths given by Internet Explorer
739
+ # (and perhaps other broken user agents) without affecting
740
+ # those which give the lone filename.
741
+ # The Windows regexp is adapted from Perl's File::Basename.
742
+ def original_filename
743
+ unless defined? @original_filename
744
+ @original_filename =
745
+ unless original_path.blank?
746
+ if original_path =~ /^(?:.*[:\\\/])?(.*)/m
747
+ $1
748
+ else
749
+ File.basename original_path
750
+ end
751
+ end
752
+ end
753
+ @original_filename
754
+ end
755
+ end
756
+
757
+ class UploadedStringIO < StringIO
758
+ include UploadedFile
759
+ end
760
+
761
+ class UploadedTempfile < Tempfile
762
+ include UploadedFile
763
+ end
764
+ end