summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenId/MessageEncoder.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenId/MessageEncoder.cs')
-rw-r--r--src/DotNetOpenId/MessageEncoder.cs111
1 files changed, 111 insertions, 0 deletions
diff --git a/src/DotNetOpenId/MessageEncoder.cs b/src/DotNetOpenId/MessageEncoder.cs
new file mode 100644
index 0000000..bbcbdf0
--- /dev/null
+++ b/src/DotNetOpenId/MessageEncoder.cs
@@ -0,0 +1,111 @@
+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) {
+ 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;
+ 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;
+ }
+
+ 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);
+ 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; }
+ }
+}