ruby-yadis 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +10 -0
- data/INSTALL +22 -0
- data/README +22 -0
- data/examples/openid.rb +18 -0
- data/lib/yadis/fetcher.rb +72 -0
- data/lib/yadis/htmltokenizer.rb +355 -0
- data/lib/yadis/parsehtml.rb +32 -0
- data/lib/yadis/xrds.rb +145 -0
- data/lib/yadis/yadis.rb +98 -0
- data/lib/yadis.rb +1 -0
- data/test/data/brian.multi.xrds +38 -0
- data/test/data/brian.multi_uri.xrds +16 -0
- data/test/data/brian.xrds +16 -0
- data/test/data/brian_priority.xrds +22 -0
- data/test/data/index.html +29 -0
- data/test/data/index_xrds.html +25 -0
- data/test/data/index_yadis.html +26 -0
- data/test/data/manifest.txt +30 -0
- data/test/runtests.rb +7 -0
- data/test/test_discovery.rb +42 -0
- data/test/test_parse.rb +37 -0
- data/test/test_xrds.rb +41 -0
- data/test/test_yadis.rb +54 -0
- metadata +72 -0
data/COPYING
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
Copyright (c) 2006, JanRain, Inc.
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
5
|
+
|
6
|
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
7
|
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
8
|
+
* Neither the name of the JanRain, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
9
|
+
|
10
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/INSTALL
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
= Ruby YADIS Library Installation
|
2
|
+
|
3
|
+
== Installation
|
4
|
+
|
5
|
+
Unpack the archive and run setup.rb (you may need to be root)
|
6
|
+
|
7
|
+
ruby setup.rb
|
8
|
+
|
9
|
+
setup.rb installs the library into your system ruby. If don't want to
|
10
|
+
add yadis to you system ruby, make sure to add the *lib* directory of
|
11
|
+
the extracted tarball to your RUBYLIB environment variable.
|
12
|
+
|
13
|
+
Make sure everything installed ok:
|
14
|
+
$> irb
|
15
|
+
irb(main):001:0> require "yadis"
|
16
|
+
=> true
|
17
|
+
|
18
|
+
== Run the test suite
|
19
|
+
|
20
|
+
Go into the test directory and execute the *runtests* script.
|
21
|
+
|
22
|
+
|
data/README
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
=Ruby Yadis
|
2
|
+
|
3
|
+
A Ruby library for performing Yadis service discovery.
|
4
|
+
|
5
|
+
Yadis Specification details:
|
6
|
+
|
7
|
+
* http://yadis.org
|
8
|
+
* http://www.openidenabled.com/yadis/yadis-notes/
|
9
|
+
|
10
|
+
Please see the INSTALL, and have a look at the YADIS interface in yadis/yadis.rb
|
11
|
+
|
12
|
+
==Authors
|
13
|
+
Brian Ellin. brian -at- janrain -dot- com
|
14
|
+
JanRain, Inc. http://www.janrain.com/
|
15
|
+
|
16
|
+
Eugene Eric Kim. eekim -at- blueoxen -dot- com
|
17
|
+
Blue Oxen Associates. http://www.blueoxen.com/
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
|
data/examples/openid.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# example of finding an OpenID server using YADIS
|
2
|
+
|
3
|
+
require 'yadis'
|
4
|
+
|
5
|
+
yadis = YADIS.discover('http://brian.myopenid.com/')
|
6
|
+
if yadis.nil? or yadis.openid_servers.length == 0
|
7
|
+
p 'No XRDS found'
|
8
|
+
else
|
9
|
+
servers = yadis.openid_servers
|
10
|
+
if servers.length == 0
|
11
|
+
p 'No OpenID servers found'
|
12
|
+
else
|
13
|
+
servers.each do |s|
|
14
|
+
p "OpenID server found: #{s.type}, #{s.uri}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require "uri"
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "net/https"
|
5
|
+
rescue LoadError # no openssl
|
6
|
+
require "net/http"
|
7
|
+
HAS_OPENSSL = false
|
8
|
+
STDERR.puts("No openssl found, won't be able to fetch https URIs.")
|
9
|
+
else
|
10
|
+
HAS_OPENSSL = true
|
11
|
+
end
|
12
|
+
|
13
|
+
class NetHTTPFetcher
|
14
|
+
|
15
|
+
def initialize(read_timeout=20, open_timeout=20)
|
16
|
+
@read_timeout = read_timeout
|
17
|
+
@open_timeout = open_timeout
|
18
|
+
end
|
19
|
+
|
20
|
+
def get(url, params = nil)
|
21
|
+
resp, final_url = do_get(url, params)
|
22
|
+
if resp.nil?
|
23
|
+
nil
|
24
|
+
else
|
25
|
+
[final_url, resp]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
# return a Net::HTTP object ready for use
|
32
|
+
|
33
|
+
def get_http_obj(uri)
|
34
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
35
|
+
http.read_timeout = @read_timeout
|
36
|
+
http.open_timeout = @open_timeout
|
37
|
+
|
38
|
+
if uri.scheme == 'https'
|
39
|
+
if HAS_OPENSSL
|
40
|
+
http.use_ssl = true
|
41
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
42
|
+
else
|
43
|
+
STDERR.puts("Cannot fetch https url without openssl installed: #{uri.to_s}")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
http
|
48
|
+
end
|
49
|
+
|
50
|
+
# do a GET following redirects limit deep
|
51
|
+
|
52
|
+
def do_get(url, params, limit=5)
|
53
|
+
if limit == 0
|
54
|
+
return nil
|
55
|
+
end
|
56
|
+
begin
|
57
|
+
uri = URI.parse(url)
|
58
|
+
http = get_http_obj(uri)
|
59
|
+
resp = http.request_get(uri.request_uri, params)
|
60
|
+
rescue
|
61
|
+
nil
|
62
|
+
else
|
63
|
+
case resp
|
64
|
+
when Net::HTTPSuccess then [resp, URI.parse(url).to_s]
|
65
|
+
when Net::HTTPRedirection then do_get(resp["location"], params, limit-1)
|
66
|
+
else
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,355 @@
|
|
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
|
+
if @cur_pos == @page.length then return nil end
|
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
|
+
if tag_end.nil?
|
77
|
+
raise "No end found to started comment:\n#{@page[@cur_pos,80]}"
|
78
|
+
end
|
79
|
+
# p @page[@cur_pos .. (tag_end+2)]
|
80
|
+
HTMLComment.new(@page[@cur_pos .. (tag_end + 2)])
|
81
|
+
else
|
82
|
+
# Token is a html tag
|
83
|
+
tag_end = @page.index('>', (@cur_pos + 1))
|
84
|
+
if tag_end.nil?
|
85
|
+
raise "No end found to started tag:\n#{@page[@cur_pos,80]}"
|
86
|
+
end
|
87
|
+
# p @page[@cur_pos .. tag_end]
|
88
|
+
HTMLTag.new(@page[@cur_pos .. tag_end])
|
89
|
+
end
|
90
|
+
else
|
91
|
+
# Next token is text
|
92
|
+
text_end = @page.index('<', @cur_pos)
|
93
|
+
text_end = text_end.nil? ? -1 : (text_end - 1)
|
94
|
+
# p @page[@cur_pos .. text_end]
|
95
|
+
HTMLText.new(@page[@cur_pos .. text_end])
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Get the next token, returns an instance of
|
100
|
+
# * HTMLText
|
101
|
+
# * HTMLToken
|
102
|
+
# * HTMLTag
|
103
|
+
def getNextToken
|
104
|
+
token = peekNextToken
|
105
|
+
if token
|
106
|
+
# @page = @page[token.raw.length .. -1]
|
107
|
+
# @page.slice!(0, token.raw.length)
|
108
|
+
@cur_pos += token.raw.length
|
109
|
+
end
|
110
|
+
#p token
|
111
|
+
#print token.raw
|
112
|
+
return token
|
113
|
+
end
|
114
|
+
|
115
|
+
# Get a tag from the specified set of desired tags.
|
116
|
+
# For example:
|
117
|
+
# <tt>foo = toke.getTag("h1", "h2", "h3")</tt>
|
118
|
+
# Will return the next header tag encountered.
|
119
|
+
def getTag(*sought_tags)
|
120
|
+
sought_tags.collect! {|elm| elm.downcase}
|
121
|
+
|
122
|
+
while (tag = getNextToken)
|
123
|
+
if tag.kind_of?(HTMLTag) and
|
124
|
+
(0 == sought_tags.length or sought_tags.include?(tag.tag_name))
|
125
|
+
break
|
126
|
+
end
|
127
|
+
end
|
128
|
+
tag
|
129
|
+
end
|
130
|
+
|
131
|
+
# Get all the text between the current position and the next tag
|
132
|
+
# (if specified) or a specific later tag
|
133
|
+
def getText(until_tag = nil)
|
134
|
+
if until_tag.nil?
|
135
|
+
if ?< == @page[@cur_pos]
|
136
|
+
# Next token is a tag, not text
|
137
|
+
""
|
138
|
+
else
|
139
|
+
# Next token is text
|
140
|
+
getNextToken.text
|
141
|
+
end
|
142
|
+
else
|
143
|
+
ret_str = ""
|
144
|
+
|
145
|
+
while (tag = peekNextToken)
|
146
|
+
if tag.kind_of?(HTMLTag) and tag.tag_name == until_tag
|
147
|
+
break
|
148
|
+
end
|
149
|
+
|
150
|
+
if ("" != tag.text)
|
151
|
+
ret_str << (tag.text + " ")
|
152
|
+
end
|
153
|
+
getNextToken
|
154
|
+
end
|
155
|
+
|
156
|
+
ret_str
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Like getText, but squeeze all whitespace, getting rid of
|
161
|
+
# leading and trailing whitespace, and squeezing multiple
|
162
|
+
# spaces into a single space.
|
163
|
+
def getTrimmedText(until_tag = nil)
|
164
|
+
getText(until_tag).strip.gsub(/\s+/m, " ")
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
# The parent class for all three types of HTML tokens
|
170
|
+
class HTMLToken
|
171
|
+
attr_accessor :raw
|
172
|
+
|
173
|
+
# Initialize the token based on the raw text
|
174
|
+
def initialize(text)
|
175
|
+
@raw = text
|
176
|
+
end
|
177
|
+
|
178
|
+
# By default, return exactly the string used to create the text
|
179
|
+
def to_s
|
180
|
+
raw
|
181
|
+
end
|
182
|
+
|
183
|
+
# By default tokens have no text representation
|
184
|
+
def text
|
185
|
+
""
|
186
|
+
end
|
187
|
+
|
188
|
+
def trimmed_text
|
189
|
+
text.strip.gsub(/\s+/m, " ")
|
190
|
+
end
|
191
|
+
|
192
|
+
# Compare to another based on the raw source
|
193
|
+
def ==(other)
|
194
|
+
raw == other.to_s
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Class representing text that isn't inside a tag
|
199
|
+
class HTMLText < HTMLToken
|
200
|
+
def text
|
201
|
+
raw
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Class representing an HTML comment
|
206
|
+
class HTMLComment < HTMLToken
|
207
|
+
attr_accessor :contents
|
208
|
+
def initialize(text)
|
209
|
+
super(text)
|
210
|
+
temp_arr = text.scan(/^<!--\s*(.*?)\s*-->$/m)
|
211
|
+
if temp_arr[0].nil?
|
212
|
+
raise "Text passed to HTMLComment.initialize is not a comment"
|
213
|
+
end
|
214
|
+
|
215
|
+
@contents = temp_arr[0][0]
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# Class representing an HTML tag
|
220
|
+
class HTMLTag < HTMLToken
|
221
|
+
attr_reader :end_tag, :tag_name
|
222
|
+
def initialize(text)
|
223
|
+
super(text)
|
224
|
+
if ?< != text[0] or ?> != text[-1]
|
225
|
+
raise "Text passed to HTMLComment.initialize is not a comment"
|
226
|
+
end
|
227
|
+
|
228
|
+
@attr_hash = Hash.new
|
229
|
+
@raw = text
|
230
|
+
|
231
|
+
tag_name = text.scan(/[\w:-]+/)[0]
|
232
|
+
if tag_name.nil?
|
233
|
+
raise "Error, tag is nil: #{tag_name}"
|
234
|
+
end
|
235
|
+
|
236
|
+
if ?/ == text[1]
|
237
|
+
# It's an end tag
|
238
|
+
@end_tag = true
|
239
|
+
@tag_name = '/' + tag_name.downcase
|
240
|
+
else
|
241
|
+
@end_tag = false
|
242
|
+
@tag_name = tag_name.downcase
|
243
|
+
end
|
244
|
+
|
245
|
+
@hashed = false
|
246
|
+
end
|
247
|
+
|
248
|
+
# Retrieve a hash of all the tag's attributes.
|
249
|
+
# Lazily done, so that if you don't look at a tag's attributes
|
250
|
+
# things go quicker
|
251
|
+
def attr_hash
|
252
|
+
# Lazy initialize == don't build the hash until it's needed
|
253
|
+
if !@hashed
|
254
|
+
if !@end_tag
|
255
|
+
# Get the attributes
|
256
|
+
attr_arr = @raw.scan(/<[\w:-]+\s+(.*)>/m)[0]
|
257
|
+
if attr_arr.kind_of?(Array)
|
258
|
+
# Attributes found, parse them
|
259
|
+
attrs = attr_arr[0]
|
260
|
+
attr_arr = attrs.scan(/\s*([\w:-]+)(?:\s*=\s*("[^"]*"|'[^']*'|([^"'>][^\s>]*)))?/m)
|
261
|
+
# clean up the array by:
|
262
|
+
# * setting all nil elements to true
|
263
|
+
# * removing enclosing quotes
|
264
|
+
attr_arr.each {
|
265
|
+
|item|
|
266
|
+
val = if item[1].nil?
|
267
|
+
item[0]
|
268
|
+
elsif '"'[0] == item[1][0] or '\''[0] == item[1][0]
|
269
|
+
item[1][1 .. -2]
|
270
|
+
else
|
271
|
+
item[1]
|
272
|
+
end
|
273
|
+
@attr_hash[item[0].downcase] = val
|
274
|
+
}
|
275
|
+
end
|
276
|
+
end
|
277
|
+
@hashed = true
|
278
|
+
end
|
279
|
+
|
280
|
+
#p self
|
281
|
+
|
282
|
+
@attr_hash
|
283
|
+
end
|
284
|
+
|
285
|
+
# Get the 'alt' text for a tag, if it exists, or an empty string otherwise
|
286
|
+
def text
|
287
|
+
if !end_tag
|
288
|
+
case tag_name
|
289
|
+
when 'img'
|
290
|
+
if !attr_hash['alt'].nil?
|
291
|
+
return attr_hash['alt']
|
292
|
+
end
|
293
|
+
when 'applet'
|
294
|
+
if !attr_hash['alt'].nil?
|
295
|
+
return attr_hash['alt']
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
return ''
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
if $0 == __FILE__
|
304
|
+
require 'test/unit'
|
305
|
+
|
306
|
+
class TC_TestHTMLTokenizer < Test::Unit::TestCase
|
307
|
+
def test_bad_link
|
308
|
+
toke = HTMLTokenizer.new("<p><a href=http://bad.com/link>foo</a></p>")
|
309
|
+
assert("http://bad.com/link" == toke.getTag("a").attr_hash['href'])
|
310
|
+
end
|
311
|
+
|
312
|
+
def test_namespace
|
313
|
+
toke = HTMLTokenizer.new("<f:table xmlns:f=\"http://www.com/foo\">")
|
314
|
+
assert("http://www.com/foo" == toke.getTag("f:table").attr_hash['xmlns:f'])
|
315
|
+
end
|
316
|
+
|
317
|
+
def test_comment
|
318
|
+
toke = HTMLTokenizer.new("<!-- comment on me -->")
|
319
|
+
t = toke.getNextToken
|
320
|
+
assert(HTMLComment == t.class)
|
321
|
+
assert("comment on me" == t.contents)
|
322
|
+
end
|
323
|
+
|
324
|
+
|
325
|
+
def test_full
|
326
|
+
page = "<HTML>
|
327
|
+
<HEAD>
|
328
|
+
<TITLE>This is the title</TITLE>
|
329
|
+
</HEAD>
|
330
|
+
<!-- Here comes the <a href=\"missing.link\">blah</a>
|
331
|
+
comment body
|
332
|
+
-->
|
333
|
+
<BODY>
|
334
|
+
<H1>This is the header</H1>
|
335
|
+
<P>
|
336
|
+
This is the paragraph, it contains
|
337
|
+
<a href=\"link.html\">links</a>,
|
338
|
+
<img src=\"blah.gif\" optional alt='images
|
339
|
+
are
|
340
|
+
really cool'>. Ok, here is some more text and
|
341
|
+
<A href=\"http://another.link.com/\" target=\"_blank\">another link</A>.
|
342
|
+
</P>
|
343
|
+
</body>
|
344
|
+
</HTML>
|
345
|
+
"
|
346
|
+
toke = HTMLTokenizer.new(page)
|
347
|
+
|
348
|
+
assert("<h1>" == toke.getTag("h1", "h2", "h3").to_s.downcase)
|
349
|
+
assert(HTMLTag.new("<a href=\"link.html\">") == toke.getTag("IMG", "A"))
|
350
|
+
assert("links" == toke.getTrimmedText)
|
351
|
+
assert(toke.getTag("IMG", "A").attr_hash['optional'])
|
352
|
+
assert("_blank" == toke.getTag("IMG", "A").attr_hash['target'])
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "yadis/htmltokenizer"
|
2
|
+
|
3
|
+
def html_yadis_location(html)
|
4
|
+
parser = HTMLTokenizer.new(html)
|
5
|
+
|
6
|
+
# to keep track of whether or not we are in the head element
|
7
|
+
in_head = false
|
8
|
+
|
9
|
+
begin
|
10
|
+
while el = parser.getTag('head', '/head', 'meta', 'body')
|
11
|
+
|
12
|
+
# we are leaving head or have reached body, so we bail
|
13
|
+
return nil if ['/head', 'body'].member?(el.tag_name)
|
14
|
+
|
15
|
+
# meta needs to be in head, so we mark it
|
16
|
+
in_head = true if el.tag_name == 'head'
|
17
|
+
continue unless in_head
|
18
|
+
|
19
|
+
if el.tag_name == 'meta' and (equiv = el.attr_hash['http-equiv'])
|
20
|
+
if ['x-xrds-location','x-yadis-location'].member?(equiv.downcase)
|
21
|
+
return el.attr_hash['content']
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
rescue
|
27
|
+
return nil
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
|
data/lib/yadis/xrds.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
|
3
|
+
# Class that handles XRDS parsing and XRD Service element extraction.
|
4
|
+
class XRDS
|
5
|
+
|
6
|
+
# Method for producing a valid XRDS object. Accepts an XML
|
7
|
+
# String. Returns an XRDS object on success, or nil on failure.
|
8
|
+
# Same as calling XRDS.new, but does not rails ArgumentErrors.
|
9
|
+
def XRDS.parse(xml)
|
10
|
+
begin
|
11
|
+
return new(xml)
|
12
|
+
rescue
|
13
|
+
return nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def XRDS.parse_verbose(xml)
|
18
|
+
new(xml)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Create a new XRDS object. Raises ArgumentError if xml_text is
|
22
|
+
# malformed or invalid XRDS.
|
23
|
+
def initialize(xml_text)
|
24
|
+
parse_xml(xml_text)
|
25
|
+
end
|
26
|
+
|
27
|
+
# The schema was defined to support multiple XRD elements, but YADIS
|
28
|
+
# will never use it, so we assume just one.
|
29
|
+
#
|
30
|
+
# multiple Type and URI funkiness
|
31
|
+
#
|
32
|
+
# Does not implement URI priority yet.
|
33
|
+
def parse_xml(xml_text)
|
34
|
+
begin
|
35
|
+
xml = REXML::Document.new(xml_text)
|
36
|
+
rescue
|
37
|
+
raise ArgumentError, "Can't parse XRDS"
|
38
|
+
end
|
39
|
+
|
40
|
+
if xml.root.nil?
|
41
|
+
raise ArgumentError, "No document root"
|
42
|
+
end
|
43
|
+
|
44
|
+
if xml.root.attributes['xmlns'] != 'xri://$xrd*($v*2.0)'
|
45
|
+
raise ArgumentError, "Unknown XRID version #{xml.root.attributes['xmlns'].to_s}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# get the last <XRD> element
|
49
|
+
xrd_elemets = xml.root.get_elements('/xrds:XRDS/XRD')
|
50
|
+
last_xrd = xrd_elemets[-1]
|
51
|
+
if last_xrd.nil?
|
52
|
+
raise ArgumentError, "No XRD Elements found"
|
53
|
+
end
|
54
|
+
|
55
|
+
# get Service elements
|
56
|
+
@services = {} # keyed by priority
|
57
|
+
priority_index = -1 # for services w/ no priority specified
|
58
|
+
last_xrd.elements.each('Service') do |s|
|
59
|
+
s.elements.each('URI') do |u|
|
60
|
+
priority_index = _create_services(s, u, priority_index)
|
61
|
+
end
|
62
|
+
if !s.elements['URI']
|
63
|
+
priority_index = _create_services(s, nil, priority_index)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns an Array of Service objects, sorted by priority. Highest
|
69
|
+
# priority is at element 0.
|
70
|
+
def services
|
71
|
+
s = []
|
72
|
+
|
73
|
+
@services.keys.sort.each do |key| @services[key]
|
74
|
+
services_list = @services[key].dup
|
75
|
+
|
76
|
+
# randomize services with the same priority
|
77
|
+
while services_list.length > 0
|
78
|
+
s << services_list.delete_at((rand * services_list.length).to_i)
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
return s
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def _add_service(priority_index, service)
|
89
|
+
unless @services.has_key?(priority_index)
|
90
|
+
@services[priority_index] = []
|
91
|
+
end
|
92
|
+
|
93
|
+
# services with the same priority are appended to the list
|
94
|
+
@services[priority_index] << service
|
95
|
+
end
|
96
|
+
|
97
|
+
# create services objects
|
98
|
+
def _create_services(service_element, uri_element, priority_index)
|
99
|
+
service_element.elements.each('Type') do |t|
|
100
|
+
service = Service.new
|
101
|
+
service.uri = uri_element.text if uri_element
|
102
|
+
service.service_type = t.text
|
103
|
+
service.element = service_element
|
104
|
+
|
105
|
+
service_element.elements.each do |e|
|
106
|
+
service.add_other(e.prefix + ':' + e.name, e.text) if e.prefix
|
107
|
+
end
|
108
|
+
|
109
|
+
if service_element.attributes['priority']
|
110
|
+
priority = service_element.attributes['priority'].to_i
|
111
|
+
_add_service(priority, service)
|
112
|
+
|
113
|
+
elsif uri_element.attributes['priority']
|
114
|
+
priority = uri_element.attributes['priority'].to_i
|
115
|
+
_add_service(priority, service)
|
116
|
+
|
117
|
+
else
|
118
|
+
_add_service(priority_index, service)
|
119
|
+
priority_index -= 1
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
return priority_index
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
# Class representing an XRD Service element.
|
130
|
+
class Service
|
131
|
+
attr_reader :service_type, :uri, :element
|
132
|
+
attr_writer :service_type, :uri, :element
|
133
|
+
|
134
|
+
def initialize
|
135
|
+
@other = Hash.new
|
136
|
+
end
|
137
|
+
|
138
|
+
def add_other(name, value)
|
139
|
+
@other[name] = value
|
140
|
+
end
|
141
|
+
|
142
|
+
def other
|
143
|
+
return @other
|
144
|
+
end
|
145
|
+
end
|
data/lib/yadis/yadis.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'yadis/xrds'
|
2
|
+
require 'yadis/fetcher'
|
3
|
+
require 'yadis/parsehtml'
|
4
|
+
|
5
|
+
class YADISParseError < StandardError; end
|
6
|
+
class YADISHTTPError < StandardError; end
|
7
|
+
|
8
|
+
class YADIS
|
9
|
+
|
10
|
+
attr_reader :uri, :xrds_uri, :xrds
|
11
|
+
|
12
|
+
# Discover services for a given URI. Please note that no normalization
|
13
|
+
# will be done to the passed in URI, it should be valid before calling
|
14
|
+
# discover.
|
15
|
+
#
|
16
|
+
# Returns nil if no XRDS was found, or a YADIS object on success.
|
17
|
+
def YADIS.discover(uri)
|
18
|
+
begin
|
19
|
+
return YADIS.discover_verbose(uri)
|
20
|
+
rescue
|
21
|
+
return nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# same as YADIS.discover, but raises YADISParseError or YADISHTTPError
|
26
|
+
# when bad things happen.
|
27
|
+
def YADIS.discover_verbose(uri)
|
28
|
+
return nil unless uri
|
29
|
+
|
30
|
+
headers = {'Accept' => 'application/xrds+xml'}
|
31
|
+
response = NetHTTPFetcher.new.get(uri, headers)
|
32
|
+
raise YADISHTTPError, "Could not fetch #{uri}" if response.nil?
|
33
|
+
|
34
|
+
uri, resp_payload = response
|
35
|
+
xrds_uri = uri
|
36
|
+
|
37
|
+
header = resp_payload['x-xrds-location']
|
38
|
+
header = resp_payload['x-yadis-location'] if header.nil?
|
39
|
+
|
40
|
+
if header
|
41
|
+
xrds_uri = header
|
42
|
+
response = NetHTTPFetcher.new.get(xrds_uri)
|
43
|
+
raise YADISHTTPError, "Could not fetch XRDS #{xrds_uri}" if response.nil?
|
44
|
+
resp_payload = response[1]
|
45
|
+
end
|
46
|
+
|
47
|
+
unless resp_payload['content-type'] == 'application/xrds+xml'
|
48
|
+
loc = html_yadis_location(resp_payload.body)
|
49
|
+
unless loc.nil?
|
50
|
+
xrds_uri, resp_payload = NetHTTPFetcher.new.get(loc)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
xrds = XRDS.parse(resp_payload.body)
|
55
|
+
raise YADISParseError, "Bad XRDS" if xrds.nil?
|
56
|
+
|
57
|
+
return new(uri, xrds_uri, xrds)
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize(uri, xrds_uri, xrds)
|
61
|
+
@uri = uri
|
62
|
+
@xrds_uri = xrds_uri
|
63
|
+
@xrds = xrds
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns an Array Service objects sorted by priority.
|
67
|
+
def services
|
68
|
+
@xrds.services
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns an Array of Service objects that represent OpenID servers,
|
72
|
+
# sorted by priority.
|
73
|
+
#
|
74
|
+
# Optionally accpets an Array of OpenID protocol versions.Versions
|
75
|
+
# should be given as strings eg: '1.0'
|
76
|
+
def openid_servers(versions=nil)
|
77
|
+
versions.collect! {|v| v.gsub('.', '\.')} if versions
|
78
|
+
|
79
|
+
base_url = 'http://openid.net/signon/'
|
80
|
+
base_url += '(' + versions.join('|') + '){1}' if versions
|
81
|
+
|
82
|
+
return @xrds.services.find_all {|s| s.service_type.match(base_url)}
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns an Array of Service objects that represent LID servers,
|
86
|
+
# sorted by priority.
|
87
|
+
#
|
88
|
+
# Optionally accpets an Array of LID protocol versions.Versions
|
89
|
+
# should be given as strings eg: '1.0'
|
90
|
+
def lid_servers(versions)
|
91
|
+
versions.collect! {|v| v.gsub('.', '\.')} if versions
|
92
|
+
|
93
|
+
base_url = 'http://lid.netmesh.org/sso/'
|
94
|
+
base_url += '(' + versions.join('|') + '){1}' if versions
|
95
|
+
|
96
|
+
return @xrds.services.find_all {|s| s.service_type.match(base_url)}
|
97
|
+
end
|
98
|
+
end
|
data/lib/yadis.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'yadis/yadis'
|
@@ -0,0 +1,38 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<xrds:XRDS
|
3
|
+
xmlns:xrds="xri://$xrds"
|
4
|
+
xmlns:openid="http://openid.net/xmlns/1.0"
|
5
|
+
xmlns="xri://$xrd*($v*2.0)">
|
6
|
+
<XRD>
|
7
|
+
|
8
|
+
<Service priority="2">
|
9
|
+
<Type>http://openid.net/signon/1.1</Type>
|
10
|
+
<URI>http://www.myopenid.com/server</URI>
|
11
|
+
<openid:Delegate>http://frank.myopenid.com/</openid:Delegate>
|
12
|
+
</Service>
|
13
|
+
|
14
|
+
</XRD>
|
15
|
+
<XRD>
|
16
|
+
|
17
|
+
<Service priority="1">
|
18
|
+
<Type>http://bar.com/</Type>
|
19
|
+
<URI>http://bar.com/server</URI>
|
20
|
+
</Service>
|
21
|
+
|
22
|
+
<Service priority="2">
|
23
|
+
<Type>http://foo.com</Type>
|
24
|
+
<URI>http://foo.com/server</URI>
|
25
|
+
</Service>
|
26
|
+
|
27
|
+
</XRD>
|
28
|
+
<XRD>
|
29
|
+
|
30
|
+
<Service priority="0">
|
31
|
+
<Type>http://openid.net/signon/1.0</Type>
|
32
|
+
<URI>http://www.myopenid.com/server</URI>
|
33
|
+
<openid:Delegate>http://brian.myopenid.com/</openid:Delegate>
|
34
|
+
</Service>
|
35
|
+
|
36
|
+
</XRD>
|
37
|
+
</xrds:XRDS>
|
38
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<xrds:XRDS
|
3
|
+
xmlns:xrds="xri://$xrds"
|
4
|
+
xmlns:openid="http://openid.net/xmlns/1.0"
|
5
|
+
xmlns="xri://$xrd*($v*2.0)">
|
6
|
+
<XRD>
|
7
|
+
|
8
|
+
<Service>
|
9
|
+
<Type>http://openid.net/signon/1.0</Type>
|
10
|
+
<URI>http://www.myopenid.com/server</URI>
|
11
|
+
<URI>http://example.com/server</URI>
|
12
|
+
</Service>
|
13
|
+
|
14
|
+
</XRD>
|
15
|
+
</xrds:XRDS>
|
16
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<xrds:XRDS
|
3
|
+
xmlns:xrds="xri://$xrds"
|
4
|
+
xmlns:openid="http://openid.net/xmlns/1.0"
|
5
|
+
xmlns="xri://$xrd*($v*2.0)">
|
6
|
+
<XRD>
|
7
|
+
|
8
|
+
<Service priority="0">
|
9
|
+
<Type>http://openid.net/signon/1.0</Type>
|
10
|
+
<URI>http://www.myopenid.com/server</URI>
|
11
|
+
<openid:Delegate>http://brian.myopenid.com/</openid:Delegate>
|
12
|
+
</Service>
|
13
|
+
|
14
|
+
</XRD>
|
15
|
+
</xrds:XRDS>
|
16
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<xrds:XRDS
|
3
|
+
xmlns:xrds="xri://$xrds"
|
4
|
+
xmlns:openid="http://openid.net/xmlns/1.0"
|
5
|
+
xmlns="xri://$xrd*($v*2.0)">
|
6
|
+
<XRD>
|
7
|
+
|
8
|
+
<Service priority="2">
|
9
|
+
<Type>http://openid.net/signon/1.0</Type>
|
10
|
+
<URI>http://www.schtuff.com/?action=openid_server</URI>
|
11
|
+
<openid:Delegate>http://users.schtuff.com/brian</openid:Delegate>
|
12
|
+
</Service>
|
13
|
+
|
14
|
+
<Service priority="1">
|
15
|
+
<Type>http://openid.net/signon/1.0</Type>
|
16
|
+
<URI>http://www.myopenid.com/server</URI>
|
17
|
+
<openid:Delegate>http://brian.myopenid.com/</openid:Delegate>
|
18
|
+
</Service>
|
19
|
+
|
20
|
+
</XRD>
|
21
|
+
</xrds:XRDS>
|
22
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<html>
|
2
|
+
|
3
|
+
<head>
|
4
|
+
|
5
|
+
<meta http-equiv="X-XRDS-Location" content="http://brian.myopenid.com/xrds" />
|
6
|
+
<meta http-equiv="X-YADIS-Location" content="http://brian.myopenid.com/xrds" />
|
7
|
+
|
8
|
+
<link rel="openid.server" href="http://www.myopenid.com/server" />
|
9
|
+
<link rel="openid.delegate" href="http://brian.myopenid.com/" />
|
10
|
+
|
11
|
+
<title>Foo</title>
|
12
|
+
<style type="text/css">
|
13
|
+
* {font-family: sans-serif; color: #555; font-size: 1em;}
|
14
|
+
body {background-color: #eee;}
|
15
|
+
a {color: #555;}
|
16
|
+
img {border:2px solid #bbb;}
|
17
|
+
#main {text-align:center; margin-top:4em;}
|
18
|
+
#menu {}
|
19
|
+
</style>
|
20
|
+
|
21
|
+
</head>
|
22
|
+
|
23
|
+
<body>
|
24
|
+
<div id="main">
|
25
|
+
stuff
|
26
|
+
</div>
|
27
|
+
</body>
|
28
|
+
|
29
|
+
</html>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<html>
|
2
|
+
|
3
|
+
<head>
|
4
|
+
|
5
|
+
<meta http-equiv="X-XRDS-Location" content="http://brian.myopenid.com/xrds" />
|
6
|
+
|
7
|
+
<title>Foo</title>
|
8
|
+
<style type="text/css">
|
9
|
+
* {font-family: sans-serif; color: #555; font-size: 1em;}
|
10
|
+
body {background-color: #eee;}
|
11
|
+
a {color: #555;}
|
12
|
+
img {border:2px solid #bbb;}
|
13
|
+
#main {text-align:center; margin-top:4em;}
|
14
|
+
#menu {}
|
15
|
+
</style>
|
16
|
+
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<div id="main">
|
21
|
+
stuff
|
22
|
+
</div>
|
23
|
+
</body>
|
24
|
+
|
25
|
+
</html>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<html>
|
2
|
+
|
3
|
+
<head>
|
4
|
+
|
5
|
+
<meta http-equiv="X-YADIS-Location" content="http://brian.myopenid.com/xrds" />
|
6
|
+
|
7
|
+
|
8
|
+
<title>Foo</title>
|
9
|
+
<style type="text/css">
|
10
|
+
* {font-family: sans-serif; color: #555; font-size: 1em;}
|
11
|
+
body {background-color: #eee;}
|
12
|
+
a {color: #555;}
|
13
|
+
img {border:2px solid #bbb;}
|
14
|
+
#main {text-align:center; margin-top:4em;}
|
15
|
+
#menu {}
|
16
|
+
</style>
|
17
|
+
|
18
|
+
</head>
|
19
|
+
|
20
|
+
<body>
|
21
|
+
<div id="main">
|
22
|
+
stuff
|
23
|
+
</div>
|
24
|
+
</body>
|
25
|
+
|
26
|
+
</html>
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# This file contains test cases for doing YADIS identity URL and
|
2
|
+
# service discovery. For each case, there are three URLs. The first
|
3
|
+
# URL is the user input. The second is the identity URL and the third
|
4
|
+
# is the URL from which the XRDS document should be read.
|
5
|
+
#
|
6
|
+
# The file format is as follows:
|
7
|
+
# User URL <tab> Identity URL <tab> XRDS URL <newline>
|
8
|
+
#
|
9
|
+
# blank lines and lines starting with # should be ignored.
|
10
|
+
#
|
11
|
+
# To use this test:
|
12
|
+
#
|
13
|
+
# 1. Run your discovery routine on the User URL.
|
14
|
+
#
|
15
|
+
# 2. Compare the identity URL returned by the discovery routine to the
|
16
|
+
# identity URL on that line of the file. It must be an EXACT match.
|
17
|
+
#
|
18
|
+
# 3. Do a regular HTTP GET on the XRDS URL. Compare the content that
|
19
|
+
# was returned by your discovery routine with the content returned
|
20
|
+
# from that URL. It should also be an exact match.
|
21
|
+
|
22
|
+
http://www.openidenabled.com/resources/yadis-test/discover/equiv http://www.openidenabled.com/resources/yadis-test/discover/equiv http://www.openidenabled.com/resources/yadis-test/discover/xrds
|
23
|
+
http://www.openidenabled.com/resources/yadis-test/discover/header http://www.openidenabled.com/resources/yadis-test/discover/header http://www.openidenabled.com/resources/yadis-test/discover/xrds
|
24
|
+
http://www.openidenabled.com/resources/yadis-test/discover/xrds http://www.openidenabled.com/resources/yadis-test/discover/xrds http://www.openidenabled.com/resources/yadis-test/discover/xrds
|
25
|
+
http://www.openidenabled.com/resources/yadis-test/discover/xrds_html http://www.openidenabled.com/resources/yadis-test/discover/xrds_html http://www.openidenabled.com/resources/yadis-test/discover/xrds_html
|
26
|
+
http://www.openidenabled.com/resources/yadis-test/discover/redir_equiv http://www.openidenabled.com/resources/yadis-test/discover/equiv http://www.openidenabled.com/resources/yadis-test/discover/xrds
|
27
|
+
http://www.openidenabled.com/resources/yadis-test/discover/redir_header http://www.openidenabled.com/resources/yadis-test/discover/header http://www.openidenabled.com/resources/yadis-test/discover/xrds
|
28
|
+
http://www.openidenabled.com/resources/yadis-test/discover/redir_xrds http://www.openidenabled.com/resources/yadis-test/discover/xrds http://www.openidenabled.com/resources/yadis-test/discover/xrds
|
29
|
+
http://www.openidenabled.com/resources/yadis-test/discover/redir_xrds_html http://www.openidenabled.com/resources/yadis-test/discover/xrds_html http://www.openidenabled.com/resources/yadis-test/discover/xrds_html
|
30
|
+
http://www.openidenabled.com/resources/yadis-test/discover/redir_redir_equiv http://www.openidenabled.com/resources/yadis-test/discover/equiv http://www.openidenabled.com/resources/yadis-test/discover/xrds
|
data/test/runtests.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'yadis'
|
3
|
+
|
4
|
+
# run all the discovery tests from
|
5
|
+
# http://www.openidenabled.com/resources/yadis-test/discover/manifest.txt
|
6
|
+
# a local copy of the test data is in data/manifest.txt
|
7
|
+
|
8
|
+
class DiscoveryTestCase < Test::Unit::TestCase
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@cases = []
|
12
|
+
File.open('data/manifest.txt').each_line do |line|
|
13
|
+
line.strip!
|
14
|
+
if line.index('#') != 0 and line
|
15
|
+
@cases << line.split(' ', 3) if line.length > 0
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_discovery
|
22
|
+
@cases.each_with_index do |x, i|
|
23
|
+
input, redir_uri, xrds_uri = x
|
24
|
+
y = YADIS.discover(input)
|
25
|
+
assert_not_nil(y)
|
26
|
+
assert_equal(redir_uri, y.uri)
|
27
|
+
assert_equal(xrds_uri, y.xrds_uri)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_bad
|
32
|
+
assert_nil(YADIS.discover(nil))
|
33
|
+
assert_nil(YADIS.discover(5))
|
34
|
+
|
35
|
+
# not a valid uri
|
36
|
+
assert_nil(YADIS.discover('foo.com'))
|
37
|
+
|
38
|
+
# not a yadis uri
|
39
|
+
assert_nil(YADIS.discover('http://google.com/?q=huh'))
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
data/test/test_parse.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'yadis/parsehtml'
|
3
|
+
|
4
|
+
class YadisHTMLParseTestCase < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def check(x, html)
|
7
|
+
result = html_yadis_location(html)
|
8
|
+
assert_equal(x, result)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_valid
|
12
|
+
check('foo', '<html><head><meta http-equiv="X-YADIS-LOCATION" content="foo"/></head></html>')
|
13
|
+
|
14
|
+
check('foo', '<html><head><meta http-equiv="X-YADIS-LOCATION" content="foo"/></head></html>')
|
15
|
+
check('foo', '<html><head><meta http-equiv="X-YADIS-LOCATION" content="foo"/></head></html>')
|
16
|
+
check('foo', '<html><head><meta HTTP-EQUIV="X-YADIS-LOCATION" CONTENT="foo"/></head></html>')
|
17
|
+
check('foo', '<html><head><meta HTTP-EQUIV="X-YADIS-Location" CONTENT="foo"/></head></html>')
|
18
|
+
check('http://brian.myopenid.com/xrds', File.open('data/index.html').read)
|
19
|
+
check('http://brian.myopenid.com/xrds', File.open('data/index_xrds.html').read)
|
20
|
+
check('http://brian.myopenid.com/xrds', File.open('data/index_yadis.html').read)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_fail
|
24
|
+
check(nil, '')
|
25
|
+
check(nil, nil)
|
26
|
+
check(nil, 5)
|
27
|
+
check(nil, '<html></html>')
|
28
|
+
|
29
|
+
# no content attr
|
30
|
+
check(nil, '<html><head><meta http-equiv="x-yadis-location" /><meta http-equiv="X-YADIS-LOCATION" content="foo"/></head></html>')
|
31
|
+
|
32
|
+
# not in head
|
33
|
+
check(nil, '<html><meta http-equiv="X-YADIS-LOCATION" content="foo"/></html>')
|
34
|
+
check(nil, '<html><body><meta http-equiv="X-YADIS-LOCATION" content="foo"/></html>')
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
data/test/test_xrds.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'yadis/xrds'
|
3
|
+
|
4
|
+
class XRDSTestCase < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_xrds_good
|
7
|
+
File.open('data/brian.xrds') do |f|
|
8
|
+
xrds = XRDS.parse(f.read)
|
9
|
+
assert_not_nil(xrds)
|
10
|
+
assert_equal(xrds.services.length, 1)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def test_xrds_good_multi
|
16
|
+
File.open('data/brian.multi.xrds') do |f|
|
17
|
+
xrds = XRDS.parse(f.read)
|
18
|
+
assert_not_nil(xrds)
|
19
|
+
assert_equal(1, xrds.services.length)
|
20
|
+
s = xrds.services[0]
|
21
|
+
assert_equal('http://openid.net/signon/1.0', s.service_type)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_xrds_good_uri_multi
|
26
|
+
File.open('data/brian.multi_uri.xrds') do |f|
|
27
|
+
xrds = XRDS.parse(f.read)
|
28
|
+
assert_not_nil(xrds)
|
29
|
+
assert_equal(2, xrds.services.length)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_xrds_bad
|
34
|
+
assert_nil(XRDS.parse(nil))
|
35
|
+
assert_nil(XRDS.parse(5))
|
36
|
+
assert_nil(XRDS.parse(''))
|
37
|
+
assert_nil(XRDS.parse('<html></html>'))
|
38
|
+
assert_nil(XRDS.parse('\000'))
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
data/test/test_yadis.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'yadis'
|
3
|
+
|
4
|
+
class YADISTestCase < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_yadis_openid
|
7
|
+
File.open('data/brian.xrds') do |f|
|
8
|
+
xrds = XRDS.parse(f.read)
|
9
|
+
|
10
|
+
assert_not_nil(xrds)
|
11
|
+
assert_equal(xrds.services.length, 1)
|
12
|
+
|
13
|
+
uri = 'http://brian.myopenid.com/'
|
14
|
+
xrds_uri = 'http://brian.myopenid.com/xrds'
|
15
|
+
y = YADIS.new(uri, xrds_uri, xrds)
|
16
|
+
|
17
|
+
servers = y.openid_servers
|
18
|
+
assert_equal(servers.length, 1)
|
19
|
+
|
20
|
+
myopenid = servers[0]
|
21
|
+
assert_equal(myopenid.service_type, 'http://openid.net/signon/1.0')
|
22
|
+
assert_equal(myopenid.uri, 'http://www.myopenid.com/server')
|
23
|
+
assert_equal(myopenid.other['openid:Delegate'], 'http://brian.myopenid.com/')
|
24
|
+
|
25
|
+
servers = y.openid_servers(['1.0'])
|
26
|
+
assert_equal(servers.length, 1)
|
27
|
+
|
28
|
+
myopenid = servers[0]
|
29
|
+
assert_equal(myopenid.service_type, 'http://openid.net/signon/1.0')
|
30
|
+
assert_equal(myopenid.uri, 'http://www.myopenid.com/server')
|
31
|
+
assert_equal(myopenid.other['openid:Delegate'], 'http://brian.myopenid.com/')
|
32
|
+
|
33
|
+
servers = y.openid_servers(['0.9'])
|
34
|
+
assert_equal(servers.length, 0)
|
35
|
+
|
36
|
+
servers = y.openid_servers(['0.9', '1.1'])
|
37
|
+
assert_equal(servers.length, 0)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_yadis_lid
|
42
|
+
# need an xrds with lid stuff in it for this part
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_priority
|
46
|
+
xrds = XRDS.parse(File.open('data/brian_priority.xrds').read)
|
47
|
+
assert_equal(2, xrds.services.length)
|
48
|
+
assert_equal('http://www.myopenid.com/server', xrds.services[0].uri)
|
49
|
+
assert_equal('http://www.schtuff.com/?action=openid_server', xrds.services[1].uri)
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
!ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.11
|
3
|
+
specification_version: 1
|
4
|
+
name: ruby-yadis
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: "0.2"
|
7
|
+
date: 2006-03-16 00:00:00 -08:00
|
8
|
+
summary: A library for performing Yadis service discovery
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: brian@janrian.com, eekim@blueoxen.com
|
12
|
+
homepage: http://www.openidenabled.com/yadis/libraries/ruby
|
13
|
+
rubyforge_project:
|
14
|
+
description:
|
15
|
+
autorequire: yadis
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
authors:
|
29
|
+
- Brian Ellin (JanRain, Inc), Eugene Eric Kim (Blue Oxen Associates)
|
30
|
+
files:
|
31
|
+
- examples/openid.rb
|
32
|
+
- lib/yadis.rb
|
33
|
+
- lib/yadis
|
34
|
+
- lib/yadis/parsehtml.rb
|
35
|
+
- lib/yadis/xrds.rb
|
36
|
+
- lib/yadis/yadis.rb
|
37
|
+
- lib/yadis/fetcher.rb
|
38
|
+
- lib/yadis/htmltokenizer.rb
|
39
|
+
- test/test_parse.rb
|
40
|
+
- test/data
|
41
|
+
- test/test_discovery.rb
|
42
|
+
- test/test_yadis.rb
|
43
|
+
- test/test_xrds.rb
|
44
|
+
- test/runtests.rb
|
45
|
+
- test/data/manifest.txt
|
46
|
+
- test/data/brian.xrds
|
47
|
+
- test/data/index.html
|
48
|
+
- test/data/brian.multi.xrds
|
49
|
+
- test/data/brian_priority.xrds
|
50
|
+
- test/data/brian.multi_uri.xrds
|
51
|
+
- test/data/index_xrds.html
|
52
|
+
- test/data/index_yadis.html
|
53
|
+
- README
|
54
|
+
- INSTALL
|
55
|
+
- COPYING
|
56
|
+
test_files:
|
57
|
+
- test/runtests.rb
|
58
|
+
rdoc_options:
|
59
|
+
- --main
|
60
|
+
- README
|
61
|
+
extra_rdoc_files:
|
62
|
+
- README
|
63
|
+
- INSTALL
|
64
|
+
- COPYING
|
65
|
+
executables: []
|
66
|
+
|
67
|
+
extensions: []
|
68
|
+
|
69
|
+
requirements: []
|
70
|
+
|
71
|
+
dependencies: []
|
72
|
+
|