context: //
  // HttpListenerResponse.cs
  //	Copied from System.Net.HttpListenerResponse.cs
  //
  // Author:
  //	Gonzalo Paniagua Javier (gonzalo@novell.com)
  //
  // Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
  // Copyright (c) 2012 sta.blockhead (sta.blockhead@gmail.com)
  //
  // Permission is hereby granted, free of charge, to any person obtaining
  // a copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to
  // permit persons to whom the Software is furnished to do so, subject to
  // the following conditions:
  // 
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  // 
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  //
  
  using System;
  using System.Globalization;
  using System.IO;
  using System.Net;
  using System.Text;
  
  namespace WebSocketSharp.Net
  {
      public sealed class HttpListenerResponse : IDisposable
      {
          #region Private Fields
  
          bool chunked;
          bool cl_set;
          Encoding content_encoding;
          long content_length;
          string content_type;
          HttpListenerContext context;
          CookieCollection cookies;
          bool disposed;
          bool force_close_chunked;
          WebHeaderCollection headers;
          bool keep_alive;
          string location;
          ResponseStream output_stream;
          int status_code;
          string status_description;
          Version version;
  
          #endregion
  
          #region Internal Fields
  
          internal bool HeadersSent;
  
          #endregion
  
          #region Constructor
  
          internal HttpListenerResponse(HttpListenerContext context)
          {
              this.context = context;
              Init();
          }
  
          #endregion
  
          #region Internal Property
  
          internal bool ForceCloseChunked => force_close_chunked;
  
          #endregion
  
          #region Public Properties
  
          public Encoding ContentEncoding
          {
              get
              {
                  if (content_encoding == null)
                      content_encoding = Encoding.Default;
                  return content_encoding;
              }
              set
              {
                  if (disposed)
                      throw new ObjectDisposedException(GetType().ToString());
  
                  // TODO: is null ok?
                  if (HeadersSent)
                      throw new InvalidOperationException("Cannot be changed after headers are sent.");
  
                  content_encoding = value;
              }
          }
  
          public long ContentLength64
          {
              get { return content_length; }
              set
              {
                  if (disposed)
                      throw new ObjectDisposedException(GetType().ToString());
  
                  if (HeadersSent)
                      throw new InvalidOperationException("Cannot be changed after headers are sent.");
  
                  if (value < 0)
                      throw new ArgumentOutOfRangeException("Must be >= 0", "value");
  
                  cl_set = true;
                  content_length = value;
              }
          }
  
          public string ContentType
          {
              get { return content_type; }
              set
              {
                  // TODO: is null ok?
                  if (disposed)
                      throw new ObjectDisposedException(GetType().ToString());
  
                  if (HeadersSent)
                      throw new InvalidOperationException("Cannot be changed after headers are sent.");
  
                  content_type = value;
              }
          }
  
          // RFC 2109, 2965 + the netscape specification at http://wp.netscape.com/newsref/std/cookie_spec.html
          public CookieCollection Cookies
          {
              get
              {
                  if (cookies == null)
                      cookies = new CookieCollection();
                  return cookies;
              }
              set { cookies = value; } // null allowed?
          }
  
          public WebHeaderCollection Headers
          {
              get { return headers; }
              set
              {
                  /**
                   *	"If you attempt to set a Content-Length, Keep-Alive, Transfer-Encoding, or
                   *	WWW-Authenticate header using the Headers property, an exception will be
                   *	thrown. Use the KeepAlive or ContentLength64 properties to set these headers.
                   *	You cannot set the Transfer-Encoding or WWW-Authenticate headers manually."
                  */
                  // TODO: check if this is marked readonly after headers are sent.
                  headers = value;
              }
          }
  
          public bool KeepAlive
          {
              get { return keep_alive; }
              set
              {
                  if (disposed)
                      throw new ObjectDisposedException(GetType().ToString());
  
                  if (HeadersSent)
                      throw new InvalidOperationException("Cannot be changed after headers are sent.");
  
                  keep_alive = value;
              }
          }
  
          public Stream OutputStream
          {
              get
              {
                  if (output_stream == null)
                      output_stream = context.Connection.GetResponseStream();
                  return output_stream;
              }
          }
  
          public Version ProtocolVersion
          {
              get { return version; }
              set
              {
                  if (disposed)
                      throw new ObjectDisposedException(GetType().ToString());
  
                  if (HeadersSent)
                      throw new InvalidOperationException("Cannot be changed after headers are sent.");
  
                  if (value == null)
                      throw new ArgumentNullException("value");
  
                  if (value.Major != 1 || (value.Minor != 0 && value.Minor != 1))
                      throw new ArgumentException("Must be 1.0 or 1.1", "value");
  
                  if (disposed)
                      throw new ObjectDisposedException(GetType().ToString());
  
                  version = value;
              }
          }
  
          public string RedirectLocation
          {
              get { return location; }
              set
              {
                  if (disposed)
                      throw new ObjectDisposedException(GetType().ToString());
  
                  if (HeadersSent)
                      throw new InvalidOperationException("Cannot be changed after headers are sent.");
  
                  location = value;
              }
          }
  
          public bool SendChunked
          {
              get { return chunked; }
              set
              {
                  if (disposed)
                      throw new ObjectDisposedException(GetType().ToString());
  
                  if (HeadersSent)
                      throw new InvalidOperationException("Cannot be changed after headers are sent.");
  
                  chunked = value;
              }
          }
  
          public int StatusCode
          {
              get { return status_code; }
              set
              {
                  if (disposed)
                      throw new ObjectDisposedException(GetType().ToString());
  
                  if (HeadersSent)
                      throw new InvalidOperationException("Cannot be changed after headers are sent.");
  
                  if (value < 100 || value > 999)
                      throw new ProtocolViolationException("StatusCode must be between 100 and 999.");
  
                  status_code = value;
                  status_description = Ext.GetStatusDescription(value);
              }
          }
  
          public string StatusDescription
          {
              get { return status_description; }
              set
              {
                  status_description = value;
              }
          }
  
          #endregion
  
          #region Private Methods
  
          void Close(bool force)
          {
              disposed = true;
              context.Connection.Close(force);
          }
  
          void IDisposable.Dispose()
          {
              Close(true); // TODO: Abort or Close?
          }
  
          bool FindCookie(Cookie cookie)
          {
              string name = cookie.Name;
              string domain = cookie.Domain;
              string path = cookie.Path;
              foreach (Cookie c in cookies)
              {
                  if (name != c.Name)
                      continue;
                  if (domain != c.Domain)
                      continue;
                  if (path == c.Path)
                      return true;
              }
  
              return false;
          }
  
          void Init()
          {
              headers = new WebHeaderCollection();
              keep_alive = true;
              status_code = 200;
              status_description = "OK";
              version = HttpVersion.Version11;
          }
  
          #endregion
  
          #region Internal Method
  
          internal void SendHeaders(bool closing, MemoryStream ms)
          {
              Encoding encoding = content_encoding;
              if (encoding == null)
                  encoding = Encoding.Default;
  
              if (content_type != null)
              {
                  if (content_encoding != null && content_type.IndexOf("charset=", StringComparison.Ordinal) == -1)
                  {
                      string enc_name = content_encoding.WebName;
                      headers.SetInternal("Content-Type", content_type + "; charset=" + enc_name);
                  }
                  else
                  {
                      headers.SetInternal("Content-Type", content_type);
                  }
              }
  
              if (headers["Server"] == null)
                  headers.SetInternal("Server", "Mono-HTTPAPI/1.0");
  
              CultureInfo inv = CultureInfo.InvariantCulture;
              if (headers["Date"] == null)
                  headers.SetInternal("Date", DateTime.UtcNow.ToString("r", inv));
  
              if (!chunked)
              {
                  if (!cl_set && closing)
                  {
                      cl_set = true;
                      content_length = 0;
                  }
  
                  if (cl_set)
                      headers.SetInternal("Content-Length", content_length.ToString(inv));
              }
  
              Version v = context.Request.ProtocolVersion;
              if (!cl_set && !chunked && v >= HttpVersion.Version11)
                  chunked = true;
  
              /* Apache forces closing the connection for these status codes:
               *	HttpStatusCode.BadRequest 				400
               *	HttpStatusCode.RequestTimeout 			408
               *	HttpStatusCode.LengthRequired 			411
               *	HttpStatusCode.RequestEntityTooLarge 	413
               *	HttpStatusCode.RequestUriTooLong 		414
               *	HttpStatusCode.InternalServerError 		500
               *	HttpStatusCode.ServiceUnavailable 		503
               */
              bool conn_close = (status_code == 400 || status_code == 408 || status_code == 411 ||
                      status_code == 413 || status_code == 414 || status_code == 500 ||
                      status_code == 503);
  
              if (conn_close == false)
                  conn_close = !context.Request.KeepAlive;
  
              // They sent both KeepAlive: true and Connection: close!?
              if (!keep_alive || conn_close)
              {
                  headers.SetInternal("Connection", "close");
                  conn_close = true;
              }
  
              if (chunked)
                  headers.SetInternal("Transfer-Encoding", "chunked");
  
              int reuses = context.Connection.Reuses;
              if (reuses >= 100)
              {
                  force_close_chunked = true;
                  if (!conn_close)
                  {
                      headers.SetInternal("Connection", "close");
                      conn_close = true;
                  }
              }
  
              if (!conn_close)
              {
                  headers.SetInternal("Keep-Alive", string.Format("timeout=15,max={0}", 100 - reuses));
                  if (context.Request.ProtocolVersion <= HttpVersion.Version10)
                      headers.SetInternal("Connection", "keep-alive");
              }
  
              if (location != null)
                  headers.SetInternal("Location", location);
  
  
gt:             if (cookies != null)