discovery.rb
153 lines
| 4.6 KiB
| text/x-ruby
|
RubyLexer
|
r2376 | |||
require 'openid/util' | ||||
require 'openid/fetchers' | ||||
require 'openid/yadis/constants' | ||||
require 'openid/yadis/parsehtml' | ||||
module OpenID | ||||
# Raised when a error occurs in the discovery process | ||||
class DiscoveryFailure < OpenIDError | ||||
attr_accessor :identity_url, :http_response | ||||
def initialize(message, http_response) | ||||
super(message) | ||||
@identity_url = nil | ||||
@http_response = http_response | ||||
end | ||||
end | ||||
module Yadis | ||||
# Contains the result of performing Yadis discovery on a URI | ||||
class DiscoveryResult | ||||
# The result of following redirects from the request_uri | ||||
attr_accessor :normalize_uri | ||||
# The URI from which the response text was returned (set to | ||||
# nil if there was no XRDS document found) | ||||
attr_accessor :xrds_uri | ||||
# The content-type returned with the response_text | ||||
attr_accessor :content_type | ||||
# The document returned from the xrds_uri | ||||
attr_accessor :response_text | ||||
attr_accessor :request_uri, :normalized_uri | ||||
def initialize(request_uri) | ||||
# Initialize the state of the object | ||||
# | ||||
# sets all attributes to None except the request_uri | ||||
@request_uri = request_uri | ||||
@normalized_uri = nil | ||||
@xrds_uri = nil | ||||
@content_type = nil | ||||
@response_text = nil | ||||
end | ||||
# Was the Yadis protocol's indirection used? | ||||
def used_yadis_location? | ||||
return @normalized_uri != @xrds_uri | ||||
end | ||||
# Is the response text supposed to be an XRDS document? | ||||
def is_xrds | ||||
return (used_yadis_location?() or | ||||
@content_type == YADIS_CONTENT_TYPE) | ||||
end | ||||
end | ||||
# Discover services for a given URI. | ||||
# | ||||
# uri: The identity URI as a well-formed http or https URI. The | ||||
# well-formedness and the protocol are not checked, but the | ||||
# results of this function are undefined if those properties do | ||||
# not hold. | ||||
# | ||||
# returns a DiscoveryResult object | ||||
# | ||||
# Raises DiscoveryFailure when the HTTP response does not have | ||||
# a 200 code. | ||||
def self.discover(uri) | ||||
result = DiscoveryResult.new(uri) | ||||
begin | ||||
resp = OpenID.fetch(uri, nil, {'Accept' => YADIS_ACCEPT_HEADER}) | ||||
rescue Exception | ||||
raise DiscoveryFailure.new("Failed to fetch identity URL #{uri} : #{$!}", $!) | ||||
end | ||||
if resp.code != "200" and resp.code != "206" | ||||
raise DiscoveryFailure.new( | ||||
"HTTP Response status from identity URL host is not \"200\"."\ | ||||
"Got status #{resp.code.inspect} for #{resp.final_url}", resp) | ||||
end | ||||
# Note the URL after following redirects | ||||
result.normalized_uri = resp.final_url | ||||
# Attempt to find out where to go to discover the document or if | ||||
# we already have it | ||||
result.content_type = resp['content-type'] | ||||
result.xrds_uri = self.where_is_yadis?(resp) | ||||
if result.xrds_uri and result.used_yadis_location? | ||||
begin | ||||
resp = OpenID.fetch(result.xrds_uri) | ||||
rescue | ||||
raise DiscoveryFailure.new("Failed to fetch Yadis URL #{result.xrds_uri} : #{$!}", $!) | ||||
end | ||||
if resp.code != "200" and resp.code != "206" | ||||
exc = DiscoveryFailure.new( | ||||
"HTTP Response status from Yadis host is not \"200\". " + | ||||
"Got status #{resp.code.inspect} for #{resp.final_url}", resp) | ||||
exc.identity_url = result.normalized_uri | ||||
raise exc | ||||
end | ||||
result.content_type = resp['content-type'] | ||||
end | ||||
result.response_text = resp.body | ||||
return result | ||||
end | ||||
# Given a HTTPResponse, return the location of the Yadis | ||||
# document. | ||||
# | ||||
# May be the URL just retrieved, another URL, or None, if I | ||||
# can't find any. | ||||
# | ||||
# [non-blocking] | ||||
def self.where_is_yadis?(resp) | ||||
# Attempt to find out where to go to discover the document or if | ||||
# we already have it | ||||
content_type = resp['content-type'] | ||||
# According to the spec, the content-type header must be an | ||||
# exact match, or else we have to look for an indirection. | ||||
if (!content_type.nil? and !content_type.to_s.empty? and | ||||
content_type.split(';', 2)[0].downcase == YADIS_CONTENT_TYPE) | ||||
return resp.final_url | ||||
else | ||||
# Try the header | ||||
yadis_loc = resp[YADIS_HEADER_NAME.downcase] | ||||
if yadis_loc.nil? | ||||
# Parse as HTML if the header is missing. | ||||
# | ||||
# XXX: do we want to do something with content-type, like | ||||
# have a whitelist or a blacklist (for detecting that it's | ||||
# HTML)? | ||||
yadis_loc = Yadis.html_yadis_location(resp.body) | ||||
end | ||||
end | ||||
return yadis_loc | ||||
end | ||||
end | ||||
end | ||||