123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572 |
- using System;
- using System.Globalization;
- using System.IO;
- using System.Net;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- using MediaBrowser.Model.IO;
- using MediaBrowser.Model.Logging;
- using MediaBrowser.Model.Text;
- using SocketHttpListener.Primitives;
- namespace SocketHttpListener.Net
- {
- public sealed 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; }
- }
- public Encoding ContentEncoding
- {
- get
- {
- if (content_encoding == null)
- content_encoding = _textEncoding.GetDefaultEncoding();
- return content_encoding;
- }
- set
- {
- if (disposed)
- throw new ObjectDisposedException(GetType().ToString());
- 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());
- 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());
- 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 (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());
- location = value;
- }
- }
- public bool SendChunked
- {
- get { return chunked; }
- set
- {
- if (disposed)
- throw new ObjectDisposedException(GetType().ToString());
- chunked = value;
- }
- }
- public int StatusCode
- {
- get { return status_code; }
- 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);
- }
- }
- internal static string GetStatusDescription(int code)
- {
- switch (code)
- {
- 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";
- }
- return "";
- }
- public string StatusDescription
- {
- get { return status_description; }
- 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");
- }
- disposed = true;
- context.Connection.Close(force);
- }
- public void Close(byte[] responseEntity, bool willBlock)
- {
- //CheckDisposed();
- if (responseEntity == null)
- {
- throw new ArgumentNullException(nameof(responseEntity));
- }
- //if (_boundaryType != BoundaryType.Chunked)
- {
- ContentLength64 = responseEntity.Length;
- }
- if (willBlock)
- {
- try
- {
- OutputStream.Write(responseEntity, 0, responseEntity.Length);
- }
- finally
- {
- Close(false);
- }
- }
- else
- {
- OutputStream.BeginWrite(responseEntity, 0, responseEntity.Length, iar =>
- {
- var thisRef = (HttpListenerResponse)iar.AsyncState;
- try
- {
- thisRef.OutputStream.EndWrite(iar);
- }
- finally
- {
- thisRef.Close(false);
- }
- }, this);
- }
- }
- public void Close()
- {
- if (disposed)
- return;
- Close(false);
- }
- public void Redirect(string url)
- {
- StatusCode = 302; // Found
- location = url;
- }
- 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;
- }
- public void DetermineIfChunked()
- {
- if (chunked)
- {
- 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;
- }
- }
- internal void SendHeaders(bool closing, MemoryStream ms)
- {
- 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));
- }
- 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)
- //{
- // _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;
- // }
- //}
- 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));
- using (StreamWriter writer = new StreamWriter(ms, encoding, 256, true))
- {
- writer.Write("HTTP/{0} {1} {2}\r\n", version, status_code, status_description);
- string headers_str = headers.ToStringMultiValue();
- writer.Write(headers_str);
- writer.Flush();
- }
- 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)
- {
- if (cookie == null)
- throw new ArgumentNullException("cookie");
- if (cookies != null)
- {
- if (FindCookie(cookie))
- throw new ArgumentException("The cookie already exists.");
- }
- 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);
- }
- }
- }
|