jellyfin-server/SocketHttpListener/Net/HttpListenerResponse.cs
2017-08-24 15:52:19 -04:00

305 lines
9.2 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Globalization;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Diagnostics;
using Microsoft.Win32.SafeHandles;
namespace SocketHttpListener.Net
{
public sealed unsafe partial class HttpListenerResponse : IDisposable
{
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 WebHeaderCollection Headers
{
get { return _webHeaders; }
}
public Encoding ContentEncoding { get; set; }
public string ContentType
{
get { return Headers["Content-Type"]; }
set
{
CheckDisposed();
if (string.IsNullOrEmpty(value))
{
Headers.Remove("Content-Type");
}
else
{
Headers.Set("Content-Type", value);
}
}
}
private HttpListenerContext HttpListenerContext { get { return _httpContext; } }
private HttpListenerRequest HttpListenerRequest { get { return HttpListenerContext.Request; } }
internal EntitySendFormat EntitySendFormat
{
get { return (EntitySendFormat)_boundaryType; }
set
{
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 bool SendChunked
{
get { return EntitySendFormat == EntitySendFormat.Chunked; ; }
set { EntitySendFormat = value ? EntitySendFormat.Chunked : EntitySendFormat.ContentLength; }
}
// 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)
{
for (int i = 0; i < s_noResponseBody.Length; i++)
{
if (responseCode == s_noResponseBody[i])
{
return false;
}
}
return true;
}
public long ContentLength64
{
get { return _contentLength; }
set
{
CheckDisposed();
CheckSentHeaders();
if (value >= 0)
{
_contentLength = value;
_boundaryType = BoundaryType.ContentLength;
}
else
{
throw new ArgumentOutOfRangeException("net_clsmall");
}
}
}
public CookieCollection Cookies
{
get { return _cookies ?? (_cookies = new CookieCollection()); }
set { _cookies = value; }
}
public bool KeepAlive
{
get { return _keepAlive; }
set
{
CheckDisposed();
_keepAlive = value;
}
}
public Stream OutputStream
{
get
{
CheckDisposed();
EnsureResponseStream();
return _responseStream;
}
}
public string RedirectLocation
{
get { return Headers["Location"]; }
set
{
// 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);
}
}
}
public string StatusDescription
{
get
{
if (_statusDescription == null)
{
// 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);
}
if (_statusDescription == null)
{
_statusDescription = string.Empty;
}
return _statusDescription;
}
set
{
CheckDisposed();
if (value == null)
{
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)
{
throw new ArgumentException("net_WebHeaderInvalidControlChars");
}
}
_statusDescription = value;
}
}
public void AddHeader(string name, string value)
{
Headers.Set(name, value);
}
public void AppendHeader(string name, string value)
{
Headers.Add(name, value);
}
public void AppendCookie(Cookie cookie)
{
if (cookie == null)
{
throw new ArgumentNullException(nameof(cookie));
}
Cookies.Add(cookie);
}
private void ComputeCookies()
{
if (_cookies != null)
{
// 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);
// }
//}
}
}
public void Redirect(string url)
{
Headers["Location"] = url;
StatusCode = (int)HttpStatusCode.Redirect;
StatusDescription = "Found";
}
public void SetCookie(Cookie cookie)
{
if (cookie == null)
{
throw new ArgumentNullException(nameof(cookie));
}
//Cookie newCookie = cookie.Clone();
//int added = Cookies.InternalAdd(newCookie, true);
//if (added != 1)
//{
// // The Cookie already existed and couldn't be replaced.
// throw new ArgumentException("Cookie exists");
//}
}
void IDisposable.Dispose()
{
Dispose();
}
private void CheckDisposed()
{
if (Disposed)
{
throw new ObjectDisposedException(GetType().FullName);
}
}
private void CheckSentHeaders()
{
if (SentHeaders)
{
throw new InvalidOperationException();
}
}
}
}