tina4ruby 3.10.5 → 3.10.10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e9679c01c6e303af77a6c9a73c595991c14010af16e36d1b4471eaf0caa6ed3
4
- data.tar.gz: 11cb40c1733ec3b360f4c9ad047558f7548b68fe8dc452dcaa7764a2f0a2d33f
3
+ metadata.gz: 3001c964ea4a136f0dcc9af6766c2de0f98f323f8b9ff93deaab922477967c28
4
+ data.tar.gz: 2abc2dfa785de1cb2a503902cf009501df413af82e9054fc6d044cb4d689525a
5
5
  SHA512:
6
- metadata.gz: 9ee5400333e5662e0f812887d75d1c95f3c00af5ea2ae64ece651acc0430f27131653510bd1239d262fda5ce39652957f6a796e141f5530fe1424c67527840e4
7
- data.tar.gz: e7fa0c88df994957e0836fa0d9437320e7f03cac2f804cd54f55e66f6702294bf9a942fb259aa1e9d7febfed1ffda8a682ed759266bdb53eb78a58682efac0e9
6
+ metadata.gz: d86f3ba17e99a972bdeee8d25a8afec34f12a89ab68ae9be21a5f1ed9bae6e3fa58b69083a8db25ee1ce17bfd1dc47790439f54196f848f6e95df0332d343eb1
7
+ data.tar.gz: '08ded077a54c8ed7c044878ccce8ba7aa099c855be82a3dcc93568ee7ab7eb32f429ae2877872e97754ead753cc2cb4ccc8a990b94703e77787163e20b30e4db'
@@ -103,16 +103,36 @@ module Tina4
103
103
 
104
104
  def ensure_tracking_table
105
105
  unless @db.table_exists?(TRACKING_TABLE)
106
- @db.execute(<<~SQL)
107
- CREATE TABLE #{TRACKING_TABLE} (
108
- id INTEGER PRIMARY KEY,
109
- migration_name VARCHAR(255) NOT NULL,
110
- description VARCHAR(255) DEFAULT '',
111
- batch INTEGER NOT NULL DEFAULT 1,
112
- executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
113
- passed INTEGER NOT NULL DEFAULT 1
114
- )
115
- SQL
106
+ if firebird?
107
+ # Firebird: no AUTOINCREMENT, no TEXT type, use generator for IDs
108
+ begin
109
+ @db.execute("CREATE GENERATOR GEN_TINA4_MIGRATION_ID")
110
+ @db.execute("COMMIT") rescue nil
111
+ rescue
112
+ # Generator may already exist
113
+ end
114
+ @db.execute(<<~SQL)
115
+ CREATE TABLE #{TRACKING_TABLE} (
116
+ id INTEGER NOT NULL PRIMARY KEY,
117
+ migration_name VARCHAR(500) NOT NULL,
118
+ description VARCHAR(500) DEFAULT '',
119
+ batch INTEGER NOT NULL DEFAULT 1,
120
+ executed_at VARCHAR(50) DEFAULT CURRENT_TIMESTAMP,
121
+ passed INTEGER NOT NULL DEFAULT 1
122
+ )
123
+ SQL
124
+ else
125
+ @db.execute(<<~SQL)
126
+ CREATE TABLE #{TRACKING_TABLE} (
127
+ id INTEGER PRIMARY KEY,
128
+ migration_name VARCHAR(255) NOT NULL,
129
+ description VARCHAR(255) DEFAULT '',
130
+ batch INTEGER NOT NULL DEFAULT 1,
131
+ executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
132
+ passed INTEGER NOT NULL DEFAULT 1
133
+ )
134
+ SQL
135
+ end
116
136
  Tina4::Log.info("Created migrations tracking table")
117
137
  end
118
138
  end
@@ -309,10 +329,22 @@ module Tina4
309
329
  # Extract description from filename (strip numeric prefix and extension)
310
330
  stem = File.basename(name, File.extname(name))
311
331
  desc = stem.sub(/\A\d+_/, "").tr("_", " ")
