From dfc422b784d3f62daa5a876d65b0ac983c33408a Mon Sep 17 00:00:00 2001 From: Leon Breedt Date: Wed, 13 Jul 2005 06:05:13 +0000 Subject: always send back SOAP responses as UTF-8, we can't guarantee that SOAP4R supports any encoding sent by caller. add documentation describing how to ensure :string types don't get converted into :base64 by SOAP4R when containing non-ASCII chars and $KCODE is not set to a value. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1822 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- actionwebservice/README | 19 +++++++++++++ .../action_web_service/protocol/soap_protocol.rb | 31 +++++++++++----------- actionwebservice/test/abstract_dispatcher.rb | 15 +++++------ .../test/dispatcher_action_controller_soap_test.rb | 25 ++++++++++------- 4 files changed, 57 insertions(+), 33 deletions(-) diff --git a/actionwebservice/README b/actionwebservice/README index a725f9db71..5b82475fe2 100644 --- a/actionwebservice/README +++ b/actionwebservice/README @@ -215,6 +215,25 @@ custom types and message definition types: The default namespace used is 'urn:ActionWebService', if you don't supply one. + +== ActionWebService and UTF-8 + +If you're going to be sending back strings containing non-ASCII UTF-8 +characters using the :string data type, you need to make sure that +Ruby is using UTF-8 as the default encoding for its strings. + +The default in Ruby to use US-ASCII encoding for strings, which causes a string +validation check in the Ruby SOAP library to fail and your string to be sent +back as a Base-64 value, which may confuse clients that expected strings +because of the WSDL. + +Two ways of doing setting the default string encoding are: + +* Start Ruby using the -Ku command-line option to the Ruby executable +* Set the $KCODE flag in config/environment.rb to the + string 'UTF8' + + == Testing your APIs diff --git a/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb b/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb index e6bb5488ab..89f222f908 100644 --- a/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb +++ b/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb @@ -11,7 +11,8 @@ module ActionWebService # :nodoc: end class SoapProtocol < AbstractProtocol # :nodoc: - DefaultEncoding = 'utf-8' + AWSEncoding = 'UTF-8' + XSDEncoding = 'UTF8' attr :marshaler @@ -27,10 +28,9 @@ module ActionWebService # :nodoc: def decode_action_pack_request(action_pack_request) return nil unless soap_action = has_valid_soap_action?(action_pack_request) service_name = action_pack_request.parameters['action'] - charset = parse_charset(action_pack_request.env['HTTP_CONTENT_TYPE']) protocol_options = { :soap_action => soap_action, - :charset => charset + :charset => AWSEncoding } decode_request(action_pack_request.raw_post, service_name, protocol_options) end @@ -42,8 +42,7 @@ module ActionWebService # :nodoc: end def decode_request(raw_request, service_name, protocol_options={}) - charset = protocol_options[:charset] || DefaultEncoding - envelope = SOAP::Processor.unmarshal(raw_request, :charset => charset) + envelope = SOAP::Processor.unmarshal(raw_request, :charset => AWSEncoding) unless envelope raise ProtocolError, "Failed to parse SOAP request message" end @@ -109,8 +108,17 @@ module ActionWebService # :nodoc: end end envelope = create_soap_envelope(response) - charset = protocol_options[:charset] || DefaultEncoding - Response.new(SOAP::Processor.marshal(envelope, :charset => charset), "text/xml; charset=#{charset}", return_value) + + # FIXME: This is not thread-safe, but StringFactory_ in SOAP4R only + # reads target encoding from the XSD::Charset.encoding variable. + # This is required to ensure $KCODE strings are converted + # correctly to UTF-8 for any values of $KCODE. + previous_encoding = XSD::Charset.encoding + XSD::Charset.encoding = XSDEncoding + response_body = SOAP::Processor.marshal(envelope, :charset => AWSEncoding) + XSD::Charset.encoding = previous_encoding + + Response.new(response_body, "text/xml; charset=#{AWSEncoding}", return_value) end def protocol_client(api, protocol_name, endpoint_uri, options={}) @@ -138,15 +146,6 @@ module ActionWebService # :nodoc: soap_action end - def parse_charset(content_type) - return DefaultEncoding if content_type.nil? - if /^text\/xml(?:\s*;\s*charset=([^"]+|"[^"]+"))$/i =~ content_type - $1 - else - DefaultEncoding - end - end - def create_soap_envelope(body) header = SOAP::SOAPHeader.new body = SOAP::SOAPBody.new(body) diff --git a/actionwebservice/test/abstract_dispatcher.rb b/actionwebservice/test/abstract_dispatcher.rb index e01d902ae1..4f8cd1fb4c 100644 --- a/actionwebservice/test/abstract_dispatcher.rb +++ b/actionwebservice/test/abstract_dispatcher.rb @@ -4,6 +4,7 @@ require 'stringio' class ActionController::Base; def rescue_action(e) raise e end; end module DispatcherTest + Utf8String = "One World Caf\303\251" WsdlNamespace = 'http://rubyonrails.com/some/namespace' class Node < ActiveRecord::Base @@ -58,6 +59,7 @@ module DispatcherTest api_method :hash_struct_return, :returns => [[Person]] api_method :thrower api_method :void + api_method :test_utf8, :returns => [:string] api_method :hex, :expects => [:base64], :returns => [:string] api_method :unhex, :expects => [:string], :returns => [:base64] end @@ -223,6 +225,10 @@ module DispatcherTest @void_called = @method_params end + def test_utf8 + Utf8String + end + def hex(s) return s.unpack("H*")[0] end @@ -398,12 +404,6 @@ module DispatcherCommonTests raise NotImplementedError end - def update_request(ap_request) - end - - def check_response(ap_response) - end - def protocol @protocol end @@ -453,11 +453,10 @@ module DispatcherCommonTests # puts body ap_request = protocol.encode_action_pack_request(service_name, public_method_name, body, :request_class => ActionController::TestRequest) ap_request.env.update(request_env) - update_request(ap_request) ap_response = ActionController::TestResponse.new container.process(ap_request, ap_response) # puts ap_response.body - check_response(ap_response) + @response_body = ap_response.body public_method_name, return_value = protocol.decode_response(ap_response.body) unless is_exception?(return_value) || virtual return_value = method.cast_returns(return_value) diff --git a/actionwebservice/test/dispatcher_action_controller_soap_test.rb b/actionwebservice/test/dispatcher_action_controller_soap_test.rb index 6e332828f2..16a20ea73e 100644 --- a/actionwebservice/test/dispatcher_action_controller_soap_test.rb +++ b/actionwebservice/test/dispatcher_action_controller_soap_test.rb @@ -67,16 +67,23 @@ class TC_DispatcherActionControllerSoap < Test::Unit::TestCase assert_equal(["bloggerCat1", "bloggerCat2"], blogger_cats) end - protected - def update_request(ap_request) - ap_request.env.update('HTTP_CONTENT_TYPE' => 'text/xml; charset=us-ascii') - end - - def check_response(ap_response) - assert_equal 'text/xml; charset=us-ascii', ap_response.headers['Content-Type'] - assert_match /xml.*?encoding="us-ascii"/, ap_response.body - end + def test_utf8 + @direct_controller.web_service_exception_reporting = true + $KCODE = 'u' + assert_equal(Utf8String, do_method_call(@direct_controller, 'TestUtf8')) + + # If $KCODE is not set to UTF-8, any strings with non-ASCII UTF-8 data + # will be sent back as base64 by SOAP4R. By the time we get it here though, + # it will be decoded back into a string. So lets read the base64 value + # from the message body directly. + $KCODE = 'NONE' + do_method_call(@direct_controller, 'TestUtf8') + retval = SOAP::Processor.unmarshal(@response_body).body.response + assert retval.is_a?(SOAP::SOAPBase64) + assert_equal "T25lIFdvcmxkIENhZsOp", retval.data.to_s + end + protected def exception_message(soap_fault_exception) soap_fault_exception.detail.cause.message end -- cgit v1.2.3