diff options
Diffstat (limited to 'src/DotNetOpenId/MessageEncoder.cs')
-rw-r--r-- | src/DotNetOpenId/MessageEncoder.cs | 235 |
1 files changed, 129 insertions, 106 deletions
diff --git a/src/DotNetOpenId/MessageEncoder.cs b/src/DotNetOpenId/MessageEncoder.cs index 3d5f840..2de2c0e 100644 --- a/src/DotNetOpenId/MessageEncoder.cs +++ b/src/DotNetOpenId/MessageEncoder.cs @@ -1,106 +1,129 @@ -using System;
-using System.Collections.Specialized;
-using System.Text;
-using System.Net;
-using System.Diagnostics;
-using DotNetOpenId.Provider;
-using System.IO;
-using System.Web;
-
-namespace DotNetOpenId {
- /// <summary>
- /// Encodes <see cref="IEncodable"/> messages into <see cref="Response"/> instances
- /// that can be interpreted by the host web site.
- /// </summary>
- internal class MessageEncoder {
- /// <summary>
- /// The maximum allowable size for a 301 Redirect response before we send
- /// a 200 OK response with a scripted form POST with the parameters instead
- /// in order to ensure successfully sending a large payload to another server
- /// that might have a maximum allowable size restriction on its GET request.
- /// </summary>
- internal static int GetToPostThreshold = 2 * 1024; // 2KB, recommended by OpenID group
- // We are intentionally using " instead of the html single quote ' below because
- // the HtmlEncode'd values that we inject will only escape the double quote, so
- // only the double-quote used around these values is safe.
- const string FormPostFormat = @"
-<html>
-<body onload=""var btn = document.getElementById('submit_button'); btn.disabled = true; btn.value = 'Login in progress'; document.getElementById('openid_message').submit()"">
-<form id=""openid_message"" action=""{0}"" method=""post"" accept-charset=""UTF-8"" enctype=""application/x-www-form-urlencoded"" onSubmit=""var btn = document.getElementById('submit_button'); btn.disabled = true; btn.value = 'Login in progress'; return true;"">
-{1}
- <input id=""submit_button"" type=""submit"" value=""Continue"" />
-</form>
-</body>
-</html>
-";
- /// <summary>
- /// Encodes messages into <see cref="Response"/> instances.
- /// </summary>
- public virtual Response Encode(IEncodable message) {
- EncodingType encode_as = message.EncodingType;
- Response wr;
-
- WebHeaderCollection headers = new WebHeaderCollection();
- switch (encode_as) {
- case EncodingType.DirectResponse:
- HttpStatusCode code = (message is Exception) ?
- HttpStatusCode.BadRequest : HttpStatusCode.OK;
- wr = new Response(code, headers, ProtocolMessages.KeyValueForm.GetBytes(message.EncodedFields), message);
- break;
- case EncodingType.IndirectMessage:
- // TODO: either redirect or do a form POST depending on payload size.
- Debug.Assert(message.RedirectUrl != null);
- if (getSizeOfPayload(message) <= GetToPostThreshold)
- wr = Create301RedirectResponse(message);
- else
- wr = CreateFormPostResponse(message);
- break;
- default:
- if (TraceUtil.Switch.TraceError) {
- Trace.TraceError("Cannot encode response: {0}", message);
- }
- wr = new Response(HttpStatusCode.BadRequest, headers, new byte[0], message);
- break;
- }
- return wr;
- }
-
- static int getSizeOfPayload(IEncodable message) {
- Debug.Assert(message != null);
- int size = 0;
- foreach (var field in message.EncodedFields) {
- size += field.Key.Length;
- size += field.Value.Length;
- }
- return size;
- }
- protected virtual Response Create301RedirectResponse(IEncodable message) {
- WebHeaderCollection headers = new WebHeaderCollection();
- UriBuilder builder = new UriBuilder(message.RedirectUrl);
- UriUtil.AppendQueryArgs(builder, message.EncodedFields);
- headers.Add(HttpResponseHeader.Location, builder.Uri.AbsoluteUri);
- return new Response(HttpStatusCode.Redirect, headers, new byte[0], message);
- }
- protected virtual Response CreateFormPostResponse(IEncodable message) {
- WebHeaderCollection headers = new WebHeaderCollection();
- MemoryStream body = new MemoryStream();
- StreamWriter bodyWriter = new StreamWriter(body);
- StringBuilder hiddenFields = new StringBuilder();
- foreach (var field in message.EncodedFields) {
- hiddenFields.AppendFormat("\t<input type=\"hidden\" name=\"{0}\" value=\"{1}\" />\r\n",
- HttpUtility.HtmlEncode(field.Key), HttpUtility.HtmlEncode(field.Value));
- }
- bodyWriter.WriteLine(FormPostFormat,
- HttpUtility.HtmlEncode(message.RedirectUrl.AbsoluteUri), hiddenFields);
- bodyWriter.Flush();
- return new Response(HttpStatusCode.OK, headers, body.ToArray(), message);
- }
- }
-
- internal class EncodeEventArgs : EventArgs {
- public EncodeEventArgs(IEncodable encodable) {
- Message = encodable;
- }
- public IEncodable Message { get; private set; }
- }
-}
+using System; +using System.Collections.Specialized; +using System.Text; +using System.Net; +using System.Diagnostics; +using DotNetOpenId.Provider; +using System.IO; +using System.Web; + +namespace DotNetOpenId { + /// <summary> + /// Encodes <see cref="IEncodable"/> messages into <see cref="Response"/> instances + /// that can be interpreted by the host web site. + /// </summary> + internal class MessageEncoder { + /// <summary> + /// The HTTP Content-Type to use in Key-Value Form responses. + /// </summary> + /// <remarks> + /// OpenID 2.0 section 5.1.2 says this SHOULD be text/plain. But this value + /// does not prevent free hosters like GoDaddy from tacking on their ads + /// to the end of the direct response, corrupting the data. So we deviate + /// from the spec a bit here to improve the story for free Providers. + /// </remarks> + const string KeyValueFormContentType = "application/x-openid-kvf"; + /// <summary> + /// The maximum allowable size for a 301 Redirect response before we send + /// a 200 OK response with a scripted form POST with the parameters instead + /// in order to ensure successfully sending a large payload to another server + /// that might have a maximum allowable size restriction on its GET request. + /// </summary> + internal static int GetToPostThreshold = 2 * 1024; // 2KB, per OpenID group and http://support.microsoft.com/kb/q208427/ + // We are intentionally using " instead of the html single quote ' below because + // the HtmlEncode'd values that we inject will only escape the double quote, so + // only the double-quote used around these values is safe. + const string FormPostFormat = @" +<html> +<body onload=""var btn = document.getElementById('submit_button'); btn.disabled = true; btn.value = 'Login in progress'; document.getElementById('openid_message').submit()""> +<form id=""openid_message"" action=""{0}"" method=""post"" accept-charset=""UTF-8"" enctype=""application/x-www-form-urlencoded"" onSubmit=""var btn = document.getElementById('submit_button'); btn.disabled = true; btn.value = 'Login in progress'; return true;""> +{1} + <input id=""submit_button"" type=""submit"" value=""Continue"" /> +</form> +</body> +</html> +"; + /// <summary> + /// Encodes messages into <see cref="Response"/> instances. + /// </summary> + public virtual Response Encode(IEncodable message) { + if (message == null) throw new ArgumentNullException("message"); + + EncodingType encode_as = message.EncodingType; + Response wr; + + WebHeaderCollection headers = new WebHeaderCollection(); + switch (encode_as) { + case EncodingType.DirectResponse: + Logger.DebugFormat("Sending direct message response:{0}{1}", + Environment.NewLine, Util.ToString(message.EncodedFields)); + HttpStatusCode code = (message is Exception) ? + HttpStatusCode.BadRequest : HttpStatusCode.OK; + // Key-Value Encoding is how response bodies are sent. + // Setting the content-type to something other than text/html or text/plain + // prevents free hosted sites like GoDaddy's from automatically appending + // the <script/> at the end that adds a banner, and invalidating our response. + headers.Add(HttpResponseHeader.ContentType, KeyValueFormContentType); + wr = new Response(code, headers, ProtocolMessages.KeyValueForm.GetBytes(message.EncodedFields), message); + break; + case EncodingType.IndirectMessage: + Logger.DebugFormat("Sending indirect message:{0}{1}", + Environment.NewLine, Util.ToString(message.EncodedFields)); + // TODO: either redirect or do a form POST depending on payload size. + Debug.Assert(message.RedirectUrl != null); + if (getSizeOfPayload(message) <= GetToPostThreshold) + wr = Create301RedirectResponse(message); + else + wr = CreateFormPostResponse(message); + break; + default: + Logger.ErrorFormat("Cannot encode response: {0}", message); + wr = new Response(HttpStatusCode.BadRequest, headers, new byte[0], message); + break; + } + return wr; + } + + /// <summary> + /// Gets the length of the URL that would be used for a simple redirect to carry + /// this indirect message to its recipient. + /// </summary> + /// <param name="message">The message.</param> + /// <returns>The number of characters in the redirect URL.</returns> + static int getSizeOfPayload(IEncodable message) { + Debug.Assert(message != null); + UriBuilder redirect = new UriBuilder(message.RedirectUrl); + UriUtil.AppendQueryArgs(redirect, message.EncodedFields); + return redirect.Uri.AbsoluteUri.Length; + } + protected virtual Response Create301RedirectResponse(IEncodable message) { + WebHeaderCollection headers = new WebHeaderCollection(); + UriBuilder builder = new UriBuilder(message.RedirectUrl); + UriUtil.AppendQueryArgs(builder, message.EncodedFields); + headers.Add(HttpResponseHeader.Location, builder.Uri.AbsoluteUri); + Logger.DebugFormat("Redirecting to {0}", builder.Uri.AbsoluteUri); + return new Response(HttpStatusCode.Redirect, headers, new byte[0], message); + } + protected virtual Response CreateFormPostResponse(IEncodable message) { + WebHeaderCollection headers = new WebHeaderCollection(); + MemoryStream body = new MemoryStream(); + StreamWriter bodyWriter = new StreamWriter(body); + StringBuilder hiddenFields = new StringBuilder(); + foreach (var field in message.EncodedFields) { + hiddenFields.AppendFormat("\t<input type=\"hidden\" name=\"{0}\" value=\"{1}\" />\r\n", + HttpUtility.HtmlEncode(field.Key), HttpUtility.HtmlEncode(field.Value)); + } + bodyWriter.WriteLine(FormPostFormat, + HttpUtility.HtmlEncode(message.RedirectUrl.AbsoluteUri), hiddenFields); + bodyWriter.Flush(); + return new Response(HttpStatusCode.OK, headers, body.ToArray(), message); + } + } + + internal class EncodeEventArgs : EventArgs { + public EncodeEventArgs(IEncodable encodable) { + Message = encodable; + } + public IEncodable Message { get; private set; } + } +} |