312
- @db.execute(
313
- "INSERT INTO #{TRACKING_TABLE} (migration_name, description, batch, passed) VALUES (?, ?, ?, ?)",
314
- [name, desc, batch, passed]
315
- )
332
+ if firebird?
333
+ # Firebird: generate ID from sequence
334
+ row = @db.fetch_one(
335
+ "SELECT GEN_ID(GEN_TINA4_MIGRATION_ID, 1) AS NEXT_ID FROM RDB\$DATABASE"
336
+ )
337
+ next_id = row ? (row[:NEXT_ID] || row[:next_id] || 1).to_i : 1
338
+ @db.execute(
339
+ "INSERT INTO #{TRACKING_TABLE} (id, migration_name, description, batch, passed) VALUES (?, ?, ?, ?, ?)",
340
+ [next_id, name, desc, batch, passed]
341
+ )
342
+ else
343
+ @db.execute(
344
+ "INSERT INTO #{TRACKING_TABLE} (migration_name, description, batch, passed) VALUES (?, ?, ?, ?)",
345
+ [name, desc, batch, passed]
346
+ )
347
+ end
316
348
  end
317
349
 
318
350
  def remove_migration_record(name)
@@ -488,6 +488,32 @@ module Tina4
488
488
  right = evaluate_expression(Regexp.last_match(3))
489
489
  return apply_math(left, op, right)
490
490
  end
491
+
492
+ # Function call with dotted name: obj.method(args)
493
+ if expr =~ /\A([\w.]+)\s*\((.*)\)\z/m
494
+ func_name = Regexp.last_match(1)
495
+ args_str = Regexp.last_match(2)
496
+ if func_name.include?(".")
497
+ last_dot = func_name.rindex(".")
498
+ obj_path = func_name[0...last_dot]
499
+ method_name = func_name[(last_dot + 1)..]
500
+ obj = resolve_variable(obj_path)
501
+ if obj.respond_to?(:call)
502
+ # obj itself is callable — unlikely but handle
503
+ elsif obj.is_a?(Hash)
504
+ callable = obj[method_name] || obj[method_name.to_sym] || obj[method_name.to_s]
505
+ if callable.respond_to?(:call)
506
+ args = args_str && !args_str.strip.empty? ? parse_filter_args(args_str) : []
507
+ return callable.call(*args)
508
+ end
509
+ elsif obj.respond_to?(method_name.to_sym)
510
+ args = args_str && !args_str.strip.empty? ? parse_filter_args(args_str) : []
511
+ return obj.send(method_name.to_sym, *args)
512
+ end
513
+ return nil
514
+ end
515
+ end
516
+
491
517
  resolve_variable(expr)
492
518
  end
493
519
 
data/lib/tina4/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tina4
4
- VERSION = "3.10.5"
4
+ VERSION = "3.10.10"
5
5
  end
data/lib/tina4/wsdl.rb CHANGED
@@ -1,13 +1,402 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "rexml/document"
4
+
3
5
  module Tina4
