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" 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" 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.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