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,20 @@
1
+ # Build list of Mime types for HTTP responses
2
+ # http://www.iana.org/assignments/media-types/
3
+
4
+ Mime::Type.register "*/*", :all
5
+ Mime::Type.register "text/plain", :text, [], %w(txt)
6
+ Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml )
7
+ Mime::Type.register "text/javascript", :js, %w( application/javascript application/x-javascript )
8
+ Mime::Type.register "text/css", :css
9
+ Mime::Type.register "text/calendar", :ics
10
+ Mime::Type.register "text/csv", :csv
11
+ Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml )
12
+ Mime::Type.register "application/rss+xml", :rss
13
+ Mime::Type.register "application/atom+xml", :atom
14
+ Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml )
15
+
16
+ Mime::Type.register "multipart/form-data", :multipart_form
17
+ Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form
18
+
19
+ # http://www.ietf.org/rfc/rfc4627.txt
20
+ Mime::Type.register "application/json", :json, %w( text/x-json )
@@ -0,0 +1,88 @@
1
+ module ActionController
2
+ module PolymorphicRoutes
3
+ def polymorphic_url(record_or_hash_or_array, options = {})
4
+ record = extract_record(record_or_hash_or_array)
5
+
6
+ namespace = extract_namespace(record_or_hash_or_array)
7
+
8
+ args = case record_or_hash_or_array
9
+ when Hash; [ record_or_hash_or_array ]
10
+ when Array; record_or_hash_or_array.dup
11
+ else [ record_or_hash_or_array ]
12
+ end
13
+
14
+ inflection =
15
+ case
16
+ when options[:action] == "new"
17
+ args.pop
18
+ :singular
19
+ when record.respond_to?(:new_record?) && record.new_record?
20
+ args.pop
21
+ :plural
22
+ else
23
+ :singular
24
+ end
25
+
26
+ named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options)
27
+ send!(named_route, *args)
28
+ end
29
+
30
+ def polymorphic_path(record_or_hash_or_array)
31
+ polymorphic_url(record_or_hash_or_array, :routing_type => :path)
32
+ end
33
+
34
+ %w(edit new formatted).each do |action|
35
+ module_eval <<-EOT, __FILE__, __LINE__
36
+ def #{action}_polymorphic_url(record_or_hash)
37
+ polymorphic_url(record_or_hash, :action => "#{action}")
38
+ end
39
+
40
+ def #{action}_polymorphic_path(record_or_hash)
41
+ polymorphic_url(record_or_hash, :action => "#{action}", :routing_type => :path)
42
+ end
43
+ EOT
44
+ end
45
+
46
+
47
+ private
48
+ def action_prefix(options)
49
+ options[:action] ? "#{options[:action]}_" : ""
50
+ end
51
+
52
+ def routing_type(options)
53
+ "#{options[:routing_type] || "url"}"
54
+ end
55
+
56
+ def build_named_route_call(records, namespace, inflection, options = {})
57
+ records = Array.new([extract_record(records)]) unless records.is_a?(Array)
58
+ base_segment = "#{RecordIdentifier.send!("#{inflection}_class_name", records.pop)}_"
59
+
60
+ method_root = records.reverse.inject(base_segment) do |string, name|
61
+ segment = "#{RecordIdentifier.send!("singular_class_name", name)}_"
62
+ segment << string
63
+ end
64
+
65
+ action_prefix(options) + namespace + method_root + routing_type(options)
66
+ end
67
+
68
+ def extract_record(record_or_hash_or_array)
69
+ case record_or_hash_or_array
70
+ when Array; record_or_hash_or_array.last
71
+ when Hash; record_or_hash_or_array[:id]
72
+ else record_or_hash_or_array
73
+ end
74
+ end
75
+
76
+ def extract_namespace(record_or_hash_or_array)
77
+ returning "" do |namespace|
78
+ if record_or_hash_or_array.is_a?(Array)
79
+ record_or_hash_or_array.delete_if do |record_or_namespace|
80
+ if record_or_namespace.is_a?(String) || record_or_namespace.is_a?(Symbol)
81
+ namespace << "#{record_or_namespace.to_s}_"
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,91 @@
1
+ module ActionController
2
+ # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
3
+ # Active Resources or pretty much any other model type that has an id. These patterns are then used to try elevate
4
+ # the view actions to a higher logical level. Example:
5
+ #
6
+ # # routes
7
+ # map.resources :posts
8
+ #
9
+ # # view
10
+ # <% div_for(post) do %> <div id="post_45" class="post">
11
+ # <%= post.body %> What a wonderful world!
12
+ # <% end %> </div>
13
+ #
14
+ # # controller
15
+ # def destroy
16
+ # post = Post.find(params[:id])
17
+ # post.destroy
18
+ #
19
+ # respond_to do |format|
20
+ # format.html { redirect_to(post) } # Calls polymorphic_url(post) which in turn calls post_url(post)
21
+ # format.js do
22
+ # # Calls: new Effect.fade('post_45');
23
+ # render(:update) { |page| page[post].visual_effect(:fade) }
24
+ # end
25
+ # end
26
+ # end
27
+ #
28
+ # As the example above shows, you can stop caring to a large extent what the actual id of the post is. You just know
29
+ # that one is being assigned and that the subsequent calls in redirect_to and the RJS expect that same naming
30
+ # convention and allows you to write less code if you follow it.
31
+ module RecordIdentifier
32
+ extend self
33
+
34
+ # Returns plural/singular for a record or class. Example:
35
+ #
36
+ # partial_path(post) # => "posts/post"
37
+ # partial_path(Person) # => "people/person"
38
+ def partial_path(record_or_class)
39
+ klass = class_from_record_or_class(record_or_class)
40
+ "#{klass.name.tableize}/#{klass.name.demodulize.underscore}"
41
+ end
42
+
43
+ # The DOM class convention is to use the singular form of an object or class. Examples:
44
+ #
45
+ # dom_class(post) # => "post"
46
+ # dom_class(Person) # => "person"
47
+ #
48
+ # If you need to address multiple instances of the same class in the same view, you can prefix the dom_class:
49
+ #
50
+ # dom_class(post, :edit) # => "edit_post"
51
+ # dom_class(Person, :edit) # => "edit_person"
52
+ def dom_class(record_or_class, prefix = nil)
53
+ [ prefix, singular_class_name(record_or_class) ].compact * '_'
54
+ end
55
+
56
+ # The DOM class convention is to use the singular form of an object or class with the id following an underscore.
57
+ # If no id is found, prefix with "new_" instead. Examples:
58
+ #
59
+ # dom_class(Post.new(:id => 45)) # => "post_45"
60
+ # dom_class(Post.new) # => "new_post"
61
+ #
62
+ # If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
63
+ #
64
+ # dom_class(Post.new(:id => 45), :edit) # => "edit_post_45"
65
+ def dom_id(record, prefix = nil)
66
+ prefix ||= 'new' unless record.id
67
+ [ prefix, singular_class_name(record), record.id ].compact * '_'
68
+ end
69
+
70
+ # Returns the plural class name of a record or class. Examples:
71
+ #
72
+ # plural_class_name(post) # => "posts"
73
+ # plural_class_name(Highrise::Person) # => "highrise_people"
74
+ def plural_class_name(record_or_class)
75
+ singular_class_name(record_or_class).pluralize
76
+ end
77
+
78
+ # Returns the singular class name of a record or class. Examples:
79
+ #
80
+ # singular_class_name(post) # => "post"
81
+ # singular_class_name(Highrise::Person) # => "highrise_person"
82
+ def singular_class_name(record_or_class)
83
+ class_from_record_or_class(record_or_class).name.underscore.tr('/', '_')
84
+ end
85
+
86
+ private
87
+ def class_from_record_or_class(record_or_class)
88
+ record_or_class.is_a?(Class) ? record_or_class : record_or_class.class
89
+ end
90
+ end
91
+ end
@@ -1,27 +1,38 @@
1
+ require 'tempfile'
2
+ require 'stringio'
3
+ require 'strscan'
4
+
1
5
  module ActionController