4
- module WSDL
6
+ # SOAP 1.1 / WSDL server — zero-dependency, mirrors tina4-python's wsdl module.
7
+ #
8
+ # Usage (class-based):
9
+ #
10
+ # class Calculator < Tina4::WSDL
11
+ # wsdl_operation output: { Result: :int }
12
+ # def add(a, b)
13
+ # { Result: a.to_i + b.to_i }
14
+ # end
15
+ # end
16
+ #
17
+ # # In a route handler:
18
+ # service = Calculator.new(request)
19
+ # response.call(service.handle)
20
+ #
21
+ # Supported:
22
+ # - WSDL 1.1 generation from Ruby type declarations
23
+ # - SOAP 1.1 request/response handling via REXML
24
+ # - Lifecycle hooks (on_request, on_result)
25
+ # - Auto type mapping (Integer -> int, String -> string, Float -> double, etc.)
26
+ # - XML escaping on all response values
27
+ # - SOAP fault responses on errors
28
+ #
29
+ class WSDL
30
+ NS_SOAP = "http://schemas.xmlsoap.org/wsdl/soap/"
31
+ NS_WSDL = "http://schemas.xmlsoap.org/wsdl/"
32
+ NS_XSD = "http://www.w3.org/2001/XMLSchema"
33
+ NS_SOAP_ENV = "http://schemas.xmlsoap.org/soap/envelope/"
34
+
35
+ RUBY_TO_XSD = {
36
+ :int => "xsd:int",
37
+ :integer => "xsd:int",
38
+ :string => "xsd:string",
39
+ :float => "xsd:double",
40
+ :double => "xsd:double",
41
+ :boolean => "xsd:boolean",
42
+ :bool => "xsd:boolean",
43
+ :date => "xsd:date",
44
+ :datetime => "xsd:dateTime",
45
+ :base64 => "xsd:base64Binary",
46
+ Integer => "xsd:int",
47
+ String => "xsd:string",
48
+ Float => "xsd:double",
49
+ TrueClass => "xsd:boolean",
50
+ FalseClass => "xsd:boolean"
51
+ }.freeze
52
+
53
+ # ── Class-level DSL ──────────────────────────────────────────────────
54
+
55
+ class << self
56
+ # Registry of operations declared via wsdl_operation + def.
57
+ # Each entry: { input: { name => type, ... }, output: { name => type, ... } }
58
+ def wsdl_operations
59
+ @wsdl_operations ||= {}
60
+ end
61
+
62
+ # Pending output hash waiting for the next method definition.
63
+ def pending_wsdl_output
64
+ @pending_wsdl_output
65
+ end
66
+
67
+ # Mark the next defined method as a WSDL operation.
68
+ #
69
+ # wsdl_operation output: { Result: :int }
70
+ # def add(a, b) ...
71
+ #
72
+ # Input parameters are inferred from the method signature.
73
+ # The +output+ hash maps response element names to XSD type symbols.
74
+ def wsdl_operation(output: {})
75
+ @pending_wsdl_output = output
76
+ end
77
+
78
+ # Hook into method definition to capture the pending operation.
79
+ def method_added(method_name)
80
+ super
81
+ return unless @pending_wsdl_output
82
+
83
+ output = @pending_wsdl_output
84
+ @pending_wsdl_output = nil
85
+
86
+ # Infer input parameter names from the method signature.
87
+ params = instance_method(method_name).parameters
88
+ input = {}
89
+ params.each do |_kind, name|
90
+ next if name.nil?
91
+ input[name] = :string # default; callers can rely on type coercion
92
+ end
93
+
94
+ wsdl_operations[method_name.to_s] = { input: input, output: output }
95
+ end
96
+
97
+ # Ensure subclasses get their own copy of the operations registry.
98
+ def inherited(subclass)
99
+ super
100
+ subclass.instance_variable_set(:@wsdl_operations, wsdl_operations.dup)
101
+ end
102
+ end
103
+
104
+ # ── Instance ─────────────────────────────────────────────────────────
105
+
106
+ attr_reader :request, :service_url
107
+
108
+ def initialize(request = nil, service_url: "")
109
+ @request = request
110
+ @service_url = service_url.empty? ? infer_url : service_url
111
+ @operations = discover_operations
112
+ end
113
+
114
+ # Main entry point. Returns WSDL XML on GET/?wsdl, or processes a SOAP
115
+ # request on POST.
116
+ def handle
117
+ return generate_wsdl if @request.nil?
118
+
119
+ method = if @request.respond_to?(:method)
120
+ @request.method.to_s.upcase
121
+ elsif @request.respond_to?(:body) && @request.body && !@request.body.to_s.empty?
122
+ "POST"
123
+ else
124
+ "GET"
125
+ end
126
+
127
+ params = (@request.respond_to?(:params) ? @request.params : nil) || {}
128
+ url = (@request.respond_to?(:url) ? @request.url : nil) || ""
129
+
130
+ if method == "GET" || params.key?("wsdl") || params.key?(:wsdl) || url.end_with?("?wsdl")
131
+ return generate_wsdl
132
+ end
133
+
134
+ body = if @request.respond_to?(:body)
135
+ @request.body.is_a?(String) ? @request.body : @request.body.to_s
136
+ else
137
+ ""
138
+ end
139
+
140
+ process_soap(body)
141
+ end
142
+
143
+ # ── Lifecycle hooks (override in subclass) ───────────────────────────
144
+
145
+ # Called before operation invocation. Override to validate/log.
146
+ def on_request(request)
147
+ # no-op
148
+ end
149
+
150
+ # Called after operation returns. Override to transform/audit.
151
+ # Must return the (possibly modified) result.
152
+ def on_result(result)
153
+ result
154
+ end
155
+
156
+ # ── WSDL generation ──────────────────────────────────────────────────
157
+
158
+ def generate_wsdl
159
+ service_name = self.class.name ? self.class.name.split("::").last : "AnonymousService"
160
+ tns = "urn:#{service_name}"
161
+
162
+ parts = []
163
+ parts << '<?xml version="1.0" encoding="UTF-8"?>'
164
+ parts << "<definitions name=\"#{service_name}\""
165
+ parts << " targetNamespace=\"#{tns}\""
166
+ parts << " xmlns:tns=\"#{tns}\""
167
+ parts << " xmlns:soap=\"#{NS_SOAP}\""
168
+ parts << " xmlns:xsd=\"#{NS_XSD}\""
169
+ parts << " xmlns=\"#{NS_WSDL}\">"
170
+ parts << ""
171
+
172
+ # Types
173
+ parts << " <types>"
174
+ parts << " <xsd:schema targetNamespace=\"#{tns}\">"
175
+
176
+ @operations.each do |op_name, meta|
177
+ # Request element
178
+ parts << " <xsd:element name=\"#{op_name}\">"
179
+ parts << " <xsd:complexType>"
180
+ parts << " <xsd:sequence>"
181
+ meta[:input].each do |pname, ptype|
182
+ xsd = xsd_type(ptype)
183
+ parts << " <xsd:element name=\"#{pname}\" type=\"#{xsd}\"/>"
184
+ end
185
+ parts << " </xsd:sequence>"
186
+ parts << " </xsd:complexType>"
187
+ parts << " </xsd:element>"
188
+
189
+ # Response element
190
+ parts << " <xsd:element name=\"#{op_name}Response\">"
191
+ parts << " <xsd:complexType>"
192
+ parts << " <xsd:sequence>"
193
+ meta[:output].each do |rname, rtype|
194
+ xsd = xsd_type(rtype)
195
+ parts << " <xsd:element name=\"#{rname}\" type=\"#{xsd}\"/>"
196
+ end
197
+ parts << " </xsd:sequence>"
198
+ parts << " </xsd:complexType>"
199
+ parts << " </xsd:element>"
200
+ end
201
+
202
+ parts << " </xsd:schema>"
203
+ parts << " </types>"
204
+ parts << ""
205
+
206
+ # Messages
207
+ @operations.each_key do |op_name|
208
+ parts << " <message name=\"#{op_name}Input\">"
209
+ parts << " <part name=\"parameters\" element=\"tns:#{op_name}\"/>"
210
+ parts << " </message>"
211
+ parts << " <message name=\"#{op_name}Output\">"
212
+ parts << " <part name=\"parameters\" element=\"tns:#{op_name}Response\"/>"
213
+ parts << " </message>"
214
+ end
215
+ parts << ""
216
+
217
+ # PortType
218
+ parts << " <portType name=\"#{service_name}PortType\">"
219
+ @operations.each_key do |op_name|
220
+ parts << " <operation name=\"#{op_name}\">"
221
+ parts << " <input message=\"tns:#{op_name}Input\"/>"
222
+ parts << " <output message=\"tns:#{op_name}Output\"/>"
223
+ parts << " </operation>"
224
+ end
225
+ parts << " </portType>"
226
+ parts << ""
227
+
228
+ # Binding
229
+ parts << " <binding name=\"#{service_name}Binding\" type=\"tns:#{service_name}PortType\">"
230
+ parts << " <soap:binding style=\"document\" transport=\"http://schemas.xmlsoap.org/soap/http\"/>"
231
+ @operations.each_key do |op_name|
232
+ parts << " <operation name=\"#{op_name}\">"
233
+ parts << " <soap:operation soapAction=\"#{tns}/#{op_name}\"/>"
234
+ parts << ' <input><soap:body use="literal"/></input>'
235
+ parts << ' <output><soap:body use="literal"/></output>'
236
+ parts << " </operation>"
237
+ end
238
+ parts << " </binding>"
239
+ parts << ""
240
+
241
+ # Service
242
+ parts << " <service name=\"#{service_name}\">"
243
+ parts << " <port name=\"#{service_name}Port\" binding=\"tns:#{service_name}Binding\">"
244
+ parts << " <soap:address location=\"#{@service_url}\"/>"
245
+ parts << " </port>"
246
+ parts << " </service>"
247
+
248
+ parts << "</definitions>"
249
+ parts.join("\n")
250
+ end
251
+
252
+ private
253
+
254
+ # ── Auto-discovery ───────────────────────────────────────────────────
255
+
256
+ def discover_operations
257
+ self.class.wsdl_operations.dup
258
+ end
259
+
260
+ def infer_url
261
+ return @request.url if @request && @request.respond_to?(:url)
262
+ "/"
263
+ end
264
+
265
+ # ── SOAP request processing ──────────────────────────────────────────
266
+
267
+ def process_soap(xml_body)
268
+ on_request(@request)
269
+
270
+ begin
271
+ doc = REXML::Document.new(xml_body)
272
+ rescue REXML::ParseException
273
+ return soap_fault("Client", "Malformed XML")
274
+ end
275
+
276
+ # Find the SOAP Body element (namespace-agnostic)
277
+ body_el = find_child(doc.root, "Body")
278
+ return soap_fault("Client", "Missing SOAP Body") unless body_el
279
+
280
+ # First child of Body is the operation element
281
+ op_el = body_el.elements.first
282
+ return soap_fault("Client", "Empty SOAP Body") unless op_el
283
+
284
+ op_name = local_name(op_el)
285
+
286
+ unless @operations.key?(op_name)
287
+ return soap_fault("Client", "Unknown operation: #{op_name}")
288
+ end
289
+
290
+ meta = @operations[op_name]
291
+
292
+ # Extract parameters from the operation element
293
+ params = {}
294
+ meta[:input].each do |param_name, param_type|
295
+ child = find_child(op_el, param_name.to_s)
296
+ if child
297
+ value = child.text || ""
298
+ params[param_name.to_s] = convert_value(value, param_type)
299
+ end
300
+ end
301
+
302
+ begin
303
+ result = send(op_name.to_sym, *meta[:input].keys.map { |k| params[k.to_s] })
304
+ result = on_result(result)
305
+ rescue StandardError => e
306
+ return soap_fault("Server", e.message)
307
+ end
308
+
309
+ soap_response(op_name, result)
310
+ end
311
+
312
+ # ── XML helpers (REXML) ──────────────────────────────────────────────
313
+
314
+ def find_child(parent, local)
315
+ parent.each_element do |el|
316
+ return el if local_name(el) == local
317
+ end
318
+ nil
319
+ end
320
+
321
+ def local_name(element)
322
+ element.name # REXML already strips the prefix for .name
323
+ end
324
+
325
+ # ── Type conversion ──────────────────────────────────────────────────
326
+
327
+ def convert_value(value, target_type)
328
+ case target_type.to_s.downcase.to_sym
329
+ when :int, :integer
330
+ value.to_i
331
+ when :float, :double
332
+ value.to_f
333
+ when :boolean, :bool
334
+ %w[true 1 yes].include?(value.downcase)
335
+ else
336
+ value
337
+ end
338
+ end
339
+
340
+ # ── Response builders ────────────────────────────────────────────────
341
+
342
+ def soap_response(op_name, result)
343
+ parts = []
344
+ parts << '<?xml version="1.0" encoding="UTF-8"?>'
345
+ parts << "<soap:Envelope xmlns:soap=\"#{NS_SOAP_ENV}\">"
346
+ parts << "<soap:Body>"
347
+ parts << "<#{op_name}Response>"
348
+
349
+ if result.is_a?(Hash)
350
+ result.each do |k, v|
351
+ if v.nil?
352
+ parts << "<#{k} xsi:nil=\"true\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"/>"
353
+ elsif v.is_a?(Array)
354
+ v.each { |item| parts << "<#{k}>#{escape_xml(item.to_s)}</#{k}>" }
355
+ else
356
+ parts << "<#{k}>#{escape_xml(v.to_s)}</#{k}>"
357
+ end
358
+ end
359
+ end
360
+
361
+ parts << "</#{op_name}Response>"
362
+ parts << "</soap:Body>"
363
+ parts << "</soap:Envelope>"
364
+ parts.join("\n")
365
+ end
366
+
367
+ def soap_fault(code, message)
368
+ '<?xml version="1.0" encoding="UTF-8"?>' \
369
+ "<soap:Envelope xmlns:soap=\"#{NS_SOAP_ENV}\">" \
370
+ "<soap:Body>" \
371
+ "<soap:Fault>" \
372
+ "<faultcode>#{code}</faultcode>" \
373
+ "<faultstring>#{escape_xml(message)}</faultstring>" \
374
+ "</soap:Fault>" \
375
+ "</soap:Body>" \
376
+ "</soap:Envelope>"
377
+ end
378
+
379
+ def escape_xml(s)
380
+ s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;").gsub('"', "&quot;")
381
+ end
382
+
383
+ def xsd_type(ruby_type)
384
+ return RUBY_TO_XSD[ruby_type] if RUBY_TO_XSD.key?(ruby_type)
385
+
386
+ sym = ruby_type.to_s.downcase.to_sym
387
+ RUBY_TO_XSD.fetch(sym, "xsd:string")
388
+ end
389
+
390
+ # ── Legacy wrapper ───────────────────────────────────────────────────
391
+ # Keeps backward compatibility with the old Tina4::WSDL::Service API
392
+ # used in demos and existing code.
393
+
5
394
  class Service
