1# xmlrpc/server.rb
2# Copyright (C) 2001, 2002, 2003, 2005 by Michael Neumann (mneumann@ntecs.de)
3#
4# Released under the same term of license as Ruby.
5
6require "xmlrpc/parser"
7require "xmlrpc/create"
8require "xmlrpc/config"
9require "xmlrpc/utils"         # ParserWriterChooseMixin
10
11
12
13module XMLRPC # :nodoc:
14
15
16# This is the base class for all XML-RPC server-types (CGI, standalone).
17# You can add handler and set a default handler.
18# Do not use this server, as this is/should be an abstract class.
19#
20# === How the method to call is found
21# The arity (number of accepted arguments) of a handler (method or Proc
22# object) is compared to the given arguments submitted by the client for a
23# RPC, or Remote Procedure Call.
24#
25# A handler is only called if it accepts the number of arguments, otherwise
26# the search for another handler will go on. When at the end no handler was
27# found, the default_handler, XMLRPC::BasicServer#set_default_handler will be
28# called.
29#
30# With this technique it is possible to do overloading by number of parameters, but
31# only for Proc handler, because you cannot define two methods of the same name in
32# the same class.
33class BasicServer
34
35  include ParserWriterChooseMixin
36  include ParseContentType
37
38  ERR_METHOD_MISSING        = 1
39  ERR_UNCAUGHT_EXCEPTION    = 2
40  ERR_MC_WRONG_PARAM        = 3
41  ERR_MC_MISSING_PARAMS     = 4
42  ERR_MC_MISSING_METHNAME   = 5
43  ERR_MC_RECURSIVE_CALL     = 6
44  ERR_MC_WRONG_PARAM_PARAMS = 7
45  ERR_MC_EXPECTED_STRUCT    = 8
46
47
48  # Creates a new XMLRPC::BasicServer instance, which should not be
49  # done, because XMLRPC::BasicServer is an abstract class. This
50  # method should be called from a subclass indirectly by a +super+ call
51  # in the initialize method.
52  #
53  # The paramter +class_delim+ is used by add_handler, see
54  # XMLRPC::BasicServer#add_handler, when an object is added as a handler, to
55  # delimit the object-prefix and the method-name.
56  def initialize(class_delim=".")
57    @handler = []
58    @default_handler = nil
59    @service_hook = nil
60
61    @class_delim = class_delim
62    @create = nil
63    @parser = nil
64
65    add_multicall     if Config::ENABLE_MULTICALL
66    add_introspection if Config::ENABLE_INTROSPECTION
67  end
68
69  # Adds +aBlock+ to the list of handlers, with +name+ as the name of
70  # the method.
71  #
72  # Parameters +signature+ and +help+ are used by the Introspection method if
73  # specified, where +signature+ is either an Array containing strings each
74  # representing a type of it's signature (the first is the return value) or
75  # an Array of Arrays if the method has multiple signatures.
76  #
77  # Value type-names are "int, boolean, double, string, dateTime.iso8601,
78  # base64, array, struct".
79  #
80  # Parameter +help+ is a String with information about how to call this method etc.
81  #
82  # When a method fails, it can tell the client by throwing an
83  # XMLRPC::FaultException like in this example:
84  #
85  #     s.add_handler("michael.div") do |a,b|
86  #       if b == 0
87  #         raise XMLRPC::FaultException.new(1, "division by zero")
88  #       else
89  #         a / b
90  #       end
91  #     end
92  #
93  # In the case of <code>b==0</code> the client gets an object back of type
94  # XMLRPC::FaultException that has a +faultCode+ and +faultString+ field.
95  #
96  # This is the second form of ((<add_handler|XMLRPC::BasicServer#add_handler>)).
97  # To add an object write:
98  #
99  #     server.add_handler("michael", MyHandlerClass.new)
100  #
101  # All public methods of MyHandlerClass are accessible to
102  # the XML-RPC clients by <code>michael."name of method"</code>. This is
103  # where the +class_delim+ in XMLRPC::BasicServer.new plays it's role, a
104  # XML-RPC method-name is defined by +prefix+ + +class_delim+ + <code>"name
105  # of method"</code>.
106  #
107  # The third form of +add_handler is to use XMLRPC::Service::Interface to
108  # generate an object, which represents an interface (with signature and
109  # help text) for a handler class.
110  #
111  # The +interface+ parameter must be an instance of XMLRPC::Service::Interface.
112  # Adds all methods of +obj+ which are defined in the +interface+ to the server.
113  #
114  # This is the recommended way of adding services to a server!
115  def add_handler(prefix, obj_or_signature=nil, help=nil, &block)
116    if block_given?
117      # proc-handler
118      @handler << [prefix, block, obj_or_signature, help]
119    else
120      if prefix.kind_of? String
121        # class-handler
122        raise ArgumentError, "Expected non-nil value" if obj_or_signature.nil?
123        @handler << [prefix + @class_delim, obj_or_signature]
124      elsif prefix.kind_of? XMLRPC::Service::BasicInterface
125        # class-handler with interface
126        # add all methods
127        @handler += prefix.get_methods(obj_or_signature, @class_delim)
128      else
129        raise ArgumentError, "Wrong type for parameter 'prefix'"
130      end
131    end
132    self
133  end
134
135  # Returns the service-hook, which is called on each service request (RPC)
136  # unless it's +nil+.
137  def get_service_hook
138    @service_hook
139  end
140
141  # A service-hook is called for each service request (RPC).
142  #
143  # You can use a service-hook for example to wrap existing methods and catch
144  # exceptions of them or convert values to values recognized by XMLRPC.
145  #
146  # You can disable it by passing +nil+ as the +handler+ parameter.
147  #
148  # The service-hook is called with a Proc object along with any parameters.
149  #
150  # An example:
151  #
152  #    server.set_service_hook {|obj, *args|
153  #      begin
154  #        ret = obj.call(*args)  # call the original service-method
155  #        # could convert the return value
156  #      rescue
157  #        # rescue exceptions
158  #      end
159  #    }
160  #
161  def set_service_hook(&handler)
162    @service_hook = handler
163    self
164  end
165
166  # Returns the default-handler, which is called when no handler for
167  # a method-name is found.
168  #
169  # It is either a Proc object or +nil+.
170  def get_default_handler
171    @default_handler
172  end
173
174  # Sets +handler+ as the default-handler, which is called when
175  # no handler for a method-name is found.
176  #
177  # +handler+ is a code-block.
178  #
179  # The default-handler is called with the (XML-RPC) method-name as first
180  # argument, and the other arguments are the parameters given by the
181  # client-call.
182  #
183  # If no block is specified the default of XMLRPC::BasicServer is
184  # used, which raises a XMLRPC::FaultException saying "method missing".
185  def set_default_handler(&handler)
186    @default_handler = handler
187    self
188  end
189
190  # Adds the multi-call handler <code>"system.multicall"</code>.
191  def add_multicall
192    add_handler("system.multicall", %w(array array), "Multicall Extension") do |arrStructs|
193      unless arrStructs.is_a? Array
194        raise XMLRPC::FaultException.new(ERR_MC_WRONG_PARAM, "system.multicall expects an array")
195      end
196
197      arrStructs.collect {|call|
198        if call.is_a? Hash
199          methodName = call["methodName"]
200          params     = call["params"]
201
202          if params.nil?
203            multicall_fault(ERR_MC_MISSING_PARAMS, "Missing params")
204          elsif methodName.nil?
205            multicall_fault(ERR_MC_MISSING_METHNAME, "Missing methodName")
206          else
207            if methodName == "system.multicall"
208              multicall_fault(ERR_MC_RECURSIVE_CALL, "Recursive system.multicall forbidden")
209            else
210              unless params.is_a? Array
211                multicall_fault(ERR_MC_WRONG_PARAM_PARAMS, "Parameter params have to be an Array")
212              else
213                ok, val = call_method(methodName, *params)
214                if ok
215                  # correct return value
216                  [val]
217                else
218                  # exception
219                  multicall_fault(val.faultCode, val.faultString)
220                end
221              end
222            end
223          end
224
225        else
226          multicall_fault(ERR_MC_EXPECTED_STRUCT, "system.multicall expected struct")
227        end
228      }
229    end # end add_handler
230    self
231  end
232
233  # Adds the introspection handlers <code>"system.listMethods"</code>,
234  # <code>"system.methodSignature"</code> and
235  # <code>"system.methodHelp"</code>, where only the first one works.
236  def add_introspection
237    add_handler("system.listMethods",%w(array), "List methods available on this XML-RPC server") do
238      methods = []
239      @handler.each do |name, obj|
240        if obj.kind_of? Proc
241          methods << name
242        else
243          obj.class.public_instance_methods(false).each do |meth|
244            methods << "#{name}#{meth}"
245          end
246        end
247      end
248      methods
249    end
250
251    add_handler("system.methodSignature", %w(array string), "Returns method signature") do |meth|
252      sigs = []
253      @handler.each do |name, obj, sig|
254        if obj.kind_of? Proc and sig != nil and name == meth
255          if sig[0].kind_of? Array
256            # sig contains multiple signatures, e.g. [["array"], ["array", "string"]]
257            sig.each {|s| sigs << s}
258          else
259            # sig is a single signature, e.g. ["array"]
260            sigs << sig
261          end
262        end
263      end
264      sigs.uniq! || sigs  # remove eventually duplicated signatures
265    end
266
267    add_handler("system.methodHelp", %w(string string), "Returns help on using this method") do |meth|
268      help = nil
269      @handler.each do |name, obj, sig, hlp|
270        if obj.kind_of? Proc and name == meth
271          help = hlp
272          break
273        end
274      end
275      help || ""
276    end
277
278    self
279  end
280
281
282
283  def process(data)
284    method, params = parser().parseMethodCall(data)
285    handle(method, *params)
286  end
287
288  private
289
290  def multicall_fault(nr, str)
291    {"faultCode" => nr, "faultString" => str}
292  end
293
294  def dispatch(methodname, *args)
295    for name, obj in @handler
296      if obj.kind_of? Proc
297        next unless methodname == name
298      else
299        next unless methodname =~ /^#{name}(.+)$/
300        next unless obj.respond_to? $1
301        obj = obj.method($1)
302      end
303
304      if check_arity(obj, args.size)
305        if @service_hook.nil?
306          return obj.call(*args)
307        else
308          return @service_hook.call(obj, *args)
309        end
310      end
311    end
312
313    if @default_handler.nil?
314      raise XMLRPC::FaultException.new(ERR_METHOD_MISSING, "Method #{methodname} missing or wrong number of parameters!")
315    else
316      @default_handler.call(methodname, *args)
317    end
318  end
319
320
321  # Returns +true+, if the arity of +obj+ matches +n_args+
322  def check_arity(obj, n_args)
323    ary = obj.arity
324
325    if ary >= 0
326      n_args == ary
327    else
328      n_args >= (ary+1).abs
329    end
330  end
331
332
333
334  def call_method(methodname, *args)
335    begin
336      [true, dispatch(methodname, *args)]
337    rescue XMLRPC::FaultException => e
338      [false, e]
339    rescue Exception => e
340      [false, XMLRPC::FaultException.new(ERR_UNCAUGHT_EXCEPTION, "Uncaught exception #{e.message} in method #{methodname}")]
341    end
342  end
343
344  def handle(methodname, *args)
345    create().methodResponse(*call_method(methodname, *args))
346  end
347
348
349end
350
351
352# Implements a CGI-based XML-RPC server.
353#
354#     require "xmlrpc/server"
355#
356#     s = XMLRPC::CGIServer.new
357#
358#     s.add_handler("michael.add") do |a,b|
359#       a + b
360#     end
361#
362#     s.add_handler("michael.div") do |a,b|
363#       if b == 0
364#         raise XMLRPC::FaultException.new(1, "division by zero")
365#       else
366#         a / b
367#       end
368#     end
369#
370#     s.set_default_handler do |name, *args|
371#       raise XMLRPC::FaultException.new(-99, "Method #{name} missing" +
372#                                        " or wrong number of parameters!")
373#     end
374#
375#     s.serve
376#
377#
378# <b>Note:</b> Make sure that you don't write to standard-output in a
379# handler, or in any other part of your program, this would cause a CGI-based
380# server to fail!
381class CGIServer < BasicServer
382  @@obj = nil
383
384  # Creates a new XMLRPC::CGIServer instance.
385  #
386  # All parameters given are by-passed to XMLRPC::BasicServer.new.
387  #
388  # You can only create <b>one</b> XMLRPC::CGIServer instance, because more
389  # than one makes no sense.
390  def CGIServer.new(*a)
391    @@obj = super(*a) if @@obj.nil?
392    @@obj
393  end
394
395  def initialize(*a)
396    super(*a)
397  end
398
399  # Call this after you have added all you handlers to the server.
400  #
401  # This method processes a XML-RPC method call and sends the answer
402  # back to the client.
403  def serve
404    catch(:exit_serve) {
405      length = ENV['CONTENT_LENGTH'].to_i
406
407      http_error(405, "Method Not Allowed") unless ENV['REQUEST_METHOD'] == "POST"
408      http_error(400, "Bad Request")        unless parse_content_type(ENV['CONTENT_TYPE']).first == "text/xml"
409      http_error(411, "Length Required")    unless length > 0
410
411      # TODO: do we need a call to binmode?
412      $stdin.binmode if $stdin.respond_to? :binmode
413      data = $stdin.read(length)
414
415      http_error(400, "Bad Request")        if data.nil? or data.bytesize != length
416
417      http_write(process(data), "Content-type" => "text/xml; charset=utf-8")
418    }
419  end
420
421
422  private
423
424  def http_error(status, message)
425    err = "#{status} #{message}"
426    msg = <<-"MSGEND"
427      <html>
428        <head>
429          <title>#{err}</title>
430        </head>
431        <body>
432          <h1>#{err}</h1>
433          <p>Unexpected error occurred while processing XML-RPC request!</p>
434        </body>
435      </html>
436    MSGEND
437
438    http_write(msg, "Status" => err, "Content-type" => "text/html")
439    throw :exit_serve # exit from the #serve method
440  end
441
442  def http_write(body, header)
443    h = {}
444    header.each {|key, value| h[key.to_s.capitalize] = value}
445    h['Status']         ||= "200 OK"
446    h['Content-length'] ||= body.bytesize.to_s
447
448    str = ""
449    h.each {|key, value| str << "#{key}: #{value}\r\n"}
450    str << "\r\n#{body}"
451
452    print str
453  end
454
455end
456
457
458# Implements a XML-RPC server, which works with Apache mod_ruby.
459#
460# Use it in the same way as XMLRPC::CGIServer!
461class ModRubyServer < BasicServer
462
463  # Creates a new XMLRPC::ModRubyServer instance.
464  #
465  # All parameters given are by-passed to XMLRPC::BasicServer.new.
466  def initialize(*a)
467    @ap = Apache::request
468    super(*a)
469  end
470
471  # Call this after you have added all you handlers to the server.
472  #
473  # This method processes a XML-RPC method call and sends the answer
474  # back to the client.
475  def serve
476    catch(:exit_serve) {
477      header = {}
478      @ap.headers_in.each {|key, value| header[key.capitalize] = value}
479
480      length = header['Content-length'].to_i
481
482      http_error(405, "Method Not Allowed") unless @ap.request_method  == "POST"
483      http_error(400, "Bad Request")        unless parse_content_type(header['Content-type']).first == "text/xml"
484      http_error(411, "Length Required")    unless length > 0
485
486      # TODO: do we need a call to binmode?
487      @ap.binmode
488      data = @ap.read(length)
489
490      http_error(400, "Bad Request")        if data.nil? or data.bytesize != length
491
492      http_write(process(data), 200, "Content-type" => "text/xml; charset=utf-8")
493    }
494  end
495
496
497  private
498
499  def http_error(status, message)
500    err = "#{status} #{message}"
501    msg = <<-"MSGEND"
502      <html>
503        <head>
504          <title>#{err}</title>
505        </head>
506        <body>
507          <h1>#{err}</h1>
508          <p>Unexpected error occurred while processing XML-RPC request!</p>
509        </body>
510      </html>
511    MSGEND
512
513    http_write(msg, status, "Status" => err, "Content-type" => "text/html")
514    throw :exit_serve # exit from the #serve method
515  end
516
517  def http_write(body, status, header)
518    h = {}
519    header.each {|key, value| h[key.to_s.capitalize] = value}
520    h['Status']         ||= "200 OK"
521    h['Content-length'] ||= body.bytesize.to_s
522
523    h.each {|key, value| @ap.headers_out[key] = value }
524    @ap.content_type = h["Content-type"]
525    @ap.status = status.to_i
526    @ap.send_http_header
527
528    @ap.print body
529  end
530
531end
532
533
534class WEBrickServlet < BasicServer; end # forward declaration
535
536# Implements a standalone XML-RPC server. The method XMLRPC::Server#serve is
537# left if a SIGHUP is sent to the program.
538#
539#    require "xmlrpc/server"
540#
541#   s = XMLRPC::Server.new(8080)
542#
543#   s.add_handler("michael.add") do |a,b|
544#     a + b
545#   end
546#
547#   s.add_handler("michael.div") do |a,b|
548#     if b == 0
549#       raise XMLRPC::FaultException.new(1, "division by zero")
550#     else
551#       a / b
552#     end
553#   end
554#
555#   s.set_default_handler do |name, *args|
556#     raise XMLRPC::FaultException.new(-99, "Method #{name} missing" +
557#                                      " or wrong number of parameters!")
558#   end
559#
560#   s.serve
561class Server < WEBrickServlet
562
563  # Creates a new XMLRPC::Server instance, which is a XML-RPC server
564  # listening on the given +port+ and accepts requests for the given +host+,
565  # which is +localhost+ by default.
566  #
567  # The server is not started, to start it you have to call
568  # XMLRPC::Server#serve.
569  #
570  # The optional +audit+ and +debug+ parameters are obsolete!
571  #
572  # All additionally provided parameters in <code>*a</code> are by-passed to
573  # XMLRPC::BasicServer.new.
574  def initialize(port=8080, host="127.0.0.1", maxConnections=4, stdlog=$stdout, audit=true, debug=true, *a)
575    super(*a)
576    require 'webrick'
577    @server = WEBrick::HTTPServer.new(:Port => port, :BindAddress => host, :MaxClients => maxConnections,
578                                      :Logger => WEBrick::Log.new(stdlog))
579    @server.mount("/", self)
580  end
581
582  # Call this after you have added all you handlers to the server.
583  # This method starts the server to listen for XML-RPC requests and answer them.
584  def serve
585    signals = %w[INT TERM HUP] & Signal.list.keys
586    signals.each { |signal| trap(signal) { @server.shutdown } }
587
588    @server.start
589  end
590
591  # Stops and shuts the server down.
592  def shutdown
593    @server.shutdown
594  end
595
596end
597
598
599# Implements a servlet for use with WEBrick, a pure Ruby (HTTP) server
600# framework.
601#
602#     require "webrick"
603#     require "xmlrpc/server"
604#
605#     s = XMLRPC::WEBrickServlet.new
606#     s.add_handler("michael.add") do |a,b|
607#       a + b
608#     end
609#
610#     s.add_handler("michael.div") do |a,b|
611#       if b == 0
612#         raise XMLRPC::FaultException.new(1, "division by zero")
613#       else
614#         a / b
615#       end
616#     end
617#
618#     s.set_default_handler do |name, *args|
619#       raise XMLRPC::FaultException.new(-99, "Method #{name} missing" +
620#                                        " or wrong number of parameters!")
621#     end
622#
623#     httpserver = WEBrick::HTTPServer.new(:Port => 8080)
624#     httpserver.mount("/RPC2", s)
625#     trap("HUP") { httpserver.shutdown }   # use 1 instead of "HUP" on Windows
626#     httpserver.start
627class WEBrickServlet < BasicServer
628  def initialize(*a)
629    super
630    require "webrick/httpstatus"
631    @valid_ip = nil
632  end
633
634  # Deprecated from WEBrick/1.2.2, but does not break anything.
635  def require_path_info?
636    false
637  end
638
639  def get_instance(config, *options)
640    # TODO: set config & options
641    self
642  end
643
644  # Specifies the valid IP addresses that are allowed to connect to the server.
645  #
646  # Each IP is either a String or a Regexp.
647  def set_valid_ip(*ip_addr)
648    if ip_addr.size == 1 and ip_addr[0].nil?
649      @valid_ip = nil
650    else
651      @valid_ip = ip_addr
652    end
653  end
654
655  # Return the valid IP addresses that are allowed to connect to the server.
656  #
657  # See also, XMLRPC::Server#set_valid_ip
658  def get_valid_ip
659    @valid_ip
660  end
661
662  def service(request, response)
663
664    if @valid_ip
665      raise WEBrick::HTTPStatus::Forbidden unless @valid_ip.any? { |ip| request.peeraddr[3] =~ ip }
666    end
667
668    if request.request_method != "POST"
669      raise WEBrick::HTTPStatus::MethodNotAllowed,
670            "unsupported method `#{request.request_method}'."
671    end
672
673    if parse_content_type(request['Content-type']).first != "text/xml"
674      raise WEBrick::HTTPStatus::BadRequest
675    end
676
677    length = (request['Content-length'] || 0).to_i
678
679    raise WEBrick::HTTPStatus::LengthRequired unless length > 0
680
681    data = request.body
682
683    if data.nil? or data.bytesize != length
684      raise WEBrick::HTTPStatus::BadRequest
685    end
686
687    resp = process(data)
688    if resp.nil? or resp.bytesize <= 0
689      raise WEBrick::HTTPStatus::InternalServerError
690    end
691
692    response.status = 200
693    response['Content-Length'] = resp.bytesize
694    response['Content-Type']   = "text/xml; charset=utf-8"
695    response.body = resp
696  end
697end
698
699
700end # module XMLRPC
701
702
703=begin
704= History
705    $Id: server.rb 44391 2013-12-24 15:46:01Z nagachika $
706=end
707
708