diff options
Diffstat (limited to 'jni/ruby/lib/xmlrpc')
-rw-r--r-- | jni/ruby/lib/xmlrpc/base64.rb | 62 | ||||
-rw-r--r-- | jni/ruby/lib/xmlrpc/client.rb | 614 | ||||
-rw-r--r-- | jni/ruby/lib/xmlrpc/config.rb | 43 | ||||
-rw-r--r-- | jni/ruby/lib/xmlrpc/create.rb | 286 | ||||
-rw-r--r-- | jni/ruby/lib/xmlrpc/datetime.rb | 129 | ||||
-rw-r--r-- | jni/ruby/lib/xmlrpc/marshal.rb | 66 | ||||
-rw-r--r-- | jni/ruby/lib/xmlrpc/parser.rb | 870 | ||||
-rw-r--r-- | jni/ruby/lib/xmlrpc/server.rb | 707 | ||||
-rw-r--r-- | jni/ruby/lib/xmlrpc/utils.rb | 171 |
9 files changed, 2948 insertions, 0 deletions
diff --git a/jni/ruby/lib/xmlrpc/base64.rb b/jni/ruby/lib/xmlrpc/base64.rb new file mode 100644 index 0000000..b898fc9 --- /dev/null +++ b/jni/ruby/lib/xmlrpc/base64.rb @@ -0,0 +1,62 @@ +# +# xmlrpc/base64.rb +# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de) +# +# Released under the same term of license as Ruby. + +module XMLRPC # :nodoc: + +# This class is necessary for 'xmlrpc4r' to determine that a string should +# be transmitted base64-encoded and not as a raw-string. +# +# You can use XMLRPC::Base64 on the client and server-side as a +# parameter and/or return-value. +class Base64 + + # Creates a new XMLRPC::Base64 instance with string +str+ as the + # internal string. When +state+ is +:dec+ it assumes that the + # string +str+ is not in base64 format (perhaps already decoded), + # otherwise if +state+ is +:enc+ it decodes +str+ + # and stores it as the internal string. + def initialize(str, state = :dec) + case state + when :enc + @str = Base64.decode(str) + when :dec + @str = str + else + raise ArgumentError, "wrong argument; either :enc or :dec" + end + end + + # Returns the decoded internal string. + def decoded + @str + end + + # Returns the base64 encoded internal string. + def encoded + Base64.encode(@str) + end + + + # Decodes string +str+ with base64 and returns that value. + def Base64.decode(str) + str.gsub(/\s+/, "").unpack("m")[0] + end + + # Encodes string +str+ with base64 and returns that value. + def Base64.encode(str) + [str].pack("m") + end + +end + + +end # module XMLRPC + + +=begin += History + $Id: base64.rb 36958 2012-09-13 02:22:10Z zzak $ +=end diff --git a/jni/ruby/lib/xmlrpc/client.rb b/jni/ruby/lib/xmlrpc/client.rb new file mode 100644 index 0000000..34543b0 --- /dev/null +++ b/jni/ruby/lib/xmlrpc/client.rb @@ -0,0 +1,614 @@ +# xmlrpc/client.rb +# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de) +# +# Released under the same term of license as Ruby. +# +# History +# $Id: client.rb 46311 2014-06-02 00:43:29Z tmm1 $ +# +require "xmlrpc/parser" +require "xmlrpc/create" +require "xmlrpc/config" +require "xmlrpc/utils" # ParserWriterChooseMixin +require "net/http" +require "uri" + +module XMLRPC # :nodoc: + + # Provides remote procedure calls to a XML-RPC server. + # + # After setting the connection-parameters with XMLRPC::Client.new which + # creates a new XMLRPC::Client instance, you can execute a remote procedure + # by sending the XMLRPC::Client#call or XMLRPC::Client#call2 + # message to this new instance. + # + # The given parameters indicate which method to call on the remote-side and + # of course the parameters for the remote procedure. + # + # require "xmlrpc/client" + # + # server = XMLRPC::Client.new("www.ruby-lang.org", "/RPC2", 80) + # begin + # param = server.call("michael.add", 4, 5) + # puts "4 + 5 = #{param}" + # rescue XMLRPC::FaultException => e + # puts "Error:" + # puts e.faultCode + # puts e.faultString + # end + # + # or + # + # require "xmlrpc/client" + # + # server = XMLRPC::Client.new("www.ruby-lang.org", "/RPC2", 80) + # ok, param = server.call2("michael.add", 4, 5) + # if ok then + # puts "4 + 5 = #{param}" + # else + # puts "Error:" + # puts param.faultCode + # puts param.faultString + # end + class Client + + USER_AGENT = "XMLRPC::Client (Ruby #{RUBY_VERSION})" + + include ParserWriterChooseMixin + include ParseContentType + + + # Creates an object which represents the remote XML-RPC server on the + # given +host+. If the server is CGI-based, +path+ is the + # path to the CGI-script, which will be called, otherwise (in the + # case of a standalone server) +path+ should be <tt>"/RPC2"</tt>. + # +port+ is the port on which the XML-RPC server listens. + # + # If +proxy_host+ is given, then a proxy server listening at + # +proxy_host+ is used. +proxy_port+ is the port of the + # proxy server. + # + # Default values for +host+, +path+ and +port+ are 'localhost', '/RPC2' and + # '80' respectively using SSL '443'. + # + # If +user+ and +password+ are given, each time a request is sent, + # an Authorization header is sent. Currently only Basic Authentication is + # implemented, no Digest. + # + # If +use_ssl+ is set to +true+, communication over SSL is enabled. + # + # Parameter +timeout+ is the time to wait for a XML-RPC response, defaults to 30. + def initialize(host=nil, path=nil, port=nil, proxy_host=nil, proxy_port=nil, + user=nil, password=nil, use_ssl=nil, timeout=nil) + + @http_header_extra = nil + @http_last_response = nil + @cookie = nil + + @host = host || "localhost" + @path = path || "/RPC2" + @proxy_host = proxy_host + @proxy_port = proxy_port + @proxy_host ||= 'localhost' if @proxy_port != nil + @proxy_port ||= 8080 if @proxy_host != nil + @use_ssl = use_ssl || false + @timeout = timeout || 30 + + if use_ssl + require "net/https" + @port = port || 443 + else + @port = port || 80 + end + + @user, @password = user, password + + set_auth + + # convert ports to integers + @port = @port.to_i if @port != nil + @proxy_port = @proxy_port.to_i if @proxy_port != nil + + # HTTP object for synchronous calls + @http = net_http(@host, @port, @proxy_host, @proxy_port) + @http.use_ssl = @use_ssl if @use_ssl + @http.read_timeout = @timeout + @http.open_timeout = @timeout + + @parser = nil + @create = nil + end + + + class << self + + # Creates an object which represents the remote XML-RPC server at the + # given +uri+. The URI should have a host, port, path, user and password. + # Example: https://user:password@host:port/path + # + # Raises an ArgumentError if the +uri+ is invalid, + # or if the protocol isn't http or https. + # + # If a +proxy+ is given it should be in the form of "host:port". + # + # The optional +timeout+ defaults to 30 seconds. + def new2(uri, proxy=nil, timeout=nil) + begin + url = URI(uri) + rescue URI::InvalidURIError => e + raise ArgumentError, e.message, e.backtrace + end + + unless URI::HTTP === url + raise ArgumentError, "Wrong protocol specified. Only http or https allowed!" + end + + proto = url.scheme + user = url.user + passwd = url.password + host = url.host + port = url.port + path = url.path.empty? ? nil : url.request_uri + + proxy_host, proxy_port = (proxy || "").split(":") + proxy_port = proxy_port.to_i if proxy_port + + self.new(host, path, port, proxy_host, proxy_port, user, passwd, (proto == "https"), timeout) + end + + alias new_from_uri new2 + + # Receives a Hash and calls XMLRPC::Client.new + # with the corresponding values. + # + # The +hash+ parameter has following case-insensitive keys: + # * host + # * path + # * port + # * proxy_host + # * proxy_port + # * user + # * password + # * use_ssl + # * timeout + def new3(hash={}) + + # convert all keys into lowercase strings + h = {} + hash.each { |k,v| h[k.to_s.downcase] = v } + + self.new(h['host'], h['path'], h['port'], h['proxy_host'], h['proxy_port'], h['user'], h['password'], + h['use_ssl'], h['timeout']) + end + + alias new_from_hash new3 + + end + + + # Returns the Net::HTTP object for the client. If you want to + # change HTTP client options except header, cookie, timeout, + # user and password, use Net::HTTP directly. + # + # Since 2.1.0. + attr_reader :http + + # Add additional HTTP headers to the request + attr_accessor :http_header_extra + + # Returns the Net::HTTPResponse object of the last RPC. + attr_reader :http_last_response + + # Get and set the HTTP Cookie header. + attr_accessor :cookie + + + # Return the corresponding attributes. + attr_reader :timeout, :user, :password + + # Sets the Net::HTTP#read_timeout and Net::HTTP#open_timeout to + # +new_timeout+ + def timeout=(new_timeout) + @timeout = new_timeout + @http.read_timeout = @timeout + @http.open_timeout = @timeout + end + + # Changes the user for the Basic Authentication header to +new_user+ + def user=(new_user) + @user = new_user + set_auth + end + + # Changes the password for the Basic Authentication header to + # +new_password+ + def password=(new_password) + @password = new_password + set_auth + end + + # Invokes the method named +method+ with the parameters given by + # +args+ on the XML-RPC server. + # + # The +method+ parameter is converted into a String and should + # be a valid XML-RPC method-name. + # + # Each parameter of +args+ must be of one of the following types, + # where Hash, Struct and Array can contain any of these listed _types_: + # + # * Fixnum, Bignum + # * TrueClass, FalseClass, +true+, +false+ + # * String, Symbol + # * Float + # * Hash, Struct + # * Array + # * Date, Time, XMLRPC::DateTime + # * XMLRPC::Base64 + # * A Ruby object which class includes XMLRPC::Marshallable + # (only if Config::ENABLE_MARSHALLING is +true+). + # That object is converted into a hash, with one additional key/value + # pair <code>___class___</code> which contains the class name + # for restoring that object later. + # + # The method returns the return-value from the Remote Procedure Call. + # + # The type of the return-value is one of the types shown above. + # + # A Bignum is only allowed when it fits in 32-bit. A XML-RPC + # +dateTime.iso8601+ type is always returned as a XMLRPC::DateTime object. + # Struct is never returned, only a Hash, the same for a Symbol, where as a + # String is always returned. XMLRPC::Base64 is returned as a String from + # xmlrpc4r version 1.6.1 on. + # + # If the remote procedure returned a fault-structure, then a + # XMLRPC::FaultException exception is raised, which has two accessor-methods + # +faultCode+ an Integer, and +faultString+ a String. + def call(method, *args) + ok, param = call2(method, *args) + if ok + param + else + raise param + end + end + + # The difference between this method and XMLRPC::Client#call is, that + # this method will <b>NOT</b> raise a XMLRPC::FaultException exception. + # + # The method returns an array of two values. The first value indicates if + # the second value is +true+ or an XMLRPC::FaultException. + # + # Both are explained in XMLRPC::Client#call. + # + # Simple to remember: The "2" in "call2" denotes the number of values it returns. + def call2(method, *args) + request = create().methodCall(method, *args) + data = do_rpc(request, false) + parser().parseMethodResponse(data) + end + + # Similar to XMLRPC::Client#call, however can be called concurrently and + # use a new connection for each request. In contrast to the corresponding + # method without the +_async+ suffix, which use connect-alive (one + # connection for all requests). + # + # Note, that you have to use Thread to call these methods concurrently. + # The following example calls two methods concurrently: + # + # Thread.new { + # p client.call_async("michael.add", 4, 5) + # } + # + # Thread.new { + # p client.call_async("michael.div", 7, 9) + # } + # + def call_async(method, *args) + ok, param = call2_async(method, *args) + if ok + param + else + raise param + end + end + + # Same as XMLRPC::Client#call2, but can be called concurrently. + # + # See also XMLRPC::Client#call_async + def call2_async(method, *args) + request = create().methodCall(method, *args) + data = do_rpc(request, true) + parser().parseMethodResponse(data) + end + + + # You can use this method to execute several methods on a XMLRPC server + # which support the multi-call extension. + # + # s.multicall( + # ['michael.add', 3, 4], + # ['michael.sub', 4, 5] + # ) + # # => [7, -1] + def multicall(*methods) + ok, params = multicall2(*methods) + if ok + params + else + raise params + end + end + + # Same as XMLRPC::Client#multicall, but returns two parameters instead of + # raising an XMLRPC::FaultException. + # + # See XMLRPC::Client#call2 + def multicall2(*methods) + gen_multicall(methods, false) + end + + # Similar to XMLRPC::Client#multicall, however can be called concurrently and + # use a new connection for each request. In contrast to the corresponding + # method without the +_async+ suffix, which use connect-alive (one + # connection for all requests). + # + # Note, that you have to use Thread to call these methods concurrently. + # The following example calls two methods concurrently: + # + # Thread.new { + # p client.multicall_async("michael.add", 4, 5) + # } + # + # Thread.new { + # p client.multicall_async("michael.div", 7, 9) + # } + # + def multicall_async(*methods) + ok, params = multicall2_async(*methods) + if ok + params + else + raise params + end + end + + # Same as XMLRPC::Client#multicall2, but can be called concurrently. + # + # See also XMLRPC::Client#multicall_async + def multicall2_async(*methods) + gen_multicall(methods, true) + end + + + # Returns an object of class XMLRPC::Client::Proxy, initialized with + # +prefix+ and +args+. + # + # A proxy object returned by this method behaves like XMLRPC::Client#call, + # i.e. a call on that object will raise a XMLRPC::FaultException when a + # fault-structure is returned by that call. + def proxy(prefix=nil, *args) + Proxy.new(self, prefix, args, :call) + end + + # Almost the same like XMLRPC::Client#proxy only that a call on the returned + # XMLRPC::Client::Proxy object will return two parameters. + # + # See XMLRPC::Client#call2 + def proxy2(prefix=nil, *args) + Proxy.new(self, prefix, args, :call2) + end + + # Similar to XMLRPC::Client#proxy, however can be called concurrently and + # use a new connection for each request. In contrast to the corresponding + # method without the +_async+ suffix, which use connect-alive (one + # connection for all requests). + # + # Note, that you have to use Thread to call these methods concurrently. + # The following example calls two methods concurrently: + # + # Thread.new { + # p client.proxy_async("michael.add", 4, 5) + # } + # + # Thread.new { + # p client.proxy_async("michael.div", 7, 9) + # } + # + def proxy_async(prefix=nil, *args) + Proxy.new(self, prefix, args, :call_async) + end + + # Same as XMLRPC::Client#proxy2, but can be called concurrently. + # + # See also XMLRPC::Client#proxy_async + def proxy2_async(prefix=nil, *args) + Proxy.new(self, prefix, args, :call2_async) + end + + + private + + def net_http(host, port, proxy_host, proxy_port) + Net::HTTP.new host, port, proxy_host, proxy_port + end + + def set_auth + if @user.nil? + @auth = nil + else + a = "#@user" + a << ":#@password" if @password != nil + @auth = "Basic " + [a].pack("m0") + end + end + + def do_rpc(request, async=false) + header = { + "User-Agent" => USER_AGENT, + "Content-Type" => "text/xml; charset=utf-8", + "Content-Length" => request.bytesize.to_s, + "Connection" => (async ? "close" : "keep-alive") + } + + header["Cookie"] = @cookie if @cookie + header.update(@http_header_extra) if @http_header_extra + + if @auth != nil + # add authorization header + header["Authorization"] = @auth + end + + resp = nil + @http_last_response = nil + + if async + # use a new HTTP object for each call + http = net_http(@host, @port, @proxy_host, @proxy_port) + http.use_ssl = @use_ssl if @use_ssl + http.read_timeout = @timeout + http.open_timeout = @timeout + + # post request + http.start { + resp = http.request_post(@path, request, header) + } + else + # reuse the HTTP object for each call => connection alive is possible + # we must start connection explicitly first time so that http.request + # does not assume that we don't want keepalive + @http.start if not @http.started? + + # post request + resp = @http.request_post(@path, request, header) + end + + @http_last_response = resp + + data = resp.body + + if resp.code == "401" + # Authorization Required + raise "Authorization failed.\nHTTP-Error: #{resp.code} #{resp.message}" + elsif resp.code[0,1] != "2" + raise "HTTP-Error: #{resp.code} #{resp.message}" + end + + # assume text/xml on instances where Content-Type header is not set + ct_expected = resp["Content-Type"] || 'text/xml' + ct = parse_content_type(ct_expected).first + if ct != "text/xml" + if ct == "text/html" + raise "Wrong content-type (received '#{ct}' but expected 'text/xml'): \n#{data}" + else + raise "Wrong content-type (received '#{ct}' but expected 'text/xml')" + end + end + + expected = resp["Content-Length"] || "<unknown>" + if data.nil? or data.bytesize == 0 + raise "Wrong size. Was #{data.bytesize}, should be #{expected}" + end + + parse_set_cookies(resp.get_fields("Set-Cookie")) + + return data + end + + def parse_set_cookies(set_cookies) + return if set_cookies.nil? + return if set_cookies.empty? + require 'webrick/cookie' + pairs = {} + set_cookies.each do |set_cookie| + cookie = WEBrick::Cookie.parse_set_cookie(set_cookie) + pairs.delete(cookie.name) + pairs[cookie.name] = cookie.value + end + cookies = pairs.collect do |name, value| + WEBrick::Cookie.new(name, value).to_s + end + @cookie = cookies.join("; ") + end + + def gen_multicall(methods=[], async=false) + meth = :call2 + meth = :call2_async if async + + ok, params = self.send(meth, "system.multicall", + methods.collect {|m| {'methodName' => m[0], 'params' => m[1..-1]} } + ) + + if ok + params = params.collect do |param| + if param.is_a? Array + param[0] + elsif param.is_a? Hash + XMLRPC::FaultException.new(param["faultCode"], param["faultString"]) + else + raise "Wrong multicall return value" + end + end + end + + return ok, params + end + + + + # XML-RPC calls look nicer! + # + # You can call any method onto objects of that class - the object handles + # XMLRPC::Client::Proxy#method_missing and will forward the method call to + # a XML-RPC server. + # + # Don't use this class directly, instead use the public instance method + # XMLRPC::Client#proxy or XMLRPC::Client#proxy2. + # + # require "xmlrpc/client" + # + # server = XMLRPC::Client.new("www.ruby-lang.org", "/RPC2", 80) + # + # michael = server.proxy("michael") + # michael2 = server.proxy("michael", 4) + # + # # both calls should return the same value '9'. + # p michael.add(4,5) + # p michael2.add(5) + class Proxy + + # Creates an object which provides XMLRPC::Client::Proxy#method_missing. + # + # The given +server+ must be an instance of XMLRPC::Client, which is the + # XML-RPC server to be used for a XML-RPC call. + # + # +prefix+ and +delim+ will be prepended to the method name called onto this object. + # + # An optional parameter +meth+ is the method to use for a RPC. + # It can be either, call, call2, call_async, call2_async + # + # +args+ are arguments which are automatically given to every XML-RPC + # call before being provided through +method_missing+. + def initialize(server, prefix, args=[], meth=:call, delim=".") + @server = server + @prefix = prefix ? prefix + delim : "" + @args = args + @meth = meth + end + + # Every method call is forwarded to the XML-RPC server defined in + # XMLRPC::Client::Proxy#new. + # + # Note: Inherited methods from class Object cannot be used as XML-RPC + # names, because they get around +method_missing+. + def method_missing(mid, *args) + pre = @prefix + mid.to_s + arg = @args + args + @server.send(@meth, pre, *arg) + end + + end # class Proxy + + end # class Client + +end # module XMLRPC + diff --git a/jni/ruby/lib/xmlrpc/config.rb b/jni/ruby/lib/xmlrpc/config.rb new file mode 100644 index 0000000..976caed --- /dev/null +++ b/jni/ruby/lib/xmlrpc/config.rb @@ -0,0 +1,43 @@ +# +# $Id: config.rb 47902 2014-10-13 08:53:16Z hsbt $ +# Configuration file for XML-RPC for Ruby +# + +module XMLRPC # :nodoc: + + module Config + + # or XMLWriter::XMLParser + DEFAULT_WRITER = XMLWriter::Simple + + # === Available parsers + # + # * XMLParser::NQXMLTreeParser + # * XMLParser::NQXMLStreamParser + # * XMLParser::XMLTreeParser + # * XMLParser::XMLStreamParser (fastest) + # * XMLParser::REXMLStreamParser + # * XMLParser::XMLScanStreamParser + # * XMLParser::LibXMLStreamParser + DEFAULT_PARSER = XMLParser::REXMLStreamParser + + # enable <code><nil/></code> tag + ENABLE_NIL_CREATE = false + ENABLE_NIL_PARSER = false + + # allows integers greater than 32-bit if +true+ + ENABLE_BIGINT = false + + # enable marshalling Ruby objects which include XMLRPC::Marshallable + ENABLE_MARSHALLING = true + + # enable multiCall extension by default + ENABLE_MULTICALL = false + + # enable Introspection extension by default + ENABLE_INTROSPECTION = false + + end + +end + diff --git a/jni/ruby/lib/xmlrpc/create.rb b/jni/ruby/lib/xmlrpc/create.rb new file mode 100644 index 0000000..695f8fe --- /dev/null +++ b/jni/ruby/lib/xmlrpc/create.rb @@ -0,0 +1,286 @@ +# +# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de) +# +# $Id: create.rb 36958 2012-09-13 02:22:10Z zzak $ +# + +require "date" +require "xmlrpc/base64" + +module XMLRPC # :nodoc: + + module XMLWriter + + class Abstract + def ele(name, *children) + element(name, nil, *children) + end + + def tag(name, txt) + element(name, nil, text(txt)) + end + end + + + class Simple < Abstract + + def document_to_str(doc) + doc + end + + def document(*params) + params.join("") + end + + def pi(name, *params) + "<?#{name} " + params.join(" ") + " ?>" + end + + def element(name, attrs, *children) + raise "attributes not yet implemented" unless attrs.nil? + if children.empty? + "<#{name}/>" + else + "<#{name}>" + children.join("") + "</#{name}>" + end + end + + def text(txt) + cleaned = txt.dup + cleaned.gsub!(/&/, '&') + cleaned.gsub!(/</, '<') + cleaned.gsub!(/>/, '>') + cleaned + end + + end # class Simple + + + class XMLParser < Abstract + + def initialize + require "xmltreebuilder" + end + + def document_to_str(doc) + doc.to_s + end + + def document(*params) + XML::SimpleTree::Document.new(*params) + end + + def pi(name, *params) + XML::SimpleTree::ProcessingInstruction.new(name, *params) + end + + def element(name, attrs, *children) + XML::SimpleTree::Element.new(name, attrs, *children) + end + + def text(txt) + XML::SimpleTree::Text.new(txt) + end + + end # class XMLParser + + Classes = [Simple, XMLParser] + + # yields an instance of each installed XML writer + def self.each_installed_writer + XMLRPC::XMLWriter::Classes.each do |klass| + begin + yield klass.new + rescue LoadError + end + end + end + + end # module XMLWriter + + # Creates XML-RPC call/response documents + # + class Create + + def initialize(xml_writer = nil) + @writer = xml_writer || Config::DEFAULT_WRITER.new + end + + + def methodCall(name, *params) + name = name.to_s + + if name !~ /[a-zA-Z0-9_.:\/]+/ + raise ArgumentError, "Wrong XML-RPC method-name" + end + + parameter = params.collect do |param| + @writer.ele("param", conv2value(param)) + end + + tree = @writer.document( + @writer.pi("xml", 'version="1.0"'), + @writer.ele("methodCall", + @writer.tag("methodName", name), + @writer.ele("params", *parameter) + ) + ) + + @writer.document_to_str(tree) + "\n" + end + + + + # + # Generates a XML-RPC methodResponse document + # + # When +is_ret+ is +false+ then the +params+ array must + # contain only one element, which is a structure + # of a fault return-value. + # + # When +is_ret+ is +true+ then a normal + # return-value of all the given +params+ is created. + # + def methodResponse(is_ret, *params) + + if is_ret + resp = params.collect do |param| + @writer.ele("param", conv2value(param)) + end + + resp = [@writer.ele("params", *resp)] + else + if params.size != 1 or params[0] === XMLRPC::FaultException + raise ArgumentError, "no valid fault-structure given" + end + resp = @writer.ele("fault", conv2value(params[0].to_h)) + end + + + tree = @writer.document( + @writer.pi("xml", 'version="1.0"'), + @writer.ele("methodResponse", resp) + ) + + @writer.document_to_str(tree) + "\n" + end + + + + private + + # + # Converts a Ruby object into a XML-RPC <code><value></code> tag + # + def conv2value(param) # :doc: + + val = case param + when Fixnum, Bignum + # XML-RPC's int is 32bit int, and Fixnum also may be beyond 32bit + if Config::ENABLE_BIGINT + @writer.tag("i4", param.to_s) + else + if param >= -(2**31) and param <= (2**31-1) + @writer.tag("i4", param.to_s) + else + raise "Bignum is too big! Must be signed 32-bit integer!" + end + end + when TrueClass, FalseClass + @writer.tag("boolean", param ? "1" : "0") + + when Symbol + @writer.tag("string", param.to_s) + + when String + @writer.tag("string", param) + + when NilClass + if Config::ENABLE_NIL_CREATE + @writer.ele("nil") + else + raise "Wrong type NilClass. Not allowed!" + end + + when Float + raise "Wrong value #{param}. Not allowed!" unless param.finite? + @writer.tag("double", param.to_s) + + when Struct + h = param.members.collect do |key| + value = param[key] + @writer.ele("member", + @writer.tag("name", key.to_s), + conv2value(value) + ) + end + + @writer.ele("struct", *h) + + when Hash + # TODO: can a Hash be empty? + + h = param.collect do |key, value| + @writer.ele("member", + @writer.tag("name", key.to_s), + conv2value(value) + ) + end + + @writer.ele("struct", *h) + + when Array + # TODO: can an Array be empty? + a = param.collect {|v| conv2value(v) } + + @writer.ele("array", + @writer.ele("data", *a) + ) + + when Time, Date, ::DateTime + @writer.tag("dateTime.iso8601", param.strftime("%Y%m%dT%H:%M:%S")) + + when XMLRPC::DateTime + @writer.tag("dateTime.iso8601", + format("%.4d%02d%02dT%02d:%02d:%02d", *param.to_a)) + + when XMLRPC::Base64 + @writer.tag("base64", param.encoded) + + else + if Config::ENABLE_MARSHALLING and param.class.included_modules.include? XMLRPC::Marshallable + # convert Ruby object into Hash + ret = {"___class___" => param.class.name} + param.instance_variables.each {|v| + name = v[1..-1] + val = param.instance_variable_get(v) + + if val.nil? + ret[name] = val if Config::ENABLE_NIL_CREATE + else + ret[name] = val + end + } + return conv2value(ret) + else + ok, pa = wrong_type(param) + if ok + return conv2value(pa) + else + raise "Wrong type!" + end + end + end + + @writer.ele("value", val) + end + + def wrong_type(value) + false + end + + + end # class Create + +end # module XMLRPC + diff --git a/jni/ruby/lib/xmlrpc/datetime.rb b/jni/ruby/lib/xmlrpc/datetime.rb new file mode 100644 index 0000000..2e3461f --- /dev/null +++ b/jni/ruby/lib/xmlrpc/datetime.rb @@ -0,0 +1,129 @@ +# +# xmlrpc/datetime.rb +# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de) +# +# Released under the same term of license as Ruby. +# +require "date" + +module XMLRPC # :nodoc: + +# This class is important to handle XMLRPC +dateTime.iso8601+ values, +# correctly, because normal UNIX-dates, ie: Date, only handle dates +# from year 1970 on, and ruby's native Time class handles dates without the +# time component. +# +# XMLRPC::DateTime is able to store a XMLRPC +dateTime.iso8601+ value correctly. +class DateTime + + # Return the value of the specified date/time component. + attr_reader :year, :month, :day, :hour, :min, :sec + + # Set +value+ as the new date/time component. + # + # Raises ArgumentError if the given +value+ is out of range, or in the case + # of XMLRPC::DateTime#year= if +value+ is not of type Integer. + def year= (value) + raise ArgumentError, "date/time out of range" unless value.is_a? Integer + @year = value + end + + # Set +value+ as the new date/time component. + # + # Raises an ArgumentError if the given +value+ isn't between 1 and 12. + def month= (value) + raise ArgumentError, "date/time out of range" unless (1..12).include? value + @month = value + end + + # Set +value+ as the new date/time component. + # + # Raises an ArgumentError if the given +value+ isn't between 1 and 31. + def day= (value) + raise ArgumentError, "date/time out of range" unless (1..31).include? value + @day = value + end + + # Set +value+ as the new date/time component. + # + # Raises an ArgumentError if the given +value+ isn't between 0 and 24. + def hour= (value) + raise ArgumentError, "date/time out of range" unless (0..24).include? value + @hour = value + end + + # Set +value+ as the new date/time component. + # + # Raises an ArgumentError if the given +value+ isn't between 0 and 59. + def min= (value) + raise ArgumentError, "date/time out of range" unless (0..59).include? value + @min = value + end + + # Set +value+ as the new date/time component. + # + # Raises an ArgumentError if the given +value+ isn't between 0 and 59. + def sec= (value) + raise ArgumentError, "date/time out of range" unless (0..59).include? value + @sec = value + end + + # Alias for XMLRPC::DateTime#month. + alias mon month + # Alias for XMLRPC::DateTime#month=. + alias mon= month= + + + # Creates a new XMLRPC::DateTime instance with the + # parameters +year+, +month+, +day+ as date and + # +hour+, +min+, +sec+ as time. + # + # Raises an ArgumentError if a parameter is out of range, + # or if +year+ is not of the Integer type. + def initialize(year, month, day, hour, min, sec) + self.year, self.month, self.day = year, month, day + self.hour, self.min, self.sec = hour, min, sec + end + + # Return a Time object of the date/time which represents +self+. + # If the <code>@year</code> is below 1970, this method returns +nil+, + # because Time cannot handle years below 1970. + # + # The timezone used is GMT. + def to_time + if @year >= 1970 + Time.gm(*to_a) + else + nil + end + end + + # Return a Date object of the date which represents +self+. + # + # The Date object do _not_ contain the time component (only date). + def to_date + Date.new(*to_a[0,3]) + end + + # Returns all date/time components in an array. + # + # Returns +[year, month, day, hour, min, sec]+. + def to_a + [@year, @month, @day, @hour, @min, @sec] + end + + # Returns whether or not all date/time components are an array. + def ==(o) + self.to_a == Array(o) rescue false + end + +end + + +end # module XMLRPC + + +=begin += History + $Id: datetime.rb 44024 2013-12-06 02:10:11Z hsbt $ +=end diff --git a/jni/ruby/lib/xmlrpc/marshal.rb b/jni/ruby/lib/xmlrpc/marshal.rb new file mode 100644 index 0000000..f07fd46 --- /dev/null +++ b/jni/ruby/lib/xmlrpc/marshal.rb @@ -0,0 +1,66 @@ +# +# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de) +# +# $Id: marshal.rb 36958 2012-09-13 02:22:10Z zzak $ +# + +require "xmlrpc/parser" +require "xmlrpc/create" +require "xmlrpc/config" +require "xmlrpc/utils" + +module XMLRPC # :nodoc: + + # Marshalling of XMLRPC::Create#methodCall and XMLRPC::Create#methodResponse + class Marshal + include ParserWriterChooseMixin + + class << self + + def dump_call( methodName, *params ) + new.dump_call( methodName, *params ) + end + + def dump_response( param ) + new.dump_response( param ) + end + + def load_call( stringOrReadable ) + new.load_call( stringOrReadable ) + end + + def load_response( stringOrReadable ) + new.load_response( stringOrReadable ) + end + + alias dump dump_response + alias load load_response + + end # class self + + def initialize( parser = nil, writer = nil ) + set_parser( parser ) + set_writer( writer ) + end + + def dump_call( methodName, *params ) + create.methodCall( methodName, *params ) + end + + def dump_response( param ) + create.methodResponse( ! param.kind_of?( XMLRPC::FaultException ) , param ) + end + + # Returns <code>[ methodname, params ]</code> + def load_call( stringOrReadable ) + parser.parseMethodCall( stringOrReadable ) + end + + # Returns +paramOrFault+ + def load_response( stringOrReadable ) + parser.parseMethodResponse( stringOrReadable )[1] + end + + end # class Marshal + +end diff --git a/jni/ruby/lib/xmlrpc/parser.rb b/jni/ruby/lib/xmlrpc/parser.rb new file mode 100644 index 0000000..1cf110f --- /dev/null +++ b/jni/ruby/lib/xmlrpc/parser.rb @@ -0,0 +1,870 @@ +# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de) +# +# $Id: parser.rb 47902 2014-10-13 08:53:16Z hsbt $ +# + + +require "date" +require "xmlrpc/base64" +require "xmlrpc/datetime" + + +module NQXML + class Node + + def removeChild(node) + @children.delete(node) + end + def childNodes + @children + end + def hasChildNodes + not @children.empty? + end + def [] (index) + @children[index] + end + + def nodeType + if @entity.instance_of? NQXML::Text then :TEXT + elsif @entity.instance_of? NQXML::Comment then :COMMENT + #elsif @entity.instance_of? NQXML::Element then :ELEMENT + elsif @entity.instance_of? NQXML::Tag then :ELEMENT + else :ELSE + end + end + + def nodeValue + #TODO: error when wrong Entity-type + @entity.text + end + def nodeName + #TODO: error when wrong Entity-type + @entity.name + end + end # class Node +end # module NQXML + +module XMLRPC # :nodoc: + + # Raised when the remote procedure returns a fault-structure, which has two + # accessor-methods +faultCode+ an Integer, and +faultString+ a String. + class FaultException < StandardError + attr_reader :faultCode, :faultString + + # Creates a new XMLRPC::FaultException instance. + # + # +faultString+ is passed to StandardError as the +msg+ of the Exception. + def initialize(faultCode, faultString) + @faultCode = faultCode + @faultString = faultString + super(@faultString) + end + + # The +faultCode+ and +faultString+ of the exception in a Hash. + def to_h + {"faultCode" => @faultCode, "faultString" => @faultString} + end + end + + # Helper class used to convert types. + module Convert + + # Converts a String to an Integer + # + # See also String.to_i + def self.int(str) + str.to_i + end + + # Converts a String to +true+ or +false+ + # + # Raises an exception if +str+ is not +0+ or +1+ + def self.boolean(str) + case str + when "0" then false + when "1" then true + else + raise "RPC-value of type boolean is wrong" + end + end + + # Converts a String to a Float + # + # See also String.to_f + def self.double(str) + str.to_f + end + + # Converts a the given +str+ to a +dateTime.iso8601+ formatted date. + # + # Raises an exception if the String isn't in +dateTime.iso8601+ format. + # + # See also, XMLRPC::DateTime + def self.dateTime(str) + case str + when /^(-?\d\d\d\d)-?(\d\d)-?(\d\d)T(\d\d):(\d\d):(\d\d)(?:Z|([+-])(\d\d):?(\d\d))?$/ + a = [$1, $2, $3, $4, $5, $6].collect{|i| i.to_i} + if $7 + ofs = $8.to_i*3600 + $9.to_i*60 + ofs = -ofs if $7=='+' + utc = Time.utc(*a) + ofs + a = [ utc.year, utc.month, utc.day, utc.hour, utc.min, utc.sec ] + end + XMLRPC::DateTime.new(*a) + when /^(-?\d\d)-?(\d\d)-?(\d\d)T(\d\d):(\d\d):(\d\d)(Z|([+-]\d\d):(\d\d))?$/ + a = [$1, $2, $3, $4, $5, $6].collect{|i| i.to_i} + if a[0] < 70 + a[0] += 2000 + else + a[0] += 1900 + end + if $7 + ofs = $8.to_i*3600 + $9.to_i*60 + ofs = -ofs if $7=='+' + utc = Time.utc(*a) + ofs + a = [ utc.year, utc.month, utc.day, utc.hour, utc.min, utc.sec ] + end + XMLRPC::DateTime.new(*a) + else + raise "wrong dateTime.iso8601 format " + str + end + end + + # Decodes the given +str+ using XMLRPC::Base64.decode + def self.base64(str) + XMLRPC::Base64.decode(str) + end + + # Converts the given +hash+ to a marshalled object. + # + # Returns the given +hash+ if an exception occurs. + def self.struct(hash) + # convert to marshalled object + klass = hash["___class___"] + if klass.nil? or Config::ENABLE_MARSHALLING == false + hash + else + begin + mod = Module + klass.split("::").each {|const| mod = mod.const_get(const.strip)} + + obj = mod.allocate + + hash.delete "___class___" + hash.each {|key, value| + obj.instance_variable_set("@#{ key }", value) if key =~ /^([a-zA-Z_]\w*)$/ + } + obj + rescue + hash + end + end + end + + # Converts the given +hash+ to an XMLRPC::FaultException object by passing + # the +faultCode+ and +faultString+ attributes of the Hash to + # XMLRPC::FaultException.new + # + # Raises an Exception if the given +hash+ doesn't meet the requirements. + # Those requirements being: + # * 2 keys + # * <code>'faultCode'</code> key is an Integer + # * <code>'faultString'</code> key is a String + def self.fault(hash) + if hash.kind_of? Hash and hash.size == 2 and + hash.has_key? "faultCode" and hash.has_key? "faultString" and + hash["faultCode"].kind_of? Integer and hash["faultString"].kind_of? String + + XMLRPC::FaultException.new(hash["faultCode"], hash["faultString"]) + else + raise "wrong fault-structure: #{hash.inspect}" + end + end + + end # module Convert + + # Parser for XML-RPC call and response + module XMLParser + + class AbstractTreeParser + + def parseMethodResponse(str) + methodResponse_document(createCleanedTree(str)) + end + + def parseMethodCall(str) + methodCall_document(createCleanedTree(str)) + end + + private + + # Removes all whitespaces but in the tags i4, i8, int, boolean.... + # and all comments + def removeWhitespacesAndComments(node) + remove = [] + childs = node.childNodes.to_a + childs.each do |nd| + case _nodeType(nd) + when :TEXT + # TODO: add nil? + unless %w(i4 i8 int boolean string double dateTime.iso8601 base64).include? node.nodeName + + if node.nodeName == "value" + if not node.childNodes.to_a.detect {|n| _nodeType(n) == :ELEMENT}.nil? + remove << nd if nd.nodeValue.strip == "" + end + else + remove << nd if nd.nodeValue.strip == "" + end + end + when :COMMENT + remove << nd + else + removeWhitespacesAndComments(nd) + end + end + + remove.each { |i| node.removeChild(i) } + end + + + def nodeMustBe(node, name) + cmp = case name + when Array + name.include?(node.nodeName) + when String + name == node.nodeName + else + raise "error" + end + + if not cmp then + raise "wrong xml-rpc (name)" + end + + node + end + + # Returns, when successfully the only child-node + def hasOnlyOneChild(node, name=nil) + if node.childNodes.to_a.size != 1 + raise "wrong xml-rpc (size)" + end + if name != nil then + nodeMustBe(node.firstChild, name) + end + end + + + def assert(b) + if not b then + raise "assert-fail" + end + end + + # The node `node` has empty string or string + def text_zero_one(node) + nodes = node.childNodes.to_a.size + + if nodes == 1 + text(node.firstChild) + elsif nodes == 0 + "" + else + raise "wrong xml-rpc (size)" + end + end + + + def integer(node) + #TODO: check string for float because to_i returnsa + # 0 when wrong string + nodeMustBe(node, %w(i4 i8 int)) + hasOnlyOneChild(node) + + Convert.int(text(node.firstChild)) + end + + def boolean(node) + nodeMustBe(node, "boolean") + hasOnlyOneChild(node) + + Convert.boolean(text(node.firstChild)) + end + + def v_nil(node) + nodeMustBe(node, "nil") + assert( node.childNodes.to_a.size == 0 ) + nil + end + + def string(node) + nodeMustBe(node, "string") + text_zero_one(node) + end + + def double(node) + #TODO: check string for float because to_f returnsa + # 0.0 when wrong string + nodeMustBe(node, "double") + hasOnlyOneChild(node) + + Convert.double(text(node.firstChild)) + end + + def dateTime(node) + nodeMustBe(node, "dateTime.iso8601") + hasOnlyOneChild(node) + + Convert.dateTime( text(node.firstChild) ) + end + + def base64(node) + nodeMustBe(node, "base64") + #hasOnlyOneChild(node) + + Convert.base64(text_zero_one(node)) + end + + def member(node) + nodeMustBe(node, "member") + assert( node.childNodes.to_a.size == 2 ) + + [ name(node[0]), value(node[1]) ] + end + + def name(node) + nodeMustBe(node, "name") + #hasOnlyOneChild(node) + text_zero_one(node) + end + + def array(node) + nodeMustBe(node, "array") + hasOnlyOneChild(node, "data") + data(node.firstChild) + end + + def data(node) + nodeMustBe(node, "data") + + node.childNodes.to_a.collect do |val| + value(val) + end + end + + def param(node) + nodeMustBe(node, "param") + hasOnlyOneChild(node, "value") + value(node.firstChild) + end + + def methodResponse(node) + nodeMustBe(node, "methodResponse") + hasOnlyOneChild(node, %w(params fault)) + child = node.firstChild + + case child.nodeName + when "params" + [ true, params(child,false) ] + when "fault" + [ false, fault(child) ] + else + raise "unexpected error" + end + + end + + def methodName(node) + nodeMustBe(node, "methodName") + hasOnlyOneChild(node) + text(node.firstChild) + end + + def params(node, call=true) + nodeMustBe(node, "params") + + if call + node.childNodes.to_a.collect do |n| + param(n) + end + else # response (only one param) + hasOnlyOneChild(node) + param(node.firstChild) + end + end + + def fault(node) + nodeMustBe(node, "fault") + hasOnlyOneChild(node, "value") + f = value(node.firstChild) + Convert.fault(f) + end + + + + # _nodeType is defined in the subclass + def text(node) + assert( _nodeType(node) == :TEXT ) + assert( node.hasChildNodes == false ) + assert( node.nodeValue != nil ) + + node.nodeValue.to_s + end + + def struct(node) + nodeMustBe(node, "struct") + + hash = {} + node.childNodes.to_a.each do |me| + n, v = member(me) + hash[n] = v + end + + Convert.struct(hash) + end + + + def value(node) + nodeMustBe(node, "value") + nodes = node.childNodes.to_a.size + if nodes == 0 + return "" + elsif nodes > 1 + raise "wrong xml-rpc (size)" + end + + child = node.firstChild + + case _nodeType(child) + when :TEXT + text_zero_one(node) + when :ELEMENT + case child.nodeName + when "i4", "i8", "int" then integer(child) + when "boolean" then boolean(child) + when "string" then string(child) + when "double" then double(child) + when "dateTime.iso8601" then dateTime(child) + when "base64" then base64(child) + when "struct" then struct(child) + when "array" then array(child) + when "nil" + if Config::ENABLE_NIL_PARSER + v_nil(child) + else + raise "wrong/unknown XML-RPC type 'nil'" + end + else + raise "wrong/unknown XML-RPC type" + end + else + raise "wrong type of node" + end + + end + + def methodCall(node) + nodeMustBe(node, "methodCall") + assert( (1..2).include?( node.childNodes.to_a.size ) ) + name = methodName(node[0]) + + if node.childNodes.to_a.size == 2 then + pa = params(node[1]) + else # no parameters given + pa = [] + end + [name, pa] + end + + end # module TreeParserMixin + + class AbstractStreamParser + def parseMethodResponse(str) + parser = @parser_class.new + parser.parse(str) + raise "No valid method response!" if parser.method_name != nil + if parser.fault != nil + # is a fault structure + [false, parser.fault] + else + # is a normal return value + raise "Missing return value!" if parser.params.size == 0 + raise "Too many return values. Only one allowed!" if parser.params.size > 1 + [true, parser.params[0]] + end + end + + def parseMethodCall(str) + parser = @parser_class.new + parser.parse(str) + raise "No valid method call - missing method name!" if parser.method_name.nil? + [parser.method_name, parser.params] + end + end + + module StreamParserMixin + attr_reader :params + attr_reader :method_name + attr_reader :fault + + def initialize(*a) + super(*a) + @params = [] + @values = [] + @val_stack = [] + + @names = [] + @name = [] + + @structs = [] + @struct = {} + + @method_name = nil + @fault = nil + + @data = nil + end + + def startElement(name, attrs=[]) + @data = nil + case name + when "value" + @value = nil + when "nil" + raise "wrong/unknown XML-RPC type 'nil'" unless Config::ENABLE_NIL_PARSER + @value = :nil + when "array" + @val_stack << @values + @values = [] + when "struct" + @names << @name + @name = [] + + @structs << @struct + @struct = {} + end + end + + def endElement(name) + @data ||= "" + case name + when "string" + @value = @data + when "i4", "i8", "int" + @value = Convert.int(@data) + when "boolean" + @value = Convert.boolean(@data) + when "double" + @value = Convert.double(@data) + when "dateTime.iso8601" + @value = Convert.dateTime(@data) + when "base64" + @value = Convert.base64(@data) + when "value" + @value = @data if @value.nil? + @values << (@value == :nil ? nil : @value) + when "array" + @value = @values + @values = @val_stack.pop + when "struct" + @value = Convert.struct(@struct) + + @name = @names.pop + @struct = @structs.pop + when "name" + @name[0] = @data + when "member" + @struct[@name[0]] = @values.pop + + when "param" + @params << @values[0] + @values = [] + + when "fault" + @fault = Convert.fault(@values[0]) + + when "methodName" + @method_name = @data + end + + @data = nil + end + + def character(data) + if @data + @data << data + else + @data = data + end + end + + end # module StreamParserMixin + + class XMLStreamParser < AbstractStreamParser + def initialize + require "xmlparser" + @parser_class = Class.new(::XMLParser) { + include StreamParserMixin + } + end + end # class XMLStreamParser + + class NQXMLStreamParser < AbstractStreamParser + def initialize + require "nqxml/streamingparser" + @parser_class = XMLRPCParser + end + + class XMLRPCParser + include StreamParserMixin + + def parse(str) + parser = NQXML::StreamingParser.new(str) + parser.each do |ele| + case ele + when NQXML::Text + @data = ele.text + #character(ele.text) + when NQXML::Tag + if ele.isTagEnd + endElement(ele.name) + else + startElement(ele.name, ele.attrs) + end + end + end # do + end # method parse + end # class XMLRPCParser + + end # class NQXMLStreamParser + + class XMLTreeParser < AbstractTreeParser + + def initialize + require "xmltreebuilder" + + # The new XMLParser library (0.6.2+) uses a slightly different DOM implementation. + # The following code removes the differences between both versions. + if defined? XML::DOM::Builder + return if defined? XML::DOM::Node::DOCUMENT # code below has been already executed + klass = XML::DOM::Node + klass.const_set(:DOCUMENT, klass::DOCUMENT_NODE) + klass.const_set(:TEXT, klass::TEXT_NODE) + klass.const_set(:COMMENT, klass::COMMENT_NODE) + klass.const_set(:ELEMENT, klass::ELEMENT_NODE) + end + end + + private + + def _nodeType(node) + tp = node.nodeType + if tp == XML::SimpleTree::Node::TEXT then :TEXT + elsif tp == XML::SimpleTree::Node::COMMENT then :COMMENT + elsif tp == XML::SimpleTree::Node::ELEMENT then :ELEMENT + else :ELSE + end + end + + + def methodResponse_document(node) + assert( node.nodeType == XML::SimpleTree::Node::DOCUMENT ) + hasOnlyOneChild(node, "methodResponse") + + methodResponse(node.firstChild) + end + + def methodCall_document(node) + assert( node.nodeType == XML::SimpleTree::Node::DOCUMENT ) + hasOnlyOneChild(node, "methodCall") + + methodCall(node.firstChild) + end + + def createCleanedTree(str) + doc = XML::SimpleTreeBuilder.new.parse(str) + doc.documentElement.normalize + removeWhitespacesAndComments(doc) + doc + end + + end # class XMLParser + + class NQXMLTreeParser < AbstractTreeParser + + def initialize + require "nqxml/treeparser" + end + + private + + def _nodeType(node) + node.nodeType + end + + def methodResponse_document(node) + methodResponse(node) + end + + def methodCall_document(node) + methodCall(node) + end + + def createCleanedTree(str) + doc = ::NQXML::TreeParser.new(str).document.rootNode + removeWhitespacesAndComments(doc) + doc + end + + end # class NQXMLTreeParser + + class REXMLStreamParser < AbstractStreamParser + def initialize + require "rexml/document" + @parser_class = StreamListener + end + + class StreamListener + include StreamParserMixin + + alias :tag_start :startElement + alias :tag_end :endElement + alias :text :character + alias :cdata :character + + def method_missing(*a) + # ignore + end + + def parse(str) + REXML::Document.parse_stream(str, self) + end + end + + end + + class XMLScanStreamParser < AbstractStreamParser + def initialize + require "xmlscan/parser" + @parser_class = XMLScanParser + end + + class XMLScanParser + include StreamParserMixin + + Entities = { + "lt" => "<", + "gt" => ">", + "amp" => "&", + "quot" => '"', + "apos" => "'" + } + + def parse(str) + parser = XMLScan::XMLParser.new(self) + parser.parse(str) + end + + alias :on_stag :startElement + alias :on_etag :endElement + + def on_stag_end(name); end + + def on_stag_end_empty(name) + startElement(name) + endElement(name) + end + + def on_chardata(str) + character(str) + end + + def on_cdata(str) + character(str) + end + + def on_entityref(ent) + str = Entities[ent] + if str + character(str) + else + raise "unknown entity" + end + end + + def on_charref(code) + character(code.chr) + end + + def on_charref_hex(code) + character(code.chr) + end + + def method_missing(*a) + end + + # TODO: call/implement? + # valid_name? + # valid_chardata? + # valid_char? + # parse_error + + end + end + + class LibXMLStreamParser < AbstractStreamParser + def initialize + require 'libxml' + @parser_class = LibXMLStreamListener + end + + class LibXMLStreamListener + include StreamParserMixin + + def on_start_element_ns(name, attributes, prefix, uri, namespaces) + startElement(name) + end + + def on_end_element_ns(name, prefix, uri) + endElement(name) + end + + alias :on_characters :character + alias :on_cdata_block :character + + def method_missing(*a) + end + + def parse(str) + parser = LibXML::XML::SaxParser.string(str) + parser.callbacks = self + parser.parse() + end + end + end + + XMLParser = XMLTreeParser + NQXMLParser = NQXMLTreeParser + + Classes = [XMLStreamParser, XMLTreeParser, + NQXMLStreamParser, NQXMLTreeParser, + REXMLStreamParser, XMLScanStreamParser, + LibXMLStreamParser] + + # yields an instance of each installed parser + def self.each_installed_parser + XMLRPC::XMLParser::Classes.each do |klass| + begin + yield klass.new + rescue LoadError + end + end + end + + end # module XMLParser + + +end # module XMLRPC + diff --git a/jni/ruby/lib/xmlrpc/server.rb b/jni/ruby/lib/xmlrpc/server.rb new file mode 100644 index 0000000..57e37ae --- /dev/null +++ b/jni/ruby/lib/xmlrpc/server.rb @@ -0,0 +1,707 @@ +# xmlrpc/server.rb +# Copyright (C) 2001, 2002, 2003, 2005 by Michael Neumann (mneumann@ntecs.de) +# +# Released under the same term of license as Ruby. + +require "xmlrpc/parser" +require "xmlrpc/create" +require "xmlrpc/config" +require "xmlrpc/utils" # ParserWriterChooseMixin + + + +module XMLRPC # :nodoc: + + +# This is the base class for all XML-RPC server-types (CGI, standalone). +# You can add handler and set a default handler. +# Do not use this server, as this is/should be an abstract class. +# +# === How the method to call is found +# The arity (number of accepted arguments) of a handler (method or Proc +# object) is compared to the given arguments submitted by the client for a +# RPC, or Remote Procedure Call. +# +# A handler is only called if it accepts the number of arguments, otherwise +# the search for another handler will go on. When at the end no handler was +# found, the default_handler, XMLRPC::BasicServer#set_default_handler will be +# called. +# +# With this technique it is possible to do overloading by number of parameters, but +# only for Proc handler, because you cannot define two methods of the same name in +# the same class. +class BasicServer + + include ParserWriterChooseMixin + include ParseContentType + + ERR_METHOD_MISSING = 1 + ERR_UNCAUGHT_EXCEPTION = 2 + ERR_MC_WRONG_PARAM = 3 + ERR_MC_MISSING_PARAMS = 4 + ERR_MC_MISSING_METHNAME = 5 + ERR_MC_RECURSIVE_CALL = 6 + ERR_MC_WRONG_PARAM_PARAMS = 7 + ERR_MC_EXPECTED_STRUCT = 8 + + + # Creates a new XMLRPC::BasicServer instance, which should not be + # done, because XMLRPC::BasicServer is an abstract class. This + # method should be called from a subclass indirectly by a +super+ call + # in the initialize method. + # + # The parameter +class_delim+ is used by add_handler, see + # XMLRPC::BasicServer#add_handler, when an object is added as a handler, to + # delimit the object-prefix and the method-name. + def initialize(class_delim=".") + @handler = [] + @default_handler = nil + @service_hook = nil + + @class_delim = class_delim + @create = nil + @parser = nil + + add_multicall if Config::ENABLE_MULTICALL + add_introspection if Config::ENABLE_INTROSPECTION + end + + # Adds +aBlock+ to the list of handlers, with +name+ as the name of + # the method. + # + # Parameters +signature+ and +help+ are used by the Introspection method if + # specified, where +signature+ is either an Array containing strings each + # representing a type of it's signature (the first is the return value) or + # an Array of Arrays if the method has multiple signatures. + # + # Value type-names are "int, boolean, double, string, dateTime.iso8601, + # base64, array, struct". + # + # Parameter +help+ is a String with information about how to call this method etc. + # + # When a method fails, it can tell the client by throwing an + # XMLRPC::FaultException like in this example: + # + # s.add_handler("michael.div") do |a,b| + # if b == 0 + # raise XMLRPC::FaultException.new(1, "division by zero") + # else + # a / b + # end + # end + # + # In the case of <code>b==0</code> the client gets an object back of type + # XMLRPC::FaultException that has a +faultCode+ and +faultString+ field. + # + # This is the second form of ((<add_handler|XMLRPC::BasicServer#add_handler>)). + # To add an object write: + # + # server.add_handler("michael", MyHandlerClass.new) + # + # All public methods of MyHandlerClass are accessible to + # the XML-RPC clients by <code>michael."name of method"</code>. This is + # where the +class_delim+ in XMLRPC::BasicServer.new plays it's role, a + # XML-RPC method-name is defined by +prefix+ + +class_delim+ + <code>"name + # of method"</code>. + # + # The third form of +add_handler is to use XMLRPC::Service::Interface to + # generate an object, which represents an interface (with signature and + # help text) for a handler class. + # + # The +interface+ parameter must be an instance of XMLRPC::Service::Interface. + # Adds all methods of +obj+ which are defined in the +interface+ to the server. + # + # This is the recommended way of adding services to a server! + def add_handler(prefix, obj_or_signature=nil, help=nil, &block) + if block_given? + # proc-handler + @handler << [prefix, block, obj_or_signature, help] + else + if prefix.kind_of? String + # class-handler + raise ArgumentError, "Expected non-nil value" if obj_or_signature.nil? + @handler << [prefix + @class_delim, obj_or_signature] + elsif prefix.kind_of? XMLRPC::Service::BasicInterface + # class-handler with interface + # add all methods + @handler += prefix.get_methods(obj_or_signature, @class_delim) + else + raise ArgumentError, "Wrong type for parameter 'prefix'" + end + end + self + end + + # Returns the service-hook, which is called on each service request (RPC) + # unless it's +nil+. + def get_service_hook + @service_hook + end + + # A service-hook is called for each service request (RPC). + # + # You can use a service-hook for example to wrap existing methods and catch + # exceptions of them or convert values to values recognized by XMLRPC. + # + # You can disable it by passing +nil+ as the +handler+ parameter. + # + # The service-hook is called with a Proc object along with any parameters. + # + # An example: + # + # server.set_service_hook {|obj, *args| + # begin + # ret = obj.call(*args) # call the original service-method + # # could convert the return value + # rescue + # # rescue exceptions + # end + # } + # + def set_service_hook(&handler) + @service_hook = handler + self + end + + # Returns the default-handler, which is called when no handler for + # a method-name is found. + # + # It is either a Proc object or +nil+. + def get_default_handler + @default_handler + end + + # Sets +handler+ as the default-handler, which is called when + # no handler for a method-name is found. + # + # +handler+ is a code-block. + # + # The default-handler is called with the (XML-RPC) method-name as first + # argument, and the other arguments are the parameters given by the + # client-call. + # + # If no block is specified the default of XMLRPC::BasicServer is + # used, which raises a XMLRPC::FaultException saying "method missing". + def set_default_handler(&handler) + @default_handler = handler + self + end + + # Adds the multi-call handler <code>"system.multicall"</code>. + def add_multicall + add_handler("system.multicall", %w(array array), "Multicall Extension") do |arrStructs| + unless arrStructs.is_a? Array + raise XMLRPC::FaultException.new(ERR_MC_WRONG_PARAM, "system.multicall expects an array") + end + + arrStructs.collect {|call| + if call.is_a? Hash + methodName = call["methodName"] + params = call["params"] + + if params.nil? + multicall_fault(ERR_MC_MISSING_PARAMS, "Missing params") + elsif methodName.nil? + multicall_fault(ERR_MC_MISSING_METHNAME, "Missing methodName") + else + if methodName == "system.multicall" + multicall_fault(ERR_MC_RECURSIVE_CALL, "Recursive system.multicall forbidden") + else + unless params.is_a? Array + multicall_fault(ERR_MC_WRONG_PARAM_PARAMS, "Parameter params have to be an Array") + else + ok, val = call_method(methodName, *params) + if ok + # correct return value + [val] + else + # exception + multicall_fault(val.faultCode, val.faultString) + end + end + end + end + + else + multicall_fault(ERR_MC_EXPECTED_STRUCT, "system.multicall expected struct") + end + } + end # end add_handler + self + end + + # Adds the introspection handlers <code>"system.listMethods"</code>, + # <code>"system.methodSignature"</code> and + # <code>"system.methodHelp"</code>, where only the first one works. + def add_introspection + add_handler("system.listMethods",%w(array), "List methods available on this XML-RPC server") do + methods = [] + @handler.each do |name, obj| + if obj.kind_of? Proc + methods << name + else + obj.class.public_instance_methods(false).each do |meth| + methods << "#{name}#{meth}" + end + end + end + methods + end + + add_handler("system.methodSignature", %w(array string), "Returns method signature") do |meth| + sigs = [] + @handler.each do |name, obj, sig| + if obj.kind_of? Proc and sig != nil and name == meth + if sig[0].kind_of? Array + # sig contains multiple signatures, e.g. [["array"], ["array", "string"]] + sig.each {|s| sigs << s} + else + # sig is a single signature, e.g. ["array"] + sigs << sig + end + end + end + sigs.uniq! || sigs # remove eventually duplicated signatures + end + + add_handler("system.methodHelp", %w(string string), "Returns help on using this method") do |meth| + help = nil + @handler.each do |name, obj, sig, hlp| + if obj.kind_of? Proc and name == meth + help = hlp + break + end + end + help || "" + end + + self + end + + + + def process(data) + method, params = parser().parseMethodCall(data) + handle(method, *params) + end + + private + + def multicall_fault(nr, str) + {"faultCode" => nr, "faultString" => str} + end + + def dispatch(methodname, *args) + for name, obj in @handler + if obj.kind_of? Proc + next unless methodname == name + else + next unless methodname =~ /^#{name}(.+)$/ + next unless obj.respond_to? $1 + obj = obj.method($1) + end + + if check_arity(obj, args.size) + if @service_hook.nil? + return obj.call(*args) + else + return @service_hook.call(obj, *args) + end + end + end + + if @default_handler.nil? + raise XMLRPC::FaultException.new(ERR_METHOD_MISSING, "Method #{methodname} missing or wrong number of parameters!") + else + @default_handler.call(methodname, *args) + end + end + + + # Returns +true+, if the arity of +obj+ matches +n_args+ + def check_arity(obj, n_args) + ary = obj.arity + + if ary >= 0 + n_args == ary + else + n_args >= (ary+1).abs + end + end + + + + def call_method(methodname, *args) + begin + [true, dispatch(methodname, *args)] + rescue XMLRPC::FaultException => e + [false, e] + rescue Exception => e + [false, XMLRPC::FaultException.new(ERR_UNCAUGHT_EXCEPTION, "Uncaught exception #{e.message} in method #{methodname}")] + end + end + + def handle(methodname, *args) + create().methodResponse(*call_method(methodname, *args)) + end + + +end + + +# Implements a CGI-based XML-RPC server. +# +# require "xmlrpc/server" +# +# s = XMLRPC::CGIServer.new +# +# s.add_handler("michael.add") do |a,b| +# a + b +# end +# +# s.add_handler("michael.div") do |a,b| +# if b == 0 +# raise XMLRPC::FaultException.new(1, "division by zero") +# else +# a / b +# end +# end +# +# s.set_default_handler do |name, *args| +# raise XMLRPC::FaultException.new(-99, "Method #{name} missing" + +# " or wrong number of parameters!") +# end +# +# s.serve +# +# +# <b>Note:</b> Make sure that you don't write to standard-output in a +# handler, or in any other part of your program, this would cause a CGI-based +# server to fail! +class CGIServer < BasicServer + @@obj = nil + + # Creates a new XMLRPC::CGIServer instance. + # + # All parameters given are by-passed to XMLRPC::BasicServer.new. + # + # You can only create <b>one</b> XMLRPC::CGIServer instance, because more + # than one makes no sense. + def CGIServer.new(*a) + @@obj = super(*a) if @@obj.nil? + @@obj + end + + def initialize(*a) + super(*a) + end + + # Call this after you have added all you handlers to the server. + # + # This method processes a XML-RPC method call and sends the answer + # back to the client. + def serve + catch(:exit_serve) { + length = ENV['CONTENT_LENGTH'].to_i + + http_error(405, "Method Not Allowed") unless ENV['REQUEST_METHOD'] == "POST" + http_error(400, "Bad Request") unless parse_content_type(ENV['CONTENT_TYPE']).first == "text/xml" + http_error(411, "Length Required") unless length > 0 + + # TODO: do we need a call to binmode? + $stdin.binmode if $stdin.respond_to? :binmode + data = $stdin.read(length) + + http_error(400, "Bad Request") if data.nil? or data.bytesize != length + + http_write(process(data), "Content-type" => "text/xml; charset=utf-8") + } + end + + + private + + def http_error(status, message) + err = "#{status} #{message}" + msg = <<-"MSGEND" + <html> + <head> + <title>#{err}</title> + </head> + <body> + <h1>#{err}</h1> + <p>Unexpected error occurred while processing XML-RPC request!</p> + </body> + </html> + MSGEND + + http_write(msg, "Status" => err, "Content-type" => "text/html") + throw :exit_serve # exit from the #serve method + end + + def http_write(body, header) + h = {} + header.each {|key, value| h[key.to_s.capitalize] = value} + h['Status'] ||= "200 OK" + h['Content-length'] ||= body.bytesize.to_s + + str = "" + h.each {|key, value| str << "#{key}: #{value}\r\n"} + str << "\r\n#{body}" + + print str + end + +end + + +# Implements a XML-RPC server, which works with Apache mod_ruby. +# +# Use it in the same way as XMLRPC::CGIServer! +class ModRubyServer < BasicServer + + # Creates a new XMLRPC::ModRubyServer instance. + # + # All parameters given are by-passed to XMLRPC::BasicServer.new. + def initialize(*a) + @ap = Apache::request + super(*a) + end + + # Call this after you have added all you handlers to the server. + # + # This method processes a XML-RPC method call and sends the answer + # back to the client. + def serve + catch(:exit_serve) { + header = {} + @ap.headers_in.each {|key, value| header[key.capitalize] = value} + + length = header['Content-length'].to_i + + http_error(405, "Method Not Allowed") unless @ap.request_method == "POST" + http_error(400, "Bad Request") unless parse_content_type(header['Content-type']).first == "text/xml" + http_error(411, "Length Required") unless length > 0 + + # TODO: do we need a call to binmode? + @ap.binmode + data = @ap.read(length) + + http_error(400, "Bad Request") if data.nil? or data.bytesize != length + + http_write(process(data), 200, "Content-type" => "text/xml; charset=utf-8") + } + end + + + private + + def http_error(status, message) + err = "#{status} #{message}" + msg = <<-"MSGEND" + <html> + <head> + <title>#{err}</title> + </head> + <body> + <h1>#{err}</h1> + <p>Unexpected error occurred while processing XML-RPC request!</p> + </body> + </html> + MSGEND + + http_write(msg, status, "Status" => err, "Content-type" => "text/html") + throw :exit_serve # exit from the #serve method + end + + def http_write(body, status, header) + h = {} + header.each {|key, value| h[key.to_s.capitalize] = value} + h['Status'] ||= "200 OK" + h['Content-length'] ||= body.bytesize.to_s + + h.each {|key, value| @ap.headers_out[key] = value } + @ap.content_type = h["Content-type"] + @ap.status = status.to_i + @ap.send_http_header + + @ap.print body + end + +end + + +class WEBrickServlet < BasicServer; end # forward declaration + +# Implements a standalone XML-RPC server. The method XMLRPC::Server#serve is +# left if a SIGHUP is sent to the program. +# +# require "xmlrpc/server" +# +# s = XMLRPC::Server.new(8080) +# +# s.add_handler("michael.add") do |a,b| +# a + b +# end +# +# s.add_handler("michael.div") do |a,b| +# if b == 0 +# raise XMLRPC::FaultException.new(1, "division by zero") +# else +# a / b +# end +# end +# +# s.set_default_handler do |name, *args| +# raise XMLRPC::FaultException.new(-99, "Method #{name} missing" + +# " or wrong number of parameters!") +# end +# +# s.serve +class Server < WEBrickServlet + + # Creates a new XMLRPC::Server instance, which is a XML-RPC server + # listening on the given +port+ and accepts requests for the given +host+, + # which is +localhost+ by default. + # + # The server is not started, to start it you have to call + # XMLRPC::Server#serve. + # + # The optional +audit+ and +debug+ parameters are obsolete! + # + # All additionally provided parameters in <code>*a</code> are by-passed to + # XMLRPC::BasicServer.new. + def initialize(port=8080, host="127.0.0.1", maxConnections=4, stdlog=$stdout, audit=true, debug=true, *a) + super(*a) + require 'webrick' + @server = WEBrick::HTTPServer.new(:Port => port, :BindAddress => host, :MaxClients => maxConnections, + :Logger => WEBrick::Log.new(stdlog)) + @server.mount("/", self) + end + + # Call this after you have added all you handlers to the server. + # This method starts the server to listen for XML-RPC requests and answer them. + def serve + signals = %w[INT TERM HUP] & Signal.list.keys + signals.each { |signal| trap(signal) { @server.shutdown } } + + @server.start + end + + # Stops and shuts the server down. + def shutdown + @server.shutdown + end + +end + + +# Implements a servlet for use with WEBrick, a pure Ruby (HTTP) server +# framework. +# +# require "webrick" +# require "xmlrpc/server" +# +# s = XMLRPC::WEBrickServlet.new +# s.add_handler("michael.add") do |a,b| +# a + b +# end +# +# s.add_handler("michael.div") do |a,b| +# if b == 0 +# raise XMLRPC::FaultException.new(1, "division by zero") +# else +# a / b +# end +# end +# +# s.set_default_handler do |name, *args| +# raise XMLRPC::FaultException.new(-99, "Method #{name} missing" + +# " or wrong number of parameters!") +# end +# +# httpserver = WEBrick::HTTPServer.new(:Port => 8080) +# httpserver.mount("/RPC2", s) +# trap("HUP") { httpserver.shutdown } # use 1 instead of "HUP" on Windows +# httpserver.start +class WEBrickServlet < BasicServer + def initialize(*a) + super + require "webrick/httpstatus" + @valid_ip = nil + end + + # Deprecated from WEBrick/1.2.2, but does not break anything. + def require_path_info? + false + end + + def get_instance(config, *options) + # TODO: set config & options + self + end + + # Specifies the valid IP addresses that are allowed to connect to the server. + # + # Each IP is either a String or a Regexp. + def set_valid_ip(*ip_addr) + if ip_addr.size == 1 and ip_addr[0].nil? + @valid_ip = nil + else + @valid_ip = ip_addr + end + end + + # Return the valid IP addresses that are allowed to connect to the server. + # + # See also, XMLRPC::Server#set_valid_ip + def get_valid_ip + @valid_ip + end + + def service(request, response) + + if @valid_ip + raise WEBrick::HTTPStatus::Forbidden unless @valid_ip.any? { |ip| request.peeraddr[3] =~ ip } + end + + if request.request_method != "POST" + raise WEBrick::HTTPStatus::MethodNotAllowed, + "unsupported method `#{request.request_method}'." + end + + if parse_content_type(request['Content-type']).first != "text/xml" + raise WEBrick::HTTPStatus::BadRequest + end + + length = (request['Content-length'] || 0).to_i + + raise WEBrick::HTTPStatus::LengthRequired unless length > 0 + + data = request.body + + if data.nil? or data.bytesize != length + raise WEBrick::HTTPStatus::BadRequest + end + + resp = process(data) + if resp.nil? or resp.bytesize <= 0 + raise WEBrick::HTTPStatus::InternalServerError + end + + response.status = 200 + response['Content-Length'] = resp.bytesize + response['Content-Type'] = "text/xml; charset=utf-8" + response.body = resp + end +end + + +end # module XMLRPC + + +=begin += History + $Id: server.rb 44049 2013-12-07 10:27:27Z a_matsuda $ +=end + diff --git a/jni/ruby/lib/xmlrpc/utils.rb b/jni/ruby/lib/xmlrpc/utils.rb new file mode 100644 index 0000000..a7e895f --- /dev/null +++ b/jni/ruby/lib/xmlrpc/utils.rb @@ -0,0 +1,171 @@ +# +# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de) +# +# $Id: utils.rb 36958 2012-09-13 02:22:10Z zzak $ +# +module XMLRPC # :nodoc: + + + # This module enables a user-class to be marshalled + # by XML-RPC for Ruby into a Hash, with one additional + # key/value pair <code>___class___ => ClassName</code> + # + module Marshallable + end + + + # Defines ParserWriterChooseMixin, which makes it possible to choose a + # different XMLWriter and/or XMLParser then the default one. + # + # The Mixin is used in client.rb (class XMLRPC::Client) + # and server.rb (class XMLRPC::BasicServer) + module ParserWriterChooseMixin + + # Sets the XMLWriter to use for generating XML output. + # + # Should be an instance of a class from module XMLRPC::XMLWriter. + # + # If this method is not called, then XMLRPC::Config::DEFAULT_WRITER is used. + def set_writer(writer) + @create = Create.new(writer) + self + end + + # Sets the XMLParser to use for parsing XML documents. + # + # Should be an instance of a class from module XMLRPC::XMLParser. + # + # If this method is not called, then XMLRPC::Config::DEFAULT_PARSER is used. + def set_parser(parser) + @parser = parser + self + end + + private + + def create + # if set_writer was not already called then call it now + if @create.nil? then + set_writer(Config::DEFAULT_WRITER.new) + end + @create + end + + def parser + # if set_parser was not already called then call it now + if @parser.nil? then + set_parser(Config::DEFAULT_PARSER.new) + end + @parser + end + + end # module ParserWriterChooseMixin + + + module Service + + # Base class for XMLRPC::Service::Interface definitions, used + # by XMLRPC::BasicServer#add_handler + class BasicInterface + attr_reader :prefix, :methods + + def initialize(prefix) + @prefix = prefix + @methods = [] + end + + def add_method(sig, help=nil, meth_name=nil) + mname = nil + sig = [sig] if sig.kind_of? String + + sig = sig.collect do |s| + name, si = parse_sig(s) + raise "Wrong signatures!" if mname != nil and name != mname + mname = name + si + end + + @methods << [mname, meth_name || mname, sig, help] + end + + private + + def parse_sig(sig) + # sig is a String + if sig =~ /^\s*(\w+)\s+([^(]+)(\(([^)]*)\))?\s*$/ + params = [$1] + name = $2.strip + $4.split(",").each {|i| params << i.strip} if $4 != nil + return name, params + else + raise "Syntax error in signature" + end + end + + end # class BasicInterface + + # + # Class which wraps a XMLRPC::Service::Interface definition, used + # by XMLRPC::BasicServer#add_handler + # + class Interface < BasicInterface + def initialize(prefix, &p) + raise "No interface specified" if p.nil? + super(prefix) + instance_eval(&p) + end + + def get_methods(obj, delim=".") + prefix = @prefix + delim + @methods.collect { |name, meth, sig, help| + [prefix + name.to_s, obj.method(meth).to_proc, sig, help] + } + end + + private + + def meth(*a) + add_method(*a) + end + + end # class Interface + + class PublicInstanceMethodsInterface < BasicInterface + def initialize(prefix) + super(prefix) + end + + def get_methods(obj, delim=".") + prefix = @prefix + delim + obj.class.public_instance_methods(false).collect { |name| + [prefix + name.to_s, obj.method(name).to_proc, nil, nil] + } + end + end + + + end # module Service + + + # + # Short-form to create a XMLRPC::Service::Interface + # + def self.interface(prefix, &p) + Service::Interface.new(prefix, &p) + end + + # Short-cut for creating a XMLRPC::Service::PublicInstanceMethodsInterface + def self.iPIMethods(prefix) + Service::PublicInstanceMethodsInterface.new(prefix) + end + + + module ParseContentType + def parse_content_type(str) + a, *b = str.split(";") + return a.strip.downcase, *b + end + end + +end # module XMLRPC + |