6
395
  attr_reader :name, :namespace, :operations
7
396
 
8
397
  def initialize(name:, namespace: "http://tina4.com/wsdl")
9
- @name = name
10
- @namespace = namespace
398
+ @name = name
399
+ @namespace = namespace
11
400
  @operations = {}
12
401
  end
13
402
 
@@ -30,8 +419,8 @@ module Tina4
30
419
  # Types
31
420
  xml += " <types>\n <xsd:schema targetNamespace=\"#{@namespace}\">\n"
32
421
  @operations.each do |op_name, op|
33
- xml += generate_elements(op_name, op[:input], "Request")
34
- xml += generate_elements(op_name, op[:output], "Response")
422
+ xml += _generate_elements(op_name, op[:input], "Request")
423
+ xml += _generate_elements(op_name, op[:output], "Response")
35
424
  end
36
425
  xml += " </xsd:schema>\n </types>\n"
37
426
 
@@ -78,44 +467,50 @@ module Tina4
78
467
  end
79
468
 
80
469
  def handle_soap_request(xml_body)
81
- # Simple SOAP envelope parser
82
- op_name = nil
83
- params = {}
470
+ doc = REXML::Document.new(xml_body)
84
471
 
85
- @operations.each_key do |name|
86
- if xml_body.include?(name)
87
- op_name = name
88
- break
89
- end
90
- end
472
+ # Find Body element (namespace-agnostic)
473
+ body_el = _find_child(doc.root, "Body")
474
+ return _soap_fault("Unknown operation") unless body_el
475
+
476
+ op_el = body_el.elements.first
477
+ return _soap_fault("Unknown operation") unless op_el
91
478
 
