|
@@ -1,572 +1,302 @@
|
|
-using System;
|
|
|
|
-using System.Globalization;
|
|
|
|
|
|
+using System;
|
|
|
|
+using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.IO;
|
|
|
|
+using System.Linq;
|
|
using System.Net;
|
|
using System.Net;
|
|
using System.Text;
|
|
using System.Text;
|
|
-using System.Threading;
|
|
|
|
using System.Threading.Tasks;
|
|
using System.Threading.Tasks;
|
|
-using MediaBrowser.Model.IO;
|
|
|
|
-using MediaBrowser.Model.Logging;
|
|
|
|
-using MediaBrowser.Model.Text;
|
|
|
|
-using SocketHttpListener.Primitives;
|
|
|
|
|
|
+using System.Globalization;
|
|
|
|
+using System.Runtime.InteropServices;
|
|
|
|
+using System.ComponentModel;
|
|
|
|
+using System.Diagnostics;
|
|
|
|
+using Microsoft.Win32.SafeHandles;
|
|
|
|
|
|
namespace SocketHttpListener.Net
|
|
namespace SocketHttpListener.Net
|
|
{
|
|
{
|
|
- public sealed class HttpListenerResponse : IDisposable
|
|
|
|
|
|
+ public sealed unsafe partial class HttpListenerResponse : IDisposable
|
|
{
|
|
{
|
|
- bool disposed;
|
|
|
|
- Encoding content_encoding;
|
|
|
|
- long content_length;
|
|
|
|
- bool cl_set;
|
|
|
|
- string content_type;
|
|
|
|
- CookieCollection cookies;
|
|
|
|
- WebHeaderCollection headers = new WebHeaderCollection();
|
|
|
|
- bool keep_alive = true;
|
|
|
|
- Stream output_stream;
|
|
|
|
- Version version = HttpVersion.Version11;
|
|
|
|
- string location;
|
|
|
|
- int status_code = 200;
|
|
|
|
- string status_description = "OK";
|
|
|
|
- bool chunked;
|
|
|
|
- HttpListenerContext context;
|
|
|
|
-
|
|
|
|
- internal bool HeadersSent;
|
|
|
|
- internal object headers_lock = new object();
|
|
|
|
-
|
|
|
|
- private readonly ILogger _logger;
|
|
|
|
- private readonly ITextEncoding _textEncoding;
|
|
|
|
- private readonly IFileSystem _fileSystem;
|
|
|
|
-
|
|
|
|
- internal HttpListenerResponse(HttpListenerContext context, ILogger logger, ITextEncoding textEncoding, IFileSystem fileSystem)
|
|
|
|
- {
|
|
|
|
- this.context = context;
|
|
|
|
- _logger = logger;
|
|
|
|
- _textEncoding = textEncoding;
|
|
|
|
- _fileSystem = fileSystem;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- internal bool CloseConnection
|
|
|
|
- {
|
|
|
|
- get
|
|
|
|
- {
|
|
|
|
- return headers["Connection"] == "close";
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public bool ForceCloseChunked
|
|
|
|
- {
|
|
|
|
- get { return false; }
|
|
|
|
- }
|
|
|
|
|
|
+ private BoundaryType _boundaryType = BoundaryType.None;
|
|
|
|
+ private CookieCollection _cookies;
|
|
|
|
+ private HttpListenerContext _httpContext;
|
|
|
|
+ private bool _keepAlive = true;
|
|
|
|
+ private HttpResponseStream _responseStream;
|
|
|
|
+ private string _statusDescription;
|
|
|
|
+ private WebHeaderCollection _webHeaders = new WebHeaderCollection();
|
|
|
|
|
|
- public Encoding ContentEncoding
|
|
|
|
|
|
+ public WebHeaderCollection Headers
|
|
{
|
|
{
|
|
- get
|
|
|
|
- {
|
|
|
|
- if (content_encoding == null)
|
|
|
|
- content_encoding = _textEncoding.GetDefaultEncoding();
|
|
|
|
- return content_encoding;
|
|
|
|
- }
|
|
|
|
- set
|
|
|
|
- {
|
|
|
|
- if (disposed)
|
|
|
|
- throw new ObjectDisposedException(GetType().ToString());
|
|
|
|
-
|
|
|
|
- content_encoding = value;
|
|
|
|
- }
|
|
|
|
|
|
+ get => _webHeaders;
|
|
}
|
|
}
|
|
|
|
|
|
- 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 Encoding ContentEncoding { get; set; }
|
|
|
|
|
|
public string ContentType
|
|
public string ContentType
|
|
{
|
|
{
|
|
- get { return content_type; }
|
|
|
|
|
|
+ get => Headers["Content-Type"];
|
|
set
|
|
set
|
|
{
|
|
{
|
|
- // TODO: is null ok?
|
|
|
|
- if (disposed)
|
|
|
|
- throw new ObjectDisposedException(GetType().ToString());
|
|
|
|
-
|
|
|
|
- content_type = value;
|
|
|
|
|
|
+ CheckDisposed();
|
|
|
|
+ if (string.IsNullOrEmpty(value))
|
|
|
|
+ {
|
|
|
|
+ Headers.Remove("Content-Type");
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ Headers.Set("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?
|
|
|
|
- }
|
|
|
|
|
|
+ private HttpListenerContext HttpListenerContext => _httpContext;
|
|
|
|
|
|
- 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;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ private HttpListenerRequest HttpListenerRequest => HttpListenerContext.Request;
|
|
|
|
|
|
- public bool KeepAlive
|
|
|
|
|
|
+ internal EntitySendFormat EntitySendFormat
|
|
{
|
|
{
|
|
- get { return keep_alive; }
|
|
|
|
|
|
+ get => (EntitySendFormat)_boundaryType;
|
|
set
|
|
set
|
|
{
|
|
{
|
|
- if (disposed)
|
|
|
|
- throw new ObjectDisposedException(GetType().ToString());
|
|
|
|
-
|
|
|
|
- keep_alive = value;
|
|
|
|
|
|
+ CheckDisposed();
|
|
|
|
+ CheckSentHeaders();
|
|
|
|
+ if (value == EntitySendFormat.Chunked && HttpListenerRequest.ProtocolVersion.Minor == 0)
|
|
|
|
+ {
|
|
|
|
+ throw new ProtocolViolationException("net_nochunkuploadonhttp10");
|
|
|
|
+ }
|
|
|
|
+ _boundaryType = (BoundaryType)value;
|
|
|
|
+ if (value != EntitySendFormat.ContentLength)
|
|
|
|
+ {
|
|
|
|
+ _contentLength = -1;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- public Stream OutputStream
|
|
|
|
|
|
+ public bool SendChunked
|
|
{
|
|
{
|
|
- get
|
|
|
|
- {
|
|
|
|
- if (output_stream == null)
|
|
|
|
- output_stream = context.Connection.GetResponseStream();
|
|
|
|
- return output_stream;
|
|
|
|
- }
|
|
|
|
|
|
+ get => EntitySendFormat == EntitySendFormat.Chunked;
|
|
|
|
+ set => EntitySendFormat = value ? EntitySendFormat.Chunked : EntitySendFormat.ContentLength;
|
|
}
|
|
}
|
|
|
|
|
|
- public Version ProtocolVersion
|
|
|
|
|
|
+ // We MUST NOT send message-body when we send responses with these Status codes
|
|
|
|
+ private static readonly int[] s_noResponseBody = { 100, 101, 204, 205, 304 };
|
|
|
|
+
|
|
|
|
+ private static bool CanSendResponseBody(int responseCode)
|
|
{
|
|
{
|
|
- get { return version; }
|
|
|
|
- set
|
|
|
|
|
|
+ for (int i = 0; i < s_noResponseBody.Length; i++)
|
|
{
|
|
{
|
|
- if (disposed)
|
|
|
|
- throw new ObjectDisposedException(GetType().ToString());
|
|
|
|
-
|
|
|
|
- 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;
|
|
|
|
|
|
+ if (responseCode == s_noResponseBody[i])
|
|
|
|
+ {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
+ return true;
|
|
}
|
|
}
|
|
|
|
|
|
- public string RedirectLocation
|
|
|
|
|
|
+ public long ContentLength64
|
|
{
|
|
{
|
|
- get { return location; }
|
|
|
|
|
|
+ get => _contentLength;
|
|
set
|
|
set
|
|
{
|
|
{
|
|
- if (disposed)
|
|
|
|
- throw new ObjectDisposedException(GetType().ToString());
|
|
|
|
-
|
|
|
|
- location = value;
|
|
|
|
|
|
+ CheckDisposed();
|
|
|
|
+ CheckSentHeaders();
|
|
|
|
+ if (value >= 0)
|
|
|
|
+ {
|
|
|
|
+ _contentLength = value;
|
|
|
|
+ _boundaryType = BoundaryType.ContentLength;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ throw new ArgumentOutOfRangeException("net_clsmall");
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- public bool SendChunked
|
|
|
|
|
|
+ public CookieCollection Cookies
|
|
{
|
|
{
|
|
- get { return chunked; }
|
|
|
|
- set
|
|
|
|
- {
|
|
|
|
- if (disposed)
|
|
|
|
- throw new ObjectDisposedException(GetType().ToString());
|
|
|
|
-
|
|
|
|
- chunked = value;
|
|
|
|
- }
|
|
|
|
|
|
+ get => _cookies ?? (_cookies = new CookieCollection());
|
|
|
|
+ set => _cookies = value;
|
|
}
|
|
}
|
|
|
|
|
|
- public int StatusCode
|
|
|
|
|
|
+ public bool KeepAlive
|
|
{
|
|
{
|
|
- get { return status_code; }
|
|
|
|
|
|
+ get => _keepAlive;
|
|
set
|
|
set
|
|
{
|
|
{
|
|
- if (disposed)
|
|
|
|
- throw new ObjectDisposedException(GetType().ToString());
|
|
|
|
-
|
|
|
|
- if (value < 100 || value > 999)
|
|
|
|
- throw new ProtocolViolationException("StatusCode must be between 100 and 999.");
|
|
|
|
- status_code = value;
|
|
|
|
- status_description = GetStatusDescription(value);
|
|
|
|
|
|
+ CheckDisposed();
|
|
|
|
+ _keepAlive = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- internal static string GetStatusDescription(int code)
|
|
|
|
|
|
+ public Stream OutputStream
|
|
{
|
|
{
|
|
- switch (code)
|
|
|
|
|
|
+ get
|
|
{
|
|
{
|
|
- case 100: return "Continue";
|
|
|
|
- case 101: return "Switching Protocols";
|
|
|
|
- case 102: return "Processing";
|
|
|
|
- case 200: return "OK";
|
|
|
|
- case 201: return "Created";
|
|
|
|
- case 202: return "Accepted";
|
|
|
|
- case 203: return "Non-Authoritative Information";
|
|
|
|
- case 204: return "No Content";
|
|
|
|
- case 205: return "Reset Content";
|
|
|
|
- case 206: return "Partial Content";
|
|
|
|
- case 207: return "Multi-Status";
|
|
|
|
- case 300: return "Multiple Choices";
|
|
|
|
- case 301: return "Moved Permanently";
|
|
|
|
- case 302: return "Found";
|
|
|
|
- case 303: return "See Other";
|
|
|
|
- case 304: return "Not Modified";
|
|
|
|
- case 305: return "Use Proxy";
|
|
|
|
- case 307: return "Temporary Redirect";
|
|
|
|
- case 400: return "Bad Request";
|
|
|
|
- case 401: return "Unauthorized";
|
|
|
|
- case 402: return "Payment Required";
|
|
|
|
- case 403: return "Forbidden";
|
|
|
|
- case 404: return "Not Found";
|
|
|
|
- case 405: return "Method Not Allowed";
|
|
|
|
- case 406: return "Not Acceptable";
|
|
|
|
- case 407: return "Proxy Authentication Required";
|
|
|
|
- case 408: return "Request Timeout";
|
|
|
|
- case 409: return "Conflict";
|
|
|
|
- case 410: return "Gone";
|
|
|
|
- case 411: return "Length Required";
|
|
|
|
- case 412: return "Precondition Failed";
|
|
|
|
- case 413: return "Request Entity Too Large";
|
|
|
|
- case 414: return "Request-Uri Too Long";
|
|
|
|
- case 415: return "Unsupported Media Type";
|
|
|
|
- case 416: return "Requested Range Not Satisfiable";
|
|
|
|
- case 417: return "Expectation Failed";
|
|
|
|
- case 422: return "Unprocessable Entity";
|
|
|
|
- case 423: return "Locked";
|
|
|
|
- case 424: return "Failed Dependency";
|
|
|
|
- case 500: return "Internal Server Error";
|
|
|
|
- case 501: return "Not Implemented";
|
|
|
|
- case 502: return "Bad Gateway";
|
|
|
|
- case 503: return "Service Unavailable";
|
|
|
|
- case 504: return "Gateway Timeout";
|
|
|
|
- case 505: return "Http Version Not Supported";
|
|
|
|
- case 507: return "Insufficient Storage";
|
|
|
|
|
|
+ CheckDisposed();
|
|
|
|
+ EnsureResponseStream();
|
|
|
|
+ return _responseStream;
|
|
}
|
|
}
|
|
- return "";
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- public string StatusDescription
|
|
|
|
|
|
+ public string RedirectLocation
|
|
{
|
|
{
|
|
- get { return status_description; }
|
|
|
|
|
|
+ get => Headers["Location"];
|
|
set
|
|
set
|
|
{
|
|
{
|
|
- status_description = value;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- void IDisposable.Dispose()
|
|
|
|
- {
|
|
|
|
- Close(true); //TODO: Abort or Close?
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public void Abort()
|
|
|
|
- {
|
|
|
|
- if (disposed)
|
|
|
|
- return;
|
|
|
|
-
|
|
|
|
- Close(true);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public void AddHeader(string name, string value)
|
|
|
|
- {
|
|
|
|
- if (name == null)
|
|
|
|
- throw new ArgumentNullException("name");
|
|
|
|
-
|
|
|
|
- if (name == "")
|
|
|
|
- throw new ArgumentException("'name' cannot be empty", "name");
|
|
|
|
-
|
|
|
|
- //TODO: check for forbidden headers and invalid characters
|
|
|
|
- if (value.Length > 65535)
|
|
|
|
- throw new ArgumentOutOfRangeException("value");
|
|
|
|
-
|
|
|
|
- headers.Set(name, value);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public void AppendCookie(Cookie cookie)
|
|
|
|
- {
|
|
|
|
- if (cookie == null)
|
|
|
|
- throw new ArgumentNullException("cookie");
|
|
|
|
-
|
|
|
|
- Cookies.Add(cookie);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public void AppendHeader(string name, string value)
|
|
|
|
- {
|
|
|
|
- if (name == null)
|
|
|
|
- throw new ArgumentNullException("name");
|
|
|
|
-
|
|
|
|
- if (name == "")
|
|
|
|
- throw new ArgumentException("'name' cannot be empty", "name");
|
|
|
|
-
|
|
|
|
- if (value.Length > 65535)
|
|
|
|
- throw new ArgumentOutOfRangeException("value");
|
|
|
|
-
|
|
|
|
- headers.Add(name, value);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private void Close(bool force)
|
|
|
|
- {
|
|
|
|
- if (force)
|
|
|
|
- {
|
|
|
|
- _logger.Debug("HttpListenerResponse force closing HttpConnection");
|
|
|
|
|
|
+ // note that this doesn't set the status code to a redirect one
|
|
|
|
+ CheckDisposed();
|
|
|
|
+ if (string.IsNullOrEmpty(value))
|
|
|
|
+ {
|
|
|
|
+ Headers.Remove("Location");
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ Headers.Set("Location", value);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- disposed = true;
|
|
|
|
- context.Connection.Close(force);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- public void Close(byte[] responseEntity, bool willBlock)
|
|
|
|
|
|
+ public string StatusDescription
|
|
{
|
|
{
|
|
- //CheckDisposed();
|
|
|
|
-
|
|
|
|
- if (responseEntity == null)
|
|
|
|
- {
|
|
|
|
- throw new ArgumentNullException(nameof(responseEntity));
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- //if (_boundaryType != BoundaryType.Chunked)
|
|
|
|
- {
|
|
|
|
- ContentLength64 = responseEntity.Length;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (willBlock)
|
|
|
|
|
|
+ get
|
|
{
|
|
{
|
|
- try
|
|
|
|
|
|
+ if (_statusDescription == null)
|
|
{
|
|
{
|
|
- OutputStream.Write(responseEntity, 0, responseEntity.Length);
|
|
|
|
|
|
+ // if the user hasn't set this, generated on the fly, if possible.
|
|
|
|
+ // We know this one is safe, no need to verify it as in the setter.
|
|
|
|
+ _statusDescription = HttpStatusDescription.Get(StatusCode);
|
|
}
|
|
}
|
|
- finally
|
|
|
|
|
|
+ if (_statusDescription == null)
|
|
{
|
|
{
|
|
- Close(false);
|
|
|
|
|
|
+ _statusDescription = string.Empty;
|
|
}
|
|
}
|
|
|
|
+ return _statusDescription;
|
|
}
|
|
}
|
|
- else
|
|
|
|
|
|
+ set
|
|
{
|
|
{
|
|
- OutputStream.BeginWrite(responseEntity, 0, responseEntity.Length, iar =>
|
|
|
|
|
|
+ CheckDisposed();
|
|
|
|
+ if (value == null)
|
|
{
|
|
{
|
|
- var thisRef = (HttpListenerResponse)iar.AsyncState;
|
|
|
|
- try
|
|
|
|
- {
|
|
|
|
- thisRef.OutputStream.EndWrite(iar);
|
|
|
|
- }
|
|
|
|
- finally
|
|
|
|
|
|
+ throw new ArgumentNullException(nameof(value));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Need to verify the status description doesn't contain any control characters except HT. We mask off the high
|
|
|
|
+ // byte since that's how it's encoded.
|
|
|
|
+ for (int i = 0; i < value.Length; i++)
|
|
|
|
+ {
|
|
|
|
+ char c = (char)(0x000000ff & (uint)value[i]);
|
|
|
|
+ if ((c <= 31 && c != (byte)'\t') || c == 127)
|
|
{
|
|
{
|
|
- thisRef.Close(false);
|
|
|
|
|
|
+ throw new ArgumentException("net_WebHeaderInvalidControlChars");
|
|
}
|
|
}
|
|
- }, this);
|
|
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ _statusDescription = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- public void Close()
|
|
|
|
|
|
+ public void AddHeader(string name, string value)
|
|
{
|
|
{
|
|
- if (disposed)
|
|
|
|
- return;
|
|
|
|
-
|
|
|
|
- Close(false);
|
|
|
|
|
|
+ Headers.Set(name, value);
|
|
}
|
|
}
|
|
|
|
|
|
- public void Redirect(string url)
|
|
|
|
|
|
+ public void AppendHeader(string name, string value)
|
|
{
|
|
{
|
|
- StatusCode = 302; // Found
|
|
|
|
- location = url;
|
|
|
|
|
|
+ Headers.Add(name, value);
|
|
}
|
|
}
|
|
|
|
|
|
- bool FindCookie(Cookie cookie)
|
|
|
|
|
|
+ public void AppendCookie(Cookie cookie)
|
|
{
|
|
{
|
|
- string name = cookie.Name;
|
|
|
|
- string domain = cookie.Domain;
|
|
|
|
- string path = cookie.Path;
|
|
|
|
- foreach (Cookie c in cookies)
|
|
|
|
|
|
+ if (cookie == null)
|
|
{
|
|
{
|
|
- if (name != c.Name)
|
|
|
|
- continue;
|
|
|
|
- if (domain != c.Domain)
|
|
|
|
- continue;
|
|
|
|
- if (path == c.Path)
|
|
|
|
- return true;
|
|
|
|
|
|
+ throw new ArgumentNullException(nameof(cookie));
|
|
}
|
|
}
|
|
-
|
|
|
|
- return false;
|
|
|
|
|
|
+ Cookies.Add(cookie);
|
|
}
|
|
}
|
|
|
|
|
|
- public void DetermineIfChunked()
|
|
|
|
|
|
+ private void ComputeCookies()
|
|
{
|
|
{
|
|
- if (chunked)
|
|
|
|
|
|
+ if (_cookies != null)
|
|
{
|
|
{
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- Version v = context.Request.ProtocolVersion;
|
|
|
|
- if (!cl_set && !chunked && v >= HttpVersion.Version11)
|
|
|
|
- chunked = true;
|
|
|
|
- if (!chunked && string.Equals(headers["Transfer-Encoding"], "chunked"))
|
|
|
|
- {
|
|
|
|
- chunked = true;
|
|
|
|
|
|
+ // now go through the collection, and concatenate all the cookies in per-variant strings
|
|
|
|
+ //string setCookie2 = null, setCookie = null;
|
|
|
|
+ //for (int index = 0; index < _cookies.Count; index++)
|
|
|
|
+ //{
|
|
|
|
+ // Cookie cookie = _cookies[index];
|
|
|
|
+ // string cookieString = cookie.ToServerString();
|
|
|
|
+ // if (cookieString == null || cookieString.Length == 0)
|
|
|
|
+ // {
|
|
|
|
+ // continue;
|
|
|
|
+ // }
|
|
|
|
+
|
|
|
|
+ // if (cookie.IsRfc2965Variant())
|
|
|
|
+ // {
|
|
|
|
+ // setCookie2 = setCookie2 == null ? cookieString : setCookie2 + ", " + cookieString;
|
|
|
|
+ // }
|
|
|
|
+ // else
|
|
|
|
+ // {
|
|
|
|
+ // setCookie = setCookie == null ? cookieString : setCookie + ", " + cookieString;
|
|
|
|
+ // }
|
|
|
|
+ //}
|
|
|
|
+
|
|
|
|
+ //if (!string.IsNullOrEmpty(setCookie))
|
|
|
|
+ //{
|
|
|
|
+ // Headers.Set(HttpKnownHeaderNames.SetCookie, setCookie);
|
|
|
|
+ // if (string.IsNullOrEmpty(setCookie2))
|
|
|
|
+ // {
|
|
|
|
+ // Headers.Remove(HttpKnownHeaderNames.SetCookie2);
|
|
|
|
+ // }
|
|
|
|
+ //}
|
|
|
|
+
|
|
|
|
+ //if (!string.IsNullOrEmpty(setCookie2))
|
|
|
|
+ //{
|
|
|
|
+ // Headers.Set(HttpKnownHeaderNames.SetCookie2, setCookie2);
|
|
|
|
+ // if (string.IsNullOrEmpty(setCookie))
|
|
|
|
+ // {
|
|
|
|
+ // Headers.Remove(HttpKnownHeaderNames.SetCookie);
|
|
|
|
+ // }
|
|
|
|
+ //}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- internal void SendHeaders(bool closing, MemoryStream ms)
|
|
|
|
|
|
+ public void Redirect(string url)
|
|
{
|
|
{
|
|
- Encoding encoding = content_encoding;
|
|
|
|
- if (encoding == null)
|
|
|
|
- encoding = _textEncoding.GetDefaultEncoding();
|
|
|
|
-
|
|
|
|
- if (content_type != null)
|
|
|
|
- {
|
|
|
|
- if (content_encoding != null && content_type.IndexOf("charset=", StringComparison.OrdinalIgnoreCase) == -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));
|
|
|
|
- }
|
|
|
|
|
|
+ Headers["Location"] = url;
|
|
|
|
+ StatusCode = (int)HttpStatusCode.Redirect;
|
|
|
|
+ StatusDescription = "Found";
|
|
|
|
+ }
|
|
|
|
|
|
- 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)
|
|
|
|
|
|
+ public void SetCookie(Cookie cookie)
|
|
|
|
+ {
|
|
|
|
+ if (cookie == null)
|
|
{
|
|
{
|
|
- headers.SetInternal("Connection", "close");
|
|
|
|
- conn_close = true;
|
|
|
|
|
|
+ throw new ArgumentNullException(nameof(cookie));
|
|
}
|
|
}
|
|
|
|
|
|
- if (chunked)
|
|
|
|
- headers.SetInternal("Transfer-Encoding", "chunked");
|
|
|
|
|
|
+ //Cookie newCookie = cookie.Clone();
|
|
|
|
+ //int added = Cookies.InternalAdd(newCookie, true);
|
|
|
|
|
|
- //int reuses = context.Connection.Reuses;
|
|
|
|
- //if (reuses >= 100)
|
|
|
|
|
|
+ //if (added != 1)
|
|
//{
|
|
//{
|
|
- // _logger.Debug("HttpListenerResponse - keep alive has exceeded 100 uses and will be closed.");
|
|
|
|
-
|
|
|
|
- // force_close_chunked = true;
|
|
|
|
- // if (!conn_close)
|
|
|
|
- // {
|
|
|
|
- // headers.SetInternal("Connection", "close");
|
|
|
|
- // conn_close = true;
|
|
|
|
- // }
|
|
|
|
|
|
+ // // The Cookie already existed and couldn't be replaced.
|
|
|
|
+ // throw new ArgumentException("Cookie exists");
|
|
//}
|
|
//}
|
|
|
|
+ }
|
|
|
|
|
|
- if (!conn_close)
|
|
|
|
- {
|
|
|
|
- if (context.Request.ProtocolVersion <= HttpVersion.Version10)
|
|
|
|
- headers.SetInternal("Connection", "keep-alive");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (location != null)
|
|
|
|
- headers.SetInternal("Location", location);
|
|
|
|
-
|
|
|
|
- if (cookies != null)
|
|
|
|
- {
|
|
|
|
- foreach (Cookie cookie in cookies)
|
|
|
|
- headers.SetInternal("Set-Cookie", cookie.ToString());
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- headers.SetInternal("Status", status_code.ToString(CultureInfo.InvariantCulture));
|
|
|
|
|
|
+ void IDisposable.Dispose() => Dispose();
|
|
|
|
|
|
- using (StreamWriter writer = new StreamWriter(ms, encoding, 256, true))
|
|
|
|
|
|
+ private void CheckDisposed()
|
|
|
|
+ {
|
|
|
|
+ if (Disposed)
|
|
{
|
|
{
|
|
- writer.Write("HTTP/{0} {1} {2}\r\n", version, status_code, status_description);
|
|
|
|
- string headers_str = headers.ToStringMultiValue();
|
|
|
|
- writer.Write(headers_str);
|
|
|
|
- writer.Flush();
|
|
|
|
|
|
+ throw new ObjectDisposedException(GetType().FullName);
|
|
}
|
|
}
|
|
-
|
|
|
|
- int preamble = encoding.GetPreamble().Length;
|
|
|
|
- if (output_stream == null)
|
|
|
|
- output_stream = context.Connection.GetResponseStream();
|
|
|
|
-
|
|
|
|
- /* Assumes that the ms was at position 0 */
|
|
|
|
- ms.Position = preamble;
|
|
|
|
- HeadersSent = true;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- public void SetCookie(Cookie cookie)
|
|
|
|
|
|
+ private void CheckSentHeaders()
|
|
{
|
|
{
|
|
- if (cookie == null)
|
|
|
|
- throw new ArgumentNullException("cookie");
|
|
|
|
-
|
|
|
|
- if (cookies != null)
|
|
|
|
|
|
+ if (SentHeaders)
|
|
{
|
|
{
|
|
- if (FindCookie(cookie))
|
|
|
|
- throw new ArgumentException("The cookie already exists.");
|
|
|
|
|
|
+ throw new InvalidOperationException();
|
|
}
|
|
}
|
|
- else
|
|
|
|
- {
|
|
|
|
- cookies = new CookieCollection();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- cookies.Add(cookie);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
|
|
|
|
- {
|
|
|
|
- return ((HttpResponseStream)OutputStream).TransmitFile(path, offset, count, fileShareMode, cancellationToken);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-}
|
|
|
|
|
|
+}
|