require 'xmlrpc/parser' require 'xmlrpc/create' require 'xmlrpc/config' require 'xmlrpc/utils' require 'singleton' module XMLRPC # :nodoc: class XmlRpcHelper # :nodoc: include Singleton include ParserWriterChooseMixin def parse_method_call(message) parser().parseMethodCall(message) end def create_method_response(successful, return_value) create().methodResponse(successful, return_value) end end end module ActionWebService # :nodoc: module Protocol # :nodoc: module XmlRpc # :nodoc: def self.append_features(base) # :nodoc: super base.register_protocol(BodyOnly, XmlRpcProtocol) end class XmlRpcProtocol < AbstractProtocol # :nodoc: def self.create_protocol_request(container_class, action_pack_request) helper = XMLRPC::XmlRpcHelper.instance service_name = action_pack_request.parameters['action'] methodname, params = helper.parse_method_call(action_pack_request.raw_post) methodname.gsub!(/^[^\.]+\./, '') unless methodname =~ /^system\./ # XXX protocol = XmlRpcProtocol.new(container_class) content_type = action_pack_request.env['HTTP_CONTENT_TYPE'] content_type ||= 'text/xml' request = ProtocolRequest.new(protocol, action_pack_request.raw_post, service_name.to_sym, methodname, content_type, :xmlrpc_values => params) request rescue nil end def self.create_protocol_client(api, protocol_name, endpoint_uri, options) return nil unless protocol_name.to_s.downcase.to_sym == :xmlrpc ActionWebService::Client::XmlRpc.new(api, endpoint_uri, options) end def initialize(container_class) super(container_class) end def unmarshal_request(protocol_request) values = protocol_request.options[:xmlrpc_values] signature = protocol_request.signature if signature values = self.class.transform_incoming_method_params(self.class.transform_array_types(signature), values) protocol_request.check_parameter_types(values, check_array_types(signature)) values else protocol_request.checked? ? [] : values end end def marshal_response(protocol_request, return_value) helper = XMLRPC::XmlRpcHelper.instance signature = protocol_request.return_signature if signature protocol_request.check_parameter_types([return_value], check_array_types(signature)) return_value = self.class.transform_return_value(self.class.transform_array_types(signature), return_value) raw_response = helper.create_method_response(true, return_value) else # XML-RPC doesn't have the concept of a void method, nor does it # support a nil return value, so return true if we would have returned # nil if protocol_request.checked? raw_response = helper.create_method_response(true, true) else return_value = true if return_value.nil? raw_response = helper.create_method_response(true, return_value) end end ProtocolResponse.new(self, raw_response, 'text/xml') end def marshal_exception(exception) helper = XMLRPC::XmlRpcHelper.instance exception = XMLRPC::FaultException.new(1, exception.message) raw_response = helper.create_method_response(false, exception) ProtocolResponse.new(self, raw_response, 'text/xml') end class << self def transform_incoming_method_params(signature, params) (1..signature.size).each do |i| i -= 1 params[i] = xmlrpc_to_ruby(params[i], signature[i]) end params end def transform_return_value(signature, return_value) ruby_to_xmlrpc(return_value, signature[0]) end def ruby_to_xmlrpc(param, param_class) if param_class.is_a?(XmlRpcArray) param.map{|p| ruby_to_xmlrpc(p, param_class.klass)} elsif param_class.ancestors.include?(ActiveRecord::Base) param.instance_variable_get('@attributes') elsif param_class.ancestors.include?(ActionWebService::Struct) struct = {} param_class.members.each do |name, klass| value = param.send(name) next if value.nil? struct[name.to_s] = value end struct else param end end def xmlrpc_to_ruby(param, param_class) if param_class.is_a?(XmlRpcArray) param.map{|p| xmlrpc_to_ruby(p, param_class.klass)} elsif param_class.ancestors.include?(ActiveRecord::Base) raise(ProtocolError, "incoming ActiveRecord::Base types are not allowed") elsif param_class.ancestors.include?(ActionWebService::Struct) unless param.is_a?(Hash) raise(ProtocolError, "expected parameter to be a Hash") end new_param = param_class.new param_class.members.each do |name, klass| new_param.send('%s=' % name.to_s, param[name.to_s]) end new_param else param end end def transform_array_types(signature) signature.map{|x| x.is_a?(Array) ? XmlRpcArray.new(x[0]) : x} end end private def check_array_types(signature) signature.map{|x| x.is_a?(Array) ? Array : x} end class XmlRpcArray attr :klass def initialize(klass) @klass = klass end end end end end end