92
- return soap_fault("Unknown operation") unless op_name
479
+ op_name = op_el.name
480
+ return _soap_fault("Unknown operation") unless @operations.key?(op_name)
93
481
 
94
482
  operation = @operations[op_name]
95
483
 
96
- # Extract parameters from XML
484
+ # Extract parameters
485
+ params = {}
97
486
  operation[:input].each_key do |param_name|
98
- if xml_body =~ /<#{param_name}>(.*?)<\/#{param_name}>/m
99
- params[param_name.to_s] = Regexp.last_match(1)
100
- end
487
+ child = _find_child(op_el, param_name.to_s)
488
+ params[param_name.to_s] = child.text if child
101
489
  end
102
490
 
103
491
  # Execute handler
104
492
  result = operation[:handler].call(params)
105
493
 
106
494
  # Build SOAP response
107
- build_soap_response(op_name, result)
108
- rescue => e
109
- soap_fault(e.message)
495
+ _build_soap_response(op_name, result)
496
+ rescue StandardError => e
497
+ _soap_fault(e.message)
110
498
  end
111
499
 
112
500
  private
113
501
 
114
- def generate_elements(op_name, params, suffix)
502
+ def _find_child(parent, local)
503
+ parent.each_element do |el|
504
+ return el if el.name == local
505
+ end
506
+ nil
507
+ end
508
+
509
+ def _generate_elements(op_name, params, suffix)
115
510
  xml = " <xsd:element name=\"#{op_name}#{suffix}\">\n"
