eaiovnaovbqoebvqoeavibavo datetime.rb000064400000007040147634037370006704 0ustar00# # 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, # correcly, 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 @year 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 36958 2012-09-13 02:22:10Z zzak $ =end client.rb000064400000044027147634037370006374 0ustar00# 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 46153 2014-05-27 02:46:43Z usa $ # 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 "/RPC2". # +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 # 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_MARSHALLABLE is +true+). # That object is converted into a hash, with one additional key/value # pair ___class___ 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 NOT 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 explicitely 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"] || "" if data.nil? or data.bytesize == 0 raise "Wrong size. Was #{data.bytesize}, should be #{expected}" end set_cookies = resp.get_fields("Set-Cookie") if set_cookies and !set_cookies.empty? require 'webrick/cookie' @cookie = set_cookies.collect do |set_cookie| cookie = WEBrick::Cookie.parse_set_cookie(set_cookie) WEBrick::Cookie.new(cookie.name, cookie.value).to_s end.join("; ") end return data 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 httpserver.rb000064400000010203147634037370007311 0ustar00# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de) # # $Id: httpserver.rb 36958 2012-09-13 02:22:10Z zzak $ # require "gserver" # Implements a simple HTTP-server by using John W. Small's (jsmall@laser.net) # ruby-generic-server: GServer. class HttpServer < GServer ## # +handle_obj+ specifies the object, that receives calls from +request_handler+ # and +ip_auth_handler+ def initialize(handle_obj, port = 8080, host = DEFAULT_HOST, maxConnections = 4, stdlog = $stdout, audit = true, debug = true) @handler = handle_obj super(port, host, maxConnections, stdlog, audit, debug) end private CRLF = "\r\n" HTTP_PROTO = "HTTP/1.0" SERVER_NAME = "HttpServer (Ruby #{RUBY_VERSION})" # Default header for the server name DEFAULT_HEADER = { "Server" => SERVER_NAME } # Mapping of status codes and error messages StatusCodeMapping = { 200 => "OK", 400 => "Bad Request", 403 => "Forbidden", 405 => "Method Not Allowed", 411 => "Length Required", 500 => "Internal Server Error" } class Request attr_reader :data, :header, :method, :path, :proto def initialize(data, method=nil, path=nil, proto=nil) @header, @data = Table.new, data @method, @path, @proto = method, path, proto end def content_length len = @header['Content-Length'] return nil if len.nil? return len.to_i end end class Response attr_reader :header attr_accessor :body, :status, :status_message def initialize(status=200) @status = status @status_message = nil @header = Table.new end end # A case-insensitive Hash class for HTTP header class Table include Enumerable def initialize(hash={}) @hash = hash update(hash) end def [](key) @hash[key.to_s.capitalize] end def []=(key, value) @hash[key.to_s.capitalize] = value end def update(hash) hash.each {|k,v| self[k] = v} self end def each @hash.each {|k,v| yield k.capitalize, v } end # Output the Hash table for the HTTP header def writeTo(port) each { |k,v| port << "#{k}: #{v}" << CRLF } end end # class Table # Generates a Hash with the HTTP headers def http_header(header=nil) # :doc: new_header = Table.new(DEFAULT_HEADER) new_header.update(header) unless header.nil? new_header["Connection"] = "close" new_header["Date"] = http_date(Time.now) new_header end # Returns a string which represents the time as rfc1123-date of HTTP-date def http_date( aTime ) # :doc: aTime.gmtime.strftime( "%a, %d %b %Y %H:%M:%S GMT" ) end # Returns a string which includes the status code message as, # http headers, and body for the response. def http_resp(status_code, status_message=nil, header=nil, body=nil) # :doc: status_message ||= StatusCodeMapping[status_code] str = "" str << "#{HTTP_PROTO} #{status_code} #{status_message}" << CRLF http_header(header).writeTo(str) str << CRLF str << body unless body.nil? str end # Handles the HTTP request and writes the response back to the client, +io+. # # If an Exception is raised while handling the request, the client will receive # a 500 "Internal Server Error" message. def serve(io) # :doc: # perform IP authentification unless @handler.ip_auth_handler(io) io << http_resp(403, "Forbidden") return end # parse first line if io.gets =~ /^(\S+)\s+(\S+)\s+(\S+)/ request = Request.new(io, $1, $2, $3) else io << http_resp(400, "Bad Request") return end # parse HTTP headers while (line=io.gets) !~ /^(\n|\r)/ if line =~ /^([\w-]+):\s*(.*)$/ request.header[$1] = $2.strip end end io.binmode response = Response.new # execute request handler @handler.request_handler(request, response) # write response back to the client io << http_resp(response.status, response.status_message, response.header, response.body) rescue Exception io << http_resp(500, "Internal Server Error") end end # class HttpServer parser.rb000064400000050010147634037370006377 0ustar00# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de) # # $Id: parser.rb 37688 2012-11-16 16:55:29Z marcandre $ # 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 # * 'faultCode' key is an Integer # * 'faultString' 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 XMLParser = XMLTreeParser NQXMLParser = NQXMLTreeParser Classes = [XMLStreamParser, XMLTreeParser, NQXMLStreamParser, NQXMLTreeParser, REXMLStreamParser, XMLScanStreamParser] # 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 config.rb000064400000001735147634037370006362 0ustar00# # $Id: config.rb 36958 2012-09-13 02:22:10Z zzak $ # 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 DEFAULT_PARSER = XMLParser::REXMLStreamParser # enable 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 create.rb000064400000015043147634037370006355 0ustar00# # 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) "" end def element(name, attrs, *children) raise "attributes not yet implemented" unless attrs.nil? if children.empty? "<#{name}/>" else "<#{name}>" + children.join("") + "" end end def text(txt) cleaned = txt.dup 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 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 base64.rb000064400000002640147634037370006175 0ustar00# # 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 utils.rb000064400000007650147634037370006257 0ustar00# # 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 ___class___ => ClassName # 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 server.rb000064400000047471147634037370006432 0ustar00# 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 paramter +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 b==0 the client gets an object back of type # XMLRPC::FaultException that has a +faultCode+ and +faultString+ field. # # This is the second form of (()). # To add an object write: # # server.add_handler("michael", MyHandlerClass.new) # # All public methods of MyHandlerClass are accessible to # the XML-RPC clients by michael."name of method". 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+ + "name # of method". # # 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 "system.multicall". 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 "system.listMethods", # "system.methodSignature" and # "system.methodHelp", 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 # # # Note: 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 one 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" #{err}

#{err}

Unexpected error occurred while processing XML-RPC request!

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" #{err}

#{err}

Unexpected error occurred while processing XML-RPC request!

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 *a 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 44391 2013-12-24 15:46:01Z nagachika $ =end marshal.rb000064400000002734147634037370006544 0ustar00# # 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 [ methodname, params ] def load_call( stringOrReadable ) parser.parseMethodCall( stringOrReadable ) end # Returns +paramOrFault+ def load_response( stringOrReadable ) parser.parseMethodResponse( stringOrReadable )[1] end end # class Marshal end