rubysl-xmlrpc 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/LICENSE +25 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/rubysl/xmlrpc.rb +1 -0
- data/lib/rubysl/xmlrpc/version.rb +5 -0
- data/lib/xmlrpc/README.txt +31 -0
- data/lib/xmlrpc/base64.rb +81 -0
- data/lib/xmlrpc/client.rb +625 -0
- data/lib/xmlrpc/config.rb +40 -0
- data/lib/xmlrpc/create.rb +290 -0
- data/lib/xmlrpc/datetime.rb +142 -0
- data/lib/xmlrpc/httpserver.rb +178 -0
- data/lib/xmlrpc/marshal.rb +76 -0
- data/lib/xmlrpc/parser.rb +813 -0
- data/lib/xmlrpc/server.rb +782 -0
- data/lib/xmlrpc/utils.rb +165 -0
- data/rubysl-xmlrpc.gemspec +23 -0
- metadata +120 -0
@@ -0,0 +1,178 @@
|
|
1
|
+
#
|
2
|
+
# Implements a simple HTTP-server by using John W. Small's (jsmall@laser.net)
|
3
|
+
# ruby-generic-server.
|
4
|
+
#
|
5
|
+
# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
|
6
|
+
#
|
7
|
+
# $Id: httpserver.rb 11708 2007-02-12 23:01:19Z shyouhei $
|
8
|
+
#
|
9
|
+
|
10
|
+
|
11
|
+
require "gserver"
|
12
|
+
|
13
|
+
class HttpServer < GServer
|
14
|
+
|
15
|
+
##
|
16
|
+
# handle_obj specifies the object, that receives calls to request_handler
|
17
|
+
# and ip_auth_handler
|
18
|
+
def initialize(handle_obj, port = 8080, host = DEFAULT_HOST, maxConnections = 4,
|
19
|
+
stdlog = $stdout, audit = true, debug = true)
|
20
|
+
@handler = handle_obj
|
21
|
+
super(port, host, maxConnections, stdlog, audit, debug)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# Constants -----------------------------------------------
|
27
|
+
|
28
|
+
CRLF = "\r\n"
|
29
|
+
HTTP_PROTO = "HTTP/1.0"
|
30
|
+
SERVER_NAME = "HttpServer (Ruby #{RUBY_VERSION})"
|
31
|
+
|
32
|
+
DEFAULT_HEADER = {
|
33
|
+
"Server" => SERVER_NAME
|
34
|
+
}
|
35
|
+
|
36
|
+
##
|
37
|
+
# Mapping of status code and error message
|
38
|
+
#
|
39
|
+
StatusCodeMapping = {
|
40
|
+
200 => "OK",
|
41
|
+
400 => "Bad Request",
|
42
|
+
403 => "Forbidden",
|
43
|
+
405 => "Method Not Allowed",
|
44
|
+
411 => "Length Required",
|
45
|
+
500 => "Internal Server Error"
|
46
|
+
}
|
47
|
+
|
48
|
+
# Classes -------------------------------------------------
|
49
|
+
|
50
|
+
class Request
|
51
|
+
attr_reader :data, :header, :method, :path, :proto
|
52
|
+
|
53
|
+
def initialize(data, method=nil, path=nil, proto=nil)
|
54
|
+
@header, @data = Table.new, data
|
55
|
+
@method, @path, @proto = method, path, proto
|
56
|
+
end
|
57
|
+
|
58
|
+
def content_length
|
59
|
+
len = @header['Content-Length']
|
60
|
+
return nil if len.nil?
|
61
|
+
return len.to_i
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
class Response
|
67
|
+
attr_reader :header
|
68
|
+
attr_accessor :body, :status, :status_message
|
69
|
+
|
70
|
+
def initialize(status=200)
|
71
|
+
@status = status
|
72
|
+
@status_message = nil
|
73
|
+
@header = Table.new
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
##
|
79
|
+
# a case-insensitive Hash class for HTTP header
|
80
|
+
#
|
81
|
+
class Table
|
82
|
+
include Enumerable
|
83
|
+
|
84
|
+
def initialize(hash={})
|
85
|
+
@hash = hash
|
86
|
+
update(hash)
|
87
|
+
end
|
88
|
+
|
89
|
+
def [](key)
|
90
|
+
@hash[key.to_s.capitalize]
|
91
|
+
end
|
92
|
+
|
93
|
+
def []=(key, value)
|
94
|
+
@hash[key.to_s.capitalize] = value
|
95
|
+
end
|
96
|
+
|
97
|
+
def update(hash)
|
98
|
+
hash.each {|k,v| self[k] = v}
|
99
|
+
self
|
100
|
+
end
|
101
|
+
|
102
|
+
def each
|
103
|
+
@hash.each {|k,v| yield k.capitalize, v }
|
104
|
+
end
|
105
|
+
|
106
|
+
def writeTo(port)
|
107
|
+
each { |k,v| port << "#{k}: #{v}" << CRLF }
|
108
|
+
end
|
109
|
+
end # class Table
|
110
|
+
|
111
|
+
|
112
|
+
# Helper Methods ------------------------------------------
|
113
|
+
|
114
|
+
def http_header(header=nil)
|
115
|
+
new_header = Table.new(DEFAULT_HEADER)
|
116
|
+
new_header.update(header) unless header.nil?
|
117
|
+
|
118
|
+
new_header["Connection"] = "close"
|
119
|
+
new_header["Date"] = http_date(Time.now)
|
120
|
+
|
121
|
+
new_header
|
122
|
+
end
|
123
|
+
|
124
|
+
def http_date( aTime )
|
125
|
+
aTime.gmtime.strftime( "%a, %d %b %Y %H:%M:%S GMT" )
|
126
|
+
end
|
127
|
+
|
128
|
+
def http_resp(status_code, status_message=nil, header=nil, body=nil)
|
129
|
+
status_message ||= StatusCodeMapping[status_code]
|
130
|
+
|
131
|
+
str = ""
|
132
|
+
str << "#{HTTP_PROTO} #{status_code} #{status_message}" << CRLF
|
133
|
+
http_header(header).writeTo(str)
|
134
|
+
str << CRLF
|
135
|
+
str << body unless body.nil?
|
136
|
+
str
|
137
|
+
end
|
138
|
+
|
139
|
+
# Main Serve Loop -----------------------------------------
|
140
|
+
|
141
|
+
def serve(io)
|
142
|
+
# perform IP authentification
|
143
|
+
unless @handler.ip_auth_handler(io)
|
144
|
+
io << http_resp(403, "Forbidden")
|
145
|
+
return
|
146
|
+
end
|
147
|
+
|
148
|
+
# parse first line
|
149
|
+
if io.gets =~ /^(\S+)\s+(\S+)\s+(\S+)/
|
150
|
+
request = Request.new(io, $1, $2, $3)
|
151
|
+
else
|
152
|
+
io << http_resp(400, "Bad Request")
|
153
|
+
return
|
154
|
+
end
|
155
|
+
|
156
|
+
# parse HTTP headers
|
157
|
+
while (line=io.gets) !~ /^(\n|\r)/
|
158
|
+
if line =~ /^([\w-]+):\s*(.*)$/
|
159
|
+
request.header[$1] = $2.strip
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
io.binmode
|
164
|
+
response = Response.new
|
165
|
+
|
166
|
+
# execute request handler
|
167
|
+
@handler.request_handler(request, response)
|
168
|
+
|
169
|
+
# write response back to the client
|
170
|
+
io << http_resp(response.status, response.status_message,
|
171
|
+
response.header, response.body)
|
172
|
+
|
173
|
+
rescue Exception => e
|
174
|
+
io << http_resp(500, "Internal Server Error")
|
175
|
+
end
|
176
|
+
|
177
|
+
end # class HttpServer
|
178
|
+
|
@@ -0,0 +1,76 @@
|
|
1
|
+
#
|
2
|
+
# Marshalling of XML-RPC methodCall and methodResponse
|
3
|
+
#
|
4
|
+
# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
|
5
|
+
#
|
6
|
+
# $Id: marshal.rb 11708 2007-02-12 23:01:19Z shyouhei $
|
7
|
+
#
|
8
|
+
|
9
|
+
require "xmlrpc/parser"
|
10
|
+
require "xmlrpc/create"
|
11
|
+
require "xmlrpc/config"
|
12
|
+
require "xmlrpc/utils"
|
13
|
+
|
14
|
+
module XMLRPC
|
15
|
+
|
16
|
+
class Marshal
|
17
|
+
include ParserWriterChooseMixin
|
18
|
+
|
19
|
+
# class methods -------------------------------
|
20
|
+
|
21
|
+
class << self
|
22
|
+
|
23
|
+
def dump_call( methodName, *params )
|
24
|
+
new.dump_call( methodName, *params )
|
25
|
+
end
|
26
|
+
|
27
|
+
def dump_response( param )
|
28
|
+
new.dump_response( param )
|
29
|
+
end
|
30
|
+
|
31
|
+
def load_call( stringOrReadable )
|
32
|
+
new.load_call( stringOrReadable )
|
33
|
+
end
|
34
|
+
|
35
|
+
def load_response( stringOrReadable )
|
36
|
+
new.load_response( stringOrReadable )
|
37
|
+
end
|
38
|
+
|
39
|
+
alias dump dump_response
|
40
|
+
alias load load_response
|
41
|
+
|
42
|
+
end # class self
|
43
|
+
|
44
|
+
# instance methods ----------------------------
|
45
|
+
|
46
|
+
def initialize( parser = nil, writer = nil )
|
47
|
+
set_parser( parser )
|
48
|
+
set_writer( writer )
|
49
|
+
end
|
50
|
+
|
51
|
+
def dump_call( methodName, *params )
|
52
|
+
create.methodCall( methodName, *params )
|
53
|
+
end
|
54
|
+
|
55
|
+
def dump_response( param )
|
56
|
+
create.methodResponse( ! param.kind_of?( XMLRPC::FaultException ) , param )
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# returns [ methodname, params ]
|
61
|
+
#
|
62
|
+
def load_call( stringOrReadable )
|
63
|
+
parser.parseMethodCall( stringOrReadable )
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# returns paramOrFault
|
68
|
+
#
|
69
|
+
def load_response( stringOrReadable )
|
70
|
+
parser.parseMethodResponse( stringOrReadable )[1]
|
71
|
+
end
|
72
|
+
|
73
|
+
end # class Marshal
|
74
|
+
|
75
|
+
end
|
76
|
+
|
@@ -0,0 +1,813 @@
|
|
1
|
+
#
|
2
|
+
# Parser for XML-RPC call and response
|
3
|
+
#
|
4
|
+
# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
|
5
|
+
#
|
6
|
+
# $Id: parser.rb 13771 2007-10-24 23:04:42Z jeg2 $
|
7
|
+
#
|
8
|
+
|
9
|
+
|
10
|
+
require "date"
|
11
|
+
require "xmlrpc/base64"
|
12
|
+
require "xmlrpc/datetime"
|
13
|
+
|
14
|
+
|
15
|
+
# add some methods to NQXML::Node
|
16
|
+
module NQXML
|
17
|
+
class Node
|
18
|
+
|
19
|
+
def removeChild(node)
|
20
|
+
@children.delete(node)
|
21
|
+
end
|
22
|
+
def childNodes
|
23
|
+
@children
|
24
|
+
end
|
25
|
+
def hasChildNodes
|
26
|
+
not @children.empty?
|
27
|
+
end
|
28
|
+
def [] (index)
|
29
|
+
@children[index]
|
30
|
+
end
|
31
|
+
|
32
|
+
def nodeType
|
33
|
+
if @entity.instance_of? NQXML::Text then :TEXT
|
34
|
+
elsif @entity.instance_of? NQXML::Comment then :COMMENT
|
35
|
+
#elsif @entity.instance_of? NQXML::Element then :ELEMENT
|
36
|
+
elsif @entity.instance_of? NQXML::Tag then :ELEMENT
|
37
|
+
else :ELSE
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def nodeValue
|
42
|
+
#TODO: error when wrong Entity-type
|
43
|
+
@entity.text
|
44
|
+
end
|
45
|
+
def nodeName
|
46
|
+
#TODO: error when wrong Entity-type
|
47
|
+
@entity.name
|
48
|
+
end
|
49
|
+
end # class Node
|
50
|
+
end # module NQXML
|
51
|
+
|
52
|
+
module XMLRPC
|
53
|
+
|
54
|
+
class FaultException < StandardError
|
55
|
+
attr_reader :faultCode, :faultString
|
56
|
+
|
57
|
+
alias message faultString
|
58
|
+
|
59
|
+
def initialize(faultCode, faultString)
|
60
|
+
@faultCode = faultCode
|
61
|
+
@faultString = faultString
|
62
|
+
end
|
63
|
+
|
64
|
+
# returns a hash
|
65
|
+
def to_h
|
66
|
+
{"faultCode" => @faultCode, "faultString" => @faultString}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
module Convert
|
71
|
+
def self.int(str)
|
72
|
+
str.to_i
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.boolean(str)
|
76
|
+
case str
|
77
|
+
when "0" then false
|
78
|
+
when "1" then true
|
79
|
+
else
|
80
|
+
raise "RPC-value of type boolean is wrong"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.double(str)
|
85
|
+
str.to_f
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.dateTime(str)
|
89
|
+
case str
|
90
|
+
when /^(-?\d\d\d\d)-?(\d\d)-?(\d\d)T(\d\d):(\d\d):(\d\d)(?:Z|([+-])(\d\d):?(\d\d))?$/
|
91
|
+
a = [$1, $2, $3, $4, $5, $6].collect{|i| i.to_i}
|
92
|
+
if $7
|
93
|
+
ofs = $8.to_i*3600 + $9.to_i*60
|
94
|
+
ofs = -ofs if $7=='+'
|
95
|
+
utc = Time.utc(*a) + ofs
|
96
|
+
a = [ utc.year, utc.month, utc.day, utc.hour, utc.min, utc.sec ]
|
97
|
+
end
|
98
|
+
XMLRPC::DateTime.new(*a)
|
99
|
+
when /^(-?\d\d)-?(\d\d)-?(\d\d)T(\d\d):(\d\d):(\d\d)(Z|([+-]\d\d):(\d\d))?$/
|
100
|
+
a = [$1, $2, $3, $4, $5, $6].collect{|i| i.to_i}
|
101
|
+
if a[0] < 70
|
102
|
+
a[0] += 2000
|
103
|
+
else
|
104
|
+
a[0] += 1900
|
105
|
+
end
|
106
|
+
if $7
|
107
|
+
ofs = $8.to_i*3600 + $9.to_i*60
|
108
|
+
ofs = -ofs if $7=='+'
|
109
|
+
utc = Time.utc(*a) + ofs
|
110
|
+
a = [ utc.year, utc.month, utc.day, utc.hour, utc.min, utc.sec ]
|
111
|
+
end
|
112
|
+
XMLRPC::DateTime.new(*a)
|
113
|
+
else
|
114
|
+
raise "wrong dateTime.iso8601 format " + str
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.base64(str)
|
119
|
+
XMLRPC::Base64.decode(str)
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.struct(hash)
|
123
|
+
# convert to marhalled object
|
124
|
+
klass = hash["___class___"]
|
125
|
+
if klass.nil? or Config::ENABLE_MARSHALLING == false
|
126
|
+
hash
|
127
|
+
else
|
128
|
+
begin
|
129
|
+
mod = Module
|
130
|
+
klass.split("::").each {|const| mod = mod.const_get(const.strip)}
|
131
|
+
|
132
|
+
obj = mod.allocate
|
133
|
+
|
134
|
+
hash.delete "___class___"
|
135
|
+
hash.each {|key, value|
|
136
|
+
obj.instance_variable_set("@#{ key }", value) if key =~ /^([\w_][\w_0-9]*)$/
|
137
|
+
}
|
138
|
+
obj
|
139
|
+
rescue
|
140
|
+
hash
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.fault(hash)
|
146
|
+
if hash.kind_of? Hash and hash.size == 2 and
|
147
|
+
hash.has_key? "faultCode" and hash.has_key? "faultString" and
|
148
|
+
hash["faultCode"].kind_of? Integer and hash["faultString"].kind_of? String
|
149
|
+
|
150
|
+
XMLRPC::FaultException.new(hash["faultCode"], hash["faultString"])
|
151
|
+
else
|
152
|
+
raise "wrong fault-structure: #{hash.inspect}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
end # module Convert
|
157
|
+
|
158
|
+
module XMLParser
|
159
|
+
|
160
|
+
class AbstractTreeParser
|
161
|
+
|
162
|
+
def parseMethodResponse(str)
|
163
|
+
methodResponse_document(createCleanedTree(str))
|
164
|
+
end
|
165
|
+
|
166
|
+
def parseMethodCall(str)
|
167
|
+
methodCall_document(createCleanedTree(str))
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
#
|
173
|
+
# remove all whitespaces but in the tags i4, int, boolean....
|
174
|
+
# and all comments
|
175
|
+
#
|
176
|
+
def removeWhitespacesAndComments(node)
|
177
|
+
remove = []
|
178
|
+
childs = node.childNodes.to_a
|
179
|
+
childs.each do |nd|
|
180
|
+
case _nodeType(nd)
|
181
|
+
when :TEXT
|
182
|
+
# TODO: add nil?
|
183
|
+
unless %w(i4 int boolean string double dateTime.iso8601 base64).include? node.nodeName
|
184
|
+
|
185
|
+
if node.nodeName == "value"
|
186
|
+
if not node.childNodes.to_a.detect {|n| _nodeType(n) == :ELEMENT}.nil?
|
187
|
+
remove << nd if nd.nodeValue.strip == ""
|
188
|
+
end
|
189
|
+
else
|
190
|
+
remove << nd if nd.nodeValue.strip == ""
|
191
|
+
end
|
192
|
+
end
|
193
|
+
when :COMMENT
|
194
|
+
remove << nd
|
195
|
+
else
|
196
|
+
removeWhitespacesAndComments(nd)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
remove.each { |i| node.removeChild(i) }
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
def nodeMustBe(node, name)
|
205
|
+
cmp = case name
|
206
|
+
when Array
|
207
|
+
name.include?(node.nodeName)
|
208
|
+
when String
|
209
|
+
name == node.nodeName
|
210
|
+
else
|
211
|
+
raise "error"
|
212
|
+
end
|
213
|
+
|
214
|
+
if not cmp then
|
215
|
+
raise "wrong xml-rpc (name)"
|
216
|
+
end
|
217
|
+
|
218
|
+
node
|
219
|
+
end
|
220
|
+
|
221
|
+
#
|
222
|
+
# returns, when successfully the only child-node
|
223
|
+
#
|
224
|
+
def hasOnlyOneChild(node, name=nil)
|
225
|
+
if node.childNodes.to_a.size != 1
|
226
|
+
raise "wrong xml-rpc (size)"
|
227
|
+
end
|
228
|
+
if name != nil then
|
229
|
+
nodeMustBe(node.firstChild, name)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
|
234
|
+
def assert(b)
|
235
|
+
if not b then
|
236
|
+
raise "assert-fail"
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
# the node `node` has empty string or string
|
241
|
+
def text_zero_one(node)
|
242
|
+
nodes = node.childNodes.to_a.size
|
243
|
+
|
244
|
+
if nodes == 1
|
245
|
+
text(node.firstChild)
|
246
|
+
elsif nodes == 0
|
247
|
+
""
|
248
|
+
else
|
249
|
+
raise "wrong xml-rpc (size)"
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
|
254
|
+
def integer(node)
|
255
|
+
#TODO: check string for float because to_i returnsa
|
256
|
+
# 0 when wrong string
|
257
|
+
nodeMustBe(node, %w(i4 int))
|
258
|
+
hasOnlyOneChild(node)
|
259
|
+
|
260
|
+
Convert.int(text(node.firstChild))
|
261
|
+
end
|
262
|
+
|
263
|
+
def boolean(node)
|
264
|
+
nodeMustBe(node, "boolean")
|
265
|
+
hasOnlyOneChild(node)
|
266
|
+
|
267
|
+
Convert.boolean(text(node.firstChild))
|
268
|
+
end
|
269
|
+
|
270
|
+
def v_nil(node)
|
271
|
+
nodeMustBe(node, "nil")
|
272
|
+
assert( node.childNodes.to_a.size == 0 )
|
273
|
+
nil
|
274
|
+
end
|
275
|
+
|
276
|
+
def string(node)
|
277
|
+
nodeMustBe(node, "string")
|
278
|
+
text_zero_one(node)
|
279
|
+
end
|
280
|
+
|
281
|
+
def double(node)
|
282
|
+
#TODO: check string for float because to_f returnsa
|
283
|
+
# 0.0 when wrong string
|
284
|
+
nodeMustBe(node, "double")
|
285
|
+
hasOnlyOneChild(node)
|
286
|
+
|
287
|
+
Convert.double(text(node.firstChild))
|
288
|
+
end
|
289
|
+
|
290
|
+
def dateTime(node)
|
291
|
+
nodeMustBe(node, "dateTime.iso8601")
|
292
|
+
hasOnlyOneChild(node)
|
293
|
+
|
294
|
+
Convert.dateTime( text(node.firstChild) )
|
295
|
+
end
|
296
|
+
|
297
|
+
def base64(node)
|
298
|
+
nodeMustBe(node, "base64")
|
299
|
+
#hasOnlyOneChild(node)
|
300
|
+
|
301
|
+
Convert.base64(text_zero_one(node))
|
302
|
+
end
|
303
|
+
|
304
|
+
def member(node)
|
305
|
+
nodeMustBe(node, "member")
|
306
|
+
assert( node.childNodes.to_a.size == 2 )
|
307
|
+
|
308
|
+
[ name(node[0]), value(node[1]) ]
|
309
|
+
end
|
310
|
+
|
311
|
+
def name(node)
|
312
|
+
nodeMustBe(node, "name")
|
313
|
+
#hasOnlyOneChild(node)
|
314
|
+
text_zero_one(node)
|
315
|
+
end
|
316
|
+
|
317
|
+
def array(node)
|
318
|
+
nodeMustBe(node, "array")
|
319
|
+
hasOnlyOneChild(node, "data")
|
320
|
+
data(node.firstChild)
|
321
|
+
end
|
322
|
+
|
323
|
+
def data(node)
|
324
|
+
nodeMustBe(node, "data")
|
325
|
+
|
326
|
+
node.childNodes.to_a.collect do |val|
|
327
|
+
value(val)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def param(node)
|
332
|
+
nodeMustBe(node, "param")
|
333
|
+
hasOnlyOneChild(node, "value")
|
334
|
+
value(node.firstChild)
|
335
|
+
end
|
336
|
+
|
337
|
+
def methodResponse(node)
|
338
|
+
nodeMustBe(node, "methodResponse")
|
339
|
+
hasOnlyOneChild(node, %w(params fault))
|
340
|
+
child = node.firstChild
|
341
|
+
|
342
|
+
case child.nodeName
|
343
|
+
when "params"
|
344
|
+
[ true, params(child,false) ]
|
345
|
+
when "fault"
|
346
|
+
[ false, fault(child) ]
|
347
|
+
else
|
348
|
+
raise "unexpected error"
|
349
|
+
end
|
350
|
+
|
351
|
+
end
|
352
|
+
|
353
|
+
def methodName(node)
|
354
|
+
nodeMustBe(node, "methodName")
|
355
|
+
hasOnlyOneChild(node)
|
356
|
+
text(node.firstChild)
|
357
|
+
end
|
358
|
+
|
359
|
+
def params(node, call=true)
|
360
|
+
nodeMustBe(node, "params")
|
361
|
+
|
362
|
+
if call
|
363
|
+
node.childNodes.to_a.collect do |n|
|
364
|
+
param(n)
|
365
|
+
end
|
366
|
+
else # response (only one param)
|
367
|
+
hasOnlyOneChild(node)
|
368
|
+
param(node.firstChild)
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def fault(node)
|
373
|
+
nodeMustBe(node, "fault")
|
374
|
+
hasOnlyOneChild(node, "value")
|
375
|
+
f = value(node.firstChild)
|
376
|
+
Convert.fault(f)
|
377
|
+
end
|
378
|
+
|
379
|
+
|
380
|
+
|
381
|
+
# _nodeType is defined in the subclass
|
382
|
+
def text(node)
|
383
|
+
assert( _nodeType(node) == :TEXT )
|
384
|
+
assert( node.hasChildNodes == false )
|
385
|
+
assert( node.nodeValue != nil )
|
386
|
+
|
387
|
+
node.nodeValue.to_s
|
388
|
+
end
|
389
|
+
|
390
|
+
def struct(node)
|
391
|
+
nodeMustBe(node, "struct")
|
392
|
+
|
393
|
+
hash = {}
|
394
|
+
node.childNodes.to_a.each do |me|
|
395
|
+
n, v = member(me)
|
396
|
+
hash[n] = v
|
397
|
+
end
|
398
|
+
|
399
|
+
Convert.struct(hash)
|
400
|
+
end
|
401
|
+
|
402
|
+
|
403
|
+
def value(node)
|
404
|
+
nodeMustBe(node, "value")
|
405
|
+
nodes = node.childNodes.to_a.size
|
406
|
+
if nodes == 0
|
407
|
+
return ""
|
408
|
+
elsif nodes > 1
|
409
|
+
raise "wrong xml-rpc (size)"
|
410
|
+
end
|
411
|
+
|
412
|
+
child = node.firstChild
|
413
|
+
|
414
|
+
case _nodeType(child)
|
415
|
+
when :TEXT
|
416
|
+
text_zero_one(node)
|
417
|
+
when :ELEMENT
|
418
|
+
case child.nodeName
|
419
|
+
when "i4", "int" then integer(child)
|
420
|
+
when "boolean" then boolean(child)
|
421
|
+
when "string" then string(child)
|
422
|
+
when "double" then double(child)
|
423
|
+
when "dateTime.iso8601" then dateTime(child)
|
424
|
+
when "base64" then base64(child)
|
425
|
+
when "struct" then struct(child)
|
426
|
+
when "array" then array(child)
|
427
|
+
when "nil"
|
428
|
+
if Config::ENABLE_NIL_PARSER
|
429
|
+
v_nil(child)
|
430
|
+
else
|
431
|
+
raise "wrong/unknown XML-RPC type 'nil'"
|
432
|
+
end
|
433
|
+
else
|
434
|
+
raise "wrong/unknown XML-RPC type"
|
435
|
+
end
|
436
|
+
else
|
437
|
+
raise "wrong type of node"
|
438
|
+
end
|
439
|
+
|
440
|
+
end
|
441
|
+
|
442
|
+
def methodCall(node)
|
443
|
+
nodeMustBe(node, "methodCall")
|
444
|
+
assert( (1..2).include?( node.childNodes.to_a.size ) )
|
445
|
+
name = methodName(node[0])
|
446
|
+
|
447
|
+
if node.childNodes.to_a.size == 2 then
|
448
|
+
pa = params(node[1])
|
449
|
+
else # no parameters given
|
450
|
+
pa = []
|
451
|
+
end
|
452
|
+
[name, pa]
|
453
|
+
end
|
454
|
+
|
455
|
+
end # module TreeParserMixin
|
456
|
+
|
457
|
+
class AbstractStreamParser
|
458
|
+
def parseMethodResponse(str)
|
459
|
+
parser = @parser_class.new
|
460
|
+
parser.parse(str)
|
461
|
+
raise "No valid method response!" if parser.method_name != nil
|
462
|
+
if parser.fault != nil
|
463
|
+
# is a fault structure
|
464
|
+
[false, parser.fault]
|
465
|
+
else
|
466
|
+
# is a normal return value
|
467
|
+
raise "Missing return value!" if parser.params.size == 0
|
468
|
+
raise "Too many return values. Only one allowed!" if parser.params.size > 1
|
469
|
+
[true, parser.params[0]]
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
def parseMethodCall(str)
|
474
|
+
parser = @parser_class.new
|
475
|
+
parser.parse(str)
|
476
|
+
raise "No valid method call - missing method name!" if parser.method_name.nil?
|
477
|
+
[parser.method_name, parser.params]
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
module StreamParserMixin
|
482
|
+
attr_reader :params
|
483
|
+
attr_reader :method_name
|
484
|
+
attr_reader :fault
|
485
|
+
|
486
|
+
def initialize(*a)
|
487
|
+
super(*a)
|
488
|
+
@params = []
|
489
|
+
@values = []
|
490
|
+
@val_stack = []
|
491
|
+
|
492
|
+
@names = []
|
493
|
+
@name = []
|
494
|
+
|
495
|
+
@structs = []
|
496
|
+
@struct = {}
|
497
|
+
|
498
|
+
@method_name = nil
|
499
|
+
@fault = nil
|
500
|
+
|
501
|
+
@data = nil
|
502
|
+
end
|
503
|
+
|
504
|
+
def startElement(name, attrs=[])
|
505
|
+
@data = nil
|
506
|
+
case name
|
507
|
+
when "value"
|
508
|
+
@value = nil
|
509
|
+
when "nil"
|
510
|
+
raise "wrong/unknown XML-RPC type 'nil'" unless Config::ENABLE_NIL_PARSER
|
511
|
+
@value = :nil
|
512
|
+
when "array"
|
513
|
+
@val_stack << @values
|
514
|
+
@values = []
|
515
|
+
when "struct"
|
516
|
+
@names << @name
|
517
|
+
@name = []
|
518
|
+
|
519
|
+
@structs << @struct
|
520
|
+
@struct = {}
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
def endElement(name)
|
525
|
+
@data ||= ""
|
526
|
+
case name
|
527
|
+
when "string"
|
528
|
+
@value = @data
|
529
|
+
when "i4", "int"
|
530
|
+
@value = Convert.int(@data)
|
531
|
+
when "boolean"
|
532
|
+
@value = Convert.boolean(@data)
|
533
|
+
when "double"
|
534
|
+
@value = Convert.double(@data)
|
535
|
+
when "dateTime.iso8601"
|
536
|
+
@value = Convert.dateTime(@data)
|
537
|
+
when "base64"
|
538
|
+
@value = Convert.base64(@data)
|
539
|
+
when "value"
|
540
|
+
@value = @data if @value.nil?
|
541
|
+
@values << (@value == :nil ? nil : @value)
|
542
|
+
when "array"
|
543
|
+
@value = @values
|
544
|
+
@values = @val_stack.pop
|
545
|
+
when "struct"
|
546
|
+
@value = Convert.struct(@struct)
|
547
|
+
|
548
|
+
@name = @names.pop
|
549
|
+
@struct = @structs.pop
|
550
|
+
when "name"
|
551
|
+
@name[0] = @data
|
552
|
+
when "member"
|
553
|
+
@struct[@name[0]] = @values.pop
|
554
|
+
|
555
|
+
when "param"
|
556
|
+
@params << @values[0]
|
557
|
+
@values = []
|
558
|
+
|
559
|
+
when "fault"
|
560
|
+
@fault = Convert.fault(@values[0])
|
561
|
+
|
562
|
+
when "methodName"
|
563
|
+
@method_name = @data
|
564
|
+
end
|
565
|
+
|
566
|
+
@data = nil
|
567
|
+
end
|
568
|
+
|
569
|
+
def character(data)
|
570
|
+
if @data
|
571
|
+
@data << data
|
572
|
+
else
|
573
|
+
@data = data
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
end # module StreamParserMixin
|
578
|
+
|
579
|
+
# ---------------------------------------------------------------------------
|
580
|
+
class XMLStreamParser < AbstractStreamParser
|
581
|
+
def initialize
|
582
|
+
require "xmlparser"
|
583
|
+
@parser_class = Class.new(::XMLParser) {
|
584
|
+
include StreamParserMixin
|
585
|
+
}
|
586
|
+
end
|
587
|
+
end # class XMLStreamParser
|
588
|
+
# ---------------------------------------------------------------------------
|
589
|
+
class NQXMLStreamParser < AbstractStreamParser
|
590
|
+
def initialize
|
591
|
+
require "nqxml/streamingparser"
|
592
|
+
@parser_class = XMLRPCParser
|
593
|
+
end
|
594
|
+
|
595
|
+
class XMLRPCParser
|
596
|
+
include StreamParserMixin
|
597
|
+
|
598
|
+
def parse(str)
|
599
|
+
parser = NQXML::StreamingParser.new(str)
|
600
|
+
parser.each do |ele|
|
601
|
+
case ele
|
602
|
+
when NQXML::Text
|
603
|
+
@data = ele.text
|
604
|
+
#character(ele.text)
|
605
|
+
when NQXML::Tag
|
606
|
+
if ele.isTagEnd
|
607
|
+
endElement(ele.name)
|
608
|
+
else
|
609
|
+
startElement(ele.name, ele.attrs)
|
610
|
+
end
|
611
|
+
end
|
612
|
+
end # do
|
613
|
+
end # method parse
|
614
|
+
end # class XMLRPCParser
|
615
|
+
|
616
|
+
end # class NQXMLStreamParser
|
617
|
+
# ---------------------------------------------------------------------------
|
618
|
+
class XMLTreeParser < AbstractTreeParser
|
619
|
+
|
620
|
+
def initialize
|
621
|
+
require "xmltreebuilder"
|
622
|
+
|
623
|
+
# The new XMLParser library (0.6.2+) uses a slightly different DOM implementation.
|
624
|
+
# The following code removes the differences between both versions.
|
625
|
+
if defined? XML::DOM::Builder
|
626
|
+
return if defined? XML::DOM::Node::DOCUMENT # code below has been already executed
|
627
|
+
klass = XML::DOM::Node
|
628
|
+
klass.const_set("DOCUMENT", klass::DOCUMENT_NODE)
|
629
|
+
klass.const_set("TEXT", klass::TEXT_NODE)
|
630
|
+
klass.const_set("COMMENT", klass::COMMENT_NODE)
|
631
|
+
klass.const_set("ELEMENT", klass::ELEMENT_NODE)
|
632
|
+
end
|
633
|
+
end
|
634
|
+
|
635
|
+
private
|
636
|
+
|
637
|
+
def _nodeType(node)
|
638
|
+
tp = node.nodeType
|
639
|
+
if tp == XML::SimpleTree::Node::TEXT then :TEXT
|
640
|
+
elsif tp == XML::SimpleTree::Node::COMMENT then :COMMENT
|
641
|
+
elsif tp == XML::SimpleTree::Node::ELEMENT then :ELEMENT
|
642
|
+
else :ELSE
|
643
|
+
end
|
644
|
+
end
|
645
|
+
|
646
|
+
|
647
|
+
def methodResponse_document(node)
|
648
|
+
assert( node.nodeType == XML::SimpleTree::Node::DOCUMENT )
|
649
|
+
hasOnlyOneChild(node, "methodResponse")
|
650
|
+
|
651
|
+
methodResponse(node.firstChild)
|
652
|
+
end
|
653
|
+
|
654
|
+
def methodCall_document(node)
|
655
|
+
assert( node.nodeType == XML::SimpleTree::Node::DOCUMENT )
|
656
|
+
hasOnlyOneChild(node, "methodCall")
|
657
|
+
|
658
|
+
methodCall(node.firstChild)
|
659
|
+
end
|
660
|
+
|
661
|
+
def createCleanedTree(str)
|
662
|
+
doc = XML::SimpleTreeBuilder.new.parse(str)
|
663
|
+
doc.documentElement.normalize
|
664
|
+
removeWhitespacesAndComments(doc)
|
665
|
+
doc
|
666
|
+
end
|
667
|
+
|
668
|
+
end # class XMLParser
|
669
|
+
# ---------------------------------------------------------------------------
|
670
|
+
class NQXMLTreeParser < AbstractTreeParser
|
671
|
+
|
672
|
+
def initialize
|
673
|
+
require "nqxml/treeparser"
|
674
|
+
end
|
675
|
+
|
676
|
+
private
|
677
|
+
|
678
|
+
def _nodeType(node)
|
679
|
+
node.nodeType
|
680
|
+
end
|
681
|
+
|
682
|
+
def methodResponse_document(node)
|
683
|
+
methodResponse(node)
|
684
|
+
end
|
685
|
+
|
686
|
+
def methodCall_document(node)
|
687
|
+
methodCall(node)
|
688
|
+
end
|
689
|
+
|
690
|
+
def createCleanedTree(str)
|
691
|
+
doc = ::NQXML::TreeParser.new(str).document.rootNode
|
692
|
+
removeWhitespacesAndComments(doc)
|
693
|
+
doc
|
694
|
+
end
|
695
|
+
|
696
|
+
end # class NQXMLTreeParser
|
697
|
+
# ---------------------------------------------------------------------------
|
698
|
+
class REXMLStreamParser < AbstractStreamParser
|
699
|
+
def initialize
|
700
|
+
require "rexml/document"
|
701
|
+
@parser_class = StreamListener
|
702
|
+
end
|
703
|
+
|
704
|
+
class StreamListener
|
705
|
+
include StreamParserMixin
|
706
|
+
|
707
|
+
alias :tag_start :startElement
|
708
|
+
alias :tag_end :endElement
|
709
|
+
alias :text :character
|
710
|
+
alias :cdata :character
|
711
|
+
|
712
|
+
def method_missing(*a)
|
713
|
+
# ignore
|
714
|
+
end
|
715
|
+
|
716
|
+
def parse(str)
|
717
|
+
parser = REXML::Document.parse_stream(str, self)
|
718
|
+
end
|
719
|
+
end
|
720
|
+
|
721
|
+
end
|
722
|
+
# ---------------------------------------------------------------------------
|
723
|
+
class XMLScanStreamParser < AbstractStreamParser
|
724
|
+
def initialize
|
725
|
+
require "xmlscan/parser"
|
726
|
+
@parser_class = XMLScanParser
|
727
|
+
end
|
728
|
+
|
729
|
+
class XMLScanParser
|
730
|
+
include StreamParserMixin
|
731
|
+
|
732
|
+
Entities = {
|
733
|
+
"lt" => "<",
|
734
|
+
"gt" => ">",
|
735
|
+
"amp" => "&",
|
736
|
+
"quot" => '"',
|
737
|
+
"apos" => "'"
|
738
|
+
}
|
739
|
+
|
740
|
+
def parse(str)
|
741
|
+
parser = XMLScan::XMLParser.new(self)
|
742
|
+
parser.parse(str)
|
743
|
+
end
|
744
|
+
|
745
|
+
alias :on_stag :startElement
|
746
|
+
alias :on_etag :endElement
|
747
|
+
|
748
|
+
def on_stag_end(name); end
|
749
|
+
|
750
|
+
def on_stag_end_empty(name)
|
751
|
+
startElement(name)
|
752
|
+
endElement(name)
|
753
|
+
end
|
754
|
+
|
755
|
+
def on_chardata(str)
|
756
|
+
character(str)
|
757
|
+
end
|
758
|
+
|
759
|
+
def on_cdata(str)
|
760
|
+
character(str)
|
761
|
+
end
|
762
|
+
|
763
|
+
def on_entityref(ent)
|
764
|
+
str = Entities[ent]
|
765
|
+
if str
|
766
|
+
character(str)
|
767
|
+
else
|
768
|
+
raise "unknown entity"
|
769
|
+
end
|
770
|
+
end
|
771
|
+
|
772
|
+
def on_charref(code)
|
773
|
+
character(code.chr)
|
774
|
+
end
|
775
|
+
|
776
|
+
def on_charref_hex(code)
|
777
|
+
character(code.chr)
|
778
|
+
end
|
779
|
+
|
780
|
+
def method_missing(*a)
|
781
|
+
end
|
782
|
+
|
783
|
+
# TODO: call/implement?
|
784
|
+
# valid_name?
|
785
|
+
# valid_chardata?
|
786
|
+
# valid_char?
|
787
|
+
# parse_error
|
788
|
+
|
789
|
+
end
|
790
|
+
end
|
791
|
+
# ---------------------------------------------------------------------------
|
792
|
+
XMLParser = XMLTreeParser
|
793
|
+
NQXMLParser = NQXMLTreeParser
|
794
|
+
|
795
|
+
Classes = [XMLStreamParser, XMLTreeParser,
|
796
|
+
NQXMLStreamParser, NQXMLTreeParser,
|
797
|
+
REXMLStreamParser, XMLScanStreamParser]
|
798
|
+
|
799
|
+
# yields an instance of each installed parser
|
800
|
+
def self.each_installed_parser
|
801
|
+
XMLRPC::XMLParser::Classes.each do |klass|
|
802
|
+
begin
|
803
|
+
yield klass.new
|
804
|
+
rescue LoadError
|
805
|
+
end
|
806
|
+
end
|
807
|
+
end
|
808
|
+
|
809
|
+
end # module XMLParser
|
810
|
+
|
811
|
+
|
812
|
+
end # module XMLRPC
|
813
|
+
|