Remove XML Parser from ActionDispatch
If you want an ability to parse XML parameters, please install `actionpack-xml_parser` gem.
This commit is contained in:
parent
e1456ad95e
commit
c9909db9f2
@ -1,5 +1,10 @@
|
||||
## Rails 4.0.0 (unreleased) ##
|
||||
|
||||
* Remove support for parsing XML parameters from request. If you still want to parse XML
|
||||
parameters, please install `actionpack-xml_parser' gem.
|
||||
|
||||
*Prem Sichanugrist*
|
||||
|
||||
* Fix `time_zone_options_for_select` to call `dup` on the returned TimeZone array.
|
||||
|
||||
Previously if you supplied :priority_zones options to `time_zone_options_for_select`
|
||||
|
@ -13,10 +13,7 @@ def initialize(message, original_exception)
|
||||
end
|
||||
end
|
||||
|
||||
DEFAULT_PARSERS = {
|
||||
Mime::XML => :xml_simple,
|
||||
Mime::JSON => :json
|
||||
}
|
||||
DEFAULT_PARSERS = { Mime::JSON => :json }
|
||||
|
||||
def initialize(app, parsers = {})
|
||||
@app, @parsers = app, DEFAULT_PARSERS.merge(parsers)
|
||||
@ -36,19 +33,13 @@ def parse_formatted_parameters(env)
|
||||
|
||||
return false if request.content_length.zero?
|
||||
|
||||
mime_type = content_type_from_legacy_post_data_format_header(env) ||
|
||||
request.content_mime_type
|
||||
|
||||
strategy = @parsers[mime_type]
|
||||
strategy = @parsers[request.content_mime_type]
|
||||
|
||||
return false unless strategy
|
||||
|
||||
case strategy
|
||||
when Proc
|
||||
strategy.call(request.raw_post)
|
||||
when :xml_simple, :xml_node
|
||||
data = request.deep_munge(Hash.from_xml(request.body.read) || {})
|
||||
data.with_indifferent_access
|
||||
when :json
|
||||
data = ActiveSupport::JSON.decode(request.body)
|
||||
data = {:_json => data} unless data.is_a?(Hash)
|
||||
@ -56,23 +47,12 @@ def parse_formatted_parameters(env)
|
||||
else
|
||||
false
|
||||
end
|
||||
rescue Exception => e # YAML, XML or Ruby code block errors
|
||||
rescue Exception => e # JSON or Ruby code block errors
|
||||
logger(env).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
|
||||
|
||||
raise ParseError.new(e.message, e)
|
||||
end
|
||||
|
||||
def content_type_from_legacy_post_data_format_header(env)
|
||||
if x_post_format = env['HTTP_X_POST_DATA_FORMAT']
|
||||
case x_post_format.to_s.downcase
|
||||
when 'yaml' then return Mime::YAML
|
||||
when 'xml' then return Mime::XML
|
||||
end
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def logger(env)
|
||||
env['action_dispatch.logger'] || ActiveSupport::Logger.new($stderr)
|
||||
end
|
||||
|
@ -32,169 +32,53 @@ def test_check_parameters
|
||||
end
|
||||
end
|
||||
|
||||
def test_post_xml
|
||||
def test_post_json
|
||||
with_test_route_set do
|
||||
post "/", '<entry attributed="true"><summary>content...</summary></entry>',
|
||||
{'CONTENT_TYPE' => 'application/xml'}
|
||||
post "/", '{"entry":{"summary":"content..."}}', 'CONTENT_TYPE' => 'application/json'
|
||||
|
||||
assert_equal 'entry', @controller.response.body
|
||||
assert @controller.params.has_key?(:entry)
|
||||
assert_equal 'content...', @controller.params["entry"]['summary']
|
||||
assert_equal 'true', @controller.params["entry"]['attributed']
|
||||
end
|
||||
end
|
||||
|
||||
def test_put_xml
|
||||
def test_put_json
|
||||
with_test_route_set do
|
||||
put "/", '<entry attributed="true"><summary>content...</summary></entry>',
|
||||
{'CONTENT_TYPE' => 'application/xml'}
|
||||
put "/", '{"entry":{"summary":"content..."}}', 'CONTENT_TYPE' => 'application/json'
|
||||
|
||||
assert_equal 'entry', @controller.response.body
|
||||
assert @controller.params.has_key?(:entry)
|
||||
assert_equal 'content...', @controller.params["entry"]['summary']
|
||||
assert_equal 'true', @controller.params["entry"]['attributed']
|
||||
end
|
||||
end
|
||||
|
||||
def test_put_xml_using_a_type_node
|
||||
def test_register_and_use_json_simple
|
||||
with_test_route_set do
|
||||
put "/", '<type attributed="true"><summary>content...</summary></type>',
|
||||
{'CONTENT_TYPE' => 'application/xml'}
|
||||
|
||||
assert_equal 'type', @controller.response.body
|
||||
assert @controller.params.has_key?(:type)
|
||||
assert_equal 'content...', @controller.params["type"]['summary']
|
||||
assert_equal 'true', @controller.params["type"]['attributed']
|
||||
end
|
||||
end
|
||||
|
||||
def test_put_xml_using_a_type_node_and_attribute
|
||||
with_test_route_set do
|
||||
put "/", '<type attributed="true"><summary type="boolean">false</summary></type>',
|
||||
{'CONTENT_TYPE' => 'application/xml'}
|
||||
|
||||
assert_equal 'type', @controller.response.body
|
||||
assert @controller.params.has_key?(:type)
|
||||
assert_equal false, @controller.params["type"]['summary']
|
||||
assert_equal 'true', @controller.params["type"]['attributed']
|
||||
end
|
||||
end
|
||||
|
||||
def test_post_xml_using_a_type_node
|
||||
with_test_route_set do
|
||||
post "/", '<font attributed="true"><type>arial</type></font>',
|
||||
{'CONTENT_TYPE' => 'application/xml'}
|
||||
|
||||
assert_equal 'font', @controller.response.body
|
||||
assert @controller.params.has_key?(:font)
|
||||
assert_equal 'arial', @controller.params['font']['type']
|
||||
assert_equal 'true', @controller.params["font"]['attributed']
|
||||
end
|
||||
end
|
||||
|
||||
def test_post_xml_using_a_root_node_named_type
|
||||
with_test_route_set do
|
||||
post "/", '<type type="integer">33</type>',
|
||||
{'CONTENT_TYPE' => 'application/xml'}
|
||||
|
||||
assert @controller.params.has_key?(:type)
|
||||
assert_equal 33, @controller.params['type']
|
||||
end
|
||||
end
|
||||
|
||||
def test_post_xml_using_an_attributted_node_named_type
|
||||
with_test_route_set do
|
||||
with_params_parsers Mime::XML => Proc.new { |data| Hash.from_xml(data)['request'].with_indifferent_access } do
|
||||
post "/", '<request><type type="string">Arial,12</type><z>3</z></request>',
|
||||
{'CONTENT_TYPE' => 'application/xml'}
|
||||
|
||||
assert_equal 'type, z', @controller.response.body
|
||||
assert @controller.params.has_key?(:type)
|
||||
assert_equal 'Arial,12', @controller.params['type'], @controller.params.inspect
|
||||
assert_equal '3', @controller.params['z'], @controller.params.inspect
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_post_xml_using_a_disallowed_type_attribute
|
||||
$stderr = StringIO.new
|
||||
with_test_route_set do
|
||||
post '/', '<foo type="symbol">value</foo>', 'CONTENT_TYPE' => 'application/xml'
|
||||
assert_response 500
|
||||
|
||||
post '/', '<foo type="yaml">value</foo>', 'CONTENT_TYPE' => 'application/xml'
|
||||
assert_response 500
|
||||
end
|
||||
ensure
|
||||
$stderr = STDERR
|
||||
end
|
||||
|
||||
def test_register_and_use_xml_simple
|
||||
with_test_route_set do
|
||||
with_params_parsers Mime::XML => Proc.new { |data| Hash.from_xml(data)['request'].with_indifferent_access } do
|
||||
post "/", '<request><summary>content...</summary><title>SimpleXml</title></request>',
|
||||
{'CONTENT_TYPE' => 'application/xml'}
|
||||
with_params_parsers Mime::JSON => Proc.new { |data| JSON.parse(data)['request'].with_indifferent_access } do
|
||||
post "/", '{"request":{"summary":"content...","title":"JSON"}}',
|
||||
'CONTENT_TYPE' => 'application/json'
|
||||
|
||||
assert_equal 'summary, title', @controller.response.body
|
||||
assert @controller.params.has_key?(:summary)
|
||||
assert @controller.params.has_key?(:title)
|
||||
assert_equal 'content...', @controller.params["summary"]
|
||||
assert_equal 'SimpleXml', @controller.params["title"]
|
||||
assert_equal 'JSON', @controller.params["title"]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_use_xml_ximple_with_empty_request
|
||||
def test_use_json_with_empty_request
|
||||
with_test_route_set do
|
||||
assert_nothing_raised { post "/", "", {'CONTENT_TYPE' => 'application/xml'} }
|
||||
assert_nothing_raised { post "/", "", 'CONTENT_TYPE' => 'application/json' }
|
||||
assert_equal '', @controller.response.body
|
||||
end
|
||||
end
|
||||
|
||||
def test_dasherized_keys_as_xml
|
||||
def test_dasherized_keys_as_json
|
||||
with_test_route_set do
|
||||
post "/?full=1", "<first-key>\n<sub-key>...</sub-key>\n</first-key>",
|
||||
{'CONTENT_TYPE' => 'application/xml'}
|
||||
assert_equal 'action, controller, first_key(sub_key), full', @controller.response.body
|
||||
assert_equal "...", @controller.params[:first_key][:sub_key]
|
||||
end
|
||||
end
|
||||
|
||||
def test_typecast_as_xml
|
||||
with_test_route_set do
|
||||
xml = <<-XML
|
||||
<data>
|
||||
<a type="integer">15</a>
|
||||
<b type="boolean">false</b>
|
||||
<c type="boolean">true</c>
|
||||
<d type="date">2005-03-17</d>
|
||||
<e type="datetime">2005-03-17T21:41:07Z</e>
|
||||
<f>unparsed</f>
|
||||
<g type="integer">1</g>
|
||||
<g>hello</g>
|
||||
<g type="date">1974-07-25</g>
|
||||
</data>
|
||||
XML
|
||||
post "/", xml, {'CONTENT_TYPE' => 'application/xml'}
|
||||
|
||||
params = @controller.params
|
||||
assert_equal 15, params[:data][:a]
|
||||
assert_equal false, params[:data][:b]
|
||||
assert_equal true, params[:data][:c]
|
||||
assert_equal Date.new(2005,3,17), params[:data][:d]
|
||||
assert_equal Time.utc(2005,3,17,21,41,7), params[:data][:e]
|
||||
assert_equal "unparsed", params[:data][:f]
|
||||
assert_equal [1, "hello", Date.new(1974,7,25)], params[:data][:g]
|
||||
end
|
||||
end
|
||||
|
||||
def test_entities_unescaped_as_xml_simple
|
||||
with_test_route_set do
|
||||
xml = <<-XML
|
||||
<data><foo "bar's" & friends></data>
|
||||
XML
|
||||
post "/", xml, {'CONTENT_TYPE' => 'application/xml'}
|
||||
assert_equal %(<foo "bar's" & friends>), @controller.params[:data]
|
||||
post "/?full=1", '{"first-key":{"sub-key":"..."}}', 'CONTENT_TYPE' => 'application/json'
|
||||
assert_equal 'action, controller, first-key(sub-key), full', @controller.response.body
|
||||
assert_equal "...", @controller.params['first-key']['sub-key']
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,182 +0,0 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class XmlParamsParsingTest < ActionDispatch::IntegrationTest
|
||||
class TestController < ActionController::Base
|
||||
class << self
|
||||
attr_accessor :last_request_parameters
|
||||
end
|
||||
|
||||
def parse
|
||||
self.class.last_request_parameters = request.request_parameters
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
|
||||
def teardown
|
||||
TestController.last_request_parameters = nil
|
||||
end
|
||||
|
||||
test "parses a strict rack.input" do
|
||||
class Linted
|
||||
undef call if method_defined?(:call)
|
||||
def call(env)
|
||||
bar = env['action_dispatch.request.request_parameters']['foo']
|
||||
result = "<ok>#{bar}</ok>"
|
||||
[200, {"Content-Type" => "application/xml", "Content-Length" => result.length.to_s}, [result]]
|
||||
end
|
||||
end
|
||||
req = Rack::MockRequest.new(ActionDispatch::ParamsParser.new(Linted.new))
|
||||
resp = req.post('/', "CONTENT_TYPE" => "application/xml", :input => "<foo>bar</foo>", :lint => true)
|
||||
assert_equal "<ok>bar</ok>", resp.body
|
||||
end
|
||||
|
||||
def assert_parses(expected, xml)
|
||||
with_test_routing do
|
||||
post "/parse", xml, default_headers
|
||||
assert_response :ok
|
||||
assert_equal(expected, TestController.last_request_parameters)
|
||||
end
|
||||
end
|
||||
|
||||
test "nils are stripped from collections" do
|
||||
assert_parses(
|
||||
{"hash" => { "person" => nil} },
|
||||
"<hash><person type=\"array\"><person nil=\"true\"/></person></hash>")
|
||||
assert_parses(
|
||||
{"hash" => { "person" => ['foo']} },
|
||||
"<hash><person type=\"array\"><person>foo</person><person nil=\"true\"/></person>\n</hash>")
|
||||
end
|
||||
|
||||
test "parses hash params" do
|
||||
with_test_routing do
|
||||
xml = "<person><name>David</name></person>"
|
||||
post "/parse", xml, default_headers
|
||||
assert_response :ok
|
||||
assert_equal({"person" => {"name" => "David"}}, TestController.last_request_parameters)
|
||||
end
|
||||
end
|
||||
|
||||
test "parses single file" do
|
||||
with_test_routing do
|
||||
xml = "<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{::Base64.encode64('ABC')}</avatar></person>"
|
||||
post "/parse", xml, default_headers
|
||||
assert_response :ok
|
||||
|
||||
person = TestController.last_request_parameters
|
||||
assert_equal "image/jpg", person['person']['avatar'].content_type
|
||||
assert_equal "me.jpg", person['person']['avatar'].original_filename
|
||||
assert_equal "ABC", person['person']['avatar'].read
|
||||
end
|
||||
end
|
||||
|
||||
test "logs error if parsing unsuccessful" do
|
||||
with_test_routing do
|
||||
output = StringIO.new
|
||||
xml = "<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{::Base64.encode64('ABC')}</avatar></pineapple>"
|
||||
post "/parse", xml, default_headers.merge('action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => ActiveSupport::Logger.new(output))
|
||||
assert_response :error
|
||||
output.rewind && err = output.read
|
||||
assert err =~ /Error occurred while parsing request parameters/
|
||||
end
|
||||
end
|
||||
|
||||
test "occurring a parse error if parsing unsuccessful" do
|
||||
with_test_routing do
|
||||
begin
|
||||
$stderr = StringIO.new # suppress the log
|
||||
xml = "<person><name>David</name></pineapple>"
|
||||
exception = assert_raise(ActionDispatch::ParamsParser::ParseError) { post "/parse", xml, default_headers.merge('action_dispatch.show_exceptions' => false) }
|
||||
assert_equal REXML::ParseException, exception.original_exception.class
|
||||
assert_equal exception.original_exception.message, exception.message
|
||||
ensure
|
||||
$stderr = STDERR
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "parses multiple files" do
|
||||
xml = <<-end_body
|
||||
<person>
|
||||
<name>David</name>
|
||||
<avatars>
|
||||
<avatar type='file' name='me.jpg' content_type='image/jpg'>#{::Base64.encode64('ABC')}</avatar>
|
||||
<avatar type='file' name='you.gif' content_type='image/gif'>#{::Base64.encode64('DEF')}</avatar>
|
||||
</avatars>
|
||||
</person>
|
||||
end_body
|
||||
|
||||
with_test_routing do
|
||||
post "/parse", xml, default_headers
|
||||
assert_response :ok
|
||||
end
|
||||
|
||||
person = TestController.last_request_parameters
|
||||
|
||||
assert_equal "image/jpg", person['person']['avatars']['avatar'].first.content_type
|
||||
assert_equal "me.jpg", person['person']['avatars']['avatar'].first.original_filename
|
||||
assert_equal "ABC", person['person']['avatars']['avatar'].first.read
|
||||
|
||||
assert_equal "image/gif", person['person']['avatars']['avatar'].last.content_type
|
||||
assert_equal "you.gif", person['person']['avatars']['avatar'].last.original_filename
|
||||
assert_equal "DEF", person['person']['avatars']['avatar'].last.read
|
||||
end
|
||||
|
||||
private
|
||||
def with_test_routing
|
||||
with_routing do |set|
|
||||
set.draw do
|
||||
post ':action', :to => ::XmlParamsParsingTest::TestController
|
||||
end
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def default_headers
|
||||
{'CONTENT_TYPE' => 'application/xml'}
|
||||
end
|
||||
end
|
||||
|
||||
class LegacyXmlParamsParsingTest < XmlParamsParsingTest
|
||||
private
|
||||
def default_headers
|
||||
{'HTTP_X_POST_DATA_FORMAT' => 'xml'}
|
||||
end
|
||||
end
|
||||
|
||||
class RootLessXmlParamsParsingTest < ActionDispatch::IntegrationTest
|
||||
class TestController < ActionController::Base
|
||||
wrap_parameters :person, :format => :xml
|
||||
|
||||
class << self
|
||||
attr_accessor :last_request_parameters
|
||||
end
|
||||
|
||||
def parse
|
||||
self.class.last_request_parameters = request.request_parameters
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
|
||||
def teardown
|
||||
TestController.last_request_parameters = nil
|
||||
end
|
||||
|
||||
test "parses hash params" do
|
||||
with_test_routing do
|
||||
xml = "<name>David</name>"
|
||||
post "/parse", xml, {'CONTENT_TYPE' => 'application/xml'}
|
||||
assert_response :ok
|
||||
assert_equal({"name" => "David", "person" => {"name" => "David"}}, TestController.last_request_parameters)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def with_test_routing
|
||||
with_routing do |set|
|
||||
set.draw do
|
||||
post ':action', :to => ::RootLessXmlParamsParsingTest::TestController
|
||||
end
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
@ -116,9 +116,9 @@ When this form is submitted, the value of `params[:client]` will be `{"name" =>
|
||||
|
||||
Note that the `params` hash is actually an instance of `ActiveSupport::HashWithIndifferentAccess`, which acts like a hash that lets you use symbols and strings interchangeably as keys.
|
||||
|
||||
### JSON/XML parameters
|
||||
### JSON parameters
|
||||
|
||||
If you're writing a web service application, you might find yourself more comfortable on accepting parameters in JSON or XML format. Rails will automatically convert your parameters into `params` hash, which you'll be able to access like you would normally do with form data.
|
||||
If you're writing a web service application, you might find yourself more comfortable on accepting parameters in JSON format. Rails will automatically convert your parameters into `params` hash, which you'll be able to access like you would normally do with form data.
|
||||
|
||||
So for example, if you are sending this JSON parameter:
|
||||
|
||||
@ -128,7 +128,7 @@ So for example, if you are sending this JSON parameter:
|
||||
|
||||
You'll get `params[:company]` as `{ :name => "acme", "address" => "123 Carrot Street" }`.
|
||||
|
||||
Also, if you've turned on `config.wrap_parameters` in your initializer or calling `wrap_parameters` in your controller, you can safely omit the root element in the JSON/XML parameter. The parameters will be cloned and wrapped in the key according to your controller's name by default. So the above parameter can be written as:
|
||||
Also, if you've turned on `config.wrap_parameters` in your initializer or calling `wrap_parameters` in your controller, you can safely omit the root element in the JSON parameter. The parameters will be cloned and wrapped in the key according to your controller's name by default. So the above parameter can be written as:
|
||||
|
||||
```json
|
||||
{ "name": "acme", "address": "123 Carrot Street" }
|
||||
@ -142,6 +142,8 @@ And assume that you're sending the data to `CompaniesController`, it would then
|
||||
|
||||
You can customize the name of the key or specific parameters you want to wrap by consulting the [API documentation](http://api.rubyonrails.org/classes/ActionController/ParamsWrapper.html)
|
||||
|
||||
NOTE: A support for parsing XML parameters has been extracted into a gem named `actionpack-xml_parser`
|
||||
|
||||
### Routing Parameters
|
||||
|
||||
The `params` hash will always contain the `:controller` and `:action` keys, but you should use the methods `controller_name` and `action_name` instead to access these values. Any other parameters defined by the routing, such as `:id` will also be available. As an example, consider a listing of clients where the list can show either active or inactive clients. We can add a route which captures the `:status` parameter in a "pretty" URL:
|
||||
|
Loading…
Reference in New Issue
Block a user