2
- # Subclassing AbstractRequest makes these methods available to the request objects used in production and testing,
3
- # CgiRequest and TestRequest
6
+ # HTTP methods which are accepted by default.
7
+ ACCEPTED_HTTP_METHODS = Set.new(%w( get head put post delete ))
8
+
9
+ # CgiRequest and TestRequest provide concrete implementations.
4
10
  class AbstractRequest
5
11
  cattr_accessor :relative_url_root
12
+ remove_method :relative_url_root
6
13
 
7
- # Returns the hash of environment variables for this request,
14
+ # The hash of environment variables for this request,
8
15
  # such as { 'RAILS_ENV' => 'production' }.
9
16
  attr_reader :env
10
17
 
11
- # Returns both GET and POST parameters in a single hash.
12
- def parameters
13
- @parameters ||= request_parameters.update(query_parameters).update(path_parameters).with_indifferent_access
18
+ # The true HTTP request method as a lowercase symbol, such as :get.
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
14
29
  end
15
30
 
16
- # Returns the HTTP request method as a lowercase symbol (:get, for example). Note, HEAD is returned as :get
17
- # since the two are supposedly to be functionaly equivilent for all purposes except that HEAD won't return a response
18
- # body (which Rails also takes care of elsewhere).
31
+ # The HTTP request method as a lowercase symbol, such as :get.
32
+ # Note, HEAD is returned as :get since the two are functionally
33
+ # equivalent from the application's perspective.
19
34
  def method
