summaryrefslogtreecommitdiff
path: root/jni/ruby/lib/xmlrpc
diff options
context:
space:
mode:
Diffstat (limited to 'jni/ruby/lib/xmlrpc')
-rw-r--r--jni/ruby/lib/xmlrpc/base64.rb62
-rw-r--r--jni/ruby/lib/xmlrpc/client.rb614
-rw-r--r--jni/ruby/lib/xmlrpc/config.rb43
-rw-r--r--jni/ruby/lib/xmlrpc/create.rb286
-rw-r--r--jni/ruby/lib/xmlrpc/datetime.rb129
-rw-r--r--jni/ruby/lib/xmlrpc/marshal.rb66
-rw-r--r--jni/ruby/lib/xmlrpc/parser.rb870
-rw-r--r--jni/ruby/lib/xmlrpc/server.rb707
-rw-r--r--jni/ruby/lib/xmlrpc/utils.rb171
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!(/&/, '&amp;')
+ cleaned.gsub!(/</, '&lt;')
+ cleaned.gsub!(/>/, '&gt;')
+ 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
+