116
511
  xml += " <xsd:complexType><xsd:sequence>\n"
117
512
  params.each do |name, type|
118
- xsd_type = ruby_to_xsd_type(type)
513
+ xsd_type = _ruby_to_xsd_type(type)
119
514
  xml += " <xsd:element name=\"#{name}\" type=\"xsd:#{xsd_type}\"/>\n"
120
515
  end
121
516
  xml += " </xsd:sequence></xsd:complexType>\n"
@@ -123,7 +518,7 @@ module Tina4
123
518
  xml
124
519
  end
125
520
 
126
- def ruby_to_xsd_type(type)
521
+ def _ruby_to_xsd_type(type)
127
522
  case type.to_s.downcase
128
523
  when "string" then "string"
129
524
  when "integer", "int" then "int"
@@ -135,30 +530,34 @@ module Tina4
135
530
  end
136
531
  end
137
532
 
138
- def build_soap_response(op_name, result)
533
+ def _build_soap_response(op_name, result)
139
534
  xml = '<?xml version="1.0" encoding="UTF-8"?>'
140
- xml += '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"'
535
+ xml += "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""
141
536
  xml += " xmlns:tns=\"#{@namespace}\">"
142
537
  xml += "<soap:Body>"
143
538
  xml += "<tns:#{op_name}Response>"