20
- @request_method ||= (!parameters[:_method].blank? && @env['REQUEST_METHOD'] == 'POST') ?
21
- parameters[:_method].to_s.downcase.to_sym :
22
- @env['REQUEST_METHOD'].downcase.to_sym
23
-
24
- @request_method == :head ? :get : @request_method
35
+ request_method == :head ? :get : request_method
25
36
  end
26
37
 
27
38
  # Is this a GET (or HEAD) request? Equivalent to request.method == :get
@@ -31,63 +42,83 @@ module ActionController
31
42
 
32
43
  # Is this a POST request? Equivalent to request.method == :post
33
44
  def post?
34
- method == :post
45
+ request_method == :post
35
46
  end
36
47
 
37
48
  # Is this a PUT request? Equivalent to request.method == :put
38
49
  def put?
39
- method == :put
50
+ request_method == :put
40
51
  end
41
52
 
42
53
  # Is this a DELETE request? Equivalent to request.method == :delete
43
54
  def delete?
44
- method == :delete
55
+ request_method == :delete
45
56
  end
46
57
 
47
- # Is this a HEAD request? HEAD is mapped as :get for request.method, so here we ask the
48
- # REQUEST_METHOD header directly. Thus, for head, both get? and head? will return true.
58
+ # Is this a HEAD request? request.method sees HEAD as :get, so check the
59
+ # HTTP method directly.
49
60
  def head?
50
- @env['REQUEST_METHOD'].downcase.to_sym == :head
61
+ request_method == :head
51
62
  end
52
63
 
53
- # Determine whether the body of a HTTP call is URL-encoded (default)
54
- # or matches one of the registered param_parsers.
64
+ def headers
65
+ @env
66
+ end
67
+
68
+ def content_length
69
+ @content_length ||= env['CONTENT_LENGTH'].to_i
70
+ end
71
+
72
+ # The MIME type of the HTTP request, such as Mime::XML.
55
73
  #
56
74
  # For backward compatibility, the post format is extracted from the
57
75
  # X-Post-Data-Format HTTP header if present.
58
76
  def content_type
59
- @content_type ||=
60
- begin
61
- content_type = @env['CONTENT_TYPE'].to_s.downcase
62
-
63
- if x_post_format = @env['HTTP_X_POST_DATA_FORMAT']
64
- case x_post_format.to_s.downcase
65
- when 'yaml'
66
- content_type = 'application/x-yaml'
67
- when 'xml'
68
- content_type = 'application/xml'
69
- end
70
- end
71
-
72
- Mime::Type.lookup(content_type)
73
- end
77
+ @content_type ||= Mime::Type.lookup(content_type_without_parameters)
74
78
  end
75
79
 
76
80
  # Returns the accepted MIME type for the request
77
81
  def accepts
78
82
  @accepts ||=
79
83
  if @env['HTTP_ACCEPT'].to_s.strip.empty?
80
- [ content_type, Mime::ALL ]
84
+ [ content_type, Mime::ALL ].compact # make sure content_type being nil is not included
81
85
  else
82
86
  Mime::Type.parse(@env['HTTP_ACCEPT'])
83
87
  end
84
88
  end
85
89
 
