ruby-openid2 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +136 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/CONTRIBUTING.md +54 -0
- data/LICENSE.txt +210 -0
- data/README.md +81 -0
- data/SECURITY.md +15 -0
- data/lib/hmac/hmac.rb +110 -0
- data/lib/hmac/sha1.rb +11 -0
- data/lib/hmac/sha2.rb +25 -0
- data/lib/openid/association.rb +246 -0
- data/lib/openid/consumer/associationmanager.rb +354 -0
- data/lib/openid/consumer/checkid_request.rb +179 -0
- data/lib/openid/consumer/discovery.rb +516 -0
- data/lib/openid/consumer/discovery_manager.rb +144 -0
- data/lib/openid/consumer/html_parse.rb +142 -0
- data/lib/openid/consumer/idres.rb +513 -0
- data/lib/openid/consumer/responses.rb +147 -0
- data/lib/openid/consumer/session.rb +36 -0
- data/lib/openid/consumer.rb +406 -0
- data/lib/openid/cryptutil.rb +112 -0
- data/lib/openid/dh.rb +84 -0
- data/lib/openid/extension.rb +38 -0
- data/lib/openid/extensions/ax.rb +552 -0
- data/lib/openid/extensions/oauth.rb +88 -0
- data/lib/openid/extensions/pape.rb +170 -0
- data/lib/openid/extensions/sreg.rb +268 -0
- data/lib/openid/extensions/ui.rb +49 -0
- data/lib/openid/fetchers.rb +277 -0
- data/lib/openid/kvform.rb +113 -0
- data/lib/openid/kvpost.rb +62 -0
- data/lib/openid/message.rb +555 -0
- data/lib/openid/protocolerror.rb +7 -0
- data/lib/openid/server.rb +1571 -0
- data/lib/openid/store/filesystem.rb +260 -0
- data/lib/openid/store/interface.rb +73 -0
- data/lib/openid/store/memcache.rb +109 -0
- data/lib/openid/store/memory.rb +79 -0
- data/lib/openid/store/nonce.rb +72 -0
- data/lib/openid/trustroot.rb +597 -0
- data/lib/openid/urinorm.rb +72 -0
- data/lib/openid/util.rb +119 -0
- data/lib/openid/version.rb +5 -0
- data/lib/openid/yadis/accept.rb +141 -0
- data/lib/openid/yadis/constants.rb +16 -0
- data/lib/openid/yadis/discovery.rb +151 -0
- data/lib/openid/yadis/filters.rb +192 -0
- data/lib/openid/yadis/htmltokenizer.rb +290 -0
- data/lib/openid/yadis/parsehtml.rb +50 -0
- data/lib/openid/yadis/services.rb +44 -0
- data/lib/openid/yadis/xrds.rb +160 -0
- data/lib/openid/yadis/xri.rb +86 -0
- data/lib/openid/yadis/xrires.rb +87 -0
- data/lib/openid.rb +27 -0
- data/lib/ruby-openid.rb +1 -0
- data.tar.gz.sig +0 -0
- metadata +331 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,290 @@
|
|
1
|
+
# = HTMLTokenizer
|
2
|
+
#
|
3
|
+
# Author:: Ben Giddings (mailto:bg-rubyforge@infofiend.com)
|
4
|
+
# Copyright:: Copyright (c) 2004 Ben Giddings
|
5
|
+
# License:: Distributes under the same terms as Ruby
|
6
|
+
#
|
7
|
+
#
|
8
|
+
# This is a partial port of the functionality behind Perl's TokeParser
|
9
|
+
# Provided a page it progressively returns tokens from that page
|
10
|
+
#
|
11
|
+
# $Id: htmltokenizer.rb,v 1.7 2005/06/07 21:05:53 merc Exp $
|
12
|
+
|
13
|
+
#
|
14
|
+
# A class to tokenize HTML.
|
15
|
+
#
|
16
|
+
# Example:
|
17
|
+
#
|
18
|
+
# page = "<HTML>
|
19
|
+
# <HEAD>
|
20
|
+
# <TITLE>This is the title</TITLE>
|
21
|
+
# </HEAD>
|
22
|
+
# <!-- Here comes the <a href=\"missing.link\">blah</a>
|
23
|
+
# comment body
|
24
|
+
# -->
|
25
|
+
# <BODY>
|
26
|
+
# <H1>This is the header</H1>
|
27
|
+
# <P>
|
28
|
+
# This is the paragraph, it contains
|
29
|
+
# <a href=\"link.html\">links</a>,
|
30
|
+
# <img src=\"blah.gif\" optional alt='images
|
31
|
+
# are
|
32
|
+
# really cool'>. Ok, here is some more text and
|
33
|
+
# <A href=\"http://another.link.com/\" target=\"_blank\">another link</A>.
|
34
|
+
# </P>
|
35
|
+
# </body>
|
36
|
+
# </HTML>
|
37
|
+
# "
|
38
|
+
# toke = HTMLTokenizer.new(page)
|
39
|
+
#
|
40
|
+
# assert("<h1>" == toke.getTag("h1", "h2", "h3").to_s.downcase)
|
41
|
+
# assert(HTMLTag.new("<a href=\"link.html\">") == toke.getTag("IMG", "A"))
|
42
|
+
# assert("links" == toke.getTrimmedText)
|
43
|
+
# assert(toke.getTag("IMG", "A").attr_hash['optional'])
|
44
|
+
# assert("_blank" == toke.getTag("IMG", "A").attr_hash['target'])
|
45
|
+
#
|
46
|
+
class HTMLTokenizer
|
47
|
+
@@version = 1.0
|
48
|
+
|
49
|
+
# Get version of HTMLTokenizer lib
|
50
|
+
def self.version
|
51
|
+
@@version
|
52
|
+
end
|
53
|
+
|
54
|
+
attr_reader :page
|
55
|
+
|
56
|
+
# Create a new tokenizer, based on the content, used as a string.
|
57
|
+
def initialize(content)
|
58
|
+
@page = content.to_s
|
59
|
+
@cur_pos = 0
|
60
|
+
end
|
61
|
+
|
62
|
+
# Reset the parser, setting the current position back at the stop
|
63
|
+
def reset
|
64
|
+
@cur_pos = 0
|
65
|
+
end
|
66
|
+
|
67
|
+
# Look at the next token, but don't actually grab it
|
68
|
+
def peekNextToken
|
69
|
+
return if @cur_pos == @page.length
|
70
|
+
|
71
|
+
if "<" == @page[@cur_pos]
|
72
|
+
# Next token is a tag of some kind
|
73
|
+
if "!--" == @page[(@cur_pos + 1), 3]
|
74
|
+
# Token is a comment
|
75
|
+
tag_end = @page.index("-->", (@cur_pos + 1))
|
76
|
+
raise HTMLTokenizerError, "No end found to started comment:\n#{@page[@cur_pos, 80]}" if tag_end.nil?
|
77
|
+
|
78
|
+
# p @page[@cur_pos .. (tag_end+2)]
|
79
|
+
HTMLComment.new(@page[@cur_pos..(tag_end + 2)])
|
80
|
+
else
|
81
|
+
# Token is a html tag
|
82
|
+
tag_end = @page.index(">", (@cur_pos + 1))
|
83
|
+
raise HTMLTokenizerError, "No end found to started tag:\n#{@page[@cur_pos, 80]}" if tag_end.nil?
|
84
|
+
|
85
|
+
# p @page[@cur_pos .. tag_end]
|
86
|
+
HTMLTag.new(@page[@cur_pos..tag_end])
|
87
|
+
end
|
88
|
+
else
|
89
|
+
# Next token is text
|
90
|
+
text_end = @page.index("<", @cur_pos)
|
91
|
+
text_end = text_end.nil? ? -1 : (text_end - 1)
|
92
|
+
# p @page[@cur_pos .. text_end]
|
93
|
+
HTMLText.new(@page[@cur_pos..text_end])
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Get the next token, returns an instance of
|
98
|
+
# * HTMLText
|
99
|
+
# * HTMLToken
|
100
|
+
# * HTMLTag
|
101
|
+
def getNextToken
|
102
|
+
token = peekNextToken
|
103
|
+
if token
|
104
|
+
# @page = @page[token.raw.length .. -1]
|
105
|
+
# @page.slice!(0, token.raw.length)
|
106
|
+
@cur_pos += token.raw.length
|
107
|
+
end
|
108
|
+
# p token
|
109
|
+
# print token.raw
|
110
|
+
token
|
111
|
+
end
|
112
|
+
|
113
|
+
# Get a tag from the specified set of desired tags.
|
114
|
+
# For example:
|
115
|
+
# <tt>foo = toke.getTag("h1", "h2", "h3")</tt>
|
116
|
+
# Will return the next header tag encountered.
|
117
|
+
def getTag(*sought_tags)
|
118
|
+
sought_tags.collect! { |elm| elm.downcase }
|
119
|
+
|
120
|
+
while (tag = getNextToken)
|
121
|
+
if tag.is_a?(HTMLTag) and
|
122
|
+
(0 == sought_tags.length or sought_tags.include?(tag.tag_name))
|
123
|
+
break
|
124
|
+
end
|
125
|
+
end
|
126
|
+
tag
|
127
|
+
end
|
128
|
+
|
129
|
+
# Get all the text between the current position and the next tag
|
130
|
+
# (if specified) or a specific later tag
|
131
|
+
def getText(until_tag = nil)
|
132
|
+
if until_tag.nil?
|
133
|
+
if "<" == @page[@cur_pos]
|
134
|
+
# Next token is a tag, not text
|
135
|
+
""
|
136
|
+
else
|
137
|
+
# Next token is text
|
138
|
+
getNextToken.text
|
139
|
+
end
|
140
|
+
else
|
141
|
+
ret_str = ""
|
142
|
+
|
143
|
+
while (tag = peekNextToken)
|
144
|
+
break if tag.is_a?(HTMLTag) and tag.tag_name == until_tag
|
145
|
+
|
146
|
+
ret_str << (tag.text + " ") if "" != tag.text
|
147
|
+
getNextToken
|
148
|
+
end
|
149
|
+
|
150
|
+
ret_str
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Like getText, but squeeze all whitespace, getting rid of
|
155
|
+
# leading and trailing whitespace, and squeezing multiple
|
156
|
+
# spaces into a single space.
|
157
|
+
def getTrimmedText(until_tag = nil)
|
158
|
+
getText(until_tag).strip.gsub(/\s+/m, " ")
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
class HTMLTokenizerError < Exception
|
163
|
+
end
|
164
|
+
|
165
|
+
# The parent class for all three types of HTML tokens
|
166
|
+
class HTMLToken
|
167
|
+
attr_accessor :raw
|
168
|
+
|
169
|
+
# Initialize the token based on the raw text
|
170
|
+
def initialize(text)
|
171
|
+
@raw = text
|
172
|
+
end
|
173
|
+
|
174
|
+
# By default, return exactly the string used to create the text
|
175
|
+
def to_s
|
176
|
+
raw
|
177
|
+
end
|
178
|
+
|
179
|
+
# By default tokens have no text representation
|
180
|
+
def text
|
181
|
+
""
|
182
|
+
end
|
183
|
+
|
184
|
+
def trimmed_text
|
185
|
+
text.strip.gsub(/\s+/m, " ")
|
186
|
+
end
|
187
|
+
|
188
|
+
# Compare to another based on the raw source
|
189
|
+
def ==(other)
|
190
|
+
raw == other.to_s
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Class representing text that isn't inside a tag
|
195
|
+
class HTMLText < HTMLToken
|
196
|
+
def text
|
197
|
+
raw
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Class representing an HTML comment
|
202
|
+
class HTMLComment < HTMLToken
|
203
|
+
attr_accessor :contents
|
204
|
+
|
205
|
+
def initialize(text)
|
206
|
+
super
|
207
|
+
temp_arr = text.scan(/^<!--\s*(.*?)\s*-->$/m)
|
208
|
+
raise HTMLTokenizerError, "Text passed to HTMLComment.initialize is not a comment" if temp_arr[0].nil?
|
209
|
+
|
210
|
+
@contents = temp_arr[0][0]
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Class representing an HTML tag
|
215
|
+
class HTMLTag < HTMLToken
|
216
|
+
attr_reader :end_tag, :tag_name
|
217
|
+
|
218
|
+
def initialize(text)
|
219
|
+
super
|
220
|
+
if "<" != text[0] or ">" != text[-1]
|
221
|
+
raise HTMLTokenizerError, "Text passed to HTMLComment.initialize is not a comment"
|
222
|
+
end
|
223
|
+
|
224
|
+
@attr_hash = {}
|
225
|
+
@raw = text
|
226
|
+
|
227
|
+
tag_name = text.scan(/[\w:-]+/)[0]
|
228
|
+
raise HTMLTokenizerError, "Error, tag is nil: #{tag_name}" if tag_name.nil?
|
229
|
+
|
230
|
+
if "/" == text[1]
|
231
|
+
# It's an end tag
|
232
|
+
@end_tag = true
|
233
|
+
@tag_name = "/" + tag_name.downcase
|
234
|
+
else
|
235
|
+
@end_tag = false
|
236
|
+
@tag_name = tag_name.downcase
|
237
|
+
end
|
238
|
+
|
239
|
+
@hashed = false
|
240
|
+
end
|
241
|
+
|
242
|
+
# Retrieve a hash of all the tag's attributes.
|
243
|
+
# Lazily done, so that if you don't look at a tag's attributes
|
244
|
+
# things go quicker
|
245
|
+
def attr_hash
|
246
|
+
# Lazy initialize == don't build the hash until it's needed
|
247
|
+
unless @hashed
|
248
|
+
unless @end_tag
|
249
|
+
# Get the attributes
|
250
|
+
attr_arr = @raw.scan(%r{<[\w:-]+\s+(.*?)/?>}m)[0]
|
251
|
+
if attr_arr.is_a?(Array)
|
252
|
+
# Attributes found, parse them
|
253
|
+
attrs = attr_arr[0]
|
254
|
+
attr_arr = attrs.scan(/\s*([\w:-]+)(?:\s*=\s*("[^"]*"|'[^']*'|([^"'>][^\s>]*)))?/m)
|
255
|
+
# clean up the array by:
|
256
|
+
# * setting all nil elements to true
|
257
|
+
# * removing enclosing quotes
|
258
|
+
attr_arr.each do |item|
|
259
|
+
val = if item[1].nil?
|
260
|
+
item[0]
|
261
|
+
elsif '"'[0] == item[1][0] or "'"[0] == item[1][0]
|
262
|
+
item[1][1..-2]
|
263
|
+
else
|
264
|
+
item[1]
|
265
|
+
end
|
266
|
+
@attr_hash[item[0].downcase] = val
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
@hashed = true
|
271
|
+
end
|
272
|
+
|
273
|
+
# p self
|
274
|
+
|
275
|
+
@attr_hash
|
276
|
+
end
|
277
|
+
|
278
|
+
# Get the 'alt' text for a tag, if it exists, or an empty string otherwise
|
279
|
+
def text
|
280
|
+
unless end_tag
|
281
|
+
case tag_name
|
282
|
+
when "img"
|
283
|
+
return attr_hash["alt"] unless attr_hash["alt"].nil?
|
284
|
+
when "applet"
|
285
|
+
return attr_hash["alt"] unless attr_hash["alt"].nil?
|
286
|
+
end
|
287
|
+
end
|
288
|
+
""
|
289
|
+
end
|
290
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# stdlib
|
2
|
+
require "cgi"
|
3
|
+
|
4
|
+
# This library
|
5
|
+
require_relative "htmltokenizer"
|
6
|
+
|
7
|
+
module OpenID
|
8
|
+
module Yadis
|
9
|
+
def self.html_yadis_location(html)
|
10
|
+
parser = HTMLTokenizer.new(html)
|
11
|
+
|
12
|
+
# to keep track of whether or not we are in the head element
|
13
|
+
in_head = false
|
14
|
+
|
15
|
+
begin
|
16
|
+
while el = parser.getTag(
|
17
|
+
"head",
|
18
|
+
"/head",
|
19
|
+
"meta",
|
20
|
+
"body",
|
21
|
+
"/body",
|
22
|
+
"html",
|
23
|
+
"script",
|
24
|
+
)
|
25
|
+
|
26
|
+
# we are leaving head or have reached body, so we bail
|
27
|
+
return if ["/head", "body", "/body"].member?(el.tag_name)
|
28
|
+
|
29
|
+
if el.tag_name == "head" && !(el.to_s[-2] == "/")
|
30
|
+
in_head = true # tag ends with a /: a short tag
|
31
|
+
end
|
32
|
+
next unless in_head
|
33
|
+
|
34
|
+
if el.tag_name == "script" && !(el.to_s[-2] == "/")
|
35
|
+
parser.getTag("/script") # tag ends with a /: a short tag
|
36
|
+
end
|
37
|
+
|
38
|
+
return if el.tag_name == "html"
|
39
|
+
|
40
|
+
next unless el.tag_name == "meta" and (equiv = el.attr_hash["http-equiv"])
|
41
|
+
if %w[x-xrds-location x-yadis-location].member?(equiv.downcase) &&
|
42
|
+
el.attr_hash.member?("content")
|
43
|
+
return CGI.unescapeHTML(el.attr_hash["content"])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
rescue HTMLTokenizerError # just stop parsing if there's an error
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require_relative "filters"
|
2
|
+
require_relative "discovery"
|
3
|
+
require_relative "xrds"
|
4
|
+
|
5
|
+
module OpenID
|
6
|
+
module Yadis
|
7
|
+
def self.get_service_endpoints(input_url, flt = nil)
|
8
|
+
# Perform the Yadis protocol on the input URL and return an
|
9
|
+
# iterable of resulting endpoint objects.
|
10
|
+
#
|
11
|
+
# @param flt: A filter object or something that is convertable
|
12
|
+
# to a filter object (using mkFilter) that will be used to
|
13
|
+
# generate endpoint objects. This defaults to generating
|
14
|
+
# BasicEndpoint objects.
|
15
|
+
result = Yadis.discover(input_url)
|
16
|
+
begin
|
17
|
+
endpoints = Yadis.apply_filter(
|
18
|
+
result.normalized_uri,
|
19
|
+
result.response_text,
|
20
|
+
flt,
|
21
|
+
)
|
22
|
+
rescue XRDSError => e
|
23
|
+
raise DiscoveryFailure.new(e.to_s, nil)
|
24
|
+
end
|
25
|
+
|
26
|
+
[result.normalized_uri, endpoints]
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.apply_filter(normalized_uri, xrd_data, flt = nil)
|
30
|
+
# Generate an iterable of endpoint objects given this input data,
|
31
|
+
# presumably from the result of performing the Yadis protocol.
|
32
|
+
|
33
|
+
flt = Yadis.make_filter(flt)
|
34
|
+
et = Yadis.parseXRDS(xrd_data)
|
35
|
+
|
36
|
+
endpoints = []
|
37
|
+
each_service(et) do |service_element|
|
38
|
+
endpoints += flt.get_service_endpoints(normalized_uri, service_element)
|
39
|
+
end
|
40
|
+
|
41
|
+
endpoints
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require "rexml/document"
|
2
|
+
require "rexml/element"
|
3
|
+
require "rexml/xpath"
|
4
|
+
|
5
|
+
require_relative "xri"
|
6
|
+
|
7
|
+
module OpenID
|
8
|
+
module Yadis
|
9
|
+
XRD_NS_2_0 = "xri://$xrd*($v*2.0)"
|
10
|
+
XRDS_NS = "xri://$xrds"
|
11
|
+
|
12
|
+
XRDS_NAMESPACES = {
|
13
|
+
"xrds" => XRDS_NS,
|
14
|
+
"xrd" => XRD_NS_2_0,
|
15
|
+
}
|
16
|
+
|
17
|
+
class XRDSError < StandardError; end
|
18
|
+
|
19
|
+
# Raised when there's an assertion in the XRDS that it does not
|
20
|
+
# have the authority to make.
|
21
|
+
class XRDSFraud < XRDSError
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.get_canonical_id(iname, xrd_tree)
|
25
|
+
# Return the CanonicalID from this XRDS document.
|
26
|
+
#
|
27
|
+
# @param iname: the XRI being resolved.
|
28
|
+
# @type iname: unicode
|
29
|
+
#
|
30
|
+
# @param xrd_tree: The XRDS output from the resolver.
|
31
|
+
#
|
32
|
+
# @returns: The XRI CanonicalID or None.
|
33
|
+
# @returntype: unicode or None
|
34
|
+
|
35
|
+
xrd_list = []
|
36
|
+
REXML::XPath.match(xrd_tree.root, "/xrds:XRDS/xrd:XRD", XRDS_NAMESPACES).each do |el|
|
37
|
+
xrd_list << el
|
38
|
+
end
|
39
|
+
|
40
|
+
xrd_list.reverse!
|
41
|
+
|
42
|
+
cid_elements = []
|
43
|
+
|
44
|
+
unless xrd_list.empty?
|
45
|
+
xrd_list[0].elements.each do |e|
|
46
|
+
next unless e.respond_to?(:name)
|
47
|
+
|
48
|
+
cid_elements << e if e.name == "CanonicalID"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
cid_element = cid_elements[0]
|
53
|
+
|
54
|
+
return unless cid_element
|
55
|
+
|
56
|
+
canonical_id = XRI.make_xri(cid_element.text)
|
57
|
+
|
58
|
+
child_id = canonical_id.downcase
|
59
|
+
|
60
|
+
xrd_list[1..-1].each do |xrd|
|
61
|
+
parent_sought = child_id[0...child_id.rindex("!")]
|
62
|
+
|
63
|
+
parent = XRI.make_xri(xrd.elements["CanonicalID"].text)
|
64
|
+
|
65
|
+
if parent_sought != parent.downcase
|
66
|
+
raise XRDSFraud.new(format(
|
67
|
+
"%s can not come from %s",
|
68
|
+
parent_sought,
|
69
|
+
parent,
|
70
|
+
))
|
71
|
+
end
|
72
|
+
|
73
|
+
child_id = parent_sought
|
74
|
+
end
|
75
|
+
|
76
|
+
root = XRI.root_authority(iname)
|
77
|
+
unless XRI.provider_is_authoritative(root, child_id)
|
78
|
+
raise XRDSFraud.new(format("%s can not come from root %s", child_id, root))
|
79
|
+
end
|
80
|
+
|
81
|
+
canonical_id
|
82
|
+
end
|
83
|
+
|
84
|
+
class XRDSError < StandardError
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.parseXRDS(text)
|
88
|
+
disable_entity_expansion do
|
89
|
+
raise XRDSError.new("Not an XRDS document.") if text.nil?
|
90
|
+
|
91
|
+
begin
|
92
|
+
d = REXML::Document.new(text)
|
93
|
+
rescue RuntimeError
|
94
|
+
raise XRDSError.new("Not an XRDS document. Failed to parse XML.")
|
95
|
+
end
|
96
|
+
|
97
|
+
return d if is_xrds?(d)
|
98
|
+
|
99
|
+
raise XRDSError.new("Not an XRDS document.")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.disable_entity_expansion
|
104
|
+
_previous_ = REXML::Document.entity_expansion_limit
|
105
|
+
REXML::Document.entity_expansion_limit = 0
|
106
|
+
yield
|
107
|
+
ensure
|
108
|
+
REXML::Document.entity_expansion_limit = _previous_
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.is_xrds?(xrds_tree)
|
112
|
+
xrds_root = xrds_tree.root
|
113
|
+
(!xrds_root.nil? and
|
114
|
+
xrds_root.name == "XRDS" and
|
115
|
+
xrds_root.namespace == XRDS_NS)
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.get_yadis_xrd(xrds_tree)
|
119
|
+
REXML::XPath.each(
|
120
|
+
xrds_tree.root,
|
121
|
+
"/xrds:XRDS/xrd:XRD[last()]",
|
122
|
+
XRDS_NAMESPACES,
|
123
|
+
) do |el|
|
124
|
+
return el
|
125
|
+
end
|
126
|
+
raise XRDSError.new("No XRD element found.")
|
127
|
+
end
|
128
|
+
|
129
|
+
# aka iterServices in Python
|
130
|
+
def self.each_service(xrds_tree, &block)
|
131
|
+
xrd = get_yadis_xrd(xrds_tree)
|
132
|
+
xrd.each_element("Service", &block)
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.services(xrds_tree)
|
136
|
+
s = []
|
137
|
+
each_service(xrds_tree) do |service|
|
138
|
+
s << service
|
139
|
+
end
|
140
|
+
s
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.expand_service(service_element)
|
144
|
+
es = service_element.elements
|
145
|
+
uris = es.each("URI") { |u| }
|
146
|
+
uris = prio_sort(uris)
|
147
|
+
types = es.each("Type/text()")
|
148
|
+
# REXML::Text objects are not strings.
|
149
|
+
types = types.collect { |t| t.to_s }
|
150
|
+
uris.collect { |uri| [types, uri.text, service_element] }
|
151
|
+
end
|
152
|
+
|
153
|
+
# Sort a list of elements that have priority attributes.
|
154
|
+
def self.prio_sort(elements)
|
155
|
+
elements.sort do |a, b|
|
156
|
+
a.attribute("priority").to_s.to_i <=> b.attribute("priority").to_s.to_i
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require_relative "../fetchers"
|
2
|
+
|
3
|
+
module OpenID
|
4
|
+
module Yadis
|
5
|
+
module XRI
|
6
|
+
# The '(' is for cross-reference authorities, and hopefully has a
|
7
|
+
# matching ')' somewhere.
|
8
|
+
XRI_AUTHORITIES = ["!", "=", "@", "+", "$", "("]
|
9
|
+
|
10
|
+
def self.identifier_scheme(identifier)
|
11
|
+
if !identifier.nil? and
|
12
|
+
identifier.length > 0 and
|
13
|
+
(identifier.match("^xri://") or
|
14
|
+
XRI_AUTHORITIES.member?(identifier[0].chr))
|
15
|
+
:xri
|
16
|
+
else
|
17
|
+
:uri
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Transform an XRI reference to an IRI reference. Note this is
|
22
|
+
# not not idempotent, so do not apply this to an identifier more
|
23
|
+
# than once. XRI Syntax section 2.3.1
|
24
|
+
def self.to_iri_normal(xri)
|
25
|
+
iri = xri.dup
|
26
|
+
iri.insert(0, "xri://") unless iri.match?("^xri://")
|
27
|
+
escape_for_iri(iri)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Note this is not not idempotent, so do not apply this more than
|
31
|
+
# once. XRI Syntax section 2.3.2
|
32
|
+
def self.escape_for_iri(xri)
|
33
|
+
esc = xri.dup
|
34
|
+
# encode all %
|
35
|
+
esc.gsub!("%", "%25")
|
36
|
+
esc.gsub!(/\((.*?)\)/) do |xref_match|
|
37
|
+
xref_match.gsub(%r{[/?\#]}) do |char_match|
|
38
|
+
CGI.escape(char_match)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
esc
|
42
|
+
end
|
43
|
+
|
44
|
+
# Transform an XRI reference to a URI reference. Note this is not
|
45
|
+
# not idempotent, so do not apply this to an identifier more than
|
46
|
+
# once. XRI Syntax section 2.3.1
|
47
|
+
def self.to_uri_normal(xri)
|
48
|
+
iri_to_uri(to_iri_normal(xri))
|
49
|
+
end
|
50
|
+
|
51
|
+
# RFC 3987 section 3.1
|
52
|
+
def self.iri_to_uri(iri)
|
53
|
+
iri.dup
|
54
|
+
# for char in ucschar or iprivate
|
55
|
+
# convert each char to %HH%HH%HH (as many %HH as octets)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.provider_is_authoritative(provider_id, canonical_id)
|
59
|
+
lastbang = canonical_id.rindex("!")
|
60
|
+
return false unless lastbang
|
61
|
+
|
62
|
+
parent = canonical_id[0...lastbang]
|
63
|
+
parent == provider_id
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.root_authority(xri)
|
67
|
+
xri = xri[6..-1] if xri.index("xri://") == 0
|
68
|
+
authority = xri.split("/", 2)[0]
|
69
|
+
root = if authority[0].chr == "("
|
70
|
+
authority[0...authority.index(")") + 1]
|
71
|
+
elsif XRI_AUTHORITIES.member?(authority[0].chr)
|
72
|
+
authority[0].chr
|
73
|
+
else
|
74
|
+
authority.split(/[!*]/)[0]
|
75
|
+
end
|
76
|
+
|
77
|
+
make_xri(root)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.make_xri(xri)
|
81
|
+
xri = "xri://" + xri if xri.index("xri://") != 0
|
82
|
+
xri
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|