144
539
  if result.is_a?(Hash)
145
- result.each { |k, v| xml += "<#{k}>#{v}</#{k}>" }
540
+ result.each { |k, v| xml += "<#{k}>#{_escape_xml(v.to_s)}</#{k}>" }
146
541
  else
147
- xml += "<result>#{result}</result>"
542
+ xml += "<result>#{_escape_xml(result.to_s)}</result>"
148
543
  end
149
544
  xml += "</tns:#{op_name}Response>"
150
545
  xml += "</soap:Body></soap:Envelope>"
151
546
  xml
152
547
  end
153
548
 
154
- def soap_fault(message)
549
+ def _soap_fault(message)
155
550
  '<?xml version="1.0" encoding="UTF-8"?>' \
156
551
  '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">' \
157
552
  "<soap:Body><soap:Fault>" \
158
553
  "<faultcode>soap:Server</faultcode>" \
159
- "<faultstring>#{message}</faultstring>" \
554
+ "<faultstring>#{_escape_xml(message)}</faultstring>" \
160
555
  "</soap:Fault></soap:Body></soap:Envelope>"
161
556
  end
557
+
558
+ def _escape_xml(s)
559
+ s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;").gsub('"', "&quot;")
560
+ end
162
561
  end
163
562
  end
164
563
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tina4ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.10.5
4
+ version: 3.10.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tina4 Team
@@ -150,6 +150,20 @@ dependencies:
150
150
  - - "~>"
151
151
  - !ruby/object:Gem::Version
152
152
  version: '3.16'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rexml
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '3.2'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '3.2'
153
167
  - !ruby/object:Gem::Dependency
154
168
  name: webrick
155
169
  requirement: !ruby/object:Gem::Requirement