StyleCop clean.
[dotnetoauth.git] / src / DotNetOpenAuth / Yadis / Yadis.cs
blob7d63a5210f40a871252a3410e5554b3f5daa1425
1 //-----------------------------------------------------------------------
2 // <copyright file="Yadis.cs" company="Andrew Arnott, Scott Hanselman">
3 // Copyright (c) Andrew Arnott, Scott Hanselman. All rights reserved.
4 // </copyright>
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth.Yadis {
8 using System;
9 using System.IO;
10 using System.Net;
11 using System.Net.Cache;
12 using System.Web.UI.HtmlControls;
13 using System.Xml;
14 using DotNetOpenAuth.Messaging;
15 using DotNetOpenAuth.OpenId;
16 using DotNetOpenAuth.Xrds;
18 /// <summary>
19 /// YADIS discovery manager.
20 /// </summary>
21 internal class Yadis {
22 /// <summary>
23 /// The HTTP header to look for in responses to declare where the XRDS document should be found.
24 /// </summary>
25 internal const string HeaderName = "X-XRDS-Location";
27 /// <summary>
28 /// Gets or sets the cache that can be used for HTTP requests made during identifier discovery.
29 /// </summary>
30 internal static readonly RequestCachePolicy IdentifierDiscoveryCachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.CacheIfAvailable);
32 /// <summary>
33 /// The maximum number of bytes to read from an HTTP response
34 /// in searching for a link to a YADIS document.
35 /// </summary>
36 private const int MaximumResultToScan = 1024 * 1024;
38 /// <summary>
39 /// Performs YADIS discovery on some identifier.
40 /// </summary>
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>
44 /// <returns>
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.
49 /// </returns>
50 public static DiscoveryResult Discover(IDirectWebRequestHandler requestHandler, UriIdentifier uri, bool requireSsl) {
51 CachedDirectWebResponse response;
52 try {
53 if (requireSsl && !string.Equals(uri.Uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) {
54 Logger.WarnFormat("Discovery on insecure identifier '{0}' aborted.", uri);
55 return null;
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);
60 return null;
62 } catch (ArgumentException ex) {
63 // Unsafe URLs generate this
64 Logger.WarnFormat("Unsafe OpenId URL detected ({0}). Request aborted. {1}", uri, ex);
65 return null;
67 CachedDirectWebResponse response2 = null;
68 if (IsXrdsDocument(response)) {
69 Logger.Debug("An XRDS response was received from GET at user-supplied identifier.");
70 response2 = response;
71 } else {
72 string uriString = response.Headers.Get(HeaderName);
73 Uri url = null;
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);
81 if (url != null) {
82 Logger.DebugFormat("{0} found in HTML Http-Equiv tag. Preparing to pull XRDS from {1}", HeaderName, url);
85 if (url != null) {
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);
90 return null;
92 } else {
93 Logger.WarnFormat("XRDS document at insecure location '{0}'. Aborting YADIS discovery.", url);
97 return new DiscoveryResult(uri, response, response2);
100 /// <summary>
101 /// Searches an HTML document for a
102 /// &lt;meta http-equiv="X-XRDS-Location" content="{YadisURL}"&gt;
103 /// tag and returns the content of YadisURL.
104 /// </summary>
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) {
111 Uri uri;
112 if (Uri.TryCreate(metaTag.Content, UriKind.Absolute, out uri)) {
113 return uri;
118 return null;
121 /// <summary>
122 /// Sends a YADIS HTTP request as part of identifier discovery.
123 /// </summary>
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);
139 if (requireSsl) {
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);
143 } else {
144 return requestHandler.GetResponse(request);
148 /// <summary>
149 /// Determines whether a given HTTP response constitutes an XRDS document.
150 /// </summary>
151 /// <param name="response">The response to test.</param>
152 /// <returns>
153 /// <c>true</c> if the response constains an XRDS document; otherwise, <c>false</c>.
154 /// </returns>
155 private static bool IsXrdsDocument(CachedDirectWebResponse response) {
156 if (response.ContentType.MediaType == ContentTypes.Xrds) {
157 return true;
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") {
167 return true;
171 return false;