riddl 0.99.105

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.
Files changed (92) hide show
  1. data/AUTHORS +1 -0
  2. data/COPYING +165 -0
  3. data/INSTALL +24 -0
  4. data/README.rdoc +2 -0
  5. data/Rakefile +17 -0
  6. data/TODO +17 -0
  7. data/contrib/riddl.jpg +0 -0
  8. data/contrib/riddl.png +0 -0
  9. data/contrib/riddl.svg +138 -0
  10. data/lib/riddl/client.rb +423 -0
  11. data/lib/riddl/commonlogger.rb +16 -0
  12. data/lib/riddl/constants.rb +5 -0
  13. data/lib/riddl/error.rb +8 -0
  14. data/lib/riddl/handlers.rb +14 -0
  15. data/lib/riddl/handlers/oauth.rb +19 -0
  16. data/lib/riddl/handlers/plain-type.rb +21 -0
  17. data/lib/riddl/handlers/relaxng.rb +16 -0
  18. data/lib/riddl/handlers/xmlschema.rb +16 -0
  19. data/lib/riddl/header.rb +9 -0
  20. data/lib/riddl/implementation.rb +57 -0
  21. data/lib/riddl/ns/common-patterns/addon-security/request.xml +25 -0
  22. data/lib/riddl/ns/common-patterns/addon-security/response.xml +25 -0
  23. data/lib/riddl/ns/common-patterns/downloadify/1.0/downloadify.xml +18 -0
  24. data/lib/riddl/ns/common-patterns/notifications-consumer/1.0/consumer.xml +100 -0
  25. data/lib/riddl/ns/common-patterns/notifications-producer/1.0/producer.xml +204 -0
  26. data/lib/riddl/ns/common-patterns/properties/1.0/properties.schema.schema +140 -0
  27. data/lib/riddl/ns/common-patterns/properties/1.0/properties.schema.xsl +89 -0
  28. data/lib/riddl/ns/common-patterns/properties/1.0/properties.xml +150 -0
  29. data/lib/riddl/ns/common/datatypes-1_0.rng +79 -0
  30. data/lib/riddl/ns/common/relaxng-modular.rng +330 -0
  31. data/lib/riddl/ns/common/relaxng.rng +10 -0
  32. data/lib/riddl/ns/declaration/1.0/declaration.rng +114 -0
  33. data/lib/riddl/ns/description/1.0/description.rng +302 -0
  34. data/lib/riddl/option.rb +9 -0
  35. data/lib/riddl/parameter.rb +54 -0
  36. data/lib/riddl/protocols/http/generator.rb +121 -0
  37. data/lib/riddl/protocols/http/parser.rb +199 -0
  38. data/lib/riddl/protocols/websocket.rb +103 -0
  39. data/lib/riddl/protocols/xmpp/generator.rb +176 -0
  40. data/lib/riddl/protocols/xmpp/parser.rb +118 -0
  41. data/lib/riddl/roles.rb +15 -0
  42. data/lib/riddl/roles/http%3A%2F%2Foauth.net%2F1.0%2Faccess_token.rb +30 -0
  43. data/lib/riddl/roles/http%3A%2F%2Foauth.net%2F1.0%2Fon_behalf.rb +22 -0
  44. data/lib/riddl/roles/http%3A%2F%2Foauth.net%2F1.0%2Frequest_token.rb +30 -0
  45. data/lib/riddl/roles/http%3A%2F%2Foauth.net%2F1.0/base.rb +67 -0
  46. data/lib/riddl/server.rb +519 -0
  47. data/lib/riddl/utils/description.rb +29 -0
  48. data/lib/riddl/utils/downloadify.rb +14 -0
  49. data/lib/riddl/utils/erbserve.rb +23 -0
  50. data/lib/riddl/utils/fileserve.rb +31 -0
  51. data/lib/riddl/utils/notifications_producer.rb +310 -0
  52. data/lib/riddl/utils/properties.rb +474 -0
  53. data/lib/riddl/utils/xsloverlay.rb +21 -0
  54. data/lib/riddl/wrapper.rb +280 -0
  55. data/lib/riddl/wrapper/declaration.rb +103 -0
  56. data/lib/riddl/wrapper/declaration/facade.rb +94 -0
  57. data/lib/riddl/wrapper/declaration/interface.rb +34 -0
  58. data/lib/riddl/wrapper/declaration/tile.rb +107 -0
  59. data/lib/riddl/wrapper/description.rb +69 -0
  60. data/lib/riddl/wrapper/description/access.rb +108 -0
  61. data/lib/riddl/wrapper/description/message_and_transformation.rb +131 -0
  62. data/lib/riddl/wrapper/description/resource.rb +271 -0
  63. data/lib/riddl/wrapper/layerchecker.rb +33 -0
  64. data/lib/riddl/wrapper/messageparser.rb +221 -0
  65. data/lib/riddl/wrapper/resourcechecker.rb +98 -0
  66. data/ns/common-patterns/addon-security/request.xml +25 -0
  67. data/ns/common-patterns/addon-security/response.xml +25 -0
  68. data/ns/common-patterns/downloadify/1.0/downloadify.xml +18 -0
  69. data/ns/common-patterns/notifications-consumer/1.0/consumer.xml +100 -0
  70. data/ns/common-patterns/notifications-producer/1.0/producer.xml +204 -0
  71. data/ns/common-patterns/properties/1.0/properties.schema.schema +140 -0
  72. data/ns/common-patterns/properties/1.0/properties.schema.xsl +89 -0
  73. data/ns/common-patterns/properties/1.0/properties.xml +150 -0
  74. data/ns/common/datatypes-1_0.rng +79 -0
  75. data/ns/common/relaxng-modular.rng +330 -0
  76. data/ns/common/relaxng.rng +10 -0
  77. data/ns/declaration/1.0/declaration.rng +114 -0
  78. data/ns/description/1.0/description.rng +302 -0
  79. data/riddl.gemspec +33 -0
  80. data/test/smartrunner.rb +48 -0
  81. data/test/tc_declaration-distributed.rb +79 -0
  82. data/test/tc_declaration-hybrid.rb +71 -0
  83. data/test/tc_declaration-local.rb +47 -0
  84. data/test/tc_helloworld.rb +17 -0
  85. data/test/tc_producer.rb +54 -0
  86. data/test/tc_properties.rb +72 -0
  87. data/tools/flash-policy-server.rb +12 -0
  88. data/tools/riddlcheck +36 -0
  89. data/tools/riddlcheck-1_0 +36 -0
  90. data/tools/riddlprocess +51 -0
  91. data/tools/riddlprocess-1_0 +51 -0
  92. metadata +291 -0
