xmlrpc 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: false
2
+ #
3
+ # Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
4
+ #
5
+ # $Id$
6
+ #
7
+
8
+ require "xmlrpc/parser"
9
+ require "xmlrpc/create"
10
+ require "xmlrpc/config"
11
+ require "xmlrpc/utils"
12
+
13
+ module XMLRPC # :nodoc:
14
+
15
+ # Marshalling of XMLRPC::Create#methodCall and XMLRPC::Create#methodResponse
16
+ class Marshal
17
+ include ParserWriterChooseMixin
18
+
19
+ class << self
20
+
21
+ def dump_call( methodName, *params )
22
+ new.dump_call( methodName, *params )
23
+ end
24
+
25
+ def dump_response( param )
26
+ new.dump_response( param )
27
+ end
28
+
29
+ def load_call( stringOrReadable )
30
+ new.load_call( stringOrReadable )
31
+ end
32
+
33
+ def load_response( stringOrReadable )
34
+ new.load_response( stringOrReadable )
35
+ end
36
+
37
+ alias dump dump_response
38
+ alias load load_response
39
+
40
+ end # class self
41
+
42
+ def initialize( parser = nil, writer = nil )
43
+ set_parser( parser )
44
+ set_writer( writer )
45
+ end
46
+
47
+ def dump_call( methodName, *params )
48
+ create.methodCall( methodName, *params )
49
+ end
50
+
51
+ def dump_response( param )
52
+ create.methodResponse( ! param.kind_of?( XMLRPC::FaultException ) , param )
53
+ end
54
+
55
+ # Returns <code>[ methodname, params ]</code>
56
+ def load_call( stringOrReadable )
57
+ parser.parseMethodCall( stringOrReadable )
58
+ end
59
+
60
+ # Returns +paramOrFault+
61
+ def load_response( stringOrReadable )
62
+ parser.parseMethodResponse( stringOrReadable )[1]
63
+ end
64
+
65
+ end # class Marshal
66
+
67
+ end
@@ -0,0 +1,642 @@
1
+ # frozen_string_literal: false
2
+ # Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
3
+ #
4
+ # $Id$
5
+ #
6
+
7
+
8
+ require "date"
9
+ require "xmlrpc/base64"
10
+ require "xmlrpc/datetime"
11
+
12
+
13
+ module XMLRPC # :nodoc:
14
+
15
+ # Raised when the remote procedure returns a fault-structure, which has two
16
+ # accessor-methods +faultCode+ an Integer, and +faultString+ a String.
17
+ class FaultException < StandardError
18
+ attr_reader :faultCode, :faultString
19
+
20
+ # Creates a new XMLRPC::FaultException instance.
21
+ #
22
+ # +faultString+ is passed to StandardError as the +msg+ of the Exception.
23
+ def initialize(faultCode, faultString)
24
+ @faultCode = faultCode
25
+ @faultString = faultString
26
+ super(@faultString)
27
+ end
28
+
29
+ # The +faultCode+ and +faultString+ of the exception in a Hash.
30
+ def to_h
31
+ {"faultCode" => @faultCode, "faultString" => @faultString}
32
+ end
33
+ end
34
+
35
+ # Helper class used to convert types.
36
+ module Convert
37
+
38
+ # Converts a String to an Integer
39
+ #
40
+ # See also String.to_i
41
+ def self.int(str)
42
+ str.to_i
43
+ end
44
+
45
+ # Converts a String to +true+ or +false+
46
+ #
47
+ # Raises an exception if +str+ is not +0+ or +1+
48
+ def self.boolean(str)
49
+ case str
50
+ when "0" then false
51
+ when "1" then true
52
+ else
53
+ raise "RPC-value of type boolean is wrong"
54
+ end
55
+ end
56
+
57
+ # Converts a String to a Float
58
+ #
59
+ # See also String.to_f
60
+ def self.double(str)
61
+ str.to_f
62
+ end
63
+
64
+ # Converts a the given +str+ to a +dateTime.iso8601+ formatted date.
65
+ #
66
+ # Raises an exception if the String isn't in +dateTime.iso8601+ format.
67
+ #
68
+ # See also, XMLRPC::DateTime
69
+ def self.dateTime(str)
70
+ case str
71
+ when /^(-?\d\d\d\d)-?(\d\d)-?(\d\d)T(\d\d):(\d\d):(\d\d)(?:Z|([+-])(\d\d):?(\d\d))?$/
72
+ a = [$1, $2, $3, $4, $5, $6].collect{|i| i.to_i}
73
+ if $7
74
+ ofs = $8.to_i*3600 + $9.to_i*60
75
+ ofs = -ofs if $7=='+'
76
+ utc = Time.utc(*a) + ofs
77
+ a = [ utc.year, utc.month, utc.day, utc.hour, utc.min, utc.sec ]
78
+ end
79
+ XMLRPC::DateTime.new(*a)
80
+ when /^(-?\d\d)-?(\d\d)-?(\d\d)T(\d\d):(\d\d):(\d\d)(Z|([+-]\d\d):(\d\d))?$/
81
+ a = [$1, $2, $3, $4, $5, $6].collect{|i| i.to_i}
82
+ if a[0] < 70
83
+ a[0] += 2000
84
+ else
85
+ a[0] += 1900
86
+ end
87
+ if $7
88
+ ofs = $8.to_i*3600 + $9.to_i*60
89
+ ofs = -ofs if $7=='+'
90
+ utc = Time.utc(*a) + ofs
91
+ a = [ utc.year, utc.month, utc.day, utc.hour, utc.min, utc.sec ]
92
+ end
93
+ XMLRPC::DateTime.new(*a)
94
+ else
95
+ raise "wrong dateTime.iso8601 format " + str
96
+ end
97
+ end
98
+
99
+ # Decodes the given +str+ using XMLRPC::Base64.decode
100
+ def self.base64(str)
101
+ XMLRPC::Base64.decode(str)
102
+ end
103
+
104
+ # Converts the given +hash+ to a marshalled object.
105
+ #
106
+ # Returns the given +hash+ if an exception occurs.
107
+ def self.struct(hash)
108
+ # convert to marshalled object
109
+ klass = hash["___class___"]
110
+ if klass.nil? or Config::ENABLE_MARSHALLING == false
111
+ hash
112
+ else
113
+ begin
114
+ mod = Module
115
+ klass.split("::").each {|const| mod = mod.const_get(const.strip)}
116
+
117
+ obj = mod.allocate
118
+
119
+ hash.delete "___class___"
120
+ hash.each {|key, value|
121
+ obj.instance_variable_set("@#{ key }", value) if key =~ /^([a-zA-Z_]\w*)$/
122
+ }
123
+ obj
124
+ rescue
125
+ hash
126
+ end
127
+ end
128
+ end
129
+
130
+ # Converts the given +hash+ to an XMLRPC::FaultException object by passing
131
+ # the +faultCode+ and +faultString+ attributes of the Hash to
132
+ # XMLRPC::FaultException.new
133
+ #
134
+ # Raises an Exception if the given +hash+ doesn't meet the requirements.
135
+ # Those requirements being:
136
+ # * 2 keys
137
+ # * <code>'faultCode'</code> key is an Integer
138
+ # * <code>'faultString'</code> key is a String
139
+ def self.fault(hash)
140
+ if hash.kind_of? Hash and hash.size == 2 and
141
+ hash.has_key? "faultCode" and hash.has_key? "faultString" and
142
+ hash["faultCode"].kind_of? Integer and hash["faultString"].kind_of? String
143
+
144
+ XMLRPC::FaultException.new(hash["faultCode"], hash["faultString"])
145
+ else
146
+ raise "wrong fault-structure: #{hash.inspect}"
147
+ end
148
+ end
149
+
150
+ end # module Convert
151
+
152
+ # Parser for XML-RPC call and response
153
+ module XMLParser
154
+
155
+ class AbstractTreeParser
156
+
157
+ def parseMethodResponse(str)
158
+ methodResponse_document(createCleanedTree(str))
159
+ end
160
+
161
+ def parseMethodCall(str)
162
+ methodCall_document(createCleanedTree(str))
163
+ end
164
+
165
+ private
166
+
167
+ # Removes all whitespaces but in the tags i4, i8, int, boolean....
168
+ # and all comments
169
+ def removeWhitespacesAndComments(node)
170
+ remove = []
171
+ childs = node.childNodes.to_a
172
+ childs.each do |nd|
173
+ case _nodeType(nd)
174
+ when :TEXT
175
+ # TODO: add nil?
176
+ unless %w(i4 i8 int boolean string double dateTime.iso8601 base64).include? node.nodeName
177
+
178
+ if node.nodeName == "value"
179
+ if not node.childNodes.to_a.detect {|n| _nodeType(n) == :ELEMENT}.nil?
180
+ remove << nd if nd.nodeValue.strip == ""
181
+ end
182
+ else
183
+ remove << nd if nd.nodeValue.strip == ""
184
+ end
185
+ end
186
+ when :COMMENT
187
+ remove << nd
188
+ else
189
+ removeWhitespacesAndComments(nd)
190
+ end
191
+ end
192
+
193
+ remove.each { |i| node.removeChild(i) }
194
+ end
195
+
196
+
197
+ def nodeMustBe(node, name)
198
+ cmp = case name
199
+ when Array
200
+ name.include?(node.nodeName)
201
+ when String
202
+ name == node.nodeName
203
+ else
204
+ raise "error"
205
+ end
206
+
207
+ if not cmp then
208
+ raise "wrong xml-rpc (name)"
209
+ end
210
+
211
+ node
212
+ end
213
+
214
+ # Returns, when successfully the only child-node
215
+ def hasOnlyOneChild(node, name=nil)
216
+ if node.childNodes.to_a.size != 1
217
+ raise "wrong xml-rpc (size)"
218
+ end
219
+ if name != nil then
220
+ nodeMustBe(node.firstChild, name)
221
+ end
222
+ end
223
+
224
+
225
+ def assert(b)
226
+ if not b then
227
+ raise "assert-fail"
228
+ end
229
+ end
230
+
231
+ # The node `node` has empty string or string
232
+ def text_zero_one(node)
233
+ nodes = node.childNodes.to_a.size
234
+
235
+ if nodes == 1
236
+ text(node.firstChild)
237
+ elsif nodes == 0
238
+ ""
239
+ else
240
+ raise "wrong xml-rpc (size)"
241
+ end
242
+ end
243
+
244
+
245
+ def integer(node)
246
+ #TODO: check string for float because to_i returnsa
247
+ # 0 when wrong string
248
+ nodeMustBe(node, %w(i4 i8 int))
249
+ hasOnlyOneChild(node)
250
+
251
+ Convert.int(text(node.firstChild))
252
+ end
253
+
254
+ def boolean(node)
255
+ nodeMustBe(node, "boolean")
256
+ hasOnlyOneChild(node)
257
+
258
+ Convert.boolean(text(node.firstChild))
259
+ end
260
+
261
+ def v_nil(node)
262
+ nodeMustBe(node, "nil")
263
+ assert( node.childNodes.to_a.size == 0 )
264
+ nil
265
+ end
266
+
267
+ def string(node)
268
+ nodeMustBe(node, "string")
269
+ text_zero_one(node)
270
+ end
271
+
272
+ def double(node)
273
+ #TODO: check string for float because to_f returnsa
274
+ # 0.0 when wrong string
275
+ nodeMustBe(node, "double")
276
+ hasOnlyOneChild(node)
277
+
278
+ Convert.double(text(node.firstChild))
279
+ end
280
+
281
+ def dateTime(node)
282
+ nodeMustBe(node, "dateTime.iso8601")
283
+ hasOnlyOneChild(node)
284
+
285
+ Convert.dateTime( text(node.firstChild) )
286
+ end
287
+
288
+ def base64(node)
289
+ nodeMustBe(node, "base64")
290
+ #hasOnlyOneChild(node)
291
+
292
+ Convert.base64(text_zero_one(node))
293
+ end
294
+
295
+ def member(node)
296
+ nodeMustBe(node, "member")
297
+ assert( node.childNodes.to_a.size == 2 )
298
+
299
+ [ name(node[0]), value(node[1]) ]
300
+ end
301
+
302
+ def name(node)
303
+ nodeMustBe(node, "name")
304
+ #hasOnlyOneChild(node)
305
+ text_zero_one(node)
306
+ end
307
+
308
+ def array(node)
309
+ nodeMustBe(node, "array")
310
+ hasOnlyOneChild(node, "data")
311
+ data(node.firstChild)
312
+ end
313
+
314
+ def data(node)
315
+ nodeMustBe(node, "data")
316
+
317
+ node.childNodes.to_a.collect do |val|
318
+ value(val)
319
+ end
320
+ end
321
+
322
+ def param(node)
323
+ nodeMustBe(node, "param")
324
+ hasOnlyOneChild(node, "value")
325
+ value(node.firstChild)
326
+ end
327
+
328
+ def methodResponse(node)
329
+ nodeMustBe(node, "methodResponse")
330
+ hasOnlyOneChild(node, %w(params fault))
331
+ child = node.firstChild
332
+
333
+ case child.nodeName
334
+ when "params"
335
+ [ true, params(child,false) ]
336
+ when "fault"
337
+ [ false, fault(child) ]
338
+ else
339
+ raise "unexpected error"
340
+ end
341
+
342
+ end
343
+
344
+ def methodName(node)
345
+ nodeMustBe(node, "methodName")
346
+ hasOnlyOneChild(node)
347
+ text(node.firstChild)
348
+ end
349
+
350
+ def params(node, call=true)
351
+ nodeMustBe(node, "params")
352
+
353
+ if call
354
+ node.childNodes.to_a.collect do |n|
355
+ param(n)
356
+ end
357
+ else # response (only one param)
358
+ hasOnlyOneChild(node)
359
+ param(node.firstChild)
360
+ end
361
+ end
362
+
363
+ def fault(node)
364
+ nodeMustBe(node, "fault")
365
+ hasOnlyOneChild(node, "value")
366
+ f = value(node.firstChild)
367
+ Convert.fault(f)
368
+ end
369
+
370
+
371
+
372
+ # _nodeType is defined in the subclass
373
+ def text(node)
374
+ assert( _nodeType(node) == :TEXT )
375
+ assert( node.hasChildNodes == false )
376
+ assert( node.nodeValue != nil )
377
+
378
+ node.nodeValue.to_s
379
+ end
380
+
381
+ def struct(node)
382
+ nodeMustBe(node, "struct")
383
+
384
+ hash = {}
385
+ node.childNodes.to_a.each do |me|
386
+ n, v = member(me)
387
+ hash[n] = v
388
+ end
389
+
390
+ Convert.struct(hash)
391
+ end
392
+
393
+
394
+ def value(node)
395
+ nodeMustBe(node, "value")
396
+ nodes = node.childNodes.to_a.size
397
+ if nodes == 0
398
+ return ""
399
+ elsif nodes > 1
400
+ raise "wrong xml-rpc (size)"
401
+ end
402
+
403
+ child = node.firstChild
404
+
405
+ case _nodeType(child)
406
+ when :TEXT
407
+ text_zero_one(node)
408
+ when :ELEMENT
409
+ case child.nodeName
410
+ when "i4", "i8", "int" then integer(child)
411
+ when "boolean" then boolean(child)
412
+ when "string" then string(child)
413
+ when "double" then double(child)
414
+ when "dateTime.iso8601" then dateTime(child)
415
+ when "base64" then base64(child)
416
+ when "struct" then struct(child)
417
+ when "array" then array(child)
418
+ when "nil"
419
+ if Config::ENABLE_NIL_PARSER
420
+ v_nil(child)
421
+ else
422
+ raise "wrong/unknown XML-RPC type 'nil'"
423
+ end
424
+ else
425
+ raise "wrong/unknown XML-RPC type"
426
+ end
427
+ else
428
+ raise "wrong type of node"
429
+ end
430
+
431
+ end
432
+
433
+ def methodCall(node)
434
+ nodeMustBe(node, "methodCall")
435
+ assert( (1..2).include?( node.childNodes.to_a.size ) )
436
+ name = methodName(node[0])
437
+
438
+ if node.childNodes.to_a.size == 2 then
439
+ pa = params(node[1])
440
+ else # no parameters given
441
+ pa = []
442
+ end
443
+ [name, pa]
444
+ end
445
+
446
+ end # module TreeParserMixin
447
+
448
+ class AbstractStreamParser
449
+ def parseMethodResponse(str)
450
+ parser = @parser_class.new
451
+ parser.parse(str)
452
+ raise "No valid method response!" if parser.method_name != nil
453
+ if parser.fault != nil
454
+ # is a fault structure
455
+ [false, parser.fault]
456
+ else
457
+ # is a normal return value
458
+ raise "Missing return value!" if parser.params.size == 0
459
+ raise "Too many return values. Only one allowed!" if parser.params.size > 1
460
+ [true, parser.params[0]]
461
+ end
462
+ end
463
+
464
+ def parseMethodCall(str)
465
+ parser = @parser_class.new
466
+ parser.parse(str)
467
+ raise "No valid method call - missing method name!" if parser.method_name.nil?
468
+ [parser.method_name, parser.params]
469
+ end
470
+ end
471
+
472
+ module StreamParserMixin
473
+ attr_reader :params
474
+ attr_reader :method_name
475
+ attr_reader :fault
476
+
477
+ def initialize(*a)
478
+ super(*a)
479
+ @params = []
480
+ @values = []
481
+ @val_stack = []
482
+
483
+ @names = []
484
+ @name = []
485
+
486
+ @structs = []
487
+ @struct = {}
488
+
489
+ @method_name = nil
490
+ @fault = nil
491
+
492
+ @data = nil
493
+ end
494
+
495
+ def startElement(name, attrs=[])
496
+ @data = nil
497
+ case name
498
+ when "value"
499
+ @value = nil
500
+ when "nil"
501
+ raise "wrong/unknown XML-RPC type 'nil'" unless Config::ENABLE_NIL_PARSER
502
+ @value = :nil
503
+ when "array"
504
+ @val_stack << @values
505
+ @values = []
506
+ when "struct"
507
+ @names << @name
508
+ @name = []
509
+
510
+ @structs << @struct
511
+ @struct = {}
512
+ end
513
+ end
514
+
515
+ def endElement(name)
516
+ @data ||= ""
517
+ case name
518
+ when "string"
519
+ @value = @data
520
+ when "i4", "i8", "int"
521
+ @value = Convert.int(@data)
522
+ when "boolean"
523
+ @value = Convert.boolean(@data)
524
+ when "double"
525
+ @value = Convert.double(@data)
526
+ when "dateTime.iso8601"
527
+ @value = Convert.dateTime(@data)
528
+ when "base64"
529
+ @value = Convert.base64(@data)
530
+ when "value"
531
+ @value = @data if @value.nil?
532
+ @values << (@value == :nil ? nil : @value)
533
+ when "array"
534
+ @value = @values
535
+ @values = @val_stack.pop
536
+ when "struct"
537
+ @value = Convert.struct(@struct)
538
+
539
+ @name = @names.pop
540
+ @struct = @structs.pop
541
+ when "name"
542
+ @name[0] = @data
543
+ when "member"
544
+ @struct[@name[0]] = @values.pop
545
+
546
+ when "param"
547
+ @params << @values[0]
548
+ @values = []
549
+
550
+ when "fault"
551
+ @fault = Convert.fault(@values[0])
552
+
553
+ when "methodName"
554
+ @method_name = @data
555
+ end
556
+
557
+ @data = nil
558
+ end
559
+
560
+ def character(data)
561
+ if @data
562
+ @data << data
563
+ else
564
+ @data = data
565
+ end
566
+ end
567
+
568
+ end # module StreamParserMixin
569
+
570
+ class REXMLStreamParser < AbstractStreamParser
571
+ def initialize
572
+ require "rexml/document"
573
+ @parser_class = StreamListener
574
+ end
575
+
576
+ class StreamListener
577
+ include StreamParserMixin
578
+
579
+ alias :tag_start :startElement
580
+ alias :tag_end :endElement
581
+ alias :text :character
582
+ alias :cdata :character
583
+
584
+ def method_missing(*a)
585
+ # ignore
586
+ end
587
+
588
+ def parse(str)
589
+ REXML::Document.parse_stream(str, self)
590
+ end
591
+ end
592
+
593
+ end
594
+
595
+ class LibXMLStreamParser < AbstractStreamParser
596
+ def initialize
597
+ require 'libxml'
598
+ @parser_class = LibXMLStreamListener
599
+ end
600
+
601
+ class LibXMLStreamListener
602
+ include StreamParserMixin
603
+
604
+ def on_start_element_ns(name, attributes, prefix, uri, namespaces)
605
+ startElement(name)
606
+ end
607
+
608
+ def on_end_element_ns(name, prefix, uri)
609
+ endElement(name)
610
+ end
611
+
612
+ alias :on_characters :character
613
+ alias :on_cdata_block :character
614
+
615
+ def method_missing(*a)
616
+ end
617
+
618
+ def parse(str)
619
+ parser = LibXML::XML::SaxParser.string(str)
620
+ parser.callbacks = self
621
+ parser.parse()
622
+ end
623
+ end
624
+ end
625
+
626
+ Classes = [REXMLStreamParser, LibXMLStreamParser]
627
+
628
+ # yields an instance of each installed parser
629
+ def self.each_installed_parser
630
+ XMLRPC::XMLParser::Classes.each do |klass|
631
+ begin
632
+ yield klass.new
633
+ rescue LoadError
634
+ end
635
+ end
636
+ end
637
+
638
+ end # module XMLParser
639
+
640
+
641
+ end # module XMLRPC
642
+