ruby-yadis 0.2

Sign up to get free protection for your applications and to get access to all the features.
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
+
@@ -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
@@ -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,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'test_discovery'
4
+ require 'test_parse'
5
+ require 'test_xrds'
6
+ require 'test_yadis'
7
+
@@ -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
@@ -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
@@ -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
+