1 //-----------------------------------------------------------------------
2 // <copyright file="Yadis.cs" company="Andrew Arnott, Scott Hanselman">
3 // Copyright (c) Andrew Arnott, Scott Hanselman. All rights reserved.
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth
.Yadis
{
11 using System
.Net
.Cache
;
12 using System
.Web
.UI
.HtmlControls
;
14 using DotNetOpenAuth
.Messaging
;
15 using DotNetOpenAuth
.OpenId
;
16 using DotNetOpenAuth
.Xrds
;
19 /// YADIS discovery manager.
21 internal class Yadis
{
23 /// The HTTP header to look for in responses to declare where the XRDS document should be found.
25 internal const string HeaderName
= "X-XRDS-Location";
28 /// Gets or sets the cache that can be used for HTTP requests made during identifier discovery.
30 internal static readonly RequestCachePolicy IdentifierDiscoveryCachePolicy
= new HttpRequestCachePolicy(HttpRequestCacheLevel
.CacheIfAvailable
);
33 /// The maximum number of bytes to read from an HTTP response
34 /// in searching for a link to a YADIS document.
36 private const int MaximumResultToScan
= 1024 * 1024;
39 /// Performs YADIS discovery on some identifier.
41 /// <param name="requestHandler">The mechanism to use for sending HTTP requests.</param>
42 /// <param name="uri">The URI to perform discovery on.</param>
43 /// <param name="requireSsl">Whether discovery should fail if any step of it is not encrypted.</param>
45 /// The result of discovery on the given URL.
46 /// Null may be returned if an error occurs,
47 /// or if <paramref name="requireSsl"/> is true but part of discovery
48 /// is not protected by SSL.
50 public static DiscoveryResult
Discover(IDirectWebRequestHandler requestHandler
, UriIdentifier uri
, bool requireSsl
) {
51 CachedDirectWebResponse response
;
53 if (requireSsl
&& !string.Equals(uri
.Uri
.Scheme
, Uri
.UriSchemeHttps
, StringComparison
.OrdinalIgnoreCase
)) {
54 Logger
.WarnFormat("Discovery on insecure identifier '{0}' aborted.", uri
);
57 response
= Request(requestHandler
, uri
, requireSsl
, ContentTypes
.Html
, ContentTypes
.XHtml
, ContentTypes
.Xrds
).GetSnapshot(MaximumResultToScan
);
58 if (response
.Status
!= System
.Net
.HttpStatusCode
.OK
) {
59 Logger
.ErrorFormat("HTTP error {0} {1} while performing discovery on {2}.", (int)response
.Status
, response
.Status
, uri
);
62 } catch (ArgumentException ex
) {
63 // Unsafe URLs generate this
64 Logger
.WarnFormat("Unsafe OpenId URL detected ({0}). Request aborted. {1}", uri
, ex
);
67 CachedDirectWebResponse response2
= null;
68 if (IsXrdsDocument(response
)) {
69 Logger
.Debug("An XRDS response was received from GET at user-supplied identifier.");
72 string uriString
= response
.Headers
.Get(HeaderName
);
74 if (uriString
!= null) {
75 if (Uri
.TryCreate(uriString
, UriKind
.Absolute
, out url
)) {
76 Logger
.DebugFormat("{0} found in HTTP header. Preparing to pull XRDS from {1}", HeaderName
, url
);
79 if (url
== null && response
.ContentType
.MediaType
== ContentTypes
.Html
) {
80 url
= FindYadisDocumentLocationInHtmlMetaTags(response
.Body
);
82 Logger
.DebugFormat("{0} found in HTML Http-Equiv tag. Preparing to pull XRDS from {1}", HeaderName
, url
);
86 if (!requireSsl
|| string.Equals(url
.Scheme
, Uri
.UriSchemeHttps
, StringComparison
.OrdinalIgnoreCase
)) {
87 response2
= Request(requestHandler
, url
, requireSsl
, ContentTypes
.Xrds
).GetSnapshot(MaximumResultToScan
);
88 if (response2
.Status
!= HttpStatusCode
.OK
) {
89 Logger
.ErrorFormat("HTTP error {0} {1} while performing discovery on {2}.", (int)response2
.Status
, response2
.Status
, uri
);
93 Logger
.WarnFormat("XRDS document at insecure location '{0}'. Aborting YADIS discovery.", url
);
97 return new DiscoveryResult(uri
, response
, response2
);
101 /// Searches an HTML document for a
102 /// <meta http-equiv="X-XRDS-Location" content="{YadisURL}">
103 /// tag and returns the content of YadisURL.
105 /// <param name="html">The HTML to search.</param>
106 /// <returns>The URI of the XRDS document if found; otherwise <c>null</c>.</returns>
107 public static Uri
FindYadisDocumentLocationInHtmlMetaTags(string html
) {
108 foreach (var metaTag
in HtmlParser
.HeadTags
<HtmlMeta
>(html
)) {
109 if (HeaderName
.Equals(metaTag
.HttpEquiv
, StringComparison
.OrdinalIgnoreCase
)) {
110 if (metaTag
.Content
!= null) {
112 if (Uri
.TryCreate(metaTag
.Content
, UriKind
.Absolute
, out uri
)) {
122 /// Sends a YADIS HTTP request as part of identifier discovery.
124 /// <param name="requestHandler">The request handler to use to actually submit the request.</param>
125 /// <param name="uri">The URI to GET.</param>
126 /// <param name="requireSsl">Whether only HTTPS URLs should ever be retrieved.</param>
127 /// <param name="acceptTypes">The value of the Accept HTTP header to include in the request.</param>
128 /// <returns>The HTTP response retrieved from the request.</returns>
129 internal static DirectWebResponse
Request(IDirectWebRequestHandler requestHandler
, Uri uri
, bool requireSsl
, params string[] acceptTypes
) {
130 ErrorUtilities
.VerifyArgumentNotNull(requestHandler
, "requestHandler");
131 ErrorUtilities
.VerifyArgumentNotNull(uri
, "uri");
133 HttpWebRequest request
= (HttpWebRequest
)WebRequest
.Create(uri
);
134 request
.CachePolicy
= IdentifierDiscoveryCachePolicy
;
135 if (acceptTypes
!= null) {
136 request
.Accept
= string.Join(",", acceptTypes
);
140 var sslRequestHandler
= requestHandler
as IDirectSslWebRequestHandler
;
141 ErrorUtilities
.VerifyArgument(sslRequestHandler
!= null, MessagingStrings
.SslOnlyRequestNotSupported
, typeof(IDirectWebRequestHandler
).Name
, typeof(IDirectSslWebRequestHandler
).Name
);
142 return sslRequestHandler
.GetResponse(request
, requireSsl
);
144 return requestHandler
.GetResponse(request
);
149 /// Determines whether a given HTTP response constitutes an XRDS document.
151 /// <param name="response">The response to test.</param>
153 /// <c>true</c> if the response constains an XRDS document; otherwise, <c>false</c>.
155 private static bool IsXrdsDocument(CachedDirectWebResponse response
) {
156 if (response
.ContentType
.MediaType
== ContentTypes
.Xrds
) {
160 if (response
.ContentType
.MediaType
== ContentTypes
.Xml
) {
161 // This COULD be an XRDS document with an imprecise content-type.
162 XmlReader reader
= XmlReader
.Create(new StringReader(response
.Body
));
163 while (reader
.Read() && reader
.NodeType
!= XmlNodeType
.Element
) {
164 // intentionally blank
166 if (reader
.NamespaceURI
== XrdsNode
.XrdsNamespace
&& reader
.Name
== "XRDS") {