@@ -0,0 +1,271 @@
1
+ module Riddl
2
+ class Wrapper
3
+ class Description < WrapperUtils
4
+
5
+ class Resource
6
+ def initialize(path=nil,recursive=false)
7
+ #{{{
8
+ @path = path
9
+ @role = nil
10
+ @resources = {}
11
+ @access_methods = {}
12
+ @composition = {}
13
+ @recursive = recursive
14
+ #}}}
15
+ end
16
+
17
+ def add_access_methods(des,desres,index,interface)
18
+ #{{{
19
+ desres.find("des:*[not(name()='resource') and not(name()='websocket') and @in and not(@in='*')]").each do |m|
20
+ method = m.attributes['method'] || m.qname.name
21
+ add_request_in_out(index,interface,des,method,m.attributes['in'],m.attributes['out'])
22
+ end
23
+ desres.find("des:*[not(name()='resource') and not(name()='websocket') and @pass and not(@pass='*')]").each do |m|
24
+ method = m.attributes['method'] || m.qname.name
25
+ add_request_in_out(index,interface,des,method,m.attributes['pass'],m.attributes['pass'])
26
+ end
27
+ desres.find("des:*[not(name()='resource') and not(name()='websocket') and @transformation]").each do |m|
28
+ method = m.attributes['method'] || m.qname.name
29
+ add_request_transform(index,interface,des,method,m.attributes['transformation'])
30
+ end
31
+ desres.find("des:*[not(name()='resource') and not(name()='websocket') and @in and @in='*']").each do |m|
32
+ method = m.attributes['method'] || m.qname.name
33
+ add_request_star_out(index,interface,des,method,m.attributes['out'])
34
+ end
35
+ desres.find("des:*[not(name()='resource') and not(name()='websocket') and not(@in)]").each do |m|
36
+ method = m.attributes['method'] || m.qname.name
37
+ add_request_star_out(index,interface,des,method,m.attributes['out'])
38
+ end
39
+ desres.find("des:*[not(name()='resource') and not(name()='websocket') and @pass and @pass='*']").each do |m|
40
+ method = m.attributes['method'] || m.qname.name
41
+ add_request_pass(index,interface,method)
42
+ end
43
+ desres.find("des:*[not(name()='resource') and name()='websocket']").each do |m|
44
+ add_websocket(index,interface)
45
+ end
46
+ @role = desres.find("string(@role)")
47
+ @role = nil if @role.strip == ''
48
+ #}}}
49
+ end
50
+
51
+ # TODO add websockets
52
+
53
+ def remove_access_methods(des,filter)
54
+ #{{{
55
+ freq = if filter['in'] && filter['in'] != '*'
56
+ t = [RequestInOut,Riddl::Wrapper::Description::Message.new(des,filter['in'])]
57
+ t << (filter['out'] ? Riddl::Wrapper::Description::Message.new(des,filter['out']) : nil)
58
+ elsif filter['pass'] && filter['pass'] != '*'
59
+ [RequestInOut,Riddl::Wrapper::Description::Message.new(des,filter['pass']),Riddl::Wrapper::Description::Message.new(des,filter['pass'])]
60
+ elsif filter['in'] && filter['in'] == '*'
61
+ t = [RequestStarOut]
62
+ t << (filter['out'] ? Riddl::Wrapper::Description::Message.new(des,filter['out']) : nil)
63
+ elsif filter['transformation']
64
+ [RequestTransformation,Riddl::Wrapper::Description::Transformation.new(des,filter['transformation'])]
65
+ elsif filter['pass'] && filter['pass'] == '*'
66
+ [RequestPass]
67
+ end
68
+ raise BlockError, "blocking #{filter.inspect} not possible" if freq.nil?
69
+
70
+ if reqs = @access_methods[filter['method']]
71
+ reqs = reqs.last # current layer
72
+ reqs.delete_if do |req|
73
+ if req.class == freq[0]
74
+ if req.class == RequestInOut
75
+ if freq[1] && freq[1].hash == req.in.hash && freq[2] && req.out && freq[2].hash == req.out.hash
76
+ true
77
+ elsif freq[1] && freq[1].hash == req.in.hash && !freq[2]
78
+ true
79
+ end
80
+ elsif req.class == RequestStarOut
81
+ true if freq[1] && req.out && freq[1].hash == req.out.hash
82
+ elsif req.class == RequestTransformation
83
+ true if freq[1] && freq[1].hash == req.trans.hash
84
+ elsif req.class == RequestPass
85
+ true
86
+ end
87
+ end
88
+ end
89
+ end
90
+ #}}}
91
+ end
92
+
93
+ def compose!
94
+ #{{{
95
+ @access_methods.each do |k,v|
96
+ ### remove all emtpy layers
97
+ v.compact!
98
+ case v.size
99
+ when 0
100
+ when 1
101
+ @composition[k] = compose_plain(v[0])
102
+ else
103
+ @composition[k] = compose_layers(k,v)
104
+ end
105
+ end
106
+ #}}}
107
+ end
108
+
109
+ def compose_layers(k,layers)
110
+ #{{{
111
+ routes = []
112
+ layers[0].find_all{|l|l.class==RequestInOut}.each do |r|
113
+ traverse_layers(container = [[r]],container[0],layers,1) unless r.used?
114
+ routes += container unless container.nil?
115
+ end
116
+ layers[0].find_all{|l|l.class==RequestTransformation}.each do |r|
117
+ traverse_layers(container = [[r]],container[0],layers,1) unless r.used?
118
+ routes += container unless container.nil?
119
+ end
120
+ layers[0].find_all{|l|l.class==RequestStarOut}.each do |r|
121
+ traverse_layers(container = [[r]],container[0],layers,1) unless r.used?
122
+ routes += container unless container.nil?
123
+ end
124
+ layers[0].find_all{|l|l.class==RequestPass}.each do |r|
125
+ traverse_layers(container = [[r]],container[0],layers,1) unless r.used?
126
+ routes += container unless container.nil?
127
+ end
128
+ routes.map do |r|
129
+ ret = nil
130
+ teh_last = r.last
131
+ begin
132
+ success = true
133
+ if r.first.respond_to?(:in) && teh_last.respond_to?(:out)
134
+ #1: responds first in + last out -> new InOut
135
+ ret = RequestInOut.new_from_message(r.first.in,teh_last.out)
136
+ elsif r.first.class == RequestTransformation && teh_last.class == RequestTransformation && teh_last.out.nil?
137
+ #2: first transform + last transform -> merge transformations
138
+ ret = RequestTransformation.new_from_transformation(r.first.trans,teh_last.trans)
139
+ elsif teh_last.respond_to?(:out)
140
+ #3: responds last out only -> new StarOut
141
+ ret = RequestStarOut.new_from_message(teh_last.out)
142
+ elsif teh_last.class == RequestPass
143
+ #4: last pass -> remove last until #1 or #2 or #3 or size == 1
144
+ if r.size > 1
145
+ teh_last = r[-2]
146
+ success = false
147
+ else
148
+ ret = teh_last
149
+ end
150
+ end
151
+ end while !success
152
+ Composition.new(r,ret)
153
+ end
154
+ #}}}
155
+ end
156
+ private :compose_layers
157
+
158
+ def compose_plain(access_method)
159
+ #{{{
160
+ access_method.map do |ret|
161
+ Composition.new(nil,ret)
162
+ end
163
+ #}}}
164
+ end
165
+ private :compose_plain
166
+
167
+ def traverse_layers(container,path,layers,layer)
168
+ #{{{
169
+ return if layers.count <= layer
170
+ current = path.last
171
+ current_path = path.dup
172
+
173
+ # messages RequestInOut and RequestStarOut with no out are not processed
174
+ return if ((current.class == RequestInOut || current.class == RequestStarOut) && current.out.nil?)
175
+
176
+ if current.class == RequestInOut ||
177
+ (current.class == RequestTransformation && !current.out.nil?) ||
178
+ current.class == RequestStarOut
179
+ # Find all where "in" matches
180
+ layers[layer].find_all{ |l| (l.class == RequestInOut && l.in.traverse?(current.out) && !l.used?) }.each do |r|
181
+ path << r
182
+ path.last.used = true
183
+ traverse_layers(container,path,layers,layer+1)
184
+ return
185
+ end
186
+ # Find all possible transformations and apply them
187
+ layers[layer].find_all{ |l| l.class == RequestTransformation }.each_with_index do |r,num|
188
+ if num > 0
189
+ path = current_path.dup
190
+ container << path
191
+ end
192
+ path << r.transform(current)
193
+ r.used = true
194
+ traverse_layers(container,path,layers,layer+1)
195
+ end
196
+ # Find all in=* matches, they are all potential matches, even when used
197
+ layers[layer].find_all{ |l| l.class == RequestPass || l.class == RequestStarOut }.each_with_index do |r,num|
198
+ add_to_path_and_split(container,path,layers,layer,num,current_path,r)
199
+ end
200
+ return
201
+ end
202
+
203
+ if (current.class == RequestTransformation && current.out.nil?) ||
204
+ current.class == RequestPass
205
+ # all unused RequestInOut and all others (even if used)
206
+ layers[layer].find_all{|l| (l.class == RequestInOut && !l.used?) || (l.class != RequestInOut) }.each_with_index do |r,num|
207
+ add_to_path_and_split(container,path,layers,layer,num,current_path,r)
208
+ end
209
+ end
210
+ #}}}
211
+ end
212
+ private :traverse_layers
213
+
214
+ def add_to_path_and_split(container,path,layers,layer,num,current_path,r)
215
+ #{{{
216
+ if num > 0
217
+ path = current_path.dup
218
+ container << path
219
+ end
220
+ path << r
221
+ path.last.used = true
222
+ traverse_layers(container,path,layers,layer+1)
223
+ #}}}
224
+ end
225
+ private :add_to_path_and_split
226
+
227
+ # add requests helper methods
228
+ #{{{
229
+ def add_request_in_out(index,interface,des,method,min,mout)
230
+ @access_methods[method] ||= []
231
+ @access_methods[method][index] ||= []
232
+ @access_methods[method][index] << RequestInOut.new(des,min,mout,interface)
233
+ end
234
+ private :add_request_in_out
235
+
236
+ def add_request_transform(index,interface,des,method,mtrans)
237
+ @access_methods[method] ||= []
238
+ @access_methods[method][index] ||= []
239
+ @access_methods[method][index] << RequestTransformation.new(des,mtrans,interface)
240
+ end
241
+ private :add_request_transform
242
+
243
+ def add_request_star_out(index,interface,des,method,mout)
244
+ @access_methods[method] ||= []
245
+ @access_methods[method][index] ||= []
246
+ @access_methods[method][index] << RequestStarOut.new(des,mout,interface)
247
+ end
248
+ private :add_request_star_out
249
+
250
+ def add_request_pass(index,interface,method)
251
+ @access_methods[method] ||= []
252
+ @access_methods[method][index] ||= []
253
+ @access_methods[method][index] << RequestPass.new(interface)
254
+ end
255
+ private :add_request_pass
256
+
257
+ def add_websocket(index,interface)
258
+ @access_methods['websocket'] ||= []
259
+ @access_methods['websocket'][index] ||= []
260
+ @access_methods['websocket'][index] << WebSocket.new(interface)
261
+ end
262
+ private :add_request_pass
263
+ #}}}
264
+
265
+ attr_reader :resources,:path,:access_methods,:composition,:recursive,:role
266
+ end
267
+
268
+ Composition = Struct.new(:route,:result)
269
+ end
270
+ end
271
+ end
@@ -0,0 +1,33 @@
1
+ module Riddl
2
+ class Wrapper
3
+ class LayerChecker
4
+ def initialize(doc)
5
+ @doc = doc
6
+ end
7
+
8
+ def check
9
+ check_layers(@doc.find("/dec:declaration/dec:facade/dec:tile/dec:layer"))
10
+ end
11
+
12
+ def check_layers(res)
13
+ #{{{
14
+ messages = []
15
+ res.each do |res|
16
+ messages += check_field(res.attributes['name'],res.parent.attributes['relative'] || '/')
17
+ end
18
+ messages
19
+ #}}}
20
+ end
21
+ private :check_layers
22
+
23
+ def check_field(name,tile)
24
+ #{{{
25
+ if @doc.find("/dec:declaration/dec:interface[@name='#{name}']").empty?
26
+ ["Tile '#{tile}': interface '#{name}' not found."]
27
+ end || []
28
+ #}}}
29
+ end
30
+ private :check_field
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,221 @@
1
+ module Riddl
2
+ class Wrapper
3
+ class MessageParser
4
+ def initialize(params,headers)
5
+ #{{{
6
+ @mist = params
7
+ @mistp = 0
8
+ @headp = {}
9
+ headers.each do |k,v|
10
+ if v.nil?
11
+ @headp[k.name.upcase.gsub(/\-/,'_')] = k.value
12
+ else
13
+ @headp[k.upcase.gsub(/\-/,'_')] = v
14
+ end
15
+ end
16
+
17
+ @numparams = 0
18
+ #}}}
19
+ end
20
+
21
+ def check(what,ignore_name=false)
22
+ #{{{
23
+ # reset for subsequent calls
24
+ @mistp = 0
25
+ @numparams = 0
26
+
27
+ # out not available
28
+ return true if what.nil? && @mist.empty?
29
+
30
+ # do it
31
+ m = what.content.root
32
+
33
+ m.find("des:header").each do |h|
34
+ return false unless header h
35
+ end
36
+
37
+ if ignore_name
38
+ # if only one parameter, ignore the name
39
+ @numparams = m.find("count(//des:parameter)").to_i
40
+ end
41
+
42
+ m.find("des:*[not(name()='header')]").each do |p|
43
+ return false unless send p.qname.to_s, p
44
+ end
45
+
46
+ @mist.count == @mistp
47
+ #}}}
48
+ end
49
+
50
+ def parameter(a)
51
+ #{{{
52
+ return false if @mistp >= @mist.length
53
+ b = @mist[@mistp]
54
+
55
+ if b.class == Riddl::Parameter::Simple && (a.attributes['fixed'] || a.attributes['type'])
56
+ b.name = a.attributes['name'] if @numparams == 1
57
+ if (b.name == a.attributes['name'] || a.attributes['name'] == '*')
58
+ @mistp += 1
59
+ return match_simple(a,b.value)
60
+ end
61
+ end
62
+ if b.class == Riddl::Parameter::Complex && a.attributes['mimetype']
63
+ b.name = a.attributes['name'] if @numparams == 1
64
+ if (b.name == a.attributes['name'] || a.attributes['name'] == '*') && match_mimetype(a,b)
65
+ if a.attributes['handler']
66
+ if Riddl::Handlers::handlers[a.attributes['handler']]
67
+ success = Riddl::Handlers::handlers[a.attributes['handler']].handle(b.value,a.children.map{|e|e.dump}.join)
68
+ b.value.rewind if b.value.respond_to?(:rewind)
69
+ if success
70
+ @mistp += 1
71
+ return true
72
+ end
73
+ else
74
+ # handler not found leads to an error
75
+ return false
76
+ end
77
+ else
78
+ @mistp += 1
79
+ return true
80
+ end
81
+ end
82
+ end
83
+ false
84
+ #}}}
85
+ end
86
+ private :parameter
87
+
88
+ def oneOrMore(a)
89
+ #{{{
90
+ tistp = @mistp
91
+ ncounter = 0
92
+ begin
93
+ counter,length = traverse_simple(a,true)
94
+ ncounter += 1 if counter == length
95
+ end while counter == length && @mistp < @mist.length
96
+ if ncounter > 0
97
+ true
98
+ else
99
+ @mistp = tistp
100
+ false
101
+ end
102
+ #}}}
103
+ end
104
+
105
+ def zeroOrMore(a)
106
+ #{{{
107
+ begin
108
+ counter,length = traverse_simple(a,true)
109
+ end while counter == length && @mistp < @mist.length
110
+ true
111
+ #}}}
112
+ end
113
+
114
+ def choice(a)
115
+ #{{{
116
+ a.find("des:*").each do |p|
117
+ return true if send p.qname.to_s, p
118
+ end
119
+ false
120
+ #}}}
121
+ end
122
+
123
+ def group(a)
124
+ #{{{
125
+ tistp = @mistp
126
+ success = true
127
+ a.find("des:*").each do |p|
128
+ unless send p.qname.to_s, p
129
+ success = false
130
+ break
131
+ end
132
+ end
133
+ if success
134
+ true
135
+ else
136
+ @mistp = tistp
137
+ false
138
+ end
139
+ #}}}
140
+ end
141
+
142
+ def optional(a)
143
+ #{{{
144
+ return true if @mistp >= @mist.length
145
+
146
+ tistp = @mistp
147
+ counter, length = traverse_simple(a,true)
148
+
149
+ if counter == 0 || counter == length
150
+ true
151
+ else
152
+ @mistp = tistp
153
+ false
154
+ end
155
+ #}}}
156
+ end
157
+
158
+ def header(a)
159
+ #{{{
160
+ name = a.attributes['name'].upcase.sub(/\-/,'_')
161
+ if @headp[name]
162
+ return match_simple(a,@headp[name])
163
+ end
164
+ false
165
+ #}}}
166
+ end
167
+ private :header
168
+
169
+ def traverse_simple(a,single_optional_protection=false)
170
+ #{{{
171
+ tistp = @mistp
172
+ nodes = a.find("des:*")
173
+ counter = 0
174
+ lastname = ''
175
+ nodes.each do |p|
176
+ lastname = p.qname.to_s
177
+ counter += 1 if send lastname, p
178
+ end
179
+ if single_optional_protection && lastname == 'optional' && tistp == @mistp
180
+ [0,-1]
181
+ else
182
+ [counter,nodes.length]
183
+ end
184
+ #}}}
185
+ end
186
+
187
+ def match_simple(a,b)
188
+ #{{{
189
+ if a.attributes['fixed']
190
+ a.attributes['fixed'] == b
191
+ else
192
+ value = XML::Smart::string("<check/>")
193
+ value.root.text = b
194
+ type = XML::Smart::string(CHECK)
195
+ data = type.root.children[0]
196
+ data.attributes['type'] = a.attributes['type']
197
+ data.add a.children
198
+ value.validate_against type
199
+ end
200
+ #}}}
201
+ end
202
+ private :match_simple
203
+
204
+ def match_mimetype(a,b)
205
+ if (a.attributes['mimetype'] == '*' || b.mimetype == a.attributes['mimetype'])
206
+ true
207
+ else
208
+ ma = a.attributes['mimetype'].split('/')
209
+ mb = b.mimetype.split('/')
210
+ if ma.length == 2 && mb.length == 2 && ((ma[0] == mb[0] && ma[1] == '*') || (ma[1] == mb[1] && ma[0] == '*'))
211
+ true
212
+ else
213
+ false
214
+ end
215
+ end
216
+ end
217
+ private :match_mimetype
218
+
219
+ end
220
+ end
221
+ end