90
+ # Returns the Mime type for the format used in the request. If there is no format available, the first of the
91
+ # accept types will be used. Examples:
92
+ #
93
+ # GET /posts/5.xml | request.format => Mime::XML
94
+ # GET /posts/5.xhtml | request.format => Mime::HTML
95
+ # GET /posts/5 | request.format => request.accepts.first (usually Mime::HTML for browsers)
96
+ def format
97
+ @format ||= parameters[:format] ? Mime::Type.lookup_by_extension(parameters[:format]) : accepts.first
98
+ end
99
+
100
+
101
+ # Sets the format by string extension, which can be used to force custom formats that are not controlled by the extension.
102
+ # Example:
103
+ #
104
+ # class ApplicationController < ActionController::Base
105
+ # before_filter :adjust_format_for_iphone
106
+ #
107
+ # private
108
+ # def adjust_format_for_iphone
109
+ # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
110
+ # end
111
+ # end
112
+ def format=(extension)
113
+ parameters[:format] = extension.to_s
114
+ format
115
+ end
116
+
86
117
  # Returns true if the request's "X-Requested-With" header contains
87
118
  # "XMLHttpRequest". (The Prototype Javascript library sends this header with
88
119
  # every Ajax request.)
89
120
  def xml_http_request?
90
- not /XMLHttpRequest/i.match(@env['HTTP_X_REQUESTED_WITH']).nil?
121
+ !(@env['HTTP_X_REQUESTED_WITH'] !~ /XMLHttpRequest/i)
91
122
  end
92
123
  alias xhr? :xml_http_request?
93
124
 
@@ -97,12 +128,17 @@ module ActionController
97
128
  # falling back to REMOTE_ADDR. HTTP_X_FORWARDED_FOR may be a comma-
98
129
  # delimited list in the case of multiple chained proxies; the first is
99
130
  # the originating IP.
131
+ #
132
+ # Security note: do not use if IP spoofing is a concern for your
133
+ # application. Since remote_ip checks HTTP headers for addresses forwarded
134
+ # by proxies, the client may send any IP. remote_addr can't be spoofed but
135
+ # also doesn't work behind a proxy, since it's always the proxy's IP.
100
136
  def remote_ip
101
137
  return @env['HTTP_CLIENT_IP'] if @env.include? 'HTTP_CLIENT_IP'
102
138
 
103
139
  if @env.include? 'HTTP_X_FORWARDED_FOR' then
104
140
  remote_ips = @env['HTTP_X_FORWARDED_FOR'].split(',').reject do |ip|
105
- ip =~ /^unknown$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i
141
+ ip.strip =~ /^unknown$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i
106
142
  end
107
143
 
108
144
  return remote_ips.first.strip unless remote_ips.empty?
@@ -111,10 +147,60 @@ module ActionController
111
147
  @env['REMOTE_ADDR']
112
148
  end
113
149
 
