ruby-openid2 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- 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
|