barx 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.
- data/MIT-LICENSE +21 -0
- data/README +11 -0
- data/examples/README +78 -0
- data/examples/dix +47 -0
- data/examples/proxri +41 -0
- data/examples/xrioid +66 -0
- data/lib/barx.rb +9 -0
- data/lib/httpclient/cacert.p7s +952 -0
- data/lib/httpclient/cookie.rb +541 -0
- data/lib/httpclient/http.rb +602 -0
- data/lib/httpclient.rb +2054 -0
- data/lib/xri_parser.rb +163 -0
- data/lib/xri_resolver.rb +1056 -0
- data/test/authority_parser_profile.rb +14 -0
- data/test/test_helper.rb +7 -0
- data/test/unit/all.rb +7 -0
- data/test/unit/authority_parser_test.rb +300 -0
- data/test/unit/input_param_parser_test.rb +93 -0
- data/test/unit/multi_stage_resolution_test.rb +44 -0
- data/test/unit/sep_selection_test.rb +87 -0
- data/test/xrds_sources/empty_sels_no_append_qxri_sep +13 -0
- data/test/xrds_sources/empty_sels_sep +13 -0
- data/test/xrds_sources/first_stage1 +38 -0
- data/test/xrds_sources/no_svc_sep +8 -0
- data/test/xrds_sources/second_stage1 +26 -0
- data/test/xrds_sources/sep1 +31 -0
- metadata +82 -0
data/lib/xri_resolver.rb
ADDED
@@ -0,0 +1,1056 @@
|
|
1
|
+
module XriResolver
|
2
|
+
XRDNS = {"xrd"=>"xri://$xrd*($v*2.0)"} # XRD xml namespace
|
3
|
+
XRDType = "xri://$res*auth*($v*2.0)"
|
4
|
+
|
5
|
+
class XRIRoot
|
6
|
+
class <<self
|
7
|
+
def authorities
|
8
|
+
{ "@"=>{:authority_uris=>["https://at.xri.net/", "http://at.xri.net/"], :verify_server_cert=>true},
|
9
|
+
"="=>{:authority_uris=>["https://equal.xri.net/", "http://equal.xri.net/"], :verify_server_cert=>true}}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class AuthorityResolver
|
15
|
+
## usage:
|
16
|
+
## resolved = XriResolver::AuthorityResolver.resolve(xri, req, stack)
|
17
|
+
##
|
18
|
+
## where xri is an XriParser::AuthorityParser object or an XRI string,
|
19
|
+
## req is an XriResolver::ParseInputParameters object.
|
20
|
+
## stack is an array of nested Ref(s) or Redirect(s) used for cycle detection
|
21
|
+
##
|
22
|
+
## Returns an XriResolver::AuthorityResolver object.
|
23
|
+
class <<self
|
24
|
+
def resolve(xri, req_obj=nil, stack=nil)
|
25
|
+
## create default ParseInputParameters object unless one is passed in
|
26
|
+
req = (XriResolver::ParseInputParameters === req_obj) ? req_obj : XriResolver::ParseInputParameters.new
|
27
|
+
self.new(xri, req, stack)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :xri, :xrd_array, :resolved, :status_code, :client, :req
|
32
|
+
|
33
|
+
def initialize(xri, req, stack)
|
34
|
+
if XriParser::AuthorityParser === xri
|
35
|
+
@xri = xri
|
36
|
+
else
|
37
|
+
begin
|
38
|
+
@xri = XriParser::AuthorityParser.parse(xri)
|
39
|
+
rescue Exception
|
40
|
+
raise
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
@req = req
|
45
|
+
@stack = stack || Array.new
|
46
|
+
|
47
|
+
if @req.saml
|
48
|
+
sts = REXML::Element.new('Status')
|
49
|
+
sts.add_attribute('code', code = '201')
|
50
|
+
sts.text = 'SAML_NOT_IMPLEMENTED'
|
51
|
+
xrd = REXML::Element.new('XRD')
|
52
|
+
xrd << sts
|
53
|
+
@xrd_array = Array.new
|
54
|
+
@xrd_array << xrd
|
55
|
+
return
|
56
|
+
end
|
57
|
+
|
58
|
+
raise UNKNOWN_ROOT, @xri.root unless Hash === XRIRoot.authorities[@xri.root]
|
59
|
+
@client = HTTPClient.new
|
60
|
+
@client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE unless XRIRoot.authorities[@xri.root][:verify_server_cert]
|
61
|
+
@accept_header = 'application/xrds+xml'
|
62
|
+
@accept_header << ';https=true' if @req.https
|
63
|
+
|
64
|
+
@xrd_array = Array.new
|
65
|
+
@resolved = "#{@xri.root}"
|
66
|
+
@status_code = String.new ## status code of the last resolved subsegment
|
67
|
+
subsegments_resolved = 0
|
68
|
+
|
69
|
+
@authority_uris = XRIRoot.authorities[@xri.root][:authority_uris].map {|u| URI.parse(u)}
|
70
|
+
|
71
|
+
errorcode = '220'
|
72
|
+
errortext = ResponseStatus[errorcode]
|
73
|
+
|
74
|
+
@xri.subsegments.each do |subsegment|
|
75
|
+
begin
|
76
|
+
@authority_uris = resolve_next(subsegment)
|
77
|
+
rescue Exception => e
|
78
|
+
errorcode = '220'
|
79
|
+
errortext = e.message
|
80
|
+
break
|
81
|
+
end
|
82
|
+
subsegments_resolved += 1
|
83
|
+
if @authority_uris.empty? or !@authority_uris.all?{|r| URI === r}
|
84
|
+
if subsegments_resolved < @xri.subsegments.length
|
85
|
+
if ele = (@xrd_array.last.elements['Ref'] or @xrd_array.last.elements['Redirect'])
|
86
|
+
@newreq = @req.clone
|
87
|
+
@newreq.sep = true
|
88
|
+
@newreq.nodefault_t = true
|
89
|
+
@newreq.service_type = XRDType
|
90
|
+
if ele.name == 'Ref'
|
91
|
+
authsep = AuthorityResolver.resolve(ele.text, @newreq, @stack)
|
92
|
+
else
|
93
|
+
authsep = URIAuthorityResolver.new(@xri, @newreq, doappend(ele).text, @stack)
|
94
|
+
end
|
95
|
+
if authsep.resolved_successfully? and not authsep.to_urilist.empty?
|
96
|
+
@xrd_array << authsep.to_xrds.root
|
97
|
+
@authority_uris = authsep.to_urilist.collect { |uri| URI.parse(uri) }
|
98
|
+
end
|
99
|
+
else
|
100
|
+
errorcode = @req.https ? '231' : '221'
|
101
|
+
errortext = ResponseStatus[errorcode]
|
102
|
+
break
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
if subsegments_resolved < @xri.subsegments.length
|
109
|
+
sts = REXML::Element.new('Status')
|
110
|
+
sts.add_attribute('code', errorcode)
|
111
|
+
sts.text = errortext
|
112
|
+
qry = REXML::Element.new('Query')
|
113
|
+
qry.text = CGI.unescape(@xri.subsegments[subsegments_resolved]).unpack('C*').pack('U*')
|
114
|
+
xrd = REXML::Element.new('XRD')
|
115
|
+
xrd << qry
|
116
|
+
xrd << sts
|
117
|
+
@xrd_array << xrd
|
118
|
+
end
|
119
|
+
|
120
|
+
serverstatus!
|
121
|
+
completeuris!
|
122
|
+
canonical!
|
123
|
+
references!
|
124
|
+
redirects!
|
125
|
+
services!
|
126
|
+
end
|
127
|
+
|
128
|
+
def resolved_successfully?
|
129
|
+
@resolved == CGI.unescape(@xri.fully_qualified) and @status_code === '100'
|
130
|
+
end
|
131
|
+
|
132
|
+
def to_s
|
133
|
+
build_xrds(@xrd_array)
|
134
|
+
end
|
135
|
+
|
136
|
+
def to_xrds
|
137
|
+
REXML::Document.new(self.to_s)
|
138
|
+
end
|
139
|
+
|
140
|
+
def last_xrd
|
141
|
+
if @xrd_array.last.root.name == 'XRDS'
|
142
|
+
REXML::XPath.match(@xrd_array.last.root, "//*[local-name()='XRD']", XRDNS).last
|
143
|
+
else
|
144
|
+
@xrd_array.last.root
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def last_xrd_doc
|
149
|
+
REXML::Document.new(self.last_xrd.to_s.unpack('U*').pack('C*'))
|
150
|
+
end
|
151
|
+
|
152
|
+
def doappend(uri, inplace=nil)
|
153
|
+
uri_txt = uri.text
|
154
|
+
uri_app = uri.attribute('append').value rescue 'none'
|
155
|
+
|
156
|
+
unless uri_app == 'none'
|
157
|
+
uri_txt.slice!(/\/$/)
|
158
|
+
uri_txt.insert(-1, '/')
|
159
|
+
end
|
160
|
+
|
161
|
+
case uri_app
|
162
|
+
when 'local'
|
163
|
+
uri_txt << @xri.local
|
164
|
+
when 'authority'
|
165
|
+
uri_txt << @xri.authority
|
166
|
+
when 'path'
|
167
|
+
uri_txt << @xri.path
|
168
|
+
when 'query'
|
169
|
+
uri_txt << @xri.query
|
170
|
+
when 'qxri'
|
171
|
+
uri_txt << @xri.qxri
|
172
|
+
end
|
173
|
+
|
174
|
+
if inplace
|
175
|
+
uri.text = uri_txt
|
176
|
+
uri.delete_attribute('append')
|
177
|
+
uri
|
178
|
+
else
|
179
|
+
newuri = REXML::Element.new('URI')
|
180
|
+
newuri.text = uri_txt
|
181
|
+
newuri
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def to_urilist
|
186
|
+
SEPSelector.select(self, self.last_xrd, @req).to_urilist
|
187
|
+
end
|
188
|
+
|
189
|
+
private
|
190
|
+
Retn = Struct.new(:header, :content)
|
191
|
+
def fetch_uri(uri)
|
192
|
+
if defined? Cache
|
193
|
+
key = uri.host + uri.path + @accept_header
|
194
|
+
if result = Cache.get(key)
|
195
|
+
return Retn.new({'FROM_CACHE' => 'true'}, result)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
begin
|
200
|
+
@client.get_with_redirect(uri, nil, {'Accept'=> @accept_header})
|
201
|
+
rescue Exception
|
202
|
+
raise
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def get_xrd_status_code(xrd)
|
207
|
+
REXML::XPath.match(xrd, "*[local-name()='Status']/@code", XRDNS).first.to_s rescue nil
|
208
|
+
end
|
209
|
+
|
210
|
+
def get_xrd_status_text(xrd)
|
211
|
+
REXML::XPath.match(xrd, "*[local-name()='Status']", XRDNS).first.to_s rescue nil
|
212
|
+
end
|
213
|
+
|
214
|
+
def get_xrd_query_string(xrd)
|
215
|
+
REXML::XPath.match(xrd, "*[local-name()='Query']", XRDNS).first.text rescue nil
|
216
|
+
end
|
217
|
+
|
218
|
+
def get_xrd_next_authority(xrd)
|
219
|
+
auth_uri = Array.new
|
220
|
+
REXML::XPath.match(xrd, "*[local-name()='Service']", XRDNS).each do |svc|
|
221
|
+
if REXML::XPath.match(svc, "*[local-name()='Type']", XRDNS).any? {|x| x.text == XRDType}
|
222
|
+
REXML::XPath.match(svc, "*[local-name()='MediaType']", XRDNS).any? {|x| mediatypeok?(x.text)}
|
223
|
+
REXML::XPath.match(svc, "*[local-name()='URI']", XRDNS).each do |r|
|
224
|
+
if urip = (URI.parse(r.text) rescue nil) and %w{http https}.include?(urip.scheme)
|
225
|
+
unless @req.https and urip.scheme != 'https'
|
226
|
+
auth_uri << [(r.attribute('priority').value rescue 0xFFFF).to_i, r.text]
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
break
|
231
|
+
end
|
232
|
+
end
|
233
|
+
auth_uri.sort{ |a, b| Common.prioritize a[0], b[0] }.map{ |y| y[1] } unless auth_uri.empty?
|
234
|
+
end
|
235
|
+
|
236
|
+
def resolve_next(subsegment)
|
237
|
+
begin
|
238
|
+
uri = @authority_uris.shift
|
239
|
+
uri.path << '/'
|
240
|
+
hxri = uri.merge(subsegment)
|
241
|
+
resolved = fetch_uri(hxri)
|
242
|
+
xml = resolved.content.unpack("C*").pack("U*") #convert to UTF8
|
243
|
+
rescue Exception => e
|
244
|
+
unless @authority_uris.empty?
|
245
|
+
retry
|
246
|
+
else
|
247
|
+
raise XRIResolutionFailure, "Unable to resolve #{subsegment} because: #{e.message}"
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
begin
|
252
|
+
doc = REXML::Document.new(xml)
|
253
|
+
rescue Exception => e
|
254
|
+
raise XRIResolutionFailure, "#{e.message} -- #{xml}"
|
255
|
+
end
|
256
|
+
|
257
|
+
new_authority_uris = Array.new
|
258
|
+
current_xrd_array = Array.new
|
259
|
+
if doc.root.name == "XRDS"
|
260
|
+
REXML::XPath.each(doc, "//*[local-name()='XRD']", XRDNS) do |xrd|
|
261
|
+
@xrd_array << xrd
|
262
|
+
current_xrd_array << xrd
|
263
|
+
@status_code = get_xrd_status_code(xrd)
|
264
|
+
@status_text = get_xrd_status_text(xrd)
|
265
|
+
if @status_code == '100'
|
266
|
+
@resolved << get_xrd_query_string(xrd).unpack("U*").pack("C*")
|
267
|
+
new_authority_uris = get_xrd_next_authority(xrd).map {|u| URI.parse(u)} rescue []
|
268
|
+
end
|
269
|
+
end
|
270
|
+
if defined?(Cache) and (resolved.header['FROM_CACHE'] != 'true')
|
271
|
+
add_to_cache(resolved.header, current_xrd_array, hxri, xml)
|
272
|
+
end
|
273
|
+
return new_authority_uris
|
274
|
+
else
|
275
|
+
raise INVALID_XRDS, doc.root.name
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def add_to_cache(hdr, current_xrd_array, hxri, xml)
|
280
|
+
header_exp = get_header_exp(hdr)
|
281
|
+
xrd_exp = get_xrd_exp(current_xrd_array)
|
282
|
+
if header_exp and xrd_exp
|
283
|
+
exp = (header_exp < xrd_exp) ? header_exp : xrd_exp
|
284
|
+
elsif header_exp
|
285
|
+
exp = header_exp
|
286
|
+
elsif xrd_exp
|
287
|
+
exp = xrd_exp
|
288
|
+
else
|
289
|
+
exp = Time.now.to_i + 3600
|
290
|
+
end
|
291
|
+
key = hxri.host + hxri.path + @accept_header
|
292
|
+
Cache.add(key, xml, exp)
|
293
|
+
end
|
294
|
+
|
295
|
+
def get_header_exp(hdr)
|
296
|
+
unless hdr['Cache-Control'].empty?
|
297
|
+
return nil if hdr['Cache-Control'].first.downcase == 'no-cache'
|
298
|
+
end
|
299
|
+
unless hdr['Expires'].empty?
|
300
|
+
a = ParseDate.parsedate(hdr['Expires'].first) rescue []
|
301
|
+
if a[6] =~ /\A(GMT|UTC|Z)\z/i
|
302
|
+
return Time.gm(*a).to_i
|
303
|
+
end
|
304
|
+
end
|
305
|
+
nil
|
306
|
+
end
|
307
|
+
|
308
|
+
def get_xrd_exp(xrd_ary)
|
309
|
+
exp_array = Array.new
|
310
|
+
xrd_ary.each do |xrd|
|
311
|
+
d = REXML::XPath.match(xrd, "*[local-name()='Expires']", XRDNS).first.text rescue ''
|
312
|
+
unless d.empty?
|
313
|
+
a = ParseDate.parsedate(d) rescue []
|
314
|
+
if a[6] =~ /\A(GMT|UTC|Z)\z/i
|
315
|
+
exp_array << Time.gm(*a).to_i
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
return exp_array.min
|
320
|
+
end
|
321
|
+
|
322
|
+
def build_xrds(xrd_array)
|
323
|
+
xrds = %Q{<XRDS xmlns="xri://$xrds" ref="xri://#{CGI.unescape(@xri.normalized)}">\n}
|
324
|
+
xrd_array.each {|xrd| xrds << "#{xrd.to_s.unpack('U*').pack('C*')}\n" }
|
325
|
+
xrds << "</XRDS>"
|
326
|
+
end
|
327
|
+
|
328
|
+
def verified?(piduri, ciduri)
|
329
|
+
begin
|
330
|
+
parsed_pid = XriParser::AuthorityParser.parse(piduri)
|
331
|
+
pid_root = parsed_pid.root
|
332
|
+
pid_segs = parsed_pid.subsegments
|
333
|
+
parsed_cid = XriParser::AuthorityParser.parse(ciduri)
|
334
|
+
cid_root = parsed_cid.root
|
335
|
+
cid_segs = parsed_cid.subsegments
|
336
|
+
pid_root == cid_root and pid_segs == cid_segs[0, pid_segs.length]
|
337
|
+
rescue
|
338
|
+
false
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def mediatypeok?(type)
|
343
|
+
mimetype, *subparams = type.split(';').map {|h| h.strip.downcase}
|
344
|
+
verdict = (mimetype == 'application/xrds+xml')
|
345
|
+
verdict &&= @req.https ? (subparams.include?('https=true') or subparam.include?('https=1')) : true
|
346
|
+
verdict &&= @req.saml ? (subparams.include?('saml=true') or subparam.include?('saml=1')) : true
|
347
|
+
end
|
348
|
+
|
349
|
+
def references!
|
350
|
+
@xrd_array.collect! { |ele| Array.new.push(ele) }
|
351
|
+
|
352
|
+
# don't follow Ref's already done as part of authority resolution
|
353
|
+
seen = @xrd_array.collect{ |x| x[0].name == 'XRDS' ? 'xrds' : nil }.compact
|
354
|
+
startflag = (seen.length == 0)
|
355
|
+
@xrd_array.each do |xrd|
|
356
|
+
if xrd[0].name == 'XRDS'
|
357
|
+
startflag = true
|
358
|
+
next
|
359
|
+
end
|
360
|
+
next unless startflag
|
361
|
+
|
362
|
+
REXML::XPath.match(xrd[0], "//*[local-name()='Ref']", XRDNS).each do |ref|
|
363
|
+
if @req.refs.nil? or @req.refs.downcase == 'true' or (@req.refs =~ /\d+/ and @req.refs.to_i > 0)
|
364
|
+
@newreq = @req.clone
|
365
|
+
@newreq.refs = (@req.refs.to_i > 0 and (lessone = @req.refs.to_i - 1)) ? lessone.to_s : @req.refs
|
366
|
+
newxri = XriParser::AuthorityParser.parse(ref.text)
|
367
|
+
newxri.path = @xri.path if @xri.path
|
368
|
+
|
369
|
+
@stack << @xri.authority
|
370
|
+
unless @stack.include?(newxri.authority)
|
371
|
+
refresolver = AuthorityResolver.resolve(newxri, @newreq, @stack)
|
372
|
+
if refresolver.resolved_successfully?
|
373
|
+
xrd << REXML::Document.new(refresolver.to_s).root
|
374
|
+
end
|
375
|
+
else
|
376
|
+
REXML::XPath.match(xrd[0], "*[local-name()='Status']", XRDNS).each do |status|
|
377
|
+
status.add_attribute('refs', 'false')
|
378
|
+
status.text = 'REF_CYCLE_DETECTED'
|
379
|
+
end
|
380
|
+
end
|
381
|
+
@stack.pop
|
382
|
+
else
|
383
|
+
REXML::XPath.match(xrd[0], "*[local-name()='Status']", XRDNS).each do |status|
|
384
|
+
status.add_attribute('refs', 'false')
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
@xrd_array.flatten!
|
391
|
+
end
|
392
|
+
|
393
|
+
def redirects!
|
394
|
+
@xrd_array.collect! { |ele| Array.new.push(ele) }
|
395
|
+
|
396
|
+
# don't follow Redirect's already done as part of authority resolution
|
397
|
+
seen = @xrd_array.collect{ |x| x[0].name == 'XRDS' ? 'xrds' : nil }.compact
|
398
|
+
startflag = (seen.length == 0)
|
399
|
+
@xrd_array.each do |xrd|
|
400
|
+
if xrd[0].name == 'XRDS'
|
401
|
+
startflag = true
|
402
|
+
next
|
403
|
+
end
|
404
|
+
next unless startflag
|
405
|
+
|
406
|
+
REXML::XPath.match(xrd[0], "//*[local-name()='Redirect']", XRDNS).each do |red|
|
407
|
+
unless @stack.include?(reduri = doappend(red).text)
|
408
|
+
@stack << reduri
|
409
|
+
redres = URIAuthorityResolver.new(@xri, @req, reduri, @stack)
|
410
|
+
@stack.pop
|
411
|
+
else
|
412
|
+
responsecode = '250'
|
413
|
+
xrd[0].elements['Status'].text = 'REDIRECT_CYCLE_DETECTED'
|
414
|
+
xrd[0].elements['Status'].add_attribute('code', responsecode)
|
415
|
+
next
|
416
|
+
end
|
417
|
+
|
418
|
+
# do synonym verification
|
419
|
+
synsok = true
|
420
|
+
synlst = %w{ProviderID LocalID EquivID CanonicalID CanonicalEquivID}
|
421
|
+
redres.last_xrd.elements.to_a.each do |ele|
|
422
|
+
if synlst.include?(ele.name)
|
423
|
+
unless xrd[0].elements[ele.name] and xrd[0].elements[ele.name].text == ele.text
|
424
|
+
synsok = false
|
425
|
+
break
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
# if verification succeeds, embed XRDS; else set return code
|
431
|
+
if synsok
|
432
|
+
xrd << redres.to_xrds
|
433
|
+
else
|
434
|
+
responsecode = '251'
|
435
|
+
xrd[0].elements['Status'].text = ResponseStatus[responsecode]
|
436
|
+
xrd[0].elements['Status'].add_attribute('code', responsecode)
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
@xrd_array.flatten!
|
442
|
+
end
|
443
|
+
|
444
|
+
def canonical!
|
445
|
+
return unless @req.cid
|
446
|
+
pid = nil; @xrd_array.each do |xrd|
|
447
|
+
next unless xrd.name == 'XRD'
|
448
|
+
if cid = xrd.elements['CanonicalID']
|
449
|
+
if uricid = (URI.parse(cid.text) rescue nil) and %w{http https}.include?(uricid.scheme)
|
450
|
+
# HTTP(S) URI Verification Rules (13.2.1)
|
451
|
+
uriqry = URI.parse(xrd.elements['Query'].text) rescue nil
|
452
|
+
uricid.fragment = nil
|
453
|
+
xrd.elements['Status'].add_attribute('cid', (uricid == uriqry) ? 'verified' : 'failed')
|
454
|
+
else
|
455
|
+
# XRI Verification Rules (13.2.2)
|
456
|
+
pid ||= xrd.elements['ProviderID'] || REXML::Element.new('ProviderID')
|
457
|
+
xrd.elements['Status'].add_attribute('cid', verified?(pid.text, cid.text) ? 'verified' : 'failed')
|
458
|
+
end
|
459
|
+
else
|
460
|
+
xrd.elements['Status'].add_attribute('cid', 'absent')
|
461
|
+
end
|
462
|
+
pid = xrd.elements['Status'].attribute('cid').value == 'verified' ? cid : nil
|
463
|
+
end
|
464
|
+
|
465
|
+
# perform CanonicalEquivID verification on last XRD in resolution chain
|
466
|
+
xrd = @xrd_array.last
|
467
|
+
if cid = xrd.elements['CanonicalID'] and ceid = xrd.elements['CanonicalEquivID']
|
468
|
+
if xrd.elements['Status'].attributes['cid'] == 'verified'
|
469
|
+
# default to CEID failed
|
470
|
+
xrd.elements['Status'].add_attribute('ceid', 'failed')
|
471
|
+
|
472
|
+
# resolve CanonicalEquivID and test for remote EquivID == local CanonicalEquivID
|
473
|
+
if ceiu = (URI.parse(ceid.text) rescue nil) and %w{http https}.include?(ceiu.scheme)
|
474
|
+
ceiudoc = REXML::Document.new(@client.get_content(ceiu)) rescue nil
|
475
|
+
lastxrd = ceiudoc.elements.to_a("//*[local-name()='XRD']").last if ceiudoc
|
476
|
+
else
|
477
|
+
ceidresolver = AuthorityResolver.resolve(ceid.text) rescue nil
|
478
|
+
lastxrd = ceidresolver.last_xrd if ceidresolver.resolved_successfully?
|
479
|
+
end
|
480
|
+
|
481
|
+
# test for remote EquivID == local CanonicalEquivID
|
482
|
+
if lastxrd and lastxrd.elements.to_a('EquivID').any? {|x| x.text == cid.text}
|
483
|
+
xrd.elements['Status'].add_attribute('ceid', 'verified')
|
484
|
+
end
|
485
|
+
end
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
def completeuris!
|
490
|
+
return unless @req.uric
|
491
|
+
@xrd_array.each do |xrd|
|
492
|
+
next unless xrd.name == 'XRD'
|
493
|
+
xpath = "//*[(local-name()='URI')|(local-name()='Redirect')]"
|
494
|
+
REXML::XPath.match(xrd, xpath, XRDNS).collect!{|uri| doappend(uri, "inplace") }
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
def serverstatus!
|
499
|
+
@xrd_array.each do |xrd|
|
500
|
+
next unless xrd.name == 'XRD'
|
501
|
+
serverstatus = REXML::Element.new('ServerStatus')
|
502
|
+
if xrd.elements['Status']
|
503
|
+
serverstatus.text = xrd.elements['Status'].text
|
504
|
+
serverstatus.add_attributes(xrd.elements['Status'].attributes)
|
505
|
+
end
|
506
|
+
|
507
|
+
newxrdeles = xrd.elements.partition do |x|
|
508
|
+
after = xrd.elements['Status'] || xrd.elements['Query']
|
509
|
+
xrd.elements.index(x) <= xrd.elements.index(after)
|
510
|
+
end
|
511
|
+
|
512
|
+
xrd.elements.delete_all('*')
|
513
|
+
newxrdeles[0].each { |x| xrd.elements << x }
|
514
|
+
xrd.elements << serverstatus
|
515
|
+
newxrdeles[1].each { |x| xrd.elements << x }
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
def noprefix(xri) xri[/(^xri:\/\/)*(.*$)/, 2]; end
|
520
|
+
|
521
|
+
def services!
|
522
|
+
return unless @req.sep
|
523
|
+
|
524
|
+
## make list of XRD elements
|
525
|
+
xrd_list = @xrd_array.clone
|
526
|
+
xrd_list.delete_if { |xrd| xrd.name == 'XRDS' }
|
527
|
+
|
528
|
+
## make list of XRDS elements
|
529
|
+
xrds_list = @xrd_array.clone
|
530
|
+
xrds_list.delete_if { |xrd| not xrd.name == 'XRDS' }
|
531
|
+
|
532
|
+
## do SEP selection on last XRD
|
533
|
+
seplist = SEPSelector.select(self, xrd_list.last, @req)
|
534
|
+
|
535
|
+
if seplist.empty?
|
536
|
+
## no Service elements selected, so delete all Service/Ref(s) or Service/Redirect(s)
|
537
|
+
xpath = "*[local-name()='Service']/*[(local-name()='Ref')|(local-name()='Redirect')]"
|
538
|
+
svclist = REXML::XPath.match(xrd_list.last, xpath, XRDNS).collect do |ele|
|
539
|
+
ele.name == 'Ref' ? noprefix(ele.text) : doappend(ele).text
|
540
|
+
end
|
541
|
+
unless svclist.empty?
|
542
|
+
xrds_list.delete_if do |xrds|
|
543
|
+
svclist.include?(noprefix(xrds.root.attributes['ref'] || xrds.root.attributes['redirect']))
|
544
|
+
end
|
545
|
+
end
|
546
|
+
else
|
547
|
+
## Service elements selected
|
548
|
+
unless seplist.to_urilist.empty?
|
549
|
+
## Service/URI selected at XRD level, so don't follow any Ref(s) or Redirect(s)
|
550
|
+
xrds_list.clear
|
551
|
+
else
|
552
|
+
## Service/Ref or Service/Redirect selected at XRD level, so follow them
|
553
|
+
svclist = Array.new
|
554
|
+
seplist.to_reflist.collect { |svclistxri| noprefix(svclistxri) }.each { |svc| svclist << svc }
|
555
|
+
seplist.to_redlist.collect { |svclisturi| svclisturi }.each { |svc| svclist << svc }
|
556
|
+
unless svclist.empty?
|
557
|
+
xrds_list.delete_if do |xrds|
|
558
|
+
not svclist.include?(noprefix(xrds.root.attributes['ref'] || xrds.root.attributes['redirect']))
|
559
|
+
end
|
560
|
+
end
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
## purge XRDS in which no Service/URI elements are present
|
565
|
+
xrds_list.delete_if do |xrds|
|
566
|
+
lastxrd = REXML::XPath.match(xrds, "//*[local-name()='XRD']", XRDNS).last
|
567
|
+
numuris = REXML::XPath.match(lastxrd, "//*[local-name()='Service']/*[local-name()='URI']", XRDNS).length
|
568
|
+
numuris == 0
|
569
|
+
end
|
570
|
+
|
571
|
+
## break ties among selected Ref(s) or Redirect(s) by priority
|
572
|
+
chosenxrds = nil
|
573
|
+
unless xrds_list.length > 1
|
574
|
+
chosenxrds = xrds_list.first
|
575
|
+
else
|
576
|
+
reflist = Array.new
|
577
|
+
xpath = "//*[(local-name()='Ref')|(local-name()='Redirect')]"
|
578
|
+
REXML::XPath.match(xrd_list.last, xpath, XRDNS).each do |ref|
|
579
|
+
reflist << [(ref.attribute('priority') ? ref.attribute('priority').value : 0xFFFF), ref]
|
580
|
+
end
|
581
|
+
reflist.sort! { |x, y| Common.prioritize x[0], y[0] }
|
582
|
+
reflist.each do |ref|
|
583
|
+
xrds_list.each do |xrds|
|
584
|
+
xrdsid = xrds.root.attributes['ref'] || xrds.root.attributes['redirect']
|
585
|
+
refsid = ref[1].name == 'Ref' ? ref[1].text : doappend(ref[1]).text
|
586
|
+
if noprefix(xrdsid) == noprefix(refsid)
|
587
|
+
chosenxrds = xrds
|
588
|
+
break
|
589
|
+
end
|
590
|
+
end
|
591
|
+
break if chosenxrds
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
## save SEP selection results to object state
|
596
|
+
@xrd_array = xrd_list[0..-2]
|
597
|
+
@xrd_array << seplist.to_xrd
|
598
|
+
@xrd_array << chosenxrds if chosenxrds
|
599
|
+
end
|
600
|
+
end
|
601
|
+
|
602
|
+
ResponseStatus = {
|
603
|
+
'100' => 'SUCCESS',
|
604
|
+
'200' => 'PERM_FAIL',
|
605
|
+
'201' => 'NOT_IMPLEMENTED',
|
606
|
+
'202' => 'LIMIT_EXCEEDED',
|
607
|
+
'210' => 'INVALID_INPUT',
|
608
|
+
'211' => 'INVALID_QXRI',
|
609
|
+
'212' => 'INVALID_RES_MEDIA_TYPE',
|
610
|
+
'213' => 'INVALID_SEP_TYPE',
|
611
|
+
'214' => 'INVALID_SEP_MEDIA_TYPE',
|
612
|
+
'215' => 'UNKNOWN_ROOT',
|
613
|
+
'220' => 'AUTH_RES_ERROR',
|
614
|
+
'221' => 'AUTH_RES_NOT_FOUND',
|
615
|
+
'222' => 'QUERY_NOT_FOUND',
|
616
|
+
'223' => 'UNEXPECTED_XRD',
|
617
|
+
'230' => 'TRUSTED_RES_ERROR',
|
618
|
+
'231' => 'HTTPS_RES_NOT_FOUND',
|
619
|
+
'232' => 'SAML_RES_NOT_FOUND',
|
620
|
+
'233' => 'HTTPS+SAML_RES_NOT_FOUND',
|
621
|
+
'234' => 'UNVERIFIED_SIGNATURE',
|
622
|
+
'240' => 'SEP_SELECTION_ERROR',
|
623
|
+
'241' => 'SEP_NOT_FOUND',
|
624
|
+
'250' => 'REDIRECT_FAILED',
|
625
|
+
'251' => 'REDIRECT_SYNONYM_VERIFICATION_FAILED',
|
626
|
+
'300' => 'TEMPORARY_FAIL',
|
627
|
+
'301' => 'TIMEOUT_ERROR',
|
628
|
+
'320' => 'NETWORK_ERROR',
|
629
|
+
'321' => 'UNEXPECTED_RESPONSE',
|
630
|
+
'322' => 'INVALID_XRDS'
|
631
|
+
}
|
632
|
+
|
633
|
+
class XRIResolutionFailure < RuntimeError; end
|
634
|
+
class INVALID_XRDS < XRIResolutionFailure; end
|
635
|
+
class UNKNOWN_ROOT < XRIResolutionFailure; end
|
636
|
+
|
637
|
+
class SEPSelector
|
638
|
+
## usage:
|
639
|
+
## SEPList = XriResolver::SEPSelector.select(xrds, xrd, inputs)
|
640
|
+
##
|
641
|
+
## where xrds is a valid XriResolver::AuthorityResolver object
|
642
|
+
## where inputs is a valid XriResolver::ParseInputParameters object
|
643
|
+
##
|
644
|
+
## Returns an SEPList object corresponding to the selected SEPs, ordered by priority
|
645
|
+
##
|
646
|
+
## (literally implements pseudocode in XRI Resolution 2.0 WD11 ED03 Section 10.6)
|
647
|
+
##
|
648
|
+
@@sel_categories = ['Path', 'Type', 'MediaType'].freeze
|
649
|
+
@@match_types = ['Positive', 'Default', 'Matched'].freeze
|
650
|
+
|
651
|
+
def SEPSelector.select(xrds, xrd, inputs)
|
652
|
+
## initialize arrays
|
653
|
+
selected_set = SEPList.new(xrds, xrd)
|
654
|
+
default_set = SEPList.new(xrds, xrd)
|
655
|
+
|
656
|
+
## initialize nodefault to false
|
657
|
+
nodefault = Hash.new
|
658
|
+
@@sel_categories.each do |sel_category|
|
659
|
+
nodefault[sel_category] = false
|
660
|
+
end
|
661
|
+
|
662
|
+
## set nodefault according to subparameters
|
663
|
+
nodefault['Path'] = inputs.nodefault_p
|
664
|
+
nodefault['Type'] = inputs.nodefault_t
|
665
|
+
nodefault['MediaType'] = inputs.nodefault_m
|
666
|
+
|
667
|
+
## normalize path
|
668
|
+
path = (xrds.xri.path[0] == ?/) ? xrds.xri.path[1..-1] : xrds.xri.path ## remove leading '/'
|
669
|
+
path = nil if path.empty? ## per XRI Resolution WD11 ED03 Section 6.1.1
|
670
|
+
|
671
|
+
## set content values
|
672
|
+
content = Hash.new
|
673
|
+
content['Path'] = path
|
674
|
+
content['Type'] = inputs.service_type
|
675
|
+
content['MediaType'] = inputs.service_media_type
|
676
|
+
|
677
|
+
## "FOR EACH SEP"
|
678
|
+
REXML::XPath.match(xrd, "*[local-name()='Service']", XRDNS).each do |sep|
|
679
|
+
## "CREATE set of SEL match flags"
|
680
|
+
## "SET all flags to FALSE"
|
681
|
+
flag = Hash.new
|
682
|
+
@@match_types.each do |match_type|
|
683
|
+
flag[match_type] = Hash.new
|
684
|
+
@@sel_categories.each do |sel_category|
|
685
|
+
flag[match_type][sel_category] = false
|
686
|
+
end
|
687
|
+
end
|
688
|
+
|
689
|
+
## we now have
|
690
|
+
## flag = {"Default"=>{"MediaType"=>false, "Type"=>false, "Path"=>false},
|
691
|
+
## "Positive"=>{"MediaType"=>false, "Type"=>false, "Path"=>false},
|
692
|
+
## "Matched"=>{"MediaType"=>false, "Type"=>false, "Path"=>false}}
|
693
|
+
|
694
|
+
## "FOR EACH SEL of category x (where x=Type, Path or Mediatype)"
|
695
|
+
REXML::XPath.match(sep, "*[(local-name()='Type')|(local-name()='Path')|(local-name()='MediaType')]", XRDNS).each do |sel|
|
696
|
+
## record match result on this sel
|
697
|
+
this_sel_positive_match = false
|
698
|
+
this_sel_default_match = false
|
699
|
+
|
700
|
+
## "SET Matched.x=TRUE"
|
701
|
+
flag['Matched'][sel.name] = true
|
702
|
+
|
703
|
+
## determine match
|
704
|
+
if sel.attributes['match'] and sel.attributes['match'] != 'content'
|
705
|
+
## determine match per XRI Resolution WD11 ED03 Section 10.3.2
|
706
|
+
case sel.attributes['match']
|
707
|
+
when 'any'
|
708
|
+
this_sel_positive_match = true
|
709
|
+
when 'default'
|
710
|
+
this_sel_default_match = true
|
711
|
+
when 'non-null'
|
712
|
+
if content[sel.name]
|
713
|
+
this_sel_positive_match = true
|
714
|
+
end
|
715
|
+
when 'null'
|
716
|
+
unless content[sel.name]
|
717
|
+
this_sel_positive_match = true
|
718
|
+
end
|
719
|
+
end
|
720
|
+
else
|
721
|
+
## if no match attribute, see if there's a content match per 10.3.6, 10.3.7 and 10.3.8
|
722
|
+
## TODO: implement normalization and other rules per these three sections
|
723
|
+
if sel.text and content[sel.name]
|
724
|
+
if sel.text.downcase == content[sel.name].downcase
|
725
|
+
this_sel_positive_match = true
|
726
|
+
end
|
727
|
+
end
|
728
|
+
end
|
729
|
+
|
730
|
+
## "IF match on this SEL is POSITIVE"
|
731
|
+
if this_sel_positive_match
|
732
|
+
if sel.attributes['select'] == 'true'
|
733
|
+
## sec 10.4.2
|
734
|
+
selected_set << [sep]
|
735
|
+
break
|
736
|
+
else
|
737
|
+
flag['Positive'][sel.name] = true
|
738
|
+
next
|
739
|
+
end
|
740
|
+
elsif this_sel_default_match
|
741
|
+
## if we have already found a positive SEL with the same name - sec. 10.3.5
|
742
|
+
if flag['Positive'][sel.name]
|
743
|
+
next
|
744
|
+
elsif nodefault[sel.name]
|
745
|
+
next
|
746
|
+
else
|
747
|
+
flag['Default'][sel.name] = true
|
748
|
+
next
|
749
|
+
end
|
750
|
+
else
|
751
|
+
next
|
752
|
+
end
|
753
|
+
|
754
|
+
## "End FOR EACH SEL"
|
755
|
+
end
|
756
|
+
|
757
|
+
## "IF Matched.x=FALSE" (Sec 10.3.3)
|
758
|
+
flag['Matched'].each do |selname, value|
|
759
|
+
unless value ## this SEL hasn't been matched, i.e. was missing
|
760
|
+
flag['Default'][selname] = true unless nodefault[selname]
|
761
|
+
end
|
762
|
+
end
|
763
|
+
|
764
|
+
## "IF Positive.Type=TRUE AND [...]"
|
765
|
+
if flag['Positive']['Type'] and
|
766
|
+
flag['Positive']['Path'] and
|
767
|
+
flag['Positive']['MediaType']
|
768
|
+
selected_set << [sep]
|
769
|
+
next
|
770
|
+
end
|
771
|
+
|
772
|
+
if (flag['Positive']['Type'] or flag['Default']['Type']) and
|
773
|
+
(flag['Positive']['Path'] or flag['Default']['Path']) and
|
774
|
+
(flag['Positive']['MediaType'] or flag['Default']['MediaType'])
|
775
|
+
default_set << [sep, flag.dup]
|
776
|
+
end
|
777
|
+
|
778
|
+
## "END FOR EACH SEP"
|
779
|
+
end
|
780
|
+
|
781
|
+
## "IF SELECTED SET != EMPTY
|
782
|
+
unless selected_set.empty?
|
783
|
+
return selected_set
|
784
|
+
end
|
785
|
+
|
786
|
+
## "IF DEFAULT SET != EMPTY ;see 10.5.2"
|
787
|
+
unless default_set.empty?
|
788
|
+
default_set.each do |sep, flag|
|
789
|
+
## two positive flags
|
790
|
+
if (flag['Positive']['Type'] and flag['Positive']['Path']) or
|
791
|
+
(flag['Positive']['Type'] and flag['Positive']['MediaType']) or
|
792
|
+
(flag['Positive']['Path'] and flag['Positive']['MediaType'])
|
793
|
+
selected_set << [sep]
|
794
|
+
end
|
795
|
+
end
|
796
|
+
|
797
|
+
## "IF SELECTED SET != EMPTY
|
798
|
+
unless selected_set.empty?
|
799
|
+
return selected_set
|
800
|
+
end
|
801
|
+
|
802
|
+
## "FOR EACH SEP IN DEFAULT SET"
|
803
|
+
default_set.each do |sep, flag|
|
804
|
+
## one positive flag
|
805
|
+
if flag['Positive']['Type'] or
|
806
|
+
flag['Positive']['Path'] or
|
807
|
+
flag['Positive']['MediaType']
|
808
|
+
selected_set << [sep]
|
809
|
+
end
|
810
|
+
end
|
811
|
+
end
|
812
|
+
|
813
|
+
## "IF SELECTED SET != EMPTY
|
814
|
+
unless selected_set.empty?
|
815
|
+
## there was one positive flag in the default set
|
816
|
+
return selected_set
|
817
|
+
else
|
818
|
+
## all default flags
|
819
|
+
return default_set
|
820
|
+
end
|
821
|
+
## "RETURN EMPTY SET"
|
822
|
+
return []
|
823
|
+
end
|
824
|
+
end
|
825
|
+
|
826
|
+
class SEPList < Array
|
827
|
+
def initialize(xrds, xrd)
|
828
|
+
@xrds = xrds
|
829
|
+
@xrd = xrd
|
830
|
+
end
|
831
|
+
|
832
|
+
def to_urilist
|
833
|
+
to_list('URI')
|
834
|
+
end
|
835
|
+
|
836
|
+
def to_reflist
|
837
|
+
to_list('Ref')
|
838
|
+
end
|
839
|
+
|
840
|
+
def to_redlist
|
841
|
+
to_list('Redirect')
|
842
|
+
end
|
843
|
+
|
844
|
+
def uris?
|
845
|
+
not to_list('URI').empty?
|
846
|
+
end
|
847
|
+
|
848
|
+
def refs?
|
849
|
+
not to_list('Ref').empty?
|
850
|
+
end
|
851
|
+
|
852
|
+
def reds?
|
853
|
+
not to_list('Redirect').empty?
|
854
|
+
end
|
855
|
+
|
856
|
+
def to_xrd
|
857
|
+
## sort SEPs by value of "priority" attribute
|
858
|
+
seplist = Array.new
|
859
|
+
self.each do |sep, flag|
|
860
|
+
sep_pri = sep.attribute('priority') ? sep.attribute('priority').value : 0xFFFF
|
861
|
+
seplist << [sep_pri, sep]
|
862
|
+
end
|
863
|
+
seplist.sort! { |x, y| Common.prioritize x[0], y[0] }
|
864
|
+
|
865
|
+
## remove all existing xrd:Service elements
|
866
|
+
newxrd = @xrd.clone
|
867
|
+
REXML::XPath.match(@xrd, "*[not(local-name()='Service')]", XRDNS).each do |ele|
|
868
|
+
newxrd << ele
|
869
|
+
end
|
870
|
+
|
871
|
+
## re-add selected and sorted xrd:Service elements per 6.2.2
|
872
|
+
priority = 0
|
873
|
+
seplist.each do |sep_pri, sep|
|
874
|
+
sep.delete_attribute('priority')
|
875
|
+
sep.add_attribute('priority', priority)
|
876
|
+
priority += 1
|
877
|
+
newxrd << sep
|
878
|
+
end
|
879
|
+
REXML::Document.new(newxrd.to_s)
|
880
|
+
end
|
881
|
+
|
882
|
+
private
|
883
|
+
def to_list(elename)
|
884
|
+
## sort SEPs, and URIs within them, by value of "priority" attribute
|
885
|
+
seplist = Array.new
|
886
|
+
self.each do |sep, flag|
|
887
|
+
sep_pri = sep.attribute('priority') ? sep.attribute('priority').value : 0xFFFF
|
888
|
+
sep_urilist = Array.new
|
889
|
+
REXML::XPath.match(sep, "*[local-name()='#{elename}']", XRDNS).each do |uri|
|
890
|
+
uri_pri = uri.attribute('priority') ? uri.attribute('priority').value : 0xFFFF
|
891
|
+
sep_urilist << [uri_pri, uri]
|
892
|
+
end
|
893
|
+
sep_urilist.sort! { |x, y| Common.prioritize x[0], y[0] }
|
894
|
+
seplist << [sep_pri, sep_urilist]
|
895
|
+
end
|
896
|
+
seplist.sort! { |x, y| Common.prioritize x[0], y[0] }
|
897
|
+
|
898
|
+
## build and return URI list per XRI Resolution Specification Section 10.7
|
899
|
+
urilist = Array.new
|
900
|
+
seplist.each do |sep|
|
901
|
+
sep[1].each do |uri|
|
902
|
+
urilist << @xrds.doappend(uri[1]).text
|
903
|
+
end
|
904
|
+
end
|
905
|
+
urilist
|
906
|
+
end
|
907
|
+
end
|
908
|
+
|
909
|
+
class ParseInputParameters
|
910
|
+
attr_accessor :resolution_media_type,
|
911
|
+
:service_media_type,
|
912
|
+
:service_type,
|
913
|
+
:uric,
|
914
|
+
:sep,
|
915
|
+
:cid,
|
916
|
+
:nodefault_p,
|
917
|
+
:nodefault_t,
|
918
|
+
:nodefault_m,
|
919
|
+
:refs,
|
920
|
+
:https,
|
921
|
+
:saml
|
922
|
+
def initialize(params={}, accept_headers="")
|
923
|
+
if params.instance_of?(Hash)
|
924
|
+
symparams = Hash.new
|
925
|
+
params.each { |k,v| symparams[k.to_sym] = v }
|
926
|
+
params = symparams
|
927
|
+
end
|
928
|
+
param_inputs = parse_query_params(params)
|
929
|
+
header_inputs = parse_accept_headers(accept_headers)
|
930
|
+
|
931
|
+
unless is_blank?(param_inputs[:res_media_type])
|
932
|
+
@resolution_media_type = param_inputs[:res_media_type]
|
933
|
+
@uric = has_true_value?(param_inputs[:uric]) ? true : false
|
934
|
+
@sep = has_true_value?(param_inputs[:sep]) ? true : false
|
935
|
+
@cid = has_false_value?(param_inputs[:cid]) ? false : true
|
936
|
+
@nodefault_p = has_true_value?(param_inputs[:nodefault_p]) ? true : false
|
937
|
+
@nodefault_t = has_true_value?(param_inputs[:nodefault_t]) ? true : false
|
938
|
+
@nodefault_m = has_true_value?(param_inputs[:nodefault_m]) ? true : false
|
939
|
+
@refs = param_inputs[:refs]
|
940
|
+
@https = has_true_value?(param_inputs[:https]) ? true : false
|
941
|
+
@saml = has_true_value?(param_inputs[:saml]) ? true : false
|
942
|
+
else
|
943
|
+
@resolution_media_type = header_inputs[:res_media_type]
|
944
|
+
@uric = has_true_value?(header_inputs[:uric]) ? true : false
|
945
|
+
@sep = has_true_value?(header_inputs[:sep]) ? true : false
|
946
|
+
@cid = has_false_value?(header_inputs[:cid]) ? false : true
|
947
|
+
@nodefault_p = has_true_value?(header_inputs[:nodefault_p]) ? true : false
|
948
|
+
@nodefault_t = has_true_value?(header_inputs[:nodefault_t]) ? true : false
|
949
|
+
@nodefault_m = has_true_value?(header_inputs[:nodefault_m]) ? true : false
|
950
|
+
@refs = header_inputs[:refs]
|
951
|
+
@https = has_true_value?(header_inputs[:https]) ? true : false
|
952
|
+
@saml = has_true_value?(header_inputs[:saml]) ? true : false
|
953
|
+
end
|
954
|
+
|
955
|
+
@service_media_type = param_inputs[:service_media_type]
|
956
|
+
@service_type = param_inputs[:service_type]
|
957
|
+
|
958
|
+
## force sep=true if RMT is text/uri-list or unspecified
|
959
|
+
@sep = true if @resolution_media_type == 'text/uri-list' or is_blank?(@resolution_media_type)
|
960
|
+
end
|
961
|
+
|
962
|
+
def parse_query_params(params)
|
963
|
+
inputs = {:res_media_type => ""}
|
964
|
+
## get Resolution Media type
|
965
|
+
if params.has_key?(:_xrd_r)
|
966
|
+
parsedrof = params[:_xrd_r].split(';') rescue []
|
967
|
+
inputs[:res_media_type] = parsedrof.shift.tr(' ', '+') rescue ''
|
968
|
+
parsedrof.each do |part|
|
969
|
+
k,v = part.downcase.split('=')
|
970
|
+
inputs["#{k}".to_sym] = v unless is_blank?(k)
|
971
|
+
end
|
972
|
+
end
|
973
|
+
## get Service Type selection element
|
974
|
+
inputs[:service_type] = (params.has_key?(:_xrd_t)) ? params[:_xrd_t].tr(' ', '+') : nil
|
975
|
+
## get Service Media Type selection element
|
976
|
+
inputs[:service_media_type] = (params.has_key?(:_xrd_m)) ? params[:_xrd_m].tr(' ', '+') : nil
|
977
|
+
|
978
|
+
inputs
|
979
|
+
end
|
980
|
+
|
981
|
+
def parse_accept_headers(accept_headers)
|
982
|
+
valid_headers = ['application/xrds+xml', 'application/xrd+xml', 'text/uri-list']
|
983
|
+
res_inputs = {:res_media_type => ""}
|
984
|
+
headers = accept_headers.split(',').map {|h| h.strip}
|
985
|
+
headers.each do |hdr|
|
986
|
+
hdr_parts = hdr.split(';')
|
987
|
+
if valid_headers.include?(hdr_parts.first)
|
988
|
+
res_inputs[:res_media_type] = hdr_parts.shift
|
989
|
+
hdr_parts.each do |part|
|
990
|
+
k,v = part.downcase.split('=')
|
991
|
+
res_inputs["#{k}".to_sym] = v unless is_blank?(k)
|
992
|
+
end
|
993
|
+
break
|
994
|
+
end
|
995
|
+
end
|
996
|
+
res_inputs
|
997
|
+
end
|
998
|
+
|
999
|
+
def has_true_value?(test)
|
1000
|
+
test == 'true' or test == '1'
|
1001
|
+
end
|
1002
|
+
|
1003
|
+
def has_false_value?(test)
|
1004
|
+
test == 'false' or test == '0'
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
def is_blank?(test)
|
1008
|
+
test.empty? or test =~ /\A\s*\z/
|
1009
|
+
end
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
class URIAuthorityResolver < AuthorityResolver
|
1013
|
+
def initialize(xri, req, uri, stack)
|
1014
|
+
if uri = (URI.parse(uri) rescue nil) and %w{http https}.include?(uri.scheme)
|
1015
|
+
@xri = xri
|
1016
|
+
@req = req
|
1017
|
+
@uri = uri
|
1018
|
+
@stack = stack || Array.new
|
1019
|
+
@client = HTTPClient.new
|
1020
|
+
uriget = fetch_uri(uri)
|
1021
|
+
urixml = uriget.content.unpack("C*").pack("U*")
|
1022
|
+
uridoc = REXML::Document.new(urixml).root
|
1023
|
+
@xrd_array = REXML::XPath.match(uridoc, "*", XRDNS)
|
1024
|
+
|
1025
|
+
serverstatus!
|
1026
|
+
completeuris!
|
1027
|
+
canonical!
|
1028
|
+
references!
|
1029
|
+
redirects!
|
1030
|
+
services!
|
1031
|
+
end
|
1032
|
+
end
|
1033
|
+
|
1034
|
+
def to_s
|
1035
|
+
self.to_xrds.to_s
|
1036
|
+
end
|
1037
|
+
|
1038
|
+
def to_xrds
|
1039
|
+
xrds = REXML::Element.new('XRDS')
|
1040
|
+
xrds.add_namespace('xri://$xrds')
|
1041
|
+
xrds.add_attribute('redirect', @uri)
|
1042
|
+
@xrd_array.each { |ele| xrds << ele }
|
1043
|
+
xrds
|
1044
|
+
end
|
1045
|
+
|
1046
|
+
def resolved_successfully?
|
1047
|
+
true
|
1048
|
+
end
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
class Common
|
1052
|
+
def Common.prioritize(x, y)
|
1053
|
+
(x.to_i == y.to_i) ? (rand(3) - 1) : (x.to_i <=> y.to_i)
|
1054
|
+
end
|
1055
|
+
end
|
1056
|
+
end
|