150
+ # Returns the lowercase name of the HTTP server software.
151
+ def server_software
152
+ (@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
153
+ end
154
+
155
+
156
+ # Returns the complete URL used for this request
157
+ def url
158
+ protocol + host_with_port + request_uri
159
+ end
160
+
161
+ # Return 'https://' if this is an SSL request and 'http://' otherwise.
162
+ def protocol
163
+ ssl? ? 'https://' : 'http://'
164
+ end
165
+
166
+ # Is this an SSL request?
167
+ def ssl?
168
+ @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
169
+ end
170
+
171
+ # Returns the host for this request, such as example.com.
172
+ def host
173
+ end
174
+
175
+ # Returns a host:port string for this request, such as example.com or
176
+ # example.com:8080.
177
+ def host_with_port
178
+ @host_with_port ||= host + port_string
179
+ end
180
+
181
+ # Returns the port number of this request as an integer.
182
+ def port
183
+ @port_as_int ||= @env['SERVER_PORT'].to_i
184
+ end
185
+
186
+ # Returns the standard port number for this request's protocol
187
+ def standard_port
188
+ case protocol
189
+ when 'https://' then 443
190
+ else 80
191
+ end
192
+ end
193
+
194
+ # Returns a port suffix like ":8080" if the port number of this request
195
+ # is not the default HTTP port 80 or HTTPS port 443.
196
+ def port_string
197
+ (port == standard_port) ? '' : ":#{port}"
198
+ end
199
+
114
200
  # Returns the domain part of a host, such as rubyonrails.org in "www.rubyonrails.org". You can specify
115
201
  # a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
116
202
  def domain(tld_length = 1)
117
- return nil if !/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/.match(host).nil? or host.nil?
203
+ return nil unless named_host?(host)
118
204
 
119
205
  host.split('.').last(1 + tld_length).join('.')
120
206
  end
@@ -123,16 +209,18 @@ module ActionController
123
209
  # You can specify a different <tt>tld_length</tt>, such as 2 to catch ["www"] instead of ["www", "rubyonrails"]
124
210
  # in "www.rubyonrails.co.uk".
125
211
  def subdomains(tld_length = 1)
126
- return [] unless host
212
+ return [] unless named_host?(host)
127
213
  parts = host.split('.')
128
214
  parts[0..-(tld_length+2)]
129
215
  end
130
216
 
131
- # Receive the raw post data.
132
- # This is useful for services such as REST, XMLRPC and SOAP
133
- # which communicate over HTTP POST but don't use the traditional parameter format.
134
- def raw_post
135
- @env['RAW_POST_DATA']
217
+ # Return the query string, accounting for server idiosyncracies.
218
+ def query_string
219
+ if uri = @env['REQUEST_URI']
220
+ uri.split('?', 2)[1] || ''
221
+ else
222
+ @env['QUERY_STRING'] || ''
223
+ end
136
224
  end
137
225
 
138
226
  # Return the request URI, accounting for server idiosyncracies.
@@ -149,23 +237,19 @@ module ActionController
149
237
  unless (env_qs = @env['QUERY_STRING']).nil? || env_qs.empty?
150
238
  uri << '?' << env_qs
151
239
  end
152
- @env['REQUEST_URI'] = uri
153
- end
154
- end
155
240
 
156
- # Return 'https://' if this is an SSL request and 'http://' otherwise.
157
- def protocol
158
- ssl? ? 'https://' : 'http://'
159
- end
160
-
161
- # Is this an SSL request?
162
- def ssl?
163
- @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
241
+ if uri.nil?
242
+ @env.delete('REQUEST_URI')
243
+ uri
244
+ else
245
+ @env['REQUEST_URI'] = uri
246
+ end
247
+ end
164
248
  end
165
249
 
166
250
  # Returns the interpreted path to requested resource after all the installation directory of this application was taken into account
167
251
  def path
168
- path = (uri = request_uri) ? uri.split('?').first : ''
252
+ path = (uri = request_uri) ? uri.split('?').first.to_s : ''
169
253
 
170
254
  # Cut off the path to the installation directory if given
171
255
  path.sub!(%r/^#{relative_url_root}/, '')
@@ -187,29 +271,20 @@ module ActionController
187
271
  end
188
272
  end
189
273
 
190
- # Returns the port number of this request as an integer.
191
- def port
192
- @port_as_int ||= @env['SERVER_PORT'].to_i
193
- end
194
274
 
195
- # Returns the standard port number for this request's protocol
196
- def standard_port
197
- case protocol
198
- when 'https://' then 443
199
- else 80
275
+ # Read the request body. This is useful for web services that need to
276
+ # work with raw requests directly.
277
+ def raw_post
278
+ unless env.include? 'RAW_POST_DATA'
279
+ env['RAW_POST_DATA'] = body.read(content_length)
280
+ body.rewind if body.respond_to?(:rewind)
200
281
  end
282
+ env['RAW_POST_DATA']
201
283
  end
202
284
 
203
- # Returns a port suffix like ":8080" if the port number of this request
204
- # is not the default HTTP port 80 or HTTPS port 443.
205
- def port_string
206
- (port == standard_port) ? '' : ":#{port}"
207
- end
208
-
209
- # Returns a host:port string for this request, such as example.com or
210
- # example.com:8080.
211
- def host_with_port
212
- host + port_string
285
+ # Returns both GET and POST parameters in a single hash.
286
+ def parameters
287
+ @parameters ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
213
288
  end
214
289
 
215
290
  def path_parameters=(parameters) #:nodoc:
@@ -222,31 +297,29 @@ module ActionController
222
297
  @symbolized_path_parameters ||= path_parameters.symbolize_keys
223
298
  end
224
299
 
225
- # Returns a hash with the parameters used to form the path of the request
300
+ # Returns a hash with the parameters used to form the path of the request.
301
+ # Returned hash keys are strings. See <tt>symbolized_path_parameters</tt> for symbolized keys.
226
302
  #
227
303
  # Example:
228
304
  #
229
- # {:action => 'my_action', :controller => 'my_controller'}
305
+ # {'action' => 'my_action', 'controller' => 'my_controller'}
230
306
  def path_parameters
231
307
  @path_parameters ||= {}
232
308
  end
233
309
 
234
- # Returns the lowercase name of the HTTP server software.
235
- def server_software
236
- (@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
237
- end
238
310
 
239
311
  #--
240
312
  # Must be implemented in the concrete request
241
313
  #++
242
- def query_parameters #:nodoc:
314
+
315
+ # The request body is an IO input stream.
316
+ def body
243
317
  end
244
318
 
245
- def request_parameters #:nodoc:
319
+ def query_parameters #:nodoc:
246
320
  end
247
321
 
248
- # Returns the host for this request, such as example.com.
249
- def host
322
+ def request_parameters #:nodoc:
250
323
  end
251
324
 
252
325
  def cookies #:nodoc:
@@ -261,5 +334,397 @@ module ActionController
261
334
 
262
335
  def reset_session #:nodoc:
263
336
  end
337
+
338
+ protected
339
+ # The raw content type string. Use when you need parameters such as
340
+ # charset or boundary which aren't included in the content_type MIME type.
341
+ # Overridden by the X-POST_DATA_FORMAT header for backward compatibility.
342
+ def content_type_with_parameters
343
+ content_type_from_legacy_post_data_format_header ||
344
+ env['CONTENT_TYPE'].to_s
345
+ end
346
+
347
+ # The raw content type string with its parameters stripped off.
348
+ def content_type_without_parameters
349
+ @content_type_without_parameters ||= self.class.extract_content_type_without_parameters(content_type_with_parameters)
350
+ end
351
+
352
+ private
353
+ def content_type_from_legacy_post_data_format_header
354
+ if x_post_format = @env['HTTP_X_POST_DATA_FORMAT']
355
+ case x_post_format.to_s.downcase
356
+ when 'yaml'; 'application/x-yaml'
357
+ when 'xml'; 'application/xml'
358
+ end
359
+ end
360
+ end
361
+
362
+ def parse_formatted_request_parameters
363
+ return {} if content_length.zero?
364
+
365
+ content_type, boundary = self.class.extract_multipart_boundary(content_type_with_parameters)
366
+
367
+ # Don't parse params for unknown requests.
368
+ return {} if content_type.blank?
369
+
370
+ mime_type = Mime::Type.lookup(content_type)
371
+ strategy = ActionController::Base.param_parsers[mime_type]
372
+
373
+ # Only multipart form parsing expects a stream.
374
+ body = (strategy && strategy != :multipart_form) ? raw_post : self.body
375
+
376
+ case strategy
377
+ when Proc
378
+ strategy.call(body)
379
+ when :url_encoded_form
380
+ self.class.clean_up_ajax_request_body! body
381
+ self.class.parse_query_parameters(body)
382
+ when :multipart_form
383
+ self.class.parse_multipart_form_parameters(body, boundary, content_length, env)
384
+ when :xml_simple, :xml_node
385
+ body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
386
+ when :yaml
387
+ YAML.load(body)
388
+ else
389
+ {}
390
+ end
391
+ rescue Exception => e # YAML, XML or Ruby code block errors
392
+ raise
393
+ { "body" => body,
394
+ "content_type" => content_type_with_parameters,
395
+ "content_length" => content_length,
396
+ "exception" => "#{e.message} (#{e.class})",
397
+ "backtrace" => e.backtrace }
398
+ end
399
+
400
+ def named_host?(host)
401
+ !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
402
+ end
403
+
404
+ class << self
405
+ def parse_query_parameters(query_string)
406
+ return {} if query_string.blank?
407
+
408
+ pairs = query_string.split('&').collect do |chunk|
409
+ next if chunk.empty?
410
+ key, value = chunk.split('=', 2)
411
+ next if key.empty?
412
+ value = value.nil? ? nil : CGI.unescape(value)
413
+ [ CGI.unescape(key), value ]
414
+ end.compact
415
+
416
+ UrlEncodedPairParser.new(pairs).result
417
+ end
418
+
419
+ def parse_request_parameters(params)
420
+ parser = UrlEncodedPairParser.new
421
+
422
+ params = params.dup
423
+ until params.empty?
424
+ for key, value in params
425
+ if key.blank?
426
+ params.delete key
427
+ elsif !key.include?('[')
428
+ # much faster to test for the most common case first (GET)
429
+ # and avoid the call to build_deep_hash
430
+ parser.result[key] = get_typed_value(value[0])
431
+ params.delete key
432
+ elsif value.is_a?(Array)
433
+ parser.parse(key, get_typed_value(value.shift))
434
+ params.delete key if value.empty?
435
+ else
436
+ raise TypeError, "Expected array, found #{value.inspect}"
437
+ end
438
+ end
439
+ end
440
+
441
+ parser.result
442
+ end
443
+
444
+ def parse_multipart_form_parameters(body, boundary, content_length, env)
445
+ parse_request_parameters(read_multipart(body, boundary, content_length, env))
446
+ end
447
+
448
+ def extract_multipart_boundary(content_type_with_parameters)
449
+ if content_type_with_parameters =~ MULTIPART_BOUNDARY
450
+ ['multipart/form-data', $1.dup]
451
+ else
452
+ extract_content_type_without_parameters(content_type_with_parameters)
453
+ end
454
+ end
455
+
456
+ def extract_content_type_without_parameters(content_type_with_parameters)
457
+ $1.strip.downcase if content_type_with_parameters =~ /^([^,\;]*)/
458
+ end
459
+
460
+ def clean_up_ajax_request_body!(body)
461
+ body.chop! if body[-1] == 0
462
+ body.gsub!(/&_=$/, '')
463
+ end
464
+
465
+
466
+ private
467
+ def get_typed_value(value)
468
+ case value
469
+ when String
470
+ value
471
+ when NilClass
472
+ ''
473
+ when Array
474
+ value.map { |v| get_typed_value(v) }
475
+ else
476
+ if value.is_a?(UploadedFile)
477
+ # Uploaded file
478
+ if value.original_filename
479
+ value
480
+ # Multipart param
481
+ else
482
+ result = value.read
483
+ value.rewind
484
+ result
485
+ end
486
+ # Unknown value, neither string nor multipart.
487
+ else
488
+ raise "Unknown form value: #{value.inspect}"
489
+ end
490
+ end
491
+ end
492
+
493
+
494
+ MULTIPART_BOUNDARY = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
495
+
496
+ EOL = "\015\012"
497
+
498
+ def read_multipart(body, boundary, content_length, env)
499
+ params = Hash.new([])
500
+ boundary = "--" + boundary
501
+ quoted_boundary = Regexp.quote(boundary, "n")
502
+ buf = ""
503
+ bufsize = 10 * 1024
504
+ boundary_end=""
505
+
506
+ # start multipart/form-data
507
+ body.binmode if defined? body.binmode
508
+ boundary_size = boundary.size + EOL.size
509
+ content_length -= boundary_size
510
+ status = body.read(boundary_size)
511
+ if nil == status
512
+ raise EOFError, "no content body"
513
+ elsif boundary + EOL != status
514
+ raise EOFError, "bad content body"
515
+ end
516
+
517
+ loop do
518
+ head = nil
519
+ content =
520
+ if 10240 < content_length
521
+ UploadedTempfile.new("CGI")
522
+ else
523
+ UploadedStringIO.new
524
+ end
525
+ content.binmode if defined? content.binmode
526
+
527
+ until head and /#{quoted_boundary}(?:#{EOL}|--)/n.match(buf)
528
+
529
+ if (not head) and /#{EOL}#{EOL}/n.match(buf)
530
+ buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
531
+ head = $1.dup
532
+ ""
533
+ end
534
+ next
535
+ end
536
+
537
+ if head and ( (EOL + boundary + EOL).size < buf.size )
538
+ content.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)]
539
+ buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
540
+ end
541
+
542
+ c = if bufsize < content_length
543
+ body.read(bufsize)
544
+ else
545
+ body.read(content_length)
546
+ end
547
+ if c.nil? || c.empty?
548
+ raise EOFError, "bad content body"
549
+ end
550
+ buf.concat(c)
551
+ content_length -= c.size
552
+ end
553
+
554
+ buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
555
+ content.print $1
556
+ if "--" == $2
557
+ content_length = -1
558
+ end
559
+ boundary_end = $2.dup
560
+ ""
561
+ end
562
+
563
+ content.rewind
564
+
565
+ head =~ /Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;]*))/ni
566
+ if filename = $1 || $2
567
+ if /Mac/ni.match(env['HTTP_USER_AGENT']) and
568
+ /Mozilla/ni.match(env['HTTP_USER_AGENT']) and
569
+ (not /MSIE/ni.match(env['HTTP_USER_AGENT']))
570
+ filename = CGI.unescape(filename)
571
+ end
572
+ content.original_path = filename.dup
573
+ end
574
+
575
+ head =~ /Content-Type: ([^\r]*)/ni
576
+ content.content_type = $1.dup if $1
577
+
578
+ head =~ /Content-Disposition:.* name="?([^\";]*)"?/ni
579
+ name = $1.dup if $1
580
+
581
+ if params.has_key?(name)
582
+ params[name].push(content)
583
+ else
584
+ params[name] = [content]
585
+ end
586
+ break if buf.size == 0
587
+ break if content_length == -1
588
+ end
589
+ raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/
590
+
591
+ begin
592
+ body.rewind if body.respond_to?(:rewind)
593
+ rescue Errno::ESPIPE
594
+ # Handles exceptions raised by input streams that cannot be rewound
595
+ # such as when using plain CGI under Apache
596
+ end
597
+
598
+ params
599
+ end
600
+ end
601
+ end
602
+
603
+ class UrlEncodedPairParser < StringScanner #:nodoc:
604
+ attr_reader :top, :parent, :result
605
+
606
+ def initialize(pairs = [])
607
+ super('')
608
+ @result = {}
609
+ pairs.each { |key, value| parse(key, value) }
610
+ end
611
+
612
+ KEY_REGEXP = %r{([^\[\]=&]+)}
613
+ BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
614
+
615
+ # Parse the query string
616
+ def parse(key, value)
617
+ self.string = key
618
+ @top, @parent = result, nil
619
+
620
+ # First scan the bare key
621
+ key = scan(KEY_REGEXP) or return
622
+ key = post_key_check(key)
623
+
624
+ # Then scan as many nestings as present
625
+ until eos?
626
+ r = scan(BRACKETED_KEY_REGEXP) or return
627
+ key = self[1]
628
+ key = post_key_check(key)
629
+ end
630
+
631
+ bind(key, value)
632
+ end
633
+
634
+ private
635
+ # After we see a key, we must look ahead to determine our next action. Cases:
636
+ #
637
+ # [] follows the key. Then the value must be an array.
638
+ # = follows the key. (A value comes next)
639
+ # & or the end of string follows the key. Then the key is a flag.
640
+ # otherwise, a hash follows the key.
641
+ def post_key_check(key)
642
+ if scan(/\[\]/) # a[b][] indicates that b is an array
643
+ container(key, Array)
644
+ nil
645
+ elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
646
+ container(key, Hash)
647
+ nil
648
+ else # End of key? We do nothing.
649
+ key
650
+ end
651
+ end
652
+
653
+ # Add a container to the stack.
654
+ def container(key, klass)
655
+ type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
656
+ value = bind(key, klass.new)
657
+ type_conflict! klass, value unless value.is_a?(klass)
658
+ push(value)
659
+ end
660
+
661
+ # Push a value onto the 'stack', which is actually only the top 2 items.
662
+ def push(value)
663
+ @parent, @top = @top, value
664
+ end
665
+
666
+ # Bind a key (which may be nil for items in an array) to the provided value.
667
+ def bind(key, value)
668
+ if top.is_a? Array
669
+ if key
670
+ if top[-1].is_a?(Hash) && ! top[-1].key?(key)
671
+ top[-1][key] = value
672
+ else
673
+ top << {key => value}.with_indifferent_access
674
+ push top.last
675
+ end
676
+ else
677
+ top << value
678
+ end
679
+ elsif top.is_a? Hash
680
+ key = CGI.unescape(key)
681
+ parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
682
+ return top[key] ||= value
683
+ else
684
+ raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
685
+ end
686
+
687
+ return value
688
+ end
689
+
690
+ def type_conflict!(klass, value)
691
+ 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."
692
+ end
693
+ end
694
+
695
+ module UploadedFile
696
+ def self.included(base)
697
+ base.class_eval do
698
+ attr_accessor :original_path, :content_type
699
+ alias_method :local_path, :path
700
+ end
701
+ end
702
+
703
+ # Take the basename of the upload's original filename.
704
+ # This handles the full Windows paths given by Internet Explorer
705
+ # (and perhaps other broken user agents) without affecting
706
+ # those which give the lone filename.
707
+ # The Windows regexp is adapted from Perl's File::Basename.
708
+ def original_filename
709
+ unless defined? @original_filename
710
+ @original_filename =
711
+ unless original_path.blank?
712
+ if original_path =~ /^(?:.*[:\\\/])?(.*)/m
713
+ $1
714
+ else
715
+ File.basename original_path
716
+ end
717
+ end
718
+ end
719
+ @original_filename
720
+ end
721
+ end
722
+
723
+ class UploadedStringIO < StringIO
724
+ include UploadedFile
725
+ end
726
+
727
+ class UploadedTempfile < Tempfile
728
+ include UploadedFile
264
729
  end
265
730
  end