diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index e9d8d8de7e..ad6b8e84a8 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -791,7 +791,8 @@ namespace Emby.Server.Implementations _configuration, NetworkManager, JsonSerializer, - XmlSerializer); + XmlSerializer, + CreateHttpListener()); HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); serviceCollection.AddSingleton(HttpServer); @@ -1125,8 +1126,6 @@ namespace Emby.Server.Implementations HttpServer.Init(GetExports(false), GetExports()); - //StartServer(); - LibraryManager.AddParts(GetExports(), GetExports(), GetExports(), @@ -1239,45 +1238,7 @@ namespace Emby.Server.Implementations }); } - protected abstract IHttpListener CreateHttpListener(); - - /// - /// Starts the server. - /// - private void StartServer() - { - try - { - ((HttpListenerHost)HttpServer).StartServer(GetUrlPrefixes().ToArray(), CreateHttpListener()); - return; - } - catch (Exception ex) - { - var msg = string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase) - ? "The http server is unable to start due to a Socket error. This can occasionally happen when the operating system takes longer than usual to release the IP bindings from the previous session. This can take up to five minutes. Please try waiting or rebooting the system." - : "Error starting Http Server"; - - Logger.LogError(ex, msg); - - if (HttpPort == ServerConfiguration.DefaultHttpPort) - { - throw; - } - } - - HttpPort = ServerConfiguration.DefaultHttpPort; - - try - { - ((HttpListenerHost)HttpServer).StartServer(GetUrlPrefixes().ToArray(), CreateHttpListener()); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error starting http server"); - - throw; - } - } + protected IHttpListener CreateHttpListener() => new WebSocketSharpListener(Logger); private CertificateInfo GetCertificateInfo(bool generateCertificate) { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index a3eaa2fb50..12c23a8693 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -44,6 +44,7 @@ namespace Emby.Server.Implementations.HttpServer private readonly IServerApplicationHost _appHost; private readonly IJsonSerializer _jsonSerializer; private readonly IXmlSerializer _xmlSerializer; + private readonly IHttpListener _socketListener; private readonly Func> _funcParseFn; public Action[] ResponseFilters { get; set; } @@ -61,7 +62,8 @@ namespace Emby.Server.Implementations.HttpServer IConfiguration configuration, INetworkManager networkManager, IJsonSerializer jsonSerializer, - IXmlSerializer xmlSerializer) + IXmlSerializer xmlSerializer, + IHttpListener socketListener) { _appHost = applicationHost; _logger = loggerFactory.CreateLogger("HttpServer"); @@ -70,15 +72,13 @@ namespace Emby.Server.Implementations.HttpServer _networkManager = networkManager; _jsonSerializer = jsonSerializer; _xmlSerializer = xmlSerializer; + _socketListener = socketListener; + _socketListener.WebSocketConnected = OnWebSocketConnected; _funcParseFn = t => s => JsvReader.GetParseFn(t)(s); Instance = this; ResponseFilters = Array.Empty>(); - _websocketlistener = new WebSocketSharpListener(_logger) - { - WebSocketConnected = OnWebSocketConnected - }; } public string GlobalResponse { get; set; } @@ -770,7 +770,8 @@ namespace Emby.Server.Implementations.HttpServer public Task ProcessWebSocketRequest(HttpContext context) { - return _websocketlistener.ProcessWebSocketRequest(context); + // TODO + return ((WebSocketSharpListener)_socketListener).ProcessWebSocketRequest(context); } //TODO Add Jellyfin Route Path Normalizer @@ -867,8 +868,6 @@ namespace Emby.Server.Implementations.HttpServer _listener.WebSocketConnected = OnWebSocketConnected; _listener.ErrorHandler = ErrorHandler; _listener.RequestHandler = RequestHandler; - - _listener.Start(UrlPrefixes); } } } diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 68fd2453e3..9e52247900 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Reflection; using Emby.Server.Implementations; using Emby.Server.Implementations.HttpServer; -using Jellyfin.Server.SocketSharp; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; using Microsoft.Extensions.Configuration; @@ -45,8 +44,5 @@ namespace Jellyfin.Server } protected override void ShutdownInternal() => Program.Shutdown(); - - protected override IHttpListener CreateHttpListener() - => new WebSocketSharpListener(Logger); } } diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index bd670df527..94a7e58cb5 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -1,4 +1,4 @@ - + jellyfin diff --git a/Jellyfin.Server/SocketSharp/HttpFile.cs b/Jellyfin.Server/SocketSharp/HttpFile.cs deleted file mode 100644 index 448b666b63..0000000000 --- a/Jellyfin.Server/SocketSharp/HttpFile.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.IO; -using MediaBrowser.Model.Services; - -namespace Jellyfin.Server.SocketSharp -{ - public class HttpFile : IHttpFile - { - public string Name { get; set; } - - public string FileName { get; set; } - - public long ContentLength { get; set; } - - public string ContentType { get; set; } - - public Stream InputStream { get; set; } - } -} diff --git a/Jellyfin.Server/SocketSharp/HttpPostedFile.cs b/Jellyfin.Server/SocketSharp/HttpPostedFile.cs deleted file mode 100644 index f38ed848ee..0000000000 --- a/Jellyfin.Server/SocketSharp/HttpPostedFile.cs +++ /dev/null @@ -1,204 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Net; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Model.Services; - -public sealed class HttpPostedFile : IDisposable -{ - private string _name; - private string _contentType; - private Stream _stream; - private bool _disposed = false; - - internal HttpPostedFile(string name, string content_type, Stream base_stream, long offset, long length) - { - _name = name; - _contentType = content_type; - _stream = new ReadSubStream(base_stream, offset, length); - } - - public string ContentType => _contentType; - - public int ContentLength => (int)_stream.Length; - - public string FileName => _name; - - public Stream InputStream => _stream; - - /// - /// Releases the unmanaged resources and disposes of the managed resources used. - /// - public void Dispose() - { - if (_disposed) - { - return; - } - - _stream.Dispose(); - _stream = null; - - _name = null; - _contentType = null; - - _disposed = true; - } - - private class ReadSubStream : Stream - { - private Stream _stream; - private long _offset; - private long _end; - private long _position; - - public ReadSubStream(Stream s, long offset, long length) - { - _stream = s; - _offset = offset; - _end = offset + length; - _position = offset; - } - - public override void Flush() - { - } - - public override int Read(byte[] buffer, int dest_offset, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (dest_offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(dest_offset), "< 0"); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), "< 0"); - } - - int len = buffer.Length; - if (dest_offset > len) - { - throw new ArgumentException("destination offset is beyond array size", nameof(dest_offset)); - } - - // reordered to avoid possible integer overflow - if (dest_offset > len - count) - { - throw new ArgumentException("Reading would overrun buffer", nameof(count)); - } - - if (count > _end - _position) - { - count = (int)(_end - _position); - } - - if (count <= 0) - { - return 0; - } - - _stream.Position = _position; - int result = _stream.Read(buffer, dest_offset, count); - if (result > 0) - { - _position += result; - } - else - { - _position = _end; - } - - return result; - } - - public override int ReadByte() - { - if (_position >= _end) - { - return -1; - } - - _stream.Position = _position; - int result = _stream.ReadByte(); - if (result < 0) - { - _position = _end; - } - else - { - _position++; - } - - return result; - } - - public override long Seek(long d, SeekOrigin origin) - { - long real; - switch (origin) - { - case SeekOrigin.Begin: - real = _offset + d; - break; - case SeekOrigin.End: - real = _end + d; - break; - case SeekOrigin.Current: - real = _position + d; - break; - default: - throw new ArgumentException("Unknown SeekOrigin value", nameof(origin)); - } - - long virt = real - _offset; - if (virt < 0 || virt > Length) - { - throw new ArgumentException("Invalid position", nameof(d)); - } - - _position = _stream.Seek(real, SeekOrigin.Begin); - return _position; - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - - public override bool CanRead => true; - - public override bool CanSeek => true; - - public override bool CanWrite => false; - - public override long Length => _end - _offset; - - public override long Position - { - get => _position - _offset; - set - { - if (value > Length) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - _position = Seek(value, SeekOrigin.Begin); - } - } - } -} diff --git a/Jellyfin.Server/SocketSharp/RequestMono.cs b/Jellyfin.Server/SocketSharp/RequestMono.cs deleted file mode 100644 index f2a08c9ae2..0000000000 --- a/Jellyfin.Server/SocketSharp/RequestMono.cs +++ /dev/null @@ -1,680 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Net; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Model.Services; - -namespace Jellyfin.Server.SocketSharp -{ - public partial class WebSocketSharpRequest : IHttpRequest - { - internal static string GetParameter(string header, string attr) - { - int ap = header.IndexOf(attr, StringComparison.Ordinal); - if (ap == -1) - { - return null; - } - - ap += attr.Length; - if (ap >= header.Length) - { - return null; - } - - char ending = header[ap]; - if (ending != '"') - { - ending = ' '; - } - - int end = header.IndexOf(ending, ap + 1); - if (end == -1) - { - return ending == '"' ? null : header.Substring(ap); - } - - return header.Substring(ap + 1, end - ap - 1); - } - - private async Task LoadMultiPart(WebROCollection form) - { - string boundary = GetParameter(ContentType, "; boundary="); - if (boundary == null) - { - return; - } - - using (var requestStream = InputStream) - { - // DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request - // Not ending with \r\n? - var ms = new MemoryStream(32 * 1024); - await requestStream.CopyToAsync(ms).ConfigureAwait(false); - - var input = ms; - ms.WriteByte((byte)'\r'); - ms.WriteByte((byte)'\n'); - - input.Position = 0; - - // Uncomment to debug - // var content = new StreamReader(ms).ReadToEnd(); - // Console.WriteLine(boundary + "::" + content); - // input.Position = 0; - - var multi_part = new HttpMultipart(input, boundary, ContentEncoding); - - HttpMultipart.Element e; - while ((e = multi_part.ReadNextElement()) != null) - { - if (e.Filename == null) - { - byte[] copy = new byte[e.Length]; - - input.Position = e.Start; - input.Read(copy, 0, (int)e.Length); - - form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy, 0, copy.Length)); - } - else - { - // We use a substream, as in 2.x we will support large uploads streamed to disk, - var sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length); - files[e.Name] = sub; - } - } - } - } - - public async Task GetFormData() - { - var form = new WebROCollection(); - files = new Dictionary(); - - if (IsContentType("multipart/form-data", true)) - { - await LoadMultiPart(form).ConfigureAwait(false); - } - else if (IsContentType("application/x-www-form-urlencoded", true)) - { - await LoadWwwForm(form).ConfigureAwait(false); - } - -#if NET_4_0 - if (validateRequestNewMode && !checked_form) { - // Setting this before calling the validator prevents - // possible endless recursion - checked_form = true; - ValidateNameValueCollection("Form", query_string_nvc, RequestValidationSource.Form); - } else -#endif - if (validate_form && !checked_form) - { - checked_form = true; - ValidateNameValueCollection("Form", form); - } - - return form; - } - - public string Accept => string.IsNullOrEmpty(request.Headers["Accept"]) ? null : request.Headers["Accept"]; - - public string Authorization => string.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"]; - - protected bool validate_cookies { get; set; } - protected bool validate_query_string { get; set; } - protected bool validate_form { get; set; } - protected bool checked_cookies { get; set; } - protected bool checked_query_string { get; set; } - protected bool checked_form { get; set; } - - private static void ThrowValidationException(string name, string key, string value) - { - string v = "\"" + value + "\""; - if (v.Length > 20) - { - v = v.Substring(0, 16) + "...\""; - } - - string msg = string.Format( - CultureInfo.InvariantCulture, - "A potentially dangerous Request.{0} value was detected from the client ({1}={2}).", - name, - key, - v); - - throw new Exception(msg); - } - - private static void ValidateNameValueCollection(string name, QueryParamCollection coll) - { - if (coll == null) - { - return; - } - - foreach (var pair in coll) - { - var key = pair.Name; - var val = pair.Value; - if (val != null && val.Length > 0 && IsInvalidString(val)) - { - ThrowValidationException(name, key, val); - } - } - } - - internal static bool IsInvalidString(string val) - => IsInvalidString(val, out var validationFailureIndex); - - internal static bool IsInvalidString(string val, out int validationFailureIndex) - { - validationFailureIndex = 0; - - int len = val.Length; - if (len < 2) - { - return false; - } - - char current = val[0]; - for (int idx = 1; idx < len; idx++) - { - char next = val[idx]; - - // See http://secunia.com/advisories/14325 - if (current == '<' || current == '\xff1c') - { - if (next == '!' || next < ' ' - || (next >= 'a' && next <= 'z') - || (next >= 'A' && next <= 'Z')) - { - validationFailureIndex = idx - 1; - return true; - } - } - else if (current == '&' && next == '#') - { - validationFailureIndex = idx - 1; - return true; - } - - current = next; - } - - return false; - } - - public void ValidateInput() - { - validate_cookies = true; - validate_query_string = true; - validate_form = true; - } - - private bool IsContentType(string ct, bool starts_with) - { - if (ct == null || ContentType == null) - { - return false; - } - - if (starts_with) - { - return ContentType.StartsWith(ct, StringComparison.OrdinalIgnoreCase); - } - - return string.Equals(ContentType, ct, StringComparison.OrdinalIgnoreCase); - } - - private async Task LoadWwwForm(WebROCollection form) - { - using (var input = InputStream) - { - using (var ms = new MemoryStream()) - { - await input.CopyToAsync(ms).ConfigureAwait(false); - ms.Position = 0; - - using (var s = new StreamReader(ms, ContentEncoding)) - { - var key = new StringBuilder(); - var value = new StringBuilder(); - int c; - - while ((c = s.Read()) != -1) - { - if (c == '=') - { - value.Length = 0; - while ((c = s.Read()) != -1) - { - if (c == '&') - { - AddRawKeyValue(form, key, value); - break; - } - else - { - value.Append((char)c); - } - } - - if (c == -1) - { - AddRawKeyValue(form, key, value); - return; - } - } - else if (c == '&') - { - AddRawKeyValue(form, key, value); - } - else - { - key.Append((char)c); - } - } - - if (c == -1) - { - AddRawKeyValue(form, key, value); - } - } - } - } - } - - private static void AddRawKeyValue(WebROCollection form, StringBuilder key, StringBuilder value) - { - form.Add(WebUtility.UrlDecode(key.ToString()), WebUtility.UrlDecode(value.ToString())); - - key.Length = 0; - value.Length = 0; - } - - private Dictionary files; - - private class WebROCollection : QueryParamCollection - { - public override string ToString() - { - var result = new StringBuilder(); - foreach (var pair in this) - { - if (result.Length > 0) - { - result.Append('&'); - } - - var key = pair.Name; - if (key != null && key.Length > 0) - { - result.Append(key); - result.Append('='); - } - - result.Append(pair.Value); - } - - return result.ToString(); - } - } - private class HttpMultipart - { - - public class Element - { - public string ContentType { get; set; } - - public string Name { get; set; } - - public string Filename { get; set; } - - public Encoding Encoding { get; set; } - - public long Start { get; set; } - - public long Length { get; set; } - - public override string ToString() - { - return "ContentType " + ContentType + ", Name " + Name + ", Filename " + Filename + ", Start " + - Start.ToString(CultureInfo.CurrentCulture) + ", Length " + Length.ToString(CultureInfo.CurrentCulture); - } - } - - private const byte LF = (byte)'\n'; - - private const byte CR = (byte)'\r'; - - private Stream data; - - private string boundary; - - private byte[] boundaryBytes; - - private byte[] buffer; - - private bool atEof; - - private Encoding encoding; - - private StringBuilder sb; - - // See RFC 2046 - // In the case of multipart entities, in which one or more different - // sets of data are combined in a single body, a "multipart" media type - // field must appear in the entity's header. The body must then contain - // one or more body parts, each preceded by a boundary delimiter line, - // and the last one followed by a closing boundary delimiter line. - // After its boundary delimiter line, each body part then consists of a - // header area, a blank line, and a body area. Thus a body part is - // similar to an RFC 822 message in syntax, but different in meaning. - - public HttpMultipart(Stream data, string b, Encoding encoding) - { - this.data = data; - boundary = b; - boundaryBytes = encoding.GetBytes(b); - buffer = new byte[boundaryBytes.Length + 2]; // CRLF or '--' - this.encoding = encoding; - sb = new StringBuilder(); - } - - public Element ReadNextElement() - { - if (atEof || ReadBoundary()) - { - return null; - } - - var elem = new Element(); - string header; - while ((header = ReadHeaders()) != null) - { - if (header.StartsWith("Content-Disposition:", StringComparison.OrdinalIgnoreCase)) - { - elem.Name = GetContentDispositionAttribute(header, "name"); - elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename")); - } - else if (header.StartsWith("Content-Type:", StringComparison.OrdinalIgnoreCase)) - { - elem.ContentType = header.Substring("Content-Type:".Length).Trim(); - elem.Encoding = GetEncoding(elem.ContentType); - } - } - - long start = data.Position; - elem.Start = start; - long pos = MoveToNextBoundary(); - if (pos == -1) - { - return null; - } - - elem.Length = pos - start; - return elem; - } - - private string ReadLine() - { - // CRLF or LF are ok as line endings. - bool got_cr = false; - int b = 0; - sb.Length = 0; - while (true) - { - b = data.ReadByte(); - if (b == -1) - { - return null; - } - - if (b == LF) - { - break; - } - - got_cr = b == CR; - sb.Append((char)b); - } - - if (got_cr) - { - sb.Length--; - } - - return sb.ToString(); - } - - private static string GetContentDispositionAttribute(string l, string name) - { - int idx = l.IndexOf(name + "=\"", StringComparison.Ordinal); - if (idx < 0) - { - return null; - } - - int begin = idx + name.Length + "=\"".Length; - int end = l.IndexOf('"', begin); - if (end < 0) - { - return null; - } - - if (begin == end) - { - return string.Empty; - } - - return l.Substring(begin, end - begin); - } - - private string GetContentDispositionAttributeWithEncoding(string l, string name) - { - int idx = l.IndexOf(name + "=\"", StringComparison.Ordinal); - if (idx < 0) - { - return null; - } - - int begin = idx + name.Length + "=\"".Length; - int end = l.IndexOf('"', begin); - if (end < 0) - { - return null; - } - - if (begin == end) - { - return string.Empty; - } - - string temp = l.Substring(begin, end - begin); - byte[] source = new byte[temp.Length]; - for (int i = temp.Length - 1; i >= 0; i--) - { - source[i] = (byte)temp[i]; - } - - return encoding.GetString(source, 0, source.Length); - } - - private bool ReadBoundary() - { - try - { - string line; - do - { - line = ReadLine(); - } - while (line.Length == 0); - - if (line[0] != '-' || line[1] != '-') - { - return false; - } - - if (!line.EndsWith(boundary, StringComparison.Ordinal)) - { - return true; - } - } - catch - { - - } - - return false; - } - - private string ReadHeaders() - { - string s = ReadLine(); - if (s.Length == 0) - { - return null; - } - - return s; - } - - private static bool CompareBytes(byte[] orig, byte[] other) - { - for (int i = orig.Length - 1; i >= 0; i--) - { - if (orig[i] != other[i]) - { - return false; - } - } - - return true; - } - - private long MoveToNextBoundary() - { - long retval = 0; - bool got_cr = false; - - int state = 0; - int c = data.ReadByte(); - while (true) - { - if (c == -1) - { - return -1; - } - - if (state == 0 && c == LF) - { - retval = data.Position - 1; - if (got_cr) - { - retval--; - } - - state = 1; - c = data.ReadByte(); - } - else if (state == 0) - { - got_cr = c == CR; - c = data.ReadByte(); - } - else if (state == 1 && c == '-') - { - c = data.ReadByte(); - if (c == -1) - { - return -1; - } - - if (c != '-') - { - state = 0; - got_cr = false; - continue; // no ReadByte() here - } - - int nread = data.Read(buffer, 0, buffer.Length); - int bl = buffer.Length; - if (nread != bl) - { - return -1; - } - - if (!CompareBytes(boundaryBytes, buffer)) - { - state = 0; - data.Position = retval + 2; - if (got_cr) - { - data.Position++; - got_cr = false; - } - - c = data.ReadByte(); - continue; - } - - if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-') - { - atEof = true; - } - else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF) - { - state = 0; - data.Position = retval + 2; - if (got_cr) - { - data.Position++; - got_cr = false; - } - - c = data.ReadByte(); - continue; - } - - data.Position = retval + 2; - if (got_cr) - { - data.Position++; - } - - break; - } - else - { - // state == 1 - state = 0; // no ReadByte() here - } - } - - return retval; - } - - private static string StripPath(string path) - { - if (path == null || path.Length == 0) - { - return path; - } - - if (path.IndexOf(":\\", StringComparison.Ordinal) != 1 - && !path.StartsWith("\\\\", StringComparison.Ordinal)) - { - return path; - } - - return path.Substring(path.LastIndexOf('\\') + 1); - } - } - } -} diff --git a/Jellyfin.Server/SocketSharp/SharpWebSocket.cs b/Jellyfin.Server/SocketSharp/SharpWebSocket.cs deleted file mode 100644 index 9b0951857f..0000000000 --- a/Jellyfin.Server/SocketSharp/SharpWebSocket.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Implementations.Net; -using Microsoft.Extensions.Logging; - -namespace Jellyfin.Server.SocketSharp -{ - public class SharpWebSocket : IWebSocket - { - /// - /// The logger - /// - private readonly ILogger _logger; - - public event EventHandler Closed; - - /// - /// Gets or sets the web socket. - /// - /// The web socket. - private SocketHttpListener.WebSocket WebSocket { get; set; } - - private TaskCompletionSource _taskCompletionSource = new TaskCompletionSource(); - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - private bool _disposed = false; - - public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger) - { - if (socket == null) - { - throw new ArgumentNullException(nameof(socket)); - } - - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } - - _logger = logger; - WebSocket = socket; - - socket.OnMessage += OnSocketMessage; - socket.OnClose += OnSocketClose; - socket.OnError += OnSocketError; - } - - public Task ConnectAsServerAsync() - => WebSocket.ConnectAsServer(); - - public Task StartReceive() - { - return _taskCompletionSource.Task; - } - - private void OnSocketError(object sender, SocketHttpListener.ErrorEventArgs e) - { - _logger.LogError("Error in SharpWebSocket: {Message}", e.Message ?? string.Empty); - - // Closed?.Invoke(this, EventArgs.Empty); - } - - private void OnSocketClose(object sender, SocketHttpListener.CloseEventArgs e) - { - _taskCompletionSource.TrySetResult(true); - - Closed?.Invoke(this, EventArgs.Empty); - } - - private void OnSocketMessage(object sender, SocketHttpListener.MessageEventArgs e) - { - if (OnReceiveBytes != null) - { - OnReceiveBytes(e.RawData); - } - } - - /// - /// Gets or sets the state. - /// - /// The state. - public WebSocketState State => WebSocket.ReadyState; - - /// - /// Sends the async. - /// - /// The bytes. - /// if set to true [end of message]. - /// The cancellation token. - /// Task. - public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken) - { - return WebSocket.SendAsync(bytes); - } - - /// - /// Sends the asynchronous. - /// - /// The text. - /// if set to true [end of message]. - /// The cancellation token. - /// Task. - public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken) - { - return WebSocket.SendAsync(text); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - if (_disposed) - { - return; - } - - if (dispose) - { - WebSocket.OnMessage -= OnSocketMessage; - WebSocket.OnClose -= OnSocketClose; - WebSocket.OnError -= OnSocketError; - - _cancellationTokenSource.Cancel(); - - WebSocket.CloseAsync().GetAwaiter().GetResult(); - } - - _disposed = true; - } - - /// - /// Gets or sets the receive action. - /// - /// The receive action. - public Action OnReceiveBytes { get; set; } - } -} diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs deleted file mode 100644 index 4dc4103cb1..0000000000 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs +++ /dev/null @@ -1,237 +0,0 @@ -using System; -using System.Collections.Generic; - using System.Net; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Implementations.HttpServer; -using Emby.Server.Implementations.Net; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - - namespace Jellyfin.Server.SocketSharp -{ - public class WebSocketSharpListener : IHttpListener - { - private HttpListener _listener; - - private readonly ILogger _logger; - - private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); - private CancellationToken _disposeCancellationToken; - - public WebSocketSharpListener( - ILogger logger) - { - _logger = logger; - - _disposeCancellationToken = _disposeCancellationTokenSource.Token; - } - - public Func ErrorHandler { get; set; } - public Func RequestHandler { get; set; } - - public Action WebSocketConnecting { get; set; } - - public Action WebSocketConnected { get; set; } - - public void Start(IEnumerable urlPrefixes) - { - // TODO - //if (_listener == null) - //{ - // _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _streamHelper, _fileSystem, _environment); - //} - - //_listener.EnableDualMode = _enableDualMode; - - //if (_certificate != null) - //{ - // _listener.LoadCert(_certificate); - //} - - //_logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes); - //_listener.Prefixes.AddRange(urlPrefixes); - - //_listener.OnContext = async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false); - - //_listener.Start(); - - if (_listener == null) - { - _listener = new HttpListener(); - } - - _logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes); - - //foreach (var urlPrefix in urlPrefixes) - //{ - // _listener.Prefixes.Add(urlPrefix); - //} - _listener.Prefixes.Add("http://localhost:8096/"); - - _listener.Start(); - - // TODO how to do this in netcore? - _listener.BeginGetContext(async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false), - null); - } - - private static void LogRequest(ILogger logger, HttpListenerRequest request) - { - var url = request.Url.ToString(); - - logger.LogInformation( - "{0} {1}. UserAgent: {2}", - request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, - url, - request.UserAgent ?? string.Empty); - } - - private Task InitTask(IAsyncResult asyncResult, CancellationToken cancellationToken) - { - var context = _listener.EndGetContext(asyncResult); - _listener.BeginGetContext(async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false), null); - IHttpRequest httpReq = null; - var request = context.Request; - - try - { - if (request.IsWebSocketRequest) - { - LogRequest(_logger, request); - - return ProcessWebSocketRequest(context); - } - - httpReq = GetRequest(context); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error processing request"); - - httpReq = httpReq ?? GetRequest(context); - return ErrorHandler(ex, httpReq, true, true); - } - - var uri = request.Url; - - return RequestHandler(httpReq, uri.OriginalString, uri.Host, uri.LocalPath, cancellationToken); - } - - private async Task ProcessWebSocketRequest(HttpListenerContext ctx) - { - try - { - var endpoint = ctx.Request.RemoteEndPoint.ToString(); - var url = ctx.Request.RawUrl; - - var queryString = new QueryParamCollection(ctx.Request.QueryString); - - var connectingArgs = new WebSocketConnectingEventArgs - { - Url = url, - QueryString = queryString, - Endpoint = endpoint - }; - - WebSocketConnecting?.Invoke(connectingArgs); - - if (connectingArgs.AllowConnection) - { - _logger.LogDebug("Web socket connection allowed"); - - var webSocketContext = await ctx.AcceptWebSocketAsync(null).ConfigureAwait(false); - - if (WebSocketConnected != null) - { - SharpWebSocket socket = null; //new SharpWebSocket(webSocketContext.WebSocket, _logger); - await socket.ConnectAsServerAsync().ConfigureAwait(false); - - WebSocketConnected(new WebSocketConnectEventArgs - { - Url = url, - QueryString = queryString, - WebSocket = null, // socket, - Endpoint = endpoint - }); - - await socket.StartReceive().ConfigureAwait(false); - } - } - else - { - _logger.LogWarning("Web socket connection not allowed"); - TryClose(ctx, 401); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "AcceptWebSocketAsync error"); - TryClose(ctx, 500); - } - } - - private void TryClose(HttpListenerContext ctx, int statusCode) - { - try - { - ctx.Response.StatusCode = statusCode; - ctx.Response.Close(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error closing web socket response"); - } - } - - private IHttpRequest GetRequest(HttpListenerContext httpContext) - { - var urlSegments = httpContext.Request.Url.Segments; - - var operationName = urlSegments[urlSegments.Length - 1]; - - var req = new WebSocketSharpRequest(httpContext, operationName, _logger); - - return req; - } - - public Task Stop() - { - _disposeCancellationTokenSource.Cancel(); - _listener?.Close(); - - return Task.CompletedTask; - } - - /// - /// Releases the unmanaged resources and disposes of the managed resources used. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private bool _disposed; - - /// - /// Releases the unmanaged resources and disposes of the managed resources used. - /// - /// Whether or not the managed resources should be disposed - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing) - { - Stop().GetAwaiter().GetResult(); - } - - _disposed = true; - } - } -} diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs deleted file mode 100644 index 6a00e283c6..0000000000 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs +++ /dev/null @@ -1,535 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Net; -using System.Text; -using Emby.Server.Implementations.HttpServer; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; -using SocketHttpListener.Net; -using IHttpFile = MediaBrowser.Model.Services.IHttpFile; -using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest; -using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; -using IResponse = MediaBrowser.Model.Services.IResponse; - -namespace Jellyfin.Server.SocketSharp -{ - public partial class WebSocketSharpRequest : IHttpRequest - { - private readonly HttpListenerRequest request; - private readonly IHttpResponse response; - - public WebSocketSharpRequest(HttpListenerContext httpContext, string operationName, ILogger logger) - { - this.OperationName = operationName; - this.request = httpContext.Request; - this.response = new WebSocketSharpResponse(logger, httpContext.Response, this); - - // HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]); - } - - public HttpListenerRequest HttpRequest => request; - - public object OriginalRequest => request; - - public IResponse Response => response; - - public IHttpResponse HttpResponse => response; - - public string OperationName { get; set; } - - public object Dto { get; set; } - - public string RawUrl => request.RawUrl; - - public string AbsoluteUri => request.Url.AbsoluteUri.TrimEnd('/'); - - public string UserHostAddress => request.UserHostAddress; - - public string XForwardedFor - => string.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"]; - - public int? XForwardedPort - => string.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"], CultureInfo.InvariantCulture); - - public string XForwardedProtocol => string.IsNullOrEmpty(request.Headers["X-Forwarded-Proto"]) ? null : request.Headers["X-Forwarded-Proto"]; - - public string XRealIp => string.IsNullOrEmpty(request.Headers["X-Real-IP"]) ? null : request.Headers["X-Real-IP"]; - - private string remoteIp; - public string RemoteIp => - remoteIp ?? - (remoteIp = CheckBadChars(XForwardedFor) ?? - NormalizeIp(CheckBadChars(XRealIp) ?? - (request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.Address.ToString()) : null))); - - private static readonly char[] HttpTrimCharacters = new char[] { (char)0x09, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20 }; - - // CheckBadChars - throws on invalid chars to be not found in header name/value - internal static string CheckBadChars(string name) - { - if (name == null || name.Length == 0) - { - return name; - } - - // VALUE check - // Trim spaces from both ends - name = name.Trim(HttpTrimCharacters); - - // First, check for correctly formed multi-line value - // Second, check for absence of CTL characters - int crlf = 0; - for (int i = 0; i < name.Length; ++i) - { - char c = (char)(0x000000ff & (uint)name[i]); - switch (crlf) - { - case 0: - { - if (c == '\r') - { - crlf = 1; - } - else if (c == '\n') - { - // Technically this is bad HTTP. But it would be a breaking change to throw here. - // Is there an exploit? - crlf = 2; - } - else if (c == 127 || (c < ' ' && c != '\t')) - { - throw new ArgumentException("net_WebHeaderInvalidControlChars"); - } - - break; - } - - case 1: - { - if (c == '\n') - { - crlf = 2; - break; - } - - throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); - } - - case 2: - { - if (c == ' ' || c == '\t') - { - crlf = 0; - break; - } - - throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); - } - } - } - - if (crlf != 0) - { - throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); - } - - return name; - } - - internal static bool ContainsNonAsciiChars(string token) - { - for (int i = 0; i < token.Length; ++i) - { - if ((token[i] < 0x20) || (token[i] > 0x7e)) - { - return true; - } - } - - return false; - } - - private string NormalizeIp(string ip) - { - if (!string.IsNullOrWhiteSpace(ip)) - { - // Handle ipv4 mapped to ipv6 - const string srch = "::ffff:"; - var index = ip.IndexOf(srch, StringComparison.OrdinalIgnoreCase); - if (index == 0) - { - ip = ip.Substring(srch.Length); - } - } - - return ip; - } - - public bool IsSecureConnection => request.IsSecureConnection || XForwardedProtocol == "https"; - - public string[] AcceptTypes => request.AcceptTypes; - - private Dictionary items; - public Dictionary Items => items ?? (items = new Dictionary()); - - private string responseContentType; - public string ResponseContentType - { - get => - responseContentType - ?? (responseContentType = GetResponseContentType(this)); - set => this.responseContentType = value; - } - - public const string FormUrlEncoded = "application/x-www-form-urlencoded"; - public const string MultiPartFormData = "multipart/form-data"; - public static string GetResponseContentType(IRequest httpReq) - { - var specifiedContentType = GetQueryStringContentType(httpReq); - if (!string.IsNullOrEmpty(specifiedContentType)) - { - return specifiedContentType; - } - - const string serverDefaultContentType = "application/json"; - - var acceptContentTypes = httpReq.AcceptTypes; - string defaultContentType = null; - if (HasAnyOfContentTypes(httpReq, FormUrlEncoded, MultiPartFormData)) - { - defaultContentType = serverDefaultContentType; - } - - var acceptsAnything = false; - var hasDefaultContentType = defaultContentType != null; - if (acceptContentTypes != null) - { - foreach (var acceptsType in acceptContentTypes) - { - // TODO: @bond move to Span when Span.Split lands - // https://github.com/dotnet/corefx/issues/26528 - var contentType = acceptsType?.Split(';')[0].Trim(); - acceptsAnything = contentType.Equals("*/*", StringComparison.OrdinalIgnoreCase); - - if (acceptsAnything) - { - break; - } - } - - if (acceptsAnything) - { - if (hasDefaultContentType) - { - return defaultContentType; - } - else - { - return serverDefaultContentType; - } - } - } - - if (acceptContentTypes == null && httpReq.ContentType == Soap11) - { - return Soap11; - } - - // We could also send a '406 Not Acceptable', but this is allowed also - return serverDefaultContentType; - } - - public const string Soap11 = "text/xml; charset=utf-8"; - - public static bool HasAnyOfContentTypes(IRequest request, params string[] contentTypes) - { - if (contentTypes == null || request.ContentType == null) - { - return false; - } - - foreach (var contentType in contentTypes) - { - if (IsContentType(request, contentType)) - { - return true; - } - } - - return false; - } - - public static bool IsContentType(IRequest request, string contentType) - { - return request.ContentType.StartsWith(contentType, StringComparison.OrdinalIgnoreCase); - } - - private static string GetQueryStringContentType(IRequest httpReq) - { - ReadOnlySpan format = httpReq.QueryString["format"]; - if (format == null) - { - const int formatMaxLength = 4; - ReadOnlySpan pi = httpReq.PathInfo; - if (pi == null || pi.Length <= formatMaxLength) - { - return null; - } - - if (pi[0] == '/') - { - pi = pi.Slice(1); - } - - format = LeftPart(pi, '/'); - if (format.Length > formatMaxLength) - { - return null; - } - } - - format = LeftPart(format, '.'); - if (format.Contains("json", StringComparison.OrdinalIgnoreCase)) - { - return "application/json"; - } - else if (format.Contains("xml", StringComparison.OrdinalIgnoreCase)) - { - return "application/xml"; - } - - return null; - } - - public static string LeftPart(string strVal, char needle) - { - if (strVal == null) - { - return null; - } - - var pos = strVal.IndexOf(needle, StringComparison.Ordinal); - return pos == -1 ? strVal : strVal.Substring(0, pos); - } - - public static ReadOnlySpan LeftPart(ReadOnlySpan strVal, char needle) - { - if (strVal == null) - { - return null; - } - - var pos = strVal.IndexOf(needle); - return pos == -1 ? strVal : strVal.Slice(0, pos); - } - - public static string HandlerFactoryPath; - - private string pathInfo; - public string PathInfo - { - get - { - if (this.pathInfo == null) - { - var mode = HandlerFactoryPath; - - var pos = request.RawUrl.IndexOf('?', StringComparison.Ordinal); - if (pos != -1) - { - var path = request.RawUrl.Substring(0, pos); - this.pathInfo = GetPathInfo( - path, - mode, - mode ?? string.Empty); - } - else - { - this.pathInfo = request.RawUrl; - } - - this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo); - this.pathInfo = NormalizePathInfo(pathInfo, mode); - } - - return this.pathInfo; - } - } - - private static string GetPathInfo(string fullPath, string mode, string appPath) - { - var pathInfo = ResolvePathInfoFromMappedPath(fullPath, mode); - if (!string.IsNullOrEmpty(pathInfo)) - { - return pathInfo; - } - - // Wildcard mode relies on this to work out the handlerPath - pathInfo = ResolvePathInfoFromMappedPath(fullPath, appPath); - if (!string.IsNullOrEmpty(pathInfo)) - { - return pathInfo; - } - - return fullPath; - } - - private static string ResolvePathInfoFromMappedPath(string fullPath, string mappedPathRoot) - { - if (mappedPathRoot == null) - { - return null; - } - - var sbPathInfo = new StringBuilder(); - var fullPathParts = fullPath.Split('/'); - var mappedPathRootParts = mappedPathRoot.Split('/'); - var fullPathIndexOffset = mappedPathRootParts.Length - 1; - var pathRootFound = false; - - for (var fullPathIndex = 0; fullPathIndex < fullPathParts.Length; fullPathIndex++) - { - if (pathRootFound) - { - sbPathInfo.Append("/" + fullPathParts[fullPathIndex]); - } - else if (fullPathIndex - fullPathIndexOffset >= 0) - { - pathRootFound = true; - for (var mappedPathRootIndex = 0; mappedPathRootIndex < mappedPathRootParts.Length; mappedPathRootIndex++) - { - if (!string.Equals(fullPathParts[fullPathIndex - fullPathIndexOffset + mappedPathRootIndex], mappedPathRootParts[mappedPathRootIndex], StringComparison.OrdinalIgnoreCase)) - { - pathRootFound = false; - break; - } - } - } - } - - if (!pathRootFound) - { - return null; - } - - var path = sbPathInfo.ToString(); - return path.Length > 1 ? path.TrimEnd('/') : "/"; - } - - private Dictionary cookies; - public IDictionary Cookies - { - get - { - if (cookies == null) - { - cookies = new Dictionary(); - foreach (var cookie in this.request.Cookies) - { - var httpCookie = (System.Net.Cookie)cookie; - cookies[httpCookie.Name] = new System.Net.Cookie(httpCookie.Name, httpCookie.Value, httpCookie.Path, httpCookie.Domain); - } - } - - return cookies; - } - } - - public string UserAgent => request.UserAgent; - - public QueryParamCollection Headers => new QueryParamCollection(request.Headers); - - private QueryParamCollection queryString; - public QueryParamCollection QueryString => queryString ?? (queryString = MyHttpUtility.ParseQueryString(request.Url.Query)); - - public bool IsLocal => request.IsLocal; - - private string httpMethod; - public string HttpMethod => - httpMethod - ?? (httpMethod = request.HttpMethod); - - public string Verb => HttpMethod; - - public string ContentType => request.ContentType; - - private Encoding contentEncoding; - public Encoding ContentEncoding - { - get => contentEncoding ?? request.ContentEncoding; - set => contentEncoding = value; - } - - public Uri UrlReferrer => request.UrlReferrer; - - public static Encoding GetEncoding(string contentTypeHeader) - { - var param = GetParameter(contentTypeHeader, "charset="); - if (param == null) - { - return null; - } - - try - { - return Encoding.GetEncoding(param); - } - catch (ArgumentException) - { - return null; - } - } - - public Stream InputStream => request.InputStream; - - public long ContentLength => request.ContentLength64; - - private IHttpFile[] httpFiles; - public IHttpFile[] Files - { - get - { - if (httpFiles == null) - { - if (files == null) - { - return httpFiles = Array.Empty(); - } - - httpFiles = new IHttpFile[files.Count]; - var i = 0; - foreach (var pair in files) - { - var reqFile = pair.Value; - httpFiles[i] = new HttpFile - { - ContentType = reqFile.ContentType, - ContentLength = reqFile.ContentLength, - FileName = reqFile.FileName, - InputStream = reqFile.InputStream, - }; - i++; - } - } - - return httpFiles; - } - } - - public static string NormalizePathInfo(string pathInfo, string handlerPath) - { - if (handlerPath != null) - { - var trimmed = pathInfo.TrimStart('/'); - if (trimmed.StartsWith(handlerPath, StringComparison.OrdinalIgnoreCase)) - { - return trimmed.Substring(handlerPath.Length); - } - } - - return pathInfo; - } - } -} diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs deleted file mode 100644 index 552ae1754d..0000000000 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs +++ /dev/null @@ -1,215 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Implementations; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; -using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; -using IRequest = MediaBrowser.Model.Services.IRequest; - -namespace Jellyfin.Server.SocketSharp -{ - public class WebSocketSharpResponse : IHttpResponse - { - private readonly ILogger _logger; - - private readonly HttpListenerResponse _response; - - public WebSocketSharpResponse(ILogger logger, HttpListenerResponse response, IRequest request) - { - _logger = logger; - this._response = response; - Items = new Dictionary(); - Request = request; - } - - public IRequest Request { get; private set; } - - public Dictionary Items { get; private set; } - - public object OriginalResponse => _response; - - public int StatusCode - { - get => this._response.StatusCode; - set => this._response.StatusCode = value; - } - - public string StatusDescription - { - get => this._response.StatusDescription; - set => this._response.StatusDescription = value; - } - - public string ContentType - { - get => _response.ContentType; - set => _response.ContentType = value; - } - - public QueryParamCollection Headers => new QueryParamCollection(_response.Headers); - - private static string AsHeaderValue(Cookie cookie) - { - DateTime defaultExpires = DateTime.MinValue; - - var path = cookie.Expires == defaultExpires - ? "/" - : cookie.Path ?? "/"; - - var sb = new StringBuilder(); - - sb.Append($"{cookie.Name}={cookie.Value};path={path}"); - - if (cookie.Expires != defaultExpires) - { - sb.Append($";expires={cookie.Expires:R}"); - } - - if (!string.IsNullOrEmpty(cookie.Domain)) - { - sb.Append($";domain={cookie.Domain}"); - } - - if (cookie.Secure) - { - sb.Append(";Secure"); - } - - if (cookie.HttpOnly) - { - sb.Append(";HttpOnly"); - } - - return sb.ToString(); - } - - public void AddHeader(string name, string value) - { - if (string.Equals(name, "Content-Type", StringComparison.OrdinalIgnoreCase)) - { - ContentType = value; - return; - } - - _response.AddHeader(name, value); - } - - public string GetHeader(string name) - { - return _response.Headers[name]; - } - - public void Redirect(string url) - { - _response.Redirect(url); - } - - public Stream OutputStream => _response.OutputStream; - - public void Close() - { - if (!this.IsClosed) - { - this.IsClosed = true; - - try - { - var response = this._response; - - var outputStream = response.OutputStream; - - // This is needed with compression - outputStream.Flush(); - outputStream.Dispose(); - - response.Close(); - } - catch (SocketException) - { - } - catch (Exception ex) - { - _logger.LogError(ex, "Error in HttpListenerResponseWrapper"); - } - } - } - - public bool IsClosed - { - get; - private set; - } - - public void SetContentLength(long contentLength) - { - // you can happily set the Content-Length header in Asp.Net - // but HttpListener will complain if you do - you have to set ContentLength64 on the response. - // workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header - //_response.ContentLength64 = contentLength; - } - - public void SetCookie(Cookie cookie) - { - var cookieStr = AsHeaderValue(cookie); - _response.Headers.Add("Set-Cookie", cookieStr); - } - - public bool SendChunked - { - get => _response.SendChunked; - set => _response.SendChunked = value; - } - - public bool KeepAlive { get; set; } - - public void ClearCookies() - { - } - const int StreamCopyToBufferSize = 81920; - public async Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken) - { - // TODO - // return _response.TransmitFile(path, offset, count, fileShareMode, cancellationToken); - var allowAsync = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - - //if (count <= 0) - //{ - // allowAsync = true; - //} - - var fileOpenOptions = FileOpenOptions.SequentialScan; - - if (allowAsync) - { - fileOpenOptions |= FileOpenOptions.Asynchronous; - } - - // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 - - using (var fs = fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions)) - { - if (offset > 0) - { - fs.Position = offset; - } - - if (count > 0) - { - await streamHelper.CopyToAsync(fs, OutputStream, count, cancellationToken).ConfigureAwait(false); - } - else - { - await fs.CopyToAsync(OutputStream, StreamCopyToBufferSize, cancellationToken).ConfigureAwait(false); - } - } - } - } -}