ruby-yadis 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/examples/openid.rb +14 -9
- data/lib/yadis/fetcher.rb~ +79 -0
- data/lib/yadis/manager.rb~ +138 -0
- data/lib/yadis/service.rb +2 -1
- data/lib/yadis/service.rb~ +24 -0
- data/lib/yadis/xrds.rb +8 -0
- data/lib/yadis/xrds.rb~ +128 -0
- data/lib/yadis/xri.rb +87 -0
- data/lib/yadis/xrires.rb +94 -0
- data/lib/yadis/yadis.rb~ +104 -0
- data/lib/yadis.rb +2 -0
- data/lib/yadis.rb~ +1 -0
- data/test/data/keturn.xrds +32 -0
- data/test/test_xri.rb +67 -0
- metadata +15 -5
data/examples/openid.rb
CHANGED
@@ -6,17 +6,22 @@ require 'yadis'
|
|
6
6
|
# Visit http://curl.haxx.se/docs/caextract.html and uncomment:
|
7
7
|
# YADIS.ca_path = '/path/to/cacert.pem'
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
url = 'http://brianellin.com/'
|
10
|
+
puts "Looking for servers for #{url}\n"
|
11
|
+
|
12
|
+
yadis = YADIS.discover(url)
|
13
|
+
openid_filter = Proc.new do |service|
|
14
|
+
service.service_types.member?('http://openid.net/signon/1.0') ? service : nil
|
15
|
+
end
|
16
|
+
|
17
|
+
if yadis.nil?
|
18
|
+
puts 'No XRDS found.'
|
12
19
|
else
|
13
|
-
|
14
|
-
if
|
15
|
-
|
20
|
+
services = yadis.filter_services(openid_filter)
|
21
|
+
if services.length > 0
|
22
|
+
services.each {|s| puts "OpenID server found: #{s.uri}"}
|
16
23
|
else
|
17
|
-
servers.
|
18
|
-
p "OpenID server found: #{s.type}, #{s.uri}"
|
19
|
-
end
|
24
|
+
puts 'No OpenID servers found.'
|
20
25
|
end
|
21
26
|
end
|
22
27
|
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require "uri"
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "net/https"
|
5
|
+
rescue LoadError
|
6
|
+
HAS_OPENSSL_ = false
|
7
|
+
require 'net/http'
|
8
|
+
else
|
9
|
+
HAS_OPENSSL_ = true
|
10
|
+
end
|
11
|
+
|
12
|
+
class NetHTTPFetcher
|
13
|
+
|
14
|
+
attr_accessor :ca_path
|
15
|
+
|
16
|
+
def initialize(read_timeout=20, open_timeout=20)
|
17
|
+
@read_timeout = read_timeout
|
18
|
+
@open_timeout = open_timeout
|
19
|
+
@ca_path = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def get(url, params = nil)
|
23
|
+
resp, final_url = do_get(url, params)
|
24
|
+
if resp.nil?
|
25
|
+
nil
|
26
|
+
else
|
27
|
+
[final_url, resp]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
# return a Net::HTTP object ready for use
|
34
|
+
def get_http_obj(uri)
|
35
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
36
|
+
http.read_timeout = @read_timeout
|
37
|
+
http.open_timeout = @open_timeout
|
38
|
+
|
39
|
+
if uri.scheme == 'https'
|
40
|
+
if HAS_OPENSSL_
|
41
|
+
http.use_ssl = true
|
42
|
+
if @ca_path
|
43
|
+
http.ca_file = @ca_path
|
44
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
45
|
+
else
|
46
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
47
|
+
STDERR.puts("Warning: fetching over https without verifying server certificate")
|
48
|
+
end
|
49
|
+
else
|
50
|
+
STDERR.puts('Warning: trying to fetch HTTPS URL without OpenSSL support')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
return http
|
55
|
+
end
|
56
|
+
|
57
|
+
# do a GET following redirects limit deep
|
58
|
+
def do_get(url, params, limit=5)
|
59
|
+
if limit == 0
|
60
|
+
return nil
|
61
|
+
end
|
62
|
+
begin
|
63
|
+
uri = URI.parse(url)
|
64
|
+
http = get_http_obj(uri)
|
65
|
+
resp = http.request_get(uri.request_uri, params)
|
66
|
+
rescue
|
67
|
+
nil
|
68
|
+
else
|
69
|
+
case resp
|
70
|
+
when Net::HTTPSuccess then [resp, URI.parse(url).to_s]
|
71
|
+
when Net::HTTPRedirection then do_get(resp["location"], params, limit-1)
|
72
|
+
else
|
73
|
+
STDERR.puts("ERROR, what to do with #{resp}")
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'yadis/yadis'
|
2
|
+
|
3
|
+
class YadisServiceManager
|
4
|
+
|
5
|
+
attr_reader :starting_url, :yadis_url, :services, :session_key, :current
|
6
|
+
|
7
|
+
def initialize(starting_url, yadis_url, services)
|
8
|
+
@starting_url = starting_url
|
9
|
+
@yadis_url = yadis_url
|
10
|
+
@services = services
|
11
|
+
@current = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def next
|
15
|
+
@current = @services.shift
|
16
|
+
end
|
17
|
+
|
18
|
+
def for_url?(url)
|
19
|
+
url == @starting_url or url == @yadis_url
|
20
|
+
end
|
21
|
+
|
22
|
+
def started?
|
23
|
+
not @current.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
def length
|
27
|
+
@services.length
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
class Discovery
|
33
|
+
|
34
|
+
@@default_suffix = nil
|
35
|
+
@@prefix = '_yadis_services_'
|
36
|
+
|
37
|
+
def initialize(session, url, session_key_siffix=nil)
|
38
|
+
@session = session
|
39
|
+
@url = url
|
40
|
+
@session_key = @@prefix + (session_key_suffix or @@default_suffix)
|
41
|
+
end
|
42
|
+
|
43
|
+
def next_service(discover_block)
|
44
|
+
manager = self.get_manager
|
45
|
+
if manager and manager.length <= 0
|
46
|
+
self.destroy_manager
|
47
|
+
manager = nil
|
48
|
+
end
|
49
|
+
|
50
|
+
unless manager
|
51
|
+
begin
|
52
|
+
yadis_url, services = self.discover
|
53
|
+
rescue YADISParseError, YADISHTTPError
|
54
|
+
manager = nil
|
55
|
+
else
|
56
|
+
manager = self.create_manager(services, yadis_url)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
if manager
|
61
|
+
service = manager.next
|
62
|
+
self.store_manager(manager)
|
63
|
+
else
|
64
|
+
service = nil
|
65
|
+
end
|
66
|
+
|
67
|
+
return service
|
68
|
+
end
|
69
|
+
|
70
|
+
def finish
|
71
|
+
manager = self.get_manager
|
72
|
+
return nil unless manager
|
73
|
+
|
74
|
+
service = manager.current
|
75
|
+
self.destroy_manager
|
76
|
+
return service
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_manager
|
80
|
+
manager = @session[@session_key]
|
81
|
+
|
82
|
+
# make sure we've got the right manager here
|
83
|
+
if manager and manager.for_url?(@url)
|
84
|
+
return manager
|
85
|
+
end
|
86
|
+
|
87
|
+
return nil
|
88
|
+
end
|
89
|
+
|
90
|
+
def create_manager(services, yadis_url=nil)
|
91
|
+
if self.get_manager
|
92
|
+
raise ArgumentError, "There is already a manager for #{@url}"
|
93
|
+
end
|
94
|
+
|
95
|
+
if services.length > 0
|
96
|
+
manager = YadisServiceManager.new(@url, yadis_url, services)
|
97
|
+
self.store_manager(manager)
|
98
|
+
else
|
99
|
+
manager = nil
|
100
|
+
end
|
101
|
+
|
102
|
+
return manager
|
103
|
+
end
|
104
|
+
|
105
|
+
def destroy_manager
|
106
|
+
if self.get_manager
|
107
|
+
begin
|
108
|
+
@session.delete(@session_key)
|
109
|
+
rescue
|
110
|
+
# sometimes Hash like session objects don't have a delete
|
111
|
+
# method. We handle that case by assigning nil to the session[key]
|
112
|
+
@session[@session_key] = nil
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def store_manager(manager)
|
118
|
+
@session[@session_key] = manager
|
119
|
+
end
|
120
|
+
|
121
|
+
# The filter argument is a Proc that will be used to call
|
122
|
+
# YADIS.filter_services. See the documentation for YADIS.filter_services
|
123
|
+
# for more information about writing filters.
|
124
|
+
def discover(filter=nil)
|
125
|
+
y = YADIS.new(@url)
|
126
|
+
|
127
|
+
# a default filter which sends through everything. you should
|
128
|
+
# probably consider writing a custom filter and passing it in.
|
129
|
+
unless filter
|
130
|
+
filter = lambda {|s| s}
|
131
|
+
end
|
132
|
+
|
133
|
+
return [y.url, y.filter_services(filter)]
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
|
data/lib/yadis/service.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
# Class representing an XRD Service element.
|
2
2
|
class ServiceEndpoint
|
3
3
|
|
4
|
-
attr_accessor :service_types, :uri, :yadis_uri, :element, :yadis
|
4
|
+
attr_accessor :service_types, :uri, :yadis_uri, :element, :yadis, :canonical_id
|
5
5
|
|
6
6
|
def initialize
|
7
7
|
@service_types = []
|
8
8
|
@uri = nil
|
9
9
|
@yadis_uri = nil
|
10
10
|
@element = nil
|
11
|
+
@canonical_id = nil
|
11
12
|
end
|
12
13
|
|
13
14
|
def match_type_uris(type_uris)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
|
3
|
+
# Class representing an XRD Service element.
|
4
|
+
class ServiceEndpoint
|
5
|
+
|
6
|
+
attr_accessor :service_types, :uri, :yadis_uri, :element, :yadis
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@service_types = []
|
10
|
+
@uri = nil
|
11
|
+
@yadis_uri = nil
|
12
|
+
@element = nil
|
13
|
+
@yadis_uri = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def match_type_uris(type_uris)
|
17
|
+
type_uris.find_all {|t| @service_types.member?(t)}
|
18
|
+
end
|
19
|
+
|
20
|
+
def ==(other)
|
21
|
+
return self.instance_variables == other.instance_variables
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/lib/yadis/xrds.rb
CHANGED
@@ -66,6 +66,14 @@ class XRDS
|
|
66
66
|
REXML::XPath.each(xrd, 'xrdns:Service', @@namespaces) do |s|
|
67
67
|
_create_services(s)
|
68
68
|
end
|
69
|
+
|
70
|
+
REXML::XPath.each(xrd, 'xrdns:CanonicalID', @@namespaces) do |c|
|
71
|
+
canonical_id = c.text.strip
|
72
|
+
if canonical_id.length > 0
|
73
|
+
self.services.each {|s| s.canonical_id = canonical_id}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
69
77
|
end
|
70
78
|
|
71
79
|
|
data/lib/yadis/xrds.rb~
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'yadis/service'
|
3
|
+
|
4
|
+
# Class that handles XRDS parsing and XRD Service element extraction.
|
5
|
+
|
6
|
+
module XRDSUtil
|
7
|
+
|
8
|
+
@@default_namespace = 'xri://$xrd*($v*2.0)'
|
9
|
+
@@xrds_namespace = {'xrds' => 'xri://$xrds'}
|
10
|
+
|
11
|
+
def last_xrd(root_element)
|
12
|
+
REXML::XPath.match(root_element, '/xrds:XRDS/XRD',
|
13
|
+
@@xrds_namespace)[-1]
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
class XRDS
|
19
|
+
|
20
|
+
include XRDSUtil
|
21
|
+
attr_reader :xml
|
22
|
+
|
23
|
+
# Method for producing a valid XRDS object. Accepts an XML
|
24
|
+
# String. Returns an XRDS object on success, or nil on failure.
|
25
|
+
# Same as calling XRDS.new, but does not rails ArgumentErrors.
|
26
|
+
def XRDS.parse(xml)
|
27
|
+
begin
|
28
|
+
return new(xml)
|
29
|
+
rescue
|
30
|
+
return nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Create a new XRDS object. Raises ArgumentError if xml_text is
|
35
|
+
# malformed or invalid XRDS.
|
36
|
+
def initialize(xml_text)
|
37
|
+
parse_xml(xml_text)
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse_xml(xml_text)
|
41
|
+
begin
|
42
|
+
xml = @xml = REXML::Document.new(xml_text)
|
43
|
+
rescue
|
44
|
+
raise ArgumentError, "Can't parse XRDS"
|
45
|
+
end
|
46
|
+
|
47
|
+
if xml.root.nil?
|
48
|
+
raise ArgumentError, "No document root"
|
49
|
+
end
|
50
|
+
|
51
|
+
xmlns = xml.root.attributes['xmlns']
|
52
|
+
if xmlns != @@default_namespace
|
53
|
+
raise ArgumentError, "Unknown XRID version #{xmlns.to_s}"
|
54
|
+
end
|
55
|
+
|
56
|
+
xrd = self.last_xrd(xml.root)
|
57
|
+
raise ArgumentError, "No XRD Elements found" if xrd.nil?
|
58
|
+
|
59
|
+
@services = {} # keyed by [service_priority, uri_priority]
|
60
|
+
xrd.elements.each('Service') {|s| _create_services(s)}
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
# Returns an Array of ServiceEndpoint objects, sorted by priority. Highest
|
65
|
+
# priority is at element 0.
|
66
|
+
def services
|
67
|
+
s = []
|
68
|
+
|
69
|
+
@services.keys.sort.each do |key|
|
70
|
+
services_list = @services[key].dup
|
71
|
+
|
72
|
+
# randomize services with the same priority
|
73
|
+
while services_list.length > 0
|
74
|
+
s << services_list.delete_at((rand * services_list.length).to_i)
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
return s
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# create services objects
|
85
|
+
def _create_services(service_element)
|
86
|
+
service = ServiceEndpoint.new
|
87
|
+
service.element = service_element
|
88
|
+
service.uri = nil
|
89
|
+
service.service_types = []
|
90
|
+
|
91
|
+
service_element.elements.each('Type') do |t|
|
92
|
+
service.service_types << t.text.strip
|
93
|
+
end
|
94
|
+
|
95
|
+
sp = service_element.attributes['priority']
|
96
|
+
service_priority = sp ? sp.to_i : -1
|
97
|
+
|
98
|
+
if service.element.elements['URI']
|
99
|
+
service.element.elements.each('URI') do |uri|
|
100
|
+
_service = service.dup
|
101
|
+
_service.uri = uri.text.strip
|
102
|
+
|
103
|
+
up = uri.attributes['priority']
|
104
|
+
uri_priority = up ? up.to_i : -1
|
105
|
+
priority = [service_priority, uri_priority]
|
106
|
+
|
107
|
+
_add_service(priority, _service)
|
108
|
+
end
|
109
|
+
|
110
|
+
else
|
111
|
+
priority = [service_priority, -1]
|
112
|
+
_add_service(priority, service)
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
def _add_service(priority, service)
|
119
|
+
unless @services.has_key?(priority)
|
120
|
+
@services[priority] = []
|
121
|
+
end
|
122
|
+
|
123
|
+
# services with the same priority are appended to the list
|
124
|
+
@services[priority] << service
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
data/lib/yadis/xri.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'yadis/xrds'
|
2
|
+
require 'yadis/fetcher'
|
3
|
+
|
4
|
+
# The '(' is for cross-reference authorities, and hopefully has a matching ')'
|
5
|
+
# somewhere.
|
6
|
+
XRI_AUTHORITIES = ["!", "=", "@", "+", "$", "("]
|
7
|
+
|
8
|
+
module XRI
|
9
|
+
|
10
|
+
def XRI.identifier_scheme(identifier)
|
11
|
+
if identifier.match('^xri://') or XRI_AUTHORITIES.member?(identifier[0].chr)
|
12
|
+
return :xri
|
13
|
+
else
|
14
|
+
return :uri
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Transform an XRI reference to an IRI reference.
|
19
|
+
# Note this is not not idempotent, so do not apply this to an identifier more
|
20
|
+
# than once.
|
21
|
+
# XRI Syntax section 2.3.1
|
22
|
+
def XRI.to_iri_normal(xri)
|
23
|
+
iri = xri.dup
|
24
|
+
iri.insert(0, 'xri://') if not iri.match('^xri://')
|
25
|
+
return escape_for_iri(iri)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Note this is not not idempotent, so do not apply this more than once.
|
29
|
+
# XRI Syntax section 2.3.2
|
30
|
+
def XRI.escape_for_iri(xri)
|
31
|
+
esc = xri.dup
|
32
|
+
# encode all %
|
33
|
+
esc.gsub!(/%/, '%25')
|
34
|
+
esc.gsub!(/\((.*?)\)/) { |xref_match|
|
35
|
+
xref_match.gsub(/[\/\?\#]/) { |char_match|
|
36
|
+
CGI::escape(char_match)
|
37
|
+
}
|
38
|
+
}
|
39
|
+
return esc
|
40
|
+
end
|
41
|
+
|
42
|
+
# Transform an XRI reference to a URI reference.
|
43
|
+
# Note this is not not idempotent, so do not apply this to an identifier more
|
44
|
+
# than once.
|
45
|
+
# XRI Syntax section 2.3.1
|
46
|
+
def XRI.to_uri_normal(xri)
|
47
|
+
return iri_to_uri(to_iri_normal(xri))
|
48
|
+
end
|
49
|
+
|
50
|
+
# RFC 3987 section 3.1
|
51
|
+
def XRI.iri_to_uri(iri)
|
52
|
+
uri = iri.dup
|
53
|
+
# for char in ucschar or iprivate
|
54
|
+
# convert each char to %HH%HH%HH (as many %HH as octets)
|
55
|
+
return uri
|
56
|
+
end
|
57
|
+
|
58
|
+
def provider_is_authoritative(provider_id, canonical_id)
|
59
|
+
lastbang = canonical_id.rindex('!')
|
60
|
+
return false unless lastbang
|
61
|
+
parent = canonical_id[0...lastbang]
|
62
|
+
return parent == provider_id
|
63
|
+
end
|
64
|
+
|
65
|
+
def root_authority(xri)
|
66
|
+
xri = xri[6..-1] if xri.index('xri://') == 0
|
67
|
+
authority = xri.split('/', 2)[0]
|
68
|
+
if authority[0].chr == '('
|
69
|
+
root = authority[0...authority.index(')')+1]
|
70
|
+
elsif XRI_AUTHORITIES.member?(authority[0].chr)
|
71
|
+
root = authority[0].chr
|
72
|
+
else
|
73
|
+
root = authority.split(/[!*]/)[0]
|
74
|
+
end
|
75
|
+
|
76
|
+
make_xri(root)
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
def make_xri(xri)
|
81
|
+
if xri.index('xri://') != 0
|
82
|
+
xri = 'xri://' + xri
|
83
|
+
end
|
84
|
+
return xri
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
data/lib/yadis/xrires.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require "cgi"
|
2
|
+
require "yadis/xri"
|
3
|
+
require "yadis/xrds"
|
4
|
+
require "yadis/fetcher"
|
5
|
+
|
6
|
+
module XRI
|
7
|
+
|
8
|
+
class XRIHTTPError < StandardError; end
|
9
|
+
|
10
|
+
class ProxyResolver
|
11
|
+
|
12
|
+
DEFAULT_PROXY = 'http://proxy.xri.net/'
|
13
|
+
|
14
|
+
def initialize(proxy_url=nil)
|
15
|
+
if proxy_url
|
16
|
+
@proxy_url = proxy_url
|
17
|
+
else
|
18
|
+
@proxy_url = DEFAULT_PROXY
|
19
|
+
end
|
20
|
+
|
21
|
+
@proxy_url += '/' unless @proxy_url.match('/$')
|
22
|
+
end
|
23
|
+
|
24
|
+
def query_url(xri, service_type=nil)
|
25
|
+
# URI normal form has a leading xri://, but we need to strip
|
26
|
+
# that off again for the QXRI. This is under discussion for
|
27
|
+
# XRI Resolution WD 11.
|
28
|
+
#
|
29
|
+
qxri = XRI.to_uri_normal(xri)[6..-1]
|
30
|
+
hxri = @proxy_url + qxri
|
31
|
+
args = {'_xrd_r' => 'application/xrds+xml'}
|
32
|
+
if service_type
|
33
|
+
args['_xrd_t'] = service_type
|
34
|
+
else
|
35
|
+
# don't perform service endpoint selection
|
36
|
+
args['_xrd_r'] += ';sep=false'
|
37
|
+
end
|
38
|
+
|
39
|
+
return XRI.append_args(hxri, args)
|
40
|
+
end
|
41
|
+
|
42
|
+
def query(xri, service_types)
|
43
|
+
# these can be query args or http headers, needn't be both.
|
44
|
+
# headers = {'Accept' => 'application/xrds+xml;sep=true'}
|
45
|
+
fetcher = NetHTTPFetcher.new
|
46
|
+
services = service_types.collect { |service_type|
|
47
|
+
url = self.query_url(xri, service_type)
|
48
|
+
response = fetcher.get(url)
|
49
|
+
raise XRIHTTPError, "Could not fetch #{xri}" if response.nil?
|
50
|
+
xrds = XRDS.new(response[1].body)
|
51
|
+
return xrds.services unless xrds.nil?
|
52
|
+
}
|
53
|
+
# TODO:
|
54
|
+
# * If we do get hits for multiple service_types, we're almost
|
55
|
+
# certainly going to have duplicated service entries and
|
56
|
+
# broken priority ordering.
|
57
|
+
services = services.inject([]) { |flatter, some_services|
|
58
|
+
flatter.concat(some_services) unless some_services.nil?
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def XRI.urlencode(args)
|
64
|
+
a = []
|
65
|
+
args.each do |key, val|
|
66
|
+
a << (CGI::escape(key) + "=" + CGI::escape(val))
|
67
|
+
end
|
68
|
+
a.join("&")
|
69
|
+
end
|
70
|
+
|
71
|
+
def XRI.append_args(url, args)
|
72
|
+
return url if args.length == 0
|
73
|
+
|
74
|
+
# rstrip question marks
|
75
|
+
rstripped = url.dup
|
76
|
+
while rstripped[-1].chr == '?'
|
77
|
+
rstripped = rstripped[0...rstripped.length-1]
|
78
|
+
end
|
79
|
+
|
80
|
+
if rstripped.index('?')
|
81
|
+
sep = '&'
|
82
|
+
else
|
83
|
+
sep = '?'
|
84
|
+
end
|
85
|
+
|
86
|
+
return url + sep + XRI.urlencode(args)
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
# Example:
|
92
|
+
# r = XRI::ProxyResolver.new('http://proxy.xri.net/')
|
93
|
+
# j = r.query('=keturn', ['xri://+i-service*(+forwarding)*($v*1.0)'])
|
94
|
+
# Stderr.puts("the answer is #{j}")
|
data/lib/yadis/yadis.rb~
ADDED
@@ -0,0 +1,104 @@
|
|
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
|
+
@@ca_path = nil
|
11
|
+
attr_accessor :uri, :xrds_uri, :xrds
|
12
|
+
|
13
|
+
# Discover services for a given URI. Please note that no normalization
|
14
|
+
# will be done to the passed in URI, it should be a textually
|
15
|
+
# valid URI string before calling discover.
|
16
|
+
#
|
17
|
+
# Returns nil if no XRDS was found, or a YADIS object on success. This
|
18
|
+
# method is essentially the same as YADIS.new, but does not raise any
|
19
|
+
# exceptions.
|
20
|
+
def YADIS.discover(uri)
|
21
|
+
return nil unless uri
|
22
|
+
begin
|
23
|
+
return YADIS.new(uri)
|
24
|
+
rescue
|
25
|
+
return nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Set the path to a certificate authority pem file, for verifying
|
30
|
+
# server certificates of HTTPS pages. If you are interested in verifying
|
31
|
+
# certs like the mozilla web browser, have a look at the files here:
|
32
|
+
#
|
33
|
+
# http://curl.haxx.se/docs/caextract.html
|
34
|
+
def YADIS.ca_path=(ca_path)
|
35
|
+
ca_path = ca_path.to_s
|
36
|
+
if File.exists?(ca_path)
|
37
|
+
@@ca_path = ca_path
|
38
|
+
else
|
39
|
+
raise ArgumentError, "#{ca_path} is not a valid file path"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Discover services for a URI using the Yadis protocol. +uri+ should
|
44
|
+
# be a valid URI represented as a string. This method may raise
|
45
|
+
# YADISParseError in the case of an invalid or unparsable XRDS file,
|
46
|
+
# or YADISHTTPError is the URI cannot be fetched.
|
47
|
+
def initialize(uri)
|
48
|
+
http = NetHTTPFetcher.new
|
49
|
+
http.ca_path = @@ca_path if @@ca_path
|
50
|
+
headers = {'Accept' => 'application/xrds+xml'}
|
51
|
+
|
52
|
+
response = http.get(uri, headers)
|
53
|
+
raise YADISHTTPError, "Could not fetch #{uri}" if response.nil?
|
54
|
+
|
55
|
+
uri, resp_payload = response
|
56
|
+
xrds_uri = uri
|
57
|
+
|
58
|
+
header = resp_payload['x-xrds-location']
|
59
|
+
header = resp_payload['x-yadis-location'] if header.nil?
|
60
|
+
|
61
|
+
if header
|
62
|
+
xrds_uri = header
|
63
|
+
response = http.get(xrds_uri)
|
64
|
+
raise YADISHTTPError, "Could not fetch XRDS #{xrds_uri}" if response.nil?
|
65
|
+
resp_payload = response[1]
|
66
|
+
end
|
67
|
+
|
68
|
+
unless resp_payload['content-type'] == 'application/xrds+xml'
|
69
|
+
loc = html_yadis_location(resp_payload.body)
|
70
|
+
unless loc.nil?
|
71
|
+
xrds_uri, resp_payload = http.get(loc)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
xrds = XRDS.parse(resp_payload.body)
|
76
|
+
raise YADISParseError, "Bad XRDS" if xrds.nil?
|
77
|
+
|
78
|
+
@uri = uri
|
79
|
+
@xrds_uri = xrds_uri
|
80
|
+
@xrds = xrds
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns an Array Service objects sorted by priority.
|
84
|
+
def services
|
85
|
+
@xrds.services.each {|s| s.yadis = self}
|
86
|
+
@xrds.services
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns a list of services, ordered by priority,
|
90
|
+
# that match the filter. filter is a Proc object that produces
|
91
|
+
# ServiceEnpoint objects, subclasses of ServiceEnpoint or nil.
|
92
|
+
# This method is useful for extracting several types of services while
|
93
|
+
# maintaining priority, for example you may write a filter Proc to extract
|
94
|
+
# OpenID and LID ServiceEnpoint objects.
|
95
|
+
def filter_services(filter)
|
96
|
+
# product a list of filtered ServiceEndpoint objects. filtered
|
97
|
+
# will contain a list of nil or ServiceEnpoint (subclasses) objects.
|
98
|
+
filtered = self.services.collect {|s| filter.call(s)}
|
99
|
+
|
100
|
+
# return all object in filtered that are not nil
|
101
|
+
return filtered.find_all {|s| s}
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
data/lib/yadis.rb
CHANGED
data/lib/yadis.rb~
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'yadis/yadis'
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<XRDS ref="xri://=keturn">
|
3
|
+
<XRD xmlns:xrd="xri://$xrd*($v*2.0)">
|
4
|
+
<Query>*keturn</Query>
|
5
|
+
<Status code="100"/>
|
6
|
+
<Expires>2006-05-31T02:45:37.000Z</Expires>
|
7
|
+
<ProviderID>xri://=</ProviderID>
|
8
|
+
<LocalID priority="10">!2397.58EC.8F7D.D097</LocalID>
|
9
|
+
<CanonicalID priority="10">=!2397.58EC.8F7D.D097</CanonicalID>
|
10
|
+
<Service priority="10">
|
11
|
+
<Type/>
|
12
|
+
<Type>xri://+i-service*(+forwarding)*($v*1.0)</Type>
|
13
|
+
<ProviderID>xri://!!1003</ProviderID>
|
14
|
+
<URI priority="10">https://forwarding.godaddy.amsoftsystems.com</URI>
|
15
|
+
</Service>
|
16
|
+
<Service priority="10">
|
17
|
+
<Type>xri://+i-service*(+contact)*($v*1.0)</Type>
|
18
|
+
<ProviderID>xri://!!1003</ProviderID>
|
19
|
+
<Path>+contact</Path>
|
20
|
+
<MediaType/>
|
21
|
+
<MediaType>text/html</MediaType>
|
22
|
+
<URI priority="10">https://contact.godaddy.amsoftsystems.com</URI>
|
23
|
+
</Service>
|
24
|
+
<Service priority="10">
|
25
|
+
<Type>xri://+i-service*(+authn)*(+saml)*($v*1.0)</Type>
|
26
|
+
<ProviderID>xri://!!1003</ProviderID>
|
27
|
+
<Path>+login</Path>
|
28
|
+
<URI priority="10">https://isso.godaddy.amsoftsystems.com</URI>
|
29
|
+
</Service>
|
30
|
+
</XRD>
|
31
|
+
</XRDS>
|
32
|
+
|
data/test/test_xri.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'yadis/xri'
|
3
|
+
|
4
|
+
class XriDiscoveryTestCase < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_isXRI?
|
7
|
+
assert_equal(:xri, XRI.identifier_scheme('=john.smith'))
|
8
|
+
assert_equal(:xri, XRI.identifier_scheme('@smiths/john'))
|
9
|
+
assert_equal(:xri, XRI.identifier_scheme('xri://=john'))
|
10
|
+
assert_equal(:xri, XRI.identifier_scheme('@ootao*test1'))
|
11
|
+
assert_equal(:uri, XRI.identifier_scheme('smoker.myopenid.com'))
|
12
|
+
assert_equal(:uri, XRI.identifier_scheme('http://smoker.myopenid.com'))
|
13
|
+
assert_equal(:uri, XRI.identifier_scheme('https://smoker.myopenid.com'))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class XriEscapingTestCase < Test::Unit::TestCase
|
18
|
+
def test_escaping_percents
|
19
|
+
assert_equal('@example/abc%252Fd/ef',
|
20
|
+
XRI.escape_for_iri('@example/abc%2Fd/ef'))
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_escaping_xref
|
24
|
+
# no escapes
|
25
|
+
assert_equal('@example/foo/(@bar)',
|
26
|
+
XRI.escape_for_iri('@example/foo/(@bar)'))
|
27
|
+
# escape slashes
|
28
|
+
assert_equal('@example/foo/(@bar%2Fbaz)',
|
29
|
+
XRI.escape_for_iri('@example/foo/(@bar/baz)'))
|
30
|
+
# escape query ? and fragment #
|
31
|
+
assert_equal('@example/foo/(@baz%3Fp=q%23r)?i=j#k',
|
32
|
+
XRI.escape_for_iri('@example/foo/(@baz?p=q#r)?i=j#k'))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class XriTransformationTestCase < Test::Unit::TestCase
|
37
|
+
def test_to_iri_normal
|
38
|
+
assert_equal('xri://@example', XRI.to_iri_normal('@example'))
|
39
|
+
end
|
40
|
+
# iri_to_url:
|
41
|
+
# various ucschar to hex
|
42
|
+
end
|
43
|
+
|
44
|
+
H = 'http://xri.example.com/'
|
45
|
+
# ST = 'http://openid.net/signon/1.2'
|
46
|
+
ST = 'xri://+i-service*(+forwarding)*($v*1.0)'
|
47
|
+
class ProxyQueryTestCase < Test::Unit::TestCase
|
48
|
+
def setup
|
49
|
+
@proxy = XRI::ProxyResolver.new(H)
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_proxy_url
|
53
|
+
args_esc = "_xrd_r=application%2Fxrds%2Bxml&_xrd_t=#{CGI::escape(ST)}"
|
54
|
+
self.assert_equal(H + '=foo?' + args_esc, @proxy.query_url('=foo', ST))
|
55
|
+
self.assert_equal(H + '=foo/bar?baz&' + args_esc,
|
56
|
+
@proxy.query_url('=foo/bar?baz', ST))
|
57
|
+
self.assert_equal(H + '=foo/bar?baz=quux&' + args_esc,
|
58
|
+
@proxy.query_url('=foo/bar?baz=quux', ST))
|
59
|
+
self.assert_equal(H + '=foo/bar?mi=fa&so=la&' + args_esc,
|
60
|
+
@proxy.query_url('=foo/bar?mi=fa&so=la', ST))
|
61
|
+
# TODO:
|
62
|
+
# self.assert_equal(H + '=foo/bar??' + args_esc,
|
63
|
+
# @proxy.query_url('=foo/bar?', ST))
|
64
|
+
# self.assert_equal(H + '=foo/bar????' + args_esc,
|
65
|
+
# @proxy.query_url('=foo/bar???', ST))
|
66
|
+
end
|
67
|
+
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: ruby-yadis
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.3.
|
7
|
-
date: 2006-
|
6
|
+
version: 0.3.3
|
7
|
+
date: 2006-08-20 00:00:00 -07:00
|
8
8
|
summary: A library for performing Yadis service discovery
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -31,29 +31,39 @@ files:
|
|
31
31
|
- examples/openid.rb
|
32
32
|
- lib/yadis.rb
|
33
33
|
- lib/yadis
|
34
|
+
- lib/yadis.rb~
|
34
35
|
- lib/yadis/parsehtml.rb
|
35
36
|
- lib/yadis/xrds.rb
|
36
37
|
- lib/yadis/yadis.rb
|
37
38
|
- lib/yadis/fetcher.rb
|
38
39
|
- lib/yadis/htmltokenizer.rb
|
39
40
|
- lib/yadis/manager.rb
|
41
|
+
- lib/yadis/yadis.rb~
|
40
42
|
- lib/yadis/service.rb
|
43
|
+
- lib/yadis/manager.rb~
|
44
|
+
- lib/yadis/service.rb~
|
45
|
+
- lib/yadis/xrds.rb~
|
46
|
+
- lib/yadis/fetcher.rb~
|
47
|
+
- lib/yadis/xri.rb
|
48
|
+
- lib/yadis/xrires.rb
|
41
49
|
- test/test_parse.rb
|
42
50
|
- test/data
|
43
51
|
- test/test_discovery.rb
|
44
52
|
- test/test_yadis.rb
|
45
53
|
- test/test_xrds.rb
|
46
54
|
- test/runtests.rb
|
55
|
+
- test/test_xri.rb
|
47
56
|
- test/data/manifest.txt
|
48
57
|
- test/data/brian.xrds
|
49
|
-
- test/data/brian_priority.xrds
|
50
|
-
- test/data/brian.multi.xrds
|
51
58
|
- test/data/index.html
|
59
|
+
- test/data/brian.multi.xrds
|
60
|
+
- test/data/brian_priority.xrds
|
52
61
|
- test/data/brian.multi_uri.xrds
|
53
|
-
- test/data/index_yadis.html
|
54
62
|
- test/data/index_xrds.html
|
63
|
+
- test/data/index_yadis.html
|
55
64
|
- test/data/proxy-june1.xrds
|
56
65
|
- test/data/weirdver.xrds
|
66
|
+
- test/data/keturn.xrds
|
57
67
|
- test/data/brianellin.mylid.xrds
|
58
68
|
- README
|
59
69
|
- INSTALL
|