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.
- data/CHANGELOG +1400 -20
- data/MIT-LICENSE +1 -1
- data/README +5 -5
- data/RUNNING_UNIT_TESTS +4 -5
- data/Rakefile +5 -6
- data/install.rb +2 -2
- data/lib/action_controller.rb +11 -15
- data/lib/action_controller/assertions.rb +12 -25
- data/lib/action_controller/assertions/dom_assertions.rb +18 -4
- data/lib/action_controller/assertions/model_assertions.rb +8 -1
- data/lib/action_controller/assertions/response_assertions.rb +35 -12
- data/lib/action_controller/assertions/routing_assertions.rb +56 -12
- data/lib/action_controller/assertions/selector_assertions.rb +105 -38
- data/lib/action_controller/assertions/tag_assertions.rb +28 -15
- data/lib/action_controller/base.rb +318 -250
- data/lib/action_controller/benchmarking.rb +33 -29
- data/lib/action_controller/caching.rb +130 -64
- data/lib/action_controller/cgi_ext.rb +16 -0
- data/lib/action_controller/cgi_ext/{cookie_performance_fix.rb → cookie.rb} +25 -40
- data/lib/action_controller/cgi_ext/query_extension.rb +22 -0
- data/lib/action_controller/cgi_ext/session.rb +73 -0
- data/lib/action_controller/cgi_ext/stdinput.rb +23 -0
- data/lib/action_controller/cgi_process.rb +34 -57
- data/lib/action_controller/components.rb +19 -36
- data/lib/action_controller/cookies.rb +10 -9
- data/lib/action_controller/dispatcher.rb +195 -0
- data/lib/action_controller/filters.rb +35 -34
- data/lib/action_controller/flash.rb +30 -35
- data/lib/action_controller/helpers.rb +121 -47
- data/lib/action_controller/http_authentication.rb +126 -0
- data/lib/action_controller/integration.rb +105 -101
- data/lib/action_controller/layout.rb +59 -47
- data/lib/action_controller/mime_responds.rb +57 -68
- data/lib/action_controller/mime_type.rb +43 -80
- data/lib/action_controller/mime_types.rb +20 -0
- data/lib/action_controller/polymorphic_routes.rb +88 -0
- data/lib/action_controller/record_identifier.rb +91 -0
- data/lib/action_controller/request.rb +553 -88
- data/lib/action_controller/request_forgery_protection.rb +126 -0
- data/lib/action_controller/request_profiler.rb +138 -0
- data/lib/action_controller/rescue.rb +185 -69
- data/lib/action_controller/resources.rb +211 -172
- data/lib/action_controller/response.rb +49 -8
- data/lib/action_controller/routing.rb +359 -236
- data/lib/action_controller/routing_optimisation.rb +119 -0
- data/lib/action_controller/session/active_record_store.rb +3 -2
- data/lib/action_controller/session/cookie_store.rb +161 -0
- data/lib/action_controller/session/mem_cache_store.rb +9 -16
- data/lib/action_controller/session_management.rb +17 -8
- data/lib/action_controller/streaming.rb +6 -3
- data/lib/action_controller/templates/rescues/_request_and_response.erb +24 -0
- data/lib/action_controller/templates/rescues/{_trace.rhtml → _trace.erb} +0 -0
- data/lib/action_controller/templates/rescues/{diagnostics.rhtml → diagnostics.erb} +2 -2
- data/lib/action_controller/templates/rescues/{layout.rhtml → layout.erb} +0 -0
- data/lib/action_controller/templates/rescues/{missing_template.rhtml → missing_template.erb} +0 -0
- data/lib/action_controller/templates/rescues/{routing_error.rhtml → routing_error.erb} +0 -0
- data/lib/action_controller/templates/rescues/{template_error.rhtml → template_error.erb} +2 -2
- data/lib/action_controller/templates/rescues/{unknown_action.rhtml → unknown_action.erb} +0 -0
- data/lib/action_controller/test_case.rb +53 -0
- data/lib/action_controller/test_process.rb +59 -46
- data/lib/action_controller/url_rewriter.rb +48 -24
- data/lib/action_controller/vendor/html-scanner/html/document.rb +7 -4
- data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +173 -0
- data/lib/action_controller/vendor/html-scanner/html/selector.rb +11 -6
- data/lib/action_controller/verification.rb +27 -21
- data/lib/action_pack.rb +1 -1
- data/lib/action_pack/version.rb +4 -4
- data/lib/action_view.rb +2 -3
- data/lib/action_view/base.rb +218 -63
- data/lib/action_view/compiled_templates.rb +1 -2
- data/lib/action_view/helpers/active_record_helper.rb +35 -17
- data/lib/action_view/helpers/asset_tag_helper.rb +395 -87
- data/lib/action_view/helpers/atom_feed_helper.rb +111 -0
- data/lib/action_view/helpers/benchmark_helper.rb +12 -5
- data/lib/action_view/helpers/cache_helper.rb +29 -0
- data/lib/action_view/helpers/capture_helper.rb +97 -63
- data/lib/action_view/helpers/date_helper.rb +295 -35
- data/lib/action_view/helpers/debug_helper.rb +6 -2
- data/lib/action_view/helpers/form_helper.rb +354 -111
- data/lib/action_view/helpers/form_options_helper.rb +171 -109
- data/lib/action_view/helpers/form_tag_helper.rb +332 -76
- data/lib/action_view/helpers/javascript_helper.rb +35 -11
- data/lib/action_view/helpers/javascripts/controls.js +484 -354
- data/lib/action_view/helpers/javascripts/dragdrop.js +88 -58
- data/lib/action_view/helpers/javascripts/effects.js +396 -364
- data/lib/action_view/helpers/javascripts/prototype.js +2817 -1107
- data/lib/action_view/helpers/number_helper.rb +84 -60
- data/lib/action_view/helpers/prototype_helper.rb +419 -43
- data/lib/action_view/helpers/record_identification_helper.rb +20 -0
- data/lib/action_view/helpers/record_tag_helper.rb +59 -0
- data/lib/action_view/helpers/sanitize_helper.rb +223 -0
- data/lib/action_view/helpers/scriptaculous_helper.rb +63 -4
- data/lib/action_view/helpers/tag_helper.rb +69 -39
- data/lib/action_view/helpers/text_helper.rb +221 -148
- data/lib/action_view/helpers/url_helper.rb +283 -165
- data/lib/action_view/partials.rb +134 -62
- data/lib/action_view/template_error.rb +4 -12
- data/lib/actionpack.rb +1 -0
- data/test/abstract_unit.rb +21 -1
- data/test/action_view_test.rb +26 -0
- data/test/active_record_unit.rb +12 -20
- data/test/activerecord/active_record_store_test.rb +2 -2
- data/test/activerecord/render_partial_with_record_identification_test.rb +74 -0
- data/test/controller/action_pack_assertions_test.rb +21 -152
- data/test/controller/addresses_render_test.rb +2 -7
- data/test/controller/assert_select_test.rb +120 -14
- data/test/controller/base_test.rb +11 -13
- data/test/controller/caching_test.rb +125 -5
- data/test/controller/capture_test.rb +23 -16
- data/test/controller/cgi_test.rb +66 -391
- data/test/controller/components_test.rb +31 -42
- data/test/controller/content_type_test.rb +1 -1
- data/test/controller/cookie_test.rb +42 -14
- data/test/controller/deprecation/deprecated_base_methods_test.rb +1 -42
- data/test/controller/dispatcher_test.rb +123 -0
- data/test/controller/fake_models.rb +5 -0
- data/test/controller/filters_test.rb +44 -7
- data/test/controller/flash_test.rb +46 -2
- data/test/controller/fragment_store_setting_test.rb +10 -8
- data/test/controller/helper_test.rb +19 -2
- data/test/controller/html-scanner/document_test.rb +124 -0
- data/test/controller/html-scanner/node_test.rb +69 -0
- data/test/controller/html-scanner/sanitizer_test.rb +250 -0
- data/test/controller/html-scanner/tag_node_test.rb +239 -0
- data/test/controller/html-scanner/text_node_test.rb +51 -0
- data/test/controller/html-scanner/tokenizer_test.rb +125 -0
- data/test/controller/http_authentication_test.rb +54 -0
- data/test/controller/integration_test.rb +12 -26
- data/test/controller/layout_test.rb +64 -12
- data/test/controller/mime_responds_test.rb +193 -38
- data/test/controller/mime_type_test.rb +30 -8
- data/test/controller/new_render_test.rb +104 -22
- data/test/controller/polymorphic_routes_test.rb +98 -0
- data/test/controller/record_identifier_test.rb +103 -0
- data/test/controller/redirect_test.rb +120 -18
- data/test/controller/render_test.rb +195 -45
- data/test/controller/request_forgery_protection_test.rb +217 -0
- data/test/controller/request_test.rb +545 -27
- data/test/controller/rescue_test.rb +501 -0
- data/test/controller/resources_test.rb +258 -132
- data/test/controller/routing_test.rb +502 -106
- data/test/controller/selector_test.rb +5 -5
- data/test/controller/send_file_test.rb +17 -7
- data/test/controller/session/cookie_store_test.rb +246 -0
- data/test/controller/session/mem_cache_store_test.rb +182 -0
- data/test/controller/session_fixation_test.rb +8 -11
- data/test/controller/session_management_test.rb +7 -7
- data/test/controller/test_test.rb +150 -38
- data/test/controller/url_rewriter_test.rb +87 -12
- data/test/controller/verification_test.rb +11 -0
- data/test/controller/view_paths_test.rb +137 -0
- data/test/controller/webservice_test.rb +11 -75
- data/test/fixtures/addresses/{list.rhtml → list.erb} +0 -0
- data/test/fixtures/db_definitions/sqlite.sql +2 -1
- data/test/fixtures/developer.rb +2 -0
- data/test/fixtures/fun/games/{hello_world.rhtml → hello_world.erb} +0 -0
- data/test/fixtures/helpers/fun/pdf_helper.rb +1 -1
- data/test/fixtures/layout_tests/alt/hello.rhtml +1 -0
- data/test/fixtures/layout_tests/layouts/multiple_extensions.html.erb +1 -0
- data/test/fixtures/layouts/{builder.rxml → builder.builder} +0 -0
- data/test/fixtures/layouts/{standard.rhtml → standard.erb} +0 -0
- data/test/fixtures/layouts/{talk_from_action.rhtml → talk_from_action.erb} +0 -0
- data/test/fixtures/layouts/{yield.rhtml → yield.erb} +0 -0
- data/test/fixtures/multipart/binary_file +0 -0
- data/test/fixtures/multipart/bracketed_param +5 -0
- data/test/fixtures/override/test/hello_world.erb +1 -0
- data/test/fixtures/override2/layouts/test/sub.erb +1 -0
- data/test/fixtures/post_test/layouts/post.html.erb +1 -0
- data/test/fixtures/post_test/layouts/super_post.iphone.erb +1 -0
- data/test/fixtures/post_test/post/index.html.erb +1 -0
- data/test/fixtures/post_test/post/index.iphone.erb +1 -0
- data/test/fixtures/post_test/super_post/index.html.erb +1 -0
- data/test/fixtures/post_test/super_post/index.iphone.erb +1 -0
- data/test/fixtures/public/404.html +1 -0
- data/test/fixtures/public/500.html +1 -0
- data/test/fixtures/public/javascripts/application.js +0 -1
- data/test/fixtures/public/javascripts/bank.js +1 -0
- data/test/fixtures/public/javascripts/robber.js +1 -0
- data/test/fixtures/public/stylesheets/bank.css +1 -0
- data/test/fixtures/public/stylesheets/robber.css +1 -0
- data/test/fixtures/replies.yml +2 -0
- data/test/fixtures/reply.rb +2 -1
- data/test/fixtures/respond_to/{all_types_with_layout.rhtml → all_types_with_layout.html.erb} +0 -0
- data/test/fixtures/respond_to/{all_types_with_layout.rjs → all_types_with_layout.js.rjs} +0 -0
- data/test/fixtures/respond_to/custom_constant_handling_without_block.mobile.erb +1 -0
- data/test/fixtures/respond_to/iphone_with_html_response_type.html.erb +1 -0
- data/test/fixtures/respond_to/iphone_with_html_response_type.iphone.erb +1 -0
- data/test/fixtures/respond_to/layouts/missing.html.erb +1 -0
- data/test/fixtures/respond_to/layouts/standard.html.erb +1 -0
- data/test/fixtures/respond_to/layouts/standard.iphone.erb +1 -0
- data/test/fixtures/respond_to/{using_defaults.rhtml → using_defaults.html.erb} +0 -0
- data/test/fixtures/respond_to/{using_defaults.rjs → using_defaults.js.rjs} +0 -0
- data/test/fixtures/respond_to/{using_defaults.rxml → using_defaults.xml.builder} +0 -0
- data/test/fixtures/respond_to/{using_defaults_with_type_list.rhtml → using_defaults_with_type_list.html.erb} +0 -0
- data/test/fixtures/respond_to/{using_defaults_with_type_list.rjs → using_defaults_with_type_list.js.rjs} +0 -0
- data/test/fixtures/respond_to/{using_defaults_with_type_list.rxml → using_defaults_with_type_list.xml.builder} +0 -0
- data/test/fixtures/scope/test/{modgreet.rhtml → modgreet.erb} +0 -0
- data/test/fixtures/test/{_customer.rhtml → _customer.erb} +0 -0
- data/test/fixtures/test/{_customer_greeting.rhtml → _customer_greeting.erb} +0 -0
- data/test/fixtures/test/_hash_greeting.erb +1 -0
- data/test/fixtures/test/_hash_object.erb +2 -0
- data/test/fixtures/test/{_hello.rxml → _hello.builder} +0 -0
- data/test/fixtures/test/_layout_for_partial.html.erb +3 -0
- data/test/fixtures/test/_partial.erb +1 -0
- data/test/fixtures/test/_partial.html.erb +1 -0
- data/test/fixtures/test/_partial.js.erb +1 -0
- data/test/fixtures/test/_partial_for_use_in_layout.html.erb +1 -0
- data/test/fixtures/test/{_partial_only.rhtml → _partial_only.erb} +0 -0
- data/test/fixtures/test/{_person.rhtml → _person.erb} +0 -0
- data/test/fixtures/test/{action_talk_to_layout.rhtml → action_talk_to_layout.erb} +0 -0
- data/test/fixtures/test/{block_content_for.rhtml → block_content_for.erb} +0 -0
- data/test/fixtures/test/calling_partial_with_layout.html.erb +1 -0
- data/test/fixtures/test/{capturing.rhtml → capturing.erb} +0 -0
- data/test/fixtures/test/{content_for.rhtml → content_for.erb} +0 -0
- data/test/fixtures/test/content_for_concatenated.erb +3 -0
- data/test/fixtures/test/content_for_with_parameter.erb +2 -0
- data/test/fixtures/test/dot.directory/{render_file_with_ivar.rhtml → render_file_with_ivar.erb} +0 -0
- data/test/fixtures/test/{erb_content_for.rhtml → erb_content_for.erb} +0 -0
- data/test/fixtures/test/formatted_html_erb.html.erb +1 -0
- data/test/fixtures/test/formatted_xml_erb.builder +1 -0
- data/test/fixtures/test/formatted_xml_erb.html.erb +1 -0
- data/test/fixtures/test/formatted_xml_erb.xml.erb +1 -0
- data/test/fixtures/test/{greeting.rhtml → greeting.erb} +0 -0
- data/test/fixtures/test/{hello.rxml → hello.builder} +0 -0
- data/test/fixtures/test/{hello_world.rxml → hello_world.builder} +0 -0
- data/test/fixtures/test/{hello_world.rhtml → hello_world.erb} +0 -0
- data/test/fixtures/test/{hello_world_container.rxml → hello_world_container.builder} +0 -0
- data/test/fixtures/test/{hello_world_with_layout_false.rhtml → hello_world_with_layout_false.erb} +0 -0
- data/test/fixtures/test/{hello_xml_world.rxml → hello_xml_world.builder} +0 -0
- data/test/fixtures/test/list.erb +1 -0
- data/test/fixtures/test/{non_erb_block_content_for.rxml → non_erb_block_content_for.builder} +0 -0
- data/test/fixtures/test/{potential_conflicts.rhtml → potential_conflicts.erb} +0 -0
- data/test/fixtures/test/{render_file_with_ivar.rhtml → render_file_with_ivar.erb} +0 -0
- data/test/fixtures/test/{render_file_with_locals.rhtml → render_file_with_locals.erb} +0 -0
- data/test/fixtures/test/{render_to_string_test.rhtml → render_to_string_test.erb} +0 -0
- data/test/fixtures/test/{update_element_with_capture.rhtml → update_element_with_capture.erb} +0 -0
- data/test/fixtures/test/using_layout_around_block.html.erb +1 -0
- data/test/fixtures/topic.rb +1 -1
- data/test/template/active_record_helper_test.rb +67 -20
- data/test/template/asset_tag_helper_test.rb +222 -54
- data/test/template/atom_feed_helper_test.rb +101 -0
- data/test/template/benchmark_helper_test.rb +2 -2
- data/test/template/compiled_templates_test.rb +76 -32
- data/test/template/date_helper_test.rb +125 -9
- data/test/template/form_helper_test.rb +326 -33
- data/test/template/form_options_helper_test.rb +822 -15
- data/test/template/form_tag_helper_test.rb +96 -30
- data/test/template/javascript_helper_test.rb +61 -13
- data/test/template/number_helper_test.rb +12 -11
- data/test/template/prototype_helper_test.rb +185 -24
- data/test/template/sanitize_helper_test.rb +49 -0
- data/test/template/scriptaculous_helper_test.rb +9 -3
- data/test/template/tag_helper_test.rb +13 -2
- data/test/template/text_helper_test.rb +38 -52
- data/test/template/url_helper_test.rb +216 -46
- metadata +144 -116
- data/examples/.htaccess +0 -24
- data/examples/address_book/index.rhtml +0 -33
- data/examples/address_book/layout.rhtml +0 -8
- data/examples/address_book_controller.cgi +0 -9
- data/examples/address_book_controller.fcgi +0 -6
- data/examples/address_book_controller.rb +0 -52
- data/examples/address_book_controller.rbx +0 -4
- data/examples/benchmark.rb +0 -52
- data/examples/benchmark_with_ar.fcgi +0 -89
- data/examples/blog_controller.cgi +0 -53
- data/examples/debate/index.rhtml +0 -14
- data/examples/debate/new_topic.rhtml +0 -22
- data/examples/debate/topic.rhtml +0 -32
- data/examples/debate_controller.cgi +0 -57
- data/lib/action_controller/assertions/deprecated_assertions.rb +0 -228
- data/lib/action_controller/cgi_ext/cgi_ext.rb +0 -36
- data/lib/action_controller/cgi_ext/cgi_methods.rb +0 -211
- data/lib/action_controller/cgi_ext/pstore_performance_fix.rb +0 -30
- data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +0 -95
- data/lib/action_controller/cgi_ext/session_performance_fix.rb +0 -30
- data/lib/action_controller/deprecated_dependencies.rb +0 -65
- data/lib/action_controller/deprecated_redirects.rb +0 -17
- data/lib/action_controller/deprecated_request_methods.rb +0 -34
- data/lib/action_controller/macros/auto_complete.rb +0 -53
- data/lib/action_controller/macros/in_place_editing.rb +0 -33
- data/lib/action_controller/pagination.rb +0 -408
- data/lib/action_controller/scaffolding.rb +0 -189
- data/lib/action_controller/templates/rescues/_request_and_response.rhtml +0 -44
- data/lib/action_controller/templates/scaffolds/edit.rhtml +0 -7
- data/lib/action_controller/templates/scaffolds/layout.rhtml +0 -69
- data/lib/action_controller/templates/scaffolds/list.rhtml +0 -27
- data/lib/action_controller/templates/scaffolds/new.rhtml +0 -6
- data/lib/action_controller/templates/scaffolds/show.rhtml +0 -9
- data/lib/action_controller/vendor/xml_node.rb +0 -97
- data/lib/action_view/helpers/deprecated_helper.rb +0 -37
- data/lib/action_view/helpers/java_script_macros_helper.rb +0 -233
- data/lib/action_view/helpers/pagination_helper.rb +0 -86
- data/test/activerecord/active_record_assertions_test.rb +0 -92
- data/test/activerecord/pagination_test.rb +0 -165
- data/test/controller/deprecated_instance_variables_test.rb +0 -48
- data/test/controller/raw_post_test.rb +0 -68
- data/test/fixtures/deprecated_instance_variables/_cookies_ivar.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_cookies_method.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_flash_ivar.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_flash_method.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_headers_ivar.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_headers_method.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_params_ivar.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_params_method.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_request_ivar.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_request_method.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_response_ivar.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_response_method.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_session_ivar.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_session_method.rhtml +0 -1
- data/test/fixtures/respond_to/layouts/standard.rhtml +0 -1
- data/test/fixtures/test/_hash_object.rhtml +0 -1
- data/test/fixtures/test/list.rhtml +0 -1
- data/test/template/deprecated_helper_test.rb +0 -36
- data/test/template/deprecated_instance_variables_test.rb +0 -43
- 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
|
-
#
|
3
|
-
|
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
|
-
#
|
14
|
+
# The hash of environment variables for this request,
|
8
15
|
# such as { 'RAILS_ENV' => 'production' }.
|
9
16
|
attr_reader :env
|
10
17
|
|
11
|
-
#
|
12
|
-
|
13
|
-
|
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
|
-
#
|
17
|
-
#
|
18
|
-
#
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
55
|
+
request_method == :delete
|
45
56
|
end
|
46
57
|
|
47
|
-
# Is this a HEAD request?
|
48
|
-
#
|
58
|
+
# Is this a HEAD request? request.method sees HEAD as :get, so check the
|
59
|
+
# HTTP method directly.
|
49
60
|
def head?
|
50
|
-
|
61
|
+
request_method == :head
|
51
62
|
end
|
52
63
|
|
53
|
-
|
54
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
#
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
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
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
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
|
-
#
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
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
|
204
|
-
|
205
|
-
|
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
|
-
# {
|
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
|
-
|
314
|
+
|
315
|
+
# The request body is an IO input stream.
|
316
|
+
def body
|
243
317
|
end
|
244
318
|
|
245
|
-
def
|
319
|
+
def query_parameters #:nodoc:
|
246
320
|
end
|
247
321
|
|
248
|
-
|
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
|